I noticed a strange problem when using Element.children and don't seem to find a good work-around.
Example 1 (expected behaviour)
Take this HTML:
<form>
<input name="hi">
<input name="bye">
</form>
And this JS
const formElement = document.querySelector('form');
for (let i = 0; i < formElement.children.length; i ) {
console.log(formElement.children[i].name);
}
The console prints the following, as you would expect:
"hi"
"bye"
Example 2 (strange behaviour)
Now, let's add an input field with name="children"
<form>
<input name="hi">
<input name="bye">
<input name="children">
</form>
The console prints nothing.
Why this happens in my opinion
formElement.children
returns an HTMLCollection.
In an HTMLCollection you can access the child elements via an index, or directly by the name attribute of the element you are trying to target.
So in order to get the "hi" element, you could say formElement.children[0]
or formElement.hi
.
However when there is an element with the name "children"
inside the collection, formElement.children
will return the element with name="children", and there is no longer any way to loop trough all the elements.
How do I work around this?
That's my question. I know I can simply not use the name "children" and choose something else instead, but surely there must be a way to make this work?
Here's a pen to illustrate the problem: https://codepen.io/pwkip/pen/XWVoXVE
EDIT:
this seems to be related to the fact that formElement is a <form>
. When I change it to a <div>
, things work. So I guess the question boils down to this:
how do I convert a HTMLFormElement to regular HTMLElement?
CodePudding user response:
This is because for quite unfortunate historic reasons, the HTMLFormElement has a named property getter, which will return the children <input>
elements that have either their .name
or their .id
property value set to the given name. Actually it's more complex than that, but not that interesting.
In my humble opinion, that's a real quirk in the specs, but it can't be removed without breaking old websites, which is something specs authors don't really like.
So yes, this named
getter will take precedence over any other getters inherited farther in the prototype chain.
If you really don't want to change your element's name
, what you can do is to call the Element.children
getter on your HTMLFormElement explicitely, or even simpler you can probably just use .querySelectorAll("*")
(assuming you don't have an element with such name or id ;-)
const form = document.querySelector("form");
console.log("with .children:", form.children); // our <input>
const getter = Object.getOwnPropertyDescriptor(Element.prototype, "children").get;
console.log("with Element's getter:", getter.call(form)); // an HTMLCollection
console.log("with querySelectorAll:", form.querySelectorAll("*")); // a NodeList
<form>
<input name="foo">
<input name="children">
</form>