Well organized exchange of client side events has been something of a holy grail for client side web development to me. I do not like these direct interactions between components that should not even know about each other. On the other hand – earlier this week I could not find out how to make components work together at all – not even in a not-so-nice-way. I decided to drop Geertjan Wielenga an email ; he is from Oracle and is one of the leading bloggers on Oracle JET. I asked him on his thoughts on a ‘client side event bus’ in JET. It turned out that he had just (as in a few minutes prior to my email) published two articles on his blog that touched on this subject. They describe how modules and template specific view models could interact in a KnockOut application such as Oracle JET. That was a great starting point. From there, I soon Googled my way into a pretty, nifty, little KnockOut extension called Postbox, introduced in 2012 and last updated in the Summer of 2015. It turns out this extension provides the event bus for client side events – in a very simple way.
The very short story:
- Observables and regular JavaScript functions can subscribe to global topics
- Anyone can publish to these global topics (Observables can even be tied directly to a topic, so every value change is immediately published)
- When a message is published to a global topic, all subscribers are notified: subscribed observables are immediately updated with the published value and any subscribed functions are invoked with the published value as a parameter
This sounds simple (I hope) and it is!
In this article I will apply the Postbox mechanism to the HRM application I worked on in my previous article with Departments and Employees synchronization (Master – Detail synchronization in an Oracle JET rich client application against a REST API)
The end result will be the following interaction chain:
When a department is selected in the table, the departmentSelectionListener() function is executed. This function publishes an event – the departmentSelection event – to the postbox. This event contains the newly selected department identifier. The ViewModel for the compContent template has subscribed a callback function with the postbox, on the topic departmentSelection event. When the postbox receives that event (in this example from the listener function in the hrm ViewModel) it will send it to all subscribers – in this case the handler function in the compContent template’s ViewModel that will refresh the employees collection.
The crucial element in all of this: postbox is a neutral, cross everything and everyone entity. Anyone publishing an event and anyone consuming an event does not need to know anything else than the postbox. There is total decouplement between the templates, modules and view models. This means that even if no one is consuming the departmentSelection event, the hrm viewmodel should still be publishing it and even if no producer publishes this event, the compContent viewmodel can still subscribe to it.
Below I will describe the implementation of this mechanism. It it very straightforward to apply and very elegant to use. I like it.
Add Knockout Postbox to the application
Use npm install knockout-postbox
or bower install knocktout-postbox
or simply go out to GitHub and clone the repository or just download the knockout-postbox.js library.
Copy the file knockout-postbox.js to the directory public\js\libs\knockout-postbox.
Edit the main.js file (in public\js) and add the line
requirejs.config({ // Path mappings for the logical module names paths: { 'knockout': 'libs/knockout/knockout-3.3.0', 'knockout-postbox': 'libs/knockout-postbox/knockout-postbox', 'jquery': 'libs/jquery/jquery-2.1.3.min', ...
to the paths property in the requirejs.config setup.
Subscribe To and Consume the Department Selection Event
The compContent module defines the view model for the compContent template that contains the table with Employees. With a single line, the view model can register a function with the postbox as handler for the departmentSelection event:
ko.postbox.subscribe("departmentSelection", function(newValue){ self.refreshEmployees(newValue.departmentId); } );
The function will be called by the postbox whenever a message is posted to the departmentSelection topic. The function receives that message – that presumably contains an object with a property called departmentId that contains the identifier for the newly selected department. Note that compContent does not have a clue where this event is going to come from.
The application at this point can be visualized like this:
Publish the Department Selection Event to the Postbox
The hrm module already contains a table selection listener – as explained in the previous article. I need to add a single line to this listener, that publishes the event to the postbox. It is as simple as this:
self.selectionListener = function (event, data) { var tableId = data.currentTarget.id; var key = currentSelection(tableId); // publish the department selection event to the world ko.postbox.publish("departmentSelection", {'departmentId': key, 'source': 'hrm.departments-table'} ); };
With this line added, any row selection in the departments table in the hrm template will result in a departmentSelection event being published to the postbox.
And action…
Putting it all together:
and when a different department is selected:
Resources
Geertjan’s articles on inter-modular communication: Intermodular Communication in Oracle JET (Part 1) and Intermodular Communication in Oracle JET (Part 2)
Knock Out Postbox module on GitHub: https://github.com/rniemeyer/knockout-postbox
Original introduction of Postbox by Ryan Niemeyer: Using KO’s Native PubSub for Decoupled Synchronization
Article on Wrap Code: Communication between multiple View Models in KnockoutJS (MVVM), the right approach!
Download the demo application discussed in this article: oraclejetwithrest_onnode.zip.
Do publishing and subscribing have to be called in an event handler ?
Excellent Post. Just read it once and my inter module communications started working
Excellent, will try this soon.