You are on page 1of 135

Learning WML - WAP Basics

By Steve Schafer

This series of articles describes how to provide Web content to mobile


devices through WML (Wireless Markup Language). This first article
covers the background of WAP, how it works, and what you need to
get started. Future articles will introduce the WML language, tips and
tricks to provide content, and how to integrate other technologies such
as PHP to make your pages more flexible.

What Is WAP?
WAP stands for Wireless Access Protocol, a general term used to
describe the multi-layered protocol and related technologies that bring
Internet content to mobile devices such as PDAs and cell phones.
Such devices are referred to as thin clients because they have one or
more constraints in the form of display, input, memory, CPU, or other
hardware or usability limitations. The platform constraints and the
slower (and more expensive) bandwidth of cellular and related
networks make standard Internet protocols difficult to utilize. Using
the growing set of WAP tools and protocols, however, the mobile
Internet is quite a capable tool.

A Brief History of WAP


As previously stated, WAP refers to a wide range of technologies and
protocols, all related to mobile Internet functionality. This functionality
has roots dating back to the mid 1990s. At that time, several vendors
were working on the mobile Internet problem as mobile device sales
skyrocketed, and several competing technologies emerged:
Nokia's Narrow Band Sockets (NBS) and Tagged Text Markup
Language (TTML)
Ericsson's Intelligent Terminal Transfer Protocol (ITTP)
Unwired Planet's Handheld Device Markup Language (HDML)
Each technology had its own purpose, but some overlapped with
others in various areas. This diversity threatened to fragment the
wireless industry along provider lines. In mid 1997, the WAP Forum
was founded to aid in communication among the developers and to

spur a common set of protocols and technologies. In the same year,


the industry took another step forward with the formation of the Open
Mobile Alliance (OMA), which combined several distinct development
and standards bodies into one.

How Does WAP Work?


These articles will focus on the delivery of WML content to mobile
devices over a cellular or related technology network. However, the
delivery of many protocols and technologies takes the same routenamely, through a proxy server that bridges the gap between the
wired Internet and the wireless service provider's network.

Figure 1.1 The WAP Gateway provides wireless networks with


Internet access and optional content translation and filtering.
This proxy server manages the communication between the wireless
client and the Internet server(s), acting as a gateway to the wired
Internet. It caches content and in some cases even translates raw
HTML into WAP-compatible protocols such as WML.
Many mobile devices have a built-in wireless browser. Although several
different browsers are in use today among the various wireless
providers, most browsers support WML, either natively or translated
into HDML. A popular precursor to WML, the Handheld Device Markup
Language (HDML), is still supported on several mobile platforms.
However, due to the limitations of HDML (supporting only a handful of
navigation tags and virtually no formatting tags), WML is becoming the
most widely used mobile markup language. That said, if you plan to

support a particular platform, it's best to test your code extensively on


that particular device.
Note: When coding for the general public, be careful to stick to the
standards and avoid using proprietary extensions to the various
languages, no matter how tempting the feature set of the extensions.
If you decide to provide the extensions to those who can use them,
you should take the necessary server steps to identify the connecting
browser and deliver code customized for that browser.

What Is WML?
WML (Wireless Markup Language) is the dominant language in use
with wireless devices today. Essentially, WML is a subset of HTML, but
has its roots in XML. Those developers with a solid base in XML should
have a relatively easy time coding WML.
The current WML standard is 1.3, although many mobile devices in use
today support only the WML 1.1 standard. Therefore it's prudent to
stay away from 1.3-specific features, unless you know that your target
market's devices are 1.3-ready.
There are several key differences between WML and standard HTML,
including the following:
WML is highly structured and very particular about syntax. Several
current HTML browsers allow for "messy" code such as missing tags
and other formatting snafus. Such mistakes are not allowed in WML;
the mobile browser will complain and generally won't display the
page.
WML is case sensitive. The tags <b> and <B> are treated as
different tags, although they accomplish the same purpose (bold
text). Therefore, you must be careful to match the case of your
opening tags with your closing tags (for example, <b>This is
bold</B> will not work as expected).
Many tags have required attributes. Developers accustomed to HTML
may be used to including only attributes they need-in some WML
tags, you must include a few attributes, even if they are blank or
default.

WML pages are structured in "decks" (see the next section), allowing
for multiple pages to be defined in each WML file.
WML also has a client-side scripting language, WMLScript, to help
automate particular tasks, validate input, and so on. WMLScript is a
subset of JavaScript and will be covered in a later article.

Understanding Decks
WML pages are structured within "decks," allowing several pages
("cards") to be defined in each WML file. This deck analogy allows
multiple pages to be delivered to the mobile client at the same time,
minimizing the loading time between related pages. However, the
limited memory on most devices constrains the deck size, usually to
less than 1024 bytes. Therefore, careful consideration and planning
should go into any WAP application; don't start coding without
investing time in planning.
Note: Remember your audience. Mobile users generally scroll through
cards rapidly and will be reading on a display that's a mere handful of
characters wide (usually less than 20 characters) and usually less than
10 lines high. Keep your content to a minimum, provide an intuitive
navigation structure, and optimize your decks to maximize links within
the deck and minimize links outside of the deck.
Visualizing a physical "deck of cards" structure can help in
understanding the principles of WML. For example, suppose we have
three simple cards (pages) as shown below:

Figure 1.2 - The physical card analogy to WML decks helps


visualize how they work.
These cards together form a deck and are delivered to the mobile
device in one file. Now suppose that each card links to the next (card
one links to card two, which links to card three, and so on), and that
each card also has a "back" link to take the user back to the previous
card. As the user navigates the deck, the cards stack in memory as
shown below:

Figure 1.3 - As the user follows the links through the deck, the
cards stack up in memory.
A developer accustomed to HTML might be tempted to implement the
"back" feature by providing a link to the deck, specifying the previous
card. However, this would cause the mobile device to re-request the

whole deck before redisplaying the card-a card it already had in


memory.
Instead, you should use the tag, which tells the browser to remove
the current page and display the previous page in the history list (like
using the Back button on a PC browser). Of course, the content of the
previous page might need to be refreshed each time it's accessed; in
that case, valid techniques could include recalling the whole deck or
specifying that the page not be cached. Proper navigation will be
covered in future articles.

Figure 1.4 - The <prev> tag "pops" the top card off the stack
(out of the history list), redisplaying the previous card in the
history.

Setting Up Your Server for WML


To configure your Web server to deliver WML, you must define the
related MIME types for WML content. Web servers and client browsers
use MIME (Multipurpose Internet Mail Extensions) to communicate the
type of data that is being sent. Before sending data, the server sends
a MIME identifier to the client browser, identifying the format of the
following data. The client browser can then properly decode and apply
the data. Most WML applications require three MIME types, as listed in
the following table.
File Extension
MIME Type Definition
Use
.wml
text/vnd.wap.wml
WML source file
.wmls
text/vnd.wap.wmlscript WML script file
.wbmp
image/vnd.wap.wbmp
Wireless bitmap file (image)

To add MIME types to your Web server, you must have administrator
access to the server. The following sections cover setting up
Microsoft's Internet Information Server (IIS) and Apache for WML. If
you're using another type of server, read your server's documentation
for more information on adding MIME types.
Adding MIME Types to Internet Information Server (IIS)
To add the MIME types to IIS, open the Internet Information Services
Management Console (MC). Access to this console varies depending on
which specific operating system you're using and how you installed
IIS, but can usually be found under Administrative Tools (Windows
2000) or Option Pack (Windows NT).
Open the IIS MC, click on the server to expand its tree, and then rightclick on Default Web Site and choose Properties. (Note: If you don't
want all the sites on your server to be able to deliver WML, right-click
on those sites you want to be WML-enabled and then continue
following these steps.)
Click on the HTTP Headers tab and then click on the File Types button
under the MIME Map section. In the File Types dialog, click on New
Type and enter the extension and MIME definition ("Content Type")
from the preceding table. Click on OK. Repeat this process for the
other two MIME types.
When you're finished, close the Web Site Properties dialog by clicking
on OK. On some servers, there may be nodes or devices that also
define HTTP codes and need to inherit the new setting(s). Choose the
appropriate options for your system. Exit the IIS MC. Usually you
won't need to restart the IIS service, but it wouldn't hurt to do so just
in case.
Tip: Before exiting the Web Site Properties, you may want to add an
entry for WML on the Documents tab (such as index.wml). This causes
the server to display that document by default, eliminating the need
for your visitors to specify a particular file in the URL to access your
site.
Adding MIME Types to Apache

To add MIME types to Apache, you must edit the httpd.conf file. This
file's location varies from system to system.
This file uses "AddType" lines to define MIME types. Find the section
where these appear and add the following lines:
AddType text/vnd.wap.wml .wml
AddType text/vnd.wap.wmlscript .wmls
AddType image/vnd.wap.wbmp .wbmp

Save and close the file and restart the Apache server to reload the
configuration with the new MIME types.
Tip: You may want to add "index.wml" or comparable entry to the
DirectoryIndex section of the Apache configuration file (requires
running mod_dir). This causes the server to display that document by
default, eliminating the need for your visitors to specify a particular file
in the URL to access your site.

A Sample WML Deck


Now that your server is set up to handle WML correctly, let's try
serving up a sample page. The following listing shows the bare
minimum coding necessary to contain a WML deck, consisting of a
single, blank card:
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
<card id="Card1" title="Sample ">
</card>
</wml>

The first line of the preceding code specifies that the file is XML and
version 1.0-compatible. The second line defines the XML scope of the
file; namely, that its DOCTYPE is WML, and where the Document Type
Definition (DTD) can be found.
Tip: If you're unfamiliar with XML and don't fully understand these
lines, just make sure that they appear at the beginning of all your

WML documents.
The next line begins the WML definition with the <wml> tag.
The <card> tag defines a card in the deck. Note that the id and title
attributes can be anything you choose, but should be short and to the
point, and the id must contain only letters and numbers (no
punctuation or spaces).
If you want to try the preceding example, create the file on your Web
server (in plain text form), adding the following lines between the
<card> tags to provide content for the card:
<p>
A Sample Card
</p>

Place the file in an accessible directory with adequate permissions to


access it from an external browser. Now visit the Wapalizer at
http://www.gelon.net. Type the URL to the file in the Wapalizer box
and click on the Wapalize button. You should see a screen similar to
the output below.

Figure 1.5 - Your sample page in the Openwave simulator.


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.)

Learning WML - Tools and Structure


This series of articles describes how to provide Web content to mobile
devices through WML (Wireless Markup Language). This article covers
some essential tools for coding and debugging WML code, as well as
the basics of creating WML decks. Future articles will cover advanced
WML language, tips and tricks to provide content, and how to integrate
other technologies such as PHP to make your pages more flexible.
Note: These articles cover WML version 1.1, which is 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.

Essential Documentation and Tools


There are several tools you should assemble before beginning to code
your WML applications.
Note: The recommendations in this article are simply that:
recommendations. You can make do with a simple text editor (such as
Windows Notepad or vi in Linux) and the Web server, but spending
some time assembling some good tools will pay off in the long run.
Tip: If you decide not to use one of the WAP SDKs/IDEs and use
Windows as your OS, try TextPad, a nifty text editor that's very
versatile and code friendly (http://www.textpad.com).

Documentation
There is an abundance of WAP/WML documentation on the Web,
mostly courtesy of the Open Mobile Alliance (OMA) and other WAP
organizations. You should spend some time reading the user interface
(UI) suggestions, developer guidelines, and language reference text at
the sites listed in the following sections.

Open Mobile Alliance - http://www.openmobilealliance.org


The Open Mobile Alliance (OMA) is a relatively new standards
organization made up of several key players in the WAP arena,
including most cellular providers and phone manufacturers - visit
http://www.openmobilealliance.org/members.html for a full list. The
new site is still evolving, but much of the content from the previous
site (the popular "WAP Forum") is still available. As of this writing, you
can access the documentation by using the "Access to WAP specific
information" link at the bottom of the main OMA page. A full list of
WML release specifications, WAP specifications, and DTD definitions is
available.
Openwave Developer Program http://developer.openwave.com
Openwave, the company behind the popular Openwave Mobile Browser
(formerly the UP.Browser) maintains a library of technical documents
and developer guidelines. Follow the links on the left side of the main
page to access WML language references (in the Technical Library), UI
guidelines, lists of supported devices, and more.
Forum Nokia - http://www.forum.nokia.com
Nokia was one of the pioneers in the WAP arena and maintains an
impressive number of emulators and related documentation. Their
Documents section has a handy search feature to help find the
document most applicable to your needs.

Emulators and SDKs


You could test your WML code with a cell phone, but most providers
charge a premium for Web access, and testing could get rather
expensive. Instead, use one of the following emulators, some of which
are packaged with a full SDK_some even include a full Integrated
Development Environment (IDE).
Note: Most of the emulators and SDKs are only available for Windows,
although a few are available for Solaris and other operating systems.

Tip: No emulator can fully replace actual device testing. Although you
can initially code and debug on one of the emulators, don_t neglect
testing on your actual target device(s) as well.
The Gelon Wapalizer - http://www.gelon.net
The Wapalizer is a Web-based WAP emulator and offers several skins
to emulate popular phones. Simply type your URL into the Wapalizer
box and click the Wapalize button to open your code in the default
emulator. Alternatively, choose a different skin and use the emulated
device's interface to navigate to your URL.
Note: The Wapalizer is not as robust as some of the other emulators.
Although it's a handy way to quickly check a page, you should also use
an emulator that closely resembles your target microbrowser.
The Openwave SDK - http://developer.openwave.com
The Openwave SDK 5.1 provides the best suite of tools I've run
across; I use it as my primary coding and testing environment. The
Openwave developer site (http://developer.openwave.com) has
several SDKs available, depending on what WML language revision and
feature set you need. SDK 5.1 can be used for most applications,
providing that you stay within the coding boundaries for your target
device's supported version of WML and you fully test your code on the
target device(s).
Nokia Emulators - http://www.forum.nokia.com
Nokia maintains an impressive list of emulators, including several that
tie in to their Mobile Internet Toolkit. Note that you must register on
the site to download any of the tools. Many are Java-based, and
therefore require you to install a Java Virtual Machine (JVM) on your
system to run the tool.
Other Emulators
If your device isn't compatible with the emulators listed above, visit
the manufacturer's site for the device to see whether they offer a
compatible tool.

Tip: If you need to find out what browser your target device uses,
access a nonexistent file on your Web server and examine your server
logs for the browser identification information. (A nonexistent file
makes it easy to find the pertinent log entry.) For example, if I use my
Samsung A400 phone to access "missingfile.jpg" on my Web server, I
find the following log entry:
[25/Jun/2002:16:27:50 -0500] "GET /wireless/missingfile.jpg
HTTP/1.1" 404 306 "-" "SEC-spha400 UP.Browser/4.1.22b1
UP.Link/5.0.2.3c"
This entry shows the client ("spha400") and the browser and version
(UP.Browser 4.1.22b, now the Openwave Mobile Browser, but still
identified as UP.Browser).

Basic WML Structure and Rules


Now that we have our server set up and our toolkit stocked, we're
almost ready to do some coding. First let's examine the basic form of
WML documents and some basic coding rules.
Basic WML Deck Structure
The following listing shows the basic elements you need in most WML
decks:
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
<head>
<meta.../>
</head>
<card id="cardid1" title="Card1">
...
</card>
<card id="cardid2" title="Card2">
...
</card>
...
</wml>

You may remember the document preamble code (first two lines) from
the previous article. These lines are necessary to identify the type of
content (WML) to the browser. They need to be present in all files.
The deck is then started with a <wml> tag, and ended with a </wml>
tag.
Note that the <head> section is optional, but is necessary to supply
any meta information (via <meta> tags) required for your deck.
Typically, cache instructions are passed via meta tags included in the
<head> section. Leaving the <head> section blank by including only
the <head> tags is a bad idea; it can cause some browsers to fail.
Include this section only if you need to include <meta> or <access>
tags.
What follows is the actual deck, made up of individual cards, each with
a mandatory ID and title parameter. (Other parameters for the
<card> and other tags will be covered in later articles.) Some
browsers display the card title at the top of the display; others don't.
It's important to make your card titles display-ready (short but
meaningful, and so on).
Tip: It may be helpful to have this information stored in a file as a
template for all your WML coding. Note that some SDKs provide a
skeletal WML structure when you create a new file.
WML Language Rules
Let's quickly review some basic WML language rules:
Most tags have opening and closing components. Those that don_t
(<access>, <br/>, <img>, <meta>, and so on) include a slash at
the end of the tag, signifying that the tag is singular (opening and
closing).
The language is case sensitive, so all closing tags must match the
capitalization of the opening tags. (<B>This is bold</b> will not
have the desired results.)
All tag parameter values must be enclosed in quotes (for example,
<p mode="nowrap">).
All text must be enclosed in a tag, even if that tag is just a simple
paragraph tag (<p>).
There are some elements that must appear in a certain order. For

example, within a element, the following must appear in order:


<onevent>, <timer>, <do>.

WML Text Formatting


Like HTML, WML supports several types of text formatting. The
following table describes the available text-formatting tags.
Tag
Name
<p>
Paragraph
<br/>
Line break
program.
<b>
Bold
<big>
Big
<em>
Emphasized
<i>
Italic
<small>
Small
<strong> Strong
size and italic.)
<u>
Underline

