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 1XML 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 2Appendix 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 3Appendix 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 4Appendix 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 5When 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 6MarkupContext 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 7Appendix 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 8xmlDocument = 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 9Finally, 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 10Appendix 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 11var 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 12Appendix 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 13Appendix 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 14Appendix 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 15If 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 16If 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 17Note 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 18Appendix 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 19Appendix 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 20Appendix 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 21markupContext 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 22Appendix 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 23Appendix 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 25Appendix 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 26properties : [{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 27var 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 28propertyValue = Array.parse(‘[‘ + propertyValue + ‘]’);
else if (propertyType && propertyType != =String)
Trang 29Appendix 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 30for (var childNodeIndex = 0;
childNodeIndex < childNode.childNodes.length; childNodeIndex++)
Trang 31Appendix 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 32As 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 33Appendix 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 34Appendix 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 35Now 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 36Appendix 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 37Appendix 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 38Appendix 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 39Appendix 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 40Appendix 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;