November 27, 2014
Hot Topics:

WML Gadgets, Building a Calculator

  • December 5, 2002
  • By Steve Schafer
  • Send Email »
  • More Articles »

This series of articles describes how to provide Web content to mobile devices through WML (Wireless Markup Language). This article covers creating small programs that extend the functionality of mobile devices, building on the previous article and example.

Note: These articles cover WML and WMLScript version 1.1, which are supported by the majority of mobile devices in use today. The articles assume a working knowledge of HTML and general Web technologies, and further assume that you have read the previous article(s) in this series.

Review

As mentioned in Part I, it is possible to add value to a mobile device by creating a small but ultimately useful application. In designing such an application, remember that the user will need to be online to use it, so the application's utility needs to be weighed against the potential cost of use.

Note: Most mobile service plans offer a base amount of online time dedicated to "Web" use. So the user generally isn't paying more for the occasional gadget use.

Simple Calculator

This project will cover how to create a simple calculator, building on some of the concepts covered in the previous article. Our design is straightforward, allowing the user to add, subtract, multiply, or divide two numbers.

Reviewing Data Input

In the last article we determined that it is easier (or at least faster) for the user to enter a floating-point number in two parts—the whole number portion and the decimal portion. The main reason lies in the inability to mix only numbers and symbols in a WML <INPUT> tag's "format" parameter. Without this ability, the user must switch to a symbol set to enter a decimal point, and then back to numbers to enter the decimal part of the entry.

Note: See documentation on the WML <INPUT> tag, or see the last article for clarification of format masks.

We will use the following code to accept input of a floating-point number:

<p>
Enter the value:
<input name="num" maxlength="8" format="*N"/><br/>
. <br/>
<input name="dec" maxlength="3" format="*N"/>
</p>

The first <INPUT> tag accepts the whole number portion of the input; the second accepts the decimal amount. Later we can combine them into the representative floating-point value.

The code above results in the following screen on the mobile device:

FIGURE 10.1
Notice the positioning of the decimal point onscreen to help the user understand the data-input scheme.

Image courtesy Openwave Systems Inc. (Openwave, the Openwave logo, Openwave SDK, Openwave SDK Universal Edition, Openwave SDK WAP Edition are trademarks of Openwave Systems Inc. All rights reserved.)

Designing the User Interface

WML supports rudimentary graphics, but displaying a graphical representation of a calculator--like you would find on a PDA--is a bit ambitious. Our design will have to be simple, boiled down to only the necessary components:

  • The two numbers involved in the computation
  • The operation to be performed
  • A few simple controls

Utilizing a <SELECT> statement, we can let the user change the input, change the operator, or perform the calculation. Consider the following rough draft of the interface:

     1     <first number>
     2     <operator>
     3     <second number>
     4     Compute

This compact display shows the user everything he/she needs to see and know—the numbers, the operator, and what to press to perform the operation. When the appropriate option is chosen, the code allows the user to change the corresponding data. For example, when the user presses "1" to change the first number, the code displays the <INPUT> statements outlined earlier. After accepting the input, the code returns to the display and displays the new value. The same is done for the second value, except the user presses "3" to enter the second number.

Choosing the operator is even easier—each selection of the option "2" cycles to a new operator: "+," "-," "x," or "/."

Finally, the user presses "4" to perform the operation.

Let's take an example and run through how the user would perform it:

  22.5  /  3    (twenty-two point five, divided by three)

The user would do the following steps:

  1. Press "1."
  2. Enter "22".
  3. Enter "5".
  4. Press "2" three times (cycling from "+" to "-" to "x" and finally to "/")
  5. Press "3."
  6. Enter "3".
  7. Enter "". (Nothing for the decimal portion)
  8. Press "4."

Our calculator would then display the result of the calculation: 7.5.

Basic Coding

WML can't perform all the necessary tasks, especially the mathematic computations of the actual calculation. For the intelligence behind the gadget, we will use WMLScript. We will need three WMLS functions:

  1. A function to initialize the calculator
  2. A function to cycle the operator
  3. A function to perform the calculation

