Question

I've a function that takes an object as a parameter, and uses the structure of the object to create nested DOM nodes, but I receive the following error:

http://new.app/:75NOT_FOUND_ERR: DOM Exception 8: An attempt was made to reference a Node in a context where it does not exist.

What I would like my function to do, is, when supplied with a suitable object as a parameter, example:

var nodes = {
    tweet: {
        children: {
            screen_name: {
                tag: "h2"
            },
            text: {
                tag: "p"
            }
        },
        tag: "article"
    }
};

It would create the following DOM nodes:

<article>
    <h2></h2>
    <p></p>
</article>

Here is my attempt so far:

function create(obj) {
    for(i in obj){  
        var tmp = document.createElement(obj[i].tag);  
        if(obj[i].children) {  
            tmp.appendChild(create(obj[i].children)); /* error */
        }; 
        document.getElementById("tweets").appendChild(tmp);  
    };  
};

I'm already struggling!

Ideally I'd like to eventually add more child key's to each object, not just tag, but also id, innerHTML, class etc.

Any hel would be much appreciated, though please: I'm sure a framework or library could do this for me in just a few lines of code, or something similar, but I'd prefer not to use one for this particular project.

If you could briefly explain your answers too it'd really help me learn how this all works, and where I went wrong!

Thank you!

NB: I've changed and marked the line in my function that the error message is talking about.

I changed it from:

mp.appendChild(obj[i].children);

to:

mp.appendChild(create(obj[i].children));

This is because I want any nested keys in the children object to also be created, so screen_name had a children key, they too would be created. Sorry, I hope you can understand this!

I'm looking at http://jsperf.com/create-nested-dom-structure for some pointers, this may help you too!

Was it helpful?

Solution

Your "create" function is going to have to be written recursively.

To create a node from your data (in general), you need to:

  1. Find the "tag" property and create a new element
  2. Give the element the "id" value of the element (taken from the data)
  3. For each element in "children", make a node and append it

Thus:

function create(elementDescription) {
  var nodes = [];
  for (var n in elementDescription) {
    if (!elementDescription.hasOwnProperty(n)) continue;
    var elem = elementDescription[n];
    var node = document.createElement(elem.tag);
    node.id = n; // optional step
    var cnodes = create(elem.children);
    for (var c = 0; c < cnodes.length; ++c)
      node.appendChild(cnodes[c]);
    nodes.push(node);
  }
  return nodes;
}

That will return an array of document elements created from the original "specification" object. Thus from your example, you'd call:

var createdNodes = create(nodes);

and "createdNodes" would be an array of one element, an <article> tag with id "tweets". That element would have two children, an <h2> tag with id "screen_name" and a <p> tag with id "text". (Now that I think of it, you might want to skip the "id" assignment unless the node description has an explicit "id" entry, or something.)

Thus if you have a <div> in your page called "tweets" (to use your example, though if so you'd definitely want to cut out the "id" setting part of my function), you'd add the results like this:

var createdNodes = create(nodes), tweets = document.getElementById('tweets');

for (var eindex = 0; eindex < createdNodes.length; ++eindex)
  tweets.appendChild(createdNodes[eindex]);

OTHER TIPS

I added a function appendList that accepts a list of elements, and the container to append to. I removed the append to "tweets" part out of the create function to more effectively separate your code.

function create(obj) {
    var els = [];
    for(i in obj){  
        var tmp = document.createElement(obj[i].tag);  
        var children;
        if(children = obj[i].children) {  
            var childEls = create(children);
            appendList(childEls, tmp);
        }
        els.push(tmp);
    }; 
    return els;
};
function appendList(list, container){
    for(var i = 0, el; el = list[i]; i++){
        container.appendChild(el);
    }
};
// gets an array of root elements populated with children
var els = create(nodes); 
// appends the array to "tweets"
appendList(els, document.getElementById("tweets")); 

Building on the previous answer: I think you still need to create the element you're trying to append:

tmp.appendChild(children[prop].tag);

should be

tmp.appendChild(document.createElement(children[prop].tag));

function create(obj) {
    for(i in obj){  
        var tmp = document.createElement(obj[i].tag);  
        var children;
        if(children = obj[i].children) {  
            for(var prop in children)
                tmp.appendChild(document.createElement(children[prop].tag));   
        }
        document.getElementById("tweets").appendChild(tmp);  
    };  
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top