Jquery: execute a function when another is finished - javascript

I need your help.
I want to animate with jquery a panel. It must get open on click on buttons (function OPEN_PANEL) and load different php pages on it, then close when click on a div with a class "close" (function CLOSE_PANEL).
This works fine, the problem is when I want to open a different panel when one is already open. It should close the open one and after that open the last I selected, but it looks like it executes both functions at the same time. How can I solve the problem?
This is the javascript code:
var panel_is_open=0;
var last_open_panel="";
function animation_open_panel(id){
window_height=$(window).height()*0.85;
$("#"+id+"_button").css({'background-color':'rgba(255,255,255,0.8)', 'box-shadow':'0px 5px 10px #39C', '-webkit-box-shadow':'0px 5px 10px #39C'});
$("#main_panel").show().animate({ height: window_height+"px" }, 1500)
.animate({ width: "90%" },1000);
$("#main_panel").queue(function(){
$(".close").show();
$("#page_container").hide().load(id+"/"+id+".php",function(){
$("#page_container").fadeIn(1000);
});
$(this).dequeue();
});
}
function animation_close_panel(){
$("#page_container").fadeOut(1000, function(){
$("#main_panel").animate({ width: "637px" }, 1000)
.animate({ height:"0px" }, 1500, function(){
$(".close").hide();
$("#"+last_open_panel+"_button").css({'background-color':'', 'box-shadow':'', '-webkit-box-shadow':''});
});
});
}
function close_panel(){
if(panel_is_open==1){
animation_close_panel();
panel_is_open=0;
}
}
function open_panel(id){
if(panel_is_open==0){
animation_open_panel(id);
last_open_panel=id;
}
else if(panel_is_open==1){
if(id!=last_open_panel){
close_panel();
open_panel(id);
}
}
panel_is_open=1;
}
Thank you very much in advance for your help.
Thank you very much for your suggestions, but I couldn't solve the problem with both solutions. I am mistaking something but I can't understand what.
This is my code:
function close_panel(){
if(panel_is_open==1){
// animations here
panel_is_open=0;
}
}
function close_open_panel(next){
close_panel();
next();
}
function open_panel(id){
if(panel_is_open==0){
// animations here
last_open_panel=id;
panel_is_open=1;
}
else if(panel_is_open==1){
if(id!=last_open_panel){
close_open_panel(function(){
open_pannel(id);
});
}
}
}
Any idea where I am mistaking?
Thanks.

If you're after jQuery specific solution then look up Deferred Object:
jQuery.Deferred(), introduced in version 1.5, is a chainable utility object that can register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function.

You can use callbacks in the functions, e.g.
function open(callback)
{
// do stuff
callback(some_value);
}
Then you can use it by doing this:
open(function(value)
{
// anything here will be executed
// after the function has finished
});
The value in the callback() and function(value) is optional, you can return a straightforward function instead of passing a value that is to be called back, however, it's useful for some certain functions that requires asynchronous callbacks.
More information about callback functions:
Getting a better understanding of callback functions in JavaScript

Related

Unsure on why .load() complete event isn't working

I'm unsure on why this isn't working:
$(document).ready(function() {
setInterval(RefreshDiv, 2000);
})
function RefreshDiv(){
$('#box').load('messages.php #box', function() {
$('#box').on('load', function() {
$('#box').scroll(0, 50);
});
});
}
The tags are correct and the .load() part works every two seconds but I don't understand why my complete event to scroll down 50px isn't working?
I've also tried another method to scroll:
var log = document.querySelector('#box');
log.scrollTop = log.scrollHeight - log.clientHeight;
but this also doesn't execute on load
Edit #1
jQuery($ => {
setInterval(RefreshDiv, 2000);
})
function RefreshDiv() {
$('#box').load('messages.php #box', () => {
$('#box').scrollTop(50);
});
}
The load event only fires on certain elements such as img and the window object. As such I presume #box is not one of them.
You don't actually need the load event handler anyway as the callback itself runs when the load() method completes its request. Try this:
jQuery($ => {
setInterval(RefreshDiv, 2000);
})
function RefreshDiv() {
$('#box').load('messages.php #box', () => {
$('#box').scrollTop(5000);
});
}
It's also worth noting that sending AJAX requests every 2 seconds is not ideal, as it will not scale as you have more concurrent visitors to your site, and can lead to server performance problems. There's likely to be a much better alternative, depending on what it is you're doing.

Jquery Animate Infinite Loop: How to Avoid Stack limit

