Recently a friend asked "Can you use Require.js with PubSub?", and since I've just done exactly that on a project I thought it was worth writing up a simple example. The Publish-Subscribe (PubSub) pattern is a useful way to pass event triggers and data around an application without having to explicitly wire the origin and the target together. A PubSub mechanism, sometimes called an Event Bus, is used to publish data to named topics, and then any other part of the code can subscribe to those topics and receive any data that is published.
In this way, for example, an AJAX API can fetch data from a server and publish the results out to your Event Bus without needing to know where the data has to go - anything that is interested in it will pick it up.
The complication in this case is that the AMD module coding style that Require.js uses prefers splitting your code up into small chunks in individual files, and its maybe not clear how an PubSub mechanism would work across all these disparate parts. The good news is, its not too difficult!
I've created a simple example of a shopping list application which fetches the ingredients you need to buy from an API, and posts back to the API when you've picked up each thing. We're making eggy bread, because that's what my daughters asked for for breakfast yesterday.
The demo consists of an HTML file, which displays the shopping list and has appropriate buttons to trigger GET and POST behaviour on the API, and four Coffeescript files.
I'm using Knockout as a viewmodel in my HTML, simply because that's what was in the original project code I've stripped this out of, but you should be able to use any MV* library you prefer or indeed any other method you like to present the data.
In the HTML we place a script element for the Require library, and set its data-main attribute to load the application config first.
<script data-main="js/config" src="js/lib/require.js"></script>
The Require config is extremely simple, we're just creating paths to both the libraries (Knockout and PubSubJS) the application uses and then requiring the main application file and calling its init method.
require ['app'], (App) ->
I'm not going to go into the minutae of how Require works, there are plenty of resources out there if you want to look at it in more detail and for now I'll assume you have at least a basic understanding.
The application as well is not complicated, we're requiring Knockout, and our Viewmodel and API classes, instantiating both of the latter and then using Knockout's applyBindings method to wire the viewmodel to the DOM.
define ['knockout','viewmodel','api'], (ko,Viewmodel,API) ->
api = new API();
vm = new Viewmodel();
You'll notice at this point there's no mention of the pubsub library, this is entirely contained within the Viewmodel and API.
Let's take a look at the API. It exposes two methods - getContent and postContent. This is entirely faked for demo purposes, so all these methods do is wait 250ms and then return some data. The API class also stores an array of ingredients which is the data sample we'll send to the Viewmodel.
define ['PubSubJS'], (pubsub) ->
delay = (ms, func) -> setTimeout func, ms
list = ['Eggs','Milk','Bread','Vanilla Essence']
pubsub.subscribe '/load', (channel, msg) =>
return unless msg
pubsub.subscribe '/remove', (channel, msg) =>
return unless msg
# equivalent of an AJAX GET
delay 250, -> pubsub.publish '/loaded', list.slice 0
postContent: (data) ->
list.splice list.indexOf(data), 1
# equivalent of an AJAX POST (or DELETE)
delay 250, -> pubsub.publish '/removed', data
You'll see that the class requires the PubSubJS library, and then in its constructor sets up a couple of subscribers for events that the Viewmodel will publish. The getContent and postContent methods then publish on their own topics when they complete. The postContent method also deletes the posted item from the array so if the user tries to get the list again we can pretend the API actually did something.
(The delay method is just a convenience wrapper for setTimeout to make it more Coffeescript-y)
And now the Viewmodel.
define ['knockout','PubSubJS'], (ko, pubsub) ->
@firstLoad = ko.observable false
@status = ko.observable()
@list = ko.observableArray()
@instructions = ko.computed =>
if @firstLoad() then 'All done!' else 'Time to go shopping'
@load = ->
pubsub.publish '/load', true
@remove = (data) ->
pubsub.publish '/remove', data
pubsub.subscribe '/loaded', (channel, msg) =>
return unless msg
@firstLoad true unless @firstLoad()
@status 'API content loaded'
pubsub.subscribe '/removed', (channel, msg) =>
return unless msg
@list.splice @list.indexOf(msg), 1
@status 'Item removed via API'
Again we require the PubSubJS library, as well as Knockout. Its constructor does the usual Knockout setup to create its observables and other viewmodel functions, and then binds the equivalent PubSub subscribers to listen for activity from the API. The @load and @remove methods are bound to buttons in the UI, and they publish the events which the API listens for to trigger its (fake) AJAX calls.
Both parts of the application are completely decoupled from each other, only communicating though PubSub, and this separation of concerns means you could replace the API or the UI without the other side ever, in theory, needing to be changed.
And that's about all of it. To make the demo meaningful I've set the Viewmodel and HTML bindings up so that a shopping list is displayed after the user clicks the 'Get list from API' button, and removes items from the list when the API reports the remove behaviour was successfully posted.
So how does it work? The PubSubJS library, used in this example, operates as a global object so when the Viewmodel and API classes require it they're referring to the same object. Therefore when topics are subscribed to, and published, PubSubJS's event bus is available to all parts of the application and data can be received and sent from anywhere. That doesn't mean you should neccessarily use that library. Backbone, for example, has PubSub built in through its event system. And others are available. But most work on the same principle so if you have a favourite, give it a quick test and make sure it works.
All the code for the demo is on Github so do check it out for yourself and play around with it.