[Prisjakt – The Story is a series of blog posts about our experiences helping Prisjakt/PriceSpy to write a Windows 8 Metro Style App using HTML/CSS/JavaScript. Today’s post talks about the JSON and the different ways data binding is accomplished in HTML/JavaScript and is authored by Peter Bryntesson]

Introduction

In a connected world the almost every app connects to a central server and consumes the data. The most common pattern is querying for that data via REST and using JSON as the data format. To view this data in a Windows 8 Metro style app, you will use data binding. This is exactly how we did it in Prisjakt/PriceSpy so let’s go through how we did this.

JSON

JSON, which stands for JavaScript Object Notation, is an efficient, text-based open standard for human-readable data exchange. It is the data format of choice for many web sites and API providers, including Prisjakt/PriceSpy. Although it has its roots in JavaScript, the data format is language and platform independent.

The support for JSON in Windows 8/Javascript apps is excellent, compact and very easy to use. To deserialize a JSON string into an object, write the following code:

var category = JSON.parse(response);

Now category contains a deserialized version of response, even if the response is a complex data structure with collections and sub-objects. Likewise, to serialize any JavaScript object to a JSON string, use the following code:

 var 

message = { "invoke": "setCenter", "latitude": cachedLatitude, "longitude": cachedLongitude }; window.parent.frames['resellerMap'].postMessage(JSON.stringify(message), '*');

Since JavaScript is very loosely typed and dynamic, it consumes JSON objects naturally and easily. Interestingly we can use this to our advantage and refine the JSON objects before passing them on to data binding. An example of this can be found when we receive a list of products and need to refine the properties in order to use data binding, like this:

products.forEach(function (product) { // just rename and add some parameters if (categoryName != null && categoryName != "") product.category = categoryName; if (product.product_name != null) product.name = product.product_name; if (product.product_id != null) product.id = product.product_id; product.image = "http://s3.pji.nu/product/standard/140/" + product.id + ".jpg"; product.largeimage = "http://s3.pji.nu/product/standard/800/" + product.id + ".jpg"; product.rank = rank++; // store the individual product in the list.

that.listProducts.push(product); }); 

JSON and JavaScript fit each other and the support in Windows 8 is excellent.

Data Binding

The only way to show non-trivial data (in lists or grids) in Windows 8 is to use data binding. This is true both for XAML and HTML/JavaScript, and we will examine how this is done in HTML/JavaScript. The basics are the same for XAML, but the actual objects/controls are different.

Binding definitions

To do data binding for an HTML element, you use a template for specifying how an item should be displayed and which data it will show. An non-trivial example from Prisjakt/PriceSpy is the Grid/ListView that displays the search result when searching for products. This element also supports semantic zoom so we need two item templates. Here is the item template for the normal view:

<div id="productitemTemplate" data-win-control="WinJS.Binding.Template"> <div class="productitem"> <div class="productimagecontainer"> <img class="productimage" src="..\images\white.png" data-win-bind="src : image" /> </div> <div class="producttitlediv"> <div class="producttitle" data-win-bind="textContent : name"></div> <div class="productprice" data-win-bind="textContent : price_str"></div> </div> <div class="productoverlay"> <div class="ratingcell"> <div class="productrating win-small" data-win-control="WinJS.UI.Rating" data-win-options="{maxRating:10, disabled:true}" data-win-bind="winControl.averageRating : user_reviews_rating"> </div> <span class="productratinglabel">(</span><span class="productratinglabel" data-win-bind="textContent : user_reviews_count"> </span>&nbsp;<span class="productratinglabel" data-win-res="{textContent : 'reviews'}"> </span><span class="productratinglabel">)</span> </div> </div> </div> </div

> 

And here is the template for the semantic zoom view:

<div id="semanticZoomTemplate" data-win-control="WinJS.Binding.Template" style="display: none"> <div class="semanticzoomitem"> <div class="semanticzoomitem-imagecell"> <img class="semanticzoomitem-image" src="#" data-win-bind="src : image" /> </div> <div class="semanticzoomitem-info"> <div class="semanticzoomitem-title" data-win-bind="innerText: group"></div> <div class="semanticzoomitem-count"> <span data-win-bind="textContent : count"></span>&nbsp;<span data-win-res="{textContent : 'products'}"> </span> </div> </div> </div> </div

> 

The important parts of this template is that the root element has the data-win-control class set to WinJS.Binding.Template and the display style set to none. This keeps this element from being displayed and tags this element to be used as data binding template. To bind the data for and individual element, use the data-win-bind attribute and associate the element’s property with the property in the data set.

