Single Page App – Separate UI from Model Using Publish, Subscribe Pattern using AmplifyJS

image8AmplifyJS is a set of components designed to solve common web application problems with a simplistic API. Amplify’s goal is to simplify all forms of data handling by providing a unified API for various data sources.

Your application may need more sophisticated control than is offered in Knockout, which provides for automatic updates in your view model. Knockout provides the observable pattern. But in the pattern described here Amplify’s publish/subscribe you do the publishing and the subscription.

In this post, you’ll learn the basics of how you can implement publish/subscribe pattern on the client using Amplify.

Amplify API

Amplify provides methods for Publish and Subscribe messaging pattern in your front-end application. One object can be broadcasting one or more messages (publishing) and another object is listening to one or more messages (subscribing). The pub/sub pattern allows for loose coupling of your components, which results in less brittle and more reusable code.

You can use custom events using jQuery to accomplish the same thing. But Amplify pub/sub component provides a slightly cleaner interface, prevents collisions between custom events and method names, and allows a priority to your messages.

Amplify Publish API

You publish by naming the topic and then passing along any additional information. For example you can publish whenever a contact is updated. The topic is the name, and the additional parameters can provide information to be passed to the subscribers: a context.

amplify.publish( "productUpdated", { 
  name: "Hot dog", 
  category: "Grocery",
  price: "2.39" 
});

publish returns a boolean indicating whether any subscribers returned false. If a subscriber returns false, then it prevents any additional subscriptions from being invoked.

Here’s the summary of amplify.publish:

amplify.publish( string topic, ... )

  • topic: The name of the message to publish.
  • Any additional parameters will be passed to the subscriptions.

Amplify Subscribe API

To subscribe, you need the topic name and a callback. The method is overloaded so you can also include the additional context information you can pass and a priority.

The following example show how you can subscribe when productUpdated is published:

amplify.subscribe( "productUpdated", 
  function( product ) {
  console.log( product.name ); // Hot dog
}, 5 );

Here is a summary of amplify.subscribe:

amplify.subscribe( string topic, function callback )
amplify.subscribe( string topic, object context, function callback )
amplify.subscribe( string topic, function callback, number priority )
amplify.subscribe( string topic, object context, function callback, number priority )

Subscribe to a message.

  • topic: Name of the message to subscribe to.
  • [context]: What this will be when the callback is invoked.
  • callback: Function to invoke when the message is published.
  • [priority]: Priority relative to other subscriptions for the same message. Lower values have higher priority. Default is 10.

Here is a summary of amplify.unsubscribe, which you use to remove a subscription:

amplify.unsubscribe( string topic, function callback )

  • topic: The topic being unsubscribed from.
  • callback: The callback that was originally subscribed.

Amplify Publish Subscript Sample

Let’s look at some code.

mustachelogoThe example assumes you have already loaded Amplify, jQuery, and Mustache into your project. In this case, I’ll use the conventions that come from loading these libraries using NuGet in Visual Studio and put them into the Scripts folder.

Starting HTML5 with Form

The file starts from the some mustache code from 2-MustacheUsingScriptTemplate.

Then you add the code to subscribe when

amplify.subscribe("productCreated", function (product) { 
  p.add(product); 
}); 
amplify.subscribe("productsStoreUpdated", function () { 
  updateProductDisplay(); 
}); 

Basically, it loads the text using jQuery.getJSON and applies the script template to the data.


