Corrado's Blog 2.0

Online thoughts of a technology funatic

MvvmStack for WinJS: Application Resuming support

I’ve checked in a new version of MvvmStack that has a small tweak and a “new” feature, let’s start with the latter:

Resuming support

Sometimes you need to be informed when app resumes, an example is because you want to download some fresh data if app has been suspended longer than a certain amount of time.

With MvvmStack all you need to do to is to add a onAppResuming method to your services and/or ViewModels, something like: (see sectionViewModel.js on sample code)

onAppResuming: function () {
            console.log("SectionViewModel notified that app has been resumed.")
        }

And it will be invoked each time app resumes.

The ‘tweak’ regards the way you add new  ViewModels, in this post, on Closing section i mention:

“3- Add a new property “pagenameViewModel” to ViewModelLocator’s  viewModels object“ 

starting from this version the step is no longer required Smile

MvvmStack for WinJS: Services persistence

While refactoring MvvmStack for WinJS code i noticed that I did not show how to persist the state of the services when the app get suspended, so i checked in a new version that persist the data contained inside imageService.js (in a real world app, the data will probably come from a remote server).

The strategy I use is to let the services that need to keep their state add themselves to a services collection exposed by applicationControllerBase object that is passed to each service instance:

(function (winjs, applicationController) {
    var images = [];
    var cachedId;
    var imageService = {
        load: function (id) {
            return new winjs.Promise(function (c, e) {
                //Returns cached images when available
                if (cachedId == id && images.length > 0) {
                    c(images);
                } else {
                    cachedId = id;
                    if (id == 1) {
                        images = [
                            { uri: "/images/data/Photo1.jpg", title: 'Photo 1' },
                            { uri: "/images/data/Photo2.jpg", title: 'Photo 2' },
                            { uri: "/images/data/Photo3.jpg", title: 'Photo 3' },
                            { uri: "/images/data/Photo4.jpg", title: 'Photo 4' },
                            { uri: "/images/data/Photo5.jpg", title: 'Photo 5' }];
                    } else {
                        images = [
                            { uri: "/images/data/Photo6.jpg", title: 'Photo 6' },
                            { uri: "/images/data/Photo7.jpg", title: 'Photo 7' },
                            { uri: "/images/data/Photo8.jpg", title: 'Photo 8' }];
                    }

                    c(images);
                }
            });
        },
        serialize: function () {
            return {
                images: images,
                id:cachedId
            };
        },
        hydrate: function (state) {
            images = state.images;
            cachedId = state.id;
        }
    };

    winjs.Namespace.define("Demo.Services", {
        imageService: imageService
    });

    applicationController.services.push(Demo.Services.imageService);

})(WinJS, Demo.Application.ApplicationController)

As you see in code above the service also exposes the same methods serialize and hydrate we met when speaking about viewmodels that serialize and deserialize service state to/from a json object.

Once we registered and added these functions, the task of invoking them when needed is contained inside Mvvm Stack’s common peristenceService.js

Here’s a fragment of serializaion code:

 //De-Serializes services that implements serialize function
            var j = 0;
            applicationController.services.forEach(function (service) {
                if (service.hydrate !== undefined) {
                    var serviceKey = "service" + j;
                    var serviceState = winjs.Application.sessionState.app[serviceKey];
                    service.hydrate(serviceState);
                }
            });

No rocket science, but it makes stack more complete and usable in real production code.

MvvmStack for WinJS Part#5

In this last post about MvvmStack I’m going to cover two aspects: Binding and Blendabilty.

Binding

I’m not going into WinJS Binding since MSDN documentation provides a lot of material, i just want to describe some binding extensions available into binding-extension.js file that extends WinJS binding capabilities.

image

Inside Binding.Extensions namespace you find:

twoWay: I’ve described it here

eventToProperty: Allows you to update a viewmodel property when a control event occurs, you can also specify a two-way mode so that when viewmodel’s property changes the control’s property is updated with new value. The demo uses it to notify the viewmodel that user flipped the image on flipview control (on section.html page) and to move to fist or last image on set using application bar’s buttons.

