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.

2012年5月28日

[Windows 8]DataBinding–dynamic binding

I want to have my metro app automatically update data source when user changes data via screen. To archive this, I need to dynamically bind data to controls.

///<reference path="//Microsoft.WinJS.0.6/js/base.js"/>
///<reference path="//Microsoft.WinJS.0.6/js/ui.js"/>
(function () {
"use strict";
//As we want the "array" variable to be updated to control,
//for some reason, we MUST declare these "array" variables with type
//WinJS.Binding.List and outside the "WinJS.Binding.as"
var observerableStores = new WinJS.Binding.List();
var observerableItems = new WinJS.Binding.List();
WinJS.Namespace.define("ViewModel", {

DynamicData: WinJS.Binding.as(
{
myName: null,
getStores: function () {
return observerableStores;
},
addStore: function (s) {
observerableStores.push(s);
},
getItems: function () {
return observerableItems;
},
addItem: function (n, q, s) {
observerableItems.push({
item: n,
quantity: q,
store: s
});
}
}
)
});
//add some data
ViewModel.DynamicData.homeZipCode = "NY 10118_D";
ViewModel.DynamicData.addStore("Store1_D");
ViewModel.DynamicData.addStore("Store2_D");
ViewModel.DynamicData.addStore("Store3_D");
ViewModel.DynamicData.addStore("Store4_D");
ViewModel.DynamicData.addStore("Store5_D");

ViewModel.DynamicData.addItem("Mac Air 1_D", 5, "Store1_D");
ViewModel.DynamicData.addItem("Mac Air 2_D", 5, "Store1_D");
ViewModel.DynamicData.addItem("Mac Air 3_D", 5, "Store1_D");
ViewModel.DynamicData.addItem("Mac Air 4_D", 5, "Store3_D");
ViewModel.DynamicData.addItem("Mac Air 5_D", 5, "Store5_D");
}
)();


To dynamically reflect changes made on data source to UI, we need to invoke WinJS.Binding.as() function to create observable variables, moreover, as WinJS.Binding.as() only makes primitive typed object as observable, such as int, float…etc. so we need to declare our array as WinJS.Binding.List type to make them observable.



And note, for some tricky restrictions, we need to declare WinJS.Binding.JS variables outside of WinJS.Binding.as() as embedded codes indicate.



Now back to our html code.



<div class="win-type-x-large">Zip code is:
<span data-win-bind="innerText:DynamicData.myName"></span>
</div>
<div class="win-type-x-large">
<label for="newmyName">Enter myName:</label>
<input id="newmyName" data-win-bind="value:DynamicData.myName" />
<button id="newmyNameButton">Update myName</button>
</div>
<div class="win-type-x-large">
Last item is : <span id="lastItem"></span>
</div>
<div class="win-type-x-large">
<button id="addItem">Add Item</button>
<button id="removeItem">Remove Item</button>
</div>
</div>


To specific data binding, I use css class “data-win-bind” with its value in this format “[attribute: dataItem]”, for example data-win-bind=”innerText: DynamicData.myName”



Now to the codes



// 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) {
initialSetup(eventObject);
} else {

}
WinJS.UI.processAll();
}
};

app.oncheckpoint = function (eventObject) {

};

app.start();

function initialSetup(e) {
//tells WinJS to process Data Binding
WinJS.Binding.processAll(document.body, ViewModel);
document.body.querySelector("#newmyNameButton").addEventListener(
"click",
function (e) {
ViewModel.UserData.homeZipCode =
WinJS.Utilities.query("#newmyName")[0].value;
});
//when click on Add/Remove button, add/pop a item to/from the list
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();
}
});
//update displayed value
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;
};
//bind event handlers to list items
var events = ["itemchanged", "iteminserted", "itemremoved", "itemmoved"];
events.forEach(function (t) {
ViewModel.DynamicData.getItems().addEventListener(t, setDynamicValue);
});

setDynamicValue();
}

})();



Run the app, and change “myName” in the textbox, click “Update myName”, and the new name will be updated automatically onto the screen.

2012年5月3日

[Windows 8]DataBinding

To get familiar with Windows 8 Metro style application programing and get my first Windows 8 Metro style app looks more “Metro”. I want to writing something that retrieves data via WCF and then display them on the app.
So, first assume we have a WCF service which returns a set of records.
   1: [OperationContract]



   2: [WebInvoke(Method = "POST",



   3:     BodyStyle = WebMessageBodyStyle.Wrapped)]



   4: CaseListResult [] GetCaseList(CaseListQueryCriteria criteria);




Now for our result page layout




   1: <!DOCTYPE html>



   2: <html>



   3: <head>



   4:     <meta charset="utf-8">



   5:     <title>itemsPage</title>



   6:     



   7:     <!-- WinJS references -->



   8:     <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">



   9:     <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
   1:  
   2:     <script src="//Microsoft.WinJS.0.6/js/ui.js">
   1: </script>
   2:     
   3:     <link href="/css/default.css" rel="stylesheet">
   4:     <link href="/css/itemsPage.css" rel="stylesheet">
   5:     <script src="/js/data_from_wcf.js">
</script>



  10: </head>



  11: <body>



  12:     <!-- item template definition -->



  13:     <div id="my_template" class="itemtemplate" data-win-bindsource="data" data-win-control="WinJS.Binding.Template">



  14:         <div class="item-overlay">



  15:             <div class="item-title" data-win-bind="innerText: DisplayID"></div>



  16:             <div class="item-subtitle" data-win-bind="innerText: CaseType"></div>



  17:         </div>



  18:     </div>



  19:     <!-- The content that will be loaded and displayed. -->



  20:     <div class="itemspage fragment">



  21:         <header aria-label="Header content" role="banner">



  22:             <button class="win-backbutton" aria-label="Back" disabled></button>



  23:             <h1 class="titlearea win-type-ellipsis">



  24:                 <span class="pagetitle">TestSplit</span>



  25:             </h1>



  26:         </header>



  27:         <section aria-label="Main content" role="main">



  28:             <div class="itemslist" data-win-control="WinJS.UI.ListView" data-win-options="{itemTemplate: select('#my_template') , layout:{type:WinJS.UI.GridLayout}}"></div>



  29:         </section>



  30:     </div>



  31: </body>



  32: </html>




I got problem displaying result on the page due to a tricky piece of code, in the list view, I have to use {itemTemplate : select(‘#my_template’)…} to get right template instead of as many other posts on the internet that simply coded like this : {itemTemlate:my_template,…}


And the java script codes to retrieve data from WCF and then bind to data list.




   1: (function () {



   2:         "use strict";



   3:         var q = { "item": { "UserID": "domain\\userid" } };



   4:         WinJS.xhr(



   5:             {



   6:                 type: "POST",



   7:                 url: "http://10.1.184.2:8080/tmmbwadapter/services/test.svc/AJAX/GetCaseList",



   8:                 headers: {



   9:                     "Content-type": "application/json",



  10:                 },



  11:                 data: JSON.stringify(q)



  12:             }



  13:  



  14:         ).done(function (result) {



  15:             var jo = JSON.parse(result.responseText);



  16:             var cases = jo.GetCaseListResult;



  17:             //var cases = [{ DisplayID : "test", Applicant : "testa"}];



  18:             var caseList = new WinJS.Binding.List(cases);



  19:             var lv = document.querySelector(".itemslist").winControl;



  20:             lv.itemDataSource = caseList.dataSource;



  21:  



  22:         },



  23:         function (err) {



  24:             //error handling goes here...



  25:         });



  26: })();


About Me