Friday, September 24, 2010

Javascript: fluent API via chaining

Javascript in the browser has a set of extension to traverse and manipulate the Document Object Model (DOM) that allows to create dynamic pages very easily. But... Well...
Nothing's new here.
One function amongst them has always bugged me: elm.insertBefore(). For some reasons (who said my age?), I can not remember which parameters come first. We will use this example to see how we can create a fluent API using Javascript chaining functionality and write code as follow:
insert(elm).before(elm2) and insert(elm).after(elm2)!

insertBefore method


First things first, let's see what is the usage of this method.
Straight from Mozilla dev center:
var insertedElement = parentElement.insertBefore(newElement, referenceElement);

The insertedElement returned is equal to newElement.
insertBefore requires the parent of the referenceElement to do its job. Therefore, parentElement === referenceElement.parentNode. Let's add a h1 before the p tag in the example below:
<div>
 <p id="myPara">I am THE reference.</p>
</div>
<script type="text/javascript">

var para = document.getElementById("myPara");//referenceElement - p

var parentElement    = referenceElement.parentNode;     //parentElement - div

var title            = document.createNode('h1');       //newElement - h1
    title.innerHTML = 'I am a new kid on the block';

parentElement.insertNode(title,para);
</script>

After running this code, you should have the following HTML:
<div>
  <h1>I am a new kid on the block</h1>
  <p id="myPara">I am THE reference.</p>
</div>

As you can see, it is not a very hard concept!
One little helper would be to not have to specify the parentNode ourself. Let's create a wrapper:
var DOM= {
  insertBefore: function(newElement,referenceElement) {

      //basic checking of the parameters
      if(!newElement || !referenceElement) return;

      var oparent = referenceElement.parentNode;

      //never know...
      if(!oparent) return;

      //go!
      return oparent.insertBefore(newElement, referenceElement);
  }
};

//usage example
var inserted = DOM.insertBefore(newElement,referenceElement);

insertAfter method


It's going to be even faster!
There is no such method!
But we can easily simulate this behavior by using the nextSibling method and one little thing I did not mention for now... again, straight from Mozilla dev center:

If referenceElement is null, newElement is inserted at the end of the list of child nodes.

So if there is no referenceElement, this is just going to appendChild the element.If we do have a reference, we just need to get the nextSibling and insertBefore it.
Therefore, we could write our own insertAfter:
var DOM= {

  //appended after insertBefore:

  insertAfter: function(newElement,referenceElement) {

      //basic checking of the parameters
      if(!newElement || !referenceElement) return;

      var oparent = referenceElement.parentNode;

      //never know...
      if(!oparent) return;

      var nextElement    = referenceElement.nextSibling;

     //in case, you have some trouble with IE
     //but could not verify this bug so...
     // if(!nextElement) return oparent.appendChild(newElement);
    //  else
      //go!
      return oparent.insertBefore(newElement, nextElement);
  }
};

//usage example
var inserted = DOM.insertAfter(newElement,referenceElement);

Alright, we have now two helper functions DOM.insertBefore and DOM.insertAfter but we did not manage to make them "fluent". We did not even make them chainable...

Fluent way


So,we still have to make this functions easier to use, more "natural".
First, let's see how we can chain functions in javascript:
var DOM = {

    elm : null,//will hold the element to insert

    insert : function(elm) {
        DOM.elm=elm;
        return DOM;
    }
};

As you can see it is not very hard! We just need to return the object itself to allow the chaining of the functions.
Let's implement before and after:
var DOM = {

    elm: null,

    insert: function(elm) {
        DOM.elm=elm;
        return DOM;
    },

    before: function(elm) {
        DOM.elm=DOM.elm.parentNode.insertBefore(elm, DOM.elm);
        return DOM;
    },

    after: function(elm) {
        DOM.elm=DOM.elm.parentNode.insertBefore(elm, DOM.elm.nextSibling);
        return DOM;
    },

    //a little helper to shortcut things...
    $: function(id){
        return document.getElementById(id);
    }
};

//usage
DOM.insert(DOM.$('myelm')).before(DOM.$('anotherelm'));

//we could add a check on the parameter to see if it's a string or an object
//we could then also write it:
DOM.insert('myelm').before('anotherelm');

That's it!
I did not check the parameters to make the code shorter but you can DIY.

Conclusion

Chaining functions in javascript is rather straight forward and can create easier APIs to use and remember by being closer to the natural language (well, English language here). Obviously, this is an example where we use a unique object (Singleton) but we could implement the chaining for multiple instances too:
var DOM= {

    insert: function(elm) {
        this.elm = elm;
        return this;
    },

    before: function(elm) {
        this.elm=this.elm.parentNode.insertBefore(elm, this.elm);
        return this;
    },

    after: function(elm) {
        this.elm=this.elm.parentNode.insertBefore(elm, this.elm.nextSibling);
        return this;
    },

 //a little helper to shortcut things...
    $: function(id){
        return document.getElementById(id);
    }
};
var dom = new DOM.insert(DOM.$('myelm')).before(DOM.$('anotherelm'));

In that case, we use the new keyword to create the instance bound to this and return this.

No comments: