HP3000 Connect User's Guide

CHAPTER 5

Advanced HP3000 Actions

Terminal-based computing differs from other types of computing (including other IBM terminal-based interactions) in a number of important ways:

These factors can make automating a terminal interaction (via an Action Model) challenging. The goal of this chapter is to suggest some strategies for dealing with common (yet potentially problematic) terminal-computing situations in the context of an eXtend Action Model.

To get the most out of this chapter, you should already have read Chapter 4, "Performing HP3000 Actions" and you should be familiar with Action Model programming constructs (such as looping via the Repeat While action). In addition, you should have some experience using ECMAScript.

 
Top of page

Data Sets that Span Screens

A common requirement in terminal computing is to capture a data set that spans multiple screens. In cases where the screen contains a line that says something like "Page 1 of 4," it's a straightforward matter to inspect the screen at the point where this line occurs (using one of the ECMAScript Screen-object methods described earlier, in the section titled "HP3000-Specific Expression Builder Extensions") and construct a loop that iterates through all available screens. But sometimes it's not obvious how many screens' worth of data there may be. In some cases, the only clue that you have may be the presence of a "More" command (for example) at the top or bottom of the screen, which changes to "Back" (or "End," or some other message) when you reach the final screen. In other cases, you may be told how many total records exist, and you may be able to determine (by visual inspection) how many records are displayed per screen; hence, you can calculate the total number of screens of information awaiting you.

The point is that if your query results in (potentially) more than one screen's worth of information, you must be prepared to iterate through all available screens using a Repeat/While action, and stop when no additional screens are available. You will have to supply your own custom logic for deciding when to stop iterating. Your logic might depend on one or more of the following strategies:

Obviously, the strategy or strategies you should use will depend on the implementation specifics of the terminal application in question. For some applications, iterating through screens until a blank record is encountered would be appropriate, whereas for others, it wouldn't be.

An example of an Action Model that combines two of these strategies will be discussed in detail further below.

 
Top of page

Dealing with Redundant Data

In terminal applications, it's common for the final screen of a multiscreen result set to be "padded" with data from the previous screen. In this way, the appearance of a full screen is maintained.

Consider the following two screen shots. The top one shows the next-to-last screen's worth of information in a query that returned six screens of information. Notice that the reversed-out status line (row 2 from the top) says "43 entries found, entries 33–40 are:", followed by line entries. Since there are 43 records in the overall data set, and the next-to-last screen ends with record number 40, you'd expect the next (and final) screen to show records 41 through 43. Instead, the final screen looks like the one at the bottom of the next page. Notice that it shows records 36 through 43—that is, it contains five records (36 through 40) from the previous screen. In most cases, you will not want to capture this redundant data. The question is: How can you detect and reject redundant records of this sort?

ECMAScript offers an easy and convenient way of maintaining unduplicated lists. The trick is to create a bare (uninitialized) Object, then attach record names as properties. Since no object can ever have two properties with identical names, assigning record names as property names means the object's property list is an unduplicated list of record names.

Aquinas2

Aquinas3

A short example will make this clearer. Suppose you have an array of items in which some items are listed more than once:


     var myArray = new Array( "Tom","Amy","Greg","Tom","Amy");


To unduplicate this array, you could assign properties to a bare object, where the property names equal the array values:


     var myObject = new Object(); // create a bare object

     for (var i = 0; i < myArray.length; i++) // loop over array

     {

             var arrayMember = myArray[ i ]; // fetch array member

             myObject[ arrayMember ] = true; // create the property

     }


      // Now obtain all property names

      // in a new, unduplicated array:

     var uniqueValues = new Array();

     var n = 0; // counter

     for (var propertyName in myObject) // enumerate property names

            uniqueValues[ n++ ] = propertyName;


     // Now 'uniqueValues' contains just "Tom","Amy","Greg"


We will use this trick to our advantage in the terminal application example discussed below.



 
Top of page

An Example of Looping over Multiple Screens

Let's look at a sample HP3000 component that combines several of the strategies we've been talking about. The host application is a university library system's book locator service. In this example, we have an input document that specifies an author's name. Based on that name, we want to query the library for all available book titles by that author and capture the results to an output DOM. We want the output document to contain an unduplicated list of titles.

