jQuery: complete handler with minimum time - javascript

I have a function that loads content using .load() (but it could use anything). Sometimes the content loads so fast that the transition animations I use don't look very good, in fact its quite off-putting. I would like to add a minimum time between the transitions so that if the content loads very quickly itll still wait the minimum time (say 500 ms).
My code currently looks like this, is there a nice jQuery way of doing this?
$("body").on("click","a[href]",function (e) {
e.preventDefault();
var href = $(this).attr("href");
// Do pre load animation (removed for clarity)
$("#rightpanel").load($(this).attr("href"), function () {
// Do post load animation (removed for clarity)
History.pushState(null, null, href);
});
});

Here is an answer involving promises :
// suggestion 1
// wait for both pre-load animation and load to complete :
$.when(
$('.gizmo').slideUp(),
$("#rightpanel").load($(this).attr("href"))
).done(function(){
$('.gizmo').stop().slideDown();
History.pushState(null, null, href);
});
// suggestion 2
// add a "500ms promise" :
function delay(time) {
var dfd = $.Deferred();
setTimeout(function(){ dfd.resolve() }, time);
return dfd.promise();
}
$.when( delay(500), $("#rightpanel").load($(this).attr("href")) ).done(function(){
//post load stuff
});
Here is a fiddle to play with.
As Chris correctly pointed out in the comments, the above code will not work with .load() : .load() applies to a jQuery selection, and returns the selected set instead of the underlying ajax promise.
The above code will work if you use $.ajax, $.get, $.post or other global jQuery functions,
or you can create an extra promise :
var loadData = $.Deferred();
$('#rightpanel').load($(this).attr('href'), function(){ loadData.resolve() });
$.when( delay(500), loadData ).done( ... )

Related

How to use Deferred objects in a for loop

