17 September 2008

Another JavaScript pattern - private members shared between object instances

In this post, I will introduce another pattern to add to my previous post about my JavaScript patterns. In fact, this is an extension to those patterns and combines both patterns together! This is a singleton pattern that creates a constructor!

In this post I will also make use of some aspects of JSquared, so please refer to the JSquared website for more information on those.

So, private members shared between object instances. What does that mean? What it means is that I want to have some private variables and functions which are accessible to multiple instances of a constructor but without them being part of the constructor or the objects the constructor will create. A fairly well known way of achieving this is by making the prototype of my constructor an object built using something similar to my singleton pattern. This would allow public and private members on the prototype of my object. But that will not give me all I wish to achieve this time and it can be a clumsy syntax.

The code I will use to help explain this concept is designed to be a simple panel object. The object will manage a series of panels on a webpage which are linked. Only one panel can be open at a time. Typically one would construct this with a singleton object which finds all instances of the panels in the DOM and adds some handling to each DOM node accordingly. I will do this in a different way. This code example is of course merely a skeleton.

var Panel = new (function() {

 

    //add a DOM load event

    addLoadEvent( function() {

        //get all DIV elements with a class of "panel"

        document.getElementsByClassName( {cssClass: "panel", tags: "div", callback: function() {

            //create a new instance of the Panel constructor for each panel

            new Panel(this);

        } } );

    } );

 

    var panels = [];

 

    function closeAll() {

        for (var i = panels.length-1; i>=0; i--) {

            panels[i].close();

        }

    }

 

    //return the Panel constructor

    return function(panelNode) {

        this.open = function() {

            closeAll();

            //perform the open logic

        }

        this.close = function() {

            //perform the close logic

        }

        //add this instance to the all instances array inside the closure

        panels.push(this);

    }

});



Lets step through each part of this example and see what it does:


var Panel = new (function() {


Create a new variable called Panel using the singleton pattern.



    //add a DOM load event

    addLoadEvent( function() {

        //get all DIV elements with a class of "panel"

        document.getElementsByClassName( {cssClass: "panel", tags: "div", callback: function() {

            //create a new instance of the Panel constructor for each panel

            new Panel(this);

        } } );

    } );


Using JSquared methods add an event handler for when the documents loads. The handler will use another JSquared method to find all elements in the document which are DIVs with a class of panel and for each one run the supplied function which will create a new instance of Panel passing in the DIV node with the class panel that was found. (see the JSquared docs for more info on how getElementsByClassName is used)



    var panels = [];

 

    function closeAll() {

        for (var i = panels.length-1; i>=0; i--) {

            panels[i].close();

        }

    }


These are the private members which each instance of Panel will have access to. We have an array of panels which will get filled with each instance of Panel that is created and we have a closeAll method that loops through each instance of Panel and calls its close method.



    //return the Panel constructor

    return function(panelNode) {


We are going to return a constructor (using the standard constructor pattern). The variable Panel that we created at the top of the code example will now take the value of this constructor. In other words, Panel becomes a constructor which we can create instances of using the new keyword.



        this.open = function() {

            closeAll();

            //perform the open logic

        }

        this.close = function() {

            //perform the close logic

        }


Create open and close methods which will perform those actions. In the open method, we first want to close all the panels ensuring only one can be open at any time. To do that we call the private closeAll method which is available through the closure around Panel.



        //add this instance to the all instances array inside the closure

        panels.push(this);


Add this new instance (this line of code is still part of the Panel constructor) to the private panels array also available through the closure we have created.


To recap, we use the singleton pattern to execute some logic before returning a constructor which is then available to us later on in the page execution. We can use the closure this creates to make private members, declared inside the self executing function which is the singleton pattern. These private members are available to each instance of the constructor but the members are not available anywhere else within any JavaScript - as is usual for a closure of this type.

This can be a very powerful and useful pattern. When building a large application, I believe it is good to keep public members of all objects to a minimum and I also prefer not to use the prototype of an object unless I am using inheritance. This pattern achieves both of these aims in an elegant and encapsulated way.

No comments: