Reliably clear intervals when switching page on a dynamic app - javascript

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];
}
});
}

Related

i have problem in ajax Post not display data

i have table with search fields seprated to
1- table header contain "search fields - table headers"
2- table body => come from ajax post method
3- pagination => come from ajax post method after data excuted "i used setTimeout to delay this function"
the data display automaticly in the table body by ajax post & if the user use search fields also the data filterd by search words and pagingation also display basd on data
i have 2 cases
1- in the main page the table loaded with the page and evry thing working fine i used the follwing function:
follwing function responsible for data
jsQueryDataLive(defaultPageNo);
function jsQueryDataLive(defaultPageNo){
var objData = {
action : dataTableName +'/QueryData',
ajaxClass : actionSplit0,
ajaxPage : defaultPageNo,
};
$('#form-query-main').find(":input").each(function(){
objData[this.name] = $(this).val();
});
// console.log(objData);
// NOTE: the below line is Responsible for determining the location we need to view data on
var locationID = '#main-table-tbody';
ajaxPost(objData, locationID);
}
the following function for pagination
jsPaginationLive(defaultPageNo);
function jsPaginationLive(defaultPageNo){
setTimeout(function () {
var objData = {
action : dataTableName +'/Pagination',
ajaxClass : actionSplit0,
ajaxPage : defaultPageNo,
};
// document.getElementById("jsData").innerHTML =JSON.stringify(objData);
// NOTE: the below line is Responsible for determining the location we need to view data on
var locationID = '#pagination';
ajaxPost(objData, locationID);
}, 100);
}
i used the following function for filter
$('.searchField').keyup(function() {
jsPageNo(defaultPageNo);
} );
function jsPageNo(defaultPageNo) {
jsQueryDataLive(defaultPageNo);
jsPaginationLive(defaultPageNo);
}
2- the second case is when i want to display the obove bage in the modal
as you see in the page the table and data display correctly
i used this function to define table and load the main bag :
var dataTableName;
jsDataTableName();
function jsDataTableName(tableName) {
if (tableName === undefined) {
dataTableName = actionSplit0;
// console.log(dataTableName);
}
else {
dataTableName = tableName;
console.log(dataTableName);
// NOTE: load External Class Query page
jsLoadExternalClass();
}
}
function jsLoadExternalClass(){
var objData = {
action : dataTableName +'/Query',
ajaxClass : actionSplit0,
ajaxPage : defaultPageNo,
};
// document.getElementById("jsData").innerHTML =JSON.stringify(objData);
// jsPaginationLive(defaultPageNo);
// NOTE: the below line is Responsible for determining the location we need to view data on
var locationID = '#externalClass1';
ajaxPost(objData, locationID);
jsPageNo(defaultPageNo);
}
the problem with me in filtered not working when i type imediatly but it work after i close the modal and re open it again
enter image description here
also if there is a better solution to start function jsPaginationLive after jsQueryDataLive finished instead of setTimeout please let me know
If you set the timer on setTimeout from 100 ms to 2,000, does it still have the issue? One of the problems I've run into when using timeouts to handle situations like these is that any variance in the loading times of the site (which can always vary for any number of reasons) can cause your timeout to fail to achieve your goal.
So instead I'd suggest this: either add a variable which is set by the data function upon completion or you could use a value in the data function which must not be undefined before proceeding, then have the pagination function in a setInterval, like this:
var interval;
interval = setInterval(function(){
if (dataHasLoaded){ // This will be whatever the you decide to name the variable
jsPaginationLive(defaultPageNo);
clearInterval(interval);
}
},500);
This will ensure that the pagination function will never attempt to load before the data has been fully loaded.
EDIT: Alternatively, you can place "jsPaginationLive(defaultPageNo);" somewhere at the end of "jsQueryDataLive(defaultPageNo);" so it won't attempt pagination until the data function has completed.
Answer to 2nd question:
If you want to only run an interval while a user is typing, you can do something like this:
var interval;
window.onkeydown = function(){
clearInterval(interval);
interval = setInterval(function(){
if (dataHasLoaded){
jsPaginationLive(defaultPageNo);
clearInterval(interval);
}
},500);
}
var keyTimeout;
window.onkeyup = function(){
clearTimeout(keyTimeout);
keyTimeout = setTimeout(function(){
clearInterval(interval);
}, 5000); // The interval will be cleared 5 seconds after the last keypress. Change as needed.
}
I feel like there's a cleaner way to do this, though. Why do you want the interval to only be active during typing?