I'm not sure about how to ask that.
I want to know the size of three images from which I have only the URL (needless to say, that's just an example).
Something tells me that Deferred is the way to do it... I've used it in other situations but I don't know how to chain various deferred objects together, one for each iteration of the for loop, and to have one done function for all of them (does it makes sense? I don't know if I'm making myself clear).
That's basically what I'm trying to do with this code, but naturally here the deferred will call done only once, and not for each image. I would like Deferred to be resolved only after I have the dimensions of all images.
update
I understand now, thanks to jgreenberg and Bergi, that I need a Deferred object for each image, as well as an array of Promises. New code:
var imagesURLArray = [
'http://placekitten.com/200/300',
'http://placekitten.com/100/100',
'http://placekitten.com/400/200'];
var promises = [];
function getImageDimensions(url){
var deferred = new $.Deferred();
var img = $('<img src="'+ imagesURLArray[i] +'"/>').load(function(){
deferred.resolve( {width: this.width, height: this.height} );
});
return deferred.promise();
}
for (var i = 0; i < imagesURLArray.length; i++) {
promises.push(getImageDimensions(imagesURLArray[i]));
}
$.when.apply($, promises).then(function(dimensions){
console.log('dimensions: ' + dimensions);
});
Sample here
However I still can't figure out how to retrieve data from all Deferred objects in then(). The dimensions argument returns only data from the first Deferred object.
Here's a little piece of code that extends the built-in jQuery promises to work for the .load() events for images. This allows you to do some very simple coding to wait for your group of images to load. Here's the add-on code for jQuery to support promises for image loading:
(function() {
// hook up a dummy animation that completes when the image finishes loading
// If you call this before doing an animation, then animations will
// wait for the image to load before starting
// This does nothing for elements in the collection that are not images
// It can be called multiple times with no additional side effects
var waitingKey = "_waitingForLoad";
var doneEvents = "load._waitForLoad error._waitForLoad abort._waitForLoad";
jQuery.fn.waitForLoad = function() {
return this.each(function() {
var self = $(this);
if (this.tagName && this.tagName.toUpperCase() === "IMG" &&
!this.complete && !self.data(waitingKey)) {
self.queue(function(next) {
// nothing to do here as this is just a sentinel that
// triggers the start of the fx queue
// it will get called immediately and will put the queue "inprogress"
}).on(doneEvents, function() {
// remove flag that we're waiting,
// remove event handlers
// and finish the pseudo animation
self.removeData(waitingKey).off(doneEvents).dequeue();
}).data(waitingKey, true);
}
});
};
// replace existing promise function with one that
// starts a pseudo-animation for any image that is in the process of loading
// so the .promise() method will work for loading images
var oldPromise = jQuery.fn.promise;
jQuery.fn.promise = function() {
this.waitForLoad();
return oldPromise.apply(this, arguments);
};
})();
This works by overriding the current .promise() method and when the .promise() method is called for each image in the collection that has not yet completed loading, it starts a dummy animation in the jQuery "fx" queue and that dummy animation completes when the image's "load" event fires. Because jQuery already supports promises for animations, after starting the dummy animation for each image that has not yet loaded, it can then just call the original .promise() method and jQuery does all the work of creating the promise and keeping track of when the animation is done and resolving the promise when the animations are all done. I'm actually surprised jQuery doesn't do this themselves because it's such a small amount of additional code and leverages a lot of things they're already doing.
Here's a test jsFiddle for this extension: http://jsfiddle.net/jfriend00/bAD56/
One very nice thing about the built-in .promise() method in jQuery is if you call it on a collection of objects, it will return to you a master promise that is only resolved when all the individual promises have been resolved so you don't have to do all that housekeeping - it will do that dirty work for you.
It is a matter of opinion whether the override of .promise() is a good way to go or not. To me, it seemed nice to just add some additional functionality to the existing .promise() method so that in addition to animations, it also lets you manage image load events. But, if that design choice is not your cup of tea and you'd rather leave the existing .promise() method as it is, then the image load promise behavior could be put on a new method .loadPromise() instead. To use it that way, instead of the block of code that starts by assigning oldPromise = ..., you would substitute this:
jQuery.fn.loadPromise = function() {
return this.waitForLoad().promise();
};
And, to retrieve a promise event that includes the image load events, you would just call obj.loadPromise() instead of obj.promise(). The rest of the text and examples in this post assume you're using the .promise() override. If you switch to .loadPromise(), you will have to substitute that in place in the remaining text/demos.
The concept in this extension could also be used for images in the DOM too as you could do something like this so you could do something as soon as a set of images in the DOM was loaded (without having to wait for all images to be loaded):
$(document).ready(function() {
$(".thumbnail").promise().done(function() {
// all .thumbnail images are now loaded
});
});
Or, unrelated to the original question, but also something useful you can do with this extension is you can wait for each individual images to load and then kick off an animation on each one as soon as it's loaded:
$(document).ready(function() {
$(".thumbnail").waitForLoad().fadeIn(2000);
});
Each image will fade in starting the moment it is done loading (or immediately if already loaded).
And, here's how your code would look using the above extension:
var imagesURLArray = [
'http://placekitten.com/200/300',
'http://placekitten.com/100/100',
'http://placekitten.com/400/200'];
// build a jQuery collection that has all your images in it
var images = $();
$.each(imagesURLArray, function(index, value) {
images = images.add($("<img>").attr("src", value));
});
images.promise().done(function() {
// all images are done loading now
images.each(function() {
console.log(this.height + ", " + this.width);
});
});
Working jsFiddle Demo: http://jsfiddle.net/jfriend00/9Pd32/
Or, in modern browsers (that support .reduce() on arrays), you could use this:
imagesURLArray.reduce(function(collection, item) {
return collection.add($("<img>").attr("src", item));
}, $()).promise().done(function() {
// all images are done loading now
images.each(function() {
console.log(this.height + ", " + this.width);
});
});;
You need to use http://api.jquery.com/jquery.when/
I tried to modify your code but I haven't run it. I believe you'll figure it out from here.
function getImageDimensions(url){
var deferred = new $.Deferred();
var img = $('<img src="'+ imagesURLArray[i] +'"/>').load(function(){
var dimensions = [this.width, this.height];
deferred.resolve(dimensions);
});
return deferred.promise()
}
var promises = []
for (var i = 0; i < imagesURLArray.length; i++) {
promises.push(getImageDimensions(imagesURLArray[i]));
}
$.when.apply($, promises).then(function(dimensions){
console.log('dimensions: ' + dimensions);
});

How to add a loading animation when loading a promise object?

I am using JavaScript and jQuery to write my website but I get the following problem.
I want to sleep the thread and display a loading animation only until the promise object is completely loaded into my website.
And I don't know how to do that, Can anyone help?
#A.Wolff
Since I have this problem when I am using the PDF.JS plugin. And I am trying to declare a self-defined class on top of it.
Following is my self-defined class
function WhiteBoardPdf3(canvasContext,url){
this.canvasContext=canvasContext;
this.url=url;
this.pdfOriPromise=PDFJS.getDocument(url);
this.pdfPromise=Promise.cast(this.pdfOriPromise);
this.pdfDoc=null;
/*----Here is the problem------*/
this.pdfPromise.then(function getPdf(_pdfDoc){
this.pdfDoc=_pdfDoc
});
/*----------------------------*/
this.pageNum=1;
this.scale=1;
this.renderPage(1);
}
WhiteBoardPdf3.prototype.getPdfPromise=function(){
return this.pdfPromise;
}
WhiteBoardPdf3.prototype.renderPage=function(){
var num=this.pageNum;
var scale=this.scale;
var canvasContext=this.canvasContext;
var canvas=canvasContext.canvas;
var canvasClassName=canvas.className;
var allCanvas=document.getElementsByClassName(canvasClassName);
var canvasContainer=document.getElementById("whiteBoardLayerContainer");
this.pdfPromise.then(function getPdf(_pdfDoc){
_pdfDoc.getPage(num).then(function(page){
var viewport=page.getViewport(scale);
for(var i=0;i<allCanvas.length;i++){
allCanvas[i].height=viewport.height;
allCanvas[i].width=viewport.width;
}
canvasContainer.style.width=viewport.width+'px';
var renderContext={
canvasContext: canvasContext,
viewport: viewport
}
page.render(renderContext);
});
});
}
WhiteBoardPdf3.prototype.getPdfNumOfPages=function(){
this.pdfDoc.numPages;
}
And the PDFJS.getDocument(url) will return a promise object.
However, the problem is that when I construct this class and call the getPdfNumOfPages() function in the main program. I notice that the program will call the getPdfNumOfPages() function before the "pdfDoc"(promise object) is finish loading. So I want to sleep the thread and display the loading animation before the promise object is finish loading. So as to the getPdfNumOfPages() function will run after the "pdfDoc" is loaded.
well you could show a image of loading on your page before sending a ajax request and hide it after a response is received .
HTML CODE:
<img name="loadingImage" id="loadingImg" src="http://www.ppimusic.ie/images/loading_anim.gif " width="100px" height="100px" />
$('#loadingImg').hide();
$.ajax({
url: "test.html",
data: {data1:'smile'},
beforeSend:function() {
$('#loadingImg').show();
},
success:function(response) {
$('#loadingImg').hide();
//process the successful response
},
error:function(response) {
$('#loadingImg').hide();
//process the error response
}
});
Happy Coding:)
Edit:
As indicated in comments (thanks A. Wolff and Frédéric Hamidi), this solution is better:
//
// launch loader
// before
//
// make xhr query
var jqxhr = $.ajax({
url: 'your/action'
});
// call the always promise method, automatically invoked
// when the $.ajax action is completed
jqxhr.always(function() {
// stop loader in every situations (success or failure)
});
Previous solving post solution below:
This solution is enought only if the xhr query is done without error.
You can use $.when
Description: Provides a way to execute callback functions based on one
or more objects, usually Deferred objects that represent asynchronous
events.
//
// set loader on here
//
$.when($.ajax('/action/to/do')).then(function(response) {
// reset your loader
// action completed ;)
});

