Ext.ux.MsgBus Plugin

20. September 2009 – 14:45

Recently I’ve been looking for a better way of inter-component communication in Ext so I’ve read a couple of posts with Message Bus implementations, I’ve skimmed over OpenAjax Hub page but I haven’t found anything suitable for my needs.

The first approach was Simple Message Bus Example but that is more concept proof than a really usable way in production environment.

I like the concept of message subjects (topics) as dot separated sequence of tokens with the ability to subscribe to messages with specific subjects and with wildcard support.

For example, if a component would subscribe to the subject eu.extjs.desktop.** it would receive message with subject eu.extjs.desktop.wallpaper.set but it wouldn’t receive message eu.extjs.taskbar.hide.

So this plugin was born.

Ext.ux.MsgBus fits in any component that is descendant of Ext.util.Observable and it does not need any other changes/overrides. You would stick it only into the components that must participate in bus messaging. It adds subscribe and publish methods to the component.

Usage example:

var p = new Ext.Panel({
     plugins:['msgbus']
    ,onWallpaper:function(subject, message) {
        // do something
    }
    // the rest of config
});
p.subscribe('eu.extjs.desktop.wallpaper.**', {fn:p.onWallpaper, single:true});

The above would call p.onWallpaper callback once upon the receipt of a “wallpaper” message.

Other example:

p.publish('eu.extjs.this.panel.move', {oldx:100, oldy:200, x:300, y:400});

I haven’t tested it fully yet so take it more as an initial idea than as a bullet-proof, worldwide-tested code. Also, I didn’t try in any means to implement OpenAjax standards and use this plugin for an inter library communication. Consider it as a one possibility of intra-Ext, inter-component communication.

Any comments and/or bug reports are welcome.

// vim: sw=4:ts=4:nu:nospell:fdc=4
/**
 * Message Bus Plugin
 *
 * @author    Ing. Jozef Sakáloš
 * @copyright (c) 2009, by Ing. Jozef Sakáloš
 * @date      19. September 2009
 * @version   $Id: Ext.ux.MsgBus.js 29 2009-09-23 09:51:55Z jozo $
 *
 * @license Ext.ux.MsgBus.js is licensed under the terms of the Open Source
 * LGPL 3.0 license. Commercial use is permitted to the extent that the 
 * code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * License details: http://www.gnu.org/licenses/lgpl.html
 */
 
/*global Ext,window */
 
/**
 * @class Ext.ux.MsgBus
 *
 * Creates new Ext.ux.MsgBus object
 * @constructor
 * @param {Object} config The config object
 */
Ext.ux.MsgBus = function(config) {
    Ext.apply(this, config, {
    });
}; // eo constructor
 