I've got some Jquery that simply zooms in and out non-stop for a banner image. When I run this, I get a stack limit error in the browser. It still runs, but is there a way to make it only load into the stack "just in time"? When looking at the stack it loads zoomIn() and zoomOut() over and over again on the initial load until it hits the limit, page loads are really slow because of it.
$(document).ready(function(){
$bannerImg = $('.post-picture img')
function zoomIn(){
$bannerImg.animate({
width: 1500,
}, 50000,'linear');
$bannerImg.promise().done(zoomOut());
}
function zoomOut(){
$bannerImg.animate({
width: 1500,
}, 50000,'linear');
$bannerImg.promise().done(zoomIn());
}
zoomIn();
});
Update: Thanks for the answers. Using done(ZoomOut/ZoomIn) worked.
.done() expects a function reference - the function pass will be executed as soon as the promise object is resolved. Instead, you're just calling the functions (which return nothing, undefined, anyways). If you do this, the functions will continually call each other, acting as an infinite loop. Use this:
$bannerImg.promise().done(zoomOut);
// and later:
$bannerImg.promise().done(zoomIn);
DEMO: http://jsfiddle.net/G6uWs/
(I had to change the numbers to make it usable)
Reference:
http://api.jquery.com/deferred.done/
You're calling the function in .done() instead of passing it as a parameter.
$bannerImg.promise().done(zoomOut());
should be
$bannerImg.promise().done(zoomOut);
and
$bannerImg.promise().done(zoomIn());
should be
$bannerImg.promise().done(zoomIn);
Looks like you are causing an infinite loop. Luckily, the jQuery has a complete callback that you can leverage to prevent the infinite loop.
Non-stop zoom in and out banner
$(document).ready(function () {
$bannerImg = $('.post-picture img');
function zoomIn() {
$bannerImg.animate({
width: 1500
}, {
duration: 10000,
complete: function () {
zoomOut();
}
});
}
function zoomOut() {
$bannerImg.animate({
width: 100
}, {
duration: 10000,
complete: function () {
zoomIn();
}
});
}
zoomIn();
});
*Source: * jsfiddle

jQuery: Call a function twice

I'm trying to run a function twice. Once when the page loads, and then again on click. Not sure what I'm doing wrong. Here is my code:
$('div').each(function truncate() {
$(this).addClass('closed').children().slice(0,2).show().find('.truncate').show();
});
$('.truncate').click(function() {
if ($(this).parent().hasClass('closed')) {
$(this).parent().removeClass('closed').addClass('open').children().show();
}
else if ($(this).parent().hasClass('open')) {
$(this).parent().removeClass('open').addClass('closed');
$('div').truncate();
$(this).show();
}
});
The problem is on line 13 where I call the truncate(); function a second time. Any idea why it's not working?
Edit jsFiddle here: http://jsfiddle.net/g6PLu/
That's a named function literal.
The name is only visible within the scope of the function.
Therefore, truncate doesn't exist outside of the handler.
Instead, create a normal function and pass it to each():
function truncate() { ...}
$('div').each(truncate);
What's the error message do you get?
You should create function and then call it as per requirement
Define the function
function truncate(){
$('div').each(function(){
});
}
Then call the function
truncate();
Another approach is to establish, then trigger, a custom event :
$('div').on('truncate', function() {
$(this).......;
}).trigger('truncate');
Then, wherever else you need the same action, trigger the event again.
To truncate all divs :
$('div').trigger('truncate');
Similarly you can truncate just one particular div :
$('div#myDiv').trigger('truncate');
The only prerequisite is that the custom event handler has been attached, so ...
$('p').trigger('truncate');
would do nothing because a truncate handler has not been established for p elements.
I know there's already an accepted answer, but I think the best solution would be a plugin http://jsfiddle.net/g6PLu/13/ It seems to be in the spirit of what the OP wants (to be able to call $('div').truncate). And makes for much cleaner code
(function($) {
$.fn.truncate = function() {
this.addClass('closed').children(":not('.truncate')").hide().slice(0,2).show();
};
$.fn.untruncate = function() {
this.removeClass('closed').children().show();
};
})(jQuery);
$('div').truncate();
$('.truncate').click(function() {
var $parent = $(this).parent();
if ($parent.hasClass('closed')) {
$parent.untruncate();
} else {
$parent.truncate();
}
});

Jquery css(width, 0) not working if the element is hidden