This example will demonstrate:

The logic for our Action Model's main loop can be summarized (in pseudocode) as follows:

Determine the number of records-per-screen

While (true) // enter a "forever" loop

    Fetch a record  

    IF Record is Valid   // i.e., not blank

         Write data to Output DOM

         IF Screen has been completely processed

                IF this is not the final screen

                         Fetch next screen

                ELSE BREAK   // final screen processed

    ELSE BREAK     // blank record reached


Initial Actions

The initial portion of the Action Model for this example looks exactly like the actions created in the earlier example (in the "HP3000 Actions" chapter) under "Recording an HP3000 Session", except that in this case our author is Thomas Aquinas. The initial actions are simply the Check Screen and Send Buffer actions necessary to conduct an Author search on "Thomas Aquinas."

The initial screen of our result set looks like:

Multirow1

At the very beginning of the second row, we're told how many records ("entries") were found. We can capture this information by using a Function Action:

FunctionTotalHits

This three-line script obtains all of Row 2 in a local variable called line2, trims leading spaces off the line, and splits the line on space characters (capturing the zeroth member of the resulting array into a variable, totalHits). After this, it's a simple matter to write the "total hits" number into the Output DOM using a Map Action.

At this point, we could use the "total hits" number as the basis for our main loop. But for illustration purposes, we're going to bypass that tactic, because not every HP3000 host reports "total hits" information on the first response screen. We will, however, take advantage of the fact that this particular application reports the number of records per screen (in row two). Here again, though, it's possible—with clever ECMAScript programming—to determine "records-per-screen" information dynamically, at runtime. Alternatively, you can just hard-code this value after visually inspecting the screen.

NOTE:   At some point, you will have to decide whether (and under what circumstances) it makes sense to hard-code something like the number of records per screen, as opposed to applying runtime logic. With terminal applications, it's rare that you can count on being able to determine every important screen characteristic dynamically. Some fore-knowledge of the host application's behavior will almost always be implicit in the final Action Model.

We will store the records-per-screen number in an ECMAScript variable, booksPerScreen. In this example, there are eight records per screen.

Setting Up the Main Loop

Before creating our main loop, we need to set up an index variable that will be used when creating nodes in our Output DOM. This index (called bookNumber)will start at one and will be incremented once for every book title we capture to Output. The reason this index starts at one instead of zero is that DOM nodes use one-based indexing. We will be using bookNumber to index our nodes.

We also will use an ECMAScript expression (in a Function Action) to create a blank ECMAScript object:


var bookTable = new Object();


By storing book titles as property names on this object, we can keep an unduplicated list of records, as explained further above (see "Dealing with Redundant Data").

To create the loop, we place a Repeat While action in the Action Model. (Right-mouse-click, then select New Action > Repeat > Repeat While.) The dialog settings for this look like:

RepeatWhile2

By setting the While condition to true, we are—in effect—creating an infinite loop. The exit conditions for this loop are twofold:

The latter condition provides a suitably robust way to break out of our infinite loop.What's more, it's generally applicable to a wide range of terminal applications—not just the library-query application.

The index variable i, which cycles from zero to booksPerScreen – 1, serves two roles:

  1. It lets us know when it's time to fetch a new screen (namely, when the value reaches booksPerScreen – 1), and

  2. It serves as the basis for our row offset when fetching records.

Screen Caching

One additional bit of pre-loop setup code involves caching the current screen. We include the following Function Action statement immediately before beginning the loop:


previousScreen = Screen.getTextAt(1,1,Screen.getColumnCount() * Screen.getRowCount());


The variable previousScreen caches the contents of the last-looked-at screen so that we can check newly obtained screens against it. If a newly obtained screen has exactly the same content as the screen we just processed, this is a hint that we have reached the final screen (and we should therefore terminate the loop).

The Main Loop

We're now in a position to look at what our Action Model's main loop actually does.

First Half

Consider the first portion of the loop as shown below. This is where most of the real work takes place.

MultiRowActionModel1

