Pause Javascript timer when the tab is not in focus - javascript

I have a requirement to show real time update of the number of people who did some action.
I implemented this functionality by making an ajax request to the server every 20 seconds.
But this ajax request happens even if the tab is not in focus/no one is looking at the update. Is there a way to figure out if the tab is active?
I have the following code(simplified version) and it doesn't work.
timer = undefined
$(document).ready ->
timedCountUpdate()
window.top.onblur = ->
clearTimeout(timer)
window.top.onfocus = ->
timer = timedCountUpdate()
#timedCountUpdate = () ->
timer = setTimeout(updateCountIndicator, 20000)
#updateCountIndicator = () ->
$('.indicator').html = 100
timedCountUpdate()
I still see the call being made every 20s even if i am not in the tab that has the app loaded. I am testing in chrome.

In Coffeescript w/ jquery:
$ ->
timeout_id = null
resumeTimer = () ->
# make ajax call here
# Prevent multiple timers from operating simultaneously:
clearTimeout timeout_id if timeout_id?
# Recursive step (ideally fires in 'success' handler of ajax call)
timeout_id = setTimeout(resumeTimer, 2000)
$(window.top).focus () =>
resumeTimer()
$(window.top).blur () =>
clearTimeout timeout_id
# Start timer immediately:
resumeTimer()

I know this is an old question, but I stumbled upon it in a Google search and wanted to provide another alternative that's better suited for what you're wanting to do.
The Page Visibility API is how these types of things should be done moving forward (or now IE10+). The API provides a visibilityChange event that triggers when the visibility of the tab changes. In the callback, checking the document.hidden property will tell you whether the tab is hidden or not.
From there, clear your interval or start it back up again.

In your case, i would do something like :
var tab_paused = false; // global
if (typeof window.top.onblur === 'function')
{
window.top.onblur = function() {
tab_paused = true;
};
}
if (typeof window.top.onfocus === 'function')
{
window.top.onfocus = function() {
tab_paused = false;
};
}
if (typeof document.onfocusout === 'function')
{
document.onfocusin = function() {
tab_paused = true;
};
}
if (typeof document.onfocusin === 'function')
{
document.onfocusin = function() {
tab_paused = false;
};
}
var ctx = setInterval(function() {
if (tab_paused === false)
{
$('.indicator').html(100);
}
}, 100);

Related

How to check if an api call has completed

I have a script in my code
<script src="https://geodata.solutions/includes/statecity.js"></script>
which is making an ajax call. This script is used to fetch states and cities and loads the value in select. How do I check whether that particular call is complete as it is in this external script and I want to set value of select using javascript/jquery
I am currently using setTimeout for setting select value and delaying it randomly for 6 seconds however it's not the right approach. Also, I have tried putting the code to set value in $(document).ready() but the api call returns the values later
setTimeout(function(){
jQuery("#stateId").val('<?php echo $rowaddress['state']; ?>').change();
setTimeout(function(){
jQuery("#cityId").val('<?php echo $rowaddress['city']; ?>').change();
}, 3000);
}, 6000);
spy on jQuery.ajax:
jQuery.ajax = new Proxy(jQuery.ajax, {
apply: function(target, thisArg, argumentsList) {
const req = target.apply(thisArg, argumentsList);
const rootUrl = '//geodata.solutions/api/api.php';
if (argumentsList[0].url.indexOf(rootUrl) !== -1) {
req.done(() => console.log(`request to ${argumentsList[0].url} completed`))
}
return req;
}
});
Having a look through the code for statecity.js, I've just seen that:
jQuery(".states").prop("disabled",false);
is executed upon completion of initial loading. This is on line 150 of the source code.
You could monitor the disabled attribute of the .states selector to be informed when the activity is completed using the handy JQuery extension watch.
To detect the completion of the event, just watch the disabled property of the .states item:
$('.states').watch('disabled', function() {
console.log('disabled state changed');
// Add your post-loading code here (or call the function that contains it)
});
Note that this is extremely hacky. If the author of statecity.js changes their code, this could stop working immediately or could behave unexpectedly.
It is always very risky to rely on tinkering in someone else's code when you have no control over changes to it. Use this solution with caution.
Unfortunately, the original link to the watch extension code seems to have expired, but here it is (not my code but reproduced from author):
// Function to watch for attribute changes
// http://darcyclarke.me/development/detect-attribute-changes-with-jquery
$.fn.watch = function(props, callback, timeout){
if(!timeout)
timeout = 10;
return this.each(function(){
var el = $(this),
func = function(){ __check.call(this, el) },
data = { props: props.split(","),
func: callback,
vals: [] };
$.each(data.props, function(i) { data.vals[i] = el.attr(data.props[i]); });
el.data(data);
if (typeof (this.onpropertychange) == "object"){
el.bind("propertychange", callback);
} else {
setInterval(func, timeout);
}
});
function __check(el) {
var data = el.data(),
changed = false,
temp = "";
for(var i=0;i < data.props.length; i++) {
temp = el.attr(data.props[i]);
if(data.vals[i] != temp){
data.vals[i] = temp;
changed = true;
break;
}
}
if(changed && data.func) {
data.func.call(el, data);
}
}
}

