Sending messages between Javascript objects

Apr 23 2009 Published by Eneko Alonso under uncategorized

When applications (or websites) get pretty big, it usually happens that a lot of different objects or classes interact with each other or, even worst, depend on each other to achieve the site functionality.

A simple example could be a Tabs manager class, which controls a set of tabs in the page. Sometimes, the content in this tabs is sensitive to when the tabs are shown or hidden. At this point you have three options: either the Tabs class needs to know about the content or you create a set of callback functions or you use custom events. Still, for every one of these options you are going to need variables referencing the instances of your classes (this is: var myTabs = new Tabs())

Personally, I don’t like having global variables for the objects I create. I think the code gets very messy and complicated.

So, after dealing with all these issues at work day after day I thought it would be very cool to have a messaging system between my classes, the same way other languages like Cocoa do (in a more simpler way, at least for now).

Sending messages between classes

Wouldn’t it be nice if the Tabs class would send a message every time a new tab is selected? Wouldn’t it be nice if the objects that are going to reside inside those tabs could listen to that message and update accordingly?

To do this, I created a Base class and a Register, where my objects will register automatically upon instantiation (thanks to the Base class).

  1. var InstanceRegister = new Class({
  2.   instances: [],
  3.   addInstance: function(object) {
  4.     this.instances.push(object);
  5.   },
  6.   broadcastMessage: function(msg) {
  7.     $A(this.instances).each(function(item, index) {
  8.       item.processMessage(msg);
  9.     });
  10.   }
  11. });
  12. var Register = new InstanceRegister();
  13.  
  14. var Base = new Class({
  15.   ClassName: 'Base',
  16.   Implements: Options,
  17.   initialize: function(options) {
  18.     this.setOptions(options);
  19.     Register.addInstance(this);
  20.   },
  21.   processMessage: function(msg) {
  22.     console.log(this.ClassName, ' msg received: ');
  23.     console.dir(msg);
  24.   },
  25.   sendMessage: function(msg) {
  26.     Register.broadcastMessage(msg);
  27.   }
  28. });

As you can see, the register is just an array of Instances. Yes, Register is a global object, but it will be the only one we will need. Meanwhile, the Base class adds the new instance to the Register automatically. For convenience, the Base class has a sendMessage method. Also, for convenience, I like to add a ClassName property to all my classes in case I need it later for logging, instance class type identification, etc.

Now, thanks to MooTools, we can create our classes and make them inherit from Base. We can override the processMessages method and act accordingly:

  1. var Tabs = new Class({
  2.  ClassName: 'Tabs',
  3.  Extends: Base,
  4.  …
  5.  activateTab: function(tab) {
  6.   …
  7.   this.sendMessage({msg:'tabActivated', data:tab});
  8.  }
  9. });
  10.  
  11. var VideoPlayer = new Class({
  12.  ClassName: 'VideoPlayer',
  13.  Extends: Base,
  14.  …
  15.  processMessage: function(msg) {
  16.   if (msg.msg == 'tabActivated' && msg.data = 'video-tab') {
  17.    // Play video
  18.   }
  19.  }
  20. });

The beautifulness of this code resides, as I mentioned before, in the absence of any object references. Tabs doesn’t know anything about the VideoPlayer class nor any other object listening to the messages. And the VideoPlayer doesn’t know anything about the Tabs class either. It only knows about the message it has to listen to. No object references, no methods, no callbacks.

Pay attention now. This is the best part! Whenever we instance our classes, no matter how many times we do, we don’t need variable references anymore:

  1. new Tabs();
  2. new VideoPlayer();

Nice, uh?

Since I implemented this class I use it a lot on every project at work. It makes my life easier, keeping my classes simple and independent from each other. It’s even better. We have got to a point where we have integrated this feature with our Flash components, so these send messages to the Register when needed and any JS object listening reacts to them as needed.

By doing this, even being unidirectional (Flash to JS only), we have achieved something great: from now on, our Flash objects don’t need to know anything about our JS objects at all. Just send a message to the register and done.

At any point, even from Firebug, you can send a message to the Register. This is very nice, even for debugging:

  1. Register.broadcastMessage({msg:'hello',data:'world'});

A listener class

I found it is very nice, while in development, to have a Listener class debugging any message received. This way I don’t need to add a console.log call on all my classes to output received messages. The Listener will do it for me:

  1. var Listener = new Class({
  2.  Extends: Base,
  3.  initialize: function() {
  4.   this.parent();
  5.   this.ignoreMessages = false;
  6.  },
  7.  processMessage: function(msg) {
  8.   if (msg.msg == 'stopListener') this.ignoreMessages = true;
  9.   if (msg.msg == 'startListener') this.ignoreMessages = false;
  10.   if (this.ignoreMessages) return;
  11.   console.log('Listener: msg received:');
  12.   console.dir(msg);
  13.  }
  14. });
  15. new Listener();

