Flatten array with (nodelists, HTMLCollections, strings etc.)

I am running some tests. I want to pass maybe an HTMLCollection or a single element wrapped in an array [element] or [elements] and end up with an iterable array [el] or [el1, el2, el3 etc..] — something I will be passing to various dom methods.

Example HTML

<div>
  <ul id='list'>
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
    <li>Item 4</li>
    <li>Item 5</li>
    <li>Item 6</li>
  </ul>
</div>

I had hoped that I could do something like [].flat.call(domElements)

So a test on the following

const elements = [
  document.getElementsByTagName('li'),
  document.getElementById('list'),
  ['a', 'b', 'b'],
  'def'
]

Array flat output

console.log(elements.flat())
// Array flat (6) [ HTMLCollection(6), ul#list, "a", "b", "b", "def" ]

The HTMLCollection is left untouched, which isn’t what I want.

I ended up writing the following

// returns a flattened array, including flattening nodelists, strings etc.
const flattenEverything = arr => {
  const isIterable = obj => typeof obj[Symbol.iterator] === 'function'

  return arr.reduce(
    (acc, val) =>
      acc.concat((isIterable(val)) ? Array.from(val) : [val])
    , []
  )
}

flattenEverything output

flattenEverything(elements)
// (13) [li, li, li, li, li, li, ul#list, "a", "b", "b", "d", "e", "f"]

I know I could just go down the if/else route, but I am kind of looking for a nice one shop route to this.

Would be interested in thoughts. codepen here

Thanks

Hi @rpg_digital, that would work but effectively be the same as Array.from(domElements)… to flatten an array containing node lists, the conversion using the isIterable() check is the way to go I suppose. If you don’t need deeper flattening, you might also do this using flatMap() though:

const isIterable = obj => typeof obj[Symbol.iterator] === 'function'  
const flattenEverything = arr => arr.flatMap(value => isIterable(value) ? [...value] : value)

Maybe just a bit closer to the actual flat() call. :-)

1 Like

I actually had a flattenEverything2 with flatMap below my reduce version, but didn’t finish off. Honest : )

I think what you have come up with is preferable, and flatMap is a good function to know about.

There’s an interesting example on MDN demonstrating adding and removing elements during the map.

Cheers @m3g4p0p

Edit: Memory is vague, but seem to remember having issues with transpiling the spread operator on dom elements, which is why I went for Array.from. Sorry I can’t be more specific

Just checking and I have the following comment in a line of code of mine

// Note Babel and IE11 has issues with [...form.elements]
1 Like

Haha wow I had not seen this… now we just have to come up with an actual use case. Maybe casting arbitrary values to a typed array?

function toUint8Array (values) {
  return new Uint8Array(values.flatMap(value =>
    typeof value === 'string'
      ? value.split('').map(char => char.charCodeAt(0))
      : typeof value === 'number'
        ? value
        : []
  ))
}

const typed = toUint8Array([42, 'foo', true, 'bar', {}])
console.log(typed) // Uint8Array(7) [42, 102, 111, 111, 98, 97, 114]

LOL

1 Like

Surely anything that’s meant to return a flat is going to need to while. Or are we assuming everything is only 1 layer deep?

A depth of 1 is fine for now and my intended usage.

1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.