jQuery .closest ()와 유사하지만 하위 항목을 순회합니까?


123

유사한 기능이 jQuery .closest()있지만 하위 항목을 탐색하고 가장 가까운 항목 만 반환 하는 기능 이 있습니까?

나는 .find()함수 가 있다는 것을 알고 있지만 가장 가까운 것이 아니라 가능한 모든 일치를 반환합니다.

편집하다:

다음은 가장 가까운 정의입니다 (적어도 나를 위해) :

처음에는 모든 자식이 순회 된 다음 각 개별 어린이가 순회됩니다.

아래 주어진 예에서 id='2'가장 가까운 .closest자손은id="find-my-closest-descendant"

<div id="find-my-closest-descendant">
    <div>
        <div class="closest" Id='1'></div>
    </div>
    <div class="closest" Id='2'></div>
</div>

JSfiddle 링크를 참조하십시오 .


4
어때 .find("filter here").eq(0)?
Shadow Wizard is Ear For You

@ShadowWizard가 깊이 우선 순회를 수행하는 경우 "가장 가까운"의 의미에 따라 결과 목록의 0 번째 요소가 "가장 가까운"요소가 아닐 수 있습니다.
Pointy

@Pointy는 :first필터 와 같지 않습니까?
그림자 마법사가 당신을 위해 귀입니다

아니요, 그렇게 생각하지 않습니다. 문제는 ": first"및 ".eq (0)"이 DOM 순서를 참조하지만 "가장 가까운"이 "가장 적은 DOM 노드 떨어져 있음"을 의미하는 경우 "손자"가됩니다. 선택자와 일치하는 나중 "자식"대신 첫 번째 자식이 반환됩니다. 당연히 이것이 OP가 무엇인지 100 % 확신하지 못합니다.
Pointy

1
당신을 위해 정확히하는 jQuery 플러그인이 있습니다 : plugins.jquery.com/closestDescendant
TLindig

답변:


44

의 정의에 closest따라 다음 플러그인을 작성했습니다.

(function($) {
    $.fn.closest_descendent = function(filter) {
        var $found = $(),
            $currentSet = this; // Current place
        while ($currentSet.length) {
            $found = $currentSet.filter(filter);
            if ($found.length) break;  // At least one match: break loop
            // Get all children of the current set
            $currentSet = $currentSet.children();
        }
        return $found.first(); // Return first match of the collection
    }    
})(jQuery);

3
jQuery .closest()함수 와 달리 이것은 호출 된 요소와도 일치합니다. 내 jsfiddle을 참조하십시오 . 그것을 변경하면 $currentSet = this.children(); // Current place대신 자식 jsfiddle로
allicarn

21
JQuery의 가장 가까운 ()도 현재 요소와 일치시킬 수 있습니다.
Dávid Horváth 2015 년

120

"가장 가까운"후손이 첫 번째 자식을 의미한다면 다음을 수행 할 수 있습니다.

$('#foo').find(':first');

또는:

$('#foo').children().first();

또는 특정 요소의 첫 번째 발생을 찾으려면 다음을 수행 할 수 있습니다.

$('#foo').find('.whatever').first();

또는:

$('#foo').find('.whatever:first');

하지만 "가장 가까운 후손"이 무엇을 의미하는지에 대한 확실한 정의가 필요합니다.

<div id="foo">
    <p>
        <span></span>
    </p>
    <span></span>
</div>

어느 <span>것이 $('#foo').closestDescendent('span')돌아 올까요?


1
자세한 답변에 대해 @ 999에게 감사드립니다. 사망에 대한 나의 기대는 먼저 모든 어린이가 통과 한 다음 각 개별 어린이입니다. 주어진 예에서 두 번째 범위가 반환됩니다
mamu

8
그래서 숨 처음이 아닌 깊이 우선
jessehouwing

1
훌륭한 대답입니다. $('#foo').find('.whatever').first();"폭 우선"인지 "깊이 우선" 인지 알고 싶습니다
Colin

1
이 솔루션의 한 가지 문제점은 jQuery가 모든 일치 기준을 찾은 다음 첫 번째 기준을 반환한다는 것입니다. Sizzle 엔진은 빠르지 만 불필요한 추가 검색을 나타냅니다. 검색을 단락시키고 첫 번째 일치 항목이 발견 된 후에 중지 할 방법이 없습니다.
icfantv

이 예제는 모두 너비 우선이 아닌 깊이 우선 후손을 선택하므로 원래 질문을 해결하는 데 사용할 수 없습니다. 내 테스트에서 그들은 모두 두 번째가 아닌 첫 번째 범위를 반환했습니다.
JrBaconCheez

18

선택기 find와 함께 사용할 수 있습니다 :first.

$('#parent').find('p:first');

위의 줄은 첫 번째를 찾을 것입니다 <p> 의 하위 항목에서 요소를#parent .


