querySelector przeszukuje bezpośrednie elementy podrzędne

101

Mam funkcję podobną do jQuery:

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

Pytanie brzmi, jak mogę zrobić to samo z querySelector()?

Problem polega na tym, że >selektor querySelector()wymaga jawnego określenia elementu nadrzędnego. Czy jest jakieś obejście?

disfated
źródło
Dodałem odpowiedź, która może działać lepiej dla Ciebie, daj mi znać;)
csuwldcat

Odpowiedzi:

126

Chociaż nie jest to pełna odpowiedź, powinieneś mieć oko na W3C Selector API v.2, który jest już dostępny w większości przeglądarek, zarówno stacjonarnych, jak i mobilnych, z wyjątkiem IE (Edge wydaje się obsługiwać): zobacz pełną listę wsparcia .

function(elem) {
  return elem.querySelectorAll(':scope > someselector');
};
avetisk
źródło
13
To jest poprawna odpowiedź. 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
7
W rzeczywistości :scopejest nadal określony na drafts.csswg.org/selectors-4/#the-scope-pseudo . Jak dotąd tylko <style scoped>atrybut został usunięty; nie jest jasne, czy doprowadzi to również do :scopeusunięcia (ponieważ <style scoped>był to ważny przypadek użycia :scope).
John Mellor
1
Co za niespodzianka: tylko Edge tego nie obsługuje
:)
33

Nie możesz. Nie ma selektora, który symulowałby Twój punkt początkowy.

Sposób, w jaki jQuery to robi (bardziej ze względu na sposób, qsaktóry nie odpowiada ich preferencjom) polega na tym, że sprawdzają, czyelem ma identyfikator, a jeśli nie, tymczasowo dodają identyfikator, a następnie tworzą pełny ciąg selektora.

Zasadniczo zrobiłbyś:

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

Z pewnością nie jest to kod jQuery, ale z tego, co pamiętam, to w zasadzie to, co robią. Nie tylko w tej sytuacji, ale w każdej sytuacji, w której uruchamiasz selektor z kontekstu zagnieżdżonego elementu.

Kod źródłowy dla Sizzle

user113716
źródło
1
Poprawna w momencie pisania, ale zobacz odpowiedź @ avetisk, aby uzyskać zaktualizowaną metodę.
lazd
24

Kompletne: wypełnienie lunety

Jak wspomniał avetisk , API 2 Selectors używa pseudo-selektora. Aby to działało we wszystkich przeglądarkach (które obsługują ), oto polyfill:scope
querySelector

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

Stosowanie

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

Ze względów historycznych moje poprzednie rozwiązanie

Na podstawie wszystkich odpowiedzi

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

Stosowanie

elem.find('> a');
zdyskwalifikowanych
źródło
Date.now()nie jest obsługiwany przez starsze przeglądarki i może zostać zastąpiony przeznew Date().getTime()
Christophe
6

Jeśli chcesz w końcu znaleźć bezpośrednie dzieci (a nie np. > div > span), Możesz spróbować Element.matches () :

const elem = document.body
const elems = [...elem.children].filter(e => e.matches('b'))

console.log(elems)
<a>1</a>
<b>2</b>
<b>3</b>
<b>4</b>
<s>5</s>

tuomassalo
źródło
5

Jeśli znasz nazwę tagu elementu, którego szukasz, możesz użyć jej w selektorze, aby osiągnąć to, co chcesz.

Na przykład, jeśli masz a, <select>które ma <option>s i <optgroups>i chcesz tylko <option>s, które są jego bezpośrednimi dziećmi, a nie tymi w środku <optgoups>:

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

Tak więc, mając odniesienie do elementu select, możesz - co zaskakujące - pobrać jego bezpośrednie elementy potomne w następujący sposób:

selectElement.querySelectorAll('select > option')

Wygląda na to, że działa w Chrome, Safari i Firefox, ale nie testował w IE. = /

Vlad GURDIGA
źródło
9
Ostrzeżenie: ta odpowiedź w rzeczywistości nie ogranicza zakresu. Jeśli wzór jest powtarzany, głębszy poziom gniazda, nadal będzie wybrany, nawet jeśli nie jest to bezpośredni potomek. <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'); -> Wszystkie 4 elementy li zostaną wybrane!
Risord,
1
Nie daj Boże, <select>w tym samym rodzicu były dwa elementy.
Tomáš Zato - Przywróć Monikę
4

ROSZCZENIE

Osobiście wziąłbym odpowiedź od patryka dw i +1 jego odpowiedzi, moja odpowiedź jest taka, aby szukać alternatywnego rozwiązania. Myślę, że nie zasługuje na złą opinię.

Oto moja próba:

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

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

Liangliang Zheng
źródło
1
Jest z tym kilka problemów. @disfated chce, aby to działało querySelector, co oznacza, że :eq()nie można go użyć. Nawet gdyby tak było, twój selektor zwróciłby element, który ma :eq()się pojawić na stronie, a nie :eq()do indeksu jego elementu nadrzędnego (czyli tam, gdzie go otrzymujesz idx).
user113716
+1 na temat: eq () i querySelector. Powinienem dodać kontekst $ (elem) .parent ().
Liangliang Zheng
Niestety to też nie zadziała. Kiedy selektor jest uruchamiany z kontekstu rodzica, zaczyna się od najbardziej lewej strony podrzędnej i znajduje wszystkie pasujące elementy, bez względu na to, jak głęboko są one zagnieżdżone. Więc powiedzmy, że elemjest pod indeksem 4, ale jest poprzedni element siostrzany, który ma inny tagName, ale zagnieżdżony w nim element z dopasowaniem tagName, ten zagnieżdżony element zostanie uwzględniony w wynikach przed tym, na który celujesz, ponownie odrzucając indeks. Oto przykład
user113716
... w każdym razie to, co ostatecznie robisz, to bardziej złożona wersja kodu w pytaniu, która działa. $('> someselector', elem);; o)
user113716
Tak, ale trzeba było na stałe zakodować ten pojedynczy przyrost. Wymagałoby to wcześniejszej wiedzy o tym. Jeśli dodam kolejny podobny element, znowu jest uszkodzony. ; o) jsfiddle.net/Lgaw5/3
user113716
2

Poniżej przedstawiono uproszczoną, ogólną metodę uruchamiania dowolnego zapytania selektora CSS tylko dla bezpośrednich elementów potomnych - uwzględnia również zapytania łączone, takie jak "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
źródło
2

Istnieje biblioteka zależna od zapytania , która jest całkiem przydatnym zamiennikiem dla selektora zapytania . Wypełnia selektor dzieci '> *'i :scope(w tym IE8), a także normalizuje :rootselektor. Również zapewnia pewne szczególne względne pseudos jak :closest, :parent, :prev, :next, na wszelki wypadek.

dy_
źródło
1

To zadziałało dla mnie:

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

będziesz musiał przekazać @this keywork przed>, jeśli chcesz szukać najbliższych dzieci.

Davi
źródło
Wydaje się, że to ta sama, co obecnie akceptowana odpowiedź ... Przy okazji, jaki jest sens dziwnej składni „@this”? Dlaczego po prostu nie przetestować wyrażenia „>” za pomocą wyrażenia regularnego?
zdyskwalifikowany
0

sprawdź, czy element ma id, w przeciwnym razie dodaj losowy identyfikator i wykonaj wyszukiwanie na jego podstawie

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

zrobi to samo, co

$("> someselector",$(elem))
Zeeshan Anjum
źródło