Utilizzo di querySelectorAll per recuperare i figli diretti


119

Sono in grado di farlo:

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

Cioè, posso recuperare con successo tutti i figli diretti myDivdell'elemento che hanno classe .foo.

Il problema è che mi dà fastidio dover includere il #myDivnel selettore, perché sto eseguendo la query myDivsull'elemento (quindi è ovviamente ridondante).

Dovrei essere in grado di lasciare #myDivdisattivato, ma poi il selettore non è una sintassi legale poiché inizia con a >.

Qualcuno sa come scrivere un selettore che ottiene solo i figli diretti dell'elemento su cui è in esecuzione il selettore?



Nessuna delle risposte ha soddisfatto ciò di cui avevi bisogno? Fornisci un feedback o seleziona una risposta.
Randy Hall

@ mattsh - Ho aggiunto una risposta migliore (IMO) per la tua esigenza, dai un'occhiata!
csuwldcat

Risposte:


85

Buona domanda. Al momento in cui è stato chiesto, non esisteva un modo universalmente implementato per eseguire "query radicate nel combinatore" (come le chiamava John Resig ).

Ora è stata introdotta la pseudo-classe : scope . Non è supportato sulle versioni [pre-Chrominum] di Edge o IE, ma è supportato da Safari già da alcuni anni. Usandolo, il tuo codice potrebbe diventare:

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

Tieni presente che in alcuni casi puoi anche saltare .querySelectorAlle utilizzare altre buone funzionalità dell'API DOM vecchio stile. Ad esempio, invece di myDiv.querySelectorAll(":scope > *")te potresti semplicemente scrivere myDiv.children, per esempio.

Altrimenti, se non puoi ancora fare affidamento :scope, non riesco a pensare a un altro modo per gestire la tua situazione senza aggiungere più logica di filtro personalizzata (ad es. Trova di myDiv.getElementsByClassName("foo")chi .parentNode === myDiv), e ovviamente non è l'ideale se stai cercando di supportare un percorso di codice che davvero vuole solo prendere una stringa di selettore arbitrario come input e un elenco di corrispondenze come output! Ma se come me hai finito per fare questa domanda semplicemente perché sei rimasto bloccato a pensare "tutto quello che avevi era un martello", non dimenticare che ci sono anche una varietà di altri strumenti offerti dal DOM.


3
myDiv.getElementsByClassName("foo")non è lo stesso di myDiv.querySelectorAll("> .foo"), è più simile a myDiv.querySelectorAll(".foo")(che in realtà funziona tra l'altro) in quanto trova tutti i discendenti .fooanziché solo i bambini.
BoltClock

Oh, scoccia! Con questo intendo: hai esattamente ragione. Ho aggiornato la mia risposta per rendere più chiaro che non è molto buona nel caso dell'OP.
natevw

grazie per il link (che sembra rispondere in modo definitivo), e grazie per aver aggiunto la terminologia "query rooted combinator".
mattsh

1
Quelle che John Resig chiama "query radicate nel combinatore", Selectors 4 ora le chiama selettori relativi .
BoltClock

@Andrew Scoped stylesheets (ie <style scoped>) non hanno nulla a che fare con lo :scopepseudo-selettore descritto in questa risposta.
natevw

124

Qualcuno sa come scrivere un selettore che ottiene solo i figli diretti dell'elemento su cui è in esecuzione il selettore?

Il modo corretto per scrivere un selettore "radicato" nell'elemento corrente è quello di utilizzare :scope.

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

Tuttavia, il supporto del browser è limitato e avrai bisogno di uno shim se vuoi usarlo. Ho creato scopedQuerySelectorShim per questo scopo.


3
Vale la pena ricordare che la :scopespecifica è attualmente una "bozza di lavoro" e quindi soggetta a modifiche. È probabile che funzionerà ancora in questo modo se / quando verrà adottato, ma è un po 'presto per dire che questo è il "modo corretto" di farlo secondo me.
Zach Lysobey

2
Sembra che sia stato deprecato nel frattempo
Adrian Moisa

1
3 anni dopo e tu salvi il mio grande gluteo!
Mehrad

5
@Adrian Moisa:: l'ambito non è stato deprecato da nessuna parte. Potresti confonderlo con l'attributo scoped.
BoltClock

6

Ecco un metodo flessibile, scritto in vanilla JS, che ti consente di eseguire una query del selettore CSS solo sui figli diretti di un elemento:

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

Ti suggerirei di aggiungere una sorta di timestamp o contatore agli ID che generi. C'è una probabilità diversa da zero (anche se minuscola) Math.random().toString(36).substr(2, 10)di produrre lo stesso token più di una volta.
Frédéric Hamidi

Non sono sicuro di quanto sia probabile che le probabilità di ripetizione degli hash siano minuscole, l'ID viene applicato solo temporaneamente per una frazione di millisecondo e qualsiasi duplicato dovrebbe essere anche figlio dello stesso nodo genitore - in pratica, il le possibilità sono astronomiche. Indipendentemente da ciò, l'aggiunta di un contatore sembra a posto.
csuwldcat

Non sono sicuro di cosa intendi any dupe would need to also be a child of the same parent node, gli idattributi sono a livello di documento. Hai ragione, le probabilità sono ancora abbastanza trascurabili, ma grazie per aver preso la strada maestra e aver aggiunto quel contatore :)
Frédéric Hamidi

