publisher-guide.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) installation | |
Place | 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 usage | |
If neither AMD nor Node.js are detected, publisher is global with a
| publisher
publisher.noConflict(); |
All of the usage is identical among environments. | |
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 | |
Advising Math | |
While doing this to a built-in isn't really advised, it makes for a great example. First we create an advisor object. | var adviseMath = publisher.advise(Math); |
Advisor objects have two methods, | |
advise before | |
Before | adviseMath.before('pow', 'math:before:pow'); |
The subscription handler gets the object being advised ( | publisher.subscribe('math:before:pow', function logPowStuff(math, x, y) {
console.log(x, y);
console.log(math === Math);
}); |
The logPowStuff function gets called. The console logs | Math.pow(2,3); |
advise after | |
After | adviseMath.after('min', '√'); |
The subscription handler gets the object being advised ( | publisher.subscribe('√', function logReturns(math, returns, x) {
console.log(returns);
}); |
logReturns gets called and the console logs 5. | Math.sqrt(25); |
You can advise multiple methods at a time by sending an object of key:value pairs. You can also chain the advisor object. | publisher.advise(Math).before({
pow: 'math:before:pow',
max: 'math:before:max'
}).after({
min: 'math:after:min',
sqrt: '√'
}); |
This is an incredibly powerful pattern for connecting the modules in your application. | |