http://www.developer.com/

Back to article

Building WML Gadgets: Phone Message Application


January 31, 2003

This series of articles describes how to provide Webcontent to mobile devices through WML (Wireless Markup Language). This articlecovers creating an application to aid the user of a mobile phone.

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

Simple Applications

Not all wireless applications have to be super-applications.Some of the best wireless applications perform simple tasks to improve wirelessfunctionality. The last few articles in this series have shown how simple,single-purpose gadgets can boost the functionality of mobile devices. Thisarticle will present a slightly more complex application in the same"extending functionality" vein.

The Application

This article will cover how to build a simple phone messageapplication. Although we live in a time of portable phones, intelligentvoicemail, and other electronic telephone magic, there are still times whenmessages are taken by one person (operator) and passed to others (recipients).For example, consider a businessman who often travels outside the home office.Many of his customers and contacts may occasionally call the home office andleave messages with his secretary. Using a simple Web form, the secretary canpass the message to the businessman's cell phone, where he can review themessage and even return the call with the simple press of a button.

Application Specifications

This application will utilize the following components:

  • A simple HTML form to input the message
  • A flat-file database to store the messages
  • A CGI script to access the database

Essentially, the application operates as shown in thefollowing diagram:

FIGURE 1 - Our application's design. The operator uses a Webform to send the data to a CGI script that stores the data in a database. Thesame script is used by a mobile user (recipient) to access that data.

We'll use Perl for the CGI script, for the same reasonswe've used it previously: It's available for most platforms and extensibleenough to perform almost any task necessary.

Note: As with previous articles, teaching Perl is out of thescope of this series. There are numerous sources on the Internet for learningPerl, including the tutorial at http://wdvl.internet.com/Authoring/Languages/Perl/PerlfortheWeb/toc.html.

Coding the Application

Let's break down the individual processes and then code foreach.

Database Design

Our "database" will be a simple delimited flatfile. Although we could go the fancy route with an actual database format, thedelimited format will work well for our simple application.

The database will contain the following fields:

  • Date and time the message was taken
  • Caller's name
  • Caller's message
  • Caller's phone number

We'll use a double vertical bar for our delimiter. We coulduse a more standard delimiter, such as a comma, but we need something thatwouldn't end up in the middle of the message field. In short, our databaserecords will resemble the following:

<data and time>||<caller's name>||<caller'smessage>||<caller's number>

HTML Form for Data Entry

We'll use a simple HTML form for entering the data:

Listing: msgform.html

<html><body><form name="msgform" method="post" action="phonemsg.pl"><center>Telephone Message</center><br><table><tr><td><input type="hidden" name="cmd" value="save">From:</td><td><input type="text" name="from" size="100" maxlength="100"></td></tr><tr><td>Message:</td><td><textarea name="message" cols="100" rows="5" wrap="virtual"></textarea></td></tr><tr><td>Callback #:</td><td><input type="text" name="number" size="12" maxlength="12"></td></tr><tr><td colspan="2"><center><input type="submit" value="Submit"></center></td></tr></table></form></body></html>

Note that we include an extra field, "cmd." Thishidden field will be passed to our CGI script to tell it what to do; namely,"save" the data.

The CGI Script

Our CGI script will be one multipurpose script, performingthe following functions:

  • Saving the data (caller info)
  • Listing the caller record(s)
  • Displaying a selected record's details
  • Automatically dialing the caller's number
  • Optionally deleting the record and then calling the number

The script could also display the input form. However, formaximum portability, we'll use a simple HTML file. With this method, our formcan easily be included in almost any Web page template, simply by applying astyle sheet or by cutting-and-pasting the "guts" of the form intoanother page.

Saving the Data

The following code fragment saves the data entered into theform:

Listing: phonemsg.pl - Save data fragment

  # Start response page  print header;  print "<html><head>";  print "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"2; URL='msgform.html'\">";  print "</head><body>Please wait...<p>";    # Grab the parameters  $from = param('from');  $message = param('message');  $callback = param('number');  # Remove any vertical bars   $message =~tr/|/ /;  # Check file lock  $count = 0;  if (-e "msgfile.lock") {    select(undef,undef,undef,0.1);    $count++;    if ($count = 10) { die "Can't open message file! </body></html>"; }  }  open LOCK, ">msgfile.lock";  # Write new record to end of file  open FILE, ">>msgfile.txt";  print FILE $date ." ". $time ."||";  print FILE $from ."||". $message ."||". $callback;  print FILE "\n";  close FILE;    close LOCK;  unlink "msgfile.lock";    # All done, close response page  print "</body></html>";

