Counters discussion

Here we go though the file counter.js broken into small pieces, explaining what’s going on.

Namespace

We define and execute an anonymous function in order to create a private namespace. Just like any other function, variables declared and used in the function are not accessible from outside, unless we explicitly provide access. Returning a value is one way to do this. Adding an attribute to the global object, or something accessible from the global object, is another.

(function()
{

This use of function to provide a namespace is similar in some ways to the module concept in Python. Some people call this use of function in JavaScript the module pattern.

Create

From one point of view, the create function should be, but is not, a built-in function in JavaScript. Instead we have to create it ourselves out of the new operator, which is but should not be part of JavaScript.

That point of view is that JavaScript’s prototype inheritance is best understood for what it is, rather than presented as if new in JavaScript is similar to Java’s new. That point of view also say’s that code should be written to use create when required, but that use of new is considered harmful (except to define create, as below).

    var _create_fn = function(){};
    var create = function(parent){

        _create_fn.prototype = parent;
        var instance = new _create_fn();
        return instance;
    };

The create function creates a new object whose parent in the inheritance tree is the parent argument. What could be simpler than that? The create function is similar to Python’s __new__, in that it gives the class/parent of an object.

The new operator has semantics such that the above code provides an implementation of create. However, we don’t for now need the somewhat complex semantics of new.

Global object

JavaScript has a global object, whereas Python does not. (Python’s main module is similar but in many important ways different.) JavaScript’s global object can be the cause of many obscure and hard-to-diagnose problems, and so it’s generally best to take care when using it.

Therefore, it is best to make explicit any use of the global object, and a good way to do this is to introduce a variable, called global of course, whose value is the global object. That way, when you access the global object or its properties, you can see that’s what you’re doing.

    var global = (function(){return this;})();

This line of code executes an anonymous function which returns the value of this during the execution of the anonymous function. Due to the semantics of JavaScript and the way the function is called, in the anonymous function this is the global object. Thus, the anonymous function returns the global object, which we store in the global variable (which confusingly is local to the function).

In some situations, such as above, the global object is available as the value of this, but sometimes this refers to something else. So using this to refer to the global object is not a good idea.

In browsers the global object has an attribute called window whose value is the global object. It is as if we had written

global.window = global;

It is better to write window rather than this, but writing global for the global object is best of all. (Command line JavaScript interpreters don’t start with window as the global object, and in the browser window has many special properties.)

Counter class

Every class needs a prototype object. We’ll call it counter. Don’t confuse the counter prototype with a Counter instance, which might also be called counter. Fortunately, in well organised code the prototype object of a class is in one namespace/module, and instances are in different namespaces.

Here’s the implementation of Counter. It relies on a function __init__, similar to Python’s __init__, which we haven’t defined yet.

    var counter = {};           // Prototype object for Counter.

    var Counter = function(){

        var instance = create(counter);
        instance.__init__.apply(instance, arguments);
        return instance;
    };

A word about the use of apply. In Python we would write something a bit like

def Counter(*args, **kwargs):

     instance = object.__new__(counter)
     instance.__init__(*args, **kwargs)
     return instance

In JavaScript we don’t have keyword arguments, and instead of args we have a keyword arguments which has special properties. These properties, together with those of apply, cause the JavaScript code above to have the same general effect as the Python code.

(We won’t sweat the details now. Most of the time use of call, apply, create and arguments can and should be hidden behind the scenes. But you need to know that this can be done so that when the time come, you can provide an efficient and elegant interface by using them to refactor complexity into something that is put behind the scenes.)

The gist of the above code is that

var my_counter = Counter(arg1, ...)

causes my_counter to be an object, whose parent is the counter prototype, and which has been initialised by the __init__ function.

Counter properties

The __init__ method is called, of course, when a newly created child of the counter prototype needs to be initialised. Each counter has a name, the thing being counted, and its count.

    counter.__init__ = function(name){

        this.name = name;
        this.count = 0;
    };

When a counter is clicked, its count is increased by one. We’ll come to the display of counters next.

    counter.onclick = function(event){

        this.count ++;
    };

Often, the most efficient way of changing the content of a DOM node is to use its innerHTML property. This method works well with delegation, as with delegation we don’t have to add (or remove) handlers from nodes created (or destroyed) in this way.

This implementation is very simple. In a production system name would be escaped. The argument parent_id is provided in case the html method wants to create IDs for its subnodes. This would happen, for example, if the returned HTML was for a slideshow with its controls.

    counter.html = function(parent_id){

        return this.name + ' ' + this.count;
    };

Onload

This code is specific to a particular page or perhaps group of pages. It creates Counter instances (sensible, as we want to count) and sets up links between the DOM and the JavaScript.

We can’t change the DOM until the nodes we wish to change have been constructed. In browsers the global object has an onload event that can be used to solve this timing problem.

Here, when the page loads we create an array of counters.

    global.onload = function(){

        var models = [
            Counter('apple'),
            Counter('banana'),
            Counter('cherry'),
            Counter('date')
        ];

We also need to link the counters, which are JavaScript objects, to DOM nodes and events. Let’s suppose we’re to display the counters in an element whose ID is example.

This will do the job. Notice the inelegant way in which we initialise the display of the counters. Clearly this won’t do if we’re inserting say a slideshow into the page. It’s an exercise to write something better.

        var element = document.getElementById('example');

        element.innerHTML = (
            '<span id="a0">apple 0</span>'
            + '<span id="a1">banana 0</span>'
            + '<span id="a2">cherry 0</span>'
            + '<span id="a3">date 0</span>'
        );

We now need to link DOM events to the counters we created. The counters are stored in the models variable (which is local to the onload function). Each counter element on the DOM corresponds, via its ID, to a counter instance in the models array. This was done on purpose.

When a counter element on the page is clicked we can, from its ID, find the correspond counter instance in the models and, so to speak, click it. We can also ask the counter instance to generate new HTML for the refreshing of the counter element.

In short, everything has been set up to make delegation as easy as possible. We’ll assume that the generic function onclick_factory will handle the delegation (and cross browser issues) for us.

        element.onclick = onclick_factory(models);

This line of code helps prevent a memory leak in Internet Explorer, prior to IE8.

        element = undefined;    // Avoid IE memory leak.

And finally we can finish the onload function.

    };

Delegation

In an ideal production environment this code would come from a well-supported standard library. However, we’re not there yet.

The onclick_factory is an example of a closure. The returned function has a hidden reference to the models argument that is passed to the onclick_factory. Each execution of onclick_factory refers to the models argument that was passed to it.

    var onclick_factory = function(models){

        var onclick = function(event){

	    event = event || global.event; // For IE event handling.
	    var target = event.target || event.srcElement;
            var id = target.id;
            if (id) {
                var id_num = +id.slice(1);
                var model = models[id_num];
                model.onclick();
                var html = model.html(id);
                if (html){
                    document.getElementById(id).innerHTML = html;
                }
            }
        };
        return onclick;
    };

Closing namespace

})();

This little piece of line noise does three things.

  1. It uses } to close the function definition.
  2. It then uses ) to close the function expresssion.
  3. Finally, it uses () to execute the function.

The final semi-colon ; closes the statement. (It’s generally best when writing code to put in the semicolons yourself, rather then let JavaScript put them in.)