MVVM in WinJS Part 2 – Observable Collections

.NET Musings

Wandering thoughts of a developer, architect, speaker, and trainer

NAVIGATION - SEARCH

MVVM in WinJS Part 2 – Observable Collections

Getting Started

As I explained in my last post on MVVM in WinJS, the goal of the MVVM pattern (as well as the Presentation Model pattern) is to create a separation of concerns between the model and the view.  This eliminates the need for the model to have knowledge of the view, and leverages data binding techniques to make sure the state of the model is always accurately represented by the view.

The next step in implementing the MVVM pattern is Observable Collections.  Just as important as having individual items being Observable by the View for changes are collections that will notify the View when items are added or deleted. Fortunately, Microsoft has made this extremely simple with the WinJS.Binding.List. 

This post picks up where my last post left off, so please download the project from here.

Updating the Model

To start I am going to add a Binding List as a property of the ViewModel.  WinJS Binding Lists work very much like JavaScript arrays, supporting push/pop/slice/splice/etc.  The main difference is accessing items – instead of using an indexer, items are accessed by using a getter and a setter – specifically getAt(x) and setAt(x). The code in Listing 1 shows the two changes to the viewModel.js code from the initial project (both highlighted in yellow).  In the namespace definition, I created a new property of the ViewModel named ContactList and set to a new WinJS.Binding.List(). 

Adding Observable Objects to the BindingList – the Wrong Way

