Observable
Observable
is a JavaScript implementation of the observer
pattern (also known as
‘publish/subscribe’), modelled on Ruby’s Observable
module.
// In the browser JS.require('JS.Observable', function(Observable) { ... }); // In CommonJS var Observable = require('jsclass/src/observable').Observable;
In JavaScript, this pattern can be made more flexible due to the fact that functions are first-class objects, and are easier to work with than lambdas and procs in Ruby. In this implementation, the listeners/observers are functions, rather than objects.
This module is similar to the ‘custom events’ found in other JS libraries. Most browser scripting is based on the observer pattern, and this module attempts to make the pattern as general-purpose as possible by making any object capable of being observed by others, not just special ‘event’ objects.
Setting up a publisher
A publisher (or ‘observable’) is any object that needs to be able to tell the world when something interesting happens to it, so that other objects can listen for such announcements and respond however they see fit. Let’s imagine we’re running a magazine:
var Magazine = new Class({ include: Observable, initialize: function(name) { this.name = name; this.issues = []; }, publishIssue: function() { var issue = new Issue(this); this.issues.push(issue); this.notifyObservers(issue); } }); var Issue = new Class({ initialize: function(publisher) { this.publisher = publisher; } });
And, let’s create an object to represent our magazine:
var mag = new Magazine('JavaScript Monthly');
So, we have a magazine that can publish issues. Whenever it does so, it calls
this.notifyObservers(issue)
, which will send the issue
object to any
object that’s subscribed to the magazine.
Setting up subscribers
A ‘subscriber’ is simply a callback function that is assigned to observe an object and fire when the observed object notifies its observers. A simple example might be:
mag.addObserver(function(issue) { // do something with the new issue });
This function will be called whenever mag
publishes a new issue. A more
complex example could involve objects attaching their methods to observe an
object:
var Reader = new Class({ receiveIssue: function(issue) { if (this.likes(issue.publisher)) this.read(issue); else this.throwIssueInTrash(issue); }, likes: function(magazine) { return /javascript/i.test(magazine.name); }, read: function(issue) {}, throwIssueInTrash: function(issue) {} }); var person = new Reader(); mag.addObserver(person.method('receiveIssue'));
So now person
will be notified when a new magazine is out.
An optional second argument to addObserver()
specifies the execution context
for the listener function. For example, the above could be restated as:
mag.addObserver(person.receiveIssue, person);
Observers can be removed, so long as you specify the exact same function and context used to set up the observer:
// Works... mag.addObserver(person.method('receiveIssue')); mag.removeObserver(person.method('receiveIssue')); // Works... mag.addObserver(person.receiveIssue, person); mag.removeObserver(person.receiveIssue, person); // Does not work - functions are different objects mag.addObserver(person.receiveIssue, person); mag.removeObserver(person.method('receiveIssue')); // Does not work - context missing mag.addObserver(person.receiveIssue, person); mag.removeObserver(person.receiveIssue);
This may seem like an annoyance, but JavaScript has no way of telling that two different function objects (or the same function in different contexts) might be somehow related, which is why this scheme is so strict.
And finally
A couple of points worth knowing:
addObserver()
is aliased assubscribe()
.removeObserver()
is aliased asunsubscribe()
.- Calling
mag.removeObservers()
removes all observers from the object.