Chapter 1. Introduction to ASP.NET 4 AJAX Client Templates

What's New in ASP.NET 4 AJAX Preview 4

The recent release of ASP.NET 4 AJAX Preview 4 has brought in some exciting additions to the ASP.NET AJAX framework. Preview 4 ushers in native support for working directly with server-side services, command bubbling, change support tracking and identity management, WCF and ASMX web service integration, and much more.

This Wrox Blox is about some of the most compelling features of ASP.NET AJAX Client Templates. The examples herein teach you the basics of Client Templates, demonstrate how to customize the templates, and walk you through getting data from the server using ASP.NET Web Forms, ASP.NET MVC, and through querying ADO.NET Data Services.

For further details on ASP.NET 4 AJAX Preview 4, go to http://aspnet.codeplex.com/Wiki/View.aspx?title=AJAX.

Please take special care as you work through the examples contained in this Wrox Blox. The code herein works against the Preview 4 release of ASP.NET AJAX and may differ from release versions of the product.

What Are Client Templates?

Developers love separation. On the server–side, developers enjoy many advances in techniques used to separate business logic from the database and, to a large extent, business logic from the user interface. When dealing with code on the client, however, this separation is usually compromised.

Often, as pages are constructed programmatically, values from JavaScript data objects are assembled together with HTML markup in order to build a page. Unfortunately, up until now there has been no simple way to completely separate a page's markup from JavaScript code.

The example below demonstrates how people sometimes approach the problem:

<html>
<head>
    <script type="text/javascript">

        window.onload = function()
        {
            var names = [
                   { Name: "John" },
                   { Name: "Paul" }
                ];

            var template = "<table>";

            for (var i = 0; i < names.length; i++)
            {
                template += "<tr><td>" + names[i].Name + "</td></tr>";
            }

            template += "</table>";

            var list = document.getElementById("list");

            list.innerHTML = template;
        }

    </script>
</head>
<body>
    <div id="list"></div>
</body>
</html>

While there are many flaws with the above code, the most relevant weakness is the practice of mingling together the JavaScript code with HTML. This practice is accompanied by the usual drawback in mixing together areas of applications: difficult code to maintain and test.

Developers may choose to use a different approach to DOM manipulation that includes creating DOM nodes in-memory and then applying data to the nodes. This approach is even more complex and still does not achieve the goal of separating client-side business logic from the true user interface.

Client Templates represent a solution to this problem by completely separating HTML markup from JavaScript code. The result is standard markup that includes tokens that act as placeholders for the data. When the template is bound to a data source, the tokens are replaced with the actual data, and true separation is achieved.

Environment Setup

Most of the examples in this Wrox Blox are implemented with plain HTML files and require no server-side processing. Near the end of the Wrox Blox, however, you implement examples that call server-side services to provide data to your view pages. The examples include demonstrations using ASP.NET Web Forms, ASP.NET MVC, and calling ADO.NET Data Services. The only project type available that supports all three of these scenarios is an ASP.NET MVC application.

If you do not have ASP.NET MVC installed on your machine or cannot install the project templates, you may create a standard ASP.NET Web Forms project to host the examples in this Wrox Blox and simply skip the section that implements calls to an ASP.NET MVC controller action.

A few of the examples use the jQuery library. If you are using an ASP.NET Web Forms project, you must manually include the library in your project.

If you have the ASP.NET MVC project templates installed on your machine, create a new project now to host the coming exercises.

Scripts Files

There are several script references that are required to power the examples highlighted in this book. No matter which type of web project you ended up creating, each exercise relies on the ASP.NET AJAX Preview 4 and jQuery script files to be located in a folder named Scripts off the root of the site.

The following links are the locations where you may download the requisite script files:

  • ASP.NET AJAX Preview 4http://aspnet.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=24645

  • jQueryhttp://docs.jquery.com/Downloading_jQuery

DataView

The DataView is a new client-side control responsible for facilitating data binding in the web browser. When considering Client Templates, the DataView is the "template" used to render data to the user.

To place the DataView in terms that many ASP.NET developers are familiar with, consider the Repeater server control. A Repeater's job is to iterate over a collection of data and render markup for each item according to the ItemTemplate. A distinctive quality of Repeaters is that they do not automatically generate HTML, unlike most other server controls. DataViews behave the same way as a Repeater, with the exception that all the processing is done client side.

In the following example, you create a page with a list of names to present to the user (see Figure 1):

List of names rendered using ASP.NET 4 AJAX Client Templates.

Figure 1. List of names rendered using ASP.NET 4 AJAX Client Templates.

Begin by creating a page named Demo1.htm in the root of your application. Enter the following code as the markup for this very simple DataView:

Even though this Wrox Blox is about ASP.NET AJAX, you use a plain HTML file for most of the exercise to demonstrate that all processing is executed in the browser. While the server is unnecessary for many of the demos, the same techniques may be used in ASPX pages as well.

<html>
<head>
    <style type="text/css">
    .sys-template {display:none;}
    </style>
</head>
<body
    xmlns:sys="javascript:Sys"
    xmlns:dataview="javascript:Sys.UI.DataView"
    sys:activate="*">

    <ul
        class="sys-template"
        sys:attach="dataview"
        dataview:data="{{beatles}}">
        <li>{{Name}}</li>
    </ul>

<script type="text/javascript"
src="scripts/MicrosoftAjax.debug.js"></script>

<script type="text/javascript"
src="scripts/MicrosoftAjaxTemplates.debug.js"></script>

<script type="text/javascript">

    var beatles = [
       { Name: "John" },
       { Name: "Paul" },
       { Name: "George" },
       { Name: "Ringo" }
    ];

</script>
</body>
</html>

DataViews are bound to data either using declarative markup or programmatic binding. The code sample above uses declarative data binding, but each approach and all the code are discussed in detail in the next section.

The script blocks in this sample are at the end of the HTML document. This is a good practice to help optimize your web pages for search engines. The execution of the code responsible for binding the template is deferred until after the page is ready in the browser — regardless of where the scripts are located on the page. Therefore, placing the scripts near the bottom makes the content of your page available to search engines as early as possible.

