1. Trang chủ
  2. » Công Nghệ Thông Tin

ASP.NET AJAX Programmer’s Reference - Chapter 25 potx

250 192 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 250
Dung lượng 2,98 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

As you’ll see later, the _getTagType name determines and returns the Type object that represents the type of the ASP.NET AJAX class associated with the xml-script node being parsed: va

Trang 1

XML Script The xml-script is an XML document enclosed within the opening and closing tags of an HTML

script element whose type attribute is set to text/xml-script The xml-script, like any other XML

document, has a single element known as the document element that encapsulates the rest of the

xml-script In other words, the document element is the outermost or containing element of an XML ment The document element in the case of the xml-script XML document is an element named page that belongs to an XML namespace named http://schemas.microsoft.com/xml-script/2005 The page document element contains a child element named components , which belongs to the same XML namespace as the page element The descendants of the components element are the declarative representations of ASP.NET AJAX client-side objects

The ASP.NET AJAX client-side framework comes with an extensible JavaScript library that parses the descendants of the components element, instantiates and initializes the ASP.NET AJAX client-side objects that these descendant elements represent, and adds these ASP.NET AJAX client-side objects to the current ASP.NET AJAX application As you’ll see later, an ASP.NET AJAX client class named MarkupContext plays an important role in the logic that parses the xml-script document

Therefore, I’ll begin by discussing this class

MarkupContext class comes with methods that you can use to search this document for

a particular DOM element

❑ An object collection that contains a list of ASP.NET AJAX components As you’ll see later, the MarkupContext class comes with methods that you can use to search this collection for a particular ASP.NET AJAX component, or to add a new component to this collection

Trang 2

Appendix A: XML Script

1276

In general, there’re two types of markup contexts:

❑ Global markup context: The XML document of the global markup context contains all the DOM

elements in the current document, including those in the xml-script In other words, the

document object is the XML document of the global markup context The object collection of the

global markup context contains all the ASP.NET AJAX components in the current page

❑ Local markup context: The XML document of a local markup context contains a subtree of DOM

elements that do not belong to the document object In other words, you cannot call the

getElementById method on the document object to access the DOM elements in this subtree

That is why MarkupContext comes with methods that enable you to access the DOM elements

in the XML document of a local markup context Local markup contexts are normally used in

ASP.NET templates As you’ll see later, the createInstance method of the ASP.NET AJAX

Template class instantiates and initializes a local MarkupContext

The following code listing presents the constructor of the MarkupContext class:

This constructor takes the four parameters shown in the following table:

Note that the constructor of the MarkupContext instantiates an internal collection named _objects ,

in which it will maintain the list of its associated ASP.NET AJAX client-side components The

MarkupContext class exposes a method named addComponent that adds a new ASP.NET AJAX

component to the _objects collection

Parameter Description

MarkupContext

global This Boolean parameter specifies whether the MarkupContext is global

parentContext This parameter references the parent markup context of the current

markup context The parent markup context of the global markup context is null

dataContext This parameter references the current data context (I’ll cover data

contexts later.)

Trang 3

Appendix A: XML Script

1277

As you’ll see later, every time the xml-script parser parses a node in xml-script and instantiates the ASP.NET AJAX component that the node represents, it calls the addComponent method on the current

MarkupContext class to add this component to its _objects collection As you can see from the following code listing, each component is stored in this collection under its id (Recall that every ASP.NET AJAX component is uniquely identified by its id ) Note that addComponent takes a second argument of type Boolean that specifies whether the specified component must also be added to the

Application object that represents the current application Also note that the addComponent method does not add the specified component to the current Application if the current markup context is a local markup context