setTimeout seems to be getting shorter

I am writing a chatbot application and wanted to catch multiple user inputs by blurring out of the input field and focussing back in after 3 seconds (when the bot responded).
I used setTimeout for this and it works the first time but it seems to get shorter after calling the function multiple times.
The code I used is in a React chat widget and looks like this:
handleKeyPress = (e: KeyboardEvent) => {
if (e.keyCode === 13 && this.input.value.replace(/\s/g, "")) {
this.input.blur();
this.say(this.input.value);
// Reset input value
this.input.value = "";
this.refocus(document.getElementById('userText'));
}
};
refocus = (element: HTMLElement) => {
var time = setTimeout(function() {
element.focus();
}, 3000);
};
In this code I use a setTimeout after sending the message to the backend bot application so that the bot has some time to answer.
I can't figure out why this is not working and could really use some suggestions...
I found a fix for my issue. It appears that the problem had something to do with the focus() / blur() methods. I used a disable = true and disable = false and focus() after the 3 second delay and now the delay is always 3 seconds.
Code now looks like this:
handleKeyPress = (e: KeyboardEvent) => {
if (e.keyCode === 13 && this.input.value.replace(/\s/g, "")) {
this.input.disabled = true;
this.say(this.input.value);
// Reset input value
this.input.value = "";
this.enable((document.getElementById('userText') as HTMLInputElement));
}
};
enable = (element: HTMLInputElement) => {
setTimeout(function() {
element.disabled = false;
element.focus();
}, 3000);
};

Throttle event calls in jQuery