Then we need to define the actual element that will display the products. This definition does not link to the item template, that connection is done in code when we set the data source for the ListView. Here is the definition:

<div class="item" id="productlist"> <div id="productsearcherror"></div> <div id="semanticZoomDiv" data-win-control="WinJS.UI.SemanticZoom"> <div class="productslistZoomedIn" id="productsListView" aria-label="List of products" data-win-control="WinJS.UI.ListView"> </div> <div class="productslistZoomedOut" id="productslistZoomedOut" aria-label="List of products" data-win-control="WinJS.UI.ListView" data-win-options="{ selectionMode: 'none' }"> </div> </div> </div

> 

We define two elements, both of the type WinJS.UI.ListView to support both the normal and semantic zoom view. Again, data-win-control attribute assigns the type of class to instantiate.

Now that we defined how an item is data bound, let’s make that data available to the control. All controls in the WinJS.UI namespace that supports binding has a data source property that has to be assigned.

Binding sources

The interfaces needed to implement a data source are WinJS.UI.IListDataAdapter and WinJS.UI.IListDataSource. The IListDataAdapter is the class responsible for fetching and caching data, and the IListDataSource is responsible for connecting a visual control with the data from the IListDataAdapter. You can implement your own IListDataAdapter (which is done in Prisjakt/PriceSpy and we will touch on this later in the article). There is an excellent article about this on dev.windows.com.

There is an easier alternative with a lot of added functionality. In the WinJS.Binding namespace, there is a class called List. This class implements both IListDataAdapter and IListDataSource interfaces plus that it adds functionality like sort, filter and group. The group functionality comes in handy when we do the semantic zoom view. The backing data store is list, so you need to copy the data to this object. This is the easiest way to create a binding data source; here is some quick code to do this. To create the List object, use the following code:

// collections that.listProducts = new

WinJS.Binding.List(); 

To add an item to the list, simply write this:

// store the individual product in the list. that.listProducts.push(product);

Now, let’s bind this together by assigning the data source and item templates to the ListView controls:

 var 

zoomedinlistview = document.getElementById("productsListView").winControl; ui.setOptions(zoomedinlistview, { itemDataSource: prisjaktData.listProducts.groupedList.dataSource, itemTemplate: document.getElementById("productitemTemplate"), groupHeaderTemplate: document.getElementById("headerTemplate"), groupDataSource: prisjaktData.listProducts.groupedList.groups.dataSource, }); zoomedinlistview.forceLayout(); var zoomedoutlistView = document.getElementById("productslistZoomedOut").winControl; ui.setOptions(zoomedoutlistView, { itemDataSource: prisjaktData.listProducts.groupedList.groups.dataSource, itemTemplate: document.getElementById("semanticZoomTemplate"

) }); 

So, we need to assign data sources and items templates for both ListViews, otherwise it’s pretty straightforward. Now that we have completed this step we are done and we can browse our data in both normal view:

clip_image014

And this is how it looks when applying the semantic zoom:

clip_image016

Creating your own IListDataAdapter

Sometime you will end up in situation where using the List class is not sufficient. In the Prisjakt/PriceSpy case, one of the entities shown is user written reviews of the products. The number of reviews can vary greatly, and the api prohibits us from fetching more the 50 at the time. Currently there is no way of detecting when the user scrolls the listview (in order to fetch more items dynamically) so the solution in this case is to derive a version of the IListDataAdapter to fetch data. This way, the listview will keep track of the data it needs and ask the data when it is needed, in an asynchronous fashion.

What you have to do is to implement the required parts of the IListDataAdapter interface. Currently, only two methods are required:

· itemFromIndex, which will supply the number of items requested

· getCount, which returns the number of items that exist

Let’s look at the implementation in Prisjakt/PriceSpy:

// Definition of the data adapter for user reviews var userReviewsDataAdapter = WinJS.Class.define( function (p, count) { // Constructor this._minPageSize = 10; // based on the default of 10 this._maxPageSize = 1000; // max request size for bing images this._maxCount = 1000; // limit on the api this._p = p; this

._count = count; }, 

Here we define the class and implement the constructor. We have a number of member variables that is set.

// IListDataDapter methods // These methods define the contract between the IListDataSource and the IListDataAdapter. { /* function to return the desired number of items requestIndex - zero-based index from where we should base the query from countBefore - how many items before requestIndex should we fetch? countAfter - how many items after requestIndex should we fetch? */ itemsFromIndex: function (requestIndex, countBefore, countAfter) { var that = this; // wrap in a try-catch just to be safe try { // sanity check - we can't get anything that is not there if (requestIndex >= that._maxCount) { return WinJS.Promise.wrapError(new WinJS.ErrorFromName(WinJS.UI.FetchError.doesNotExist)); }

