Отметить все чекбоксы родительского уровня при отметке дочернего
Самописный движок, куча чекбоксов выводится следующим образом:
<input type="checkbox" data-current="101" data-parent="100" data-level="1" name="checkboxes[]" value="NNN">
<input type="checkbox" data-current="256" data-parent="101" data-level="2" name="checkboxes[]" value="NNN">
<input type="checkbox" data-current="1234" data-parent="256" data-level="3" name="checkboxes[]" value="NNN">
<input type="checkbox" data-current="1236" data-parent="256" data-level="3" name="checkboxes[]" value="NNN">
<input type="checkbox" data-current="2274" data-parent="1236" data-level="4" name="checkboxes[]" value="NNN">
<input type="checkbox" data-current="257" data-parent="101" data-level="2" name="checkboxes[]" value="NNN">
<input type="checkbox" data-current="7434" data-parent="257" data-level="3" name="checkboxes[]" value="NNN">
<input type="checkbox" data-current="102" data-parent="100" data-level="1" name="checkboxes[]" value="NNN">
<input type="checkbox" data-current="336" data-parent="102" data-level="2" name="checkboxes[]" value="NNN">
<input type="checkbox" data-current="7458" data-parent="336" data-level="3" name="checkboxes[]" value="NNN">
<input type="checkbox" data-current="9800" data-parent="336" data-level="3" name="checkboxes[]" value="NNN">
Видно, что у списка чекбоксов соблюдается структура. Есть чекбоксы 1-го уровня data-level="1", второго и тд. Также у каждого чекбокса есть его айди data-current, и айди родителя data-parent.
Нужно сделать так, чтобы при отметке любого чекбокса, автоматически отмечался его родитель, а также родители родителя, если они есть. Желательно также, чтобы при снятии отметки с родителя, снимались все отметки с его дочерних.
Спасибо.
Ответы (1 шт):
Предлагаю воспользоваться делегацией события (это сильно снизит нагрузку, т.к. не будем на каждый input вешать слушатель):
Оборачиваем все инпуты в один большой контейнер. Обёртка в дополнительный
labelбыл сделан чисто для удобства стилизацииВешаем слушателя на событие клик (
inputsContainer.addEventListener('click') на, обёртывающий все инпуты, контейнерДалее убеждаемся что мы нажали именно на наш
checkbox(target.type !== 'checkbox')Далее в зависимости от значения
checkbox-а (newState) выполняем один из двух действий (action):Если мы выбрали, то запускаем функцию (
activateAllParents), которая найдёт все нужные родительские элементы и выберет ихЕсли мы сняли выбор, то запускаем функцию (
deactivateAllChildren), которая найдёт все нужные дочерние элементы и у всех снимет выбор
Теперь разберёмся в том как работают наши функции activateAllParents и deactivateAllChildren
Начнём с activateAllParents:
На вход ожидается нажатый
checkbox, от которого мы и будем отталкиватьсяНаходим его родителя, с помощью функции
getParent2.1. По данной структуре, родителем для элемента считается тот, у кого аттрибут
data-currentравен аттрибутуdata-parent, у самого элемента. Следовательно нужно достать значение аттрибутаdata-parentу самого элемента (child.dataset.parent) и сделать поиск внутри контейнера (inputsContainer.querySelector) и найти того, у кого вdata-currentнаписано то же самое ([data-current="${child.dataset.parent}"]). Выбран методquerySelectorпотому что у элемента, не может больше одного родителяДалее запускаем цикл (
while), который запускает ряд дейтсвий до тех пор пока родитель находится (parent !== null):3.1. Если родитль уже выбран (
parent.checked === true), то нет смысла идти дальше вверх по цепочке, потому можно завершить работу функции3.2. Выбираем родителя (
parent.checked = true)3.3. Ищем родителя родителя (
parent = getParent(parent))
Теперь рассмотрим deactivateAllChildren:
На вход ожидается нажатый checkbox, от которого мы и будем отталкиваться
Находим все его дочерние элементы, с помощью функции
getAllChildren2.1. По данной структуре, дочерними для элемента считаются те, у которых аттрибут
data-parentравен аттрибутуdata-current, у самого элемента. Следовательно нужно достать значение аттрибутаdata-currentу самого элемента (parent.dataset.current) и сделать поиск внутри контейнера (inputsContainer.querySelectorAll) и найти тех, у которых вdata-parentнаписано то же самое ([data-parent="${parent.dataset.current}"]). Выбран методquerySelectorAllпотому что у элемента, может больше одного дочернего элементаПроходимся по всем дочерним элементам
children.forEach3.1. Если дочерний элемент и так уже не выбран (
child.checked === false), то нет смысла идти дальше вглубь, потому можно завершить работу для этого дочернего элемента3.2. Убираем выбор у дочернего (
child.checked = false)3.3. Рекурсивно убираем выбор у всех дочерних элементов данного дочернего элемента (
deactivateAllChildren(child))
Про dataset можете прочитать тут
Конечный результат:
const inputsContainer = document.querySelector('.inputs');
const getParent = child => inputsContainer.querySelector(`[data-current="${child.dataset.parent}"]`);
const getAllChildren = parent => inputsContainer.querySelectorAll(`[data-parent="${parent.dataset.current}"]`);
const activateAllParents = child => {
let parent = getParent(child);
while (parent !== null) {
if (parent.checked === true) return;
parent.checked = true;
parent = getParent(parent);
}
}
const deactivateAllChildren = parent => {
const children = getAllChildren(parent);
children.forEach(child => {
if (child.checked === false) return;
child.checked = false;
deactivateAllChildren(child);
})
}
inputsContainer.addEventListener('click', e => {
const target = e.target;
if (target.type !== 'checkbox') return;
const newState = target.checked;
const action = newState ? activateAllParents : deactivateAllChildren;
action(target);
})
.inputs {
display: flex;
flex-direction: column;
align-items: flex-start;
}
label {
cursor: pointer;
}
<div class="inputs">
<label>
<input type="checkbox" data-current="101" data-parent="100" data-level="1" name="checkboxes[]" value="NNN">1
</label>
<label>
<input type="checkbox" data-current="256" data-parent="101" data-level="2" name="checkboxes[]" value="NNN">1.1
</label>
<label>
<input type="checkbox" data-current="1234" data-parent="256" data-level="3" name="checkboxes[]" value="NNN">1.1.1
</label>
<label>
<input type="checkbox" data-current="1236" data-parent="256" data-level="3" name="checkboxes[]" value="NNN">1.1.2
</label>
<label>
<input type="checkbox" data-current="2274" data-parent="1236" data-level="4" name="checkboxes[]" value="NNN">1.1.2.1
</label>
<label>
<input type="checkbox" data-current="257" data-parent="101" data-level="2" name="checkboxes[]" value="NNN">1.2
</label>
<label>
<input type="checkbox" data-current="7434" data-parent="257" data-level="3" name="checkboxes[]" value="NNN">1.2.1
</label>
<label>
<input type="checkbox" data-current="102" data-parent="100" data-level="1" name="checkboxes[]" value="NNN">2
</label>
<label>
<input type="checkbox" data-current="336" data-parent="102" data-level="2" name="checkboxes[]" value="NNN">2.1
</label>
<label>
<input type="checkbox" data-current="7458" data-parent="336" data-level="3" name="checkboxes[]" value="NNN">2.1.1
</label>
<label>
<input type="checkbox" data-current="9800" data-parent="336" data-level="3" name="checkboxes[]" value="NNN">2.1.2
</label>
</div>