Search code examples
javascripthtmldomselectors-api

Element's name property is invisible to querySelectorAll and doesn't appear in its HTML


I want to select all list items with the name 'viewer'.

This returns an empty NodeList:

document.querySelectorAll('[name="viewer"]');

But this returns an array containing the correct items:

[...document.querySelectorAll('li')].filter(({ name }) => name == 'viewer');

The list items were created like this:

const listItem = document.createElement('li');
listItem.name = 'viewer';
listItem.id = 'viewer-1337';

When I inspect them in the console, I see their id in their HTML but not their name. For example

<li id="viewer-1337">foo</li>

It seems they do have the name property set, but it's not accessible via document.querySelectorAll or visible in their HTML.

Why not?

const names = ['foo', 'bar'];
names.forEach(name => {
  const li = document.createElement('li');
  li.id = 'viewer-' + name;
  li.name = 'viewer';
  li.innerHTML = name;
  const ul = document.querySelector('ul');
  ul.appendChild(li);
});

const nodeList = document.querySelectorAll('[name="viewer"]');
console.log([...nodeList]);

const array = [...document.querySelectorAll('li')].filter(({ name }) => name == 'viewer');
console.log(array);
<ul></ul>


Solution

  • This is occurring because the name attribute is reserved:

    name gets or sets the name property of an element in the DOM. It only applies to the following elements: <a>, <applet>, <button>, <form>, <frame>, <iframe>, <img>, <input>, <map>, <meta>, <object>, <param>, <select>, and <textarea>.

    When you set the name property of an element which is not one of those types, the element will not receive that value as an attribute value. It'll still be put onto the name property of the JavaScript object, but it won't be put into the attribute.

    const div = document.createElement('div');
    console.log(div.attributes.length);
    div.name = 'foo';
    console.log(div.attributes.length);
    console.log(div.hasOwnProperty('name'));

    So the query string [name="viewer"] does not select the elements (because they do not have the name attribute), but [...document.querySelectorAll('li')].filter(({ name }) => name == 'viewer') works because the filter callback is successfully retrieving the name property from the JavaScript representation of the element.

    You can fix it by putting it into the dataset instead:

    const names = ['foo', 'bar'];
    names.forEach(name => {
      const li = document.createElement('li');
      li.id = 'viewer-' + name;
      li.dataset.name = 'viewer';
      li.innerHTML = name;
      const ul = document.querySelector('ul');
      ul.appendChild(li);
    });
    
    const nodeList = document.querySelectorAll('[data-name="viewer"]');
    console.log(nodeList.length);
    <ul></ul>