Subscriber App for Loosely-Coupled Interactions
This section builds a sample custom app named Loosely Coupled Subscriber. This sample has two subscriptions:
One that is an open subscription to accept any published topic payload.
One that has a specific topic payload to accept location information. This app uses the location information to invoke a mashup and retrieve airport, hotel and limo information for that location.
The tasks involved in building this custom app to support loosely-coupled interactions include:
HTML and CSS for the Loosely-Coupled Subscriber Sample App
We start with the HTML needed for this custom app to receive and render results from subscriptions:
<div
>
<h2>Loosely Coupled Subscriber Sample</h2>
<table
>
<thead>
<tr><th>Airports</th><th>Address</th><th>URL</th><th>Phone</th></tr>
</thead>
<tbody
></tbody>
<thead>
<tr><th>Hotels</th><th>Address</th><th>URL</th><th>Phone</th></tr>
</thead>
<tbody
></tbody>
<thead>
<tr><th>Limos</th><th>Address</th><th>URL</th><th>Phone</th></tr>
</thead>
<tbody
></tbody>
</table>
<h3>Messages Received for Topic Any</h3>
<div
/>
</div>
The <table> renders the results of three queries from a mashup. This mashup is invoked by a subscription that has a specific payload with location information. The <div> for event messages is used to render results from an open subscription.
The CSS for this example, shown here, is fairly typical. It is important, however, that all of the styles are class-based. This pattern ensures that styles for this app will not affect other apps in a workspace or interfere with styles in the various containers where the app or workspace are deployed.
div.loose-wiring-sample { font-family: Arial, Helvetica, sans-serif; }
div.loose-wiring-sample h2, div.loose-wiring-sample h3 { font-weight: bold;
color: #555; padding-top: 10px; }
div.loose-wiring-sample div, { font-size: 9pt; margin: 5px;}
.event-message { border: 1px solid #CFCFCF; height: auto; overflow: hidden;
width: 99%; border-bottom: 0px; }
.event-messages { border-bottom: 1px solid #CFCFCF; }
table.stockTable {border: 1px solid #999999; border-collapse: collapse; }
table.stockTable thead {background-color: #ededed; font-size: 9pt;}
table.stockTable th, table.stockTable td {padding:5px;
border: 1px solid #999999;}
Declaring Subscribe Topics in the App Specification
In addition to the basic changes to the default specification, such as id, name, and jsclass, the App Specification for this example has added the jQuery Templating library in <requires>, declared the dependency to a mashup in <dependson> and update <presto-meta> device compatibility flags.
In order to support loosely-coupled interactions, a <topics> section has been added where two subscriptions are declared:
<?xml version="1.0" encoding="UTF-8"?>
<app name="Loosely Coupled Subscriber" id="Loosely_Coupled_Subscriber"
jsclass="Sample.LooselyCoupledSubscriber"
minimizable="false" draggable="false" height="auto" width="600">
<title>Loosely Coupled Subscriber Sample</title>
<description>Sample demonstrates an App that subscribes to two topics for loosely-coupled interactions. </description>
<dependson>
<resource name="TravelInfo_for_Zip" operation="runMashup" type="service"/>
</dependson>
<properties/>
<presto-meta name="presto.desktopCompatible" type="text">true</presto-meta>
<presto-meta name="presto.phoneCompatible" type="text">false</presto-meta>
<presto-meta name="presto.tabletCompatible" type="text">false</presto-meta>
<topics>
<topic name="Sample.specificPayload" datatype="object" subscribe="true">
<description>This topic expects location information including a Zip or Postal code. </description>
<properties>
<property name="city" datatype="string"/>
<property name="state" datatype="string" />
<property name="country" datatype="string"/>
<property name="postalCode" datatype="string"/>
</properties>
</topic>
<topic name="Sample.anyPayload" datatype="any" subscribe="true">
<description>Use this topic to wire to any published topic. No property wiring is possible because the datatype is "any". </description>
</topic>
</topics>
<requires>
<require name="jquery-tmpl" type="library" version="1.0"/>
<require src="js/app.js" type="script"/>
<require src="css/app.css" type="css"/>
<require src="html/app.html" type="html"/>
</requires>
</app>
The Sample.anyPayload subscription is an open subscription because the datatype is any. With open subscriptions, a custom app accepts published topics with any payload.
The Sample.specificPayload has a datatype of object, which is the more common pattern for declaring topics. This indicates it accepts messages with a complex content. The expected payload is defined using <properties and <property> elements.
For more information on declaring topics and message payloads, see
Declare App Topics and Payloads.
The Initial Subscriber App Constructor
The constructor for this example follows common patterns, declaring a namespace for the app, finding the root element for the app and other nodes of interest within the app,
Presto.namespace("Sample");
Sample.LooselyCoupledSubscriber = function( app ) {
var root = jQuery( app.getRootElement() );
var queryTable = root.find(".queries");
var airportBody = queryTable.find(".airportBody");
var hotelBody = queryTable.find(".hotelBody");
var limoBody = queryTable.find(".limoBody");
var messageDiv = root.find(".event-messages");
var subRowMarkup = "<tr><td>${Title}</td><td>${Address}</td><td>${Url}</td><td>${Phone}</td></tr>";
var subRowTemplate = jQuery.template("subRowTemplate", subRowMarkup);
root.find(".queries").hide();
};
It also defines a jQuery template that will be used to render and populate rows in the table once a messages for the Sample.specificPayload topic is received.
The Subscription Handler for a Topic with Any as the Payload
To receive messages from a subscription, apps must implement the receive(topic, msg) method from the Presto App API. For more details on this method or other methods for apps, see the Presto App API.
This app has two subscriptions that the receive method must handle. For the Sample.anyPayload topic, this app receives the message, converts it to JSON and then simply displays the entire message in <div /> like this:
...
root.find(".queries").hide();
// App subscription handler
this.receive = function(topic, message) {
message = typeof message == 'object' ? Object.toJSON(message) : message;
messageDiv.append("<div class='event-message'>" + topic + ": " +
message + "</div>");
};
};
A more common approach with a open subscription message would be to check for properties of use to the app and use them to update the app. The handler must also handle receiving messages that have no properties of interest. For example:
Sample.Subscriber = function(app){
var showMessage = function(msg){
//message received logic
},
showAddress = function(address, city, state){
//address received logic
};
// This App is interested in 2 types of event, one that carries a
// message property and another which carries properties
// address, city and state
this.receive = function(topic,msg){
if(typeof msg == 'object'){
if(msg.message){
showMessage(msg.message);
} else if(msg.address && msg.city && msg.state){
showAddress(msg.address, msg.city, msg.state);
} else {
console.error('unexpected event data received', msg);
}
}
};
}
The Subscription Handler for a Topic with Specific Properties
The subscription for the Sample.specificPayload topic is the second subscription that this sample must handle in the existing receive method. To do this, the handler must be able to differentiate messages received from different topics.
This typically involves checking for specific properties that are unique to each topic payload.
Note: | Although both the message and topic name are sent in loosely-coupled interactions, the name that is passed is the publisher’s topic name. In tightly-coupled interactions, subscriber apps can test the topic name for a given message because the names are known in advance. With loose-coupling, however, this is not true. |
First, we need to add the conditions to determine which subscription topic a given message belongs to. Since we know the Sample.specificPayload payload, we can test for those properties:
...
root.find(".queries").hide();
// App subscription handler
this.receive = function(topic, message) {
if (message && message.postalCode) {
//invoke mashup
} else {
message = typeof message == 'object' ? Object.toJSON(message) : message;
messageDiv.append("<div class='event-message'>" + topic + ": " +
message + "</div>");
}
};
};
And finally, we need to handle the Sample.specificPayload messages. In this example, we use properties from the message to update input parameters for a mashup that returns three query results for a given location.
The handler invokes the mashup, using the default connection to Presto for the app, and two callbacks for successful and failed responses:
...
root.find(".queries").hide();
// App subscription handler
this.receive = function(topic, message) {
if (message && message.postalCode) {
//invoke mashup
var prestoUrl = "/presto/edge/api/rest/TravelInfo_for_Zip/runMashup?x-presto-resultFormat=json&zip_2=" + (message.postalCode || '');
app.getConnection().request({
url: prestoUrl,
type: "get",
contentType: "application/x-www-form-urlencoded",
data: ""
},
{ onSuccess: function(response) {
//render table },
onFailure: function(e) {
app.handleException({
message: 'Failed to find travel information: ' + e.message
});
}
});
} else {
message = typeof message == 'object' ? Object.toJSON(message) : message;
messageDiv.append("<div class='event-message'>" + topic + ": " +
message + "</div>");
}
};
};
The onSuccess callback must render the results from the mashup using the jQuery template into three separate sets of body rows:
...
root.find(".queries").hide();
// App subscription handler
this.receive = function(topic, message) {
if (message && message.postalCode) {
//invoke mashup
var prestoUrl = "/presto/edge/api/rest/TravelInfo_for_Zip/runMashup?x-presto-resultFormat=json&zip_2=" + (message.postalCode || '');
app.getConnection().request({
url: prestoUrl,
type: "get",
contentType: "application/x-www-form-urlencoded",
data: ""
},
{ onSuccess: function(response) {
queryTable.show();
if (response.travelinfo && response.travelinfo.Query){
//clear previous body rows
airportBody.empty();
hotelBody.empty();
limoBody.empty();
//render body rows for each query from mashup
for (var i=0; i < response.travelinfo.Query.length; i +=1) {
switch (response.travelinfo.Query[i].value) {
case 'airport':
var airports = response.travelinfo.Query[i].Result;
jQuery.tmpl(subRowTemplate, airports).appendTo(airportBody);
case 'hotel':
var hotels = response.travelinfo.Query[i].Result;
jQuery.tmpl(subRowTemplate, hotels).appendTo(hotelBody);
case 'limo':
var limos = response.travelinfo.Query[i].Result;
jQuery.tmpl(subRowTemplate, limos).appendTo(limoBody);
}
};
} else {
queryTable.hide();
}
},
onFailure: function(e) {
app.handleException({
message: 'Failed to find travel information: ' + e.message
});
}
});
} else {
message = typeof message == 'object' ? Object.toJSON(message) : message;
messageDiv.append("<div class='event-message'>" + topic + ": " +
message + "</div>");
}
};
};
Wiring Subscription Topics in Mashboard and Testing the Interactions
With this sample subscriber app, you cannot easily test it using the app template index.html file as it only renders information when it receives a message for one of it’s two subscriptions. To test this app, you must upload it to Presto, using the App Editor, add it to a workspace in Mashboard and wire the subscriptions.
First, upload
Loosely Coupled Subscriber or your custom app using the
App Editor (for more information, see
Create Custom Apps from the Base App Package).
To test the interaction
2. Open the Apps tab and drag Loosely Coupled Subscriber into the last cell of the Basic and Custom Apps Workspace workspace.
Next, wire the Sample.anyPayload subscription topic:
1. Click
Wire and choose the
Sample.anyPayload topic as the subscriber.
2. Choose the Sample.stock topic from the Loosely Coupled Publisher app as the publisher.
3. Click Finish. This saves the wiring configuration and closes the wiring window.
Note: | This wiring did not require you to map message payloads. Because the subscription topic is open, with a datatype of any, no property mapping is possible. |
To test this interaction, click any row in the Loosely Coupled Publisher app. This should update the chart in the Customer Stocks app and add the raw message data, in JSON format, to the Messages Received section in Loosely Coupled Subscriber:
To test the Sample.specificPayload subscription in Loosely Coupled Subscriber, we must wire this topic to the Sample.location publish topic in Loosely Coupled Publisher:
1. Click
Wire and choose the
Sample.specificPayload topic as the subscriber.
2. Choose the Sample.location topic from the Loosely Coupled Publisher app as the publisher.
3. This wiring requires mapping the message payloads, so click Next. Map the zip property from the published message payload to the postalCode property in the expected subscription payload.
4. Click Finish. This saves the wiring configuration and closes the wiring window.
To test this final interaction, click any row in the Loosely Coupled Publisher app. Three updates should happen:
This should update the chart in the
Customer Stocks app.
It should also add the raw message data, in JSON format, to the Messages Received section in
Loosely Coupled Subscriber.
Finally, it should retrieve location queries to the selected client’s location and render airport, hotel and limo results in a table in
Loosely Coupled Subscriber.