Because the form and the mobile device could both beutilizing the script (and hence, the database) simultaneously, we must use filelockingto avoid having two processes accessing the database and corrupting ourdata. We'll use a simple method: creating a file to lock the databaseif thefile exists, the process waits for it to be deleted before accessing thedatabase. When a process is done with the database, the lock file is deletedand other processes are allowed to access the database. Instead of failingright away if the file is locked, or waiting forever for the file to beunlocked, the code loops 10 times, waiting a tenth of a second between theiterations. If the file is still locked, we assume that something has gonewrong and exit with an appropriate error message.

Windows users: Some implementations of Perl on the Windowsplatform don't support the four-argument call of "select" used in ourfile-locking loop. If this code generates errors on a Windows system,substitute another delay function.

Listing the Records on the Mobile Device

The following code fragment comprises the code to list fiverecords on the mobile device, along with an option to move to the next fiverecords:

Listing: phonemsg.pl - List records fragment

  #Pass WML header  print header(-type=>'text/vnd.wap.wml');# Print WML header and beginning tagsprint <<ENDHEADER;<?xml version="1.0"?><!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"  "http://www.wapforum.org/DTD/wml_1.1.xml"><wml><card>ENDHEADER  # Print start of cardprint <<BEGINCARD;<p mode="nowrap">Call List<br/><select name="Display" title="Display">BEGINCARD    # Check file lock  $count = 0;  if (-e "msgfile.lock") {    select(undef,undef,undef,0.1);    $count++;    if ($count = 10) {       die "Can't open message file! </select></p></card></wml>";    }  }  open LOCK, ">msgfile.lock";  # Read records into array  open FILE,"msgfile.txt";  push(@lines,$_) while (<FILE>);  close FILE;  close LOCK;  unlink "msgfile.lock";  # Nextitem = first item to display  $nextitem = param('nextitem');  if ("$nextitem" eq "") { $nextitem = 0; }  # Lastitem = last item to display  #  (nextitem + 4 or end of file)  if ($nextitem + 4 <= @lines) {    $lastitem = $nextitem + 4;  } else {    $lastitem = @lines - 1;  }   # Print first item through last item  for ($i = $nextitem; $i <= $lastitem; $i++) {    ($datetime,$from,$message,$callback) = split /\|\|/,$lines[$i];    print "<option onpick='phonemsg.pl?cmd=display&rec=$i'>";    print $from ." (". $datetime .") \n";    print "</option> \n";  }    # Set nextitem for NEXT function  $nextitem = $lastitem + 1;    # Display NEXT option  print "<option onpick='phonemsg.pl?cmd=list&nextitem=$nextitem'>";  print "Next</option> \n";  print "</select> \n</p> \n";  print "</card> \n</wml> \n";

Most of the work for this fragment happens in the lastquarter of the code. After the stage has been set (WML card defined), thedatabase is read into an array and then the correct set of records isdisplayed, five records at a time. Initially, the first five records aredisplayed and an option is set for the next five to be selected (through the"nextitem" variable). If the user picks the "Next" option,the script is called again, with the name/value pair "nextitem" setto the "next item" to display.

Each record is displayed as an <option> whose "onpick"attribute results in the script being called with the record number to display.(See the next section for the record-display format and the whole scriptsection for details on the format and use of the $cmd variable.)

The file-locking routine for the database differs only inthe message that's displayed if a lock cannot be achieved. The messageincorporates enough WML code to complete the card, ensuring that the user getsthe failure message instead of a WML compile error.

Displaying a Specific Record on the Mobile Device

When the user picks a record from the laundry list ofrecords, the script needs to be able to display the record's details. Thefollowing code takes care of that activity:

Listing: phonemsg.pl - Display specific record fragment

  # What record to display  $rec = param('rec');  #Pass WML header  print header(-type=>'text/vnd.wap.wml');# Print beginning of WML fileprint <<ENDHEADER;<?xml version="1.0"?><!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"  "http://www.wapforum.org/DTD/wml_1.1.xml"><wml><card>ENDHEADER    print "<p mode=\"wrap\"> \n";  # Check file lock  $count = 0;  if (-e "msgfile.lock") {    select(undef,undef,undef,0.1);    $count++;    if ($count = 10) { die "Can't open message file!</p></card></wml>"; }  }  open LOCK, ">msgfile.lock";  # Load records into array  open FILE,"msgfile.txt";  push(@lines,$_) while (<FILE>);  close FILE;  close LOCK;  unlink "msgfile.lock";  # Split record into fields  ($datetime,$from,$message,$callback) = split /\|\|/,$lines[$rec];# Display record with "call" #    and "call & delete" optionsprint <<MESSAGE;$datetime<br/>$from<br/><br/>$message<br/>Callback Number:<br/>$callback<br/><a href="wtai://wp/mc;$callback" title="Call">Call</a> <a href="./phonemsg.pl?cmd=callNdel&number=$callback&rec=$rec"   title="CallnDel">Call & Del</a></p></card></wml>MESSAGE

The record number to display is passed to the script via the"rec" variable. As in the other cases, the file lock is checked, thedatabase is locked, the database is read into an array, and then the databaseis released (unlocked). The required record is then read from the array,unpacked into fields (based on the delimiter), and displayed.

Notice that two links are placed at the bottom of thedisplayed record: "Call" and "Call & Del(ete)". Thefirst simply uses the "call this number" URL format to make themobile device dial a number. The latter option allows the user to delete the messagebefore placing the call, since the message is being returned and should nolonger be stored in the database as an open message.

Placing a Call and Optionally Deleting a Record

The script handles returning a call by including a link to aURL containing the number to call (see the previous section). Deleting a recordbefore the call is slightly more complex, and is handled by the following code:

Listing:  phonemsg.pl - Place call/delete record fragment

  # What number to call and what record to delete  $number = param('number');  $rec = param('rec');  #Pass WML header  print header(-type=>'text/vnd.wap.wml');# Print start of WML fileprint <<ENDHEADER;<?xml version="1.0"?><!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"  "http://www.wapforum.org/DTD/wml_1.1.xml"><wml><card>ENDHEADER# Print beginning of card #  (Number is dialed in 10 secs)print <<BEGCARD;  <onevent type="ontimer">      <go href="wtai://wp/mc;$number" /></onevent><timer name="delay" value="100"/><p mode="wrap">Deleting record $rec, preparing to call...BEGCARD  # Check file lock  $count = 0;  if (-e "msgfile.lock") {    select(undef,undef,undef,0.1);    $count++;    if ($count = 10) { die "Can't open message file!</p></card></wml>"; }  }  open LOCK, ">msgfile.lock";  # Read records into array  open FILE,"msgfile.txt";  push(@lines,$_) while (<FILE>);  close FILE;  # Print all records (except deleted)   # back to file  open FILE,">msgfile.txt";  for ($i = 0; $i < @lines; $i++) {    if ($i ne $rec) {      print FILE $lines[$i];    }  }  close FILE;  close LOCK;  unlink "msgfile.lock";  # Close card  print "</p> \n </card> \n </wml>";

To delete and call a number, the script is passed the numberand the record number to delete. The number could be retrieved from the recordbefore the deletion, but is passed separately so it doesn't have to be parsedfrom the record. This design decision falls into the "half-a-dozen versussix" category of decisions; I opted to pass the number we already have,saving the lines required to decode the record before deleting it.

To perform the deletion, we read the entire file into anarray and then reconstruct the file by writing all records except the deletedrecord back to the file.

The Completed Script

After adding a few declaration lines, the controllingstructure using the $cmd variable, and some connecting tissue, our completedscript becomes the following:

Listing: phonemsg.pl

