Iterating Through N Level Children - javascript

This seems like something neat that might be "built into" jQuery but I think it's still worth asking.
I have a problem where that can easily be solved by iterating through all the children of a element. I've recently discovered I need to account for the cases where I would need to do a level or two deeper than the "1 level" (just calling .children() once) I am currently doing.
jQuery.each(divToLookAt.children(), function(index, element)
{
//do stuff
}
);
This is what I'm current doing. To go a second layer deep, I run another loop after doing stuff code for each element.
jQuery.each(divToLookAt.children(), function(index, element)
{
//do stuff
jQuery.each(jQuery(element).children(), function(indexLevelTwo, elementLevelTwo)
{
//do stuff
}
);
}
);
If I want to go yet another level deep, I have to do this all over again.
This is clearly not good. I'd love to declare a "level" variable and then have it all take care of. Anyone have any ideas for a clean efficient jQueryish solution?
Thanks!

This is an awesome question because of the levels deep catch. Check out the fiddle.
Converted this to a plugin.
Activate
$('#div').goDeep(3, function(deep){ // $.fn.goDeep(levels, callback)
// do stuff on `this`
});
Plugin
$.fn.goDeep = function(levels, func){
var iterateChildren = function(current, levelsDeep){
func.call(current, levelsDeep);
if(levelsDeep > 0)
$.each(current.children(), function(index, element){
iterateChildren($(element), levelsDeep-1);
});
};
return this.each(function(){
iterateChildren($(this), levels);
});
};

This question is awesome :-)
If you know your DOM is not too gigantic, you could just find all the descendants and filter out the ones that don't qualify:
var $parent = $('#parent');
var $childrenWithinRange = $parent.find('*').filter(function() {
return $(this).parents('#parent').length < yourMaxDepth;
});
After that, the jQuery instance "$childrenWithinRange" would be all the child nodes of that parent <div> that are within some maximum depth. If you wanted exactly that depth, you'd switch "<" to "===". I may be off by one somewhere.

You should be able to just do it with the all-selector(docs), the child-selector(docs) and multiple-selector(docs) like this:
Example: http://jsfiddle.net/mDu9q/1/
$('#start > *,#start > * > *,#start > * > * > *').doSomething();
...or if you only wanted to target the children 3 levels deep, you could do this:
Example: http://jsfiddle.net/mDu9q/2/
$('#start > * > * > *').doSomething();
Both of these selectors are valid for querySelectorAll, which means big performance boost in supported browsers.