Use
Mark blocks of text as paragraphs.
Break the current line, much like pressing Enter in a word processing
Boldface the delineated text.
Make the text appear in a large point size.
Emphasize the text (usually with italics).
Display the text in an italic font.
Display the text in a small point size.
Display the text in a strongly emphasized font. (Usually a large point
Display the text in an underlined font.

Note: Many of these text attributes (such as <big>, and so on) are not
supported by all mobile browsers.
The most important text formatting tag is the paragraph tag (<p>).
This tag must appear around all text not delineated by other tags. It
also controls how the browser presents the text if you add the optional
mode attribute. The two supported modes are "wrap" and "nowrap",
with "wrap" as the default. The wrap mode allows the text to flow
down the device screen, breaking lines at appropriate spaces and
punctuation. The nowrap mode uses a marquee-like display to display
the text, scrolling the text horizontally when this mode is selected.
For example, take the following code:
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
<card id="card1" title="Text Wrapping">
<p mode="nowrap">
No Wrap: The quick brown fox jumped over the lazy dog.
</p>
<p mode="wrap">
Wrap: The quick brown fox jumped over the lazy dog.
</p>

</card>
</wml>

It results in the following display:

Figure 2-1 - The mode attribute determines how the


tag wraps text.
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 2-2 - Text set to nowrap will scroll horizontally when the
browser selects that line. Note how the first line (nowrap) has
scrolled in this figure. The text will continue to scroll until it
reaches the end of the paragraph, where it will start over.
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.)
If you don't use the mode attribute, the client browser uses the
currently selected mode, or the default "wrap" if no mode had been
defined previously. Typically the nowrap mode is used for headlines
and selection text, while wrap is used for general and body text.

ard Navigation
Now let's consider how to navigate between cards within a deck. As an
example, we'll use a riddle, having the user navigate between the
question and answer.
Take the following riddle:
Q: You cannot see it, you cannot touch it, it isn't a liquid, it isn't a

solid, it isn't a gas, but it can be broken. What is it?


A: Silence.
We'll put the question in one card, the answer in another, and define
links to move between the two. For the sake of variety, let's define the
link forward (to the answer) as a button and the link backward (back
to the question) as an in-text link. Consider the following code:
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
<card id="card1" title="Question">
<do type="accept" label="Answer">
<go href="#card2" />
</do>
<p mode="wrap">
You cannot see it, you cannot touch
it, it isn't a liquid, it isn't a
solid, it isn't a gas, but it can
be broken. <br/>
What is it?
</p>
</card>
<card id="card2" title="Answer">
<p mode="nowrap">
Silence.<br/>
<br/><br/>
<anchor>
<prev></prev>
Back to Question
</anchor>
</p>
</card>
</wml>

Card 1
The first card is simply text, enclosed in paragraph tags. However,
we've added a <do> element to change the behavior of the Accept key
- one of the default navigation keys on most mobile devices (see the

next section on keys for more information). This <do> element has
type and label attributes. The type attribute tells the browser how the
<do> is called. In this case it's by remapping the Accept key's
function. The label supplies a label for the key, namely "Answer" in
this example.
Note: This label bends one of the UI suggestions of using a maximum
of five letters for key labels.
Within the <do> element is a <go> instruction, supplying the address
(href) to "go" to when the key is pressed. The href is in the following
form:
<Deck>#<CardID>

Since the destination is in the same deck, the deck name can be
omitted and only the CardID is necessary.
Note: The order of the <do> and <p> elements is arbitrary. I prefer
to put my navigation directives before my text wherever possible. It
would be acceptable to reverse their order. Whichever you choose it
pays to be consistent.
Card 2
The second card also uses a paragraph element, but incorporates a
link within the element by using the <anchor> tag. The form of the
<anchor> tag is as follows:
<anchor title="label">
type
text
</anchor>

The label attribute supplies an optional label to the Accept button


when the link is selected. We've omitted this attribute in the example,
allowing the default "Link" (or other device default) to be displayed.
The type of anchor can be <go>, <prev>, or <refresh> - you simply
add the appropriate element to define the type. We've used <prev> to
return to the previous card.
The text is the actual text shown for the link, in this case "Back to

Question".
Note that the <anchor> is enclosed in the paragraph and that the
paragraph is set to nowrap. This will prevent the link from wrapping to
several lines. We aren't worried about the plain text ("Silence")
wrapping in this example, but we could easily enclose the <anchor> in
its own nowrap <p> tag and change the text paragraph to wrap.
Result
The above code has the following result:

Figure 2-3 - The left display is Card1, the right is Card2. Notice
that the emulator displays a check mark instead of the
standard "Link" text for the Accept button.
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.)
Expanding the Example
You might have noticed that our second navigation method - mapping

the <prev> command to the Accept key - is superfluous. Most devices


incorporate a standard "Back" button that could be used to return to
the question without the overhead of remapping a key. With that in
mind, let's put the key to a better purpose: moving to another
question.
Change the <anchor> to the following:
<anchor>
<go href="#card3" />
Next Question
</anchor>

Then, duplicate cards 1 and 2 and re-label them as 3 and 4. You also
need to change the link in card 3 (to card 4) and supply a new
question and answer. The result would be similar to the following:
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
<card id="card1" title="Question">
<do type="accept" label="Answer">
<go href="#card2" />
</do>
<p mode="wrap">
You cannot see it, you cannot touch
it, it isn't a liquid, it isn't a
solid, it isn't a gas, but it can
be broken. <br/>
What is it?
</p>
</card>
<card id="card2" title="Answer">
<p mode="nowrap">
Silence.<br/>
<br/><br/>
<anchor>
<go href="#card3" />
Next Question
</anchor>
</p>
</card>
<card id="card3" title="Question">

<do type="accept" label="Answer">


<go href="#card4" />
</do>
<p mode="wrap">
What is greater than God, more evil
than the devil, poor people have it,
rich people want it, and if you eat
it you will die?
</p>
</card>
<card id="card4" title="Answer">
<p mode="wrap">
Nothing. Nothing's greater than God,
nothing's more evil than the devil, poor
people have nothing, rich people
want nothing more than they already
have, and if you eat nothing you will die.
</p>
<p mode="nowrap">
<br/><br/>
<anchor>
<go href="#card5" />
Next Question
</anchor>
</p>
</card>
</wml>

Note: Because the answer to the second question is lengthy, I moved


the <anchor> to its own nowrap paragraph, as discussed earlier. Also,
I set up the last link to a nonexistent "card5," assuming that we would
add more questions and answers.

Understanding Device Keys


Most mobile devices have a limited number of keys to aid in
navigation. Many have a default Back key (my Samsung cell phone
uses the CLR key), as well as Accept and Options keys (my Samsung
uses OK and MENU, respectively).
Note: See the WAP documentation for a particular device to determine
which keys perform which default purposes.

The Accept and Option keys are referred to as soft keys because you
can redefine their meaning and default labels with the software, in this
case WML code. Many of the navigation elements allow you to specify
which keys you want the soft keys to interact with. For example, in our
riddle example we specified "accept" to modify the Accept key. We
could have used "options" to redefine the other soft key (on the lower
right side of the display).
Tip: When defining multiple <do> elements on the same card, the
affected soft key will be labeled "MENU" and allow access to a menu of
defined <do> elements.
Unfortunately, there are few firm standards for default keys. Some
device manufacturers map different keys to the WAP interface, while
some have more proprietary solutions such as a jog wheel to move
between options and functions. The best thing to do before planning
an interface is to read the UI and application style guides and test your
target devices.

Learning WML - Navigation, User Input, and


Graphics
This series of articles describes how to provide Web content to mobile
devices through WML (Wireless Markup Language). This article covers
more advanced navigation, accepting user input, and displaying
graphics. Future articles will cover advanced WML language, tips and
tricks to provide content, and how to integrate other technologies such
as PHP to make your pages more flexible.
Note: These articles cover WML version 1.1, which is 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.

More Navigation Between Cards and Decks


In the previous article ("Learning WML - WML Tools and Coding
Basics"), we covered basic navigation using <anchor> and <do>

elements. These basic navigation constructs enable you to map links


to the soft keys or place standard HTML-style hyperlinks into your
cards. There are a few more useful navigation methods we should also
cover.
Select Lists
The select list provides an easy way for the user to select between
several predetermined values. Each value is enclosed within an
<option> tag, much like an HTML list:
<select>
<option>First option</option>
<option>Second option</option>
</select>

Each option is automatically assigned a shortcut number, displayed


next to it by the browser, as shown in the following figure.

Each <option> element is assigned a shortcut number which is


displayed next to the element's text.
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.)
The user can either press the shortcut number associated with the
option or use the standard navigation (arrow) keys to select the
appropriate option and then press the Accept key.
Tip: The shortcut keys are only useful if you have 10 or fewer options,
since there are only 10 numeric keys (0-9) that can be used to quickly
select an option. If you have more than 10 options, consider adding a
"More" option that displays the options in groups of 10 or fewer.
The "onpick" parameter can be used to select a deck/card to navigate
to if the user selects that option. For example, the following code will
navigate the user to the appropriate "color" card within the current
deck:
<select>
<option onpick="#red">Red</option>
<option onpick="#green">Green</option>
<option onpick="#blue">Blue</option>
</select>

A full deck example follows:


<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
<card id="card1" title="Select example">
<p>
<select>
<option title="Red" onpick="#red">Red</option>
<option title="Green" onpick="#green">Green</option>
<option title="Blue" onpick="#blue">Blue</option></select>
</p>
</card>
<card id="red" title="Red">
<p>
Your choice was Red.
</p>

</card>
<card id="green" title="Green">
<p>
Your choice was Green.
</p>
</card>
<card id="blue" title="Blue">
<p>
Your choice was Blue.
</p>
</card>
</wml>

Tip: Notice the addition of the "title" parameter in each <option> tag.
The text provided with this parameter is displayed as the Accept soft
key's label when the option is selected.
We used the options to navigate to cards within the current deck.
However, you could just as easily navigate to other decks or specific
cards within those decks.

Learning WML - Navigation, User Input, and


Graphics
Events
You can also set up navigation that is caused by a particular event.
The <onevent> element provides several possible actions triggered by
one or more intrinsic events.
The <onevent> tag has the following format:
<onevent type="type">
task
</onevent>

The "type" can be one of the following:

Type
Onpick
Onenterforward
Onenterbackward
Ontimer

Event
User selects or deselects an <option> item.
User navigates to a card in a forward manner
(e.g., from a <go> or other link)
User navigates to a card in a backward manner
(e.g., from a <prev> element or Back command)
A specified <timer> element expires

The "onenter" types can be used to display or omit particular


information depending on how the user navigated to the card, or to
force a refresh of dynamic information if the user reached the card by
going backward (thereby getting the card from the cache and not a
fresh copy from the server).
The "onpick" type is used when the <onevent> is nested within an
<option> element to provide an action when the option is selected.
The "ontimer" type is used to provide a task at a particular time,
independent of user actions (refreshing a page, moving to another
page, etc).
The various tasks that can be used with the <onevent> element are
described in the following table.
Task
Use
<go>
Navigates to a new card.
<prev>
Navigates to the previous card.
<noop>
Performs no task (no operation).
<refresh> Refreshes the current card's content.
<exit>
Exits the current context. *
<spawn>
Spawns a child task. *
<throw>
"Throws" an exception that can be caught by an
optional
<catch>
element. *
* These tasks are specific to the Openwave browser. See the
developer documentation at http://developer.openwave.com for more
information.
The "ontimer" event can be used to display a splash screen (containing
welcoming or copyright information), or to automatically return to a
card after displaying an error. It_s also very useful for refreshing a
card to display up-to-date dynamic data.

In the following example, the deck displays a splash screen before the
main menu:
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
<card id="splash" title="Welcome!">
<onevent type="ontimer">
<go href="#main" />
</onevent>
<timer name="delay" value="50"/>
<p mode="wrap">
Welcome to ACME Incorporated.
</p>
</card>

<card id="main" title="Main Menu">


<p>Main menu here...</p>
</card>
</wml>

Note: The <timer> element's value parameter is measured in tenths


of a second, so our splash screen stays active for 5 seconds (50
tenths).
Tip: The <card> element includes an optional "ontimer" parameter,
which alleviates the need for a separate <onevent> element. Using
this parameter, the above example could be shortened as follows:
...
<card id="splash" title="Welcome!" ontimer="#main">
<timer name="delay" value="50"/>

Accepting User Input

Although the forms capability of WML isn't as robust as standard


HTML, the <input> element is very flexible and allows a variety of
data to be accepted from the user.
The <input> element has the following minimal form:
<input name="variable_name" />

In this form, the element accepts user input and stores it in the
variable specified. The input is freeform; that is, it is not constrained
to any particular format (numeric, alphanumeric, etc.) or length-although most mobile browsers impose a length limit of 256
characters.
Note: A full discussion of WML variables is outside the scope of this
article, but will be provided in the next article. For this exercise it is
important to know that variables exist and can be set by <input> and
<setvar> elements, among others. A variable can then be referenced
by prefixing its name with a dollar sign ("$"), or using the preferred
method of enclosing the name in parentheses and prefixing the whole
structure with a dollar sign. For example, the variable "firstname" can
be referenced as "$firstname" or "$(firstname)". When the variable is
referenced, WML will substitute the variable's value for its reference.
The <input> element also supports the optional parameters shown in
the following table.
Parameter Use
Title
Supplies a title for the element. Some devices display this
title in the default Accept soft key label; others display it
as "tooltip" text while the element is selected.
Type
Set to "text" or "password," this parameter controls
whether the text is displayed as it is entered or displayed
in the browser's password character(s) (usually "*").
Value
The default value for the element. Note that if the
variable specified in the "name" parameter already has a
value, or if the value specified with this parameter doesn't
conform to the specified "format" parameter, the value is
ignored.

Accesskey
Format
Emptyok
Maxlength

Displays the specified number (0-9) next to the element.


The key can be used to quickly select the element.
Specifies the format mask of the input. See the format
section below for details on masking.
Controls whether the element can be left empty. The valid
values for this parameter are "true" (the default) or
"false."
Maximum length of the input, in characters.

Note: The "accesskey" parameter allows you to create a form of


