6
\$\begingroup\$

My goal was to create a tiny DOM builder library that made creating element trees less gross.

This is the syntax I decided on:

var foo = Builder.create('div', function(){
    //set attributes
    this.id = "foo-div";
    this.title = "Foo Div";

    //append a child node
    this.create('button', function(){

        //append a text node
        this.text('Click Me!');

        this.on('click', function(){
            this.parentNode.style.backgroundColor = 'red';
        });
    });
 });

 document.body.appendChild(foo);

The builder factory also binds the created element to the function so you can also work with the element without all of the magic:

Builder.create('div', function(element){
    element.style.color = '#ffffff';
});

Here is the implementation:

var Builder = function(el){
    //set root and element shortcuts
    this.root = el;

    //create a new element
    this.create = function(tag, func){
        var builder, property, obj = {};
        var el = document.createElement(tag);

        //setup new builder
        builder = new Builder(el);

        if(func != undefined){
            obj = new (this.attach(builder, func))(el);
        }

        //apply attributes
        for (property in obj){
            if (obj.hasOwnProperty(property)) {

                //set name and value
                builder.set(property, obj[property]);    
            }
        }

        return this.append(el);
    };

    //set attribute
    this.set = function(name, value){

        //try setAttribute (from jquery source)
        if (typeof this.root.setAttribute !== "undefined") {
            if (value === false) {
                this.root.removeAttribute(name);
            } else {
                this.root.setAttribute(name, value === true ? name : value);
            }               
        }else{
            this.root[name] = value;
        }

        return this;        
    }

    //attach prototypes
    this.attach = function(builder, func){
        func.prototype.create = this.create.bind(builder);
        func.prototype.set = this.set.bind(builder);
        func.prototype.text = this.text.bind(builder);
        func.prototype.html = this.html.bind(builder);
        func.prototype.on = this.on.bind(builder);

        return func;
    };

    //append element
    this.append = function(el){
        if(this.root == null){
            return this.root = el;
        }

        return this.root.appendChild(el);
    };

    //append text node
    this.text = function(content){
        return this.append(document.createTextNode(content));
    };

    //append html
    this.html = function(content){
        this.root.innerHTML += content;
        return this.root.lastChild;
    };

    //events
    this.on = function (event, handler) {
        if(this.root.addEventListener){
            this.root.addEventListener(event, handler);
        }else{
            this.root.attachEvent('on' + event, function(){
                handler.call(this.root);
            });
        }
    }

};

/**
 * DOM Builder factory
 */
Builder.create = function(tag, func){
    return (new Builder()).create(tag, func);
};

Thoughts:

  • I really like the syntax, but the way I bind the methods to the function feels gross.

  • I also think having the methods (this.create, this.html, this.text, this.on) exist in the same space that the attributes do (this.id) could cause a problem down the road if there was a conflict between a method and an attribute name.

  • Performance is good (better than jQuery) but I'm not sure about memory. Is this thing a nightmare for the garbage collector?

jsFiddle

\$\endgroup\$
  • \$\begingroup\$ You're overwriting el inside the create function. \$\endgroup\$ – hjpotter92 Aug 26 '14 at 5:26
  • \$\begingroup\$ Sorry, but I'm not getting the point of this. You're creating a new way to create DOM sections with code? It looks like a lot of code to me to create a few DOM elements with attributes. I'd expect a lot more of a table/data driven approach if you're trying to really make things more compact and minimize the code. \$\endgroup\$ – jfriend00 Aug 26 '14 at 5:40
  • 2
    \$\begingroup\$ FYI, it's rarely hard to beat jQuery for performance. That is not its strength. Can you beat a plain HTML string for performance? \$\endgroup\$ – jfriend00 Aug 26 '14 at 5:43
  • \$\begingroup\$ @jfriend00 it's faster than creating nodes with an html string, but slower than cloning existing nodes. The only performance goal was to be practical. \$\endgroup\$ – John M Aug 26 '14 at 6:05
  • \$\begingroup\$ @jfriend00 The point is to create a shorthand to fire up element trees with dynamic or conditional structure. Since it's just working with regular DOM objects, you would use this alongside another library \$\endgroup\$ – John M Aug 26 '14 at 6:23
2
\$\begingroup\$

Interesting,

I agree with your assessment that mentioned the way I bind the methods to the function feels gross.

I forked your demo here : http://jsfiddle.net/konijn_gmail_com/hz62smvp/ and replaced

    if(func != undefined){
       obj = new (this.attach(builder, func))(new_element);
    }

with

    if(func != undefined){
       obj = func.bind(builder)(new_element) ;
    }

and it seems to work fine, negating the need for attach altogether.

Furthermore, you should consider adding functions to the prototype of Builder instead of to the instances of builder, this should reduce your memory foot-print.

Other than that:

  • Run this through JsHint in JSFiddle, there are a few missing semicolons and a few dirty comparisons to null and undefined.
  • Consider adding 'use strict' at the start of Builder
  • Just a thought, but I would default el to document.body if it is not provided

    this.root = el || document.body;
    
  • In the same vein, in append I would expect an error if el is not set, instead you make silently the provided parameter the root. The name of that function does not match what it does

Everything else looks good and maintainable.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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