querySelector recherche les enfants immédiats

96

J'ai une fonction de type jquery:

function(elem) {
    return $('> someselector', elem);
};

La question est de savoir comment puis-je faire la même chose avec querySelector()?

Le problème est que le >sélecteur dans querySelector()nécessite que le parent soit explicitement spécifié. Y a-t-il une solution de contournement?

défait
la source
J'ai ajouté une réponse qui pourrait mieux fonctionner pour vous, faites le moi savoir;)
csuwldcat

Réponses:

119

Bien que ce ne soit pas une réponse complète, vous devriez garder un œil sur l' API du sélecteur W3C v.2 qui est déjà disponible dans la plupart des navigateurs, à la fois de bureau et mobiles, sauf IE (Edge semble prendre en charge): voir la liste complète du support .

function(elem) {
  return elem.querySelectorAll(':scope > someselector');
};
avetisk
la source
12
C'est la bonne réponse. 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.
lazd
6
En fait, :scopeest toujours spécifié à drafts.csswg.org/selectors-4/#the-scope-pseudo . Jusqu'à présent, seul l' <style scoped>attribut a été supprimé; on ne sait pas si cela conduira également à :scopeêtre supprimé (car <style scoped>c'était un cas d'utilisation important pour :scope).
John Mellor
1
Quelle surprise: seul Edge ne supporte pas cela
:)
31

Vous ne pouvez pas. Il n'y a pas de sélecteur qui simulera votre point de départ.

La façon dont jQuery le fait (plus à cause d'une manière qui qsane leur convient pas), c'est qu'ils vérifient si elema un ID, et sinon, ils ajoutent temporairement un ID, puis créent une chaîne de sélection complète.

En gros, vous feriez:

var sel = '> someselector';
var hadId = true;
if( !elem.id ) {
    hadID = false;
    elem.id = 'some_unique_value';
}

sel = '#' + elem.id + sel;

var result = document.querySelectorAll( sel );

if( !hadId ) {
    elem.id = '';
}

Ce n'est certainement pas du code jQuery, mais d'après ce dont je me souviens, c'est essentiellement ce qu'ils font. Pas seulement dans cette situation, mais dans toute situation où vous exécutez un sélecteur à partir du contexte d'un élément imbriqué.

Code source pour Sizzle

utilisateur113716
la source
1
Correct au moment de la rédaction, mais voir la réponse de @ avetisk pour une méthode mise à jour.
lazd
23

Complet: portée polyfill

Comme avetisk l' a mentionné, Selectors API 2 utilise un :scopepseudo-sélecteur.
Pour que cela fonctionne dans tous les navigateurs (qui prennent en charge querySelector) voici le polyfill

(function(doc, proto) {
  try { // check if browser supports :scope natively
    doc.querySelector(':scope body');
  } catch (err) { // polyfill native methods if it doesn't
    ['querySelector', 'querySelectorAll'].forEach(function(method) {
      var nativ = proto[method];
      proto[method] = function(selectors) {
        if (/(^|,)\s*:scope/.test(selectors)) { // only if selectors contains :scope
          var id = this.id; // remember current element id
          this.id = 'ID_' + Date.now(); // assign new unique id
          selectors = selectors.replace(/((^|,)\s*):scope/g, '$1#' + this.id); // replace :scope with #ID
          var result = doc[method](selectors);
          this.id = id; // restore previous id
          return result;
        } else {
          return nativ.call(this, selectors); // use native code for other selectors
        }
      }
    });
  }
})(window.document, Element.prototype);

Usage

node.querySelector(':scope > someselector');
node.querySelectorAll(':scope > someselector');

Pour des raisons historiques, ma solution précédente

Basé sur toutes les réponses

// Caution! Prototype extending
Node.prototype.find = function(selector) {
    if (/(^\s*|,\s*)>/.test(selector)) {
        if (!this.id) {
            this.id = 'ID_' + new Date().getTime();
            var removeId = true;
        }
        selector = selector.replace(/(^\s*|,\s*)>/g, '$1#' + this.id + ' >');
        var result = document.querySelectorAll(selector);
        if (removeId) {
            this.id = null;
        }
        return result;
    } else {
        return this.querySelectorAll(selector);
    }
};

Usage

elem.find('> a');
défaits
la source
Date.now()n'est pas pris en charge par les navigateurs plus anciens, et pourrait être remplacé parnew Date().getTime()
Christophe
4

Si vous connaissez le nom de balise de l'élément que vous recherchez, vous pouvez l'utiliser dans le sélecteur pour obtenir ce que vous voulez.

Par exemple, si vous avez un <select>qui a <option>s et <optgroups>, et que vous ne voulez que les <option>s qui sont ses enfants immédiats, pas ceux à l'intérieur <optgoups>:

<select>
  <option>iPhone</option>
  <optgroup>
    <option>Nokia</option>
    <option>Blackberry</option>
  </optgroup>
</select>

Ainsi, ayant une référence à l'élément select, vous pouvez - étonnamment - obtenir ses enfants immédiats comme ceci:

selectElement.querySelectorAll('select > option')

Il semble fonctionner dans Chrome, Safari et Firefox, mais n'a pas été testé dans les IE. = /

Vlad GURDIGA
la source
8
Attention: cette réponse ne limite pas réellement la portée. Si le motif est répété au niveau d'imbrication plus profond, il sera toujours sélectionné même s'il ne s'agit pas d'enfants directs. <ul id="start"> <li>A</li> <li> <ul> <li>b</li> <li>c</li> </ul> </li> </ul> var result = document.getElementById('start').querySelectorAll('ul>li'); -> Les 4 éléments li seront sélectionnés!
Risord
1
Dieu nous en préserve, il y avait deux <select>éléments dans le même parent.
Tomáš Zato - Réintégrer Monica
4

Si vous voulez finalement trouver des enfants directs (et pas par exemple > div > span), vous pouvez essayer Element.matches () :

const elems = Array.from(elem.children).filter(e => e.matches('.my-class'))
Tuomassalo
la source
2

PRÉTENDRE

Personnellement, je prendrais la réponse de patrick dw, et +1 sa réponse, ma réponse est de chercher une solution alternative. Je ne pense pas que cela mérite un vote défavorable.

Voici ma tentative:

function q(elem){
    var nodes = elem.querySelectorAll('someSeletor');
    console.log(nodes);
    for(var i = 0; i < nodes.length; i++){
        if(nodes[i].parentNode === elem) return nodes[i];
    }
}

voir http://jsfiddle.net/Lgaw5/8/

Liangliang Zheng
la source
1
Quelques problèmes avec ça. @disfated veut qu'il fonctionne avec querySelector, ce qui signifie qu'il :eq()ne peut pas être utilisé. Même s'il le pouvait, votre sélecteur renverrait l'élément qui est :eq()à son apparence sur la page, pas :eq()à l'index de son parent (où vous obtenez le idx).
user113716
+1 à propos de: eq () & querySelector. Et je devrais ajouter le contexte $ (elem) .parent ().
Liangliang Zheng
Malheureusement, cela ne fonctionnera pas non plus. Lorsque le sélecteur s'exécute à partir du contexte du parent, il commence par l'enfant le plus à gauche et trouve tous les éléments correspondants, quelle que soit la profondeur de leur imbrication, le continue. Donc disons que le elemest à l'index 4, mais il y a un frère précédent qui a un autre tagName, mais il a imbriqué à l'intérieur un élément avec la correspondance tagName, cet élément imbriqué sera inclus dans les résultats avant celui que vous ciblez, encore une fois l'index. Voici un exemple
user113716
... de toute façon, ce que vous faites en fin de compte est une version plus complexe du code de la question, qui fonctionne. $('> someselector', elem);; o)
user113716
Oui, mais vous aviez besoin de coder en dur ce seul incrément. Cela exigerait une connaissance préalable de celui-ci. Si j'ajoute un autre élément similaire, il est à nouveau cassé. ; o) jsfiddle.net/Lgaw5/3
user113716
1