Declarative Data Binding

One of the new features available in ASP.NET 4 AJAX is the ability to create objects and bind them to data with a declarative syntax. This section describes the declarative model in detail.

The listing begins with the following code:

<style type="text/css">
.sys-template {display:none;}
</style>

The CSS class sys-template is a convention used by the framework to initially hide templates until they are bound to data. The class must always be defined at a minimum with the style of display:none. Once data binding is complete, the class is removed from the element, effectively displaying the element on the page.

Next, the BODY element includes some extensions:

<body
    xmlns:sys="javascript:Sys"
    xmlns:dataview="javascript:Sys.UI.DataView"
    sys:activate="*">

The BODY element is marked up with attributes using the HTML's schema extensibility features. The Microsoft ASP.NET 4 AJAX namespaces are imported into the document using the xmlns attributes. The root-level Sys library is imported first, then the library that contains the JavaScript class for the DataView is imported into the document. Once the resources are available, then the document may call commands made available through the imported libraries.

The last attribute on the BODY element is sys:activate. This tells the document to scan the entire page for possible instances of a DataView and mark them for data binding.

The * syntax is convenient but is not recommended for large pages. In the event that your page contains many elements, you may pass a comma-delimited list of element IDs that contain DataViews on the page. This more explicit approach helps increase performance of the page.

Next, the template is defined:

<ul
    class="sys-template"
    sys:attach="dataview"
    dataview:data="{{beatles}}">
    <li>{{Name}}</li>
</ul>

The unordered list uses the sys-template CSS class to initially hide the template from the user. This approach saves the user from first seeing a blank template with odd-looking placeholder text before data is bound to the template. Using the sys-template class ensures that the data view is only shown to the user when data is bound to the control.

The sys:attach element tells the document that the element on the page is a DataView control. Once the element is declared as a DataView, then object-specific properties are available for manipulation. The next element, dataview:data, sets the object's data property equal to an array of objects.

The body of the template contains placeholders that the Client Template Framework replaces with bound data. The placeholder uses double curly braces with the data object's property name to insert data into the control. This approach creates a one-way, one-time binding between the view and the data source.

The last section of the markup imports the updated Microsoft AJAX library and Microsoft AJAX Templates library, and the data used to populate the controls is declared as a JSON object array.

<script type="text/javascript"
src="scripts/MicrosoftAjax.debug.js"></script>

<script type="text/javascript"
src="scripts/MicrosoftAjaxTemplates.debug.js"></script>

<script type="text/javascript">

    var beatles = [
       { Name: "John" },
       { Name: "Paul" },
       { Name: "George" },
       { Name: "Ringo" }
    ];

</script>

Programmatic Data Binding

The declarative model allows you to add functionality to your page without requiring you to write much code, but this convenience comes at a price. When using the declarative features, you lose explicit control over how your page interacts with the libraries making complex interactions difficult. Fortunately, the Client Templates Framework features a programmatic model as well.

The following code listing results in the same list of names for the user, but the markup and script are updated to use a programmatic approach to data binding.

Make a copy of your Demo1.htm, naming it Demo2.htm. Update the code to match the following listing. Note that the changed lines of code are highlighted:

<html>
<head>
    <style type="text/css">
    .sys-template {display:none;}
    </style>
</head>
<body>

    <ul id="dv" class="sys-template">
        <li>{{Name}}</li>
    </ul>

<script type="text/javascript" src="scripts/MicrosoftAjax.debug.js"></script>
<script type="text/javascript" src="scripts/MicrosoftAjaxTemplates.debug.js">
</script>
<script type="text/javascript">

    Sys.Application.add_load(appLoaded);

    function appLoaded()
    {
        $create(Sys.UI.DataView, { data: beatles }, null, null, $get("dv"));
    }

    var beatles = [
       { Name: "John" },
       { Name: "Paul" },
       { Name: "George" },
       { Name: "Ringo" }
    ];

</script>
</body>
</html>

The programmatic approach drastically cleans up the HTML. No longer must you declare extra XML namespaces in the body — nor flag containers for scanning by the framework. The HTML that remains is simple, common markup that makes use of an element ID and CSS class as hooks for the Client Template Framework to pick up.

The updated JavaScript gives you explicit control over when and how the template is bound to its data. Using the add_load function of Sys.Application, an event handler is added to the page to run once the application is loaded into the browser.

The appLoaded function uses the $create shortcut function to create an instance of the Sys.UI.DataView class. The argument list for $create is as follows:

Optional parameters.

Type name

The name of the class being instantiated

Properties*

A JSON object of property and value pairs to set on the object once instantiated

Events*

A JSON object describing events and associated handlers

Element ID*

The element the resulting object is attached to on the page

The $create function is a shortcut to the Sys.Component.create function. Further details are at www.asp.net/Ajax/documentation/live/ClientReference/Sys/ComponentClass/ComponentCreateMethod.aspx.

When using the $create function, once the object is created, the data is bound to the template and the final rendered view is displayed to the user.

Updating a DataView

Once you have a DataView bound to a data source, often you may want to update the data being displayed in the template. The DataView class includes a method named set_data, which takes an object list or array as a parameter. Calling the set_data method will bind the DataView to the new data objects.

In this section, you update your code to create a page that allows a user to change the template's data source between two object arrays (see Figure 2).

Using the DataView's set_data function.

Figure 2. Using the DataView's set_data function.

Begin by making a copy of Demo2.htm and name it Demo3.htm. To add the desired interaction, you will keep the structure of the document but change the JavaScript and HTML.

Update the JavaScript

Return to the script block and update the code to match the following listing:

<script type="text/javascript">

Sys.Application.add_load(appLoaded);

function appLoaded()
{
   $create(Sys.UI.DataView, { data: beatles }, null, null, $get("dv"));
}

function changeTo(data)
{
   $find("dv").set_data(data);
}

var beatles = [
   { Name: "John" },
   { Name: "Paul" },
   { Name: "George" },
   { Name: "Ringo" }
];

