Używanie querySelectorAll do pobierania bezpośrednich elementów podrzędnych

119

Potrafię to zrobić:

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

Oznacza to, że mogę pomyślnie pobrać wszystkie bezpośrednie elementy potomne myDivelementu, który ma klasę .foo.

Problem w tym, że przeszkadza mi to, że muszę #myDivw selektorze zawrzeć , bo odpytuję na myDivelement (więc jest oczywiście zbędne).

Powinienem móc zostawić #myDivwyłączone, ale wtedy selektor nie jest legalną składnią, ponieważ zaczyna się od >.

Czy ktoś wie, jak napisać selektor, który pobiera tylko bezpośrednie dzieci elementu, na którym działa selektor?

mattsh
źródło
Czy żadna z odpowiedzi nie spełniła Twoich oczekiwań? Prześlij opinię lub wybierz odpowiedź.
Randy Hall
@mattsh - dodałem lepszą (IMO) odpowiedź na Twoje potrzeby, sprawdź to!
csuwldcat

Odpowiedzi:

85

Dobre pytanie. W czasie, gdy o to zapytano, uniwersalnie zaimplementowany sposób wykonywania „zapytań zakorzenionych w kombinatorze” (jak je nazywał John Resig ) nie istniał.

Teraz pseudoklasa : scope została wprowadzona. Nie jest obsługiwany w wersjach Edge ani IE [sprzed Chrominum], ale już od kilku lat jest obsługiwany przez Safari. Używając tego, twój kod może stać się:

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

Zauważ, że w niektórych przypadkach możesz także pominąć .querySelectorAlli użyć innych dobrych, staromodnych funkcji DOM API. Na przykład zamiast myDiv.querySelectorAll(":scope > *")ciebie mógłbyś myDiv.childrenna przykład po prostu napisać .

W przeciwnym razie, jeśli nie możesz jeszcze polegać :scope, nie mogę wymyślić innego sposobu radzenia sobie z twoją sytuacją bez dodawania większej logiki niestandardowego filtru (np. Znajdź myDiv.getElementsByClassName("foo")kogo .parentNode === myDiv) i oczywiście nie jest idealny, jeśli próbujesz obsługiwać jedną ścieżkę kodu, która naprawdę chce po prostu pobrać dowolny łańcuch selektora jako wejście i listę dopasowań jako wyjście! Ale jeśli tak jak ja zadałeś to pytanie po prostu dlatego, że utknąłeś, myśląc „wszystko, co miałeś to młotek”, nie zapominaj, że istnieje wiele innych narzędzi, które oferuje również DOM.

natevw
źródło
3
myDiv.getElementsByClassName("foo")to nie to samo co myDiv.querySelectorAll("> .foo"), bardziej przypomina myDiv.querySelectorAll(".foo")(co faktycznie działa) w tym, że znajduje wszystkie potomne .foos, a nie tylko dzieci.
BoltClock
Och, kłopocz się! Przez co mam na myśli: masz dokładnie rację. Zaktualizowałem swoją odpowiedź, aby była bardziej zrozumiała, że ​​w przypadku OP nie jest ona zbyt dobra.
natevw
dzięki za link (który zdaje się odpowiadać na nie definitywnie) i dziękuję za dodanie terminologii „zapytania zakorzenione w kombinatorze”.
mattsh
1
To, co John Resig nazywa „zapytaniami zakorzenionymi w kombinatorze”, Selectors 4 nazywa je teraz selektorami względnymi .
BoltClock
@Andrew Scoped stylesheets (ie <style scoped>) nie mają nic wspólnego z :scopepseudo-selektorem opisanym w tej odpowiedzi.
natevw,
124

Czy ktoś wie, jak napisać selektor, który pobiera tylko bezpośrednie dzieci elementu, na którym działa selektor?

Prawidłowym sposobem napisania selektora, który jest "zakorzeniony" w bieżącym elemencie, jest użycie :scope.

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

Jednak obsługa przeglądarki jest ograniczona i będziesz potrzebować podkładki, jeśli chcesz z niej korzystać. W tym celu zbudowałem scopedQuerySelectorShim .

lazd
źródło
3
Warto wspomnieć, że :scopespecyfikacja jest obecnie „wersją roboczą” i dlatego może ulec zmianie. Prawdopodobnie nadal będzie działać w ten sposób, jeśli / kiedy zostanie przyjęty, ale trochę za wcześnie, aby powiedzieć, że moim zdaniem jest to „właściwy sposób”.
Zach Lysobey
2
Wygląda na to, że został w międzyczasie wycofany
Adrian Moisa
1
3 lata później i ratujesz mój gluteus maximus!
Mehrad
5
@Adrian Moisa:: zakres nie został nigdzie wycofany. Być może mylisz się z atrybutem zakresu.
BoltClock
6

