Try OpenEdge Now
skip to main content
Developing WebSpeed Applications
Web Objects : HTML mapping examples : Complex HTML mapping
 

Complex HTML mapping

The following figure shows an example of an HTML-mapping Web object that is more typical of those that you might find in a full-featured Web application. The Web object, generated from the example code in w-cstinf.w, maps to an HTML form with submit buttons and returns customer information from the Sports2000 database.
Figure 8. Web page generated from w-cstinf.w
Basically, the Web object does the following:
*Initially, it generates a Web page similar to the one shown above. The Web page contains several buttons and form fields that contain information from the first record (according to the value of CustNum) of the Customer table of the Sports2000 database.
*If you enter a value in the Enter Name field and choose the Search button, the Web object takes the value and finds the customer whose name is equal to or greater than the entered value.
Thus, entering the value Dar causes w-cstinf.w to return the first customer whose name begins with Dar (which is Dark Alley Bowling).
*The Next button finds and returns the customer record that comes just after the record specified by the current value of the Name element.
*If you change any of the displayed fields, the Update button causes the Web object to update the appropriate customer record.
After successfully updating the database, the Web object returns the updated Web page with a comment containing today's date appended to the current Comment value. This appended value is also stored in the database for future reference.
The following shows w-custinf.htm, which is the HTML file mapped to w-cstinf.w:

w-custinf.htm

<!DOCTYPE HTML PUBLIC "-//Netscape Corp.//DTD HTML plus Tables//EN" "html-net.dtd">
<HTML>
<HEAD>
<TITLE>Customer Search and Update</TITLE></HEAD>
<BODY BGCOLOR="#FFFFFF">
<H1>Customer Information</H1>
<FORM ACTION="w-cstinf.w" METHOD="POST">
  <P>Enter Name: <INPUT NAME="Name" SIZE="20">
  <INPUT TYPE="SUBMIT" NAME="Button Value" VALUE="Search"><BR><BR>
  ID: <INPUT NAME="CustNum" SIZE="5">
  --------Phone: <INPUT NAME="Phone" SIZE="20"><BR><BR>
  Address 1: <INPUT NAME="Address" SIZE="20"><BR>
  Address 2: <INPUT NAME="Address2" SIZE="20"><BR><BR>
  City: <INPUT NAME="City" SIZE="12">
  ---State: <INPUT NAME="State" SIZE="20">
  ---Zip: <INPUT NAME="PostalCode" SIZE="10"><BR><BR>
  Comments: <TEXTAREA NAME="Comments" ROWS="2" COLS="30"></TEXTAREA><BR><BR>
  <INPUT TYPE="SUBMIT" NAME="Button Value" VALUE="Next">
  <INPUT TYPE="SUBMIT" NAME="Button Value" VALUE="Update">
  </P>
</FORM>
</BODY>
</HTML>
The Web object makes explicit reference to two form elements in this file:
*"CustNum" form field
*"Button Value" submit button
You can follow how the Web object runs by looking at the relevant code sections of the process-web-request procedure for w-cstinf.w.
When the user first goes to the URL for this Web object, w-cstinf.w returns the form defined in w-cstinf.htm to the browser. It does this by executing the process-web-request procedure. First, the procedure generates the default HTTP header for the Web page that the object is about to return, by calling the default outputHeader procedure (STEP 0 in the following code):

w.cstinf.w (RUN outputHEADER)

  . . .
/* STEP 0 -
* Output the MIME header and set up the object as state-less or
* state-aware. This is required if any HTML is to be returned to the
* browser.
*
* NOTE: Move RUN outputHeader to the GET section below if you are going
* to simulate another Web request by running a Web Object from this
* procedure. Running outputHeader precludes setting any additional
* cookie information.
*
*/
RUN outputHeader.
. . .
It then determines whether the CGI request method is a GET or a POST. In this case, as the first URL request, it is a GET, causing the code to take the ELSE branch of the test:

w-cstinfo.w (GET branch)

/* REQUEST-METHOD = GET */
ELSE DO:
    . . .