var monkees = [
   { Name: "Peter" },
   { Name: "Mike" },
   { Name: "Davy" },
   { Name: "Micky" }
];

</script>

Here you are creating a handler for the ASP.NET AJAX application load event using the add_load method. This gives you a place to reliably execute code once the page is loaded and ready for manipulation.

Once the page is loaded, the appLoaded function is run, and a new data view is created in the same fashion as in the previous exercises.

The changeTo function is run when a user clicks on one of the input buttons to change the template's data source. The data view is instantiated by using the ASP.NET AJAX $find method. Finally, calling the set_data method will re-bind the existing template to the new data source.

The remainder of the script is the initialization of the two object arrays that the page uses as data sources.

Update the HTML

The HTML requires very little adjustment. The template stays the same, and you simply need to add the two buttons to the page to facilitate the data source change. The changes to the HTML are highlighted:

<ul id="dv" class="sys-template">
    <li>{{Name}}</li>
</ul>

<input type="button" value="Beatles" onclick="changeTo(beatles);" />
<input type="button" value="Monkees" onclick="changeTo(monkees);" />

Once you complete these changes, run Demo3.htm in the browser to test the page.

Data-Binding Syntax

The DataView is bound to data using a binding syntax that can be adjusted for different purposes. The data-binding markup will be very familiar to those who have experience in XAML-based environments like WPF or Silverlight. The binding syntax comes in the following modes:

  • One-way/one-time binding

  • One-way/live binding

  • Two-way/live binding

Binding in ASP.NET 4 AJAX is flexible enough to accommodate the view reflecting changes to the data source as well as the UI having direct access to the data values. The following section describes each of these scenarios in detail.

One-Way/One-Time Binding

In many cases, developers want to display data to users that is read-only and will not change during the course of the page view. Consider a table of sports scores or statistics on last month's sales revenues; the purpose of these views is to simply present data to the user. When this type of scenario is required, the use of one-way/one-time binding is most appropriate.

Just as you implemented in some of the previous exercises, this type of binding is represented with the following syntax:

{{PropertyName}}

The name of the data property inside double curly braces will bind the data in the view as data binding is happening and is not altered even if the underlying data source changes.

One-Way/Live Binding

The next scenario is when you want a read-only portion of your view to reflect changes to the original data source. The one-way/live binding does not give the user direct access to edit the data but will automatically display any changes made to the data to which it is originally bound.

This type of binding is represented with the following syntax:

{binding PropertyName}

The name of the data property inside single curly braces, pre-pended with the keyword binding, will update the element's value when the data changes.

JavaScript is a case-sensitive language; therefore, binding must be in all lowercase, unlike in XAML applications.

Two-Way/Live Binding

The last scenario involves the times when you wish the UI to update when underlying changes occur and allow the user to make direct changes to the data. The best example of this type of use case is a data entry form.

This type of binding is used in conjunction with input elements and is represented with the following syntax:

<input type="text" value="{binding PropertyName}" />

Using the same format as the one-way/live binding placed inside an input element gives the user direct access to the data objects.

Create a Live Bound List Editor

Consider a page as shown in Figure 3 that allows you to edit the names of a list of people.

Changes to the data from the input controls are automatically reflected in the markup for elements with live binding.

Figure 3. Changes to the data from the input controls are automatically reflected in the markup for elements with live binding.

When the data is changed in the textbox, the original data object is immediately updated. Elements in the view that are bound with a live binding syntax reflect the changes to the data, and the elements bound with {{PropertyName}} reflect the original data.

To begin, make a copy of Demo3.htm, name it Demo4.htm, and change the markup inside the BODY element to match the following code:

<table id="dv" class="sys-template">
    <tr>
        <td>{{Name}}</td>
        <td>{binding Name}</td>
        <td><input type="text" value="{binding Name}" /></td>
    </tr>
</table>

Run the page in the browser and try changing a few values on the page. Note that only the values in the second column change when the data is updated.

Binding Customizations

The data-binding syntax includes not only the modes discussed in the previous section, but also customizations that aid in manipulating data as it is being bound to the template.

Consider the edit screen shown in Figure 4.

Binding customizations include providing default values and executing value converters.

Figure 4. Binding customizations include providing default values and executing value converters.

When the data is bound to the template, John's last name is a null value. Rather than rendering a blank space, the binding syntax allows developers the chance to provide a default value. In this case, the default value for the last name is two question marks.

Also, just as is found in XAML applications, the binding has extensibility points for developers to implement value converters. To see how a value converter works, notice how the view changes when the value is changed in one of the textboxes in Figure 5.

Using a value converter to convert text into a Boolean value.

Figure 5. Using a value converter to convert text into a Boolean value.

Changing the value in the textbox on Paul's row from "no" to "yes" automatically updates the description. What is most notable about this implementation is that the data object only understands a Boolean value for the IsDrummer property. The value converter function handles the interaction of understanding the text values of "yes" or "no" and rendering the appropriate description when binding to the data view.

Using Binding Customizations

This section demonstrates how to implement the example presented above. Create a new HTML file and name it Demo5.htm.

<html>
<head>
    <style type="text/css">
    .sys-template {display:none;}
    </style>
</head>
<body>
    <table id="dv" class="sys-template">
        <tr>
            <td>
                <span>{{ First }}</span>
                <span>{ binding Last, defaultValue=?? }</span>
                <span>{ binding IsDrummer, convert=drummerText }</span>
            </td>
<td><input type="text"
                value="{ binding IsDrummer,
                    convert=convertDrummer,
                    convertBack=convertDrummerBack }" /></td>
        </tr>
    </table>

