2012年5月30日

[Windows 8]DataBinding–Dynamic binding with Template

[Updated]To have static binding and dynamic binding work correctly within the same page, I find the following should be more reasonable in Metro world

function performInitialSetup(e){
//...
WinJS.UI.processAll().then(function () {
WinJS.Binding.processAll(document.body, ViewModel).then(function () {
UI.List.showListItems();
UI.List.bindListEvents();
});
});
//...
}




 



In ASP.Net, there is a feature “Template” which I think very useful, in Windows 8 Metro-Style App developing, Microsoft also introduce this feature, here I want to modify my app to apply this feature to display data.



First we have to create template. To achieve this, simply write some HTML codes and add a data-win-control=”WinJS.Binding.Template” to it. for example, I’d like my data displayed as a normal HTML table content. I have to write a template as following.



<table>
<tbody id="itemTemplate" data-win-control="WinJS.Binding.Template">
<tr class="groceryItem">
<td data-win-bind="innerText: quantity"></td>
<td data-win-bind="innerText: item"></td>
<td data-win-bind="innerText: store"></td>
</tr>
</tbody>
</table>



Note that, when writing templates, although, we only need <tbody>, WinJS requires whole template are well-formatted HTML, which means, we’ll need to ensure our HTML codes can be correctly parsed, in our case, we need to start the template form <table> then <tbody>.



Now we need a placeholder where we want our data to be displayed.



<div id="templateTestContainer">
<table id="targetTable">
<thead>
<tr>
<td>Quantity</td>
<td class="itemName">Item</td>
<td>Store</td>
</tr>
</thead>
<tbody id="targetBody"></tbody>
</table>
</div>



As you can see, <tbody id=”targetBody”/> is where we would like our data to be rendered to.



Now we need to change some codes. In our previous version, there is a function called performInitialSetup() in default.js, there is only one line of code in it : WinJS.Binding.processAll(); which is to tell WinJS to process data binding. As we are going to do dynamic data binding with template, we are going to change it as following:



function performInitialSetup(e) {
document.body.querySelector("#newZipButton").addEventListener("click",
function (e) {
ViewModel.UserData.homeZipCode = WinJS.Utilities.query("#newZip")[0].value;
});

WinJS.Utilities.query("button").listen("click", function (e) {
if (this.id == "addItem") {
ViewModel.DynamicData.addItem("Ice cream", 2, "Vanilla");
} else if (this.id == "removeItem") {
ViewModel.DynamicData.getItems().pop();
}
});

var setDynamicValue = function () {
var list = ViewModel.DynamicData.getItems();

if (document.getElementById("lastItem") != null && document.getElementById("lastItem") != "undefined")
if (list != null && list != "undefined" && list.length > 0)
document.getElementById("lastItem").innerText = list.getAt(list.length - 1).item;
};

var events = ["itemchanged", "iteminserted", "itemremoved", "itemmoved"];
events.forEach(function (t) {
ViewModel.DynamicData.getItems().addEventListener(t, setDynamicValue);
});

setDynamicValue();
//NOTE: the sequenec of the following two lines cannot be changed,
// We need WinJS to start process UI, then tell it to bind static data,
// To change the sequence will result in dyncamic binding failed.
//WinJS.UI.processAll();
//WinJS.Binding.processAll(document.body, ViewModel);//correct
WinJS.UI.processAll().then(function(){WinJS.Binding.processAll(document.body, ViewModel).then(function(){UI.List.showListItems();
UI.List.bindListEvents();});});
}



Whole default.js file will looks like this:



// For an introduction to the Blank template, see the following documentation:
// http://go.microsoft.com/fwlink/?LinkId=232509
(function () {
"use strict";

var app = WinJS.Application;

app.onactivated = function (eventObject) {
if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) {
// TODO: This application has been newly launched. Initialize
// your application here.
performInitialSetup(eventObject);
} else {
// TODO: This application has been reactivated from suspension.
// Restore application state here.
performRestore(eventObject);
}
//WinJS.UI.processAll();
}
};

app.oncheckpoint = function (eventObject) {
performSuspend(eventObject);
};

app.start();

function performInitialSetup(e) {
//WinJS.Binding.processAll(document.body, ViewModel);//.then(function () {
document.body.querySelector("#newZipButton").addEventListener("click",
function (e) {
ViewModel.UserData.homeZipCode = WinJS.Utilities.query("#newZip")[0].value;
});

WinJS.Utilities.query("button").listen("click", function (e) {
if (this.id == "addItem") {
ViewModel.DynamicData.addItem("Ice cream", 2, "Vanilla");
} else if (this.id == "removeItem") {
ViewModel.DynamicData.getItems().pop();
}
});

var setDynamicValue = function () {
var list = ViewModel.DynamicData.getItems();

if (document.getElementById("lastItem") != null && document.getElementById("lastItem") != "undefined")
if (list != null && list != "undefined" && list.length > 0)
document.getElementById("lastItem").innerText = list.getAt(list.length - 1).item;
};

var events = ["itemchanged", "iteminserted", "itemremoved", "itemmoved"];
events.forEach(function (t) {
ViewModel.DynamicData.getItems().addEventListener(t, setDynamicValue);
});

setDynamicValue();

WinJS.UI.processAll();
WinJS.Binding.processAll(document.body, ViewModel);//correct
UI.List.showListItems();
UI.List.bindListEvents();
}
//}

function performRestore(e) {
WinJS.UI.processAll();
}

function performSuspend(e) {
WinJS.UI.processAll();
}
})();



Now we need some codes to make dynamic binding with template works. Create template_ui.js as following:



/// <reference path="//Microsoft.WinJS.0.6/js/base.js" />
/// <reference path="//Microsoft.WinJS.0.6/js/ui.js" />

(function () {
"use strict";

WinJS.Namespace.define("UI.List", {
showListItems: function () {
//get template
var template = document.getElementById("itemTemplate");
//get targe
var target = document.getElementById("targetBody");
//empty target element
WinJS.Utilities.empty(target);
//get list items
var list = ViewModel.DynamicData.getItems();
//render item to ui
for (var i = 0; i < list.length; i++) {
template.winControl.render(list.getAt(i), target);
}
//when user select a item, set "selected" flag on
WinJS.Utilities.children(target).listen("click", function (e) {
ViewModel.State.selectedItemIndex = this.rowIndex - 1;
WinJS.Utilities.children(target).removeClass("selected");
WinJS.Utilities.addClass(this, "selected");
});
},

bindListEvents: function () {
var eventTypes = ["itemchanged", "iteminserted", "itemremoved", "itemmoved"];
var itemList = ViewModel.DynamicData.getItems();
//when item changed, refresh list control
eventTypes.forEach(function (t) {
itemList.addEventListener(t, UI.List.showListItems);
});
}
});
})
();



In default.htm, include required java script files,



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

<!-- Application1 references -->
<link href="/css/list.css" rel="stylesheet">
<link href="/css/default.css" rel="stylesheet">
<script src="/js/data_dynamicBinding.js"></script>
<script src="/js/template_ui.js"></script>
<script src="/js/default.js"></script>



Note the sequence of these files is important. The most tricky thing is this sample is that to make static binding and dynamic binding works correctly within the same page, we need to ensure that WinJS.UI.processAll() been called before WinJS.Binding.processAll(), then to start dynamic binding process.

沒有留言:

About Me