The question sounds like the answer could be XPATH. I'm not well informed about the browser-support, but in XPATH you only need to create a path like
/*/*/*/*
https://developer.mozilla.org/en/introduction_to_using_xpath_in_javascript
(works in FF,Chrome,Safari,Opera)
http://msdn.microsoft.com/en-us/library/aa335968%28v=vs.71%29.aspx
(didn't try it yet)

var lvlFunc = function(elmt, depth) {
if(depth > 0) {
elmt.children().each(function(i, e){
// do stuff on the way down
lvlFunc($(this), --depth);
// do stuff on the way out
});
// do stuff
}
};
lvlFunc(divToLookAt, 3);
Make sure that you put your "do stuff" code in the right location if matters which order the "stuff" is performed in.

Related

Any chance to reduce the size of this snippet of code?

IS there any way I could reduce the size of the snippet of code below? Something like if (!$('body#pagina_blog_1 to body#pagina_blog_10).length) Online javascript minifier tools do not help.
jQuery(function($){
if (!$('body#pagina_blog_1, body#pagina_blog_2, body#pagina_blog_3, body#pagina_blog_4, body#pagina_blog_5, body#pagina_blog_6, body#pagina_blog_7, body#pagina_blog_8, body#pagina_blog_9, body#pagina_blog_10').length)
return;
// do stuff
});
Yes you can: $('*[id^="pagina_blog_"]')
For more details refer jquery selectors: http://api.jquery.com/attribute-starts-with-selector/
If you know it's an id on the body tag, you don't even need to use selectors as you can just get the id string directly and compare it to anything you want using a regex. For example, you could do this:
if (document.body.id.match(/^pagina_blog_\d+$/)) {
// code here
}
Or, for just any one or two digits at the end:
if (document.body.id.match(/^pagina_blog_\d{1,2}$/)) {
// code here
}
Or, if you wanted to actually see if the number after the id is in some specific numeric range such as 1-10, you could do this:
var num, matches = document.body.id.match(/^pagina_blog_(\d+)$/);
if (matches) {
num = +matches[1];
if (num >= 1 && num <= 10) {
// code here
}
}
It's not really minifying, but how about just
if ( $('[id^="pagina_blog_"]').length === 0 ) {
// do stuff
}
Give them a shared class to select on.
If you must use the ids for some reason, I would suggest...
!$('body').is('[id^="pagina_blog_"]')
The reason you do not want to put the id selector as the first selector is this would result in a complete dom scan, which is not desired. However in your logic it looks like your only concerned with the body tag having it.

jQuery how does one peek ahead in each()?

I can't seem to find a definite answer for this. Consider the following:
var dupe = false;
$(".syndUID").sort(function(a,b) {
return a - b;
}).each(function(i, el) {
alert($(this).val() + " - " + $(this).next().val());
if($(this).val() == $(this).next().val()) {
dupe = true;
return;
};
});
This code is an attempt to find duplicate values in a set of inputs with the class syndUID. They are scattered about a form, so not next to eachother in the DOM.
next().val() is always undefined though. am I using the wrong function? How do I simply peek ahead to the next element? I have access to the index, but I don't know how to even make use of it.
EDIT:
After reading the comments and answers I realized there really is no proper iterator in jQuery, which seems really stupid to me since it provides each(). I also had another bug with the above code. Here is the final solution I used that works:
// duplicate check
var dupe = false;
var prevVal = undefined;
$(".syndUID").sort(function(a,b) {
return $(a).val() - $(b).val();
}).each(function() {
if($(this).val() == prevVal) {
dupe = true;
return false;
}
prevVal = $(this).val();
});
For anyone who finds this via google, the answers provided by others may be a decent alternative solution, but for my needs I found this sufficed.
You can do something like $('.syndUID')[i+1] to regrab the list, focusing on that element (and optionally turning it back into a jQuery object)
Using [i+1] will return a DOM element, but you can use .eq(i+1) to return a jQuery object. Or if you hate your future developers, wrap the DOM element in $() because screw readability!
As Andreas stated -- regrabbing the list is wasting memory. Before the loop, cache the object into a variable with var element = $('.syndUID') so you're not iterating the DOM so much.
next() grabs the next sibling - not the next ancestor or parent.
For example, if we have an unordered list, next() works like this:
var a = $('#listone.first').next().val();
console.log(a) //outputs "Second"
<ul id="listone">
<li class="first">First</li>
<li class="second">Second</li>
</ul>
<ul id="listtwo">
<li class="third">Third</li>
<li class="fourth">Forth</li>
</ul>
So, if you are trying to grab the next parent, .val() applied to .next() wont work. I dont know what your outputting to (table, UL, DIV, etc) so it is hard to give you a working solution - but that is likely your problem. You are not traversing the DOM properly.
Well, you have your sorted array already, maybe instead of trying to do this with .each() you just use a simple for loop?
var elemArray = $(".syndUID").sort(function(a,b) {
return a - b;
});
for(var i = 0; i < elemArray.length; i++){
if(elemeArray[i].value == elemArray[i+1].value){
//do something with comparison
}
}
However, this will only check for a duplicate in the next syndUID element in the array. In order to do a complete search you would need to check each element against every element (excluding itself) from the array. That would involve a nested loop which will add an order of n^2 to your function

Efficiently tracking and updating an array of DOM elements with Javascript / jQuery?

Inside of a module I'm writing (its kind of a slider / timeline interface component) I've got a method that updates the controls which are a set of clickable elemetns along the bottom that are updated on click and when the user scrolls.
I'm doing the following to attach classes to the items up until the active one. While the approach I'm using works, its feels very inefficient as I'm looping over a set of DOM elements each time.
updateTimeLine : function(pos, cb) {
var p = pos;
var timeline = $('.timer').toArray();
if (p > 15)
p = 15;
$.each(timeline, function(index,value) {
var that = $(this);
if (index >= p) {
if (that.children('span').hasClass('active'))
that.children('span').removeClass('active');
} else {
that.children('span').addClass('active');
}
});
if (cb && typeof(cb) === "function") {
cb();
}
return this;
},
Is there a better way to do this? If so, how?
Is this a good use case for something like the observer pattern? which I don't fully get, having not spent any time with it yet, so if it is, I'd really like to know how to apply this pattern properly.
Observer patterns notify subscribed objects by looping through and invoking listeners on each subscriber when a relevant change occurs. Because of that, you'd probably end up using $.each anyways. I think what you have is equally efficient.
If you feel bad about iterating over the dom each time, consider this: there exists no such algorithm that can update each dom element without iterating through them. Caching the DOM array theoretically would improve performance, but my money says the browser's already doing that. Try it yourself on this jsperf...

Best way to do a non-mutating inverse tail in javascript using Underscore/Lodash

I'm an avid user of lodash/underscore in my node.js projects, and I find myself in a situation where I need to recursively iterate through an array from right to left in a manner similar to the below code (assuming that all calls are synchronous):
function makeDir(pathArray) {
var unsulliedPath = clone(pathArray);
var lastGuy = pathArray.pop();
var restOfEm = pathArray;
if dirExists(unsulliedPath) {
console.log("Your job is done!");
} else {
makeDir(restOfEm);
makeDir(unsulliedPath);
}
}
Having to clone and mutate the pathArray argument bugs me, however. So I could do this:
function makeDir(pathArray) {
var lastGuy = _.last(pathArray);
// EITHER I could...
var restOfEm = _(pathArray).reverse().tail().reverse().value();
// OR I could do...
var restOfEm = _.first(pathArray, pathArray.length - 1);
if dirExists(pathArray) {
console.log("Your job is done!");
} else {
makeDir(restOfEm);
makeDir(pathArray);
}
}
Okay, that takes care of having to clone the argument passed in. That underscore incantation is slightly ugly, though.
Do lodash/underscore contain a simpler and clearer method for getting the inverse of _.rest(), that is, every element except the last? If not, is there a preferred idiomatic solution for implementing this method in Javascript for use alongside lodash/underscore-style functional libraries, or is this simply nit-picking?
Thanks in advance, and apologies for any glaring omissions or errors in my question.
Why bother with underscore/lodash.
var last = pathArray.slice(-1)[0];
var rest = pathArray.slice(0, -1);
slice will do this all for you very easily...
Use Array.splice https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice and if you do not want to modify the original, do Array.slice to copy the original and then Array.splice.
ie:
var rest = pathArray.slice(0);
var last = rest.splice(-1, 1)[0];

Test if two elements are the same

I would suspect this to work at first:
if ($('#element') == $('#element')) alert('hello');
But it does not. How does one test if elements are the same?
As of jquery 1.6 you can now simply do:
$element1.is($element2)
This should work:
if ($(this)[0] === $(this)[0]) alert('hello');
so should this
if (openActivity[0] == $(this)[0]) alert('hello');
Or just
if (openActivity[0] == this) alert('hello');
(without a new jQuery instance ;-)
As somebody already told, the same HTML element wrapped in two different moments generates two different jQuery instances, so they can never be equal.
Instead, the HTML elements wrapped may be compared that way, since the memory location they occupy is the same if it is the same HTML element, so:
var LIs = $('#myUL LI');
var $match = $('#myUL').find('LI:first');
alert(LIs.eq(0) === $match); // false
alert(LIs.get(0) === $match.get(0)) // TRUE! yeah :)
Best regards!
I would use addClass() for marking the opened and you can check that easily.
9 years later, without jQuery
If two elements are the same one, two elements must have the same pointer.
Thus,
document.body === document.body // true
document.querySelector('div') === document.querySelector('div') // true
document.createElement('div') === document.createElement('div') // false
Like silky or Santi said, a unique ID or class would be the easiest way to test. The reason your if statements don't work like you'd expect is because it's comparing 2 objects and seeing if they're the same object in memory.
Since it's always a new object getting created by $(this), they can never equal each other. That's why you have to test on a property of the object. You could get away with no unique id/class if each openActivity element was guaranteed to have different content that you could test against.

Categories

Resources