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.