function Sys$Preview$MarkupContext$addComponent(component, noRegisterWithApp){

var id = component.get_id();

if(id) this._addComponentByID(id, component, noRegisterWithApp);

}function Sys$Preview$MarkupContext$_addComponentByID(id, object, noRegisterWithApp){

If the second parameter is specified, the findComponent method simply calls the findComponent method on the Application object that represents the current application, to look for the component among the child components of the specified parent

function Sys$Preview$MarkupContext$findComponent(id, parent){

if(parent) return Sys.Application.findComponent(id, parent);

else { var object = this._objects[id];

if (!object) {

parent = this._parentContext || Sys.Application;

object = parent.findComponent(id);

} return object;

}}

Trang 4

Appendix A: XML Script

1278

The MarkupContext class also exposes a method named getComponents that returns all the

components in its _objects collection of the current markup context

function Sys$Preview$MarkupContext$getComponents()

{

var res = [];

var objects = this._objects;

for (var id in objects)

res[res.length] = objects[id];

return res;

}

Another interesting method of the MarkupContext class is one named findElement As the name

implies, this method returns a reference to the DOM element with the specified id HTML attribute

value It’s interesting to see how the search for this DOM element is performed The findElement

method first calls the getElementById method to search for the DOM element in the _document XML

document fragment Recall that the _document field of MarkupContext contains the XML document

fragment associated with MarkupContext In other words, MarkupContext assumes that the DOM

element you’re looking for is in this XML document fragment If it can’t find the element there, it

searches the XML document fragment of its parent MarkupContext for the DOM element

function Sys$Preview$MarkupContext$findElement(id)

{

if (this._opened)

{

var element = Sys.UI.DomElement.getElementById(id, this._document);

if (!element && this._parentContext)

element = Sys.UI.DomElement.getElementById(id, this._parentContext);

return element;

}

return null;

}

Note that the constructor of the MarkupContext class instantiates another internal collection named

_ pendingReferences As we discussed earlier, when the xml-script parser parses a node in the

xml-script into its associated ASP.NET AJAX component, it calls the addComponent method on the

current MarkupContext to add the component to its _objects collection If this component contains a

property that references another ASP.NET AJAX component, the parser also invokes the addReference

method on the current MarkupContext to add a reference to the _ pendingReferences collection for

this property This reference is a JavaScript object literal that has three properties: o , which refers to the

ASP.NET AJAX component that owns the property that references another ASP.NET AJAX component,

p , which refers to the propertyInfo object that represents this property, and r , which refers to the id of

the referenced ASP.NET AJAX component In other words, instead of trying to initialize the property that

references another ASP.NET AJAX component, the parser makes a note of it by adding this JavaScript

object literal to the _ pendingReferences collection of the current MarkupContext so it can

initialize the property when it’s done with parsing all the xml-script nodes associated with the current

MarkupContext This is necessary because the property could be referencing an ASP.NET AJAX

compo-nent associated with an xml-script node that hasn’t yet been parsed To put it differently, the parser

performs all the cross-references when it’s done with parsing all the xml-script nodes associated with the

current markup context

Trang 5

When the xml-script parser is finally done with parsing all the xml-script nodes associated with the current markup context, it invokes the close method of the current MarkupContext to resolve the previously mentioned cross-references, as shown in the following code listing The close method iter-ates through the JavaScript object literals in the _pendingReferences collection and takes the following steps for each enumerated object First, it calls the findComponent method on the MarkupContext , passing in the value of the r property of the enumerated JavaScript object literal Recall that this property contains the id of the component referenced by the property being initialized Also recall that the findComponent method first looks for the referenced component in its own _objects collection If

it can’t find the component there, it calls the findComponent method on its parent MarkupContext to look for the referenced component in the _objects collection of the parent MarkupContext This search finally ends when the referenced component is located in the _objects collection of the first ancestor

MarkupContext of the current MarkupContext

Next, the close method creates the string that contains the name of the setter method of the property being initialized, and uses this string as an index into the ASP.NET AJAX component whose property is being ini-tialized to return a reference to this setter method:

var setter = instance[‘set_’ + propertyInfo.name];

Next, the close method invokes the setter method to set the value of the specified property of the referenced component

function Sys$Preview$MarkupContext$close(){

this._opened = false;

var i;

for (i = 0; i < this._pendingReferences.length; i++) {

var pendingReference = this._pendingReferences[i];

var instance = pendingReference.o;

var propertyInfo = pendingReference.p;

var propertyValue = pendingReference.r;

var object = this.findComponent(propertyValue);

var setter = instance[‘set_’ + propertyInfo.name];

setter.call(instance, object);

} this._pendingReferences = null;

}

Trang 6

MarkupContext Note that the document object is passed into the constructor of the MarkupContext

class to create the global markup context

The MarkupContext class also exposes a static method named createLocalContext that creates a

local MarkupContext Note that the XML document fragment associated with this local markup context

is passed into the constructor of the MarkupContext class Also note that the parent MarkupContext is

passed as the third argument to this constructor

Processing the xml-script XML Document

Listing A-1 presents the script that starts the processing of the xml-script XML document This script is

part of the PreviewScript.js JavaScript file and is loaded and executed automatically As you can see,

this script first invokes the createGlobalContext static method on the MarkupContext class to create

the global MarkupContext Recall that the XML document associated with the global markup context

is the document object Note that the script shown in Listing A-1 stores this global MarkupContext in

the _markupContext private field of the Application object that represents the current ASP.NET AJAX

application

Sys.Application._markupContext = Sys.Preview.MarkupContext.createGlobalContext();

Next, the script shown in Listing A-1 registers a method of the Application object named

initHandler as an event handler for the init event of the Application object:

Sys.Application.add_init(Sys.Application. initHandler);

Trang 7

Appendix A: XML Script

1281

Listing A-1: The Script that Starts the Processing of the xml-script XML Document

if(!Sys.Application._markupContext){

Sys.Application._markupContext = Sys.Preview.MarkupContext.createGlobalContext(); Sys.Application.add_init(Sys.Application. initHandler);

Sys.Application.add_unload(Sys.Application. unloadHandler);

}

When the Application object that represents the current ASP.NET AJAX application finally raises its

init event, the Application object automatically invokes the initHandler method As you can see from Listing A-2 , this method in turn invokes the processDocument static method on an ASP.NET AJAX client class named MarkupParser , passing in the global MarkupContext :

Sys.Preview.MarkupParser.processDocument(Sys.Application._markupContext);

Listing A-2: The initHandler Method

Sys.Application. initHandler = function Sys$Application$ initHandler()

{ Sys.Application.remove_init(Sys.Application. initHandler);

Sys.Preview.MarkupParser.processDocument(Sys.Application._markupContext);

}

processDocument

Listing A-3 presents a simplified version of the implementation of the processDocument method

Listing A-3: The processDocument Method

Sys.Preview.MarkupParser.processDocument = function Sys$Preview$MarkupParser$processDocument(markupContext){

var pageNodes = [];

var scriptElements = document.getElementsByTagName(‘script’);

var xmlScriptElement = null;

for (var e = 0; e < scriptElements.length; e++) {

if (scriptElements[e].type == ‘text/xml-script’) {

xmlScriptElement = scriptElements[e];

break;

} }

(continued)

Trang 8

xmlDocument = new XMLDOM(xmlScriptElement.innerHTML);

var documentElement = xmlDocument.documentElement;

This method first calls the getElementsByTagName method on the document object to return an array

that contains references to all script HTML elements on the current page:

var scriptElements = document.getElementsByTagName(‘script’);

Next, it searches through the script HTML elements in this array for a script HTML element with the

type attribute value of text/xml-script :

for (var e = 0; e < scriptElements.length; e++)

As I mentioned earlier, Listing A-3 presents a simplified version of the ProcessDocument method The

full version of this method supports multiple script HTML elements with a type attribute value of

text/xml-script

Next the processDocument method loads the content of this script HTML element into an XMLDOM

document:

var xmlDocument = new XMLDOM(xmlScriptElement.innerHTML);

Then it references the document element of the xml-script XML document As we discussed earlier, the

document element of the xml-script XML document is an element named page If the document element

Trang 9

Finally, processDocument invokes the processDocumentScripts static method on the MarkupParser class, passing in the global MarkupContext and the document element of the xml-script XML document

— that is, the page element or node:

Sys.Preview.MarkupParser.processDocumentScripts(markupContext, documentElement);

processDocumentScripts

Listing A-4 presents the implementation of the processDocumentScripts method

Listing A-4: The processDocumentScripts Method

Sys.Preview.MarkupParser.processDocumentScripts = function Sys$Preview$MarkupParser$processDocumentScripts(markupContext, pageNode){

markupContext.open();

var componentNodes = [];

var pageChildNodes = pageNode.childNodes;

for (var i = pageChildNodes.length - 1; i > =0; i ) {

var pageChildNode = pageChildNodes[i];

if (pageChildNode.nodeType != =1) continue;

var pageChildNodeName = Sys.Preview.MarkupParser.getNodeName(pageChildNode);

pageChildNodeName = pageChildNodeName.toLowerCase();

if (pageChildNodeName === ‘components’) {

for (var c = 0; c < pageChildNode.childNodes.length; c++) {

var componentNode = pageChildNode.childNodes[c];

if (componentNode.nodeType != =1) continue;

Array.add(componentNodes, componentNode);

} } } Sys.Preview.MarkupParser.parseNodes(componentNodes, markupContext);

markupContext.close();

}

Trang 10

Appendix A: XML Script

1284

This method first invokes the open method on the global MarkupContext Recall that the open method

instantiates the _pendingReferences collection:

markupContext.open();

Next, the processDocumentScripts method searches through the child elements of the page node

for a child element named components It then iterates through the child elements of the components

element and adds each enumerated child element to a local collection named componentNodes

(Keep in mind that each child element of the components element is a declarative representation of an

ASP.NET AJAX client component In other words, each child element of the components element

MarkupParser class, passing in two parameters The first parameter is the array that contains

the references to all the component child nodes of the components element The second parameter

references the global MarkupContext

As you’ll see later, the parseNodes method parses the nodes in its first parameter, determines the

ASP.NET AJAX type associated with each node, instantiates this type, and adds it to the _objects

collection of the MarkupContext object that is passed into the method as its second argument

Trang 11

var processedObject = Sys.Preview.MarkupParser.parseNode(node, markupContext);

As you’ll see later, the parseNode method parses the specified xml-script node into its associated ASP.NET AJAX object, and returns this object The parseNodes method then adds the object to an internal collection:

if (processedObject) Array.add(objects, processedObject);

As you can see, this internal collection basically collects all the ASP.NET AJAX objects associated with the xml-script nodes passed into the parseNodes method as its first argument The parseNodes method then returns this collection to its caller:

if (processedObject) Array.add(objects, processedObject);

Listing A-5: The parseNodes Method

Sys.Preview.MarkupParser.parseNodes = function Sys$Preview$MarkupParser$parseNodes(nodes, markupContext){

var processedObject = Sys.Preview.MarkupParser.parseNode(node, markupContext);

if (processedObject) Array.add(objects, processedObject);

} return objects;

markupContext References the current MarkupContext For example, as you’ll see later,

each <template> element in xml-script is associated with a local markup context, which means that all descendant nodes of this element are parsed within this local markup context

Trang 12

Appendix A: XML Script

1286

parseNode

The parseNode method, shown in Listing A-6 , takes two parameters, as shown in the following table:

The main responsibility of the parseNode method is to parse the specified xml-script node to its

associ-ated ASP.NET AJAX object It begins by invoking the _getTagType static method on the MarkupParser

class, passing in the reference to the xml-script node being parsed As you’ll see later, the _getTagType

name determines and returns the Type object that represents the type of the ASP.NET AJAX class

associated with the xml-script node being parsed:

var tagType = Sys.Preview.MarkupParser._getTagType(node);

Next, the parseNode method checks whether the ASP.NET AJAX class associated with the xml-script

node being parsed contains a static method named parseFromMarkup If so, it uses this method as the

parsing method If not, it walks up the ancestors of this ASP.NET AJAX class until it reaches an ancestor

that supports the parseFromMarkup static method, and assigns this method as the static method of the

ASP.NET AJAX class

var parseMethod = tagType.parseFromMarkup;

Next, it invokes the parseFromMarkup static method, passing in four parameters: the first is null , the

second references the Type object that represents the ASP.NET AJAX class associated with the xml-script

node being parsed, the third references the xml-script node being parsed, and the fourth references the

current MarkupContext

parsedObject = parseMethod.call(null, tagType, node, markupContext);

As you’ll see later, the parseFromMarkup static method of the ASP.NET AJAX class that’s associated

with the xml-script node being parsed instantiates, initializes, and returns an instance of this class

Trang 13

Appendix A: XML Script

1287

Listing A-6: The parseNode Method

Sys.Preview.MarkupParser.parseNode = function Sys$Preview$MarkupParser$parseNode(node, markupContext){

var parsedObject = null;

var tagType = Sys.Preview.MarkupParser._getTagType(node);

if (tagType) {

var parseMethod = tagType.parseFromMarkup;

if (!parseMethod) {

var baseType = tagType.getBaseType();

while (baseType) {

parseMethod = baseType.parseFromMarkup;

if (parseMethod) break;

baseType = baseType.getBaseType();

} tagType.parseFromMarkup = parseMethod;

}

if (parseMethod) parsedObject = parseMethod.call(null, tagType, node, markupContext);

} return parsedObject;

var tagName = Sys.Preview.MarkupParser.getNodeName(node);

It then determines the XML namespace to which the node being parsed belongs:

var namespaceURI = node.namespaceURI ||

Sys.Preview.MarkupParser._defaultNamespaceURI;

Note that the MarkupParser class exposes a static field named _defaultNamespaceURI with the following value:

Sys.Preview.MarkupParser._defaultNamespaceURI = ‘http://schemas.microsoft.com/xml-script/2005’;

Trang 14

Appendix A: XML Script

1288

The MarkupParser class also exposes another static collection field named _cachedNamespaceURILists ,

which caches the ASP.NET AJAX namespaces associated with each XML namespace

Sys.Preview.MarkupParser._cachedNamespaceURILists = {};

The _getTagType method uses the XML namespace of the node being parsed as an index into this cache

to return the list of ASP.NET AJAX namespaces associated with the node:

var nspaceList = Sys.Preview.MarkupParser._cachedNamespaceURILists[namespaceURI];

If the cache does not contain any ASP.NET AJAX namespaces associated with the XML namespace of the

node being parsed, the _getTagType method invokes the _processNamespaceURI static method on

the MarkupParser class to evaluate and return the list of ASP.NET AJAX namespaces associated with

this XML namespace:

nspaceList = Sys.Preview.MarkupParser._processNamespaceURI(namespaceURI);

The _getTagType method then uses the XML namespace as an index into the cache to add this list of

ASP.NET AJAX namespaces to the cache As a result, the next request for this list will be serviced directly

from the cache to improve performance:

Sys.Preview.MarkupParser._cachedNamespaceURILists[namespaceURI] = nspaceList;

Next, _getTagType uses the toUpperCase method to convert all lowercase characters in the name of

the xml-script node to uppercase:

var upperTagName = tagName.toUpperCase();

Next, _getTagType iterates through the list of ASP.NET AJAX namespaces associated with the XML

namespace of the xml-script node being parsed, and invokes the parse static method on the Type class,

passing in the name of the xml-script node and the enumerated ASP.NET AJAX namespace The parse

static method determines whether the enumerated ASP.NET AJAX namespace contains an ASP.NET

AJAX type with the same name as the xml-script node If so, the method returns a reference to the

constructor of the ASP.NET AJAX type

for(var i=0; i < nspaceList.length; i++)

{

var nspace = nspaceList[i];

var type = Type.parse(tagName, nspace);

if(typeof(type) === ‘function’)

return type;

}

If none of the ASP.NET AJAX namespaces associated with the specified XML namespace contains an

ASP.NET AJAX type with the same name as the xml-script node, the _getTagType checks whether the

xml-script node’s name is APPLICATION If so, it simply returns a reference to the Sys._Application

constructor

if(upperTagName === “APPLICATION”)

return Sys._Application;

Trang 15

If the xml-script node’s name is not WEBREQUESTMANAGER either, the _getTagType gives up and returns

null to tell its caller that there’s no ASP.NET AJAX type with the same name as the xml-script node whose type is being determined:

return null;

Listing A-7: The _getTagType Method

Sys.Preview.MarkupParser._getTagType = function Sys$Preview$MarkupParser$_getTagType(node){

var tagName = Sys.Preview.MarkupParser.getNodeName(node);

var namespaceURI = node.namespaceURI ||

Sys.Preview.MarkupParser._defaultNamespaceURI;

var nspaceList = Sys.Preview.MarkupParser._cachedNamespaceURILists[namespaceURI];

if (typeof(nspaceList) === ‘undefined’) {

nspaceList = Sys.Preview.MarkupParser._processNamespaceURI(namespaceURI);

Sys.Preview.MarkupParser._cachedNamespaceURILists[namespaceURI] = nspaceList;

} var upperTagName = tagName.toUpperCase();

for(var i=0; i < nspaceList.length; i++) {

var nspace = nspaceList[i];

var type = Type.parse(tagName, nspace);

if(typeof(type) === ‘function’) return type;

} if(upperTagName === “APPLICATION”) return Sys._Application;

if(upperTagName === “WEBREQUESTMANAGER”) return Sys.Net._WebRequestManager;

return null;

}

_processNamespaceURI

The main responsibilities of the _processNamespaceURI static method of the MarkupParser class are

to create and return an array that contains all the ASP.NET AJAX namespaces associated with the specified XML namespace URI Listing A-8 contains the implementation of this method

Trang 16

If the specified XML namespace URI is the default namespace (that is, the standard http://schemas

.microsoft.com/xml-script/2005 XML namespace) the _processNamespaceURI delegates to the

_getDefaultNamespaces static method of the MarkupParser class the responsibility of creating and

returning the array that contains all the ASP.NET AJAX namespaces associated with this standard XML

namespace I’ll discuss this static method in the following section

if(!namespaceURI ||

namespaceURI === Sys.Preview.MarkupParser._defaultNamespaceURI)

return Sys.Preview.MarkupParser._getDefaultNamespaces();

Trang 17

Note that if the value that the eval method returns is not a valid ASP.NET AJAX namespace, the

_processNamespaceURI raises an exception:

if (!nspace || !Type.isNamespace(nspace)) throw Error.invalidOperation(String.format(“’{0}’ is not a valid namespace.”, nspaceName));

Also note that the _processNamespaceURI collects in a local array the return values of the calls into the eval method (that is, the actual ASP.NET AJAX namespaces) This array is then returned to the caller of the method:

Array.add(list, nspace);

_getDefaultNamespaces

As you can see from Listing A-9 , the _getDefaultNamespaces method populates the _defaultNamespace static array field of the MarkupParser class with the list of standard ASP.NET AJAX namespaces such as

Sys , Sys.UI , Sys.Net , and so on

Listing A-9: The _getDefaultNamespaces Method

Sys.Preview.MarkupParser._getDefaultNamespaces = function Sys$Preview$MarkupParser$_getDefaultNamespaces(){

if(!Sys.Preview.MarkupParser._defaultNamespaces) {

var list = [ Sys, Sys.UI, Sys.Net, Sys.Preview, Sys.Preview.UI, Sys.Preview.Net, Sys.Preview.Data, Sys.Preview.UI.Data, Sys.Preview.Services.Components ];

if(Sys.Preview.UI.Effects) Array.add(list, Sys.Preview.UI.Effects);

Sys.Preview.MarkupParser._defaultNamespaces = list;

} return Sys.Preview.MarkupParser._defaultNamespaces;

}

Trang 18

Appendix A: XML Script

1292

parseFromMarkup

Each ASP.NET AJAX type either defines a static method named parseFromMarkup or inherits this

method from its first ancestor, which defines this method through the process discussed earlier

This method takes the three parameters shown in the following table:

Parameter Description

type This parameter references the constructor of the ASP.NET AJAX type

associated with the xml-script node being parsed Recall that this ASP.NET AJAX type has the same name as the xml-node being parsed

node This parameter references the xml-node being parsed

markupContext This parameter references the current MarkupContext Recall that the

current MarkupContext internally maintains three important entities

The first one is a document fragment that contains a subtree of nodes ( _document ) The second is a collection of ASP.NET AJAX components ( _objects ) The third is a collection of JavaScript object literals, each of which represents a property of an ASP.NET AJAX component that refer-ences another ASP.NET AJAX component ( _pendingReferences )

As mentioned earlier, the parseFromMarkup method is a static method: it is defined on an ASP.NET

AJAX type itself, not its prototype property The parseFromMarkup static method of a given

ASP.NET AJAX type has the following main responsibilities:

❑ It must instantiate an instance of the ASP.NET AJAX type Since the first parameter of the

parseFromMarkup method references the constructor of this type, instantiating an instance is

normally as simple as invoking the new JavaScript operator on this constructor:

var instance = new type();

❑ It must initialize this instance Initializing an ASP.NET AJAX object involves two main tasks:

❑ Initializing the properties of the instance: The second parameter of the parseFromMarkup

method references the xml-script node that represents this instance in xml-script This xml-script node contains attributes or child nodes with the same names as the properties of this ASP.NET AJAX instance The parseFromMarkup method must parse the values of these attributes or child nodes and assign them to the properties of the ASP.NET AJAX instance with the same names For some properties of this ASP.NET AJAX instance, such as those simple properties that directly map to the attributes on the xml-script node, the

parseFromMarkup method may simply use the DOM API to access the values of these attributes and directly assign them to the properties of the instance For some more complex properties, the parseFromMarkup method delegates the responsibility of initialization to the initializeObject static method of the MarkupParser class

❑ Registering event handlers for the events of the instance: As just mentioned, the second

parameter of the parseFromMarkup method references the xml-script node that represents this instance in xml-script This xml-script node contains attributes or child nodes with the same names as the events of this ASP.NET AJAX instance The parseFromMarkup method must parse the values of these attributes or child nodes These values are nothing but the

Trang 19

Appendix A: XML Script

1293

names of the event handlers that must be registered for the specified events Since the

initializeObject static method of the MarkupParser class already contains the logic that knows how to get a reference to the event handler with the specified name, the

parseFromMarkup method normally delegates the responsibility of registering event handlers for the events of the instance to the initializeObject method

❑ It must add this ASP.NET AJAX instance to the _objects collection of the current MarkupContext Recall that this collection maintains the list of all the ASP.NET AJAX instances parsed within the current MarkupContext As we discussed earlier, the current MarkupContext exposes a method named addComponent that the parseFromMarkup method can use to add the newly parsed ASP.NET AJAX instance to this collection

The parseFromMarkup method is the most important extensibility point of the ASP.NET AJAX xml-script-parsing infrastructure Your custom classes can define a custom parseFromMarkup method

to take complete control over how the xml-script node that represents an instance of your custom class in xml-script must be parsed The custom parseFromMarkup method of your custom class must meet the following requirements:

❑ It must be named parseFromMarkup

❑ It must take three parameters The first references the constructor of your custom class, the second references the xml-script node that represents the current instance of your custom class

in xml-script, and the third references the current MarkupContext

❑ It must instantiate, initialize, and return the instance of your custom class that represents the xml-script node referenced by the second parameter

❑ It must be static — that is, it must be defined on your custom class, not its prototype property You’ll see an example of a custom parseFromMarkup method in Appendix E If your custom component does not define its own parseFromMarkup method, the ASP.NET AJAX xml-script-parsing infrastructure walks up the ancestors of your custom class searching for the first ancestor that support this method and assigns it to your custom class In other words, your custom class will end up using the

parseFromMarkup method of its first ancestor that supports this method

For example, if you implement a custom control that derives from the Control base class, and if your custom control does not directly support its own parseFromMarkup method, it will end up using the

parseFromMarkup method of the Control base class, as shown in Listing A-10

Listing A-10: The parseFromMarkup Method

Sys.UI.Control.parseFromMarkup = function Sys$UI$Control$parseFromMarkup(type, node, markupContext){

var idAttribute = node.attributes.getNamedItem(‘id’);

var id = idAttribute.nodeValue;

var associatedElement = markupContext.findElement(id);

var dataContextHidden = false;

var dataContext = markupContext.get_dataContext();

if (dataContext) dataContextHidden = markupContext.hideDataContext();

(continued)

Trang 20

Appendix A: XML Script

1294

Listing A-10 (continued)

var newControl = new type(associatedElement);

var control = Sys.Preview.MarkupParser.initializeObject(newControl, node,

This listing shows an example of the implementation of the parseFromMarkup method You can follow

this example to implement your own custom parseFromMarkup method for your own custom classes

Next, I’ll walk you through this sample parseFromMarkup method

As you can see, it begins by calling the getNamedItem method on the attributes collection of the

xml-script node that represents the current control in xml-script to return a reference to the attribute

node that represents the id attribute of the xml-script node:

var idAttribute = node.attributes.getNamedItem(‘id’);

Next, it invokes the nodeValue property on this attribute node to access the value of the id attribute:

var id = idAttribute.nodeValue;

Then it invokes the findElement method on the current MarkupContext , passing in the id attribute

value to return a reference to the associated DOM element of the control Recall that each ASP.NET AJAX

client control is associated with a DOM element whose id HTML attribute is given by the id attribute of

the xml-script node that represents the client control in xml-script:

var associatedElement = markupContext.findElement(id);

Next, it invokes the new JavaScript operator on the constructor of the client control, passing in the

refer-ence to the associated DOM element of the control to instantiate the client control associated with the

specified xml-script node Recall that the first parameter of the parseFromMarkup method references the

constructor of the ASP.NET AJAX type associated with the specified xml-script node:

var newControl = new type(associatedElement);

Then the parseFromMarkup method delegates the responsibility of initializing the properties of the

newly instantiated client control, and the responsibility of registering event handlers for its events, to the

initializeObject static method of the MarkupParser class:

Trang 21

markupContext References the current MarkupContext

As the name suggests, the initializeObject method initializes the ASP.NET AJAX object that its first parameter references This initialization involves two main tasks:

❑ Initializing the properties of the ASP.NET AJAX object: The xml-script node referenced by the ond parameter of the initializeObject method exposes attributes or child nodes with the same names as the properties of the ASP.NET AJAX object being initialized The initializeObject method extracts the required values from these attributes or child nodes and assigns them to the properties of the ASP.NET AJAX object with the same names

sec-❑ Registering event handlers for the events of the ASP.NET AJAX object: The xml-script node erenced by the second parameter of the initializeObject method exposes attributes or child nodes with the same names as the events of the ASP.NET AJAX object being initialized The

ref-initializeObject method extracts the required event handlers from these attributes or child nodes and registers them as event handlers for the events of the ASP.NET AJAX object with the same names

Before diving into the implementation of the initializeObject method we need to revisit the ASP.NET AJAX TypeDescriptor class, because the initializeObject method makes extensive use of this class and its methods Recall from Chapter 10 that every ASP.NET AJAX type, such as a class, is associated with an object known as a type descriptor, which generically describes the properties, events, methods, and metadata attributes of the type This object allows the client of an ASP.NET AJAX type to inspect the type generically without knowing the actual type of the type

The ASP.NET AJAX TypeDescriptor class comes with a static method named getTypeDescriptor that takes an ASP.NET AJAX object as its argument and returns a reference to the type descriptor object that describes the type of this ASP.NET AJAX object

Trang 22

Appendix A: XML Script

1296

As you’ll see shortly, to initialize the ASP.NET AJAX object passed into it in a generic way, the

initializeObject method uses the type descriptor object returned by the getTypeDescriptor

static method of the TypeDescriptor class Therefore, the type descriptor object associated with a

given ASP.NET AJAX type determines how an instance of the type is initialized from the attributes and

child nodes of the xml-script node that represents the instance in xml-script

Therefore, if you want to enable the clients of your custom ASP.NET AJAX type to instantiate and

initial-ize instances of your custom type in xml-script in a purely declarative fashion, without their writing any

JavaScript code, you must take extra steps to make sure that the getTypeDescriptor static method of

the TypeDescriptor class returns the appropriate type descriptor object to the initializeObject

method You have two options:

❑ Have your custom type implement the ICustomTypeProvider interface, so the

getTypeDescriptor static method returns your own custom type descriptor object

❑ Define a descriptor static property on your custom type to describe the properties, events,

methods, and metadata attributes of your type, as we discussed earlier

Which approach is better? It depends on the specifics of your application requirements In general, the

first approach is more flexible than the second but requires more coding Keep in mind that your custom

type must either implement the ICustomTypeProvider interface or expose a static property named

descriptor Otherwise no one will be able to use your custom type declaratively in xml-script

The implementation of the initializeObject method is quite complex To help you get a better

understanding of this method, I’ll present an example of its implementation This example consists of

four ASP.NET AJAX client classes named MyCustomType , MyEnumeration , MyType , and MyType2

A JavaScript file named MyClientTypes.js contains the implementation of these three client classes

Listing A-11 presents the content of this JavaScript file

Listing A-11: The Content of the MyClientTypes.js JavaScript File

var params = value.split(‘,’);

alert(“Instantiating a MyType2 object and initializing it with “ + params);

return new CustomComponents.MyType2(params[0], params[1], params[2]);

Trang 23

Appendix A: XML Script

1297

CustomComponents.MyEnumeration.prototype = {

EnumValue1: 0, EnumValue2: 1, EnumValue3: 2}

this._myTypeProperty = value;

alert(“myTypeProperty was set to “ + value);

}function CustomComponents$MyType$get_myTypeProperty(){

return this._myTypeProperty;

}CustomComponents.MyType.prototype ={

get_myTypeProperty : CustomComponents$MyType$get_myTypeProperty, set_myTypeProperty : CustomComponents$MyType$set_myTypeProperty}

CustomComponents.MyType.registerClass(“CustomComponents.MyType”);

CustomComponents.MyType.descriptor ={

properties : [{name : “myTypeProperty”, type : String}]

}//////////////////////////////

CustomComponents.MyCustomType = function CustomComponents$MyCustomType(){

CustomComponents.MyCustomType.initializeBase(this);

}function CustomComponents$MyCustomType$add_myEvent(eventHandler){

this.get_events().addHandler(“myEvent”, eventHandler);

alert(eventHandler + “ \n\nwas registered as event handler for myEvent event!”);

}function CustomComponents$MyCustomType$remove_myEvent(eventHandler){

this.get_events().removeHandler(“myEvent”, eventHandler);

}function CustomComponents$MyCustomType$set_myProperty(value){

Trang 25

Appendix A: XML Script

1299

function CustomComponents$MyCustomType$get_myObjectProperty(){

alert(“The value of myObjectProperty is being retrieved!”);

return this._myObjectProperty;

}function CustomComponents$MyCustomType$get_myNonObjectNonArrayProperty(){

alert(“The value of myNonObjectNonArrayProperty is being retrieved!”);

if (!this._myNonObjectNonArrayProperty) this._myNonObjectNonArrayProperty = new CustomComponents.MyType();

return this._myNonObjectNonArrayProperty;

}function CustomComponents$MyCustomType$set_myEnumProperty(value){

this._myEnumProperty = value;

alert(“myEnumProperty was set to “ + value);

}function CustomComponents$MyCustomType$get_myEnumProperty(){

return this._myEnumProperty;

}CustomComponents.MyCustomType.prototype ={

_myReadOnlyArrayProperty : [], _myObjectProperty : {}, set_myProperty : CustomComponents$MyCustomType$set_myProperty, get_myProperty : CustomComponents$MyCustomType$get_myProperty, set_myNonReadOnlyStringProperty :

CustomComponents$MyCustomType$set_myNonReadOnlyStringProperty, get_myNonReadOnlyStringProperty :

CustomComponents$MyCustomType$get_myNonReadOnlyStringProperty, set_myProperty2 : CustomComponents$MyCustomType$set_myProperty2,

get_myProperty2 : CustomComponents$MyCustomType$get_myProperty2, set_myReferenceProperty : CustomComponents$MyCustomType$set_myReferenceProperty, get_myReferenceProperty : CustomComponents$MyCustomType$get_myReferenceProperty, set_myArrayProperty : CustomComponents$MyCustomType$set_myArrayProperty,

get_myArrayProperty : CustomComponents$MyCustomType$get_myArrayProperty, set_myEnumProperty : CustomComponents$MyCustomType$set_myEnumProperty, get_myEnumProperty : CustomComponents$MyCustomType$get_myEnumProperty, get_myReadOnlyArrayProperty :

CustomComponents$MyCustomType$get_myReadOnlyArrayProperty, get_myObjectProperty : CustomComponents$MyCustomType$get_myObjectProperty, get_myNonObjectNonArrayProperty :

CustomComponents$MyCustomType$get_myNonObjectNonArrayProperty, add_myEvent : CustomComponents$MyCustomType$add_myEvent,

remove_myEvent : CustomComponents$MyCustomType$remove_myEvent}

(continued)

Trang 26

properties : [{name : ‘myProperty’, type : null, isDomElement : true},

{name : ‘myNonReadOnlyStringProperty’, type : String},

{name : ‘myReferenceProperty’,

type : CustomComponents.MyCustomType},

{name : ‘myProperty2’, type : CustomComponents.MyType2},

{name : ‘myArrayProperty’, type : Array},

{name : ‘myEnumProperty’, type : CustomComponents.MyEnumeration},

{name : ‘myReadOnlyArrayProperty’, type : Array, readOnly : true},

{name : ‘myObjectProperty’, type : Object, readOnly : true},

{name : ‘myNonObjectNonArrayProperty’,

type : CustomComponents.MyType, readOnly : true}],

events : [{name : “myEvent”}]

}

if(typeof(Sys)!==’undefined’)

Sys.Application.notifyScriptLoaded();

Listing A-12 presents a page that uses these client classes in xml-script in a purely declarative fashion

Listing A-12: A Page that Uses the Client Classes Defined in Listing A-11 in xml-script

<script language=”text/javascript” type=”text/javascript”>

function myEventHandler (sender, eventArgs) { }

</script>

</head>

<body>

<form id=”form1” runat=”server”>

<asp:ScriptManager runat=”server” ID=”ScriptManager1”>

Trang 27

var td = Sys.Preview.TypeDescriptor.getTypeDescriptor(instance);

if (!td) return null;

var supportsBatchedUpdates = false;

if ((instance.beginUpdate && instance.endUpdate && instance !==Sys.Application)) {

supportsBatchedUpdates = true;

instance.beginUpdate();

} var i, a;

var attr, attrName;

var propertyInfo, propertyName, propertyType, propertyValue;

var eventInfo, eventValue;

var setter, getter;

var properties = td._getProperties();

var events = td._getEvents();

var attributes = node.attributes;

(continued)

Trang 28

propertyValue = Array.parse(‘[‘ + propertyValue + ‘]’);

else if (propertyType && propertyType != =String)

Trang 29

Appendix A: XML Script

1303

else { eventInfo = events[attrName];

if (eventInfo) {

var handler = Function.parse(attr.nodeValue);

if (handler) {

eventValue = instance[‘add_’ + eventInfo.name];

if (eventValue) eventValue.apply(instance, [handler]);

else throw Error.invalidOperation(String.format(

“The event ‘{0}’ is specified in the type descriptor, but add_{0} was not found.”, eventInfo.name));

} } else throw Error.invalidOperation(

String.format(‘Unrecognized attribute “{0}” on object of type “{1}”’, attrName, Object.getTypeName(instance)));

} } } var childNodes = node.childNodes;

if (childNodes && (childNodes.length ! =0)) {

for (i = childNodes.length - 1; i >= 0; i ) {

var childNode = childNodes[i];

if (childNode.nodeType != 1) continue;

var nodeName = Sys.Preview.MarkupParser.getNodeName(childNode);

propertyInfo = properties[nodeName];

if (propertyInfo) {

propertyName = propertyInfo.name;

propertyType = propertyInfo.type;

if (propertyInfo.readOnly) {

getter = instance[‘get_’ + propertyName];

var nestedObject = getter.call(instance);

if (propertyType === Array) {

if (childNode.childNodes.length) {

var items = Sys.Preview.MarkupParser.parseNodes(childNode.childNodes, markupContext);

for (var itemIndex = 0; itemIndex < items.length; itemIndex++) {

var item = items[itemIndex];

if(typeof(nestedObject.add) === “function”) nestedObject.add(item);

(continued)

Trang 30

for (var childNodeIndex = 0;

childNodeIndex < childNode.childNodes.length; childNodeIndex++)

Trang 31

Appendix A: XML Script

1305

else { eventInfo = events[nodeName];

if (eventInfo) {

var actions = Sys.Preview.MarkupParser.parseNodes(childNode.childNodes, markupContext);

if (actions.length) {

eventValue = instance[“add_” + eventInfo.name];

if(eventValue) {

for (var e = 0; e < actions.length; e++) {

var action = actions[e];

action.set_eventName(eventInfo.name);

action.set_eventSource(instance);

} } else throw Error.invalidOperation(String.format(

“The event ‘{0}’ is specified in the type descriptor, but add_{0} was not found.”, eventInfo.name));

} } else { var type = null;

var upperName = nodeName.toUpperCase();

if(upperName === ‘BINDINGS’) type = Sys.Preview.BindingBase;

else if(upperName === ‘BEHAVIORS’) type = Sys.UI.Behavior;

if(type) {

if (childNode.childNodes.length) {

var items = Sys.Preview.MarkupParser.parseNodes(childNode.childNodes, markupContext);

for (var itemIndex = 0; itemIndex < items.length; itemIndex++) {

var item = items[itemIndex];

debug.assert(type.isInstanceOfType(item), String.format(“The ‘{0}’ element may only contain child elements

of type ‘{1}’.”, nodeName, type.getName()));

if(typeof(item.setOwner) === “function”) item.setOwner(instance);

} } }

(continued)

Trang 32

As you can see from Listing A-13 , the initializeObject method begins by invoking the

getT ypeDescriptor static method on the TypeDescriptor class, passing in the reference to the

ASP.NET AJAX object being initialized This method returns a reference to the type descriptor object

that describes the type of the ASP.NET AJAX object being initialized

var td = Sys.Preview.TypeDescriptor.getTypeDescriptor(instance);

Next, the initializeObject method invokes the getProperties method on the type descriptor

object to return a dictionary that contains the complete information about the properties of the ASP.NET

AJAX object being initialized:

var properties = td._getProperties();

Next, the initializeObject method invokes the getEvents method on the type descriptor object to

return a dictionary that contains the complete information about the events of the ASP.NET AJAX object

being initialized:

var events = td._getEvents();

Then the method accesses the attributes collection of the xml-script node referenced by the second

parameter of the method (Recall that this xml-script node represents the ASP.NET AJAX object

being initialized.) The attributes collection contains one attribute node for each attribute on this

xml-script node:

var attributes = node.attributes;

Next, the initializeObject method iterates through the attribute nodes in the attributes collection

and performs several tasks for each enumerated attribute node First, the initializeObject method

accesses the name of the attribute that the attribute node represents:

attr = attributes[a];

attrName = attr.nodeName;

Trang 33

Appendix A: XML Script

1307

The initializeObject method ignores the id attribute if the ASP.NET AJAX object being initialized is

a client control Recall from Listing A-10 that the parseFromMarkup method of the Control class has already taken care of the id attribute:

if(attrName === “id” && Sys.UI.Control.isInstanceOfType(instance)) continue;

The method then uses the attribute name as an index into the properties collection, to return a ence to the property info object that contains the complete information about the property with the same name as the attribute Recall that the properties collection is the return value of the call into the

initializeObject method invokes the addReference method on the current MarkupContext to add

a reference for this property to the _ pendingReferences collection of the current MarkupContext Recall that this collection contains one JavaScript object literal for each property that references another component This JavaScript object literal exposes three properties: the first references the object that owns the property (which is the object being initialized), the second references the property info object that represents the property, and the last is the attribute value, which will be used to evaluate the property value Recall that the actual evaluation takes place when the close method is invoked on the current MarkupContext This is to ensure that the object being referenced is instantiated and initialized before it is referenced:

if (propertyType &&

(propertyType === Object ||

propertyType === Sys.Component ||

propertyType.inheritsFrom(Sys.Component))) markupContext.addReference(instance, propertyInfo, propertyValue);

Next, I’ll present an example of this case As Listing A-11 shows, the MyCustomType class exposes a property named myReferenceProperty that references another MyCustomType component in the current application:

function CustomComponents$MyCustomType$set_myReferenceProperty(value){

this._myReferenceProperty = value;

alert(“myReferenceProperty was set to the component with the id value of “ + value.get_id());

}

Trang 34

Appendix A: XML Script

1308

Note that the setter method associated with this property pops up an alert that displays the new value of

the property As Listing A-11 shows, the value of the properties property of the descriptor static

property of the MyCustomType contains the object literal shown in the boldface portion of the following

code fragment, where the value of the type property of this object literal references the constructor of

the MyCustomType type:

This object literal tells the initializeObject method that the myReferenceProperty property of the

MyCustomType object references another MyCustomType object in the current application As Listing A-12

shows, this enables you to use the boldface declarative syntax shown in the following code fragment to

specify the value of this property in xml-script if the value of the myReferenceProperty attribute on the

<custom:MyCustomType> element with an id property value of myCustomType1 is set to the value of

the id property of the <custom:MyCustomType> element with an id property value of myCustomType2 :

<custom:MyCustomType id=”myCustomType1” myReferenceProperty=”myCustomType2” >

</custom:MyCustomType>

<custom:MyCustomType id=”myCustomType2” />

Now back to the implementation of the initializeObject method If the property references a DOM

element, the initializeObject invokes the findElement method on the current MarkupContext ,

passing in the attribute value to return a reference to the DOM element that will be used as the value of

the property:

if (propertyInfo.isDomElement || propertyType === Sys.UI.DomElement)

propertyValue = markupContext.findElement(propertyValue);

Listing A-11 shows an example of this case, in which the MyCustomType component exposes a property

named myProperty that references a DOM element As you can see from this code listing, the value of

the properties property of the descriptor property of the MyCustomType component contains an

object literal, shown in the boldface portion of the following excerpt from this code listing:

This enables you to declaratively specify the value of the myProperty property by declaring an attribute

named myProperty on an xml-script <custom:MyCustomType> node in xml-script, and setting the value

Trang 35

Now back to the implementation of the initializeObject method If the property is a JavaScript array, the initializeObject generates a string that encloses the attribute value within square brackets and parses this string into a JavaScript array, which will be used as the value of the property:

if (propertyType === Array) propertyValue = Array.parse(‘[‘ + propertyValue + ‘]’);

Listing A-11 also shows an example of this case, in which MyCustomType exposes a property of type

Array named myArrayProperty Note that the properties property of the descriptor static property of MyCustomType contains the object literal shown in the boldface portion of the following excerpt from Listing A-11 :

CustomComponents.MyCustomType.descriptor = {

}

This enables you to declaratively specify the value of the myArrayProperty property by declaring in xml-script an attribute named myArrayProperty on the xml-script <custom:MyCustomType> node and setting the value of this attribute to a comma-separated list of values, as shown in the boldface portion of the following excerpt from Listing A-12 :

<custom:MyCustomType myArrayProperty=”’value1’,’value2’” >

</custom:MyCustomType>

Trang 36

Appendix A: XML Script

1310

This excerpt sets the value of the myArrayProperty attribute on the <custom:MyCustomType>

xml-script node to a comma-separated list of two values Since the descriptor static property of

MyCustomType specifies Array as the type of the myArrayProperty property, the initializeObject

method automatically encloses the preceding comma-separated list of values in square brackets and

passes the result into the parse method of the Array class to parse the list into a valid JavaScript array,

to arrive at the actual value of the myArrayProperty property

Now back to the implementation of the initializeObject method If the property is not a string, the

initializeObject method checks whether the property is an enumeration If so, it parses the attribute

value into the appropriate enumeration value, which will be used as the value of the property

if(Type.isEnum(propertyType))

propertyValue = propertyType.parse(propertyValue, true);

Next, we’ll look at the portions of Listings A-11 and A-12 that cover this case As Listing A-11 shows,

MyCustomType exposes a property of type MyEnumeration named myEnumProperty , which has three

possible values named EnumValue1 , EnumValue2 , and EnumValue3 Notice that the properties

prop-erty of the descriptor static property of MyCustomType contains an object literal with the type value

of CustomComponents.MyEnumeration , as shown in the boldface portion of the following excerpt from

This enables you to declaratively specify the value of the myEnumProperty property by declaring in

xml-script an attribute named myEnumProperty on the xml-script <custom:MyCustomType> node and

setting the value of this attribute to one of the possible values of the MyEnumeration — that is,

EnumValue1 , EnumValue2 , or EnumValue3 — without having to specify the complete name of the

value, as shown in the boldface portion of the following excerpt from Listing A-12 The complete name

is prefixed by CustomComponents.MyEnumeration

<custom:MyCustomType myEnumProperty=”EnumValue2” >

</custom:MyCustomType>

This example sets the value of the myEnumProperty attribute on the <custom:MyCustomType>

xml-script node to the enumeration value of EnumValue2 without your having to specify the complete

name of the enumeration value, MyNamespace.MyEnumeration.MyEnumValue2 Since the descriptor

static property of MyCustomType specifies CustomComponents.MyEnumeration as the type of the

myEnumProperty property, the initializeObject method automatically calls the parse method

to parse this attribute value to the actual MyNamespace.MyEnumeration.MyEnumValue2

enumeration value

Now back to the implementation of the initializeObject method If the property is not an

enumera-tion, the initializeObject method simply invokes the parse static method on the type of the

Trang 37

Appendix A: XML Script

1311

property to parse the attribute value, which will be used as the value of the property In other words,

initializeObject assumes that the parse static method of this type knows how to parse this attribute value to the type that the property expects

else propertyValue = propertyType.parse(propertyValue);

Next, I’ll take a look at an example of this case As you can see from Listing A-11 , MyCustomType features a property named myProperty2 :

function CustomComponents$MyCustomType$set_myProperty2(value){

],

}

As Listing A-11 shows, CustomComponents.MyType2 exposes the following parse static method, which parses the specified value into a CustomComponents.MyType2 object and returns this object to its caller (the caller in our case being the initializeObject method):

CustomComponents.MyType2.parse = function (value){

var params = value.split(‘,’);

alert(“Instantiating a MyType2 object and initializing it with “ + params);

return new CustomComponents.MyType2(params[0], params[1], params[2]);

}

This is a powerful technique that you can use in your own client code to enable page developers to instantiate instances of your custom client classes from xml-script in a purely declarative fashion, as you can from the boldface portion of the following excerpt from Listing A-12 Here the page developer uses a declarative approach to instantiate an instance of CustomComponents.MyType2 and to assign this instance to the myProperty2 property of the specified MyCustomType component:

<custom:MyCustomType myProperty2=”’value1’,’value2’,’value3’” >

</custom:MyCustomType>

Trang 38

Appendix A: XML Script

1312

Now back to the implementation of the initializeObject method Now that the property’s value has

been determined, it’s time to assign this value to the property The initializeObject method takes

several steps to accomplish this

❑ First, it accesses the property name:

propertyName = propertyInfo.name;

❑ Then it generates a string that contains the name of the setter method for the property and uses

this string as an index into the object being initialized to return a reference to this setter method:

setter = instance[‘set_’ + propertyName];

❑ Finally, it invokes the call method on this reference to set the value of the property to the

specified value:

setter.call(instance, propertyValue);

As you can see, the initializeObject method expects the getter and setter methods associated with a

given property to follow these naming conventions:

get_ PropertyName

set_ PropertyName

page developers to declare an attribute with the same name as the property on the xml-script node that

represents an instance of your custom type in xml-script, and to assign the appropriate value to this

attribute and rest assured that the initializeObject method will automatically invoke the underlying

setter method to assign the specified value to the property

Your custom ASP.NET AJAX type must explicitly describe its properties in the value of the properties

property of its descriptor static property, as you saw in the previous examples

It’s very important to realize that the initializeObject method does not pick up the property

information, such as name and type, from the prototype property of your custom type Such

informa-tion is picked up from the descriptor property If you do not add object literals describing your

properties to the value of the properties property of the descriptor property of your custom type,

the initializeObject will have no way of knowing that your type exposes those properties As we

discussed earlier, another approach is to have your type implement the ICustomTypeProvider

interface to return a type descriptor that describes these properties

If the properties collection does not contain a property with the same name as the attribute, the

initializeObject method uses the attribute name as an index into the events collection to return a

reference to the event info object that contains the complete information about the event with the same

name as the attribute Recall that the events collection is the return value of the call into the

_getEvents method:

eventInfo = events[attrName];

Trang 39

Appendix A: XML Script

1313

If the events collection does indeed contain an event with the same name as the attribute, the

initializeObject method performs the following tasks:

❑ First, it invokes the parse static method on the Function class, passing in the attribute value to return a reference to the event handler being registered As you can see, the initializeObject method assumes that the value assigned to the attribute is the name of the event handler being registered for the event with the same name as the attribute name:

var handler = Function.parse(attr.nodeValue);

❑ Next, it generates a string that contains the name of the method that registers event handlers for the event with the specified name, and uses this string as an index into the object being initial-ized in order to return a reference to this method:

eventValue = instance[‘add_’ + eventInfo.name];

❑ Then the initializeObject method calls the apply method on this reference to invoke the method and consequently to register the specified event handler for the specified event of the object being initialized:

eventValue.apply(instance, [handler]);

As you can see, the initializeObject method expects the methods of your custom ASP.NET AJAX type that register event handlers for events with specified names to follow this naming convention:

add_ EventName

EventName stands for the name of the event Following this naming convention will enable page

developers to declare an attribute with the same name as the event on the xml-script node that represents an instance of your custom type in xml-script, and to assign the name of the desired event handler to this attribute and rest assured that the initializeObject method will auto-matically invoke the underlying add method to register the specified event handler for the event with the specified name

For example, as you can see from Listing A-11 , MyCustomType exposes a method named add_myEvent :

function CustomComponents$MyCustomType$add_myEvent(eventHandler){

events : [ {name : “myEvent”} ]}

Trang 40

Appendix A: XML Script

1314

This object literal tells the initializeObject method that the attribute named myEvent contains the name

of an event handler that must be registered for the myEvent event of the specified MyCustomType object

This enables the page developer to register an event handler such as myEventHandler in a purely

declara-tive fashion in xml-script, as shown in the boldface portion of the following excerpt from Listing A-12 :

<custom:MyCustomType myEvent=”myEventHandler” >

</custom:MyCustomType>

When the initializeObject method encounters this boldface portion, it automatically calls the parse

method to return a reference to the myEventHandler JavaScript function:

var refTomyEventHandler = Function.parse(“myEventHandler”);

It then creates the “add_myEvent” string and uses it as an index to return a reference to the

add_myEvent method of your custom type:

var refToadd_myEvent = instance[“add_” + “myEvent”];

It then calls the apply method on the refToadd_myEvent reference to invoke the add_myEvent method

of MyCustomType to register the myEventHandler JavaScript function for myEvent event:

refToadd_MyEvent.apply(instance, [refTomyEventHandler]);

Again, it’s very important to realize that the initializeObject method does not pick up the event

information from the prototype property of your custom type That information is picked up from the

descriptor property If you do not describe the events of your custom type in the events property of

the descriptor property of your custom type, the initializeObject will have no way of knowing

that your type exposes those events

If neither the properties nor the events collection contains an entry with the same name as the

attri-bute, the initializeObject method raises an exception because the specified attribute on the

xml-script node referenced by the second parameter of the initializeObject method is unrecognized

else

throw Error.invalidOperation(

String.format(‘Unrecognized attribute “{0}” on object of type “{1}”’,

attrName, Object.getTypeName(instance)));

As you can see, the xml-script does not support expando or custom attributes Every attribute on an

xml-script node that represents an ASP.NET AJAX object must map into either a property or an event

of the ASP.NET AJAX object with the same name as the attribute

Next, the initializeObject method accesses the collection that contains the child nodes of the

xml-script node referenced by the second parameter of the initializeObject method Recall that this

xml-script node represents the ASP.NET AJAX object being initialized:

var childNodes = node.childNodes;

Ngày đăng: 09/08/2014, 06:23

TỪ KHÓA LIÊN QUAN