TL;DR: How do I get an action like find(), but block traversal (not full stop, just skip) for a certain selector?
ANSWERS: $(Any).find(Selector).not( $(Any).find(Mask).find(Selector) )
There were many truly great answers, I wish I could some how distribute the bounty points more, maybe I should make some 50 pt bounties in response to some of these ;p I choose Karl-André Gagnon's because this answer managed to make findExclude unrequired in one, slightly long, line. While this uses three find calls and a heavy not filter, in most situations jQuery can use very fast implementation that skips traversal for most find()s.
Especially good answers are listed below:
falsarella: Good improvement on my solution, findExclude(), best in many situatoins
Zbyszek: A filter-based solution similar to falsarella's, also good on efficiency
Justin: A completely different, but manageable and functional solution to the underlaying issues
Each of these have their own unique merits and and are deserving of some mention.
I need to descend into an element fully and compare selectors, returning all matched selectors as an array, but skip descending into the tree when another selector is encountered.
Edit: replacing original code sample with some from my site
This is for a message forum which may have reply message-groups nested inside any message.
Notice, however, we cannot use the message or content classes because the script is also used for other components outside of the forum. Only InterfaceGroup
, Interface
and controls
classes are potentially useful - and preferably just Interface and controls.
Interact with the code and see it in JS Fiddle, thanks Dave A, here Click on the buttons while viewing a JavaScript console to see that the controls class is being bound to one extra time per level of .Interface nesting.
Visual A, Forum Layout Struture:
<li class="InterfaceGroup">
<ul class="Interface Message" data-role="MessagePost" >
<li class="instance"> ... condensed ... </li>
<li class="InterfaceGroup"> ... condensed ...</li>
</ul>
<ul class="Interface Message" data-role="MessagePost" >
<li class="instance"> ... condensed ... </li>
</ul>
<ul class="Interface Message" data-role="MessagePost" >
<li class="instance"> ... condensed ... </li>
<li class="InterfaceGroup"> ... condensed ...</li>
</ul>
</li>
Inside of each <li class="InterfaceGroup">
there could be any number of repetitions of the same structure (each group is a thread of messages) and/or deeper nesting such as..
<li class="InterfaceGroup">
<ul class="Interface Message" data-role="MessagePost" >
<li class="instance"> ... condensed ... </li>
<li class="InterfaceGroup">
<ul class="Interface Message" data-role="MessagePost" >
<li class="instance"> ... condensed ... </li>
<li class="InterfaceGroup"> ... condensed ...</li>
</ul>
</li>
</ul>
</li>
Inside of each <li class="instance"> ... </li>
there are arbitrary places decided by another team where class="controls"
may appear and an event listener should be bound. Though these contain messages, other components structure their markup arbitrarily but will always have .controls
inside of .Interface
, which are collected into an .InterfaceGroup
.A reduced-complexity version of the inner-content (for forum posts) is below for reference.
Visual B, Message Contents with controls class:
<ul class="Interface Message" data-role="MessagePost" >
<li class="instance">
<ul class="profile"> ...condensed, nothing clickable...</ul>
<ul class="contents">
<li class="heading"><h3>Hi there!</h3></li>
<li class="body"><article>TEST Message here</article></li>
<li class="vote controls">
<button class="up" data-role="VoteUp" ><i class="fa fa-caret-up"> </i><br/>1</button>
<button class="down" data-role="VoteDown" >0<br/><i class="fa fa-caret-down"> </i></button>
</li>
<li class="social controls">
<button class="reply-btn" data-role="ReplyButton" >Reply</button>
</li>
</ul>
</li>
<li class="InterfaceGroup" > <!-- NESTING OCCURRED -->
<ul class="Interface Message" data-role="MessagePost" >
<li class="instance">... condensed ... </li>
<li class="InterfaceGroup" >... condensed ... </li>
</ul>
</li>
</ul>
We can only bind to controls that are within an Interface class, instance
may or may not exist but Interface will. Events bubble to .controls
elements and have a reference to the .Interface
which holds them..
So I am trying to $('.Interface').each( bind to any .controls not inside a deeper .Interface )
That's the tricky part, because
.Interface .controls
will select the same.control
multiple times in the .each()- .not('.Interface .Interface .controls') cancels out controls in any deeper nesting
How can I do this using jQuery.find() or a similar jQuery method for this?
I have been considering that, perhaps, using children with a not selector could work and could be doing the same thing as find under the hood, but I'm not so sure that it actually is or wont cause horrible performance. Still, an answer recursing .children effectively is acceptable.
UPDATE: Originally I tried to use a psuedo-example for brevity, but hopefully seeing a forum structure will help clarify the issue since they're naturally nested structures. Below I'm also posting partial javascript for reference, line two of the init function is most important.
Reduced JavaScript partial:
var Interface=function()
{
$elf=this;
$elf.call=
{
init:function(Markup)
{
$elf.Interface = Markup;
$elf.Controls = $(Markup).find('.controls').not('.Interface .controls');
$elf.Controls.on('click mouseenter mouseleave', function(event){ $elf.call.events(event); });
return $elf;
},
events:function(e)
{
var classlist = e.target.className.split(/\s+/), c=0, L=0;
var role = $(e.target).data('role');
if(e.type == 'click')
{
CurrentControl=$(e.target).closest('[data-role]')[0];
role = $(CurrentControl).data('role');
switch(role)
{
case 'ReplyButton':console.log('Reply clicked'); break;
case 'VoteUp':console.log('Up vote clicked'); break;
case 'VoteDown':console.log('Down vote clicked'); break;
default: break;
}
}
}
}
};
$(document).ready( function()
{
$('.Interface').each(function(instance, Markup)
{
Markup.Interface=new Interface().call.init(Markup);
});
} );
$('.group').each(function() { var $element = $(this).find('.target'); });
– Henning$('.group').each(function() { var $element = $(this).find('.target').not('.group .target'); });
is about as close as I can get to putting the idea into shorter terms, but is also wrong. I've already tried iterating over them, but I need to DOM traverse into .group looking for .target (just like find does) but skip .group elements during the find. – Shammer$(document).on('click', '.controls', function(){ ... })
? – Prestodata-*
attributes to describe the intended handler and supporting json data to be sent, they're able to trigger AJAX requests (or js functions from within the context of a given semantic component) to the server without any further JS coding. I wanted all the.controls
grouped into an object attached to the closest.Interface
, in order to enforce efficient event bubbling and to prevent some ambiguities – Shammer