These functions are used in the following cases:

  1. Initialization: When the calculator is first called, or reset
  2. Cycle operator: When the user presses "2" on the main menu
  3. Perform calculation: When the user presses "4" on the main menu

What we won't need is any logic for the other two main menu options—the input of both numbers. For simplicity's sake, let's start by coding the WML cards to display the main menu and accept the numeric input:

<!-- Display current values and operator -->
<card id="display">
     <p>
     <select>
          <option onpick="#num1">$(num1).$(dec1)</option>
          <option onpick="[change operator funct]">$
                (operator)</option>
          <option onpick="#num2">$(num2).$(dec2)</option>
          <option onpick="[do calc funct]">Compute</option>
     </select>
     </p>
</card>

<!-- Accept input for value 1 -->
<card id="num1">
     <do type="accept">
          <go href="#display"/>
     </do>
     <!-- Offer easy way to clear values (options key) -->
     <do type="options" label="clear">
          <refresh>
               <setvar name="num1" value=""/>
               <setvar name="dec1" value=""/>
          </refresh>
     </do>
     <p>
     Enter the value:
     <input name="num1" maxlength="8" format="*N"/><br/>
     .<br/>
     <input name="dec1" maxlength="3" format="*N"/>
     </p>
</card>

<!-- Accept input for value 2 -->
<card id="num2">
     <do type="accept">
          <go href="#display"/>
     </do>
     <!-- Offer easy way to clear values (options key) -->
     <do type="options" label="clear">
          <refresh>
               <setvar name="num2" value=""/>
               <setvar name="dec2" value=""/>
          </refresh>
     </do>
     <p>
     Enter the value:
     <input name="num2" maxlength="8" format="*N"/><br/>
     .<br/>
     <input name="dec2" maxlength="3" format="*N"/>
     </p>
</card>

Note that for now we are using pseudo-calls in the main menu for the WMLS function calls (options 2 and 4).

The code is straightforward: a separate card is called for each number entry, the card accepts a whole number and decimal, and then the program returns to the display card (which displays the data entered). Each data-entry card offers a "clear" function by pressing the "option" key--pressing that key clears the input in the whole number and decimal inputs via the <REFRESH> mechanism.

Note that we do not combine the numbers, but display them manually on the "display" card (e.g., "$(num1).$(dec1)"). This allows us to maintain the entry of the whole number and decimal as two separate pieces. When the user chooses to modify these values, they are shown as entered, namely, as two separate values.

Note: You might have noticed the one artifact to our display method: the decimal is always displayed, even when there is no entry for the number.

FIGURE 10.02
When the user chooses to edit an entry, it is shown as entered—in two components.

Image courtesy Openwave Systems Inc. (Openwave, the Openwave logo, Openwave SDK, Openwave SDK Universal Edition, Openwave SDK WAP Edition are trademarks of Openwave Systems Inc. All rights reserved.)

Scripting Code

Let's take a look at the functions we spec'd out earlier:

Initialization

Simple enough: this code just initializes the variables used by the calculator and then calls the "display" card:

// Initialize variables
extern function init() {

WMLBrowser.setVar("num1","");
WMLBrowser.setVar("dec1","");

WMLBrowser.setVar("num2","");
WMLBrowser.setVar("dec2","");

WMLBrowser.setVar("operator","+");
WMLBrowser.setVar("result",0);

WMLBrowser.go("calc.wml#display");
}
Cycling the Operator

Essentially a handful of IF statements, this code cycles to the next operator every time it is called, rotating back to the first operator when it reaches the end. The "display" card is then called to display the new operator.

// Rotate operator through + - * /
extern function changeop() {

var operator = WMLBrowser.getVar("operator");

if (operator == "+") {
  WMLBrowser.setVar("operator","-");
} 
if (operator == "-") {
  WMLBrowser.setVar("operator","x");
}
if (operator == "x") {
  WMLBrowser.setVar("operator","/");
}
if (operator == "/") {
  WMLBrowser.setVar("operator","+");
}

WMLBrowser.go("calc.wml#display");
}

Note the use of "x" as the multiplication operator instead of the programming equivalent ("*") for the non-programming user.