several input fields where the user can easily navigate between the
fields. However, as the Openwave applications point out
(http://developer.openwave.com), wizards that display card-sized
chunks of data entry fields are much better received and make a
better application design.
The "format" parameter's value is composed of a series of characters
that provide a mask for the input. The following table describes the
valid instructional characters for the format mask:
Character
Meaning
A
Any symbol or uppercase alpha character (no numbers).
A
Any symbol or lowercase alpha character (no numbers).
N
Any number (no symbols or alphabetic characters).
X
Any symbol, number, or uppercase alpha character
(cannot be changed to lowercase alpha).
X
Any symbol, number, or lowercase alpha character
(cannot be changed to uppercase alpha).
M
Any symbol, number, or alpha character; by default, the
first character is uppercase.
M
Any symbol, number, or alpha character; by default, the
first character is lowercase.
The format mask can also include symbols, which will be displayed in
their appropriate position in the input.
For example, to accept a phone number, including the area code, you
could use this format mask:
format = "(NNN) NNN-NNNN"

When using the same character multiple times in a row, you can prefix

the character with a number indicating how many times it repeats.


Using this method, our phone number mask could be written like this:
format = "(3N) 3N-4N"

However, this format isn't as recognizable as a phone number mask.


Note: To specify an unlimited number of the same character, prefix the
character with an asterisk (*).
A simple deck to accept a person's first name (up to 25 characters)
and then greet the person by name would resemble the following
listing:
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
<card id="GetName" title="Get Name">
<do type="accept">
<go href="#Hello"/>
</do>
<p>
What is your first name?
<input name="name" format="*M" maxlength="25" />
</p>
</card>
<card id="Hello" title="Hello">
<p>
Hello, $(name)!
</p>
</card>
</wml>

Notice that the <input> element uses the "M" format mask, supplying
an uppercase letter as the default first character. The "maxlength"
parameter constrains the input's length, despite the "*" in the mask.
The deck navigation in this example is controlled by a <do> element
linked to the Accept key. In some devices the user will have to press
the Accept key twice--once to exit the input element and another to

trigger the navigation.


There are many useful things you can do with user-supplied input
besides echo it back in other cards. The next article in this series will
discuss several other uses for user input.

Graphics and Icons


The current generation of mobile devices includes very limited graphics
capability. Most mobile browsers only support black-and-white
bitmapped images, although a few recent browsers have added
support for color and even animated formats (GIF, PNG, JPG). Some
mobile gateways also perform on-the-fly conversions of graphics, most
notably from BMP to WBMP.
Tip: Unless you are developing for a particular platform where you
know the capabilities, use only small black-and-white WBMP graphics.
Most graphics applications do not support the WBMP format, but
several converters exist, including a Web-based converter at Teraflops
(http://www.teraflops.com/wbmp/).
Note: Refrain from using graphics whenever possible; using graphics
breaks the mobile application development rules of "fast" and
"simple."
To create a graphic for mobile use you will need a graphics application
that supports the WBMP format, or at least black-and-white bitmap
format and a suitable converter. Drawing upon our earlier splash
screen example, let's create a graphical logo for ACME.
To start, we need to choose a suitable size for the graphic. It's
advisable to stick with a small size such as 100 x 100 pixels. The
graphic shown in the following figure was created with the following
steps (note that the options and procedure might differ in your
graphics application):
1 Create a new graphic, sized 80 x 80 pixels.
2 Convert the graphic to black-and-white, or "line art."
3 Select the appropriate background color (black or white) and fill
the entire graphic.
4 Use the text tool to create text in appropriate sizes and fonts

(using the opposite color of the background).


5 Save the results in GIF format.
6 Use the online tool at Teraflops.com to convert the GIF to WBMP
format.
7 Upload the resulting graphic (acme.wbmp) to the Web server.
Tip: We advise using a graphics program that supports objects instead
of straight bitmapped images. Graphics programs allow you to work
with individual objects_moving, sizing, etc_before committing them to
the electronic canvas.
The ACME Incorporated logo.
Images are added to WML cards using the <img> tag. This tag has the
following syntax:
<img alt="alt-text" src="url" />

Note that the "alt" and "src" parameters are mandatory in all <img>
tags. The <img> tag also supports the following optional parameters:
Parameter Use
localsrc
Specifies an icon. (See the later section on icons.)
align
Aligns the image relative to the current line of text.
Supports "top," "middle," and "bottom."
height
Specifies the exact height of the displayed image.
width
Specifies the exact width of the displayed image.
hspace
Specifies how much white space should be displayed at
each side of the image (horizontal space).
vspace
Specifies how much white space should be displayed
above and below the image (vertical space).
Note: Not all mobile browsers support the sizing and spacing
parameters.
Incorporating the logo into our previous splash screen example, we get
the following result (see figure):
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>

<card id="splash" title="Welcome!" ontimer="#main">


<timer name="delay" value="50"/>
<p mode="wrap" align="center">
Welcome to<br/>
<img alt="ACME Incorporated" src="acme.wbmp" />
</p>
</card>
<card id="main" title="Main Menu">
<p>Main menu here...</p>
</card>
</wml>

Our new splash screen.


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.)

Icons are very small graphics to be used as highlights or


ornamentation. Most mobile browsers support at least a limited
number of icons. For the purposes of this discussion we will use icons
supported by the Openwave browser. A full list of these icons can be
found in the WML Language Reference documents on the Openwave
developer site (http://developer.openwave.com).
Icons are placed as graphics, using the <img> tag and its "localsrc"
attribute, along with the appropriate icon number(s). For example, a
smiley face is icon number 68; it would be placed in the contents of a
card with the following tag:
<img alt=":-)" src="" localsrc="68" />

Note that the "alt" and "src" tags are mandatory, even if left blank.
Tip: When using icons for ornamentation, be sure to specify alternate
text that can serve the same purpose or that resembles the icon.
Let's suppose that ACME is a travel agency. We could add an airplane
icon to the bottom of the splash screen with this tag (see figure):
<img alt="airplane" src="" localsrc="168" />

Our new splash screen complete with airplane icon.


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.)
Tip: Because the WML language is fairly limited for emphasizing text,
consider using icons such as triangles (icons 5-8) to draw attention to
specific text. For example, this code is used to call attention to the fact
that the user has reached the end of an article:
<img alt="--" src="" localsrc="righttri1" />
End
<img alt="--" src="" localsrc="lefttri1" />

Learning WML - Variables and Scripting


This series of articles describes how to provide Web content to mobile
devices through WML (Wireless Markup Language). This article covers
variables and beginning scripting with WMLScript. Future articles will
cover more advanced scripting and how to integrate other technologies
such as PHP to make your pages more flexible.
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.

Understanding Variables
WML supports variables that can hold transitional data between cards,
provide custom output tailored to individual users, and more.
Variables are special holding places for values. They can hold numeric
or alpha values and their values can be changed by code at the
programmer's whim. However, it is usually good practice to dedicate
variables to particular purposes, and hence, to particular types of data.

Variables in WML consist of words, enclosed in parentheses, prefixed


with a dollar sign ($). For example:
$(name)
$(address)
$(link)
$(target_url)
$(_method)

Note: Because the dollar sign ($) is used to signify a variable, it is a


reserved character in WML. If you want an actual dollar sign to appear
anywhere in your card(s), use a double dollar sign instead ("$$").
Variable names must start with a letter or underscore. Subsequent
characters can be alpha, numeric, or underscores. Variables are case
sensitive; "phone_number" is different from "Phone_Number."
Wherever a variable is referenced, WML will substitute the value of the
variable where the variable name appears. For example, the following
code:
<p>
Hello $(name)!
</p>

would produce the following result, if "David" was stored in the


variable $(name):
Hello David!

Note: WML reserves the ampersand symbol (&) for entities. To use this
symbol in your deck, you must substitute the corresponding entity
"&amp;".

Setting Variables
Variables can be set by the following elements:
<input>
<select>/<option>
<setvar>

Each of these elements is discussed in more detail below. Note that


some of these elements were introduced in previous articles, and their
parameters and such will not be detailed here.
The <input> Element
The <input> element is straightforward; it accepts user input and
stores it in the variable specified. For example, the following code
would accept input for a phone number and store it in the variable
$(phone_number). The number is then displayed on the second card.
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
<card id="card1" title="Enter Number">
<do type="accept" label="Next">
<go href="#card2" />
</do>
<p>
What is your phone number?
<input name="phone_number"
type="text" format="(NNN) NNN-NNNN" />
</p>
</card>
<card id="card2" title="Display Number">
<p>
Your number is:<br/>
$(phone_number)
</p>
</card>
</wml>

Note: The <input> element accepts a "value" attribute that can set a
default value for the input. However, if the variable specified has
already been assigned a value, the "value" attribute is ignored.
The <select> and <option> Elements

Each <option> element within a <select> can also set variables. When
the user selects an option, that option's "value" parameter is stored in
the variable specified by the select's name parameter. Although it
sounds convoluted, it is pretty straightforward in practice. The
following example would store the selected color's hex value in the
variable "color":
<select name="color">
<option value="#FF0000">Red</option>
<option value="#00FF00">Green</option>
<option value="#0000FF">Blue</option>
</select>

For example, if the user selected "Green," the variable "color" would
be set to "#00FF00".
Note: The <select> element accepts a "value" attribute that can set a
default value for the input. However, if the variable specified has
already been assigned a value, the "value" attribute is ignored.
The <setvar> Element
The <setvar> element is perhaps the most pure of the variable-setting
elements, because it serves the sole purpose of simply setting a
variable. In the following example, the variable "color" is set to
"Green":
<setvar name="color" value="Green" />

The use of <setvar> is complicated by the fact that it can only be


executed upon a <go>, <prev>, or <refresh> action. As such, you
cannot simply embed a <setvar> anywhere you need to set a variable.
It is common practice to set variables before moving to a card where
you will need them, by embedding the proper <setvar> elements
within the corresponding <go> or other action element.
If you need to set variables for the current card and doing so from a
previous page is impractical (for example, for the first card displayed
in the deck), you can use <onevent> and <refresh> elements to force
a <setvar> to execute: For example, the following code will set the

appropriate variables when the user enters this card from a forward
direction:
<card id="card1">
<onevent type="onenterforward">
<refresh>
<setvar . . . />
<setvar . . . />
</refresh>
</onevent>

Note: Unlike the other elements above, the <setvar> element sets the
value of the specified variable whether or not it has previously been
assigned a value.

Using Variables
Variables are very flexible and can be used in a variety of ways. You
can store the results of user choices or status flags, display variable
text, etc. Using the <postfield> element, you can even pass a
variable's value to an external program.
The <postfield> element defines a name/value pair to be sent to the
HTTP server with the next <go>. The <postfield> element is
encapsulated within <go> elements as follows:
<go . . . >
<postfield name="name" value="value" />
...
</go>

The <go> element can include an optional "method" parameter that


can be set to "get" or "post," which determines how the variable/value
pairs are sent to the server. There are many differences between the
two methods, but generally speaking "get" encodes the pairs in the
calling URL while "post" sends the pairs in the body of the calling
message. Of the two, "post" should be used wherever possible
because it is more robust.
Using this method, you can pass values to CGI scripts or other
external applications. For example, the following example passes the

name/value pair color/Red to colorselect.cgi:


<go href="colorselect.cgi">
<postfield name="color" value="Red" />
</go>

Note that the "value" attribute is mandatory and is not ignored if the
variable has been set previously. To send a value of a variable that has
been set elsewhere, use the variable tag as the "value" attribute. For
example, the following code will send the current value of the color
variable:
<postfield name="color" value="$(color)" />

Note: You can also include variables in the URL of <go> and other
navigation elements by using the standard "?name=value" notation:
<go href="colorselect.cgi?color=$(color) />

As flexible as variables are, they only serve a static purpose on their


own. Although you can store a variety of information, no decisionmaking ability is provided to set variables or make decisions regarding
the status of variables within standard WML. However, you can extend
the use of variables by using WMLScript to complement your WML
code.

Beginning WMLScript Programming


WMLScript provides a means to add basic scripting to WML. WMLScript
is similar to JavaScript-knowing JavaScript will certainly help you learn
WMLScript. However, if you have any programming experience you
should be able to learn WMLScript and be coding in no time.
Note: WMLScript does not translate to HDML. If you are creating pages
that might be delivered to an HDML-compatible device (usually
through a translation gateway), avoid using WMLScript.

Basic WMLScript Concepts

Although a full primer on WMLScript is outside the scope of these


articles, it is important to understand a few basic concepts.
WMLScript is case sensitive. All language elements must be spelled
using the proper capitalization of letters.
WMLScript supports integer, floating point, string, and Boolean
literals. String literals must be enclosed in single or double quotes ('
' or " ") and Boolean values are set to "true" or "false."
WMLScript will automatically convert between values to deliver the
right type of value (integer, string, etc.) to the function that receives
the value.
Whitespace is ignored in WMLScript, allowing the programmer to
format the code as desired.
Comments in WMLScript can consist of a single line or multiple lines.
Single-line comments start with a double slash (//). For multiple-line
comments, start the block with a slash followed by an asterisk (/*)
and end the block with an asterisk followed by a slash (*/).
As with any language, WMLScript has a handful of reserved words
such as "function," "var," "continue," etc. For a full list, consult the
language guide for the version of WMLScript you are using.
You must declare all variables in WMLScript.
WMLScript can access variables set in WML. You can set a variable in
a WML card and reference that same variable in a WMLScript. You
can also pass values to WMLScript functions and return values from
the function to the calling WML.
WMLScript is contained in external files (usually with a WMLS
extension) and called from standard WML navigation elements using
the following expression:
<script filename>#<function name>(<arguments/parameters>)

For example, to call the function "validate" in the file "validation.wmls"


you would use
validation.wmls#validate()

Your First Script


The easiest way to learn a scripting language is by example. The
following basic example accepts a number, multiplies it by 2, and

displays the result. This is a relatively useless script, but it helps


simply to illustrate the mechanics between WML and WMLScript.
This example uses the following two files:
sample.wml
1. <?xml version="1.0"?>
2. <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
3. <wml>
4. <card id="card1" title="Enter Number">
5.
<onevent type="onenterforward">
6.
<refresh>
7.
<setvar name="num" value=""/>
8.
</refresh>
9.
</onevent>
10. <p>
11. <do type="accept" label="Multiply">
12.
<go href="sample.wmls#multiply()"/>
13. </do>
14. <input name="num" format="*N" />
15. </p>
16. </card>
17.
18.
19.
20.
21.
22.

<card id="card2" title="Result">


<p>
Your number ( $(num) ) multiplied by 2 is:<br/>
$(numX2)
</p>
</card>

23. </wml>

sample.wmls
24. extern function multiply() {
25. var Num = WMLBrowser.getVar("num");
26. var NumX2;
27.
28.
29.
30.

NumX2 = Num * 2;
WMLBrowser.setVar("numX2", NumX2);
WMLBrowser.go("sample.wml#card2");
}

Notice that the WMLScript file begins with an "extern" keyword in front
of the function definition. This keyword allows a function to be
accessed from outside the file; for this example, from our WML deck.
Functions accessed internally by the same WMLScript file do not need
this keyword.
Our example executes as follows:
1 As the first card is displayed, the <onevent> handler causes the
variable "num" to be initialized as blank (lines 5-9). Although not
completely necessary, this step is good practice to ensure that the
variable exists and is in a known state.
2 Input is received via the <input> element and stored in the variable
"num" (line 14). Note that the "format" parameter forces numeric
input.
3 The related <do> element (lines 11-13) calls the "multiply" function
in the wmls file.
4 The "multiply" function declares two variables, setting the variable
"Num" to the value of the XML variable "num," by using the
WMLBrowser.getVar function (lines 25-26).
5 The variable "Num" is multiplied by 2; the resulting value is stored in
the variable "NumX2" (line 27).
6 The script stores the value of NumX2 into a WML variable
("numX2"), using the WMLBrowser.setVar function (line 28).
7 The script uses a "go" function to jump to the second card in the
WML deck (line 29).
8 The second card (lines 17-22) displays our text, using the variables
"num" and "numX2."
Note: The WMLBrowser library functions (WMLBrowser.setVar, etc.)
provide an easy interface between XML and XMLScript. Consult a
WMLScript language reference for more information on these
functions.

A More Useful Script


One handy purpose for WMLScript is validating input and acting
accordingly. For example, suppose we had a card that asked for a twoletter U.S. state abbreviation. Although the <input> element can

ensure that we have two uppercase letters, it can't match the input
against an acceptable list. For that, we will use WMLScript.
input.wml
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
<card id="state" title="Enter State">
<onevent type="onenterforward">
<refresh>
<setvar name="stateabbr" value=""/>
</refresh>
</onevent>
<p>
<do type="accept" label="Validate">
<go href="validate.wmls#state()"/>
</do>
<input name="stateabbr" format="AA" />
</p>
</card>
<card id="stateok" title="StateValid">
<p>
$(stateabbr) is a valid State code.
</p>
</card>
</wml>

validate.wmls
extern function state() {
var stateabbr = WMLBrowser.getVar("stateabbr");
var pos, x;
var okay = false;
var statelist =
"ALAKASAZARCACOCTDEDCFMFLGAGUHIIDILINIAKS
KYLAMEMHMDMAMIMNMSMOMTNENVNHNJNMNYNCNDMP
OHOKORPWPAPRRISCSDTNTXUTVTVIVAWAWVWIWY";
if (String.length(stateabbr) == 2) {

pos = String.find(statelist,stateabbr);
x = pos / 2;
if (Float.int(x) == x) { okay = true; }
}
if (okay) {
WMLBrowser.go("input.wml#stateok");
} else {
Dialogs.alert("Invalid input!");
WMLBrowser.go("input.wml#state");
}
}

The concept is relatively simple. The WML file takes the input, stores it
in the "stateabbr" variable, and then calls the validation function. The
function grabs the state abbreviation that was entered and performs
two checks:
Is the abbreviation two characters long?
Does it appear in a valid list?
The String library function "length" is used to determine whether the
abbreviation is two characters long. If so, the second check is
performed. The second check attempts to find the abbreviation in the
"statelist" string. The position is then divided by two-if the result is an
integer, the state is valid.
Note: A simple substring search ("String.find") is not enough to
validate the input, as combinations like "SK" would pass the test. The
abbreviation must be in an even-numbered position (evenly divisible
by 2). If the abbreviation is not found, the find function returns -1,
which also fails the even number test.

More Information on WMLScript


A wealth of information can be found at the Openwave developer site,
http://developer.openwave.com. Take a look at their Technical Library
and be sure to visit the Developer Forum for answers to more difficult
problems.

WML Scripting Tips and Integration with PHP


This series of articles describes how to provide Web content to mobile
devices through WML (Wireless Markup Language). This article covers
some additional uses for WMLScript and how to integrate PHP with
WML. Future articles will cover more advanced PHP and WML
techniques.
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.

WMLScripting
In the last article we discussed how to use WMLScript to perform basic
scripting tasks within your WML pages. As mentioned in that article,
WMLScript can be used to validate input and perform basic operations
on WML variables. WMLScript can also be used to control the display of
various cards and the information contained on them. For example,
the following code will display an animation by changing an image's
source and refreshing a card every two seconds:
animation.wml
<wml>
<card id="card1">
<onevent type="onenterforward">
<refresh>
<setvar name="num" value="1" />
<setvar name="image" value="image1.wbmp" />
</refresh>
</onevent>
<onevent type="ontimer">
<go href="junk.wmls#main()" />
</onevent>
<timer value="20" />
<p><img alt="$(image)" src="$(image)" /></p>
</card>
<card id="card2">
<p>Animation Done!</p>

</card>
</wml>

animation.wmls
extern function main() {
var Num = WMLBrowser.getVar("num");
var Image = "";
if (Num < 9) {
Num++;
Image = "image" + Num + ".wbmp";
WMLBrowser.setVar("num", Num);
WMLBrowser.setVar("image", Image);
WMLBrowser.refresh();
} else {
WMLBrowser.go("animation.wml#card2");
}
}

The deck (animation.wml) sets two variables and forces a refresh.


These are the variables:
num:
The number to append to the image source
image:
The starting image source (image1.wbmp)
Every two seconds, the main() function in animation.wmls is called.
This function adds one to "num," creates a new image source
(image<num>.wbmp), and refreshes the card, which causes the new
image to be displayed. After the ninth image (image9.wbmp) is
displayed, the script goes to the second card in the deck, ending the
animation.
Note: The above example assumes that your animation consists of
nine images that, when displayed sequentially, create the animation.
WMLScripting is flexible and brings basic scripting to WML. However,
it's possible to lend more programming power to WML by infusing it
with PHP, as covered in the next section.

WML Scripting Tips and Integration with PHP

Integrating PHP into WML


Although WML is well suited to most mundane content delivery tasks,
it falls short of being useful for database integration or extremely
dynamic content.
Another Web technology, PHP, fills this gap quite nicely-integrating
into most databases and other Web structures and languages. It's
possible to "cross-breed" mime types in Apache and IIS to enable PHP
to deliver WML content. The following sections show you how to
configure your server and how to integrate the two technologies.
Note: This section assumes that you have a basic understanding of
PHP. To learn more about PHP, visit the PHP Web site (www.php.net)
and browse through the language documentation. You can also find a
series of articles here on Developer.com for learning PHP. The first is
Learning PHP: The What's and the Why's
What Is PHP?
According to the PHP documentation, "PHP (recursive acronym for
'PHP: Hypertext Preprocessor') is a widely-used Open Source generalpurpose scripting language that is especially suited for Web
development and can be embedded into HTML."
Essentially, this scripting language is a server-side language that's
processed before being sent to the requesting client. This is in contrast
to scripting languages like JavaScript that are client-side and
processed by the client's browser after being sent to the client.
The benefit of being a preprocessed language is that PHP pages can be
highly dynamic. Decisions can be made from user input, databases,
server conditions, and so on about how the requested content is
delivered to the client. It's exactly what we need to increase the power
of our WML.
Installing PHP
PHP is available for most major Web server platforms, including
Apache and Microsoft's Internet Information Server (IIS). Visit

www.php.net, download the appropriate version for your server, and


install it by following the included instructions.
Note: If you're running Debian, Red Hat, or another version of Linux
with a robust packaging system, PHP is probably available in a
preconfigured package. Check your distribution's Web site and
documentation for more info on PHP package(s).
Cross-breeding with WML: Apache
To enable WML pages to be parsed by PHP, you simply have to add the
WML suffix to the PHP application definition in your Apache
configuration file:
AddType application/x-httpd-Php .php .wml

Note the addition of ".wml" to the definition. This will cause the server
to parse all .wml files with PHP. Don't forget to restart Apache so it will
read the new configuration. Make sure that your WML definitions
appear before the PHP definition to ensure that Apache recognizes
".wml" as a valid type.
Tip: As mentioned above, this causes all .wml files to be parsed by
PHP. Although this is usually desirable, you could create another mime
type ("pwml") and include it in both the WML and PHP definitions.
Then you could continue to use .wml for pure WML files and the new
type for PHP-parsed WML.
Cross-breeding with WML: IIS
To enable WML pages to be parsed by PHP, you need to add PHP as an
application to handle your .wml files.
To do this, follow the steps below:
1 Open the Internet Information Services Management Console.
2 Right-click on the site where you want to add this functionality (or
use Default Web Site) and choose Properties.

3 Access the Home Directory tab and click the Configuration button
near the bottom of the dialog.
4 Click the Add button to add an application.
5 Add the path and filename of the PHP executable (usually
"C:\PHP\php.exe") in the Executable field and ".wml" in the
Extension field.
6 Ensure that "Script Engine" and "Check that file exists" are both
checked.
7 Select OK back to the IIS MC.
8 Although not usually required, you may need to restart your Web
server.
In step 6, above, setting the "Check that file exists" option is not
entirely necessary, but can be helpful in debugging server issues.
Without that option being set, IIS will pass all requests for .wml pages
to PHP, even if the requested page doesn't exist. Instead of generating
a "404 - Not Found" error, the Web server will report a problem with
PHP (which failed because there was no file to parse).
These steps will result in all .wml files being parsed by PHP. Although
this is usually desirable, you could create another mime type ("pwml")
and include it in the WML definition and define PHP as an application
for the new type. Then you could continue to use .wml for pure WML
files and the new type for PHP-parsed WML.
Getting the Client to Recognize PHP-parsed WML
Now that the server has been configured to deliver PHP-parsed WML,
you need to ensure that the client will receive it as WML. This is
accomplished by having each PHP-parsed page send the appropriate
header to the client.
You should begin each PHP file with the following line:
header("Content-type: text/vnd.wap.wml");

This line needs to come before anything else is sent to the client-it
pays to make a habit of including it right after the "<?php" start tag.
The following example shows how to display a one-card deck with the
text "Hello world" using PHP:
<?php
header("Content-type: text/vnd.wap.wml");
print "<?xml version=\"1.0\"?>\n";
print "<!DOCTYPE wml PUBLIC \"-//WAPFORUM//DTD WML 1.1//EN\""
. " \"http://www.wapforum.org/DTD/wml_1.1.xml\">\n";
print
print
print
print
print

"<wml>\n";
"<card>\n";
"<p>Hello world</p>\n";
"</card>\n";
"</wml>\n";

?>

Note that there are several ways to accomplish sending the WML to
the client. I've chosen to use individual print statements, each ending
with a newline code. This keeps the resulting WML fairly readable and
PHP mimics the line breaks. In some cases, namely long stretches of
code, a print "here document" structure would work better. The above
example would resemble the following in "here document" structure:
print <<<END
<?xml version=\"1.0\"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
<card>
<p>Hello world</p>
</card>
</wml>
END;

In any case, remember that your target platform is WML-stick to WMLcompatible tags and structure and avoid using reserved characters
(such as "$") in places where they might be misinterpreted. Also,
debugging the code is twice as hard as straight WML because you have
to debug the PHP code as well as the resulting WML. The next article in
this series will provide more debugging tips.
Utilizing the Power of PHP
As previously mentioned, PHP's interoperability with other technologies
makes it a powerful ally for WML. One such technology, databases, is
especially useful.
As an introduction, we'll cover a basic example here. The next article
in the series will showcase more examples of using PHP to extend
WML.
For this example we'll use MySQL, a popular open-source database
solution, although any database with PHP connectivity would work as
well.
Suppose that we have a team of salespeople in the field who need
regular access to customer data. In this example we'll only be
concerned with the customer's phone number, but it illustrates the
underlying power of using PHP while creating a useful "online"
phonebook.
Our database for this example is very
First Name
Last Name
John
Smith
Kathy
Lamarr
Sam
Kinkaid
Holly
Haute

simple:
Phone Number
555-723-0900
555-876-2222
555-243-8766
555-327-0987

In MySQL, the database and table would be created using the following
code:
CREATE DATABASE Customer;
USE Customer;

CREATE TABLE Phone (


FirstName varchar(30) default NULL,
LastName varchar(30) default NULL,
Phone varchar(12) default NULL
);
INSERT
INSERT
INSERT
INSERT

INTO
INTO
INTO
INTO

Phone
Phone
Phone
Phone

VALUES
VALUES
VALUES
VALUES

('John','Smith','555-723-0900');
('Kathy','Lamarr','555-876-2222');
('Sam','Kinkaid','555-243-8766');
('Holly','Haute','555-327-0987');

Now we'll create a relatively simple PHP program to display the


customers in a select list. When the user selects an entry, the phone
will dial the selected number.
1. <?php
2. header("Content-type: text/vnd.wap.wml");
3. print "<?xml version=\"1.0\"?>\n";
4. print "<!DOCTYPE wml PUBLIC \"-//WAPFORUM//DTD WML 1.1//EN\""
5. . " \"http://www.wapforum.org/DTD/wml_1.1.xml\">\n";
6. print "<wml>\n";
7. print "<card id=\"Phonebook\">\n";
8. print "<p><select>\n";
9. $link = mysql_connect("localhost", "user", "password")
10. or die("Could not connect to database!");
11. mysql_select_db("customers")
12. or die("Could not select database!");
13. $query = "select * from Phone order by LastName";
14. $result = mysql_query($query,$link)
15. or die("Query failed:$query");
16.
17.
18.
19.
20.
21.

while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {


print "<option title=\"Call\"";
print " onpick=\"wtai://wp/mc;$line[Phone]\" >";
print "$line[FirstName] $line[LastName] ($line[Phone])\n";
print "</option>\n";
}

22. print "</select></p>\n";


23. print "</card>\n";

24. print "</wml>\n";


25. ?>

The code is fairly straightforward:


Lines 1-5 are our standard WML preamble.
Lines 6-8 open the required tags.
Lines 9-12 create the link to the database. Be sure to substitute an
actual username and password for the placeholders.
Lines 13-15 query the database for all fields in all rows of the
Customers database.
Lines 16-21 step through the results row by row and build an
<option> for each one.
Lines 22-24 close all open tags.
Each <option> includes an "onpick" attribute that will dial the selected
number on most devices, via the "wtai://wp/mc;<phone number>"
URL scheme. This is a useful trick for all users to dial numbers from
within your WML.
The generated output resembles the following figure:

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.)
Quite a few changes can be made to the code above:
The "order by" clause of the query can be changed to sort by a
different field or by multiple fields.
Although the current layout allows each record to be displayed in its
entirety, using the "wrap" option in the <p> tags would allow more
records to be displayed per screen (but each would have to be
highlighted for the full record to be seen).
This example is convenient because there are only four rows in our
database. To make our user interface more "friendly," we should
create paging code to step through the data nine rows at a time
(ensuring that each row has a quick access key [1-9] associated with
it).

Learning WML -Scripting Tips And Integration


With PHP
Project Description
In our last article we implemented a simple phone list using PHP and
MySQL. The basic premise was to give salespeople access to a central
contact database without the need of synchronizing their
phones/PDAs. The script pulled contact data from a database,
displayed the records, and dialed the selected record. This project will
expand on that example, adding the following features:
The contact database will include address information.
There will be more contacts, requiring the list to be displayed a few
records at a time.
A search feature will be added.
Because there will be more than one feature (list and search), we will
also need a menu so the user can choose what feature he or she
wants. Each screen will also need a way to return to the menu.

PHP Script Outline


The project will use one PHP script and a controlling variable ($cmd)

that tells the script card that it is supposed to display. The script
recursively calls itself with the proper value of $cmd, matching the
function it is supposed to perform next. The script's functionality
resembles the following:
If $cmd is empty, set it to "Menu"
If $cmd is set to "Menu," display the Menu card
If $cmd is set to "List," display the List card
If $cmd is set to "Display,"
display the Display card, with the appropriate record
If $cmd is set to "Search," display the Search card
When a function is chosen,
set $cmd appropriately and recursively call self.

The search function will search through the database looking for first
or last names that meet the text entered in the search card. The list
function will display five records at a time and will include options for
moving to the next set of five records or returning to the home menu.
Each option (record or link) will have a quick key. Essentially, the list
will resemble the following:
1 <DB record>
2 <DB record>
3 <DB record>
4 <DB record>
5 <DB record>
6 [Next 5 link]
7 [Home link]
Selecting a record results in displaying all of the data associated with
the record, along with "dial" and "home" links.

The Contact List


Our contact database will contain the following data:
FirstName
Jack

LastName

Phone

Address

Hampton
317-555-2200
46222
2002-08-22
Samuel
Marks
317-555-8764
46203
2002-08-22
Sally
Nash
317-555-8765
46875
2002-08-22

City

State Zip

LastUpdate

213 Main St
2 Northridge Dr
644 Innovation Pl

Indianapolis

IN

Fishers

IN

Ft Wayne

IN

Bill

Haskins
317-555-8766
201 W 103rd
Indianapolis
46240
2002-08-24
Jill
Payton
317-555-0098
55 W 96th St
Westfield
IN
46222
2002-08-22
Mary
Martinez
317-555-7544
9433 E. 75th Ave
Greenwood
IN
46784
2002-08-22
Ned
Tanner
317-555-9877
77 E Marchen
Ft Wayne
IN
46875
2002-08-23
Bruce
Wilten
317-555-1111
3 Prospect
Indianapolis
IN
46038
2002-08-22
Naomi
Waters
317-555-4323
1121 Central Pl
Westfield
IN
46055
2002-08-22
Angela
Renault
317-555-0988
5674 E 6th Ave
Noblesville
IN
46234
2002-08-22
Markus
Elliot
317-555-3232
9755 Carter
Indianapolis
IN
46250
2002-08-22
Steve
Albert
317-555-5444
95 Crescent Dr
Indianapolis
IN
46250
2002-08-26
Martin
Rolfsen
317-555-6767
5678 E 7th Ave
Indianapolis
IN
46234
2002-08-22
Lisa
Biggins
317-555-3644
7732 Allisonville
Indianapolis
IN
46240
2002-08-22
Eric
Gonday
317-555-0500
9466 Pike Plaza
Greenfield
IN
46533
2002-08-22
Douglas
Poser
317-555-0123
55 Tower Pl
Noblesville
IN
46234
2002-08-24
John
Palmer
317-555-4444
12433 N Cumberland
Fishers
IN
46038
2002-08-22
IN

The database will also contain an "ID" field as a primary key (integer).
That will serve as a unique key into each record. To create the data in
MySQL, the following commands should be used:
CREATE DATABASE customers;
USE customers;
CREATE TABLE Phone (
Id int(11) NOT NULL auto_increment,
FirstName varchar(30) default NULL,
LastName varchar(30) default NULL,
Phone varchar(12) default NULL,
Address varchar(30) default NULL,
City varchar(30) default NULL,
State char(2) default NULL,
Zip varchar(5) default NULL,
LastUpdate date default NULL,
PRIMARY KEY (Id)
) TYPE=MyISAM;
INSERT INTO Phone VALUES (1,'Jack','Hampton','317-555-2200',
'213 Main St','Indianapolis','IN','46222','2002-08-22');
INSERT INTO Phone VALUES (2,'Samuel','Marks','317-555-8764',
'2 Northridge Dr','Fishers','IN','46203','2002-08-22');

INSERT INTO Phone VALUES (3,'Sally','Nash','317-555-8765',


'644 Innovation Pl','Ft Wayne','IN','46875','2002-08-22');
INSERT INTO Phone VALUES (4,'Bill','Haskins','317-555-8766',
'201 W 103rd','Indianapolis','IN','46240','2002-08-24');
INSERT INTO Phone VALUES (5,'Jill','Payton','317-555-0098',
'55 W 96th St','Westfield','IN','46222','2002-08-22');
INSERT INTO Phone VALUES (6,'Mary','Martinez','317-555-7544',
'9433 E 75th Ave','Greenwood','IN','46784','2002-08-22');
INSERT INTO Phone VALUES (7,'Ned','Tanner','317-555-9877',
'77 E Marchen','Ft Wayne','IN','46875','2002-08-23');
INSERT INTO Phone VALUES (8,'Bruce','Wilten','317-555-1111',
'3 Prospect','Indianapolis','IN','46038','2002-08-22');
INSERT INTO Phone VALUES (9,'Naomi','Waters','317-555-4323',
'1121 Central Pl','Westfield','IN','46055','2002-08-22');
INSERT INTO Phone VALUES (10,'Angela','Renault','317-555-0988',
'5674 E 6th Ave','Noblesville','IN','46234','2002-08-22');
INSERT INTO Phone VALUES (11,'Markus','Elliot','317-555-3232',
'9755 Carter','Indianapolis','IN','46250','2002-08-22');
INSERT INTO Phone VALUES (12,'Steve','Albert','317-555-5444',
'95 Crescent Dr','Indianapolis','IN','46250','2002-08-26');
INSERT INTO Phone VALUES (13,'Martin','Rolfsen','317-555-6767',
'5678 E 7th Ave','Indianapolis','IN','46234','2002-08-22');
INSERT INTO Phone VALUES (14,'Lisa','Biggins','317-555-3644',
'7732 Allisonville','Indianapolis','IN','46240','2002-08-22');
INSERT INTO Phone VALUES (15,'Eric','Gonday','317-555-0500',
'9466 Pike Plaza','Greenfield','IN','46533','2002-08-22');
INSERT INTO Phone VALUES (16,'Douglas','Poser','317-555-0123',
'55 Tower Pl','Noblesville','IN','46234','2002-08-24');
INSERT INTO Phone VALUES (17,'John','Palmer','317-555-4444',
'12433 N Cumberland','Fishers','IN','46038','2002-08-22');

The Cards
The following sections list the PHP code used for each function/card.
Note that the WML and header code is taken care of by global
statements-each card need only output the <card> tags and
everything in-between. The $cmd variable controls what function is
executed, and hence, which card is displayed.
Note: I've used "echo" commands in this script, but "print" commands
would do just as well. Also, to increase the readability of the output
I've added line feeds (via a variable, $lf, set to ASCII character 10) to
most lines of output. I've chosen to append this variable to the "echo"
statements for readability purposes--using "\n" in your "echo"

commands would do just as well but tends to clutter the information


between the quotes. Also note the use of the entity "&amp;" in the
URLs instead of a straight ampersand ("&"). This is necessary to keep
WML from assuming that the ampersand is the beginning of an entity
name.
Menu
The menu function displays a simple select list, allowing the user to
choose what function he or she wants to access:
echo "<card id=\"Menu\">\n";
echo "<p mode=\"nowrap\">".$lf;
// Set up Select menu list
echo "<select name=\"Select\" title=\"Select:\">".$lf;
// Go through results from Query, listing each as a CHOICE entry
echo "<option onpick=\"?cmd=List\">";
echo "List Contacts</option>".$lf;
echo "<option onpick=\"?cmd=Search\">";
echo "Search Contacts</option>".$lf;
// Close select
echo "</select>".$lf;
// Close card
echo "</p>".$lf."</card>".$lf;

The code is straightforward, defining a simple <select> list. Each


<option> in the list calls the current script, passing the appropriate
value of $cmd. Note that we need to handle the case when $cmd is
empty, which it will be when the script is first called. In the body, near
the beginning of the script, we add the following line:
if (empty($cmd)) { $cmd = "Menu"; }

That ensures that if no command is given (via $cmd), the menu will be
displayed.

List
This is the meat of the script, displaying both the raw list as well as
search results, five records at a time. (For reference after the listing,
each line has been numbered.)
1 // Construct appropriate *count* query
2: $query = "select count(*) from Phone";
3: if (!empty($search)) {
4:
$query = $query." where FirstName like \"%".$search."%\" or";
5:
$query = $query." LastName like \"%".$search."%\"";
6: }
7:
8:

$result = mysql_query($query,$link)
or die("Query failed:$query");

9:

list($total_rows) = mysql_fetch_array($result);

10:
11:
12:
13:
14:
15:

// Construct appropriate query


$query = "select * from Phone";
if (!empty($search)) {
$query = $query." where FirstName like \"%".$search."%\" or";
$query = $query." LastName like \"%".$search."%\"";
}

16:
17:

// Get first/next five records


$query = $query." order by LastName limit ".$idx.",5";

18:
19:

$result = mysql_query($query,$link)
or die("Query failed:$query");

20:
21:

// Advance DB index
$next = $idx + 5;

22:
23:
24:

// Start card
echo "<card id=\"Contacts\">\n";
echo "<do type=\"accept\" label=\"View\"> <go href=\"\"/> </do>".$lf;

25:
26:
27:
28:
29:
30:

// Display appropriate full/search heading


if (empty($search)) {
echo "<p mode=\"nowrap\"><b>Phone Book</b>".$lf;
} else {
echo "<p mode=\"nowrap\"><b>Search Results</b>".$lf;
}

31:
32:

// Set up Select list (list of five records)


echo "<select name=\"View\" title=\"View:\">".$lf;

33:
34:

// Go through results from Query, listing each as a CHOICE entry


while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {

35:
36:
37:
38:

$recordid = $line[Id];
$Name = $line[LastName] . ", " . $line[FirstName];
$Number = $line[Phone];
$Prompt = $Name . " (" . $Number . ")";

39: // Build URL for option, include DB index and search


40:
$option = "<option onpick=\"";
41:
$option = $option."?cmd=Display&amp;id=".$recordid."&amp";
42:
$option =
$option.";idx=".$recordid."&amp;search=".$search."\">";
43:
$option = $option.$Prompt."</option>".$lf;
44:
echo $option.$lf;
45:

46:
47:
48:

// If there are more records to display, set up paging


// else mark end of list (to keep Home as same option)
if ($total_rows >= $next) {

49:
50:

// Link to next five


echo "<option title=\"Next\" onpick=\"?cmd=List&amp;idx=$next";

51:
52:
53:
54:

// Pass Search criteria if exists


if (!empty($search)) {
echo "&amp;search=".$search;
}

55:
56:

echo "\">[Next Records]".$lf."</option>".$lf;


} else {

57:

echo "<option title=\"End of List\" onpick=\"?cmd=List&amp;idx=$idx";

58:
59:
60:
61:

// Pass Search criteria if exists


if (!empty($search)) {
echo "&amp;search=".$search;
}

62:
63:

// Close tags
echo "\">[End of List]".$lf."</option>".$lf;

64:

65:

// Add option for Home

66:
67:
68:

echo "<option onpick=\"?cmd=Menu\" title=\"Home\">".$lf;


echo "[Back to Home]".$lf;
echo "</option>".$lf;

69:
70:

// Close select
echo "</select>".$lf;

71:
72:

// Close card
echo "</p>".$lf."</card>".$lf;

This code makes use of global variables ($idx, $search) to display the
list of contacts. Both variables are passed as a name/value pair when
the script is called with $cmd equal to "List." If $search is empty, the
full list of contacts is displayed, else the text of $search is added to the
query and records are returned only if FirstName or LastName contains
the search text.
The $idx variable marks what results the script currently is listing. The
record at location $idx is the first record on the current page.
This becomes more self-explanatory as we work through the code:
Lines 1-9 construct and execute a "count" query, storing the number
of returned rows in the variable $total_rows. This value is used later
(line 48) to determine whether there are more pages of data to
display. Note that the search text is added if $search is not empty
(hence contains search criteria).
Lines 10-19 construct a query to return the target dataset. Again, the
search criterion is added to the query, if it exists. Line 17 appends a
limit clause to the query, causing the query to return only five records
(or less), starting at the record indicated by the value of $idx. If $idx
is zero (which it will be the first time the script executes List), the first
five records are returned. The variable $next is set to a value of $idx +
5 (line 21) and used to call the next iteration of List, causing the next
five records to be displayed.
Note: This method is far from perfect. For example, if a record is
added or modified that causes a record to be added or removed from
the returned dataset, the next page will return different results than it

would before the record was added or modified. If the list is being
displayed while the records are being modified-which happens in most
database applications-the displayed results can be somewhat
unpredictable.
Lines 22-30 begin the display card definition, including the appropriate
header-"Phone Book" if the search string is empty (raw list being
displayed) or "Search Results" if the search string is not empty (search
results being displayed). This helps guarantee that the result set
returned will be the same, allowing consistent paging through the set
(with the caveat explained above).
Line 32 begins the <select> list, with lines 34-70 building and
displaying five items as <option>s. Lines 35-38 build the text for the
<option> prompt, while lines 39-44 construct the <option> statement
with an appropriate "onpick" parameter that recursively calls the script
with $cmd equal to "Display" and the ID of the record to display. Note
that $idx and $search are also passed to maintain their values through
the recursive call, just in case we need them later.
Lines 49-64 build option number 6 in our select list. If there are more
records to display ($total_rows >= $next) the script generates the
option "[Next Records]" with an appropriate "onpick" parameter to
recursively call the script, specifying the next starting record to display
(via idx=$next). If there are no more records to display, the script
generates an "[End of List]" option, which recursively calls the script
with the same starting point as is currently displayed. Each option also
includes the search criteria if it exists (lines 51-54 and 58-61).
Finally, a "Home" option is created (lines 65-68) to allow the user to
return to the home menu from any page of the listing. The open tags
are then closed.
Search
The search function is simply an input tag that accepts up to 10
characters and recursively calls the script supplying the text entered
and sets $cmd equal to "List."
echo "<card id=\"Search\">\n";
echo "<do type=\"accept\" label=\"Go\">".$lf;

echo "<go href=\"?cmd=List&amp;search=\$searchtext\">".$lf;


echo "</go>".$lf;
echo "</do>".$lf;
echo
echo
echo
echo
echo
echo

"<p>".$lf;
"<b>Phone Book Search</b><br/>Search for:".$lf;
"<input name=\"searchtext\" title=\"Search\" type=\"text\"";
" format=\"10m\"/>";
"</p>".$lf;
"</card>".$lf;

Display
Display uses the record ID passed in $idx to select a record from the
database and display all of its related information (name, address,
phone, etc.).
echo "<card>\n";
// Get specific record
$query = "select * from Phone where Id = \"".$idx."\"";
$result = mysql_query($query,$link)
or die("Query failed:$query");
// Get data and display
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
$recordid = $line[Idx];
echo
echo
echo
echo
echo
echo
echo
echo

"<p mode=\"wrap\">".$lf;
"$line[LastName], $line[FirstName]<br />".$lf;
"$line[Address]<br />".$lf;
"$line[City], $line[State] $line[Zip]<br />".$lf;
"<a href=\"wtai://wp/mc;$line[Phone]\" title=\"Dial\">";
"$line[Phone]</a><br />".$lf;
"<a href=\"?cmd=Menu\" title=\"Menu\">";
"[Home Menu]</a><br /><br />".$lf;

$Date = date("M j, Y", strtotime($line[LastUpdate]));


echo "Record Updated:<br />$Date";
// Close record display card
echo $lf."</p></card>".$lf;
}

Note that the card has a "Dial" option mapped to the Accept key and is
displayed with the phone number highlighted. This allows the user to
quickly dial the selected number on devices that support URL dialing.
Other items of note include a "Home Menu" link and a more verbose
format for the last update date. We do not need to provide any
functionality to return to the last page of record listings-the user can
do so by pressing the Back key on his or her device.

The Entire Script


Now that we've defined the various functions of the script, let's tie it
all together with a "switch" statement and some additional initialization
statements:
<?php
header("Content-type: text/vnd.wap.wml");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
// expires in the past
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
// Last modified, right now
header("Cache-Control: no-cache, must-revalidate");
// Prevent caching, HTTP/1.1
header("Pragma: no-cache");
// Prevent caching, HTTP/1.0
echo "<?xml version=\"1.0\"?>\n";
echo "<!DOCTYPE wml PUBLIC \"-//WAPFORUM//DTD WML 1.1//EN\""
. " \"http://www.wapforum.org/DTD/wml_1.1.xml\">\n";
echo "<wml>\n";
// Open link to DB
$link = mysql_connect("localhost", "webuser", "webby99")
or die("Could not connect to database!");
mysql_select_db("customers")
or die("Could not select database!");
// Line feed
$lf = chr(10);
// Make sure that index into DB has value

if (empty($idx)) { $idx = 0; }
if (empty($cmd)) { $cmd = "Menu"; }
switch ($cmd) {
case "Menu";
echo "<card id=\"Menu\">\n";
echo "<p mode=\"nowrap\">".$lf;
// Set up Select menu list
echo "<select name=\"Select\" title=\"Select:\">".$lf;
// Go through results from Query, listing each as a CHOICE entry
echo "<option onpick=\"?cmd=List\">";
echo "List Contacts</option>".$lf;
echo "<option onpick=\"?cmd=Search\">";
echo "Search Contacts</option>".$lf;
// Close select
echo "</select>".$lf;
// Close card
echo "</p>".$lf."</card>".$lf;
break;
case "List";
// Construct appropriate *count* query
$query = "select count(*) from Phone";
if (!empty($search)) {
$query = $query." where FirstName like \"%".$search."%\" or";
$query = $query." LastName like \"%".$search."%\"";
}
$result = mysql_query($query,$link)
or die("Query failed:$query");
list($total_rows) = mysql_fetch_array($result);
// Construct appropriate query
$query = "select * from Phone";
if (!empty($search)) {
$query = $query." where FirstName like \"%".$search."%\" or";
$query = $query." LastName like \"%".$search."%\"";
}

// Get first/next five records


$query = $query." order by LastName limit ".$idx.",5";
$result = mysql_query($query,$link)
or die("Query failed:$query");
// Advance DB index
$next = $idx + 5;
// Start card
echo "<card id=\"Contacts\">\n";
echo "<do type=\"accept\" label=\"View\"> <go href=\"\"/> </do>".$lf;
// Display appropriate full/search heading
if (empty($search)) {
echo "<p mode=\"nowrap\"><b>Phone Book</b>".$lf;
} else {
echo "<p mode=\"nowrap\"><b>Search Results</b>".$lf;
}
// Set up Select list (list of five records)
echo "<select name=\"View\" title=\"View:\">".$lf;
// Go through results from Query, listing each as a CHOICE entry
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
$recordid = $line[Id];
$Name = $line[LastName] . ", " . $line[FirstName];
$Number = $line[Phone];
$Prompt = $Name . " (" . $Number . ")";
// Build URL for option, include DB index and search
$option = "<option onpick=\"";
$option = $option."?cmd=Display&amp;id=".$recordid."&amp";
$option = $option.";idx=".$recordid."&amp;search=".$search."\">";
$option = $option.$Prompt."</option>".$lf;
echo $option.$lf;
}
// If there are more records to display, set up paging
// else mark end of list (to keep Home as same option)
if ($total_rows >= $next) {
// Link to next five
echo "<option title=\"Next\" onpick=\"?cmd=List&amp;idx=$next";

// Pass Search criteria if exists


if (!empty($search)) {
echo "&amp;search=".$search;
}
echo "\">[Next Records]".$lf."</option>".$lf;
} else {
echo "<option title=\"End of List\" onpick=\"?cmd=List&amp;idx=$idx";
// Pass Search criteria if exists
if (!empty($search)) {
echo "&amp;search=".$search;
}
// Close tags
echo "\">[End of List]".$lf."</option>".$lf;
}
// Add option for Home
echo "<option onpick=\"?cmd=Menu\" title=\"Home\">".$lf;
echo "[Back to Home]".$lf;
echo "</option>".$lf;
// Close select
echo "</select>".$lf;
// Close card
echo "</p>".$lf."</card>".$lf;
break;

case "Search";
echo
echo
echo
echo
echo

"<card id=\"Search\">\n";
"<do type=\"accept\" label=\"Go\">".$lf;
"<go href=\"?cmd=List&amp;search=\$searchtext\">".$lf;
"</go>".$lf;
"</do>".$lf;

echo
echo
echo
echo
echo

"<p>".$lf;
"<b>Phone Book Search</b><br/>Search for:".$lf;
"<input name=\"searchtext\" title=\"Search\" type=\"text\"";
" format=\"10m\"/>";
"</p>".$lf;

echo "</card>".$lf;
break;

case "Display";
echo "<card>\n";
// Get specific record
$query = "select * from Phone where Id = \"".$idx."\"";
$result = mysql_query($query,$link)
or die("Query failed:$query");
// Get data and display
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {
$recordid = $line[Idx];
echo
echo
echo
echo
echo
echo
echo
echo

"<p mode=\"wrap\">".$lf;
"$line[LastName], $line[FirstName]<br />".$lf;
"$line[Address]<br />".$lf;
"$line[City], $line[State] $line[Zip]<br />".$lf;
"<a href=\"wtai://wp/mc;$line[Phone]\" title=\"Dial\">";
"$line[Phone]</a><br />".$lf;
"<a href=\"?cmd=Menu\" title=\"Menu\">";
"[Home Menu]</a><br /><br />".$lf;

$Date = date("M j, Y", strtotime($line[LastUpdate]));


echo "Record Updated:<br />$Date";
// Close record display card
echo $lf."</p></card>".$lf;
}
break;
}
echo $lf."</wml>".$lf;
mysql_close($link);
?>

Note that we pass a handful of headers at the beginning of the script


to inhibit caching. Since our database is frequently updated and
correct/up-to-date information in the field is valuable, we do not want
the device to display cached information instead of recently updated
information. However, generally speaking, inhibiting the cache is a bad
idea and should be done sparingly, if at all.

The Script in Action


Now let's see the script in action. The following figures demonstrate
each function:
Note: All images 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 1 - The menu.

Figure 2 - The list. Notice the [Next Records] option.

Figure 3 - The end of the list. Notice the [End of List] option.

Figure 4 - The search form.

Figure 5 - The results of a search (for "in"). Note the first result
is "Biggins, Lisa" but the text has scrolled to the phone number
due to the "nowrap."

Figure 6 - A record in the display card.

Room for Improvement


This script has plenty of room for improvement, including the following
items:
Applying the cache inhibitor headings only to cards that could cause
problems (such as Display), instead of globally.
Optimizing the output to avoiding duplicating code (such as the
addition of search criteria to the URL(s)).
Adding more prompts for the user through card titles, etc.
Optimizing and standardizing variable naming and usage.
Providing means for the user to edit records. Although it can be
tedious to enter data on most mobile devices, simple corrections or
notes would be welcome. A "last called" field could also be entered
automatically each time a contact is called.
This script represents only a small portion of what can be done with
PHP and a database such as MySQL. This example could be expanded
to offer group calendaring, scheduling, order placement, stock
checking, etc. As long as you keep the target audience and the
respective design goals in mind, the sky's the limit.

Delivering HTML To a WML Device


This series of articles describes how to provide Web content to mobile
devices through WML (Wireless Markup Language). This article covers
techniques to use when delivering standard HTML to WML-compatible
devices.
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.

Delivering Converted HTML


There may be several reasons why you may need to deliver standard
HTML markup text to a WML-compatible device. You may have data
stored in a database that is typically displayed in a standard browser;
legacy data or pages that resist conversion; or cross-platform text that
needs to be primarily available for a standard HTML browser, but
would be useful if delivered to WML clients.
For example, I'm the administrator for a movie news Web site. The
articles for the site are marked up using standard HTML, stored in a
SQL database, and delivered to the clients using PHP pages. The bitesized articles also make for great wireless content_something that can
be browsed while in an airport or during other downtime_so I decided
to make this content available in WML. Unfortunately, I quickly found
out how incompatible even minor HTML tags are with WML, creating
the need for some simple conversion procedures. Although not perfect,
those procedures are used as the basis for this article.
Tip: The quick-and-dirty methods described in this article are handy as
a temporary or short-term measure. If you intend to support a
particular platform long-term, I recommend creating custom code for
that platform.

Standard HTML vs. WML


Standard HTML documents don't work well on WML-capable devices.

Even if the wireless services offer translation services through their


gateways, standard HTML seldom displays as the developer or user
would like.
Limited Tags
WML supports a very limited subset of HTML tags. Among those
supported are the following tags:
Character formatting
<B> - Bold
<I> - Italic
<U> - Underline
<BIG> - Big text
<SMALL> - Small text
<STRONG> - Strong (visually emphasized) text
<P> - Paragraph
Table tags
<TABLE>
<TR> - Table row
<TD> - Table column/cell
Several tags nearly alike between the two languages, but their
formatting and/or parameters are different enough to cause problems.
For example, the line break tag is simply <br> in HTML, but <br /> in
WML. Also, tags such as the table tags support many more options and
parameters in HTML than in WML, and rarely allow HTML tables to
display properly in WML.
Device Display Limitations
Standard HTML documents are generally designed for large displays,
such as 800 x 600 resolution CRTs connected to a PC, not a 240 x 320
LCD on a PDA (or smaller, if a cell phone). Even devices that run
HTML-compliant browsers (such as IE in Windows CE devices) have
problems with the majority of today's Web sites.

The almost unrecognizable internet.com home page, displayed in IE on


a Pocket PC (Windows CE).
Tip: To gauge roughly how a page will look on a smaller device, shrink
your standard PC browser window down to that size.
Most mobile devices don't support the vast array of text formatting
available toPC browsers. For example, earlier versions of certain
mobile browsers don't support underlining; others don't support italic
or bold text. Tables are especially problematic due to their width.
Device memory is also a problem. Most mobile browsers only support
pages (decks) a few kilobytes in size, requiring the content to be
broken down into bite-sized chunks and displayed across several
cards, if not several decks.

Finally, most modern PC-based browsers (IE, Mozilla, Netscape, and so


on) have built-in logic to handle incomplete or misused tags. For
example, most PC browsers are forgiving of HTML documents that fail
to close a major element such as a table or the body of the document.
Most mobile browsers are far less forgiving, requiring very strict use of
tags.
Device Input Limitations
Interactive Web pages present even more challenges to the mobile
user. Anyone who has needed to tap/write out even a short note on a
PDA can appreciate the need to keep interfaces simple. Those who
have tried to compose more than just a few characters on a standard
cell phone keypad can appreciate this even more strongly.
The simplest Web interface is the form, whose structure is
considerably different in WML. Simply converting the structure and
tags isn't sufficient; you also have to consider how it will affect the end
user on his or her individual platform. For example, choosing the
correct state code from a drop-down list is easy on a standard
browser. However, drop-down lists translate to select lists in WML,
necessitating a list of 50 entries that the user must scrolled through
(usually 9 items per page) to select the proper code.

When Is Converting Worth the Effort?


Given the discussion above, there are a few HTML-to-WML conversions
that are more problematic than they are worth:
Tables
Unless you know that every table in the document is extremely
narrow and contains no fancy formatting/parameters, you should
simply remove the tags.
Graphics
Some gateways will convert graphic files into the prerequisite WBMP
format. However, most will simply refuse to display the standard
JPG/GIF/PNG Web formats. Unless you have the appropriate
graphics available in the WBMP format, remove the graphics.
Code
HTML pages that rely on Java, JavaScript, or some other scripting

language generally will not be compatible with mobile devices,


especially those compatible only with WML. Devices using IE (such
as CE-equipped PDAs) will fare much better, but you can't rely on
that.
In short, only textual pages are worth the time to convert. More
complex pages should be redesigned for each individual platform you
want to support. Keep in mind that straight WML does not have the
facility to convert HTML--you must use a CGI or PHP script to deliver
the content instead.
Note: See the two previous articles on how to integrate PHP into your
WML delivery.

Conversion Procedures
Converting standard HTML-formatted text is a two-step process. First,
remove any tags that are not supported by the target platform.
Second, tailor supported tags to the target platform. For example, the
line break tag is supported by WML, but needs to have the slash added
("<br />").
Removing Unsupported Tags
To ensure a smooth conversion, remove all but the following tags from
the HTML code:
<p>
</p>
<br>
You can also retain text-formatting tags that your target browser
supports, such as <i>, <b>, etc.
If you are using PHP, the code to strip the offending tags is very
simple:
$wml = strip_tags($html,'<p><br><i><b><u>');
Using the HTML (stored in $html), the above code removes all tags but
those given in the "strip_tags" parameter, and stores the result in the

variable $wml.
If you are feeling adventurous and know the format of tables in the
code, you can parse the table tags down to the bare minimum
parameters (as supported by your target browser). However, only the
smallest tables will display conveniently on mobile devices.
Converting Supported Tags
Although paragraph (<p>) and line break (<br />) tags are supported
in WML, their usage varies from that in HTML. For example, blocks of
text must be enclosed in paragraph tags; you cannot use a stray tag
to separate paragraphs, like this:
Paragraph . . .
<p>
Paragraph . . .
Although such use is sloppy when used anywhere, it has become
prevalent in HTML pages. Instead of creating a sophisticated parsing
scheme to ensure the matching pairs of tags, it's much easier to
convert all open and closing paragraph tags to double line break tags.
This causes the current line to break where the paragraph tag was
used, and inserts the extra space between the paragraphs.
Again, if you are using PHP, the code is straightforward:
$wml = str_replace("<p>","<br /><br />",$wml);
$wml = str_replace("</p>","<br /><br />",$wml);
The above code will replace every "<p>" and "</p>" with "<br /><br
/>".
Each line break tag in WML must end in a slash. A similar PHP
str_replace statement takes care of this requirement:
$wml = str_replace("<br>","<br />",$wml);

Note: PHP functions that support regular expressions can be more


versatile and can do more work per statement if constructed correctly.
I prefer to use individual statements for later flexibility and clearer
code.
Miscellaneous Cleanup
Two more items need to be cleaned up to display correctly in WML:
ampersands ("&") and dollar signs ("$"). An ampersand must be
converted to an entity ("&amp;"), and a dollar sign must be doubled
("$$").
Again, in PHP you can use the str_replace function:
$wml = str_replace("&","&amp;",$wml);
$wml = str_replace("$","$$",$wml);
Note: An abundance of special characters can find their way into
otherwise mundane HTML code. For example, when text is cut-andpasted from a word processing document into HTML documents, single
and double quotes usually appear as extended ASCII characters, and
must be converted to the appropriate plain text characters or HTML
entities. Only direct experience and experimentation with your specific
documents can determine what problems you may have and need to
work around.

Interactive Fun and Games with WAP and


WML
This series of articles describes how to provide Web content to mobile
devices through WML (Wireless Markup Language). This article covers
creating an interactive game for deployment on mobile WML devices.
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.

Uses for Mobile Devices


Mobile devices are the most useful when they are connected to data
sources and have the ability to deliver various data whenever needed.
However, mobile devices are also very useful for entertainment
purposes-I've spent many hours in airports with only my PDA and
phone for company. Although WML doesn't lend itself to a complex
gaming experience, it is fairly easy to create interactive entertainment.
In this article I will lead you through the steps to create a rudimentary
"hangman" game.

Project Specifications
Hangman is a straightforward game. For anyone unfamiliar with the
game, a word is shown as blanks and the player guesses letters that
may be in the word. If a letter guessed is in the word, all instances of
that letter are revealed. If a guessed letter is not in the word, part of a
hanging stick figure is drawn on a scaffold, at the end of a
noose_usually starting with the head, then the body, then the limbs
(one at a time). The game ends when the word is guessed or the
figure is completely drawn. In the former case the player wins, in the
latter the player loses.
The functionality for our project is as simple as the pen-and-paper
version of the game:
1 A word is chosen and displayed as blanks.
2 The player picks a letter.
3 The letter is compared to the letters in the word; if the letter is in
the word, the appropriate blanks are changed to display the letter.
If the letter is not in the word, a piece is added to the hanging stick
figure.
4 The game ends when the user knows the word or the figure is
complete.
Once the functionality has been determined, the interface must be
drafted.
All output must fit on a display that is approximately 16 characters
wide and 4-10 lines tall.
The stick figure will be represented by ASCII characters instead of
graphics since we can't ensure that the player will be using a

graphics-capable device. The figure will be made of 6 segments,


giving the player 6 letter selections.
Several cards will be used, one for each function. That will help
segregate the functions and keep the display lean.
The words must be random and taken from a decent-sized list to
keep the player engaged.
To simplify coding, all words will be in lowercase and the player's
input will be forced to lowercase.

Coding the Basics


This project will utilize WML for the input and output and WMLScript
for the behind-the-scenes processing. Because debugging tools for
WMLScript are limited, we will build this project in small stages, adding
features only after the current feature set is solid.
To start, we will create a simple deck of two cards. The first card will
call a WMLScript function to initialize the game's variables. The second
card will then be displayed, showing the word as blanks and as plain
text (for debugging purposes).
Our skeletal code looks like this:
WML
1 <?xml version="1.0"?>
2 <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
3 "http://www.wapforum.org/DTD/wml_1.1.xml">
4 <wml>
5 <card id="card1" title="Hangman">
6 <!-- When this card is entered, call the init routine -->
7 <onevent type="onenterforward">
8 <go href="hangman.wmls#init()"/>
9 </onevent>
10
<p>
11
<!-- Tell the user we are initializing. When the
12
init function is finished it will call card2 -->
13
Initializing...
14
</p>

15
16
17
18
19
20
21
22

</card>
<card id="card2" title="Hangman">
<p>
$word<br/>
$blank
</p>
</card>
</wml>

WMLS
1 extern function initword() {
2 // Define word list (pseudo array)
3 var words = "animal announce banana doctor elephant giraffe";
4 var idx,x = 0;
5 var blank,word = "";
6 // Randomize a word
7 idx = Lang.random(6);
8 word = String.elementAt(words,idx," ");
9 // Add an "*" for every letter in the chosen word
10
for (x = 1; x <= String.length(word); x++ ) {
11
blank = blank + "*";
12
}
13
// Set the WML vars and call card2
14
WMLBrowser.setVar("word", word);
15
WMLBrowser.setVar("blank",blank);
16
WMLBrowser.go("hangman.wml#card2");
17
}
The first design decision is how to store the word list. Because
standard WML technologies do not provide a convenient link to the
outside world (databases, etc.), the list has to be stored internally,
within the script itself. The lack of true arrays in WMLScript contributes
to the problem as well. However, WMLScript's string handling solves
the problem well enough_the words will be stored in delimited form
within one long string (WMLS line 3). The String.elementAt function
could then be used to parse one of the words from the list (WMLS line
8).
Next the "blank" template must be constructed for the word (lines 912). At this point, the variable $word will contain the actual word,
while $blank contains the blank representation. The variables are

pushed out to the browser and the next card is displayed, showing all
the values.
Note: Even this part of the script was developed in pieces, though for
the sake of brevity in this article I've chosen to start with this chunk of
code. Initially, I created the stub WML code to call the init function,
which simply set a variable. Then I created the word list and
randomized a word, which was displayed by the WML. Finally, I
created the blanking code to create the blank word.
Tip: The Openwave SDK provides a great environment to develop
applications in a stairstep method. Visit the developer site at
www.openwave.com for more information or to download the SDK.
Next we need to add the player entry code and the ability to check the
entry against the word. The new code resembles the following listings:
WML
1 <?xml version="1.0"?>
2 <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
3 "http://www.wapforum.org/DTD/wml_1.1.xml">
4 <wml>
5 <card id="card1" title="Hangman">
6 <onevent type="onenterforward">
7 <go href="hangman.wmls#initword()"/>
8 </onevent>
9 <p>
10
Initializing...
11
</p>
12
</card>
13
<card id="card2" title="Hangman">
14
<do type="accept">
15
<go href="hangman.wmls#guess()" />
16
</do>
17
<p>
18
Guess: <input name="guess" maxlength="1" format="a" />
19
$word<br/>
20
$blank
21
</p>
22
</card>
23
</wml>

WMLS
1 extern function initword() {
2 var words = "animal announce banana doctor elephant giraffe";
3 var idx,x,hang = 0;
4 var blank,word = "";
5 idx = Lang.random(6);
6 word = String.elementAt(words,idx," ");
7 for (x = 1; x <= String.length(word); x++ ) {
8 blank = blank + "*";
9 }
10
WMLBrowser.setVar("word", word);
11
WMLBrowser.setVar("blank",blank);
12
WMLBrowser.setVar("hang",hang);
13
WMLBrowser.go("hangman.wml#card2");
14
}
15
extern function guess() {
16
var x = 0;
17
var temp,ch = "";
18
var word = WMLBrowser.getVar("word");
19
var blank = WMLBrowser.getVar("blank");
20
var guess = WMLBrowser.getVar("guess");
21
// Check each letter in word. Transfer letters/blanks
22
// from blank, revealing any new matches.
23
for (x = 0; x <= String.length(word); x++ ) {
24
ch = String.subString(word,x,1);
25
if ( ch == guess ) {
26
temp = temp + guess;
27
} else {
28
temp = temp + String.subString(blank,x,1);
29
}
30
}
31
WMLBrowser.setVar("blank",temp);
32
WMLBrowser.go("hangman.wml#card2");
33
}
The new code (mostly the guess function, WMLS lines 15-33) accepts
user input (WML line 18) and compares it to the letters in the word. A
"temporary" word is built, revealing each matching letter and leaving
the rest of the word as revealed or hidden as it was before the guess.
The temporary variable ($temp) is then passed back to the browser as

the new $blank variable.

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.)
The new code allows for input and displays debugging output. Note
that "a" was guessed in the previous round, revealing the 3 a's in
"banana." Because the $guess variable is persistent, the value sticks in
the input field. We will have to fix that eventually.