<!DOCTYPE html>
<html>
<head>
<title>Get Products</title>
</head>
<body>
<div class="main-content">
<h1>All Products</h1>
<div id="sampleArea"></div>
</div>
<div id="alertArea"></div>
<div id="addProductDiv">
<h5>Add Product</h5>
<form id="addProductForm">
<div>
<label for="productName">Product Name</label>
<input type="text" name="productName" placeholder="Product" />
</div>
<div>
<label for="category">Category</label>
<input type="text" name="category" placeholder="Category" />
</div>
<div>
<label for="price">Price</label>
<input type="number" name="price" min="0" />
</div>
<button id='addProductButton'>Add</button>
</form>
</div>
<div><button id='clearProductsButton'>Clear</button></div>
<script id="productTmpl" type="text/template">
<ul>
{{#products}}
<li>{{productName}} {{category}} {{price}}</li>
{{/products}}
</ul>
{{^products}}
<p>No products listed.</p>
{{/products}}
</script>
<script src="Scripts/jquery-1.4.4.js"></script>
<script src="Scripts/mustache.js"></script>
<script src="Scripts/amplify.js"></script>
<script src="Scripts/app/utils.js"></script>
<script src="Scripts/app/product.js"></script>
<script>
var p = app.product;
updateProductDisplay();
function updateProductDisplay() {
var products = p.all();
var template = $("#productTmpl").html();
var html = Mustache.to_html(template, products);
$('#sampleArea').html(html);
}
// Add product button handler creates a new PRODUCT object
// which publishes the addition
$("#addProductButton").click(function () {
var form = $('#addProductForm');
p.create(
form.find('[name=productName]').val(),
form.find('[name=category]').val(),
form.find('[name=price]').val()
);
form.find('input').val('');
});
amplify.subscribe("productCreated", function (product) {
p.add(product);
});
amplify.subscribe("productsStoreUpdated", function () {
updateProductDisplay();
});
$("#clearProductsButton").click(function () {
p.clear();
});
</script>
</body>
</html>

Subscribe Code Walkthrough

The HTML provides a place to list the products in sampleArea.

The addProductDiv is a quick form where we an put some data in. In real life, you might make that into a modal form. productTmpl script is a Mustache template that makes a list of the products. In real life, you can turn that into a table and provide much cooler formatting. And you can place the template in a separate file.

We have separated out the display logic from the data, and what happens to the data. The user interface subscribes to two events that will be published by the product model”: productCreated and productStoreUpdated. This separation gives us the flexibility to change the behavior of user experience when a product is created and when the local storage for products is updated.

NOTE: You can have multiple subscriptions to the same publish.

Encapsulate the product functions into a single file. Create a file to hold the application script called Scripts/app/product.js.

Scripts/app/product.js


var app = {};
app.product = function () {
this.create = function (productName, category, price) {
var product = {
productName: productName,
category: category,
price: price
};
// publish that you have added a product
amplify.publish('productCreated', product);
return product;
};
this.all = function () {
var store = amplify.store("productsStore");
if (store === undefined) {
store = new Object();
store.products = new Array();
amplify.store("productsStore", store);
}
return store;
};
this.add = function (product) {
var data = this.all();
data.products.push(product);
// store the new data
amplify.store("productsStore", data);
// publish that the product store has been updated
amplify.publish("productsStoreUpdated");
};
this.clear = function () {
amplify.store("productsStore", null);
amplify.publish("productsStoreUpdated");
};
return {
create: create,
all: all,
add: add,
clear: clear
}
}(amplify);

Note the amplify.publish calls in the section in create, add, and clear methods.

Publish Code Walkthrough

First, app namespace lets you keep the global namespace from being polluted.

You then create an product object with public create, all, add, clear methods.

create publish that a product object has been created. The subscriber can then do additional validation before adding it to the products array. Note too that amplify.publish passes along the product, which can be used by the subscriber.

In the add and clear methods, you store the values, and you retrieve the value using all.

Inside all, you deal with calling amplify.store for the first time when there is no data.

The add and clear methods publish productsStoreUpdated that notifies subscribers that products has been updated and may require some action. In this case, I have two methods that can tell the UI that the products have been updated.

In this case, to show that you do not need to pass out data in your publishers,  productsStoreUpdated publisher does not pass along a value. Thinking about it, it probably should since you could change the code to have more than one products list.

Summary

You have seen how you can use Amplify’s publish and subscribe features to separate concerns of an object from its display. And you have seen how you can integrate the display with local store using Amplify’s store.

As an exercise for the reader, you can quickly change this app into retrieving and storing the data on a server by changing amplify.store to amplify.retrieve, and connecting these up with a server.

Sample Code

Sample code for this post is available in the DevDays GitHub repository: https://github.com/devdays/single-page-app/tree/master/Amplify
See 5b-PublishAndSubscribe.html.

Resources