Here’s how is declared inside section.html page:

        <div id="flip"
            data-win-control="WinJS.UI.FlipView"
            data-win-bind="winControl.itemDataSource:images.dataSource; winControl.onpagecompleted$currentPage.value:winControl.currentPage$two Binding.Extensions.eventToProperty"
            data-win-options="{ itemTemplate : select('.flipItemTemplate') }">
        </div>

The syntax is [triggerEvent]$[viewModelProperty]:[controlProperty][$two], previous html snippet shows how, when FlipView’s onPageCompleted triggers, we update viewmodel’s currentpage.value property using control’s currentPage property in two way mode.

invokeOnEvent: Invokes a viewmodel’s method when an event occurs, passing event source as parameter to target function (sort of ‘sender’ parameter C# counterpart) if you don’t need sender, just use standard WinJS binding.

arrayBind: Allows binding to viewmodel’s properties exposed as arrays using this syntax: [sourceProp]:[property][index][property] e.g: data-win-bind="src:dataTile[0].image"

Blendability

If you, like me, use Blend for HTML tool to design the views you know that it offers an interesting interactive mode.

By clicking following button:

image

your app is run, you can play with it, then exiting from interaction mode, start modifying the live DOM acting on current live state.

Sometimes running the app and reaching the page you want to edit is tedios, that’s why i prefer to use an alternative way.

Inside defaul.html you’ll see that there’s a commented PageControlNavigator definition, this is the one i use to start app from a specific page, let’s change default.html markup this way.

<body>
    <!--<div id="contenthost" data-win-control="Application.PageControlNavigator" data-win-options="{home:'/pages/home/home.html'}"></div>-->

    <!--Uncomment this when you want to design a specific application page, dont forget to create fake data inside ViewModelLocator-->
    <div id="contenthost" data-win-control="Application.PageControlNavigator" data-win-options="{home:'/pages/section/section.html'}"></div>
</body>

Next we need to create some design-time data for editing purposes, so we uncomment the call to createDesignTimeViewModels line inside viewModelLocator.js

  //Uncomment this to create a fake design time viewmodel to use with the page associated with default.html's page navigator
    viewModelLocator.createDesignTimeViewModels();

what the function does is create some fake data when the page is loaded inside Blend.

Opening default.html in Blend now shows the page populated with fake data so you can start editing it.

image

Closing

Think I’ve covered everything you need to know to play with sample code, as stated initially there’s room for improvements and modifications. My goal was to provide an example of how to use MVVM patter in WinJS based applications.

Hope you enjoyed reading. Winking smile

MvvmStack for WinJS Part #4

In part3 we learned how to customize the components tied to our application, it is now time to investigate the ViewModels associated with each view.

Lets’ start seeing how a viewmodel is declared, inspecting homeViewModel.js, the one paired with demo application home view.

(function (winjs, mvvmStack, target) {
    "use strict";

    var HomeViewModel = winjs.Class.derive(mvvmStack.ViewModelBase, function (applicationController) {
        var data = [{ name: "Section 1", id: 1, description: "My color photos", image: "/images/phones/lumia920.jpg" },
            { name: "Sezione 2", id: 2, description: "My b/w photos", image: "/images/phones/lumia820.jpg" }];

        this._appController = applicationController;
        this.sections = new winjs.Binding.List(data).dataSource;
        this.processAll();
    },
        {
            sections: null,
            templateRenderer: function (itemPromise) {
                return itemPromise.then(function (item) {
                    // Select either normal product template or on sale template
                    var itemTemplate;
                    if (item.data.id == 1) {
                        itemTemplate = document.getElementsByClassName("productTemplate1")[0];
                    } else {
                        itemTemplate = document.getElementsByClassName("productTemplate2")[0];
                    }

                    // Render selected template to DIV container
                    var container = document.createElement("div");
                    itemTemplate.winControl.render(item.data, container);
                    return container;
                })
            },
            itemInvoked: function (e) {
                var self = this;
                e.detail.itemPromise.then(function (item) {
                    var selectedItem = item.data;
                    self._appController.selectedHomeSection = selectedItem.id;
                    //Navigates to detail page
                   mvvmStack.Navigation.navigate(target.section)
                });
            }
        });


    winjs.Namespace.define("Demo.ViewModels", {
        HomeViewModel: HomeViewModel
    });

})(WinJS, MvvmStack, Demo.Navigation.target)

As you see, it is a class inheriting from ViewModelBase that receives some external dependencies like other MvvmStack references, target urls object and, obviously, WinJS namespace.

Inside constructor we create the sections that are going to appear in home page using static data (in real world they might come from an injected external service), in this case we have section 1 and section 2, each one has its own characteristics, also note that at the end we invoke base class function processAll that marks all function exposed by the class “safe for databinding” and ensures that ‘this’ inside any instance functions safely points to class instance (if this sounds weird to you read here) please note that, at the moment, processAll doesn’t handle functions exposed by class within nested objects.

Then we have instance properties:

sections: The sections we’re going to bind to homepage listview.

templateRenderer: Since we want each section to appear differently, we created a couple of listview templates and render them through this function depending on item’s Id value.

invoked: Is the function that gets invoked when user taps a listview item, as you see, we store section id value into shared applicationcontroller object then we use mvvmstack navigation infrastructure to navigate to section page, this will create sectionViewModel first, then navigates to section.html page.

How do we pair this viewModel to homepage view?

(function (winjs,viewModels) {
    "use strict";

    WinJS.UI.Pages.define("/pages/home/home.html", {
        // This function is called whenever a user navigates to this page. It
        // populates the page elements with the app's data.
        ready: function (element, options) {
            winjs.Binding.processAll(element, viewModels.homeViewModel);
        }
    });
})(WinJS,Demo.ViewModelLocator.viewModels);

The code is quite simple, it just uses WinJS.Binding.ProcessAll to set homeViewModel as datacontext for current view, nothing more, nothing less.

Mapping viewmodel to View elements, databinding to the rescue!

Connection of HomeViewModel properties to view elements is done exclusively via databinding, as any other MVVM implementation, here’s a homepage.html fragment:

<div class="fragment homepage">
        <header aria-label="Header content" role="banner">
            <button class="win-backbutton" aria-label="Back" disabled type="button"></button>
            <h1 class="titlearea win-type-ellipsis">
                <span class="pagetitle">MVVMStack demo...</span>
            </h1>
        </header>

        <div data-win-control="WinJS.UI.ListView"
            class="homeListView"
            data-win-bind="winControl.itemDataSource:sections; winControl.itemTemplate:templateRenderer; winControl.oniteminvoked:itemInvoked"
            data-win-options="
        {
            layout: {type: WinJS.UI.GridLayout},
            tapBehavior: 'directSelect',
            selectionMode: 'none'
        }">
        </div>
    </div>

please note the use of winControl property to bind listview control’s property with viewmodel’s.

ViewModel class naming convention

In order to let the infrastructure know what are the viewmodels to persist when a suspend event occurs, viewmodel class name must be [pageName]+”ViewModel”, so if you add an “about.html” page, associated viewmodel class must be named “aboutViewModel”.

ViewModel Persistence

homeViewModel is made of static data so it is not necessary to persist it when a suspension event occurs, let’s see instead what happens inside sectionViewModel.js using following snippet:

{
        sectionTitle: null,
        images: null,
        currentPage: null,
        id: 0,
        load: function () {
            var self = this;
            this._imageService.load(self.id).then(function (photos) {
                photos.forEach(function (photoInfo) {
                    self.images.push(photoInfo);
                });
            });
        },
        first: function () {
            this.currentPage.value = 0;
        },
        last: function () {
            this.currentPage.value = this.images.length - 1;
        },
        serialize: function () {
            return {
                currentPage: this.currentPage.value
            };
        },
        hydrate: function (bag) {
            this.currentPage.value = bag.sectionViewModel.currentPage;
        }
    }

In this case the ViewModel exposes two functions: serialize and hydrate, former is invoked by persistenceService when it needs to save viewmodel’s status, latter when it is time to rehydrated it using saved informations. If your viewmodel requires state persistance just implement both functions, persistenceService.js will take care of the rest.

Closing

Once you have setup all infrastructure, here are the step required to add new page to a project:

  • 1-  Add a new pagecontrol to the project
  • 2- Add a new viemodel class named “pagenameViewModel
  • 3- Add a new property “pagenameViewModel” to ViewModelLocator’s  viewModels object
  • 4- Add a new entry to navigationTargets.js
  • 5- Add a new switch entry into ViewModelLocator’s createViewModelForUrl function.
  • 6- Modify page code behind to set ViewModel instance as page datacontext

[note] Step 3 no longer required, see here.

MvvmStack for WinJS Part #3

In this 3rd episode of the MvvmStack saga I’m going to describe how to use the set of core mvvm modules we saw in previous posts describing application dependent modules.

image

Let’s start describing navigationTargets.js

(function (winjs) {
    "use strict";

    winjs.Namespace.define("Demo.Navigation", {
        target: {
            home: "/pages/home/home.html",
            section: "/pages/section/section.html",
            noConnectivity: "/pages/noconnectivity/noconnectivity.html"
        }
    });
    
}(WinJS))

It’s a simple object, nested into Demo.Navigation namespace (use your own namespace in production code) that exposes a set of uri pointing to application pages, this allows us to centralize pages uri and pass these infos to various modules. As soon as you add new pages this object must be updated with new properties.

applicationController.js represents an optional application object shared among viewmodels, it can be used as a general data cache and it’s also persisted during suspend/resume events.

(function (winjs,mvvmStack) {
    "use strict";

    var ApplicationController = WinJS.Class.derive(mvvmStack.ApplicationControllerBase,null,
        {
            selectedHomeSection: 0,
            serialize: function () {
                return {
                    selectedHomeSection: this.selectedHomeSection
                };
            },
            hydrate: function (applicationState) {
                this.selectedHomeSection = applicationState.selectedHomeSection;
            }
        });

    var applicationController = new ApplicationController;

    winjs.Namespace.define("Demo.Application", {
        ApplicationController: applicationController
    });

})(WinJS,MvvmStack)

As you see in previous code, the object inherits from ApplicatioControllerBase class and exposes a selectedHomeSection that is used by homepageViewModel to inform sectionViewModel about what section has been selected by the user.

serialize and hydrate are functions invoked by persistence service when object must be persisted and lately rehydrated.

ApplicationController class is nested inside Demo.Application namespace.

networkService.js

While not mandatory, it is a service that monitors network connection status and using messenger object it posts messages when network connectivity status changes. The message is subscribed and handled inside connectivityService.js module

(function (winjs, messenger) {

    var _networkInfo = Windows.Networking.Connectivity.NetworkInformation;
    _networkInfo.addEventListener("networkstatuschanged", onNetworkStatusChange);
    var notifiedStatus = isInternetAvailable();

    //Handles network status changes
    function onNetworkStatusChange() {
        var mes = messenger.networkStatusChanged();
        mes.isInternetAvailable = isInternetAvailable();
        if (mes.isInternetAvailable != notifiedStatus) {
            notifiedStatus = mes.isInternetAvailable;
            messenger.send(mes);
        }
    }

    //Gets a value indicating whether internet connection is available
    function isInternetAvailable() {
        var connectivityLevel = Windows.Networking.Connectivity.NetworkConnectivityLevel.none;
        var profile = _networkInfo.getInternetConnectionProfile();
        if (profile != null) connectivityLevel = _networkInfo.getInternetConnectionProfile().getNetworkConnectivityLevel();
        return connectivityLevel === Windows.Networking.Connectivity.NetworkConnectivityLevel.internetAccess;
    }

    winjs.Namespace.define("Demo.Application.Services.Network", {
        isInternetAvailable: isInternetAvailable
    });

})(WinJS, Demo.Application.Messenger)

connectivityService.js is a completely optional component that in the demo application handles connectivity changes messages navigating to a static noConnectivity.html pagecontrol. Your application can freely ignore this module and handle connectivity changes in a total different way, all you need to do is to subscribe the message as connectivityService does.

viewModelLocator.js takes care of creating the viewModels associated with application pages, it’s a class that derives from viewModelLocator base and overrides createViewModelForUrl function.

createViewModelForUrl is the core function that take care of creating a viewModel before page gets loaded, depending on target url.

 createViewModelForUrl: function (uri) {
                switch (uri) {
                    case target.home:
                        this.viewModels.homeViewModel = this.viewModels.homeViewModel || new Demo.ViewModels.HomeViewModel(applicationController);
                        break;
                    case target.section:
                        this.viewModels.sectionViewModel = new Demo.ViewModels.SectionViewModel(applicationController, services.imageService);
                        this.viewModels.sectionViewModel.load();
                        break;
                }
            },

The snippet demonstrates how a single instance viewModel is creates for home page, while when user navigates to section page, a SectionViewModel instance is instantiated and related images loaded.

Since each viewModel becomes a property of viewModels object, when a new ViewModel is added the object must be updated adding a property that matches ViewModel name.

this.viewModels = {
            homeViewModel: null,
            sectionViewModel: null
        };

Note: I’m sure code can be refactored to avoid this step, maybe in a future release… Smile

The code also includes a createDesignTimeViewModels function, I’ll describe it in a forthcoming post regarding Blendability.

Closing

In next post we’ll analyze pages ViewModels, how to create and pair them with related View.

MvvmStack for WinJS Part #2

Following part 1, let’s now see how the code is structured:

image

Inside mvvm folder there are all the files that provides core functionality, this means that you can reuse this files in different apps without modification.

Inside pages folder there are, as usual, the PageControls representing application pages, as you see in this case there’s a new actor: the associated viewmodel (e.g homeViewModel.js)

Infrastructure bootstrapping

The demo is based around HTML Navigation Application template, if you need to create a new app, just select it and let Visual Studio 2012 create all required files for you, then delete navigator.js file since it is already included inside mvvm folder in a slightly modified version.

Dependencies

Looking at the code you’ll see that I like to explicitly indicate what are each file dependencies passing them to module self-invoking function, that implies that modules must be loaded using a prefixed sequence otherwise dependency would become unresolved.
The sequence is available inside default.html file that represents application master page:

<head>
    <meta charset="utf-8" />
    <title>MvvmStack</title>

    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.1.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.1.0/js/ui.js"></script>


    <link href="default.css" rel="stylesheet" />
    
    <!--MVVM-->
    <script src="../../js/mvvm/applicationControllerBase.js"></script>
    <script src="../../js/mvvm/viewModelLocatorBase.js"></script>
    <script src="../../js/mvvm/viewModelBase.js"></script>
    <script src="../../js/mvvm/binding-extensions.js"></script>
    <script src="../../js/mvvm/messenger.js"></script>
    <script src="../../js/mvvm/navigator.js"></script>

    <script src="default.js"></script>
    <script src="../../js/applicationController.js"></script>
    <script src="../../js/navigationTargets.js"></script>
    <script src="../../converters/converters.js"></script>

    <!--core services-->   
    <script src="../../js/networkService.js"></script>

    <!--data services-->
    <script src="../../js/imageService.js"></script>

    <!--Viewmodels-->
    <script src="../home/homeViewModel.js"></script>
    <script src="../section/sectionViewModel.js"></script>

    <!--services-->
    <script src="../../js/ViewModelLocator.js"></script>
    <script src="../../js/mvvm/persistenceService.js"></script>
    <script src="../../js/mvvm/navigationService.js"></script>

</head>

As evident mvvm core modules are loaded first, then modules that depends/inherits from core modules then other optional core services like networkService (a service that monitors network connection status) followed by page viewmodels and ending with modules that require all modules accessibility like ViewModelLocator, PersistenceService and NavigationService.

Core Mvvm modules

Let’s now see what are the core mvvm modules:

  • ApplicationControllerBase.js

  • Is the base class for ApplicationController, an object that can be shared among ViewModels that I use as a quick way to pass informations among them, it’s state is normally persisted when app gets suspended. Obvisouly it is an optional part but demo shows how you can use it to share parameters from home page to section page.

  • binding-extensions.js

  • This is a general binding helper module an not tied to Mvvm, it contains helper functions that add two-way binding, two-way binding triggered by an event and a event to method invoker. If you want to extend WinJS binding this file would help you.

  • messenger.js

  • This represents a generic message broker allowing you to send messages among loaded modules, app uses it to navigate to a “no connectivity” page when connection drops. (try it running the app then activating flight mode) It includes a couple of predefined messages: networkStatusChanged and navigatedBack.

  • navigationservice.js

  • This is the modules that handles page navigation, it uses WinJS navigation infratructures but it takes care of instantiating the page viewmodels when required.

  • navigator.js

  • This is the same navigator objects that you get when you choose a WinJS navigation template as starting Javascript template in Visual Studio 2012, it has been slightly modified to send a navigatedBack message when uses navigates back from a page, the message instruct the infrastructure that associated viewmodel should not be persisted in case of suspension.

  • persistenceService.js

  • This is the service that, when a suspension request occurs, serializes applicationController and all viewModels implementing serialize method. It also re-instantiate viewmodels and rehydrates them when application resumes.

  • viewModelBase.js

  • Base class for all viewmodels, it includes a processAll function that marks all viewmodel’s function as ‘safe for processing’ and set ‘this’ context to viewmodel instance.

  • viewModelLocatorBase.js

  • Base class for ViewModelLocator, a class that takes care of creating/deleting page viewModels.

Closing

That’s all for now, on next episode we’ll see what you need to customize to use the stack in your app.

MvvmStack for WinJS Part #1

At recent Community Days 2013 conference i had a talk about WinJS development where, at the end, i introduced a demo about MVVM development in HTML 5/Windows Store app, since i found near to zero documentation about this, i decided to share some personal experience.

I’m not going to explain what MVVM (Model-View-ViewModel) is, there is a lot of documentation in Internet, if you need a starting point have a look Laurent Bugnion’s article here, but, as seasoned XAML developer, i admit that I can no longer create production apps without it.

In this first post I’m going to describe what you get when you download the code from http://mvvmstack.codeplex.com.

Running the sample from Visual Studio 2012 open this starting page:

image

This is just a basic page that lets you navigate to anoter section passing a param depending on which choice you select, in both cases you land to a page where you can ‘flip’ some images

image

image

This section also includes an application bar with a couple of buttons that let you to select first and last image on set (not really helpful indeed, but it shows how to bind app bar commands to page ViewModel)

Demo also includes suspend and resume functionality, in any moment you can use Visual Studio 2012 Suspend command to trigger app Termination and Resume it to the exact point it was left.

image

Another interesting aspect is that it supports Expression Blend interactive mode: just open the default.html page in Expression Blend and click Turn on Interactive Mode to start interacting with the app.

image

navigate to the page you wish to edit, press the button again to exit interactive mode and start editing the live DOM.

image

Since navigating to a page from the start might be a cumbersome in some cases, the demo lets you generate some design time data and edit a page without navigation.

image

In previous screenshot you see section.html opened in Expression Blend with some dummy data.

Closing

Get the code from http://mvvmstack.codeplex.com and start playing with it, while far from complete and without presuming to be the best MVVM implementation, i hope you’ll find interesting hints for your next HTML5 Windows Store Application.

I’ll dig into more detail in coming posts…

 

The code is provided “as is”, without warranty of any kind, feedbacks are welcome!

WinJS Custom Controls

Controls are the basic blocks of any application, you use controls everywhere and also HTML Metro apps are part of the game, you add a control inside a HTML Metro page just placing a <div> with a special “data-win-control” attribute in it:

here’s an example:

<div data-win-control="WinJS.UI.SemanticZoom" > </div>

If you have pages (even in different applications) that need some special kind of functionality paired with some custom UI, custom controls allows you to reach the “write once, use everywhere” paradigm (unless you like reinventing the wheel of course…)Let’s see how you can create your own custom control and use it inside a HTML Metro application, simulating a simple custom countdown control.

Step 1: Design the user interface

While not strictly a requirement I personally like to start from the appearance of my control, so I fire up Expression Blend and start design it:

image

I’ve highlighted the HTML representing my control’s UI and, on right side, associated CSS, question now is: how do we turn it in a WinJS custom control?

Step2: Define public interface

The countdown control will expose:

  • initialValue property: will be initialized with countdown initial value
  • start method: begins countdown
  • countdownexpired and coundownstarted events


Step3: The code

below is the control’s code contained in a separate countdown.js file:

//Countdown control
(function (winJs) {
    var _events = ["countdownexpired", "coundownstarted"];

    WinJs.Namespace.define("MyApp.Controls", {
        Countdown: WinJs.Class.define(function (element, options) {
            if (!element) throw "element must be provided";
            options = options || {};
            this._element = element;
            this._element.winControl = this;
            this._buildVisualTree();
            winJs.UI.setOptions(this, options);
        },
            {
                //Private members
                _element: null,
                _cdn_host: null,
                _cdn_content: null,
                _timerId: 0,
                _progress: 0,
                _buildVisualTree: function () {
                    this._cdn_host = document.createElement("div");
                    this._cdn_host.className = "cdn-host";
                    this._cdn_content = document.createElement("span");
                    this._cdn_content.className = "cdn-content win-type-xx-large";
                    this._cdn_host.appendChild(this._cdn_content);
                    this._element.appendChild(this._cdn_host);
                },
                _onTick: function () {
                    this._progress--;
                    this._cdn_content.innerText = this._progress;
                    if (this._progress === 0) {
                        this.dispatchEvent("countdownexpired");
                        clearInterval(this._timerId);
                        this._timerId = 0;
                    }
                },

                //Public members
                element: {
                    get: function () { return this._element; }
                },
                initialValue: {
                    get: function () { return this._cdn_content.innerText; },
                    set: function (value) {
                        this._cdn_content.innerText = value;
                    }
                },

                //Play advertising
                start: function () {
                    if (this._timerId === 0) {
                        this._progress = this.initialValue;
                        this._timerId = setInterval(this._onTick.bind(this), 1000);
                        this.dispatchEvent("coundownstarted");
                    }
                }
            })
    });

    winJs.Class.mix(MyApp.Controls.Countdown, winJs.Utilities.eventMixin);
    winJs.Class.mix(MyApp.Controls.Countdown, winJs.Utilities.createEventProperties(_events));

Let’s dissect it:

A WinJS custom control is just a class that has a constructor accepting two parameters: the element decorated with data-win-control attribute and optional options object containing control’s initialization parameters.

Our control will be contained inside a MyApp.Controls namespace.

Inside constructor I associate passed element with control’s element property and I also add associate control’s instance with a conventionally named winControl property appended to passed element so that it can easily accessible from page’s javascript code.

The buildVisualTree function uses javascript to recreate control’s DOM and appends it to passed element while setOptions is a WinJS helper method that associates passed options to control so that it can be correctly initialized.

Since the control exposes events we finally use a couple of WinJS helper methods to ‘mix’ control’s class with a class defined inside WinJS framework to add all stuff required to handle and raise events (like onXYZ methods or addEventListener/removeEventListener support)

The rest of the code is just countdown code implementation, nothing really new here.

Step 4: Using the control

To embed the code the control inside a HTML page we use exactly the same approach used for any WinJS control: add both required javasript and css references and decorate placeholder div with appropriate attributes, here’s the complete HTML page content:

<html>
<head>
    <meta charset="utf-8" />
    <title>DemoCustomControl</title>

    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.1.0.RC/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.1.0.RC/js/base.js"></script>
    <script src="//Microsoft.WinJS.1.0.RC/js/ui.js"></script>

    <!-- DemoCustomControl references -->
    <link href="/css/default.css" rel="stylesheet" />
    <link href="/countdown.css" rel="stylesheet" type="text/css">
    <script type="text/javascript" src="js/controls/countdown.js"></script>
    <script src="/js/default.js"></script>

    <script>
        WinJS.UI.processAll();
        WinJS.Utilities.ready(function () {
            var countdown = document.getElementById("countdown1");
            countdown.winControl.addEventListener("countdownexpired", function () {
                var msg = document.getElementById("result");
                msg.textContent = "done!";
            }, false);

            document.getElementById("start").addEventListener("click", function () {
                countdown.winControl.start();
            }, false);

        }, true).done();
    </script>

</head>
    <body>
        <div id="countdown1" data-win-control="MyApp.Controls.Countdown" data-win-options="{initialValue:4}">
        </div>
        <button id="start">Start</button>
        <div id="result"></div>
    </body>
</html>

A couple of notes:

  • we invoke WinJS.UI.processAll() method to force ‘transformation’ of ‘countdown1’ div into countdown control
  • we use WinJS.Utilities.ready() function to be sure that DOM tree is available when code runs (something that JQuery users know quite well).
  • note how countdown control is initialized using data-win-options attribute to 4 seconds.

Pressing start you will now see countdown starting and a message appearing at the end.< I found little documentation about creating custom control, that’s the reason of this post, if you want to know more I recommend this Build session: http://channel9.msdn.com/Events/BUILD/BUILD2011/APP-846T