/* STEP 1-
* If there are DATABASE fields, find the relevant record that needs
* to be assigned.
RUN findRecords.
The default procedures in this code branch move any initial data to the form buffer, set up the input form elements to receive user input, and send out the prepared Web page.
Note: None of this initial GET request requires you to add any additional code to process-web-request. However, you might want to improve the way the Web object fetches the first record. By default, it finds the first record according to the value of CustNum. It might be more useful to display the first customer alphabetically by Name. To change the default behavior, comment out Run findRecords. Then, add a line of SpeedScript (for example, FIND FIRST customer USE-INDEX name.)
The Web object returns a page to the browser that looks similar to the Web page shown in Figure 8. The Web page is interactive. The user can view a different record by using the Submit or Next buttons. In addition, the user can change a record by using the Update button. In order to implement this functionality, the following highlighted code is added to the POST branch of process-web-request (recall that POST is specified as the request method in w-custinf.htm):

w-cstinf.w (POST branch)

IF REQUEST_METHOD = "POST":U THEN DO:
    vButton = get-value(INPUT "Button Value").
/* STEP 1 -
* Copy HTML input field values to the form buffer. */
RUN inputFields.
/* STEP 2 -
* If there are DATABASE fields, find the relevant record that needs to be * assigned.
RUN findRecords.
*/
RUN findCustomer.
IF vButton = "Update" AND AVAILABLE(Customer) THEN
    RUN assignFields.
/* STEP 3 -
* You will need to refind the record EXCLUSIVE-LOCK if you want to assign * database fields below. For example, you would add the following line.
* FIND CURRENT Customer EXCLUSIVE-LOCK NO-ERROR.
*/
IF vButton = "Update" AND AVAILABLE(Customer) THEN
    RUN assignFields.
The first code fragment calls the get-value API function to return a value when the submit button is selected:
vButton = get-value(INPUT "Button Value").
This function returns the value of any form element or CGI query string element when given the element name. In w-cstinf.htm, "Button Value" is the name of all SUBMIT buttons defined in the form (with values "Search", "Next", and "Update"). So, the value of the button that the user pressed to initiate this POST is returned in the variable vButton.
The vButton variable is defined in the definitions section that is global to all procedures in the Web object, w-cstinf.w. (Look in the Definitions section of the Section Editor.) This variable has the data type CHARACTER, a variable-length string data type:
DEFINE VARIABLE vButton AS CHARACTER NO-UNDO. /* Submit button value */
Note: The NO-UNDO option is often used to improve SpeedScript memory efficiency. It prevents the automatic saving-to-disk of the specified variable for recovery from a failed database transaction. For more information on database transactions, see
The form input values sent with the URL is read by calling the default inputFields procedure (STEP 1).
Next, the Customer record based on the value of the SUBMIT button is located. This is done by executing the findCustomer procedure (STEP 2 in the POST branch).
In the code for findCustomer, you can see that a newly defined character variable, vName, receives the value of the Name form element entered by the user.
The scope of this variable is local to the findCustomer procedure. However, access to the vButton variable still exists, since it is declared in a section whose scope is the entire Web object:

findCustomer

/*-------------------------------------------------------------------------
Purpose:
Parameters: <none>
Notes:
-------------------------------------------------------------------------*/
  DEFINE VARIABLE vName AS CHARACTER NO-UNDO.

vName = get-value("Name"). /* Name value for customer search */

CASE vButton:
    WHEN "Search" THEN DO WITH FRAME {&FRAME-NAME}:
      FIND FIRST Customer WHERE Name >= vName NO-LOCK NO-ERROR.
      IF NOT AVAILABLE(Customer) THEN
        FIND FIRST Customer USE-INDEX Name NO-LOCK NO-ERROR.
      IF NOT AVAILABLE(Customer) THEN
        Name:SCREEN-VALUE = "*** NO RECORDS ***".
    END.

    WHEN "Next" THEN DO WITH FRAME {&FRAME-NAME}:
      FIND FIRST Customer WHERE Name > vName NO-LOCK NO-ERROR.
      IF NOT AVAILABLE(Customer) THEN
        FIND FIRST Customer USE-INDEX NAME NO-LOCK NO-ERROR.
      IF NOT AVAILABLE(Customer) THEN
        Name:SCREEN-VALUE = "*** NO RECORDS ***".
    END.

    WHEN "Update" THEN DO WITH FRAME {&FRAME-NAME}: /* CustNum to update */
      FIND Customer WHERE CustNum = INTEGER(get-value("CustNum"))
        EXCLUSIVE-LOCK NO-WAIT NO-ERROR.
      IF LOCKED(Customer) THEN
        Name:SCREEN-VALUE = "*** LOCKED ***".
      ELSE IF NOT AVAILABLE(Customer) THEN
        Name:SCREEN-VALUE = "*** NOT FOUND ***".
      ELSE
        Comments:SCREEN-VALUE = Comments:SCREEN-VALUE +
                                "~n*** Updated " + STRING(TODAY) + " ***".
    END.
