Corrado's Blog 2.0

Online thoughts of a technology funatic

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.

1 Comment “MvvmStack for WinJS Part #4”

  1. Ciao, scrivo in italiano così mi spiego meglio.
    Io ho una ListView e vorrei cambiare dinamicamente il tapBehavior perchè in alcune condizioni devo selezionare più elementi in altre dovrò solo aprire una pagina di dettaglio.
    E’ possibile utilizzare una proprietà nel viewModel per fare questo?

    Comment by simone — 18/07/2014 @ 15:58

RSS feed for comments on this post. TrackBack URL

Leave a Response