Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 250 trang
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
Nội dung
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 docu- 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 Every instance of the MarkupContext ASP.NET AJAX class contains two important pieces of information: ❑ An XML document that contains a subtree of DOM elements. As you’ll see later, the 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. bapp01.indd 1275bapp01.indd 1275 8/20/07 8:58:14 PM8/20/07 8:58:14 PM 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: Sys.Preview.MarkupContext = function Sys$Preview$MarkupContext(document, global, parentContext, dataContext) { this._document = document; this._global = global; this._ parentContext = parentContext; this._objects = { }; this._ pendingReferences = []; this._ pendingEndUpdates = []; } 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 document This parameter references the XML document associated with the 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.) bapp01.indd 1276bapp01.indd 1276 8/20/07 8:58:14 PM8/20/07 8:58:14 PM 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) { this._objects[id] = object; if(!noRegisterWithApp && this._global && Sys.Component.isInstanceOfType(object)) Sys.Application.addComponent(object); } This makes finding an ASP.NET AJAX component interesting. The MarkupContext exposes a method named findComponent that takes two parameters, the first being a string that contains the id of the component that you’re looking for. If the second parameter is not specified, findComponent first searches through the _objects collection of the current markup context for the component with the specified id . If it can’t find it there, findComponent then calls the findComponent method on the parent MarkupContext object. In most cases, the parent MarkupContext object is the global markup context. 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; } } bapp01.indd 1277bapp01.indd 1277 8/20/07 8:58:15 PM8/20/07 8:58:15 PM 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. bapp01.indd 1278bapp01.indd 1278 8/20/07 8:58:15 PM8/20/07 8:58:15 PM Appendix A: XML Script 1279 function Sys$Preview$MarkupContext$addReference(instance, propertyInfo, reference) { Array.add(this._pendingReferences, { o: instance, p: propertyInfo, r: reference }); } 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; } bapp01.indd 1279bapp01.indd 1279 8/20/07 8:58:15 PM8/20/07 8:58:15 PM Appendix A: XML Script 1280 The open method of MarkupContext instantiates the _pendingReferences collection and marks the MarkupContext as open: function Sys$Preview$MarkupContext$open() { this._pendingReferences = []; this._pendingEndUpdates = []; this._opened = true; } MarkupContext exposes a static method named createGlobalContext that creates the global MarkupContext . Note that the document object is passed into the constructor of the MarkupContext class to create the global markup context. Sys.Preview.MarkupContext.createGlobalContext = function Sys$Preview$MarkupContext$createGlobalContext() { return new Sys.Preview.MarkupContext(document, true); } 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. Sys.Preview.MarkupContext.createLocalContext = function Sys$Preview$MarkupContext$createLocalContext(documentFragment, parentContext, dataContext) { return new Sys.Preview.MarkupContext(documentFragment, false, parentContext, dataContext); } 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); bapp01.indd 1280bapp01.indd 1280 8/20/07 8:58:16 PM8/20/07 8:58:16 PM 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) bapp01.indd 1281bapp01.indd 1281 8/20/07 8:58:16 PM8/20/07 8:58:16 PM Appendix A: XML Script 1282 Listing A-3 (continued) if (xmlScriptElement) { var xmlDocument; if (Sys.Net.XMLDOM) xmlDocument = new Sys.Net.XMLDOM(scriptMarkup); else xmlDocument = new XMLDOM(xmlScriptElement.innerHTML); var documentElement = xmlDocument.documentElement; if (!documentElement || Sys.Preview.MarkupParser.getNodeName(documentElement) != =”page”) throw Error.create(‘Missing page element in xml script block.’, scriptMarkup); Sys.Preview.MarkupParser.processDocumentScripts(markupContext, 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++) { if (scriptElements[e].type == ‘text/xml-script’) { xmlScriptElement = scriptElements[e]; break; } } 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 bapp01.indd 1282bapp01.indd 1282 8/20/07 8:58:16 PM8/20/07 8:58:16 PM Appendix A: XML Script 1283 does not exist or if it is anything other than the page element, the processDocument method raises an exception: var documentElement = xmlDocument.documentElement; if (!documentElement || Sys.Preview.MarkupParser.getNodeName(documentElement) != =”page”) throw Error.create(‘Missing page element in xml script block.’, scriptMarkup); 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(); } bapp01.indd 1283bapp01.indd 1283 8/20/07 8:58:16 PM8/20/07 8:58:16 PM 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 is a component node.) 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); } } Next, the processDocumentScripts method invokes the parseNodes static method on the 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. Sys.Preview.MarkupParser.parseNodes(componentNodes, markupContext); Finally, the processDocumentScripts method invokes the close method on the global MarkupContext . Recall that the close method resolves the component cross-references. markupContext.close(); parseNodes The parseNodes static method of the MarkupParser class takes two parameters, as shown in the fol- lowing table: bapp01.indd 1284bapp01.indd 1284 8/20/07 8:58:17 PM8/20/07 8:58:17 PM [...]... property of an ASP.NET AJAX component that references 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... 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... 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... list of all the ASP.NET AJAX components parsed in the current markup context in this collection: markupContext.addComponent(control); initializeObject The initializeObject method takes the three parameters described in the following table: Parameter Description instance References the ASP.NET AJAX object being initialized node References the xml-script node that represents the ASP.NET AJAX object being... a reference to the constructor of the type of the property: propertyType = propertyInfo.type; It then accesses the attribute value: propertyValue = attr.nodeValue; If the property references a JavaScript object, or an instance of the ASP.NET AJAX Component class, or an instance of the ASP.NET AJAX class that derives from the ASP.NET AJAX Component class, the initializeObject method invokes the addReference... 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. .. nodes into their associated ASP.NET AJAX objects As Listing A-5 shows, the parseNodes method iterates through these xml-script nodes and takes the following steps for each enumerated xml-script node First, it invokes the parseNode static method on the MarkupParser class, passing in two parameters: the first is the reference to the enumerated xml-script node, and the second is the reference to the current... 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... 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... 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 . 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. 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. 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