<script type="text/javascript" src="scripts/MicrosoftAjax.debug.js"></script>
<script type="text/javascript" src="scripts/MicrosoftAjaxTemplates.debug.js">
</script>
<script type="text/javascript">

    Sys.Application.add_load(appLoaded);

    function appLoaded()
    {
        $create(Sys.UI.DataView, { data: beatles }, null, null, $get("dv"));
    }

    var beatles = [
       { First: "John", Last:null, IsDrummer: false },
       { First: "Paul", Last: "McCartney", IsDrummer: false },
       { First: "George", Last: "Harrison", IsDrummer: false },
       { First: "Ringo", Last: "Starr", IsDrummer: true }
    ];

   function drummerText(value)
   {
       return (value) ? "is a drummer" : "is not a drummer";
   }

   function convertDrummer(value)
   {
       return (value) ? "yes" : "no";
   }

   function convertDrummerBack(value)
   {
       return value.toLowerCase() == "yes";
   }

</script>
</body>
</html>

Default Value

Begin by looking at the binding syntax for the last name:

{ binding Last, defaultValue=?? }

The last name uses a one-way/live-binding to the view and is given a default value. Default values appear when the data member is null or does not exist in the object. Notice that although the value provided is a string, there are no double or single quotes around the value. The view will render any characters after the equal sign until it reachs a curly brace or a comma. A comma would terminate the syntax for a new parameter; therefore, you may not use a comma inside your default value.

Value Converters

This example uses value converters in two ways. The first use is to render a verbose description to the view based on a value in the data object, and the second is to translate an open text entry into typed values.

The code responsible for displaying the value for the IsDrummer property is defined like this:

{ binding IsDrummer, convert=drummerText }

When the data is being bound to the view, instead of simply rendering the value for the property, the rendering is delegated to the function designated by the convert argument. The target function gets the value being bound to the view in its argument array.

function drummerText(value)
{
   return (value) ? "is a drummer" : "is not a drummer";
}

The drummerText function simply examines the value from the data object and decides what to do based on the value. This function must return a string and in many cases will include HTML as the result is what is injected into the placeholder in the data view.

The code responsible for allowing the data entry is defined like this:

<input type="text"
value="{ binding IsDrummer,
convert=convertDrummer,
convertBack=convertDrummerBack }" />

In this case, the convert function is defined as convertDrummer:

function convertDrummer(value)
{
   return (value) ? "yes" : "no";
}

When the textbox is rendered, the Boolean value for the IsDrummer property is replaced with the corresponding string representation.

After the data is changed in the box, the convertBack function is run in the same manner as the convert function:

function convertDrummerBack(value)
{
   return value.toLowerCase() == "yes";
}

Observer Pattern in JavaScript

The observer pattern is defined as a way of creating a one-to-many relationship among objects in which change notifications are automatically dispatched to listening objects. The common terms used in describing the observer pattern include designating a "publisher" and a "subscriber." As the publisher is updated, each of the subscribing objects has an opportunity to respond to the change.

The following is a quick primer on how the observer pattern is implemented in the ASP.NET 4 AJAX Framework. Consider a page that allows you to update a person's last name, as shown in Figure 6.

Simple UI to change a person's name.

Figure 6. Simple UI to change a person's name.

Under normal circumstances you might be inclined to explicitly edit the object's data in a function wired up to the Update button. Once the object's state is modified, the process of displaying the results back to the user is a trivial process. This approach works but tightly couples the button to the click handler to the data object.

Another way to accomplish the same goal is to allow the data object to notify any interested parties that a change is made to its state — delegating the responsibility to respond to this change to some unknown object or function.

The following code demonstrates how to update the above scenario using the observer pattern. Create a new HTML file, name it Demo6.htm, and enter the following:

<html>
<head>
    <title></title>
</head>
<body>
    <div id="personName"></div>
<input type="text" id="lastName" name="lastName" />
    <input type="button" value="Update" onclick="update();" />

<script type="text/javascript" src="scripts/MicrosoftAjax.debug.js"></script>
<script type="text/javascript" src="scripts/MicrosoftAjaxTemplates.debug.js">
</script>
<script type="text/javascript" src="scripts/jquery-1.3.2.min.js"></script>
<script type="text/javascript">

var person = { FirstName:"John", LastName:"Smith"}

Sys.Observer.makeObservable(person);
Sys.Observer.addPropertyChanged(person, personPropertyChanged);

Sys.Application.add_load(appLoaded);

function appLoaded()
{
    personPropertyChanged(null, null);
}

function update()
{
    person.beginUpdate();
    person.setValue("LastName",$("#lastName").val());
    person.endUpdate();
}

function personPropertyChanged(sender, eventArgs)
{
    $("#personName").html(person.FirstName + " " + person.LastName);
}

</script>
</body>
</html>

The markup inside the BODY element acts as a placeholder for the person's name followed by some basic input controls to manipulate the data.

By now, the script blocks that import the Microsoft AJAX, AJAX Templates, and jQuery libraries should be very familiar to you. Following the script imports is the construction of a person object using JSON. This object receives special attention to make changes observable by other objects.

var person = { FirstName:"John", LastName:"Smith"}

Sys.Observer.makeObservable(person);
Sys.Observer.addPropertyChanged(person, personPropertyChanged);

The makeObservable function updates the object to include the methods of an observable object. These methods help route changes to the object through the observer pattern framework.

Next, the addPropertyChanged function associates changes in the object instance with a function to run when a change is triggered. In this case, the personPropertyChanged function is called anytime a change is made to the observable object.

function personPropertyChanged(sender, eventArgs)
{
    $("#personName").html(person.FirstName + " " + person.LastName);
}

When personPropertyChanged is called, the placeholder DIV is updated with the latest name information contained in the person object. The function responsible for triggering the change is the click handler for the Update button:

function update()
{
    person.beginUpdate();
    person.setValue("LastName",$("#lastName").val());
    person.endUpdate();
}

The beginUpdate method marks the object, making it ready to accept change notifications for subscribers.

An important distinction must be made when changing values to an observable object. Under normal circumstances you might approach updating the object's state with a line of code like this one:

person.LastName = $("#lastName").val();

Instead of using this traditional approach, observable objects must route changes through the Sys.Observer framework using methods that call the property changed events. Therefore, changes to this object must look like this:

person.setValue("LastName",$("#lastName").val());

The setValue takes the object member name as the first argument and the member's new value as the second argument.

If you are updating an object collection, the add method will add the new item to the collection and fire the change events.

