jQuery onClick execution - javascript

I have this bit of javascript written with jQuery 1.2.5. It's contained inside the main function() of a plugin that I wrote. The plugin is a horizontal gallery scroller very similar to jCarousel. It does alot of auto calculating of widths and determines how many to scroll based on that and the size of the images, which is what all the calculations are that are going on.
What my question is, how do I prevent this from firing off before a previous execution is finished. For instance, if I get a little click happy and just frantically mash down on .digi_next. Things don't go so well in the UI when that happens and I'd like to fix it :) I thought the answer might lie in queue, but all my attempts at using it haven't turned out anything worthwhile.
var self = this;
$(".digi_next", this.container).click(function(){
var curLeft = $(".digi_container", self.container).css("left").split("px")[0];
var newLeft = (curLeft*1) - (self.containerPad + self.containerWidth) * self.show_photos;
if (newLeft < ((self.digi_gal_width - (self.containerPad + self.containerWidth) * self.show_photos)) * -1) {
newLeft = ((self.digi_gal_width - (self.containerPad + self.containerWidth) * self.show_photos)) * -1;
}
$(".digi_container", self.container).animate({
left: newLeft + "px"
}, self.rotateSpeed);
});

Just use a global busy flag. When you enter your click handler, check it, and only proceed if it's false. Immediately set it to true, and then set it back to false when the animation ends. JavaScript is single-threaded, so there is no race condition to worry about.
var busy = false;
$("...").onclick(function() {
if (busy) return false;
busy = true;
$("...").animate(..., ..., ..., function() {
busy= false;
});
return false;
});

Take a look at jQuery UI. Specifically the effects-part of the plug in. I use the slide-effect on my personal website (click on the arrows at the sides of the boxes).
I prevent users triggering the effect more than once - before the effect has ended - with the one event-handler and a callback function.
Here's the source-code

As an alternative to the afformentioned global flag, you could assign the value to the DOM element, that way enabling multiple elements on the page to have the same behaviour:
$("...").onclick(function(el) {
var self = el;
if (self.busy) return false;
self.busy = true;
$("...").animate(..., ..., ..., function() {
self.busy= false;
});
return false;
});

Since JavaScript functions calls are asyncronus, you can pass as a in parameter a callback function that's called when the previous call ends (same for errors).
You can pass the function you wrote in this post as the callback for the function that fire before.
Hope this helps.
Regards

Related

Js Animation using setInterval getting slow over time

I have a web site which displays a portfolio using this scrollbar http://manos.malihu.gr/jquery-custom-content-scroller/ .
I want to have an automatic scroll when the page is
fully loaded. When a user pass over the portfolio it stops the scroll and whe, he leaves it starts moving again.
It currently works well but sometimes it's getting slow or sluggish randomly.
Is there something wrong with my js function?
Here's the page having problems : http://www.lecarreau.net/new/spip.php?rubrique5 (I need the change the hosting provider, I know the site is slow).
This is the function:
(function($){
var timerId = 0;
$(window).load(function(){
$("#carousel").show().mCustomScrollbar( {
mouseWheel:false,
mouseWheelPixels:50,
horizontalScroll:true,
setHeight:370,
scrollButtons:{
enable: true,
scrollSpeed:50
},
advanced:{
updateOnContentResize:true
}
});
timerId = setInterval(function(){myTimer()},50);
$('#carousel').mouseenter(function () {
clearInterval(timerId);
});
$('#carousel').mouseleave(function () {
timerId = setInterval(function(){myTimer()},50);
});
});
})(jQuery);
function myTimer()
{
var width = $(".mCSB_container").width();
var left = $(".mCSB_container").position().left;
if (width-left>0) {
var scrollTo = -left+4;
$("#carousel").mCustomScrollbar("scrollTo", scrollTo);
}
}
Is there something obvious I'm missing?
Timing in the browser is never guaranteed. Since all functionality contents for time on a single event loop, continuous repeated tasks sometimes get a little janky.
One thing you could do that might decrease the jank is to cache the jQuery objects you're creating every 50ms:
var mCSBContainerEl = $(".mCSB_container");
var carouselEl = $("#carousel")
function myTimer()
{
var width = mCSBContainerEl.width();
var left = mCSBContainerEl.position().left;
if (width-left>0) {
var scrollTo = -left+4;
carouselEl.mCustomScrollbar("scrollTo", scrollTo);
}
}
Selecting these to elements only needs to happen once. From there they can just reference a variable instead of having to find the element on the page and wrap it in jQuery again and again.
Caching the variable in this case depends on them being in the DOM when the code runs, which isn't guaranteed here. You may have to move the variable assignment and function declaration into the function passed to load.
Also, as John Smith suggested, consider checking out requestAnimationFrame. Here's a shim I use by Paul Irish: http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/

JavaScript callback - multithreading