Oto elastyczna metoda napisana w waniliowym języku JS, która umożliwia uruchomienie zapytania selektora CSS tylko dla bezpośrednich elementów potomnych elementu:

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
źródło
Sugerowałbym dodanie jakiegoś znacznika czasu lub licznika do generowanych identyfikatorów. Istnieje niezerowe (aczkolwiek niewielkie) prawdopodobieństwo Math.random().toString(36).substr(2, 10)wyprodukowania tego samego tokena więcej niż raz.
Frédéric Hamidi
Nie jestem pewien, na ile prawdopodobne jest, że biorąc pod uwagę prawdopodobieństwo powtórzenia hashowania, jest ono znikome, identyfikator jest stosowany tylko tymczasowo przez ułamek milisekundy, a każdy duplikat musiałby również być dzieckiem tego samego węzła nadrzędnego - w zasadzie szanse są astronomiczne. Niezależnie od tego dodanie licznika wydaje się w porządku.
csuwldcat
Nie wiem, co masz na myśli mówiąc any dupe would need to also be a child of the same parent node, idatrybuty są szeroko dokumentu. Masz rację, szanse są nadal znikome, ale dziękuję za wybranie dobrej drogi i dodanie tego licznika :)
Frédéric Hamidi
8
JavaScript nie jest wykonywany równolegle, więc szanse są w rzeczywistości zerowe.
WGH,
1
@WGH Wow, nie mogę w to uwierzyć, jestem absolutnie oszołomiony, że bym pierdnął mózg w tak prostym punkcie (pracuję w Mozilli i często recytuję ten fakt, muszę się wyspać! LOL)
csuwldcat
5

jeśli wiesz na pewno, że element jest unikalny (na przykład Twoja sprawa z identyfikatorem):

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

Aby uzyskać bardziej „globalne” rozwiązanie: (użyj podkładki 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;
}

gdzie elmjest twój element nadrzędny i seljest twoim selektorem. Może być również całkowicie wykorzystany jako prototyp.

Randy Hall
źródło
@lazd to nie jest część pytania.
Randy Hall
Twoja odpowiedź nie jest rozwiązaniem „globalnym”. Ma określone ograniczenia, na które należy zwrócić uwagę, stąd mój komentarz.
lazd
@lazd, który odpowiada na zadane pytanie. Więc dlaczego głosowanie przeciw? Czy po prostu zdarzyło ci się skomentować w ciągu kilku sekund od głosowania negatywnego na roczną odpowiedź?
Randy Hall,
Rozwiązanie używa matchesSelectornieprefiksowanego (co nie działa nawet w najnowszej wersji Chrome), zanieczyszcza globalną przestrzeń nazw (ret nie został zadeklarowany), nie zwraca NodeList, jak oczekuje się od querySelectorMethods. Nie sądzę, żeby to było dobre rozwiązanie, stąd głosy przeciw.
lazd
@lazd - Oryginalne pytanie używa funkcji bez prefiksu i globalnej przestrzeni nazw. To bardzo dobre obejście zadanego pytania. Jeśli uważasz, że to nie jest dobre rozwiązanie, nie używaj go ani nie sugeruj zmiany. Jeśli jest to niepoprawne funkcjonalnie lub spam, rozważ głos przeciw.
Randy Hall
2

Poniższe rozwiązanie różni się od dotychczas proponowanych i działa u mnie.

Powodem jest to, że najpierw wybierasz wszystkie pasujące dzieci, a następnie odfiltrowujesz te, które nie są bezpośrednimi dziećmi. Dziecko jest bezpośrednim dzieckiem, jeśli nie ma pasującego rodzica z tym samym selektorem.

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
źródło
Podoba mi się to za zwięzłość i prostotę podejścia, chociaż najprawdopodobniej nie działałby dobrze, gdyby był wywoływany z dużą częstotliwością z dużymi drzewami i nadmiernie chciwymi selektorami.
brennanyoung
1

Stworzyłem funkcję, która poradzi sobie z tą sytuacją, pomyślałem, że się nią podzielę.

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

Zasadniczo to, co robisz, to generowanie losowego ciągu (funkcja randomString jest tutaj zaimportowanym modułem npm, ale możesz stworzyć własny), a następnie używając tego losowego ciągu, aby zagwarantować, że otrzymasz element, którego oczekujesz w selektorze. Następnie możesz swobodnie korzystać z >późniejszego.

Powodem, dla którego nie używam atrybutu id, jest to, że atrybut id może już być używany i nie chcę go zastępować.

cpi
źródło
0

Cóż, możemy łatwo uzyskać wszystkie bezpośrednie elementy potomne elementu, używając childNodesi możemy wybrać przodków z określoną klasą querySelectorAll, więc nietrudno sobie wyobrazić, że moglibyśmy stworzyć nową funkcję, która pobiera oba i porównuje je.

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

Uwaga: to zwróci tablicę węzłów, a nie listę węzłów.

Stosowanie

 document.getElementById("myDiv").queryDirectChildren(".foo");
Dustin Poissant
źródło
0

Chciałbym dodać, że można rozszerzyć kompatybilność : scope, po prostu przypisując atrybut tymczasowy do bieżącego węzła.

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
źródło
-1

Poszedłbym z

var myFoo = document.querySelectorAll("#myDiv > .foo");
var myDiv = myFoo.parentNode;
Lee Chase
źródło
-2

Po prostu robię to, nawet nie próbując. Czy to zadziała?

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

Spróbuj, może to działa, a może nie. Apolovies, ale nie jestem teraz na komputerze, aby to wypróbować (odpowiadając z mojego iPhone'a).

Greeso
źródło
3
QSA jest ograniczone do elementu, do którego jest wywoływane, więc szuka innego elementu w myDiv z tym samym identyfikatorem co myDiv, a następnie jego elementów podrzędnych z .foo. Możesz zrobić coś takiego jak `document.querySelectorAll ('#' + myDiv.id + '> .foo');
prawdopodobnie do