|
The 4.0 release of ASP.NET AJAX will include a significant number
of new features targeted at client-side programming. Earlier releases of
ASP.NET AJAX included client-side programming features like web service
proxies, global helper functions (like $get and $create), client library
additions (like Sys.StringBuilder), and a complete set of extensions to add
classes, namespaces, interfaces, and inheritance to the JavaScript language.
Most of these features, however, were targeted at supporting the developer in
building client-side features by hand, and did not help much with generating
client-side HTML or manipulating the DOM. Version 4.0 introduces two major
features for simplifying client-side development: client-side data binding and
declarative component creation. As we’ll see, the addition of these two
features greatly expands the options you have when writing client pages. Additionally,
declarative instantiation and templating define a client-side model that is
very familiar and therefore easy to use.
As you may be aware, the first release of ASP.NET AJAX had a
supplemental futures release that
contained an implementation of declarative instantiation and client binding, so
we always knew this was coming. However, the earlier implementation was based
on XML and required that the user insert a script element marked with the
type=’text/xml-script’ attribute below the HTML for the page. While initially
very promising, this technique had several drawbacks including its verbosity,
its dependence on a client XML parser, and performance issues. Finally in
version 4.0, we have a completely new implementation that doesn’t suffer from
any of these limitations, and looks to be really useful and easy to use.
This article is based on the preview 4 release of ASP.NET Ajax available for download at http://www.codeplex.com/aspnet/, and
thus the details of some features explained here are subject to change before
the final release.
DataView and client templates
We will start with a simple example that demonstrates how to
associate an array of items in JavaScript with a client template. The core
mechanism for client data binding and templating is a client-side class named Sys.UI.DataView.
This class inherits from the Sys.UI.Control class which defines a mechanism for
associating with an HTML element in the DOM, and it also defines several
properties and methods including the data
property which can be initialized with an array of data to bind to a template. The
page below uses the DataView class to associate a list of courses and their
codes to an HTML table template (note we are manually including the script
files for ASP.NET Ajax 4.0 – these will be included through the standard ScriptManager
control in the final release).
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<style
type="text/css">
.sys-template {display:none}
</style>
<script
src="scripts/MicrosoftAjax.debug.js"
type="text/javascript"></script>
<script
src="scripts/MicrosoftAjaxTemplates.debug.js"
type="text/javascript"></script>
<script
type="text/javascript">
var courses = [
{
CourseCode: "AP17", Course: "ASP.NET Ajax Fundamentals" },
{
CourseCode: "AP10", Course: "ASP.NET 3.5 Fundamentals" },
{
CourseCode: "AP27", Course: "ASP.NET MVC Fundamentals" },
{
CourseCode: "AP23", Course: ".NET 3.5 Fundamentals" },
{
CourseCode: "AP25", Course: "LINQ Fundamentals" },
{ CourseCode: "AP19",
Course: "Silverlight Fundamentals" },
{
CourseCode: "AP14", Course: "WCF Fundamentals" },
{
CourseCode: "AP16", Course: "Windows Workflow Fundamentals" },
{
CourseCode: "AP15", Course: "WPF Fundamentals" }
];
function pageLoad()
{
$create(Sys.UI.DataView,
{ data: courses }, null, null,
$get("resultTable"));
}
</script>
</head>
<body>
<table
id="resultTable"
border="1"
class="sys-template">
<tr>
<td>{{CourseCode}}</td>
<td>{{Course}}</td>
</tr>
</table>
</body>
</html>
When run, the page above will display the following table of
courses:

As soon as the DataView class is created with the $create()
method, and initialized with the courses
array as its data property, the data in that array is bound to the template
defined by the resultTable element.
The entire data binding process is very similar conceptually to the equivalent
server-side data binding process most ASP.NET developers are familiar with: a
collection of items is bound to a template
which is rendered once per element in the collection, replacing any data-bound
expressions with data from the current bound element.
There is no equivalent concept to the server-side controls’ ItemTemplate or AlternatingItemTemplate properties in client-side binding. Instead,
you specify the identifier of an HTML element in the page whose child elements
all constitute the template to be replicated. This keeps the markup simple
without introducing any additional syntax, and maintains XHTML-compliance for
validation. The one other requirement for defining a template is the parent
element must have the sys-template
CSS class applied, and that class must be defined with display set to none, as
shown in the example above. This convention serves two purposes – it helps the
parser identify which elements are part of a template on your page (which will
become important when we use declarative instantiation), and it keeps the
template markup hidden until ASP.NET Ajax has completed the binding (it will
toggle the display to be visible).
Customizing Templates
The first thing you should know about customizing templates,
is that the template expression language is JavaScript. This means you can
inject arbitrary JavaScript expressions into your template markup, which gives
you a lot of flexibility. For example, if we wanted to update our table
template to show the course code prefixed to the course name, we could use the
JavaScript concatenation operator to generate the string as shown below.
<table id="resultTable"
border="1"
class="sys-template">
<tr>
<td>{{CourseCode}}</td>
<td>{{CourseCode + ":" + Course}}</td>
</tr>
</table>
Even with the entire JavaScript language at your disposal in
your binding expressions, however, you will quickly find limitations in what
you can achieve. For example, suppose we wanted to include a thead element within our templated table
shown above – what would the following end up rendering?
<table id="resultTable"
border="1"
class="sys-template">
<thead>
<tr>
<th>Code</th>
<th>Course</th>
</tr>
</thead>
<tr>
<td>{{CourseCode}}</td>
<td>{{Course}}</td>
</tr>
</table>
Since the thead
element is one of the child elements of the table, which is the container of
the template, it will be replicated along with the row template for each item
in the bound array, resulting in n
head rows which is obviously not what we want. Instead, we need some way to
tell the template to render the thead
element for only the first element, and ignore it for all others. The solution
is to use template code to provide conditional rendering, as shown below.
<body xmlns:code="http://schemas.microsoft.com/aspnet/code">
<!-- ... -->
<thead code:if="$index==0">
<tr>
<th>Code</th>
<th>Course</th>
</tr>
</thead>
<!-- ... -->
The AJAX libraries use the namespace extensibility of XHTML
to inject code into the template markup, so before you can use this code feature, you must declare the code namespace on the body tag of your page. With that in
place, you can use code-prefixed
attributes in any of your template elements, and the templating engine will
evaluate the code during the binding process. Notice we are also taking
advantage of a pseudo column called $index which refers to the ordinal index
of the row being bound (starting at 0). The complete list of code attributes
and pseudo columns is shown in the table below.
|
code:if
|
If the expression evaluates to
true, element is rendered, otherwise not.
|
|
code:before
|
The expression is evaluated prior
to rendering the element.
|
|
code:after
|
The expression is evaluated after
rendering the element.
|
|
$index
|
Ordinal index or the row
(0-based).
|
|
$dataItem
|
Current item being bound.
|
Code attributes and pseudo columns
As a somewhat arbitrary example, here’s a version of our
table template that uses code to iterate over each character in a course’s name
and convert it into its Morse code equivalent using a function called AsciiToMorse (not shown). The updated
rendering is shown below the code sample.
<table id="resultTable"
border="1"
class="sys-template">
<thead code:if="$index==0">
<tr>
<th>Code</th>
<th>Course</th>
</tr>
</thead>
<tr>
<td>{{CourseCode}}</td>
<td>
<span
code:before="for(var i=0;i<$dataItem.Course.length;i++){"
code:after="}">
{{AsciiToMorse($dataItem.Course[i])}}
</span>
</td>
</tr>
</table>

