How to stop ajax call being triggered several times when using .scroll? - javascript

I wrote a piece of jQuery that allows to me to amend some html on the fly.
$(document).ready(function() {
$(window).scroll(function () {
if ($(window).scrollTop() > $('#target').height() / 2) {
request = $.ajax({
// ajax call
});
request.done(function(html){
$(html).appendTo('#target');
});
}
}
});
So basically when the user scrolls past half way on the element with id='target' it triggers with ajax call which gets some new html and gets appended to target.
Also when scrolling up and down around the boundary (the half the height of target) can lead to multiple triggers of the call.
Is there a way to only allow one ajax call when it passes half way then it cannot make another call until it passes the new halfway point of #target with the appended html?

You can maintain a state for that.
$(document).ready(function() {
let isRequestInProcess = false; // state of request
$(window).scroll(function () {
if (
($(window).scrollTop() > $('#target').height() / 2) &&
!isRequestInProcess
) {
isRequestInProcess = true; // change state as in process
request = $.ajax({
// ajax call
});
request.done(function(html){
isRequestInProcess = false; // change state again to false
$(html).appendTo('#target');
});
}
}
});

Related

How to handle page numbers for an infinite scroll with Jquery / Ajax?

This is my first time trying to implement an infinite scroll with JQuery / Ajax. This is where I am currently:
<script>
$(document).ready(function() {
// see if we're at the bottom of the page to potentially load more content
$(window).on('scroll', scrollProducts);
function scrollProducts() {
var end = $("#footer").offset().top;
var viewEnd = $(window).scrollTop() + $(window).height();
var distance = end - viewEnd;
// when we're almost at the bottom
if (distance < 300) {
// unbind to prevent excessive firing
$(window).off('scroll', scrollProducts);
console.log('we reached the bottom');
$.ajax({
type: 'GET',
url: "foo/bar/2",
success: function(data) {
console.log("success!");
$('#container').append(data).fadeIn();
// rebind after successful update
$(window).on('scroll', scrollProducts);
}
});
}
}
});
</script>
I'd like to understand the correct way to update the page number in the url: foo/bar/2.
I've read that due to the difference between synchronous and asynchronous calls you can't use a global variable but instead need a callback (although I'm failing to understand it). I've also seen a solution where someone updated the values of hidden fields and then referenced those, although that seems like an ugly workaround.
What is the correct or recommended way to handle page numbers in this situation, so that the number increases with each request until there are no more pages?
keep a counter and use it in you request
var page = 1;
$(document).ready(function() {
// see if we're at the bottom of the page to potentially load more content
$(window).on('scroll', scrollProducts);
function scrollProducts() {
var end = $("#footer").offset().top;
var viewEnd = $(window).scrollTop() + $(window).height();
var distance = end - viewEnd;
// when we're almost at the bottom
if (distance < 300) {
// unbind to prevent excessive firing
$(window).off('scroll', scrollProducts);
console.log('we reached the bottom');
$.ajax({
type: 'GET',
url: "foo/bar/" + page,
success: function(data) {
console.log("success!");
$('#container').append(data).fadeIn();
// rebind after successful update
$(window).on('scroll', scrollProducts);
page++;
}
});
}
}
});

Reliably clear intervals when switching page on a dynamic app

I load all content inside a div in the index. Some of these pages require starting intervals, and when switching page those intervals keep going, so I created a destroyjs() function which gets overridden by pages that need it, and is also called every time you switch pages.
The goServiceXXX functions are called onclick in the website's navbar.
var destroyjs = function(){};
function loading(elem)
{
if (typeof destroyjs == 'function')
destroyjs();
document.getElementById("mbody").innerHTML = '<div class="container4"><img src="dist/img/loading.gif" alt="Loading..."></div>';
}
function goServiceManager()
{
var element = "#mbody";
var link = "loadservicemanager.php";
loading(element);
$(element).load(link, function(){
reloadServerStatus();
statusInterval = setInterval(function()
{
reloadServerStatus();
}, 2000);
destroyjs = function(){
clearInterval(statusInterval);
clearInterval(modalInterval);
alert("destroyed manager, interval #"+statusInterval);
}
});
}
function goServiceMonitor()
{
var element = "#mbody";
var link = "loadservicemonitor.php";
loading(element);
$(element).load(link, function(){
reloadServerStatus();
statusInterval = setInterval(function()
{
reloadServerStatus();
}, 2000);
destroyjs = function(){
clearInterval(statusInterval);
alert("destroyed monitor, interval #"+statusInterval);
}
});
}
This works fine when used normally however if I spam click between the two pages, intervals start adding up and the 2 second query is now being called 10 times every two seconds. I added the alerts to debug but they slow the interface down enough for everything to work properly.
Is there a hole in my logic somewhere? I already thought of disabling all navbar buttons when one is clicked and enabling them at the end of .load; but I'd like to know why my current implementation is not working and if it can be fixed more easily.
Edit:: So I tried to make a fiddle with the problem and in the process realized that the problem happens when destroyjs() is called before .load() finishes. Moving the interval right before .load() fixes the problem but can create a scenario where if the content never loads (or doesn't load in two seconds) there are missing elements which the function inside the interval tries to fill. Disabling the navbar temporarily and wait for .load to finish is the easy way out after all but I'd still like more opinions on this or maybe ideas for a better implementation.
destroyjs isn't defined until the load() completes. If you switch tabs before the previous tab has loaded, you won't be able to call the correct destroyjs.
Therefore, you will want to cancel any outstanding load requests when switching tabs. To do this, you can use jQuery's ajax method. Just store a reference to the resulting XHR object and call abort() when loading a new tab. Aborting an outstanding ajax request will prevent it's success callback from firing.
Here's an example (DEMO FIDDLE). Notice that I've also changed the way that intervals are cleared:
//ajax request used when loading tabs
var tabRequest = null;
//Any intervals that will need to be cleared when switching tabs
var runningIntervals = [];
function loading(elem)
{
if (tabRequest) {
//Aborts the outstanding request so the success callback won't be fired.
tabRequest.abort();
}
runningIntervals.forEach(clearInterval);
document.getElementById("mbody").innerHTML = '<div>Loading...</div>';
}
function goServiceManager()
{
var element = "#mbody";
var link = "loadservicemanager.php";
loading(element);
tabRequest = $.ajax({
url: link,
success: function(data) {
$(element).html(data);
reloadServerStatus();
statusInterval = setInterval(reloadServerStatus, 2000);
modalInterval = setInterval(modalFunction, 2000);
runningIntervals = [statusInterval, modalInterval];
}
});
}
function goServiceMonitor()
{
var element = "#mbody";
var link = "loadservicemonitor.php";
loading(element);
tabRequest = $.ajax({
url: link,
success: function(data) {
$(element).html(data);
reloadServerStatus();
statusInterval = setInterval(reloadServerStatus, 2000);
runningIntervals = [statusInterval];
}
});
}

ajax image viewer , back button and history - missing html and css

I am playing with jquery and js, trying to build an ajax overlay image viewer for a PHP website. With this code included at the bottom of the 'gallery page', the viewer opens and i can navigate with next and previous links inside the viewer. But the back button and the history is hard to understand. The browser often shows only the response of the ajax call, without the underlying page and css files, after some clicks back.
Perhaps somebody knows what is generally happening in such a case? I would like to understand why back sometimes results in a broken page, i.e. only the ajax response.
<script type="text/javascript">
$(document).ready(function() {
function loadOverlay(href) {
$.ajax({
url: href,
})
.done(function( data ) {
var theoverlay = $('#flvr_overlay');
theoverlay.html( data );
var zoompic = $('#zoompic');
zoompic.load(function() {
var nih = zoompic.prop('naturalHeight');
var photobox = $('#photobox');
if($(window).width() >= 750){
photobox.css('height',nih);
}
theoverlay.show();
$('body').css('overflow-y','hidden');
$(window).resize(function () {
var viewportWidth = $(window).width();
if (viewportWidth < 750) {
photobox.css('height','auto');
zoompic.removeClass('translatecenter');
}else{
photobox.css('height',nih);
zoompic.addClass('translatecenter');
}
});
});
});
return false;
}
var inithref = window.location.href;
$(window).on('popstate', function (e) {
if (e.originalEvent.state !== null) {
//load next/previous
loadOverlay(location.href);
} else {
//close overlay
$('#flvr_overlay').hide().empty();
$('body').css('overflow-y','scroll');
history.replaceState(null, inithref, inithref);
}
});
$(document).on('click', '.overlay', function () {
var href = $(this).attr('href');
history.pushState({}, href, href);
loadOverlay(href);
return false;
});
});
</script>
edit
clicking forward works:
/photos (normal page)
/photos/123 (overlay with '/photos' below)
/locations/x (normal page)
/photos/567 (overlay with '/locations/x' below)
clicking back gives me the broken view at point 2.
Do you need to prevent the default behaviour in your popstate to prevent the browser from actually navigating back to the previous page?
you have to manage it by own code.
You have a few options.
Use localstorage to remember the last query
Use cookies (but don't)
Use the hash as you tried with document.location.hash = "last search" to update the url. You would look at the hash again and if it is set then do another ajax to populate the data. If you had done localstorage then you could just cache the last ajax request.
I would go with the localstorage and the hash solution because that's what some websites do. You can also copy and paste a URL and it will just load the same query. This is pretty nice and I would say very accessible
Changing to document.location.hash = "latest search" didn't change anything.t.
This goes into the rest of the jQuery code:
// Replace the search result table on load.
if (('localStorage' in window) && window['localStorage'] !== null) {
if ('myTable' in localStorage && window.location.hash) {
$("#myTable").html(localStorage.getItem('myTable'));
}
}
// Save the search result table when leaving the page.
$(window).unload(function () {
if (('localStorage' in window) && window['localStorage'] !== null) {
var form = $("#myTable").html();
localStorage.setItem('myTable', form);
}
});
Another solution is that use INPUT fields to preserved while using back button. So, I do like that :
My page contains an input hidden like that :
Once ajax content is dynamicaly loaded, I backup content into my hidden field before displaying it:
function loadAlaxContent()
{
var xmlRequest = $.ajax({
//prepare ajax request
// ...
}).done( function(htmlData) {
// save content
$('#bfCache').val( $('#bfCache').val() + htmlData);
// display it
displayAjaxContent(htmlData);
});
}
And last thing to do is to test the hidden field value at page loading. If it contains something, that because the back button has been used, so, we just have to display it.
jQuery(document).ready(function($) {
htmlData = $('#bfCache').val();
if(htmlData)
displayAjaxContent( htmlData );
});

Wait for page to load when navigating back in jquery mobile

I have a single-page web app built with jQuery Mobile. After the user completes a certain action, I want to programmatically bring them back to a menu page, which involves going back in history and then performing some actions on elements of the menu page.
Simply doing
window.history.go(-1); //or $.mobile.back();
doSomethingWith(menuPageElement);
doesn't work, because the going-back action is asynchronous, i.e. I need a way of waiting for the page to load before calling doSomethingWith().
I ended up using window.setTimeout(), but I'm wondering if there's not an easier way (different pattern?) to do this in jQM. One other option is to listen for pageload events, but I find it worse from code organization point of view.
(EDIT: turns out native js promises are not supported on Mobile Safari; will need to substitute by a 3rd-party library)
//promisify window.history.go()
function go(steps, targetElement) {
return new Promise(function(resolve, reject) {
window.history.go(steps);
waitUntilElementVisible(targetElement);
//wait until element is visible on page (i.e. page has loaded)
//resolve on success, reject on timeout
function waitUntilElementVisible(element, timeSpentWaiting) {
var nextCheckIn = 200;
var waitingTimeout = 1000;
timeSpentWaiting = typeof timeSpentWaiting !== 'undefined' ? timeSpentWaiting : 0;
if ($(element).is(":visible")) {
resolve();
} else if (timeSpentWaiting >= waitingTimeout) {
reject();
} else { //wait for nextCheckIn ms
timeSpentWaiting += nextCheckIn;
window.setTimeout(function() {
waitUntilElementVisible(element, timeSpentWaiting);
}, nextCheckIn);
}
}
});
}
which can be used like this:
go(-2, menuPageElement).then(function() {
doSomethingWith(menuPageElement);
}, function() {
handleError();
});
Posting it here instead of in Code Review since the question is about alternative ways to do this in jQM/js rather than performance/security of the code itself.
Update
To differntiate whether the user was directed from pageX, you can pass a custom parameter in pagecontainer change function. On pagecontainerchange, retrieve that parameter and according bind pagecontainershow one time only to doSomething().
$(document).on("pagecreate", "#fooPage", function () {
$("#bar").on("click", function () {
/* determine whether the user was directed */
$(document).one("pagecontainerbeforechange", function (e, data) {
if ($.type(data.toPage) == "string" && $.type(data.options) == "object" && data.options.stuff == "redirect") {
/* true? bind pagecontainershow one time */
$(document).one("pagecontainershow", function (e, ui) {
/* do something */
$(".ui-content", ui.toPage).append("<p>redirected</p>");
});
}
});
/* redirect user programmatically */
$.mobile.pageContainer.pagecontainer("change", "#menuPage", {
stuff: "redirect"
});
});
});
Demo
You need to rely on pageContainer events, you can choose any of these events, pagecontainershow, pagecontainerbeforeshow, pagecontainerhide and pagecontainerbeforehide.
The first two events are emitted when previous page is completely hidden and before showing menu page.
The second two events are emitted during hiding previous page but before the first two events.
All events carry ui object, with two different properties ui.prevPage and ui.toPage. Use these properties to determine when to run code.
Note the below code only works with jQM 1.4.3
$(document).on("pagecontainershow", function (e, ui) {
var previous = $(ui.prevPage),
next = $(ui.toPage);
if (previous[0].id == "pageX" && next[0].id == "menuPage") {
/* do something */
}
});

OnScrolling new page is automatically loading

Actually i have seen a website, where while scrolling the new page is automatically loaded and it is appended to the old page.
Not only the page, URL also getting changed while scrolling.
Completely I don't know how to implement this. This is the website which I have seen matt. Here just scroll down, there will be a infinite scrollbar concept, and also URL address bar will change automatically.
if you want to append dynamic contents to the existed page from some database on scroll then make a ajax call on scroll and also rate limit the number of calls by using throttle function which will return you a throttled version of ajax call that is your ajax call will only be served atmost once during the wait millisecond time period.
var myajax = _.throttle(/*your ajax call goes here*/, wait/*time in ms*/);
_.throttle() is part of underscore.js library and if you don't want to include this library, then you can use my version of throttle that is,
function myThrottle(func, wait, leading) {
var lastCall = 0, timeout = null,
execute = function() {
clearTimeout(timeout);
timeout = null;
func();
};
return function() {
var currentTime = new Date().getTime();
if (leading && (lastCall == 0 || (currentTime - lastCall) > wait)) {
lastCall = currentTime;
func();
}
else if (!leading && !timeout)
timeout = setTimeout(execute, wait);
};
}
here the third argument leading if true than call will be made on leading edge of wait duration blocking further calls otherwise on trailing edge(default behaviour).
You can use something like this:
var documentBottom = $(document).height(), // cache document height
page = 0; // current page number
$(document).on('scroll', function () {
// if window scroll position bigger than document bottom minus 300px,
// then make ajax request and append result to container
if($(window).scrollTop() > documentBottom - 300) {
$.ajax({
type: "POST",
url: "some.php",
data: { page: page },
success: function (data) {
$('.my-container').append(data); // appending result
// cache new document height
documentBottom = $(document).height();
page += 1; // change page number
//change url in address bar
window.history.pushState({},"","/page/"+page);
}
});
}
});

Categories

Resources