2
나는 이것이 OP가 원했던 것이라고 생각하며 플러그인이 필요하지 않습니다. 그러면 선택한 요소에 대해 선택한 요소의 하위 항목에서 첫 번째로 일치하는 요소가 선택됩니다. 따라서 원래 선택과 4 개의 일치 항목이있는 경우 find('whatever:first').
RustyToms 2014-06-11

3

이 접근법은 어떻습니까?

$('find-my-closest-descendant').find('> div');

이 "직계 자녀"선택기가 저에게 적합합니다.


OP는 구체적으로 자손을 가로 지르는 무언가를 요청하지만이 답변은 그렇지 않습니다. 이것은 좋은 대답 일 수 있지만이 특정 질문에는 적합하지 않습니다.
jumps4fun

@KjetilNordin 아마도 그것은 내 대답 이후 질문이 편집 되었기 때문일 것입니다. 더욱이 가장 가까운 후손의 정의는 적어도 나에게는 아직 명확하지 않습니다.
klawipo

나는 확실히 동의합니다. 가장 가까운 후손은 많은 것을 의미 할 수 있습니다. 이 경우 부모를 사용하면 더 쉽게 각 수준에서 요소에 대한 단일 부모가 항상 있습니다. 그리고 가능한 편집에 대해서도 맞습니다. 특히 이미 동일한 유형의 진술이 있었기 때문에 내 의견이 어떤 식 으로든 도움이되지 않았다는 것을 알았습니다.
jumps4fun

1

순수 JS 솔루션 (ES6 사용).

export function closestDescendant(root, selector) {
  const elements = [root];
  let e;
  do { e = elements.shift(); } while (!e.matches(selector) && elements.push(...e.children));
  return e.matches(selector) ? e : null;
}

다음 구조를 고려하십시오.

div == $ 0
├── div == $ 1
│ ├── div
│ ├── div.findme == $ 4
│ ├── div
│ └── div
├── div.findme == $ 2
│ ├── div
│ └── div
└── div == $ 3
    ├── div
    ├── div
    └── div
closestDescendant($0, '.findme') === $2;
closestDescendant($1, '.findme') === $4;
closestDescendant($2, '.findme') === $2;
closestDescendant($3, '.findme') === null;


이 솔루션은 작동하지만 내가 좋아하지 않기 때문에 대체 ES6 솔루션 ( stackoverflow.com/a/55144581/2441655 )을 게시했습니다 . 1) while부작용 이있는 표현식. 2) 한 .matches줄이 아닌 두 줄에 수표를 사용합니다 .
Venryx

1

누군가가 순수한 JS 솔루션 (jQuery 대신 ES6 사용)을 찾고있는 경우 다음을 사용합니다.

Element.prototype.QuerySelector_BreadthFirst = function(selector) {
    let currentLayerElements = [...this.childNodes];
    while (currentLayerElements.length) {
        let firstMatchInLayer = currentLayerElements.find(a=>a.matches && a.matches(selector));
        if (firstMatchInLayer) return firstMatchInLayer;
        currentLayerElements = currentLayerElements.reduce((acc, item)=>acc.concat([...item.childNodes]), []);
    }
    return null;
};

이봐, 당신의 대답을 내 관심에 가져 주셔서 감사합니다! 당신 말이 맞아, 내 자신을 돌아 보면 너무 영리하고 실제로 어색해 matches지려 는 것 같은 느낌이 든다 ( 두번 뛰는 것도 나를 꽤 짜증나게한다). +1 당신을 위해 :)
ccjmne

0

먼저 "가장 가까운"이 무엇을 의미하는지 정의해야한다고 생각합니다. 부모 링크 측면에서 가장 짧은 거리 인 기준과 일치하는 하위 노드를 의미하는 경우 ": first"또는 ".eq (0)"을 사용하는 것이 반드시 작동하는 것은 아닙니다.

<div id='start'>
  <div>
    <div>
      <span class='target'></span>
    </div>
  </div>
  <div>
    <span class='target'></span>
  </div>
</div>

이 예에서 두 번째 ".target" <span>요소는 "start"에 "closer"입니다.<div> . 부모 홉이 하나뿐이기 때문입니다. 이것이 "가장 가깝다"는 뜻이라면 필터 함수에서 최소 거리를 찾아야합니다. jQuery 선택기의 결과 목록은 항상 DOM 순서입니다.

아마도:

$.fn.closestDescendant = function(sel) {
  var rv = $();
  this.each(function() {
    var base = this, $base = $(base), $found = $base.find(sel);
    var dist = null, closest = null;
    $found.each(function() {
      var $parents = $(this).parents();
      for (var i = 0; i < $parents.length; ++i)
        if ($parents.get(i) === base) break;
      if (dist === null || i < dist) {
        dist = i;
        closest = this;
      }
    });
    rv.add(closest);
  });
  return rv;
};

