Ähnlich wie jQuery .closest (), aber Nachkommen durchlaufen?

123

Gibt es eine ähnliche Funktion wie das jQuery .closest()Durchqueren von Nachkommen und das Zurückgeben nur der nächsten?

Ich weiß, dass es eine .find()Funktion gibt, aber sie gibt alle möglichen Übereinstimmungen zurück, nicht die nächsten.

Bearbeiten:

Hier ist die Definition von am nächsten (zumindest für mich) :

Zuerst werden alle Kinder durchquert, dann werden die einzelnen Kinder durchquert.

In dem unten angegebenen Beispiel sind die id='2'nächsten .closestNachkommen vonid="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>

Bitte beachten Sie den JSfiddle-Link .

mamu
quelle
4
Was ist mit .find("filter here").eq(0)?
Schatten-Assistent ist Ohr für Sie
@ShadowWizard gut, wenn es eine Tiefenüberquerung durchführt, ist das nullte Element in der Ergebnisliste möglicherweise nicht das "nächstgelegene", je nachdem, was "am nächsten" bedeutet.
Pointy
@Pointy ist es nicht dasselbe wie :firstFilter?
Schatten-Assistent ist Ohr für Sie
Nein, ich glaube nicht - das Problem ist, dass ": first" und ".eq (0)" sich auf die DOM-Reihenfolge beziehen, aber wenn "am nächsten" "wenige DOM-Knoten entfernt" bedeutet, dann ein "Enkel" des Das erste Kind wird anstelle eines späteren "Kindes" zurückgegeben, das dem Selektor entspricht. Ich bin mir nicht 100% sicher, worum es beim OP natürlich geht.
Pointy
1
Es gibt ein jQuery-Plugin, was genau das für Sie tun kann: plugins.jquery.com/closestDescendant
TLindig

Antworten:

44

Gemäß Ihrer Definition von closesthabe ich das folgende Plugin geschrieben:

(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);
Rob W.
quelle
3
Im Gegensatz zur jQuery- .closest()Funktion entspricht diese auch dem Element, für das sie aufgerufen wurde. Siehe meine jsfiddle . Wenn Sie $currentSet = this.children(); // Current placees ändern, beginnen Sie mit den Kindern stattdessen jsfiddle
allicarn
21
JQuerys next () kann auch mit dem aktuellen Element abgeglichen werden.
Dávid Horváth
120

Wenn Sie mit "nächstgelegenem" Nachkommen das erste Kind meinen, können Sie Folgendes tun:

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

Oder:

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

Um nach dem ersten Auftreten eines bestimmten Elements zu suchen, können Sie Folgendes tun:

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

Oder:

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

Wirklich, wir brauchen eine solide Definition dessen, was "nächster Nachkomme" bedeutet.

Z.B

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

Welches <span>würde $('#foo').closestDescendent('span')zurückkehren?

James
quelle
1
danke @ 999 für die ausführliche Antwort, meine Erwartung an Verstorbene ist, dass zuerst alle Kinder durchquert werden, dann jedes einzelne Kind. In dem Beispiel, das Sie gegeben haben, würde die zweite Spanne zurückgegeben
mamu
8
Also Atem zuerst, nicht Tiefe zuerst
Jessehouwing
1
Hervorragende Antwort. Es würde mich interessieren, ob $('#foo').find('.whatever').first();"Breite zuerst" oder "Tiefe zuerst" ist
Colin
1
Das einzige Problem bei dieser Lösung ist, dass jQuery ALLE übereinstimmenden Kriterien findet und dann das erste zurückgibt. Während die Sizzle-Engine schnell ist, bedeutet dies unnötige zusätzliche Suche. Es gibt keine Möglichkeit, die Suche kurzzuschließen und anzuhalten, nachdem das erste Spiel gefunden wurde.
icfantv
In diesen Beispielen werden alle Nachkommen mit der Tiefe zuerst und nicht die Breite zuerst ausgewählt, sodass diese nicht zur Lösung der ursprünglichen Frage verwendet werden können. Bei meinen Tests gaben alle die erste Spanne zurück, nicht die zweite.
JrBaconCheez
18

Sie können findmit dem :firstSelektor verwenden:

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

In der obigen Zeile finden Sie das erste <p>Element in den Nachkommen von #parent.

FishBasketGordo
quelle
2
Ich denke, das ist es, was das OP wollte, kein Plugin erforderlich. Dadurch wird für jedes ausgewählte Element das erste übereinstimmende Element in den Nachkommen des ausgewählten Elements ausgewählt. Wenn Sie also 4 Übereinstimmungen mit Ihrer ursprünglichen Auswahl haben, können Sie mit der 4 4 Übereinstimmungen erzielen find('whatever:first').
RustyToms
3

Was ist mit diesem Ansatz?

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

Dieser "Direktkind" -Selektor funktioniert für mich.

