jQuery how does one peek ahead in each()? - javascript

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

Related

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...

Finding if there any element in the set has style.display !== "none";

var isAnyBool = $(selector)
.filter(function(){ return this.style.display !== "none"; })
.length;
// if (isAnyBool) { .. }
This works as expected but needlessly counts all elements when all it's needed is simple true/false. How it could be improved to be more efficient?
UPDATE: since visible doesn't know if element is directly hidden, or some of the parents are actually hidden, I have to check style.display or something equal in functionality
I don't know about performance efficiency, but this is far easier to read:
var isAnyBool = $(selector).is(':visible');
Edit #2:
I think #dandavis's answer is the best one
var isAnyBool = $(selector+"[style*='display: none']").length > 0 ;
should be MUCH faster in modern browsers than iteration of any type.
since jQuery's :visible can be affected by more than just style.display, it's not the right tool for this job.
this checks the style attrib only, and does so without js iteration, making it the fastest and simplest solution offered at time of writing.
You can make it simple with :visible
var isAnyBool = $(selector).filter(':visible').length;
Instead of filtering and counting the elements, you can use the .is() method with the same callback:
var isAnyBool = $(selector).is(function(){ return this.style.display !== "none"; })
Can't say for sure if this is more or less overhead, but seems more legible to me if you did it something like this:
$(selectSomeStuff).filter(!$(this).is(':visible'));
... or testable variable
var isAnyBool = $(selectSomeStuff).filter(!$(this).is(':visible')).length
... maybe as if statement
if ($(selectSomeStuff).filter(!$(this).is(':visible'))){
var isAnyBool = true;
// OR
//.. do stuff here ...
}
It doesn't count.. length is a property not a function. So there is not much going on when you call .length
Instead of storing the length, I would store the result in case if it can be used elsewhere.
var mySelector = $(selector).filter(function(){ return this.style.display !== "none"; })
if (mySelector.length) { .. }

jQuery has conditional

Trying to write a conditional with jQuery that basically states, if div.gathering does not contain a.cat-link then do the following. I have tried the following but it doesn't seem to work. Can anyone shed some light on this?
if($("div.gathering:contains('a.cat-link')")){
$(".gathering").append("<a href='#"+data[i]["categories"][0]["category_id"]+"div' class='cat-link' id='"+data[i]["categories"][0]["category_id"]+"' rel='external'>"+data[i]["categories"][0]["category_name"]+"<br />");
}
How about this :
if($("div.gathering").find("a.cat-link").length == 0){
// Conditional statement returned TRUE
}
jQuery selectors return arrays of objects that matched the given selector. This is why we use the length property.
The method that you used - $("div.gathering:contains('a.cat-link')")
would return an empty array and when testing against any object that actually exists (even if it is an empty array) JavaScript will return true.
Example -
var nateArr = [];
if (nateArr){
// Do the dishes...
}else{
// Eat some waffles...
}
If you test this for yourself you will never stop washing those dishes because even though the nateArr contains zero elements it still exists therefore the conditional statement will always return true.
And your fingers will go all wrinkly
try this....
$("div.gathering:not(:contains(a.cat-link))")
.append("<a href='#"+data[i]["categories"][0]["category_id"]+"div' class='cat-link' id='"+data[i]["categories"][0]["category_id"]+"' rel='external'>"+data[i]["categories"][0]["category_name"]+"<br />")
this will only return the div with class gathering which does not have a.cat-link....
hope this helps....

jQuery find - What order does it return elements in?

I have been using jQuery's find method and it's been very useful.
However, I came across an example where the find seemed to be duplicating things and not returning elements in the order they appeared in the document. (I'm not sure if find is supposed to do this - I doubt it).
However, it shouldn't have duplicates in the elements it finds and show the wrong order, surely?
Full example can be found here: jsFiddle - Notice how span[9] and span[10] are in the wrong order and duplicated.
Why is this the case?
Update
Updated so that output is written to document, please use new link above.
.find() returns elements in document order. More info here: http://docs.jquery.com/Release%3ajQuery_1.3.2
I think the anomaly has something to do with the wildcard selectors. Are those necessary? Removing them seems to resolve the issue.
you add unused * in your code, replace this find with your code:
$('#div1').find("*[class=w_line_" + i + "]").each(function () {
and have this, have good time;
I can´t find anything strange with the order.
$(document).ready(function() {
for (var i = 1; i <= 10; i++) {
console.log(i);
$('#div1').find("*[class*=w_line_" + i + "]").each(function() {
console.log(i, $(this));
});
}
});
This selector seems to return the elements in the same order as yours and I can´t see any duplicates.
$('#div1 *[class*=w_line_' + i + ']')

Iterating Through N Level Children

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.

Categories

Resources