Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Event Framework for OO Architecture in JS

3.67/5 (2 votes)
18 May 2010CPOL2 min read 10.3K  
A reusable event framework for all your JavaScript needs.

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:

JavaScript
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:

JavaScript
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.

JavaScript
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.

JavaScript
 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.

JavaScript
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:

JavaScript
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:

JavaScript
var secondTicks = 0;

function secondIncrement() {
  secondTicks++;
  window.status = secondTicks;
}

var secondTimer = new uiTimer(1000);

secondTimer.OnTick.Register(secondIncrement);

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)