klawipo
quelle
Das OP bittet ausdrücklich um etwas, das Nachkommen durchquert, was diese Antwort nicht tut. Dies könnte eine gute Antwort sein, aber nicht für diese spezielle Frage.
jumps4fun
@KjetilNordin Vielleicht liegt das daran, dass die Frage seit meiner Antwort bearbeitet wurde. Darüber hinaus ist die Definition des nächsten Nachkommen zumindest für mich immer noch nicht so klar.
klawipo
Ich stimme definitiv zu. Der nächste Nachkomme könnte so viele Dinge bedeuten. In diesem Fall ist es für Eltern einfacher, wenn Elemente auf jeder Ebene immer nur übergeordnet sind. Und Sie haben auch Recht mit möglichen Änderungen. Ich sehe jetzt, dass mein Kommentar in keiner Weise hilfreich war, zumal es bereits eine Aussage des gleichen Typs gab.
jumps4fun
1

Reine JS-Lösung (mit 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;
}

Beispiel

Betrachtet man die folgende Struktur:

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;

ccjmne
quelle
Diese Lösung funktioniert, aber ich habe hier eine alternative ES6-Lösung veröffentlicht ( stackoverflow.com/a/55144581/2441655 ), da mir Folgendes nicht gefällt: 1) Der whileAusdruck hat Nebenwirkungen. 2) Verwenden Sie den .matchesScheck auf zwei Zeilen anstatt nur auf einer.
Venryx
1

Für den Fall, dass jemand nach einer reinen JS-Lösung sucht (mit ES6 anstelle von jQuery), verwende ich Folgendes:

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;
};
Venryx
quelle
Hey, danke, dass du mich auf deine Antwort aufmerksam gemacht hast! Du hast Recht, matcheswenn ich auf meine zurückblicke, fühlt es sich so an, als würde es versuchen, zu klug und tatsächlich umständlich zu sein ( zweimal zu laufen ärgert mich auch ziemlich). +1 für Sie :)
ccjmne
0

Ich denke, dass man zuerst definieren muss, was "am nächsten" bedeutet. Wenn Sie meinen, dass der Nachkommenknoten Ihren Kriterien entspricht und in Bezug auf die elterlichen Verbindungen die kürzeste Entfernung entfernt ist, funktioniert die Verwendung von ": first" oder ".eq (0)" nicht unbedingt:

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

In diesem Beispiel ist das zweite ".target" <span>-Element "näher" am "Start" <div>, da es nur einen elterlichen Sprung entfernt ist. Wenn Sie das mit "am nächsten" meinen, müssen Sie den Mindestabstand in einer Filterfunktion ermitteln. Die Ergebnisliste eines jQuery-Selektors befindet sich immer in DOM-Reihenfolge.

Vielleicht:

$.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;
};

Das ist eine Art Hack-Plugin, weil es das Ergebnisobjekt erstellt, aber die Idee ist, dass Sie aus allen übereinstimmenden Elementen, die Sie finden, den kürzesten elterlichen Pfad finden müssen. Dieser Code wird aufgrund der <Prüfung in Richtung der Elemente im DOM-Baum nach links verschoben. <=würde nach rechts voreingenommen sein.

Spitze
quelle
0

Ich habe mir das ausgedacht, noch keine Implementierung für Positionsselektoren (sie brauchen mehr als nur matchesSelector):

Demo: 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);

Es gibt wahrscheinlich einige Fehler, die nicht sehr oft getestet wurden.

Esailija
quelle
0

Rob Ws Antwort hat bei mir nicht ganz funktioniert. Ich habe es daran angepasst, was funktioniert hat.

//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
}
Daniel Tonon
quelle
Für mich sieht das genau so aus .find(filter).first(), nur langsamer;)
Matthias Samsel
Ja, du hast recht. Ich habe das geschrieben, als ich noch ziemlich neu im Codieren war. Ich denke, ich werde diese Antwort löschen
Daniel Tonon
0

Ich würde Folgendes verwenden, um das Ziel selbst einzuschließen, wenn es mit dem Selektor übereinstimmt:

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

Markup:

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

Obwohl es ein altes Thema ist, konnte ich nicht widerstehen, mein engstes Kind zu implementieren. Liefert den ersten gefundenen Nachkommen mit der geringsten Reise (Atem zuerst). Einer ist rekursiv (persönlicher Favorit), der andere verwendet eine Aufgabenliste, also ohne Rekursion als jQquery-Erweiterungen.

Hoffe, jemand profitiert.

Hinweis: Die Rekursive wird gestapelt, und ich habe die andere verbessert, die jetzt der vorherigen Antwort ähnelt.

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 $();
    },

});  
Eelco
quelle
0

Das folgende Plugin gibt die n-ten nächsten Nachkommen zurück.

$.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;
};
cole
quelle
0

Ich suchte nach einer ähnlichen Lösung (ich wollte alle engsten Nachkommen, dh zuerst die Breite + alle Übereinstimmungen, unabhängig davon, welches Level es gibt). Am Ende habe ich Folgendes getan:

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

Ich hoffe das hilft.

Sharique Abdullah
quelle
0

Sie können einfach sagen,

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