Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
<strong>Learning</strong> <strong>WML</strong> - <strong>WAP</strong> <strong>Basics</strong><br />
By Steve Schafer<br />
This series of articles describes how to provide Web content to mobile<br />
devices through <strong>WML</strong> (Wireless Markup Language). This first article<br />
covers the background of <strong>WAP</strong>, how it works, and what you need to<br />
get started. Future articles will introduce the <strong>WML</strong> language, tips and<br />
tricks to provide content, and how to integrate other technologies such<br />
as PHP to make your pages more flexible.<br />
What Is <strong>WAP</strong>?<br />
<strong>WAP</strong> stands for Wireless Access Protocol, a general term used to<br />
describe the multi-layered protocol and related technologies that bring<br />
Internet content to mobile devices such as PDAs and cell phones.<br />
Such devices are referred to as thin clients because they have one or<br />
more constraints in the form of display, input, memory, CPU, or other<br />
hardware or usability limitations. The platform constraints and the<br />
slower (and more expensive) bandwidth of cellular and related<br />
networks make standard Internet protocols difficult to utilize. Using<br />
the growing set of <strong>WAP</strong> tools and protocols, however, the mobile<br />
Internet is quite a capable tool.<br />
A Brief History of <strong>WAP</strong><br />
As previously stated, <strong>WAP</strong> refers to a wide range of technologies and<br />
protocols, all related to mobile Internet functionality. This functionality<br />
has roots dating back to the mid 1990s. At that time, several vendors<br />
were working on the mobile Internet problem as mobile device sales<br />
skyrocketed, and several competing technologies emerged:<br />
• Nokia's Narrow Band Sockets (NBS) and Tagged Text Markup<br />
Language (TTML)<br />
• Ericsson's Intelligent Terminal Transfer Protocol (ITTP)<br />
• Unwired Planet's Handheld Device Markup Language (HDML)<br />
Each technology had its own purpose, but some overlapped with<br />
others in various areas. This diversity threatened to fragment the<br />
wireless industry along provider lines. In mid 1997, the <strong>WAP</strong> Forum<br />
was founded to aid in communication among the developers and to
spur a common set of protocols and technologies. In the same year,<br />
the industry took another step forward with the formation of the Open<br />
Mobile Alliance (OMA), which combined several distinct development<br />
and standards bodies into one.<br />
How Does <strong>WAP</strong> Work?<br />
These articles will focus on the delivery of <strong>WML</strong> content to mobile<br />
devices over a cellular or related technology network. However, the<br />
delivery of many protocols and technologies takes the same routenamely,<br />
through a proxy server that bridges the gap between the<br />
wired Internet and the wireless service provider's network.<br />
Figure 1.1 The <strong>WAP</strong> Gateway provides wireless networks with<br />
Internet access and optional content translation and filtering.<br />
This proxy server manages the communication between the wireless<br />
client and the Internet server(s), acting as a gateway to the wired<br />
Internet. It caches content and in some cases even translates raw<br />
HTML into <strong>WAP</strong>-compatible protocols such as <strong>WML</strong>.<br />
Many mobile devices have a built-in wireless browser. Although several<br />
different browsers are in use today among the various wireless<br />
providers, most browsers support <strong>WML</strong>, either natively or translated<br />
into HDML. A popular precursor to <strong>WML</strong>, the Handheld Device Markup<br />
Language (HDML), is still supported on several mobile platforms.<br />
However, due to the limitations of HDML (supporting only a handful of<br />
navigation tags and virtually no formatting tags), <strong>WML</strong> is becoming the<br />
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<br />
that particular device.<br />
Note: When coding for the general public, be careful to stick to the<br />
standards and avoid using proprietary extensions to the various<br />
languages, no matter how tempting the feature set of the extensions.<br />
If you decide to provide the extensions to those who can use them,<br />
you should take the necessary server steps to identify the connecting<br />
browser and deliver code customized for that browser.<br />
What Is <strong>WML</strong>?<br />
<strong>WML</strong> (Wireless Markup Language) is the dominant language in use<br />
with wireless devices today. Essentially, <strong>WML</strong> is a subset of HTML, but<br />
has its roots in XML. Those developers with a solid base in XML should<br />
have a relatively easy time coding <strong>WML</strong>.<br />
The current <strong>WML</strong> standard is 1.3, although many mobile devices in use<br />
today support only the <strong>WML</strong> 1.1 standard. Therefore it's prudent to<br />
stay away from 1.3-specific features, unless you know that your target<br />
market's devices are 1.3-ready.<br />
There are several key differences between <strong>WML</strong> and standard HTML,<br />
including the following:<br />
• <strong>WML</strong> is highly structured and very particular about syntax. Several<br />
current HTML browsers allow for "messy" code such as missing tags<br />
and other formatting snafus. Such mistakes are not allowed in <strong>WML</strong>;<br />
the mobile browser will complain and generally won't display the<br />
page.<br />
• <strong>WML</strong> is case sensitive. The tags and are treated as<br />
different tags, although they accomplish the same purpose (bold<br />
text). Therefore, you must be careful to match the case of your<br />
opening tags with your closing tags (for example, This is<br />
bold will not work as expected).<br />
• Many tags have required attributes. Developers accustomed to HTML<br />
may be used to including only attributes they need-in some <strong>WML</strong><br />
tags, you must include a few attributes, even if they are blank or<br />
default.
• <strong>WML</strong> pages are structured in "decks" (see the next section), allowing<br />
for multiple pages to be defined in each <strong>WML</strong> file.<br />
<strong>WML</strong> also has a client-side scripting language, <strong>WML</strong>Script, to help<br />
automate particular tasks, validate input, and so on. <strong>WML</strong>Script is a<br />
subset of JavaScript and will be covered in a later article.<br />
Understanding Decks<br />
<strong>WML</strong> pages are structured within "decks," allowing several pages<br />
("cards") to be defined in each <strong>WML</strong> file. This deck analogy allows<br />
multiple pages to be delivered to the mobile client at the same time,<br />
minimizing the loading time between related pages. However, the<br />
limited memory on most devices constrains the deck size, usually to<br />
less than 1024 bytes. Therefore, careful consideration and planning<br />
should go into any <strong>WAP</strong> application; don't start coding without<br />
investing time in planning.<br />
Note: Remember your audience. Mobile users generally scroll through<br />
cards rapidly and will be reading on a display that's a mere handful of<br />
characters wide (usually less than 20 characters) and usually less than<br />
10 lines high. Keep your content to a minimum, provide an intuitive<br />
navigation structure, and optimize your decks to maximize links within<br />
the deck and minimize links outside of the deck.<br />
Visualizing a physical "deck of cards" structure can help in<br />
understanding the principles of <strong>WML</strong>. For example, suppose we have<br />
three simple cards (pages) as shown below:
Figure 1.2 - The physical card analogy to <strong>WML</strong> decks helps<br />
visualize how they work.<br />
These cards together form a deck and are delivered to the mobile<br />
device in one file. Now suppose that each card links to the next (card<br />
one links to card two, which links to card three, and so on), and that<br />
each card also has a "back" link to take the user back to the previous<br />
card. As the user navigates the deck, the cards stack in memory as<br />
shown below:<br />
Figure 1.3 - As the user follows the links through the deck, the<br />
cards stack up in memory.<br />
A developer accustomed to HTML might be tempted to implement the<br />
"back" feature by providing a link to the deck, specifying the previous<br />
card. However, this would cause the mobile device to re-request the
whole deck before redisplaying the card-a card it already had in<br />
memory.<br />
Instead, you should use the tag, which tells the browser to remove<br />
the current page and display the previous page in the history list (like<br />
using the Back button on a PC browser). Of course, the content of the<br />
previous page might need to be refreshed each time it's accessed; in<br />
that case, valid techniques could include recalling the whole deck or<br />
specifying that the page not be cached. Proper navigation will be<br />
covered in future articles.<br />
Figure 1.4 - The tag "pops" the top card off the stack<br />
(out of the history list), redisplaying the previous card in the<br />
history.<br />
Setting Up Your Server for <strong>WML</strong><br />
To configure your Web server to deliver <strong>WML</strong>, you must define the<br />
related MIME types for <strong>WML</strong> content. Web servers and client browsers<br />
use MIME (Multipurpose Internet Mail Extensions) to communicate the<br />
type of data that is being sent. Before sending data, the server sends<br />
a MIME identifier to the client browser, identifying the format of the<br />
following data. The client browser can then properly decode and apply<br />
the data. Most <strong>WML</strong> applications require three MIME types, as listed in<br />
the following table.<br />
File Extension MIME Type Definition Use<br />
.wml text/vnd.wap.wml <strong>WML</strong> source file<br />
.wmls<br />
text/vnd.wap.wmlscript <strong>WML</strong> script file<br />
.wbmp image/vnd.wap.wbmp Wireless bitmap file (image)
To add MIME types to your Web server, you must have administrator<br />
access to the server. The following sections cover setting up<br />
Microsoft's Internet Information Server (IIS) and Apache for <strong>WML</strong>. If<br />
you're using another type of server, read your server's documentation<br />
for more information on adding MIME types.<br />
Adding MIME Types to Internet Information Server (IIS)<br />
To add the MIME types to IIS, open the Internet Information Services<br />
Management Console (MC). Access to this console varies depending on<br />
which specific operating system you're using and how you installed<br />
IIS, but can usually be found under Administrative Tools (Windows<br />
2000) or Option Pack (Windows NT).<br />
Open the IIS MC, click on the server to expand its tree, and then rightclick<br />
on Default Web Site and choose Properties. (Note: If you don't<br />
want all the sites on your server to be able to deliver <strong>WML</strong>, right-click<br />
on those sites you want to be <strong>WML</strong>-enabled and then continue<br />
following these steps.)<br />
Click on the HTTP Headers tab and then click on the File Types button<br />
under the MIME Map section. In the File Types dialog, click on New<br />
Type and enter the extension and MIME definition ("Content Type")<br />
from the preceding table. Click on OK. Repeat this process for the<br />
other two MIME types.<br />
When you're finished, close the Web Site Properties dialog by clicking<br />
on OK. On some servers, there may be nodes or devices that also<br />
define HTTP codes and need to inherit the new setting(s). Choose the<br />
appropriate options for your system. Exit the IIS MC. Usually you<br />
won't need to restart the IIS service, but it wouldn't hurt to do so just<br />
in case.<br />
Tip: Before exiting the Web Site Properties, you may want to add an<br />
entry for <strong>WML</strong> on the Documents tab (such as index.wml). This causes<br />
the server to display that document by default, eliminating the need<br />
for your visitors to specify a particular file in the URL to access your<br />
site.<br />
Adding MIME Types to Apache
To add MIME types to Apache, you must edit the httpd.conf file. This<br />
file's location varies from system to system.<br />
This file uses "AddType" lines to define MIME types. Find the section<br />
where these appear and add the following lines:<br />
AddType text/vnd.wap.wml .wml<br />
AddType text/vnd.wap.wmlscript .wmls<br />
AddType image/vnd.wap.wbmp .wbmp<br />
Save and close the file and restart the Apache server to reload the<br />
configuration with the new MIME types.<br />
Tip: You may want to add "index.wml" or comparable entry to the<br />
DirectoryIndex section of the Apache configuration file (requires<br />
running mod_dir). This causes the server to display that document by<br />
default, eliminating the need for your visitors to specify a particular file<br />
in the URL to access your site.<br />
A Sample <strong>WML</strong> Deck<br />
Now that your server is set up to handle <strong>WML</strong> correctly, let's try<br />
serving up a sample page. The following listing shows the bare<br />
minimum coding necessary to contain a <strong>WML</strong> deck, consisting of a<br />
single, blank card:<br />
<br />
<br />
<br />
<br />
<br />
<br />
The first line of the preceding code specifies that the file is XML and<br />
version 1.0-compatible. The second line defines the XML scope of the<br />
file; namely, that its DOCTYPE is <strong>WML</strong>, and where the Document Type<br />
Definition (DTD) can be found.<br />
Tip: If you're unfamiliar with XML and don't fully understand these<br />
lines, just make sure that they appear at the beginning of all your
<strong>WML</strong> documents.<br />
The next line begins the <strong>WML</strong> definition with the tag.<br />
The tag defines a card in the deck. Note that the id and title<br />
attributes can be anything you choose, but should be short and to the<br />
point, and the id must contain only letters and numbers (no<br />
punctuation or spaces).<br />
If you want to try the preceding example, create the file on your Web<br />
server (in plain text form), adding the following lines between the<br />
tags to provide content for the card:<br />
<br />
A Sample Card<br />
<br />
Place the file in an accessible directory with adequate permissions to<br />
access it from an external browser. Now visit the Wapalizer at<br />
http://www.gelon.net. Type the URL to the file in the Wapalizer box<br />
and click on the Wapalize button. You should see a screen similar to<br />
the output below.<br />
Figure 1.5 - Your sample page in the Openwave simulator.<br />
Image courtesy Openwave Systems Inc. (Openwave, the Openwave<br />
logo, Openwave SDK, Openwave SDK Universal Edition, Openwave
SDK <strong>WAP</strong> Edition are trademarks of Openwave Systems Inc. All rights<br />
reserved.)<br />
<strong>Learning</strong> <strong>WML</strong> - Tools and Structure<br />
This series of articles describes how to provide Web content to mobile<br />
devices through <strong>WML</strong> (Wireless Markup Language). This article covers<br />
some essential tools for coding and debugging <strong>WML</strong> code, as well as<br />
the basics of creating <strong>WML</strong> decks. Future articles will cover advanced<br />
<strong>WML</strong> language, tips and tricks to provide content, and how to integrate<br />
other technologies such as PHP to make your pages more flexible.<br />
Note: These articles cover <strong>WML</strong> version 1.1, which is supported by the<br />
majority of mobile devices in use today. The articles assume a working<br />
knowledge of HTML and general Web technologies, and further assume<br />
that you have read the previous article(s) in this series.<br />
Essential Documentation and Tools<br />
There are several tools you should assemble before beginning to code<br />
your <strong>WML</strong> applications.<br />
Note: The recommendations in this article are simply that:<br />
recommendations. You can make do with a simple text editor (such as<br />
Windows Notepad or vi in Linux) and the Web server, but spending<br />
some time assembling some good tools will pay off in the long run.<br />
Tip: If you decide not to use one of the <strong>WAP</strong> SDKs/IDEs and use<br />
Windows as your OS, try TextPad, a nifty text editor that's very<br />
versatile and code friendly (http://www.textpad.com).<br />
Documentation<br />
There is an abundance of <strong>WAP</strong>/<strong>WML</strong> documentation on the Web,<br />
mostly courtesy of the Open Mobile Alliance (OMA) and other <strong>WAP</strong><br />
organizations. You should spend some time reading the user interface<br />
(UI) suggestions, developer guidelines, and language reference text at<br />
the sites listed in the following sections.
Open Mobile Alliance - http://www.openmobilealliance.org<br />
The Open Mobile Alliance (OMA) is a relatively new standards<br />
organization made up of several key players in the <strong>WAP</strong> arena,<br />
including most cellular providers and phone manufacturers - visit<br />
http://www.openmobilealliance.org/members.html for a full list. The<br />
new site is still evolving, but much of the content from the previous<br />
site (the popular "<strong>WAP</strong> Forum") is still available. As of this writing, you<br />
can access the documentation by using the "Access to <strong>WAP</strong> specific<br />
information" link at the bottom of the main OMA page. A full list of<br />
<strong>WML</strong> release specifications, <strong>WAP</strong> specifications, and DTD definitions is<br />
available.<br />
Openwave Developer Program -<br />
http://developer.openwave.com<br />
Openwave, the company behind the popular Openwave Mobile Browser<br />
(formerly the UP.Browser) maintains a library of technical documents<br />
and developer guidelines. Follow the links on the left side of the main<br />
page to access <strong>WML</strong> language references (in the Technical Library), UI<br />
guidelines, lists of supported devices, and more.<br />
Forum Nokia - http://www.forum.nokia.com<br />
Nokia was one of the pioneers in the <strong>WAP</strong> arena and maintains an<br />
impressive number of emulators and related documentation. Their<br />
Documents section has a handy search feature to help find the<br />
document most applicable to your needs.<br />
Emulators and SDKs<br />
You could test your <strong>WML</strong> code with a cell phone, but most providers<br />
charge a premium for Web access, and testing could get rather<br />
expensive. Instead, use one of the following emulators, some of which<br />
are packaged with a full SDK_some even include a full Integrated<br />
Development Environment (IDE).<br />
Note: Most of the emulators and SDKs are only available for Windows,<br />
although a few are available for Solaris and other operating systems.
Tip: No emulator can fully replace actual device testing. Although you<br />
can initially code and debug on one of the emulators, don_t neglect<br />
testing on your actual target device(s) as well.<br />
The Gelon Wapalizer - http://www.gelon.net<br />
The Wapalizer is a Web-based <strong>WAP</strong> emulator and offers several skins<br />
to emulate popular phones. Simply type your URL into the Wapalizer<br />
box and click the Wapalize button to open your code in the default<br />
emulator. Alternatively, choose a different skin and use the emulated<br />
device's interface to navigate to your URL.<br />
Note: The Wapalizer is not as robust as some of the other emulators.<br />
Although it's a handy way to quickly check a page, you should also use<br />
an emulator that closely resembles your target microbrowser.<br />
The Openwave SDK - http://developer.openwave.com<br />
The Openwave SDK 5.1 provides the best suite of tools I've run<br />
across; I use it as my primary coding and testing environment. The<br />
Openwave developer site (http://developer.openwave.com) has<br />
several SDKs available, depending on what <strong>WML</strong> language revision and<br />
feature set you need. SDK 5.1 can be used for most applications,<br />
providing that you stay within the coding boundaries for your target<br />
device's supported version of <strong>WML</strong> and you fully test your code on the<br />
target device(s).<br />
Nokia Emulators - http://www.forum.nokia.com<br />
Nokia maintains an impressive list of emulators, including several that<br />
tie in to their Mobile Internet Toolkit. Note that you must register on<br />
the site to download any of the tools. Many are Java-based, and<br />
therefore require you to install a Java Virtual Machine (JVM) on your<br />
system to run the tool.<br />
Other Emulators<br />
If your device isn't compatible with the emulators listed above, visit<br />
the manufacturer's site for the device to see whether they offer a<br />
compatible tool.
Tip: If you need to find out what browser your target device uses,<br />
access a nonexistent file on your Web server and examine your server<br />
logs for the browser identification information. (A nonexistent file<br />
makes it easy to find the pertinent log entry.) For example, if I use my<br />
Samsung A400 phone to access "missingfile.jpg" on my Web server, I<br />
find the following log entry:<br />
[25/Jun/2002:16:27:50 -0500] "GET /wireless/missingfile.jpg<br />
HTTP/1.1" 404 306 "-" "SEC-spha400 UP.Browser/4.1.22b1<br />
UP.Link/5.0.2.3c"<br />
This entry shows the client ("spha400") and the browser and version<br />
(UP.Browser 4.1.22b, now the Openwave Mobile Browser, but still<br />
identified as UP.Browser).<br />
Basic <strong>WML</strong> Structure and Rules<br />
Now that we have our server set up and our toolkit stocked, we're<br />
almost ready to do some coding. First let's examine the basic form of<br />
<strong>WML</strong> documents and some basic coding rules.<br />
Basic <strong>WML</strong> Deck Structure<br />
The following listing shows the basic elements you need in most <strong>WML</strong><br />
decks:<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
...<br />
<br />
<br />
...<br />
<br />
...<br />
You may remember the document preamble code (first two lines) from<br />
the previous article. These lines are necessary to identify the type of<br />
content (<strong>WML</strong>) to the browser. They need to be present in all files.<br />
The deck is then started with a tag, and ended with a <br />
tag.<br />
Note that the section is optional, but is necessary to supply<br />
any meta information (via tags) required for your deck.<br />
Typically, cache instructions are passed via meta tags included in the<br />
section. Leaving the section blank by including only<br />
the tags is a bad idea; it can cause some browsers to fail.<br />
Include this section only if you need to include or <br />
tags.<br />
What follows is the actual deck, made up of individual cards, each with<br />
a mandatory ID and title parameter. (Other parameters for the<br />
and other tags will be covered in later articles.) Some<br />
browsers display the card title at the top of the display; others don't.<br />
It's important to make your card titles display-ready (short but<br />
meaningful, and so on).<br />
Tip: It may be helpful to have this information stored in a file as a<br />
template for all your <strong>WML</strong> coding. Note that some SDKs provide a<br />
skeletal <strong>WML</strong> structure when you create a new file.<br />
<strong>WML</strong> Language Rules<br />
Let's quickly review some basic <strong>WML</strong> language rules:<br />
• Most tags have opening and closing components. Those that don_t<br />
(, , , , and so on) include a slash at<br />
the end of the tag, signifying that the tag is singular (opening and<br />
closing).<br />
• The language is case sensitive, so all closing tags must match the<br />
capitalization of the opening tags. (This is bold will not<br />
have the desired results.)<br />
• All tag parameter values must be enclosed in quotes (for example,<br />
).<br />
• All text must be enclosed in a tag, even if that tag is just a simple<br />
paragraph tag ().<br />
• There are some elements that must appear in a certain order. For
example, within a element, the following must appear in order:<br />
, , .<br />
<strong>WML</strong> Text Formatting<br />
Like HTML, <strong>WML</strong> supports several types of text formatting. The<br />
following table describes the available text-formatting tags.<br />
Tag Name Use<br />
Paragraph Mark blocks of text as paragraphs.<br />
Line break Break the current line, much like pressing Enter in a word processing<br />
program.<br />
Bold Boldface the delineated text.<br />
Big Make the text appear in a large point size.<br />
Emphasized Emphasize the text (usually with italics).<br />
Italic Display the text in an italic font.<br />
Small Display the text in a small point size.<br />
Strong Display the text in a strongly emphasized font. (Usually a large point<br />
size and italic.)<br />
Underline Display the text in an underlined font.<br />
Note: Many of these text attributes (such as , and so on) are not<br />
supported by all mobile browsers.<br />
The most important text formatting tag is the paragraph tag ().<br />
This tag must appear around all text not delineated by other tags. It<br />
also controls how the browser presents the text if you add the optional<br />
mode attribute. The two supported modes are "wrap" and "nowrap",<br />
with "wrap" as the default. The wrap mode allows the text to flow<br />
down the device screen, breaking lines at appropriate spaces and<br />
punctuation. The nowrap mode uses a marquee-like display to display<br />
the text, scrolling the text horizontally when this mode is selected.<br />
For example, take the following code:<br />
<br />
<br />
<br />
<br />
<br />
No Wrap: The quick brown fox jumped over the lazy dog.<br />
<br />
<br />
Wrap: The quick brown fox jumped over the lazy dog.<br />
<br />
It results in the following display:<br />
Figure 2-1 - The mode attribute determines how the<br />
tag wraps text.<br />
Image courtesy Openwave Systems Inc. (Openwave, the Openwave<br />
logo, Openwave SDK, Openwave SDK Universal Edition, Openwave<br />
SDK <strong>WAP</strong> Edition are trademarks of Openwave Systems Inc. All rights<br />
reserved.)
Figure 2-2 - Text set to nowrap will scroll horizontally when the<br />
browser selects that line. Note how the first line (nowrap) has<br />
scrolled in this figure. The text will continue to scroll until it<br />
reaches the end of the paragraph, where it will start over.<br />
Image courtesy Openwave Systems Inc. (Openwave, the Openwave<br />
logo, Openwave SDK, Openwave SDK Universal Edition, Openwave<br />
SDK <strong>WAP</strong> Edition are trademarks of Openwave Systems Inc. All rights<br />
reserved.)<br />
If you don't use the mode attribute, the client browser uses the<br />
currently selected mode, or the default "wrap" if no mode had been<br />
defined previously. Typically the nowrap mode is used for headlines<br />
and selection text, while wrap is used for general and body text.<br />
ard Navigation<br />
Now let's consider how to navigate between cards within a deck. As an<br />
example, we'll use a riddle, having the user navigate between the<br />
question and answer.<br />
Take the following riddle:<br />
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?<br />
A: Silence.<br />
We'll put the question in one card, the answer in another, and define<br />
links to move between the two. For the sake of variety, let's define the<br />
link forward (to the answer) as a button and the link backward (back<br />
to the question) as an in-text link. Consider the following code:<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
You cannot see it, you cannot touch<br />
it, it isn't a liquid, it isn't a<br />
solid, it isn't a gas, but it can<br />
be broken. <br />
What is it?<br />
<br />
<br />
<br />
<br />
Silence.<br />
<br />
<br />
<br />
Back to Question<br />
<br />
<br />
<br />
<br />
Card 1<br />
The first card is simply text, enclosed in paragraph tags. However,<br />
we've added a element to change the behavior of the Accept key<br />
- one of the default navigation keys on most mobile devices (see the
next section on keys for more information). This element has<br />
type and label attributes. The type attribute tells the browser how the<br />
is called. In this case it's by remapping the Accept key's<br />
function. The label supplies a label for the key, namely "Answer" in<br />
this example.<br />
Note: This label bends one of the UI suggestions of using a maximum<br />
of five letters for key labels.<br />
Within the element is a instruction, supplying the address<br />
(href) to "go" to when the key is pressed. The href is in the following<br />
form:<br />
#<br />
Since the destination is in the same deck, the deck name can be<br />
omitted and only the CardID is necessary.<br />
Note: The order of the and elements is arbitrary. I prefer<br />
to put my navigation directives before my text wherever possible. It<br />
would be acceptable to reverse their order. Whichever you choose it<br />
pays to be consistent.<br />
Card 2<br />
The second card also uses a paragraph element, but incorporates a<br />
link within the element by using the tag. The form of the<br />
tag is as follows:<br />
<br />
type<br />
text<br />
<br />
The label attribute supplies an optional label to the Accept button<br />
when the link is selected. We've omitted this attribute in the example,<br />
allowing the default "Link" (or other device default) to be displayed.<br />
The type of anchor can be , , or - you simply<br />
add the appropriate element to define the type. We've used to<br />
return to the previous card.<br />
The text is the actual text shown for the link, in this case "Back to
Question".<br />
Note that the is enclosed in the paragraph and that the<br />
paragraph is set to nowrap. This will prevent the link from wrapping to<br />
several lines. We aren't worried about the plain text ("Silence")<br />
wrapping in this example, but we could easily enclose the in<br />
its own nowrap tag and change the text paragraph to wrap.<br />
Result<br />
The above code has the following result:<br />
Figure 2-3 - The left display is Card1, the right is Card2. Notice<br />
that the emulator displays a check mark instead of the<br />
standard "Link" text for the Accept button.<br />
Image courtesy Openwave Systems Inc. (Openwave, the Openwave<br />
logo, Openwave SDK, Openwave SDK Universal Edition, Openwave<br />
SDK <strong>WAP</strong> Edition are trademarks of Openwave Systems Inc. All rights<br />
reserved.)<br />
Expanding the Example<br />
You might have noticed that our second navigation method - mapping
the command to the Accept key - is superfluous. Most devices<br />
incorporate a standard "Back" button that could be used to return to<br />
the question without the overhead of remapping a key. With that in<br />
mind, let's put the key to a better purpose: moving to another<br />
question.<br />
Change the to the following:<br />
<br />
<br />
Next Question<br />
<br />
Then, duplicate cards 1 and 2 and re-label them as 3 and 4. You also<br />
need to change the link in card 3 (to card 4) and supply a new<br />
question and answer. The result would be similar to the following:<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
You cannot see it, you cannot touch<br />
it, it isn't a liquid, it isn't a<br />
solid, it isn't a gas, but it can<br />
be broken. <br />
What is it?<br />
<br />
<br />
<br />
<br />
Silence.<br />
<br />
<br />
<br />
Next Question<br />
<br />
<br />
<br />
<br />
<br />
<br />
What is greater than God, more evil<br />
than the devil, poor people have it,<br />
rich people want it, and if you eat<br />
it you will die?<br />
<br />
<br />
<br />
<br />
Nothing. Nothing's greater than God,<br />
nothing's more evil than the devil, poor<br />
people have nothing, rich people<br />
want nothing more than they already<br />
have, and if you eat nothing you will die.<br />
<br />
<br />
<br />
<br />
<br />
Next Question<br />
<br />
<br />
<br />
<br />
Note: Because the answer to the second question is lengthy, I moved<br />
the to its own nowrap paragraph, as discussed earlier. Also,<br />
I set up the last link to a nonexistent "card5," assuming that we would<br />
add more questions and answers.<br />
Understanding Device Keys<br />
Most mobile devices have a limited number of keys to aid in<br />
navigation. Many have a default Back key (my Samsung cell phone<br />
uses the CLR key), as well as Accept and Options keys (my Samsung<br />
uses OK and MENU, respectively).<br />
Note: See the <strong>WAP</strong> documentation for a particular device to determine<br />
which keys perform which default purposes.
The Accept and Option keys are referred to as soft keys because you<br />
can redefine their meaning and default labels with the software, in this<br />
case <strong>WML</strong> code. Many of the navigation elements allow you to specify<br />
which keys you want the soft keys to interact with. For example, in our<br />
riddle example we specified "accept" to modify the Accept key. We<br />
could have used "options" to redefine the other soft key (on the lower<br />
right side of the display).<br />
Tip: When defining multiple elements on the same card, the<br />
affected soft key will be labeled "MENU" and allow access to a menu of<br />
defined elements.<br />
Unfortunately, there are few firm standards for default keys. Some<br />
device manufacturers map different keys to the <strong>WAP</strong> interface, while<br />
some have more proprietary solutions such as a jog wheel to move<br />
between options and functions. The best thing to do before planning<br />
an interface is to read the UI and application style guides and test your<br />
target devices.<br />
<strong>Learning</strong> <strong>WML</strong> - Navigation, User Input, and<br />
Graphics<br />
This series of articles describes how to provide Web content to mobile<br />
devices through <strong>WML</strong> (Wireless Markup Language). This article covers<br />
more advanced navigation, accepting user input, and displaying<br />
graphics. Future articles will cover advanced <strong>WML</strong> language, tips and<br />
tricks to provide content, and how to integrate other technologies such<br />
as PHP to make your pages more flexible.<br />
Note: These articles cover <strong>WML</strong> version 1.1, which is supported by the<br />
majority of mobile devices in use today. The articles assume a working<br />
knowledge of HTML and general Web technologies, and further assume<br />
that you have read the previous article(s) in this series.<br />
More Navigation Between Cards and Decks<br />
In the previous article ("<strong>Learning</strong> <strong>WML</strong> - <strong>WML</strong> Tools and Coding<br />
<strong>Basics</strong>"), we covered basic navigation using and
elements. These basic navigation constructs enable you to map links<br />
to the soft keys or place standard HTML-style hyperlinks into your<br />
cards. There are a few more useful navigation methods we should also<br />
cover.<br />
Select Lists<br />
The select list provides an easy way for the user to select between<br />
several predetermined values. Each value is enclosed within an<br />
tag, much like an HTML list:<br />
<br />
First option<br />
Second option<br />
<br />
Each option is automatically assigned a shortcut number, displayed<br />
next to it by the browser, as shown in the following figure.<br />
Each element is assigned a shortcut number which is<br />
displayed next to the element's text.<br />
Image courtesy Openwave Systems Inc. (Openwave, the Openwave
logo, Openwave SDK, Openwave SDK Universal Edition, Openwave<br />
SDK <strong>WAP</strong> Edition are trademarks of Openwave Systems Inc. All rights<br />
reserved.)<br />
The user can either press the shortcut number associated with the<br />
option or use the standard navigation (arrow) keys to select the<br />
appropriate option and then press the Accept key.<br />
Tip: The shortcut keys are only useful if you have 10 or fewer options,<br />
since there are only 10 numeric keys (0-9) that can be used to quickly<br />
select an option. If you have more than 10 options, consider adding a<br />
"More" option that displays the options in groups of 10 or fewer.<br />
The "onpick" parameter can be used to select a deck/card to navigate<br />
to if the user selects that option. For example, the following code will<br />
navigate the user to the appropriate "color" card within the current<br />
deck:<br />
<br />
Red<br />
Green<br />
Blue<br />
<br />
A full deck example follows:<br />
<br />
<br />
<br />
<br />
<br />
<br />
Red<br />
Green<br />
Blue<br />
<br />
<br />
<br />
<br />
Your choice was Red.<br />
<br />
<br />
Your choice was Green.<br />
<br />
<br />
<br />
<br />
Your choice was Blue.<br />
<br />
<br />
<br />
Tip: Notice the addition of the "title" parameter in each tag.<br />
The text provided with this parameter is displayed as the Accept soft<br />
key's label when the option is selected.<br />
We used the options to navigate to cards within the current deck.<br />
However, you could just as easily navigate to other decks or specific<br />
cards within those decks.<br />
<strong>Learning</strong> <strong>WML</strong> - Navigation, User Input, and<br />
Graphics<br />
Events<br />
You can also set up navigation that is caused by a particular event.<br />
The element provides several possible actions triggered by<br />
one or more intrinsic events.<br />
The tag has the following format:<br />
<br />
task<br />
<br />
The "type" can be one of the following:
Type<br />
Onpick<br />
Onenterforward<br />
Onenterbackward<br />
Ontimer<br />
Event<br />
User selects or deselects an item.<br />
User navigates to a card in a forward manner<br />
(e.g., from a or other link)<br />
User navigates to a card in a backward manner<br />
(e.g., from a element or Back command)<br />
A specified element expires<br />
The "onenter" types can be used to display or omit particular<br />
information depending on how the user navigated to the card, or to<br />
force a refresh of dynamic information if the user reached the card by<br />
going backward (thereby getting the card from the cache and not a<br />
fresh copy from the server).<br />
The "onpick" type is used when the is nested within an<br />
element to provide an action when the option is selected.<br />
The "ontimer" type is used to provide a task at a particular time,<br />
independent of user actions (refreshing a page, moving to another<br />
page, etc).<br />
The various tasks that can be used with the element are<br />
described in the following table.<br />
Task Use<br />
Navigates to a new card.<br />
Navigates to the previous card.<br />
Performs no task (no operation).<br />
Refreshes the current card's content.<br />
Exits the current context. *<br />
Spawns a child task. *<br />
"Throws" an exception that can be caught by an<br />
optional element. *<br />
* These tasks are specific to the Openwave browser. See the<br />
developer documentation at http://developer.openwave.com for more<br />
information.<br />
The "ontimer" event can be used to display a splash screen (containing<br />
welcoming or copyright information), or to automatically return to a<br />
card after displaying an error. It_s also very useful for refreshing a<br />
card to display up-to-date dynamic data.
In the following example, the deck displays a splash screen before the<br />
main menu:<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Welcome to ACME Incorporated.<br />
<br />
<br />
<br />
Main menu here...<br />
<br />
<br />
Note: The element's value parameter is measured in tenths<br />
of a second, so our splash screen stays active for 5 seconds (50<br />
tenths).<br />
Tip: The element includes an optional "ontimer" parameter,<br />
which alleviates the need for a separate element. Using<br />
this parameter, the above example could be shortened as follows:<br />
. . .<br />
<br />
<br />
Accepting User Input
Although the forms capability of <strong>WML</strong> isn't as robust as standard<br />
HTML, the element is very flexible and allows a variety of<br />
data to be accepted from the user.<br />
The element has the following minimal form:<br />
<br />
In this form, the element accepts user input and stores it in the<br />
variable specified. The input is freeform; that is, it is not constrained<br />
to any particular format (numeric, alphanumeric, etc.) or length--<br />
although most mobile browsers impose a length limit of 256<br />
characters.<br />
Note: A full discussion of <strong>WML</strong> variables is outside the scope of this<br />
article, but will be provided in the next article. For this exercise it is<br />
important to know that variables exist and can be set by and<br />
elements, among others. A variable can then be referenced<br />
by prefixing its name with a dollar sign ("$"), or using the preferred<br />
method of enclosing the name in parentheses and prefixing the whole<br />
structure with a dollar sign. For example, the variable "firstname" can<br />
be referenced as "$firstname" or "$(firstname)". When the variable is<br />
referenced, <strong>WML</strong> will substitute the variable's value for its reference.<br />
The element also supports the optional parameters shown in<br />
the following table.<br />
Parameter Use<br />
Title Supplies a title for the element. Some devices display this<br />
title in the default Accept soft key label; others display it<br />
as "tooltip" text while the element is selected.<br />
Type Set to "text" or "password," this parameter controls<br />
whether the text is displayed as it is entered or displayed<br />
in the browser's password character(s) (usually "*").<br />
Value The default value for the element. Note that if the<br />
variable specified in the "name" parameter already has a<br />
value, or if the value specified with this parameter doesn't<br />
conform to the specified "format" parameter, the value is<br />
ignored.
Accesskey<br />
Format<br />
Emptyok<br />
Maxlength<br />
Displays the specified number (0-9) next to the element.<br />
The key can be used to quickly select the element.<br />
Specifies the format mask of the input. See the format<br />
section below for details on masking.<br />
Controls whether the element can be left empty. The valid<br />
values for this parameter are "true" (the default) or<br />
"false."<br />
Maximum length of the input, in characters.<br />
Note: The "accesskey" parameter allows you to create a form of<br />
several input fields where the user can easily navigate between the<br />
fields. However, as the Openwave applications point out<br />
(http://developer.openwave.com), wizards that display card-sized<br />
chunks of data entry fields are much better received and make a<br />
better application design.<br />
The "format" parameter's value is composed of a series of characters<br />
that provide a mask for the input. The following table describes the<br />
valid instructional characters for the format mask:<br />
Character Meaning<br />
A<br />
Any symbol or uppercase alpha character (no numbers).<br />
A<br />
Any symbol or lowercase alpha character (no numbers).<br />
N<br />
Any number (no symbols or alphabetic characters).<br />
X<br />
Any symbol, number, or uppercase alpha character<br />
(cannot be changed to lowercase alpha).<br />
X<br />
Any symbol, number, or lowercase alpha character<br />
(cannot be changed to uppercase alpha).<br />
M<br />
Any symbol, number, or alpha character; by default, the<br />
first character is uppercase.<br />
M<br />
Any symbol, number, or alpha character; by default, the<br />
first character is lowercase.<br />
The format mask can also include symbols, which will be displayed in<br />
their appropriate position in the input.<br />
For example, to accept a phone number, including the area code, you<br />
could use this format mask:<br />
format = "(NNN) NNN-NNNN"<br />
When using the same character multiple times in a row, you can prefix
the character with a number indicating how many times it repeats.<br />
Using this method, our phone number mask could be written like this:<br />
format = "(3N) 3N-4N"<br />
However, this format isn't as recognizable as a phone number mask.<br />
Note: To specify an unlimited number of the same character, prefix the<br />
character with an asterisk (*).<br />
A simple deck to accept a person's first name (up to 25 characters)<br />
and then greet the person by name would resemble the following<br />
listing:<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
What is your first name?<br />
<br />
<br />
<br />
<br />
<br />
Hello, $(name)!<br />
<br />
<br />
<br />
Notice that the element uses the "M" format mask, supplying<br />
an uppercase letter as the default first character. The "maxlength"<br />
parameter constrains the input's length, despite the "*" in the mask.<br />
The deck navigation in this example is controlled by a element<br />
linked to the Accept key. In some devices the user will have to press<br />
the Accept key twice--once to exit the input element and another to
trigger the navigation.<br />
There are many useful things you can do with user-supplied input<br />
besides echo it back in other cards. The next article in this series will<br />
discuss several other uses for user input.<br />
Graphics and Icons<br />
The current generation of mobile devices includes very limited graphics<br />
capability. Most mobile browsers only support black-and-white<br />
bitmapped images, although a few recent browsers have added<br />
support for color and even animated formats (GIF, PNG, JPG). Some<br />
mobile gateways also perform on-the-fly conversions of graphics, most<br />
notably from BMP to WBMP.<br />
Tip: Unless you are developing for a particular platform where you<br />
know the capabilities, use only small black-and-white WBMP graphics.<br />
Most graphics applications do not support the WBMP format, but<br />
several converters exist, including a Web-based converter at Teraflops<br />
(http://www.teraflops.com/wbmp/).<br />
Note: Refrain from using graphics whenever possible; using graphics<br />
breaks the mobile application development rules of "fast" and<br />
"simple."<br />
To create a graphic for mobile use you will need a graphics application<br />
that supports the WBMP format, or at least black-and-white bitmap<br />
format and a suitable converter. Drawing upon our earlier splash<br />
screen example, let's create a graphical logo for ACME.<br />
To start, we need to choose a suitable size for the graphic. It's<br />
advisable to stick with a small size such as 100 x 100 pixels. The<br />
graphic shown in the following figure was created with the following<br />
steps (note that the options and procedure might differ in your<br />
graphics application):<br />
1 Create a new graphic, sized 80 x 80 pixels.<br />
2 Convert the graphic to black-and-white, or "line art."<br />
3 Select the appropriate background color (black or white) and fill<br />
the entire graphic.<br />
4 Use the text tool to create text in appropriate sizes and fonts
(using the opposite color of the background).<br />
5 Save the results in GIF format.<br />
6 Use the online tool at Teraflops.com to convert the GIF to WBMP<br />
format.<br />
7 Upload the resulting graphic (acme.wbmp) to the Web server.<br />
Tip: We advise using a graphics program that supports objects instead<br />
of straight bitmapped images. Graphics programs allow you to work<br />
with individual objects_moving, sizing, etc_before committing them to<br />
the electronic canvas.<br />
The ACME Incorporated logo.<br />
Images are added to <strong>WML</strong> cards using the tag. This tag has the<br />
following syntax:<br />
<br />
Note that the "alt" and "src" parameters are mandatory in all <br />
tags. The tag also supports the following optional parameters:<br />
Parameter Use<br />
localsrc Specifies an icon. (See the later section on icons.)<br />
align Aligns the image relative to the current line of text.<br />
Supports "top," "middle," and "bottom."<br />
height Specifies the exact height of the displayed image.<br />
width Specifies the exact width of the displayed image.<br />
hspace Specifies how much white space should be displayed at<br />
each side of the image (horizontal space).<br />
vspace Specifies how much white space should be displayed<br />
above and below the image (vertical space).<br />
Note: Not all mobile browsers support the sizing and spacing<br />
parameters.<br />
Incorporating the logo into our previous splash screen example, we get<br />
the following result (see figure):<br />
<br />
<br />
<br />
<br />
Welcome to<br />
<br />
<br />
<br />
<br />
Main menu here...<br />
<br />
<br />
Our new splash screen.<br />
Image courtesy Openwave Systems Inc. (Openwave, the Openwave<br />
logo, Openwave SDK, Openwave SDK Universal Edition, Openwave<br />
SDK <strong>WAP</strong> Edition are trademarks of Openwave Systems Inc. All rights<br />
reserved.)
Icons are very small graphics to be used as highlights or<br />
ornamentation. Most mobile browsers support at least a limited<br />
number of icons. For the purposes of this discussion we will use icons<br />
supported by the Openwave browser. A full list of these icons can be<br />
found in the <strong>WML</strong> Language Reference documents on the Openwave<br />
developer site (http://developer.openwave.com).<br />
Icons are placed as graphics, using the tag and its "localsrc"<br />
attribute, along with the appropriate icon number(s). For example, a<br />
smiley face is icon number 68; it would be placed in the contents of a<br />
card with the following tag:<br />
<br />
Note that the "alt" and "src" tags are mandatory, even if left blank.<br />
Tip: When using icons for ornamentation, be sure to specify alternate<br />
text that can serve the same purpose or that resembles the icon.<br />
Let's suppose that ACME is a travel agency. We could add an airplane<br />
icon to the bottom of the splash screen with this tag (see figure):<br />
Our new splash screen complete with airplane icon.<br />
Image courtesy Openwave Systems Inc. (Openwave, the Openwave<br />
logo, Openwave SDK, Openwave SDK Universal Edition, Openwave<br />
SDK <strong>WAP</strong> Edition are trademarks of Openwave Systems Inc. All rights<br />
reserved.)<br />
Tip: Because the <strong>WML</strong> language is fairly limited for emphasizing text,<br />
consider using icons such as triangles (icons 5-8) to draw attention to<br />
specific text. For example, this code is used to call attention to the fact<br />
that the user has reached the end of an article:<br />
<br />
End<br />
<br />
<strong>Learning</strong> <strong>WML</strong> - Variables and Scripting<br />
This series of articles describes how to provide Web content to mobile<br />
devices through <strong>WML</strong> (Wireless Markup Language). This article covers<br />
variables and beginning scripting with <strong>WML</strong>Script. Future articles will<br />
cover more advanced scripting and how to integrate other technologies<br />
such as PHP to make your pages more flexible.<br />
Note: These articles cover <strong>WML</strong> and <strong>WML</strong>Script version 1.1, which are<br />
supported by the majority of mobile devices in use today. The articles<br />
assume a working knowledge of HTML and general Web technologies,<br />
and further assume that you have read the previous article(s) in this<br />
series.<br />
Understanding Variables<br />
<strong>WML</strong> supports variables that can hold transitional data between cards,<br />
provide custom output tailored to individual users, and more.<br />
Variables are special holding places for values. They can hold numeric<br />
or alpha values and their values can be changed by code at the<br />
programmer's whim. However, it is usually good practice to dedicate<br />
variables to particular purposes, and hence, to particular types of data.
Variables in <strong>WML</strong> consist of words, enclosed in parentheses, prefixed<br />
with a dollar sign ($). For example:<br />
$(name)<br />
$(address)<br />
$(link)<br />
$(target_url)<br />
$(_method)<br />
Note: Because the dollar sign ($) is used to signify a variable, it is a<br />
reserved character in <strong>WML</strong>. If you want an actual dollar sign to appear<br />
anywhere in your card(s), use a double dollar sign instead ("$$").<br />
Variable names must start with a letter or underscore. Subsequent<br />
characters can be alpha, numeric, or underscores. Variables are case<br />
sensitive; "phone_number" is different from "Phone_Number."<br />
Wherever a variable is referenced, <strong>WML</strong> will substitute the value of the<br />
variable where the variable name appears. For example, the following<br />
code:<br />
<br />
Hello $(name)!<br />
<br />
would produce the following result, if "David" was stored in the<br />
variable $(name):<br />
Hello David!<br />
Note: <strong>WML</strong> reserves the ampersand symbol (&) for entities. To use this<br />
symbol in your deck, you must substitute the corresponding entity<br />
"&".<br />
Setting Variables<br />
Variables can be set by the following elements:<br />
<br />
/<br />
Each of these elements is discussed in more detail below. Note that<br />
some of these elements were introduced in previous articles, and their<br />
parameters and such will not be detailed here.<br />
The Element<br />
The element is straightforward; it accepts user input and<br />
stores it in the variable specified. For example, the following code<br />
would accept input for a phone number and store it in the variable<br />
$(phone_number). The number is then displayed on the second card.<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
What is your phone number?<br />
<br />
<br />
<br />
<br />
<br />
Your number is:<br />
$(phone_number)<br />
<br />
<br />
<br />
Note: The element accepts a "value" attribute that can set a<br />
default value for the input. However, if the variable specified has<br />
already been assigned a value, the "value" attribute is ignored.<br />
The and Elements
Each element within a can also set variables. When<br />
the user selects an option, that option's "value" parameter is stored in<br />
the variable specified by the select's name parameter. Although it<br />
sounds convoluted, it is pretty straightforward in practice. The<br />
following example would store the selected color's hex value in the<br />
variable "color":<br />
<br />
Red<br />
Green<br />
Blue<br />
<br />
For example, if the user selected "Green," the variable "color" would<br />
be set to "#00FF00".<br />
Note: The element accepts a "value" attribute that can set a<br />
default value for the input. However, if the variable specified has<br />
already been assigned a value, the "value" attribute is ignored.<br />
The Element<br />
The element is perhaps the most pure of the variable-setting<br />
elements, because it serves the sole purpose of simply setting a<br />
variable. In the following example, the variable "color" is set to<br />
"Green":<br />
<br />
The use of is complicated by the fact that it can only be<br />
executed upon a , , or action. As such, you<br />
cannot simply embed a anywhere you need to set a variable.<br />
It is common practice to set variables before moving to a card where<br />
you will need them, by embedding the proper elements<br />
within the corresponding or other action element.<br />
If you need to set variables for the current card and doing so from a<br />
previous page is impractical (for example, for the first card displayed<br />
in the deck), you can use and elements to force<br />
a to execute: For example, the following code will set the
appropriate variables when the user enters this card from a forward<br />
direction:<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Note: Unlike the other elements above, the element sets the<br />
value of the specified variable whether or not it has previously been<br />
assigned a value.<br />
Using Variables<br />
Variables are very flexible and can be used in a variety of ways. You<br />
can store the results of user choices or status flags, display variable<br />
text, etc. Using the element, you can even pass a<br />
variable's value to an external program.<br />
The element defines a name/value pair to be sent to the<br />
HTTP server with the next . The element is<br />
encapsulated within elements as follows:<br />
<br />
<br />
. . .<br />
<br />
The element can include an optional "method" parameter that<br />
can be set to "get" or "post," which determines how the variable/value<br />
pairs are sent to the server. There are many differences between the<br />
two methods, but generally speaking "get" encodes the pairs in the<br />
calling URL while "post" sends the pairs in the body of the calling<br />
message. Of the two, "post" should be used wherever possible<br />
because it is more robust.<br />
Using this method, you can pass values to CGI scripts or other<br />
external applications. For example, the following example passes the
name/value pair color/Red to colorselect.cgi:<br />
<br />
<br />
<br />
Note that the "value" attribute is mandatory and is not ignored if the<br />
variable has been set previously. To send a value of a variable that has<br />
been set elsewhere, use the variable tag as the "value" attribute. For<br />
example, the following code will send the current value of the color<br />
variable:<br />
<br />
Note: You can also include variables in the URL of and other<br />
navigation elements by using the standard "?name=value" notation:<br />
Although a full primer on <strong>WML</strong>Script is outside the scope of these<br />
articles, it is important to understand a few basic concepts.<br />
• <strong>WML</strong>Script is case sensitive. All language elements must be spelled<br />
using the proper capitalization of letters.<br />
• <strong>WML</strong>Script supports integer, floating point, string, and Boolean<br />
literals. String literals must be enclosed in single or double quotes ('<br />
' or " ") and Boolean values are set to "true" or "false."<br />
• <strong>WML</strong>Script will automatically convert between values to deliver the<br />
right type of value (integer, string, etc.) to the function that receives<br />
the value.<br />
• Whitespace is ignored in <strong>WML</strong>Script, allowing the programmer to<br />
format the code as desired.<br />
• Comments in <strong>WML</strong>Script can consist of a single line or multiple lines.<br />
Single-line comments start with a double slash (//). For multiple-line<br />
comments, start the block with a slash followed by an asterisk (/*)<br />
and end the block with an asterisk followed by a slash (*/).<br />
• As with any language, <strong>WML</strong>Script has a handful of reserved words<br />
such as "function," "var," "continue," etc. For a full list, consult the<br />
language guide for the version of <strong>WML</strong>Script you are using.<br />
• You must declare all variables in <strong>WML</strong>Script.<br />
• <strong>WML</strong>Script can access variables set in <strong>WML</strong>. You can set a variable in<br />
a <strong>WML</strong> card and reference that same variable in a <strong>WML</strong>Script. You<br />
can also pass values to <strong>WML</strong>Script functions and return values from<br />
the function to the calling <strong>WML</strong>.<br />
• <strong>WML</strong>Script is contained in external files (usually with a <strong>WML</strong>S<br />
extension) and called from standard <strong>WML</strong> navigation elements using<br />
the following expression:<br />
#()<br />
For example, to call the function "validate" in the file "validation.wmls"<br />
you would use<br />
validation.wmls#validate()<br />
Your First Script<br />
The easiest way to learn a scripting language is by example. The<br />
following basic example accepts a number, multiplies it by 2, and
displays the result. This is a relatively useless script, but it helps<br />
simply to illustrate the mechanics between <strong>WML</strong> and <strong>WML</strong>Script.<br />
This example uses the following two files:<br />
sample.wml<br />
1. <br />
2. <br />
3. <br />
4. <br />
5. <br />
6. <br />
7. <br />
8. <br />
9. <br />
10. <br />
11. <br />
12. <br />
13. <br />
14. <br />
15. <br />
16. <br />
17. <br />
18. <br />
19. Your number ( $(num) ) multiplied by 2 is:<br />
20. $(numX2)<br />
21. <br />
22. <br />
23. <br />
sample.wmls<br />
24. extern function multiply() {<br />
25. var Num = <strong>WML</strong>Browser.getVar("num");<br />
26. var NumX2;<br />
27. NumX2 = Num * 2;<br />
28. <strong>WML</strong>Browser.setVar("numX2", NumX2);<br />
29. <strong>WML</strong>Browser.go("sample.wml#card2");<br />
30. }
Notice that the <strong>WML</strong>Script file begins with an "extern" keyword in front<br />
of the function definition. This keyword allows a function to be<br />
accessed from outside the file; for this example, from our <strong>WML</strong> deck.<br />
Functions accessed internally by the same <strong>WML</strong>Script file do not need<br />
this keyword.<br />
Our example executes as follows:<br />
1 As the first card is displayed, the handler causes the<br />
variable "num" to be initialized as blank (lines 5-9). Although not<br />
completely necessary, this step is good practice to ensure that the<br />
variable exists and is in a known state.<br />
2 Input is received via the element and stored in the variable<br />
"num" (line 14). Note that the "format" parameter forces numeric<br />
input.<br />
3 The related element (lines 11-13) calls the "multiply" function<br />
in the wmls file.<br />
4 The "multiply" function declares two variables, setting the variable<br />
"Num" to the value of the XML variable "num," by using the<br />
<strong>WML</strong>Browser.getVar function (lines 25-26).<br />
5 The variable "Num" is multiplied by 2; the resulting value is stored in<br />
the variable "NumX2" (line 27).<br />
6 The script stores the value of NumX2 into a <strong>WML</strong> variable<br />
("numX2"), using the <strong>WML</strong>Browser.setVar function (line 28).<br />
7 The script uses a "go" function to jump to the second card in the<br />
<strong>WML</strong> deck (line 29).<br />
8 The second card (lines 17-22) displays our text, using the variables<br />
"num" and "numX2."<br />
Note: The <strong>WML</strong>Browser library functions (<strong>WML</strong>Browser.setVar, etc.)<br />
provide an easy interface between XML and XMLScript. Consult a<br />
<strong>WML</strong>Script language reference for more information on these<br />
functions.<br />
A More Useful Script<br />
One handy purpose for <strong>WML</strong>Script is validating input and acting<br />
accordingly. For example, suppose we had a card that asked for a twoletter<br />
U.S. state abbreviation. Although the element can
ensure that we have two uppercase letters, it can't match the input<br />
against an acceptable list. For that, we will use <strong>WML</strong>Script.<br />
input.wml<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
$(stateabbr) is a valid State code.<br />
<br />
<br />
<br />
validate.wmls<br />
extern function state() {<br />
var stateabbr = <strong>WML</strong>Browser.getVar("stateabbr");<br />
var pos, x;<br />
var okay = false;<br />
var statelist =<br />
"ALAKASAZARCACOCTDEDCFMFLGAGUHIIDILINIAKS<br />
KYLAMEMHMDMAMIMNMSMOMTNENVNHNJNMNYNCNDMP<br />
OHOKORPWPAPRRISCSDTNTXUTVTVIVAWAWVWIWY";<br />
if (String.length(stateabbr) == 2) {
pos = String.find(statelist,stateabbr);<br />
x = pos / 2;<br />
if (Float.int(x) == x) { okay = true; }<br />
}<br />
if (okay) {<br />
<strong>WML</strong>Browser.go("input.wml#stateok");<br />
} else {<br />
Dialogs.alert("Invalid input!");<br />
<strong>WML</strong>Browser.go("input.wml#state");<br />
}<br />
}<br />
The concept is relatively simple. The <strong>WML</strong> file takes the input, stores it<br />
in the "stateabbr" variable, and then calls the validation function. The<br />
function grabs the state abbreviation that was entered and performs<br />
two checks:<br />
• Is the abbreviation two characters long?<br />
• Does it appear in a valid list?<br />
The String library function "length" is used to determine whether the<br />
abbreviation is two characters long. If so, the second check is<br />
performed. The second check attempts to find the abbreviation in the<br />
"statelist" string. The position is then divided by two-if the result is an<br />
integer, the state is valid.<br />
Note: A simple substring search ("String.find") is not enough to<br />
validate the input, as combinations like "SK" would pass the test. The<br />
abbreviation must be in an even-numbered position (evenly divisible<br />
by 2). If the abbreviation is not found, the find function returns -1,<br />
which also fails the even number test.<br />
More Information on <strong>WML</strong>Script<br />
A wealth of information can be found at the Openwave developer site,<br />
http://developer.openwave.com. Take a look at their Technical Library<br />
and be sure to visit the Developer Forum for answers to more difficult<br />
problems.
<strong>WML</strong> Scripting Tips and Integration with PHP<br />
This series of articles describes how to provide Web content to mobile<br />
devices through <strong>WML</strong> (Wireless Markup Language). This article covers<br />
some additional uses for <strong>WML</strong>Script and how to integrate PHP with<br />
<strong>WML</strong>. Future articles will cover more advanced PHP and <strong>WML</strong><br />
techniques.<br />
Note: These articles cover <strong>WML</strong> and <strong>WML</strong>Script version 1.1, which are<br />
supported by the majority of mobile devices in use today. The articles<br />
assume a working knowledge of HTML and general Web technologies,<br />
and further assume that you have read the previous article(s) in this<br />
series.<br />
<strong>WML</strong>Scripting<br />
In the last article we discussed how to use <strong>WML</strong>Script to perform basic<br />
scripting tasks within your <strong>WML</strong> pages. As mentioned in that article,<br />
<strong>WML</strong>Script can be used to validate input and perform basic operations<br />
on <strong>WML</strong> variables. <strong>WML</strong>Script can also be used to control the display of<br />
various cards and the information contained on them. For example,<br />
the following code will display an animation by changing an image's<br />
source and refreshing a card every two seconds:<br />
animation.wml<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Animation Done!
<br />
animation.wmls<br />
extern function main() {<br />
var Num = <strong>WML</strong>Browser.getVar("num");<br />
var Image = "";<br />
if (Num < 9) {<br />
Num++;<br />
Image = "image" + Num + ".wbmp";<br />
<strong>WML</strong>Browser.setVar("num", Num);<br />
<strong>WML</strong>Browser.setVar("image", Image);<br />
<strong>WML</strong>Browser.refresh();<br />
} else {<br />
<strong>WML</strong>Browser.go("animation.wml#card2");<br />
}<br />
}<br />
The deck (animation.wml) sets two variables and forces a refresh.<br />
These are the variables:<br />
num: The number to append to the image source<br />
image: The starting image source (image1.wbmp)<br />
Every two seconds, the main() function in animation.wmls is called.<br />
This function adds one to "num," creates a new image source<br />
(image.wbmp), and refreshes the card, which causes the new<br />
image to be displayed. After the ninth image (image9.wbmp) is<br />
displayed, the script goes to the second card in the deck, ending the<br />
animation.<br />
Note: The above example assumes that your animation consists of<br />
nine images that, when displayed sequentially, create the animation.<br />
<strong>WML</strong>Scripting is flexible and brings basic scripting to <strong>WML</strong>. However,<br />
it's possible to lend more programming power to <strong>WML</strong> by infusing it<br />
with PHP, as covered in the next section.<br />
<strong>WML</strong> Scripting Tips and Integration with PHP
Integrating PHP into <strong>WML</strong><br />
Although <strong>WML</strong> is well suited to most mundane content delivery tasks,<br />
it falls short of being useful for database integration or extremely<br />
dynamic content.<br />
Another Web technology, PHP, fills this gap quite nicely-integrating<br />
into most databases and other Web structures and languages. It's<br />
possible to "cross-breed" mime types in Apache and IIS to enable PHP<br />
to deliver <strong>WML</strong> content. The following sections show you how to<br />
configure your server and how to integrate the two technologies.<br />
Note: This section assumes that you have a basic understanding of<br />
PHP. To learn more about PHP, visit the PHP Web site (www.php.net)<br />
and browse through the language documentation. You can also find a<br />
series of articles here on Developer.com for learning PHP. The first is<br />
<strong>Learning</strong> PHP: The What's and the Why's<br />
What Is PHP?<br />
According to the PHP documentation, "PHP (recursive acronym for<br />
'PHP: Hypertext Preprocessor') is a widely-used Open Source generalpurpose<br />
scripting language that is especially suited for Web<br />
development and can be embedded into HTML."<br />
Essentially, this scripting language is a server-side language that's<br />
processed before being sent to the requesting client. This is in contrast<br />
to scripting languages like JavaScript that are client-side and<br />
processed by the client's browser after being sent to the client.<br />
The benefit of being a preprocessed language is that PHP pages can be<br />
highly dynamic. Decisions can be made from user input, databases,<br />
server conditions, and so on about how the requested content is<br />
delivered to the client. It's exactly what we need to increase the power<br />
of our <strong>WML</strong>.<br />
Installing PHP<br />
PHP is available for most major Web server platforms, including<br />
Apache and Microsoft's Internet Information Server (IIS). Visit
www.php.net, download the appropriate version for your server, and<br />
install it by following the included instructions.<br />
Note: If you're running Debian, Red Hat, or another version of Linux<br />
with a robust packaging system, PHP is probably available in a<br />
preconfigured package. Check your distribution's Web site and<br />
documentation for more info on PHP package(s).<br />
Cross-breeding with <strong>WML</strong>: Apache<br />
To enable <strong>WML</strong> pages to be parsed by PHP, you simply have to add the<br />
<strong>WML</strong> suffix to the PHP application definition in your Apache<br />
configuration file:<br />
AddType application/x-httpd-Php .php .wml<br />
Note the addition of ".wml" to the definition. This will cause the server<br />
to parse all .wml files with PHP. Don't forget to restart Apache so it will<br />
read the new configuration. Make sure that your <strong>WML</strong> definitions<br />
appear before the PHP definition to ensure that Apache recognizes<br />
".wml" as a valid type.<br />
Tip: As mentioned above, this causes all .wml files to be parsed by<br />
PHP. Although this is usually desirable, you could create another mime<br />
type ("pwml") and include it in both the <strong>WML</strong> and PHP definitions.<br />
Then you could continue to use .wml for pure <strong>WML</strong> files and the new<br />
type for PHP-parsed <strong>WML</strong>.<br />
Cross-breeding with <strong>WML</strong>: IIS<br />
To enable <strong>WML</strong> pages to be parsed by PHP, you need to add PHP as an<br />
application to handle your .wml files.<br />
To do this, follow the steps below:<br />
1 Open the Internet Information Services Management Console.<br />
2 Right-click on the site where you want to add this functionality (or<br />
use Default Web Site) and choose Properties.
3 Access the Home Directory tab and click the Configuration button<br />
near the bottom of the dialog.<br />
4 Click the Add button to add an application.<br />
5 Add the path and filename of the PHP executable (usually<br />
"C:\PHP\php.exe") in the Executable field and ".wml" in the<br />
Extension field.<br />
6 Ensure that "Script Engine" and "Check that file exists" are both<br />
checked.<br />
7 Select OK back to the IIS MC.<br />
8 Although not usually required, you may need to restart your Web<br />
server.<br />
In step 6, above, setting the "Check that file exists" option is not<br />
entirely necessary, but can be helpful in debugging server issues.<br />
Without that option being set, IIS will pass all requests for .wml pages<br />
to PHP, even if the requested page doesn't exist. Instead of generating<br />
a "404 - Not Found" error, the Web server will report a problem with<br />
PHP (which failed because there was no file to parse).<br />
These steps will result in all .wml files being parsed by PHP. Although<br />
this is usually desirable, you could create another mime type ("pwml")<br />
and include it in the <strong>WML</strong> definition and define PHP as an application<br />
for the new type. Then you could continue to use .wml for pure <strong>WML</strong><br />
files and the new type for PHP-parsed <strong>WML</strong>.<br />
Getting the Client to Recognize PHP-parsed <strong>WML</strong><br />
Now that the server has been configured to deliver PHP-parsed <strong>WML</strong>,<br />
you need to ensure that the client will receive it as <strong>WML</strong>. This is<br />
accomplished by having each PHP-parsed page send the appropriate<br />
header to the client.<br />
You should begin each PHP file with the following line:<br />
header("Content-type: text/vnd.wap.wml");
This line needs to come before anything else is sent to the client-it<br />
pays to make a habit of including it right after the "\n";<br />
print "\n";<br />
print "\n";<br />
print "\n";<br />
print "Hello world\n";<br />
print "\n";<br />
print "\n";<br />
?><br />
Note that there are several ways to accomplish sending the <strong>WML</strong> to<br />
the client. I've chosen to use individual print statements, each ending<br />
with a newline code. This keeps the resulting <strong>WML</strong> fairly readable and<br />
PHP mimics the line breaks. In some cases, namely long stretches of<br />
code, a print "here document" structure would work better. The above<br />
example would resemble the following in "here document" structure:<br />
In any case, remember that your target platform is <strong>WML</strong>-stick to <strong>WML</strong>compatible<br />
tags and structure and avoid using reserved characters<br />
(such as "$") in places where they might be misinterpreted. Also,<br />
debugging the code is twice as hard as straight <strong>WML</strong> because you have<br />
to debug the PHP code as well as the resulting <strong>WML</strong>. The next article in<br />
this series will provide more debugging tips.<br />
Utilizing the Power of PHP<br />
As previously mentioned, PHP's interoperability with other technologies<br />
makes it a powerful ally for <strong>WML</strong>. One such technology, databases, is<br />
especially useful.<br />
As an introduction, we'll cover a basic example here. The next article<br />
in the series will showcase more examples of using PHP to extend<br />
<strong>WML</strong>.<br />
For this example we'll use MySQL, a popular open-source database<br />
solution, although any database with PHP connectivity would work as<br />
well.<br />
Suppose that we have a team of salespeople in the field who need<br />
regular access to customer data. In this example we'll only be<br />
concerned with the customer's phone number, but it illustrates the<br />
underlying power of using PHP while creating a useful "online"<br />
phonebook.<br />
Our database for this example is very simple:<br />
First Name Last Name Phone Number<br />
John Smith 555-723-0900<br />
Kathy Lamarr 555-876-2222<br />
Sam Kinkaid 555-243-8766<br />
Holly Haute 555-327-0987<br />
In MySQL, the database and table would be created using the following<br />
code:<br />
CREATE DATABASE Customer;<br />
USE Customer;
CREATE TABLE Phone (<br />
FirstName varchar(30) default NULL,<br />
LastName varchar(30) default NULL,<br />
Phone varchar(12) default NULL<br />
);<br />
INSERT INTO Phone VALUES ('John','Smith','555-723-0900');<br />
INSERT INTO Phone VALUES ('Kathy','Lamarr','555-876-2222');<br />
INSERT INTO Phone VALUES ('Sam','Kinkaid','555-243-8766');<br />
INSERT INTO Phone VALUES ('Holly','Haute','555-327-0987');<br />
Now we'll create a relatively simple PHP program to display the<br />
customers in a select list. When the user selects an entry, the phone<br />
will dial the selected number.<br />
1.
24. print "\n";<br />
25. ?><br />
The code is fairly straightforward:<br />
• Lines 1-5 are our standard <strong>WML</strong> preamble.<br />
• Lines 6-8 open the required tags.<br />
• Lines 9-12 create the link to the database. Be sure to substitute an<br />
actual username and password for the placeholders.<br />
• Lines 13-15 query the database for all fields in all rows of the<br />
Customers database.<br />
• Lines 16-21 step through the results row by row and build an<br />
for each one.<br />
• Lines 22-24 close all open tags.<br />
Each includes an "onpick" attribute that will dial the selected<br />
number on most devices, via the "wtai://wp/mc;"<br />
URL scheme. This is a useful trick for all users to dial numbers from<br />
within your <strong>WML</strong>.<br />
The generated output resembles the following figure:<br />
Image courtesy Openwave Systems Inc. (Openwave, the Openwave<br />
logo, Openwave SDK, Openwave SDK Universal Edition, Openwave
SDK <strong>WAP</strong> Edition are trademarks of Openwave Systems Inc. All rights<br />
reserved.)<br />
Quite a few changes can be made to the code above:<br />
• The "order by" clause of the query can be changed to sort by a<br />
different field or by multiple fields.<br />
• Although the current layout allows each record to be displayed in its<br />
entirety, using the "wrap" option in the tags would allow more<br />
records to be displayed per screen (but each would have to be<br />
highlighted for the full record to be seen).<br />
• This example is convenient because there are only four rows in our<br />
database. To make our user interface more "friendly," we should<br />
create paging code to step through the data nine rows at a time<br />
(ensuring that each row has a quick access key [1-9] associated with<br />
it).<br />
<strong>Learning</strong> <strong>WML</strong> -Scripting Tips And Integration<br />
With PHP<br />
Project Description<br />
In our last article we implemented a simple phone list using PHP and<br />
MySQL. The basic premise was to give salespeople access to a central<br />
contact database without the need of synchronizing their<br />
phones/PDAs. The script pulled contact data from a database,<br />
displayed the records, and dialed the selected record. This project will<br />
expand on that example, adding the following features:<br />
• The contact database will include address information.<br />
• There will be more contacts, requiring the list to be displayed a few<br />
records at a time.<br />
• A search feature will be added.<br />
Because there will be more than one feature (list and search), we will<br />
also need a menu so the user can choose what feature he or she<br />
wants. Each screen will also need a way to return to the menu.<br />
PHP Script Outline<br />
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<br />
recursively calls itself with the proper value of $cmd, matching the<br />
function it is supposed to perform next. The script's functionality<br />
resembles the following:<br />
If $cmd is empty, set it to "Menu"<br />
If $cmd is set to "Menu," display the Menu card<br />
If $cmd is set to "List," display the List card<br />
If $cmd is set to "Display,"<br />
display the Display card, with the appropriate record<br />
If $cmd is set to "Search," display the Search card<br />
When a function is chosen,<br />
set $cmd appropriately and recursively call self.<br />
The search function will search through the database looking for first<br />
or last names that meet the text entered in the search card. The list<br />
function will display five records at a time and will include options for<br />
moving to the next set of five records or returning to the home menu.<br />
Each option (record or link) will have a quick key. Essentially, the list<br />
will resemble the following:<br />
1 <br />
2 <br />
3 <br />
4 <br />
5 <br />
6 [Next 5 link]<br />
7 [Home link]<br />
Selecting a record results in displaying all of the data associated with<br />
the record, along with "dial" and "home" links.<br />
The Contact List<br />
Our contact database will contain the following data:<br />
FirstName LastName Phone Address City State Zip LastUpdate<br />
Jack Hampton 317-555-2200 213 Main St Indianapolis IN<br />
46222 2002-08-22<br />
Samuel Marks 317-555-8764 2 Northridge Dr Fishers IN<br />
46203 2002-08-22<br />
Sally Nash 317-555-8765 644 Innovation Pl Ft Wayne IN<br />
46875 2002-08-22
Bill Haskins 317-555-8766 201 W 103rd Indianapolis<br />
IN 46240 2002-08-24<br />
Jill Payton 317-555-0098 55 W 96th St Westfield<br />
IN 46222 2002-08-22<br />
Mary Martinez 317-555-7544 9433 E. 75th Ave Greenwood<br />
IN 46784 2002-08-22<br />
Ned Tanner 317-555-9877 77 E Marchen Ft Wayne<br />
IN 46875 2002-08-23<br />
Bruce Wilten 317-555-1111 3 Prospect Indianapolis IN<br />
46038 2002-08-22<br />
Naomi Waters 317-555-4323 1121 Central Pl Westfield<br />
IN 46055 2002-08-22<br />
Angela Renault 317-555-0988 5674 E 6th Ave Noblesville<br />
IN 46234 2002-08-22<br />
Markus Elliot 317-555-3232 9755 Carter Indianapolis IN<br />
46250 2002-08-22<br />
Steve Albert 317-555-5444 95 Crescent Dr Indianapolis IN<br />
46250 2002-08-26<br />
Martin Rolfsen 317-555-6767 5678 E 7th Ave Indianapolis<br />
IN 46234 2002-08-22<br />
Lisa Biggins 317-555-3644 7732 Allisonville Indianapolis<br />
IN 46240 2002-08-22<br />
Eric Gonday 317-555-0500 9466 Pike Plaza Greenfield<br />
IN 46533 2002-08-22<br />
Douglas Poser 317-555-0123 55 Tower Pl Noblesville IN<br />
46234 2002-08-24<br />
John Palmer 317-555-4444 12433 N Cumberland Fishers<br />
IN 46038 2002-08-22<br />
The database will also contain an "ID" field as a primary key (integer).<br />
That will serve as a unique key into each record. To create the data in<br />
MySQL, the following commands should be used:<br />
CREATE DATABASE customers;<br />
USE customers;<br />
CREATE TABLE Phone (<br />
Id int(11) NOT NULL auto_increment,<br />
FirstName varchar(30) default NULL,<br />
LastName varchar(30) default NULL,<br />
Phone varchar(12) default NULL,<br />
Address varchar(30) default NULL,<br />
City varchar(30) default NULL,<br />
State char(2) default NULL,<br />
Zip varchar(5) default NULL,<br />
LastUpdate date default NULL,<br />
PRIMARY KEY (Id)<br />
) TYPE=MyISAM;<br />
INSERT INTO Phone VALUES (1,'Jack','Hampton','317-555-2200',<br />
'213 Main St','Indianapolis','IN','46222','2002-08-22');<br />
INSERT INTO Phone VALUES (2,'Samuel','Marks','317-555-8764',<br />
'2 Northridge Dr','Fishers','IN','46203','2002-08-22');
INSERT INTO Phone VALUES (3,'Sally','Nash','317-555-8765',<br />
'644 Innovation Pl','Ft Wayne','IN','46875','2002-08-22');<br />
INSERT INTO Phone VALUES (4,'Bill','Haskins','317-555-8766',<br />
'201 W 103rd','Indianapolis','IN','46240','2002-08-24');<br />
INSERT INTO Phone VALUES (5,'Jill','Payton','317-555-0098',<br />
'55 W 96th St','Westfield','IN','46222','2002-08-22');<br />
INSERT INTO Phone VALUES (6,'Mary','Martinez','317-555-7544',<br />
'9433 E 75th Ave','Greenwood','IN','46784','2002-08-22');<br />
INSERT INTO Phone VALUES (7,'Ned','Tanner','317-555-9877',<br />
'77 E Marchen','Ft Wayne','IN','46875','2002-08-23');<br />
INSERT INTO Phone VALUES (8,'Bruce','Wilten','317-555-1111',<br />
'3 Prospect','Indianapolis','IN','46038','2002-08-22');<br />
INSERT INTO Phone VALUES (9,'Naomi','Waters','317-555-4323',<br />
'1121 Central Pl','Westfield','IN','46055','2002-08-22');<br />
INSERT INTO Phone VALUES (10,'Angela','Renault','317-555-0988',<br />
'5674 E 6th Ave','Noblesville','IN','46234','2002-08-22');<br />
INSERT INTO Phone VALUES (11,'Markus','Elliot','317-555-3232',<br />
'9755 Carter','Indianapolis','IN','46250','2002-08-22');<br />
INSERT INTO Phone VALUES (12,'Steve','Albert','317-555-5444',<br />
'95 Crescent Dr','Indianapolis','IN','46250','2002-08-26');<br />
INSERT INTO Phone VALUES (13,'Martin','Rolfsen','317-555-6767',<br />
'5678 E 7th Ave','Indianapolis','IN','46234','2002-08-22');<br />
INSERT INTO Phone VALUES (14,'Lisa','Biggins','317-555-3644',<br />
'7732 Allisonville','Indianapolis','IN','46240','2002-08-22');<br />
INSERT INTO Phone VALUES (15,'Eric','Gonday','317-555-0500',<br />
'9466 Pike Plaza','Greenfield','IN','46533','2002-08-22');<br />
INSERT INTO Phone VALUES (16,'Douglas','Poser','317-555-0123',<br />
'55 Tower Pl','Noblesville','IN','46234','2002-08-24');<br />
INSERT INTO Phone VALUES (17,'John','Palmer','317-555-4444',<br />
'12433 N Cumberland','Fishers','IN','46038','2002-08-22');<br />
The Cards<br />
The following sections list the PHP code used for each function/card.<br />
Note that the <strong>WML</strong> and header code is taken care of by global<br />
statements-each card need only output the tags and<br />
everything in-between. The $cmd variable controls what function is<br />
executed, and hence, which card is displayed.<br />
Note: I've used "echo" commands in this script, but "print" commands<br />
would do just as well. Also, to increase the readability of the output<br />
I've added line feeds (via a variable, $lf, set to ASCII character 10) to<br />
most lines of output. I've chosen to append this variable to the "echo"<br />
statements for readability purposes--using "\n" in your "echo"
commands would do just as well but tends to clutter the information<br />
between the quotes. Also note the use of the entity "&" in the<br />
URLs instead of a straight ampersand ("&"). This is necessary to keep<br />
<strong>WML</strong> from assuming that the ampersand is the beginning of an entity<br />
name.<br />
Menu<br />
The menu function displays a simple select list, allowing the user to<br />
choose what function he or she wants to access:<br />
echo "\n";<br />
echo "".$lf;<br />
// Set up Select menu list<br />
echo "".$lf;<br />
// Go through results from Query, listing each as a CHOICE entry<br />
echo "";<br />
echo "List Contacts".$lf;<br />
echo "";<br />
echo "Search Contacts".$lf;<br />
// Close select<br />
echo "".$lf;<br />
// Close card<br />
echo "".$lf."".$lf;<br />
The code is straightforward, defining a simple list. Each<br />
in the list calls the current script, passing the appropriate<br />
value of $cmd. Note that we need to handle the case when $cmd is<br />
empty, which it will be when the script is first called. In the body, near<br />
the beginning of the script, we add the following line:<br />
if (empty($cmd)) { $cmd = "Menu"; }<br />
That ensures that if no command is given (via $cmd), the menu will be<br />
displayed.
List<br />
This is the meat of the script, displaying both the raw list as well as<br />
search results, five records at a time. (For reference after the listing,<br />
each line has been numbered.)<br />
1 // Construct appropriate *count* query<br />
2: $query = "select count(*) from Phone";<br />
3: if (!empty($search)) {<br />
4: $query = $query." where FirstName like \"%".$search."%\" or";<br />
5: $query = $query." LastName like \"%".$search."%\"";<br />
6: }<br />
7: $result = mysql_query($query,$link)<br />
8: or die("Query failed:$query");<br />
9: list($total_rows) = mysql_fetch_array($result);<br />
10: // Construct appropriate query<br />
11: $query = "select * from Phone";<br />
12: if (!empty($search)) {<br />
13: $query = $query." where FirstName like \"%".$search."%\" or";<br />
14: $query = $query." LastName like \"%".$search."%\"";<br />
15: }<br />
16: // Get first/next five records<br />
17: $query = $query." order by LastName limit ".$idx.",5";<br />
18: $result = mysql_query($query,$link)<br />
19: or die("Query failed:$query");<br />
20: // Advance DB index<br />
21: $next = $idx + 5;<br />
22: // Start card<br />
23: echo "\n";<br />
24: echo " ".$lf;<br />
25: // Display appropriate full/search heading<br />
26: if (empty($search)) {<br />
27: echo "Phone Book".$lf;<br />
28: } else {<br />
29: echo "Search Results".$lf;<br />
30: }<br />
31: // Set up Select list (list of five records)<br />
32: echo "".$lf;
33: // Go through results from Query, listing each as a CHOICE entry<br />
34: while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {<br />
35: $recordid = $line[Id];<br />
36: $Name = $line[LastName] . ", " . $line[FirstName];<br />
37: $Number = $line[Phone];<br />
38: $Prompt = $Name . " (" . $Number . ")";<br />
39: // Build URL for option, include DB index and search<br />
40: $option = "";<br />
43: $option = $option.$Prompt."".$lf;<br />
44: echo $option.$lf;<br />
45: }<br />
46: // If there are more records to display, set up paging<br />
47: // else mark end of list (to keep Home as same option)<br />
48: if ($total_rows >= $next) {<br />
49: // Link to next five<br />
50: echo "[Next Records]".$lf."".$lf;<br />
56: } else {<br />
57: echo "[End of List]".$lf."".$lf;<br />
64: }<br />
65: // Add option for Home
66: echo "".$lf;<br />
67: echo "[Back to Home]".$lf;<br />
68: echo "".$lf;<br />
69: // Close select<br />
70: echo "".$lf;<br />
71: // Close card<br />
72: echo "".$lf."".$lf;<br />
This code makes use of global variables ($idx, $search) to display the<br />
list of contacts. Both variables are passed as a name/value pair when<br />
the script is called with $cmd equal to "List." If $search is empty, the<br />
full list of contacts is displayed, else the text of $search is added to the<br />
query and records are returned only if FirstName or LastName contains<br />
the search text.<br />
The $idx variable marks what results the script currently is listing. The<br />
record at location $idx is the first record on the current page.<br />
This becomes more self-explanatory as we work through the code:<br />
Lines 1-9 construct and execute a "count" query, storing the number<br />
of returned rows in the variable $total_rows. This value is used later<br />
(line 48) to determine whether there are more pages of data to<br />
display. Note that the search text is added if $search is not empty<br />
(hence contains search criteria).<br />
Lines 10-19 construct a query to return the target dataset. Again, the<br />
search criterion is added to the query, if it exists. Line 17 appends a<br />
limit clause to the query, causing the query to return only five records<br />
(or less), starting at the record indicated by the value of $idx. If $idx<br />
is zero (which it will be the first time the script executes List), the first<br />
five records are returned. The variable $next is set to a value of $idx +<br />
5 (line 21) and used to call the next iteration of List, causing the next<br />
five records to be displayed.<br />
Note: This method is far from perfect. For example, if a record is<br />
added or modified that causes a record to be added or removed from<br />
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<br />
displayed while the records are being modified-which happens in most<br />
database applications-the displayed results can be somewhat<br />
unpredictable.<br />
Lines 22-30 begin the display card definition, including the appropriate<br />
header-"Phone Book" if the search string is empty (raw list being<br />
displayed) or "Search Results" if the search string is not empty (search<br />
results being displayed). This helps guarantee that the result set<br />
returned will be the same, allowing consistent paging through the set<br />
(with the caveat explained above).<br />
Line 32 begins the list, with lines 34-70 building and<br />
displaying five items as s. Lines 35-38 build the text for the<br />
prompt, while lines 39-44 construct the statement<br />
with an appropriate "onpick" parameter that recursively calls the script<br />
with $cmd equal to "Display" and the ID of the record to display. Note<br />
that $idx and $search are also passed to maintain their values through<br />
the recursive call, just in case we need them later.<br />
Lines 49-64 build option number 6 in our select list. If there are more<br />
records to display ($total_rows >= $next) the script generates the<br />
option "[Next Records]" with an appropriate "onpick" parameter to<br />
recursively call the script, specifying the next starting record to display<br />
(via idx=$next). If there are no more records to display, the script<br />
generates an "[End of List]" option, which recursively calls the script<br />
with the same starting point as is currently displayed. Each option also<br />
includes the search criteria if it exists (lines 51-54 and 58-61).<br />
Finally, a "Home" option is created (lines 65-68) to allow the user to<br />
return to the home menu from any page of the listing. The open tags<br />
are then closed.<br />
Search<br />
The search function is simply an input tag that accepts up to 10<br />
characters and recursively calls the script supplying the text entered<br />
and sets $cmd equal to "List."<br />
echo "\n";<br />
echo "".$lf;
echo "".$lf;<br />
echo "".$lf;<br />
echo "".$lf;<br />
echo "".$lf;<br />
echo "Phone Book SearchSearch for:".$lf;<br />
echo "";<br />
echo "".$lf;<br />
echo "".$lf;<br />
Display<br />
Display uses the record ID passed in $idx to select a record from the<br />
database and display all of its related information (name, address,<br />
phone, etc.).<br />
echo "\n";<br />
// Get specific record<br />
$query = "select * from Phone where Id = \"".$idx."\"";<br />
$result = mysql_query($query,$link)<br />
or die("Query failed:$query");<br />
// Get data and display<br />
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {<br />
$recordid = $line[Idx];<br />
}<br />
echo "".$lf;<br />
echo "$line[LastName], $line[FirstName]".$lf;<br />
echo "$line[Address]".$lf;<br />
echo "$line[City], $line[State] $line[Zip]".$lf;<br />
echo "";<br />
echo "$line[Phone]".$lf;<br />
echo "";<br />
echo "[Home Menu]".$lf;<br />
$Date = date("M j, Y", strtotime($line[LastUpdate]));<br />
echo "Record Updated:$Date";<br />
// Close record display card<br />
echo $lf."".$lf;
Note that the card has a "Dial" option mapped to the Accept key and is<br />
displayed with the phone number highlighted. This allows the user to<br />
quickly dial the selected number on devices that support URL dialing.<br />
Other items of note include a "Home Menu" link and a more verbose<br />
format for the last update date. We do not need to provide any<br />
functionality to return to the last page of record listings-the user can<br />
do so by pressing the Back key on his or her device.<br />
The Entire Script<br />
Now that we've defined the various functions of the script, let's tie it<br />
all together with a "switch" statement and some additional initialization<br />
statements:<br />
if (empty($idx)) { $idx = 0; }<br />
if (empty($cmd)) { $cmd = "Menu"; }<br />
switch ($cmd) {<br />
case "Menu";<br />
echo "\n";<br />
echo "".$lf;<br />
// Set up Select menu list<br />
echo "".$lf;<br />
// Go through results from Query, listing each as a CHOICE entry<br />
echo "";<br />
echo "List Contacts".$lf;<br />
echo "";<br />
echo "Search Contacts".$lf;<br />
// Close select<br />
echo "".$lf;<br />
// Close card<br />
echo "".$lf."".$lf;<br />
break;<br />
case "List";<br />
// Construct appropriate *count* query<br />
$query = "select count(*) from Phone";<br />
if (!empty($search)) {<br />
$query = $query." where FirstName like \"%".$search."%\" or";<br />
$query = $query." LastName like \"%".$search."%\"";<br />
}<br />
$result = mysql_query($query,$link)<br />
or die("Query failed:$query");<br />
list($total_rows) = mysql_fetch_array($result);<br />
// Construct appropriate query<br />
$query = "select * from Phone";<br />
if (!empty($search)) {<br />
$query = $query." where FirstName like \"%".$search."%\" or";<br />
$query = $query." LastName like \"%".$search."%\"";<br />
}
Get first/next five records<br />
$query = $query." order by LastName limit ".$idx.",5";<br />
$result = mysql_query($query,$link)<br />
or die("Query failed:$query");<br />
// Advance DB index<br />
$next = $idx + 5;<br />
// Start card<br />
echo "\n";<br />
echo " ".$lf;<br />
// Display appropriate full/search heading<br />
if (empty($search)) {<br />
echo "Phone Book".$lf;<br />
} else {<br />
echo "Search Results".$lf;<br />
}<br />
// Set up Select list (list of five records)<br />
echo "".$lf;<br />
// Go through results from Query, listing each as a CHOICE entry<br />
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {<br />
}<br />
$recordid = $line[Id];<br />
$Name = $line[LastName] . ", " . $line[FirstName];<br />
$Number = $line[Phone];<br />
$Prompt = $Name . " (" . $Number . ")";<br />
// Build URL for option, include DB index and search<br />
$option = "";<br />
$option = $option.$Prompt."".$lf;<br />
echo $option.$lf;<br />
// If there are more records to display, set up paging<br />
// else mark end of list (to keep Home as same option)<br />
if ($total_rows >= $next) {<br />
// Link to next five<br />
echo "
Pass Search criteria if exists<br />
if (!empty($search)) {<br />
echo "&search=".$search;<br />
}<br />
echo "\">[Next Records]".$lf."".$lf;<br />
} else {<br />
}<br />
echo "[End of List]".$lf."".$lf;<br />
// Add option for Home<br />
echo "".$lf;<br />
echo "[Back to Home]".$lf;<br />
echo "".$lf;<br />
// Close select<br />
echo "".$lf;<br />
// Close card<br />
echo "".$lf."".$lf;<br />
break;<br />
case "Search";<br />
echo "\n";<br />
echo "".$lf;<br />
echo "".$lf;<br />
echo "".$lf;<br />
echo "".$lf;<br />
echo "".$lf;<br />
echo "Phone Book SearchSearch for:".$lf;<br />
echo "";<br />
echo "".$lf;
echo "".$lf;<br />
break;<br />
case "Display";<br />
echo "\n";<br />
// Get specific record<br />
$query = "select * from Phone where Id = \"".$idx."\"";<br />
$result = mysql_query($query,$link)<br />
or die("Query failed:$query");<br />
// Get data and display<br />
while ($line = mysql_fetch_array($result, MYSQL_ASSOC)) {<br />
$recordid = $line[Idx];<br />
}<br />
echo "".$lf;<br />
echo "$line[LastName], $line[FirstName]".$lf;<br />
echo "$line[Address]".$lf;<br />
echo "$line[City], $line[State] $line[Zip]".$lf;<br />
echo "";<br />
echo "$line[Phone]".$lf;<br />
echo "";<br />
echo "[Home Menu]".$lf;<br />
$Date = date("M j, Y", strtotime($line[LastUpdate]));<br />
echo "Record Updated:$Date";<br />
// Close record display card<br />
echo $lf."".$lf;<br />
break;<br />
}<br />
echo $lf."".$lf;<br />
mysql_close($link);<br />
?>
Note that we pass a handful of headers at the beginning of the script<br />
to inhibit caching. Since our database is frequently updated and<br />
correct/up-to-date information in the field is valuable, we do not want<br />
the device to display cached information instead of recently updated<br />
information. However, generally speaking, inhibiting the cache is a bad<br />
idea and should be done sparingly, if at all.<br />
The Script in Action<br />
Now let's see the script in action. The following figures demonstrate<br />
each function:<br />
Note: All images courtesy Openwave Systems Inc. (Openwave, the<br />
Openwave logo, Openwave SDK, Openwave SDK Universal Edition,<br />
Openwave SDK <strong>WAP</strong> Edition are trademarks of Openwave Systems<br />
Inc. All rights reserved.)<br />
Figure 1 - The menu.
Figure 2 - The list. Notice the [Next Records] option.<br />
Figure 3 - The end of the list. Notice the [End of List] option.
Figure 4 - The search form.<br />
Figure 5 - The results of a search (for "in"). Note the first result<br />
is "Biggins, Lisa" but the text has scrolled to the phone number<br />
due to the "nowrap."
Figure 6 - A record in the display card.<br />
Room for Improvement<br />
This script has plenty of room for improvement, including the following<br />
items:<br />
• Applying the cache inhibitor headings only to cards that could cause<br />
problems (such as Display), instead of globally.<br />
• Optimizing the output to avoiding duplicating code (such as the<br />
addition of search criteria to the URL(s)).<br />
• Adding more prompts for the user through card titles, etc.<br />
• Optimizing and standardizing variable naming and usage.<br />
• Providing means for the user to edit records. Although it can be<br />
tedious to enter data on most mobile devices, simple corrections or<br />
notes would be welcome. A "last called" field could also be entered<br />
automatically each time a contact is called.<br />
This script represents only a small portion of what can be done with<br />
PHP and a database such as MySQL. This example could be expanded<br />
to offer group calendaring, scheduling, order placement, stock<br />
checking, etc. As long as you keep the target audience and the<br />
respective design goals in mind, the sky's the limit.
Delivering HTML To a <strong>WML</strong> Device<br />
This series of articles describes how to provide Web content to mobile<br />
devices through <strong>WML</strong> (Wireless Markup Language). This article covers<br />
techniques to use when delivering standard HTML to <strong>WML</strong>-compatible<br />
devices.<br />
Note: These articles cover <strong>WML</strong> and <strong>WML</strong>Script version 1.1, which are<br />
supported by the majority of mobile devices in use today. The articles<br />
assume a working knowledge of HTML and general Web technologies,<br />
and further assume that you have read the previous article(s) in this<br />
series.<br />
Delivering Converted HTML<br />
There may be several reasons why you may need to deliver standard<br />
HTML markup text to a <strong>WML</strong>-compatible device. You may have data<br />
stored in a database that is typically displayed in a standard browser;<br />
legacy data or pages that resist conversion; or cross-platform text that<br />
needs to be primarily available for a standard HTML browser, but<br />
would be useful if delivered to <strong>WML</strong> clients.<br />
For example, I'm the administrator for a movie news Web site. The<br />
articles for the site are marked up using standard HTML, stored in a<br />
SQL database, and delivered to the clients using PHP pages. The bitesized<br />
articles also make for great wireless content_something that can<br />
be browsed while in an airport or during other downtime_so I decided<br />
to make this content available in <strong>WML</strong>. Unfortunately, I quickly found<br />
out how incompatible even minor HTML tags are with <strong>WML</strong>, creating<br />
the need for some simple conversion procedures. Although not perfect,<br />
those procedures are used as the basis for this article.<br />
Tip: The quick-and-dirty methods described in this article are handy as<br />
a temporary or short-term measure. If you intend to support a<br />
particular platform long-term, I recommend creating custom code for<br />
that platform.<br />
Standard HTML vs. <strong>WML</strong><br />
Standard HTML documents don't work well on <strong>WML</strong>-capable devices.
Even if the wireless services offer translation services through their<br />
gateways, standard HTML seldom displays as the developer or user<br />
would like.<br />
Limited Tags<br />
<strong>WML</strong> supports a very limited subset of HTML tags. Among those<br />
supported are the following tags:<br />
• Character formatting<br />
◦ - Bold<br />
◦ - Italic<br />
◦ - Underline<br />
◦ - Big text<br />
◦ - Small text<br />
◦ - Strong (visually emphasized) text<br />
◦ - Paragraph<br />
• Table tags<br />
◦ <br />
◦ - Table row<br />
◦ - Table column/cell<br />
Several tags nearly alike between the two languages, but their<br />
formatting and/or parameters are different enough to cause problems.<br />
For example, the line break tag is simply in HTML, but in<br />
<strong>WML</strong>. Also, tags such as the table tags support many more options and<br />
parameters in HTML than in <strong>WML</strong>, and rarely allow HTML tables to<br />
display properly in <strong>WML</strong>.<br />
Device Display Limitations<br />
Standard HTML documents are generally designed for large displays,<br />
such as 800 x 600 resolution CRTs connected to a PC, not a 240 x 320<br />
LCD on a PDA (or smaller, if a cell phone). Even devices that run<br />
HTML-compliant browsers (such as IE in Windows CE devices) have<br />
problems with the majority of today's Web sites.
The almost unrecognizable internet.com home page, displayed in IE on<br />
a Pocket PC (Windows CE).<br />
Tip: To gauge roughly how a page will look on a smaller device, shrink<br />
your standard PC browser window down to that size.<br />
Most mobile devices don't support the vast array of text formatting<br />
available toPC browsers. For example, earlier versions of certain<br />
mobile browsers don't support underlining; others don't support italic<br />
or bold text. Tables are especially problematic due to their width.<br />
Device memory is also a problem. Most mobile browsers only support<br />
pages (decks) a few kilobytes in size, requiring the content to be<br />
broken down into bite-sized chunks and displayed across several<br />
cards, if not several decks.
Finally, most modern PC-based browsers (IE, Mozilla, Netscape, and so<br />
on) have built-in logic to handle incomplete or misused tags. For<br />
example, most PC browsers are forgiving of HTML documents that fail<br />
to close a major element such as a table or the body of the document.<br />
Most mobile browsers are far less forgiving, requiring very strict use of<br />
tags.<br />
Device Input Limitations<br />
Interactive Web pages present even more challenges to the mobile<br />
user. Anyone who has needed to tap/write out even a short note on a<br />
PDA can appreciate the need to keep interfaces simple. Those who<br />
have tried to compose more than just a few characters on a standard<br />
cell phone keypad can appreciate this even more strongly.<br />
The simplest Web interface is the form, whose structure is<br />
considerably different in <strong>WML</strong>. Simply converting the structure and<br />
tags isn't sufficient; you also have to consider how it will affect the end<br />
user on his or her individual platform. For example, choosing the<br />
correct state code from a drop-down list is easy on a standard<br />
browser. However, drop-down lists translate to select lists in <strong>WML</strong>,<br />
necessitating a list of 50 entries that the user must scrolled through<br />
(usually 9 items per page) to select the proper code.<br />
When Is Converting Worth the Effort?<br />
Given the discussion above, there are a few HTML-to-<strong>WML</strong> conversions<br />
that are more problematic than they are worth:<br />
• Tables<br />
Unless you know that every table in the document is extremely<br />
narrow and contains no fancy formatting/parameters, you should<br />
simply remove the tags.<br />
• Graphics<br />
Some gateways will convert graphic files into the prerequisite WBMP<br />
format. However, most will simply refuse to display the standard<br />
JPG/GIF/PNG Web formats. Unless you have the appropriate<br />
graphics available in the WBMP format, remove the graphics.<br />
• Code<br />
HTML pages that rely on Java, JavaScript, or some other scripting
language generally will not be compatible with mobile devices,<br />
especially those compatible only with <strong>WML</strong>. Devices using IE (such<br />
as CE-equipped PDAs) will fare much better, but you can't rely on<br />
that.<br />
In short, only textual pages are worth the time to convert. More<br />
complex pages should be redesigned for each individual platform you<br />
want to support. Keep in mind that straight <strong>WML</strong> does not have the<br />
facility to convert HTML--you must use a CGI or PHP script to deliver<br />
the content instead.<br />
Note: See the two previous articles on how to integrate PHP into your<br />
<strong>WML</strong> delivery.<br />
Conversion Procedures<br />
Converting standard HTML-formatted text is a two-step process. First,<br />
remove any tags that are not supported by the target platform.<br />
Second, tailor supported tags to the target platform. For example, the<br />
line break tag is supported by <strong>WML</strong>, but needs to have the slash added<br />
("").<br />
Removing Unsupported Tags<br />
To ensure a smooth conversion, remove all but the following tags from<br />
the HTML code:<br />
• <br />
• <br />
• <br />
You can also retain text-formatting tags that your target browser<br />
supports, such as , , etc.<br />
If you are using PHP, the code to strip the offending tags is very<br />
simple:<br />
$wml = strip_tags($html,'');<br />
Using the HTML (stored in $html), the above code removes all tags but<br />
those given in the "strip_tags" parameter, and stores the result in the
variable $wml.<br />
If you are feeling adventurous and know the format of tables in the<br />
code, you can parse the table tags down to the bare minimum<br />
parameters (as supported by your target browser). However, only the<br />
smallest tables will display conveniently on mobile devices.<br />
Converting Supported Tags<br />
Although paragraph () and line break () tags are supported<br />
in <strong>WML</strong>, their usage varies from that in HTML. For example, blocks of<br />
text must be enclosed in paragraph tags; you cannot use a stray tag<br />
to separate paragraphs, like this:<br />
Paragraph . . .<br />
<br />
Paragraph . . .<br />
Although such use is sloppy when used anywhere, it has become<br />
prevalent in HTML pages. Instead of creating a sophisticated parsing<br />
scheme to ensure the matching pairs of tags, it's much easier to<br />
convert all open and closing paragraph tags to double line break tags.<br />
This causes the current line to break where the paragraph tag was<br />
used, and inserts the extra space between the paragraphs.<br />
Again, if you are using PHP, the code is straightforward:<br />
$wml = str_replace("","",$wml);<br />
$wml = str_replace("","",$wml);<br />
The above code will replace every "" and "" with "".<br />
Each line break tag in <strong>WML</strong> must end in a slash. A similar PHP<br />
str_replace statement takes care of this requirement:<br />
$wml = str_replace("","",$wml);
Note: PHP functions that support regular expressions can be more<br />
versatile and can do more work per statement if constructed correctly.<br />
I prefer to use individual statements for later flexibility and clearer<br />
code.<br />
Miscellaneous Cleanup<br />
Two more items need to be cleaned up to display correctly in <strong>WML</strong>:<br />
ampersands ("&") and dollar signs ("$"). An ampersand must be<br />
converted to an entity ("&"), and a dollar sign must be doubled<br />
("$$").<br />
Again, in PHP you can use the str_replace function:<br />
$wml = str_replace("&","&",$wml);<br />
$wml = str_replace("$","$$",$wml);<br />
Note: An abundance of special characters can find their way into<br />
otherwise mundane HTML code. For example, when text is cut-andpasted<br />
from a word processing document into HTML documents, single<br />
and double quotes usually appear as extended ASCII characters, and<br />
must be converted to the appropriate plain text characters or HTML<br />
entities. Only direct experience and experimentation with your specific<br />
documents can determine what problems you may have and need to<br />
work around.<br />
Interactive Fun and Games with <strong>WAP</strong> and<br />
<strong>WML</strong><br />
This series of articles describes how to provide Web content to mobile<br />
devices through <strong>WML</strong> (Wireless Markup Language). This article covers<br />
creating an interactive game for deployment on mobile <strong>WML</strong> devices.<br />
Note: These articles cover <strong>WML</strong> and <strong>WML</strong>Script version 1.1, which are<br />
supported by the majority of mobile devices in use today. The articles<br />
assume a working knowledge of HTML and general Web technologies,<br />
and further assume that you have read the previous article(s) in this<br />
series.
Uses for Mobile Devices<br />
Mobile devices are the most useful when they are connected to data<br />
sources and have the ability to deliver various data whenever needed.<br />
However, mobile devices are also very useful for entertainment<br />
purposes-I've spent many hours in airports with only my PDA and<br />
phone for company. Although <strong>WML</strong> doesn't lend itself to a complex<br />
gaming experience, it is fairly easy to create interactive entertainment.<br />
In this article I will lead you through the steps to create a rudimentary<br />
"hangman" game.<br />
Project Specifications<br />
Hangman is a straightforward game. For anyone unfamiliar with the<br />
game, a word is shown as blanks and the player guesses letters that<br />
may be in the word. If a letter guessed is in the word, all instances of<br />
that letter are revealed. If a guessed letter is not in the word, part of a<br />
hanging stick figure is drawn on a scaffold, at the end of a<br />
noose_usually starting with the head, then the body, then the limbs<br />
(one at a time). The game ends when the word is guessed or the<br />
figure is completely drawn. In the former case the player wins, in the<br />
latter the player loses.<br />
The functionality for our project is as simple as the pen-and-paper<br />
version of the game:<br />
1 A word is chosen and displayed as blanks.<br />
2 The player picks a letter.<br />
3 The letter is compared to the letters in the word; if the letter is in<br />
the word, the appropriate blanks are changed to display the letter.<br />
If the letter is not in the word, a piece is added to the hanging stick<br />
figure.<br />
4 The game ends when the user knows the word or the figure is<br />
complete.<br />
Once the functionality has been determined, the interface must be<br />
drafted.<br />
• All output must fit on a display that is approximately 16 characters<br />
wide and 4-10 lines tall.<br />
• The stick figure will be represented by ASCII characters instead of<br />
graphics since we can't ensure that the player will be using a
graphics-capable device. The figure will be made of 6 segments,<br />
giving the player 6 letter selections.<br />
• Several cards will be used, one for each function. That will help<br />
segregate the functions and keep the display lean.<br />
• The words must be random and taken from a decent-sized list to<br />
keep the player engaged.<br />
• To simplify coding, all words will be in lowercase and the player's<br />
input will be forced to lowercase.<br />
Coding the <strong>Basics</strong><br />
This project will utilize <strong>WML</strong> for the input and output and <strong>WML</strong>Script<br />
for the behind-the-scenes processing. Because debugging tools for<br />
<strong>WML</strong>Script are limited, we will build this project in small stages, adding<br />
features only after the current feature set is solid.<br />
To start, we will create a simple deck of two cards. The first card will<br />
call a <strong>WML</strong>Script function to initialize the game's variables. The second<br />
card will then be displayed, showing the word as blanks and as plain<br />
text (for debugging purposes).<br />
Our skeletal code looks like this:<br />
<strong>WML</strong><br />
1 <br />
2 <br />
4 <br />
5 <br />
6 <br />
7 <br />
8 <br />
9 <br />
10 <br />
11 <br />
13 Initializing...<br />
14
15 <br />
16 <br />
17 <br />
18 $word<br />
19 $blank<br />
20 <br />
21 <br />
22 <br />
<strong>WML</strong>S<br />
1 extern function initword() {<br />
2 // Define word list (pseudo array)<br />
3 var words = "animal announce banana doctor elephant giraffe";<br />
4 var idx,x = 0;<br />
5 var blank,word = "";<br />
6 // Randomize a word<br />
7 idx = Lang.random(6);<br />
8 word = String.elementAt(words,idx," ");<br />
9 // Add an "*" for every letter in the chosen word<br />
10 for (x = 1; x
pushed out to the browser and the next card is displayed, showing all<br />
the values.<br />
Note: Even this part of the script was developed in pieces, though for<br />
the sake of brevity in this article I've chosen to start with this chunk of<br />
code. Initially, I created the stub <strong>WML</strong> code to call the init function,<br />
which simply set a variable. Then I created the word list and<br />
randomized a word, which was displayed by the <strong>WML</strong>. Finally, I<br />
created the blanking code to create the blank word.<br />
Tip: The Openwave SDK provides a great environment to develop<br />
applications in a stairstep method. Visit the developer site at<br />
www.openwave.com for more information or to download the SDK.<br />
Next we need to add the player entry code and the ability to check the<br />
entry against the word. The new code resembles the following listings:<br />
<strong>WML</strong><br />
1 <br />
2 <br />
4 <br />
5 <br />
6 <br />
7 <br />
8 <br />
9 <br />
10 Initializing...<br />
11 <br />
12 <br />
13 <br />
14 <br />
15 <br />
16 <br />
17 <br />
18 Guess: <br />
19 $word<br />
20 $blank<br />
21 <br />
22 <br />
23
<strong>WML</strong>S<br />
1 extern function initword() {<br />
2 var words = "animal announce banana doctor elephant giraffe";<br />
3 var idx,x,hang = 0;<br />
4 var blank,word = "";<br />
5 idx = Lang.random(6);<br />
6 word = String.elementAt(words,idx," ");<br />
7 for (x = 1; x
the new $blank variable.<br />
Image courtesy Openwave Systems Inc. (Openwave, the Openwave<br />
logo, Openwave SDK, Openwave SDK Universal Edition, Openwave<br />
SDK <strong>WAP</strong> Edition are trademarks of Openwave Systems Inc. All rights<br />
reserved.)<br />
The new code allows for input and displays debugging output. Note<br />
that "a" was guessed in the previous round, revealing the 3 a's in<br />
"banana." Because the $guess variable is persistent, the value sticks in<br />
the input field. We will have to fix that eventually.<br />
Adding the Scoring (Hangman)<br />
As Figure 8.1 shows, we are quickly running out of screen real estate<br />
for our game. Since we are using text to depict our hanging man, we<br />
will need 4-6 more lines to display him - and we obviously do not have<br />
enough lines. Time for an interface change.<br />
Instead of putting the input on the same page as the status, we will<br />
move it to a "guess" card. It means more work for the user (he or she<br />
must select a "guess" button to display the input), but cleans up our<br />
interface.<br />
For the hanging man, we will use the following ASCII representation:
0<br />
-|-<br />
/ \<br />
He is crude but recognizable. We can break the man down into six<br />
pieces: the head, torso, two arms, and two legs. If we get creative and<br />
treat each piece as a string, we can add appropriate line breaks and<br />
print each string in sequence, effectively "building" the man. Consider<br />
the following:<br />
Head: "0"<br />
Arm1: "-"<br />
Torso: "|"<br />
Arm2: "-"<br />
Leg1: "/"<br />
Leg2: "\"<br />
When printed in sequence, the hanging man is displayed. If we print<br />
only the first three strings, only three pieces of the man (head, arm,<br />
torso) are displayed.<br />
Again, because <strong>WML</strong>S lacks real arrays, we will build a delimited string<br />
that contains all the pieces:<br />
" , 0\r,-,|,-\r,/,\\\r"<br />
Note that a comma is used as the delimiting character, and that we<br />
put a blank piece (a space) in the beginning to give the first real piece<br />
an index of 1 instead of 0. Also note that we use escape codes for line<br />
breaks (\r = newline) and we must escape the backslash. Because<br />
<strong>WML</strong> doesn't parse variables, using <strong>WML</strong> code for line breaks would<br />
only result in the tags (such as "") being displayed as text<br />
instead of being interpreted as line breaks.<br />
Finishing Up
Although it seems like there is a lot left to do, the project is mopped<br />
up pretty quickly by doing the following:<br />
• Adding a variable and code to count the incorrect guesses and<br />
determine loss<br />
• Checking whether the word has been totally revealed (win)<br />
• Adding appropriate cards for win/loss<br />
• Building a string variable to display the hanging man<br />
• Various housekeeping and cleanup chores (renaming some<br />
functions, cleaning up some variables, adding comments, etc.)<br />
Most of these tasks revolve around counting the number of guesses.<br />
The last task is the usual housekeeping that takes place at the end of<br />
a project. The final code looks like this:<br />
<strong>WML</strong><br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Initializing...<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
|<br />
$man<br />
$blank<br />
<br />
<br />
<br />
<br />
<br />
<br />
$blank<br />
Guess: <br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
You've been hanged!<br />
Word was:<br />
$word<br />
Your guess:<br />
$blank<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
You win!<br />
Word was:<br />
$word<br />
<br />
<br />
<br />
<strong>WML</strong>S<br />
// hangman.wmls<br />
// Functions for hangman game
Variables:<br />
// word = word to be guessed<br />
// blank = blank representation of 'word', letters<br />
// begin as asterisks ("*") and are revealed as guessed<br />
// hang = current count of incorrect guesses<br />
// man = current ASCII representation of hanging man<br />
// guess = current character guessed<br />
// Initialize game<br />
extern function init() {<br />
// Init vars. Change words every so often (or add to them)<br />
var words = "animal announce banana doctor elephant giraffe";<br />
var idx,x,hang = 0;<br />
var blank,word,man = "";<br />
// Pick a random word from list<br />
idx = Lang.random(6);<br />
word = String.elementAt(words,idx," ");<br />
// Build a blank string (letters all "*") that<br />
// is the same length as our word<br />
for (x = 1; x
Get current values<br />
var word = <strong>WML</strong>Browser.getVar("word");<br />
var hang = <strong>WML</strong>Browser.getVar("hang");<br />
var blank = <strong>WML</strong>Browser.getVar("blank");<br />
var guess = <strong>WML</strong>Browser.getVar("guess");<br />
// Walk one character at a time through word<br />
// If guess = character, reveal character<br />
// If guess != character, keep current value<br />
// (revealed character or blank)<br />
// Also, set "correct" if at least one char found<br />
for (x = 0; x
} else {<br />
<strong>WML</strong>Browser.go("hangman.wml#status");<br />
}<br />
}<br />
}<br />
The starting screen of the game, complete with hangman's noose.<br />
Pressing the Accept key brings up the guessing card.
As the player progresses, correct guesses are shown by revealing<br />
characters in the word; incorrect guesses add to the hanging man.<br />
Images are courtesy Openwave Systems Inc. (Openwave, the<br />
Openwave logo, Openwave SDK, Openwave SDK Universal Edition,<br />
Openwave SDK <strong>WAP</strong> Edition are trademarks of Openwave Systems<br />
Inc. All rights reserved.)<br />
If the player guesses six incorrect letters, the game ends. Guessing all
characters in the word, conversely, shows a "win" screen. Note the<br />
"Restart" function mapped to Accept.<br />
Things to Add<br />
There's always room for improvement, especially in programming and<br />
interfaces. Given time, I'd add the following to the game:<br />
• A graphic version of the hanging man as a default, allowing users<br />
to switch to text if necessary.<br />
• Tracking and displaying the incorrect characters guessed<br />
• A splash screen (introductory graphical screen), help text, and a<br />
cheat function (displays one character not yet revealed)<br />
I'd also lengthen the word list and dynamically change it from time to<br />
time. One idea would be to run a script on the server that swaps<br />
different versions of the <strong>WML</strong>S file into place, each with a different<br />
word list. Another idea is to implement the entire game using PHP or<br />
another, more robust scripting language that could tie into a massive<br />
word database.<br />
What Do You Want from <strong>WML</strong>?<br />
I'm interested in hearing what you need/want to do with <strong>WML</strong>. I'll use<br />
some of the more challenging or common ideas in upcoming articles.<br />
Send your ideas to the address below.<br />
Building <strong>WML</strong> Gadgets: World Time Clock<br />
Review<br />
As mentioned in the last few articles, it is possible to add value to a<br />
mobile device by creating a small but ultimately useful application. In<br />
designing such an application, remember that the user will need to be<br />
online to use it, so the application's utility needs to be weighed against<br />
the potential cost of use.<br />
Note: Most mobile service plans offer a base amount of online time<br />
dedicated to "Web" use. So the user generally isn't paying more for<br />
the occasional gadget use.
World Time Application<br />
This article describes how to create another useful small application: a<br />
world time clock. This clock tells the time in prominent time zones and<br />
areas around the world. If you need to make a call to Sydney,<br />
Australia, for example, it would be nice to know if you are in danger of<br />
waking someone up, or are calling during the lunch hour.<br />
Our world time clock should accomplish the following:<br />
• Allow the user to search for a given time zone or area<br />
• Display the accurate time for the zone(s) found<br />
• Be easy to use, but complete enough to be useful<br />
More Help from CGI<br />
We could utilize only mobile technologies (<strong>WML</strong> and <strong>WML</strong>Script) to<br />
accomplish our goals. However, we'd have to do the following:<br />
• Find a comprehensive time zone database<br />
• Parse the database into an array in <strong>WML</strong>Script<br />
• Do time and date calculations to determine the time in various zones<br />
from the resulting data<br />
At first, this approach doesn't seem too bad. There are a finite number<br />
of time zones worldwide. However, you also have to take daylight<br />
saving time into account. For example, central Indiana (which includes<br />
Indianapolis) does not observe daylight savings. This means that<br />
Indiana, geographically in the Central time zone, seemingly bounces<br />
between Central and Eastern time zones. (In reality, central Indiana<br />
stays on Eastern Standard Time, and the zones around it shift.)<br />
A comprehensive open source database exists that can be used for this<br />
purpose. The database, often referred to as the tz or zoneinfo<br />
database, is packaged with most implementation of the GNU C Library,<br />
primarily Linux installations. Several tools are available for reading the<br />
data from the database, including a Perl module, Time::Timezone<br />
(available from CPAN, www.cpan.org).<br />
Note: As with previous articles, teaching Perl is out of the scope of this<br />
series. There are numerous sources on the Internet for learning Perl,
including the tutorial at<br />
http://wdvl.internet.com/Authoring/Languages/Perl/PerlfortheWeb/toc.<br />
html.<br />
Starting the Script -- Reading Time Zones and Telling Time<br />
To start the script, we will simply output the time zones and the<br />
current time for each. The simple script shown below accomplishes this<br />
objective:<br />
Listing: tztest.pl<br />
#!/usr/bin/perl<br />
use Time::ZoneInfo ':all';<br />
my $zones = Time::ZoneInfo->new();<br />
foreach my $zone ($zones->zones) {<br />
print $zone."\n";<br />
}<br />
This script simply creates a list of all the available zones and outputs<br />
each to the console. Next, we need to know what time it is in each<br />
zone. Fortunately, the operating system (Linux, in this case) can do<br />
the work for us.<br />
Most Linux applications that need to tell time do so by referencing the<br />
environment variable TZ. This variable contains the current time zone<br />
for the system. The OS can use this variable to decode its internal time<br />
clock into the correct value for the zone. If you change this variable<br />
and request the time from an application that uses it, you can easily<br />
tell the time in another zone.<br />
With help from the Date::Calc module, we can add the current time<br />
zone to our test:<br />
Listing: tztest.pl<br />
#!/usr/bin/perl
use Time::ZoneInfo ':all';<br />
use Date::Calc ':all';<br />
my $zones = Time::ZoneInfo->new();<br />
foreach my $zone ($zones->zones) {<br />
$ENV{TZ} = $zone;<br />
($year,$month,$day,$hour,$min,$sec, $doy,$dow,$dst) =<br />
System_Clock();<br />
print $zone.":";<br />
print $year."-".$month."-".$day." ";<br />
print $hour.":".$min.":".$sec."\n";<br />
}<br />
Sample tztest.pl output:<br />
. . .<br />
America/New_York: 2002-12-29 23:35:22<br />
America/Detroit: 2002-12-29 23:35:22<br />
America/Louisville: 2002-12-29 23:35:22<br />
America/Kentucky/Monticello: 2002-12-29 23:35:22<br />
America/Indianapolis: 2002-12-29 23:35:22<br />
America/Indiana/Marengo: 2002-12-29 23:35:22<br />
America/Indiana/Knox: 2002-12-29 23:35:22<br />
America/Indiana/Vevay: 2002-12-29 23:35:22<br />
America/Chicago: 2002-12-29 22:35:22<br />
America/Menominee: 2002-12-29 22:35:22<br />
America/North_Dakota/Center: 2002-12-29 22:35:22<br />
America/Denver: 2002-12-29 21:35:22<br />
America/Boise: 2002-12-29 21:35:22<br />
America/Shiprock: 2002-12-29 21:35:22<br />
America/Phoenix: 2002-12-29 21:35:22<br />
America/Los_Angeles: 2002-12-29 20:35:22<br />
America/Anchorage: 2002-12-29 19:35:22<br />
America/Juneau: 2002-12-29 19:35:22<br />
America/Yakutat: 2002-12-29 19:35:22<br />
America/Nome: 2002-12-29 19:35:22<br />
America/Adak: 2002-12-29 18:35:22
. . .<br />
Note that we simply change the TZ environment variable and call the<br />
System_Clock method. The variable is only changed within the<br />
application space, so the change in time zone doesn't affect the whole<br />
system--just our application.<br />
Searching for Time Zones<br />
Next, we need the ability to search for a particular time zone. Adding a<br />
variable, "findzone", and a simple substring search accomplishes this<br />
goal:<br />
Listing: tztest.pl<br />
#!/usr/bin/perl<br />
use Time::ZoneInfo ':all';<br />
use Date::Calc ':all';<br />
my $findzone = (shift @ARGV);<br />
my $zones = Time::ZoneInfo->new();<br />
foreach my $zone ($zones->zones) {<br />
if ((index uc($zone), uc($findzone)) != -1) {<br />
$ENV{TZ} = $zone;<br />
($year,$month,$day,$hour,$min,$sec, $doy,$dow,$dst) =<br />
System_Clock();<br />
print $zone.":";<br />
print $year."-".$month."-".$day." ";<br />
print $hour.":".$min.":".$sec."\n";<br />
}<br />
}<br />
Note the use of the IF statement. We uppercase both the zone and the<br />
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<br />
entry:<br />
./tztest.pl pacific<br />
displays the following:<br />
Pacific/Easter: 2002-12-29 23:40:20<br />
Pacific/Galapagos: 2002-12-29 22:40:20<br />
Pacific/Yap: 2002-12-30 14:40:20<br />
Pacific/Truk: 2002-12-30 14:40:20<br />
Pacific/Ponape: 2002-12-30 15:40:20<br />
Pacific/Kosrae: 2002-12-30 15:40:20<br />
Pacific/Tarawa: 2002-12-30 16:40:20<br />
Pacific/Enderbury: 2002-12-30 17:40:20<br />
Pacific/Kiritimati: 2002-12-30 18:40:20<br />
Pacific/Majuro: 2002-12-30 16:40:20<br />
Pacific/Kwajalein: 2002-12-30 16:40:20<br />
Pacific/Auckland: 2002-12-30 17:40:20<br />
Pacific/Chatham: 2002-12-30 18:25:20<br />
Pacific/Tahiti: 2002-12-29 18:40:20<br />
Pacific/Marquesas: 2002-12-29 19:10:20<br />
Pacific/Gambier: 2002-12-29 19:40:20<br />
Pacific/Johnston: 2002-12-29 18:40:20<br />
Pacific/Midway: 2002-12-29 17:40:20<br />
Pacific/Wake: 2002-12-30 16:40:20<br />
Pacific/Honolulu: 2002-12-29 18:40:20<br />
Suppose that the user doesn't know what time zone he/she is looking<br />
for. For example, Pacific/Honolulu covers Hawaii, but isn't necessarily<br />
intuitive to someone looking for "Hawaii." To help the user, we will add<br />
"ALL" as a valid search that returns all zones. To do so, we doctor the<br />
IF statement accordingly:<br />
if (((index uc($zone), uc($findzone)) != -1) ||<br />
("uc($findzone)" eq "ALL" )) {
Now, if the user enters "ALL" for the search, he/she will get all known<br />
zones.<br />
Creating <strong>WML</strong> Cards<br />
Using methods described in previous articles, it's relatively easy to<br />
output compliant <strong>WML</strong> for mobile devices. We will need another Perl<br />
module, CGI, for a couple of purposes:<br />
• Parsing arguments passed to the script<br />
• Supplying the mobile browser with an appropriate <strong>WML</strong> header<br />
Note: After my calendar gadget article was posted, a reader wrote to<br />
chastise me for writing my own argument-parsing routine. As I stated<br />
in the article, the fact that the script was passing itself cleanly<br />
formatted parameters was justification for simple argument parsing.<br />
However, one point I missed (brought up by the reader) was that the<br />
code might go on to be incorporated in other scripts where the sterility<br />
of the parameters could not be guaranteed. To help promote solid<br />
coding, I've pledged to use the CGI method "param" from now on.<br />
To output a <strong>WML</strong> card, we use the following code:<br />
# Pass <strong>WML</strong> header<br />
print header(-type=>'text/vnd.wap.wml');<br />
# Print <strong>WML</strong> header and beginning tags<br />
# Print closing tags<br />
if ( "$findzone" eq "" ) {<br />
# No time zone specified; display input card<br />
print zones) {<br />
if (((index uc($zone), uc($findzone)) != -1) ||<br />
( "uc($findzone)" eq "ALL" )) {<br />
# Display each match, or all if "ALL" was entered<br />
print $zone." :";<br />
$ENV{TZ} = $zone;<br />
($year,$month,$day, $hour,$min,$sec,<br />
$doy,$dow,$dst) = System_Clock();<br />
print $year."-".$month."-".$day." ";<br />
print $hour.":".$min.":".$sec."\n";<br />
$matches++;<br />
}<br />
}
# If no time zone matches, tell user<br />
if ( $matches eq 0 ) {<br />
print "No time zone match for: \n";<br />
print $findzone."";<br />
}<br />
}<br />
# Print closing tags<br />
As with previous projects, several things could be added to improve<br />
our world time clock:<br />
• A better format for the time output. Right now values under 10 are<br />
output as a single digit. For example, nine o'clock AM is displayed<br />
as: "9:0." Using a format mask or some simple logic we could pad<br />
the time accordingly (e.g., "09:00").<br />
• Break long listings into multiple pages/cards. For example, the "ALL"<br />
timezone listing ends up weighing in at just under 5K. That's five<br />
times the suggested 1K card data limit. Using some simple logic,<br />
the Perl script could display the data in a sequence of cards, each<br />
containing 7-12 records.<br />
• Add a control on the results card(s) to return to the search card.<br />
• Find/create a more comprehensive time zone database that<br />
includes named zones such as EST, CST, etc. (A comprehensive<br />
database can be compiled by downloading the source files found at<br />
ftp://elsie.nci.nih.gov/pub.)<br />
Building <strong>WML</strong> Gadgets: Phone Message<br />
Application<br />
This series of articles describes how to provide Web content to mobile<br />
devices through <strong>WML</strong> (Wireless Markup Language). This article covers<br />
creating an application to aid the user of a mobile phone.<br />
Note: These articles cover <strong>WML</strong> and <strong>WML</strong>Script version 1.1, which are<br />
supported by the majority of mobile devices in use today. The articles<br />
assume a working knowledge of HTML and general Web technologies,<br />
and further assume that you have read the previous article(s) in this<br />
series.<br />
Simple Applications<br />
Not all wireless applications have to be super-applications. Some of<br />
the best wireless applications perform simple tasks to improve wireless<br />
functionality. The last few articles in this series have shown how<br />
simple, single-purpose gadgets can boost the functionality of mobile<br />
devices. This article will present a slightly more complex application in
the same "extending functionality" vein.<br />
The Application<br />
This article will cover how to build a simple phone message<br />
application. Although we live in a time of portable phones, intelligent<br />
voicemail, and other electronic telephone magic, there are still times<br />
when messages are taken by one person (operator) and passed to<br />
others (recipients). For example, consider a businessman who often<br />
travels outside the home office. Many of his customers and contacts<br />
may occasionally call the home office and leave messages with his<br />
secretary. Using a simple Web form, the secretary can pass the<br />
message to the businessman's cell phone, where he can review the<br />
message and even return the call with the simple press of a button.<br />
Application Specifications<br />
This application will utilize the following components:<br />
• A simple HTML form to input the message<br />
• A flat-file database to store the messages<br />
• A CGI script to access the database<br />
Essentially, the application operates as shown in the following<br />
diagram:
FIGURE 1 - Our application's design. The operator uses a Web form to<br />
send the data to a CGI script that stores the data in a database. The<br />
same script is used by a mobile user (recipient) to access that data.<br />
We'll use Perl for the CGI script, for the same reasons we've used it<br />
previously: It's available for most platforms and extensible enough to<br />
perform almost any task necessary.<br />
Note: As with previous articles, teaching Perl is out of the scope of this<br />
series. There are numerous sources on the Internet for learning Perl,<br />
including the tutorial at<br />
http://wdvl.internet.com/Authoring/Languages/Perl/PerlfortheWeb/toc.<br />
html.<br />
Coding the Application<br />
Let's break down the individual processes and then code for each.<br />
Database Design<br />
Our "database" will be a simple delimited flat file. Although we could<br />
go the fancy route with an actual database format, the delimited
format will work well for our simple application.<br />
The database will contain the following fields:<br />
• Date and time the message was taken<br />
• Caller's name<br />
• Caller's message<br />
• Caller's phone number<br />
We'll use a double vertical bar for our delimiter. We could use a more<br />
standard delimiter, such as a comma, but we need something that<br />
wouldn't end up in the middle of the message field. In short, our<br />
database records will resemble the following:<br />
||||
<br />
Note that we include an extra field, "cmd." This hidden field will be<br />
passed to our CGI script to tell it what to do; namely, "save" the data.<br />
The CGI Script<br />
Our CGI script will be one multipurpose script, performing the<br />
following functions:<br />
• Saving the data (caller info)<br />
• Listing the caller record(s)<br />
• Displaying a selected record's details<br />
• Automatically dialing the caller's number<br />
• Optionally deleting the record and then calling the number<br />
The script could also display the input form. However, for maximum<br />
portability, we'll use a simple HTML file. With this method, our form<br />
can easily be included in almost any Web page template, simply by<br />
applying a style sheet or by cutting-and-pasting the "guts" of the form<br />
into another page.<br />
Saving the Data<br />
The following code fragment saves the data entered into the form:<br />
Listing: phonemsg.pl - Save data fragment<br />
# Start response page<br />
print header;<br />
print "";<br />
print "";<br />
print "Please wait...";<br />
# Grab the parameters<br />
$from = param('from');<br />
$message = param('message');<br />
$callback = param('number');<br />
# Remove any vertical bars<br />
$message =~tr/|/ /;<br />
# Check file lock
$count = 0;<br />
if (-e "msgfile.lock") {<br />
select(undef,undef,undef,0.1);<br />
$count++;<br />
if ($count = 10) { die "Can't open message file! "; }<br />
}<br />
open LOCK, ">msgfile.lock";<br />
# Write new record to end of file<br />
open FILE, ">>msgfile.txt";<br />
print FILE $date ." ". $time ."||";<br />
print FILE $from ."||". $message ."||". $callback;<br />
print FILE "\n";<br />
close FILE;<br />
close LOCK;<br />
unlink "msgfile.lock";<br />
# All done, close response page<br />
print "";<br />
Because the form and the mobile device could both be utilizing the<br />
script (and hence, the database) simultaneously, we must use file<br />
lockingto avoid having two processes accessing the database and<br />
corrupting our data. We'll use a simple method: creating a file to lock<br />
the database_if the file exists, the process waits for it to be deleted<br />
before accessing the database. When a process is done with the<br />
database, the lock file is deleted and other processes are allowed to<br />
access the database. Instead of failing right away if the file is locked,<br />
or waiting forever for the file to be unlocked, the code loops 10 times,<br />
waiting a tenth of a second between the iterations. If the file is still<br />
locked, we assume that something has gone wrong and exit with an<br />
appropriate error message.<br />
Windows users: Some implementations of Perl on the Windows<br />
platform don't support the four-argument call of "select" used in our<br />
file-locking loop. If this code generates errors on a Windows system,<br />
substitute another delay function.<br />
Listing the Records on the Mobile Device<br />
The following code fragment comprises the code to list five records on<br />
the mobile device, along with an option to move to the next five
ecords:<br />
Listing: phonemsg.pl - List records fragment<br />
#Pass <strong>WML</strong> header<br />
print header(-type=>'text/vnd.wap.wml');<br />
# Print <strong>WML</strong> header and beginning tags<br />
# (nextitem + 4 or end of file)<br />
if ($nextitem + 4
When the user picks a record from the laundry list of records, the<br />
script needs to be able to display the record's details. The following<br />
code takes care of that activity:<br />
Listing: phonemsg.pl - Display specific record fragment<br />
# What record to display<br />
$rec = param('rec');<br />
#Pass <strong>WML</strong> header<br />
print header(-type=>'text/vnd.wap.wml');<br />
# Print beginning of <strong>WML</strong> file<br />
$datetime<br />
$from<br />
$message<br />
Callback Number:<br />
$callback<br />
<br />
Call<br />
<br />
Call & Del<br />
<br />
<br />
<br />
MESSAGE<br />
The record number to display is passed to the script via the "rec"<br />
variable. As in the other cases, the file lock is checked, the database is<br />
locked, the database is read into an array, and then the database is<br />
released (unlocked). The required record is then read from the array,<br />
unpacked into fields (based on the delimiter), and displayed.<br />
Notice that two links are placed at the bottom of the displayed record:<br />
"Call" and "Call & Del(ete)". The first simply uses the "call this<br />
number" URL format to make the mobile device dial a number. The<br />
latter option allows the user to delete the message before placing the<br />
call, since the message is being returned and should no longer be<br />
stored in the database as an open message.<br />
Placing a Call and Optionally Deleting a Record<br />
The script handles returning a call by including a link to a URL<br />
containing the number to call (see the previous section). Deleting a<br />
record before the call is slightly more complex, and is handled by the<br />
following code:<br />
Listing: phonemsg.pl - Place call/delete record fragment<br />
# What number to call and what record to delete<br />
$number = param('number');<br />
$rec = param('rec');<br />
#Pass <strong>WML</strong> header<br />
print header(-type=>'text/vnd.wap.wml');
# Print start of <strong>WML</strong> file<br />
close LOCK;<br />
unlink "msgfile.lock";<br />
# Close card<br />
print " \n \n ";<br />
To delete and call a number, the script is passed the number and the<br />
record number to delete. The number could be retrieved from the<br />
record before the deletion, but is passed separately so it doesn't have<br />
to be parsed from the record. This design decision falls into the "halfa-dozen<br />
versus six" category of decisions; I opted to pass the number<br />
we already have, saving the lines required to decode the record before<br />
deleting it.<br />
To perform the deletion, we read the entire file into an array and then<br />
reconstruct the file by writing all records except the deleted record<br />
back to the file.<br />
The Completed Script<br />
After adding a few declaration lines, the controlling structure using the<br />
$cmd variable, and some connecting tissue, our completed script<br />
becomes the following:<br />
Listing: phonemsg.pl<br />
#!/usr/bin/perl<br />
# Include modules<br />
use CGI qw(:standard);<br />
use Date::Calc qw(:all);<br />
# Set command to execute; default = list<br />
$cmd = param('cmd');<br />
if ("$cmd" eq "") { $cmd = "list"; }<br />
# Set current time/date<br />
($year,$month,$day) = Today();<br />
($hour,$min,$sec) = Now();<br />
$time = $hour.":".$min;<br />
$date = $month."/".$day."/".$year;<br />
#<br />
# Call from HTML form, save the data, and return to form<br />
if ("$cmd" eq "save") {
# Start response page<br />
print header;<br />
print "";<br />
print "";<br />
print "Please wait...";<br />
# Grab the parameters<br />
$from = param('from');<br />
$message = param('message');<br />
$callback = param('number');<br />
# Remove any vertical bars<br />
$message =~tr/|/ /;<br />
# Check file lock<br />
$count = 0;<br />
if (-e "msgfile.lock") {<br />
select(undef,undef,undef,0.1);<br />
$count++;<br />
if ($count = 10) { die "Can't open message file! "; }<br />
}<br />
open LOCK, ">msgfile.lock";<br />
# Write new record to end of file<br />
open FILE, ">>msgfile.txt";<br />
print FILE $date ." ". $time ."||";<br />
print FILE $from ."||". $message ."||". $callback;<br />
print FILE "\n";<br />
close FILE;<br />
close LOCK;<br />
unlink "msgfile.lock";<br />
# All done, close response page<br />
print "";<br />
}<br />
#<br />
# Call from mobile device to list calls<br />
if ("$cmd" eq "list") {<br />
#Pass <strong>WML</strong> header<br />
print header(-type=>'text/vnd.wap.wml');<br />
# Print <strong>WML</strong> header and beginning tags<br />
<br />
<br />
ENDHEADER<br />
# Print start of card<br />
print "";<br />
print $from ." (". $datetime .") \n";<br />
print " \n";<br />
}<br />
# Set nextitem for NEXT function<br />
$nextitem = $lastitem + 1;<br />
# Display NEXT option<br />
print "";<br />
print "Next \n";<br />
print " \n \n";<br />
print " \n \n";<br />
}<br />
#<br />
# Call from mobile device to show call detail<br />
if ("$cmd" eq "display") {<br />
# What record to display<br />
$rec = param('rec');<br />
#Pass <strong>WML</strong> header<br />
print header(-type=>'text/vnd.wap.wml');<br />
# Print beginning of <strong>WML</strong> file<br />
push(@lines,$_) while ();<br />
close FILE;<br />
close LOCK;<br />
unlink "msgfile.lock";<br />
# Split record into fields<br />
($datetime,$from,$message,$callback) = split /\|\|/,$lines[$rec];<br />
# Display record with "call"<br />
# and "call & delete" options<br />
ENDHEADER<br />
# Print beginning of card<br />
# (Number is dialed in 10 secs)<br />
Note that the default action of the script is to list the records. This<br />
allows the mobile device to call the script without arguments<br />
("http://URL/phonemsg.pl") to get the ball rolling. Subsequent calls<br />
are handled by the script ("display," "callNdel," etc.) where it controls<br />
the parameters, saving the mobile user from having to<br />
enter/bookmark them.<br />
To test the application, we seed the database with the following data:<br />
Listing: msgfile.txt - Sample data<br />
1/25/2003 12:15||Caller Number01||Sample message, from sample caller.||317-<br />
555-1212<br />
1/25/2003 12:25||Caller Number02||Sample message, from sample caller.||317-<br />
555-1212<br />
1/25/2003 12:35||Caller Number03||Sample message, from sample caller.||317-<br />
555-1212<br />
1/25/2003 13:15||Caller Number04||Sample message, from sample caller.||317-<br />
555-1212<br />
1/26/2003 14:15||Caller Number05||Sample message, from sample caller.||317-<br />
555-1212<br />
1/26/2003 14:23||Caller Number06||Sample message, from sample caller.||317-<br />
555-1212<br />
1/26/2003 15:15||Caller Number07||Sample message, from sample caller.||317-<br />
555-1212<br />
1/27/2003 9:15||Caller Number08||Sample message, from sample caller.||317-<br />
555-1212<br />
1/27/2003 9:35||Caller Number09||Sample message, from sample caller.||317-<br />
555-1212<br />
1/28/2003 8:05||Caller Number10||Sample message, from sample caller.||317-<br />
555-1212<br />
1/28/2003 10:15||Caller Number11||Sample message, from sample caller.||317-<br />
555-1212<br />
1/28/2003 11:11||Caller Number12||Sample message, from sample caller.||317-<br />
555-1212<br />
1/28/2003 12:01||Caller Number13||Sample message, from sample caller.||317-<br />
555-1212<br />
1/28/2003 14:15||Caller Number14||Sample message, from sample caller.||317-<br />
555-1212<br />
1/28/2003 16:45||Caller Number15||Sample message, from sample caller.||317-<br />
555-1212<br />
1/29/2003 8:25||Caller Number16||Sample message, from sample caller.||317-<br />
555-1212<br />
1/29/2003 9:04||Caller Number17||Sample message, from sample caller.||317-<br />
555-1212<br />
1/29/2003 10:35||Caller Number18||Sample message, from sample caller.||317-<br />
555-1212
1/29/2003 10:39||Caller Number19||Sample message, from sample caller.||317-<br />
555-1212<br />
1/30/2003 12:15||Caller Number20||Sample message, from sample caller.||317-<br />
555-1212<br />
1/30/2003 15:02||Caller Number21||Sample message, from sample caller.||317-<br />
555-1212<br />
1/30/2003 16:05||Caller Number21||Sample message, from sample caller.||317-<br />
555-1212<br />
Using this data, our application resembles the following graphics on a<br />
mobile device:<br />
FIGURE 2 - The laundry list of messages.
FIGURE 3 - A selected message is displayed.<br />
FIGURE 4 - Two links at the bottom of the record allow the user to call<br />
and optionally delete the message.<br />
Images are courtesy Openwave Systems Inc. (Openwave, the<br />
Openwave logo, Openwave SDK, Openwave SDK Universal Edition,<br />
Openwave SDK <strong>WAP</strong> Edition are trademarks of Openwave Systems<br />
Inc. All rights reserved.)
Room for Improvement<br />
This application makes a nice, general phone message system.<br />
However, given time and incentive, the following improvements could<br />
be made:<br />
• The code could be streamlined. Because it was written in sections for<br />
this article, the code is not as svelte as it could be_in multiple<br />
places, code is duplicated that could be placed in commonly<br />
accessed functions/subroutines. Also, the code breaks a few "good<br />
Perl coding" rules (non-local variables, loose variable naming, etc.);<br />
that problem should be rectified.<br />
• There's no value checking in the HTML form and it is only set up to<br />
accept domestic numbers (12 characters, area code, prefix, suffix,<br />
and two dashes).<br />
• A real database structure could be used for the data, alleviating the<br />
need for stringent file locking and enabling true random access.<br />
• Another option could be added to enable the user to back up through<br />
the list of messages (we already allow forward access via the Next<br />
link).<br />
• A search feature could be added to find particular messages or to<br />
display messages in a specified timeframe.<br />
• Multiple users could be added by specifying the person taking the<br />
message (operator) and the person for whom the message is<br />
designated (recipient). Then multiple operators could take<br />
messages for multiple recipients. This would also necessitate a login<br />
or other authentication process for the mobile user (identifying<br />
himself/herself as the intended recipient), unless multiple users<br />
return calls from "the pool."<br />
• A status field could be added so the records could be<br />
tracked. Instead of the record simply existing ("need to call") or being<br />
deleted ("called"), a message could be flagged for a variety of<br />
purposes, including archiving.<br />
Tracking Users Using <strong>WML</strong><br />
This article describes how to provide Web content to mobile devices<br />
through <strong>WML</strong> (Wireless Markup Language). More specifically, this<br />
article covers how to track users; that is, how to recognize a repeat
visitor to your site.<br />
<strong>WML</strong> and <strong>WML</strong>Script version 1.1 are supported by the majority of<br />
mobile devices in use today. The articles assume a working knowledge<br />
of HTML and general Web technologies, and further assume that you<br />
have read the previous article(s) in this series.<br />
The Value of Recognizing Users<br />
There are a variety of reasons to implement a recognition system that<br />
acknowledges that a user that has previously visited your site. The<br />
most useful of these reasons is to remember user preferences.<br />
For example, suppose that you offer a service of finding particular<br />
restaurants near a user. Each user may prefer a certain type of food,<br />
environment, etc., and knowing where the user is located is important<br />
so that you can find restaurants in that vicinity. Of course, users don't<br />
want to input all their preferences every time they visit your site—it's<br />
better to save most of the settings, recognize users when they return,<br />
and recall their settings.<br />
How To Recognize Users<br />
When <strong>WML</strong> was first implemented, the code could retrieve the user's<br />
cell phone number from the device; this phone number could act as a<br />
unique identifier. Unfortunately, this ability was recognized as an<br />
invasion of privacy, and the feature was discontinued. Now there is no<br />
way to retrieve a unique identifier from the user's device.<br />
What options are left? You could have the user input his or her<br />
telephone number on each visit, assign the visitor a login name, or<br />
have the user enter an identifier during each visit. Better yet, why not<br />
store the identifier on the user's device for recall each time he or she<br />
visits your site?<br />
Cookies: The Good, the Bad, and the Ugly<br />
The term cookie refers to the HTTP technology that allows a site to<br />
store data on a user's machine. When cookies were first used, they<br />
were fairly innocuous, intended primarily for storing user preferences.
However, it didn't take long for the business side of the Web to realize<br />
the potential of cookies and begin using them for their own purposes—<br />
tracking user activities, shopping habits, and other bits of personal<br />
information.<br />
Shortly thereafter, the media and user advocates led the charge<br />
against cookies, causing most modern browsers to offer the option of<br />
refusing to store any cookies, or allowing the user to choose when a<br />
cookie should or should not be stored. Unfortunately, refusing cookies<br />
causes problems for Web sites that use cookies to store user<br />
preferences and login info, requiring the user to reenter such info on<br />
each visit.<br />
Note: I don't condone the illicit use of cookies for tracking users'<br />
personal information, but do recognize the utility of storing frequently<br />
used information to aid the user experience.<br />
How Cookies Work<br />
Cookies work by storing data on the user's local computer/device. This<br />
data is stored via an HTTP dialog between the server and the client.<br />
That data can then be recalled by the server, processed, updated, etc.<br />
Figure 1 shows the data paths associated with storing and retrieving<br />
cookies.<br />
Figure 1 - Cookie data is passed back and forth between the<br />
server and client via the HTTP stream, but the data is actually
stored on the client side.<br />
The information stored in the cookie can be just about any type of<br />
data: string, date, an integer, or a real number. Most sites choose to<br />
encode cookie data into a lengthy string that can be decoded and<br />
parsed by the site code.<br />
Note: Windows users can examine the cookies that have been stored<br />
on the local machine. Look in your local settings directory(ies) for<br />
"cookies" files. Windows XP users can find the cookies in the following<br />
directory:<br />
C:\Documents and Settings\{Username}\Cookies<br />
In addition to data, cookies are stored with a time to live (TTL),<br />
specifying how long the cookie should be stored before being<br />
discarded. The cookie also indicates the scope for which it should be<br />
used—that is, what directory(ies) on the server are valid for that<br />
cookie. This allows a site to store multiple cookies with the same<br />
name, but different scopes. For example, a site with several sections<br />
could store preferences for each section in a cookie named "prefs."<br />
Using Cookies with <strong>WML</strong><br />
Because <strong>WML</strong> doesn't have any built-in cookie functions, you have to<br />
use other technologies to store and retrieve cookies. This article shows<br />
how to use Perl, which has robust cookie-handling abilities.<br />
We'll use the HTTP header Set-Cookie to set cookies in the examples.<br />
Although <strong>WML</strong> has a tag, don't confuse the HTTP header with<br />
the <strong>WML</strong> card head—they're different animals. Cookies must be set<br />
before the end of the HTTP header; the <strong>WML</strong> comes after the<br />
HTTP header has been sent, and therefore it cannot be used to set<br />
cookies.<br />
You can use almost any language that supports cookie functions to<br />
accomplish the goals in this article. For example, PHP's header()<br />
function can be used to set cookies. Even more control can be<br />
accomplished with PHP's setcookie() function. Several variable<br />
structures exist in PHP to read cookies, including<br />
$HTTP_COOKIE_VARS and $_COOKIE arrays.
Handling Cookies with Perl<br />
There are many options for handling cookies in Perl, including simple<br />
HTTP methods and even dedicated Perl libraries such as cookie-lib.pl.<br />
We'll use the simple HTTP methods for this article.<br />
For more robust cookie management, the reader is encouraged to<br />
check out other cookie-handling methods, such as cookie-lib.pl and<br />
HTTP::Cookies. The former is available online at The CGI Resource<br />
Index<br />
(http://cgi.resourceindex.com/Programs_and_Scripts/Perl/Cookies/);<br />
the latter is from CPAN (http://search.cpan.org/author/RSE/lcwa-<br />
1.0.0/lib/lwp/lib/HTTP/Cookies.pm).<br />
Setting a Cookie with Perl<br />
As discussed earlier, we can use the HTTP header Set-Cookie to set<br />
cookies. In its simplest usage, this header takes the following form:<br />
Set-Cookie: =<br />
For example, a real header might be as follows:<br />
Set-Cookie: name=Steve<br />
When this header is passed to a browser, it sets the cookie "name"<br />
equal to "Steve." Because the header doesn't include a time to live<br />
(TTL), the cookie is only valid for the current session. When the<br />
browser is closed, the cookie expires and is deleted.<br />
To include a TTL, you add the parameter expires as shown in the<br />
following example:<br />
Set-Cookie: name=Steve; expires=Monday, 24-Mar-03 23:59:59 GMT<br />
Notice the use of a semicolon (;) to delimit the parameters. The date<br />
is in the format "weekday, dd/Mon/yy hh:mm:ss." In the example<br />
above, the cookie will expire at one second before midnight on<br />
Monday, March 24, 2003, Greenwich Mean Time.<br />
Tip: It's important to include at least one blank line after the Set-<br />
Cookie header and before the <strong>WML</strong> headers so the client correctly<br />
identifies the <strong>WML</strong> headers.
Let's look at a real example of using Perl to set a cookie for a <strong>WML</strong><br />
deck. The following code snippet shows how Perl is used to set a<br />
cookie and output a status ("Cookie set") message:<br />
#!/usr/bin/perl<br />
# Define minimal deck<br />
$deck = '<br />
<br />
<br />
<br />
Cookie set.<br />
<br />
<br />
';<br />
# Send Content-type and Set-Cookie headers<br />
print "Content-type: text/vnd.wap.wml \n";<br />
print "Set-Cookie: name=Steve \n";<br />
# Send <strong>WML</strong> header info<br />
print "\n\n";<br />
print "\n";<br />
# Send deck<br />
print $deck;<br />
The cookie in the code above was set without a TTL. To set an<br />
expiration date, we draw on the Date::Calc module to do our date<br />
calculations:<br />
#!/usr/bin/perl<br />
# Include Date::Calc.<br />
use Date::Calc':all';<br />
# Get today in GMT<br />
($year,$month,$day) = Today([$gmt]);<br />
# Add a year (365 days)<br />
($year,$month,$day) =<br />
Add_Delta_Days($year,$month,$day,"365");<br />
# Get textual representations of month and day of week<br />
$dow = Day_of_Week_to_Text(Day_of_Week($year,$month,$day));<br />
$month = Month_to_Text($month);<br />
# Make sure day is two digits<br />
if ($day
}<br />
# Assemble expiration date<br />
$date = $dow.", ".$day."-".$month."-".$year." 23:59:59 GMT";<br />
# Define deck<br />
$deck = '<br />
<br />
<br />
<br />
Cookie set.<br />
<br />
<br />
';<br />
# Send Content-type and Set-Cookie headers<br />
print "Content-type: text/vnd.wap.wml \n";<br />
print "Set-Cookie: name=Steve; expires=$date; \n";<br />
# Send <strong>WML</strong> headers<br />
print "\n\n";<br />
print "\n";<br />
# Send the deck<br />
print $deck;<br />
Note: The Date::Calc module has been covered in several previous<br />
articles. The module is available from CPAN, at<br />
http://search.cpan.org/author/STBEY/Date-Calc-5.3/Calc.pod.<br />
Reading Cookies with Perl<br />
Reading cookies with Perl is even easier than setting them, thanks to<br />
the environment variable HTTP_COOKIE. This variable contains<br />
name/value pairs for all applicable cookies (those matching the current<br />
scope).<br />
To parse the name/value list, you could use the following code:<br />
@nvpairs=split(/[,;] */, $ENV{'HTTP_COOKIE'});<br />
foreach $pair (@nvpairs) {<br />
($name, $value) = split(/=/, $pair);<br />
$cookie{$name} = $value;<br />
}
This code effectively parses the cookie list into the array $cookie,<br />
where each value can be accessed by its name. For example, our<br />
earlier "name" example would yield:<br />
$cookie{'name'} = "Steve"<br />
An extended example, displaying the cookie value in <strong>WML</strong>, is shown<br />
below:<br />
#!/usr/bin/perl<br />
# Break cookies into name/value pairs<br />
# and store into cookie array<br />
@nvpairs=split(/[,;] */, $ENV{'HTTP_COOKIE'});<br />
foreach $pair (@nvpairs) {<br />
($name, $value) = split(/=/, $pair);<br />
$cookie{$name} = $value;<br />
}<br />
# Define <strong>WML</strong> deck<br />
$deck = '<br />
<br />
<br />
<br />
Cookie (name) = '<br />
.$cookie{'name'}.'<br />
<br />
<br />
';<br />
# Send Content-type and Set-Cookie headers<br />
print "Content-type: text/vnd.wap.wml \n";<br />
print "Set-Cookie: name=Steve; expires=$date; \n";<br />
# Send <strong>WML</strong> headers<br />
print "\n\n";<br />
print "\n";<br />
# Send the deck<br />
print $deck;<br />
If the cookie was still set, this code would display the following:<br />
Cookie (name) = Steve<br />
Deleting Cookies<br />
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<br />
code earlier but subtract a day or two to expire the cookie. The code,<br />
using Date::Calc functions, would resemble the following:<br />
# Get today in GMT<br />
($year,$month,$day) = Today([$gmt]);<br />
# Subtract a year (365 days)<br />
($year,$month,$day) =<br />
Add_Delta_Days($year,$month,$day,"-365");<br />
# Get textual representations of month and day of week<br />
$dow = Day_of_Week_to_Text(Day_of_Week($year,$month,$day));<br />
$month = Month_to_Text($month);<br />
# Make sure day is two digits<br />
if ($day
Alternatively, you could encode all this data into one lengthy string.<br />
My advice, however, would be to tie one unique piece of data that<br />
identifies the user to a database record. Basically, store only what you<br />
need on the client device, and store the rest of the data in a database<br />
accessible by the server. The flowchart for an application using this<br />
method might resemble Figure 2.<br />
Click here for larger image<br />
Figure 2 - Flowchart of application using described method<br />
On entering the site, the user's browser is checked for an ID cookie. If<br />
the cookie is found, the user's preferences are retrieved from the
server's database and the site is displayed with those preferences. If<br />
the cookie is not set, the user is taken to a preference form and<br />
queried for his/her preferences. Those preferences are stored in the<br />
server's database and the user's ID is stored as a cookie.<br />
http://www.developer.com/lang/php/