2

I am working on a Editor and want to clone a HTML Node with custom properties using JavaScript. I only found a way using setAttribute() but it converts my custom attribute into a string:

// Using custom attributes
var html = document.createElement("div");
var obj  = {test: 123,html: html};
html.obj = obj;
var cloned = html.cloneNode(true);
console.log(cloned.obj); // returns null

// Using setAttribute
var html = document.createElement("div");
var obj  = {test: 123, html: html};
html.setAttribute("obj") = obj;
var cloned = html.cloneNode(true);
console.log(cloned.getAttribute("obj")); // returns "[object Object]"

How do I clone the html element with the object?

Community
  • 1
  • 1
B. Colin Tim
  • 293
  • 5
  • 13
  • `html.setAttribute("obj") = obj;` "should be" `html.setAttribute("obj", obj);` - Also I don't know why you're trying to insert that same HTMLDIVElement into it's own custom attribute... which is BTW and object.... If you want to use custom properties than you should go by using `data-*` attributes. – Roko C. Buljan Jan 02 '17 at 15:09

4 Answers4

1

Attributes in HTML are string values, not JavaScript Objects and JavaScript Properties. The cloneNode operation only clones HTML intrinsics and not anything you add on top, it is not the same thing as a deep object copy.

You will need to do it manually:

function cloneCustomNode(node) {

    var clone  node.cloneNode(); // the deep=true parameter is not fully supported, so I'm not using it
    clone.obj = node.obj; // this will copy the reference to the object, it will not perform a deep-copy clone of the 'obj' object
    return clone;
}

This can be generalised to copy any custom JavaScript properties from one object to another, excluding those already defined in the default (defaultNode).

var defaultNode = document.createElement("div");

function cloneNodeWithAdditionalProperties(node) {

    var clone  node.cloneNode();

    for(var propertyName in node) {

        if( !( propertyName in genericNode ) ) {

            clone[ propertyName ] = node[ propertyName ];
        }
    }

    return clone;
}

cloneNodeWithAdditionalProperties will run in O( n ) time because the if( x in y ) operation is a hashtable lookup with O( 1 ) complexity (where n is the number of properties).

Dai
  • 141,631
  • 28
  • 261
  • 374
0

You could use a property of HTMLElement.dataset however the api only allows storing strings which would mean using JSON.stringify() while setting and JSON.parse() while getting arrays or objects

var html = document.createElement("div");
var obj  = {test: 123,html: html};
html.dataset.obj = JSON.stringify(obj);
var cloned = html.cloneNode(true);
console.log(JSON.parse(cloned.dataset.obj)); 
charlietfl
  • 170,828
  • 13
  • 121
  • 150
0

One approach is to use Object.keys() to iterate over the node (which is an Object) and apply the discovered properties, and their property-values, to the created node, for example:

// named function to follow DRY principles;
// n: DOM node, the node to be cloned:
function customClone(n) {

  // creating a temporary element to hold the
  // cloned node:
  let temp = n.cloneNode(true);

  // Using Object.keys() to obtain an Array of
  // properties of the Object which are not
  // inherited from its prototype, and then
  // using Array.prototype.forEach() to iterate
  // over that array of properties:
  Object.keys(n).forEach(

    // using an Arrow function, here 'property' is
    // the current property of the Array of properties
    // over which we're iterating, and then we
    // explicitly assign the property-value of the
    // node that was cloned to the property-value of
    // that same property on the clone:
    property => temp[property] = n[property]
  );

  // returning the clone to the calling context:
  return temp;
}

let html = document.createElement("div"),
  obj = {
    test: 123,
    html: html
  };
html.obj = obj;

let cloned = customClone(html);
console.log(cloned.obj);

function customClone(n) {
  let temp = n.cloneNode(true);
  Object.keys(n).forEach(
    property => temp[property] = n[property]
  );
  return temp;
}

let html = document.createElement("div"),
  obj = {
    test: 123,
    html: html
  };
html.obj = obj;

let cloned = customClone(html);
console.log(cloned.obj);

JS Fiddle demo.

References:

David Thomas
  • 249,100
  • 51
  • 377
  • 410
0

Extending the accepted answer, I created a snippet that deep clones all the childNodes as well.

// Make a generic element to compare default properties
const DIV = document.createElement('div');

function fullClone(n: Node) {
  // Clone the element without DEEP, so we can manually clone the child nodes
  const temp = n.cloneNode();

  // Loop through all the properties
  for (let prop in n) {
  
    // Skip if the property also exists in the div element
    if (prop in DIV) {
      continue;
    }

    // We try/catch in case the property is readonly
    try {
      // Copy the value
      temp[prop] = n[prop];
    } catch {
      // readOnly prop
    }
  }

  // Remove any childNodes left (text nodes)
  temp.childNodes.forEach(c => temp.removeChild(c));
  // Deep clone all the childNodes
  n.childNodes.forEach(c => temp.appendChild(fullClone(c)));

  return temp;
}
janarvaez
  • 139
  • 1
  • 2