Blog.

Ext.ux.MsgBus Plugin

Code in this post can be obsolete, however, principles and theory may still apply.
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

saki
Follow me:
Latest posts by saki (see all)

28 Responses

  1. 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.

  2. 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?

  3. 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 …

  4. @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});
    }

  5. 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.

  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!

  7. 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.

  8. 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!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Enter your username and password to log into your account. Don't have an account? Sign up.

Want to collaborate on an upcoming project?