How do you clone an element’s NamedNodeMap to an empty object?

Tags: , , ,



I have a javaScript array of objects, where the objects are called text, where each object element contains information about an HTML element that looks like the following using the Chrome browser’s inspect Source Watch area:

text:
  attributes: NamedNodeMap
                0: baseURI: "file:.../awesomplete_textarea.html"
                   childNodes: NodeList []
                   firstChild: null
                   isConnected: false
                   lastChild: null
                   localName: "label"
                   name: "label"
                   namespaceURI: null
                   nextSibling: null
                   nodeName: "label"
                   nodeType: 2
                   nodeValue: "Alternative Rock"
                   ownerDocument: document
                   ownerElement: li
                   parentElement: null
                   parentNode: null
                   prefix: null
                   previousSibling: null
                   specified: true
                   textContent: "Alternative Rock"
                   value: "Alternative Rock"
                   __proto__: Attr }
              length: 1
              label: { same content as 0, above }
              __proto__: NamedNodeMap }
  label: "Alternative Rock"
  tagName: "LI"
  value: "Alternative Rock (Alternative)"
  length: 16
  __proto__: String

Note: While the attributes member of text object (above) only contains the information for the label attribute, it could also contain other attributes items, such as a style and/or class, which were copied from a set of <li> tags in my web-page, in which case there would be additional entries in the text object and attributes member for those attributes items.

When the information <li> tags are collected, the tag’s attributes are copied into text object using the copyAttributes function that is shown below, which works fine.

However, when later try to use the same copyAttributes function to copy the text.attributes to the attr variable to then create a new HTML element, I get this error:

mycode.js:98 Uncaught TypeError: to.setNamedItem is not a function
  at copyAttributes (mycode.js:98)
  at Function._.ITEM (mycode.js:940)
  at _.item (mycode_textarea.html:625)
  at mycode.js:826
  at Array.forEach (<anonymous>)
  at _.evaluate (mycode.js:825)

The following code shows how this call is made, and what I’m trying to with the information copied into attr object variable.

function copyAttributes( from, to ) {
  var attr;

  for( const [ key, value ] of Object.entries( from ) ) {

    if( !isNaN( key ) ) {  // Only add the named attributes ...

      let name = from[ key ].name;;  // get the attribute's name

      if( to[ name ] === undefined ) {

        attr       = document.createAttribute( name ); // Create the attribute.
        attr.value = value.value;                      // Assign its value.

        to.setNamedItem( attr );                       // Add it to to

      }

    }

  }

}

var tagName = 'li';
var attribute = {};

// Copy the text.attributes from the text object to attributes.

copyAttributes( text.attributes, attributes );

// Add the information in the {...} object to the attributes object ...

Object.assign( attributes,
               attributes,
               { innerHTML: label,             // Add these to those just copied.
                 'role':    'option',
                 'id':      'list_' + this.count +
                            '_item_' + item_id,
                 'value':   value } );

// Create the new `<li>` with the original tag's attributes and those added, above.

$.create( tagName, attributes );

The problem seems to be that the to parameter of the copyAttributes function isn’t the NamedNodeMap type, so it doesn’t support the setNamedItem method.

How can I create an empty attributes variable of this type?

Or is there a better/easier way to populate the attributes variable with the test.attributes information?

Thanks.

Answer

Here is a codepen of a working version of the drow-down combobox that properly created the li tag elements and copies the desired attributes into it.

This create function is from the Awesomplete plug-in, but it tha been modified to not set the focud on the newly created element.

$.create = function( tag, o ) {
  var element = document.createElement( tag );

  for( var i in o ) {
    var val = o[ i ];

    if( i === 'inside' ) {
      $( val ).appendChild( element );
    }
    else if( i === 'around' ) {

      var ref = $( val );

      ref.parentNode.insertBefore( element, ref );
      element.appendChild( ref );

      if( ref.getAttribute( 'autofocus' ) != null ) {
        ref.focus();
      }
    }
    else if( i in element ) {
      element[ i ] = val;
    }
    else {
      element.setAttribute( i, val );
    }
  }

  return element;
};

This is my modified code that calls the above create function and assign the attributes.

if( tagName === 'LI' ) {

  matched = ( inputValue = ( ', ' +
                             me.input.value.trim().toLowerCase() +
                             ',' ) )    // Normalize input.value so that the first item can be found.
                           .includes( ', ' + value.toLowerCase() + ',' ) ||         // Find the normalized value in the normalized input.value ...
                           inputValue.includes( ', ' +
                                                label.toLowerCase() + ',' );        // Find the normalized label in the normalized input.value ...

  Object.assign( attributes,
         attributes,
         { 'role':          'option',
           'id':            'awesomplete_list_' +
                            this.count +
                            '_item_' + item_id,
           'value':         value,                          // Give every option ( li ) a value attribute.
           'aria-selected': matched.toString() } );                 // If a match was found then set aria-selected to 'true'
                                                // else set area-selected to 'false'.

}
else {

  matched = false;

}

newTag = $.create( tagName );

    newTag.innerHTML = label;

if( text.attributes.length ) {

  // Now copy the attributes ...

  copyAttributes( text.attributes, newTag.attributes );

  // Add the rest of the attributes ...

  copyAttributes( attributes, newTag.attributes, true );

}

return newTag;


Source: stackoverflow