END CASE.
END PROCEDURE.
The code first returns the value of the Name form element from the get-value API function. Then it checks for the three possible values of the Submit button returned in process-web-request. This is done using a CASE statement. It executes the WHEN option that specifies the current value of the CASE statement expression, in this case, vButton.
In the case of "Search", the first customer record whose Name field is equal to or greater than the value of the Name form element is found. If such a record does not exist, it returns the first customer record to restart the search. If no customers are on file, it returns a suitable message in the Name form element.
Note: The WITH FRAME {&FRAME-NAME} option indicates that all data item references in the block (in this case a DO block) that require a parent SpeedScript frame default to {&FRAME-NAME}. This is a shortcut to avoid specifying a frame for each data item reference.
In the case of "Next", the processing is almost the same as for "Search". Assuming that the user has already seen this record, it searches for the record with the next greater value for Name. Again, if no such record exists, it returns the first record, and if no customers are on file it returns a suitable message.
Note: The current value of Name is being used as if it were the name that the user last requested. However, there is no guarantee of this, since the user could change the value before submitting the form. If this Web object were state aware, you could replace the FIND FIRST Customer statement with a FIND NEXT Customer statement that would automatically find the next Customer record after the current one in the database. This guarantees that the application returns the next record found and not the one based on the returned value of the Name form element. In a stateless Web object, there is no previous record, since it always executes as if for the first time. Thus, the entered value of Name must be used. (You could also use a hidden HTML form element to duplicate the returned value of the database Name field for later reference.) The Web object examples do include a state-aware version of this Web object, w-cststa.w, which you can compare to w-cstinf.w. For more information on building state-aware Web objects, see
For "Update", you must find a unique record that corresponds to the returned values, because there can be duplicate customer names. Since the CustNum field is the primary key for Customer records, you can assume that the value passed back in the CustNum form element specifies the record intended for update. However, the user could have changed it. If there is no such record, or the specified record is locked by another client, the form element Name value is replaced with a suitable message.
If the record does exist, a confirmation is concatenated (+) to the Comments form element value, both for immediate confirmation and for future reference. The SpeedScript TODAY function returns today's date, and the STRING function converts the DATE value (a separate data type) to a character string for the concatenation. Also, ~n specifies a new line in a string.
Note: The FIND statement for the "Update" case specifies that the retrieved record be locked (EXCLUSIVE-LOCK). This prevents other clients from updating the record while the current update takes place. For more information on record locking in SpeedScript, see OpenEdge Development: ABL Reference.
At this point, the process-web-request method procedure has either found the record or has not found the record that the user intended to update. The procedure returns from findCustomer and executes the following code from w-cstinf.w (POST branch):
/* STEP 3 -
* Assign the fields from the form buffer to the database. */
IF vButton = "Update" AND AVAILABLE(Customer) THEN
      RUN assignFields.
The test of vButton is added, again, because the next default event procedure, assignFields, assumes that you want to update the current record with the values received from the user. However, this is only true if the user submitted the form for update, and only if the specified record was actually found. The SpeedScript AVAILABLE function returns a logical value of TRUE if there is a record in the buffer for the specified database table, in this case, Customer.
The remaining default event procedures in the POST request branch move the field values from the current (possibly updated) record to the form buffer, prepare any form elements for further input from the user, and output the Web page with the resulting content to the browser.