Redirect with inactivity

My objective is to keep a user in a view as long as he/she keeps clicking a button within a certain lapse.
I'm using Rails and was exploring a solution via an embedded JS in the pertinent view.
So far I'm able to set a time after which the user will be redirected to root path with the following script:
var delayedRedirect = function (){
window.location = "/";
}
var delay = 10000;
$(document).ready(function() {
setTimeout('delayedRedirect()', delay);
});
I've been trying to write a function that resets the value of 'delay'or that calls the setTimeoutFunction again.
$('#btn-persist').click(function() {
delay = 3000;
// or calling again setTimeout('delayedRedirect()', delay);
});
But I noticed that changing the variable won't affect the setTimeout function that has already been called.
I've also tried to use the clearTimeout function as below without success
var delayedRedirect = function (){
window.location = "/persists";
}
var delay = 3000;
var triggerRedirect = function() { setTimeout('delayedRedirect()', delay);
}
var stopRedirect = function (){
clearTimeout(triggerRedirect);
}
$(document).ready(function() {
triggerRedirect();
$('#btn-persist').click(function() {
stopRedirect();
});
});
I wonder why this may not be working and if there's any other way to stop the execution of the setTimeout function that has already been called so I can call it again to effectively reset the time to the original value of 'delay'.
At the same time, I don't want to stop any other JS functions that are running in parallel.
Do you see a better solution to achieve this?
The main problem why clearTimeout is not working. because you are clearing a anonymous function instead of a setTimeout variable
change this
var triggerRedirect = function() { setTimeout('delayedRedirect()', delay);
}
to this
var triggerRedirect = setTimeout('delayedRedirect()', delay);
Edit:
also change this (if you want to restart the inactive redirect trigger)
$('#btn-persist').click(function() {
stopRedirect();
});
to this
$('#btn-persist').click(function() {
stopRedirect();
triggerRedirect();
});

jQuery - Why document unloading stops my requests?

Some pages on my application are very, very slow to display, for normal reasons (business intelligence, that calls many external APIs, etc.). Sometimes, 3-5 minutes are needed.
I want to display a progress bar with text, notifying the user what's currently going on and why he's waiting.
Here's how I imagined the feature:
Every time the user displays a page, a new token is generated in the form. When he submits the form, while the browser waits for the response, a route can be called with the given token, that returns an JSON response giving the percentage of progress and the operation currently being done.
So, I wrote a little jQuery code that polls this route, and updates the progress bar as well.
var pollAjax = function () {
this.url = '';
this.interval = 5000;
this.callback = '';
this.running = true;
this.poll = function poll() {
var that = this;
if (that.running) {
$.get(that.url, function (data) {
that.callback(data);
setTimeout(function () {
that.poll();
}, that.interval);
});
}
};
this.setInterval = function setInterval(interval) {
this.interval = interval;
return this;
};
this.setCallback = function setCallback(callback) {
this.callback = callback;
return this;
};
this.setUrl = function setUrl(url) {
this.url = url;
return this;
};
this.stop = function stop() {
this.running = false;
return this;
}
};
$(document).ready(function () {
var progressBars = $('.progress .progress-bar');
progressBars.each(function () {
var progressPoll = new pollAjax();
var progressBar = $(this);
progressPoll.setUrl($(this).data('refreshurl'));
progressPoll.setInterval(2000);
progressPoll.setCallback(function (data) {
if (data.progress) {
$('#progressbar').show();
progressPoll.setInterval(200);
progressBar.attr('data-transitiongoal', data.progress.percentage).progressbar();
$('#progress_text').text(data.progress.text);
console.log(data.progress);
}
});
progressPoll.poll();
});
});
Sorry for the code quality, I'm better with PHP :)
This code works fine. When I fake some data in the poll url, the progressbar correctly updates every 200ms.
My problem is that this code stops working as soon as the user leaves the page (and that's bad because this is when I want the code to be triggered).
When he submits the form, the page is still displayed while the browser is waiting long minutes for the response, but the automatic poll immediately stops. As if the unload event prevents scripts to continue running, even if the page is still displayed.
Do you know how I can handle this ?
Thanks,
Ben

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);
}
});
}
});

How do I make my live jQuery search wait a second before performing the search?

