views:

69

answers:

4

I'm trying to query an element and its children to find ID's that begin with a particular string.

var foundIDs = containerElement.find('[id^=something]').andSelf().filter('id^=something');

The find() method only searches descendants so I thought I'd try andSelf(). However, andSelf() does not take a selector. This means that the container element is included regardless of whether it matches the find query or not and I then have to perform a secondary filter() on it to remove the container element if it didn't match after all.

I attempted to put andSelf() before the find() but it didn't seem to pick up the container element into the stack.

containerElement.andSelf().find('[id^=something]');

Is there any better way to achieve what I'm doing?

+1  A: 

Just off the top of my head (not tested):

var foundIDs = containerElement
                     .filter('id^=something')
                     .add(containerElement.find('id^=something'));

This isn't that much more efficient than your first effort, but I think it's a little cleaner.

Philippe Leybaert
I guess there's no avoiding having to use the the same selector twice.
Soviut
+1  A: 
containerElement.find('*').andSelf().filter('[id^=something]');
J-P
should work, but I'd like to see the performance of that one
Philippe Leybaert
Makes sense but my version with a selector in find will reduce the queryset so that the filter will have less to process later on.
Soviut
+1  A: 

I know you've already accepted an answer, but I'll throw this one out there anyway.

var foundIDs = containerElement.wrap('<div>')             // Wrap
                               .parent()                  // Traverse up
                               .find('[id^=something]');  // Perform find

containerElement.unwrap();  // DOM is untouched

Because you are unwrapping the containerElement before the function is complete, the DOM remains untouched (in case you were wondering).

I verified it with livequery plugin, which never detects the new div.

If you log the ID (or whatever) of foundIDs, you'll see that the top level is your containerElement (assuming it matched the .find() criteria.

console.log( foundIDs.attr('id') );  // Log the ID of the root element.

EDIT:

With regard to testing, I performed a 1,000 iteration loop on both versions against a containerElement with only 2 nested elements.

I only tested in Safari on Mac.

The accepted answer was around 7 times faster.

If I added 100 nested elements, the gap closed to less that 2 times faster.

Here's the trouble. Both versions returned 0 elements if the containerElement didn't match. I'm not sure why this is.

patrick dw
Clever, but it feels fairly hackish. I wonder what kind of performance hit that incurs on the stack?
Soviut
@Soviut - Good question. I did a little testing. I'll update my answer with my informal, semi-unscientific results. One thing to note is that the accepted answer seems to fail completely if the `containerElement` doesn't match the selector in the `.find()`.
patrick dw
@Soviut - Disregard my comments about the selectors failing. I was making a bonehead mistake. Although I found a faster method than both answers. I'll post it at the top of my answer.
patrick dw
You should post it as a second answer to reduce clutter. That way both can be upvoted.
Soviut
Upvoted anyway for lots of good suggestions.
Soviut
@Soviut - Thanks. Did a little uncluttering. :)
patrick dw
+1  A: 

This one has better performance than my other answer, but you lose the chaining ability that your accepted answer offers.

    // Do a typical find.
var found = containerElement.find('[class^=something]');

    // Test the containerElement directly,
    //    and push it into the 'found' object if it matches.
if( containerElement.is('[class^=something]') ) found.push(containerElement);
patrick dw
I was trying to do it all in a single query due to how I'm processing 'each()' containerElement that I receive.
Soviut
@Soviut - Yeah, I think your selected answer is a good one.
patrick dw