Doing the Calculation

This function is a bit more complex because it must combine the whole number and decimal portions of both numbers and operate on the floating-point value of the combination. The routine then calls a "result" card to display the result of the calculation.

// Do the operation specified
extern function domath() {

var operator = WMLBrowser.getVar("operator");
var num1 = WMLBrowser.getVar("num1");
var dec1 = WMLBrowser.getVar("dec1");
var num2 = WMLBrowser.getVar("num2");
var dec2 = WMLBrowser.getVar("dec2");
var result = 0;

// Zero values if nothing was entered
if (String.isEmpty(num1)) { num1 = 0; }
if (String.isEmpty(dec1)) { dec1 = 0; }
if (String.isEmpty(num2)) { num2 = 0; }
if (String.isEmpty(dec2)) { dec2 = 0; }

// Assemble full numbers from whole values
//  and decimals
num1 = num1 + "." + dec1;
num2 = num2 + "." + dec2;

// Do calculation, depending on what operator 
//  was selected
if (operator == "+") {
  result = Lang.parseFloat(num1) + Lang.parseFloat(num2);
}

if (operator == "-") {
  result = num1 - num2;
}

if (operator == "x") {
  result = num1 * num2;
}

if (operator == "/") {
  result = num1 / num2;
}

WMLBrowser.setVar("result", result);
WMLBrowser.setVar("num2",num2);
WMLBrowser.setVar("num1",num1);
WMLBrowser.go("calc.wml#result");
}

As you may recall from Part I, we can take advantage of WMLScript's untyped variables, using string functions to combine the whole number and decimal portions of each number--by concatenating the whole number, a decimal, and the decimal portion. Judicious use of the parseFloat function saves us from embarrassing results like this:

22.5 + 3.5  =  22.53.5   (concatenation instead of addition)

Putting It all Together

Putting all the code together, we arrive at the following completed example:

CALC.WML
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
   "http://www.wapforum.org/DTD/wml_1.1.xml">

<wml>
   <!-- Call init routine when deck is first entered,
      or when card is called -->
   <card id="init">
      <onevent type="onenterforward">
         <go href="calc.wmls#init()"/>
      </onevent>
      <p>
      Initializing...
      </p>

   </card>


   <!-- Display current values and operator -->
   <card id="display">
      <p>
      <select>
         <option onpick="#num1">$(num1).$(dec1)</option>
         <option onpick="calc.wmls#changeop()">$
                  (operator)</option>
         <option onpick="#num2">$(num2).$(dec2)</option>
         <option onpick="calc.wmls#domath()">Compute</option>
      </select>
      </p>

   </card>

   <!-- Accept input for value 1 -->
   <card id="num1">
      <do type="accept">
         <go href="#display"/>
      </do>
      <!-- Offer easy way to clear values 
         (options key) -->
      <do type="options" label="clear">
         <refresh>
            <setvar name="num1" value=""/>
            <setvar name="dec1" value=""/>
         </refresh>
      </do>
      <p>
      Enter the value:
      <input name="num1" maxlength="8" format="*N"/><br/>
      .<br/>
      <input name="dec1" maxlength="3" format="*N"/>
      </p>
   </card>

   <!-- Accept input for value 2 -->
   <card id="num2">
      <do type="accept">
         <go href="#display"/>
      </do>
      <!-- Offer easy way to clear values 
         (options key) -->
      <do type="options" label="clear">
         <refresh>
            <setvar name="num2" value=""/>
            <setvar name="dec2" value=""/>
         </refresh>
      </do>
      <p>
      Enter the value:
      <input name="num2" maxlength="8" format="*N"/><br/>
      .<br/>
      <input name="dec2" maxlength="3" format="*N"/>
      </p>
   </card>

   <!-- Display result of calculation -->
   <card id="result">
      <do type="accept">
         <go href="#init"/>
      </do>
      <p>
      The result of <br/>
      $(num1) $(operator) $(num2) is:<br/>
      $(result)
      </p>
   </card>

</wml>
CALC.WMLS
// Calc.wmls - WMLScript routines for calculator gadget