I have created a quick jsfiddle here showing it not working.
The problem I have is with the slide up. I want it to work so that it only sets the width to 0 after the slideup has finished. The obvious callback function does not seem to be getting called after the slideup has finished.
I would like it to work like this:
Shows the red box by sliding down and increasing the width together.
Click again and the box slides up then sets the width the 0. So that if the user clicks the button again the first animation would appear the same.
var $foo = $("#elm");
$("#btn").toggle(function() {
showDropDown();
}, function() {
hideDropDown();
});
function showDropDown(){
$foo.slideDown({duration:500, queue:false}).animate({"width": 400}, 250);
}
function hideDropDown(){
$foo.slideUp({duration:800, queue:false},function(){
$foo.css({"width": 0});
});
}
UPDATE:
The strange thing is that if I add a alert() into the callback function for slidedown it never gets called.
Edit: Sorry for the first answer, didn't pay attention.
The problem is that the callback is not executed, because you don't give the parameters according to the API, and the callback is not "wired" in.
Instead, you can use the promise().done(...) combination to achieve the objective you wanted.
So, you should modify your hideDropDown method as follows:
function hideDropDown(){
$foo.slideUp({duration:800, queue:false}).promise().done(function(){
$foo.css("width", "0px");
});
}
From the jQuery docs:
"The .promise() method returns a dynamically generated Promise that is resolved once all actions of a certain type bound to the collection, queued or not, have ended."
Maybe you just need to use animate to reset width to 0 like this:
var $foo = $("#elm");
$("#btn").toggle(function() {
showDropDown();
}, function() {
hideDropDown();
});
function showDropDown(){
$foo.slideDown({duration:500, queue:false}).animate({"width": 400}, 250);
}
function hideDropDown(){
$foo.slideUp({duration:800, queue:false}).animate({"width": 0}, 1);
}
or set width to 0 like this:
function hideDropDown(){
$foo.slideUp({duration:800, queue:false}).width(0);
}
Why not chain .animate() after .slideUp()?
$foo.slideUp({duration: 800, queue: false}).animate({"width": 0}, 800);

Change the asynchronous jQuery Dialog to be synchronous?

Currently, I'm working to replace "alert'/"confirm" with the jquery dialog.
But most of legacy codes is written in some asynchronous way, which make it difficult to change. Is there any way to make jquery dialog work in a synchronous way? ( don't use loop or callback function )
For example:
function run()
{
var result = confirm("yes or no");
alert( result );
\\more codes here
}
In this example the alert and other codes will be executed after user's choice.
If we use jquery dialog
var result = $dialog.open()
It will continue to execute the alert, which is asynchronous.
Currently, my solution is to use call back function in the OK|Cancel function.
For example:
OK: function ()
{
$dialog.close();
alert("yes");
//more codes here
}
This method works but it is difficult to make all the synchronous codes become asynchronous, which requires a lot of change (see the following example). So I'm looking for the synchronous jQuery Dialog, is it possible??
For example: ( The real codes are much more complicated than the following example)
function f1()
{
if ( confirm("hello") ) f2();
alert("no");
}
function f2()
{
if( confirm("world") ) f3();
alert("no");
}
function f3()
{
return confirm("!") ;
}
Another example:
vendorObject.on('some-event', function() {
if(confirm("Do you really want to do that?")) {
return true;
}
else {
return false; // cancel the event
}
});
... here the vendor object fires an event, which has to be cancelled if the user confirms. The event can only be cancelled if the event handler returns false - synchronously.
The short answer is no, you won't be able to keep your code synchronous. Here's why:
In order for this to be synchronous, the currently executing script would have to wait for the user to provide input, and then continue.
While there is a currently executing script, the user is unable to interact with the UI. In fact, the UI doesn't even update until after the script is done executing.
If the script can't continue until the user provides input, and the user can't provide input until the script is finished, the closest you'll ever get is a hung browser.
To illustrate this behavior, debug your code and set a break point on the line following a line that changes the UI:
$("body").css("backgroundColor", "red");
var x = 1; // break on this line
Notice that your page background is not yet red. It won't change to red until you resume execution and the script finishes executing. You are also unable to click any links in your page while you've got script execution paused with your debugger.
There is an exception to this rule for alert() and confirm(). These are browser controls, and are treated differently than actual web page UI elements.
The good news is that it really shouldn't be very hard to convert your code. Presumably, your code currently looks something like this:
if (confirm("continue?")) {
// several lines of code if yes
}
else {
// several lines of code if no
}
// several lines of code finally
Your asynchronous version could create a function ifConfirm(text, yesFn, noFn, finallyFn) and your code would look very much the same:
ifConfirm("continue?", function () {
// several lines of code if yes
},
function () {
// several lines of code if no
},
function () {
// several lines of code finally
});
Edit: In response to the additional example you added to your question, unfortunately that code will need to be refactored. It is simply not possible to have synchronous custom confirmation dialogs. To use a custom confirmation dialog in the scenario where an event needs to either continue or cancel, you'll just have to always cancel the event and mimic the event in the yesFn callback.
For example, a link:
$("a[href]").click(function (e) {
e.preventDefault();
var link = this.href;
ifConfirm("Are you sure you want to leave this awesome page?", function () {
location.href = link;
});
});
Or, a form:
$("form").submit(function (e) {
e.preventDefault();
var form = this;
ifConfirm("Are you sure you're ready to submit this form?", function () {
form.submit();
});
});
I'm not exactly sure what the motivation behind not using callbacks is so it is hard to judge what solution might satisfy your requirements, but another way to delay execution is through jQuery's "deferred" object.
http://api.jquery.com/category/deferred-object/
You could set up a function that opens the jquery dialog and add code that "waits" for dialog to close. This ends up working in a fairly similar way to a callback in the case you've laid out but here is an example:
function confirm () {
var defer = $.Deferred();
$('<div>Do you want to continue?</div>').dialog({
autoOpen: true,
close: function () {
$(this).dialog('destroy');
},
position: ['left', 'top'],
title: 'Continue?',
buttons: {
"Yes": function() {
defer.resolve("yes"); //on Yes click, end deferred state successfully with yes value
$( this ).dialog( "close" );
},
"No": function() {
defer.resolve("no"); //on No click end deferred successfully with no value
$( this ).dialog( "close" );
}
}
});
return defer.promise(); //important to return the deferred promise
}
$(document).ready(function () {
$('#prod_btn').click(function () {
confirm().then(function (answer) {//then will run if Yes or No is clicked
alert('run all my code on ' + answer);
});
});
});
Here it is working in jsFiddle: http://jsfiddle.net/FJMuJ/
No, you can't do anything sync in Javascript (alert is breaking the rules, in fact). Javascript is built with "single threaded, async" in the core.
What you can do, though, is disable functionality of the underlying page (lightbox-like) so no event get triggered from the page until you don't take the dialog action, be it OK or Cancel. Thought this does not help you to get your sync code working. You have to rewrite.
Here's some ideas - what you actually want is to block your async event to make it look like sync. Here's some links:
Queuing async calls
Mobl
Narrative JavaScript
Hope this helps you further!!
To answer David Whiteman's more specific question, here's how I'm implementing a "deferred" postback for a LinkButton Click event. Basically, I'm just preventing the default behaviour and firing the postback manually when user feedback is available.
function MyClientClickHandler(event, confirmationMessage, yesText, noText) {
// My LinkButtons are created dynamically, so I compute the caller's ID
var elementName = event.srcElement.id.replace(/_/g, '$');
// I don't want the event to be fired immediately because I want to add a parameter based on user's input
event.preventDefault();
$('<p>' + confirmationMessage + '</p>').dialog({
buttons: [
{
text: yesText,
click: function () {
$(this).dialog("close");
// Now I'm ready to fire the postback
__doPostBack(elementName, 'Y');
}
},
{
text: noText,
click: function () {
$(this).dialog("close");
// In my case, I need a postback when the user presses "no" as well
__doPostBack(elementName, 'N');
}
}
]
});
}
You can use a real modal dialog.
[dialog] is an element for a popup box in a web page, including a modal option which will make the rest of the page inert during use. This could be useful to block a user's interaction until they give you a response, or to confirm an action.
https://github.com/GoogleChrome/dialog-polyfill
I don't really see why you are opposed to using Jquery callbacks to achieve the behavior in your example. You will definitely have to rewrite some code but something like:
function f1() {
$( "#testdiv" ).dialog({
modal: true,
buttons: {
"OK": function() {
$( this ).dialog( "close" );
f2();
},
Cancel: function() {
$( this ).dialog( "close" );
alert('no');
}
}
});
}
function f2() {
$( "#testdiv2" ).dialog({
modal: true,
buttons: {
"OK": function() {
$( this ).dialog( "close" );
f3();
},
Cancel: function() {
$( this ).dialog( "close" );
alert('no');
}
}
});
}
function f3() {
$( "#testdiv3" ).dialog({
modal: true,
buttons: {
"OK": function() {
$( this ).dialog( "close" );
},
Cancel: function() {
$( this ).dialog( "close" );
}
}
});
}
<div id="testdiv" title="Hello"/>
<div id="testdiv2" title="World"/>
<div id="testdiv3" title="!"/>

Categories