For the past eight(8) years Schalk Neethling has been working as a freelance developer under the pseudo of Volume4 and is now the president of Overt Strategy Consulting. During this period he has completed over 300 projects ranging from full web application development to complete branding. As president and lead developer of Overt Strategy Consulting, Schalk Neethling and his team has released a 100% Java standards based content management system called AlliedBridge and business document exchange and review system, called Doc-Central. Schalk Neethling is also actively involved on a daily basis in the open source, web standards and accessibility areas and is a current active member of the Web Standards Group. Schalk is also the co-founder and president of the non-profit The South Web Standards and Accessibility Group, which aims to actively educate and raise awareness of web standards and accessibility to both the developer society as well as business large and small. Schalk also has a long relationship with DZone and is currently zone leader for both the web builder, css.dzone.com, as well as the .NET zone, dotnet.dzone.com, and you can find a lot of his writing there as well as on his blog located at schalkneethling.alliedbridge.com. Schalk is constantly expanding on his knowledge of various aspects of technology and loves to stay in touch with the latest happenings. For Schalk web development and the internet is not just a job, it is a love, a passion and a life style. Schalk has posted 173 posts at DZone. View Full User Profile

ASP.NET Ajax 4.0 , Master-Details View With The DataView

08.04.2008
| 11666 views |
  • submit to reddit

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
 List ListElement
 Properties Properties
 ListID ListElementText
 ListTitle ListElementID
 Associations Associations
 ListElements List

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.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://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>
Original Author

Original Article Written By Phani Raj

References
Published at DZone with permission of its author, Schalk Neethling. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)