Finally, to give the placeholder its value when the page loads, the standard ASP.NET AJAX application load syntax is used to run personPropertyChanged when the page is loaded:

Sys.Application.add_load(appLoaded);

function appLoaded()
{
    personPropertyChanged(null, null);
}

For further details on the observer framework, you may read the official documentation at http://msdn.microsoft.com/en-us/library/dd393710(VS.100).aspx.

Update the List Editor

Earlier you learned to update an existing data view with a new data source, but what if you want your template to respond to underlying changes to the original data source? This section teaches you to combine ASP.NET 4 AJAX DataViews and the JavaScript observable collections.

The following sample makes the object array an observable collection. When changes are made to the collection, the data view is able to respond to those changes.

To visualize this functionality in action, consider a page on which a few input controls appear to the user when a contact name is clicked (see Figure 7). When the page enters Edit mode, the user has the option to edit the selected item, delete it from the list, or cancel out of the operation.

List editor in Edit mode.

Figure 7. List editor in Edit mode.

If the user chooses to update the data, the DataView is updated with the changes without having to re-bind the template to the data source.

List editor after update.

Figure 8. List editor after update.

Once the underlying data source is updated (Figure 8), then you may choose whether you grant the user an opportunity for further manipulation of the data or a chance to send the changes back to the server for persistence.

Create the Page

To build the sample seen in the screenshots (Figures 7 and 8), begin by creating a new HTML document and name it Demo7.htm.

Add the HTML

Add the following code within the HEAD element on the page:

<style type="text/css">
.sys-template, .hide
{
    display:none;
}

#editor
{
    width:350px;
    padding:10px;
    background-color:#eee;
    border:1px solid #ccc;
}
</style>

The style sheet begins by styling the sys-template class used by convention for templates to initially hide the markup from the user. The next entry styles a DIV on the page used as the editor template for the data item.

Next, add the following markup inside the BODY element:

<div id="dv" class="sys-template">
    <div sys:onclick="{{ 'select(' + $index + '),' }}">
        <a href="javascript:void(0);" onclick="return false;">
            { binding Name }
        </a>
    </div>
</div>

The HTML begins with a simple data view that creates a DIV for each of the names in the list. When the user clicks on the item-level DIV, the select function is run.

In the above code listing, notice the variable named $index and the use of the sys syntax to add element attributes. The $index variable is a pseudo-column made available by the Client Template Framework to expose the current item's index value in the array. The keyword sys is required to attach data-bound attributes. Details about pseudo-columns' other data-binding keywords are explained in detail in the section, "Template Manipulation."

The select function uses the item index to populate the textbox with the selected value and then displays the editor to the user.

Note the use of the live binding syntax inside the anchor element. This binding gives the ability to update the element's data automatically when underlying data changes occur.

Next, add the following code:

<div id="editor" class="hide">
    <input type="text" id="name" name="name" />
    <input type="button" value="Update" onclick="update();" />
    <input type="button" value="Delete" onclick="del();" />
    <a href="javascript:void(0);" onclick="closeEditor();">close</a>
</div>

This markup creates the structure for the item editor. A textbox is available to edit the item's value, while the Update and Delete buttons are rendered to the user for the appropriate action. The last element is a link to close the editor. This item is rendered as a link to give less weight to keep the small editing space visually balanced.

Add the JavaScript

Begin by adding references to the ASP.NET AJAX and jQuery libraries just after the closing tag for the BODY element:

<script type="text/javascript" src="scripts/MicrosoftAjax.debug.js"></script>
<script type="text/javascript" src="scripts/MicrosoftAjaxTemplates.debug.js">
</script>
<script type="text/javascript" src="scripts/jquery-1.3.2.min.js"></script>

Next, enter the following code:

<script type="text/javascript">

var beatles = [
   { Name: "John" },
   { Name: "Paul" },
   { Name: "George" },
   { Name: "Ringo" }
];

Sys.Application.add_load(appLoaded);

function appLoaded()
{
    $create(Sys.UI.DataView, { data: beatles }, null, null, $get("dv"));
}

The code here is largely familiar to you from previous examples. The data source is prepared by creating an object array. Then, the ASP.NET AJAX Sys.Application.add_load function is called to create a handler for the application load event. When the application loads, the appLoaded function runs and a DataView is created and bound to the object array.

Next, a variable is created to keep track of the selected item in the array and the function to manage its value:

var _selectedIndex = 0;

function select(index)
{
    _selectedIndex = index;
    $("#editor").removeClass("hide");
    $("#name").val(beatles[_selectedIndex].Name);
}

The select function is called when the user clicks on a name in the list. The index value is then set aside for later use. Then the editor is displayed to the user, and the selected name is copied into the textbox for editing.

Next, the array is marked as observable:

Sys.Observer.makeObservable(beatles);

function update()
{
    beatles.beginUpdate();
    var beatle = beatles[_selectedIndex];
    beatle.Name = $get("name").value;
    beatles.removeAt(_selectedIndex);
    beatles.insert(_selectedIndex, beatle);
    beatles.endUpdate();
    closeEditor();
}

function closeEditor()
{
    $("#editor").addClass("hide");
}

When the update function runs, the object array is already prepared as an observable collection and has the appropriate methods available to mark the object to track changes. The selected object is pulled from the array, and the Name property is updated from the value found in the textbox.

The approach used in this function to fire the property changed events is to remove the current item in the array and then add the edited copy back into the same location in the array. Using the removeAt method and the insert method allows the manipulation of the array to fire any required property changed events.

In the previous section on the observer pattern, you learned that in order to update an object's member you must use the setValue method. The reason the update method is not calling setValue on the data object is because the observable object in scope is the array, not the data object. Therefore, the insert and removeAt methods are used to keep changes within messages the observer framework can manage.

Once all changes are complete, the endUpdate method is called to restrict any other changes from being published. The closeEditor function simply hides the item editor from the user.

The last function required to complete the editable list is a function responsible for deleting items from the list:

function del()
{
    beatles.beginUpdate();
    beatles.removeAt(_selectedIndex);
    beatles.endUpdate();
    $("#name").val("");
    closeEditor();
}

