Tutorial
This section illustrates a few common uses of WMLScript, including validation, user interaction, formatting, calculations, and telephone operations. We show how simple procedural logic and library functions can enhance an existing WML application.
When adding WMLScript to your application, you can use the following steps:
-
Decide on a name for your WMLScript compilation unit (file), and create a file with this name plus a comment header in the file that describes the file's functionality.
-
Declare public functions that are to be accessible from WML or other WMLScript as extern.
-
Build one function at a time, and unit test these functions using a test WML deck that invokes each function. Make use of debug statements in your code using print statements such as the Console print functions provided by the Phone.com simulator.
-
Create private functions to support the public functions.
-
We used the Phone.com UP Simulator (version 4.0), available from www.phone.com, to test our application. To run WMLScript functions, simply load the test deck that contains the calls to your functions as you would any WML deck. For example, to load a local WML deck on the C drive, you would enter something like this in the Go area: file://c:/wap/tutorial/mydeck.wml.
Error Handling
WMLScript does not provide an exception-handling mechanism like that found in Java and other heavier-weight languages. You should be careful when testing your WMLScript programs to exercise all the branches of your scripts during testing. Common coding mistakes that can lead to errors include these:
-
Using invalid variables in calculations
-
Dividing by zero
-
Using integers where floats are expected, and vice versa
-
Nesting functions too deeply (the stack memory is very limited on many handsets)
Predictable errors, such as user errors, should be trapped, and a consistently formatted error message should be displayed along with a means to cancel or continue as appropriate.
The Tutorial Application
The application that we will use for our tutorial is an extension of the health inspection system introduced in the Wireless Markup Language articles. We will add functionality to the kitchen inspection cards to calculate the size of the kitchen and to validate the information entered about the kitchen (for example, to ensure that there are enough fans to ventilate a kitchen of the specified size). The complete WML and WMLScript for this application is found here.
The size of the kitchen will be determined from its length and width (assuming that the kitchen is rectangular). For this, we will need a function called area that accepts length and width parameters and that sets the area in a WML Browser variable. We need to declare this function inside a compilation unit and will call this file kitcalc1.wmls, where the wmls extension stands for WMLScript:
/* Health Inspection Application Kitchen calculations */ // Calculate the area of a rectangle and go to // the results card extern function area(length, width) { var area = length * width; WMLBrowser.setVar("area", area); WMLBrowser.go("#Results"); };
The WMLScript begins with a block comment describing this compilation unit. This is followed by the area function with its own comments. This is a public function (using the extern keyword), and it accepts parameters for length and width. It allocates a variable area and assigns the results of multiplying length by width to this variable. It then sets the area variable in the browser context and returns control to the browser at the Results card within the calling deck (relative URL #Results). Note the use of a semicolon to terminate the function declaration (`};').
Following our own advice, we create a WML deck to test our new function. Initially this will contain two cards, Enter and Results. Note that the XML prologue has been omitted for clarity:
<wml> <!--Kitchen Functions Test Deck--> <card id="Enter" newcontext="true"> <do type="accept" label="Submit"> <go href="kitcalc.wmls#area($length, $width)"/> </do> <p> <strong>Enter Kitchen Size</strong><br/> Length (m): <input name="length" format="N*N"/> Width (m): <input name="width" format="N*N"/> </p> </card> <card id="Results"> <do type="accept" label="Another"> <go href="#Enter"/> </do> <p> Area is: $area sq.metres<br/> </p> </card> </wml>
The Enter card prompts the user to enter the size of the kitchen, storing the input in length and width variables. Note the use of the N*N format mask attribute to force entry of at least one digit. This card defines an accept event handler that invokes the area function in the kitcalc.wmls file. The newcontext attribute ensures that the length and width variables will be cleared every time this card is displayed. The second card, Results, displays the area browser variable, relying on the WMLScript to set this variable and transfer control to this card. It defines an event handler that allows the tester to enter another kitchen size. Figure 1 displays these cards.
Figure 1 area test deck
The next thing we need to do is to create a function that validates the items that a health inspector notes about a kitchen. We will call this function validate and will pass it the values entered on the inspection card. For now, we will just validate the lighting and ventilation values.
/* Validate light and fans for the specified area Light minimum is measured at counter level for preparation area (550 LUX) Assume fan averages 500 cubic metres/hour, kitchen ceiling is 2.5 metres high, and we need 8 complete air changes per hour: # of fans = (room volume * 8) / 500 or (room area / 25) */ extern function validate(area, light, fans) { // valid values var validLight = 550; // LUX measured at counter height var validFans = Float.ceil(area / 25); // see above var warning = ""; // build warning message if required if (light < validLight) warning += "Light is < " + validLight + " lux. "; if (fans < validFans) warning += "Should have at least " + validFans + " fans. "; // return to the appropriate card in the calling deck if (warning == "") { // no problems WMLBrowser.setVar("validLight", validLight); WMLBrowser.setVar("validFans", validFans); WMLBrowser.go("#OKResults"); } else { // update browser context with warning WMLBrowser.setVar("warning", warning); WMLBrowser.go("#Warnings"); }; };
First we declare some variables that are set to the valid minimum values for light and number of fans. To calculate how many fans are needed, the area is divided by 25 (see the code for an explanation of this number). Note the use of the ceil function from the Float library, which ensures that we always round up (for example, 26 square metres will require two fans). The next statement initializes a warning string that is added to depending on whether the light meets the minimum standard (550 LUX) and whether the number of fans is at least that required. The final block tests to see whether we have any warnings and, if there are no problems, sets the valid values in the browser context so that they may be displayed for reference on the OKResults card. If there are problems, the warning text is set in the browser context, and control is transferred to the Warnings card.
To test this we need to add some cards to the test.wml deck. First, we will add a new card that allows us to select which WMLScript function to test:
We rename the Enter card from our previous test deck to EnterLogin and add a new card to enter inspection values (EnterValues):
<card id="EnterValues" newcontext="true"> <do type="accept" label="Validate"> <go href="kitcalc.wmls#validate($area, $light, $fans)"/> </do> <p> <strong>Enter Values</strong><br/> Area (sq.m.): <input name="area" format="N*N"/> Light (lux): <input name="light" format="N*N"/> Number of Fans: <input name="fans" value="0" format="N*N"/> </p> </card>This card enables the tester to enter an area, as well as illumination and ventilation values. It provides an event handler that passes these values to the validate function in kitcalc.wmls. Note that all values are required (use of format mask N*N) to ensure that there is no problem when calling the validate routine.
We now need cards to display results for when the inspection values are alright (OKResults) and for when there are problems (Warnings):
Figure 2 shows how these cards and the validate function work. The first five screens show a sequence leading to a warning card. The final card shows the results of a test where no violations were found.
Figure 2 Validation test sequences
So far, our health inspection example has shown how we can use WMLScript to process user input, performing calculations and validation. Next, we will add entry and validation of the fuels that are used for cooking. This requires modifications to our test WML deck to add a multiple select list for cooking fuels and new validation output added to the OKResults card:
Cooking Fuel <select name="fuel" value="gas" multiple="true"> <option value="gas">Natural Gas</option> <option value="electric">Electric</option> <option value="propane">Propane</option> <option value="wood">Wood</option> </select> . . . Fuels: $fuel OKWe also need to update the kitcalc.wmls file to add a new fuel validation routine and call this from the general validate function:
// use getVar to get fuels list var fuel = WMLBrowser.getVar("fuel"); var VALID = "VALID"; // assume fuel OK var validFuel = validateFuel(fuel, VALID); . . . if (light < validLight) warning += String.format("Light is < %d lux. ", validLight); if (validFuel != VALID) warning += validFuel; . . . // Validate fuel combinations returning a warning // string if invalid or VALID if OK function validateFuel(fuel, VALID) { var SEPARATOR = ";"; var GASES="gas,propane"; var gasesSelected = false; var WOOD="wood"; var woodSelected = false; var WARNING = "Illegal to mix gas fuels and wood!"; var nFuels = String.elements(fuel, SEPARATOR); for (var i=0; i= 0) gasesSelected = true; if (thisFuel == WOOD) woodSelected = true; if (gasesSelected && woodSelected) break; }; if (woodSelected && gasesSelected) return WARNING; return VALID; }; The first few lines were added to the validate function to obtain the fuel value from the browser, set a default (VALID) result, and call the validateFuel private function. The next group of lines also were added to validate; the test for valid light was updated to illustrate the use of string formatting (light intensity is now displayed using the format function). A new test of the output from validateFuel will append this function's output to the warning message if the function returns an invalid result string. Note the use of the += operator, which performs string concatenation because its operands are both strings.
The validateFuel function accepts a string containing one or more fuels and a default return value named VALID. Recall that the result of a multiple select list is a string that may contain multiple tokens separated by a semicolon (;). We determine how many fuels have been selected using the elements function of the String standard library. We use this value (nElements) to construct a for loop that searches for gases (propane or gas) as well as wood. The String function elementAt retrieves the fuel tokens one by one, and the String.find function determines whether a fuel is a gas. The break element is used to exit this loop as soon as we have found at least one gas and wood. Note the use of capital letters for constants GASES and WOOD; these constants localize changes to the WML Script if we need to update the fuel list in the calling WML deck. If we find a combination of gas and wood, we return a warning message; otherwise, we return VALID. Figure 3 illustrates the new validation input and results. The inputs include substandard illumination and ventilation, as well as an illegal fuel combination of natural gas and wood.
Figure 3 Fuel validation sequences
So far, we have relied on WML cards to interact with the user. The WMLScript Dialogs library gives us another way to do this. We will alter our application to include a dialog that alerts the user to warnings if the validation function finds problems and offers two options—a new test or re-entry of the existing test.
if (Dialogs.confirm(warning, "New Test", "Redo" )) WMLBrowser.go("#SelectTest"); else WMLBrowser.go("#EnterValues");Figure 4 illustrates this change.
Figure 4 Confirm dialog
The final modification that we will make to our health inspection application is to provide an option for an inspector to dial the office in case there is a problem with an inspection. We will modify the dialog created previously to provide this option and add a WMLScript that uses the WTA public library to make a voice call:
if (Dialogs.confirm(warning, "New Test", "Call Office" )) WMLBrowser.go("#SelectTest"); else WTAPublic.makeCall("4577175"); WMLBrowser.go("#SelectTest");Following display of the dialog, the user could choose to make a voice call to the office. A confirmation dialog will be displayed to allow the user to confirm the telephone number. The WTA Public library function makeCall will then initiate a voice call, suspending the WMLScript function until this call is completed; at that point, control will return to the browser at the SelectTest card. Because of limitations of the Phone.com simulator, it is not possible to illustrate this function.
That concludes our tutorial. The WML test deck and WMLScript code can be found here along with a modified health inspection deck that uses the WMLScript functions. Join us next time for a discussion of where WAP is heading, including current issues and trends.