If so, it invokes the add method on the data collection to add an empty record to the collection: if Sys.Preview.Data.IData.isImplementedBythis._data this._data.add{}; If the bound dat
Trang 1Appendix 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, {});
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)
Trang 2Appendix D: Data Control
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:
Trang 3Appendix 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);
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);
}}
Trang 4Appendix 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)
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;
Trang 5Appendix 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);
Trang 6Appendix D: Data Control
var oldState = this.prepareChange();
var newIndex = this.get_dataIndex() - 1;
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);
Trang 7Appendix 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’ } ]}
Trang 8Appendix 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
var isArray = true;
var dataSource = this.get_data();
Trang 9Appendix D: Data Control
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)
Trang 10Appendix D: Data Control
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
<script type=”text/javascript” language=”javascript”>
function onSuccess(result, userContext, methodName)
Trang 11Appendix D: Data Control
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”>
Trang 12Appendix 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
Trang 13Appendix 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
<add connectionString=”Data Source=ServerName;Initial Catalog=BooksDB;
Integrated Security=SSPI” name=”MyConnectionString” />
private string title;
public string Title {
get { return this.title; } set { this.title = value; } }
(continued)
Trang 14Appendix 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; }
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++)
Trang 15Templated 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 tion: the DOM subtree that the template represents and its associated ASP.NET AJAX components
informa-(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();
}
Trang 16Appendix E: Templated Controls
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;
}
Trang 17Appendix 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){
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
MarkupContext is normally the global MarkupContext Recall that the global MarkupContext represents the current
document object
Trang 18Appendix 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
node being parsed, which is the <template> xml-script node in this case
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
Trang 19Appendix 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);
containerElement References the DOM element on the current page
that will contain the subtree of DOM elements 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
Trang 20Appendix 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 fragdocu-ment:
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
Trang 21Appendix 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
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)
Trang 22Appendix E: Templated Controls
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
Trang 23Appendix 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
Trang 24Appendix E: Templated Controls
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
Trang 25Appendix 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
Trang 26Appendix E: Templated Controls
1392
implementation of the CustomTable templated data control This is a data control because it derives
from the DataControl base class:
CustomComponents.CustomTable.registerClass(“CustomComponents.CustomTable”,
Sys.Preview.UI.Data.DataControl);
I’ll discuss the members of this custom templated data control in the following sections
Listing E-10: The Content of the CustomTable.js JavaScript File that Contains the
Implementation of the CustomTable Templated Data Control
Trang 27Appendix E: Templated Controls
1393
function CustomComponents$CustomTable$get_alternatingItemCssClass(){
return this._alternatingItemCssClass;
}function CustomComponents$CustomTable$set_alternatingItemCssClass(value){
this._alternatingItemCssClass = value;
}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!’);
var table = document.createElement(“table”);
if (this._cssClass) table.className = this._cssClass;
var length = isArray ? dataSource.length : dataSource.get_length();
headerRow = table.insertRow(index);
if (this._headerCssClass) headerRow.className = this._headerCssClass;
this._toggleCssClassHandler = Function.createDelegate(this, this._toggleCssClass);
for (var i=0; i<length; i++) {
dataItem = isArray? dataSource[i] : dataSource.getItem(i);
(continued)
Trang 28Appendix E: Templated Controls
1394
Listing E-10 (continued)
if (this._fields)
{
dataRow = table.insertRow(index + i);
$addHandler(dataRow, “mouseover”, this._toggleCssClassHandler);
$addHandler(dataRow, “mouseout”, this._toggleCssClassHandler);
if ((i % 2 === 1) && (this._alternatingItemCssClass))
Trang 29Appendix E: Templated Controls
1395
CustomComponents.CustomTable.descriptor ={
properties: [{name: “fields”, type: Array, readOnly: true}, {name: ‘cssClass’, type: String },
{name: ‘hoverCssClass’, type: String }, {name: ‘headerCssClass’, type: String }, {name: ‘itemCssClass’, type: String }, {name: ‘alternatingItemCssClass’, type: String } ]
}if(typeof(Sys)!==’undefined’) Sys.Application.notifyScriptLoaded();
fields
The CustomTable template data control exposes an array property named fields , as shown in Listing E-11 As you’ll see later, page developers declaratively add instances of the TemplateField template to this array in xml-script They do this in order to specify which data fields of each data record
of the data collection bound to the CustomTable must be displayed, and what header text must be used for each data column
Listing E-11: The Getter Method for the Fields Property
function CustomComponents$CustomTable$get_fields(){
cssClass Specifies the name of the CSS class for the containing
<table> element of the CustomTable control
headerCssClass Specifies the name of the CSS class for the header row
hoverCssClass Specifies the name of the CSS class for the data row
when the mouse hovers over the data row
itemCssClass Specifies the name of the CSS class for the
numbered data rows
alternatingItemCssClass Specifies the name of the CSS class for the
numbered data rows
render
Every data control that inherits from the DataControl base class must implement a method named
render Listing E-12 presents the CustomTable class’s implementation of this method As you can see,
Trang 30Appendix E: Templated Controls
1396
it begins by calling the get_data method to return a reference to the data collection bound to the
CustomTable control The CustomTable control, like any other data control, inherits the get_data
method from the DataControl base class:
var dataSource = this.get_data();
Next, it raises an exception if the bound data collection is not a JavaScript array and does not implement
the IData interface:
if (Sys.Preview.Data.IData.isImplementedBy(dataSource))
isArray = false;
else if (!Array.isInstanceOfType(dataSource))
throw Error.createError(‘Unknown data source type!’);
Then it creates a table DOM element:
var table = document.createElement(“table”);
Next, it assigns the value of the cssClass style property to the className property of the table DOM
element:
if (this._cssClass)
table.className = this._cssClass;
Next, the CustomTable control determines the total number of data records in the data collection bound
to the CustomTable data control:
var length = isArray ? dataSource.length : dataSource.get_length();
Then it inserts a new row into the table DOM element and sets its className property to the value of
the headerCssClass property:
headerRow = table.insertRow(index);
if (this._headerCssClass)
headerRow.className = this._headerCssClass;
index++;
Next, it iterates through the template objects in the fields collection, inserts a cell into the newly added row,
and sets the inner text of this cell to the value of the headerText property of the enumerated template object:
for (var c in this._fields)
Trang 31Appendix E: Templated Controls
1397
Next, the CustomTable control iterates through the data records in the data collection bound to the
CustomTable control and performs the following tasks for each enumerated data record
dataItem = isArray? dataSource[i] : dataSource.getItem(i);
First, it inserts a new row into the table DOM element and registers the _toggleCssClassHandler delegate as the event handler for the mouseover and mouseout events of the newly added row
dataRow = table.insertRow(index + i);
$addHandler(dataRow, “mouseover”, this._toggleCssClassHandler);
$addHandler(dataRow, “mouseout”, this._toggleCssClassHandler);
If the row is an even-numbered row, the CustomTable control assigns the value of the
itemCssClass property to the className property of the row Otherwise, it assigns the value of the alternatingItemCssClass property to the className property:
if ((i % 2 === 1) && (this._alternatingItemCssClass)) dataRow.className = this._alternatingItemCssClass;
else if (this._itemCssClass) dataRow.className = this._itemCssClass;
Next, it iterates through the template objects in the fields collection, inserts a new cell for each plate object, and calls the createInstance method of the template object to render the HTML enclosed within the template object into the newly added cell to display the current data record:
for (var c in this._fields) {
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!’);
(continued)
Trang 32Appendix E: Templated Controls
1398
Listing E-12 (continued)
var table = document.createElement(“table”);
dataRow = table.insertRow(index + i);
$addHandler(dataRow, “mouseover”, this._toggleCssClassHandler);
$addHandler(dataRow, “mouseout”, this._toggleCssClassHandler);
if ((i % 2 === 1) && (this._alternatingItemCssClass))
Trang 33Appendix E: Templated Controls
1399
_toggleCssClass
Recall from Listing E-12 that the render method registers the delegate that represents the
_toggleCssClass method as an event handler for the mouseout and mouseover events of the CustomTable control As Listing E-13 shows, the _toggleCssClass method invokes the
toggleCssClass static method on the DomElement to toggle the CSS class name
Listing E-13: The _toggleCssClass Method
function CustomComponents$CustomTable$_toggleCssClass(evt){
Listing E-14: The Descriptor Static Property
CustomComponents.CustomTable.descriptor ={
properties: [{name: “fields”, type: Array, readOnly: true}, {name: ‘cssClass’, type: String },
{name: ‘hoverCssClass’, type: String }, {name: ‘headerCssClass’, type: String }, {name: ‘itemCssClass’, type: String }, {name: ‘alternatingItemCssClass’, type: String } ]
}
Using the TemplateField and CustomTable Templated
Data Controls
Follow these steps to use the TemplateField and CustomTable templated data controls:
1 Create a new Ajax-enabled Web site in Visual Studio 2005
2 Add a new JavaScript file named TemplateField.js to the root directory of this application and add Listing E-6 to this file
3 Add a new JavaScript file named CustomTable.js to the root directory of this application and add Listing E-10 to this file
Trang 34Appendix E: Templated Controls
1400
4 Add a new Web page named CustomTable.aspx to the root directory of this application and
add Listing E-15 to this file
5 Add a new Web service named WebService.asmx to the root directory of this application
and add Listing E-16 to this file
Keep in mind that this example uses the same BooksDB database discussed in Appendix D
Listing E-15: A Page that Uses the TemplateField and CustomTable Templated
<script type=”text/javascript” language=”javascript”>
function onSuccess(result, userContext, methodName)
{
userContext.set_data(result);
}
Trang 35Appendix E: Templated Controls
var customTable = Sys.Application.findComponent(“customTable”);
MyWebService.GetBooks(onSuccess, onFailure, customTable);
} </script>
</head>
<body>
<form id=”form1” runat=”server”>
<asp:ScriptManager runat=”server” ID=”ScriptManager1”>
Trang 36Appendix E: Templated Controls
Trang 37Appendix E: Templated Controls
1403
Listing E-16: The MyWebService Web Service
<%@ WebService Language=”C#” Class=”MyWebService” %>
private string title;
public string Title {
get { return this.title; } set { this.title = value; } }
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 = “http://tempuri.org/”)]
string connectionString = settings.ConnectionString;
string commandText = “Select Title, AuthorName, Publisher, Price “ + “From Books Inner Join Authors “ +
“On Books.AuthorID = Authors.AuthorID “;
(continued)
Trang 38Appendix E: Templated Controls
1404
Listing E-16 (continued)
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++)
Using a custom ASP.NET AJAX class in xml-script requires you to define on the <page> element an
XML namespace prefix that maps to the ASP.NET AJAX namespace containing the custom ASP.NET
AJAX class In this case, Listing E-15 defines an XML namespace prefix named custom that maps to the
ASP.NET AJAX CustomComponents namespace, because this is the namespace that contains the
You must qualify with this XML namespace prefix the names of the XML elements that represent
instances of your custom client class in xml-script In this case, Listing E-15 qualifies the names of the
TemplateField and CustomTable XML elements with the prefix custom , as shown in the highlighted
portions of the following excerpt from Listing E-15 :
Trang 39Appendix E: Templated Controls
Trang 40Appendix E: Templated Controls
1406
Also note that each TemplateField contains a <label> element that represents a <span> element,
which is the subelement of the associated HTML element of the TemplateField For example, in the
preceding two excerpts, the <label> subelement of the <custom:TemplateField> represents the
<span> subelement of the <div> element associated with this TemplateField
Note that each <label> element contains a <bindings> subelement, which in turn contains a
<binding> subelement This <binding> subelement binds the text property of its associated <label>
element to the specified data field of the current data record of the data collection bound to the
CustomTable data control For example, in the case of the following excerpt from Listing E-15 , the
<binding> subelement binds the text property of the <label> element that has the id attribute value
of title to the Title data field of the current data record:
<custom:TemplateField layoutElement=“field1” headerText=“Title“>