Verwenden von querySelectorAll zum Abrufen direkter untergeordneter Elemente

119

Ich kann das:

<div id="myDiv">
   <div class="foo"></div>
</div>
myDiv = getElementById("myDiv");
myDiv.querySelectorAll("#myDiv > .foo");

Das heißt, ich kann erfolgreich alle direkten untergeordneten myDivElemente des Elements mit Klasse abrufen .foo.

Das Problem ist, es stört mich, dass ich das #myDivin den Selektor aufnehmen muss, weil ich die Abfrage für das myDivElement ausführe (es ist also offensichtlich redundant).

Ich sollte in der Lage sein, das #myDivAus zu lassen, aber dann ist der Selektor keine legale Syntax, da er mit a beginnt >.

Weiß jemand, wie man einen Selektor schreibt, der nur die direkten untergeordneten Elemente des Elements erhält, auf dem der Selektor ausgeführt wird?

mattsh
quelle
Hat keine der Antworten das erreicht, was Sie brauchten? Bitte geben Sie Feedback oder wählen Sie eine Antwort.
Randy Hall
@mattsh - Ich habe eine bessere (IMO) Antwort für Ihre Bedürfnisse hinzugefügt, probieren Sie es aus!
csuwldcat

Antworten:

83

Gute Frage. Zu der Zeit, als es gefragt wurde, gab es keine universell implementierte Möglichkeit, "kombinatorisch verwurzelte Abfragen" (wie John Resig sie nannte ) durchzuführen .

Nun wurde die Pseudoklasse: scope eingeführt. Es wird in [Pre-Chrominum] -Versionen von Edge oder IE nicht unterstützt , wird jedoch bereits seit einigen Jahren von Safari unterstützt. Damit könnte Ihr Code werden:

let myDiv = getElementById("myDiv");
myDiv.querySelectorAll(":scope > .foo");

Beachten Sie, dass Sie in einigen Fällen auch .querySelectorAllandere gute, altmodische DOM-API-Funktionen überspringen und verwenden können. Zum Beispiel myDiv.querySelectorAll(":scope > *")könnten Sie stattdessen einfach schreiben myDiv.children.

Andernfalls, wenn Sie sich noch nicht darauf verlassen können :scope, kann ich mir keinen anderen Weg vorstellen, um mit Ihrer Situation umzugehen, ohne mehr benutzerdefinierte Filterlogik hinzuzufügen (z. B. myDiv.getElementsByClassName("foo")deren zu finden .parentNode === myDiv), und natürlich nicht ideal, wenn Sie versuchen, einen Codepfad wirklich zu unterstützen Ich möchte nur eine beliebige Auswahlzeichenfolge als Eingabe und eine Liste von Übereinstimmungen als Ausgabe verwenden! Aber wenn Sie wie ich diese Frage gestellt haben, nur weil Sie nicht weiter dachten: "Alles, was Sie hatten, war ein Hammer", vergessen Sie nicht, dass das DOM auch eine Vielzahl anderer Tools anbietet.

natevw
quelle
3
myDiv.getElementsByClassName("foo")ist nicht dasselbe wie myDiv.querySelectorAll("> .foo"), es ist eher so myDiv.querySelectorAll(".foo")(was übrigens tatsächlich funktioniert), dass es alle Nachkommen findet .foound nicht nur Kinder.
BoltClock
Oh, stört! Womit ich meine: du bist genau richtig. Ich habe meine Antwort aktualisiert, um klarer zu machen, dass sie im Fall des OP nicht sehr gut ist.
Natevw
Vielen Dank für den Link (der ihn definitiv zu beantworten scheint) und vielen Dank für das Hinzufügen der Terminologie "Kombinierte verwurzelte Abfragen".
Mattsh
1
Was John Resig "kombinatorgestützte Abfragen" nennt, nennt Selectors 4 sie jetzt relative Selektoren .
BoltClock
@ Andrew Scoped Stylesheets (dh <style scoped>) haben nichts mit dem :scopein dieser Antwort beschriebenen Pseudo-Selektor zu tun .
Natevw
124

Weiß jemand, wie man einen Selektor schreibt, der nur die direkten untergeordneten Elemente des Elements erhält, auf dem der Selektor ausgeführt wird?

Die richtige Methode zum Schreiben eines Selektors, der im aktuellen Element "verwurzelt" ist, ist die Verwendung :scope.

var myDiv = getElementById("myDiv");
var fooEls = myDiv.querySelectorAll(":scope > .foo");

Die Browserunterstützung ist jedoch begrenzt und Sie benötigen eine Unterlegscheibe, wenn Sie sie verwenden möchten. Zu diesem Zweck habe ich scopedQuerySelectorShim erstellt .

