This is a followup to a question I asked yesterday. I'm having a different problem related to jquery promises.
function setOverrides2() {
var dfd = new $.Deferred();
// do something
return dfd.promise();
}
function overrideDialog1() {
var deferred = new $.Deferred();
ConfirmMessage.onConfirmYes = function() {
ConfirmMessage.hideAll();
// do stuff
deferred.resolve();
}
ConfirmMessage.onConfirmNo = function() {
ConfirmMessage.hideAll();
// do stuff
deferred.reject();
}
ConfirmMessage.showConfirmMessage("Do you wish to override primary eligibility?");
return deferred.promise();
}
function overrideDialog2() {
var deferred = new $.Deferred();
ConfirmMessage.onConfirmYes = function() {
ConfirmMessage.hideAll();
// do stuff
deferred.resolve();
}
ConfirmMessage.onConfirmNo = function() {
ConfirmMessage.hideAll();
// do stuff
deferred.reject();
}
ConfirmMessage.showConfirmMessage("Do you wish to override secondary eligibility?");
return deferred.promise();
}
setOverrides2().done(function(data) {
// shows both dialogs at once
overrideDialog().then(overrideDialog2()).then(function() {
alert("test");
});
// waits for one dialog to complete before showing the other
// overrideDialog1().done(function() {
// overrideDialog2().done(function() {
// alert("test two!");
// });
// });
});
As shown above, when I use done(), it works perfectly, but when I use then(), it shows both dialogs simultaneously. I want to be able to be able to use reject() to abort the chain the first time the user clicks the No button (defined by the onConfirmNo() callback).
The commented .done() section waits for one dialog to finish before triggering the next, but does not abort processing if the user clicks No on the first dialog.
I think I almost have this right, so if anyone can assist on this last piece of the puzzle, I'd greatly appreciate it.
Jason
overrideDialog().then(overrideDialog2())
Should be:
overrideDialog().then(overrideDialog2)
The reason done was working was because you wrapped it inside a function (which did not immediately execute)
Related
I have the following function that creates a dialog and is using jQuery deferred to wait for the user response:
function myPageUnsaved() {
var defer = $.Deferred();
$('<div></div>')
.html('You have unsaved changes. Leave this page and lose your changes?')
.dialog({
autoOpen: true,
modal: true,
title: 'Confirmation',
buttons: {
"No": function () {
defer.resolve("false");
$(this).dialog("close");
},
"Yes": function () {
defer.resolve("true");
$(this).dialog("close");
}
},
close: function () {
$(this).dialog('destroy').remove();
}
});
return defer.promise();
}
I call the function within the js below:
// function to check for user changes prior to navigating to a new page
function checkPageChange() {
if (pageChanges == true) {
myPageUnsaved().then(function (answer) {
// if user answered no, stay on the page
if(answer == 'false') {
return false;
// else the user answered yes, so leave the page
} else {
return true;
}
});
} else {
return true;
}
}
I am running jQuery 1.11.
The dialog is presented to the user, but immediately is removed. What am I missing?
The pattern used seems correct, my initial answer was incorrect. .promise is a method of deferred and all member methods can be seen here. http://api.jquery.com/category/deferred-object/
Example of the code provided by original poster which seems to work in jsfiddle
https://jsfiddle.net/n7ewqfc0/
Initial answer - incorrect asumptions
Have you tried just returning the defer as opposed to defer.promise(); That doesn't seem right to me.
Looking at the spec for Deferred there is no reference to the promise method.
https://api.jquery.com/jquery.deferred/
Obviously I have hugely simplified your code to provide a resolving Promise but the principles are the same. You must return the promise object i.e. Deferred to be able to use the promise based callbacks .then etc.
function myPageUnsaved() {
var defer = $.Deferred();
setTimeout(function(){
defer.resolve('hey back from inside the promise')
}, 2000)
return defer;
}
var p = myPageUnsaved()
p.then(function(response){
console.log(response)
})
This question is part of me trying to learn promises and deferreds. Suppose you have a button that submits a POST:
$("#submit").click( function() {
$.post({...})
})
My understanding is that AJAX is itself a promise (which makes obvious sense), so what I'd like to do is, when user clicks #check, it returns the output of the AJAX once it is complete (assuming that #check can and will always be clicked only after #submit has been clicked).
I thought this was simple enough, so my initial code was:
$("#check").click(function() {
$.when($.post({...})).done( function(data) {
console.log("data")
})
})
But I realize that in this implementation, the AJAX wouldn't start POSTing until #check is clicked. There's no need for any #submit button and having the AJAX .post in #submit is redundant.
Is there a way to achieve what I'm doing using promises/deferreds?
Just store the promise returned by post.
var myPromise = null;
$("#submit").click( function() {
myPromise = $.post({...});
});
$("#check").click(function() {
if (myPromise) {
myPromise.then( function(data) {
console.log("data");
});
}
});
The other changes I made are using then() instead of done() (a single function to accept success, failure or progress) and I added statement-ending semicolons (because automatic semicolon insertion kills puppies).
And once you're done studying promises, move on swiftly to observables. With JavaScript the fun never stops.
Based on a comment on the question:
i want to submit something via AJAX, but then i want to use the result of that AJAX ONLY LATER when button check is clicked.
You may be overcomplicating this. You don't really need to dissect the AJAX request/promise/etc. between these two buttons. Simply make the request in the first button and store the result, then use the result in the second button. Something as simple as this:
// disable the check button until there is a result to check
$('#check').prop('disabled', true);
var ajaxResult;
$("#submit").click( function() {
$.post({...})
.done(function (result) {
// any other logic you want to put here, then...
ajaxResult = result;
$('#check').prop('disabled', false);
});
})
$('#check').click(function() {
// the result is in ajaxResult, use it as needed here
});
Basically the "check" button doesn't have anything to do with AJAX. It's just performing an action on data which exists in memory. That button is simply enabled when that data is successfully fetched.
You could create a Promise when the #submit button is pressed, and then use that to establish a handler for the #check button.
$("#submit").click(function() {
var requestPromise = $.post( "xxx", function(response) {
// do something here
})
.fail(function() {
alert( "error" );
})
$("#check").click(function() {
requestPromise.done(function(response) {
// do something meaningful with response here, or other logic
});
// disable #check button and remove click handler here
}
// enable #check button here
})
EDIT - as requested by OP
Here's the version using a compliant Promise:
$("#submit").click(function() {
var requestPromise = new Promise(function(resolve, reject) {
$.post( "xxx", function(response) {
// do something here
resolve(response);
})
.fail(function(response) {
alert( "error" );
reject(response);
});
});
$("#check").click(function() {
requestPromise.then(function(response) {
// do something meaningful with response here, or other logic
});
// disable #check button and remove click handler here
}
// enable #check button here
})
This question already has answers here:
How to attach callback to jquery effect on dialog show?
(5 answers)
Closed 8 years ago.
I want my javascript/jquery application to stop executing all animations, and only continue executing when a loading .gif is shown.
I did manage to show the loading .gif while other animations where going on, but I really need it to already show before anything else is animated.
So I fabricated a method that waits for the callback to be executed, but it doesn't work as expected.
var Shown = false;
function ShowLoadingGif() {
$("#loading").show("fast", function() {
Shown = true;
});
while(!Shown) {
//wait for callback to be executed
}
Shown = false;
}
See this JFiddle example. I would not only like to know how to properly go about solving this problem; I would also appreciate any input as to why it doesn't work as I expect it to.
Thanks in advance!
You can use something like this, using the jQuery Deferred Object ( see Docs )
var Shown = false;
function ShowLoadingGif() {
var $promise = $.Deferred();
$("#loading").show("fast", function() {
$promise.resolve("Im done");
});
$promise.done(function(data) {
// data === "Im done"
});
}
I have updated your Fiddle that now alerts when the stuff has finished as you would expect
Fiddle (http://jsfiddle.net/k5Wan/3/)
Also I have updated the code quality
var $promise = $.Deferred();
$promise.done(function() {
alert("Done...");
});
$(function() {
$("button").on("click", function() {
$("#loading").show("slow", function() {
$promise.resolve();
});
});
});
You can shorten all that:
$('#loading').show('fast', function(){
console.log('Im loaded');
}).promise().done(function(){
console.log('great loading with you!');
});
DEMO
I've found a lot of questions about deferring, promises, running javascript synchronously, etc. and I've tried numerous things already but still can't get this to work.
Edit Here's a little more explanation on the problem. fetchData has a routine that depends on all the code inside showStuff being complete. In particular, there's divs that get created using percentage of screen size, and we need to get the height of those divs so we can draw gauges inside them. fetchData is running before slideDown() is complete. Please see the additional console.log code I've added directly below.
My button onClick() calls showOverlay().
function showOverlay() {
showStuff().promise().done( function() {
console.log($("#gauge1").height()); //returns -0.5625 or something close
fetchData(); //ajax call
});
}
function showStuff() {
$("#overlay").fadeIn(200);
$("#gauges").slideDown(800);
$(".gauge").each(function() {
$( this ).show(); //unhides #gauge1 div
});
}
The error I'm getting says: cannot call method 'promise' of undefined.
I'm not showing my fetchData() function but it basically uses ajax to call a web service and then creates gauges on the screen using Raphael. If fetchData runs before the animations are complete the gauges are not displayed correctly because their size is relative to the .gauge div's.
Edit1
Neither of the examples below work. They both run without errors but return too quickly.
function showOverlay() {
showStuff().promise().done(function() {
fetchData();
});
}
function showStuff() {
var def = $.Deferred();
$("#overlay").fadeIn(200);
$("#gauges").slideDown(800);
$(".gauge").each(function() {
$( this ).show();
});
def.resolve();
return def;
}
Doesn't work either:
function showOverlay() {
$.when(showStuff()).done(function() {
fetchData();
});
}
function showStuff() {
$("#overlay").fadeIn(200);
$("#gauges").slideDown(800);
$(".gauge").each(function() {
$( this ).show();
});
}
You've 2 issues, the deferred and thats not how you run animations one after the other.
This will get you part of the way:
function showStuff() {
var deferred = $.Deferred();
$("#overlay").fadeIn(300,function(){
$("#gauges").slideDown(800,function(){
$(".gauge").show(); //doing this one after another takes more code.
deferred.resolve();
});
});
return deferred;
}
Heres the codepen: http://codepen.io/krismeister/pen/pvgKj
If you need to do sophisticated animations like this. You might find better results with GSAP.
Heres how to stagger:
http://www.greensock.com/jump-start-js/#stagger
Try to use $.when() instead:
$.when(showStuff()).done(function() {
fetchData();
});
You a) need to return something from showStuff b) should return a promise directly, so that the .promise() method is unnecessary:
function showOverlay() {
showStuff().done(function() {
fetchData();
});
}
function showStuff() {
return $("#overlay").fadeIn(200).promise().then(function() {
return $("#gauges").slideDown(800).promise();
}).then(function() {
return $(".gauge").show().promise();
});
}
I'm trying to set animations on rendering and closing an ItemView with Backbone.Marionette. For rendering a view, this is fairly simple:
MyItemView = Backbone.Marionette.View.extend({
...
onRender: function() {
this.$el.hide().fadeIn();
}
...
});
This will have my view fade in when I render it. But let's say I want to fade out my view upon close.
beforeClose: function() {
this.$el.fadeOut(); // doesn't do anything....
}
This won't work, because the item closes immediately after calling this.beforeClose(), so the animation doesn't have time to complete.
Is there any way, using Marionette as it stands, to accomplish a closing animation?
Alternatively, this is the workaround I've been using:
_.extend(Backbone.Marionette.ItemView.prototype, {
close: function(callback) {
if (this.beforeClose) {
// if beforeClose returns false, wait for beforeClose to resolve before closing
// Before close calls `run` parameter to continue with closing element
var dfd = $.Deferred(), run = dfd.resolve, self = this;
if(this.beforeClose(run) === false) {
dfd.done(function() {
self._closeView(); // call _closeView, making sure our context is still `this`
});
return true;
}
}
// Run close immediately if beforeClose does not return false
this._closeView();
},
// The standard ItemView.close method.
_closeView: function() {
this.remove();
if (this.onClose) { this.onClose(); }
this.trigger('close');
this.unbindAll();
this.unbind();
}
});
Now I can do this:
beforeClose: function(run) {
this.$el.fadeOut(run); // continue closing view after fadeOut is complete
return false;
},
I'm new to using Marionette, so I'm not sure if this is the best solution. If this is the best way, I'll submit a pull request, though I'll want to put a bit more thought into how this could work with other types of views.
This could potentially be used for other purposes, such as asking for confirmation on close (see this issue), or running any kind of asynchronous request.
Thoughts?
Overriding the close method is the one way to do this, but you can write it bit shorter, as you can call the Marionettes close method instead of duplicating it:
_.extend(Backbone.Marionette.ItemView.prototype, {
close: function(callback) {
var close = Backbone.Marionette.Region.prototype.close;
if (this.beforeClose) {
// if beforeClose returns false, wait for beforeClose to resolve before closing
// Before close calls `run` parameter to continue with closing element
var dfd = $.Deferred(), run = dfd.resolve, self = this;
if(this.beforeClose(run) === false) {
dfd.done(function() {
close.call(self);
});
return true;
}
}
// Run close immediately if beforeClose does not return false
close.call(this);
},
});
Another idea is to overide the remove method of your view. So you fade out the element of the view and then remove it from the DOM
remove: function(){
this.$el.fadeOut(function(){
$(this).remove();
});
}