Adding the Scoring (Hangman)


As Figure 8.1 shows, we are quickly running out of screen real estate
for our game. Since we are using text to depict our hanging man, we
will need 4-6 more lines to display him - and we obviously do not have
enough lines. Time for an interface change.
Instead of putting the input on the same page as the status, we will
move it to a "guess" card. It means more work for the user (he or she
must select a "guess" button to display the input), but cleans up our
interface.
For the hanging man, we will use the following ASCII representation:

0
-|/\

He is crude but recognizable. We can break the man down into six
pieces: the head, torso, two arms, and two legs. If we get creative and
treat each piece as a string, we can add appropriate line breaks and
print each string in sequence, effectively "building" the man. Consider
the following:
Head: "<space>0<linebreak>"
Arm1: "-"
Torso: "|"
Arm2: "-<linebreak>"
Leg1: "/"
Leg2: "<space>\"
When printed in sequence, the hanging man is displayed. If we print
only the first three strings, only three pieces of the man (head, arm,
torso) are displayed.
Again, because WMLS lacks real arrays, we will build a delimited string
that contains all the pieces:
" , 0\r,-,|,-\r,/,\\\r"
Note that a comma is used as the delimiting character, and that we
put a blank piece (a space) in the beginning to give the first real piece
an index of 1 instead of 0. Also note that we use escape codes for line
breaks (\r = newline) and we must escape the backslash. Because
WML doesn't parse variables, using WML code for line breaks would
only result in the tags (such as "<br />") being displayed as text
instead of being interpreted as line breaks.