#!/usr/bin/perl# Include modulesuse CGI qw(:standard);use Date::Calc qw(:all);# Set command to execute; default = list$cmd = param('cmd');if ("$cmd" eq "") { $cmd = "list"; }# Set current time/date($year,$month,$day) = Today();($hour,$min,$sec) = Now();$time = $hour.":".$min;$date = $month."/".$day."/".$year; # # Call from HTML form, save the data, and return to formif ("$cmd" eq "save") {  # Start response page  print header;  print "<html><head>";  print "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"2; URL='msgform.html'\">";  print "</head><body>Please wait...<p>";    # Grab the parameters  $from = param('from');  $message = param('message');  $callback = param('number');  # Remove any vertical bars   $message =~tr/|/ /;  # Check file lock  $count = 0;  if (-e "msgfile.lock") {    select(undef,undef,undef,0.1);    $count++;    if ($count = 10) { die "Can't open message file! </body></html>"; }  }  open LOCK, ">msgfile.lock";  # Write new record to end of file  open FILE, ">>msgfile.txt";  print FILE $date ." ". $time ."||";  print FILE $from ."||". $message ."||". $callback;  print FILE "\n";  close FILE;    close LOCK;  unlink "msgfile.lock";    # All done, close response page  print "</body></html>";}# # Call from mobile device to list callsif ("$cmd" eq "list") {    #Pass WML header  print header(-type=>'text/vnd.wap.wml');# Print WML header and beginning tagsprint <<ENDHEADER;<?xml version="1.0"?><!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"  "http://www.wapforum.org/DTD/wml_1.1.xml"><wml><card>ENDHEADER  # Print start of cardprint <<BEGINCARD;<p mode="nowrap">Call List<br/><select name="Display" title="Display">BEGINCARD    # Check file lock  $count = 0;  if (-e "msgfile.lock") {    select(undef,undef,undef,0.1);    $count++;    if ($count = 10) {       die "Can't open message file! </select></p></card></wml>";    }  }  open LOCK, ">msgfile.lock";  # Read records into array  open FILE,"msgfile.txt";  push(@lines,$_) while (<FILE>);  close FILE;  close LOCK;  unlink "msgfile.lock";  # Nextitem = first item to display  $nextitem = param('nextitem');  if ("$nextitem" eq "") { $nextitem = 0; }  # Lastitem = last item to display  #  (nextitem + 4 or end of file)  if ($nextitem + 4 <= @lines) {    $lastitem = $nextitem + 4;  } else {    $lastitem = @lines - 1;  }   # Print first item through last item  for ($i = $nextitem; $i <= $lastitem; $i++) {    ($datetime,$from,$message,$callback) = split /\|\|/,$lines[$i];    print "<option onpick='phonemsg.pl?cmd=display&rec=$i'>";    print $from ." (". $datetime .") \n";    print "</option> \n";  }    # Set nextitem for NEXT function  $nextitem = $lastitem + 1;    # Display NEXT option  print "<option onpick='phonemsg.pl?cmd=list&nextitem=$nextitem'>";  print "Next</option> \n";  print "</select> \n</p> \n";  print "</card> \n</wml> \n";}# # Call from mobile device to show call detailif ("$cmd" eq "display") {  # What record to display  $rec = param('rec');  #Pass WML header  print header(-type=>'text/vnd.wap.wml');# Print beginning of WML fileprint <<ENDHEADER;<?xml version="1.0"?><!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"  "http://www.wapforum.org/DTD/wml_1.1.xml"><wml><card>ENDHEADER    print "<p mode=\"wrap\"> \n";  # Check file lock  $count = 0;  if (-e "msgfile.lock") {    select(undef,undef,undef,0.1);    $count++;    if ($count = 10) { die "Can't open message file!</p></card></wml>"; }  }  open LOCK, ">msgfile.lock";  # Load records into array  open FILE,"msgfile.txt";  push(@lines,$_) while (<FILE>);  close FILE;  close LOCK;  unlink "msgfile.lock";  # Split record into fields  ($datetime,$from,$message,$callback) = split /\|\|/,$lines[$rec];# Display record with "call" #    and "call & delete" optionsprint <<MESSAGE;$datetime<br/>$from<br/><br/>$message<br/>Callback Number:<br/>$callback<br/><a href="wtai://wp/mc;$callback" title="Call">Call</a> <a href="./phonemsg.pl?cmd=callNdel&number=$callback&rec=$rec"   title="CallnDel">Call & Del</a></p></card></wml>MESSAGE}## Call from mobile device to dial number and del recordif ("$cmd" eq "callNdel" ) {  # What number to call and what record to delete  $number = param('number');  $rec = param('rec');  #Pass WML header  print header(-type=>'text/vnd.wap.wml');# Print start of WML fileprint <<ENDHEADER;<?xml version="1.0"?><!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"  "http://www.wapforum.org/DTD/wml_1.1.xml"><wml><card>ENDHEADER# Print beginning of card #  (Number is dialed in 10 secs)print <<BEGCARD;  <onevent type="ontimer">      <go href="wtai://wp/mc;$number" /></onevent><timer name="delay" value="100"/><p mode="wrap">Deleting record $rec, preparing to call...BEGCARD  # Check file lock  $count = 0;  if (-e "msgfile.lock") {    select(undef,undef,undef,0.1);    $count++;    if ($count = 10) { die "Can't open message file!</p></card></wml>"; }  }  open LOCK, ">msgfile.lock";  # Read records into array  open FILE,"msgfile.txt";  push(@lines,$_) while (<FILE>);  close FILE;  # Print all records (except deleted)   # back to file  open FILE,">msgfile.txt";  for ($i = 0; $i < @lines; $i++) {    if ($i ne $rec) {      print FILE $lines[$i];    }  }  close FILE;  close LOCK;  unlink "msgfile.lock";  # Close card  print "</p> \n </card> \n </wml>";}

