Подтвердить что ты не робот

Использование querySelectorAll для извлечения прямых детей

Я могу это сделать:

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

То есть, я могу успешно получить все прямые дочерние элементы myDiv, у которых есть класс .foo.

Проблема в том, что мне нужно включить #myDiv в селектор, потому что я запускаю запрос в элементе myDiv (поэтому он явно избыточен).

Мне нужно оставить #myDiv выключенным, но селектор не является законным синтаксисом, так как он начинается с >.

Кто-нибудь знает, как написать селектор, который получает только прямые дочерние элементы элемента, на котором работает селектор?

4b9b3361

Ответ 1

Хороший вопрос. В то время, когда об этом спрашивали, универсально реализованного способа выполнения "корневых запросов комбинатора" (как их называл Джон Резиг) не существовало.

Теперь введен псевдокласс : scope. Он не поддерживается в [pre-Chrominum] версиях Edge или IE, но поддерживается Safari уже несколько лет. Используя это, ваш код может стать:

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

Обратите внимание, что в некоторых случаях вы также можете пропустить .querySelectorAll и использовать другие старые добрые функции DOM API. Например, вместо myDiv.querySelectorAll(":scope > *") вы можете просто написать myDiv.children, например.

В противном случае, если вы еще не можете положиться на :scope, я не могу придумать другой способ справиться с вашей ситуацией без добавления дополнительной настраиваемой логики фильтра (например, найти myDiv.getElementsByClassName("foo") чей .parentNode === myDiv), и, очевидно, не идеально, если вы пытаетесь поддерживать один путь кода, который на самом деле просто хочет взять произвольную строку селектора в качестве входных данных и список совпадений в качестве выходных! Но если, как и я, вы в конечном итоге задали этот вопрос просто потому, что застряли, думая, что "все, что у вас было, это молоток", не забывайте, что есть множество других инструментов, которые предлагает DOM.

Ответ 2

Кто-нибудь знает, как написать селектор, который получает только прямые дочерние элементы элемента, на котором работает селектор?

Правильный способ написать селектор, который "укоренен" в текущем элементе, это использовать :scope.

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

Однако поддержка браузера ограничена, и вам понадобится шайба, если вы хотите ее использовать. Я построил scopedQuerySelectorShim для этой цели.

Ответ 3

Вот гибкий метод, написанный в vanilla JS, который позволяет запускать запрос выбора CSS только для прямых дочерних элементов элемента:

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

если вы точно знаете, что элемент уникален (например, ваш случай с идентификатором):

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

Для более "глобального" решения: (используйте matchSelector 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 - ваш родительский элемент, а sel - ваш селектор. Может быть полностью использован как прототип.

Ответ 5

Я создал функцию для обработки этой ситуации, подумал, что хочу поделиться ею.

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

По сути дела, вы создаете случайную строку (функция randomString - это импортированный модуль npm, но вы можете сделать свой собственный.), затем используя эту случайную строку, чтобы гарантировать, что вы получите элемент, который вы ожидаете в селектор. Затем вы можете использовать > после этого.

Причина, по которой я не использую атрибут id, заключается в том, что атрибут id уже может использоваться, и я не хочу его переопределять.

Ответ 6

Следующее решение отличается от предложенного до сих пор и работает для меня.

Обоснование заключается в том, что вы сначала выбираете все соответствующие дочерние элементы, а затем отфильтровываете те, которые не являются прямыми детьми. Ребенок является прямым потомком, если у него нет соответствующего родителя с тем же селектором.

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

НТН!

Ответ 7

Ну, мы можем легко получить все прямые дочерние элементы элемента с помощью childNodes, и мы можем выбрать предков с определенным классом с помощью querySelectorAll, поэтому нетрудно представить, что мы могли бы создать новую функцию, которая получает оба и сравнивает два.

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

Примечание. Это вернет массив узлов, а не NodeList.

Использование

 document.getElementById("myDiv").queryDirectChildren(".foo");

Ответ 8

Я хотел бы добавить, что вы можете расширить совместимость : scope, просто назначив временный атрибут текущему узлу.

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

Ответ 9

Я просто делаю это, даже не пытаясь это сделать. Будет ли это работать?

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

Попробуй, возможно, это работает, может быть, и нет. Apolovies, но сейчас я не на компьютере, чтобы попробовать (отвечая с моего iPhone).

Ответ 10

Я бы пошел с

var myFoo = document.querySelectorAll("#myDiv > .foo");
var myDiv = myFoo.parentNode;