Tải bản đầy đủ (.pdf) (158 trang)

ASP.NET AJAX Programmer’s Reference with ASP.NET 2.0 or ASP.NET 3.5 phần 10 pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (2.7 MB, 158 trang )

Appendix D: Data Control
1367
Next, it checks whether the bound data collection implements the IData interface. If so, it invokes the

add method on the data collection to add an empty record to the collection:
if (Sys.Preview.Data.IData.isImplementedBy(this._data))
this._data.add({});
If the bound data collection is a JavaScript array, and if it exposes a method named add , the addItem
method simply calls this method to add an empty record to the data collection:
else if (this._data instanceof Array)
{
if(typeof(this._data.add) === “function”)
this._data.add({});
If the bound data collection is a JavaScript array but it does not expose the add method, the addItem
method simply calls the
add static method on the Array class to add an empty record to the data
collection:
else if (this._data instanceof Array)
{
if(typeof(this._data.add) === “function”)
this._data.add({});

else
Array.add(this._data, {});
}
Next, the addItem method sets the current data index to the index of the newly added data record:
this.set_dataIndex(this.get_length() - 1);
Finally, the addItem method calls the triggerChangeEvents method, passing the JavaScript object that
contains the old values of the
dataIndex , canMoveNext , and canMovePrevious properties to raise the
appropriate events, as discussed earlier:


this.triggerChangeEvents(oldState);
Listing D-14: The addItem Method
function Sys$Preview$UI$Data$DataControl$addItem()
{
if (this._data)
{
var oldState = this.prepareChange();
if (Sys.Preview.Data.IData.isImplementedBy(this._data))
this._data.add({});

(continued)
bapp04.indd 1367bapp04.indd 1367 8/20/07 8:59:57 PM8/20/07 8:59:57 PM
Appendix D: Data Control
1368
Listing D-14 (continued)
else if (this._data instanceof Array)
{
if(typeof(this._data.add) === “function”)
this._data.add({});

else
Array.add(this._data, {});
}
this.set_dataIndex(this.get_length() - 1);
this.triggerChangeEvents(oldState);
}
}
deleteCurrentItem
As the name suggests, the deleteCurrentItem method deletes the current data record from the bound
data collection — if the data control is indeed bound to a data collection. As Listing D-15 shows, this

method begins by invoking the
prepareChange method to return the JavaScript object that contains the
current values of the
dataIndex , canMoveNext , and canMovePrevious properties:
var oldState = this.prepareChange();
Next, it sets an internal flag to true to signal that all change notifications must be suspended because
we’re about to introduce new changes:
this._suspendChangeNotifications = true;
Then it calls the get_dataItem getter to return a reference to the current data record:
var item = this.get_dataItem();
Next, it resets the current data index if the current data record is the last data record in the data
collection:
if (this.get_dataIndex() === this.get_length() - 1)
this.set_dataIndex(Math.max(0, this.get_length() - 2));
Then it checks whether the bound data collection implements the IData interface. If so, it invokes the

remove method on the bound data collection to remove the current data record:
if (Sys.Preview.Data.IData.isImplementedBy(this._data))
this._data.remove(item);
Next, the deleteCurrentItem method checks whether the bound data collection is a JavaScript array
and whether it supports the
remove method. If so, it invokes the remove method on the data collection
to remove the current data record:
bapp04.indd 1368bapp04.indd 1368 8/20/07 8:59:57 PM8/20/07 8:59:57 PM
Appendix D: Data Control
1369
else if (this._data instanceof Array)
{
if(typeof(this._data.remove) === “function”)
this._data.remove(item);

If the bound data collection is a JavaScript array but does not support the remove method, it calls the

remove static method on the Array class to remove the current data record for the data collection:
else if (this._data instanceof Array)
{
if(typeof(this._data.remove) === “function”)
this._data.remove(item);

else
Array.remove(this._data, item);
}
Next, it resets the _suspendChangeNotifications flag to allow change notifications:
this._suspendChangeNotifications = false;
Finally, it invokes the triggerChangeEvents method, passing in the JavaScript object that contains the
old values of the
dataIndex , canMoveNext , and canMovePrevious properties, to trigger the required
events, as discussed earlier:
this.triggerChangeEvents(oldState);
Listing D-15: The deleteCurrentItem Method
function Sys$Preview$UI$Data$DataControl$deleteCurrentItem()
{
if (this._data)
{
var oldState = this.prepareChange();
this._suspendChangeNotifications = true;
var item = this.get_dataItem();
if (this.get_dataIndex() === this.get_length() - 1)
this.set_dataIndex(Math.max(0, this.get_length() - 2));

if (Sys.Preview.Data.IData.isImplementedBy(this._data))

this._data.remove(item);

else if (this._data instanceof Array)
{
if(typeof(this._data.remove) === “function”)
this._data.remove(item);

else
Array.remove(this._data, item);
}
this._suspendChangeNotifications = false;
this.triggerChangeEvents(oldState);
}
}
bapp04.indd 1369bapp04.indd 1369 8/20/07 8:59:58 PM8/20/07 8:59:58 PM
Appendix D: Data Control
1370
getItem
The getItem method of the DataControl base class enables you to return a reference to the data record
with the specified data index. As you can see from Listing D-16 , this method first checks whether the
data control is indeed bound to a data collection. If not, it returns
null . If so, it checks whether the
bound data collection implements the
IData interface. If so, it simply calls the getItem method on the
data collection to return a reference to the data record with the specified index:
if (Sys.Preview.Data.IData.isImplementedBy(this._data))
return this._data.getItem(index);
If not, it checks whether the bound data collection is a JavaScript array. If so, it uses the specified data
index as an index into the data collection to return a reference to the data record with the specified index:
if (this._data instanceof Array)

return this._data[index];
Listing D-16: The getItem Method
function Sys$Preview$UI$Data$DataControl$getItem(index)
{
if (this._data)
{
if (Sys.Preview.Data.IData.isImplementedBy(this._data))
return this._data.getItem(index);

if (this._data instanceof Array)
return this._data[index];
}
return null;
}
moveNext
The moveNext method of the DataControl base class enables you to move to the next data record in the
bound data collection. As Listing D-17 shows, if the data control is not bound to any data collection, the

moveNext method does not do anything. This method begins by invoking the prepareChange method,
as usual:
var oldState = this.prepareChange();
Next, it calls the get_dataIndex getter to return the current data index, and increments this value by
one to arrive at the new value for the current data index:
var newIndex = this.get_dataIndex() + 1;
bapp04.indd 1370bapp04.indd 1370 8/20/07 8:59:58 PM8/20/07 8:59:58 PM
Appendix D: Data Control
1371
If the new value is not greater than or equal to the total number of data records in the bound collection, it
calls the
set_dataIndex setter to set the current data index to the new value:

if (newIndex < this.get_length())
this.set_dataIndex(newIndex);
Finally, it invokes the triggerChangeEvents method as usual to trigger the necessary events:
this.triggerChangeEvents(oldState);
Listing D-17: The moveNext Method
function Sys$Preview$UI$Data$DataControl$moveNext()
{
if (this._data)
{
var oldState = this.prepareChange();
var newIndex = this.get_dataIndex() + 1;
if (newIndex < this.get_length())
this.set_dataIndex(newIndex);

this.triggerChangeEvents(oldState);
}
}
movePrevious
As the name suggests, the movePrevious method of the DataControl base class enables you to move
to the previous data record of the bound data collection. As Listing D-18 shows, this method begins by
calling the
prepareChange method as usual:
var oldState = this.prepareChange();
Next, it calls the get_dataIndex getter to return the current data index and decrements this value by
one to arrive at the new value:
var newIndex = this.get_dataIndex() - 1;
If the new value is a positive number, it invokes the set_dataIndex setter to set the current data index
to the new value:
if (newIndex >=0)
this.set_dataIndex(newIndex);

Finally, it invokes the triggerChangeEvents method as usual:
this.triggerChangeEvents(oldState);
bapp04.indd 1371bapp04.indd 1371 8/20/07 8:59:58 PM8/20/07 8:59:58 PM
Appendix D: Data Control
1372
Listing D-18: The movePrevious Method
function Sys$Preview$UI$Data$DataControl$movePrevious()
{
if (this._data)
{
var oldState = this.prepareChange();
var newIndex = this.get_dataIndex() - 1;
if (newIndex >=0)
this.set_dataIndex(newIndex);

this.triggerChangeEvents(oldState);
}
}
onBubbleEvent
The DataControl base class overrides the onBubbleEvent method that it inherits from the Control
base class, as shown in Listing D-19 . Recall that the
onBubbleEvent method is where a client control
captures the command events raised by its child controls. The
DataControl base class’ implementation
of this method only handles the
select event; that is why the method begins by calling the
get_commandName method on its second parameter to determine whether the current event is a

select event. If so, it takes these steps to handle the event. First, it calls the get_argument method
on its second parameter to return the index of the selected data record:

var arg = args.get_argument();
If no data index has been specified, the onBubbleEvent takes these steps to access the current data
index, and uses this index as the selected index. First, it invokes the
get_dataContext to return a
reference to the current data record:
var dataContext = source.get_dataContext();
Next, it invokes the get_index method on the current data record to return its index, and uses this
index as the selected index:
arg = dataContext.get_index();
Next, it calls the set_dataIndex method to specify the selected index as the current data index:
this.set_dataIndex(arg);
bapp04.indd 1372bapp04.indd 1372 8/20/07 8:59:58 PM8/20/07 8:59:58 PM
Appendix D: Data Control
1373
Listing D-19: The onBubbleEvent Method
function Sys$Preview$UI$Data$DataControl$onBubbleEvent(source, args)
{
if (args.get_commandName() === “select”)
{
var arg = args.get_argument();
if (!arg && arg !== 0)
{
var dataContext = source.get_dataContext();
if (dataContext)
arg = dataContext.get_index();
}

if (arg && String.isInstanceOfType(arg))
arg = Number.parseInvariant(arg);


if (arg || arg === 0)
{
this.set_dataIndex(arg);
return true;
}
}
return false;
}
descriptor
The DataControl base class, like any other ASP.NET AJAX client class, exposes a static property named
descriptor that describes its methods and properties to enable its clients to use the ASP.NET AJAX
client-side type inspection facilities to inspect its methods and properties generically, without knowing
the actual type of the class, as shown in Listing D-20 .
Listing D-20: The descriptor Property
Sys.Preview.UI.Data.DataControl.descriptor =
{
properties: [ { name: ‘canMoveNext’, type: Boolean, readOnly: true },
{ name: ‘canMovePrevious’, type: Boolean, readOnly: true },
{ name: ‘data’, type: Sys.Preview.Data.DataTable },
{ name: ‘dataIndex’, type: Number },
{ name: ‘dataItem’, type: Object, readOnly: true },
{ name: ‘length’, type: Number, readOnly: true } ],
methods: [ { name: ‘addItem’ },
{ name: ‘deleteCurrentItem’ },
{ name: ‘moveNext’ },
{ name: ‘movePrevious’ } ]
}
bapp04.indd 1373bapp04.indd 1373 8/20/07 8:59:59 PM8/20/07 8:59:59 PM
Appendix D: Data Control
1374

Developing a Custom Data Control
Listing D-21 presents the content of a JavaScript file named CustomTable.js that contains the
implementation of a new version of the
CustomTable control that derives from the DataControl base
class. As you can see, the
render method is where all the action is. This is the method that renders the
user interface of the
CustomTable custom data control. As you can see, this method begins by invoking
the
get_data method to return a reference to the data collection bound to the CustomTable data
control. This control, like any other data control, inherits the
get_data method from the DataControl
base class:
var dataSource = this.get_data();
Next, the render method raises an exception if the data collection bound to the data control is neither a
JavaScript array nor an
IData object:
if (Sys.Preview.Data.IData.isImplementedBy(dataSource))
isArray = false;

else if (!Array.isInstanceOfType(dataSource))
throw Error.createError(‘Unknown data source type!’);
Next, the render method simply iterates through the data records in the data collection bound to the
data control to render each record in a
<tr> DOM element.
Listing D-21: The Content of the CustomTable.js JavaScript File that Contains the
Implementation of the CustomTable Custom Data Control
Type.registerNamespace(“CustomComponents”);
CustomComponents.CustomTable = function
CustomComponents$CustomTable(associatedElement)

{
CustomComponents.CustomTable.initializeBase(this, [associatedElement]);
}
function CustomComponents$CustomTable$set_dataFieldNames(value)
{
this._dataFieldNames = value;
}
function CustomComponents$CustomTable$get_dataFieldNames()
{
return this._dataFieldNames;
}
function CustomComponents$CustomTable$render()
{
var isArray = true;
var dataSource = this.get_data();

if (Sys.Preview.Data.IData.isImplementedBy(dataSource))
isArray = false;

else if (!Array.isInstanceOfType(dataSource))
throw Error.createError(‘Unknown data source type!’);

bapp04.indd 1374bapp04.indd 1374 8/20/07 8:59:59 PM8/20/07 8:59:59 PM
Appendix D: Data Control
1375
var sb = new Sys.StringBuilder(‘<table align=”center” id=”products” ‘);
sb.append(‘style=”background-color:LightGoldenrodYellow; border-color:Tan;’);
sb.append(‘border-width:1px; color:Black”’);
sb.append(‘ cellpadding=”5”>’);


var propertyNames = [];

var length = isArray ? dataSource.length : dataSource.get_length();

for (var i=0; i<length; i++)
{
var dataItem = isArray? dataSource[i] : dataSource.getItem(i);

if (i == 0)
{
sb.append(‘<tr style=”background-color:Tan; font-weight:bold”>’);
for (var c in this._dataFieldNames)
{
sb.append(‘<td>’);
sb.append(this._dataFieldNames[c]);
sb.append(‘</td>’);
}
sb.append(‘</tr>’);
}

if (i % 2 == 1)
sb.append(‘<tr style=”background-color:PaleGoldenrod”>’);
else
sb.append(‘<tr>’);

for (var j in this._dataFieldNames)
{
var dataFieldName = this._dataFieldNames[j];

var dataFieldValue = Sys.Preview.TypeDescriptor.getProperty(dataItem,

dataFieldName, null);
var typeName = Object.getTypeName(dataFieldValue);

if (typeName !== ‘String’ && typeName !== ‘Number’ && typeName !== ‘Boolean’)
{
var convertToStringMethodName =
Sys.Preview.TypeDescriptor.getAttribute(dataFieldValue,
“convertToStringMethodName”);

if (convertToStringMethodName)
dataFieldValue = Sys.Preview.TypeDescriptor.invokeMethod(dataFieldValue,
convertToStringMethodName);
}

sb.append(‘<td>’)
sb.append(dataFieldValue);
sb.append(‘</td>’);
}

(continued)
bapp04.indd 1375bapp04.indd 1375 8/20/07 8:59:59 PM8/20/07 8:59:59 PM
Appendix D: Data Control
1376
Listing D-21 (continued)
sb.append(‘</tr>’);
}

sb.append(‘</table>’);
this.get_element().innerHTML = sb.toString();
}

function CustomComponents$CustomTable$initialize()
{
CustomComponents.CustomTable.callBaseMethod(this, “initialize”);
}
CustomComponents.CustomTable.prototype =
{
get_dataFieldNames : CustomComponents$CustomTable$get_dataFieldNames,
set_dataFieldNames : CustomComponents$CustomTable$set_dataFieldNames,
render : CustomComponents$CustomTable$render,
initialize : CustomComponents$CustomTable$initialize
}
CustomComponents.CustomTable.registerClass(“CustomComponents.CustomTable”,
Sys.Preview.UI.Data.DataControl);
CustomComponents.CustomTable.descriptor =
{
properties: [{name : “dataFieldNames”, type: Array}]
}
if(typeof(Sys)!==’undefined’)
Sys.Application.notifyScriptLoaded();
Listing D-22 shows a page that uses the CustomTable data control. If you run this page, you’ll get the
result shown in Figure D-1 .
Listing D-22: A Page that Uses the CustomTable Data Control
<%@ Page Language=”C#” %>
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“ /><html xmlns=” /><head id=”Head1” runat=”server”>
<title>Untitled Page</title>
<script type=”text/javascript” language=”javascript”>
function onSuccess(result, userContext, methodName)
{
userContext.set_data(result);

}

function onFailure(result, userContext, methodName)
{
var builder = new Sys.StringBuilder();
builder.append(“timedOut: “);
builder.append(result.get_timedOut());
builder.appendLine();
builder.appendLine();
bapp04.indd 1376bapp04.indd 1376 8/20/07 8:59:59 PM8/20/07 8:59:59 PM
Appendix D: Data Control
1377
builder.append(“message: “);
builder.append(result.get_message());
builder.appendLine();
builder.appendLine();
builder.append(“stackTrace: “);
builder.appendLine();
builder.append(result.get_stackTrace());
builder.appendLine();
builder.appendLine();
builder.append(“exceptionType: “);
builder.append(result.get_exceptionType());
builder.appendLine();
builder.appendLine();
builder.append(“statusCode: “);
builder.append(result.get_statusCode());
builder.appendLine();
builder.appendLine();
builder.append(“methodName: “);

builder.append(methodName);

alert(builder.toString());
}

function pageLoad()
{
var properties = [];
properties[“dataFieldNames”] = [‘Title’, ‘AuthorName’, ‘Publisher’];
var customTable = $create(CustomComponents.CustomTable, properties,
null, null, $get(“mydiv”));
MyWebService.GetBooks(onSuccess, onFailure, customTable);
}
</script>
</head>
<body>
<form id=”form1” runat=”server”>
<asp:ScriptManager runat=”server” ID=”ScriptManager1”>
<Services>
<asp:ServiceReference InlineScript=”true” Path=”WebService.asmx” />
</Services>
<Scripts>
<asp:ScriptReference Assembly=”Microsoft.Web.Preview”
Name=”PreviewScript.js” />
<asp:ScriptReference Path=”CustomTable.js” />
</Scripts>
</asp:ScriptManager>
<div id=”myDiv”>
</div>
</form>

</body>
</html>
bapp04.indd 1377bapp04.indd 1377 8/20/07 9:00:00 PM8/20/07 9:00:00 PM
Appendix D: Data Control
1378
Listing D-22 retrieves the data from the Web service shown in Listing D-23 . This code listing presents the
content of the
WebService.asmx file that contains the implementation of our Web service. As you can
see, this Web service exposes a method named
GetBooks that retrieves the data from the underlying
database and populates an array of
Book objects with them.
Note that the underlying database is a database named
BooksDB that contains two tables named Books
and
Authors . The following table describes the Books database table:
Figure D-1
bapp04.indd 1378bapp04.indd 1378 8/20/07 9:00:00 PM8/20/07 9:00:00 PM
Appendix D: Data Control
1379
The following table describes the Authors database table:
Column Name Data Type
BookID int
Title nvarchar(50)

Publisher nvarchar(50)

Price decimal(18, 0)
AuthorID int
Column Name Data Type

AuthorID int

AuthorName nvarchar(50)
Make sure you add the following fragment to the Web.config file of the application that contains the
Web service:
<configuration>
<connectionStrings>
<add connectionString=”Data Source=ServerName;Initial Catalog=BooksDB;
Integrated Security=SSPI” name=”MyConnectionString” />
</connectionStrings>
</configuration>
Listing D-23: The Content of the WebService.asmx File that Contains the
Implementation of the Web Service Used by the Page Shown in Listing D-22
<%@ WebService Language=”C#” Class=”MyWebService” %>
using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Web.Script.Services;
using System.Web.Script.Serialization;
using System.Collections;
public class Book
{
private string title;
public string Title
{
get { return this.title; }

set { this.title = value; }
}
(continued)
bapp04.indd 1379bapp04.indd 1379 8/20/07 9:00:00 PM8/20/07 9:00:00 PM
Appendix D: Data Control
1380
Listing D-23 (continued)
private string authorName;
public string AuthorName
{
get { return this.authorName; }
set { this.authorName = value; }
}
private string publisher;
public string Publisher
{
get { return this.publisher; }
set { this.publisher = value; }
}
private decimal price;
public decimal Price
{
get { return this.price; }
set { this.price = value; }
}
}
[WebService(Namespace = “ />[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class MyWebService : System.Web.Services.WebService
{

[WebMethod]
public Book[] GetBooks()
{
ConnectionStringSettings settings =
ConfigurationManager.ConnectionStrings[“MyConnectionString”];
string connectionString = settings.ConnectionString;
string commandText = “Select Title, AuthorName, Publisher, Price “ +
“From Books Inner Join Authors “ +
“On Books.AuthorID = Authors.AuthorID “;
DataTable dt = new DataTable();
SqlDataAdapter ad = new SqlDataAdapter(commandText, connectionString);
ad.Fill(dt);
Book[] books = new Book[dt.Rows.Count];
for (int i=0; i<dt.Rows.Count; i++)
{
books[i] = new Book();
books[i].Title = (string)dt.Rows[i][“Title”];
books[i].AuthorName = (string)dt.Rows[i][“AuthorName”];
books[i].Publisher = (string)dt.Rows[i][“Publisher”];
books[i].Price = (decimal)dt.Rows[i][“Price”];
}
return books;
}
}
bapp04.indd 1380bapp04.indd 1380 8/20/07 9:00:01 PM8/20/07 9:00:01 PM
Templated Controls


Appendix D implemented a client control named CustomTable that uses predetermined HTML
content to render its user interface to display the specified data records. This client control is an

example of one that hard-codes its HTML content. A templated client control is a control that
enables page developers to customize the HTML content that makes up its user interface. In other
words, a templated client control does not hard-code its HTML. Every ASP.NET AJAX templated
client control exposes a property of type
ITemplate . As Listing E-1 shows, the ITemplate inter-
face exposes the methods discussed in the following table:
Method Description
createInstance Every subclass of the ITemplate interface must implement
this method. The subclass must contain the appropriate logic to
create the DOM subtree that the template represents and to
attach this subtree to the document object.
initialize Every subclass of the ITemplate interface must implement
this method to initialize itself.
disposeInstance A static method that must be used as is. This method simply
disposes the current
MarkupContext. Recall that the current
MarkupContext maintains two important pieces of informa-
tion: the DOM subtree that the template represents and its
associated ASP.NET AJAX components.
(continued)
Listing E-1: The ITemplate Interface
}
Sys.Preview.UI.ITemplate = function Sys$Preview$UI$ITemplate()
{
throw Error.notImplemented();
}
function Sys$Preview$UI$ITemplate$createInstance()
{
throw Error.notImplemented();
}

bapp05.indd 1381bapp05.indd 1381 8/20/07 9:00:27 PM8/20/07 9:00:27 PM
Appendix E: Templated Controls
1382
Listing E-1 (continued)
function Sys$Preview$UI$ITemplate$initialize()
{
throw Error.notImplemented();
}
Sys.Preview.UI.ITemplate.prototype =
{
createInstance: Sys$Preview$UI$ITemplate$createInstance,
initialize: Sys$Preview$UI$ITemplate$initialize
}
Sys.Preview.UI.ITemplate.registerInterface(‘Sys.Preview.UI.ITemplate’);
Sys.Preview.UI.ITemplate.disposeInstance =
function Sys$Preview$UI$ITemplate$disposeInstance(container)
{
if (container.markupContext)
{
container.markupContext.dispose();
container.markupContext = null;
}
TemplateInstance
Property Description
instanceElement References the root DOM element of the subtree of DOM elements
represented by the template and its associated
MarkupContext

callbackResult Normally references a DOM element with a specified id HTML
attribute value

A subclass of the ITemplate interface normally instantiates and initializes an instance of the

TemplateInstance class inside the createInstance method, and returns this instance as
the return value of the
createInstance method.
Listing E-2: The TemplateInstance Type
Sys.Preview.UI.TemplateInstance = function Sys$Preview$UI$TemplateInstance()
{
this.instanceElement = null;
this.callbackResult = null;
}
bapp05.indd 1382bapp05.indd 1382 8/20/07 9:00:27 PM8/20/07 9:00:27 PM
Appendix E: Templated Controls
1383
Template
The ASP.NET AJAX client-side framework comes with an implementation of the ITemplate interface
named
Template , as shown in Listing E-3 , which is used in ASP.NET AJAX templated controls such as

ListView . I’ll discuss the members of this class in the following sections.
Listing E-3: The Template Type
Sys.Preview.UI.Template =
function Sys$Preview$UI$Template(layoutElement, scriptNode, parentMarkupContext)
{
Sys.Preview.UI.Template.initializeBase(this);
this._layoutElement = layoutElement;
this._scriptNode = scriptNode;
this._parentMarkupContext = parentMarkupContext;
}
Sys.Preview.UI.Template.prototype =

{
createInstance: Sys$Preview$UI$Template$createInstance,
dispose: Sys$Preview$UI$Template$dispose,
initialize: Sys$Preview$UI$Template$initialize
}
Sys.Preview.UI.Template.registerClass(‘Sys.Preview.UI.Template’, null,
Sys.Preview.UI.ITemplate, Sys.IDisposable);
Constructor
The constructor of the Template class takes three parameters, as shown in the following table:
Parameter Description
layoutElement References the DOM element, such as a <div> HTML element,
that represents the template on the current page. Every
ASP.NET AJAX template must be associated with an HTML
element. This HTML element is known as a layout element.

scriptNode References the xml-script <template> element that represents
the template in the xml-script.

parentMarkupContext References the parent MarkupContext of the local

MarkupContext that the template uses to represent the
subtree of nodes associated with the template. The parent

MarkupContext is normally the global MarkupContext .
Recall that the global
MarkupContext represents the current

document object.
bapp05.indd 1383bapp05.indd 1383 8/20/07 9:00:28 PM8/20/07 9:00:28 PM
Appendix E: Templated Controls

1384
parseFromMarkup
Every ASP.NET AJAX component either exposes a parseFromMarkup static method or inherits this static
method from its parent component through the process discussed in Appendix A . When the xml-script
parser is parsing an xml-script node that represents an ASP.NET AJAX client class of type
Component , it
first accesses a reference to the type and then invokes the
parseFromMarkup method of the type, pass-
ing in three parameters to have the type parse the child xml-script nodes of the xml-script node that
represents the type. The following table presents these three parameters:
Parameter Description
Type References the ASP.NET AJAX type associated with the xml-script
node being parsed, which is the
<template> xml-script node in
this case.

Node References the xml-script node being parsed.

markupContext References the current MarkupContext . (Recall that the current

MarkupContext maintains two important pieces of information:
a DOM subtree and its associated ASP.NET AJAX components.)
As you can see from Listing E-4 , the
parseFromMarkup static method of the Template class first
calls the
getNamedItem method on the attributes collection property of the node that references the

<template> xml-script element to return a reference to the attribute node named layoutElement :
var layoutElementAttribute = node.attributes.getNamedItem(‘layoutElement’);
Next, it calls the nodeValue property on the attribute node to return the value of the attribute node. This

value is a string that contains the value of the
id HTML attribute of the HTML element, such as a <div>
element, that represents the template:
var layoutElementID = layoutElementAttribute.nodeValue;
Next, it calls the findElement instance method on the current MarkupContext to return a reference to
the HTML DOM element in the subtree of nodes represented by the current MarkupContext . Keep in
mind that this subtree of nodes is not part of the document object. As a result, you cannot call the

getElementById method on the document object to return a reference to this DOM element. The

document object is part of the global MarkupContext , not the local MarkupContext , which is local to
the current template:
var layoutElement = markupContext.findElement(layoutElementID);
Finally, it instantiates a Template object, passing in three parameters. The first parameter references the
HTML DOM element returned by the call into the
findElement method. This DOM element is the root
node of the subtree that the current
MarkupContext represents. The second parameter references the
xml-script
<template> node that represents the template in the xml-script XML document. The third
parameter references the current
MarkupContext .
bapp05.indd 1384bapp05.indd 1384 8/20/07 9:00:28 PM8/20/07 9:00:28 PM
Appendix E: Templated Controls
1385
Listing E-4: The parseFromMarkup Static Method of the Template Class
Sys.Preview.UI.Template.parseFromMarkup =
function Sys$Preview$UI$Template$parseFromMarkup(type, node, markupContext)
{
var layoutElementAttribute = node.attributes.getNamedItem(‘layoutElement’);

var layoutElementID = layoutElementAttribute.nodeValue;
var layoutElement = markupContext.findElement(layoutElementID);
return new Sys.Preview.UI.Template(layoutElement, node, markupContext);
}
createInstance
The Template client class, like any other template class, implements the createInstanc e method of
the
ITemplate interface. This method takes the parameters shown in the following table:
Parameter Description
containerElement References the DOM element on the current page
that will contain the subtree of DOM elements gen-
erated by the call into the
createInstance
method. The
createInstance method basically
parses the specified
<template> node in xml-
script to extract the information that it needs to
generate this subtree of DOM nodes.

dataContext References the current data context, which is nor-
mally the current data record in the underlying
data record collection.

instanceElementCreatedCallback References a JavaScript function or delegate that
will be invoked right after the parsing of the child
nodes of the
<template> node that represents the
template in the xml-script document.


callbackContext References contextual information that will be
passed into the JavaScript function or delegate
as is.
As Listing E-5 shows, the
Template class’s implementation of the createInstance method performs
the following tasks. First, it instantiates an instance of the
TemplateInstance class:
var result = new Sys.Preview.UI.TemplateInstance();
Next, it invokes the cloneNode method on the DOM element that represents the template on the current
page. Note that the
createInstance method passes true to the cloneNode method to instruct it to
clone the descendants of this DOM element as well. In other words, the return value of the
cloneNode is
bapp05.indd 1385bapp05.indd 1385 8/20/07 9:00:28 PM8/20/07 9:00:28 PM
Appendix E: Templated Controls
1386
a subtree of DOM nodes, in which the root is the clone of the DOM element that represents the template
on the current page. The
createInstance method then stores this subtree in the instanceElement
property of the newly instantiated
TemplateInstance object:
result.instanceElement = this._layoutElement.cloneNode(true);
Then, it invokes the createDocumentFragment method on the document object to create a new docu-
ment fragment:
var documentFragment = document.createDocumentFragment();
Next, it appends the cloned sub tree of DOM nodes to this document fragment:
documentFragment.appendChild(result.instanceElement);
Then the createInstance method invokes the createLocalContext static method on the

MarkupContext class to create a new local MarkupContext to represent the preceding document frag-

ment. Note that the
createInstance method passes three parameters into the createLocalContext
method. The first parameter references the new document fragment, which contains the preceding
cloned subtree. The second parameter references the current
MarkupContext , which is normally the
global
MarkupContext . While the global MarkupContext represents the document object, the local
markup context represents this document fragment. (Keep in mind that this document fragment, which
contains the cloned subtree, is not part of the document object. In other words, you cannot invoke the

getElementById method on the document object to access the DOM elements in this closed subtree.)
The third parameter is the reference to the current data context. The data context is normally the current
data record in the underlying data record collection:
var markupContext =
Sys.Preview.MarkupContext.createLocalContext(documentFragment,
this._parentMarkupContext, dataContext);
Next, the createInstance method invokes the open method on the newly created MarkupContext
object. Recall that the
open method simply instantiates the _pendingReferences collection of the

MarkupContext object.
markupContext.open();
Then it invokes the parseNodes static method on the MarkupParser class to parse the child nodes of
the
<template> node that represents the template in xml-script. Note that the createInstance method
passes two parameters into the
parseNodes method. The first is an array that contains the references to
all the child nodes of the
<template> node that represents the template in xml-script. (These child
nodes are normally the ASP.NET AJAX components that the page developer declares between the

opening and closing of the template element.) The second parameter references the newly instantiated
local
MarkupContext . This is the MarkupContext that represents a cloned subtree of nodes:
Sys.Preview.MarkupParser.parseNodes(this._scriptNode.childNodes, markupContext);
The caller of the createInstance method can pass a reference to a JavaScript function or delegate that
represents a JavaScript function, and use this as the third parameter of the
createInstance method.
The
createInstance method invokes this JavaScript function or delegate at this point and passes three
bapp05.indd 1386bapp05.indd 1386 8/20/07 9:00:29 PM8/20/07 9:00:29 PM
Appendix E: Templated Controls
1387
parameters into it. The first parameter references the cloned subtree of nodes. The second parameter
references the newly created
MarkupContext . The third parameter references the JavaScript object that
the caller of the
createInstance method has passed into the method as its last parameter (if any). As
you can see, the
createInstance method doesn’t do anything with its last parameter. It simply passes
it back into its caller through the JavaScript function, or the delegate that the caller passed into the

createInstance method as its third argument. It is the responsibility of this JavaScript function or
delegate to use the parameters passed into it to run the necessary custom code and return the result to
the
createInstance method. The createInstance method simply stores the returned value of this
JavaScript function or delegate in the
callbackResult property of the TemplateInstance object. The
caller of the
createInstance method can then access this return value via the callbackResult prop-
erty of this object.

if (instanceElementCreatedCallback)
result.callbackResult = instanceElementCreatedCallback(result.instanceElement,
markupContext, callbackContext);
Next, the createInstance method stores the newly created markupContext in the markupContext
property of the
instanceElement property of the TemplateInstance object.
result.instanceElement.markupContext = markupContext;
Then the createInstance method appends the cloned subtree of nodes as the child element of the
DOM element passed into the method as its first parameter:
containerElement.appendChild(result.instanceElement);
Next, the createInstance method invokes the close method on the newly created MarkupContext .
Recall that this method resolves the cross-references among the ASP.NET AJAX objects that represent the
parsed nodes:
markupContext.close();
Finally, the createInstance method returns the TemplateInstance object to its caller:
return result;
Listing E-5: The createInstance Method
function Sys$Preview$UI$Template$createInstance(containerElement, dataContext,
instanceElementCreatedCallback,
callbackContext)
{
var result = new Sys.Preview.UI.TemplateInstance();
result.instanceElement = this._layoutElement.cloneNode(true);
var documentFragment = document.createDocumentFragment();
documentFragment.appendChild(result.instanceElement);
var markupContext =
Sys.Preview.MarkupContext.createLocalContext(documentFragment,
this._parentMarkupContext, dataContext);
markupContext.open();
Sys.Preview.MarkupParser.parseNodes(this._scriptNode.childNodes, markupContext);

(continued)
bapp05.indd 1387bapp05.indd 1387 8/20/07 9:00:29 PM8/20/07 9:00:29 PM
Appendix E: Templated Controls
1388
Listing E-5 (continued)
if (instanceElementCreatedCallback)
result.callbackResult = instanceElementCreatedCallback(result.instanceElement,
markupContext, callbackContext);
result.instanceElement.markupContext = markupContext;
containerElement.appendChild(result.instanceElement);
markupContext.close();
return result;
}
Developing a Custom Template
Listing E-6 presents the content of a JavaScript file named TemplateField.js that contains the imple-
mentation of a custom template named
TemplateField . (You’ll see an application of this custom
template later in this appendix.) As you can see, the
TemplateField inherits from the Template class
and extends its functionality to add support for a new property named
headerText :
CustomComponents.TemplateField.registerClass(“CustomComponents.TemplateField”,
Sys.Preview.UI.Template);
I’ll walk you through the implementation of the members of this template in the following sections.
Listing E-6: The Content of the TemplateField.js JavaScript File that Contains the
Implementation of the TemplateField Custom Template
Type.registerNamespace(“CustomComponents”);
CustomComponents.TemplateField =
function CustomComponents$TemplateField(layoutElement, scriptNode,
parentMarkupContext, headerText)

{
CustomComponents.TemplateField.initializeBase(this,
[layoutElement, scriptNode, parentMarkupContext]);
this._headerText = headerText;
}
function CustomComponents$TemplateField$get_headerText()
{
return this._headerText;
}
CustomComponents.TemplateField.prototype =
{
get_headerText : CustomComponents$TemplateField$get_headerText
}
CustomComponents.TemplateField.registerClass(“CustomComponents.TemplateField”,
Sys.Preview.UI.Template);
CustomComponents.TemplateField.parseFromMarkup =
function Sys$Preview$UI$Template$parseFromMarkup(type, node, markupContext)
{
var layoutElementAttribute = node.attributes.getNamedItem(‘layoutElement’);
Sys.Debug.assert(!!(layoutElementAttribute &&
layoutElementAttribute.nodeValue.length),
‘Missing layoutElement attribute on template definition’);
bapp05.indd 1388bapp05.indd 1388 8/20/07 9:00:29 PM8/20/07 9:00:29 PM
Appendix E: Templated Controls
1389
var layoutElementID = layoutElementAttribute.nodeValue;
var layoutElement = markupContext.findElement(layoutElementID);
Sys.Debug.assert(!!layoutElement,
String.format(‘Could not find the HTML element with ID “{0}”
associated with the template’,

layoutElementID));
var headerTextAttribute = node.attributes.getNamedItem(‘headerText’);
var headerText = headerTextAttribute.nodeValue;
return new CustomComponents.TemplateField(layoutElement, node,
markupContext, headerText);
}
if(typeof(Sys)!==’undefined’)
Sys.Application.notifyScriptLoaded();
Constructor
As Listing E-7 shows, the constructor of the TemplateField custom template takes a fourth parameter,
in addition to the parameters that the
Template constructor takes. This fourth parameter is used to
set the
headerText property of this custom template:
this._headerText = headerText;
Note that the constructor of the TemplateField custom template passes its first three parameters to
the constructor of the
Template class:
CustomComponents.TemplateField.initializeBase(this,
[layoutElement, scriptNode, parentMarkupContext]);
Listing E-7: The Constructor of the TemplateField Custom Template
CustomComponents.TemplateField =
function CustomComponents$TemplateField(layoutElement, scriptNode,
parentMarkupContext, headerText)
{
CustomComponents.TemplateField.initializeBase(this,
[layoutElement, scriptNode, parentMarkupContext]);
this._headerText = headerText;
}
headerText

The TemplateField custom template extends the functionality of the Template base class to add
support for a read-only string property named
headerText . You can set this property only through the
constructor of the
TemplateField custom template. Therefore, this custom template does not expose a
setter method for setting this property. Listing E-8 presents the implementation of the getter method for
this property.
bapp05.indd 1389bapp05.indd 1389 8/20/07 9:00:30 PM8/20/07 9:00:30 PM
Appendix E: Templated Controls
1390
Listing E-8: The Getter Method for the HeaderText Property
function CustomComponents$TemplateField$get_headerText()
{
return this._headerText;
}
parseFromMarkup
Every time you implement a custom template that derives from the Template class, you must also
implement a method for your custom template that meets the following criteria:
❑ It must be named parseFromMarkup .
❑ It must be static — that is, it must be defined on your custom template, not its prototype
property.
❑ This method must take the following three parameters:
❑ type : This parameter references the Type object that describes the ASP.NET AJAX type
that represents the DOM node referenced by the second parameter.
❑ node : This parameter references the DOM node that represents your custom template in
xml-script.
❑ markupContext : This parameter references the current MarkupContext , which is
normally the global
MarkupContext .
❑ This method must instantiate an instance of your custom template and return the instance to

its caller.
Listing E-9 presents the
TemplateField custom template’s implementation of the parseFromMarkup
method. This method begins by invoking the
getNamedItem method on the attributes collection
property of the DOM node that represents your custom template in xml-script, in order to return a refer-
ence to the DOM node that represents the
layoutElement attribute on your custom template:
var layoutElementAttribute = node.attributes.getNamedItem(‘layoutElement’);
Next, it invokes the nodeValue property on the DOM node that represents the layoutElement
attribute, to access the value of this attribute:
var layoutElementID = layoutElementAttribute.nodeValue;
Then it invokes the findElement method on the current MarkupContext to return a reference to the
associated DOM element of the
TemplateField custom template:
var layoutElement = markupContext.findElement(layoutElementID);
Note that parseFromMarkup raises an exception if the current page does not contain the specified
DOM element.
bapp05.indd 1390bapp05.indd 1390 8/20/07 9:00:30 PM8/20/07 9:00:30 PM
Appendix E: Templated Controls
1391
Next, the parseFromMarkup method invokes the getNamedItem method on the attributes collection
property of the DOM node that represents the
TemplateField in xml-script, in order to return a refer-
ence to the DOM node that represents the
headerText attribute:
var headerTextAttribute = node.attributes.getNamedItem(‘headerText’);
Then it invokes the nodeValue property on the attribute node to access the value of this attribute:
var headerText = headerTextAttribute.nodeValue;
Next, it instantiates and returns an instance of the TemplateField , passing in the reference to the

associated DOM element of the template, the reference to the DOM element that represents the template
in xml-script, the reference to the current
MarkupContext , and the header text.
return new CustomComponents.TemplateField(layoutElement, node,
markupContext, headerText);
Listing E-9: The parseFromMarkup Method
CustomComponents.TemplateField.parseFromMarkup =
function Sys$Preview$UI$Template$parseFromMarkup(type, node, markupContext)
{
var layoutElementAttribute = node.attributes.getNamedItem(‘layoutElement’);
Sys.Debug.assert(!!(layoutElementAttribute &&
layoutElementAttribute.nodeValue.length),
‘Missing layoutElement attribute on template definition’);
var layoutElementID = layoutElementAttribute.nodeValue;

var layoutElement = markupContext.findElement(layoutElementID);
Sys.Debug.assert(!!layoutElement,
String.format(‘Could not find the HTML element with ID “{0}”
associated with the template’,
layoutElementID));
var headerTextAttribute = node.attributes.getNamedItem(‘headerText’);
var headerText = headerTextAttribute.nodeValue;
return new CustomComponents.TemplateField(layoutElement, node,
markupContext, headerText);
}
Developing a Custom Templated Data Control
Recall that we developed a custom data control named CustomTable in Appendix D . The main problem
with this data control is that its user interface is not customizable. The great thing about templates is that
they enable page developers to customize the HTML content of their associated client controls. In this
section, I’ll present and discuss the implementation of a new version of the

CustomTable data control
that enables page developers to declare instances of the
TemplateField custom template in xml-script
to customize the HTML content of the
CustomTable client control and the appearance of the control.
Listing E-10 presents the content of a JavaScript file named
CustomTable.js that contains the
bapp05.indd 1391bapp05.indd 1391 8/20/07 9:00:30 PM8/20/07 9:00:30 PM

×