This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


MIND

Beyond the Browser

beyond@microsoft.com

Download the code (6KB)
Ken Spencer

Advanced PageObjects
L
ast month (December 1998) I showed you how to create properties in an ASP page that can be used by that page and other pages in an application. The PageObject is the key to creating and using properties and methods in your application and sharing them across pages. It makes page reuse much easier than the process of creating include files and building the code into them, or copying and pasting snippets around your application.
      This month I'll look at how you can use the PageObject to expose methods that can be accessed by other pages. The sample application that I'll use ties database actions in one page to a function that performs a different database action in another page. The resulting application interface is shown in Figure 1.
Figure 1: The Customer Information Page
      Figure 1: The Customer Information Page

      This page uses the Visual InterDev 6.0 data environment and several design-time controls to provide the key database interface. The user can scroll through the page using the navigation controls at the bottom. When the user clicks the Get Order Total button, the application retrieves the total dollar amount for all of the customer's orders from the server. The PageObject provides the functionality for retrieving that information. Let's take a look at how this page was constructed.

Creating the Base Functionality
      First, make sure you have SQL Server 7.0 installed (it had just been released at press time) and Visual InterDev 6.0 fired up. Then create a blank ASP file and name it CustomerdInformation.asp. Your project will also need a data connection to the Northwind database (or the database that you are using). To create the connection, right-click Global.asa and select Add Data Connection. You can either select an existing data source or create a new one. Once you have selected the data source, you must name the connection. In this sample, I've named the connection Northwind.
      Next, add a recordset to the page and set its SQL statement to:


 SELECT * FROM customers
I used a Data Command object to create the recordset. If you prefer, you can drop a Recordset design-time control on the page directly.
      Next, add Textbox design-time controls that will be used to display the values for each field. For each textbox, open its properties and set the Recordset property to the name of the recordset you created in the last step. Set the Field property to the name of the field that control will display, then apply the changes. You can save time by placing all the controls on the form first. Then open the properties for the first one, change them, and click Apply instead of OK. Then you'll be able to click the next control in the editor and the property page will change to display the properties for that control. Repeat this process for each control.
      Next, place a RecordsetNavBar design-time control on the page. Drop this control after the last Textbox design-time control. Open the properties for the control and change the Recordset property to the recordset you created earlier. This completes the basic functionality for the page. Now save the page and view it in the browser to make sure it works correctly.
      Now you need to add the PageObject design-time control to the page. The PageObject will do two things. First, it will let you store the current customer ID and expose it as a property for use in other pages. Second, when you create methods on this page, the PageObject control will let you access them from a separate ASP file.
      To add the PageObject control, drag it from the toolbox and drop it on the page after the other controls that are already there. Open the PageObject, click the Properties tab, and add the property CustomerID. The property page should look like the one shown in Figure 2. When it does, click OK to close the property pages.
Figure 2: PageObject Properties
      Figure 2: PageObject Properties

      Now, click the Source tab of the editor to display the page in source view. Open the script outline by clicking the appropriate tab under the toolbox or by selecting View | Other Windows from the menu. Open Server Objects & Events and find your Recordset object. Open it and double-click the onrowenter event. This will enter the event block in the page. Next, change the event to look like this:

 <SCRIPT ID=serverEventHandlersVBS LANGUAGE=vbscript RUNAT=Server>
 Sub Recordset1_onrowenter()
     thisPage.setCustomerID(Recordset1.fields.getValue("CustomerID"))
 End Sub
This code sets the CustomerID property in the PageObject to the current CustomerID from the current row in the recordset. This event fires each time the recordset changes to a new record.

Adding the Order Total Method
      Now you are ready to create the method that will retrieve your order total information. Create a new page and name it CustomerStatus.asp. Next, open this page in the source editor. This page contains a method to retrieve the total orders for a customer. The method pulls the information from the database and calculates the total, and it's useful in many pages throughout the application. Anywhere you need to get the total dollar value of a customer's orders, you can retrieve it using this method instead of having to reinvent the wheel.
      The first thing you'll add to the page is the database recordset. Drag a Recordset design-time control and drop it on the page. Then open its property pages. Click the Implementation tab and uncheck the "Automatically open the recordset" checkbox. This will allow you to open the recordset in your ASP code when you're ready. Next, click the General tab, then click the SQL Statement radio button. Click the SQL Builder command button to open Query Designer. Create the following SQL statement:


 SELECT Orders.OrderID, Orders.CustomerID, "Order Details".UnitPrice,
 "Order Details".Quantity, "Order Details".UnitPrice * "Order
 Details".Quantity AS LineItemTotal FROM Orders INNER JOIN "Order Details"
 ON Orders.OrderID = " Order Details".OrderID WHERE (Orders.CustomerID = ?)
This statement links the Orders and Order Details tables. It also creates an expression named LineItemTotal. The expression is created using this syntax:

 "Order Details".UnitPrice * "Order Details".Quantity AS LineItemTotal
      You can create the expression by entering the first part of the statement (up to the AS) as the Column property in the grid pane, then entering LineItemTotal into the Alias column. This returns LineItemTotal as a field in the recordset, making it unnecessary to do the math in your function. In fact, you could calculate the orders completely in SQL, but I've chosen to do it in the recordset to demonstrate how to walk through it in your ASP code. An "= ?" entry in the Criteria column sets this up as your parameter for this query. This will let you pass in the CustomerID value to the query after the user selects a customer. You can see the criteria and the entire SQL statement in Figure 3.
      Next, let's create your method. First, create a script block in your code. You can enter the <% and %> tags directly into code, or right-click in the page where you want the script block and select Script Block from the shortcut menu. Next, select Server to insert the code block. Now, you can generate the header for the GetOrderTotalByCustomer function. This function can take one parameter, the CustomerID:

 function GetOrderTotalByCustomer(CustomerID)
      Wouldn't it be nice if you could make this function generic so it could be called from many different places? You can do this by allowing for different types of parameters (see Figure 4). I'll walk you through it step by step.
      First, dimension a variable named lTotal in GetOrderTotalByCustomer. Next, insert an If…Then statement into the code. This block of code checks the parameter that's passed to the method. If the parameter is blank, the code tries to pull the value for sCustomerId from the CustomerID property you created in CustomerdInformation.asp. If this value is also blank, the code looks in the querystring and form variables for a value. This extra checking provides a lot of robustness to the method.
      Once the code finds a valid CustomerID, it performs its logic on it. The Open method executes the SQL statement you embedded in the Recordset object, creating the actual recordset like this:

 rsOrdersByCustomerID.open
