24

Consider the following HTML:

<div class="foo" id="obj">
   I should be changed red
   <div class="bar" style="color:black;">
      I should not be changed red.
      <div class="foo">I should be changed red.</div>
   </div>
</div>

Given a DOM element obj and an expression, how do I go about selecting any children and possibly obj? I'm looking for something similar to "select descendants" but also including the parent, if it matches the expression.

var obj = $("#obj")[0];

//wrong, may include siblings of 'obj'
$(".foo", $(obj).parent()).css("color", "red");

//wrong -- excludes 'obj'
$(".foo", obj).css("color", "red");

//correct way, but it's annoying
var matches = $(".foo", obj);
if ($(obj).is(".foo")) matches = matches.add(obj);
matches.css("color", "red");

Is there a more elegant solution to this?

Keavon
  • 6,837
  • 9
  • 51
  • 79
Sam
  • 6,167
  • 6
  • 26
  • 24
  • 1
    +1: Great question. Just hit this situation today. Have taken Andrew's answer and your comment and added a trivial jQuery `findAndSelf` extension as you suggested for everyone to use. Cheers. – iCollect.it Ltd Jun 05 '14 at 11:03

8 Answers8

28

If I understand you correctly:

$(currentDiv).contents().addBack('.foo').css('color','red');

I renamed the "div" to "currentDiv" for clarity. This selects the current element and all of the elements it contains, then filters out the ones that do not have class foo and applies the style to the remainder, i.e., the ones that do have class foo.

EDIT A slight optimization

$(currentDiv).find('.foo').addBack('.foo').css('color','red');

EDIT

This answer has been updated to incorporate newer jQuery methods. It was originally

$(currentDiv).find('.foo').andSelf().filter('.foo').css('color','red');

which is still required for jQuery older than 1.8

tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • 1
    Ah, thank you... the "andSelf()" is what I was missing. Unfortunately this method is ineffecient... all of the children and found and then all are traversed to be filtered, rather than just a single pass. – Sam Dec 13 '08 at 04:07
  • 1
    This is still slightly less efficient than `addback` (JQ 1.8 onward) as this runs the `filter` a second time on the descendants even though they all already match. – iCollect.it Ltd Jun 05 '14 at 10:33
  • `Note: This function has been deprecated and is now an alias for .addBack(), which should be used with jQuery 1.8 and later` – dsdsdsdsd Sep 05 '14 at 07:00
9

jQuery 1.8 introduced .addBack(), which takes a selector, in favor of .andSelf().So tvanfosson's code becomes much more efficient as

$(currentDiv).find(".foo").addBack(".foo").css("color", "red");

If you didn't have that, I think that

$(currentDiv).find(".foo").add(currentDiv.filter(".foo")).css("color", "red");

would be pretty efficient, if not very pretty.

Andrew
  • 4,145
  • 2
  • 37
  • 42
  • 1
    Definitely preferred from jQuery 1.8. Have added your suggestion as a jQuery `findAndSelf` extension method below. Turned out very clean using `addBack`, *full credit to you*. Cheers :) – iCollect.it Ltd Jun 05 '14 at 11:01
2

Andrew's answer was so useful and the most efficient of all the answers (from jQuery 1.8 onward), so I did as Sam suggested and turned it into a trivial jQuery extension method for everyone to use:

Extension code:

jQuery.fn.findAndSelf = function (selector){
    return this.find(selector).addBack(selector);
};

use like this:

$(function(){
    $('#obj').findAndSelf('.foo').css('color', 'red');
});

JSFiddle: http://jsfiddle.net/BfEwK/

Full credit to Andrew for suggesting addBack() as the best option (as at this date). Have upvoted him accordingly and suggest everyone do the same.

Community
  • 1
  • 1
iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
1

A shorter way to select all descendant elements, including the parent element:

$('*', '#parent').addBack();

Which is the same as:

$('#parent').find('*').addBack();

$('*', '#parent').addBack().css('border', '1px solid #f00');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="parent">
  <span>Child</span>
  <span>Another child</span>
</div>

Of course, you can change the universal selector to the desired element.

$('.foo', '#parent').addBack();

or

$('#parent').find('.foo').addBack();
Josh Crozier
  • 233,099
  • 56
  • 391
  • 304
1

Barring a nicer solution, I've created a new function and use it instead of $:

var _$ = function(expr, parent){ 
   return $(parent).is(expr) ? $(expr, parent).add(parent) : $(expr, parent); 
}
Sam
  • 6,167
  • 6
  • 26
  • 24
  • 3
    I think I'll just use this and extend jQuery to make it "findAndSelf()" or something like that. – Sam Dec 13 '08 at 04:12
  • Have applied your `findAndSelf` suggestion (good name) to Andrew's answer instead and posted for all to cut & paste. – iCollect.it Ltd Jun 05 '14 at 10:57
0

If I am not mistaken could you not just do the following?

$("#obj").css("color", "red").find(".foo").css("color", "red");

Find intended object/element apply CSS, then find all objects/elements within said object/element with said selector and then apply the CSS to is as well?

iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
-1
 $('div.foo, div.foo > *').css('color','red');

The main idea is that you can separate different rules to match on by commas. Just like in css. As far as I know everything here is supoprted by jquery and can be "ORed" by comma-separating.

rz.
  • 19,861
  • 10
  • 54
  • 47
-1

Doesn't this do what you want (unless I misunderstand...)

$(document).ready(function(){
    $("div.foo").css("color", "red");
});
cLFlaVA
  • 1,468
  • 3
  • 13
  • 17
  • This will "work", *but only because they happened to all be `divs` in the example*. The question was how to include an initial element (if it matches) along with any descendants (that also match). What if there were more `.foo`s outside of `#obj`? They would also be included (which is not the desired behavior). – iCollect.it Ltd Jun 05 '14 at 10:27