Ext.override(Ext.ux.MsgBus, {
    /**
     * @cfg {String} busName Name of the global Observable instance
     */
     busName:'Ext.ux.Bus'
    /**
     * @private
     */
    ,bus:false
    /**
     * Initializes the plugin and component
     * @private
     */
    ,init:function(cmp) {
        this.cmp = cmp;
        cmp.bus = this.getBus();
        cmp.bus.addEvents('message');
        cmp.subs = {};
        this.applyConfig();
    } // eo function init
    // {{{
    /**
     * Returns or creates the global Observable instance
     * @private
     */
    ,getBus:function() {
        var bus = window;
        var a = this.busName.split('.');
        var last = a.pop();
        Ext.each(a, function(n) {
            if(!Ext.isObject(bus[n])) {
                bus = false;
                return false;
            }
            else {
                bus = bus[n];
            }
        }, this);
        if(false === bus) {
            Ext.ns(this.busName);
            return this.getBus();
        }
        if(!(bus[last] instanceof Ext.util.Observable)) {
            bus[last] = new Ext.util.Observable();
        }
        return bus[last];
    } // eo function getBus
    // }}}
    // {{{
    /**
     * Creates RegExp for message filtering.
     * Override it if you need another logic.
     * @param {String} subject The message subject
     * @return {RegExp} RegExp used for message filtering
     */
    ,getFilterRe:function(subject) {
        var a = subject.split('.');
        var last = a.length - 1;
        a[last] = '**' === a[last] ? '.*' : a[last];
        var re = /^\w+$/;
        Ext.each(a, function(token, i) {
            if(!re.test(token) && '*' !== token && '.*' !== token) {
                throw 'Invalid subject: ' + subject;
            }
            if('*' === token) {
                a[i] = '\\w+';
            }
        });
        return new RegExp('^' + a.join('\\.') + '$');
    } // eo function getFilter
    // }}}
    // {{{
    /**
     * Applies new methods to the component
     * @private
     */
    ,applyConfig:function() {
        Ext.applyIf(this.cmp, {
            /**
             * Subscribes to messages (parent component method)
             * @param {String} subject Dotted notation subject with wildcards.
             * See http://www.openajax.org/member/wiki/OpenAjax_Hub_2.0_Specification_Topic_Names
             * @param {Object} config Same as addListener config object
             * @return {Boolean} success true on success, false on failure (subscription exists)
             */
             subscribe:function(subject, config) {
                 var sub = this.subs[subject];
                if(sub) {
                    return false;
                }
                config = config || {};
                config.filter = this.getFilterRe(subject);
                this.subs[subject] = {config:config, fn:this.filterMessage.createDelegate(this, [config], true)};
                this.bus.on('message', this.subs[subject].fn, config.scope || this, config);
                return true;
            }
 
            /**
             * Unsubscribes from messages (parent component method)
             * @param {String} subject Dotted notation subject with wildcards.
             * @return {Boolean} success true on success, false on failure (nonexistent subscription)
             */
            ,unsubscribe:function(subject) {
                var sub = this.subs[subject];
                if(!sub) {
                    return false;
                }
                this.bus.un('message', sub.fn, sub.scope || this, sub.config);
                delete this.subs[subject];
                sub = null;
                return true;
            } // eo function unsubscribe
 
            /**
             * Publishes the message (parent component method)
             * @param {String} subject Message subject
             * @param {Mixed} message Message body, most likely an object
             */
            ,publish:function(subject, message) {
                this.getFilterRe(subject);
                this.bus.fireEvent('message', subject, message);
            } // eo function publish
 
            /**
             * Returns current subscriptions
             * @return {Object} subscriptions
             */
            ,getSubscriptions:function() {
                return this.subs;
            } // eo function
 
            /**
             * @private
             */
            ,getFilterRe:this.getFilterRe
 
            /**
             * Filters incoming messages
             * @private
             */
            ,filterMessage:function(subject, message, config) {
                if(config.filter.test(subject)) {
                    (config.fn || this.onMessage).call(config.scope || this, subject, message);
                }
            } // eo function filterMessage
 
            /**
             * Default message processing function
             * @param {String} subject The message subject
             * @param {Mixed} message The message body
             */
            ,onMessage:Ext.emptyFn
        });
    } // eo function applyConfig
    // }}}
 
}); // eo override
 
// register ptype
Ext.preg('msgbus', Ext.ux.MsgBus); 
 
// eof

Enjoy!

The accompanying example: Ext.ux.MsgBus


