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
- Ext, Angular, React, and Vue - 27. June 2019
- The Site Resurgence - 11. February 2018
- Configuring ViewModel Hierarchy - 19. June 2015
28 Responses
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.
Saki,
is there a way to use this to build a chat? So two people on two different computers could chat?
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?
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 …
Using onRender worked like a charm.
Thank you!
Yes, placing subscriptions in onRender shall work.
@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});
}
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.
@Mike,
it was a bug in fact; I’ve already fixed it in the above code. Can you re-test please?
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.
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… 🙁
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!
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.
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!
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.
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?