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

error_Bind_Again

“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.

IE7PRO

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>