最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - jQuery function similar to closest that will return elements outside of the parent chain - Stack Overflow

programmeradmin4浏览0评论

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.

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.

Share Improve this question edited Nov 30, 2012 at 10:26 tie asked Nov 30, 2012 at 9:03 tietie 5436 silver badges14 bronze badges 8
  • 4 Up, sideways and down? Thats, uhm... the whole dom tree – Jamiec Commented Nov 30, 2012 at 9:05
  • Exactly, you have an ID, id is unique (right?) so you just use the id selector – Jamiec Commented Nov 30, 2012 at 9:05
  • Would nextAll() or prevAll() be useful here? – Nope Commented Nov 30, 2012 at 9:06
  • 3 @tie, what you mean by closest is not clear. There are two axes and four directions to take into account (ancestor and sibling chains). For instance, what if there are two target elements, one two ranks higher in the ancestor chain and the other two ranks across in the sibling chain? Which one is closest? – Frédéric Hamidi Commented Nov 30, 2012 at 9:11
  • 1 Looking at your edit, why on earth are you avoiding the obvious .parents('.row').find('.target')? – Jamiec Commented Nov 30, 2012 at 9:21
 |  Show 3 more ments

7 Answers 7

Reset to default 7

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 mon parent directly:

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

This is a tricky one. As has been mented, 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/zCvJM/ (Source A only hides Target A, Same for B)

If you know exactly the structure of the dom and level of nesting, have you consider to use the eq() method

$(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 ment 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/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.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论