Finishing Up

Although it seems like there is a lot left to do, the project is mopped
up pretty quickly by doing the following:
Adding a variable and code to count the incorrect guesses and
determine loss
Checking whether the word has been totally revealed (win)
Adding appropriate cards for win/loss
Building a string variable to display the hanging man
Various housekeeping and cleanup chores (renaming some
functions, cleaning up some variables, adding comments, etc.)
Most of these tasks revolve around counting the number of guesses.
The last task is the usual housekeeping that takes place at the end of
a project. The final code looks like this:
WML
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
"http://www.wapforum.org/DTD/wml_1.1.xml">
<!-- hangman.wml - WML hangman game -->
<wml>
<!-- Game initialization - only calls init script -->
<card id="init" title="Hangman">
<onevent type="onenterforward">
<go href="hangman.wmls#init()"/>
</onevent>
<p>
Initializing...
</p>
</card>
<!-- Status card. Shows hanged man and revealed word -->
<!-- User presses Accept key to make a guess -->
<card id="status" title="Hangman">
<do type="accept" label="Guess">
<go href="#guess" />
</do>
<p>
&nbsp;|<br/>
$man<br/><br/>
$blank
</p>

</card>
<!-- Accepts user's guess (one character, lowercase) -->
<card id="guess" title="Hangman">
<do type="accept">
<go href="hangman.wmls#guess()" />
</do>
<p>
$blank<br/>
Guess: <input name="guess" maxlength="1" format="a" />
</p>
</card>
<!-- Game over, player has been hanged. -->
<card id="hung" title="Hangman">
<do type="accept" label="Restart">
<go href="#init" />
</do>
<p>
You've been hanged!<br/><br/>
Word was:<br/>
&nbsp;$word<br/>
Your guess:<br/>
$blank
</p>
</card>
<!-- Game over, player wins..-->
<card id="win" title="Hangman">
<do type="accept" label="Restart">
<go href="#init" />
</do>
<p>
You win!<br/><br/>
Word was:<br/>
&nbsp;$word<br/>
</p>
</card>
</wml>

