Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
242 views
in Technique[技术] by (71.8m points)

javascript - Skip recursion in jQuery.find() for a selector?

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.

Selection Path Illustration 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);
    });
} );
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

If you want to exclude element in you find, you can use a not filter. As for example, I've taken you function that exclude element and made it way shorter :

$.fn.findExclude = function( Selector, Mask,){
    return this.find(Selector).not(this.find(Mask).find(Selector))
}

Now, ill be honest with you, I did not fully understand what you want. But, when i took a look at your function, I saw what you were trying to do.

Anyway, take a look at this fiddle, the result is the same as your : http://jsfiddle.net/KX65p/8/


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...