In my previous articles I've shown how to build KO components that start off with a data source of their own and are pretty self contained with respect to interactions with the rest of the page. But, in the first part of this article we built a tree control that's I am planning to use as an Index page. So an action like 'Clicking' a link needs to affect another part of the application/web page.
Solution? A light weight message passing system, that subscribes to events for you and publishes the event to all who have subscribed.
Message passing whaa...
Did you just wince at the sound of 'message passing system', fear not, it's not rocket science, in fact it's pretty simple. Take the two scenarios below.
Here, reference by the id of the element, breaks the model view separation where the View Model has explicit knowledge of the View's elements. This is hard coupling and should be avoided.
Instead of attaching our event handler code directly, we list our function in a centrally located - let's call it a 'registry' for now, using a unique string key.
Next when the particular event happens we tell the 'registry' that a 'named' event has happened and that all subscribers should be notified.
The registry takes the name and looks up to see if any functions where registered against it, if so, those functions are called.
Thus, by using a central location for registering event names and their handlers, we have decoupled View and ViewModel. Now View can raise an event, ViewModel handles the event via KO's event handling mechanism and then tells the central registry to dispatch a named event to all the subscribers.
The subscribers need not be a part of this ViewModel, they could be part of any ViewModel on the page. They would register to listen of the 'named event' by providing an event handler function. This is the key, no 'ids' need to be shared across ViewModels.
Once the named event occurs the event handler functions are called.
Now that we've got an idea of how we can decouple the UI from the view model and still pass data around components, let's use AmplifyJS as our 'events broker' and see what it takes to implement it.
Amplify was created by the team at http://appendto.com/team and is made available under MIT and GNU OSS licenses.
Installing AmplifyJS is easy. Use nuget package manager console and install package using the following command
[sourcecode language="powershell" padlinenumbers="true"] install-package AmplifyJS [/sourcecode]
This will create a Scripts/amplify folder and put each amplify component in a separate file here. It will also place amplify.js and amplify.min.js in the Scripts folder. Since we'll be using the entire library we'll delete the separate component files and move the amplify.* files from Scripts to Scripts/amplify
Since we'll need Amplify globally to watch over all messages passed to and from it, we'll set it up in the configuration require.config.js as highlighted below
Updating tree-node to handle click event
We update the tree-node view to wrap the text span in an anchor, and attach a click handler
[sourcecode language="html" padlinenumbers="true" highlight="19,21"][/sourcecode]
In the viewModel we handle the click event and raise
The following snippet handles the click event
First up it simply raises an alert that says hello with the name of the node. We have also setup the node to flip the expanded flag.
If we run the application now and click a node, we'll see the popup.
So far so good. The node has directly responded to the click event.
Raising Event using Amplify
In the tree-node.js' nodeClicked function we can 'raise' an event or pass message to our queue that the click event has happened. To do this we first add reference to amplify in our module. Since we have registered it globally all we have to do is use its alias:
Next we upload the clicked event function as follows:
As you can see the call to 'raise' or 'publish' the event via amplify is pretty simple. All we have done is specified a string key, and then passed on the value we want to event receiver to get, which in this case is the nodeTitle. We could have passed the entire node if we wanted.
Note: I have removed the alert from the nodeClicked Event. Now we need another component to subscribe to this event and handle it when it occurs.
The 'Content Pane'
The node click is supposed to be handled by a 'Content Pane' component that pulls content from another source. For brevity, I'll 're-use' the greeter component. We update the greeter component such that it handles the click event and updates the greeting.
First we add reference to Amplify
Next we add a 'subscription' for the event and update the greeting text
As we can see, subscribing to the 'event' is as simple as
- Specifying the same key that we published it with,
- Providing a function that will be called when the event happens.
In the function we have changed the greeting property of the viewModel.
Now we add the greeter page component in the docs.html
This page serves as the root for SilkThread documentation. It is itself built with SilkThread and we'll see how muliple web-components can be put together to create a neat App.
We are all set, when we run the app initially we come up with this
When we click on Node 0 the greeting changes to
Also note that the node has collapsed because of the code we wrote to toggle the expanded state.
SignalR is another example of a Pub-Sub system that maintains subscribers on the server and publishes actions to all connected clients. But it is used across server and client systems.
So as you can see the Pub-Sub system is a versatile software development pattern that we leveraged on the client side today.
That concludes this sub-series of my overall Silkthread series. Next we will see how we can use Amplify to make http requests and send data to server and retrieve data back.