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

Custom Events Using JavaScript

4.00/5 (1 vote)
18 May 2010CPOL3 min read 11.9K  
Describes a framework for creating events using JavaScript.

Introduction

This article will cover the event framework. There will be an explanation of the framework and an implementation example.

Background

When producing a drag and drop editor, I needed an event framework that would allow me to register multiple delegates with an event (excuse the .NET terminology). The following is the result of that requirement.

This framework also includes code for implementing timers. I'll explain why this was required in the "Points of Interest" section.

The Code

The following is the code for the event framework.

An Easy Life with Prototyping

Collections in most frameworks include methods for adding and removing items from the collection. JavaScript doesn't by default, but they can be added by using prototyping. The first section of this framework prototypes on top of Array to extend it with the Add, RemoveAt, and IndexOf functions.

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; }
var uiEventRegistration = function() {
  this.ID = null;
  this.Method = null;
  this.Caller = null;
}

The following code creates a framework for using timers. There is a timer array for holding all the created timers. A function called "uiTimerExecute" is used to check the timer expired states. There is also the uiTimer class which recieves an interval (in milliseconds) in the constructor.

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 last section is the main section of the event framework. There is a class called uiEvent. This has a method "Register". This is passed a delegate and a context object. The context object is the object referred to as "this" when the delegate is called by the method. Register returns a registration ID. This can be used with the "Deregister" method to remove the delegate from the event stack. The last method is "Fire". When this is called, all parameters passed in are then sent as parameters to the registered delegates.

JavaScript
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])
    }
  }
}

For the Fire event, there were two possible approaches. When a function is passed as a parameter, you can execute the "call" method on the function. This has the same effect as invoking through Reflection. The second option was to use eval to call the method. eval is considerably slower than using the "call" method. When using the "call" method, there is a limitation. The parameters for call need to be explicitly included in the call. This means there is a fixed limit to the number of parameters an event can handle. The good news is the framework can be extended to handle as many as required. The call above includes parameters such as: a[0] == null ? null : a[0].

By continuing past index position 9, you can extend the parameter count limitation as far as required for your implementation.

Using the Code

The following is one of the most basic implementations of the framework. Try the following code:

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');

The following message boxes will be displayed:

  • "Dave is sad"
  • "sad is not a good thing for Chris"

Points of Interest

There are base events within the DOM such as onmousemove, onkeypress. You would assume that with a single thread, all code stacks would be assigned the same priority and processed in order. This is not the case, e.g., if you have a mouse move event which is quite an intensive process. If a subsequent mouse move event fires before the previous finishes (no idle time on the thread), the mouse move event will always be given priority over executions planned using setInterval and setTimeout. This became an issue when implementing the Animation Framework, that will be described in a later document.

Therefore in this framework, I have created a timer event. When a non-timer event fires, it checks all the registered timer events to see if they have expired, and if so, they get executed first. The following is an example implementation of the timer event.

This example implements a second timer which increments a count every second and updates the window status.

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)