Traverse Form Elements w/ Fieldset Structure

If I do the following:

<html>

<head>
<script type='text/javascript'>
window.addEventListener('load', () => {
    const form = document.forms[0];
    const sel = form.querySelectorAll('input,select,fieldset,textarea');
    console.log(sel);
});
</script>
</head>

<form>

<input type='text' name='e00'/>
<input type='text' name='e01'/>
<fieldset>
    <input type='text' name='e10'/>
    <input type='text' name='e11'/>
    <fieldset>
        <input type='text' name='e20'/>
    </fieldset>
</fieldset>
<input type='text' name='e02'/>

</form>

</html>

The querySelectorAll call (or form.elements) returns all form elements in a collection like:

0: input e00
1: input e01
2: fieldset
3: input e10
4: input e11
5: fieldset
6: input e20
7: input e02

But this does not tell me which input elements are within each fieldset. All form elements are flattened into the elements / querySelectorAll result.

Q: Is there a way to traverse the hierarchy of form elements such that I can determine which are within each fieldset?

The fieldset element has an elements collection as well. So perhaps when I encounter a fieldset I can traverse it’s elements and then use that knowledge to ignore them in the parent elements collections. But that seems awful clumsy. Is there a better way?

UPDATE:

I suppose I could just do this:

function traverse(node, ret) {
    switch (node.tagName) {
        case 'SELECT':
        case 'INPUT':
        case 'TEXTAREA':
            ret[node.name] = node.tagName;
            return;
        case 'FORM':
        case 'FIELDSET':
            ret = ret[node.name] = {}; 
            break;
    }   
    for (let ci = 0; ci < node.children.length; ci++) {
        traverse(node.children[ci], ret);
    }   
}
window.addEventListener('load', () => {
    const form = document.forms[0];
    let obj = {}; 
    traverse(form, obj);
    console.log(obj);
});

which captures the fieldset tree structure:

f0:
  e00: "INPUT"
  e01: "INPUT"
  e02: "INPUT"
  s00:
    e10: "INPUT"
    e11: "INPUT"
    s10:
      e20: "INPUT"

As I am unclear as to what you are trying to do, this is only a guess at a solution.

I added some values to the ‘input’ elements and a ‘name’ to the fieldset elements.
Depending upon your requirements, the ‘name’ might be better used as an ‘id’,
but that might screw-up your form values if you send it via a ‘submit’ (???)

<html>
<!-- From: https://www.sitepoint.com/community/t/traverse-form-elements-w-fieldset-structure/331136 -->

<head>
</head>
<form>

<input type='text' name='e00' value="e00" />
<input type='text' name='e01' value="e01" />
<fieldset name="fs01">
    <input type='text' name='e10' value="e10" />
    <input type='text' name='e11' value="e11" />
    <fieldset name="fs02">
        <input type='text' name='e20' value="e20" />
    </fieldset>
</fieldset>
<input type='text' name='e02' value="e02" />

</form>

<script>
console.clear();
window.addEventListener('load', () => {
    const form = document.forms[0];
    const sel = form.querySelectorAll('input,select,fieldset,textarea');
//    const sel = form.querySelectorAll('input,fieldset');
  for (let s of sel) { console.log(s.name); }
});
</script>
</html>

I did not see a ‘select’ or ‘textarea’ element, so I eliminated the ‘querySelectorAll’ list temporarily for testing only. May not answer your original question, but it may provide more “food for thought”.

What is your final purpose for this code?