If you are coming to WinJS from a traditional object oriented language (like C#), you might be tempted to add records to the list as I show in the last line of the self executing function.  This works for one record, but once you attempt to add more than one record in this manner, it becomes apparent that they are all pointing to the same record. The problem with this is that JavaScript doesn’t have classes like C# (experienced JavaScript developers know this already). 

/// <reference path="//Microsoft.WinJS.1.0/js/ui.js" />
/// <reference path="//Microsoft.WinJS.1.0/js/base.js" />
(function () {
    "use strict";
    
    WinJS.Namespace.define("ViewModel", {
        Contact: WinJS.Binding.as({
            firstName: '',
            lastName: '',
            age: 0
        }),
        ContactList: new WinJS.Binding.List(),
    });
    ViewModel.Contact.firstName = "Philip";
    ViewModel.Contact.lastName = "Japikse";
    ViewModel.Contact.age = 44;
    ViewModel.ContactList.push(ViewModel.Contact);
})();

Listing 1 – Initial View Model with Binding List

Adding Observable Records to a BindingList – the Correct Way

To add observable records to the BindingList use the WinJS.Binding.as method (this was explained in detail in my earlier post).  Start by adding a property function to the ViewModel namespace that adds a new record as shown in Listing 2.  Wrapping the anonymous object with WinJS.Binding.as() makes it observable.  We lose the predefined Contact “class”, but in a later post I will show how to use the WinJS.Class.define() construct to create a Contact Class.

addContact: function (firstName, lastName, age) { 
    this.ContactList.push(WinJS.Binding.as(
        { firstName: firstName, lastName: lastName, age: age }));
},
Listing 2 – Function for adding an Observable object to the BindingList

Adding contact records is as simple as calling the function on the ViewModel and passing in the correct parameters, as shown in Listing 3.

ViewModel.addContact("Philip", "Japikse", 44);
ViewModel.addContact("Fred", "Flinstone", 35);
ViewModel.addContact("Barney", "Rubble", 31);

Listing 3 – Adding Multiple Records

Adding A ListView to the Display

The ListView is a very powerful UI component, capable of displaying templated lists of items in various formats.  For this example, I am only going to use a portion of the capabilities.

There are two ways to add a list view into our html pages – typing the html by hand or using Blend (which is now free with paid Visual Studio editions).  Normally I use Blend for all of my User Interface work, but adding a ListView to our default page is very trivial.

We need to add two items.  The first is the actual ListView, the second is the template to clean up the display. 

ListView Binding Templates

Binding templates customize the display of each item in a ListView.  They are created by assigning a <div> tag the WinJS.Binding.Template data-win-control, and then filling in the html with appropriate markup and data-win-bind statements.

For simplicity, I am going to use the same two column display in the item detail (from my last post), with some slight modifications. The first point to call out is the “extra” <div> tag.  The first tag is for the data-win-control.  To handle the layout through CSS, the markup needs to be in a tag with the “twoColumnLayout” class.  However, these are ignored in the tag that defines the Template, so the markup contains another <div> tag with the correct class assignment.  The only other difference is that I reduced the size of the text, using “win-type-x-large” instead of “win-type-xx-large”, both of which are defined in the CSS files that are part of the Windows Library for JavaScript 1.0 reference.  The template is shown in Listing 4.

<div id="listViewTemplate" data-win-control="WinJS.Binding.Template">
    <div style="width:400px;" class="win-type-x-large twoColumnLayout">
        <div class="leftColumn" >
            First Name: <br />
            Last Name: <br />
            Age: <br />
        </div>
        <div class="rightColumn">
            <span data-win-bind="textContent:firstName"></span> <br />
            <span data-win-bind="textContent:lastName"></span> <br />
            <span data-win-bind="textContent:age"></span> <br />
        </div>
    </div>
</div>
Listing 4 – Template for ListView

Updates to default.js

The initial project bound the detail section to the Contact property of the ViewModel NameSpace and acted against this as well when the record was updated through the button click.  This needs to be changed to bind to the first record of the BindingList.

Open up the default.js file and navigate to the performInitialSetup function.  The first change is in the WinJS.Binding.processAll call.  Instead of setting the context to ViewModel.Contact, the detail section data context needs to be bound to a single record from the ViewList.  A common use case is to create a master detail screen where the detail section is bound to the selected record in the ListView.  For the sake of simplicity, I am going to arbitrarily set it to the first record in the list.  This is done by setting the data context to the ViewModel.ContactList.getAt(0).

The second change is in the “click” event listener for the updateContactButton.  For simplicity, I added a line to create a variable (“contact”) to hold the reference to the first record in the BindlingList (the same record bound to the detail section). The rest of the event handler uses this variable instead of the ViewModel.Contact value as in the original code.  All of the changes are shown in Listing 5.

WinJS.Binding.processAll(
    document.getElementById('contactDetail'),
    ViewModel.ContactList.getAt(0));
var updateContactButton = document.getElementById('updateContactCommand');
updateContactButton.addEventListener('click', function (e) {
    var contact = ViewModel.ContactList.getAt(0);
    contact.firstName += "s";
    contact.lastName += "t";
    contact.age += 1;
    var msg = new Windows.UI.Popups
    .MessageDialog("The value of the contact is this:\r\n"
                    + "First Name: " + contact.firstName + "\r\n"
                    + "Last Name: " + contact.lastName + "\r\n"
                    + "Age: " + contact.age);
    msg.showAsync();
});
Listing 5 – performInitialSetup function changes

The Resulting Goodness

With the changes in place, run the application.  The list indeed shows the three distinct records (as shown in Figure 1), and clicking on the Update Contact button still gives immediate feedback to the view.

SNAGHTML66cff24
Figure 1 – List of Observable objects

Adding Contacts at Run Time

The final step is to show that when objects are added at run time the view accurately reflects the changes.  To accomplish this, I added another button to the default.html page, right next to the “Update Contact” button.  I then added an event listener in default.js for the click event that calls the ViewModel.addContact function. The markup for the “Add Contact” button is shown in Listing 6 and the event listener is shown in Listing 7.

NOTE: This breaks recommended design guidelines, as commands should be placed in the AppBar.  I placed them on the form like I did for simplicity of the demonstration.

<button id="addContactCommand" class="win-type-x-large">
   Add Contact
</button>
Listing 6 – Add Contact button
var addContactButton = document.getElementById('addContactCommand');
addContactButton.addEventListener('click', function (e) {
    ViewModel.addContact("First", "Last", 24);
});

Listing 7 – Add Contact click event handler

Running the application and clicking on the “Add Contact” button causes the view to be updated with the new records, as shown in Figure 2.

image  
Figure 2 – Observable Collection in action

Summary

In this post, we converted our single record Observable Model from my last post on MVVM to an Observable List of Observable Models (taking a wrong turn on the way as a memory aid for all of us .NET developers out there).  While this doesn’t complete the entire MVVM puzzle, it provides the core pieces that is the required foundation for separating the concerns of the Model from the View.

You can download the project here: MVVM2.zip

About the author

Philip Japikse

2012STLDODN.93x112 Philip Japikse an international speaker, a Microsoft MVP, INETA Community Champion, MCSD, CSM/ CSP, and a passionate member of the developer community, Phil Japikse has been working with .Net since the first betas, developing software for over 20 years, and heavily involved in the agile community since 2005. Phil works as a Developer Evangelist for Telerik's RadControls for Windows 8 as well as the Just family of products (JustCode, JustMock, JustTrace, and JustDecompile) and hosts the Zero To Agile podcast (www.telerik.com/zerotoagile). Phil is also the Lead Director for the Cincinnati .Net User’s Group (http://www.cinnug.org). You can follow Phil on twitter via www.twitter.com/skimedic read his Telerik blog at http://blogs.telerik.com/skimedic and his personal blog at www.skimedic.com/blog.

Comments (2) -

alternatively, if you create the Binding.List like:

  var list = new WinJS.Binding.List(null, { binding: true });

Then the list always implicitly holds onto the observable proxies:

  list.push({ a: 'something', b: 2 });

  // fires change notification
  list.getAt(0).a = 'something else';

@Josh,

Great stuff!  Thanks for the info.  I've updated my core code for the rest of the series.

Pingbacks and trackbacks (2)+

Comments are closed
Managed Windows Shared Hosting by OrcsWeb