I have a keyup event bound to a function that takes about a quarter of a second to complete.
$("#search").keyup(function() {
//code that takes a little bit to complete
});
When a user types an entire word, or otherwise presses keys rapidly, the function will be called several times in succession and it will take a while for them all to complete.
Is there a way to throttle the event calls so that if there are several in rapid succession, it only triggers the one that was most recently called?
Take a look at jQuery Debounce.
$('#search').keyup($.debounce(function() {
// Will only execute 300ms after the last keypress.
}, 300));
Here is a potential solution that doesn't need a plugin. Use a boolean to decide whether to do the keyup callback, or skip over it.
var doingKeyup = false;
$('input').keyup(function(){
if(!doingKeyup){
doingKeyup=true;
// slow process happens here
doingKeyup=false;
}
});
You could also use the excellent Underscore/_ library.
Comments in Josh's answer, currently the most popular, debate whether you should really throttle the calls, or if a debouncer is what you want. The difference is a bit subtle, but Underscore has both: _.debounce(function, wait, [immediate]) and _.throttle(function, wait, [options]).
If you're not already using Underscore, check it out. It can make your JavaScript much cleaner, and is lightweight enough to give most library haters pause.
Here's a clean way of doing it with JQuery.
/* delayed onchange while typing jquery for text boxes widget
usage:
$("#SearchCriteria").delayedChange(function () {
DoMyAjaxSearch();
});
*/
(function ($) {
$.fn.delayedChange = function (options) {
var timer;
var o;
if (jQuery.isFunction(options)) {
o = { onChange: options };
}
else
o = options;
o = $.extend({}, $.fn.delayedChange.defaultOptions, o);
return this.each(function () {
var element = $(this);
element.keyup(function () {
clearTimeout(timer);
timer = setTimeout(function () {
var newVal = element.val();
newVal = $.trim(newVal);
if (element.delayedChange.oldVal != newVal) {
element.delayedChange.oldVal = newVal;
o.onChange.call(this);
}
}, o.delay);
});
});
};
$.fn.delayedChange.defaultOptions = {
delay: 1000,
onChange: function () { }
}
$.fn.delayedChange.oldVal = "";
})(jQuery);
Two small generic implementations of throttling approaches. (I prefer to do it through these simple functions rather than adding another jquery plugin)
Waits some time after last call
This one is useful when we don't want to call for example search function when user keeps typing the query
function throttle(time, func) {
if (!time || typeof time !== "number" || time < 0) {
return func;
}
var throttleTimer = 0;
return function() {
var args = arguments;
clearTimeout(throttleTimer);
throttleTimer = setTimeout(function() {
func.apply(null, args);
}, time);
}
}
Calls given function not more often than given amount of time
The following one is useful for flushing logs
function throttleInterval(time, func) {
if (!time || typeof time !== "number" || time < 0) {
return func;
}
var throttleTimer = null;
var lastState = null;
var eventCounter = 0;
var args = [];
return function() {
args = arguments;
eventCounter++;
if (!throttleTimer) {
throttleTimer = setInterval(function() {
if (eventCounter == lastState) {
clearInterval(throttleTimer);
throttleTimer = null;
return;
}
lastState = eventCounter;
func.apply(null, args);
}, time);
}
}
}
Usage is very simple:
The following one is waiting 2s after the last keystroke in the inputBox and then calls function which should be throttled.
$("#inputBox").on("input", throttle(2000, function(evt) {
myFunctionToThrottle(evt);
}));
Here is an example where you can test both: click (CodePen)
I came across this question reviewing changes to zurb-foundation. They've added their own method for debounce and throttling. It looks like it might be the same as the jquery-debounce #josh3736 mentioned in his answer.
From their website:
// Debounced button click handler
$('.button').on('click', Foundation.utils.debounce(function(e){
// Handle Click
}, 300, true));
// Throttled resize function
$(document).on('resize', Foundation.utils.throttle(function(e){
// Do responsive stuff
}, 300));
Something like this seems simplest (no external libraries) for a quick solution (note coffeescript):
running = false
$(document).on 'keyup', '.some-class', (e) ->
return if running
running = true
$.ajax
type: 'POST',
url: $(this).data('url'),
data: $(this).parents('form').serialize(),
dataType: 'script',
success: (data) ->
running = false

JQuery: How to call RESIZE event only once it's FINISHED resizing?

