views:

568

answers:

6

Given this example:

<img class="a" />
<img />
<img class="a" />
<img class="a" id="active" />
<img class="a" />
<img class="a" />
<img />
<img class="a" />

(I've just used img tags as an example, that's not what it is in my code)

Using jQuery, how would you select the img tags with class "a" that are adjacent to #active (the middle four, in this example)?

You could do it fairly easily by looping over all the following and preceding elements, stopping when the filter condition fails, but I was wondering if jQuery could it natively?

+2  A: 

I believe looping is your best bet. But you could try, each active, and then move before and after until the condition breaks, which if the set is large enough would be faster.

Xenph Yan
A: 

The tilde (~) is the siblings selector:

$('#active ~ img.a').hide();
Prestaul
A: 

@Prestaul

$('#active ~ img.a')

would only select the following siblings, and would include the non-consecutive siblings too. Docs: http://docs.jquery.com/Selectors/siblings#prevsiblings

nickf
A: 

This is another way to do it, though the sibling selector answer is pretty cool:

var next = $('#active').next('.a');
var prev = $('#active').prev('.a');

Edit: I re-read your requirements and this isn't quite what you want. You could use nextAll and prevAll, but those, too, would not stop at the IMGs without the class name.

Andrew Hedges
+1  A: 

Here's what I came up with in the end.

// here's our active element.
var $active = $('#active');

// here is the filter we'll be testing against.
var filter = "img.a";

// $all will be the final jQuery object with all the consecutively matched elements.
// start it out by populating it with the current object.
var $all = $active;

for ($curr = $active.prev(filter); $curr.length > 0; $curr = $curr.prev(filter)) {
    $all = $all.add($curr);
}
for ($curr = $td.next(filter); $curr.length > 0; $curr = $curr.next(filter)) {
    $all = $all.add($curr);
}

For a follow up question, I could see how this could easily be generalised by making it into a function which takes two arguments: an initial element, and a filter string - can anyone point me in the right direction to find out how to extend the jQuery object to add such a function?


Edit: I've since found that the each() function would do this rather well for some purposes. In my own case it doesn't work as cleanly, since I want a single jQuery object for all those elements, but here's how you could use each for a different purpose (hiding consecutive ".a" elements, in this example:)

$('#active')
    .nextAll()
    .each(hideConsecutive)
    .end()
    .prevAll()
    .each(hideConsecutive)
;
function hideConsecutive(index, element) {
    var $e = $(element);
    if (!$e.is(".a")) {
        return false;    // this stops the each function.
    } else {
        $e.hide('slow');
    }
}

--

Edit: I've put this together into a plugin now. Take a look at http://plugins.jquery.com/project/Adjacent if you're interested.

nickf
+1  A: 

The below code will add two new functions, nextConsecutive() and prevConsecutive(). They should do what you want.

$.each( ['prev', 'next'], function(unusedIndex, name) { $.fn[ name + 'Consecutive' ] = function(matchExpr) {

 var $all = 
  (name == 'prev')
    ? $(this).prevAll()
    : $(this).nextAll();
 if (!matchExpr)
  return $all;

 var $notMatch = $($all).not(matchExpr).filter(':first');
 if ($all.index($notMatch) != -1)
  return $allConsecutive = $all.slice(0, $all.index($notMatch));

 return $all;
};

});