StumbleUpon Toolbar
  1. 19 Responses to “Ext.ux.MsgBus Plugin”

  2. Great idea Saki. I love your blog and examples. I had a question though. What advantages does this give you over using Ext\’s built in custom event functions?

    By Charles Himmer on Sep 21, 2009

  3. Selective event listening. If you listen to global events (bus events) you can ignore maybe 90% of them. With MsgBus you can say which “category” you process.

    By Saki on Sep 21, 2009

  4. Saki, this plugin rules.

    Question: How do I set the subscription during the initComponent function?
    ie:
    ,initComponent:function() {
    var config = {
    bla bla bla
    Ext.apply(this, Ext.apply(this.initialConfig, config));
    bdApp.appVW.superclass.initComponent.apply(this, arguments);
    this.subscribe(\’bdApp.mainMenu.nodeClick\’);
    } // eo function initComponent

    The subscription fails (this.subscribe is not a function
    [Break on this error] this.subscribe(\’bdApp.mainMenu.nodeClick\’);\\n)

    TIA!

    By Jorge Mariani on Sep 22, 2009

  5. Plugins are initialize after the initComponent runs. You can subscribe at the end of constructor override. If you want to know more details post please a forum query and send me the link to the post.

    By Saki on Sep 22, 2009

  6. Love the plugin. I want to attach it to a Ext.data.Store, which (not being a component) does not have a “plugins” list.

    It seemed to work when I did this:

    var q = new Ext.ux.MsgBus;
    q.init(ds_batches);
    ds_batches.subscribe(’foobar’, {fn: ds_batches.onFoobar});

    I want to use messages to let a form tell a data-store that a new record needs to be posted and maybe many other things.

    schweet!

    By Mike Robinson on Sep 22, 2009

  7. P.S. I faithfully copied the “single: true” from your example but it seemed to have no particular effect. I therefore not quite sure why you had it in your example. If there WAS a particular reason for it, please advise.

    By Mike Robinson on Sep 22, 2009

  8. To tell truth, I haven’t tested it thoroughly, but the listener should be installed with options from config. I need to debug if not… :(

    By Saki on Sep 22, 2009

  9. @Mike,

    it was a bug in fact; I’ve already fixed it in the above code. Can you re-test please?

    By Saki on Sep 23, 2009

  10. great job saki! really!
    But I have got a little problem. I would like to use msgTarget = “side” to notify validation error to the user. How can I fix it? Thank you in advance for posting answer.

    By jeu simulation on Sep 25, 2009

  11. @Jorge Mariani
    You can also put subscription code in onRender part of your extension:

    Eg:
    onRender: function(){
    MyComponent.ContentPane.superclass.onRender.apply(this, arguments);
    this.subscribe(\\\’App.MainMenu.nodeClick\\\’, {fn:this.nodeClick});
    }

    By Sumit on Sep 26, 2009

  12. Yes, placing subscriptions in onRender shall work.

    By Saki on Sep 28, 2009

  13. Using onRender worked like a charm.

    Thank you!

    By Jorge Mariani on Oct 16, 2009

  14. Nice plug-in! I will probably use it in one of my projects. Therefore I would need some information: is it possible to add the publish and un-/subscribe methods to the prototype instead to each object instance? Or do you think that\\\’s not a good idea …

    By dixon on Apr 7, 2010

  15. Great plugin, it matched my needs exactly. I just had one question: before a subscriber component is destroyed, should it unsubscribe from all messages? It seems to me as if this is necessary to avoid orphaned subscriptions being left in the bus.

    If it is neccessary, this seems to do the trick :

    for (subject in this.subs) {
    result = this.unsubscribe(subject);
    }

    Maybe it could be added to a function called on the beforedestroy event, as part of the plug in?

    By Plant on Apr 19, 2010

  16. Saki,
    is there a way to use this to build a chat? So two people on two different computers could chat?

    By Michael on May 8, 2010

  17. If you attach plugin to Ext.util.Observable or to your class, that extends Observable, it does not work. I wanted it for my core classes which just do some work, they do not appear visually. But ended up with changing parent for these classes to Ext.Component in order to get it working OK.

    By Konstantin on Jun 18, 2010

  18. re: Konstantin

    I think that is a limitation of Ext, since direct Observable descendents to not take the “plugin” property. tried it on a RowSelectionModel instance and it failed as well.

    By joe on Aug 13, 2010

  19. Very useful stuff indeed. My approach of what I started today before i found your blog post was an event broker component that would pool all events that are relayed to it. Although I do prefer your solution of a message bus.

    However, my approach was hooking the necessary code right into Ext.util.Observable, so that all objects that are descendants of Observable could communicate (right now anything thats not of the Component class won\’t accept plugins). However, I failed to override Ext.util.Observable properly, my skillz are not at leet ;)

    I\’d love to any pointers by anyone on how to override Ext.util.Observable properly, since it seems to be quite different to overriding Component, to fit the code into the override and have the messagebus be accessible by any descendant of Observable.

    By joe on Aug 13, 2010

  20. Take a look at forum and/or learn section of http://sencha.com/learn Hint: override constructor.

    By Saki on Aug 16, 2010

Post a Comment

This is a captcha-picture. It is used to prevent mass-access by robots. (see: www.captcha.net)

You must read and type the 5 chars within 0..9 and A..F, and submit the form.

  

Oh no, I cannot read this. Please, generate a