When the del function is called, the array is again placed into an edit state tracked by the observer framework. The selected item is removed from the array, and the edit state is closed. To clean up the item editor for future use, the name textbox is cleared out, and then the editor is hidden from the user.

Template Manipulation

As you begin to work with the DataView control, invariably you will want to customize and manipulate how the view is rendered beyond simply displaying data on the screen.

Manipulating the template is possible by using a mixture of two concepts available in a data view: pseudo-columns and code injection.

Pseudo-Columns

Pseudo-columns are variables containing context and metadata about the data view and the current data context. Available pseudo-columns include:

$index

The array index of the data during data binding

$element

The last HTML element opened or closed to which the data is being bound

$dataItem

The instance of the current data object

You can always tell when a pseudo-column is being used by the $ prefix of the variable.

Code Injection

Code injection refers to the practice of placing JavaScript code inside the template for execution during data binding. The following represent ways you may tap into the data-binding action of a template to execute code:

code:if

If the executing code returns true, then the element is rendered in the template.

code:before

Code executed before binding the current data item to the template

code:after

Code executed after binding the current data item to the template

To put these concepts in context, the next section demonstrates how to use pseudo-columns and code injection to manipulate a data view.

A Working Example

Consider a template (Figure 9) that includes a header, footer, and manipulation of the style sheet based on values found in the data source.

Using pseudo-columns and code injection to manipulate the data view.

Figure 9. Using pseudo-columns and code injection to manipulate the data view.

To implement this example, create a new HTML file, naming it Demo8.htm, and enter the following code:

<html>
<head>
    <style type="text/css">
    .sys-template {display:none;}
    .drummer {font-weight:bold;}
    .alt {background-color:#eee;}
    </style>
</head>
<body>
    <table id="dv" class="sys-template"
        border="1" style="border-collapse:collapse;">
        <tr code:if="isFirstItem($index)">
            <th>Name</th>
        </tr>
        <tr>
            <td code:after="formatItem($element, $dataItem, $index)">
                <a href="javascript:void(0);" onclick="{{ 'alert(' + $index + '),
                         return false;' }}">
                    {{Name}}

                </a>
            </td>
        </tr>
        <tfoot code:if="isLastItem($index)">
            <tr>
                <td>Note: Bold items designate a drummer.</td>
            </tr>
        </tfoot>
    </table>

<script type="text/javascript" src="scripts/MicrosoftAjax.debug.js"></script>
<script type="text/javascript" src="scripts/MicrosoftAjaxTemplates.debug.js">
</script>
<script type="text/javascript" src="scripts/jquery-1.3.2.min.js"></script>
<script type="text/javascript">

    Sys.Application.add_load(appLoaded);

    function appLoaded()
    {
        $create(Sys.UI.DataView, { data: beatles }, null, null, $get("dv"));
    }

    var beatles = [
       { Name: "John", IsDrummer:false },
       { Name: "Paul", IsDrummer: false },
       { Name: "George", IsDrummer: false },
       { Name: "Ringo", IsDrummer: true }
    ];
function isFirstItem(index)
   {
       return index == 0;
   }

   function isLastItem(index)
   {
       return index == (beatles.length - 1);
   }

   function isOddItem(index)
   {
       return (index % 2) != 0;
   }

   function formatItem(element, dataItem, index)
   {
       if (isOddItem(index))
       {
           $(element).addClass("alt");
       }

       if (dataItem.IsDrummer)
       {
           $(element).addClass("drummer");
       }
   }

</script>
</body>
</html>

To learn about the new concepts presented in this code sample, examine the segment of code for the "header" of the template:

<tr code:if="isFirstItem($index)">
    <th>Name</th>
</tr>

The above code calls the following function:

function isFirstItem(index)
{
   return index == 0;
}

The header row is rendered only one time because the isFirstItem function returns a true value only once. The function inspects the value of the $index pseudo-column to see if it is equal to 0, which is the index value for the first record.

Next, the template uses the code:after syntax to inspect the data item after it is bound to the element.

<tr>
    <td code:after="formatItem($element, $dataItem, $index)">
        <a href="javascript:void(0);" onclick="{{ 'alert(' + $index + '),
                 return false;' }}">
            {{Name}}
        </a>
    </td>
</tr>

The above code uses the following functions:

function formatItem(element, dataItem, index)
{
   if (isOddItem(index))
   {
       $(element).addClass("alt");
   }

   if (dataItem.IsDrummer)
   {
       $(element).addClass("drummer");
   }
}

function isOddItem(index)
{
   return (index % 2) != 0;
}

The formatItem function first executes the isOddItem function to determine whether or not the current item has an odd index value. Odd values are detected by applying the JavaScript modulus operator to the item index.

Next, the function accesses the dataItem and interrogates the object to find out if the current record is a drummer. Then the element argument that points to the HTML element that the data is bound to is manipulated using jQuery to update the style sheet.

Finally, the TFOOT element uses the code:if syntax once again to detect the footer of the template.

Fetching Data from the Server

Up until this point, your work with the templates has simply used object arrays that existed solely on the client. In most cases, real-world scenarios will require that your pages fetch data from the server. The following includes demonstrations and discussions of three common ways developers may choose to provide data to the UI pages:

  • ASP.NET Web Forms Page Methods

  • ASP.NET MVC Controller Action

  • ADO.NET Data Service

While these three examples are featured in this Wrox Blox, there are other options available to you as well. The DataView control also works well with WCF JSON services, ASMX web services, JSONP services, and others.

Using ASP.NET Page Methods

One of the most common scenarios is the need to work within ASP.NET Web Forms. The following example makes use of ASP.NET AJAX Page Methods to provide data to a data view. Consider a page (Figure 10) much like what you implemented earlier in the Wrox Blox, where an ASPX page displays a list of names.

Fetching data using ASP.NET AJAX Page Methods.

Figure 10. Fetching data using ASP.NET AJAX Page Methods.

Begin by creating a new ASPX page and naming it Demo9.aspx.

Implement the ASPX Code

Enter the following markup into the ASPX file:

<html>
<head runat="server">
    <style type="text/css">
    .sys-template{display:none;}
    </style>
</head>
<body>
    <form runat="server">
    <asp:ScriptManager EnablePageMethods="true" runat="server">
        <Scripts>
            <asp:ScriptReference
                Name="MicrosoftAjax.js" Path="~/scripts/MicrosoftAjax.js" />
            <asp:ScriptReference
                ScriptMode="Inherit" Path="~/scripts/MicrosoftAjaxTemplates.js" />
        </Scripts>
    </asp:ScriptManager>
    </form>
    <ul id="dv" class="sys-template">
<li>{{FirstName}}</li>
    </ul>
    <script type="text/javascript">
        Sys.Application.add_load(appLoaded);

        function appLoaded()
        {
            PageMethods.GetPeople(success, fail);
        }

        function success(result)
        {
            $create(Sys.UI.DataView, { data: result }, null, null, $get("dv"));
        }

        function fail(result)
        {
            alert(result.get_error());
        }
    </script>
</body>
</html>

This page opens by adding a server form and the ASP.NET AJAX ScriptManager. The ScriptManager is required to enable PageMethods on the page, and the form is a prerequisite for the ScriptManager.

The code of interest for this section is in the JavaScript. Just as you are accustomed to seeing in the HTML pages implemented earlier, the interaction with the page is initiated by adding a function to run once the page is loaded. In this case, instead of binding to the template directly, a PageMethod is called to request data from the server.

When the request completes and the success function runs, then the returned JSON array is used to bind to the template.

Implement the Code-Behind

The code-behind for this page simply builds up an object array to return to the client:

public partial class PageMethods : System.Web.UI.Page
{
    [System.Web.Services.WebMethod]
    public static Person[] GetPeople()
    {
        Person[] people = {
                          new Person() { FirstName = "John" },
                          new Person() { FirstName = "Paul" },
                          new Person() { FirstName = "George" },
                          new Person() { FirstName = "Ringo"}
                          };

        return people;
    }
}

Now launch your page in the browser to verify it works as expected.

Using an ASP.NET MVC Controller Action

Another common scenario includes using the result of an ASP.NET MVC controller action to provide data to a Client Template. In order to make an equal comparison, this example has the view rendering the same list of names to the user, but this time in a view page, as shown in Figure 11.

Fetching data using an ASP.NET MVC controller action.

Figure 11. Fetching data using an ASP.NET MVC controller action.

To begin, create a new folder inside the Views folder named ControllerAction. In the new view page, enter the following code inside the MainContent content placeholder:

<ul id="dv" class="sys-template">
    <li>{{FirstName}}</li>
</ul>

<script type="text/javascript" src="/scripts/MicrosoftAjax.debug.js"></script>
<script type="text/javascript" src="/scripts/MicrosoftAjaxTemplates.debug.js">
</script>
<script type="text/javascript" src="/scripts/jquery-1.3.2.min.js"></script>
<script type="text/javascript">

    $(document).ready(function()
    {
        $.post("/ControllerAction/GetPeople",
            null,
            function(data)
            {
                $create(Sys.UI.DataView, { data: data }, null, null, $get("dv"));
            },
            "json");
    });
</script>

In this case, instead of using ASP.NET AJAX Page Methods to request data from the server, a jQuery function named post is used to make the call and manage the call back.

In the same fashion as the last example, the initial interaction with the page is set up by using the standard jQuery ready function. This ensures that the needed code will run only once the page is loaded and ready for execution.

The arguments for the post function include:

URL

The URL of the page or, in this case, the controller action, to load

Data

A JSON object representing arguments passed to the URL destination

Callback

The function to execute once a response is returned from the server

Type

The type of data being returned from the URL destination

Next, create a new class in the Controllers folder named ControllerActionController.cs and add the following code:

public class ControllerActionController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public JsonResult GetPeople()
    {
        Person[] people = {
                          new Person() { FirstName = "John" },
                          new Person() { FirstName = "Paul" },
                          new Person() { FirstName = "George" },
                          new Person() { FirstName = "Ringo"}
                          };

        return Json(people);
    }
}