How do I call a function once the browser windows has FINISHED resizing?
I'm trying to do it like so, but am having problems. I'm using the JQuery Resize event function:
$(window).resize(function() {
... // how to call only once the browser has FINISHED resizing?
});
However, this function is called continuously if the user is manually resizing the browser window. Which means, it might call this function dozens of times in short interval of time.
How can I call the resize function only a single time (once the browser window has finished resizing)?
UPDATE
Also without having to use a global variable.
Here is an example using thejh's instructions
You can store a reference id to any setInterval or setTimeout. Like this:
var loop = setInterval(func, 30);
// some time later clear the interval
clearInterval(loop);
Debounce.
function debouncer( func , timeout ) {
var timeoutID , timeout = timeout || 200;
return function () {
var scope = this , args = arguments;
clearTimeout( timeoutID );
timeoutID = setTimeout( function () {
func.apply( scope , Array.prototype.slice.call( args ) );
} , timeout );
}
}
$( window ).resize( debouncer( function ( e ) {
// do stuff
} ) );
Note, you can use this method for anything you want to debounce (key events etc).
Tweak the timeout parameter for optimal desired effect.
You can use setTimeout() and clearTimeout() in conjunction with jQuery.data:
$(window).resize(function() {
clearTimeout($.data(this, 'resizeTimer'));
$.data(this, 'resizeTimer', setTimeout(function() {
//do something
alert("Haven't resized in 200ms!");
}, 200));
});
Update
I wrote an extension to enhance jQuery's default on (& bind)-event-handler. It attaches an event handler function for one or more events to the selected elements if the event was not triggered for a given interval. This is useful if you want to fire a callback only after a delay, like the resize event, or else.
https://github.com/yckart/jquery.unevent.js
;(function ($) {
var methods = { on: $.fn.on, bind: $.fn.bind };
$.each(methods, function(k){
$.fn[k] = function () {
var args = [].slice.call(arguments),
delay = args.pop(),
fn = args.pop(),
timer;
args.push(function () {
var self = this,
arg = arguments;
clearTimeout(timer);
timer = setTimeout(function(){
fn.apply(self, [].slice.call(arg));
}, delay);
});
return methods[k].apply(this, isNaN(delay) ? arguments : args);
};
});
}(jQuery));
Use it like any other on or bind-event handler, except that you can pass an extra parameter as a last:
$(window).on('resize', function(e) {
console.log(e.type + '-event was 200ms not triggered');
}, 200);
http://jsfiddle.net/ARTsinn/EqqHx/
var lightbox_resize = false;
$(window).resize(function() {
console.log(true);
if (lightbox_resize)
clearTimeout(lightbox_resize);
lightbox_resize = setTimeout(function() {
console.log('resize');
}, 500);
});
Just to add to the above, it is common to get unwanted resize events because of scroll bars popping in and out, here is some code to avoid that:
function registerResize(f) {
$(window).resize(function() {
clearTimeout(this.resizeTimeout);
this.resizeTimeout = setTimeout(function() {
var oldOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
var currHeight = $(window).height(),
currWidth = $(window).width();
document.body.style.overflow = oldOverflow;
var prevUndefined = (typeof this.prevHeight === 'undefined' || typeof this.prevWidth === 'undefined');
if (prevUndefined || this.prevHeight !== currHeight || this.prevWidth !== currWidth) {
//console.log('Window size ' + (prevUndefined ? '' : this.prevHeight + "," + this.prevWidth) + " -> " + currHeight + "," + currWidth);
this.prevHeight = currHeight;
this.prevWidth = currWidth;
f(currHeight, currWidth);
}
}, 200);
});
$(window).resize(); // initialize
}
registerResize(function(height, width) {
// this will be called only once per resize regardless of scrollbars changes
});
see jsfiddle
Underscore.js has a couple of great methods for this task: throttle and debounce. Even if you're not using Underscore, take a look at the source of these functions. Here's an example:
var redraw = function() {'redraw logic here'};
var debouncedRedraw = _.debounce(redraw, 750);
$(window).on('resize', debouncedRedraw);
This is my approach:
document.addEventListener('DOMContentLoaded', function(){
var tos = {};
var idi = 0;
var fn = function(id)
{
var len = Object.keys(tos).length;
if(len == 0)
return;
to = tos[id];
delete tos[id];
if(len-1 == 0)
console.log('Resize finished trigger');
};
window.addEventListener('resize', function(){
idi++;
var id = 'id-'+idi;
tos[id] = window.setTimeout(function(){fn(id)}, 500);
});
});
The resize-event-listener catches all incoming resize calls, creates a timeout-function for each and saves the timeout-identifier along with an iterating number prepended by 'id-' (to be usable as array key) in the tos-array.
each time, the timout triggers, it calls the fn-function, that checks, if that was the last timeout in the tos array (the fn-function deletes every executed timout). if true (= if(len-1 == 0)), the resizing is finished.
jQuery provides an off method to remove event handler
$(window).resize(function(){
if(magic == true) {
$(window).off('resize', arguments.callee);
}
});