WMLS
// hangman.wmls
// Functions for hangman game

//
//
//
//
//
//
//

Variables:
word = word to be guessed
blank = blank representation of 'word', letters
begin as asterisks ("*") and are revealed as guessed
hang = current count of incorrect guesses
man = current ASCII representation of hanging man
guess = current character guessed

// Initialize game
extern function init() {
// Init vars. Change words every so often (or add to them)
var words = "animal announce banana doctor elephant giraffe";
var idx,x,hang = 0;
var blank,word,man = "";
// Pick a random word from list
idx = Lang.random(6);
word = String.elementAt(words,idx," ");
// Build a blank string (letters all "*") that
// is the same length as our word
for (x = 1; x <= String.length(word); x++ ) {
blank = blank + "*";
}
// Pass all values to browser
WMLBrowser.setVar("word",word);
WMLBrowser.setVar("blank",blank);
WMLBrowser.setVar("hang",hang);
WMLBrowser.setVar("man",man);
// Display status card
WMLBrowser.go("hangman.wml#status");
}
// Evaluate current guess
extern function guess() {
// Init vars
var x = 0;
var temp = "";
var correct = false;
var man = "";
// Pieces for the hanging man,comma delimited
var manpieces = " , 0\r,-,|,-\r,/, \\\r";

// Get current values


var word = WMLBrowser.getVar("word");
var hang = WMLBrowser.getVar("hang");
var blank = WMLBrowser.getVar("blank");
var guess = WMLBrowser.getVar("guess");
//
//
//
//
//

Walk one character at a time through word


If guess = character, reveal character
If guess != character, keep current value
(revealed character or blank)
Also, set "correct" if at least one char found

for (x = 0; x <= String.length(word); x++ ) {


if ( String.subString(word,x,1) == guess ) {
temp = temp + guess;
correct = true;
} else {
temp = temp + String.subString(blank,x,1);
}
}
// If letter wasn't found, add one to hanging counter
if (! correct) { hang++; }
// Build our hanging man
if (hang > 0) {
for (x = 1; x <= hang; x++ ) {
man = man + String.elementAt(manpieces,x,",");
}
}
// Blank the guess so <input> is blank
guess = "";
// Pass current values to browser
WMLBrowser.setVar("blank",temp);
WMLBrowser.setVar("guess",guess);
WMLBrowser.setVar("hang",hang);
WMLBrowser.setVar("man",man);
// Determine whether player has won (no more
// "*" in blank), has lost (6 pieces of man
// displayed, or keep playing (else).
if ( String.find(temp,"*") == -1 ) {
WMLBrowser.go("hangman.wml#win");
} else {
if (hang >= 6) {
WMLBrowser.go("hangman.wml#hung");

} else {
WMLBrowser.go("hangman.wml#status");
}
}
}

The starting screen of the game, complete with hangman's noose.

Pressing the Accept key brings up the guessing card.

As the player progresses, correct guesses are shown by revealing


characters in the word; incorrect guesses add to the hanging man.

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.)
If the player guesses six incorrect letters, the game ends. Guessing all

characters in the word, conversely, shows a "win" screen. Note the


"Restart" function mapped to Accept.

Things to Add
There's always room for improvement, especially in programming and
interfaces. Given time, I'd add the following to the game:
A graphic version of the hanging man as a default, allowing users
to switch to text if necessary.
Tracking and displaying the incorrect characters guessed
A splash screen (introductory graphical screen), help text, and a
cheat function (displays one character not yet revealed)
I'd also lengthen the word list and dynamically change it from time to
time. One idea would be to run a script on the server that swaps
different versions of the WMLS file into place, each with a different
word list. Another idea is to implement the entire game using PHP or
another, more robust scripting language that could tie into a massive
word database.

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.

Building WML Gadgets: World Time Clock


Review
As mentioned in the last few articles, 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.

World Time Application


This article describes how to create another useful small application: a
world time clock. This clock tells the time in prominent time zones and
areas around the world. If you need to make a call to Sydney,
Australia, for example, it would be nice to know if you are in danger of
waking someone up, or are calling during the lunch hour.
Our world time clock should accomplish the following:
Allow the user to search for a given time zone or area
Display the accurate time for the zone(s) found
Be easy to use, but complete enough to be useful
More Help from CGI
We could utilize only mobile technologies (WML and WMLScript) to
accomplish our goals. However, we'd have to do the following:
Find a comprehensive time zone database
Parse the database into an array in WMLScript
Do time and date calculations to determine the time in various zones
from the resulting data
At first, this approach doesn't seem too bad. There are a finite number
of time zones worldwide. However, you also have to take daylight
saving time into account. For example, central Indiana (which includes
Indianapolis) does not observe daylight savings. This means that
Indiana, geographically in the Central time zone, seemingly bounces
between Central and Eastern time zones. (In reality, central Indiana
stays on Eastern Standard Time, and the zones around it shift.)
A comprehensive open source database exists that can be used for this
purpose. The database, often referred to as the tz or zoneinfo
database, is packaged with most implementation of the GNU C Library,
primarily Linux installations. Several tools are available for reading the
data from the database, including a Perl module, Time::Timezone
(available from CPAN, www.cpan.org).
Note: As with previous articles, teaching Perl is out of the scope of this
series. There are numerous sources on the Internet for learning Perl,

including the tutorial at


http://wdvl.internet.com/Authoring/Languages/Perl/PerlfortheWeb/toc.
html.
Starting the Script -- Reading Time Zones and Telling Time
To start the script, we will simply output the time zones and the
current time for each. The simple script shown below accomplishes this
objective:
Listing: tztest.pl

#!/usr/bin/perl
use Time::ZoneInfo ':all';
my $zones = Time::ZoneInfo->new();
foreach my $zone ($zones->zones) {
print $zone."\n";
}
This script simply creates a list of all the available zones and outputs
each to the console. Next, we need to know what time it is in each
zone. Fortunately, the operating system (Linux, in this case) can do
the work for us.
Most Linux applications that need to tell time do so by referencing the
environment variable TZ. This variable contains the current time zone
for the system. The OS can use this variable to decode its internal time
clock into the correct value for the zone. If you change this variable
and request the time from an application that uses it, you can easily
tell the time in another zone.
With help from the Date::Calc module, we can add the current time
zone to our test:
Listing: tztest.pl

#!/usr/bin/perl

use Time::ZoneInfo ':all';


use Date::Calc ':all';
my $zones = Time::ZoneInfo->new();
foreach my $zone ($zones->zones) {
$ENV{TZ} = $zone;
($year,$month,$day,$hour,$min,$sec, $doy,$dow,$dst) =
System_Clock();
print $zone.":";
print $year."-".$month."-".$day." ";
print $hour.":".$min.":".$sec."\n";
}
Sample tztest.pl output:

...
America/New_York: 2002-12-29 23:35:22
America/Detroit: 2002-12-29 23:35:22
America/Louisville: 2002-12-29 23:35:22
America/Kentucky/Monticello: 2002-12-29 23:35:22
America/Indianapolis: 2002-12-29 23:35:22
America/Indiana/Marengo: 2002-12-29 23:35:22
America/Indiana/Knox: 2002-12-29 23:35:22
America/Indiana/Vevay: 2002-12-29 23:35:22
America/Chicago: 2002-12-29 22:35:22
America/Menominee: 2002-12-29 22:35:22
America/North_Dakota/Center: 2002-12-29 22:35:22
America/Denver: 2002-12-29 21:35:22
America/Boise: 2002-12-29 21:35:22
America/Shiprock: 2002-12-29 21:35:22
America/Phoenix: 2002-12-29 21:35:22
America/Los_Angeles: 2002-12-29 20:35:22
America/Anchorage: 2002-12-29 19:35:22
America/Juneau: 2002-12-29 19:35:22
America/Yakutat: 2002-12-29 19:35:22
America/Nome: 2002-12-29 19:35:22
America/Adak: 2002-12-29 18:35:22

...
Note that we simply change the TZ environment variable and call the
System_Clock method. The variable is only changed within the
application space, so the change in time zone doesn't affect the whole
system--just our application.
Searching for Time Zones
Next, we need the ability to search for a particular time zone. Adding a
variable, "findzone", and a simple substring search accomplishes this
goal:
Listing: tztest.pl

