Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια

C# and .NET Tips and Tricks

Quests in programming in .NET

Παρουσίαση με Ετικέτες

Όλες οι Ετικέτε... » WinJS » HTML5   (RSS)
(Windows8) WinJS single page navigation and ViewModels

In the previous post, we have seen simple data-binding scenarios using WinJS and got a glimpse on the way a ViewModel can be created (following the MVVM pattern). In this post, we will build and application where there are more than one views (pages - simple page navigation app) with simple html elements and ListViews with real data. The question we need to answer is:

 

How do the ViewModels and MVVM can be applied in a single page navigation WinJS application?”

(FOR .NET Developers: if you are coming from the .NET world and especially XAML you will also get a chance to see how things like INotifyPropertyChanged and ObservableCollections apply to the WinJS world. The ViewModel presented here is as close as possible to the one that would be applied in a WPF-Silverlight window)

 

We will create a simple app that displays the births and deaths of celebrities in today’s date. Data will come from the RSS feeds provided by IMDB. Clicking on a celebrity should lead us to a new page where we get the IMDB webpage of that celebrity. Within this application, ViewModels will be used for each page. The requirements we have for our ViewModels are as follows:

 

  • There should be one ViewModel for each page.
  • The ViewModel should hold all the variables and methods required to provide the data to the view (html page)
  • The view will connect to the ViewModel through data binding.
  • All events generated by the page should also fire methods of the ViewModel.

 

Open VS 2011 and create a “Navigation Application” project. The “default.html” file that is generated contains the following:

 

<div id="contenthost" data-win-control="BirthsDeaths.PageControlNavigator" 
data-win-options="{home: '/html/homePage.html'}">
</div>

That defines an area that will be loading your pages according to your application’s state. This area is initialized and loads the “homePage.html” page (VS has also generated for you a navigate.js file that handles the navigation. For an introduction to single page navigation you can read this article).

 

You should consider each page that will be loaded in this area as a separate window or view of your application. Therefore, each page should have its own ViewModel. In our case, we have two pages: one (homePage) that displays the births and deaths in two ListViews and one (actorDetails) that displays the actor’s details when the user clicks on an item in the lists.

 

Let’s start with the homePage view that will look like the one in the following figure:

 

image

 

That is, there should be two lists one showing the deaths and one showing the births. Also, just below the main header we need to display the date and time and a downloading message when the data are fetched from IMDB. The html that generates that view is (of course there is the .css that also does the layout magic):

 

<section aria-label="Main content" role="main">
<span id="dateSpan"></span>&nbsp;&nbsp;
<span id="downloadingSpan"></span>
<section class="listsContainer">
<h2 class="birthsHeader">born today</h2>
<h2 class="deathsHeader">died today</h2>
<div id="birthsListElement" data-win-control="WinJS.UI.ListView"></div>
<div id="deathsListElement" data-win-control="WinJS.UI.ListView"></div>
</section>
</section>

So for our ViewModel we will need: A property for the date/time, a property for the “downloading” message, two lists for the births and deaths, two methods to fetch the deaths and births and the event handler that will be invoked when the user clicks on a list item. In the “homepage.js” file, the process for creating this ViewModel is as follows:

Within the self executing function that is generated for us, we define a new class (WinJS.Class.define) that we name HomePageViewModelClass:

 

var HomePageViewModelClass = WinJS.Class.define(
function () {
this.onShowActorDetailsBirths = this.showActorDetailsBirths.bind(this);
this.onShowActorDetailsDeaths = this.showActorDetailsDeaths.bind(this);
},
{
_today: "",
today: {
get: function () {
return this._today;
},
set: function (value) {
this._today = value;
this.notify("today", value);
}
},

_downloading: "",
downloading: (...),

birthsList: new WinJS.Binding.List(),
deathsList: new WinJS.Binding.List(),
getBirths: function () {
this.downloading = "(downloading...)";
var that = this;
var syn = new Windows.Web.Syndication.SyndicationClient();
var url = new Windows.Foundation.Uri("http://rss.imdb.com/daily/born/");
syn.retrieveFeedAsync(url).then(
function (feed) {
for (var i = 0, len = feed.items.length; i < len; i++) {
var item = feed.itemsIdea;
var birth = {
title: item.title.text,
date: item.publishedDate,
content: item.summary.text,
link: item.links[0].nodeValue
}
that.birthsList.push(birth);
}
that.downloading = "";
});
},
getDeaths: (...),
showActorDetailsBirths: function (e) {
this._showActorDetails(this.birthsList.getAt(e.detail.itemIndex));
},
showActorDetailsDeaths: function (e) {
this._showActorDetails(this.deathsList.getAt(e.detail.itemIndex));
},

_showActorDetails: function (item) {
WinJS.Navigation.navigate("/html/actorDetail.html", item);
}

}
);


There are a few things to note here:

 

  • For the properties “today” and “downloading” we use accessors. The accessors get and set their corresponding private fields. Note that after they set their value they call a “strange” (for now) method which is called notify with the name of the property that has changed and the new value. You will see in just a minute where this method is defined but for now remember that this is what makes the data-bound part of the UI update when this property changes (for .NET Developers: the equivalent of PropertyChanged).

 

  • The “showActorDetailsBirths” and “showActorDetailsDeaths” methods are the ones that will be called when the user presses on a list item and therefore will be bound to the “oniteminvoked” event. Due to the javascript’s issues with the “this” keyword”, on the class constructor, we create two new methods from those ones (prefixed with “on”) using the bind method, that have the correct context for “this”, that is the object itself, and those will be the ones that will be bound to the events.

 

  • The two lists are of “WinJS.Binding.List” type in order to provide the necessary events for the UI to be updated when items are added to them (for .NET Developers” the equivalent to ObservableCollection).

 

