Как использовать селектор CSS, который получает все совпадающие элементы, кроме тех, которые находятся внутри указанного c дочернего элемента? - PullRequest
0 голосов
/ 24 января 2020

Предположим структуру HTML, как показано:

<div id="container">
     <div class="A">
         <div id="excludedElement">
             <p>
                 <span class="MyClass">1</span>
                 <span class="MyClass">2</span>
                 <span class="MyClass">3</span>
             </p>
         </div>
     </div>
     <div class="B">
         <p>
             <span class="MyClass">4</span>
             <span class="MyClass">5</span>
             <span class="MyClass">6</span>
         </p>
     </div>
</div>

Я хочу, чтобы все элементы внутри div «container» имели класс «MyClass», за исключением элементов внутри div «selectedElement». В этом случае результат содержит только промежутки 4, 5 и 6.

Мое текущее решение состоит в том, чтобы сначала получить все элементы с помощью «MyClass», а затем получить все элементы внутри excludeElement с «MyClass». Для каждого элемента в первом списке мы проверяем, есть ли он во втором, и пропускаем его, если это так. Это время O (n ^ 2), поэтому я бы хотел этого избежать. Psuedocode для справки:

const allElements = container.querySelectorAll('.MyClass');
const excludedElements = container.querySelectorAll('#excludedElement .MyClass');
var result = [];

for (const element in allElements)
{
    if (!excludedElements.Contains(element))
    {
        result.Add(element);
    }
}

Есть ли способ создать селектор CSS в querySelectorAll (), который может извлечь этот конкретный набор элементов?

Один из способов - временно удалить исключенный элемент из дерева, запрос для «MyClass», затем замените excludeElement, но я хочу избежать изменения DOM.

Ответы [ 4 ]

2 голосов
/ 24 января 2020

Если структура предсказуема и уже известна:

container.querySelectorAll('div:not(#excludedElement) > p .MyClass');

Если структура не известна, и вы можете добавлять классы для того, чтобы избегать O (n ^ 2):

const excludes = [...container.querySelectorAll('#excludedElement .MyClass')];
excludes.forEach(element => element.classList.add('excluded'));

const filteredMyClass = [...container.querySelectorAll('.MyClass:not(.excluded)')];
1 голос
/ 24 января 2020

Вы можете выбрать всех .MyClass потомков, затем .filter коллекции по тому, есть ли у текущего элемента, который повторяется, предок #excludedElement с .closest:

const classes = [...container.querySelectorAll('.MyClass')]
  .filter(span => !span.closest('#excludedElement'));
for (const span of classes) {
  span.style.backgroundColor = 'yellow';
}
<div id="container">
     <div class="A">
         <div id="excludedElement">
             <p>
                 <span class="MyClass">1</span>
                 <span class="MyClass">2</span>
                 <span class="MyClass">3</span>
             </p>
         </div>
     </div>
     <div class="B">
         <p>
             <span class="MyClass">4</span>
             <span class="MyClass">5</span>
             <span class="MyClass">6</span>
         </p>
     </div>
</div>

Если вы заранее не знаете точную структуру потомков #container, я не думаю, что есть элегантный способ сделать это с одиночная строка запроса; :not принимает только простые селекторы.

Просто для информационных целей глупый и повторяющийся метод, который вы не должны использовать, будет использовать строку запроса:

:scope > .MyClass,
:scope > *:not(#excludedElement) > .MyClass,
:scope > *:not(#excludedElement) > *:not(#excludedElement) > .MyClass
...

const selector = `
:scope > .MyClass,
:scope > *:not(#excludedElement) > .MyClass,
:scope > *:not(#excludedElement) > *:not(#excludedElement) > .MyClass
`;

const classes = container.querySelectorAll(selector);
for (const span of classes) {
  span.style.backgroundColor = 'yellow';
}
<div id="container">
     <div class="A">
         <div id="excludedElement">
             <p>
                 <span class="MyClass">1</span>
                 <span class="MyClass">2</span>
                 <span class="MyClass">3</span>
             </p>
         </div>
     </div>
     <div class="B">
         <p>
             <span class="MyClass">4</span>
             <span class="MyClass">5</span>
             <span class="MyClass">6</span>
         </p>
     </div>
</div>
0 голосов
/ 24 января 2020

Вы можете использовать этот точный селектор в .querySelectorAll():

:not(#excludedElement) > p > .MyClass

Пример работы:

const includedSpans = [... document.querySelectorAll(':not(#excludedElement) > p > .MyClass')];

includedSpans.forEach((includedSpan) => console.log(includedSpan.textContent));
<div id="container">
     <div class="A">
         <div id="excludedElement">
             <p>
                 <span class="MyClass">1</span>
                 <span class="MyClass">2</span>
                 <span class="MyClass">3</span>
             </p>
         </div>
     </div>
     <div class="B">
         <p>
             <span class="MyClass">4</span>
             <span class="MyClass">5</span>
             <span class="MyClass">6</span>
         </p>
     </div>
</div>
0 голосов
/ 24 января 2020

У меня есть это ....

const  Excludes   = [...container.querySelectorAll('#excludedElement .MyClass')]
  ,    noExcludes = [...container.querySelectorAll('.MyClass')].filter(el=>(!Excludes.includes(el)))
  ;
noExcludes.forEach(element => element.style.backgroundColor = 'lightgreen');
<div id="container">
  <div class="A">
    <div id="excludedElement">
      <p>
        <span class="MyClass">1</span>
        <span class="MyClass">2</span>
        <span class="MyClass">3</span>
      </p>
    </div>
  </div>
  <div class="B">
    <p>
      <span class="MyClass">4</span>
      <span class="MyClass">5</span>
      <span class="MyClass">6</span>
    </p>
  </div>
</div>
...