I want to develope a very simple strategic game.
You can find an example on http://wiesenberg.info/hope/ !
If you click on a button, that project will be created (progressbar). After it is finished, the project will show up on the playground. As you can see it works if you click one by one. But once you click a button twice (start 2 project of the same kind), the second project will not be finished because the first has not finished and increased the variable. Is there an easy way of doing multithreading or just solving my bug?
I also want to add a function that you can stop the project by clicking on the progressbar. I tried it out, it dissapears in the list, but the icon still shows up, because the function didnt get that another function was executed. so I also need a break function for the progressbar. I hope you undertood my problem! Thanks
Heres the important code:
function addSygehus() {
sygehusBarID++;
sygehusID++;
$("#addProject").append("<div id=sygehus" + sygehusID +
" class='progress progress-striped active'><div class='progress-bar progress-bar-success' id=sygehusbar"
+ sygehusBarID + " aria-valuetransitiongoal='100'>Sygehus</div></div>");
function countdown(callback) {
var bar = document.getElementById("sygehusbar"+sygehusBarID),
time = 0, max = firsttime,
int = setInterval(function() {
bar.style.width = Math.floor(100 * time++ / max) + '%';
if (time - 1 == max) {
clearInterval(int);
// 600ms - width animation time
callback && setTimeout(callback, 600);
}
}, 1000);
}
countdown(function() {
$("#sygehus" + sygehusID).remove();
$("#sygehusPic" + sygehusID).show(1000);
});
}
I would look at JQuery Deferred (http://api.jquery.com/jQuery.Deferred/) and Promises Documentation in general to manage what your trying to achieve. So you can apply some control to the order in which the asynchronous calls occur.
You've also got Q.js (https://github.com/kriskowal/q) is a nice library for managing these kinds of scenarios.
Otherwise you have web workers as already mentioned for emulating multithreaded behaviour but I don't think that's what your looking for.

What is the difference between this.click() and $(this).click()?

In the end, I have decided that this isn't a problem that I particularly need to fix, however it bothers me that I don't understand why it is happening.
Basically, I have some checkboxes, and I only want the users to be able to select a certain number of them. I'm using the code below to achieve that effect.
$j( function () {
$j('input[type=checkbox].vote_item').click( function() {
var numLeft = (+$j('#vote_num').text());
console.log(numLeft);
if ( numLeft == 0 && this.checked ) {
alert('I\'m sorry, you have already voted for the number of items that you are allowed to vote for.');
return false;
} else {
if ( this.checked == true ) {
$j('#vote_num').html(numLeft-1);
} else {
$j('#vote_num').html(numLeft+1);
}
}
});
});
And when I was testing it, I noticed that if I used:
$j('input[type=checkbox]').each( function () {
this.click()
});
The JavaScript reacted as I would expect, however when used with:
$j('input[type=checkbox]').each( function () {
$j(this).click()
});
It would actually make the counter count UP.
I do realize that it isn't the most secure way to keep count using the counter, however I do have server side error-checking that prevents more than the requisite amount from being entered in the database, that being the reason that I have decided that it doesn't actually need fixing.
Edit: The $j is due to the fact that I have to use jQuery in noConflict mode...
$(this) contains a jQuery wrapper (with lots of functions) whereas this is solely the DOM object.
The fact that counter is going up gave me the clue that there is a link between checked attribute, which you are using, and firing the click event manually.
I searched Google for 'jquery checkbox click event raise' and found this link, where author faces the exact same problem and the workaround he used.
http://www.bennadel.com/blog/1525-jQuery-s-Event-Triggering-Order-Of-Default-Behavior-And-triggerHandler-.htm
On a side note, I think you can simplify your code further:
$j('input[type=checkbox].vote_item').click(
function()
{
var maxNumberOfChoices = 5;
//get number of checked checkboxes.
var currentCheckedCount = $j('input[type=checkbox].vote_item :checked');
if(currentCheckedCount > maxNumberOfChoices)
{
//It's useful if you show how many choices user can make. :)
alert('You can only select maximum ' + maxNumberOfChoices + ' checkboxes.');
return false;
}
return true;
});
this.click() calls the browser DOM method click().
$(this).click() calls the jQuery method click(), which does more than just call the browser method: see the implementation of the function trigger for details.

Best way to prevent a javascript function from executing while it already is or another one is?

I'm using jquery and what I'm doing is binding the toggle method to a number of buttons on a webpage. It looks something like this
$('.button').toggle(function(){
// first function
}, function(){
// second function
});
However, there are animation in both of those functions. So a user can click the button while the first or second function is executing. And this messes up the order of the HTML elements and may make them move to the end of the page. Because essentially what these functions do is move one element to the end on the first click, and on the other click move it back where it originally was.
Of course, it is difficult to click the button once it is moving around the page. But it's possible.
You could use a flag. Set a flag 'isAnimating' to true when an animation begins, and false when it ends. Any subsequent animation can only proceed if this value is false.
You could also possibly check to see if the :animated selector applies to the owner of the event. And base your decisions off of that.
You could use a bool as a semiphore.. Obviously, this is in no way secure, but javascript doesn't really support locking, so you could easily have deadlocks and / or race conditions with this approach, but it will work 99,9% of the times :)
Seems like you'll be happier implementing your own toggle. Toggle really only works for cases with 0 additional logic.
$('.button').click(
function () {
if( $(self).is(":animated") {
return false;
}
if($(self).is(".rolledup")) {
self.apply(roll_window_down);
} else {
self.apply(roll_window_up);
}
});
function roll_window_up() {
$(self).addClass( 'rolledup' );
// first function
}
function roll_window_down() {
$(self).removeClass( 'rolledup' );
// first function
}
You need to place the two functions you pass to toggle in a context in which you can hold a flag to control function entrance:-
(function() {
var toggling = false;
$('.button').toggle(function(){
if (!toggling) {
toggling = true;
// first function
toggling = false;
} else {
// whatever you want to happen if re-entrance attempted
}
}, function(){
if (!toggling) {
toggling = true;
// second function
toggling = false;
} else {
// whatever you want to happen if re-entrance attempted
}
})
)();
N.B. This serialises all toggles of elements that have the .button class. IOW there is only one toggling flag for all buttons. If you want each button to have its own toggling flag:-
$('.button').each(function() {
var toggling = false;
$(this).toggle(function(){
if (!toggling) {
toggling = true;
// first function
toggling = false;
} else {
// whatever you want to happen if re-entrance attempted
}
}, function(){
if (!toggling) {
toggling = true;
// second function
toggling = false;
} else {
// whatever you want to happen if re-entrance attempted
}
});
);
You need a queue. You can build one with a semaphore variable, but jQuery already provides one, so maybe you want to use it:
$('.button').toggle(function() {
$(document).queue("foo", function() {
...
});
}, function() {
$(document).queue("foo", function() {
...
});
});
jQuery normally uses the "fx" queue to serialize animations, but you can use this "foo" queue for whatever you want.
The queue can be put on any object, so maybe you want to put it on the container that has all the .button objects in it. You cannot put it on the button (this) themselves, or you'll be back to where you're at now.
Once you've done that, all you really need to do is abort an animation. This can be done by expressly emptying the "fx" queue, or you can use $('.button').stop(); to stop all the old animations.

Javascript Event Hierarchy in Prototype framework

is there any event to be handled between the dom:loaded and load using Prototype javascript framework?
I've implemented a preloader using prototype which is looking like this:
Event.observe(window,"load",preload);
function preload(){
if($('wrapper'))
$('wrapper').setStyle({"display":"block"});
if($('loading'))
setTimeout('$("loading").fade({duration: 5.0});',4000);
}
then I've an another handler using for column height fixatioan which is:
Event.observe(window,"load",function(){
var bottomExtraOffset = (Prototype.Browser.IE) ? 100 : 130;
if(parseInt($$('.col1')[0].getStyle('height')) > parseInt($$('.col2')[0].getStyle('height')))
$('wrapper').setStyle({'height' : parseInt($$('.col1')[0].getStyle('height'))+bottomExtraOffset+'px'});
else
$('wrapper').setStyle({'height' : parseInt($$('.col2')[0].getStyle('height'))+bottomExtraOffset+'px'});
});//observe
It's working pretty nice in any browser but IE! It seems that IE is not appending the second handler to the onload handlers list, so when the first one is trying to get height of any of columns it returns 0 coz it's still displayed as none.
is there any other event than load & dom:loaded to be handled and get me outta this!?
There's no such event AFAIK, and even the dom:loaded event is implemented by Prototype developers and that's not a default event in JS. You might want to use a helper to make a delay in performing of the second handler, so the first one will be performed first:
function foo(){
var bottomExtraOffset = (Prototype.Browser.IE) ? 100 : 130;
if (parseInt($$('.col1')[0].getStyle('height')) > parseInt($$('.col2')[0].getStyle('height'))) {
$('wrapper').setStyle({'height' : parseInt($$('.col1')[0].getStyle('height')) + bottomExtraOffset + 'px'});
}
else {
$('wrapper').setStyle({'height' : parseInt($$('.col2')[0].getStyle('height')) + bottomExtraOffset + 'px'});
}
}
function bar() {
// One msec delay.
setTimeout(foo, 1);
}
Event.observe(window, "load", bar); // or window.observe("load",bar);
Also, you can check if the user agent is some hell like IE, then use this method and your said one otherwise.
Can you simply call the second function after you've finished preload() ?
Or if you dont want to directly call it after preload() then you could do something like set a flag and then set a timeout in the section function to wait until the first is done to execute.
If order is important you need to communicate between the functions.

Categories