The first action inside the loop is a Function Action, which fetches the 53 characters beginning at column 9 of row 4 + i. The rows we're interested in include rows 4 through 11, inclusive; this is the zone in which the host reports our line items. Since i cycles from zero to 7, we can use "4 + i" as a row offset in our code.

Once we've obtained a record, we do a validation check before proceeding. Only if the zone that the record came from is non-empty will we continue with the loop. We use a Decision Action with a decision expression of:


Screen.getTextAt( 4 + i, 9, 53) != (new Array(53) ).join(" ")


The statement on the right side of the expression means "create a new, empty array of length 53, and convert it to a String by joining the array members together, using a single space character as the delimiter." Since each array member is null, this essentially forms a String consisting of 53 space characters in a row. We can compare this String with the onscreen string to determine if a blank record was encountered.

In the TRUE branch of our Decision Action, we immediately check to see if the book title we just fetched has already been encountered. (We don't want duplicates.) Since we've been using the tactic of keeping book titles as property names on the bookTable object (see discussion further above), all we have to do to check for prior existence of the book is execute a Decision Action against the expression:


bookTable[ bookTitle ] == null


If this statement is true, it means the bookTable object has no property whose name matches the String in bookTitle. When this is the case, it means we can go ahead and do our mapping operations. (Otherwise, we fall through and keep iterating.)

In the TRUE branch of this decision, we mark bookTable[ bookTitle ] as true; this assigns a new, non-null property to bookTable. We then map an index number as well as the book title to new nodes in our Output DOM. By applying a target expression of


Output.createXPath("InquiryResponse/Books[$bookNumber]/Title")


for mapping, we are able to use the running index in bookNumber to create a new node instance under InquiryResponse/Books with element name Title.

Finally, we increment bookNumber.

Second Half

In the final portion of our loop, we check to see if it's time to fetch a new screen. If so, we execute the necessary Send Buffer command to tell the host we want to page forward to the next screen.

MultirowActionModel2

Notice that as soon as we've fetched the new screen, we capture its contents into a String variable, thisScreen. Then we execute a Decision Action in which we simply compare thisScreen to previousScreen. If the two are equal, we use a Break Action to break out of the loop. Otherwise we fall through and continue executing.

NOTE:   Use care when deciding a Min Wait time for the Check Screen action shown above. If the Min Wait is short and the go-ahead condition is true, it's possible you could unintentionally skip a screen and break out of the loop prematurely.

If we're still executing, we reset i (the row index variable) and stuff thisScreen into previousScreen in preparation for the next round.

The Output DOM resulting from our loop ends up looking something like this:

OutputDOM

The DOM lists all the titles found for this author, numbered sequentially. And even though the final screen's worth of data contains a significant amount of information duplicated from the preceding screen, our DOM contains no duplicate titles.

 
Top of page

Performance Considerations

You can perform millisecond-based timing of your Action Model's actions by wrapping individual actions (or block of actions) in timing calls.

Procedure To time an Action:

  1. Click into the Action Model and place a new Function Action immediately before the action you wish to time. (Right-mouse-click, then New Action > Function.)

  2. In the Function Action, enter an ECMAScript expression of the form:

    startTime = Number(new Date)

  3. Insert a new Function Action immediately after the action you wish to time.

  4. In the Function Action, enter an ECMAScript expression of the form:

    endTime = Number(new Date)

  5. Create a Map Action that maps endTime – startTime to a temporary DOM element. (Right-mouse-click, New Action > Map.)

  6. Run the Component. (Click the Execute button in the main toolbar.)

If you do extensive profiling of your Action Model, you will probably find that the overwhelming majority of execution time is spent in Check Screen actions. (You will seldom, if ever, encounter a Check Screen that executes in less than 150 milliseconds.) Two implications of this worth considering are:

Finally, remember that testing is not truly complete until the deployed service has been tested (and proven reliable) on the app server.

For additional performance optimization through the use of shared connections, be sure to read the next chapter, on Logon Components.



Copyright © 2003 Novell, Inc. All rights reserved. Copyright © 1997, 1998, 1999, 2000, 2001, 2002, 2003 SilverStream Software, LLC. All rights reserved.  more ...