// Initialize variables
extern function init() {

WMLBrowser.setVar("num1","");
WMLBrowser.setVar("dec1","");

WMLBrowser.setVar("num2","");
WMLBrowser.setVar("dec2","");

WMLBrowser.setVar("operator","+");
WMLBrowser.setVar("result",0);

WMLBrowser.go("calc.wml#display");
}


// Rotate operator through + - * /
extern function changeop() {

var operator = WMLBrowser.getVar("operator");

if (operator == "+") {
  WMLBrowser.setVar("operator","-");
} 
if (operator == "-") {
  WMLBrowser.setVar("operator","x");
}
if (operator == "x") {
  WMLBrowser.setVar("operator","/");
}
if (operator == "/") {
  WMLBrowser.setVar("operator","+");
}

WMLBrowser.go("calc.wml#display");
}


// Do the operation specified
extern function domath() {

var operator = WMLBrowser.getVar("operator");
var num1 = WMLBrowser.getVar("num1");
var dec1 = WMLBrowser.getVar("dec1");
var num2 = WMLBrowser.getVar("num2");
var dec2 = WMLBrowser.getVar("dec2");
var result = 0;

// Zero values if nothing was entered
if (String.isEmpty(num1)) { num1 = 0; }
if (String.isEmpty(dec1)) { dec1 = 0; }
if (String.isEmpty(num2)) { num2 = 0; }
if (String.isEmpty(dec2)) { dec2 = 0; }

// Assemble full numbers from whole values
//  and decimals
num1 = num1 + "." + dec1;
num2 = num2 + "." + dec2;

// Do calculation, depending on what operator 
//  was selected
if (operator == "+") {
   result = Lang.parseFloat(num1) + Lang.parseFloat(num2);
}

if (operator == "-") {
   result = num1 - num2;
}

if (operator == "x") {
   result = num1 * num2;
}

if (operator == "/") {
   result = num1 / num2;
}

WMLBrowser.setVar("result", result);
WMLBrowser.setVar("num2",num2);
WMLBrowser.setVar("num1",num1);
WMLBrowser.go("calc.wml#result");
}

Calculator in Action

FIGURE 10.3
After entering the numbers and changing the operator, the user presses "4" to perform the calculation.

Image courtesy Openwave Systems Inc. (Openwave, the Openwave logo, Openwave SDK, Openwave SDK Universal Edition, Openwave SDK WAP Edition are trademarks of Openwave Systems Inc. All rights reserved.)

FIGURE 10.4
The result is displayed along with the components of the calculation.

Image courtesy Openwave Systems Inc. (Openwave, the Openwave logo, Openwave SDK, Openwave SDK Universal Edition, Openwave SDK WAP Edition are trademarks of Openwave Systems Inc. All rights reserved.)

Room for Improvement

There are several things we could do to make the calculator more useful:

  • Store the result in the "first number" variable. This would allow the user to enter several calculations all in a row.
  • Add more functions: Exponents, square roots, etc. (Note that some functions—like square roots—would be performed only on the first number.)
  • Add a "Clear" function on the main "display" page to clear all data and reset the calculator.
  • Add a memory feature to store the result in memory for later recall. The ambitious programmer could add a host of memory features such as "Store in Memory," "Add to Memory," "Subtract from Memory," etc.
  • Fix the "decimal display" artifact.

Note that some of these features would also necessitate using more logic to parse the numbers into and out of the <INPUT> routine. For example, passing a value with a decimal amount to the first number would require breaking the number into two components (whole number and decimal) and storing each in the appropriate variable(s), or completely changing the scheme of how numbers are input.

Next Time

In the next article we will take these concepts further by implementing a calendar gadget with a little CGI integration.

What Do You Want from WML?

I'm interested in hearing what you need/want to do with WML. I'll use some of the more challenging or common ideas in upcoming articles. Send your ideas to the address below.

About the Author

Steve Schafer is the chief operating officer of Progeny Linux Systems, a Linux-based consulting company in Indianapolis, Indiana. He has written several technical books and articles and can be reached at sschafer@synergy-tech.com.

# # #






Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel