I have a lot of divs in the html. Each div contains animation or some js code which is call only for this div when it visible:
<button id='start' class='start'>Start</button>
<div class="step1 later">
<div class='red later'>Show first</div>
<div class='aqua later'>Show later</div>
</div>
<div class="step2 later">
<div class='green later'>Show together</div>
<div class='yellow later'>Show together</div>
</div>
My goal is show div with step1 class and call some js, then show step2 and so on. It looks like slider. For this example I want show step1 and then show div with red class, after delay I want to show aqua. Next, we go to step2 and show two divs: yellow, green without delay. I have decided to use promises (Q.js) for this. This is my code:
$(function () {
var currentStep = 1,
handlers = {};
$('#start').click(function() {
for(var i = 0; i < 2; i++) {
showStep(currentStep + i);
}
});
function showStep(step) {
var name = 'step' + step;
$('.' + name).show();
handlers[name]();
}
handlers.step1 = function() {
Q().then(function() {
$('.notification').html('Step1 started');
})
.delay(2000)
.then(function() {
showElem('red');
})
.delay(2000)
.then(function() {
showElem('aqua');
});
};
handlers.step2 = function() {
Q().then(function() {
$('.notification').append('Step2 started');
})
.delay(2000)
.then(function() {
showElem('green');
showElem('yellow');
});
};
function showElem(classSelector) {
$('.' + classSelector).fadeIn(100);
}
});
All animations and delay works find except one thing. My code doesn't wait when one step will finish, handlers start almost at the same time.
DEMO : JSBIN
It's rather unconventional to use Q to schedule jQuery actions. With judicious use of its .delay() and .promise() methods, jQuery is well capable of doing this job reliably - on its own.
In the following rewrite of the code :
promises are returned from various functions to allow chaining
the master (start) routine is a simple two-step .then() chain
showElem() and showStep() are unnecessary and disappear
handlers is written as an object literal.
jQuery(function ($) {
var handlers = {
'step1': function() {
$('.notification').html('<div>Step1 started</div>');
return $(".step1").show(0).delay(2000).promise().then(function() {
return $('.red').fadeIn(100).delay(2000).promise();
}).then(function() {
return $('.aqua').fadeIn(100).promise();
});
},
'step2': function() {
$('.notification').append('<div>Step2 started</div>');
return $(".step2").show(0).delay(2000).promise().then(function() {
return $('.green, .yellow').fadeIn(100);
});
}
};
$('#start').click(function() {
handlers.step1().then(handlers.step2);
});
});
DEMO
EDIT
For a large number of handlers, you could longhand out the "start" routine as follows :
$('#start').click(function() {
handlers.step1()
.then(handlers.step2)
.then(handlers.step3)
.then(handlers.step4)
.then(handlers.step5)
.then(handlers.step6)
...
;
});
However, that could be a lot of typing and won't cater for a variable number of handlers.
The solution is reasonably simple.
First, write handlers as an Array, instead of Object.
var handlers = [
//step 0
function() {
$('.notification').html('<div>Step1 started</div>');
return $(".step1").show(0).delay(2000).promise().then(function() {
return $('.red').fadeIn(100).delay(2000).promise();
}).then(function() {
return $('.aqua').fadeIn(100).promise();
});
},
//step 1
function() {
$('.notification').append('<div>Step2 started</div>');
return $(".step2").show(0).delay(2000).promise().then(function() {
return $('.green, .yellow').fadeIn(100);
});
},
//step 2,
...
//step 3,
...
];
Now, you can use Array method .reduce() to scan the array, building a promise chain as it goes :
$('#start').click(function() {
handlers.reduce(function(promise, handler) {
return promise.then(handler);
}, $.when());
});
Here, $.when() is a resolved "seed" promise that gets the chain started.
DEMO
The issue lies in your initial "for" loop, where you execute all your "showStep" in a go, without waiting for promises.
You could do something like that instead:
$('#start').click(function() {
showStep(1);
});
function showStep(step) {
var name = 'step' + step;
$('.' + name).show();
handlers[name]().then(function() {
if (step < 2) {
showStep(step+1);
}
});
}
And make you handlers return the promise.
Related
I am trying to use callbacks in order to effectively "overwrite" the standard alert and confirm actions in JavaScript.
The code I am using is a bit long winded so I jotted it into a working jsfiddle
I am trying to get it so that a callback is used to determine true or false, but it is coming back as undefined as the callback function is fired before a click is
My questions, is how can I change this to effectively overcome the functions value being returned before the click is called via jQuery?
Example usage:
<button onclick="confirm('This is a test')">Show dialog (confirm)</button>
Example jQuery events:
if (confirm("This is a test")) {
alert("Confirmed")
}
else {
alert("Did not confirm")
}
Edit
Using a loop within the callback messed it us a lot...
You are mixing things up when waiting for the return value.
You are passing dialog.checkForInput as the callback. But in the dialog.show() function you do:
var ret = callback();
...
return ret;
But the dialog.checkForInput function doesn't return anything, it merely sets event listeners.
As events all run "asynchronously" it would be more sensible to give your dialog a callback function which will be run when there actually would be an event. Meaning: in your checkForInput function (I would name it differently, but whatever) run the callback and pass the action as a parameter. Something like:
checkForInput: function () {
$(document).ready(function () {
$(".dialog_confirm_okay").on("click", function () {
dialog.hide();
callback('confirm');
})
$(".dialog_confirm_cancel").on("click", function () {
dialog.hide();
callback('cancel');
})
$(".dialog_alert_okay").on("click", function () {
dialog.hide();
callback('alert');
})
})
}
And your callback could look like this (assuming your callback was called dialogCallback):
function dialogCallback ( action ) {
alert('Dialog closed with action: ' + action);
};
Some points I conclude from your code:
The reason why statement callback() return undefined value is because dialog.checkForInput return nothing.
The $(document).ready inside checkForInput is async, so returned value from that block is meaningless (it won't become the return value of the checkForInput as well).
And also you put the return statement inside event declaration, it'll become return value of the event (when the event triggered), not the checkForInput.
I did some modification on your code, this one working. Basically I create new method called onclick, which will be called every time button yes or no is clicked.
show: function (e_type, e_content) {
var d = dialog;
var d_head = e_type == "confirm" ? "Confirm Action" : "Error";
var d_buttons = e_type = "confirm" ? d.parts.buttons.okay + d.parts.buttons.cancel : d.dparts.buttons.alert_okay;
var _dialog = d.parts.main + d.parts.head.replace("{DIV_HEADER}", d_head) + d.parts.body + e_content + "<div class='dialog_button_container'>" + d_buttons + "</div>" + d.parts.footer;
$("body").append(_dialog);
},
onclick: function (ret) {
$(".errors").text("Return value was: " + ret);
},
showError: function (e_content) {
dialog.show("alert", e_content);
dialog.checkForInput();
},
showConfirm: function (e_content) {
dialog.show("confirm", e_content);
dialog.checkForInput();
},
checkForInput: function () {
var self = this;
$(".dialog_confirm_okay").on("click", function () {
dialog.hide();
self.onclick(true);
})
$(".dialog_confirm_no").on("click", function () {
dialog.hide();
self.onclick(false);
})
$(".dialog_alert_okay").on("click", function () {
dialog.hide();
self.onclick(false);
})
},
Working example: https://jsfiddle.net/p83uLeop/1/
Hope this will help you.
EDITED
From the comment section I assume that you want this alert to become a blocking function like window.confirm, so you can do something like if (confirm('Are you sure')).
But sadly it's impossible to achieve this case.
I have some suggestion, you can encapsulate your code better, and implement clean callbacks or promises. Maybe something like this:
showConfirm(function (ok) {
if (ok) {
// "yes" clicked
} else {
// "no" clicked
}
})
// or
showConfirm(function () {
// "yes" clicked
}, function () {
// "no clicked"
})
// or
var customConfirm = showConfirm()
customConfirm.on('yes', function () {
// "yes" clicked
})
customConfirm.on('no', function () {
// "no" clicked
})
I am executing this JavaScript which is kind of self explanatory (I think). There are around 100 buttons on a page with class button and I want them to click one by one. It's working fine.
But I want to add a delay of 5 seconds before this clicks next button.
var mybtn = document.getElementsByClassName('.button');
for( var i=0; i<100; i++ ) {
mybtn[i].click();
}
You can use setInterval for that functionality.
Instead of manually specifying 100 use the length property.
Also avoid using getElementsByClassName its not standard. Instead document.querySelectorAll is supported by most of the browsers.
var mybtn = document.querySelectorAll('.button');
var i = 0;
var timer = setInterval(function() {
if( i < mybtn.length) {
mybtn[i].click();
console.log("Click handler for button " + i + " fired");
} else {
clearInterval(timer);
}
i = i + 1;
}, 5000);
<div class="button">Hi1</div>
<div class="button">Hi2</div>
<div class="button">Hi3</div>
<div class="button">Hi4</div>
<div class="button">Hi5</div>
Something like this?
//var buttons = document.getElementsByClassName('.button'); // Not standard
var buttons = document.querySelectorAll('.button');
function clickButton(index) {
mybtn[index].click();
if(index < buttons.length) {
setTimeout("clickButton(" + (index+1) + ");", 5000);
}
}
setTimeout("clickButton(0);", 5000);
For testing, I added alert():
//var buttons = document.querySelectorAll('.button');
function clickButton(index) {
//mybtn[index].click();
alert("button" + index);
if(index < 100) {
setTimeout("clickButton(" + (index+1) + ");", 5000);
}
}
setTimeout("clickButton(0);", 5000);
You can use the setTimeout function to call the click function every 5 seconds.
Something like:
setTimeout(function(){ mybtn[i].click(); }, 5000);
If you don't mind using another library to solve this problem, then I suggest you use Kristopher Michael Kowal's Q library. Using promises by using Array.prototype.reduce, to iterate and resolve promises when they need to be fulfilled.
DEMO
// Query all buttons
var buttons = document.querySelectorAll('.button');
// Register Click Event Handlers
Array.prototype.forEach.call(buttons, registerButtonClickHandlers);
// Invoke button click
Array.prototype.reduce.call(
buttons,
reduceButtonPromises ,
Q.when()
);
function registerButtonClickHandlers(button) {
button.addEventListener('click', function() {
// display time end
console.timeEnd(button.innerHTML);
});
}
function reduceButtonPromises(promise, button) {
// register promise callback
return promise.then(function() {
// deferred object
var deferred = Q.defer();
setTimeout(function() {
// set time start
console.time(button.innerHTML);
// click button
button.click();
// resolve the promsie to trigger the next promise
deferred.resolve();
}, 100);
// promise
return deferred.promise;
});
}
On my page I have a simple piece of code based on nested functions, which helps me to show one elements after another in right order:
var fade = 700;
$('#s2icon-center').fadeIn(fade, function() {
$('#s2icon-1').fadeIn(fade, function() {
$('#s2icon-3, #s2icon-9').fadeIn(fade, function() {
$('#s2icon-6, #s2icon-11, #s2icon-17').fadeIn(fade, function() {
$('#s2icon-5, #s2icon-14, #s2icon-19').fadeIn(fade, function() {
$('#s2icon-8, #s2icon-13, #s2icon-22').fadeIn(fade, function() {
$('#s2icon-2, #s2icon-16, #s2icon-21').fadeIn(fade, function() {
$('#s2icon-4, #s2icon-10, #s2icon-24').fadeIn(fade, function() {
$('#s2icon-7, #s2icon-12, #s2icon-18').fadeIn(fade, function() {
$('#s2icon-15, #s2icon-20').fadeIn(fade, function() {
$('#s2icon-23').fadeIn(fade);
});
});
});
});
});
});
});
});
});
});
But in my opinion, because of using too many functions in one place, page is getting laggy and I get an error in console: 'Uncaught RangeError: Maximum call stack size exceeded'
Now my question to you guys is, how (in most easy way) can I get same effect but more productive?
Thanks in advance!
------------- EDIT
Here how it's should look:
Codepen Full view
And the code is right here:
Codepen DEMO
What you seem to have a problem with is the animation callback:
Complete Function
If supplied, the complete callback function is fired once the
animation is complete. This can be useful for stringing different
animations together in sequence. The callback is not sent any
arguments, but this is set to the DOM element being animated. If
multiple elements are animated, the callback is executed once per
matched element, not once for the animation as a whole.
That's not what you want. Instead, use promise, which should also prevent the stack overflow.
var fade = 700;
$('#s2icon-center').fadeIn(fade).promise().then(function() {
return $('#s2icon-1').fadeIn(fade).promise();
}).then(function() {
return $('#s2icon-3, #s2icon-9').fadeIn(fade).promise();
}).then(function() {
return $('#s2icon-6, #s2icon-11, #s2icon-17').fadeIn(fade).promise();
}).then(function() {
return $('#s2icon-5, #s2icon-14, #s2icon-19').fadeIn(fade).promise();
}).then(function() {
return $('#s2icon-8, #s2icon-13, #s2icon-22').fadeIn(fade).promise();
}).then(function() {
return $('#s2icon-2, #s2icon-16, #s2icon-21').fadeIn(fade).promise();
}).then(function() {
return $('#s2icon-4, #s2icon-10, #s2icon-24').fadeIn(fade).promise();
}).then(function() {
return $('#s2icon-7, #s2icon-12, #s2icon-18').fadeIn(fade).promise();
}).then(function() {
return $('#s2icon-15, #s2icon-20').fadeIn(fade).promise();
}).then(function() {
return $('#s2icon-23').fadeIn(fade).promise();
}).then(function() {
console.log("all finished");
});
You can use delay() which delays execution of the next function in chain by a specified amount of time.
var fade = 700;
// ID's of all elements you want to fade
var elems = ["#d1", "#d2", "#d3", "#d4", "#d5"];
// In your case:
// var elems = ['#s2icon-center', '#s2icon-1', '#s2icon-3, #s2icon-9', '#s2icon-6, #s2icon-11, #s2icon-17']; // and so on...
// Loop over all those elements
for (var e = 0; e < elems.length; e += 1) {
// Get element by it's ID (from the array)
var element = $(elems[e]);
// Use .delay() to delay execution of fade by the amount
// "fade * e", so 0, 700, 1400, ...
element.delay(fade * e).fadeOut(fade);
}
(note I've used fadeOut() to keep example clear - you'll need to change it to fadeOut().
DEMO.
I have JavaScript function:
function validateAddToCartForm(object) {
value = $(".product-detail-qty-box").val();
if(!isInt(value) || value < 1) {
$(".product-detail-error").show();
return false;
} else {
$(".product-detail-error").hide();
var product_name = $("#product_detail_name").text();
var NewDialog = $('<div id="MenuDialog">\ ' + product_name + '</div>');
NewDialog.dialog({
modal: true,
title: "title",
show: 'clip',
hide: {effect: "fadeOut", duration: 1000}
});
}
return true;
}
I need to pause 3 to 5 seconds before returning true, because I want to show a New Dialog box for a while. How can I implement this delay?
The only way to simulate delay in js is callback on timeout.
change the function to:
function validateAddToCartForm(object,callback) {
value = $(".product-detail-qty-box").val();
if(!isInt(value) || value < 1) {
$(".product-detail-error").show();
callback(false);
} else {
$(".product-detail-error").hide();
var product_name = $("#product_detail_name").text();
var NewDialog = $('<div id="MenuDialog">\ ' + product_name + '</div>');
NewDialog.dialog({
modal: true,
title: "title",
show: 'clip',
hide: {effect: "fadeOut", duration: 1000}
});
}
setTimeout(function() {callback(true);},5000);
}
where you call it you should do something like:
instead of
function somefunct() {
//code before call
if (validateAddToCartForm(object)) {
//process true
} else {
//process false
}
//rest of the function
}
place something like:
function somefunct() {
//code before call
validateAddToCartForm(object,function(ret) {
{
if (ret) {
//process true
} else {
//process false
}
//rest of the function
}
}
In to answer to your comment.
I assume:
that you want to prevent click event if validate false,
that all elements that you added onclick="..." have class ".clickme",
the element now looks like
<input type="submit" onclick="return validateAddToCartForm(this)" class="clickme" />
so 1st change the element to
<input type="submit" class="clickme" />
add to your javascript the following:
//this handle original click, drop it out, and only pass after validation
$(function () {
$('.clickme').click(function (e) {
var $t = $(this);
//if event triggered return true
if (e.isTrigger) return true;
validateAddToCartForm(this, function (ret) {
if (ret) {
$t.trigger('click');
}
});
return false;
});
});
also I suggest to use "submit" event on the form itself instead of "click" (the demo of submit)
Instead of blocking, you can use seTimeout to remove the #MenuDialog after a certain time.
function validateAddToCartForm(o){
var keep_dialog_time = 5 * 1000; // five seconds.
// Whatever...
/* Use setTimeout(function, milliseconds) to delay deletion of #MenuDialog.
This will get executed keep_dialog_time milliseconds after this call, and
won't block. */
setTimeout(function(){
$('#MenuDialog').hide(); // maybe there is another function to do this, I'm not a jQuery guy really.
}, keep_dialog_time);
return true;
}
JavaScript is single threaded. This means, when you block, you block the everything. Thus, the DOM uses an event loop model, where callbacks are assigned to events. Such a model is also present in node.js too. Above, because setTimeout does not block, code after that call will continue to run without waiting the function we passed to setTimeout to get executed.
I'd suggest to study DOM in depth to get more comfortable with web front-end programming. You may use various search engines to find out cool documentation.
The problem is that when I try doing multiple animations they all happen the same time.
Is there any way to have animations go one after another without using callbacks?
Here's what I want to do:
$('#a1').click(function() { $('#div1').hide(3000); });
$('#a2').click(function() { $('#div2').hide(3000); });
$('#a3').click(function() { $('#div3').show(3000); });
If you click on #a1 and then click on #a2 then #a3 before the first animation completes then it shouldn't start right away but instead wait until the animation queue is empty then start the next one.
Take this demo for example
I want to be able to click a1 then a2 then a3 one after the another and first have it hide the first div completely, then the second completely, and then show the third.
My example is overly simple and while this can be done with callbacks, my real problem can't so callbacks aren't an option.
In essence, if you click all three the animation should complete in 9 seconds.
This DEMO should alert ('took around 9 seconds to complete')
Use .queue() on a common jQuery object:
var q = $({}); // this could also be a common parent, e.g. $('body')
$('#a1').click(function() {
q.queue(function(next) {
$('#div1').hide(3000, next);
});
return false;
});
$('#a2').click(function() {
q.queue(function(next) {
$('#div2').hide(3000, next);
});
return false;
});
$('#a3').click(function() {
q.queue(function(next) {
$('#div3').show(3000, next);
});
return false;
});
Demo
Use .promise() to sidestep callbacks on show and hide:
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.
By default, type is "fx", which means the returned Promise is resolved when all animations of the selected elements have completed
Use .queue() to limit the number of animations resolved per promise (See also jsFiddle):
var promises = $({});
$('#a1').click(function() {
promises.queue(function(next) {
$('div').promise().done(function() {
$('#div1').hide(3000);
next();
});
});
return false;
});
$('#a2').click(function() {
promises.queue(function(next) {
$('div').promise().done(function() {
$('#div2').hide(3000);
next();
});
});
return false;
});
$('#a3').click(function() {
promises.queue(function(next) {
$('div').promise().done(function() {
$('#div3').show(3000);
next();
});
});
return false;
});
Try to create some array with queue, and check if there is something in it, as callback for animation, and run it again if there is. I've played with your example a little.
check it out:
http://jsfiddle.net/acrashik/nqh6x/6/
var queue = {
q: [],
run: function(elem, type, time, recall) {
if (queue.isRunning && !recall) {
console.log('pushed: ' + elem + type + time);
queue.q.push({elem:elem, type:type, time:time});
} else {
console.log('running:' + elem);
queue.isRunning = true;
if (type=='hide') {
$(elem).hide(time, function(){
queue.recall();
})
} else {
$(elem).show(time, function(){
queue.recall();
})
}
}
},
recall: function(){
console.log(queue.q.length);
if (queue.q.length > 0) {
queue.run(queue.q[0].elem, queue.q[0].type, queue.q[0].time, true);
queue.q = queue.q.splice(1,queue.q.length);
} else {
queue.isRunning = false;
queue.q = [];
}
},
isRunning: false
}
$('#a1').click(function() { queue.run('#div1','hide',2200) });
$('#a2').click(function() { queue.run('#div2','hide',2200) });
$('#a3').click(function() { queue.run('#div3','show',2200) });
I would use the animate() function as it comes with a complete function which is called when the animation finishes http://api.jquery.com/animate/.
So to use the jQuery doc example:
$('#clickme').click(function() {
$('#book').animate({
opacity: 0.25,
left: '+=50',
height: 'toggle'
}, 5000, function() {
// Animation complete this is where you call the next animation...
});
});
function another_animation () {
$('xyz').animate({
opacity: 0.25,
left: '+=50',
}5000, function() {
// Animation complete this is where you call the next animation
I think that this is the cleanest way...
You could do something like this:
(function(){
var col=(function(){
var i=1;
return function(n){alert("you clicked link nr " + n + ", total clicks: "+i++)};
})();
$('#a1').click(function(){col(1)});
$('#a2').click(function(){col(2)});
$('#a3').click(function(){col(3)});
})();
Im not gona write the entire code for you but that should give you a good idea of how to do it.
Also none of the variables or functions are accessible from the global scope or any other.
Add stop() before the animation:
$('#a1').click(function() { $('#div1').stop().hide(3000); });
$('#a2').click(function() { $('#div2').stop().hide(3000); });
$('#a3').click(function() { $('#div3').stop().show(3000); });
Use this
$(element).promise().done(function () {...})
In your case
$('#a1').click(function () {
$('#div1').hide(3000);
});
$('#a2').click(function () {
$("#div1").promise().done(function () {
$('#div2').hide(3000);
});
});
$('#a3').click(function () {
$("#div2").promise().done(function () {
$('#div3').hide(3000);
});
});
The next animation will be performed only when the previous animation on selected element is complete.