Tuesday, July 29, 2014

JavaScript Event Manager

I am making some graphical plotting widgets for website with D3.js. Overall, it's a very user-friendly tool and I highly recommend it. However, there is one thing that really bothers me - events handling. The reason I want to use this is that I have the need to synchronize data across several plot widgets. As each widget is self-contained and defined within their own namespace, I need to use events to achieve this goal. After some research I found D3.js does come with an event handling functionality - dispatch. Here is how to use it:

// create dispatch and pass in the event name
var dispatch = d3.dispatch("my_event");

// send the event
dispatch.my_event(object);

// listens to the event
dispatch.on("my_event", function(object){
    // my work
});

The code is pretty self-explanatory, and frankly speaking, quite easy to use, but there is one catch that I decide to do it myself.

If an event listener was already registered for the same type, the existing listener is removed before the new listener is added. To register multiple listeners for the same event type, the type may be followed by an optional namespace, such as "click.foo" and "click.bar".

Although it claimed differently, it is effectively saying that you just can't add multiple listeners to one event. So I quickly decided the requirements for my own events handler class:

  • Send event
  • Register/remove event listener
  • Allow multiple listeners for one event

And this is what I came up with:

// constructor
function EventDispatch () {
 // initialize parameters
 this.event_name_list = [];  // list of event names
 this.event_handle_list = {}; // container of event handles
}

// register event
EventDispatch.prototype.registerEvent = function (event_name) {
 if (event_name != null && event_name != undefined) {
  // add to event_list and event_handle_list
  // if event is not in the list
  if (!this.eventNameExist(event_name)) {
   this.event_name_list.push(event_name);
   this.event_handle_list[event_name] = [];
  }
 }
};

// send message
EventDispatch.prototype.send = function (event_name, event) {
 if (this.eventNameExist(event_name)) {
  var callbacks = this.event_handle_list[event_name];
  for (var k=0; k<callbacks.length; k++) {
   callbacks[k](event);
  }
 }
};

// register event listener
EventDispatch.prototype.registerListener = function (event_name, callback) {
 if (this.eventNameExist(event_name)) {
  this.event_handle_list[event_name].push(callback);
 }
 else {
  this.registerEvent(event_name);
  this.event_handle_list[event_name].push(callback);
 }
};

// remove event listener
EventDispatch.prototype.removeListener = function (event_name, callback) {
 if (this.eventNameExist(event_name)) {
  for (var k=0; k<this.event_handle_list[event_name].length; k++) {
   if (this.event_handle_list[event_name][k] == callback) {
    this.event_handle_list[event_name].splice(k, 1);
   }
  }
 }
};

// utility function: check even_name in event_list
EventDispatch.prototype.eventNameExist = function (event_name) {
 var is_found = false;
 for (var k=0; k<this.event_name_list.length; k++) {
  if (this.event_name_list[k] == event_name) {
   is_found = true;
  }
 }
 return is_found;
};

As a result, it works pretty well~




No comments:

Post a Comment