Как правильно получить всех соседей элемента?

Имеется такой код:

function getAllNextSiblings(element) {
    let siblings = [];
    let sibling = element;

    while (sibling.nextSibling) {
        sibling = sibling.nextSibling;
        sibling.nodeType == 1 && sibling.classList.contains("main-element__block") && siblings.push(sibling);
    }

    return siblings;
}

const getElementsCollection = () => {
    const containerElements = document.querySelectorAll(".main-element");

    const storageOfSetsElements = [];

    [...containerElements].forEach(containerElement => {
        const mainObject = {
            heading: "",
            subElements: []
        };

        mainObject.heading = containerElement.textContent;

        const getSubElements = () => {
            [...getAllNextSiblings(containerElement.nextElementSibling)].forEach(subElementOfContainer => {
                let subObj = {};

                const $idSelector = subElementOfContainer.querySelector(".main-element__block--id");
                const $valueSelector = subElementOfContainer.querySelector(".main-element__block--value");

                subObj.id = $idSelector.textContent.trim();
                subObj.value = $valueSelector.textContent.trim();

                mainObject.subElements.push(subObj);
            });

            storageOfSetsElements.push(mainObject);
        };

        const nextElement = containerElement.nextElementSibling;

        nextElement.classList.contains("main-element__button") && getSubElements();
    });

    return storageOfSetsElements;
};

getElementsCollection();

И такая верстка:

<div class="container">

        <div class="main-element">Контейнер 1</div>
        <div class="main-element__button">Кнопка</div>
        <div class="main-element__block element-1">
            <span class="main-element__block--id">1 —</span>
            <span class="main-element__block--value">Клубника</span>
        </div>

        <div class="main-element__block element-2">
            <span class="main-element__block--id">2 —</span>
            <span class="main-element__block--value">Банан</span>
        </div>

        <div class="main-element__block element-3">
            <span class="main-element__block--id">3 —</span>
            <span class="main-element__block--value">Апельсин</span>
        </div>

        <div class="main-element__block element-4">
            <span class="main-element__block--id">4 —</span>
            <span class="main-element__block--value">Вишня</span>
        </div>


        <div class="main-element">Контейнер 2</div>
        <div class="main-element__button">Кнопка</div>
        <div class="main-element__block element-5">
            <span class="main-element__block--id">5 —</span>
            <span class="main-element__block--value">Ананас</span>
        </div>

        <div class="main-element">Контейнер 3</div>
        <div class="main-element__button">Кнопка</div>
        <div class="main-element__block element-6">
            <span class="main-element__block--id">6 —</span>
            <span class="main-element__block--value">Авокадо</span>
        </div>
        <div class="main-element__block element-7">
            <span class="main-element__block--id">7 —</span>
            <span class="main-element__block--value">Груша</span>
        </div>
    </div>

На первый взгляд всё работает, но стоит заглянуть в полученные данные, и сразу видно ошибку.

function getAllNextSiblings(element) {
    let siblings = [];
    let sibling = element;

    while (sibling.nextSibling) {
        sibling = sibling.nextSibling;
        sibling.nodeType == 1 && sibling.classList.contains("main-element__block") && siblings.push(sibling);
    }

    return siblings;
}

const getElementsCollection = () => {
    const containerElements = document.querySelectorAll(".main-element");

    const storageOfSetsElements = [];

    [...containerElements].forEach(containerElement => {
        const mainObject = {
            heading: "",
            subElements: []
        };

        mainObject.heading = containerElement.textContent;

        const getSubElements = () => {
            [...getAllNextSiblings(containerElement.nextElementSibling)].forEach(subElementOfContainer => {
                let subObj = {};

                const $idSelector = subElementOfContainer.querySelector(".main-element__block--id");
                const $valueSelector = subElementOfContainer.querySelector(".main-element__block--value");

                subObj.id = $idSelector.textContent.trim();
                subObj.value = $valueSelector.textContent.trim();

                mainObject.subElements.push(subObj);
            });

            storageOfSetsElements.push(mainObject);
        };

        const nextElement = containerElement.nextElementSibling;

        nextElement.classList.contains("main-element__button") && getSubElements();
    });

    return storageOfSetsElements;
};

console.log(getElementsCollection());
<div class="container">

        <div class="main-element">Контейнер 1</div>
        <div class="main-element__button">Кнопка</div>
        <div class="main-element__block element-1">
            <span class="main-element__block--id">1 —</span>
            <span class="main-element__block--value">Клубника</span>
        </div>

        <div class="main-element__block element-2">
            <span class="main-element__block--id">2 —</span>
            <span class="main-element__block--value">Банан</span>
        </div>

        <div class="main-element__block element-3">
            <span class="main-element__block--id">3 —</span>
            <span class="main-element__block--value">Апельсин</span>
        </div>

        <div class="main-element__block element-4">
            <span class="main-element__block--id">4 —</span>
            <span class="main-element__block--value">Вишня</span>
        </div>


        <div class="main-element">Контейнер 2</div>
        <div class="main-element__button">Кнопка</div>
        <div class="main-element__block element-5">
            <span class="main-element__block--id">5 —</span>
            <span class="main-element__block--value">Ананас</span>
        </div>

        <div class="main-element">Контейнер 3</div>
        <div class="main-element__button">Кнопка</div>
        <div class="main-element__block element-6">
            <span class="main-element__block--id">6 —</span>
            <span class="main-element__block--value">Авокадо</span>
        </div>
        <div class="main-element__block element-7">
            <span class="main-element__block--id">7 —</span>
            <span class="main-element__block--value">Груша</span>
        </div>
    </div>

