简体   繁体   中英

jQuery function similar to closest that will return elements outside of the parent chain

Is there any jQuery function similar to closest() that will return elements outside of the parent chain, traversing sideways? For example, I want to call a function foo() on the div source that would return the div target. I know I could navigate using parent() and siblings(), but I need something generic that would go as many levels as needed, up, sideways and down?

var allsources = $('.source');

allsources.click(function()){
  $(this).closest('.target').hide();
});

<div class="row">
  <div>
     <div class="target" ></div>
  </div>
  <div>
    <div>
      <div class="source"></div>
    </div>
  </div>
</div>

<div class="row">
  <div>
     <div class="target" ></div>
  </div>
  <div>
    <div>
      <div class="source"></div>
    </div>
  </div>
</div>

EDIT:

My definition of closest: you have an element source. Try to find it down. If find more than one, return one that is less node hoops down/next/prev. If not found, go one level up, and try to find again. Repeat until no parent.

If, by closest, you mean "travel up as little as possible, then anywhere downwards", then you can do

$("#source")
  .closest(":has(.target)")
  .find(".target:first")  //make sure we only select one element in case of a tie

In your case, it would be better to specify the common parent directly:

$(this)
  .closest(".row")
  .find(".target")        //there's no tie here, no need to arbitrate

This is a tricky one. As has been commented, how do you define closest in this context? Assuming you can decide on some rules; for example:

Traverse up: 3pt
Traverse down: 2pts
Move sideways: 1pts

And then consider the item with the lowest points to be "closest" then it would be easy enough to author a plugin, named something such as closestAll , which would do the recursive traversal of the whole dom tree to determine the closest item.

However, looking at your recent edit, one (of many!) right solutions to the problem stated is:

var allsources = $('.source');

allsources.click(function(){
  $(this).parents('.row').find('.target').hide();
});

Live example: http://jsfiddle.net/zCvJM/ (Source A only hides Target A, Same for B)

如果您确切知道dom的结构和嵌套级别,您是否考虑使用eq()方法

$(this).parents().eq(1).prev().children(".target")

I don't think there is a way to do this other than basically querying the whole DOM:

 
 
 
  
  $('#target')
 
  

Because if you want to go up and across (never mind down as well) then the target element isn't related to the child element. If you also want to check for the presence of the child element you will have to do that separately.

-Edit:

After reading your comment on wanting to find the closest element regardless of whether it is a parent, I think you will have to write a custom function to crawl back up the dom one node at a time. I have tested the following and it works:

Markup

<div id="parent">
    <div id="child1">   
        <div id="source"></div>
    </div>
    <div id="child2">
        <div class="target" rel="right"></div>
    </div>
    <div id="child3">
        <div>
            <div class="target" rel="wrong"></div>
        </div>
    </div>
</div>

Script

$(document).ready(function () {
    var tgt = findClosest($('#source'), '.target');
    if (tgt != undefined) {
        alert(tgt.attr('rel'));
    }
});


function findClosest(source, targetSel) {
    var crawledNodes = $();
    var target = null;

    // Go up
    source.parents().each(function () {
        console.log(crawledNodes.index($(this)));
        if (crawledNodes.index($(this)) == -1 && target == null) {
            crawledNodes.add($(this));
            target = findTarget($(this), targetSel);

            // Go across
            $(this).siblings().each(function () {
                console.log("Sibling");
                if (crawledNodes.index($(this)) == -1 && target == null) {
                    crawledNodes.add($(this));
                    target = findTarget($(this), targetSel);
                }
            });
        }
    });

    return target;
}

function findTarget(el, targetSel) {
    console.log(targetSel);
    var target = el.find(targetSel);
    if (target.size() > 0) {
        return target.eq(0);
    }
    else 
    {
        return null;
    }
}

If I understood the specification correctly you mean something like the function closest defined below:

var allsources = $(".source");

function closest($source,selector) {
    if($source == null) return  $([]);
    var $matchingChildren = $source.find(selector);
    if($matchingChildren.length != 0) return $($matchingChildren.get(0));
    else return closest($source.parent(), selector)
}

allsources.click(closest($(this),'.target').hide();});

You can see it working at http://jsfiddle.net/y2wJV/1/

Your definition requires that when choosing among matching children the function must return one that is less node hoops down/next/prev. This requirement has not been met, but this function is quite flexible and seems to do what you want to do in the case of the example you provided.

I found this code that is simple but does not solve the tie issue (returns the first)...

(function ($) {
  $.fn.findClosest = function (filter) {
    var $found = $(),
            $currentSet = this; // Current place

    while ($currentSet.length) {
        $found = $currentSet.find(filter);
        if ($found.length) break; // At least one match: break loop
        // Get all children of the current set
        $currentSet = $currentSet.parent();
    }
    return $found.first(); // Return first match of the collection
  };
})(jQuery);

I encountered a similar problem, i had a table i needed to find the next element which may be outside the current td, so i made a jquery function:

    $.fn.nextAllLevels = function(sel) {
    if ($(this).nextAll(sel).length != 0) {
        return $(this).nextAll(sel).eq(0);
    } else if ($(this).nextAll(':has(' +  sel + ')').length != 0) {
        return $(this).nextAll(':has(' +  sel + ')').find(sel).eq(0);
    } else {
        return $(this).parent().nextAllLevels(sel);
    }

So to use this you simply call

$('#current').nextAllLevels('.target');

To give you the element closest in the foward direction, regardsless of whether in is in the current parent or not.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM