ASp.net Ajax 4.0 , Master-Details View with the DataView
By now , you must have heard about the new JavaScript templating engine with the release of ASP.NET AJAX 4.0 CodePlex Preview 1
This post will show you how to use the built-in control DataView to build a master details view.
As an addition , I will use an astoria service to serve the data and the Ajax Library to retrieve the data.
Consider the following model..
Parent Entity | Child Entity | ||||||||||||
|
|
The properties of the entity you are binding are represented in a syntax similar to this ..
{{ PropertyName }}
To bind all lists , the template would look like this ..
<div id="listTemplate" class="sys-template">
<ul>
<li>
{{ ListTitle }}
</li>
</ul>
</div>
Retrieve data using Astoria Ajax Client Library
function loadData() {
//Create a Data Service Proxy
_dataServiceProxy = new Sys.Data.DataService("ListService.svc");
//The URI of the resources to download
var listURI = "/Lists?$expand=ListElements";
//Query the Data Service with the URI
_dataServiceProxy.query(
listURI , /*Resource URI*/
dataLoaded, /*Success callback*/
null, /*Failure callback */
null, /*user context*/
null); /*Web Request*/
}
Bind the data to the template
function dataLoaded(result,context) {
//Get the Template for the List Results
var dv = new Sys.Preview.UI.DataView($get("listTemplate"));
//Pass the data regarding the Lists to be bound
dv.set_data(result);
//Render the template
dv.render();
}
This is cool , but what about ListElements ? Why not bind the ListElements in the same template?
In short , why not do this ?
<div id="listTemplate" class="sys-template">
<ul>
<li>
{{ ListTitle }}
<ul>
<li>{{ ListElementText }} </li>
</ul>
</li>
</ul>
</div>
Well, it doesn’t work .
From what I can surmise ( from looking at the script code and the readme that came along with it ) , the control doesn’t allow you to bind collection properties which are complex types .
In English , this means that for the type Lists , you can’t just write a template
that binds all Lists, the ListElements for each List and bind Lists and ListElements at one shot.
How about we have a separate template for the ListElements ?
Something that looks like this …
<div id="listTemplate" class="sys-template">
<ul>
<li>
<h3>{{ ListTitle }}</h3>
<ul id="'listElementsTemplate" class="sys-template">
<li>{{ ListElementText }}</li>
</ul>
</li>
</ul>
</div>
When will I bind the internal template ?
well, it turns out that there is an event called “ItemCreated” fired by the DataView when the item is rendered.
you can add a handler to be notified of this event by calling add_itemCreated with the handler function
as a parameter. The event handler is called with the usual parameters as your .net Event Handlers.
function onItemCreated(sender,eventArgs) {
//Function body
}
sender is the DataView which is being bound ,eventArgs is of type : Sys.Preview.UI.DataViewItemEventArgs.
call eventArgs.get_dataItem() to get the data being bound to the DataView.
call eventArgs.get_templateResult() to get the template result , i.e the template with the data filled in for the bindings.
So, the dataLoaded function would change to be :
function dataLoaded(result,context) {
//Get the Template for the List Results
var dv = new Sys.Preview.UI.DataView($get("listTemplate"));
//Pass the data regarding the Lists to be bound
dv.set_data(result);
//add a handler to listen to the ItemCreated Event
dv.add_itemCreated(onItemCreated);
//Render the template
dv.render();
}
function onItemCreated(sender,eventArgs) {
var currentList = eventArgs.get_dataItem();
//Get the Child Template for the parent List Template
var dv = new Sys.Preview.UI.DataView($get("listElementsTemplate"));
//Bind the Template to the ListElements for the current List
dv.set_data(currentList.ListElements);
//Render the template
dv.render();
}
Well, this still doesn’t work !There is just one copy of the internal listElementsTemplate for multiple copies of the
Parent template for Lists being bound .The first one works , the second one shows us this ..
“Sys.InvalidOperationException: A control is already associated with the element”.
Which means that we need one copy of the internal listElementsTemplate for each copy of the ListTemplate.
So, we change the template to be :
<div id="listTemplate" class="sys-template">
<ul>
<li>
<h3>{{ ListTitle }}</h3>
<ul id="{{ 'listElementsTemplate' +ListID}}" class="sys-template">
<li>{{ ListElementText }}</li>
</ul>
</li>
</ul>
</div>
And the function to bind the listElements changes to be :
function onItemCreated(sender,eventArgs) {
var currentList = eventArgs.get_dataItem();
//Get the Child Template for the parent List Template
var dv = new Sys.Preview.UI.DataView($get("listElementsTemplate"+currentList.ListID ));
//Bind the Template to the ListElements for the current List
dv.set_data(currentList.ListElements);
//Render the template
dv.render();
}
And then , you are done !!!
Tip to debug Template Generation with IE 7 ..
Install IE 7 Pro and you get this neat context-menu , “View Generated Source”.
Which lets you see the html that is generated after binding the data.
Complete sample Code is below , I swapped out the DataService code for local data .
You will need the following script files .
AJAX Client Library for ADO.NET Data Services (optional)
ASP.NET AJAX 4.0 CodePlex Preview 1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="https://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>Master Details with ASP.NET AJAX 4.0</title>
<link href="../css/SoberTable.css" rel="stylesheet" type="text/css" />
<style>
.sys-template
{
}
</style>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager runat="server" ID="scrpManager">
<Scripts>
<asp:ScriptReference Path="~/AjaxTemplate/Scripts/MicrosoftAjaxTemplates.debug.js" />
<asp:ScriptReference Path="~/AjaxTemplate/Scripts/DataService.debug.js" />
</Scripts>
</asp:ScriptManager>
<div id="listTemplate" class="sys-template">
<ul>
<li>
<h3>
{{ ListTitle }}
</h3>
<ul id="{{ 'listElementsTemplate' +ListID}}" class="sys-template">
<li>{{ ListElementText }} </li>
</ul>
</li>
</ul>
</div>
<script language="javascript" type="text/javascript">
var _dataServiceProxy = null;
function onItemCreated(sender,eventArgs) {
var currentList = eventArgs.get_dataItem();
//Get the Child Template for the parent List Template
var dv = new Sys.Preview.UI.DataView($get("listElementsTemplate"+currentList.ListID ));
//Bind the Template to the ListElements for the current List
dv.set_data(currentList.ListElements);
//Render the template
dv.render();
}
function dataLoaded(result) {
//Get the Template for the List Results
var dv = new Sys.Preview.UI.DataView($get("listTemplate"));
//Pass the data regarding the Lists to be bound
dv.set_data(result);
//add a handler to listen to the ItemCreated Event
dv.add_itemCreated(onItemCreated);
//Render the template
dv.render();
}
function loadData() {
//Create a Data Service Proxy
_dataServiceProxy = new Sys.Data.DataService("ListService.svc");
//The URI of the resources to download
var listURI = "/Lists?$expand=ListElements";
//Query the Data Service with the URI
_dataServiceProxy.query(
listURI , /*Resource URI*/
dataLoaded, /*Success callback*/
null, /*Failure callback */
null, /*user context*/
null); /*Web Request*/
}
function loadLocalData() {
var localData ={ 'd' : [
{'ListID': 1, 'ListTitle': 'Future Blog Posts to write', 'ListElements': [
{ 'ListElementID': 2, 'ListElementText': 'Working with 1..N associations'},
{ 'ListElementID': 3, 'ListElementText': 'Working with ServiceOps and the client'},
{ 'ListElementID': 4, 'ListElementText': 'Data Literal Table'},
{ 'ListElementID': 5, 'ListElementText': 'Calling Stored Procedures from ServiceOps'} ]
},
{'ListID': 2, 'ListTitle': 'My Life List',
'ListElements': [{'ListElementID': 64, 'ListElementText': 'Learn Spanish'}] }
] };
dataLoaded(localData.d);
}
function pageLoad() {
loadLocalData ();
}
</script>
</form>
</body>
</html>
Comments
Anonymous
July 25, 2008
This is great, but I have a couple of comments. First, you can use the $id function to generate the ids, and second you shouldn't call render on the data lists but rather initialize.Anonymous
July 25, 2008
This is great, but I have a couple of comments. First, you can use the $id function to generate the ids, and second you shouldn't call render on the data lists but rather initialize. Also, it would be simpler if you set localData to a literal directly intead of creating a JSON string and deserializing it.Anonymous
July 25, 2008
If you are just tuning in , you can read Part 1 here Parent Entity Child Entity List Properties       Anonymous
July 26, 2008
- 44个令人惊奇的Silverlight视频教程。 2. Expression Encoder 2 VB.NET update 3. HD View for Internet Explorer HD
- Anonymous
July 28, 2008
This week I am coming to you from the Microsoft Campus. So as you would expect I have a lot of energy