Обход DOM без исключенных элементов - PullRequest
0 голосов
/ 02 апреля 2020

Я создаю простой скрипт, который проходит через DOM и возвращает объект дерева с элементами, найденными в DOM. Сам рекурсивный обход довольно прост, но я хочу / нужно пропустить некоторые элементы и включить другие элементы. Как мне это сделать?

Это мой HTML:

<div data-element="from-here">
  <div>skip me</div>
  <div>
    <div data-element="awesome">awesome text</div>
    <div data-element="collect-me">
      awesome text
      <div data-element="also-me">
        other text
        <div class="but-not-me">...</div>
      </div>
    </div>
  </div>
</div>

И мой код рекурсивного обхода:

const root = document.querySelector('[data-element="from-here"]');

function traverse(node) {
  return {
    element: node.dataset.element,
    children: Array.from(node.querySelectorAll(':scope > div')).map(childNode => traverse(childNode)),
  };
}

traverse(root);

Как вы можете видеть запросы кода все элементы div, но мне нужны только элементы с атрибутом data-element. Я не могу просто сделать `node.querySelectorAll (': scope> [data-element]'), потому что это не достигнет первого div.

Вот результат, который я хочу:

{
  element: 'from-here',
  children: [
    {
      element: 'awesome',
      children: [],
    },
    {
      element: 'collect-me',
      children: [
        {
          element: 'also-me',
          children: [],
        }
      ]
    }
  ]
}

Любая помощь будет принята с благодарностью!

Ответы [ 3 ]

2 голосов
/ 02 апреля 2020

Вы можете использовать Array.filter() для фильтрации элементов без атрибута data-element.

Но , чтобы включить div обертки, вам может понадобиться добавить атрибут data-element, как показано ниже.

const root = document.querySelector('[data-element="from-here"]');

function traverse(node) {
  if (node.dataset.element) {
    return {
      element: node.dataset.element,
      children: Array.from(node.querySelectorAll(':scope > div'))
        .filter(child => {
          return child.dataset.element || Array.from(child.children).some(grandChild => grandChild.dataset.element)
         // If element has dataset     or  The child has some children with dataset attribute
        })
        .map(childNode => traverse(childNode)),
    };
  } else if (Array.from(node.children).some(child => child.dataset.element)) {
    return Array.from(node.querySelectorAll(':scope > div')).filter(child => child.dataset.element).map(childNode => traverse(childNode))
  }
}

console.log(traverse(root));
<div data-element="from-here">
  <div>skip me</div>
  <div>
    <div data-element="awesome">awesome text</div>
    <div data-element="collect-me">
      awesome text
      <div data-element="also-me">
        other text
        <div class="but-not-me">...</div>
      </div>
    </div>
  </div>
</div>
2 голосов
/ 02 апреля 2020

Использование Array.prototype.flatMap может значительно снизить сложность вашего преобразования -

  1. Если узел не имеет значения data-element,
  2. Не включать узел; включать только результаты своих потомков.
  3. В противном случае (по индукции) узел имеет значение data-element. Включите element в вывод, а также children.

Пронумерованные точки выше соответствуют комментариям источника ниже -

const root = document.querySelector('[data-element="from-here"]');

const toTree = ({ dataset = {}, children = [] }) =>
  dataset.element === undefined              // 1
    ? Array.from(children).flatMap(toTree)   // 2
    : [ { element: dataset.element           // 3
        , children: Array.from(children).flatMap(toTree)
        }
      ]

console.log(toTree(root)[0])
<div data-element="from-here">
  <div>skip me</div>
  <div>
    <div data-element="awesome">awesome text</div>
    <div data-element="collect-me">
      awesome text
      <div data-element="also-me">
        other text
        <div class="but-not-me">...</div>
      </div>
    </div>
  </div>
</div>

Выше обратите внимание, что мы не использовали querySelectorAll, так как никаких дополнительных запросов к документу не требуется. Тем не менее, некоторые очевидные улучшения:

  1. определяют форму вашего дерева как отдельную функцию, branch
  2. определяют помощника для повторного задания, allToTree
const branch = (element = "", children = []) =>    // 1
  ({ element, children })

const allToTree = (nodes = []) =>                  // 2
  Array.from(nodes).flatMap(toTree)

toTree теперь свободен от сложности. Наша цель ясна, и каждая функция проста в написании, тестировании и обслуживании -

const toTree = ({ dataset = {}, children = [] }) =>
  dataset.element === undefined
    ? allToTree(children)
    : [ branch(dataset.element, allToTree(children) ]
0 голосов
/ 02 апреля 2020

Почему бы не использовать вместо него node.querySelectorAll(':scope [data-element]')? Это будет охватывать все элементы, имеющие атрибут, а не только первый слой потомков.

Вот скрипка: https://jsfiddle.net/cp21ykng/2/ или я что-то упустил?

...