Declarative Instantiation
Although there wasn’t a lot of code involved with
associating a DataView class with an HTML element, the ASP.NET team wanted to
make it possible to uses this new client-side binding mechanism in a completely
declarative fashion – without any code whatsoever. What they came up with to
achieve this is similar to the original futures
implementation of declarative component instantiation using XML script, but
this time they implemented it using the namespace extensibility of XHTML. The
result is a concise, easy to read, XHTML-compliant syntax that gives you the
ability to associate JavaScript components with your HTML elements in a
completely declarative fashion.
As an example, here is how we would declare our templatized
table to be declaratively associated with a DataView, and bound to the same courses array.
<body xmlns:sys="javascript:Sys"
xmlns:code="http://schemas.microsoft.com/aspnet/code"
xmlns:dataview="javascript:Sys.UI.DataView"
sys:activate="*">
<!-- ... -->
<table id="resultTable"
border="1"
class="sys-template"
sys:attach="dataview"
dataview:data="{{courses}}">
<tr>
<td>{{CourseCode}}</td>
<td>{{Course}}</td>
</tr>
</table>
<!-- ... -->
Notice that we have included two additional namespace
mappings on our body element: sys and dataview. We then associate a new instance of the DataView class
with our table element using sys:attach
and populate the data property of the
DataView with the courses array. If
there were other parameters we wanted to populate on the DataView, we would add
additional dataview-prefixed
attributes to our table element with the values we wanted them to take on. The sys:activate attribute added to the body element describes where in the DOM
to search for attached JavaScript components. Because we specified ‘*’ the
parser will scan the entire page looking for any elements with a sys:attach attribute, and then will take
care of creating new instances of the associated class and associating it with
the HTML element. If you have a large page, you may want to consider scoping
the scan of the parser to a small area by specifying a containing div or other element within which it
should restrict its search. With this technique it is now possible to create
and associate a DataView with any element on your page, without resorting to
writing any script to perform the creation and association.
This declarative instantiation mechanism is not restricted
to the DataView class, it in fact works for any client component that inherits
from the Sys.UI.Control base class, so you can build your own controls and
behaviors and attach them to HTML elements using the same declarative technique.
For example, the page below leverages a custom class called
PS.HighlightBehavior that adds highlighting behavior to an input element.
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<style
type="text/css">
.sys-template {display:none}
.highlight {background-color:Yellow}
.lowlight {background-color:White}
</style>
<!-- ... -->
<script
src="scripts/HighlightBehavior.js"
type="text/javascript" />
</head>
<body xmlns:sys="javascript:Sys"
xmlns:ps="javascript:PS.HighlightBehavior"
sys:activate="*">
<input
type="text"
sys:attach="ps"
ps:highlightclass="highlight"
ps:lowlightclass="lowlight" />
</body>
</html>
Conclusion
ASP.NET Ajax 4.0 is introducing a brand new client
templating and data binding model that is intuitive to use, powerful, flexible,
and XHTML-compliant. This is a welcome addition to the framework, filling a
hole that exists today forcing developers to come up with their own techniques
for rendering data to client components. This article described the basic
binding and templating mechanism supported by the DataView component, but there
is an equal amount that we didn’t explore, including automatic data retrieval
from web services and client-side data context classes.
If you'd like to see a demo of these features in action, check out the
following screencast:
Also, be sure to watch for coverage of these features and more in
Pluralsight On-Demand!
Bio:
Fritz Onion is a
co-founder of Pluralsight where he heads the Web development curriculum. Fritz
is the author of Essential ASP.NET (Addison Wesley 2003) and Essential ASP.NET 2.0 (Addison Wesley
2006). Reach him at pluralsight.com/fritz.
|