Introduction
This article will look at the event framework I've developed. It allows me to have a more traditional OO approach to developing JavaScript.
Background
Whilst being a client-side editor in HTML 4, I desperately needed an easy way to create events which worked in a similar way to other languages whereby multiple delegate instances can be registered with an event, causing all to be invoked when the event is raised.
Method Invocation in JavaScript
Lots of languages have Reflection libraries so you can make best use of the polymorphic nature of OO languages. JavaScript doesn't have a Reflection library, but you can invoke a method whilst controlling the context under which it is run.
The process works using the "call
" method of a function. The following example shows a simple implementation of the "call
" method:
var fTest = function() { alert('test'); }
fTest.call();
This will call the fTest
method and show the alert box.
You can also provide context when invoking the method through "call
". By context, I'm referring to the object reference as "this
" within the method body. Consider the following example:
var fTest = function(text) { alert(this.Name + ': ' + text); }
var context = { Name: 'dave' };
fTest.call(context, 'forename');
The resulting alert will display 'dave: forename'. Because the context
JSON object is referenced as this
for the method call, this.Name
returns 'dave'. Subsequent parameters are passed into the "call
" method and passed through as normal parameters.
The Code
The first section of the code improves the Array
object in JavaScript by extending it with the Add
, RemoveAt
, and IndexOf
methods.
In the event framework, array objects are used to store lists of registered actions.
Array.prototype.Add = function(obj) { this[this.length] = obj; }
Array.prototype.RemoveAt = function(index) {
var result = new Array();
var offset = 0;
for (var x = 0; x < this.length; x++) {
if (x != index)
result[result.length] = this[x];
}
return result;
}
Array.prototype.IndexOf = function(value) {
for (var x = 0; x < this.length; x++)
if (this[x] == value)
return x;
return -1;
}
The next section defines the uiEvent
object. This is the object that is instantiated to create a new event. An implementation example will be provided later in this document.
var uiEventRegistration = function() {
this.ID = null;
this.Method = null;
this.Caller = null;
}
var uiEvent = function() {
this.TimerEvent = false;
this.__registered = new Array();
this.__currentID = 0;
this.Register = function(func, caller) {
var reg = new uiEventRegistration();
reg.ID = this.__currentID;
reg.Method = func;
reg.Caller = caller;
this.__registered.Add(reg);
var returnID = this.__currentID;
this.__currentID++;
return returnID;
}
this.Deregister = function(RegistrationID) {
var index = -1;
for (var x = 0; x < this.__registered.length; x++)
if (this.__registered[x].ID == RegistrationID) {
index = x;
break;
}
if (index != -1) {
this.__registered = this.__registered.RemoveAt(index);
}
}
this.Fire = function() {
if (!this.TimerEvent)
uiTimerExecute();
var a = arguments;
for (var x = 0; x < this.__registered.length; x++) {
this.__registered[x].Method.call(this.__registered[x].Caller,
a[0] == null ? null : a[0],
a[1] == null ? null : a[1],
a[2] == null ? null : a[2],
a[3] == null ? null : a[3],
a[4] == null ? null : a[4],
a[5] == null ? null : a[5],
a[6] == null ? null : a[6],
a[7] == null ? null : a[7],
a[8] == null ? null : a[8],
a[9] == null ? null : a[9])
}
}
}
We now have a class called uiEvent
which can be used to create traditional events in JavaScript. The following example is a basic implementation of the framework.
var eventTest = new uiEvent();
var context1 = { Name: 'Dave' };
var context2 = { Name: 'Chris' };
function delegate1(mood) {
alert(this.Name + ' is ' + mood);
}
function delegate2(mood) {
alert(mood + ' is not a good thing for ' + this.Name);
}
eventTest.Register(delegate1, context1);
eventTest.Register(delegate2, context2);
eventTest.Fire('sad');
In the above example, two alerts would appear. The first would say 'Dave is sad'. The second would say 'sad is not a good thing for Chris'.
Unfair Priority
In IE UI events such as onmousemove
and onkeypress
are given higher priority than calls executed from setTimeout
or setInterval
. This created an issue when developing the animation framework (the framework will be further discussed in a later article).
When I was building a drag and drop editor, there was a heavy code base linked to the onmousemove
event. This would not always complete before the next onmousemove
event registers with the JavaScript engine. This had the effect of blocking the setInterval
call until the mouse was kept still.
To reverse the priority and force timer based events to fire first, I've included the following class in the event framework:
var uiTimers = new Array();
var uiTimerCount = 0;
function uiTimerExecute(timerID) {
for(var x = 0; x < uiTimers.length; x++)
{
if (timerID == null || uiTimers[x].TimerID == timerID) {
if (uiTimers[x].Expired()) {
uiTimers[x].OnTick.Fire();
var d = new Date();
uiTimers[x].NextTime = new Date(d.getYear(), d.getMonth(), d.getDate(),
d.getHours(), d.getMinutes(), d.getSeconds(),
d.getMilliseconds() + (uiTimers[x].Interval));
}
}
}
}
var uiTimer = function(interval) {
uiTimers.Add(this);
this.Interval = interval;
this.StartDate = new Date();
this.NextTime = new Date(this.StartDate.getYear(), this.StartDate.getMonth(),
this.StartDate.getDate(), this.StartDate.getHours(),
this.StartDate.getMinutes(), this.StartDate.getSeconds(),
this.StartDate.getMilliseconds() + (interval));
this.OnTick = new uiEvent();
this.OnTick.TimerEvent = true;
this.Expired = function() { return this.NextTime < new Date(); };
this.TimerID = uiTimerCount;
setInterval('uiTimerExecute(' + this.TimerID + ');', interval);
}
The uiTimer
class will track if a planned interval has expired, and raise an event with a higher priority than normal, as the event framework checks if it's expired instead of waiting for the setInterval
evaluation to be run.
The following is an implementation example of the uiTimer
:
var secondTicks = 0;
function secondIncrement() {
secondTicks++;
window.status = secondTicks;
}
var secondTimer = new uiTimer(1000);
secondTimer.OnTick.Register(secondIncrement);