5 March 2012
This article assumes knowledge of JavaScript, HTML and XML and Adobe Digital Publishing Suite.
Additional requirements
Intermediate
Note: If you have questions about this article, use the comments feature at the bottom of the article. Please don’t contact technical support with questions about Adobe Developer Connection articles.
A Digital Publishing Suite viewer application optionally can contain a number of custom navigation slots beyond the standard "Library" and "Viewer". These slots implement webviews for rendering of HTML5, CSS3, and JavaScript. Each of these slots has its DOM modified at run time to provide a bridge to services exposed by the application. These services are detailed in the JavaScript Viewer API for Digital Publishing Suite. By using these services, a publisher can create a custom, branded and highly designed merchandising view to present folio or other digital content for sale. The webpage is hosted by the publisher and loaded at runtime allowing the use of server technologies (PHP, JSP, CMS). Publishers are free to create any visual design or technical functionality of their choosing; however, for purposes of this article we assume a merchandising view of Digital Publishing Suite folio content available for in app purchase.
This article will cover the basics for creating a custom storefront.
Note: Only Digital Publishing Suite Enterprise Licensees can add Custom Navigation slots.
You implement custom storefront code using HTML5, CSS3, and Javascript in conjunction with the Javascript Viewer APIs for DPS. You package your custom storefront with your application when you create a viewer application using Viewer Builder. Specifically, you provide an archive (zip) file of the code that implements your storefront as the "Icon xxx HTML Resources ZIP".
Your code can only begin using the JavaScript Viewer APIs after receiving the onadobedpscontextloaded event from the window object. To ensure that no method calls are used before they should be, postpone any initialization until this event is received.
$(document).ready(function() {
var init = function() {
// Define a function in case the main JavaScript file has not loaded yet.
if (!window.onnetworkconnection) {
window.onnetworkconnection = function() {
window.connected = true;
}
}
}
if (navigator.userAgent.toLowerCase().indexOf("ipad") == -1) {
// On desktop so call init() immediately.
init();
}
else {
window.onadobedpscontextloaded = init;
}
});
The above code initializes based upon whether the code is executing within the iPad or within a desktop browser (Safari on a Mac). The latter being very useful for development purposes. After init() has been executed, it is safe to begin store operations.
Check for the presence of existing folios using the getFolioData() and updateLibrary() methods. The getFolioData() retrieves an array of folio descriptors known to the Viewer's library. Valid folioState values:
folioState value |
Description |
0 |
Invalid |
50 |
Unavailable. The folio is not yet available for purchase |
100 |
Purchasable. Can be purchased and downloaded |
101 |
Purchasing. There is an active or paused Purchase Transaction for the folio |
200 |
Downloadable. The folio is free, or its Purchase Transaction completed |
201 |
Downloading. There is an active or paused Download Transaction for the folio. |
400 |
Extractable. The folio content is stored locally. |
401 |
Extracting. There is an active or pause Extraction Transaction for the folio. |
1000 |
Viewable. The folio is can be loaded in the viewer. |
1500 |
Update available. The folio is viewable but can be updated. |
1501 |
Downloading update. There is an active update download for this folio |
1502 |
Update extractable. The folio is viewable but has an update that can be installed. |
1503 |
Extracting update. There is an active update extraction for this folio. |
There are two methods for initiating the purchase: download and/or viewing of a folio.
Method |
Description |
buyFolio |
Initiates the eCommerce with the AppStore to begin purchase of a folio. Actual purchasing is all handled by the viewer, AppStore and iOS. Once complete, the application will present it's Library view and the folio will start downloading. |
viewFolio |
Initiates the download and/or viewing of a Folio. If the folio has been published as FREE, then it is downloaded. Once the folio has been downloaded, calling |
For RETAIL folios, pricing information from iTunesConnect are contained within the folio description objects returned from getFolioData().
Let's walk through an example of a basic storefront. This example showcases the JavaScript required to implement a storefront. The templates provide basic implementations. This example will walk through:
storefolio1.$(document).ready(function() {
var init = function() {
// Define a function in case the main JavaScript file has not loaded yet.
if (!window.onnetworkconnection) {
window.onnetworkconnection = function() {
window.connected = true;
}
}
}
$.ajax({
type: "GET",
url: "http://www.google.com",
success: function() {
window.onnetworkconnection();
},
error: function() {
$("body").append("<div id='offline'>You must be connected to the Internet to access the store.</div>");
}
})
if (navigator.userAgent.toLowerCase().indexOf("ipad") == -1) {
// On desktop so call init() immediately.
init();
}
else {
window.onadobedpscontextloaded = init;
}
});
The above file first uses JQuery's .ajax function to fetch a well known web address to determine if the device is currently on-line. If successful, it sets a property window.connected for later inspection.
You implement the core of this example in the basic_store1.js file.
First, define the productID we will be offering for purchase/download.
var productID = 'storefolio1';
The next block of code sets up callback for receiving notification upon an update library event. Whenever the library is updated, the code fetches descriptions of all the folios in the account. Depending upon the state 'folioState' of the folio, the label on the purchase button is set to "Buy", "View" (for downloaded or free content), or "Invalid" (shouldn't happen).
var onFolioData = function (data) {
var len = data.length;
// The library returns an array of folio descriptor objects. Put them into a hash indexed by something more convenient (ie productId)
for (var i = 0; i < len; i++) {
folioDataHash[data[i].productId] = data[i];
console("Folio: "+data[i].productId);
}
var folio = folioDataHash[productID];
if (folio) {
// Also fetch our preview image.
adobe.dps.store.getPreviewImage(productID, true, 768, 1024, onPreviewImage);
}
else {
console("The productId you have defined for purchase/download does not exist within your Fulfillment account");
}
};
var onPreviewImage = function(data) {
var s="";
var folio = folioDataHash[productID];
if (!folio) return; // un-known folio...nothing to show.
////
// At this point, we have valid folio data representing our folios and a folio image for our target product ID.
////
// Create the image
s += "<img src='"+data['path']+"' width='160' height='222' />";
// Create the Title
s += "<div id='title'>"+folio.title+"</div>";
// Create the Folio Number
s += "<div id='manifestXRef'>"+folio.description+"</div>";
// Create the buy|view button and then create the 'buy' or 'view' label and assign a click handler, if valid
s += "<div class='buyButton' id='buyButton1'>";
if (folio.folioState=='100') { // buy folio
s +="Buy";
}
else {
s += "View";
}
s += "</div>";
var node = document.getElementById("ourItem");
if (node) {
node.innerHTML = s;
node.onclick = node.onclick=buy;
}
};
var onLibraryUpdate = function () {
adobe.dps.store.getFolioData(onFolioData);
// Unregister for library updates
unregisterUpdateLibraryHandler();
}
var registerUpdateLibraryHandler = function ()
{
if (window.adobedpscontextloaded)
{
// call into the updateLibrary API
adobe.dps.store.registerLibraryUpdateCompleteHandler(onLibraryUpdate);
adobe.dps.store.updateLibrary();
}
else {
console("Failed to find Javascript API");
}
};
var unregisterUpdateLibraryHandler = function ()
{
if (window.adobedpscontextloaded)
{
// call into the updateLibrary API
adobe.dps.store.unregisterLibraryUpdateCompleteHandler(onLibraryUpdate);
}
}
registerUpdateLibraryHandler();
Previously referred to as "Custom HTML Store" or "Store APIs", the JavaScript Viewer API for Digital Publishing Suite provides a bridge between web pages hosted within a custom navigation slot or entitlement banner. Both areas have access to the same functionality. However, pages opened (the 'slide-up webview') by either the entitlement banner or the custom navigation slot, do not have access to these APIs.
The JavaScript Viewer API for Digital Publishing Suite provides access to:
The API is an asynchronous interface. This means you must provide callback handlers to receive content at future time, as in the following example:
adobe.dps.store.getFolioData(onFolioData);
The getFolioData() method passes a reference to the onFolioData() method. When the folio data has been collected by the viewer, it will invoke the callback onFolioData() and provide an array object containing the data.
The examples included with this article fetch information about all available folios directly from the viewer. The viewer gets this information from your Adobe Fulfillment account. This account contains your published, public content. When you develop your storefront on a desktop or test within a browser, you can access this data from the fulfillment server.
It remains a best practice to allow only the viewer to fetch this data – and to have the storefront code fetch it from the viewer (with a call to getFolioData). Not only does this pattern eliminate potentially costly and redundant web calls, it also ensures that the correct and consistent rendition logic is applied to the folio list.
The following steps help you access your fulfillment account if you are required to.
Each Adobe ID references a specific Adobe Digital Publishing Suite fulfillment account. Likewise, every magazine application pulls folios from a specific Adobe Digital Publishing Suite fulfillment account. Fulfillment accounts are identified by a GUID (for example, 943864f0a0d2431aa2992987caf78cb0).
For applications that want to create a customized view of their folios, access the data within the fulfillment account can be very useful.
To access your fulfillment account associated with your Adobe ID (or your magazine's AdobeID), you need to discover your GUID. This can be found by inspecting the LibraryConfig.plist file within your application's contents, as follows:
You can then retrieve an XML description of all the public folios within your fulfillment account at the following URL:
http://edge.adobe-dcfs.com/ddp/issueServer/issues?accountId=<your GUID>&targetDimension=1024x768,768x1024,1024x748,748x1024
or:
http://edge.adobe-dcfs.com/ddp/issueServer/issues?accountId=<your GUID>&targetDimension=all
for example:
http://edge.adobe-dcfs.com/ddp/issueServer/issues?accountId=b8c8d8164adb4dcf8e69e94fae1c58f6&targetDimension=1024x768
The following is an example response containing a single folio:
<results status="SUCCESS" message="Success">
<issues>
<issue id="bdfb4254-1276-4393-abc6-3578540b750e" productId="look14" formatVersion="1.7.0" version="4" subpath="">
<magazineTitle>Daily Fashion</magazineTitle>
<issueNumber>14</issueNumber>
<publicationDate>2011-05-01T07:00:00Z</publicationDate>
<description>bracelet</description>
<manifestXRef>May 2011</manifestXRef>
<state>production</state>
<libraryPreviewUrl landscapeVersion="1" portraitVersion="1">http://edge.adobe-dcfs.com/ddp/issueServer/issues/bdfb4254-1276-4393-abc6-3578540b750e/libraryPreview</libraryPreviewUrl>
<brokers>
<broker>noChargeStore</broker>
</brokers>
</issue>
</issues>
</results>
Note: Multiple <issue> nodes will probably exist.
Within the XML returned from fulfillment, each <issue> node contains a <libraryPreviewUrl> element. Use this URL with either portrait or landscape appended to access your images.
Eg. http://edge.adobe-dcfs.com/ddp/issueServer/issues/bdfb4254-1276-4393-abc6-3578540b750e/libraryPreview/portrait
While all the code for a store implementation can be embedded within the viewer, this makes updates to the code dependent upon updates to the viewer. Instead, only code that is not likely to change (JQuery.js) should be included. Also, the archive you provide to Viewer Builder for inclusion must contain an index.html file. Keep this simple.
From our example:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Banner</title>
<script src="jquery-1.5.min.js"></script>
<script src="network_detection.js"></script>
<script src="basic_store1.js"></script>
<link rel="stylesheet" href="offline.css" type="text/css">
<link rel="stylesheet" href="styles.css" type="text/css">
</head>
<body>
<p id="console"/>
<div id="ourItem" />
</body>
</html>
Modify the file to allow rapid iterations and web updates by addressing the implementation files on a server.
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Banner</title>
<script src="jquery-1.5.min.js"></script>
<script src="network_detection.js"></script>
<script src="http://<yourserver>/basic_store1.js"></script>
<link rel="stylesheet" href="offline.css" type="text/css">
<link rel="stylesheet" href="http://<yourserver>/styles.css" type="text/css">
</head>
<body>
<p id="console"/>
<div id="ourItem" />
</body></html>
Adobe's Digital Publishing Suite Customer Enablement team has created template implementations of storefronts that can be leveraged as turnkey implementations, or as a bootstrap to your own custom storefront. These all implement a grid layout of folios with various extra modules added as examples.
An archive of these implementations can be found in the Custom_store_templates.zip file.
The Marketing template provides a Grid view of folios and a rotating banner area at the top.
The Subscribe template provides a Grid view with a transient subscription image. Once subscribed, this image disappears.
The rotating banner template provides a Grid view with a rotating promotional area above that contains a featured folio.
It is important to realize that these storefront templates are simply HTML5+JavaScript+CSS3. You can add anything, even a Twitter feed.
Read the "Merchandising with Adobe Digital Publishing Suite" white paper for more information about how to drive additional revenue through a Custom Store.
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.