ion-infinite-scroll loops forever calling on-infinite callback - ionic-framework

The problem: If I exit the current ion-view when the on-finite callback hasn't finished processing, the on-infinite callback will start getting called in a loop until the app freezes, even if I'm not in the view anymore where the ion-infinite-scroll was installed.
This was difficult to reproduce, but I have found a way to easily manifest it. Setup the on-infinite callback on the view.
<ion-infinite-scroll
immediate-check="false"
on-infinite="loadMore()"
distance="5%">
</ion-infinite-scroll>
And then use $timeout to artificially slow down the loadMore() function to 2 seconds. Something like this:
$scope.loadMore = function() {
console.log("Loading more...");
$timeout(function() {
$scope.$broadcast('scroll.infiniteScrollComplete');
}, 2000);
};
Now, trigger the infinite scroll, and while your loadMore() function is "processing" for 2 seconds, leave your view. You will see that even that you are in another view, the console will continue to print "Loading more..." every 2 seconds. That is, loadMore() is being called in a loop.
On a real scenario, this bug is causing the app to freeze badly. On some links this issue has been addressed but no solutions (the ionic forum is down atm, btw). Some solutions out there suggest calling infiniteScrollComplete inside an $apply(), but that doesn't solve it and actually triggers a digest already in progress error. Others suggest to use $timeout(...,0) as an alternative to $apply() but still the issue will manifest if, for instance, your loadMore() does async calls to your server, in such case there is still a chance that the user exits the view while loadMore() is busy.
Is there any suggested workaround to this?
Ionic team: this is a bug. And all documentation everywhere about ion-infinite-scroll completely ignores this. There should be, at the very least, a warning. My ionic version 1.7.16.

Try below code for stop load more callback.
html
<ion-view view-title="Search">
<ion-content>
<h1>Search</h1>
<ion-infinite-scroll
immediate-check="false"
ng-if="!noMoreItemsAvailable"
on-infinite="loadMore()"
distance="5%">
</ion-infinite-scroll>
</ion-content>
</ion-view>
Controller
$scope.loadMore = function()
{
$http({
method:'GET',
url:'http://headers.jsontest.com/'
}).then(function(response)
{
console.log(response);
$scope.noMoreItemsAvailable = true;
}, function(response)
{});
}

Try below code for infinite loop functionality
HTML
<ion-infinite-scroll
ng-if="!noMoreDaTa" on-infinite="loadData()"
distance="2%" immediate-check="false">
</ion-infinite-scroll>
Controller
$scope.loadData = function() {
if ($scope.dataRecords.length >= $scope.total_records) {
$scope.noMoreDaTa = true;
}
}
Please let me know, if you need any help :)

Related

Protractor: Wait for angular to finish animation

I have a slider menu on my page which appear after an interval of time through an animation. I would to know how can I handle click action without using browser.wait(condition, timeout) since depending on the network traffic to fetch data in a remote database.
The page rendering can take long time thus protractor is triggering timeouts error. I have been trying as bellow to use jQuery in order to wait for all transition and animation event to finish, but it is still not working.
BasePage.prototype.clickMenuButton = function(menuName) {
var link = element(by.linkText(menuName));
browser.ignoreSynchronization = true;
browser.executeScript("jQuery('html > *').one('animationend webkitAnimationEnd oAnimationEnd MSAnimationEnd', function(){})").then(
function() {
link.click();
},
function (error) { console.log("Error : ", error); }
);
}
Does anyone know a way to wait for angularjs to finish rendering and animation without using browser.wait and timeouts?
It's a Protractor "incompatibility" with Angular JS. Most of the people calls this a bug, but nobody is sure about this.
However, if this issue occurs, the Angular and Protractor teams recommends in order to fix the $timeout awaits, to replace the $timeout function with a $interval function. They're almost the same, they both achieve the same thing.
$interval function fixes the protractor $timeout issue.
Official documentation states that You should use the $interval for anything that polls continuously (introduced in Angular 1.2rc3).
In this case, $timeout it's not a good option for Front End Developers(AngularJS developers) neither for JavaScript Automation Developers(Protractor developers).
Another option would be to either use a timeout in your test application, either turn the browser sync off.
Here's my example for ignoreSynchronization function.
browser.ignoreSynchronization = true;
browser.wait(toast.isPresent(), 3000).then(
function(arr) {
if (arr) {
toast.getText().then(function(txt) {
// console.log(txt);
expect(txt).toContain(caption);
});
} else {
toast.getText().then(function(txt) {
console.log('current toast: ' + txt);
console.log('modal not catched. see bug with $timeout \n ' +
caption + " \n" +
"==> Error! NOT PRESENT");
});
}
}
);
browser.ignoreSynchronization = false;
You could also have a look over their official github issue tracker. This page states that if you have a $timeout function, that the developer cannot change this(this seems a bit unlikely, and seems like one of the bad QA/dev relationship) you should use the ignoreSynchronization function.
The main conclusion is that, in order to fix protractor $timeout awaits for Angular the Front End developer should easily replace $timeout with $interval.
Let me know if it helps.

