homepage Welcome to WebmasterWorld Guest from 54.221.175.46
register, free tools, login, search, pro membership, help, library, announcements, recent posts, open posts,
Become a Pro Member
Visit PubCon.com
Home / Forums Index / Code, Content, and Presentation / JavaScript and AJAX
Forum Library, Charter, Moderator: open

JavaScript and AJAX Forum

    
run a function after jQuery .each() is done
sssweb




msg:4597443
 5:23 pm on Jul 29, 2013 (gmt 0)

I have a function like:

function do(arg1, arg2, callBack) {
// var defs
return jQuery(this).each(function()
{ // some stuff });
}

I want to run callBack(), defined elsewhere, after the last each() iteration.

Where do I put it and what is the syntax?

 

Readie




msg:4597470
 7:31 pm on Jul 29, 2013 (gmt 0)

It would be nice if there were such an in-built means of doing an end-of-loop callback.

Anyway. There's a fairly simple way, regardless of whether you're each'ing over a selector, an object, or an array.

First, declare your selector / object into a local variable with the var keyword, then create a local variable as an incrementer, also with the var keyword. Then simply compare the incrementer to the source length at the end of your each function.

Any variables you declare with the var keyword are global within the current scope, so any descendent functions will have access to that variable unless they or a higher level descendent overwrites it.

As an example:

var selector = $('.someClass'),
i = 0;

selector.each(function() {
i += 1;
// Some stuff
if(i == selector.length) {
// "callback"
}
});

Just a note, if you're dealing with an object, then you should first pre-build it's length into a variable outside of the loob. Getting the length of an object involves invoking the Object class which I'm lead to believe is a bit heavy, as you're basically parsing half the object into an array.

var objectLength = Object.keys(myObject).length;


---
EDIT
---

I believe the above may not work in 100% of cases... The $.each creates several asynchronous threads, so you may have a race condition [en.wikipedia.org] on your hands. If someone can think of a solution to that without resorting to arbitrary window.setTimeouts, please correct my logic.

sssweb




msg:4597493
 8:43 pm on Jul 29, 2013 (gmt 0)

That's actually what I tried (except creating a var for the selector). In my case, the 'selector' for the each loop is 'jQuery(this)' (as posted above). I tried an i incrementer exactly as you suggest, but for some reason it satisfies the i == selector.length conditional every time (i.e. for every 'each' item).

Changing to a var defined selector as you suggest didn't change anything.

Also, once it gets in the conditional, it doesn't run the callback. I want to run an externally defined function by calling it like so: if(i == ...){ do_func(); } -- but I keep getting the error "function expected"

sssweb




msg:4597514
 10:08 pm on Jul 29, 2013 (gmt 0)

I got it so that it only enters the conditional on the last each() iteration like it should, but I still get the "function expected" error. Is there a reason I can't call a function defined in an external script? Both are defined before the DOM is loaded, and are only run after it loads.

daveVk




msg:4597544
 12:07 am on Jul 30, 2013 (gmt 0)

Wondering why not just

function do(arg1, arg2, callBack) {
// var defs
jQuery(this).each(function()
{ // some stuff });
}
callBack();
return jQuery(this);
}

is there a difference I am missing ?

sssweb




msg:4597643
 1:02 pm on Jul 30, 2013 (gmt 0)

That doesn't do the trick either -- it tries to run the function BEFORE the each() loop, plus I still get the "function expected" error.

Even when I use:
if(i == jQuery(this).size()) { callBack(); }
inside the each() loop, it tries to run the function first, then complete the loop. (I even used alert(i); to confirm that it only enters that conditional on the last each().)

Re the "function expected" error, I must be making some basic coding or syntax mistake. I've done alert(callBack); and it shows the function name correctly, but it doesn't find the externally defined function.

If it matters, jQuery(this) is an array of hidden HTML divs. The function iterates through and fades them in sequentially. I want it to run the callback after the last one fades in.

Readie




msg:4597644
 1:02 pm on Jul 30, 2013 (gmt 0)

That's... Odd SSS. As for the "function expected" try mapping the function to a local variable:

var a = function() {};
a();


@Dave:
$.each spawns child threads, and then the parent thread just carries on without waiting for those child threads to finish

[edited by: Readie at 1:05 pm (utc) on Jul 30, 2013]

sssweb




msg:4597645
 1:04 pm on Jul 30, 2013 (gmt 0)

yeah, I agree.

sssweb




msg:4597650
 1:18 pm on Jul 30, 2013 (gmt 0)

My javascript knowledge is very limited, so just to make sure I'm not omitting pertinent info, the primary function listed first in my OP is actually inside another function. The full syntax:

(function(jQuery) {
jQuery.fn.funcName = function(arg1, arg2, callBack)
{
// stuff
return jQuery(this).each(function()
{
// stuff (inc conditional test for last each() + callBack)
});
};
})(jQuery);

My callBack is defined externally like:

function callBack() {
// stuff
}

I know enough that syntax matters in determining how functions act, but I don't know the details of each delaration type.

Readie




msg:4597723
 5:07 pm on Jul 30, 2013 (gmt 0)

Hey again,

First of all, this is pointless:
(function(jQuery) {

})(jQuery);

As what that syntax does is it maps the function params to the value of the second braces for the scope of the function. As an example:
var a = '1';
var b = '2';
var c = '3';
(function(a, b) {
alert(a + ' ' + b); // Outputs '2 3'
})(b, c);

alert(a + ' ' + b); // Outputs '1 2'

So you're mapping the global variable "jQuery" to the local variable "jQuery"

--

Anyway, pedantism over. I've thought up some logic which will avoid the race condition problem my earlier solution presented, and should greatly simplify matters for you. It's a new jQuery extension which I've imaginativley named "eachWithCallback"; As long as your each primary method does not spawn new asynchronous threads which you expect this callback to run after, you should be good.

(function($) {
$.fn.eachWithCallback = function() {
var $this = {};
$this.length = $(this).length;
$this.hits = 0;

$this.primaryMethod = arguments[0];
$this.callback = arguments[1];

return this.each(function() {
var i = 0;
var key = 'cb_' + i;
while(typeof this[key] != 'undefined') {
i += 1;
key = 'cb_' + i;
}

this[key] = $this.primaryMethod;
this[key]();
delete this[key];

$this.hits += 1;
if($this.hits == $this.length) {
$this.callback();
}
});
}

$('a').eachWithCallback(function() {
$(this).html('test');
}, function() {
alert('done');
});
})(jQuery);

[edited by: Readie at 5:14 pm (utc) on Jul 30, 2013]

Readie




msg:4597727
 5:10 pm on Jul 30, 2013 (gmt 0)

Please note that the above will only work in the $('.selector').each(function() {}) style.

It will not work in the $.each(object, function() {}) style.

sssweb




msg:4597755
 6:09 pm on Jul 30, 2013 (gmt 0)

Wow -- first of all, thanks for taking the time to do that; second, I was hoping for a much simpler solution.

That said, I couldn't get this to work with my limited js knowledge. Questions:

1. Is this supposed to replace my function (with minor edits to do the actions I need) or to be used along with my function. I assumed it's the former, and tried to merge the two w/o success.

2. You don't include arguments in your function, yet you rely on them inside; I assume you left them out for brevity and included them like so:
$.fn.eachWithCallback = function(arg1, arg2, callback)
Is that correct?

3. You have:
$this.primaryMethod = arguments[0];
$this.callback = arguments[1];
but I need 2 args plus the callback; I changed line 2 to:
$this.callback = arguments[2];
but how to handle line 1?

My console log says only: "LOG: [object Arguments]" -- is that correct?

4. (maybe related to #3) I get this error:
"The value of the property 'cb_0' is not a Function object"

5. You close eachWithCallback() with a simple curly bracket; my function closes with the bracket plus semi-colon -- does it matter? (I tried both; still doesn't work)

6. I don't understand your test call; I have this simple code (tweaked to accomodate your fn):
$("#test").eachWithCallback(1500, 2500, 'callback_func');

sssweb




msg:4597758
 6:27 pm on Jul 30, 2013 (gmt 0)

In hope of simplifying this whole thing, I will state the obvious:

I don't need the above function to run a callback after the last each() if I write the call TO the above function in such a way that it runs the callback when the result from the above function is returned.

Readie




msg:4597785
 7:49 pm on Jul 30, 2013 (gmt 0)

My function is designed to replace .each - It will iterate over a selector, running function 1, and once function 1 has completed for all selected elements, will run function 2. It only accepts functions, not textual representations of functions or anything

$('.selector').eachWithCallback(function() {}, function() {});

Anyway, on to answering questions.

1. Is this supposed to replace my function (with minor edits to do the actions I need) or to be used along with my function. I assumed it's the former, and tried to merge the two w/o success.

Mine is designed to be used in conjunction to yours - just replacing your .each

2. You don't include arguments in your function, yet you rely on them inside; I assume you left them out for brevity and included them like so:
$.fn.eachWithCallback = function(arg1, arg2, callback)
Is that correct?

I use the arguments array. This is a reserved javascript variable name which is present inside all function declarations.

As an example:
function a() {
alert(arguments[0] + ' ' + arguments[2]);
}

a('fus', 'ro', 'dah'); // Outputs 'fus dah'


3. You have:
$this.primaryMethod = arguments[0];
$this.callback = arguments;
but I need 2 args plus the callback; I changed line 2 to:
$this.callback = arguments[2];
but how to handle line 1?

N/A

My console log says only: "LOG: [object Arguments]" -- is that correct?

It should be an ordered Array object containing all arguments passed into the function

4. (maybe related to #3) I get this error:
"The value of the property 'cb_0' is not a Function object"

This may be because you changed the code - whatever you're passing in that is getting assigned to $this.primaryMethod is not a function

5. You close eachWithCallback() with a simple curly bracket; my function closes with the bracket plus semi-colon -- does it matter? (I tried both; still doesn't work)

EDIT: Mis-read

Semi colons actually don't do a whole lot in JavaScript unless you're putting lots on one line. But by strict standards I should have put a semi colon there as it's an object property assignment.

6. I don't understand your test call; I have this simple code (tweaked to accomodate your fn):
$("#test").eachWithCallback(1500, 2500, 'callback_func');

N/A

[1][edited by: Readie at 8:05 pm (utc) on Jul 30, 2013]

Readie




msg:4597787
 7:57 pm on Jul 30, 2013 (gmt 0)

To simplify that. My function is jQuery.each() with an extra parameter, which is a callback function :)

For passing your callback in, declare the function to a variable if you're having trouble reaching the function. Remember, the var keyword makes the variable global within the current scope.

So,

var myCallbackVariable = function() {}

//....
// Nested in several functions etc
//...

$('.someSelector').eachWithCallback(function() {
// Usual $.each function
}, myCallbackVariable);

sssweb




msg:4597816
 9:11 pm on Jul 30, 2013 (gmt 0)

Thanks for all that effort, but I found the simple code I'm looking for. For the initial call, instead of:

$("#ID").my_func(arg1, arg2, callback);

use:

$.when( $("#ID").my_func(arg1, arg2) ).done( function() { // callback func } );

Doesn't require any change to my_func or the each() loop within it.

daveVk




msg:4597855
 11:25 pm on Jul 30, 2013 (gmt 0)


@Dave:
$.each spawns child threads, and then the parent thread just carries on without waiting for those child threads to finish


Please provide a reference for that, it is not obvious from jQuery documentation.

Following test case alerts 'done' last ?

$("li").each(function(){
alert($(this).text())
});
alert('done');

Readie




msg:4598122
 4:33 pm on Jul 31, 2013 (gmt 0)

Hey Dave

Source... Experience from... A while ago, which caused me to always avoid the need to execute after an each has finished ever since.

Decided to do a non-trivial test with unpredictable loop-method run times and it seems either my memory is bad, the case I experienced was special (maybe implicit timeouts or something?), or the behaviour has changed. Either way, definite 100%, $.each does not spawn additional threads.

If a mod could be so kind as to edit my erroneous post with a message stating that comment is flat-out wrong I'd appreciate it.

$('a').each(function() {
var i = null;
while(i != '0.55555') {
i = String(Math.random().toFixed(5));
}
console.log(this);
});
console.log('done');
<a title="Some Page 1" href="/some_page1">
jquery....min.js (line 3)
<a title="Some Page 2" href="/some_page_1/some_page_2">
jquery....min.js (line 3)
<a title="Some Page 3" href="/some_page_1/some_page_2/some_page_3">
jquery....min.js (line 3)
<a title="Sitemap" href="/sitemap">
jquery....min.js (line 3)
done

daveVk




msg:4598276
 1:03 am on Aug 1, 2013 (gmt 0)

Thanks for clearing that up Readie.

My limited understanding is that JS in the browser is a single thread that serially processes events. Been working with node.js recently, it is based on similar model.

Global Options:
 top home search open messages active posts  
 

Home / Forums Index / Code, Content, and Presentation / JavaScript and AJAX
rss feed

All trademarks and copyrights held by respective owners. Member comments are owned by the poster.
Home ¦ Free Tools ¦ Terms of Service ¦ Privacy Policy ¦ Report Problem ¦ About ¦ Library ¦ Newsletter
WebmasterWorld is a Developer Shed Community owned by Jim Boykin.
© Webmaster World 1996-2014 all rights reserved