Delaying click event

I'm wondering whether there's a simple way to delay the click event from being processed for a specified period of time. For example we could have
$('#someElement').on('click', 'a', function(event) {
var duration = 1000;
someAsynchronousFunction(); // Start as soon as click occurs
... // Code to delay page transition from taking place for duration specified
});
So in this case the asynchronous function would be guaranteed some amount of time to run. If it hasn't completed it's work in this time I wouldn't care and would just like to continue with the page transition. I know that it's possible to accomplish something close with
event.preventDefault();
...
setTimeout(function(){
window.location = $(this).attr('href');
}, duration);
But this only works when the link being clicked goes to a full page. I want to be able to deal with links that are used for ajax calls (which don't change the url) as well.
I noticed that the mixpanel library has a function track_links which seems to accomplish the delay on the page transition, though that function doesn't seem to work well with the support for ajax links that I mentioned.
Any help would be great! Thanks.
Edit: So I suppose my question wasn't exactly clear, so I'll try to provide some more details below.
I don't care if the async function finishes running! I only want to give it the guarantee that it has some set amount of time to execute, after which I don't care if it finishes, and would prefer to go ahead with the page transition.
i.e. I want to delay not the start of the async function, but the start of the page transition. The async function would start running as soon as the click occured.
Hopefully this is a bit more clear!
I figured out a way to solve the problem:
var secondClick = false;
var duration = 1000;
$('#someElement').on('click', 'a', function(event) {
var that = $(this);
if(!secondClick) {
event.stopPropagation();
setTimeout(function(){
secondClick = true;
that.click();
}, duration);
someAsynchronousFunction();
} else {
secondClick = false;
}
});
When the user clicks the link, it internally prevents that click from actually having any effect, and gives the asynchronous function a set amount of time to do it's work before doing a second click on the link which behaves normally.
setTimeout allows you to delay running code by however many ms you want
setTimeout(function(){
console.log('Stuff be done'); //This will be delayed for one second
}, 1000);
In reality, if you're dealing with ajax you want to respond when the ajax call is complete. It may take more or less than 1000ms. $.ajax allows you do this with the .done() method. This is an example from the docs:
$.ajax({
url: "test.html",
context: document.body
}).done(function() {
$(this).addClass("done");
});
window.setTimeout will execute any given function after a specified delay.
You'd call it like this:
$('yourElement').click(function (event) {
setTimeout(function () { console.log('hi'); }, 1000);
});
But I have to wonder why you need to do this. What's the problem you're trying to solve? Usually delaying stuff doesn't really solve anything.
jQuery's ajax functionality provides exactly what you are looking for. You can define a callback function to run after your ajax request.
Something like this:
$('#someElement').click(function(event){
event.preventDefault();
var loc = $(this).attr('href');
$.ajax({
url: "test.html",
complete: function(){
// Handle the complete event
loc = $(this).attr('href');
window.location.href = loc;
}
});
});
You may want to use ajaxStop instead of complete, it seems like your motivation for delaying navigation is because you have a bunch of asynchronous stuff going on and you want to make sure all your ajax stuff is complete before you navigate to that page.
Regardless I would recommend looking at http://api.jquery.com/Ajax_Events/ (a very useful page of documentation).

Set minimum wait time for AJAX callback action

I have an app built in HTML that loads fullscreen inside a modal with $.get()
I have a loading screen that is triggered when someone clicks the icon to load the app.
I want the loading screen to appear for a minimum of 2 seconds before clearing out and showing the app.
Is there a good, safe way to start a timer that runs for 2000 milliseconds, checks a variable that is populated by the callback to see if it is null, and then proceeds with a certain action only when the variable is loaded?
I know a while loop will do the trick, but might cycle 1000 times before the content loads on a slow day, and this seems like an inefficient way to do things.
Answer
$('#testApp').click(function() {
var twoSecMin = $.Deferred();
setTimeout(function () { twoSecMin.resolve(); }, 2000);
$('#appModal').fadeIn(400);
$.when($.get("/usm/portal/_layouts/monkeysphere/test.aspx"),twoSecMin).then(function (data) {
$('#appContainer').html(data[0]);
$('#appContainer').show();
$('#appModal').fadeOut(200);
});
});
If you're using a recent version of jQuery (1.5 or later), you can create a $.Deferred and resolve it with a fixed timeout:
var twoSecMin = $.Deferred();
setTimeout(function () { twoSecMin.resolve(); }, 2000);
Also, save the jqXHR returned by $.get, which is an extended Deferred object:
var ajaxReq = $.get("/usm/test.aspx", ...);
Then wait for both:
$.when(twoSecMin, ajaxReq).then(function (_, res) {
// ready...
var data = res[0];
// ...
});
You could probably just use a setTimeout(fn, 2e3) to do this.
For testing if your variable is null, you could use yourVariable === null.

How can I delay an ajax call in jQuery?

Is there a way to delay an ajax request from executing until an animation finished?
var showAjaxLoader = function(selector) {
$("." + selector).fadeToggle("slow");
$("." + selector).parent().html('<img src="ajax-loader.gif" />').fadeIn();
};
$(".add, .delete")
.bind("ajax:beforeSend", function(event, data, status, xhr) {
showAjaxLoader(this.className);
});
Basically, the response of the request will replace the lay contents, but I don't want this to happen until the effect took place. Otherwise the image will just pop up with no effect...
U can use a callback methode for when the animation is finished:
$("p").fadeToggle("slow", function () {
$("div").append("<div>finished animation</div>");
// Here you can put your ajax request
});
Just use callback, as in the .fadeToggle(), .fadeIn() and .fadeOut() documentation. Make AJAX call within this callback.
And I believe you do not want to make request even when fading out that box (at least I don't see the reason here), but only when fading in.

Categories