(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Windows Store apps with XAML and HTML: Organize your UI layer in HTML

(1 votes)
Andrea Boschin
>
Andrea Boschin
Joined Nov 17, 2009
Articles:   91
Comments:   9
More Articles
0 comments   /   posted on Sep 24, 2013
Categories:   Windows 8

Tweet

In my last article I showed a simple example about the organization of the UI layer using Model View ViewModel in XAML. There is not any doubt that this pattern is a great help to make the application more maintainable, thanks to the effective separation between logic and views. MVVM is born with XAML, but after you embraced its power, it is hard to give up and return to the previous ways to develop applications. So my question is: "is it possible to apply the same pattern outside of XAML, also in an HTML5 app?" The first and evident obstacle to this achievement is the complete lack of databinding in Javascript and additionally the lack of some tools, like observables types, that directly feed the pattern.

Meet Knockout JS

Luckily someone has my same feel and he created a javascript framework that enables MVVM in web pages, to support that so called Single Page Applications (something you have to try if you are a web developer). Knockout JS is a very impressive and powerful toolset that adds the missing tools to Javascript. It is based on the concept of observables object and on a set of bindings that may be added to a plain html page to simulate the databinding you are use to see in XAML. As I said Knockout JS (http://knockoutjs.com/) is for html pages but it is enough smart and flexible to be used inside a Windows Store app, without great troubles.

At the very basis of the library there are observables. These are objects that are able to encapsulate a plain variable and make it able to notify its status changes. After you included Knockout JS in a page, you can make a variable (or an array) observable using something like this:

   1: // simple types
   2: this.name = ko.observable("Andrea")
   3:  
   4: // arrays
   5: this.names = ko.observableArray(["Andrea", "Gaia", "Daniela"]);

After you exposed a property like these in a Javascript class (the viewmodel), you can connect it to the view using a simple "data-bind" attribute. It appears like this:

   1: <span data-bind="text: name" />

Then, the last thing is to connect the View with ViewModel. Usually this happens in the document's ready event. This starts Knockout binding and make the magic happen. Usually people that uses jQuery write something like this:

   1: $(function(ev)
   2: {
   3:     ko.applyBindings(new MyViewModel());
   4: });

So now you can update your property and have it updated into the view automatically. The trick is to call a method with the same name of the property instead of assigning the value to the property itself:

   1: // for simple properties
   2: this.name("Gaia");
   3:  
   4: // for arrays (add an element)
   5: this.names.push("Vanessa");

Observable arrays can be manipulated with a set of methods that is similar to the ones you are use to see in a plain javascript array.

Apply Knockout JS to Windows Store apps

What you seen in the previous section, also applies to a Windows Store app in HTML. Knockout JS works like a breeze and you only have to take care of where to activate the bindings. Usually the best place is the ready event into the page's codebehind.

But we should make a step behind: let say we want to replicate the example of the previous article. You firstly create the NamePage.html and this automatically creates the NamePage.js. Then you reference this page in the default.html as usual and then you need to add references to Knockout JS. The default.html page is the best place to put the script tags because it is the container for each other page:

   1: <!DOCTYPE html>
   2: <html>
   3: <head>
   4:     <meta charset="utf-8" />
   5:     <title>Elite.HTML5vsXAML.Html</title>
   6:  
   7:     <!-- WinJS references -->
   8:     <link href="http://Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
   9:     <script src="http://Microsoft.WinJS.1.0/js/base.js"></script>
   2:     <script src="http://Microsoft.WinJS.1.0/js/ui.js"></script>
   1: 
   2:  
   3:     <!-- Knockout references -->
   4:     <script src="/js/knockout-2.3.0.js"></script>
   1:     <script src="/js/knockout-winjs.js"></script>
   2:  
   3:     <!-- Elite.HTML5vsXAML.Html references -->
   4:     <link href="/css/default.css" rel="stylesheet" />
   5:     <script src="/js/default.js"></script>
   2:     <script src="/js/navigator.js"></script>
  10: </head>
  11: <body>
  12:     <div id="contenthost" 
  13:         data-win-control="Application.PageControlNavigator" 
  14:         data-win-options="{home: '/views/NamePage/NamePage.html'}"></div>
  15: </body>
  16: </html>

Then you go to the NamePage.js file and into the ready event you activate the binding this way:

   1: /// <reference path="../../js/knockout-2.3.0.js" />
   2: /// <reference path="../../js/knockout-winjs.js" />
   3:  
   4: (function ()
   5: {
   6:     "use strict";
   7:     WinJS.UI.Pages.define("/views/NamePage/NamePage.html", {
   8:         ready: function (element, options)
   9:         {
  10:             ko.applyBindings(new NamePageViewModel(WinJS.Navigation.state));
  11:         }
  12:     });
  13: })();

The NamePageViewModel class is the ViewModel assigned to this page. It is created as a plain Javascript class and exposes two properties: detail and related. If you remember, in the previous article I've separated the two parts of the page in two different usercontrols. The purpose of these properties is to feed the two parts also in this HTML example.

   1: /// <reference path="../../js/knockout-2.3.0.js" />
   2: /// <reference path="../../js/knockout-winjs.js" />
   3: /// <reference path="../../js/data.js" />
   4:  
   5: var NamePageViewModel = (function ()
   6: {
   7:     function NamePageViewModel(id)
   8:     {
   9:         var _this = this;
  10:  
  11:         _this.detail = ko.observable(new NameDetailViewModel(id));
  12:         _this.related = ko.observable(new RelatedNamesViewModel());
  13:     }
  14:  
  15:     return NamePageViewModel;
  16: })();

Each property is made observable because it is subject of binding to the corresponding View. In the page each part is represented by a div element and I use a special Knockout's binding that has the purpose of converting a property in the ViewModel of an element. It is the "with" binding:

   1: <section aria-label="Main content" role="main">
   2:     <div id="detail" data-bind="with: detail" style="margin: 80px 0px 0px 120px;"
   3:         data-win-control="WinJS.UI.HtmlControl" data-win-options="{uri: '/views/NameDetail/NameDetail.html'}"></div>
   4:     <div id="related" data-bind="with: related" style="margin: 80px 0px 0px 40px;"
   5:         data-win-control="WinJS.UI.HtmlControl" data-win-options="{uri: '/views/RelatedNames/RelatedNames.html'}"></div>
   6: </section>

The two parts of the interface are created as WinJS HtmlControls. They appear to be similar to a normal page with the sole difference they do not have a root <html> tag but they start with a <div> element. Here I show the RelatedNames.html page where I placed a ListView with the GridLayout. This grid is feeded by an observable property called "names". Unfortunately the ListView does not directly support KnockoutJS's binding because ist "itemDataSource" property is only exposed to Javascript and not as a plain html property. Have a look at the HTML:

   1: <div id="relatedGrid"
   2:     data-bind="listViewBinding: names"
   3:     class="win-selectionstylefilled"
   4:     data-win-control="WinJS.UI.ListView"
   5:     data-win-options="{  
   6:         itemTemplate: select('#itemTemplate'),  
   7:         selectionMode: 'single',  
   8:         tapBehavior: 'invokeOnly',  
   9:         swipeBehavior: 'none',  
  10:         layout: { type: WinJS.UI.GridLayout }  }">
  11: </div>

The listViewBinding key into the "data-bind" attribute of the ListView is something I've created as an extension of KnockoutJS. It enables the binding of a plain observable, converted to a WinJS List, to the itemDataSource property of the ListView. The binding is a simple extension included in the downloadable example in the knockout-winjs.js file. Here it is:

   1: ko.bindingHandlers.listViewBinding = {
   2:     init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
   3:     {
   4:         var observable = valueAccessor();
   5:         var data = observable();
   6:         WinJS.UI.setOptions(element.winControl, { itemDataSource: new WinJS.Binding.List(data).dataSource })
   7:     },
   8:  
   9:     update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
  10:     {
  11:         var observable = valueAccessor();
  12:         var data = observable();
  13:         WinJS.UI.setOptions(element.winControl, { itemDataSource: new WinJS.Binding.List(data).dataSource })
  14:     }
  15: };

The binding extension does two things: it handles initialization and updates connection the property it receive in the "valueAccessor" (it's a function, not a variable) and wraps it into a WinJS.Binding.List to provide a valid datasource to the ListView. The trick is that the return value of valueAccessor() is again a function from KnockoutJS that returns the observable array.

So another step forward is done. Now, given that the html controls are in place for both the parts, we should have made all the bindings. You have to be aware that WinJS also have its binding system that usually is applied for item templates. It continue to work as usual, and you can to choose between the two ways every time, using the best that matches your goal. Now you should have the two parts working and we have to dive to get user interactions (clicks on ListView) and update according the other parts. We should add to the ListView another custom binding:

   1: data-bind="listViewBinding: names, itemInvoked: itemClicked"

The new binding is required for the same reason of the previous. KnokoutJS contains a "click" binding that is usually fitted to handle click of elements to reflect them as commands in the ViewModel. Unfortunately Knockout is not aware of ListView so you need a custom binding to wire the itemInvoked event and map it to the view model. The event is someway similar to the other I shown but it have some tricky code:

   1: ko.bindingHandlers.itemInvoked = {
   2:     init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
   3:     {
   4:         element.winControl.oniteminvoked =
   5:             function (e)
   6:             {
   7:                 e.detail.itemPromise.then(function (item)
   8:                 {
   9:                     var handler = valueAccessor();
  10:                     handler.apply(viewModel, [item.data, item.index]);
  11:                 });
  12:             }
  13:     },
  14:     update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
  15:     {
  16:         element.winControl.oniteminvoked =
  17:             function (e)
  18:             {
  19:                 e.detail.itemPromise.then(function (item)
  20:                 {
  21:                     var handler = valueAccessor();
  22:                     handler.apply(viewModel, [item.data, item.index]);
  23:                 });
  24:             }
  25:     }
  26: }

The trick comes from the usage of the "apply" method to call the target method. The binding simply wires the oniteminvoked event of the element (using the winControl property). Then, when the event is catched, it calls the derired method on the viewmodel passing the viewmodel itself as context for the call. This enable the appropriate use of "this" into the viewmodel class, that differently would be unable to access its own instance.

   1: RelatedNamesViewModel.prototype.itemClicked = function (item, index)
   2: {
   3:     // here this is pointed to the class itself
   4: }

Finally, at the end of this long walk into the application flow, we need to send a notification to the other viewmodel as we did with the Messenger in XAML. Neither javascript or knockout have something that can help in this goal. Simple messenger does not exists so we have to create something by ourself. Here is a simple basic dispatcher that is able to receive messages and notify a number of registered subscribers:

   1: var Dispatcher = (function ()
   2: {
   3:     function Dispatcher()
   4:     {
   5:         this.subscribers = [];
   6:     }
   7:     Dispatcher.current = new Dispatcher();
   8:     Dispatcher.prototype.subscribe = function (handler)
   9:     {
  10:         var item = this.subscribers.indexOf(handler);
  11:         if (item == -1)
  12:         {
  13:             this.subscribers.push(handler);
  14:         }
  15:     };
  16:     Dispatcher.prototype.send = function (message, arg)
  17:     {
  18:         this.subscribers.forEach(function (item)
  19:         {
  20:             item(message, arg);
  21:         });
  22:     };
  23:     return Dispatcher;
  24: })();

This example lacks many features, but is gives an idea about how to create its own messenger. You probably need to add methosd to unsubscribe, the capability of delivering targeted messages and so on but for the sake of this example it suffices. Here is hot to send a message:

   1: RelatedNamesViewModel.prototype.itemClicked = function (item, index)
   2: {
   3:     Dispatcher.current.send("selected", item);
   4: }
And here how you can subscribe to receive notifications:
   1: Dispatcher.current.subscribe(function (message, arg)
   2: {
   3:     _this.name(arg.name);
   4:     _this.gender(arg.gender);
   5:     _this.description(arg.description);
   6:     _this.origin(arg.origin);
   7: });

Conclusions

In these months I've shown a number of features implemented by both html5 and xaml. As a conclusion I would like to remark that the purpose of this series was to enable people to easily migrate from a platform to the other when a customer requires an implementation in the language that is not yours. I'm really confident that this series will not move anyone from html to xaml or viceversa just because, as I'm strongly trained with xaml and I manage to use it every time I can just because it makes me fast as the light, someone may have the opposite feel. So I hope you may find useful reading these words and they help you to cross the line that separated these two representations of the same story.


Subscribe

Comments

No comments

Add Comment

Login to comment:
  *      *       

From this series