Download code

The next item I need to address is object events. I have a mechanism for attaching event handlers to DOM objects, so when the user clicks a button, or changes the value of a text box, or does some other interaction with a DOM element, the View can handle that event. But then the View will need to fire its own event to notify a Controller object (yet to be created) that the user is attempting to accomplish some task.

For example, the user might click a Search button, or press the Enter key on a text box, both of which might fire the View’s “onSearch” event. A Controller listening for this event could then initiate the search. When the search is complete, the Controller could fire an event to indicate that the results are available. Maybe an “onSearchResults” event or something. Models will also need to fire events to notify other objects when data is loaded, changed, etc.

In fact, I figure there are very few objects that won’t need to fire events. Therefore, I decided to include events and event handling as a basic core functionality of all classes. This means revisiting the Class function and making some enhancements.

Here is the original Class function. If you are unfamiliar with it, you might want to review the original series about creating the Class object. It’s probably not necessary, but it might help you understand the changes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
LivingMachines.Class = function(init) {
 
	// Create constructor function that will check if we are inheriting, 
	// then call real constructor
	var cstr = function() {
		if (arguments[0] === inheriting) return;
		this.construct.apply(this, arguments);
	};
 
	// If we are inheriting, copy the prototype, otherwise assign a new prototype
	if (init.inherits) {
		cstr.prototype = new init.inherits(inheriting);
		cstr.base = init.inherits.prototype;
		delete init.inherits;	// Keeps it from being added to the prototype later
	};
 
	// Copy init properties to the prototype (adds/overrides base class methods)
	for (var p in init)
		cstr.prototype[p] = init[p];
 
	// Return the constructor function (this is the class)
	return cstr;
 
};

The basic premise here is that I only need to add the event methods to a base class. Any descendant classes will then automatically inherit those methods. As I see it, the methods will be createEvent (to add an event to the class), listenTo (to add an event handler to an event), unlistenTo (to remove an event handler from an event, and yes I know that “unlisten” not a real word, but it works), and fire (to execute all event handlers that are attached to an event).

Since I am going to allow multiple event handlers, I will need an array to hold the handlers (which are just functions that will be executed when the event is fired). I decided to create an object, called events, that will hold an array for each event of the class. This will be an instance object, and so will need to be added to the constructor (line 7, below).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
LivingMachines.Class = function(init) {
 
	// Create constructor function that will check if we are inheriting, 
	// then call real constructor
	var cstr = function() {
		if (arguments[0] === inheriting) return;
		this.events = { };    // To hold our arrays of event handlers
		this.construct.apply(this, arguments);
	};
 
	// If we are inheriting, copy the prototype, otherwise assign a new prototype
	if (init.inherits) {
		cstr.prototype = new init.inherits(inheriting);
		cstr.base = init.inherits.prototype;
		delete init.inherits;	// Keeps it from being added to the prototype later
	};
 
	// Copy init properties to the prototype (adds/overrides base class methods)
	for (var p in init)
		cstr.prototype[p] = init[p];
 
	// Return the constructor function (this is the class)
	return cstr;
 
};

Next, I need to add the event methods. Since all classes will have event capabilities, I simply need to add event methods to the class’s prototype if this is a base class, that is, if this is a class that is not inherited from any other class. This will ensure that all top-level classes have event capabilities, and those capabilities will pass on to all descendant classes through inheritance.

If a class inherits from another class, then the inherits property of the init argument will be set, and I’m already checking for it (line 12). So, I simply need to add an else to the if, and add the event methods there (lines 17-47, below).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
LivingMachines.Class = function(init) {
 
    // Create constructor function that will check if we are inheriting, 
    // then call real constructor
    var cstr = function() {
        if (arguments[0] === inheriting) return;
        this.events = { };    // To hold our arrays of event handlers
        this.construct.apply(this, arguments);
    };
 
	// If we are inheriting, copy the prototype, otherwise assign a new prototype
    if (init.inherits) {
        cstr.prototype = new init.inherits(inheriting);
        cstr.base = init.inherits.prototype;
        delete init.inherits;	// Keeps it from being added to the prototype later
    }
    else {
 
        // Since we are not inheriting, then we must add event methods,
        // otherwise they will be included via inheritance.
 
        cstr.prototype.createEvents = function(/* event */) {
            for(var i = 0, len = arguments.length; i < len; i++)
                this.events[arguments[i]] = [ ];
        };
 
        cstr.prototype.listenTo = function(obj, event, handler) {
            obj.events[event].push( { handler: handler, scope: this } );
        };
 
        cstr.prototype.unlistenTo = function(obj, event, handler) {
            var ev = obj.events[event];
            // Loop down, just in case handler was added multiple times (and will be removed multiple times)
            for (var i = ev.length - 1; i >= 0; i--) {
                if (ev[i].handler === handler)
                    ev.splice(i, 1);     // Deletes one element starting at index i
            }
        };
 
        cstr.prototype.fire = function(event, data) {
            var handlers = this.events[event];
            for (var i = 0, len = handlers.length; i < len; i++) {
                var h = handlers[i];
                h.handler.call(h.scope, this, data);
            }
        }
    }
 
    // Copy init properties to the prototype (adds/overrides base class methods)
    for (var p in init)
        cstr.prototype[p] = init[p];
 
    // Return the constructor function (this is the class)
    return cstr;
 
};