#!/usr/bin/perl
use Time::ZoneInfo ':all';
use Date::Calc ':all';
my $findzone = (shift @ARGV);
my $zones = Time::ZoneInfo->new();
foreach my $zone ($zones->zones) {
if ((index uc($zone), uc($findzone)) != -1) {
$ENV{TZ} = $zone;
($year,$month,$day,$hour,$min,$sec, $doy,$dow,$dst) =
System_Clock();
print $zone.":";
print $year."-".$month."-".$day." ";
print $hour.":".$min.":".$sec."\n";
}
}
Note the use of the IF statement. We uppercase both the zone and the
findzone variables to help ensure a match (if "asia" is entered, it will

still match all "Asia" entries). For example, running the script with this
entry:
./tztest.pl pacific
displays the following:
Pacific/Easter: 2002-12-29 23:40:20
Pacific/Galapagos: 2002-12-29 22:40:20
Pacific/Yap: 2002-12-30 14:40:20
Pacific/Truk: 2002-12-30 14:40:20
Pacific/Ponape: 2002-12-30 15:40:20
Pacific/Kosrae: 2002-12-30 15:40:20
Pacific/Tarawa: 2002-12-30 16:40:20
Pacific/Enderbury: 2002-12-30 17:40:20
Pacific/Kiritimati: 2002-12-30 18:40:20
Pacific/Majuro: 2002-12-30 16:40:20
Pacific/Kwajalein: 2002-12-30 16:40:20
Pacific/Auckland: 2002-12-30 17:40:20
Pacific/Chatham: 2002-12-30 18:25:20
Pacific/Tahiti: 2002-12-29 18:40:20
Pacific/Marquesas: 2002-12-29 19:10:20
Pacific/Gambier: 2002-12-29 19:40:20
Pacific/Johnston: 2002-12-29 18:40:20
Pacific/Midway: 2002-12-29 17:40:20
Pacific/Wake: 2002-12-30 16:40:20
Pacific/Honolulu: 2002-12-29 18:40:20
Suppose that the user doesn't know what time zone he/she is looking
for. For example, Pacific/Honolulu covers Hawaii, but isn't necessarily
intuitive to someone looking for "Hawaii." To help the user, we will add
"ALL" as a valid search that returns all zones. To do so, we doctor the
IF statement accordingly:
if (((index uc($zone), uc($findzone)) != -1) ||
("uc($findzone)" eq "ALL" )) {

Now, if the user enters "ALL" for the search, he/she will get all known
zones.

Creating WML Cards


Using methods described in previous articles, it's relatively easy to
output compliant WML for mobile devices. We will need another Perl
module, CGI, for a couple of purposes:
Parsing arguments passed to the script
Supplying the mobile browser with an appropriate WML header
Note: After my calendar gadget article was posted, a reader wrote to
chastise me for writing my own argument-parsing routine. As I stated
in the article, the fact that the script was passing itself cleanly
formatted parameters was justification for simple argument parsing.
However, one point I missed (brought up by the reader) was that the
code might go on to be incorporated in other scripts where the sterility
of the parameters could not be guaranteed. To help promote solid
coding, I've pledged to use the CGI method "param" from now on.
To output a WML card, we use the following code:
# Pass WML header
print header(-type=>'text/vnd.wap.wml');
# Print WML header and beginning tags
print <<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
This sets up the beginnings of a generic card. Subsequent PRINT
statements can supply <do> and/or <p> tags accordingly. Let's close
up the card:

# Print closing tags


print <<ENDFOOTER;
</p>
</card>
</wml>
ENDFOOTER
Now all that's left is more Perl logic to control the flow and the
resulting output.

The Final Script


Here's a listing of our final script:
Listing: timezone.pl
#!/usr/bin/perl
use Time::ZoneInfo ':all';
use Date::Calc ':all';
use CGI qw(:standard);
my $findzone = param('findzone');
# Pass WML header
print header(-type=>'text/vnd.wap.wml');
# Print WML header and beginning tags
print <<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
# Do we have a time zone to look up?

if ( "$findzone" eq "" ) {
# No time zone specified; display input card
print <<INPUTCARD
<do type="accept">
<go href="timezone.pl?findzone=\$findzone"/>
</do>
<p>
Enter zone text to search<br/>
for or ALL for all zones:
<input name="findzone" maxlength="40" format="*A"/>
INPUTCARD
} else {
$findzone =~ tr/ /_/;
$findzone =~ tr/+/_/;
# Set up zones
my $zones = Time::ZoneInfo->new();
my $matches = 0;
print "<p>\n";
# Run through zones, looking for match
foreach my $zone ($zones->zones) {

if (((index uc($zone), uc($findzone)) != -1) ||


( "uc($findzone)" eq "ALL" )) {
# Display each match, or all if "ALL" was entered
print $zone." :<br/>";
$ENV{TZ} = $zone;
($year,$month,$day, $hour,$min,$sec,
$doy,$dow,$dst) = System_Clock();
print $year."-".$month."-".$day." ";
print $hour.":".$min.":".$sec."<br/>\n";
$matches++;
}

# If no time zone matches, tell user


if ( $matches eq 0 ) {
print "No time zone match for:<br/> \n";
print $findzone."<br/><br/>";
}
}
# Print closing tags
print <<ENDFOOTER;
</p>
</card>
</wml>
ENDFOOTER
# End of script
The most notable addition is the IF statement to control which card is
displayed. When the script is first executed (without a parameter), the
input card is displayed to allow the user to input search text. The script
is then called again with the name/value pair "findzone" and the zones
are searched for a match.
We've also added logic to tell whether any results were returned. If no
results were displayed, the code tells the user ("No time zone match
for. . . ").
Note: Pay particular attention to the escaped dollar sign ($) in the
<go> tag. Without the escape backslash (\), Perl would interpret
"$findzone" as one of its local variables. Since the Perl variable
"$findzone" is empty when the script is first called, the resulting <go>
tag would incorrectly be sent to the browser as follows:
<go href="timezone.pl?findzone="/>

Room for Improvement

As with previous projects, several things could be added to improve


our world time clock:
A better format for the time output. Right now values under 10 are
output as a single digit. For example, nine o'clock AM is displayed
as: "9:0." Using a format mask or some simple logic we could pad
the time accordingly (e.g., "09:00").
Break long listings into multiple pages/cards. For example, the "ALL"
timezone listing ends up weighing in at just under 5K. That's five
times the suggested 1K card data limit. Using some simple logic,
the Perl script could display the data in a sequence of cards, each
containing 7-12 records.
Add a control on the results card(s) to return to the search card.

Find/create a more comprehensive time zone database that


includes named zones such as EST, CST, etc. (A comprehensive
database can be compiled by downloading the source files found at
ftp://elsie.nci.nih.gov/pub.)

Building WML Gadgets: Phone Message


Application
This series of articles describes how to provide Web content to mobile
devices through WML (Wireless Markup Language). This article covers
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. 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.

Simple Applications
Not all wireless applications have to be super-applications. Some of
the best wireless applications perform simple tasks to improve wireless
functionality. The last few articles in this series have shown how
simple, single-purpose gadgets can boost the functionality of mobile
devices. This article 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 message
application. Although we live in a time of portable phones, intelligent
voicemail, and other electronic telephone magic, there are still times
when messages 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 and leave messages with his
secretary. Using a simple Web form, the secretary can pass the
message to the businessman's cell phone, where he can review the
message 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 the following
diagram:

FIGURE 1 - Our application's design. The operator uses a Web form to


send the data to a CGI script that stores the data in a database. The
same script is used by a mobile user (recipient) to access that data.
We'll use Perl for the CGI script, for the same reasons we've used it
previously: It's available for most platforms and extensible enough to
perform almost any task necessary.
Note: As with previous articles, teaching Perl is out of the scope of this
series. There are numerous sources on the Internet for learning Perl,
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 for each.

Database Design
Our "database" will be a simple delimited flat file. Although we could
go the fancy route with an actual database format, the delimited

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 could use a more
standard delimiter, such as a comma, but we need something that
wouldn't end up in the middle of the message field. In short, our
database records will resemble the following:
<data and time>||<caller's name>||<caller's message>||<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." This hidden 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, performing the
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, for maximum
portability, we'll use a simple HTML file. With this method, our form
can easily be included in almost any Web page template, simply by
applying a style sheet or by cutting-and-pasting the "guts" of the form
into another page.
Saving the Data
The following code fragment saves the data entered into the form:
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 be utilizing the
script (and hence, the database) simultaneously, we must use file
lockingto avoid having two processes accessing the database and
corrupting our data. We'll use a simple method: creating a file to lock
the database_if the file exists, the process waits for it to be deleted
before accessing the database. When a process is done with the
database, the lock file is deleted and other processes are allowed to
access the database. Instead of failing right away if the file is locked,
or waiting forever for the file to be unlocked, the code loops 10 times,
waiting a tenth of a second between the iterations. If the file is still
locked, we assume that something has gone wrong and exit with an
appropriate error message.
Windows users: Some implementations of Perl on the Windows
platform don't support the four-argument call of "select" used in our
file-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 five records on
the mobile device, along with an option to move to the next five

records:
Listing: phonemsg.pl - List records fragment
#Pass WML header
print header(-type=>'text/vnd.wap.wml');
# Print WML header and beginning tags
print <<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 card
print <<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 last quarter of the
code. After the stage has been set (WML card defined), the database is
read into an array and then the correct set of records is displayed, five
records at a time. Initially, the first five records are displayed 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" set to 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
script section for details on the format and use of the $cmd variable.)
The file-locking routine for the database differs only in the message
that's displayed if a lock cannot be achieved. The message
incorporates enough WML code to complete the card, ensuring that the
user gets the 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 of records, the
script needs to be able to display the record's details. The following
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 file
print <<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" options
print <<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, the database is
locked, the database is read into an array, and then the database is
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 the displayed record:
"Call" and "Call & Del(ete)". The first simply uses the "call this
number" URL format to make the mobile device dial a number. The
latter option allows the user to delete the message before placing the
call, since the message is being returned and should no longer 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 a URL
containing the number to call (see the previous section). Deleting a
record before 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 file


print <<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 number and the
record number to delete. The number could be retrieved from the
record before the deletion, but is passed separately so it doesn't have
to be parsed from the record. This design decision falls into the "halfa-dozen versus six" 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 an array and then
reconstruct the file by writing all records except the deleted record
back to the file.
The Completed Script
After adding a few declaration lines, the controlling structure using the
$cmd variable, and some connecting tissue, our completed script
becomes the following:
Listing: phonemsg.pl
#!/usr/bin/perl
# Include modules
use 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 form
if ("$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 calls
if ("$cmd" eq "list") {
#Pass WML header
print header(-type=>'text/vnd.wap.wml');
# Print WML header and beginning tags
print <<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 card
print <<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 detail
if ("$cmd" eq "display") {
# What record to display
$rec = param('rec');
#Pass WML header
print header(-type=>'text/vnd.wap.wml');
# Print beginning of WML file
print <<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" options
print <<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 record
if ("$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 file
print <<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 the records. This
allows the mobile device to call the script without arguments
("http://URL/phonemsg.pl") to get the ball rolling. Subsequent calls
are handled by the script ("display," "callNdel," etc.) where it controls
the parameters, saving the mobile user from having to
enter/bookmark them.
To test the application, we seed the database with the following data:
Listing: msgfile.txt - Sample data
1/25/2003
555-1212
1/25/2003
555-1212
1/25/2003
555-1212
1/25/2003
555-1212
1/26/2003
555-1212
1/26/2003
555-1212
1/26/2003
555-1212
1/27/2003
555-1212
1/27/2003
555-1212
1/28/2003
555-1212
1/28/2003
555-1212
1/28/2003
555-1212
1/28/2003
555-1212
1/28/2003
555-1212
1/28/2003
555-1212
1/29/2003
555-1212
1/29/2003
555-1212
1/29/2003
555-1212

12:15||Caller Number01||Sample message, from sample caller.||31712:25||Caller Number02||Sample message, from sample caller.||31712:35||Caller Number03||Sample message, from sample caller.||31713:15||Caller Number04||Sample message, from sample caller.||31714:15||Caller Number05||Sample message, from sample caller.||31714:23||Caller Number06||Sample message, from sample caller.||31715:15||Caller Number07||Sample message, from sample caller.||3179:15||Caller Number08||Sample message, from sample caller.||3179:35||Caller Number09||Sample message, from sample caller.||3178:05||Caller Number10||Sample message, from sample caller.||31710:15||Caller Number11||Sample message, from sample caller.||31711:11||Caller Number12||Sample message, from sample caller.||31712:01||Caller Number13||Sample message, from sample caller.||31714:15||Caller Number14||Sample message, from sample caller.||31716:45||Caller Number15||Sample message, from sample caller.||3178:25||Caller Number16||Sample message, from sample caller.||3179:04||Caller Number17||Sample message, from sample caller.||31710:35||Caller Number18||Sample message, from sample caller.||317-

1/29/2003
555-1212
1/30/2003
555-1212
1/30/2003
555-1212
1/30/2003
555-1212

10:39||Caller Number19||Sample message, from sample caller.||31712:15||Caller Number20||Sample message, from sample caller.||31715:02||Caller Number21||Sample message, from sample caller.||31716:05||Caller Number21||Sample message, from sample caller.||317-

Using this data, our application resembles the following graphics 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 the user 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 be_in 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.

Tracking Users Using WML


This article describes how to provide Web content to mobile devices
through WML (Wireless Markup Language). More specifically, this
article covers how to track users; that is, how to recognize a repeat

visitor to your site.


WML and WMLScript version 1.1 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.

The Value of Recognizing Users


There are a variety of reasons to implement a recognition system that
acknowledges that a user that has previously visited your site. The
most useful of these reasons is to remember user preferences.
For example, suppose that you offer a service of finding particular
restaurants near a user. Each user may prefer a certain type of food,
environment, etc., and knowing where the user is located is important
so that you can find restaurants in that vicinity. Of course, users don't
want to input all their preferences every time they visit your siteit's
better to save most of the settings, recognize users when they return,
and recall their settings.
How To Recognize Users
When WML was first implemented, the code could retrieve the user's
cell phone number from the device; this phone number could act as a
unique identifier. Unfortunately, this ability was recognized as an
invasion of privacy, and the feature was discontinued. Now there is no
way to retrieve a unique identifier from the user's device.
What options are left? You could have the user input his or her
telephone number on each visit, assign the visitor a login name, or
have the user enter an identifier during each visit. Better yet, why not
store the identifier on the user's device for recall each time he or she
visits your site?
Cookies: The Good, the Bad, and the Ugly
The term cookie refers to the HTTP technology that allows a site to
store data on a user's machine. When cookies were first used, they
were fairly innocuous, intended primarily for storing user preferences.

However, it didn't take long for the business side of the Web to realize
the potential of cookies and begin using them for their own purposes
tracking user activities, shopping habits, and other bits of personal
information.
Shortly thereafter, the media and user advocates led the charge
against cookies, causing most modern browsers to offer the option of
refusing to store any cookies, or allowing the user to choose when a
cookie should or should not be stored. Unfortunately, refusing cookies
causes problems for Web sites that use cookies to store user
preferences and login info, requiring the user to reenter such info on
each visit.
Note: I don't condone the illicit use of cookies for tracking users'
personal information, but do recognize the utility of storing frequently
used information to aid the user experience.
How Cookies Work
Cookies work by storing data on the user's local computer/device. This
data is stored via an HTTP dialog between the server and the client.
That data can then be recalled by the server, processed, updated, etc.
Figure 1 shows the data paths associated with storing and retrieving
cookies.

Figure 1 - Cookie data is passed back and forth between the


server and client via the HTTP stream, but the data is actually

stored on the client side.


The information stored in the cookie can be just about any type of
data: string, date, an integer, or a real number. Most sites choose to
encode cookie data into a lengthy string that can be decoded and
parsed by the site code.
Note: Windows users can examine the cookies that have been stored
on the local machine. Look in your local settings directory(ies) for
"cookies" files. Windows XP users can find the cookies in the following
directory:
C:\Documents and Settings\{Username}\Cookies

In addition to data, cookies are stored with a time to live (TTL),


specifying how long the cookie should be stored before being
discarded. The cookie also indicates the scope for which it should be
usedthat is, what directory(ies) on the server are valid for that
cookie. This allows a site to store multiple cookies with the same
name, but different scopes. For example, a site with several sections
could store preferences for each section in a cookie named "prefs."

Using Cookies with WML


Because WML doesn't have any built-in cookie functions, you have to
use other technologies to store and retrieve cookies. This article shows
how to use Perl, which has robust cookie-handling abilities.
We'll use the HTTP header Set-Cookie to set cookies in the examples.
Although WML has a <head> tag, don't confuse the HTTP header with
the WML card headthey're different animals. Cookies must be set
before the end of the HTTP header; the WML <head> comes after the
HTTP header has been sent, and therefore it cannot be used to set
cookies.
You can use almost any language that supports cookie functions to
accomplish the goals in this article. For example, PHP's header()
function can be used to set cookies. Even more control can be
accomplished with PHP's setcookie() function. Several variable
structures exist in PHP to read cookies, including
$HTTP_COOKIE_VARS and $_COOKIE arrays.

Handling Cookies with Perl


There are many options for handling cookies in Perl, including simple
HTTP methods and even dedicated Perl libraries such as cookie-lib.pl.
We'll use the simple HTTP methods for this article.
For more robust cookie management, the reader is encouraged to
check out other cookie-handling methods, such as cookie-lib.pl and
HTTP::Cookies. The former is available online at The CGI Resource
Index
(http://cgi.resourceindex.com/Programs_and_Scripts/Perl/Cookies/);
the latter is from CPAN (http://search.cpan.org/author/RSE/lcwa1.0.0/lib/lwp/lib/HTTP/Cookies.pm).
Setting a Cookie with Perl

As discussed earlier, we can use the HTTP header Set-Cookie to set


cookies. In its simplest usage, this header takes the following form:
Set-Cookie: <name of cookie>=<value of cookie>

For example, a real header might be as follows:


Set-Cookie: name=Steve

When this header is passed to a browser, it sets the cookie "name"


equal to "Steve." Because the header doesn't include a time to live
(TTL), the cookie is only valid for the current session. When the
browser is closed, the cookie expires and is deleted.
To include a TTL, you add the parameter expires as shown in the
following example:
Set-Cookie: name=Steve; expires=Monday, 24-Mar-03 23:59:59 GMT

Notice the use of a semicolon (;) to delimit the parameters. The date
is in the format "weekday, dd/Mon/yy hh:mm:ss." In the example
above, the cookie will expire at one second before midnight on
Monday, March 24, 2003, Greenwich Mean Time.
Tip: It's important to include at least one blank line after the SetCookie header and before the WML headers so the client correctly
identifies the WML headers.

Let's look at a real example of using Perl to set a cookie for a WML
deck. The following code snippet shows how Perl is used to set a
cookie and output a status ("Cookie set") message:
#!/usr/bin/perl
# Define minimal deck
$deck = '
<wml>
<card>
<p>
Cookie set.
</p>
</card>
</wml>';
# Send Content-type and Set-Cookie headers
print "Content-type: text/vnd.wap.wml \n";
print "Set-Cookie: name=Steve \n";
# Send WML header info
print "\n<?xml version=\"1.0\"?>\n";
print "<!DOCTYPE wml PUBLIC \"-//WAPFORUM//DTD WML 1.1//EN\""
. " \"http://www.wapforum.org/DTD/wml_1.1.xml\">\n";
# Send deck
print $deck;

The cookie in the code above was set without a TTL. To set an
expiration date, we draw on the Date::Calc module to do our date
calculations:
#!/usr/bin/perl
# Include Date::Calc.
use Date::Calc':all';
# Get today in GMT
($year,$month,$day) = Today([$gmt]);
# Add a year (365 days)
($year,$month,$day) =
Add_Delta_Days($year,$month,$day,"365");
# Get textual representations of month and day of week
$dow = Day_of_Week_to_Text(Day_of_Week($year,$month,$day));
$month = Month_to_Text($month);
# Make sure day is two digits
if ($day<10){
$day = '0'.$day;

}
# Assemble expiration date
$date = $dow.", ".$day."-".$month."-".$year." 23:59:59 GMT";
# Define deck
$deck = '
<wml>
<card>
<p>
Cookie set.
</p>
</card>
</wml>';
# Send Content-type and Set-Cookie headers
print "Content-type: text/vnd.wap.wml \n";
print "Set-Cookie: name=Steve; expires=$date; \n";
# Send WML headers
print "\n<?xml version=\"1.0\"?>\n";
print "<!DOCTYPE wml PUBLIC \"-//WAPFORUM//DTD WML 1.1//EN\""
. " \"http://www.wapforum.org/DTD/wml_1.1.xml\">\n";
# Send the deck
print $deck;

Note: The Date::Calc module has been covered in several previous


articles. The module is available from CPAN, at
http://search.cpan.org/author/STBEY/Date-Calc-5.3/Calc.pod.
Reading Cookies with Perl

Reading cookies with Perl is even easier than setting them, thanks to
the environment variable HTTP_COOKIE. This variable contains
name/value pairs for all applicable cookies (those matching the current
scope).
To parse the name/value list, you could use the following code:
@nvpairs=split(/[,;] */, $ENV{'HTTP_COOKIE'});
foreach $pair (@nvpairs) {
($name, $value) = split(/=/, $pair);
$cookie{$name} = $value;
}

This code effectively parses the cookie list into the array $cookie,
where each value can be accessed by its name. For example, our
earlier "name" example would yield:
$cookie{'name'} = "Steve"

An extended example, displaying the cookie value in WML, is shown


below:
#!/usr/bin/perl
# Break cookies into name/value pairs
# and store into cookie array
@nvpairs=split(/[,;] */, $ENV{'HTTP_COOKIE'});
foreach $pair (@nvpairs) {
($name, $value) = split(/=/, $pair);
$cookie{$name} = $value;
}
# Define WML deck
$deck = '
<wml>
<card>
<p>
Cookie (name) = '
.$cookie{'name'}.'
</p>
</card>
</wml>';
# Send Content-type and Set-Cookie headers
print "Content-type: text/vnd.wap.wml \n";
print "Set-Cookie: name=Steve; expires=$date; \n";
# Send WML headers
print "\n<?xml version=\"1.0\"?>\n";
print "<!DOCTYPE wml PUBLIC \"-//WAPFORUM//DTD WML 1.1//EN\""
. " \"http://www.wapforum.org/DTD/wml_1.1.xml\">\n";
# Send the deck
print $deck;

If the cookie was still set, this code would display the following:
Cookie (name) = Steve
Deleting Cookies

To remove a cookie, you simply set the expiration of the cookie to a

date/time that has already passed. For example, we could use the
code earlier but subtract a day or two to expire the cookie. The code,
using Date::Calc functions, would resemble the following:
# Get today in GMT
($year,$month,$day) = Today([$gmt]);
# Subtract a year (365 days)
($year,$month,$day) =
Add_Delta_Days($year,$month,$day,"-365");
# Get textual representations of month and day of week
$dow = Day_of_Week_to_Text(Day_of_Week($year,$month,$day));
$month = Month_to_Text($month);
# Make sure day is two digits
if ($day<10){
$day = '0'.$day;
}
# Assemble expiration date
$date = $dow.", ".$day."-".$month."-".$year." 23:59:59 GMT";

Note the use of a minus sign in the Add_Delta_Days function to


subtract a year from today. We could just as easily subtract only one
day.

Tying It All Together


Now that we can set and retrieve cookies, what exactly do we do with
them? As stated earlier, we could store a variety of information in
cookies for use in helping the user interface when the visitor returns to
our site.
In the earlier restaurant example, for instance, you could utilize the
following cookies:
Name:
User's name
Phone-Number:
Device phone number
Zipcode:
Target ZIP code for restaurant matches
Genre:
User's favorite type of food
_
...

Alternatively, you could encode all this data into one lengthy string.
My advice, however, would be to tie one unique piece of data that
identifies the user to a database record. Basically, store only what you
need on the client device, and store the rest of the data in a database
accessible by the server. The flowchart for an application using this
method might resemble Figure 2.

Click here for larger image

Figure 2 - Flowchart of application using described method


On entering the site, the user's browser is checked for an ID cookie. If
the cookie is found, the user's preferences are retrieved from the

server's database and the site is displayed with those preferences. If


the cookie is not set, the user is taken to a preference form and
queried for his/her preferences. Those preferences are stored in the
server's database and the user's ID is stored as a cookie.
http://www.developer.com/lang/php/

You might also like