Lets say a page is composed of 3 child pages with their own view model. Some user action on one page needs to send some data to other child or parent page(s). This article shows how this can be done easily in Durandal.
Durandal view model are not shared or visible to each other by default. This is to keep the structure clean.
There is a view model tree propagation mechanism (
preserveContext:true). But it's turned off by default. This is to be used on very specific scenario. More details about Context are available in the reference document in
http://durandaljs.com/documentation/Composition/.
To handle generic interactions among view models Durandal exposes nice set of API. This is done using event publication and subscribe mechanism.
Event source publishes an event with an object argument. All interested parties registers for that event. When an event is raised by its source the framework passes it to all registered clients. As simple as that. There is a nice video on this in you tube by Ryan Keeter (DurandalJS and Pub-Sub with Events
http://www.youtube.com/watch?v=wuCpf-Tvff4).
Following code example walks through the mechanism:
This image shows a page ( created on the Durandal basic MVC template) containing two child pages.
Container page published a event and it's subscribed by both child pages. Similarly child page 2 also generates event's that is being subscribed by both child 1 and 2.
This is the code of the container page. Nothing really special here. One button to generate event and two composition.
<section><div class="container">
<div class="row" style="background-color: powderblue; height: 400px">
<div class="span4" >
<h2 data-bind="html: displayName"></h2>
<button data-bind='click: registerClick'>Click me</button>
<!--Sidebar content-->
</div>
<div class="span4" data-bind="compose: { model: 'viewmodels/sub', activate: true }" style="background-color:mistyrose; height: 300px"></div>
<div class="span4" data-bind="compose: { model: 'viewmodels/sub2', activate: true }" style="background-color:navajowhite; height: 300px">
</div>
</div>
</div>
</section>
Here is the view model:
define(function(require) {
var self = this;
self.app = require('durandal/app');
self.registerClick = function() {
self.app.trigger('myevent.name', { someValue: 'parent' });
};
return {
displayName: 'Parent Page',
registerClick: self.registerClick
};
});
The magic starts from the highlighted line. Durandal App module provides a trigger method to publish an event. It takes a name and object. Subscriber subscribe using this event name and the object is passed to the subscriber as an event argument.
Child view 1
<section>
<h3 data-bind="html: displayName"></h3>
<h3 data-bind="html: eventMessage"></h3>
<h3 data-bind="html: eventArgument"></h3>
</section>
Child model 1:
define(function(require) {
var self = this;
self.app = require('durandal/app');
self.eventCount = ko.observable(0);
self.eventArgument = ko.observable("no event.");
self.eventMessage = ko.computed(function() {
debugger;
if (self.eventCount() == 0)
return "No event received Yet.";
return "Number of event received: " + self.eventCount();
});
self.myEventHandler = function(eventArg) {
self.eventCount(self.eventCount() + 1);
self.eventArgument("Event Received from :"+ eventArg.someValue);
};
return {
displayName: 'Child Page 1.',
eventMessage: self.eventMessage,
eventArgument:self.eventArgument,
activate: function() {
self.app.on('myevent.name', self.myEventHandler);
self.app.on('myevent.child', self.myEventHandler);
}
};
});
The highlighted line above is the subscriber. The
on method of Durandal app module registers a function for an event. First argument is the event name and second argument is a method.
Following image shows when parent published the image.
Image below shows when child 2 publish an image.
This shows event can be published from anywhere and subscribed by anyone.
Just for completeness source code of Child View 2:
<section>
<h3 data-bind="html: displayName2"></h3>
<h3 data-bind="html: eventMessage2"></h3>
<h3 data-bind="html: eventArgument2"></h3>
<button data-bind='click: registerClick2'>Click me</button>
</section>
and Child model 2:
define(function(require) {
var self = this;
self.app = require('durandal/app');
self.registerClick2 = function() {
self.app.trigger('myevent.child', { someValue: 'Child2' });
};
self.eventCount2 = ko.observable(0);
self.eventArgument2 = ko.observable("no event.");
self.eventMessage2 = ko.computed(function() {
debugger;
if (self.eventCount2() == 0)
return "No event received Yet.";
return "Number of event received: " + self.eventCount2();
});
self.myEventHandler2 = function(eventArg) {
self.eventCount2(self.eventCount2() + 1);
self.eventArgument2("Event Received from :" + eventArg.someValue);
};
return {
displayName2: 'Child Page 2',
registerClick2: self.registerClick2,
eventMessage2: self.eventMessage2,
eventArgument2: self.eventArgument2,
activate: function() {
self.app.on('myevent.name', self.myEventHandler2);
self.app.on('myevent.child', self.myEventHandler2);
}
};
});