По идее, скрипт должен найти каждый элемент с классом: "main-element", от него находим стоящий рядом элемент с классом: "main-element__button", и уже от этого элемента ищем стоящие рядом с ним элементы с классом: "main-element__block", получив ссылки на коллекцию элементов, перебираем их в цикле, и получаем определенные данные, а уже далее проделываем остальные операции, с которыми проблем нет. Всё.

Основной проблемой является тот факт, что по идее в первый объект должны попасть ссылки на стоящие рядом с первой "кнопкой", элементы с селектором "main-element", но, однако же происходит другое. Каким-то образом, в первый объект попадают элементы которые должны попасть во второй объект, и так далее, то есть во второй падают данные из третьего, и так далее.

Получается что функция игнорирует селекторы "main-element" && "main-element__button" и собирает "main-element__block", но это не правильно...

Ожидаемый результат:

[
    {
        "heading": "Контейнер 1",
        "subElements": [
            {
                "id": "1 —",
                "value": "Клубника"
            },
            {
                "id": "2 —",
                "value": "Банан"
            },
            {
                "id": "3 —",
                "value": "Апельсин"
            },
            {
                "id": "4 —",
                "value": "Вишня"
            }
        ]
        
    },
    {
        "heading": "Контейнер 2",
        "subElements": [
            {
                "id": "5 —",
                "value": "Ананас"
            }
        ]
    },
    {
        "heading": "Контейнер 3",
        "subElements": [
            {
                "id": "6 —",
                "value": "Авокадо"
            },
            {
                "id": "7 —",
                "value": "Груша"
            }
        ]
    }
]

Получаемый результат:

[
    {
        "heading": "Контейнер 1",
        "subElements": [
            {
                "id": "1 —",
                "value": "Клубника"
            },
            {
                "id": "2 —",
                "value": "Банан"
            },
            {
                "id": "3 —",
                "value": "Апельсин"
            },
            {
                "id": "4 —",
                "value": "Вишня"
            },
            {
                "id": "5 —",
                "value": "Ананас"
            },
            {
                "id": "6 —",
                "value": "Авокадо"
            },
            {
                "id": "7 —",
                "value": "Груша"
            }
        ]
        
    },
    {
        "heading": "Контейнер 2",
        "subElements": [
            {
                "id": "5 —",
                "value": "Ананас"
            },
            {
                "id": "6 —",
                "value": "Авокадо"
            },
            {
                "id": "7 —",
                "value": "Груша"
            }
        ]
    },
    {
        "heading": "Контейнер 3",
        "subElements": [
            {
                "id": "6 —",
                "value": "Авокадо"
            },
            {
                "id": "7 —",
                "value": "Груша"
            }
        ]
    }
]

Это возможно как-то решить? Что нужно исправить в коде чтобы получать "правильный" набор данныx? И вообще, почему это происходит?


Ответы (1 шт):

Автор решения: UModeL

Если не хочется "тянуть" всю библиотеку jQuery, тогда можно создать упрощённый аналог метода .nextUntil(). И, если правильно его использовать, он подойдёт для любой вложенности.

function fNextUntil(el, sSelector, sFilter) {
  let aSiblings = [];
  el = el.nextElementSibling;
  while (el) {
    if (el.matches(sSelector)) break;
    if (sFilter && !el.matches(sFilter)) {
      el = el.nextElementSibling;
      continue;
    }
    aSiblings.push({
      id: el.querySelector('.main-element__block--id').textContent,
      value: el.querySelector('.main-element__block--value').textContent
    });
    el = el.nextElementSibling;
  }
  return aSiblings;
}

let aResult = [];
[...document.querySelectorAll('.main-element')].forEach((el) => {
  aResult.push({
    heading: el.textContent,
    subElements: fNextUntil(el, '.main-element', '.main-element__block')
  });
});

// Выводим результат в консоль
console.log(aResult);
<div class="container">

  <div class="main-element">Контейнер 1</div>
  <div class="main-element__button">Кнопка</div>
  <div class="main-element__block element-1">
    <span class="main-element__block--id">1 —</span>
    <span class="main-element__block--value">Клубника</span>
  </div>

  <div class="main-element__block element-2">
    <span class="main-element__block--id">2 —</span>
    <span class="main-element__block--value">Банан</span>
  </div>

  <div class="main-element__block element-3">
    <span class="main-element__block--id">3 —</span>
    <span class="main-element__block--value">Апельсин</span>
  </div>

  <div class="main-element__block element-4">
    <span class="main-element__block--id">4 —</span>
    <span class="main-element__block--value">Вишня</span>
  </div>

  <div class="main-element">Контейнер 2</div>
  <div class="main-element__button">Кнопка</div>
  <div class="main-element__block element-5">
    <span class="main-element__block--id">5 —</span>
    <span class="main-element__block--value">Ананас</span>
  </div>

  <div class="main-element">Контейнер 3</div>
  <div class="main-element__button">Кнопка</div>
  <div class="main-element__block element-6">
    <span class="main-element__block--id">6 —</span>
    <span class="main-element__block--value">Авокадо</span>
  </div>
  <div class="main-element__block element-7">
    <span class="main-element__block--id">7 —</span>
    <span class="main-element__block--value">Груша</span>
  </div>
</div>

→ Ссылка