Cela a fonctionné pour moi:

Node.prototype.search = function(selector)
{
    if (selector.indexOf('@this') != -1)
    {
        if (!this.id)
            this.id = "ID" + new Date().getTime(); 
        while (selector.indexOf('@this') != -1)
            selector = selector.replace('@this', '#' + this.id);
        return document.querySelectorAll(selector);
    } else 
        return this.querySelectorAll(selector);
};

vous devrez passer le @this keywork avant le> lorsque vous voulez rechercher des enfants immédiats.

Davi
la source
Semble la même chose que la réponse actuellement acceptée ... Btw, quel est l'intérêt de la syntaxe étrange "@this"? Pourquoi ne pas simplement tester ">" avec regexp?
défait
1

Ce qui suit est une méthode générique simplifiée pour exécuter une requête de sélecteur CSS uniquement sur des enfants directs - elle tient également compte des requêtes combinées, telles que "foo[bar], baz.boo":

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


*** Example Use ***

queryChildren(someElement, '.foo, .bar[xyz="123"]');
csuwldcat
la source
1

Il existe une bibliothèque relative à la requête , qui remplace assez facilement le sélecteur de requête . Il polyfills les sélecteurs enfants '> *'et :scope(inc. IE8), ainsi que normalise le :rootsélecteur. En outre , il fournit des pseudos spéciaux relatifs comme :closest, :parent, :prev, :next, juste au cas où.

dy_
la source
0

vérifier si l'élément a un identifiant sinon ajouter un identifiant aléatoire et effectuer une recherche en fonction de celui-ci

function(elem) {
      if(!elem.id)
          elem.id = Math.random().toString(36).substr(2, 10);
      return elem.querySelectorAll(elem.id + ' > someselector');
    };

fera la même chose que

$("> someselector",$(elem))
Zeeshan Anjum
la source