web-dev-qa-db-fra.com

Utilisation de querySelectorAll pour récupérer des enfants directs

Je suis capable de faire ceci:

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

Autrement dit, je peux récupérer avec succès tous les enfants directs de l'élément myDiv qui ont la classe .foo.

Le problème, c'est que cela me dérange de devoir inclure le #myDiv dans le sélecteur, car j'exécute la requête sur l'élément myDiv (il est donc manifestement redondant).

Je devrais pouvoir laisser le #myDiv désactivé, mais le sélecteur n’est pas une syntaxe légale car il commence par un >.

Est-ce que quelqu'un sait comment écrire un sélecteur qui obtient uniquement les enfants directs de l'élément sur lequel tourne le sélecteur?

76
mattsh

Bonne question, mais comme vous le voyez, il n’ya aucun moyen de faire des "requêtes enracinées dans le combinateur" comme John Resig les nomme .

Cependant, dans certains cas, vous pouvez simplement ignorer .querySelectorAll et utiliser d'autres fonctionnalités de l'ancienne API DOM à l'ancienne. Au lieu de la myDiv.querySelectorAll("> *") non prise en charge, vous pouvez simplement écrire myDiv.children, par exemple.

Malheureusement, je ne trouve pas de moyen de gérer votre situation sans ajouter de logique de filtrage personnalisée (par exemple, trouver myDiv.getElementsByClassName("foo") dont .parentNode === myDiv), et évidemment pas idéal si vous essayez de prendre en charge un chemin de code qui veut vraiment prendre un sélecteur quelconque chaîne en entrée et une liste de correspondances en sortie! Mais si, comme moi, vous avez fini par poser cette question simplement parce que vous vous êtes retrouvé coincé en pensant que "tout ce que vous aviez était un marteau", n'oubliez pas qu'il existe une variété d'outils autres proposés par DOM.

34
natevw

Est-ce que quelqu'un sait comment écrire un sélecteur qui obtient uniquement les enfants directs de l'élément sur lequel tourne le sélecteur?

La manière correcte d'écrire un sélecteur qui est "enraciné" dans l'élément en cours consiste à utiliser :scope .

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

Cependant,la prise en charge du navigateur est limitée et vous aurez besoin d'un shim si vous souhaitez l'utiliser. J'ai construit scopedQuerySelectorShim à cet effet.

93
lazd

Voici une méthode flexible, écrite en Vanilla JS, qui vous permet d'exécuter une requête de sélecteur CSS uniquement sur les enfants directs d'un élément:

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

si vous savez avec certitude que l'élément est unique (comme votre cas avec l'ID):

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

Pour une solution plus "globale": (utilisez un matchesSelector 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;
}

Elm est votre élément parent et sel est votre sélecteur. Pourrait totalement être utilisé comme un prototype aussi.

2
Randy Hall

La solution suivante est différente de celles proposées jusqu'à présent et fonctionne pour moi.

La raison est que vous sélectionnez d'abord tous les enfants correspondants, puis que vous filtrez ceux qui ne sont pas des enfants directs. Un enfant est un enfant direct s'il n'a pas de parent correspondant avec le même sélecteur.

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!

1
Melle

J'ai créé une fonction pour gérer cette situation, j'ai pensé la partager.

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

Essentiellement, vous générez une chaîne aléatoire (la fonction randomString est un module npm importé, mais vous pouvez créer le vôtre.), Puis vous utilisez cette chaîne aléatoire pour vous assurer que l'élément que vous attendez est sélectionné. Ensuite, vous êtes libre d’utiliser le > après cela. 

La raison pour laquelle je n'utilise pas l'attribut id est que l'attribut id peut déjà être utilisé et je ne veux pas le remplacer. 

1
cpi

Eh bien, nous pouvons facilement obtenir tous les enfants directs d'un élément en utilisant childNodes et nous pouvons sélectionner des ancêtres avec une classe spécifique avec querySelectorAll. Il n'est donc pas difficile d'imaginer que nous pourrions créer une nouvelle fonction qui obtiendra les deux et les comparera.

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

Note: Ceci retournera un tableau de noeuds, pas une liste de noeuds.

Utilisation

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

Je voudrais ajouter que vous pouvez étendre la compatibilité de: scope en attribuant simplement un attribut temporaire au nœud actuel.

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