I have a function binded to a .click() event. Each time the user clicks, the browser fadeOut() the current element and animate() the marginLeft of the new element. For some reason when I click fast on the button which is binded to this click event, in Chrome it jumpes the animation and just go on directly to the next on (only if I click like 5 times in 1 second) and don't add the marginLeft, in this case it causes a major usability issue for the UI. Is there any fallback for this scenario? Like if the animation is not completed directly add it with css() or something like that?
Thanks
E: The click() event calls a function where all the magic happens, if it helps ..
You may want to look into jquery's .stop() method. It allows you to short-circuit any currently-running animations on an element.
Simple example:
$myElement.on('click', function() {
// first 'true' clears animations in the queue for this element
// second 'true' completes the currently-running animation immediately
$(this).stop(true, true).fadeOut();
});
http://api.jquery.com/stop/
Related
I have a carousel set up and I have click events tied to the navigation arrows. When they are clicked I use jQuery animate to animate in the elements of the next carousel slide, and due to the nature of the animations I use setTimeout() a few times.
I have one bug, where if I click on an arrow, and then quickly click the previous or next arrow, the animation will get confused due to the timeouts (which are necessary in this project) and render nothing on the screen. I've looked at this for awhile, and I figure that the best method might be to prevent the other arrows from being clickable until the current animation has completed.
Each arrow is controlled by click not on:('click', function() ... etc:
$("#arrow1").click(function() {...}
I've looked at
$("element").off("click");
But as they are not attached using the 'on' method I don't think this will work.
I want something like:
$("#arrow1").click(function() {
$("#arrow2, #arrow3, #arrow4").off("click");
// do my animations, then
$("#arrow2, #arrow3, #arrow4").on("click");
}
But I haven't been able to get this to work yet.
.off and .on do not work as you expect. The .click method and other event methods use the .on method behind the scenes for binding the handlers. When the .off method is called without any specific handlers it removes (and not temporary disables) all the bound handlers for a specific event. And calling .on without passing a handler has no effect.
In your case you can use a flag instead of manipulating event handlers which is also more efficient than (re)binding and removing the event handlers.
You could use the unbind function
$("#arrow1").click(function() {
// $("#arrow2, #arrow3, #arrow4").off("click");
$("#arrow2, #arrow3, #arrow4").unbind("click");
// do my animations, then
$("#arrow2, #arrow3, #arrow4").on("click");
}
http://api.jquery.com/unbind/
Try to use unbind() function it should remove the event.
Description: Remove a previously-attached event handler from the elements.
$("#arrow1").unbind( "click" );
The off() function should work also.
Description: Remove an event handler.
$("#arrow1").off( "click" );
Hope this helps.
What I would do is set a boolean isLoading = false from the beginning. Then while the function is executing set:
isLoading = true. If isLoading == true, $('button').attr('disabled', 'disabled'); else enable it
When it completes then change it back to false.
Sorry it's psudo code, it's just off the top of my head.
According to jQuery document on .delay(),
The .delay() method is best for delaying between queued jQuery
effects. Because it is limited—it doesn't, for example, offer a way to
cancel the delay—.delay() is not a replacement for JavaScript's native
setTimeout function, which may be more appropriate for certain use
cases.
Could someone please expand on this? When is it more appropriate to use .delay(), and when is it better to use .setTimeout()?
I think what you posted explains itself really.
Use .delay() for jQuery effects including animations.
setTimeout() is best used for everything else. For example when you need to trigger an event at a certain elapsed time.
As far as I understand, .delay() is meant specifically for adding a delay between methods in a given jQuery queue. For example, if you wanted to fade an image into view during the span of 1 second, have it visible for 5 seconds, and then spend another second to fade it out of view again, you could do the following:
$('#image').fadeIn(1000).delay(5000).fadeOut(1000);
In this instance, .delay() is more intuitive to use since it is specifically meant for delaying events in a given jQuery queue. setImeout(), on the other hand, is a native JavaScript method that isn't intended explicitly for a queue line. If you wanted an alert box to pop up 1 second after clicking on a button, you could do the following:
function delayAlert() {
var x = setTimeout("alert('5 seconds later!')", 5000);
}
<input type="submit" value="Delay!" onclick="delayAlert();" />
You can use delay with animations, for example:
$('.message').delay(5000).fadeOut();
You can also use timeOut to delay the start of animations, for example:
window.setTimeout(function(){
$('.message').fadeOut();
}, 5000);
As you see, it's easier to use delay than setTimeout with animations.
You can delay pretty much anything with setTimeout, but you can only delay animations with delay. Methods that aren't animations are not affected by delay, so this would not wait a while before hiding the element, it would hide it immediately:
$('.message').delay(5000).hide();
.delay() is mostly used for chaining together animation effects with pauses in between.
As the docs mention, there is no way to cancel the delay. In the case where you may want to cancel the delay, a setTimeout() should be used instead so you can cancel it with clearTimeout()
Another side effect of delay(): it seems to disable the ability to hide (or fadeOut, etc) the objecting being delayed, until the delay is over.
For example, I set up the following code (perhaps a stackoverflow developer will recognize the css names....) to hide a 'div':
$j(document).ready(function(){
var $messageDiv = $j("<div>").addClass('fading_message')
.text("my alert message here").hide();
var $closeSpan = $j("<span>").addClass('notify_close').text("x");
$closeSpan.click(function() {$j(this).parent().slideUp(400);});
$messageDiv.append($closeSpan);
$j('.content_wrapper_div').prepend($messageDiv);
$messageDiv.fadeTo(500, .9).delay(5000).fadeTo(800,0);
});
Clicking the "x" that's in the span (that's in the 'div') did fire off the click function (I tested with an alert in there), but the div didn't slideUp as directed. However, If I replace the last line with this:
$messageDiv.fadeTo(500, .9);
..then it did work - when I clicked the "x", the surrounding div slideUp and and away. It seems as if the background running of the "delay()" function on the $messageDiv "locked" that object, so that a separate mechanism trying to close it couldn't do so until the delay was done.
I'm creating a slideshow, which will run on a timer (shown by progress bar), but allow users to click arrows to force next. I'm trying to use as much CSS3 as possible, so for my loop timer I'm using the CSS3 animation of the progress bar.
The way it works is that I start my progress bar at width:0, and set it to width:100%;. It has a CSS3 transition of 5s. I then watch for the end of the animation, and use that to call my resetprogress and changeimage functions, after which I then start the progress again. It loops indefinitely.
I've created a jsFiddle, simplified, to show what I'm talking about: http://jsfiddle.net/a3H9L/
Code for the simplified version is below. As you can see, I call startProgress, in which I start the CSS3 animation by changing the width, then set a watcher for the end of said animation, at which point I reset and then start again.
startProgress();
function startProgress() {
$('div').width('100%');
$('div').one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend',function(e){
resetProgress();
startProgress();
});
}
function resetProgress (){
$('div').addClass('notransition'); // Disable transitions
$('div').width('0');
$('div')[0].offsetHeight; // Trigger a reflow, flushing the CSS changes
$('div').removeClass('notransition'); // Re-enable transitions
}
$('button').click(function(event){
resetProgress();
startProgress();
});
My question is, if a user clicks the reset (which will eventually be Next and Previous), how do I break the original loop before resetting and starting a new loop? Right now, I think that I'm starting a new loop without ending the original, which is getting me two loops running at the same time.
EDIT: The reason I think something is wrong is that as I clicked reset a few times, things in the loop start happening at other times besides when the progress is reset.
TL;DR:
Simply call an .off() before chaining your .one()
Let's run an experiment using your fiddle.
Experiment 1:
Using the console to log a simple message everytime the .one() function is called:
$('div').one('...', function(e) {
console.log('one called!');
resetProgress();
startProgress();
});
Observation:
Yes, you are right, they do stack! If you click on the button, more .one() functions are added and will fire together at the same time once the bar reaches the end of the animation. ALL of them will still fire once the animation completes the following and subsequent times!
i.e.: Run fiddle, click your button five times. On completion of the first animation, console logs six messages (5 + 1). The bar resets itself and produces another six more messages. This goes on in multiples of six.
Experiment 2:
Now, let's try turning itself off at the start of the function:
$('div').one('...', function(e) {
$(this).off(e);
console.log('one called!');
resetProgress();
startProgress();
});
Observation:
This didn't produce the cancelling effect we were expecting. Same result as the first experiment.
Experiment 3:
Let's try turning all the handlers off (by omitting the "e"):
$('div').one('...', function(e) {
$(this).off();
console.log('one called!');
resetProgress();
startProgress();
});
Observation:
All queued .one() handlers execute at the end of the animation, but they terminate themselves after running once and do not fire the next time the animation completes.
Experiment 4:
What you actually wanted to do, was to cancel all previously queued handlers before setting a new one. So let's do this:
$('div').off().one('...', function(e) {
console.log('one called!');
resetProgress();
startProgress();
});
Observation:
There's your answer! This function now runs once, as the previous handlers were unset before a new one has been placed. Simply call an .off() before chaining your .one()
Disclaimer:
These experiments assume that those were your only event handlers on your element. If you have additional handlers set by .on(), .one() or similar, instead of using .off() to clear everything, you have to specify which handlers you want to clear, like so:
.off('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend')
I need to be able to chain click events, and temporarily pause event propagation between them.
So, for a given element, it runs three different click events, but the second one needs user input, so it pauses propagation while the user fills in the form and then continues.
clickAction2 = ->
#Pause event propagation
somehow.pauseEventPropegationRightHere()
#Go and handle the dialogs, user input, JS requests
goDoSomething().thenDoCallback( ->
#User is now authenticated completely.
somehow.continueEventPropegationAsIfNothingHappened()
)
In an effort to allow single responsibility, and events chained higher/lower shouldn't have knowledge that the event propagation was paused and nothing should be called twice.
No, the three click events can't be called sequentially from another function or any similar primitive solution.
This is in relation to AngularJS directives, but the solution does not need to rely on it.
There is a similar question but none of the answers are satisfactory: How to continue event propagation after cancelling?.
Edit:
What I need is a cleaner way to call e.stopImmediatePropagation(), and then continue from that point. As of right now, my best option is by manually entering the jQuery "private' data[1] and calling the functions manually.
$._data( $(element)[0], 'events' ).click[].handler()
I had a similar issue myself and I hope my solution is satisfactory because I realize it may not directly apply to your situation. However hopefully the principle I show here helps you find a solution of your own.
The problem (if I understand correctly) is that you need a way to prevent a child element from triggering an event if a parent element has triggered the same event at essentially the same time. Whereas e.stopImmediatePropagation() prevents event bubbling, effectively halting any parent elements from triggering the event. I believe the solution is using a setTimeout() with a zero millisecond delay to perform the desired function and if the triggering event ever occurs again while the setTimeout() is in use, use clearTimeout() to stop the previous event from occuring. The best way I can demonstrate this approach is by creating a parent and child element and watching the mouseleave event.
Here is the JS/jQuery:
var timerActive = false;
$('#div1, #div2').mouseleave(function(e) {
var trigger = $(this).attr('id'); // for monitoring which element triggered the event
if (timerActive) {
clearTimeout(announce); // stops the previous event from performing the function
}
window.announce = setTimeout(function() {
alert('mouse exited'+trigger); // trigger could be use in an if/else to perform unique tasks
timerActive = false;
},0);
timerActive = true;
});
Here is a JSFiddle demo: http://jsfiddle.net/XsLWE/
Since the events are triggered sequentially from child to parent, clearing each previous timeout effectively waits for the last or top-most element to trigger the event. At which point you could use an IF/ELSE statement to perform desired tasks. Or perform one function then a callback on complete.
In the demo, the effect is that on the left and bottom edges of the div elements, each div element is allowed to trigger the event individually. However on the top and right edges of div2, the only element allowed to trigger the event is div1.
Again, from your original post, I take it you are after something slightly different. But perhaps this approach will help you in some way.
Try something like
elem.on('event',function(e) {
e.stopPropagation();
goDoSomething(function(){
elem.parent().trigger(e);
});
});
goDoSomething() could do an AJAX call or something else async, and then for whatever reason call the callback to have the event propagation continue
I have created a simple drop-down menu with JQuery, following a tutorial. LINK :
http://wabism.com/development/jquerydropdown/
Its jQuery code is this:
$('body').ready(function() {
// Add the 'hover' event listener to our drop down class
$('.dropdown').hover(function() {
// When the event is triggered, grab the current element 'this' and
// find it's children '.sub_navigation' and display/hide them
$(this).find('.sub_navigation').slideToggle();
});
});
Drop-down works fine with 1 error. If you rollover and out a menu item 3 or 4 times quickly, it does all the 3 or 4 slidetoggle() 's. How can I overcome this problem ?
Use .stop() method before animation.
As you described, animations in jQuery would be queued and all of them would be executed, unless you explicitly specify that you want any pending animation to stop. You would do this using .stop() function. In your case you should write:
$(this)
.find('.sub_navigation')
.stop()
.slideToggle();
It has some parameters too which would give you more granular level of control. See here.