2
\$\begingroup\$

For the MDN documentation website we have written this jsonToDOM function and I was wondering how you all would improve it. It takes a json structure and creates DOM elements out of it and appends it to the document. It is written in such a way that the user has to pass the window/document as they might run this code from a scope which has no access to window object (Browser addons for example)

function jsonToDOM(json, doc, nodes) {

    var namespaces = {
        html: 'http://www.w3.org/1999/xhtml',
        xul: 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'
    };
    var defaultNamespace = namespaces.html;

    function namespace(name) {
        var m = /^(?:(.*):)?(.*)$/.exec(name);        
        return [namespaces[m[1]], m[2]];
    }

    function tag(name, attr) {
        if (Array.isArray(name)) {
            var frag = doc.createDocumentFragment();
            Array.forEach(arguments, function (arg) {
                if (!Array.isArray(arg[0]))
                    frag.appendChild(tag.apply(null, arg));
                else
                    arg.forEach(function (arg) {
                        frag.appendChild(tag.apply(null, arg));
                    });
            });
            return frag;
        }

        var args = Array.slice(arguments, 2);
        var vals = namespace(name);
        var elem = doc.createElementNS(vals[0] || defaultNamespace, vals[1]);

        for (var key in attr) {
            var val = attr[key];
            if (nodes && key == 'key')
                nodes[val] = elem;

            vals = namespace(key);
            if (typeof val == 'function')
                elem.addEventListener(key.replace(/^on/, ''), val, false);
            else
                elem.setAttributeNS(vals[0] || '', vals[1], val);
        }
        args.forEach(function(e) {
            try {
                elem.appendChild(
                                    Object.prototype.toString.call(e) == '[object Array]'
                                    ?
                                        tag.apply(null, e)
                                    :
                                        e instanceof doc.defaultView.Node
                                        ?
                                            e
                                        :
                                            doc.createTextNode(e)
                                );
            } catch (ex) {
                elem.appendChild(doc.createTextNode(ex));
            }
        });
        return elem;
    }
    return tag.apply(null, json);
}

Here's an example of the usage:

var json =
['html:div', {style:'background-color:springgreen'},
    ['html:form', {id:'myFirstForm'},
            ['html:input', {type:'text', value:'my field'}],
            ['html:button', {id:'myBtn'}, 'Button Text Content']
    ],
    ['html:form', {id:'mySecondForm'},
            ['html:input', {type:'text', value:'my field for second form'}],
            ['html:div', {},
                'Sub Div with textcontent and siblings',
                ['html:br', {}],
                ['html:input', {type:'checkbox', id:'mycheck'}],
                ['html:label', {for:'mycheck'},
                    'here is text of label, click this text will check the box'
                ]
            ]
    ]
];

document.body.appendChild(jsonToDOM(json, document, {}));
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$
function jsonToDOM(json, doc, nodes)

I would suggest better naming for these.

  • json is a bad name since JSON is a string data transfer format based on JS literal object notation (hence the name). I suggest something like domTree (since it's a representation of the DOM) or something.

  • doc is vague. Why not name it as document straight away. If it is document then it will just be shadowing the document from the outer scope. No harm done.

  • nodes... Not sure what this does judging by the name. Your example provides a {} which isn't very clear what it does. Better name it to something better.


jsonToDom([
  // very deep tree
], document, {});

// to

jsonToDom(document, {}, [
  // very deep tree
]);

I suggest moving dom and nodes before json to allow users to create inlined calls without burying dom and nodes below the call.


var namespaces = {
    html: 'http://www.w3.org/1999/xhtml',
    xul: 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'
};

// to

var NAMESPACES= {
    HTML: 'http://www.w3.org/1999/xhtml',
    XUL: 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'
};

These appear to be "constants". Use uppercase and underscores convention to indicate constant-ness.

In addition, anything "constant" should be placed in a variable. This includes string literals (like [object Array]), patterns (like /^on/), and others.


var defaultNamespace = namespaces.html;

This appears to be useless since it's always HTML. Did you forget something?


Your ternary needs a bit of cleaning. The way I usually do it is to line up the the ? and : for visibility of the two options that come after.

condition1 ? foo
           : condition2 ? bar
                        : baz;

If an if, for, while and similar operations have blocks that have more than one line, regardless if the operation is one expression, just put {} for safety. Better, just use {} for blocks all the time.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks so much for this feedback! I'll writ an example that uses the third argument. :) \$\endgroup\$
    – Noitidart
    Commented Jul 10, 2015 at 15:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.