2012.01.16

Signal Emitter

When I released JS-Signals I decided to create a document explaining the difference between different kinds of Observers and the possible pros and cons of each pattern, and as you can see on the document every approach has its pros/cons and depending on the scenario the recommended approach might change.

Before coding JS-Signals I was using a very basic EventEmitter “class” that could be used to listen/dispatch arbitrary events but ever since I released JS-Signals I almost didn’t used arbitrary events anymore (because of the benefits of using a Signal), but a couple weeks ago I had to propagate changes on my model classes to the UI and the changes are coming from many different inputs, so the easiest way to keep everything in sync was to dispatch events every time my model objects updated with a new value. In that case it is way easier to use a string ID for the event type than to create a new Signal object manually for each value, since the project was already using Signals everywhere I decided to code a simple EventEmitter that would use JS-Signals internal mechanism (so I could use the advanced features if needed) but still allow arbitrary event types.

define(['signals'], function (signals) {

    /**
     * Signal Emitter
     * JS-Signals wrapper to convert it into a regular Event Emitter, which can
     * lister/dispatch arbitrary events.
     * @author Miller Medeiros
     * @version 0.1.1 (2012/01/23)
     * @license WTFPL
     */
    function SignalEmitter(){
        this._signals = {};
    }

    var _proto = SignalEmitter.prototype = {

        addListener : function(id, handler, scope, priority) {
            if (! this._signals[id]) {
                this._signals[id] = new signals.Signal();
            }
            return this._signals[id].add(handler, scope, priority);
        },

        removeListener : function(id, handler) {
            var sig = this._signals[id];
            if (! sig) return;
            sig.remove(handler);
        },

        getSignal : function(id) {
            return this._signals[id];
        },

        dispatch : function(id, args) {
            var sig = this._signals[id];
            if (! sig) return;
            if (args) {
                sig.dispatch.apply(sig, args);
            } else {
                sig.dispatch();
            }
        }

    };

    SignalEmitter.augment = function(target) {
        SignalEmitter.call(target);
        for (var key in _proto) {
            if (_proto.hasOwnProperty(key)) {
                target[key] = _proto[key];
            }
        }
    };

    return SignalEmitter;

});

You can use it as a standalone object:

var obj = new SignalEmitter();
obj.addListener('foo', console.log, console);
obj.dispatch('foo', ['bar']);

Or extend an existing object to add the SignalEmitter behavior to it:

var obj = {
    doSomething : function(args){
        this.dispatch('foo', ['lorem', 'ipsum']);
    }
};
//"inherit" from SignalEmitter object
SignalEmitter.augment(obj);

obj.addListener('foo', console.log, console);
obj.doSomething();

The method addListener returns a SignalBinding object and you can also get a reference to the underlaying Signal object by calling getSignal(id) so you will have access to all the advanced features from JS-Signals while keeping a very flexible structure.

Feel free to remove unneeded features like SignalEmitter.augment() or getSignal(), added more as a reference and because on my current project I needed both things.

Very simple code but may be useful to somebody else…

PS: please note that listening/dispatching non-existent events will fail silently (one of the main reasons why I started using Signals), use with care.


Comments

So If I understand well you have one event kind/one execution path for each possible update in your model/UI ?

Did you consider using data-binding/data-linking ? Although I have yet to see a good implementation; It may just be impossible with the current version of javascript :(

@Alex.G you can implement one-way data-binding "easily" with events, at each change on your model class you dispatch a event and propagate this change to any object listening to the event and/or trigger a new render.

On my current project each property change dispatches 2 events, a generic changed signal passing the property id and value and also a specific event to each property (which could be "namespaced" like Backbone.js change:foo), that way you can have a centralized data-binding controller (which checks all elements on the page that contain a specific attribute and updates it's value) or have granular control of each change and update the UI manually.

For advanced data-binding you can use Ember.js or Angular.js, if you just need some basic structure Backbone.js may be a good option. (I'm not using any of these tho) Or you could simply dispatch events at each change on your own models..

Cheers.

Indeed a data binding is little more than adding an event listener and adding syntactic sugar on top of it.

IMO, Those frameworks you listed don't achieve a nice synthax for data binding thus I really doubt they are worth using by someone who has experience;

By the way, You blogged about a few techniques that seem useful for enterprise development (single entry point, signals, component approach) but what about your entire workflow when working on a complex app ? If i'm not mistaken you don't use any of those "do-it-all-and-more frameworks" so you must have some favorite way to wire things up (model and widgets, maybe controllers too) ? I guess you use AMD for that :)

[…] are basically an Object that creates/dispatches multiple Signals types on-demand and usually use Strings to define the message […]

Leave a Comment

Please post only comments that will add value to the content of this page. Please read the about page to understand the objective of this blog and the way I think about it. Thanks.

Comments are parsed as Markdown and you can use basic HTML tags (a, blockquote, cite, code, del, em, strong) but some code may be striped if not escaped (specially PHP and HTML tags that aren't on the list). Line and paragraph breaks automatic. Code blocks should be indented with 4 spaces.