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 (5KB)
Ken Spencer

Using the Visual InterDev 6.0 FormManager
O
ne of the big enhancements in Visual InterDev™ 6.0 is the introduction of a new design-time control format. The new controls are more modular than the ones in Visual InterDev 1.0, and you can use them in discrete pieces. For instance, you can use the Textbox design-time control to add functionality to a page without binding it to any other design-time controls.

    The new FormManager design-time control is a state machine—it manages the state of the controls that you use in a page. A state machine is an application or control that implements state diagrams. A state diagram describes a finite set of states (conditions) that can occur in the system. This means that all the states are known to the system.

    State diagrams have long been used to design control systems for various implementations. When you use a microwave oven that has a computer control, that control system has a state machine in it. This state machine handles the various interactions that users have with the system, reliably performing certain tasks over and over.

    Wouldn't it be nice to create a state machine in your Web application that makes it as reliable as a piece of hardware? That's where the FormManager comes in. Figure 1 shows what a state diagram for a Web application might look like. Each oval represents a state the application may be in. The name for the state is shown in the yellow area. The text inside the purple area lists the actions that will occur for each state. For instance, the Select state will disable the Update and Delete buttons while enabling the Edit button.

Figure 1: A State Diagram for a Web App
Figure 1: A State Diagram for a Web App

The arrows indicate the allowable state transitions. For instance, if the application is in the Select state, it can move to the Edit state. There is no way for it to go to any other state. This is part of the beauty of the state diagram and a state machine: you can define exactly how your application will behave. Tracing the arrows from one state to another will allow you to discover the interactions of each state in the application.

    FormManager is like a super design-time control in that it uses all the other design-time controls on the page. The FormManager is used to build pages that have various states of operation. Say you have a page that contains a listbox and several Textbox design-time controls. When a user selects an item from the list, you display the data in the textboxes. The user needs to be able to enter Edit mode to make changes or delete the record. The user also needs to have the option of creating a new record, which would require a New mode.

    This is where the FormManager comes in. It can manage the entire process of switching modes and setting the various design-time controls to different states for each mode. The FormManager does all this with no code—you don't need to set up select statements or other types of logic to track the modes the page goes through. The only code you need to add is the code that handles the specific actions of your application. Let's take a look at an example.

Creating a Form

    Normally, I create a page with the various design-time controls that make up the interface before I set up the Form Manager. This lets me test the page and make sure the database connections work with a simple form. Next, I will add the FormManager, define the various modes, and add any script code necessary to perform specific actions for each mode.
Figure 2: CustomerOrders.asp Page
Figure 2: CustomerOrders.asp Page

The sample application I created uses the Northwind sample database that ships with Visual Studio® 6.0 and SQL Server™ 7.0. Figure 2 shows the ASP file I created for this application (CustomerOrders.asp). This page uses two Recordset design-time controls, one for the Customer table (rsCustomer) that pulls all rows from the table and another (rsCustomerbyID) for the customer table that pulls only one record by CustomerID column. Both of these recordsets use Data Command objects to retrieve the data:
RsCustomer - getCustomer
 RsCustomerbyID - getCustomerbyID
The SQL for the Data Command objects is:
getCustomer    SELECT CustomerID, Description,
                CompanyName, ContactName,
                ContactTitle, Address, City, Region,
                PostalCode, Country, Phone, Fax,
                MailBrochures, IndustryClassification
                FROM Customers
                ORDER BY CompanyName
 getCustomerbyID    SELECT CustomerID, Description,
                    CompanyName, ContactName,
                    ContactTitle, Address, City, Region,
                    PostalCode, Country, Phone, Fax
                    FROM Customers
                    WHERE (CustomerID = ?)
The page also contains a listbox whose lookup properties are bound to rsCustomer. CustomerID is the bound field while CompanyName is the lookup field. Four button design-time controls, btnNew, btnUpdate, btnEdit, and btnDelete, will control the interaction of the form.

    Last but not least, the page contains a textbox for each field in rsCustomerbyID that I want to update. The textboxes were generated by dragging the getCustomerbyID data command and dropping it on the page, then dragging the fields from the command and dropping them under the recordset created when getCustomerbyID was dropped on the page.

    You also need to uncheck the "Automatically open the recordset" checkbox for rsCustomerbyID. This setting is found on the Implementation page of the recordset's properties.

    If I were building this part of the application with ASP script, I would need to publish the results to another page to interpret the user's actions or add this code page to process the results. Instead, I am going to use the FormManager control to manage these actions. At this point, I can begin to work with the FormManager and put the actions of the page in place, letting this control drive the state management. The FormManager uses several terms that define the various elements the FormManager addresses (see Figure 3).

    After you drag the FormManager design-time control from the Toolbox and drop it on the page after the last design-time control in the file, the next step is to define the modes for the page. I've decided that this page will require three modes of operation (or states), one for each type of operation the form will perform:

  • In Edit mode, the form allows users to change the data
  • In Add mode, the user can enter a new record
  • Select mode is the active mode when users have selected a record but have not clicked the Edit button
The FormManager will use these modes to control the page. When the user clicks a button or performs another action to change the page's mode, the FormManager will automatically change the mode accordingly. For instance, in Select mode the user should be able to select a record from the list, but not delete or edit a record. In Edit mode, the user can make changes to or delete a record.

    To define a mode, open the FormManager's properties. Enter the mode's name in the New Mode box, then click the > button to add the mode. Enter the Select, New, and Edit modes. The first mode you entered automatically becomes the default mode when the page displays. Figure 4 shows the Form Mode property page with the three modes defined.

Figure 4: Form Mode Property Page
Figure 4: Form Mode Property Page

