Since the OP already does make use of a data-* global attribute like data-children, the OP might think about a generic implementation of displaying/updating child-count changes which will be fully agnostic to DOM structures, thus it always is going to work, regardless of chosen tag-names, class-names and nesting of markup structures.
One, for instance, could take inspiration from the HTML for attribute. Just the minimum set of related data-* attributes like ...
<tagName
data-child-count="2"
data-child-count-for="23fd215b-4c2a-4861-b53a-5c517e67a56e"
>
The amount of children of the next element is 2.
</tagName>
... and ...
<tagName data-child-count-id="23fd215b-4c2a-4861-b53a-5c517e67a56e">
<!--
... any child structure like ...
-->
<tagName></tagName>
<tagName></tagName>
</tagName>
... already enables a fully generic implementation of displaying/updating child-count changes.
For instance [data-child-count-for="23fd215b-4c2a-4861-b53a-5c517e67a56e"] marks an element which has to display/update a child-count change. The element's related dataset.childCountFor-value does identify the element where the child-count change is expected to happen. It is the very element which was marked by e.g. [data-child-count-id="23fd215b-4c2a-4861-b53a-5c517e67a56e"].
The next provided example code shows how the function, the OP is looking for, would work upon such a generic markup.
function updateChildCount(childCountNode) {
const parentId = childCountNode.dataset.childCountFor;
const parentNode = document
.querySelector(`[data-child-count-id="${ parentId ?? '' }"]`);
if (parentNode) {
const childCount = parentNode.childElementCount;
childCountNode.dataset.childCount = childCount;
childCountNode.textContent =
`The amount of children of the next element is ${ childCount }.`;
}
}
function updateAnyChildCount() {
document
.querySelectorAll('[data-child-count-for]')
.forEach(updateChildCount);
}
// (re)render initial child-count values.
updateAnyChildCount();
body { margin: 0; }
p { margin: 4px 0; }
h2 { font-size: 1em; margin: 8px 0 4px 0; }
button[data-add],
button[data-delete] { float: right; margin-left: 2px; }
<h2
data-child-count="unknown"
data-child-count-for="23fd215b-4c2a-4861-b53a-5c517e67a56e"
>
The amount of children of the next element is unknown.
</h2>
<div data-child-count-id="23fd215b-4c2a-4861-b53a-5c517e67a56e">
<div>
<p>Lorem ipsum dolor sit amet.</p>
<button>Button</button>
<button data-add>clone 'n append</button>
<button data-delete>delete</button>
</div>
</div>
<h2
data-child-count="unknown"
data-child-count-for="69e7b5bf-698f-4492-94a0-e48243fcf1f9"
>
The amount of children of the next element is unknown.
</h2>
<div data-child-count-id="69e7b5bf-698f-4492-94a0-e48243fcf1f9">
<div>
<p>Lorem ipsum dolor sit amet.</p>
<button>Button</button>
<button data-add>clone 'n append</button>
<button data-delete>delete</button>
</div>
<div>
<p>Lorem ipsum dolor sit amet.</p>
<button>Button</button>
<button data-add>clone 'n append</button>
<button data-delete>delete</button>
</div>
<div>
<p>Lorem ipsum dolor sit amet.</p>
<button>Button</button>
<button data-add>clone 'n append</button>
<button data-delete>delete</button>
</div>
</div>
<script>
// - example's helper functionality which is going to invoke
// the child-count update-function.
document
.addEventListener('click', ({ target }) => {
const button = target.closest('[data-add], [data-delete]');
if (button) {
// mutate the DOM.
if (button.matches('[data-add]')) {
const { parentNode } = button;
parentNode
.parentNode
.appendChild(
parentNode.cloneNode(true)
);
} else {
button
.parentNode
.remove();
}
// invoke the child-count update-function.
updateAnyChildCount();
}
});
</script>
The above implementation could even be changed to just updating the data-child-count attribute. And since the element which features said attribute does always carry the current child-count via this attribute's value, one could introduce an additional single css-rule as simple as ...
[data-child-count]:after {
content: attr(data-child-count);
}
... in order to always assure an up-to-date UI as well.
The following, slightly changed, example code just proves the last statement.
function updateChildCount(childCountNode) {
const parentId = childCountNode.dataset.childCountFor;
const parentNode = document
.querySelector(`[data-child-count-id="${ parentId ?? '' }"]`);
if (parentNode) {
childCountNode.dataset.childCount = parentNode.childElementCount;
}
}
function updateAnyChildCount() {
document
.querySelectorAll('[data-child-count-for]')
.forEach(updateChildCount);
}
body { margin: 0; }
p { margin: 4px 0; }
h2 { font-size: 1em; margin: 8px 0 4px 0; }
button[data-add],
button[data-delete] { float: right; margin-left: 2px; }
[data-child-count]:after {
content: attr(data-child-count);
}
<h2
data-child-count="1"
data-child-count-for="23fd215b-4c2a-4861-b53a-5c517e67a56e"
>
The amount of children of the next element is
</h2>
<div data-child-count-id="23fd215b-4c2a-4861-b53a-5c517e67a56e">
<div>
<p>Lorem ipsum dolor sit amet.</p>
<button>Button</button>
<button data-add>clone 'n append</button>
<button data-delete>delete</button>
</div>
</div>
<h2
data-child-count="3"
data-child-count-for="69e7b5bf-698f-4492-94a0-e48243fcf1f9"
>
The next element's child-count is
</h2>
<div data-child-count-id="69e7b5bf-698f-4492-94a0-e48243fcf1f9">
<div>
<p>Lorem ipsum dolor sit amet.</p>
<button>Button</button>
<button data-add>clone 'n append</button>
<button data-delete>delete</button>
</div>
<div>
<p>Lorem ipsum dolor sit amet.</p>
<button>Button</button>
<button data-add>clone 'n append</button>
<button data-delete>delete</button>
</div>
<div>
<p>Lorem ipsum dolor sit amet.</p>
<button>Button</button>
<button data-add>clone 'n append</button>
<button data-delete>delete</button>
</div>
</div>
<script>
// - example's helper functionality which is going to invoke
// the child-count update-function.
document
.addEventListener('click', ({ target }) => {
const button = target.closest('[data-add], [data-delete]');
if (button) {
// mutate the DOM.
if (button.matches('[data-add]')) {
const { parentNode } = button;
parentNode
.parentNode
.appendChild(
parentNode.cloneNode(true)
);
} else {
button
.parentNode
.remove();
}
// invoke the child-count update-function.
updateAnyChildCount();
}
});
</script>
In addition the OP should make use of MutationObserver and MutationRecord where the latter gets provided as an array of records to an observer's callback/handler function.
As for the OP's use case, one just needs to have a look at/into both NodeList's of each record which are addedNodes and removedNodes. A child-count change has happened whenever either of the node-list's carries at least a single element-reference.
The decision for MutationObserver enables a maximum generic implementation for it decouples the change-handling/management entirely from any code which would trigger DOM-mutations such as element-insertion and -deletion.
In order to not being forced to having to observe the entire DOM-tree one would make use of a component-like approach based on the already introduced/proposed data-* attributes in cooperation with an element's related dataset-property.
The next provided example code demonstrates how one would identify such a loosely coupled (markup and DOM -wise) component, and how one would create a mutation observer for each such component.
function getChildCountCopy(template, count) {
return template
.replace(/\$\{\s*childCount\s*\}/g, count);
}
function renderChildCount(childCountNode, observedNode) {
const childCount = observedNode.childElementCount;
childCountNode.textContent = getChildCountCopy(
childCountNode.dataset.childCountTemplate, childCount,
);
childCountNode.dataset.childCount = childCount;
}
function handleChildCountChangeForBoundNodes(
records/*, observer,*/
) {
const { childCountNode, observedNode } = this;
records
.forEach(record => {
const { addedNodes, removedNodes } = record;
if (addedNodes.length + removedNodes.length > 0) {
renderChildCount(childCountNode, observedNode);
}
});
}
function initializeChildCountMutationObserver(
childCountNode, observedNode,
) {
const observer = new MutationObserver(
handleChildCountChangeForBoundNodes
.bind({
childCountNode,
observedNode,
}),
);
observer
.observe(observedNode, {
childList: true,
// subtree: true,
// attributes: true,
});
}
function enableInstantChildCount(childCountNode) {
const { childCountFor: observedId } = childCountNode.dataset;
const observedNode = document
.querySelector(`[data-child-count-id="${ observedId ?? '' }"]`);
if (observedNode) {
// (re)render initial/current child-count.
renderChildCount(childCountNode, observedNode);
// register handling of element related DOM mutations.
initializeChildCountMutationObserver(
childCountNode, observedNode,
);
}
}
document
.querySelectorAll('[data-child-count-for]')
.forEach(enableInstantChildCount);
body { margin: 0; }
p { margin: 4px 0; }
h2 { font-size: 1em; margin: 8px 0 4px 0; }
button[data-add],
button[data-delete] { float: right; margin-left: 2px; }
<h2
data-child-count="1"
data-child-count-for="23fd215b-4c2a-4861-b53a-5c517e67a56e"
data-child-count-template="The amount of children of the next element is ${ childCount }."
>
The amount of children of the next element is 1.
</h2>
<div data-child-count-id="23fd215b-4c2a-4861-b53a-5c517e67a56e">
<div>
<p>Lorem ipsum dolor sit amet.</p>
<button>Button</button>
<button data-add>clone 'n append</button>
<button data-delete>delete</button>
</div>
</div>
<h2
data-child-count-for="69e7b5bf-698f-4492-94a0-e48243fcf1f9"
data-child-count-template="The next element's child-count is ${ childCount }."
>
The next element's child-count is unknown.
</h2>
<div data-child-count-id="69e7b5bf-698f-4492-94a0-e48243fcf1f9">
<div>
<p>Lorem ipsum dolor sit amet.</p>
<button>Button</button>
<button data-add>clone 'n append</button>
<button data-delete>delete</button>
</div>
<div>
<p>Lorem ipsum dolor sit amet.</p>
<button>Button</button>
<button data-add>clone 'n append</button>
<button data-delete>delete</button>
</div>
<div>
<p>Lorem ipsum dolor sit amet.</p>
<button>Button</button>
<button data-add>clone 'n append</button>
<button data-delete>delete</button>
</div>
</div>
<script>
// example's helper functionality which is going to trigger DOM mutations.
document
.addEventListener('click', ({ target }) => {
const button = target.closest('[data-add], [data-delete]');
if (button) {
// mutate the DOM.
if (button.matches('[data-add]')) {
const { parentNode } = button;
parentNode
.parentNode
.appendChild(
parentNode.cloneNode(true)
);
} else {
button
.parentNode
.remove();
}
}
});
</script>
forloop you wrote, someone might be able to help you fix it.