The first event method, createEvents (line 22) takes a variable number of arguments. Each argument is the name of an event to be added to the class. It simply loops through each argument (line 23) and adds the name of the event to the events object (line 24). Each event is initialized to an empty array, and it’s these arrays that will hold all the handlers for a particular event.

The createEvents method should be called from the construct method of a class. I had to do it this way since each event handlers array has to be associated with a class instance, and not the class itself. So, for example, executing createEvents('onSave', 'onCancel') will add onSave and onCancel as properties to the events object (the one added in line 7). These properties are initialized as empty arrays. Since these are instance properties, each instance of the class can have a different set of handlers.

The listenTo method (line 27) is used to add an event handler to the appropriate event handler array. It takes three arguments, obj (the object that has the event to listen to), event (the name of the event to listen to), and handler (the function that will handle the event). It then adds the handler to the given event handlers array of the object that has the event (line 28). Note that this is actually pushing the handler function and this as a scope object into the handlers array. I will talk about why I am doing this when I describe the fire method.

The unlistenTo method (line 31) is used to remove a specific event handler from the list of handlers. It takes three arguments, obj (the object that has the event we are no longer interested in), event (the name of the event that the handler is to be removed from), and handler (the previously added function that is to be removed). This method first grabs the handlers list for the given event (line 32). Next, it loops backward through the handlers list (line 34). I typically loop backwards when removing items from a list. The reason is that I don’t have to adjust the index after an item is removed. If I’m on index 3, then whether or not I remove the item at index 3, the next index is always 2. If I were to loop forward, and I removed the item at index 3, then the next item to check is still index 3 (since the item at index 4 has moved down to 3). I also would need to adjust the stop index since the size of the array changed. When you loop backward, the stop index is always 0. Anyway, enough of that digression… It loops backward through the handlers list and if the function passed in matches one in the list (line 35) then it is removed (line 36). You can delete an array element with the delete keyword, but that will leave a gap in the array indexes, whereas using splice reindexes the array. Hence my use of splice over delete.

The last event method is the fire method (line 40). It takes two arguments, event (the event to be fired) and data (an object containing any data about the event). This method loops through all of the event handlers for the given event (line 42) and executes each one (line 44). This is where we need to be concerned with scope. If I were to simply call the handler function, then the this variable inside that function would point to the current object. That is, the this variable inside the handler function would point to the object that is firing the event. But what I really want is for the this variable inside the handler function to point to the object that is handling the event. That’s why I pass in the this variable as the scope object to the listenTo method when the handler is set up (line 28). Then, in the fire method, I can call the handler using that scope object, ensuring that the this variable in the handler is set to the object handling the event (line 44).

Here is a simple example of how to use this event mechanism.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var VariableSwitch = LivingMachines.Class({
    construct: function() {
        this.switchId = 'A';
        this.level = 0;
        createEvents('onLevelChanged');
    },
 
    set: function(level) {
        this.level = level;
        this.fire('onLevelChanged', level);
    }
});
 
var LightBulb = LivingMachines.Class({
    construct: function(sender, level) {
        this.brightness = 0;
    },
 
    setBrightness: function(sender, level) {
        this.brightness = level;
        alert('Brightness was set by switch ' + sender.switchId);
    }
});
 
var variableSwitch = new VariableSwitch();
var lightBulb = new LightBulb();
 
lightBulb.listenTo(variableSwitch, 'onLevelChanged', lightBulb.setBrightness);
 
variableSwitch.set(5);

I’m creating two sample classes here. The first is VariableSwitch (line 1-12). It has an switchId property so we can tell which switch it is (line 3), a level property to indicate the current level of the switch (line 4) and an onLevelChanged event that will be fired when the switch level is changed (line 5). This class also has one method, set (line 8), that will change the level of the switch (line 9) and fire the onLevelChanged event (line 10).

The second class is LightBulb (line 14-23). It has a single property, brightness (line 16) to indicate the current brightness of the bulb. It also has one method, setBrightness (line 19) that will set the brightness property to a given level (line 20) and alert the ID of the switch that caused the brightness to change (line 21). This function is an event handler. Notice the two arguments. sender is the object that sent the event, which will be an instance of VariableSwitch in our case, and level is the data object of the event.

Next I create an instance of VariableSwitch and LightBulb (lines 25 and 26). Then I attach the event handler (line 28). The lightBulb object will listen to the onLevelChanged event of the variableSwitch object. The lightBulb.setBrightness method will be executed when the event is fired.

Finally, I set the level of the variableSwitch (line 30) object. This will cause the onLevelChanged event to be fired, and the setBrightness event handler method will be executed, alerting that “Brightness was set by switch A”.

And voilĂ , JavaScript classes now have event capabilities.