Metro JavaScript App - strongly typed?

Let’s face it, even when I choose to build Metro style apps with HTML5, JavaScript and CSS because I love the dynamic nature of this environment, there are times when I’m a little bit jealous of that strong typed stuff that you get in that boring C#/XAML world ;-).

Let see if we can take an existing application like Jeremy Foster’s excellent A Metro Netflix browser in HTML/JS - The Hub page, add some magic (JayData) to it and see if that helps reducing my jealousy .

Start by downloading Jeremy’s code that is attached to the blog. While on it don’t miss to download and install Fiddler 4 and the Windows 8 AppContainer Loopback Utility.

Simply follow the Jeremy’s instructions, fire up fiddler and run the code.

Checkpoint: Make sure that you see some titles coming back from Netflix. Fiddler Response

Looking at fiddler you should see something like the above. Notice the response size of about 740K. At the moment we are not leveraging OData to its full extend. I know that if you’re an OData expert you already spoted a way to reduce the response by using server side projection. But wait, maybe there are others that aren’t experts yet, so we come back to this a little later.

Next download the latest version of JayData and JaySvcUtil from the download area. Extract both files into two separate directories, we don’t want all of them lingering around in our projcect directory, we cherry pick instead the bare minimum.

So from the JayData directory add the following files into the project’s js directory

  • JayData-vsdoc.js
  • JayData.js
  • datajs-1.0.2.js

Open up your preferred command line and navigate to the JaySvcUtil directory. JaySvcUtil.exe is command line utility that allows converting OData $metada information into a strongly typed JavaScript object. While it has several switches for fine tuning, we only require three of them.

  • -m $metadata url of your service e.g http://odata.netflix.com/catalog/$metadata
  • -n Namespace e.g. here Netflix
  • -o output file e.g. Netflix.js
Rainer@METRORP ~/Downloads/JaySvcUtil
$ JaySvcUtil.exe -m 'http://odata.netflix.com/catalog/$metadata' -o Netflix.js -n 'Netflix'

If everything went smoothly the system should have generated your ‘Netflix.js’ file. Depending on the complexity of your service you might receive a couple of warning messages, which - most of the time - can safely be ignored. Please add the newly created ‘YourOutputFile.js’ to the project’s js directory as well.

Your default.html should now look like this:

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="utf-8" />
 5     <title>NetflixBrowser</title>
 6 
 7     <!-- WinJS references -->
 8     <link href="//Microsoft.WinJS.1.0.RC/css/ui-dark.css" rel="stylesheet" />
 9     <script src="//Microsoft.WinJS.1.0.RC/js/base.js"></script>
10     <script src="//Microsoft.WinJS.1.0.RC/js/ui.js"></script>
11 
12     <!-- NetflixBrowser references -->
13     <link href="/css/default.css" rel="stylesheet" />
14     <script src="/js/default.js"></script>
15     <script src="/js/navigator.js"></script>
16 
17     <!-- JayData references -->
18     <script src="/js/datajs-1.0.2.js"></script>
19     <script src="/js/JayData.js"></script>
20     <script src="/js/netflix.js"></script>
21 
22 </head>
23 <body>
24     <div id="contenthost" data-win-control="Application.PageControlNavigator" data-win-options="{home: '/pages/home/home.html'}"></div>
25     <!-- <div id="appbar" data-win-control="WinJS.UI.AppBar">
26         <button data-win-control="WinJS.UI.AppBarCommand" data-win-options="{id:'cmd', label:'Command', icon:'placeholder'}"></button>
27     </div> -->
28 </body>
29 </html>

Checkpoint: Run the app just to see if all files can be loaded and no error occurs. Netflix.js errors

While doing that in my case Visual studio was complaining about some errors in Netflix.js and right so. You can see that there are a couple of mistyped ..., params : , ... in the code begining at line 166. If that the case for you as well go ahead and remove them. Not quite sure if that’s Netflix or an JayData issue, but it’s reported to JayData.

Alright almost done with the setup phase. Let’s open up js/home.js and

  1. get rid of the current WinJS.xhr call
  2. add some references to the JayData files to get Intellisense support.

You’re home.js should look like below. Remember that you need to compile once to get Intellisense picking up the changes.

 1 /// <reference path="/js/jaydata.js" />
 2 /// <reference path="/js/jaydata-vsdoc.js" />
 3 /// <reference path="/js/netflix.js" />
 4 (function () {
 5  "use strict";
 6 
 7  WinJS.UI.Pages.define("/pages/home/home.html", {
 8      // This function is called whenever a user navigates to this page. It
 9      // populates the page elements with the app's data.
10      ready: function (element, options) {
11 
12          var titlesListGrouped = new WinJS.Binding.List().createGrouped(
13              function (i) { return i.title.charAt(0).toUpperCase(); },
14              function (i) { return { firstLetter: i.title.charAt(0).toUpperCase() }; }
15          );
16 
17          var list = q("#list").winControl;
18          list.itemDataSource = titlesListGrouped.dataSource;
19          list.itemTemplate = q("#template");
20          list.groupDataSource = titlesListGrouped.groups.dataSource;
21          list.groupHeaderTemplate = q("#headertemplate");
22 
23      }
24  });
25 })();