The Index controller action is required to serve the page Index view page to the user. The GetPeople method returns an instance of the JsonResult class as the view page expects an array of JSON objects to bind to the template.

Using ADO.NET Data Services

Another avenue available to provide server-side interaction to your pages is to integrate with ADO.NET Data Services. For the uninitiated, ADO.NET Data Services is a series of patterns and libraries that provide REST-based services to an underlying data context.

In the following demonstration (see Figure 12), your HTML page will call an ADO.NET Data Service to list product names from the Northwind database.

Using ADO.NET Data Services to fill a Client Template.

Figure 12. Using ADO.NET Data Services to fill a Client Template.

Before you may implement the code for the Client Template, you must first build a data context and the data service to host interaction with the data.

Create the Entity Set

The following example will expose an Entity Framework entity set using ADO.NET Data Services.

The following example requires the Northwind database. If you need to download the database, please go to http://tinyurl.com/northwind.

To begin, add a new entity model to the Models folder of your application, naming it Northwind.edmx, as shown in Figure 13.

Add an Entity Framework data model to your application.

Figure 13. Add an Entity Framework data model to your application.

Click on the Add button. Next, the wizard asks you to select the model contents, as shown in Figure 14.

Select what the model contains.

Figure 14. Select what the model contains.

Click on the "Generate from database" icon, and click Next. The resulting dialog gives you an opportunity to create or select the connection to the Northwind database, as shown in Figure 15.

Connect to the Northwind database.

Figure 15. Connect to the Northwind database.

Once you make the connection to the database, click Next. The following dialog will allow you to select what database assets you wish to model. See Figure 16.

Selecting database objects to model.

Figure 16. Selecting database objects to model.

For the purposes of this example, check the box next to the Tables node and click Finish.

If the Entity Designer opens in the Editor window, you may close it.

Create the ADO.NET Data Service

