Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 156 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
156
Dung lượng
2,09 MB
Nội dung
Chapter 19: UpdatePanel and ScriptManager This is where the IStateManager interface comes into play The type of MasterContainerStyle and DetailContainerStyle complex properties — that is, the TableItemStyle — implements this interface IStateManager exposes one Boolean property, IsTrackingViewState, and three methods, TrackViewState, SaveViewState, and LoadViewState When the TrackViewState method of a control is called, the method calls the TrackViewState methods of its complex properties The TrackViewState method of a complex property does exactly what the TrackViewState method of a control does — sets an internal Boolean field to true to specify that any state changes will be marked as dirty and saved at the end of the current request When the SaveViewState method of a control is called, the method calls the SaveViewState methods of its complex properties The SaveViewState method of a complex property does exactly what the SaveViewState method of a control does — it saves its state into an appropriate object and returns the object It then collects the objects returned from the SaveViewState methods of its complex properties and saves them into the same object to which it saves its own state Finally, it returns the object that contains the states of both the control and its complex properties When the LoadViewState method of a control is called, it retrieves the objects that contain the states of its complex properties It then calls the LoadViewState method of each complex property and passes the object that contains the saved state into it The LoadViewState method of a complex property does exactly what the LoadViewState of a control does As you can see from the implementation of the MasterContainerStyle and DetailContainerStyle properties shown in Listing 19-13, when these two style properties are created and BaseMasterDetailControl server control is tracking its view state, the control calls the TrackViewState method of these two properties to inform them that they must start tracking their view states TrackViewState BaseMasterDetailControl overrides TrackViewState to call the TrackViewState methods of its style properties, as shown in Listing 19-13 Note that TrackViewState calls the TrackViewState method of a style property if and only if the style isn’t null — that is, if the page developer has specified the style SaveViewState BaseMasterDetailControl overrides SaveViewState to call the SaveViewState methods of its style properties, as shown in Listing 19-13 The SaveViewState method of each style property stores its view state in an appropriate object and returns the object to the SaveViewState method of BaseMasterDetailControl, which in turn puts all these objects, and the object that contains the view state of its base class, in an array and returns the array to its caller Notice that SaveViewState checks whether all the objects that the array contains are null If they are, it returns null If at least one of the objects isn’t null, it returns the whole array 899 c19.indd 899 8/20/07 8:33:32 PM Chapter 19: UpdatePanel and ScriptManager LoadViewState BaseMasterDetailControl overrides LoadViewState to call the LoadViewState methods of its style properties, as shown in Listing 19-13 As you can see, the LoadViewState method of BaseMasterDetailControl retrieves the array of objects that contains the saved view state of its base class and style properties The method then calls the LoadViewState methods of its base class and properties in the order in which the SaveViewState method of BaseMasterDetailControl called their SaveViewState methods The LoadViewState method of each style property loads its view state with the saved view state Adding a Container Control to a Composite Control The BaseMasterDetailControl server control implements a method named AddContainer, shown in Listing 19-13, that encapsulates the code that adds a container control to the Controls collection of the BaseMasterDetailControl control Note that this method is marked as protected virtual to enable others to override it — in order, for example, to raise an event before or after the container is added to the Controls collection Rendering a Container Control The BaseMasterDetailControl server control exposes a method named RenderContainer, shown in Listing 19-13, which encapsulates the code that renders a container This method is marked as protected virtual to enable others to override it Overriding CreateChildControls: One-Stop Shopping for All Your Child Controls The Control class exposes a method named CreateChildControls that you must override to create the child controls that you need in order to assemble your custom control One important thing to keep in mind about child controls is that they’re created on demand Don’t assume that they’re created at a particular stage of your custom control’s life cycle They can be created at any time In other words, the CreateChildControls method can be called at any stage of your custom control’s life cycle to create the child controls This has important consequences One of these is that you must create the child controls of your custom control in one and only one place — the CreateChildControls method Your custom control mustn’t create any of its child controls in any other place If you create your child controls in any other place, they cannot be created on demand because the on-demand child-control creation feature of the ASP.NET Framework is accomplished via calling the CreateChildControls method Think of CreateChildControls as your one-stop shopping place for all your child controls You mustn’t shop anywhere else! Next, I’ll walk you through the implementation of the CreateChildControls method shown in Listing 19-13 This method first calls the Clear method of the Controls collection to clear the collection This ensures that multiple copies of child controls aren’t added to the Controls collection when the CreateChildControls method is called multiple times: Controls.Clear(); 900 c19.indd 900 8/20/07 8:33:32 PM Chapter 19: UpdatePanel and ScriptManager If you examine the implementation of the BaseMasterDetailControl server control, you’ll notice that this method is never called multiple times You may be wondering, then, why you should bother with clearing the collection You’re right as far as the implementation of the BaseMasterDetailControl server control goes, because you’re the author of this control and you can make sure your implementation of it doesn’t call the CreateChildControls method multiple times However, you have no control over others when they’re deriving from your control to author their own custom controls There’s nothing that would stop them from calling the CreateChildControls method multiple times This example shows that when you’re writing a custom control you must take the subclasses of your custom control into account Then it takes the following actions for each cell shown in Figure 19-7 to create the child control that goes into the cell: ❑ It calls the CreateContainer method to create the container control that represents the cell For example, the following call to the CreateContainer method creates the container control that represents the cell number in Figure 19-7: detailContainer = CreateContainer(ContainerType.Detail); ❑ It calls the CreateContainerChildControls method and passes the container control into it As I mentioned earlier, the CreateContainerChildControls method creates the child controls, initializes them, and adds them to the container control For example, the following call to the CreateContainerChildControls method creates the detail server control and adds it to the detailContainer server control: CreateContainerChildControls(detailContainer); ❑ It calls the AddContainer method to add the container control to the BaseMasterDetailControl server control For example, the following code adds the container control that represents the cell number in Figure 19-7 to the BaseMasterDetailControl control: AddContainer(detailContainer); After all the child controls are created, the method then sets the ChildControlsCreated property to true: ChildControlsCreated = true; As I mentioned, the child controls aren’t created at any particular stage of your custom control’s life cycle They’re created on demand This means that the CreateChildControls method can be called multiple times, though this will waste server resources because this method recreates the child controls every single time it’s called, regardless of whether or not the child controls have already been created To address this problem, the Control class exposes a method named EnsureChildControls and a Boolean property named ChildControlsCreated The EnsureChildControls method checks whether the ChildControlsCreated property is set to false If it is, the method first calls the CreateChildControls method and then sets the ChildControlsCreated property to true The EnsureChildControls method uses this property to avoid multiple invocations of the CreateChildControls method 901 c19.indd 901 8/20/07 8:33:33 PM Chapter 19: UpdatePanel and ScriptManager That is why your custom control’s implementation of the CreateChildControls method must set the ChildControlsCreated property to true to signal the EnsureChildControls method that child controls have been created and the CreateChildControls mustn’t be called again Overriding the TagKey Property Your custom control must use the TagKey property to specify the HTML element that will contain the entire contents of your custom control — that is, the containing element of your custom control Since BaseMasterDetailControl displays its contents in a table, the control overrides the TagKey property to specify the table HTML element as its containing element (see Listing 19-13) Overriding the CreateControlStyle Method Your custom control must override the CreateControlStyle method to specify the appropriate Style subclass The properties of this Style subclass are rendered as CSS style attributes on the containing HTML element Since BaseMasterDetailControl uses a table HTML element as its containing element, it overrides the CreateControlStyle method to use a TableStyle instance (see Listing 19-13) The TableStyle class exposes properties such as GridLines, CellSpacing, CellPadding, HorizontalAlign, and BackImageUrl that are rendered as CSS table style attributes Exposing Style Properties When you override the CreateControlStyle method, you must also define new style properties for your custom control that expose the corresponding properties of the Style subclass This provides page developers with a convenient mechanism to set the CSS style properties of the containing HTML element BaseMasterDetailControl exposes five properties named GridLines, CellSpacing, CellPadding, HorizonalAlign, and BackImageUrl that correspond to the properties of the TableStyle class with the same names as shown in Listing 19-13 Overriding the RenderContents Method The CreateChildControls method is where you create and initialize the child controls that you need in order to assemble your custom control The RenderContents method is where you the assembly — that is, where you assemble your custom control from the child controls First you need to understand how the default implementation (the WebControl class’s implementation) of the RenderContents method assembles your custom control from the child controls The WebControl class’s implementation of RenderContents calls the Render method of its base class, the Control class: protected internal virtual void RenderContents(HtmlTextWriter writer) { base.Render(writer); } 902 c19.indd 902 8/20/07 8:33:33 PM Chapter 19: UpdatePanel and ScriptManager Render calls the RenderChildren method of the Control class: protected internal virtual void Render(HtmlTextWriter writer) { RenderChildren(writer); } RenderChildren calls the RenderControl methods of the child controls in the order in which they are added to the Controls collection: protected internal virtual void RenderChildren(HtmlTextWriter writer) { foreach (Control childControl in Controls) childControl.RenderControl(writer); } In conclusion, the default implementation of the RenderContents method assembles the child controls in the order in which the CreateChildControls method adds them to the Controls collection This default assembly of the BaseMasterDetailControl custom control will simply lay down the child controls on the page one after another in a linear fashion, which is not the layout you want As Listing 19-13 shows, the BaseMasterDetailControl server control overrides the RenderContents method to compose or assemble the child controls in a tabular fashion As Figure 19-7 shows, the BaseMasterDetailControl server control renders its contents in a table that consists of two rows The RenderContents method in Listing 19-13 first calls the ApplyContainerStyles method to apply container styles Then, for each table row, it calls the RenderBeginTag method of the HtmlTextWriter object passed in as its argument to render the opening tag of the tr HTML element that represents the row: writer.RenderBeginTag(HtmlTextWriterTag.Tr); It then calls the RenderContainer method to render the masterContainer and detailContainer container controls that represent the cells numbered and 2, respectively, in Figure 19-7: RenderContainer(masterContainer,writer); RenderContainer(detailContainer,writer); Finally, it calls the RenderEndTag method of the HtmlTextWriter object to render the closing tag of the tr HTML element that represents the row: writer.RenderEndTag(); Exposing the Properties of Child Controls Your composite control must expose the properties of its child controls as if they were its own properties in order to enable page developers to treat these properties as attributes on the tag that represents your custom control on an ASP.NET page BaseMasterDetailControl exposes the following properties of its child master and detail controls as its own properties, as shown in Listing 19-13 903 c19.indd 903 8/20/07 8:33:33 PM Chapter 19: UpdatePanel and ScriptManager Since the child controls of your custom composite control are created on demand, there are no guarantees that the child controls are created when the getters and setters of these properties access them That’s why the getters and setters of these properties call EnsureChildControls before they access the respective child controls In general, your custom control must call EnsureChildControls before it accesses any of its child controls Exposing the properties of child controls as the top-level properties of your composite control provides page developers with the following benefits: ❑ They can set the property values of child controls as attributes on the tag that represents your composite control on an ASP.NET page ❑ If your custom composite control doesn’t expose the properties of its child controls as its top-level properties, page developers will have no choice but to use the error-prone approach of indexing the Controls collection of the composite control to access the desired child control and set its properties ❑ They can treat your custom control as a single entity In other words, your composite control enables page developers to set the properties of its child controls as if they were setting its own properties What Your Custom Control Inherits from CompositeControl The ASP.NET CompositeControl provides the basic features that every composite control must support: ❑ Overriding the Controls collection ❑ Implementing INamingInterface ❑ Overriding the DataBind method ❑ Implementing the ICompositeControlDesignerAccessor interface This interface exposes a single method named RecreateChildControls that enables designer developers to recreate the child controls of a composite control on the designer surface This is useful if you want to develop a custom designer for your composite control A designer is a component that enables page developers to work with your custom composite control in a designer such as Visual Studio (This chapter doesn’t cover designers.) ❑ Overriding the Render method to call EnsureChildControls when the control is in design mode before the actual rendering begins This ensures that child controls are created before they are rendered Overriding the Controls Collection As I discussed earlier, the child controls that you need in order to assemble your custom control aren’t created at any particular phase of your control’s life cycle They’re created on demand Therefore, there are no guarantees that the child controls are created when the Controls collection is accessed That’s 904 c19.indd 904 8/20/07 8:33:34 PM Chapter 19: UpdatePanel and ScriptManager why CompositeControl overrides the Collection property to call the EnsureChildControls method to ensure that the child controls are created before the collection is accessed: public override ControlCollection Controls { get { EnsureChildControls(); return base.Controls; } } INamingContainer Interface As Listing 19-13 shows, the BaseMasterDetailControl server control assigns unique values to the ID properties of all of its child controls For example, it assigns the string value MasterServerControl to the ID property of the master child control This string value is unique in that no other child control of the BaseMasterDetailControl control has the same ID property Now let’s examine what happens when page developers use two instances of the BaseMasterDetailControl control on the same ASP.NET Web page Call the first instance MasterDetailControl_1 and the second instance MasterDetailControl_2 Even though the ID properties of the child controls of each instance are unique within the scope of the instance, they aren’t unique within the page scope, because the ID property of a given child control of one instance is the same as the ID property of the corresponding child control of the other instance For example, the ID property of the master child control of the MasterDetailControl_1 instance is the same as the ID property of the master child control of the MasterDetailControl_2 instance So can the ID property value of a child control of a composite control be used to locate the control? It depends Any code within the scope of the composite control can use the ID property value of a child control to locate it, because the ID property values are unique within the scope of the composite control However, if the code isn’t within the scope of the composite control, it can’t use the ID property to locate the child control on the page if the page contains more than one instance of the composite control Two very good examples of this circumstance are as follows: ❑ The client-side code uses the id attribute of a given HTML element to locate it on the page This scenario is very common, because DHTML is so popular ❑ The page needs to uniquely identify and locate a server control on the page to delegate postback and postback data events to it So what property of the child control should the code from outside the scope of the composite control use to locate the child control on the page? The Control class exposes two important properties named ClientID and UniqueID The page is responsible for assigning values to these two properties that are unique on the page The ClientID and UniqueID properties of a control are rendered as the id and 905 c19.indd 905 8/20/07 8:33:34 PM Chapter 19: UpdatePanel and ScriptManager name HTML attributes on the HTML element that contains the control As you know, client code uses the id attribute to locate the containing HTML element on the page while the page uses the name attribute to locate the control on the page The page doesn’t automatically assign unique values to the ClientID and UniqueID properties of the child controls of a composite control The composite control must implement the INamingContainer interface to request the page to assign unique values to these two properties The INamingContainer interface is a marker interface and doesn’t expose any methods, properties, or events You may wonder how the page assigns unique values to the ClientID and UniqueID properties of the child controls of a composite control A child control, like any other control, inherits the NamingContainer property from the Control class This property refers to the first ascendant control of the child control that implements the INamingContainer interface If your custom composite control implements this interface, it becomes the NamingContainer of its child controls The page concatenates the ClientID of the NamingContainer of a child control to its ID with an underscore character as the separator to create a unique string value for the ClientID of the child control The page does the same thing to create a unique string value for the UniqueID of the child control with one difference — the separator character is a dollar sign character rather than an underscore character BaseMasterDetailControl2 One of the best choices for a detail server control is the ASP.NET DetailsView server control, and one of the best choices for a master server control is the subclasses of BaseDataBoundControl, which include GridView, BulletedList, ListBox, CheckBoxList, RadioButtonList, and so on I’ll implement another abstract base class named BaseMasterDetailControl2 that derives from BaseMasterDetailControl and extends its functionality to use a DetailsView server control as detail server control and a BaseDataBoundControl server control as master server control, as shown in Listing 19-16 Listing 19-16: The BaseMasterDetailControl2 Server Control using using using using using using using using using using using using System; System.Data; System.Configuration; System.Web; System.Web.Security; System.Web.UI; System.Web.UI.WebControls; System.Web.UI.WebControls.WebParts; System.Web.UI.HtmlControls; System.Collections; System.Drawing; System.ComponentModel; 906 c19.indd 906 8/20/07 8:33:34 PM Chapter 19: UpdatePanel and ScriptManager namespace CustomComponents { public abstract class BaseMasterDetailControl2 : BaseMasterDetailControl { protected override Control CreateMaster() { BaseDataBoundControl master = this.CreateBaseDataBoundControlMaster(); master.DataBound += new EventHandler(Master_DataBound); return master; } protected abstract void Master_DataBound(object sender, EventArgs e); protected abstract BaseDataBoundControl CreateBaseDataBoundControlMaster(); protected override Control CreateDetail() { DetailsView detail = new DetailsView(); detail.AllowPaging = false; detail.AutoGenerateDeleteButton = true; detail.AutoGenerateEditButton = true; detail.AutoGenerateInsertButton = true; detail.AutoGenerateRows = true; detail.ID=”DetailDetailsView”; return detail; } protected override void RegisterDetailEventHandlers() { ((DetailsView)Detail).ItemDeleted += new DetailsViewDeletedEventHandler(UpdateMaster); ((DetailsView)Detail).ItemInserted += new DetailsViewInsertedEventHandler(UpdateMaster); ((DetailsView)Detail).ItemUpdated += new DetailsViewUpdatedEventHandler(UpdateMaster); } public string MasterDataSourceID { get { return ((BaseDataBoundControl)Master).DataSourceID; } set { ((BaseDataBoundControl)Master).DataSourceID = value; } } (continued) 907 c19.indd 907 8/20/07 8:33:35 PM Chapter 19: UpdatePanel and ScriptManager Listing 19-16 (continued) public string DetailDataSourceID { get { return ((DetailsView)Detail).DataSourceID; } set { ((DetailsView)Detail).DataSourceID = value; } } } } CreateMaster As you can see from Listing 19-16, the CreateMaster method first invokes another method named CreateBaseDataBoundControlMaster to create and return a BaseDataBoundControl server control as the master server control: BaseDataBoundControl master = this.CreateBaseDataBoundControlMaster(); Next, it registers a method named Master_DataBound as event handler for the DataBound event of the master server control: master.DataBound += new EventHandler(Master_DataBound); As you’ll see later, the master server control is normally bound to an ASP.NET data source control such as SqlDataSource A BaseDataBoundControl server control raises the DataBound event every time it is bound or rebound to the underlying data source control This normally happens when the DataBind method of the control is invoked Since rebinding the master server control causes the control to download fresh data from the underlying data store and to reload, you need to ensure that the selected record is set back to the original record if the fresh data contains the original record That is why the BaseMasterDetailControl2 registers the Master_DataBound method as an event handler for the DataBound event of the master server control As Listing 19-16 shows, the CreateBaseDataBoundControlMaster method is an abstract method and must be implemented by the subclasses of BaseMasterDetailControl2 This allows each subclass to use a different subclass of BaseDataBoundControl as a master server control: protected abstract BaseDataBoundControl CreateBaseDataBoundControlMaster(); As you can see from Listing 19-16, the Master_DataBound is an abstract method and must be implemented by the subclasses of BaseMasterDetailControl2 This allows each subclass to perform tasks specific to the specific type of the BaseDataBoundControl server control being used: protected abstract void Master_DataBound(object sender, EventArgs e); 908 c19.indd 908 8/20/07 8:33:35 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager The _initializeInternal Method of the Client-Side PageRequestManager Understanding the internal implementation of the _initializeInternal method of the current PageRequestManager instance requires a good understanding of the two common types of page postback Therefore, before diving into the implementation of this method, we need to study these two different types The first relies on the Submit button As you know, when the user clicks the Submit button, the form DOM element raises the submit event and consequently invokes the onsubmit event handler If the onsubmit event handler does not return false, the browser takes these steps: Collects the names and values of the form elements Creates an HTTP POST request Generates a list of items separated by the & character, where each item contains the name and value of a form element Each item consists of two parts separated by the equals sign (=), the first containing the name of the form element and the second containing the value Adds the list of items to the body of the request Sets the request headers, such as Content-Type, Content-Length, Host, etc Submits the request to the server synchronously The onsubmit event handler normally validates the values of the form elements and returns false to cancel the form submission if the validation fails The main problem with the first type of page postback is its strict reliance on the Submit button for form submission There are times when the form must be submitted via DOM elements other than the Submit button For example, you may want the form submission to occur when the user selects an item from a certain HTML element This is where the second type of page postback comes into play This type relies on the doPostBack JavaScript function The ASP.NET server controls, such as the DropDownList, register this JavaScript function as an event handler for one of their events For example, the DropDownList server control registers the _doPostBack JavaScript function as event handler for the onchange event of the HTML element associated with the server control if the AutoPostBack property of the server control is set to true Listing 22-5 contains the definition of the _doPostBack JavaScript function Since this JavaScript function is a global one, it is automatically considered as a method on the window object As you can see, _doPostBack takes two arguments The first is the value of the UniqueID property of the server control that caused the postback For example, in the case of the DropDownList server control, this will be the value of the UniqueID property of the DropDownList control itself The second argument is optional In the case of the DropDownList server control, this will be the value of the value property of the selected element of the element associated with the server control As you can see from Listing 22-5, the _doPostBack JavaScript function takes the following steps First, it invokes the onsubmit event handler Recall that this event handler normally validates the values of the form elements and returns false if the validation fails As Listing 22-5 shows, if the onsubmit event handler does not return false, the _doPostBack JavaScript function assigns its first parameter to the value property of a hidden field named EVENTTARGET and its second parameter to the value 1040 c22.indd 1040 8/20/07 8:38:10 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager property of a hidden field named EVENTARGUMENT For example, in the case of the DropDownList server control, the _doPostBack JavaScript function assigns the UniqueID property value of the server control to the value property of the EVENTTARGET hidden field and the value of the value property of the selected option subelement of the select element associated with the server control to the value property of the EVENTARGUMENT hidden field theForm. EVENTTARGET.value = eventTarget; theForm. EVENTARGUMENT.value = eventArgument; Finally, the _doPostBack JavaScript function invokes the submit method on the form DOM element to submit the values of the form elements to the server When the submit method is invoked, under the hood, the browser takes these steps: Collects the names and values of the form elements Creates an HTTP POST request Generates a list of items separated by the & character, where each item contains the name and value of a form element Each item consists of two parts separated by the equals sign (=), the first containing the name of the form element and the second containing the value Adds to the body of the request the list of items shown in Step Sets the request headers, such as Content-Type, Content-Length, Host, etc Submits the request to the server synchronously Note that the preceding six steps are the same ones the browser takes for the first type of page postback — that is, the page postback via the Submit button In other words, both the page postback via the Submit button and the page postback via the _doPostBack JavaScript function rely on the browser to take these steps Listing 22-5: The Standard doPostBack JavaScript Function Both the page postback via the Submit button and page postback via the _doPostBack JavaScript function suffer from the following fundamental shortcomings: 1041 c22.indd 1041 8/20/07 8:38:10 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager ❑ The browser submits the request to the server synchronously A synchronous request is blocking, meaning that the end user cannot interact with the page until the server response arrives This dramatically degrades the responsiveness, performance, and usability of a Web application that relies heavily on normal synchronous page postbacks ❑ In both types of page postbacks, when the server response finally arrives, the entire page reloads even though only a small portion of the page requires refreshing This also dramatically degrades the responsiveness, performance, and usability of a graphic-heavy Web page, which takes a lot of time to re-render As you’ll see in this chapter, the current PageRequestManager instance resolves both of these problems, as follows: ❑ Unlike page postback via the Submit button or the _doPostBack JavaScript function, it does not rely on the browser’s default synchronous form submission Instead, the current PageRequestManager instance uses the ASP.NET AJAX client-server communication layer discussed in previous chapters to make asynchronous page postback requests to the server ❑ Unlike page postback via the Submit button or the _doPostBack JavaScript function, it does not rely on the browser’s default rendering mechanism, which re-renders the entire page when the server response arrives Instead, the current PageRequestManager instance uses the ASP.NET AJAX client-side framework to refresh only those parts of the page that need refreshing Now that you have a good understanding of the two main types of page postbacks and their shortcomings, you’re ready to dive into the internal implementation of the _initializeInternal method of the PageRequestManager, as shown in Listing 22-6 Listing 22-6: The _initializeInternal Intance Method of the PageRequestManager Client Class function Sys$WebForms$PageRequestManager$_initializeInternal(scriptManagerID, formElement) { this._scriptManagerID = scriptManagerID; this._form = formElement; this._detachAndStoreOriginalFormOnSubmit(); this._registerHandlerForFormSubmitEvent (); this._detachAndStoreOriginalDoPostBack(); this._attachNewDoPostBack(); this._registerHandlerForWindowLoadEvent(); this._registerHandlerForFormClickEvent(); this._registerHandlerForWindowUnloadEvent(); this._storeOriginalFormAction(); } As you can see, this method takes two arguments The first is a string that contains the value of the UniqueID property of the ScriptManager server control The second references the form DOM element of the current page The method assigns the following two parameters to the _scriptManagerID and _form private fields of the current PageRequestManager instance: this._scriptManagerID = scriptManagerID; this._form = formElement; 1042 c22.indd 1042 8/20/07 8:38:10 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager Next, the _initializeInternal method calls the _detachAndStoreOriginalFormOnSubmit method shown in the following code listing As you can see, this method detaches the onsubmit method from the form DOM element and stores it in a local field named _onsubmit for future reference: function Sys$WebForms$PageRequestManager$_detachAndStoreOriginalFormOnSubmit() { this._onsubmit = this._form.onsubmit; this._form.onsubmit = null; } Then the _initializeInternal method calls the _registerHandlerForFormSubmitEvent method shown in the following code listing As you can see, this method first creates a delegate that represents the _onFormSubmit method of the current PageRequestManager instance, and then registers this delegate as an event handler for the submit event of the form element: function Sys$WebForms$PageRequestManager$_registerHandlerForFormSubmitEvent () { this._onFormSubmitHandler = Function.createDelegate(this, this._onFormSubmit); Sys.UI.DomEvent.addHandler(this._form, ‘submit’, this._onFormSubmitHandler); } Recall that page postback via the Submit button causes the form DOM element to fire its submit event Since the current PageRequestManager instance has registered the _onFormSubmitHandler delegate for this event, this delegate and consequently the _onFormSubmit method of the current PageRequestManager instance are automatically invoked This allows the current PageRequestManager instance to take complete control over the first type of page postback mechanism before the page is actually posted back to the server As you’ll see later, the _onFormSubmit method of the current PageRequestManager instance will first determine whether the form submission must be done asynchronously If so, it will bypass the browser’s default synchronous form submission and use the ASP.NET AJAX client-server communication layer discussed in previous chapters to make an asynchronous page postback to the server If the _onFormSubmit method of the current PageRequestManager instance comes to the conclusion that the form must be submitted synchronously, the method gets out of the way and allows the browser to take over the form submission and submit the form synchronously Now back to the discussion of the implementation of the _initializeInternal method of the PageRequestManager Next, this method calls the _detachAndStoreOriginalDoPostBack method shown in the following code listing As you can see, this method detaches the _doPostBack JavaScript function shown in Listing 22-5 from the window object and stores it in the _originalDoPostBack field of the current PageRequestManager instance for future reference: function Sys$WebForms$PageRequestManager$_detachAndStoreOriginalDoPostBack() { this._originalDoPostBack = window. doPostBack; window. doPostBack = null; } The _initializeInternal method then calls the _attachNewDoPostBack method shown in the following code listing As you can see, this method first creates a delegate that represents the _doPostBack method of the current PageRequestManager instance and then attaches this method to the window object as its _doPostBack method: 1043 c22.indd 1043 8/20/07 8:38:11 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager function Sys$WebForms$PageRequestManager$_attachNewDoPostBack() { window. doPostBack = Function.createDelegate(this, this._doPostBack); } Recall that the second type of page postback invokes the _doPostBack method of the window object to submit the form to the server Since the current PageRequestManager instance has replaced the original _doPostBack method (that is, the one shown in Listing 22-5) with the delegate that represents the _doPostBack method of the current PageRequestManager instance, when a server control such as DropDownList calls the _doPostBack method of the window object, this delegate and consequently the _doPostBack method of the current PageRequestManager instance will be called instead of the original _doPostBack method shown in Listing 22-5 This allows the current PageRequestManager instance to take complete control of the second type of page postback mechanism before the page is actually posted back to the server As you’ll see later in this chapter, the _doPostBack method of the current PageRequestManager instance will first determine whether the form submission must be done asynchronously If so, it will bypass the browser’s default synchronous form submission and use the ASP NET AJAX client-server communication layer discussed in previous chapters to make an asynchronous page postback to the server If the _doPostBack method of the current PageRequestManager instance comes to the conclusion that the form must be submitted synchronously, the method gets out of the way and allows the browser to take over the form submission and submit the form synchronously Now back to the discussion of the implementation of the _initializeInternal method Next, this method calls the _registerHandlerForWindowLoadEvent method shown in the following code listing As you can see, this method first creates a delegate that represents the _pageLoadedInitialLoad method of the current PageRequestManager instance, and then registers this delegate as an event handler for the load event of the window object Therefore, when the window raises its load event, this delegate and consequently the _pageLoadedInitialLoad method of the current PageRequestManager instance are invoked: function Sys$WebForms$PageRequestManager$_registerHandlerForWindowLoadEvent() { this._pageLoadedHandler = Function.createDelegate(this, this._pageLoadedInitialLoad); Sys.UI.DomEvent.addHandler(window, ‘load’, this._pageLoadedHandler); } Next, the _initializeInternal method calls the _registerHandlerFormClickEvent method shown in the following code listing As you can see, this method first creates a delegate that represents the _onFormElementClick method of the current PageRequestManager instance, and then registers this delegate as an event handler for the click event of the form element: function Sys$WebForms$PageRequestManager$_registerHandlerForFormClickEvent() { this._onFormElementClickHandler = Function.createDelegate(this, this._onFormElementClick); Sys.UI.DomEvent.addHandler(this._form, ‘click’, this._onFormElementClickHandler); } Then the _initializeInternal method calls the _registerHandlerForWindowUnloadEvent method shown in the following code listing As you can see, this method first creates a delegate that represents the _onWindowUnload method of the current PageRequestManager instance, and then registers this delegate as an event handler for the unload event of the window object: 1044 c22.indd 1044 8/20/07 8:38:11 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager function Sys$WebForms$PageRequestManager$_registerHandlerForWindowUnloadEvent() { this._onWindowUnloadHandler = Function.createDelegate(this, this._onWindowUnload); Sys.UI.DomEvent.addHandler(window, ‘unload’, this._onWindowUnloadHandler); } Finally, the method calls the _storeOriginalFormAction method shown in the following code listing As you can see, this method stores the value of the action property of the form DOM element in a custom property on the form named _initialAction for future reference As you’ll see later, the current PageRequestManager instance uses the initial action value to determine whether a given request is a cross-page postback function Sys$WebForms$PageRequestManager$_storeOriginalFormAction() { this._form._initialAction = this._form.action; } Figure 22-3 updates Figure 22-2 with the method calls that the _initializeInternal method triggers Note that Figure 22-3 inherits the bottom dashed line from Figure 22-2 Recall that this dashed line represents the method calls triggered by the call into the _updateControls method PageRequestManager Instantiation/Initialization PageRequestManager _initialize(scriptManagerUniqueID, formElement) _ensureSinglePageRequestManagerInstance ( ) getInstance ( ) _createPageRequestManagerInstance ( ) _initializePageRequestManagerInstance (scriptManagerUniqueID, formElement) _initializeInternal (scriptManagerUniqueID, formElement) _detachAndStoreOriginalFormOnSubmit ( ) _registerHandlerForFormSubmitEvent ( ) _detachAndStoreOriginalDoPostBack ( ) _attachNewDoPostBack ( ) _registerHandlerForWindowLoadEvent ( ) _registerHandlerForFormClickEvent ( ) _registerHandlerForWindowUnloadEvent ( ) _updateControls([…], […], […], asyncPostBackTimeout) _storeOrigianlFormAction ( ) Figure 22-3 1045 c22.indd 1045 8/20/07 8:38:11 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager _updateControls Listing 22-7 presents the internal implementation of the _updateControls method of the PageRequestManager This method takes the following four parameters: ❑ updatePanelIDs: This parameter is an array that contains the values of the UniqueID properties of all the UpdatePanel server controls on the current page ❑ asyncPostBackControlIDs: This parameter is an array that contains the values of the UniqueID properties of all the server controls on the current page that cause asynchronous page postbacks ❑ postBackControlIDs: This parameter is an array that contains the values of the UniqueID properties of all the server controls on the current page that cause synchronous page postbacks ❑ asyncPostBackTimeout: This parameter is a string that contains the asynchronous page post- back request timeout Listing 22-7: The _updateControls Method of the PageRequestManager function Sys$WebForms$PageRequestManager$_updateControls(updatePanelIDs, asyncPostBackControlIDs, postBackControlIDs, asyncPostBackTimeout) { if (updatePanelIDs) { this._updatePanelIDs = new Array(updatePanelIDs.length); this._updatePanelClientIDs = new Array(updatePanelIDs.length); this._updatePanelHasChildrenAsTriggers = new Array(updatePanelIDs.length); for (var i = 0; i < updatePanelIDs.length; i++) { this._updatePanelHasChildrenAsTriggers[i] = (updatePanelIDs[i].charAt(0) === ‘t’); this._updatePanelIDs[i] = updatePanelIDs[i].substr(1); this._updatePanelClientIDs[i] = this._uniqueIDToClientID(updatePanelIDs[i].substr(1)); } this._asyncPostBackTimeout = asyncPostBackTimeout * 1000; } else { this._updatePanelIDs = []; this._updatePanelClientIDs = []; this._updatePanelHasChildrenAsTriggers = []; this._asyncPostBackTimeout = 0; } this._asyncPostBackControlIDs = []; this._asyncPostBackControlClientIDs = []; 1046 c22.indd 1046 8/20/07 8:38:12 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager for (var i = 0; i < asyncPostBackControlIDs.length; i++) { Array.add(this._asyncPostBackControlIDs, asyncPostBackControlIDs[i]); Array.add(this._asyncPostBackControlClientIDs, this._uniqueIDToClientID(asyncPostBackControlIDs[i])); } this._postBackControlIDs = []; this._postBackControlClientIDs = []; for (var i = 0; i < postBackControlIDs.length; i++) { Array.add(this._postBackControlIDs, postBackControlIDs [i]); Array.add(this._postBackControlClientIDs, this._uniqueIDToClientID(postBackControlIDs [i])); } } The _updateControls method takes the following steps First, it instantiates the _updatePanelIDs, _updatePanelClientIDs, and _updatePanelHasChildrenAsTriggers array fields of the current PageRequestManager instance: this._updatePanelIDs = new Array(updatePanelIDs.length); this._updatePanelClientIDs = new Array(updatePanelIDs.length); this._updatePanelHasChildrenAsTriggers = new Array(updatePanelIDs.length); Next, it iterates through the UniqueID property values in the updatePanelIDs parameter and takes the following actions for each enumerated UniqueID property value (Keep in mind that this value consists of two substrings, the first containing the character t or f, and the second containing the actual UniqueID property value): ❑ As mentioned, the current PageRequestManager instance contains a private array field named _updatePanelIDs that contains the UniqueID property values of all the UpdatePanel server controls on the current page The _updateControls method retrieves the second substring of the enumerated value and adds it to the _updatePanelIDs array: this._updatePanelIDs[i] = updatePanelIDs[i].substr(1); ❑ The current PageRequestManager instance also contains a private array field named _updatePanelClientIDs that contains the ClientID property values of all the UpdatePanel server controls on the current page The _updateControls method calls the _uniqueIDToClientID method to return the ClientID property value associated with the UniqueID property value and adds this ClientID property value to the _updatePanelClientIDs array: this._updatePanelClientIDs[i] = this._uniqueIDToClientID(this._updatePanelIDs[i]); ❑ The current PageRequestManager instance contains a private Boolean array field named _updatePanelHasChildrenAsTriggers that contains one Boolean value for each UpdatePanel server control on the page, which specifies whether its child server controls trigger partial page updates The _updateControls method retrieves the first substring of the enumerated value If this substring contains the character t, asynchronous updates of the server control are triggered by the child controls of the UpdatePanel server control whose UniqueID property is given by the second substring, and the _updateControls method adds the Boolean value of true to the _updatePanelHasChildrenAsTriggers collection Otherwise it adds false 1047 c22.indd 1047 8/20/07 8:38:12 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager this._updatePanelHasChildrenAsTriggers[i] = (updatePanelIDs[i].charAt(0) === ‘t’); ❑ The current PageRequestManager instance also contains an integer field named _asyncPostBackTimeout that specifies the timeout (in seconds) for all asynchronous page postback requests The _updateControls method converts to seconds the value passed into it as its last parameter, and assigns the value to this field: this._asyncPostBackTimeout = asyncPostBackTimeout * 1000; Next, the _updateControls method iterates through the UniqueID property values in the asyncPostBackControlIDs array and takes these actions for each value: ❑ The current PageRequestManager instance contains a private array field named _asyncPostBackControlIDs that contains the UniqueID property values of all the server controls on the current page that trigger asynchronous page postbacks The _updateControls method adds the enumerated UniqueID property value to this array field: Array.add(this._asyncPostBackControlIDs, asyncPostBackControlIDs[i]); ❑ The current PageRequestManager instance contains a private array field named _asyncPostBackControlClientIDs that contains the ClientID property values of all the server controls on the current page that trigger asynchronous page postbacks The _updateControls method first calls the _uniqueIDToClientID method to return the ClientID property value associated with the enumerated UniqueID property value, and then adds this return value to this array field: Array.add(this._asyncPostBackControlClientIDs, this._uniqueIDToClientID(asyncPostBackControlIDs[i])); Next, the _updateControls method iterates through the UniqueID property values in the postBackControlIDs array and takes these actions for each value: ❑ The current PageRequestManager instance contains a private array field named _postBackControlIDs that contains the UniqueID property values of all the server controls on the current page that trigger synchronous page postbacks The _updateControls method adds the enumerated UniqueID property value to this array field: Array.add(this._postBackControlIDs, postBackControlIDs[i]); ❑ The current PageRequestManager instance contains a private array field named _postBackControlClientIDs that contains the ClientID property values of all the server controls on the current page that trigger asynchronous page postbacks The _updateControls method first calls the _uniqueIDToClientID method to return the ClientID property value associated with the enumerated UniqueID property value, and then adds this return value to this array field: Array.add(this._postBackControlClientIDs, this._uniqueIDToClientID(postBackControlIDs[i])); As Listing 22-8 shows, the _uniqueIDToClientID method takes an UniqueID value as its argument and replaces all the dollar signs ($) with the underscore character (_) 1048 c22.indd 1048 8/20/07 8:38:12 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager The UniqueID and ClientID property values of an ASP.NET server control are read-only, which means that only the ASP.NET can set their values The UniqueID property value of a server control is a string that consists of two substrings separated by the dollar sign ($), the first containing the value of the ID property of the server control and the second containing the value of the UniqueID property of the parent of the server control The ClientID property value of a server control is a string that consists of two substrings separated by the underscore character (_), the first containing the value of the ID property of the server control and the second containing the value of the ClientID property of the parent of the server control As you can see, the only difference between the UniqueID and the ClientID property values of a server control is the separator That is why the _uniqueIDToClientID method replaces the dollar signs with the underscore characters to arrive at the ClientID property value Listing 22-8: The _uniqueIDToClientID Method of the PageRequestManager function Sys$WebForms$PageRequestManager$_uniqueIDToClientID(uniqueID) { // Convert unique IDs to client IDs by replacing all ‘$’ with ‘_’ return uniqueID.replace(/\$/g, ‘_’); } Figure 22-4 updates Figure 22-3 with the new method calls PageRequestManager Instantiation/Initialization PageRequestManager _initialize(scriptManagerUniqueID, formElement) _ensureSinglePageRequestManagerInstance ( ) getInstance ( ) _createPageRequestManagerInstance ( ) _initializePageRequestManagerInstance (scriptManagerUniqueID, formElement) _initializeInternal (scriptManagerUniqueID, formElement) _detachAndStoreOriginalFormOnSubmit ( ) _registerHandlerForFormSubmitEvent ( ) _detachAndStoreOriginalDoPostBack ( ) _attachNewDoPostBack ( ) _registerHandlerForWindowLoadEvent ( ) _registerHandlerForFormClickEvent ( ) _registerHandlerForWindowUnloadEvent ( ) _updateControls([…], […], […], asyncPostBackTimeout) _updatePanelClientIDs add ( ) _asyncPostBackTimeout add ( ) _postBackControlIDs add ( ) _storeOrigianlFormAction ( ) _updatePanelIDs _updatePanelHasChildrenAs add ( ) add ( ) _asyncPostBackControlIDs _asyncPostBackControlClientIDs add ( ) add ( ) _postBackControlClientIDs add ( ) Figure 22-4 1049 c22.indd 1049 8/20/07 8:38:13 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager The _pageLoadedInitialLoad Method of the Client-Side PageRequestManager When the current page is finally loaded, the window object raises the load event and calls the _pageLoadedHandler delegate, which in turn calls the _pageLoadedInitialLoad instance method of the current client-side PageRequestManager instance, as shown in Listing 22-9 Listing 22-9: The _pageLoadedInitialLoad Method of the PageRequestManager Client Class function Sys$WebForms$PageRequestManager$_pageLoadedInitialLoad(evt) { this._pageLoaded(true); } As you can see, the _pageLoadedInitialLoad method calls the _pageLoaded method of the current client-side PageRequestManager instance, passing in true as its argument The _pageLoaded Method of the Client-Side PageRequestManager Listing 22-10 presents the internal implementation of the _pageLoaded method of the client-side PageRequestManager instance Listing 22-10: The _pageLoaded Method of the PageRequestManager Client Class function Sys$WebForms$PageRequestManager$_pageLoaded(initialLoad) { var handler = this._get_eventHandlerList().getHandler(“pageLoaded”); if (handler) handler(this, this._getPageLoadedEventArgs(initialLoad)); if (!initialLoad) { // If this isn’t the first page load (i.e we are doing an async postback), we // need to re-raise the Application’s load event Sys.Application.raiseLoad(); } } This method first calls the _get_eventHandlerList method to return a reference to the EventHandlerList object that contains all the event handlers registered for the events of the current client-side PageRequestManager instance, and then calls the getHandler method on the EventHandlerList to return a reference to the JavaScript function whose invocation automatically 1050 c22.indd 1050 8/20/07 8:38:13 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager invokes all the event handlers registered for the pageLoaded event of the current client-side PageRequestManager instance I’ll discuss this event later in the chapter var handler = this._get_eventHandlerList().getHandler(“pageLoaded”); Next, the _pageLoaded method calls the _getPageLoadedEventArgs method to create and return a PageLoadedEventArgs object As you’ll see later, the PageLoadedEventArgs class is the event data class for the pageLoaded event of the client-side PageRequestManager instance var pageLoadedEventArgs = this._getPageLoadedEventArgs(initialLoad); Then it calls the JavaScript function, passing in a reference to the current client-side PageRequestManager instance and a reference to the PageLoadedEventArgs instance This JavaScript function in turn calls all the event handlers registered for the pageLoaded event of the current client-side PageRequestManager instance, passing in the same two references if (handler) handler(this, this._getPageLoadedEventArgs(initialLoad)); This enables you to perform application-specific tasks by registering an event handler for the pageLoaded event of the current PageRequestManager instance Figure 22-5 updates Figure 22-4 with the latest method calls This wraps up our discussion of the instantiation/initialization process of the current client-side PageRequestManager instance Keep in mind that this process occurs only for the first request and subsequent normal synchronous page postback requests In other words, it does not occur for subsequent asynchronous page postback requests In summary, the previous chapter followed the first request from the time it arrived in ASP.NET to the time the server response text, including the PageRequestManager instantiation/initialization script block — such as the one shown in Listing 22-1 — was sent back to the client The previous sections of this chapter then followed this server response text from the time it arrived on the client side to the time the instantiation and initialization of the current PageRequestManager instance were completed Now the current PageRequestManager instance is sitting there waiting for the first or second type of page postback to occur Recall that there are two types of page postbacks, via the Submit button and via the _doPostBack method of the window object As we discussed earlier, as soon as the first type of page postback occurs, the _onFormSubmit method of the current PageRequestManager instance will intercept it before the page is actually posted back to the server; and as soon as the second type of page postback occurs, the _doPostBack method of the current PageRequestManager instance will intercept it before the page is actually posted back to the server Both the _onFormSubmit and _doPostBack methods of the current PageRequestManager instance will first determine whether the page postback must be done asynchronously If so, both methods bypass the browser’s default synchronous form submission and use the ASP.NET AJAX client-server communication layer (discussed in previous chapters) to submit the form asynchronously If these methods determine that the page postback must be done synchronously, they simply get out of the way and let the browser’s default synchronous form submission take over and submit the form synchronously 1051 c22.indd 1051 8/20/07 8:38:13 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager PageRequestManager Instantiation/Initialization PageRequestManager _initialize(scriptManagerUniqueID, formElement) _ensureSinglePageRequestManagerInstance ( ) getInstance ( ) _createPageRequestManagerInstance ( ) _initializePageRequestManagerInstance (scriptManagerUniqueID, formElement) _initializeInternal (scriptManagerUniqueID, formElement) _detachAndStoreOriginalFormOnSubmit ( ) _registerHandlerForFormSubmitEvent ( ) _detachAndStoreOriginalDoPostBack ( ) _attachNewDoPostBack ( ) _registerHandlerForWindowLoadEvent ( ) _registerHandlerForFormClickEvent ( ) _registerHandlerForWindowUnloadEvent ( ) _updateControls([…], […], […], asyncPostBackTimeout) _updatePanelClientIDs add ( ) _asyncPostBackTimeout add ( ) _postBackControlIDs add ( ) _pageLoaded ( ) _storeOrigianlFormAction ( ) _updatePanelIDs _updatePanelHasChildrenAs add ( ) add ( ) _asyncPostBackControlIDs _asyncPostBackControlClientIDs add ( ) add ( ) _postBackControlClientIDs add ( ) _pageLoadedInitialLoad ( ) window EventHandlerList getHandler (“pageLoaded”) _getPageLoadedEventArgs ( ) Figure 22-5 The page Loaded Event The previous sections followed the current client-side PageRequestManager instance through its instantiation/initialization life cycle phases As you can see from Figure 22-5, the current client-side PageRequestManager instance fires its pageLoaded event at the end of its instantiation/initialization phase to enable you to perform application-specific tasks that must be performed right after the current client-side PageRequestManager instance is instantiated and initialized The client-side PageRequestManager class uses the standard ASP.NET AJAX event-implementation pattern to implement its pageLoaded event as follows: The PageRequestManager class defines a collection property of type EventHandlerList to store all the event handlers registered for the events of the current client-side PageRequestManager instance It defines a getter method that returns a reference to this EventHandlerList object: 1052 c22.indd 1052 8/20/07 8:38:13 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager function Sys$WebForms$PageRequestManager$_get_eventHandlerList() { if (!this._events) this._events = new Sys.EventHandlerList(); return this._events; } It defines a method named add_pageLoaded that enables you to register event handlers for the pageLoaded event of the current client-side PageRequestManager instance As the following code fragment shows, this method first calls the get_eventHandlerList method on the current PageRequestManager instance to return a reference to the EventHandlerList object Then it calls the addHandler method on this EventHandlerList object to register the specified handler as an event handler for the pageLoaded event of the current PageRequestManager instance: function Sys$WebForms$PageRequestManager$add_pageLoaded(handler) { this._get_eventHandlerList().addHandler(“pageLoaded”, handler); } It defines a method named remove_pageLoaded that allows you to unregister an event handler registered for the pageLoaded event of the instance: function Sys$WebForms$PageRequestManager$remove_pageLoaded(handler) { this._get_eventHandlerList().removeHandler(“pageLoaded”, handler); } It defines a method named _pageLoaded that raises the pageLoaded event and consequently invokes all the event handlers registered for this event, as shown in Listing 22-10 The pageLoaded event, like any other, is associated with an event data class whose instance acts as a container for the associated event data The event data class associated with the pageLoaded event is an ASP.NET AJAX client class named PageLoadedEventArgs Listing 22-11 presents the internal implementation of the PageLoadedEventArgs class Listing 22-11: The Internal Implementation of the PageLoadedEventArgs Class Sys.WebForms.PageLoadedEventArgs = function Sys$WebForms$PageLoadedEventArgs(panelsUpdated, panelsCreated, dataItems) { /// /// /// Sys.WebForms.PageLoadedEventArgs.initializeBase(this); this._panelsUpdated = panelsUpdated; this._panelsCreated = panelsCreated; // Need to use “new Object()” instead of “{}”, since the latter breaks code // coverage this._dataItems = dataItems || new Object(); } (continued) 1053 c22.indd 1053 8/20/07 8:38:14 PM Chapter 22: ASP.NET AJAX Client-Side PageRequestManager Listing 22-11 (continued) function Sys$WebForms$PageLoadedEventArgs$get_dataItems() { /// return this._dataItems; } function Sys$WebForms$PageLoadedEventArgs$get_panelsCreated() { /// return this._panelsCreated; } function Sys$WebForms$PageLoadedEventArgs$get_panelsUpdated() { /// return this._panelsUpdated; } Sys.WebForms.PageLoadedEventArgs.prototype = { get_dataItems: Sys$WebForms$PageLoadedEventArgs$get_dataItems, get_panelsCreated: Sys$WebForms$PageLoadedEventArgs$get_panelsCreated, get_panelsUpdated: Sys$WebForms$PageLoadedEventArgs$get_panelsUpdated } Sys.WebForms.PageLoadedEventArgs.registerClass(‘Sys.WebForms.PageLoadedEventArgs’, Sys.EventArgs); As you can see, the constructor of the PageLoadedEventArgs class takes three parameters The first is an array that contains the list of updated UpdatePanel server controls on the current page, the second is an array that contains the list of newly created UpdatePanel server controls, and the last is optional The last parameter is null when the pageLoaded event is raised at the end of the instantiation/initialization of the current PageRequestManager instance However, as you’ll see in the following chapters, the current client-side PageRequestManager instance also raises the pageLoaded event when it is processing the server response to an asynchronous page postback request where the last parameter of the constructor of the PageLoadedEventArgs class comes into play As you can see from Listing 22-11, the constructor of the PageLoadedEventArgs class stores its parameters in private fields named _panelsUpdated, _panelsCreated, and _dataItems Note that the PageLoadedEventArgs class exposes three getters named get_panelsUpdated, get_panelsCreated, and get_dataItems, that return these private fields Now let’s revisit Listing 22-10, as shown again in the following code listing: function Sys$WebForms$PageRequestManager$_pageLoaded(initialLoad) { var handler = this._get_eventHandlerList().getHandler(“pageLoaded”); if (handler) { var args = this._getPageLoadedEventArgs(initialLoad)); handler(this, args); } 1054 c22.indd 1054 8/20/07 8:38:14 PM ... Font-Bold=”True” ForeColor=”White” />