Even you can control the Listener by sending custom messages to it! Awesome!

Could this be achieved with custom events?

With Mootools, creating custom events is piece of cake. But from what I understand, custom events work only inside the object that is firing them. This is, if object A fires an event ‘customEvent’, you can only listen to it if you add a callback function when you instantiate that class:

  1. var A = new Class({
  2.   Implements: Events,
  3.   initialize: function() {
  4.     document.body.addEvent('click', function() {
  5.       console.log('A: body clicked');
  6.       this.fireEvent('bodyClicked', [this]);
  7.     }.bind(this));
  8.   }
  9. });
  10.  
  11. var B = new Class({
  12.   doSomething: function() {
  13.     console.log('B: doing something…');
  14.   }
  15. });
  16.  
  17. new A({
  18.   onBodyClicked: function() {
  19.     // Tell B to do something (you need a reference to B!)
  20.   }
  21. });
  22. new B(); // Will never do something

Ideally, B should be able to listen to the bodyClicked event without passing any info when A is instantiated. But if I’m not mistaken, this is not possible.

Revised version

I decided that it would be cool to have the Messenger as something my classes could implement, instead of inherit from (Base). Also, it would be good if one object could register for a specific message only, which would improve performance too.

  1. var NotificationServiceCenter = new Class({
  2.  instances: {},
  3.  addObserver: function(msg, instance) {
  4.   if (!this.instances[msg]) {
  5.    this.instances[msg] = [];
  6.   }
  7.   this.instances[msg].push(instance);
  8.  },
  9.  broadcastMessage: function(msg, data) {
  10.   if (this.instances[msg]) {
  11.    $A(this.instances[msg]).each(function(item, index) {
  12.     item.processMessage(msg, data);
  13.    });
  14.   }
  15.  }
  16. });
  17. var NotificationService = new NotificationServiceCenter();
  18.  
  19. var Messenger = new Class({
  20.  addMsgEvent: function(msg) {
  21.   NotificationService.addObserver(msg, this);
  22.  },
  23.  sendMessage: function(msg, data) {
  24.   NotificationService.broadcastMessage(msg, data);
  25.  },
  26.  processMessage: function(msg, data) {
  27.   // For implementation on the final Class
  28.  }
  29. });
  30.  
  31. var A = new Class({
  32.  Implements: Messenger,
  33.  initialize: function() {
  34.   document.body.addEvent('click', function(){
  35.    console.log('A: document body clicked');
  36.    this.sendMessage('documentbodyclicked', 'Hello world!');
  37.   }.bind(this));
  38.  }
  39. });
  40.  
  41. var B = new Class({
  42.  Implements: Messenger,
  43.  initialize: function() {
  44.   this.addMsgEvent('documentbodyclicked');
  45.  },
  46.  processMessage: function(msg, data) {
  47.   console.log('B: msg received: ', data);
  48.  }
  49. });
  50.  
  51. new A();
  52. new B();

Here you go!

2 responses so far

Gravity in the DOM

Dec 18 2008 Published by Eneko Alonso under uncategorized

A couple of days ago I created a demo to simulate planets on a 2D universe interacting each other like Gravity does in our lives. To compute the movements every planet had to be evaluated with each other on every step. Their position will be affected by the other planet position and in proportion to their respective mass. This means the speed of the planets doesn’t impact the attraction between them.

Today I decided to extend this demo a little bit more and I added a few things:

  • Mass parameter to make mass independent from size.
  • Collision detector to merge planets when they meet each other with color effect.
  • Orbit trails

Gravity

The result is very nice. The first option make it possible to create bigger planets with more mass but with a size that fits appropiately on the screen. The second option merged planets that collide, making it more like a real world. Finally, the orbit trails are just some sugar to see the trajectory followed by the plantes. To avoid the screen getting very messy, the trails clean up themselves after 25 seconds.

As you can see, the universe controller uses the Thread class I was talking about few days ago.

See both demos here:
http://dev.enekoalonso.com/research/gravity.php
http://dev.enekoalonso.com/research/gravity-2.php

PS: Please note there is no Canvas involved here. All objects on screen are DOM elements. Of course this is not the best way to do graphic animations on the web, but it is fun!

No responses yet