Note that the default action of the script is to list therecords. This allows the mobile device to call the script without arguments("http://URL/phonemsg.pl") to get the ball rolling. Subsequent callsare handled by the script ("display," "callNdel," etc.)where it controls the parameters, saving the mobile user from having toenter/bookmark them.

To test the application, we seed the database with thefollowing data:

Listing: msgfile.txt - Sample data

1/25/2003 12:15||Caller Number01||Sample message, from sample caller.||317-555-12121/25/2003 12:25||Caller Number02||Sample message, from sample caller.||317-555-12121/25/2003 12:35||Caller Number03||Sample message, from sample caller.||317-555-12121/25/2003 13:15||Caller Number04||Sample message, from sample caller.||317-555-12121/26/2003 14:15||Caller Number05||Sample message, from sample caller.||317-555-12121/26/2003 14:23||Caller Number06||Sample message, from sample caller.||317-555-12121/26/2003 15:15||Caller Number07||Sample message, from sample caller.||317-555-12121/27/2003 9:15||Caller Number08||Sample message, from sample caller.||317-555-12121/27/2003 9:35||Caller Number09||Sample message, from sample caller.||317-555-12121/28/2003 8:05||Caller Number10||Sample message, from sample caller.||317-555-12121/28/2003 10:15||Caller Number11||Sample message, from sample caller.||317-555-12121/28/2003 11:11||Caller Number12||Sample message, from sample caller.||317-555-12121/28/2003 12:01||Caller Number13||Sample message, from sample caller.||317-555-12121/28/2003 14:15||Caller Number14||Sample message, from sample caller.||317-555-12121/28/2003 16:45||Caller Number15||Sample message, from sample caller.||317-555-12121/29/2003 8:25||Caller Number16||Sample message, from sample caller.||317-555-12121/29/2003 9:04||Caller Number17||Sample message, from sample caller.||317-555-12121/29/2003 10:35||Caller Number18||Sample message, from sample caller.||317-555-12121/29/2003 10:39||Caller Number19||Sample message, from sample caller.||317-555-12121/30/2003 12:15||Caller Number20||Sample message, from sample caller.||317-555-12121/30/2003 15:02||Caller Number21||Sample message, from sample caller.||317-555-12121/30/2003 16:05||Caller Number21||Sample message, from sample caller.||317-555-1212

Using this data, our application resembles the followinggraphics on a mobile device:

FIGURE 2 - The laundry list of messages.

FIGURE 3 - A selected message is displayed.

FIGURE 4 - Two links at the bottom of the record allow theuser to call and optionally delete the message.

Images are 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

This application makes a nice, general phone message system.However, given time and incentive, the following improvements could be made:

  • The code could be streamlined. Because it was written in sections for this article, the code is not as svelte as it could bein multiple places, code is duplicated that could be placed in commonly accessed functions/subroutines. Also, the code breaks a few "good Perl coding" rules (non-local variables, loose variable naming, etc.); that problem should be rectified.
  • There's no value checking in the HTML form and it is only set up to accept domestic numbers (12 characters, area code, prefix, suffix, and two dashes).
  • A real database structure could be used for the data, alleviating the need for stringent file locking and enabling true random access.
  • Another option could be added to enable the user to back up through the list of messages (we already allow forward access via the Next link).
  • A search feature could be added to find particular messages or to display messages in a specified timeframe.
  • Multiple users could be added by specifying the person taking the message (operator) and the person for whom the message is designated (recipient). Then multiple operators could take messages for multiple recipients. This would also necessitate a login or other authentication process for the mobile user (identifying himself/herself as the intended recipient), unless multiple users return calls from "the pool."
  • A status field could be added so the records could be tracked. Instead of the record simply existing ("need to call") or being deleted ("called"), a message could be flagged for a variety of purposes, including archiving.

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 ProgenyLinux Systems, a Linux-based consulting company in Indianapolis, Indiana. Hehas written several technical books and articles and can be reached at sschafer@synergy-tech.com.

# # #

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date