Since the FormManager is really a state manager, it knows about modes (states). Now that you have defined the modes, you can define the actions that occur when a mode becomes active. For instance, when the Select mode becomes active, the actions defined for it on the Form Mode page occur.

    You can define a set of all types of actions for various objects on the page. For instance, you can set properties or execute methods of a design-time control such as a textbox. You can also use actions with any design-time controls such as recordset controls.

    To define an action, select the mode the action applies to in the Form Mode box. As soon as you select a mode, the actions currently defined for that mode will show up in the Actions Performed For Mode grid at the bottom of the property page. Of course, the grid will be blank when you select a newly defined mode.

    Next, click once in the object column in the first row of the grid. This will display a dropdown list of all the objects on the page. When you select an object from the list, the properties and methods for that object will populate the member list on the same row. To complete the action definition, you select a property or method from the member list. If you select a property, you need to enter the value to set the property to in the Value list. Figure 5 shows the actions for the Edit mode for the sample page.
Figure 5: Actions Performed for the Edit Mode
Figure 5: Actions Performed for the Edit Mode

In this sample, design-time controls for the various modes are either enabled or disabled when a mode change occurs. The modes and the object settings for the actions are defined in Figure 6.

    Once you have set the actions for the page, click the Action tab. Using this property page, you can define the actions that change the form's mode and actions that occur before the mode change occurs (see Figure 7). For instance, when the user clicks the Edit button, the form needs to change to the Edit mode. You define the actions that trigger a transition in the Form Mode Transitions section of this property page. To define a action to trigger a transition, use these steps:

  1. Select the current mode of the form in the Current Mode column of the grid. The current mode is the mode the form is in before it changes. For instance, if the user is in Edit mode, then selects an entry from the list, the current mode is Edit and the triggering action is the onchange event of the list.
  2. Select the object whose event will trigger the mode change in the Object column.
  3. Select the triggering event in the Event column.
  4. Select the next mode in the Next Mode column.
Figure 7 shows the transition settings for this page. You can see how this set of actions can be mapped to a state diagram to graphically display the transition actions.
Figure 7: The Action Tab
Figure 7: The Action Tab

In addition to the three transition actions shown in Figure 7, there is one more action that is not shown:
New      btnUpdate      onclick     Select
It's easy to define the transition modes because the FormManager takes your settings and generates the code. For instance, you don't need to add lots of code to make the transitions; the FormManager does it for you. But you will add some code to work with the modes to make the form functional.

    The Actions Performed Before Transition section of this page allows you to define another set of actions for an event. For instance, you could select a Recordset object and trigger an UpdateRecord method when a mode changes. To set these actions, you select the line in the Current Mode section that indicates the current mode, then you can set the actions that occur before the transition from that mode. In this sample, I am not using this grid.

Adding the Code

    The first change I made after finishing with the Form Manager was to insert a PageObject design-time control. Then I added the thisPage_onenter event handler and used it to add a blank entry as the first item in the list.

    To complete this page, I added several procedures and event handlers. The event handlers were entered using the Script Outline in the Source Editor. You insert an event handler using the Script Outline by exposing the Server Objects and Events tab, then finding the object in the list and exposing it. Then you can double-click an event to add the event handler for it to the ASP code in the file. The complete ASP code for CustomerOrders.asp is shown in Figure 8.

    Next, add this event handler for the listbox:

dim CustomerID
 Sub lstCustomer_onchange()
     rsCustomerbyID.close
     CustomerID = lstCustomer.getValue()
     rsCustomerbyID.open
 End Sub 
Notice that CustomerID is defined outside of the subroutine code. This is necessary to make the variable global to the page so it can be used to set the parameter for the rsCustomerbyID recordset. If the variable is dimensioned inside the event handler routine, it will have local scope within that procedure and the recordset will not be able to see it. This will generate a runtime error when the user selects an entry from the list.

    The event handler takes the bound value for the item the user selects from the list and places it in the CustomerID variable. The other two lines close, then reopen the recordset.

    Now, let's define two general-purpose procedures. The ClearFields procedure clears all the textboxes. The SetList procedure is used when the listbox needs to be reset, say after a user enters a new record using the New mode. The first line in the procedure clears the listbox. The next line adds the blank entry to the list again, as it was cleared along with the other list entries.

    lstCustomer.clear()
     lstCustomer.addItem "","",0 
Next, the requery method repopulates the recordset from the database:
    rsCustomer.requery()
My next step was to add the event code for the buttons. The New button uses this code to clear the textboxes, then executes the addRecord method to add a new empty record to the recordset:
Sub btnNew_onclick()
     ClearFields
     rsCustomerbyID.addRecord()
 End Sub 
The Update button first calls the updateRecord method to post the changes to the database. Then it uses the SetList procedure to reload the query.
Sub btnUpdate_onclick()
     rsCustomerbyID.updateRecord()
     SetList
 End Sub
In a production application, you should only execute requery when you need to. You could set this action to occur when an update occurs after a new record is added.

Conclusion

    At this point, you have a functional application. You can see how the FormManager makes it easy to define the modes and actions that a typical form takes.

    Now for the hard part. Be careful when setting the values for properties or methods in FormManager. For instance, the following error occurred when I accidentally selected the Ad-vise method set when I intended to select Disabled, then viewed the page in the browser:

Microsoft JScript runtime error '800a138a' 
 Function expected 
 /AdvisorProjects/CustomerOrders.asp, line 552 
This is confusing until you realize that the FormManager is implemented in JScript, and invalid values have been passed to a method. You can use the debugger to walk through the code and try to find the error. You can also look up the line in the source editor by its line number and determine which design-time control the error is occurring in. Then you can look over the method and property settings and make sure they are correct.

From the March 1999 issue of Microsoft Internet Developer.