8
JavaScript non viene eseguito in parallelo, quindi le possibilità sono infatti zero.
WGH

1
@ WGH Wow, non posso crederci, sono assolutamente sbalordito dal fatto che scoreggerei il cervello su un punto così semplice (lavoro a Mozilla e
recito

5

se sai per certo che l'elemento è unico (come il tuo caso con l'ID):

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

Per una soluzione più "globale": (usa uno shim matchSelector )

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

dov'è il elmtuo elemento genitore ed selè il tuo selettore. Potrebbe anche essere usato totalmente come prototipo.


@lazd che non fa parte della domanda.
Randy Hall

La tua risposta non è una soluzione "globale". Ha limitazioni specifiche che dovrebbero essere notate, da qui il mio commento.
lazd

@lazd che risponde alla domanda posta. Allora perché il voto negativo? O ti è capitato di commentare pochi secondi dopo il voto negativo sulla risposta di un anno?
Randy Hall

La soluzione utilizza matchesSelectorunprefixed (che non funziona nemmeno nell'ultima versione di Chrome), inquina lo spazio dei nomi globale (ret non è stato dichiarato), non restituisce un NodeList come ci si aspetta querySelectorMethods. Non credo sia una buona soluzione, da qui il voto negativo.
lazd

@lazd - La domanda originale utilizza una funzione non prefissata e uno spazio dei nomi globale. È una soluzione perfetta per la domanda data. Se pensi che non sia una buona soluzione, non usarla o suggerisci una modifica. Se è funzionalmente errato o spam, prendi in considerazione un voto negativo.
Randy Hall

2

La seguente soluzione è diversa da quelle proposte finora e per me funziona.

La logica è che si selezionano prima tutti i figli corrispondenti, quindi si filtrano quelli che non sono figli diretti. Un figlio è un figlio diretto se non ha un genitore corrispondente con lo stesso selettore.

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!


Mi piace per la sua brevità e la semplicità dell'approccio, anche se molto probabilmente non funzionerebbe bene se chiamato ad alta frequenza con grandi alberi e selettori eccessivamente avidi.
brennanyoung

1

Ho creato una funzione per gestire questa situazione, ho pensato di condividerla.

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

In sostanza, ciò che stai facendo è generare una stringa casuale (la funzione randomString qui è un modulo npm importato, ma puoi crearne uno tuo), quindi utilizzare quella stringa casuale per garantire di ottenere l'elemento che ti aspetti nel selettore. Allora sei libero di usare il >dopo.

Il motivo per cui non utilizzo l'attributo id è che l'attributo id potrebbe già essere utilizzato e non voglio sovrascriverlo.


0

Bene, possiamo facilmente ottenere tutti i figli diretti di un elemento usando childNodese possiamo selezionare gli antenati con una classe specifica con querySelectorAll, quindi non è difficile immaginare di poter creare una nuova funzione che ottiene entrambi e confronta i due.

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

Nota: questo restituirà un array di nodi, non un NodeList.

uso

 document.getElementById("myDiv").queryDirectChildren(".foo");

0

Vorrei aggiungere che è possibile estendere la compatibilità di : scope semplicemente assegnando un attributo temporaneo al nodo corrente.

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

-1

Sarei andato con

var myFoo = document.querySelectorAll("#myDiv > .foo");
var myDiv = myFoo.parentNode;

-2

Lo sto facendo senza nemmeno provarlo. Funzionerebbe?

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

Provalo, forse funziona, forse no. Apolovies, ma ora non sono su un computer per provarlo (risponde dal mio iPhone).


3
QSA è limitato all'elemento su cui viene chiamato, quindi questo cercherà un altro elemento all'interno di myDiv con lo stesso ID di myDiv, quindi i suoi figli con .foo. Potresti fare qualcosa come `document.querySelectorAll ('#' + myDiv.id + '> .foo');
probabilmente fino al
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.