I've got a search input which sends data from an input to a php file as I type. The php file does a search on my database and shows up a list of search options. You know, the ajax style live searching.
My problem is, if you type something really fast, it might just conduct a search off of the first 1 or 2 letters even though another 10 have been typed. This causes a few problems.
My jQuery looks a bit like this:
$(document).ready(function(){
$('#searchMe').keyup(function(){
lookup(this.value);
});
});
and
function lookup(searchinput) {
if(searchinput.length == 0) {
// Hide the suggestion box.
$("#suggestions").hide();
} else {
$('#loading').fadeIn();
$.post("/RPCsearch.php", {queryString: ""+searchinput+""}, function(data){
if(data.length > 0) {
$("#suggestions").html(data).show();
$('#loading').fadeOut();
}
});
}
} // lookup
So I'm just curious, how can I make it so that my script waits until I've finished typing before running the function? My logic says something like if a key hasn't been pressed for 200 micro seconds, run the function, otherwise hold up a bit.
How is this done?
Easy, using setTimeout. Of course you only want one timer going at once, so it's important to use clearTimeout at the beginning of the function...
$(function() {
var timer;
$("#searchMe").keyup(function() {
clearTimeout(timer);
var ms = 200; // milliseconds
var val = this.value;
timer = setTimeout(function() {
lookup(val);
}, ms);
});
});
You may be interested in my bindDelayed jQuery mini-plugin. It:
Allows you to specify a delay before kicking off the request
Automatically cancels any previous requests that were scheduled to go off
Automatically cancels any in-air XHR requests that were in progress when you make your request
Only invokes your callback for the latest request
If the user types "s", waits long enough for the request to go out, and then types "a", and the response for "s" comes back before the response for "sa" you won't have to deal with it.
The answer to the original question using bindDelayed would look like so:
// Wait 200ms before sending a request,
// avoiding, cancelling, or ignoring previous requests
$('#searchMe').bindDelayed('keyup',200,'/RPCsearch.php',function(){
// Construct the data to send with the search each time
return {queryString:this.value};
},function(html){
// Use the response, secure in the knowledge that this is the right response
$("#suggestions").html(html).show();
},'html','post');
In case my site is down, here's the plugin code for Stack Overflow posterity:
(function($){
// Instructions: http://phrogz.net/jquery-bind-delayed-get
// Copyright: Gavin Kistner, !#phrogz.net
// License: http://phrogz.net/js/_ReuseLicense.txt
$.fn.bindDelayed = function(event,delay,url,dataCallback,callback,dataType,action){
var xhr, timer, ct=0;
return this.on(event,function(){
clearTimeout(timer);
if (xhr) xhr.abort();
timer = setTimeout(function(){
var id = ++ct;
xhr = $.ajax({
type:action||'get',
url:url,
data:dataCallback && dataCallback(),
dataType:dataType||'json',
success:function(data){
xhr = null;
if (id==ct) callback.call(this,data);
}
});
},delay);
});
};
})(jQuery);
You really ought to look at using the jQuery autocomplete plugin. I find this plugin to be very useful and it already does what you need. Look particularly at the delay option, which you can customize to change how long the plugin waits after a keystroke to run.
1 solution in psuedocode:
OnKeyPress()
txt = getTxt
sleep(200)
newTxt = getTxt
if (txt == newTxt) // nothing has been typed so do something
run my thing
this one is happy
$(document).ready(function(){
$("#searchMe").keyup(function () {
try{window.clearTimeout(timeoutID);}catch(e){}
timeoutID = window.setTimeout(run, 2000); //delay
function run()
{ //dowhatev
var text = $("#searchMe").val();
//$("#showit").html(text);
}
});
});
I have found the best success when attaching the event to keypress, keydown, and keyup inputs. Safari/FireFox/IE all seem to handle special keypresses (delete, backspace, etc.) a bit differently but using all events together seems to cover it. The only way that running all events works though is to use setTimeout so that when they all fire it just resets the timer and ultimately the callback only gets executed once.
var delay = 200;
var search_timer = null;
$("#searchMe").keydown(function(e) {
if(search_timer) {
clearTimeout(search_timer);
}
search_timer = setTimeout(lookup, delay);
});
$("#searchMe").keypress(function(e) {
if(search_timer) {
clearTimeout(search_timer);
}
search_timer = setTimeout(lookup, delay);
});
$("#searchMe").keyup(function(e) {
if(search_timer) {
clearTimeout(search_timer);
}
search_timer = setTimeout(lookup, delay);
});

Categories

Resources