I was playing with a fiddle earlier today while trying to answer a question and found a confusing thing. Being a JS newbie I am not being able to debug whats going wrong myself. I even tried to check the source0 of $.fn.show in jQuery source but couldn't figure out whats going wrong.
HTML:
<input type='text' id='dataBox'/>
<input type='button' value='toggle' id='toggleButton' />
jQuery code:
jQuery(function ($) {
var _oldShow = $.fn.show;
$.fn.show = function (speed, oldCallback) {
return $(this).each(function () {
var obj = $(this),
newCallback = function () {
if ($.isFunction(oldCallback)) {
oldCallback.apply(obj);
}
obj.trigger('afterShow');
};
obj.trigger('beforeShow');
if(speed)
_oldShow.apply(obj, [speed,newCallback]);
else
_oldShow.apply(obj, [newCallback]);
});
}
});
$('#dataBox').bind('beforeShow', function () {
alert('beforeShow');
});
$('#toggleButton').click(function(){
$('#dataBox').show();
});
The problem is for some mistake that I did, is causing this line to execute infinite number of times
obj.trigger('beforeShow');
and hence the alert in this block
$('#dataBox').bind('beforeShow', function () {
alert('beforeShow');
});
seems not to stop.
Irrespect of what I am trying to do or if this can be done any other way, can someone please explain what I am doing wrong here. I have been trying for several hours but couldn't figure out.
FIDDLE
Let's take a look at this section
if(speed)
_oldShow.apply(obj, [speed,newCallback]);
else
_oldShow.apply(obj, [newCallback]);
});
_oldShow is assigned as $.fn.show; earlier, and .apply() calls a function with arguments as an array and the ability to set the context of this. (see here)
So, at the end of the function, we always end up calling the function again, infinitely triggering beforeShow.
Look into show function code(its in alert):
http://jsfiddle.net/D9vP6/4/
It seemed to call inself under some condition in order to normalyze argument. After redefining this function you receive infinite recursion.
To avoid such behaviour you should have all this normalizing in you code and do not trigger event under some conditions.
The problem with the above code is that at some point jQuery calls the $.fn.show from within itself and that creates the infinite loop. So the proper way to prevent that is to do some argument checking like follows:
jQuery(function($) {
var _oldShow = $.fn.show;
$.fn.show = function(speed, easing, oldCallback) {
var args = Array.prototype.slice.call(arguments),
duration = args[0] || 0,
easing = 'linear',
callback = function() {},
callbackArgIndex = 1;
// jQuery recursively calls show sometimes; we shouldn't
// handle such situations. Pass it to original show method.
if (!this.selector) {
_oldShow.apply(this, args);
return this;
}
if (args.length === 2) {
if ($.isFunction(args[1])) {
callback = args[1];
callbackArgIndex = 1;
}
else {
easing = args[1];
}
}
else if (args.length === 3) {
easing = args[1];
callback = args[2];
callbackArgIndex = 2;
}
return this.each(function() {
var obj = $(this),
oldCallback = callback,
newCallback = function() {
if ($.isFunction(oldCallback)) {
oldCallback.apply(obj);
}
obj.trigger('afterShow');
};
obj.trigger('beforeShow');
args[0] = duration;
if (callback) {
args[callbackArgIndex] = newCallback;
}
else {
args.push(callback);
}
_oldShow.apply(obj, args);
});
}
});
$('#dataBox').bind('beforeShow afterShow', function(e) {
alert(e.type);
});
$('#toggleButton').click(function() {
$('#dataBox').show();
});
This works fine and the events are fired properly.
The block which is preventing the infinite loop is this
if (!this.selector) {
_oldShow.apply(this, args);
return this;
}
What this is doing is, calling the original function and returning in cases jQuery calls $.fn.show multiple times(jQuery seems to be doing so for some reason).
Working Fiddle
Related
I'm trying to cancel a requestAnimationFrame loop, but I can't do it because each time requestAnimationFrame is called, a new timer ID is returned, but I only have access to the return value of the first call to requestAnimationFrame.
Specifically, my code is like this, which I don't think is entirely uncommon:
function animate(elem) {
var step = function (timestamp) {
//Do some stuff here.
if (progressedTime < totalTime) {
return requestAnimationFrame(step); //This return value seems useless.
}
};
return requestAnimationFrame(step);
}
//Elsewhere in the code, not in the global namespace.
var timerId = animate(elem);
//A second or two later, before the animation is over.
cancelAnimationFrame(timerId); //Doesn't work!
Because all subsequent calls to requestAnimationFrame are within the step function, I don't have access to the returned timer ID in the event that I want to call cancelAnimationFrame.
Looking at the way Mozilla (and apparently others do it), it looks like they declare a global variable in their code (myReq in the Mozilla code), and then assign the return value of each call to requestAnimationFrame to that variable so that it can be used any time for cancelAnimationFrame.
Is there any way to do this without declaring a global variable?
Thank you.
It doesn't need to be a global variable; it just needs to have scope such that both animate and cancel can access it. I.e. you can encapsulate it. For example, something like this:
var Animation = function(elem) {
var timerID;
var step = function() {
// ...
timerID = requestAnimationFrame(step);
};
return {
start: function() {
timerID = requestAnimationFrame(step);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation = new Animation(elem);
animation.start();
animation.cancel();
timerID; // error, not global.
EDIT: You don't need to code it every time - that's why we are doing programming, after all, to abstract stuff that repeats so we don't need to do it ourselves. :)
var Animation = function(step) {
var timerID;
var innerStep = function(timestamp) {
step(timestamp);
timerID = requestAnimationFrame(innerStep);
};
return {
start: function() {
timerID = requestAnimationFrame(innerStep);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation1 = new Animation(function(timestamp) {
// do something with elem1
});
var animation2 = new Animation(function(timestamp) {
// do something with elem2
});
I am trying to understand how this code works. I finally figured out it is a loop. It is not a "while" or "for" loop, but it is a loop nonetheless by virtue of calling itself I think (please correct me if I am wrong).
I understand it's main function: to pass JQuery when it is loaded to my 'foo' function, when ever jQuery has loaded. To do that it checks for jQuery in Window and if not there it resets the timer(). That is the loop. I get that.
Let me explain what I do not understand:
the call: CheckDependency.Deferred.execute(foo);
why the "Deferred" keyword?
execute baffles me: I expect that if I call CheckDependency.Deferred.execute that it would only execute that method. Why does it obviously run the timer function. why could it not simply have that code after the timer() since it keeps looping there and then return jquery?
Speaking of return. Why is there a method in there? CheckDependency.Deferred.execute(foo); is as crazy to me as CheckDependency.Deferred.RETURN.execute(foo); (or some similar crazy statement)
I am fairly new to JavaScript (from PHP). Here the code:
function foo(){ console.log('jQuery found!');
}
var CheckDependency = CheckDependency || { };
CheckDependency.Deferred = function ()
{
var functions = [];
var timer = function() {
if (window.jQuery) {/* && window.jQuery.ui*/
while (functions.length) {
functions.shift()(window.jQuery);
}
} else {
window.setTimeout(timer, 250);
}
};
timer();
return {
execute: function(onJQueryReady)
{
if (window.jQuery) { // && window.jQuery.ui
onJQueryReady(window.jQuery);
} else {
functions.push(onJQueryReady);
}
}
};
}();
CheckDependency.Deferred.execute(foo);
Let me start by saying I'm not a javascript expert, but I dabble :) I'll take a stab at describing what is going on here.
First, This creates a new object called "CheckDependency".
var CheckDependency = CheckDependency || { };
Next, it runs an anonymous function, and stores the result in CheckDependency.Deferred.
CheckDependency.Deferred = function ()
{
.
.
.
.
}()
The anonymous function runs the following code:
var functions = [];
var timer = function() {
if (window.jQuery) {/* && window.jQuery.ui*/
while (functions.length) {
functions.shift()(window.jQuery);
}
} else {
window.setTimeout(timer, 250);
}
};
timer();
The last part of the function code returns a new function execute, which gives CheckDependency.Deferred a function execute.
return {
execute: function(onJQueryReady)
{
if (window.jQuery) { // && window.jQuery.ui
onJQueryReady(window.jQuery);
} else {
functions.push(onJQueryReady);
}
}
};
Finally, this new function is called
CheckDependency.Deferred.execute(foo);
The final result of this is that the code starts a background timer that calls itself until window.jQuery is true - which means jQuery is loaded. Then, the function passed to execute is passed into this loop and so will once jQuery is available, the original function passed to "execute" will be called with the instance of window.jQuery.
I hope I did this justice, and I hope my answer helps! Please let me know if you have any question.
Apologies for what I'm sure is a repost; I really have looked quite widely for an answer to my question (that I also understood).
What I'm trying to learn to do is to arbitrarily chain functions such that they must complete before the next occurs, which, as I understand it, is the purpose of jQuery's deferred(). So in the below code, what I'm imagining should happen is:
the function contained within the load deferred objects executes; after which
the function contained in then() executes; after which
the function contained in done() executes.
Every tutorial in the universe uses a $.ajax() object after $.when(), which is useless if all one wants is control of execution sequence in a local context.
Here's what I've been trying:
var preloadDone = false,
var testDone = false,
var load = $.deferred(function() {
//cacheImages() is a plugin, works fine
$("img.image-loader.preload").cacheImages();
preloadDone = true;
});
var loader = $.when(load)
.then(function() {
if (preloadDone) {
console.log("then called in sequence");
} else {
console.log("then called out of sequence"); // wrong order, every time
}
XBS.cache.cbDone = true;
}).done(function() {
if (XBS.cache.cbDone) {
console.log("even done was called in right sequence!"); // proper order, every time
} else {
console.log("done() wasn't called in order..");
}
});
load.resolve(); // nothing happens
// load(); also tried this; nothing happens
So far as I can tell, this is identical to the example given in the jQuery $.when() documentation. Lil help?
Here is a demo on how to run many functions one after another but only after each funtion has completed. This is achieved by using an Async function.
Demo (Runs 3 functions one after the other. Where i have alert("starting *") that is were you want to put the work you like to do and in the done function you include the next function you want to run. )
http://jsfiddle.net/5xLbk91c/
//the Assync function. Pause is the time in miliseconds to pause between loops
var asyncFor = function(params) {
var defaults = {
total: 0,
limit: 1,
pause: 10,
context: this
},
options = $.extend(defaults, params),
def = $.Deferred(),
step = 0,
done = 0;
this.loop = function() {
if (done < options.total) {
step = 0;
for (; step < options.limit; step += 1, done += 1) {
def.notifyWith(options.context, [done]);
}
setTimeout.apply(this, [this.loop, options.pause]);
} else {
def.resolveWith(options.context);
}
};
setTimeout.apply(this, [this.loop, options.pause]);
return def;
};
function one() {
asyncFor({
total: 1, // run only once. If you want to loop then increase to desired total.
context: this
}).progress(function(step) {
alert("starting one")
}).done(function() {
alert("finished one")
two()
});
}
function two() {
asyncFor({
total: 1,
context: this
}).progress(function(step) {
alert("starting two")
}).done(function() {
alert("finished two")
three()
});
}
function three() {
asyncFor({
total: 1,
context: this
}).progress(function(step) {
alert("starting three")
}).done(function() {
alert("finished three and all done")
});
}
you may want to start your investigations by this change to your code:
var load = function() {
var deferred = $.Deferred();
$("img.image-loader.preload").cacheImages();
preloadDone = true;
return deferred;
};
Please also note you may pass array of promises to $.when().
Best regards
I see different topics about the toggle function in jquery, but what is now really the best way to toggle between functions?
Is there maybe some way to do it so i don't have to garbage collect all my toggle scripts?
Some of the examples are:
var first=true;
function toggle() {
if(first) {
first= false;
// function 1
}
else {
first=true;
// function 2
}
}
And
var first=true;
function toggle() {
if(first) {
// function 1
}
else {
// function 2
}
first = !first;
}
And
var first=true;
function toggle() {
(first) ? function_1() : function_2();
first != first;
}
function function_1(){}
function function_2(){}
return an new function
var foo = (function(){
var condition
, body
body = function () {
if(condition){
//thing here
} else {
//other things here
}
}
return body
}())`
Best really depends on the criteria your application demands. This might not be the best way to this is certainly a cute way to do it:
function toggler(a, b) {
var current;
return function() {
current = current === a ? b : a;
current();
}
}
var myToggle = toggler(function_1, function_2);
myToggle(); // executes function_1
myToggle(); // executes function_2
myToggle(); // executes function_1
It's an old question but i'd like to contribute too..
Sometimes in large project i have allot of toggle scripts and use global variables to determine if it is toggled or not. So those variables needs to garbage collect for organizing variables, like if i maybe use the same variable name somehow or things like that
You could try something like this..: (using your first example)
function toggle() {
var self = arguments.callee;
if (self.first === true) {
self.first = false;
// function 1
}
else {
self.first = true;
// function 2
}
}
Without a global variable. I just added the property first to the function scope.
This way can be used the same property name for other toggle functions too.
Warning: arguments.callee is forbidden in 'strict mode'
Otherwise you may directly assign the first property to the function using directly the function name
function toggle() {
if (toggle.first === true) {
toggle.first = false;
// function 1
}
else {
toggle.first = true;
// function 2
}
}
Situation : I have an event listener on an item. When I press on it, it calls a method that will perform a webkitAnimation and I return the end of the animation as a result.
Problem : If I click several times on my item, the webkit animation's listener is not reset, so I get many callbacks ..
I tried to use removeEventListener but it doesn't work..
Thanks in advance!
var Test = (function () {
function Test(listItem) {
this.listItem = listItem;
listItem.addEventListener('click', function(event) {
this.startAnim(function() {
});
}
}
Test.prototype.startAnim = function(callback) {
this.listItem.style.webkitAnimationName = 'simpleAnim';
this.listItem.style.webkitAnimationDuration = '220ms';
this.listItem.addEventListener('webkitAnimationEnd', function() {
this.style.webkitAnimationName = '';
// This calls my callback too many times..
callback();
// the following doesn't work!
this.removeEventListener('webkitAnimationEnd', function() {
// this doesn't work....
}, false);
}, false);
};
return Test;
}
You have to remove the same function you added; the browser can't guess what function you mean to remove (as there can be many functions added). You're removing two different functions created at different times, so of course it doesn't work. Remember a reference to the function you added, and then remove that function.
E.g.:
Test.prototype.startAnim = function(callback) {
this.listItem.style.webkitAnimationName = 'simpleAnim';
this.listItem.style.webkitAnimationDuration = '220ms';
// Add a specific function
this.listItem.addEventListener('webkitAnimationEnd', animationEndHandler, false);
function animationEndHandler() {
this.style.webkitAnimationName = '';
// This calls my callback too many times..
callback();
// Remove the same specific function
this.removeEventListener('webkitAnimationEnd', animationEndHandler, false);
}
};