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はEar For You

@ShadowWizardが深さ優先のトラバーサルを行う場合、結果リストの0番目の要素は、「最も近い」という意味によっては「最も近い」とは限りません。
先のとがった

@Pointyは:firstフィルターと同じではありませんか?
シャドウウィザードはあなたのための耳である

いいえ、私はそうは思いません-「:first」と「.eq(0)」がDOMの順序を参照するという問題ですが、「最も近い」が「最少数のDOMノード」を意味する場合、セレクタに一致する後の「子」ではなく、最初の子が返されます。もちろん、それがOPの目的であるかどうかは100%わかりません。
先のとがっ

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を参照してください。それに変更することで、 jsfiddleの$currentSet = this.children(); // Current place代わりにその子から始まります
allicarn

21
jQueryのnearest()は、現在の要素と照合することもできます。
デヴィッド・Horvathの

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を感謝します。私の最初のすべての子供がトラバースされ、次に各個人の子供がトラバースされると、私は故意に期待しています。指定した例では、2番目のスパンが返されます
mamu

8
したがって、深さ優先ではなく、呼吸優先
jessehouwing 2014年

1
すばらしい答えです。$('#foo').find('.whatever').first();「幅優先」か「深さ優先」かを知りたい
コリン

1
このソリューションの1つの問題は、jQueryがすべての一致基準を見つけて、最初の基準を返すことです。Sizzleエンジンは高速ですが、これは不要な余分な検索を意味します。検索を短絡して、最初の一致が見つかった後に停止する方法はありません。
icfantv 2014年

これらの例はすべて、幅優先ではなく深さ優先の子孫を選択するため、これらを元の質問の解決に使用することはできません。私のテストでは、すべてが2番目ではなく最初のスパンを返しました。
JrBaconCheez

18

あなたは使うことができfind:first、セレクタ:

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

上記の行は<p>、の子孫の最初の要素を見つけます#parent


2
これはOPが望んだものであり、プラグインは必要ありません。これにより、選択した要素ごとに、選択した要素の子孫で最初に一致する要素が選択されます。したがって、元の選択と4つの一致がある場合、を使用して4つの一致になる可能性がありfind('whatever:first')ます。
RustyToms 2014年

3

このアプローチはどうですか?

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

この「直接の子」セレクターは私にとってはうまくいきます。


OPは、子孫を横断するものを具体的に要求しますが、この答えはそうではありません。これは良い答えかもしれませんが、この特定の質問には適していません。
jumps4fun

@KjetilNordinたぶんそれは私の回答以来質問が編集されたためです。さらに、少なくとも私にとって、最も近い子孫の定義はまだそれほど明確ではありません。
クラウィポ

私は間違いなく同意します。最も近い子孫は非常に多くのことを意味する可能性があります。この場合、各レベルで常に要素への単一の親が存在するため、親の方が簡単です。そして、あなたは可能な編集についても正しいです。私のコメントは、特に同じ種類のステートメントが既にあったので、特に役に立たなかったことがわかります。
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)を投稿しましたwhile。2).matches1行ではなく2行のチェックを使用します。
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;
};

ねえ、私の注意をあなたの答えに持ってきてくれてありがとう!あなたは正しい、私自身を振り返ると、それはあまりにも賢くて実際にぎこちないようにしようとしているように感じます(matches2回実行することも、私にかなりいらいらさせます)。+1 for you :)
ccjmne

0

最初に、「最も近い」の意味を定義する必要があると思います。親リンクに関して最も短い距離である基準に一致する子孫ノードを意味する場合、「:first」または「.eq(0)」の使用は必ずしも機能しません。

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

その例では、2番目の「.target」<span>要素は「start」<div>に「より近い」ので、これは親から1ホップだけ離れているためです。それが「最も近い」という意味であれば、フィルター関数で最小距離を見つける必要があります。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

ロブ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()に見えますが、遅くなるだけです;)
Matthias Samsel

うん、そうだね。これは、コーディングがまだ初めてのときに書きました。私はこの答えを削除すると思います
Daniel 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

これは古いトピックですが、closestChildの実装に抵抗できませんでした。移動が最も少ない最初の子孫を配信します(最初に息を吐く)。1つは再帰的(個人的なお気に入り)であり、もう1つはToDoリストを使用しているため、jQquery拡張機能として再帰的ではありません。

誰かが利益を期待しています。

注:再帰的にスタックオーバーフローが発生し、もう1つは改善されました。以前の答えと同じようになりました。

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
});

これがお役に立てば幸いです。




弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.