Now just after the aforementioned class definition, we create from it a new class definition as follows:

 

var BindableHomePageViewModelClass = WinJS.Class.mix(HomePageViewModelClass, WinJS.Binding.mixin);

This actually uses the “WinJS.Class.mix” to add to our HomePageViewModelClass class all methods and properties that are contained in the “WinJS.Binding.mixin” class and guess what, those are the methods needed for providing change notifications of the data-binding (this is the source of the notify method). This also means that we should never use the HomePageViewModelClass but instead always use the BindableHomePageViewModelClass since this is the “complete” one after the application of the mix method.

 

Now we make our ViewModel public in the “ProgwareOrg” namespace as follows creating a new object. This is the object that will be bound to the view:

 

WinJS.Namespace.define("ProgwareOrg", {
HomePageViewModel: new BindableHomePageViewModelClass()
});

In the page’s functions that handle the basic events of our page we can bind the ViewModel to the View as follows (for .NET Developers: this code should be considered as the xaml.cs file and the binding to the ViewModel that occurs there):

 

WinJS.UI.Pages.define("/html/homePage.html", {
init: function init(element, options) {

var calendar = new Windows.Globalization.Calendar();
ProgwareOrg.HomePageViewModel.today = calendar.dayOfWeekAsString() + " " +

calendar.day + " " + calendar.monthAsString() + " " + calendar.yearAsString();
ProgwareOrg.HomePageViewModel.getBirths();
ProgwareOrg.HomePageViewModel.getDeaths();
},
ready: function ready(element, options) {
WinJS.Binding.processAll(home, ProgwareOrg.HomePageViewModel);
}
});


We initialize our ViewModel and in the ready function we bind it to the view via the “WinJS.Binding.ProcessAll” method (for .NET Developers: this would be the this.DataContext=ViewModel in XAML). This method takes as first parameter the root element where the ViewModel is applied and in our case is the one with the id=”home”.

 

The binding in the view is as follows:

 

<div class="homepage" id="home">
<section aria-label="Main content" role="main">
<span id="dateSpan" data-win-bind="innerText:today"></span>&nbsp;&nbsp;
<span id="downloadingSpan" data-win-bind="innerText:downloading"></span>
<section class="listsContainer">
(...)
<div id="birthsListElement"
data-win-control="WinJS.UI.ListView"
data-win-options="{itemDataSource:ProgwareOrg.HomePageViewModel.birthsList.dataSource,
itemTemplate:select('#template'),
layout:{type:WinJS.UI.ListLayout},
oniteminvoked : ProgwareOrg.HomePageViewModel.onShowActorDetailsBirths}"
>
</div>
<div id="deathsListElement" (...)></div>
</section>
</section>
</div>

First note that the top-level element (the one used in processAll) is the id=”home” element. Also note the “data-win-bind”  attributes that provide the binding for the HTML elements.

 

For the two ListViews note the following:

 

The itemDataSource is bound to our ViewModel but it is using the full qualified name. This means that it does not take into consideration the processAll method. I do not know why this happens and I think that this is something that needs to be fixed but for now the hack I found is that for the controls of WinJS for binding with the ViewModel we need to use the full access paths of our ViewModel. This also applies to the binding of the oniteminvoked event handler. The template set is defined in the page just above this code and not displayed here for simplicity.

 

And this summarizes the way the ViewModel can be bound to our view. The ViewModel for the actorDetails page is simpler:

 

(function () {
"use strict";

var ActoDetailViewModelClass = WinJS.Class.define(
function () {},
{
_pageToShow: "",
pageToShow: {
get: function () {
return this._pageToShow;
},
set: function (value) {
this._pageToShow = value;
this.notify("pageToShow", value);
}
}
}
);

var BindableActoDetailViewModelClass = WinJS.Class.mix(ActoDetailViewModelClass, WinJS.Binding.mixin);

WinJS.Namespace.define("ProgwareOrg", {
ActoDetailViewModel: new BindableActoDetailViewModelClass()
});

WinJS.UI.Pages.define("/html/actorDetail.html", {
ready: function ready(element, options) {
ProgwareOrg.ActoDetailViewModel.pageToShow = options.link;
WinJS.Binding.processAll(mainSection, ProgwareOrg.ActoDetailViewModel);
},
updateLayout: function updateLayout(element, viewState) { }
});
})();

And the view:

 

<div id="mainSection" class="actorDetail fragment">
...
<section aria-label="Main content" role="main">
<iframe id="actorsPage" data-win-bind="src:pageToShow" style="width: 100%;height: 100%" />
</section>
</div>

Data are passed between pages as the second parameter of the WinJS.Navigation.navigate("/html/actorDetail.html", item);. They can be retrieved from the navigated page through the options parameter of the ready function.

The “actorDetail” page uses an iframe to display the webpage. Because the page has ActiveX controls when you run the project from within VS you will be getting the error “JavaScript runtime error: Access is denied”.If you look at the Javascript Console you will see that the error is “Cannot load the ActiveX plug-in that has (…)” and that “Apps can't load ActiveX controls”. But if you compile and deploy in “Release” go to Windows8 start page and run the app this will not appear. This does not mean that the error disappears. It just means that in release mode the ActiveX will just don’t load which is fine with us.

 

So in this post we have seen an approach on how to create ViewModels in a single navigation application in Windows8.

Posted: Παρασκευή, 23 Μαρτίου 2012 6:43 πμ από iwannis | 2 σχόλια
Δημοσίευση στην κατηγορία: , ,