그것은 결과 객체를 만드는 방식 때문에 일종의 해킹 플러그인이지만, 당신이 찾은 모든 일치하는 요소에서 가장 짧은 부모 경로를 찾아야한다는 생각입니다. 이 코드는 <검사로 인해 DOM 트리의 왼쪽 요소로 편향됩니다 . <=오른쪽으로 편향됩니다.


0

나는 이것을 요리했지만, 위치 선택자에 대한 구현은 matchesSelector아직 없습니다 (그 이상이 필요합니다 ).

데모 : http://jsfiddle.net/TL4Bq/3/

(function ($) {
    var matchesSelector = jQuery.find.matchesSelector;
    $.fn.closestDescendant = function (selector) {
        var queue, open, cur, ret = [];
        this.each(function () {
            queue = [this];
            open = [];
            while (queue.length) {
                cur = queue.shift();
                if (!cur || cur.nodeType !== 1) {
                    continue;
                }
                if (matchesSelector(cur, selector)) {
                    ret.push(cur);
                    return;
                }
                open.unshift.apply(open, $(cur).children().toArray());
                if (!queue.length) {
                    queue.unshift.apply(queue, open);
                    open = [];
                }
            }
        });
        ret = ret.length > 1 ? jQuery.unique(ret) : ret;
        return this.pushStack(ret, "closestDescendant", selector);
    };
})(jQuery);

하지만 버그가있을 수 있지만 많이 테스트하지는 않았습니다.


0

Rob W의 대답은 저에게 효과적이지 않았습니다. 나는 이것을 일한 것에 적용했습니다.

//closest_descendent plugin
$.fn.closest_descendent = function(filter) {
    var found = [];

    //go through every matched element that is a child of the target element
    $(this).find(filter).each(function(){
        //when a match is found, add it to the list
        found.push($(this));
    });

    return found[0]; // Return first match in the list
}

나를 위해이 보이는 정확히 같은 .find(filter).first()) 만 느린
마티아스 Samsel

네 말이 맞아요. 나는 아직 코딩에 익숙하지 않았을 때 이것을 썼습니다. 나는이 답변 삭제됩니다 생각
다니엘 TONON

0

선택자와 일치하는 경우 다음을 사용하여 대상 자체를 포함합니다.

    var jTarget = $("#doo");
    var sel = '.pou';
    var jDom = jTarget.find(sel).addBack(sel).first();

마크 업 :

<div id="doo" class="pou">
    poo
    <div class="foo">foo</div>
    <div class="pou">pooo</div>
</div>

0

오래된 주제 임에도 불구하고 가장 가까운 아이를 구현하는 것을 거부 할 수 없었습니다. 이동이 가장 적은 첫 번째 발견 된 후손을 전달합니다 (호흡 먼저). 하나는 재귀 적 (개인적으로 선호하는)이고 다른 하나는 jQquery 확장으로 재귀없이 할 일 목록을 사용합니다.

누군가가 혜택을 받기를 바랍니다.

참고 : 재귀는 스택 오버플로를 가져오고 다른 것을 개선했으며 이제는 이전 답변과 유사합니다.

jQuery.fn.extend( {

    closestChild_err : function( selector ) { // recursive, stack overflow when not found
        var found = this.children( selector ).first();
        if ( found.length == 0 ) {
            found = this.children().closestChild( selector ).first(); // check all children
        }
        return found;
    },

    closestChild : function( selector ) {
        var todo = this.children(); // start whith children, excluding this
        while ( todo.length > 0 ) {
            var found = todo.filter( selector );
            if ( found.length > 0 ) { // found closest: happy
                return found.first();
            } else {
                todo = todo.children();
            }
        }
        return $();
    },

});  

0

다음 플러그인은 n 번째로 가장 가까운 자손을 반환합니다.

$.fn.getNthClosestDescendants = function(n, type) {
  var closestMatches = [];
  var children = this.children();

  recursiveMatch(children);

  function recursiveMatch(children) {
    var matches = children.filter(type);

    if (
      matches.length &&
      closestMatches.length < n
    ) {
      var neededMatches = n - closestMatches.length;
      var matchesToAdd = matches.slice(0, neededMatches);
      matchesToAdd.each(function() {
        closestMatches.push(this);
      });
    }

    if (closestMatches.length < n) {
      var newChildren = children.children();
      recursiveMatch(newChildren);
    }
  }

  return closestMatches;
};

0

비슷한 솔루션을 찾고있었습니다 (가장 가까운 모든 자손을 원했습니다. 즉, 존재하는 수준에 관계없이 폭 우선 + 모든 일치 항목을 원했습니다). 여기에 내가 한 일이 있습니다.

var item = $('#find-my-closest-descendant');
item.find(".matching-descendant").filter(function () {
    var $this = $(this);
    return $this.parent().closest("#find-my-closest-descendant").is(item);
}).each(function () {
    // Do what you want here
});

이게 도움이 되길 바란다.


0

간단히 말하면,

$("#find-my-closest-descendant").siblings('.closest:first');


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.