Now, roll drum… here’s the magic moment :), type in Netflix.context. and you should see Intellisene kicking in. Before we move on let’s celebrate by leaving the computer and doing whatever you prefer to do when you celebrate.

Intellisense

Alright, back to work! Your first goal would be to come up with an equivalent of the original WinJS.xhr call

 1 WinJS.xhr({ url: "http://odata.netflix.com/Catalog/Titles?$format=json&$top=200" })
 2     .then(function (xhr) {
 3         var titles = JSON.parse(xhr.response).d;
 4         titles.forEach(function (i) {
 5             titlesListGrouped.push({
 6                 title: i.ShortName,
 7                 imageUrl: i.BoxArt.LargeUrl
 8             });
 9         });
10     });

Converting this is pretty straight forward. Netflix.context is already aware of the URL and JayData will add the appropriate Request-Header, so that the Odata service returns Json. That leaves us with teaching the system that we would like to retrieve 200 Titles. By looking at the available methods for Netflix.context.Titles we notice the take()method, which probably sounds familiar to many of you. There are several other methods like include(), filter(), orderBy(), skip(), map() and forEach that are all part of the JavaScript Language Query. If you never heard of any of those or want to learn more about it check them out at (JSLQ) 101 and come back later.

The last thing we need to do is reusing the logic of the original forEach loop to push the results into titlesListGrouped.

1 Netflix.context.Titles
2     .take(200)
3     .forEach(function(item) {
4         titlesListGrouped.push({
5             title: item.title,
6             imageUrl: item.BoxArt.LargeUrl
7         });
8     })

Please note that at the time of this writing, while there is an JayData deferred.js adapter available for jQuery there’s NO WinJS aware adapter… yet. So at the moment you won’t be able to create promises and use .then() or .done().

Checkpoint: Run the code and make sure that you still see titles coming back from Netflix. Alright how does that feel so far? Without being an OData expert you just conducted an OData query, without leaving the comfort zone of Visual Studio and its Intellisene. Not too shabby I’d say. But we’ll take it even a step further and try making our OData request a little bit more precise.

Can we do that? Of course we can by leveraging the .map method; and you’ll see, which benefits that brings in just a moment. Please note that I couldn’t bring Intellisense to show available properties for item, but when you look at the Json returned by the service in Fiddler, you can see that we map title: item.ShortName and BoxArt: item.BoxArt and return that as an anonymous object. So in the forEach loop we access those as item.title and item.BoxArt.

 1 Netflix.context.Titles
 2     .map(function(item) {
 3         return {
 4             title: item.ShortName,
 5             BoxArt: item.BoxArt
 6         };
 7     })
 8     .take(200)
 9     .forEach(function(item) {
10         titlesListGrouped.push({
11             title: item.title,
12             imageUrl: item.BoxArt.LargeUrl
13         });
14     })

Fiddler 140K If you are now running the application and watch Fiddler you see that the return set is way smaller than before. We are down from 740K to about 140K. So by using the strongly typed Netflix object you not only gained the benefit of Intellisense support, you somehow became an OData expert as well ;-). Closer watching the URL in Fiddler reveals that there’s a new $select=ShortName,BoxArt parameter that was added by JayData, so now the projection is accomplished at the server side and no longer at the client.

That’s it for today and truth told I feel my jealousy about strongly typed C#/XAML fading away and my excitement about JavaScript grow. But everybody’s different, so here are two choices if you want to move on with Jeremy’s Metro app:

  1. You can get rid of all JayData files you’ve added and apply the knowledge about the $select parameter to enhance the original WinJS code manually. You will see the same performance benefits that we got here.

    Just so that you know, there’s nothing wrong with this approach, really.

  2. But maybe you want to stay a little with JayData and see how it pays off.

For those of you, who are on the fence line, it might be good to know that JayData supports a couple of other formats that can be all accessed using the same JSQL you started getting familiar with.

Facebook Query Language, SqlLite, Yahoo Open Data Tables, InMemoryDB, WebSQL, IndexedDb

and there are more in the pipeline

I’d be glad to hear from you about your decission and the reasoning behind it.

PS: For the impatient ones that wants to start immediately with JayData version of Jeremy’s Metro example feel free to clone this repo.

Published: July 13 2012

blog comments powered by Disqus