The next step in the process is to create an ADO.NET Data Service that hosts the entity set you just created. Right-click on the project name, and select "Add Items" to add an ADO.NET Data Service to the root of the site named nw.svc, as shown in Figure 17.

Add the ADO.NET Data Service.

Figure 17. Add the ADO.NET Data Service.

Once the code window for the service opens in the Editor, update the code to match the following listing:

public class nw : DataService<NorthwindEntities>
{
    // This method is called only once to initialize service-wide policies.
    public static void InitializeService(IDataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("*", EntitySetRights.All);
    }
}

Calling the SetEntitySetAccessRule method as shown configures the service to allow complete and unrestricted access to the service.

This practice is used in the context of this example for brevity and is not recommended unless you know exactly what you are doing.

Declarative Data Binding

To begin working with the Data Service, the first implementation demonstrates how to use declarative markup to interact with a Data Service. The following example fills the Client Template with the contents of the Product table without requiring you to write a single line of JavaScript.

To begin, create a new HTML file, naming it Demo11.htm, and enter the following code:

You are skipping a file named Demo10 because the ASP.NET MVC example required names by convention. The numbering is preserved here so you may look up code examples by number in the accompanying code download.

<html>
<head>
    <style type="text/css">
    .sys-template { display:none;}
    </style>
</head>
<body xmlns:sys="javascript:Sys"
      xmlns:dataview="javascript:Sys.UI.DataView"
      sys:activate="*">

    <ul class="sys-template"
        sys:attach="dataview"
        dataview:autofetch="true"
        dataview:dataprovider="{{ new Sys.Data.AdoNetServiceProxy('nw.svc') }}"
        dataview:fetchoperation="Products">
        <li>{{ ProductName }}</li>
    </ul>

    <script type="text/javascript" src="scripts/MicrosoftAjax.debug.js"></script>
    <script type="text/javascript" src="scripts/MicrosoftAjaxTemplates.debug.js">
</script>
    <script type="text/javascript" src="scripts/MicrosoftAjaxAdoNet.debug.js">
</script>

</body>
</html>

The material changes to this example from the previous declarative example lie in the extra attributes attached to the data view. The dataprovider attribute points to the URI of the services used to provide data to the template. The fetchoperation tells the request which operation to call to request data from the server.

Another notable change in this example is that you must include the MicrosoftAjaxAdoNet script file in the page to expose the classes necessary to interact with ADO.NET Data Services.

Imperative Data Binding

Although the declarative model is simple and effective, developers often need more control over the interaction with the page. The following example demonstrates how to extend interaction with the data and use an imperative approach to declaring and binding a DataView against ADO.NET Data Services.

This exercise updates the view to use live data binding and demonstrates how easy data manipulation is made when integrating with data services. Consider a page on which the user is presented with a textbox to edit a product name, as shown in Figure 18.

Product name editor using Client Templates and ADO.NET Data Services.

Figure 18. Product name editor using Client Templates and ADO.NET Data Services.

Using data services and live data binding, this page achieves read/write access to the data without having to write the code that you may expect under other circumstances. The only code necessary to make this Edit page work is to live-bind the Client Template to an ADO.NET Data Services data context.

Once changes are made to the data, the button click calls a single method on the data context to persist changes back to the database.

To begin, create a new HTML file, naming it Demo12.htm, and enter the following code:

<html>
<head>
    <style type="text/css">
    .sys-template { display:none;}
    </style>
</head>
<body>

    <div id="dv" class="sys-template">
        <div><input type="text" value="{ binding ProductName }" /></div>
    </div>

    <input type="button" value="Save" onclick="_dataContext.saveChanges();" />

    <script type="text/javascript" src="scripts/MicrosoftAjax.debug.js"></script>
<script type="text/javascript" src="scripts/MicrosoftAjaxTemplates.debug.js">
</script>
    <script type="text/javascript" src="scripts/MicrosoftAjaxAdoNet.debug.js">
</script>
    <script type="text/javascript">

        Sys.Application.add_load(appLoaded);

        var _dataContext;
        var _dv;

        function appLoaded()
        {
            _dataContext = $create(Sys.Data.AdoNetDataContext,
            {
                serviceUri: "nw.svc"
            });

            _dv = $create(Sys.UI.DataView,
            {
                autoFetch: true,
                dataProvider: _dataContext,
                fetchOperation: "Products(1)"
            },
            null, null, $get("dv"));
        }

    </script>

</body>
</html>

Once again, the HTML markup is significantly simplified by removing the declarative syntax. The template uses the two-way live-binding syntax so the underlying data source will recognize changes to the data.

The scripts included on the page include the MicrosoftAjax, MicrosoftAjaxTemplates, and MicrosoftAjaxAdoNet libraries.

The code in the block after the imported scripts begins with the requisite addition of a load handler. When appLoaded is run, a data context is created. This context holds the URI used when making the call to the server for data.

Now, with an instance of the data context, the data view is instantiated using the $create method. The arguments passed to the create method resemble the attributes defined in the previous declarative example.

In this page, instead of listing the entire contents of the Products table, the URI is updated to request only the Product where the primary key value is equal to 1. This is possible by defining:

fetchOperation: "Products(1)".

Finally, turn your attention back up to the HTML and review the code for the input button:

<input type="button" value="Save" onclick="_dataContext.saveChanges();" />

The button calls the saveChanges method of the instance of the data context.

Launch this page in the browser and try to edit the product name, and then refresh the page to ensure that your changes are persisted.

Wrapping Up

The Preview 4 release includes many advances that make working inside the browser a much more streamlined experience for the developer. These advancements include:

  • Simple ways for templates to interface with server-side services of many types

  • Markup extensions and the implementation of the observer pattern in JavaScript enable a development experience in JavaScript that closely resembles the practices of XAML-based environments like WPF and Silverlight.

  • True separation of HTML markup and JavaScript code

  • Live binding to data sources

  • A rich DataView control and accompanying DataContext component

As always with working with preview code, please be sure to check the ASP.NET CodePlex site (http://aspnet.codeplex.com/Wiki/View.aspx?title=AJAX) regularly for updates to the framework.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.109.234