Here we define the itemFromIndex method and do a sanity check that the request isn’t entirely out-of-range.

 var 

fetchSize, fetchIndex; // See which side of the requestIndex is the overlap. if (countBefore > countAfter) { // Limit the overlap countAfter = Math.min(countAfter, 10); // Bound the request size based on the minimum and maximum sizes. var fetchBefore = Math.max(Math.min(countBefore, that._maxPageSize - (countAfter + 1)), that._minPageSize - (countAfter + 1)); fetchSize = fetchBefore + countAfter + 1; fetchIndex = requestIndex - fetchBefore; } else { // Limit the overlap countBefore = Math.min(countBefore, 10); // Bound the request size based on the minimum and maximum sizes. var

fetchAfter = Math.max(Math.min(countAfter, that._maxPageSize - (countBefore + 1)), that._minPageSize - (countBefore + 1)); fetchSize = countBefore + fetchAfter + 1; fetchIndex = requestIndex - countBefore; } 

This code translates the incoming range defining parameters and normalizes them to a index-size pair, taking into account the desired page size. We then construct the query against Prisjakt/PriceSpy and calls the fetch the result. That code is omitted for brevity.

 

 // execute the query and implement the continuation function 

return execQueryInternal(queryString).then( // if all goes well function (request) { var results = [], count; // wrap this into a try catch try { // parse the JSON response string var userReviews = JSON.parse(request.responseText); count = userReviews.length; // loop the user reviews result set for (var i = 0, itemsLength = count ; i < itemsLength; i++) { var userReview = userReviews[i]; userReview.comment = userReview.comment.replace(/\n/g, "<br>"); userReview.avatar = "http://www.prisjakt.nu/bilder/user_avatar.php?name=" + userReview.username + "&max=50"; // if this hasn't been added before if (i >= requestIndex) // add it to the result set results.push({ key: (i).toString(), data: userReview }); } } catch (ex) { // signal that an error has occurred return

WinJS.UI.FetchError.doesNotExist; } 

We get the response back and parse the JSON string. Then we loop the result set, adding some properties and then we add the item to the returning list, if we have provided this item before.  // possibly update the count if (count < (fetchIndex + fetchSize)) { that._count = count; } // didn't we get any new items if (results.length == 0) { return WinJS.UI.FetchError.doesNotExist; } // return the result set and counters return { items: results, // The array of items. offset: 0, // The index of the requested item in the items array. totalCount: Math.min(that._count, that._maxCount), // The total number of records. }; }, // the error handler function (response) { return WinJS.UI.FetchError.noResponse; }); } catch (ex) { // signal unknown error return WinJS.Promise.wrapError(new WinJS.ErrorFromName("unknown error:"

, ex.message)); } },   

Possibly update the item count and return the created list of items. The implementation of getCount is quite simple.

// Gets the number of items in the result list. // The count can be updated in itemsFromIndex. getCount: function () { var that = this; // simply return min of what we've got return

Math.min(that._count, that._maxCount); } 

There are additional methods in the IListDataAdapter that you can implement, but for this example they don’t have to be. We just comment them out for clarity.

 // setNotificationHandler: not implemented // itemsFromStart: not implemented // itemsFromEnd: not implemented // itemsFromKey: not implemented // itemsFromDescription: not implemented 

That’s the code you have to write to implement your own IListDataAdapter. To complete the implementation, we have to write our own IListDataSource as well. Turns out there is a base class written for this that you derive from and implement a constructor where you supply a new instance of your IListDataAdapter and you’re done. Here the implementation of this:

/* The data source for user reviews, which is consumed by the ListView */ var userReviewsDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource, function (p, count) { // create a user reviews data adapter and pass it the the VirtualizedDataSource constructor this._baseDataSourceConstructor(new userReviewsDataAdapter(p, count)); }); // define the DataSources namespace WinJS.Namespace.define("DataSources"

, { userReviewsDataSource: userReviewsDataSource }); 

Finally, here is the code that uses this data source.

var reviewsDataSource = new DataSources.userReviewsDataSource(productID, reviewscount); var reviewslistview = document.getElementById("productreviewslist").winControl; ui.setOptions(reviewslistview, { itemDataSource: reviewsDataSource, itemTemplate: document.getElementById("productReviewTemplate"

), }); 

Conclusion

The support for JSON in JavaScript/Windows 8 is excellent and very easy to use. Data binding is a key component in visualizing your data and is straightforward to use. You can even creating your own data sources if your needs are more complex. What is your experience of using data binding? Let us know!