DOM elements not accessible after onsen pageinit in ons.ready

I am using the Onsen framework with jQuery and jQuery mobile, it appears that there is no way to catch the event that fires once the new page is loaded.
My current code, which executes in the index.html file (the master page)
<script src="scripts/jQuery.js"></script>
<script src="scripts/jquery.mobile.custom.min.js"></script>
<script src="scripts/app.js"></script>
<script>
ons.bootstrap();
ons.ready(function() {
$(document.body).on('pageinit', '#recentPage', function() {
initRecentPage();
});
});
in app.js is the following code
function initRecentPage() {
$("#yourReports").on("tap", ".showReport", recentShowReport);
var content = document.getElementById("yourReports");
ons.compile(content);
}
and the HTML:
<ons-page id="recentPage">
<ons-toolbar id="myToolbar">
<div id="toolBarTitle" class="center">Recent Checks</div>
<div class="right">
<ons-toolbar-button ng-click="mySlidingMenu.toggleMenu()">
<ons-icon icon="bars"></ons-icon>
</ons-toolbar-button>
</div>
</ons-toolbar>
<ons-scroller>
<h3 class="headingTitle"> Checks</h3>
<div id="Free" class="tabArea">
<ons-list id="yourReports">
</ons-list>
<ons-button id="clearFreeRecentButton">
<span id="clearRecentText" class="bold">Clear Recent Checks</span>
</ons-button>
</div>
</ons-scroller>
</ons-page>
in this instance the variable 'content' is always null. I've debuged significantly, and the element I am trying to get is not present when this event fires. It is loaded later.
So, the question is, how do I ensure that all of the content is present before using a selector. It feels like this is an onsen specific issue.
In the end I could only find one reliable way of making this work.
Essentially I had to wait, using setTimeout 300 milliseconds for the DOM elements to be ready. It feels like a hack, but honestly there is no other reliable way of making this behave. The app is in the app store and works well, so even though it seems like a hack, it works:
$(document).on('pageinit', '#homePage', function() {
initHomePage();
});
function initHomePage() {
setTimeout(function() {
setUpHomePage();
}, 300);
}
Move your $(document.body).on('pageinit', '#recentPage', function() { outside of ons.ready block.
JS
ons.bootstrap();
ons.ready(function() {
console.log("ready");
});
$(document.body).on('pageinit', '#recentPage', function() {
initRecentPage();
});
function initRecentPage() {
//$("#yourReports").on("tap", ".showReport", recentShowReport);
var content = document.getElementById("yourReports");
alert(content)
ons.compile(content);
}
I commented out a line because I do not have access to that "recentShowReport"
You can see how it works here: 'http://codepen.io/vnguyen972/pen/xCqDe'
The alert will show that 'content' is not NULL.
Hope this helps.

infinite scroll manual trigger callback

I'm using Infinite Scroll plugin in a (I know it is unrecommended http://isotope.metafizzy.co/docs/help.html#infinite_scroll_with_filtering_or_sorting), Infinite Scroll + Isotipe Filtering combination.
Now sometimes happend that after i run my filter, if I get an empty list i manually trigger infinite scroll to load more elements.
$('.items').isotope({ filter: filter }, function( $items ) {
var id = this.attr('class'),
len = $items.length;
if (len == 0){getElement();}
});
Here is my function that load elements, but it seems that the callback is not working.
function getElement(){
$('.items').infinitescroll('retrieve',function(items){
console.log('callback');
console.log(items);
});
}
Unfortunally Infinite Scroll documentation is not the best for manual trigger (it suggest a not-working way to call it - $(document).trigger('retrieve.infscr'); i found the solution here: infinite scroll manual trigger) so I'm a little bit stucked here.
Any suggestion?
I know it is a very old post. But I find it over Google as I searched for the self problem. But I also find the solution for this. Perhaps it helps somebody...
You must put the callback not to the main function. Because manual function only push the main function.
jQuery(document).ready(function($) {
var $infinitecontainer = $(".infinite-content").infinitescroll({
navSelector: ".nav-links",
nextSelector: ".nav-links a:first",
itemSelector: ".infinite-post",
errorCallback: function(){ $(".inf-more-but").css("display", "none") }
}, function() { // callback
alert("Manual click load finished");
});
$(window).unbind(".infscr");
$(".inf-more-but").click(function(e){
e.preventDefault();
var infinite_scroll = $(".infinite-content").infinitescroll("retrieve");
return false;
});

iscroll rubber band effect in jQTouch

I am a new developer and am trying to create a jQTouch application to display some scrollable content throughout multiple pages. I've decided to use iscroll and it only works fine on the home page. I've read that I need to refresh iscroll after each page but I am completely lost on how to do this. Here is my script:
<script type="text/javascript">
var myScroll, myScroll2;
function loaded() {
setTimeout(function () {
myScroll = new iScroll('wrapper1');
}, 100);
setTimeout(function () {
myScroll2 = new iScroll('wrapper2');
}, 100);
}
document.addEventListener('touchmove', function (e) { e.preventDefault(); }, false);
document.addEventListener('DOMContentLoaded', loaded, false);
</script>
In my html I have a div id="wrapper1" which works fine until I navigate to the second page where the div id="wrapper2" has the rubber band effect.
In case you haven't figured this out yet (although I'm sure you have), you want:
myScroll.refresh()
or
myScroll2.refresh()
Ok finally got this working. To get jQTOuch and iScroll to play nice with each other, the scrolling areas on the page need to be reset each time JQTouch makes them disappear. In other words, once you hide the div, iScroll doesn't know what to scroll the next time it's made visible. So as a result, you get the infamous rubberband effect. To solve this, just add an event listener that resets the scrolling area right after the div is called. Make sure you give it 100 to 300ms delay. This code below assumes your variable is called myScroll:
$(".about").tap(function(){
setTimeout(function(){myScroll.refresh()},300);
});
And on a side note, here's how to establish multiple scrollers using iScroll:
var scroll1, scroll2;
function loaded() {
scroll1 = new iScroll('wrapper1');
scroll2 = new iScroll('wrapper2');
}

Mobile Safari: Disable scrolling pages "out of screen"

I want to block scrolling page "out of the iPhone screen" (when gray Safari's background behind the page border is visible). To do this, I'm cancelling touchmove event:
// Disables scrolling the page out of the screen.
function DisableTouchScrolling()
{
document.addEventListener("touchmove", function TouchHandler(e) { e.preventDefault(); }, true);
}
Unfortunately, this also disables mousemove event: when I tap on a button then move my finger out of it, then release the screen, the button's onclick event is triggered anyway.
I've tried mapping touch events on mouse events, as desribed here: http://ross.posterous.com/2008/08/19/iphone-touch-events-in-javascript/, but to no avail (the same behavior).
Any ideas?
From what I understand of your question, you've attempted to combine the code you've presented above with the code snippet provided by Ross Boucher on Posterous. Attempting to combine these two snippets back-to-back won't work, because in disabling touchmove, you've also disabled the shim that allows mousemove to work via his sample.
This question and its answers sketch out a workable solution to your problem. You should try these two snippets to see if they resolve your issue:
This snippet, which disables the old scrolling behavior:
elementYouWantToScroll.ontouchmove = function(e) {
e.stopPropagation();
};
Or this one, from the same:
document.ontouchmove = function(e) {
var target = e.currentTarget;
while(target) {
if(checkIfElementShouldScroll(target))
return;
target = target.parentNode;
}
e.preventDefault();
};
Then, drop in the code on Posterous:
function touchHandler(event)
{
var touches = event.changedTouches,
first = touches[0],
type = "";
switch(event.type)
{
case "touchstart": type = "mousedown"; break;
case "touchmove": type="mousemove"; break;
case "touchend": type="mouseup"; break;
default: return;
}
//initMouseEvent(type, canBubble, cancelable, view, clickCount,
// screenX, screenY, clientX, clientY, ctrlKey,
// altKey, shiftKey, metaKey, button, relatedTarget);
var simulatedEvent = document.createEvent("MouseEvent");
simulatedEvent.initMouseEvent(type, true, true, window, 1,
first.screenX, first.screenY,
first.clientX, first.clientY, false,
false, false, false, 0/*left*/, null);
first.target.dispatchEvent(simulatedEvent);
event.preventDefault();
}
And that should do it for you. If it doesn't, something else isn't working with Mobile Safari.
Unfortunately I haven't had the time to check out to above yet but was working on an identical problem and found that the nesting of elements in the DOM and which relation you apply it to affects the handler a lot (guess the above solves that, too - 'var target = e.currentTarget').
I used a slightly different approach (I'd love feedback on) by basically using a class "locked" that I assign to every element which (including all its children) i don't want the site to scroll when someone touchmoves on it.
E.g. in HTML:
<header class="locked">...</header>
<div id="content">...</div>
<footer class="locked"></div>
Then I have an event-listener running on that class (excuse my lazy jquery-selector):
$('.ubq_locked').on('touchmove', function(e) {
e.preventDefault();
});
This works pretty well for me on iOs and Android and at least gives me the control to not attach the listener to an element which I know causes problems. You do need to watch your z-index values by the way.
Plus I only attach the listener if it is a touch-device, e.g. like this:
function has_touch() {
var isTouchPad = (/hp-tablet/gi).test(navigator.appVersion);
return 'ontouchstart' in window && !isTouchPad;
}
This way non-touch devices will not be affected.
If you don't want to spam your HTML you could of course just write the selectors into an array and run through those ontouchmove, but I would expect that to be more costly in terms of performance (my knowledge there is limited though). Hope this can help.