lazd
quelle
3
Erwähnenswert ist, dass die :scopeSpezifikation derzeit ein "Arbeitsentwurf" ist und sich daher ändern kann. Es ist wahrscheinlich, dass es immer noch so funktioniert, wenn es angenommen wird, aber ein bisschen früh zu sagen, dass dies meiner Meinung nach der "richtige Weg" ist, dies zu tun.
Zach Lysobey
2
Sieht aus wie es in der Zwischenzeit veraltet ist
Adrian Moisa
1
3 Jahre später und du rettest meinen Gluteus Maximus!
Mehrad
5
@Adrian Moisa :: Umfang wurde nirgendwo veraltet. Möglicherweise wird es mit dem Gültigkeitsbereichsattribut verwechselt.
BoltClock
6

Hier ist eine flexible Methode, die in Vanilla JS geschrieben ist und es Ihnen ermöglicht, eine CSS-Selektorabfrage nur über die direkten untergeordneten Elemente eines Elements auszuführen:

var count = 0;
function queryChildren(element, selector) {
  var id = element.id,
      guid = element.id = id || 'query_children_' + count++,
      attr = '#' + guid + ' > ',
      selector = attr + (selector + '').replace(',', ',' + attr, 'g');
  var result = element.parentNode.querySelectorAll(selector);
  if (!id) element.removeAttribute('id');
  return result;
}
csuwldcat
quelle
Ich würde vorschlagen, dass Sie den von Ihnen generierten IDs eine Art Zeitstempel oder Zähler hinzufügen. Es besteht eine Wahrscheinlichkeit ungleich Null (wenn auch winzig) Math.random().toString(36).substr(2, 10), dass derselbe Token mehr als einmal erzeugt wird.
Frédéric Hamidi
Ich bin mir nicht sicher, wie wahrscheinlich es ist, dass die Wahrscheinlichkeit von wiederholten Hashes winzig ist, die ID wird nur vorübergehend für einen Bruchteil einer Millisekunde angewendet, und jeder Betrüger müsste auch ein Kind desselben übergeordneten Knotens sein - im Grunde genommen der Die Chancen stehen gut. Unabhängig davon scheint das Hinzufügen eines Zählers in Ordnung zu sein.
csuwldcat
Nicht sicher, was Sie damit meinen any dupe would need to also be a child of the same parent node, idAttribute sind dokumentweit. Sie haben Recht, die Chancen sind immer noch vernachlässigbar, aber danke, dass Sie die Hauptstraße genommen und diesen Zähler hinzugefügt haben :)
Frédéric Hamidi
8
JavaScript wird nicht parallel ausgeführt, daher sind die Chancen tatsächlich Null.
WGH
1
@WGH Wow, ich kann es nicht glauben, ich bin absolut fassungslos, dass ich in einem so einfachen Punkt einen Hirnfurz machen würde (ich arbeite bei Mozilla und rezitiere diese Tatsache oft, ich muss mehr schlafen! LOL)
csuwldcat
5

Wenn Sie sicher sind, dass das Element eindeutig ist (z. B. Ihr Fall mit der ID):

myDiv.parentElement.querySelectorAll("#myDiv > .foo");

Für eine "globalere" Lösung: (Verwenden Sie einen Matchselector-Shim )

function getDirectChildren(elm, sel){
    var ret = [], i = 0, l = elm.childNodes.length;
    for (var i; i < l; ++i){
        if (elm.childNodes[i].matchesSelector(sel)){
            ret.push(elm.childNodes[i]);
        }
    }
    return ret;
}

Wo elmist Ihr übergeordnetes Element und selist Ihr Selektor. Könnte auch als Prototyp verwendet werden.

Randy Hall
quelle
@lazd das ist nicht Teil der Frage.
Randy Hall
Ihre Antwort ist keine "globale" Lösung. Es gibt spezielle Einschränkungen, die beachtet werden sollten, daher mein Kommentar.
Lazd
@lazd, der die gestellte Frage beantwortet. Warum also die Abstimmung? Oder haben Sie sich innerhalb von Sekunden nach der Abstimmung über die Antwort des Jahres kommentiert?
Randy Hall
Die Lösung verwendet matchesSelectorohne Präfix (die nicht einmal Arbeit in der neuesten Version von Chrome), verschmutzen sie den globalen Namensraum (ret wurde nicht erklärt), ist es nicht zurückgeben NodeList wie querySelectorMethods zu erwarten sind. Ich denke nicht, dass es eine gute Lösung ist, daher die Ablehnung.
Lazd
@lazd - Die ursprüngliche Frage verwendet eine nicht festgelegte Funktion und einen globalen Namespace. Es ist eine perfekte Lösung für die gestellte Frage. Wenn Sie nicht der Meinung sind, dass dies eine gute Lösung ist, verwenden Sie sie nicht und schlagen Sie keine Bearbeitung vor. Wenn es funktional falsch oder Spam ist, ziehen Sie eine Ablehnung in Betracht.
Randy Hall
2