DOM onresize event

If I have this
window.onresize = function() {
alert('resized!!');
};
My function gets fired multiple times throughout the resize, but I want to capture the completion of the resize. This is in IE.
Any ideas? There are various ideas out there, but not has worked for me so far (example IE's supposed window.onresizeend event.)
In this case, I would strongly suggest debouncing. The most simple, effective, and reliable way to do this in JavaScript that I've found is Ben Alman's jQuery plugin, Throttle/Debounce (can be used with or without jQuery - I know... sounds odd).
With debouncing, the code to do this would be as simple as:
$(window).resize($.debounce(1000, function() {
// Handle your resize only once total, after a one second calm.
...
}));
Hope that can help someone. ;)
I always use this when I want to do something after resizing. The calls to setTimeout and clearTimeout are not of any noticable impact on the speed of the resizing, so it's not a problem that these are called multiple times.
var timeOut = null;
var func = function() { /* snip, onresize code here */};
window.onresize = function(){
if(timeOut != null) clearTimeout(timeOut);
timeOut = setTimeout(func, 100);
}
This is not perfect but it should give you the start you need.
var initialX = null;
var initialY = null;
var lastResize = null;
var waiting = false;
var first = true;
var id = 0;
function updateResizeTime()
{
if (initialX === event.clientX && initialY === event.clientY)
{
return;
}
initialX = event.clientX;
initialY = event.clientY;
if (first)
{
first = false;
return;
}
lastResize = new Date();
if (!waiting && id == 0)
{
waiting = true;
id = setInterval(checkForResizeEnd, 1000);
}
}
function checkForResizeEnd()
{
if ((new Date()).getTime() - lastResize.getTime() >= 1000)
{
waiting = false;
clearInterval(id);
id = 0;
alert('hey!');
}
}
window.onresize = function()
{
updateResizeTime();
}
You get multiple events because there really are multiple events. Windows animates the resize by doing it several times as you drag the window (by default, you can change it in the registry I think).
What you could do is add a delay. Do a clearTimeout, setTimout(myResize,1000) every time the IE event fires. Then, only the last one will do the actual resize.
simple pure javascript solution, just change the 1000 int value to be lower for more responsiveness
var resizing = false;
window.onresize = function() {
if(resizing) return;
console.log("resize");
resizing = true;
setTimeout(function() {resizing = false;}, 1000);
};
Not sure if this might help, but since it seems to be working perfectly, here it is.
I have taken the snippet from the previous post and modified it slightly. The function doCenter() first translates px to em and than substracts the width of the object and divides the remainder by 2. The result is assigned as left margin. doCenter() is executed to center the object. timeout fires when the window is resized executing doCenter() again.
function doCenter() {
document.getElementById("menuWrapper").style.position = "fixed";
var getEM = (window.innerWidth * 0.063);
document.getElementById("menuWrapper").style.left = (getEM - 40) / 2 + "em";
}
doCenter();
var timeOut = null;
var func = function() {doCenter()};
window.onresize = function(){
if (timeOut != null) clearTimeout(timeOut);
timeOut = setTimeout(func, 100);
};
I liked Pim Jager's elegant solution, though I think that there's an extra paren at the end and I think that maybe the setTimeout should be "timeOut = setTimeout(func,100);"
Here's my version using Dojo (assuming a function defined called demo_resize())...
var _semaphorRS = null;
dojo.connect(window,"resize",function(){
if (_semaphorRS != null) clearTimeout(_semaphorRS);
_semaphorRS = setTimeout(demo_resize, 500);
});
Note: in my version the trailing paren IS required.

Categories

Resources