Использование querySelectorAll для извлечения прямых детей
Я могу это сделать:
<div id="myDiv">
<div class="foo"></div>
</div>
myDiv = getElementById("myDiv");
myDiv.querySelectorAll("#myDiv > .foo");
То есть, я могу успешно получить все прямые дочерние элементы myDiv
, у которых есть класс .foo
.
Проблема в том, что мне нужно включить #myDiv
в селектор, потому что я запускаю запрос в элементе myDiv
(поэтому он явно избыточен).
Мне нужно оставить #myDiv
выключенным, но селектор не является законным синтаксисом, так как он начинается с >
.
Кто-нибудь знает, как написать селектор, который получает только прямые дочерние элементы элемента, на котором работает селектор?
Ответы
Ответ 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;