Die folgende Lösung unterscheidet sich von den bisher vorgeschlagenen und funktioniert für mich.

Das Grundprinzip ist, dass Sie zuerst alle übereinstimmenden Kinder auswählen und dann diejenigen herausfiltern, die keine direkten Kinder sind. Ein Kind ist ein direktes Kind, wenn es kein übereinstimmendes Elternteil mit demselben Selektor hat.

function queryDirectChildren(parent, selector) {
    const nodes = parent.querySelectorAll(selector);
    const filteredNodes = [].slice.call(nodes).filter(n => 
        n.parentNode.closest(selector) === parent.closest(selector)
    );
    return filteredNodes;
}

HTH!

Melle
quelle
Ich mag dies wegen seiner Kürze und der Einfachheit des Ansatzes, obwohl es höchstwahrscheinlich nicht gut funktionieren würde, wenn es mit großen Bäumen und übermäßig gierigen Selektoren mit hoher Frequenz aufgerufen würde.
brennanyoung
1

Ich habe eine Funktion erstellt, um mit dieser Situation umzugehen, und dachte, ich würde sie teilen.

getDirectDecendent(elem, selector, all){
    const tempID = randomString(10) //use your randomString function here.
    elem.dataset.tempid = tempID;

    let returnObj;
    if(all)
        returnObj = elem.parentElement.querySelectorAll(`[data-tempid="${tempID}"] > ${selector}`);
    else
        returnObj = elem.parentElement.querySelector(`[data-tempid="${tempID}"] > ${selector}`);

    elem.dataset.tempid = '';
    return returnObj;
}

Im Wesentlichen generieren Sie eine Zufallszeichenfolge (die randomString-Funktion ist hier ein importiertes npm-Modul, aber Sie können Ihre eigene erstellen.). Verwenden Sie dann diese Zufallszeichenfolge, um sicherzustellen, dass Sie das erwartete Element im Selektor erhalten. Dann können Sie das >danach verwenden.

Der Grund, warum ich das ID-Attribut nicht verwende, ist, dass das ID-Attribut möglicherweise bereits verwendet wird und ich dies nicht überschreiben möchte.

cpi
quelle
0

Nun, wir können problemlos alle direkten untergeordneten Elemente eines Elements verwenden childNodesund Vorfahren mit einer bestimmten Klasse mit auswählen. querySelectorAllEs ist also nicht schwer vorstellbar, dass wir eine neue Funktion erstellen können, die beides erhält und beide vergleicht.

HTMLElement.prototype.queryDirectChildren = function(selector){
  var direct = [].slice.call(this.directNodes || []); // Cast to Array
  var queried = [].slice.call(this.querySelectorAll(selector) || []); // Cast to Array
  var both = [];
  // I choose to loop through the direct children because it is guaranteed to be smaller
  for(var i=0; i<direct.length; i++){
    if(queried.indexOf(direct[i])){
      both.push(direct[i]);
    }
  }
  return both;
}

Hinweis: Dies gibt ein Array von Knoten zurück, keine NodeList.

Verwendung

 document.getElementById("myDiv").queryDirectChildren(".foo");
Dustin Poissant
quelle
0

Ich möchte hinzufügen, dass Sie die Kompatibilität von : scope erweitern können, indem Sie dem aktuellen Knoten nur ein temporäres Attribut zuweisen.

let node = [...];
let result;

node.setAttribute("foo", "");
result = window.document.querySelectorAll("[foo] > .bar");
// And, of course, you can also use other combinators.
result = window.document.querySelectorAll("[foo] + .bar");
result = window.document.querySelectorAll("[foo] ~ .bar");
node.removeAttribute("foo");
Vasile Alexandru Peşte
quelle
-1

Ich wäre mitgegangen

var myFoo = document.querySelectorAll("#myDiv > .foo");
var myDiv = myFoo.parentNode;
Lee Chase
quelle
-2

Ich mache das nur, ohne es zu versuchen. Würde das funktionieren?

myDiv = getElementById("myDiv");
myDiv.querySelectorAll(this.id + " > .foo");

Probieren Sie es aus, vielleicht funktioniert es vielleicht nicht. Apolovies, aber ich bin jetzt nicht auf einem Computer, um es zu versuchen (Antwort von meinem iPhone).

Greeso
quelle
3
QSA ist auf das Element beschränkt, auf das es aufgerufen wird. Daher wird in myDiv nach einem anderen Element mit derselben ID wie myDiv gesucht, dann nach den untergeordneten Elementen mit .foo. Sie könnten so etwas wie `document.querySelectorAll ('#' + myDiv.id + '> .foo') tun;
wahrscheinlich bis