publisher.js | |
---|---|
| |
Please use github issues to report any bugs. Pull requests welcome! | |
About Pub/Sub in JavaScriptWhen moving from bang-it-out domready code to modular development most of us start to create an unhealthy mess of object dependencies and coupling. Ideally, you don't want your objects to know about each other. They need to exist atomically, for testability, maintainability, and portability. Enter Pub/Sub and Aspect Oriented Programming. | |
Installation - Universal JavaScript support | |
| |
AMD (RequireJS) installationPlace | require(['path/to/publisher'], function (publisher) {
/* Do stuff with publisher here */
}); |
Node.js InstallationInstall with npm
Include like everything else | var publisher = require('publisher'); |
Other browser usageIf ender, jQuery, or $ are defined, publisher is assigned to it--otherwise it hangs from the global object (window). | ender.publisher
jQuery.publisher
$.publisher
publisher |
All of the usage is identical among enviornments. | |
Basic Example | |
Subscribing to a channel | |
First you "subscribe" to a channel, and provide a handler to call when the channel is published. | publisher.subscribe('onAwesome', function () {
console.log('awesome');
}); |
Publishing a channel | |
When interesting things happen in your application, you publish the channel of interest. Any attached subscription handlers will be called. In this case, the console will log "awesome". | publisher.publish('onAwesome'); |
Publish with arguments | |
You can also send along some arguments that your subscriptions may be interested in when you publish. | publisher.subscribe('onAwesome', function (one, two, foo){
console.log(one, two, foo);
});
publisher.publish('onAwesome', 1, 2, 'foo'); |
Widget example | |
The channel name is simply a string, which is often namespaced to objects in the applicaton and then the method or action they are taking. | var datepicker = {
open: function (){
/* some real code */
publisher.publish('datepicker/open');
},
select: function (){
var oldValue = this.input.value,
newValue = this.getSelected();
/* some real code */
publisher.publish('datepicker/select', newValue, oldValue);
},
close: function (){
/* some real code */
publisher.publish('datepicker/close');
}
}; |
Subscribe to all the channels | publisher.subscribe('datepicker/open', function () {
blurOtherStuff();
});
publisher.subscribe('datepicker/select', doSomeThingOnDateSelect);
publisher.subscribe('datepicker/close', theForm.submit.bind(theForm)); |
Creating Individual Publishers | |
While | var datepicker = publisher({
open: function (){ |
| this.publish('open');
},
select: function (){
this.publish('select', newValue, oldValue);
},
close: function (){
this.publish('close');
}
}); |
| datepicker.subscribe('open', doSomethingOnOpen); |
This pattern makes extending the behavior of an object trivial. | |
It's a lot like DOM events ... | |
Here we make the window a publisher and publish the 'load' channel in its
| publisher(window);
window.onload = (function (){
var start = +new Date;
return function (){
var loadTime = +new Date - start;
this.publish('load', loadTime);
}
}());
window.subscribe('load', function (loadTime){
console.log('loaded in ', loadTime, 'ms');
}); |
Binding the context of handlers to other objects | |
Subscription handlers are naturally bound to the object. We can verify this behavior in our previous examples. | publisher.subscribe('onAwesome', function () {
console.log(this === publisher); //> true
});
datepicker.subscribe('open', function () {
console.log(this === datepicker); //> true
}); |
Sometimes you may want to change the context of the method, | var context = {};
publisher.subscribe('onAwesome', function () {
console.log(this === context); //> true
}, context); |
It's most useful when calling the method of another object | publisher.subscribe('onAwesome', datepicker.open, datepicker); |
Subscription ObjectsThe
| |
Store the subscription in a variable | var subscription = publisher.subscribe('foo', function () {
console.log('hello');
}); |
console logs 'foo' | publisher.publish('foo'); |
Detach the subscription and console logs nothing | subscription.detach();
publisher.publish('foo'); |
Attach the subscription and things are back to normal | subscription.attach();
publisher.publish('foo'); |
The methods return the subscription object so you can detach upon creation | var subscription2 = publisher.subscribe('foo', function () {
console.log('foo 2');
}).detach(); |
Advising Objects, 100% Decoupled ApplicationsMost pub/sub systems require your objects to know about the publisher. As
with all of the previous examples, we had to reference the publisher object
within our modules. The goal of pub/sub is to decouple our objects--and
requiring every module to know about With | |
While doing this to a built-in isn't really advised, it makes for a great example. | |
publisher.advise and advisor methods | |
First we create an advisor object (for those in the know, we're using Aspect Oriented Programming techniques to achieve this functionality). | var adviseMath = publisher.advise(Math); |
Advisor objects have two methods, | adviseMath.before('pow', 'math:before:pow');
adviseMath.after('min', 'math:after:min'); |
The arguments to the method being published "before" it's called are passed into the subscription handler, and the final argument is a reference to the object. | publisher.subscribe('math:before:pow', function (x, y, mathReference) {
console.log(x, y);
console.log(mathReference === Math); //> true
}); |
The arguments to the subscription handler for methods being published "after" they are called are the return value of the method a reference to the object. | publisher.subscribe('math:after:min', function (returns, mathReference) {
console.log(returns);
}); |
This is an incredibly powerful pattern for connecting the modules in your application. | |