Once this is open, you can start looping through the recordset with a Do while…Loop block:

 do while not rsOrdersByCustomerID.EOF
The next line pulls the LineItemTotal from the recordset and adds it to the lTotal variable you dimensioned earlier:

 lTotal = lTotal + rsOrdersByCustomerID.fields.getValue("LineItemTotal")
Next, the code moves through the recordset with the MoveNext method. After tallying all the LineItemTotal values, the Loop statement completes the operation:

     rsOrdersByCustomerID.moveNext
 loop
The last line in the GetOrderTotalByCustomer method sets the method name equal to the lTotal counter:

 GetOrderTotalByCustomer = lTotal
      Now you need to let other pages use this method. To do this, you must export the method with the PageObject in one of two ways. You can make the method available as an Execute method that can be used only from client-side scripts. You can also export a method as a Navigate method. This second choice lets both client and server-side scripts execute the method.
      When you export a Navigate method, executing it causes a jump to the page that contains the method. It can perform its processing, then move to another page, or simply display the page containing the method. An Execute method will simply execute without navigating to another page. Let's export the method I just created as an Execute method.
Figure 5: Selecting a PageObject Method
      Figure 5: Selecting a PageObject Method

      First, drag a PageObject design-time control from the toolbox and drop it on the page after the code I added earlier. Open the Properties dialog (see Figure 5) and select the method you created from the Execute method list. That's it! The method's now exported.
      Now you need to create a reference to the CustomerdInformation.asp file. This reference will let your method access the CustomerID property. To reference another page, click the References tab, then click the … button next to the first entry in the Name column. Select CustomerdInformation.asp, and close the Create URL and the PageObject properties dialogs. When you've completed these steps, save the page.

Using the Method
      Now for the fun part. The next step involves adding code to your first page to use the new method. To accomplish this, I'll show you how to add some client-side features to the page.
      First, open CustomerdInformation.asp in the source editor. Click the HTML tab in the toolbox and add an HTML textbox near the bottom of the page. Open the properties for the textbox and name it txtOrderTotal. Drop an HTML button on the page next to the textbox. Name this control cmdGetOrder. Now your page should look like the one shown in Figure 1.
      Next, open the PageObject control and create a reference to CustomerStatus.asp. This reference is all you need to access the method you exported from that page. To use this method, you need to add some client-side code. Open the Script Outline window and expand Client Objects and Events, then expand thisForm. Now, find the HTML button (cmdGetOrder) and expand it. Double-click the onclick event to add its event handler to the page. Add the following lines of code to the event handler:


 CustomerStatus.execute.GetOrderTotalByCustomer(thisPage.getCustomerID(), _
 displayCustomerOrderTotal)
 document.thisForm.cmdGetOrder.disabled = true
The first statement executes the method in your ASP script. The method call passes in the customer ID that you are using in this page (thisPage.getCustomerID). It also passes in the name of another method (displayCustomerOrderTotal), which I'll show you how to create in a moment. The second line of code disables the cmdGetOrder button to prevent the user from clicking it again before the method completes.
      The displayCustomerOrderTotal method will execute when the server method completes. This is known as asynchronous execution because the client application does not wait for the server method to complete before it continues to the next line of code. Instead, the client application continues with its tasks and lets the user interact with it. When the server method completes, it will execute displayCustomerOrderTotal, which is known as the callback method because it is called when the server method completes. The code for this method is:

 function displayCustomerOrderTotal(retObj)
 {
     document.thisForm.txtOrderTotal.value = retObj.return_value;
     document.thisForm.cmdGetOrder.disabled = false
 }
When the displayCustomerOrderTotal method executes, it takes the return value from the server method, which is stored in the retObj object that was passed in as a parameter. This value is displayed on the page by setting the Value property of the HTML textbox. The second line enables cmdGetOrder, so the user can execute it again.
      You do not have to use a callback method. Instead, you can execute it synchronously using this syntax:

 document.thisForm.txtOrderTotal.value = 
 CustomerStatus.execute.GetOrderTotalByCustomer(thisPage.getCustomerID())
However, the callback method is helpful when you execute methods on the server that contain database code. If the code takes an unexpectedly long time to execute, the user is not locked into waiting for it. The user can continue to work on other features in the page until the server code returns. You should provide feedback to the user by disabling the button. This lets the user know that something is happening in the application.

Conclusion
      The PageObject can turn any Active Server Page into an object. It lets you work with a page's properties and methods, treating the entire page as an object. The fact that these features work on both the server and client at the same time creates even more capabilities than having only server or client features.
      Take the time to understand how the PageObject works and use it consistently in your pages. Doing so will allow you to take advantage of it in your application and provide standard features to your application. Always test your applications on any browsers that will use it. This example uses Microsoft® Internet Explorer 4.0 and was not tested on other browsers. Testing becomes particularly important when you begin to add code features to the client, as I did here.

From the January 1999 issue of Microsoft Internet Developer.