XForms Basics (part 2)

Find out how to submit XForms data to a server-side script or save it to a local client file.

Revving Up

In the first part of this series, I gave you a quick introduction to the newly-released XForms 1.0 specification, by explaining the fundamental concepts of the XForms model. I showed you how to define an XForms model and form instance data, as well as the logical components that govern how the form behaves on user interaction. I also took you on a whirlwind tour of the various input controls available in XForms 1.0 - as you will have seen, these controls correspond closely with the controls available in traditional HTML, and then some.

Now, all this is fine and dandy - but how about submitting the form and actually doing something with the user's data? Well, that's where this article comes in. Over the next few pages, I'm going to answer this vexing question, showing you how to handle form submissions on both the server and the client. XForms offers the creative developer an immense amount of control over the process, primarily by doing away with the traditional name=value submission format in favour of standard XML markup for user data.

I won't just stop there, though. Once you've understood how to store user data on the server with XForms, I'll show you how to control the quality of that data by having XForms validate user input at the time of submission. As you'll see, XForms' built-in support for data validation means you don't need JavaScript or server-side logic any more. Instead, you can quality-control user input through built-in XForms constructs or - if you want to get really advanced - hook your XForms up to an XML Schema to enforce more sophisticated validation and typing rules.

Excited? I hope so.

Welcome To Immigration

I'll begin with a simple XForm, one that you're already familiar with from the previous segment of this tutorial:

<html xmlns="http://www.w3.org/1999/xhtml"  xmlns:xforms="http://www.w3.org/2002/xforms/cr">

<head>

<!-- form model -->
<xforms:model id="immigration">
    <xforms:instance src="immigration.xml" />
    <xforms:submission id="submit" action="/tmp/immigrant.xml" method="put" />
</xforms:model>
<basefont face="Arial">

</head>

<body>

<!-- define interface controls -->
<table cellspacing="5" cellpadding="5" border="0">
<tr>
<td colspan="2" align="center"><font color="red" size="4">Welcome to Immigration</font></td>
</tr>

<tr>
<td>
    <xforms:input id="txtname" model="immigration" ref="/immigrant/name">
        <xforms:label>Name</xforms:label>
        <xforms:hint>Enter your name here</xforms:hint>
    </xforms:input>
</td>
</tr>

<tr>
<td>
    <xforms:input id="txtcitizenship" model="immigration" ref="/immigrant/citizenship">
        <xforms:label>Citizenship</xforms:label>
        <xforms:hint>Enter your country of origin here</xforms:hint>
    </xforms:input>
</td>

</tr>
<tr>
<td align="left">
    <xforms:select1 model="immigration" ref="/immigrant/purpose" appearance="full">
        <xforms:label>Purpose of visit</xforms:label>
        <xforms:hint>Please state the purpose of your visit</xforms:hint>
        <xforms:item>
            <xforms:label>Business</xforms:label>
            <xforms:value>B</xforms:value>
        </xforms:item>
        <xforms:item>
            <xforms:label>Pleasure</xforms:label>
            <xforms:value>P</xforms:value>
        </xforms:item>
        <xforms:item>
            <xforms:label>Other</xforms:label>
            <xforms:value>O</xforms:value>
        </xforms:item>
    </xforms:select1>
</td>
</tr>

<tr>
<td align="left">
    <xforms:select model="immigration" ref="/immigrant/immunization" appearance="full">
        <xforms:label>Immunization</xforms:label>
        <xforms:hint>Please select the diseases that you have been immunized against</xforms:hint>
        <xforms:item>
            <xforms:label>Smallpox</xforms:label>
            <xforms:value>100</xforms:value>
        </xforms:item>
        <xforms:item>
            <xforms:label>Malaria</xforms:label>
            <xforms:value>113</xforms:value>
        </xforms:item>
        <xforms:item>
            <xforms:label>Yellow fever</xforms:label>
            <xforms:value>56</xforms:value>
        </xforms:item>
        <xforms:item>
            <xforms:label>Typhoid</xforms:label>
            <xforms:value>174</xforms:value>
        </xforms:item>
    </xforms:select>
</tr>

<tr>
<td align="left">
    <xforms:textarea model="immigration" ref="/immigrant/address">
        <xforms:label>Address in home country</xforms:label>
    </xforms:textarea>
</td>
</tr>
</table>

<xforms:submit submission="submit">
    <xforms:label>Save</xforms:label>
    <xforms:hint>Save the information entered above to a local file</xforms:hint>
</xforms:submit>

</body>
</html>

Looks familiar? It should - this is the same form I used to demonstrate the various input controls earlier, with one important addition: the ability to actually do something with the data once it has been entered by the user.

The XForms specification defines an <xforms:submission> element, that specifies how form submission is to be handled. Typically, this element appears in the <head> of the document, within an <xforms:model> element, and contains information on the URL to which the form is to be submitted, the method of submission, and the format and structure of the submitted XML. Here's an example:

<xforms:submission id="submit" action="/tmp/immigrant.xml" method="put" />

This is similar to the data that appears in the standard HTML <form> tag. Note the addition of an "id" element - this is used to link the <xforms:submission> element with the actual form submit button - and the method used (PUT, because in this first example, I'll be writing the form data to a local file, not a server storage engine).

The <xforms:submission> element is only part of the puzzle. The other half is the submit button itself, represented by the <xforms:submit> input control (remember this from last time's lesson?). Here's what it looks like:

<xforms:submit submission="submit">
    <xforms:label>Save</xforms:label>
    <xforms:hint>Save the information entered above to a local file</xforms:hint>
</xforms:submit>

Pretty standard, this - like other input controls, it contains optional <xforms:label> and <xforms:hint> elements to give the user additional information on what it's supposed to do. The novel thing here, though, is the additional "submission" attribute, which associates this submit button with the <xforms:submission> element defined in the XForms model. Because of this link, the <xforms:submit> element will trigger the "action" specified in the <xforms:submission> element when invoked.

Now, let's give it a whirl to see it if works as advertised. Enter some data into the form and hit the "Save" button. Then, navigate to the location specified in the "action" attribute and open up the target file in a text editor. You should see the data you entered, formatted in XML as per your form model. Here's an example:

<!-- immigration.xml-->
<immigrant><name>Chewbacca</name><citizenship>Tatooine</citizenship><purpose>B</purpose><immunization>56 113 100</immunization><address>Planet Tatooine</address></immigrant>

Behind the scenes, here's what happens when the form is submitted:

  1. An "xforms-submit" event is triggered (more on events shortly).

  2. The instance data tree beginning at the root specified in the <xforms:submission> element is selected. If no root is specified (as in the example above), the entire instance data tree is selected.

  3. The selected instance data is validated as per validation rules that may be specified in the XForm. If an error occurs in validation, processing stops and an exception is generated.

  4. If the data passes all the validation tests, it is serialized and submitted using the information provided in the "method" and "action" attributes of the <xforms:submission> element.

Data Overload

The previous example showed you how to store the information provided by the user in a local file on the client. Though this seems interesting at first glance, it isn't very useful in real life (when was the last time you wanted to do this?) Most often, you would want the data to be sent to the server, safe and secure in a database or other storage engine. How does XForms stand up to this challenge? Pretty well, actually - and it even adds some interesting attributes to control the data being submitted.

In order to demonstrate, I'll revise the previous example to submit the user data to a server-side script, which takes care of adding it to a MySQL database. In this example, the database is called "db1", the table is called "immigrants", and the SQL code to create the table looks like this:

CREATE TABLE `immigrants` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(30) NOT NULL default '',
  `citizenship` varchar(50) NOT NULL default '',
  `purpose` char(1) NOT NULL default '',
  `immunization` varchar(50) NOT NULL default '',
  `address` varchar(255) NOT NULL default '',
   PRIMARY KEY  (`id`)
)

As you can see, this is pretty straightforward stuff, with each field of the table mapping to a node in the XML file created in the previous example.

Next up, altering the XForm model to point to a server-side PHP script instead of a local file:

<!-- form model -->
`<xforms:model id="immigration">`
    `<xforms:instance src="immigration.xml" />
    `<xforms:submission id="submit" action="/scripts/register.php" method="post" />
</xforms:model>

Notice that no change is needed to the form input controls, or any other section of the XForm - this is an example of the separation between form and function that XForms promises.

Finally, here's the PHP script that takes the submitted form data and converts it into an INSERT query:

<?php

// initialize some variables;
$currentTag = "";
$values = array();
$allowedFields = array("name", "citizenship", "purpose", "immunization", "address");

// database parameters
$host = "localhost";
$usr = "john";
$pwd = "doe";
$db = "db1";

// handlers
function startElementHandler($parser, $name, $attributes)
{
    global $currentTag;
    $currentTag = $name;
}

function endElementHandler($parser, $name)
{
    global $values, $currentTag;
    global $connection, $table;

    if (strtolower($name) == "immigrant") {
        // generate SQL
        $query = "INSERT INTO immigrants";
        $query .= "(name, citizenship, purpose, immunization, address)";
        $query .= "VALUES(\"" . join("\",\"", $values) . "\");";

        // uncomment for debug purposes
        // print $query;

        // execute query
        $result = mysql_query($query) or die("Error in query: $query. " . mysql_error());

        // reset variables
        $values = array();
        $currentTag = "";
    }
}

function characterDataHandler($parser, $data)
{
    global $currentTag, $values, $allowedFields;

    $currentTag = strtolower($currentTag);

    if (in_array($currentTag, $allowedFields) && trim($data) != "") {
        $values[$currentTag] = mysql_escape_string($data);
    }
}

$parser = xml_parser_create();

// get the XML data
$data = $HTTP_RAW_POST_DATA;

// set SAX parser options
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);

// set element handlers
xml_set_element_handler($parser, "startElementHandler", "endElementHandler");
xml_set_character_data_handler($parser, "characterDataHandler");

// connect to database
$conn = mysql_connect($host, $usr, $pwd) or die("Unable to connect to the database");
mysql_select_db($db) or die("Unable to select database");

// parse XML
if (!xml_parse($parser, $data)) {
    die(sprintf(
        "XML error: %s at line %d",
    xml_error_string(xml_get_error_code($parser)),
    xml_get_current_line_number($parser)
    ));
}

// clean up
xml_parser_free($parser);
mysql_close();

?>

Now try it out and see for yourself - enter some data into the form, submit it and then check the database to see if your values were inserted correctly. Here's what you might see:

mysql> SELECT * FROM immigrants;
+----+-----------+-------------+---------+--------------+-----------------+
| id | name      | citizenship | purpose | immunization | address         |
+----+-----------+-------------+---------+--------------+-----------------+
|  1 | Chewbacca | Tatooine    | B       | 56 113       | Planet Tatooine |
+----+-----------+-------------+---------+--------------+-----------------+
1 row in set (0.00 sec)

How did this happen? Well, unlike traditional forms, which submit data using name-value pairs, XForms submits data as a well-formed XML document. This document can then be parsed using either a DOM or SAX parser, or even transferred directly to any other application that understands XML.

PHP comes with a built-in SAX parser, which is what I've used in the example above to parse the XML document. SAX, or the Simple API for XML, is one of the most common methods of parsing an XML document. Essentially, a SAX parser reads the XML document sequentially, triggering specific user-defined functions when it finds an opening tag, character data, closing tag, CDATA block and so on. In the example above, these user-defined functions are called startElementHandler(), endElementHandler() and characterDataHandler().

$parser = xml_parser_create();
xml_set_element_handler($parser, "startElementHandler", "endElementHandler");
xml_set_character_data_handler($parser, "characterDataHandler");

Of these, the major work in the script above is done by the characterDataHandler() function - this reads the various values entered by the user from the XML document tree and builds the SQL query after using the mysql_escape_string() function to make the values ready for insertion in the database.

function characterDataHandler($parser, $data)
{
    global $currentTag, $values, $allowedFields;

    $currentTag = strtolower($currentTag);

    if(in_array($currentTag, $allowedFields) && trim($data) != "")
    {
        $values[$currentTag] = mysql_escape_string($data);
    }

}

The script above won't make much sense to you unless you've played a little with SAX. In case you haven't, drop by http://www.melonfire.com/community/columns/trog/article.php?id=71 and find out what you missed, then come back here and review the script again. You can also read more about SAX at http://www.saxproject.org/ and http://www.xmlphp.com/

You can also parse the XML document submitted by the XForm using the DOM - I leave that to you as an exercise.

A Custom Job

In addition to the attributes you've already seen, the element also comes with a bunch of others, which can assist in customizing the manner in which the form data is submitted. Here's a brief list:

"ref" - specifies the node to use as root when submitting instance data, defaults to /

"version" - specifies XML version to use when submitting form data

"indent" - toggles indenting of XML data in form submission

"encoding" - specifies XML encoding to use when submitting form data

"omit-xml-declaration" - toggles XML declaration when submitting form data

"standalone " - toggles standalone declaration when submitting form data

"replace" - specifies what to do with the response to the submission

Consider the following example, which demonstrates some of these in action:

`<html xmlns="http://www.w3.org/1999/xhtml"  xmlns:xforms="http://www.w3.org/2002/xforms/cr">`

`<head>`

<!-- form model -->
`<xforms:model id="information">`
    `<xforms:instance>`
        `<user>`
            `<name />`
            `<email />`
            `<age />`
        </user>
    </xforms:instance>
    `<xforms:submission id="submit" ref="/user/name" action="/tmp/user.xml" method="put" indent="yes" omit-xml-declaration="no" />
    </xforms:model>
`<basefont face="Arial">`

</head>

`<body>`

<!-- define interface controls -->
`<table cellspacing="5" cellpadding="5" border="0">`
`<tr>`
`<td>`
    `<xforms:input id="txtname" model="information" ref="/user/name">`
        `<xforms:label>`Name</xforms:label>
        `<xforms:hint>`Enter your name here</xforms:hint>
    </xforms:input>
</td>
</tr>

`<tr>`
`<td>`
    `<xforms:input id="txtemail" model="information" ref="/user/email">`
        `<xforms:label>`Email address</xforms:label>
        `<xforms:hint>`Enter your email address here</xforms:hint>
    </xforms:input>
</td>

`<td>`
    `<xforms:input id="txtage" model="information" ref="/user/age">`
        `<xforms:label>`Age</xforms:label>
        `<xforms:hint>`Enter your age here</xforms:hint>
    </xforms:input>
</td>

</table>

`<xforms:submit submission="submit">`
    `<xforms:label>`Save</xforms:label>
    `<xforms:hint>`Save the information entered above to a local file</xforms:hint>
</xforms:submit>

</body>
</html>

In this case, I've explicitly told the XForms processor to include the XML declaration in the final form submission, but to only include that part of the instance data tree beginning with the element . Here's the output:

<?xml version="1.0" encoding="ISO-8859-1"?>`<name>`Harish</name>

It's important to note, also, that XForms supports submitting data in more than just PUT and POST methods, and that if you really need to, you can even replicate the behaviour of traditional HTML forms by submitting your data in the standard name=value format. The method atribute of the element can take any of the values "post", "get", "put", "multipart-post" and "form-data-post" - take a look at the specification to see what each one of these does.

Not My Type

Speak to any Web developer, and they're sure to complain about the need to write complex JavaScript code to carry out elementary data validation on form input. With XForms, they are no longer hostage to JavaScript - XForms is closely integrated with XML Schema, and can use XML Schema datatypes to perform both simple and complex form validation. Take a look at the next example, which illustrates:

`<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xforms="http://www.w3.org/2002/xforms/cr">`
`<head>`

<!-- define the form model -->
`<xforms:model id="enter">`
    `<xforms:instance>`
        <user xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
          `<name xsi:type="xsd:string" />
          `<age xsi:type="xsd:integer" />
          `<dob xsi:type="xsd:date" />
          `<height xsi:type="xsd:float" />
          `<weight xsi:type="xsd:decimal" />
          `<immunizaton xsi:type="xsd:boolean" />
        </user>
    </xforms:instance>
</xforms:model>

`<basefont face="Arial">`

</head>

`<body>`

`<font size="+1">`So you wanna join The Army?</font>`<br />``<br />`

<!-- form controls -->
`<xforms:input id="txtname" model="enter" ref="/user/name">`
    `<xforms:label>`Name</xforms:label>
    `<xforms:hint>`Enter your name</xforms:hint>
    `<xforms:alert>`Haven't you got a name, mate?</xforms:alert>
</xforms:input>

`<xforms:input id="txtage" model="enter" ref="/user/age">`
    `<xforms:label>`Age</xforms:label>
    `<xforms:hint>`Enter your age in years</xforms:hint>
    `<xforms:alert>`Can't you read? Tell me your age!</xforms:alert>
</xforms:input>

`<xforms:input id="txtdob" model="enter" ref="/user/dob">`
    `<xforms:label>`Date of birth</xforms:label>
    `<xforms:hint>`Enter your date of birth</xforms:hint>
    `<xforms:alert>`You can forget your birthday gift, brother!</xforms:alert>
</xforms:input>

`<xforms:input id="txtheight" model="enter" ref="/user/height">`
    `<xforms:label>`Height</xforms:label>
    `<xforms:hint>`Enter your height in feet and inches</xforms:hint>
    `<xforms:alert>`Don't have a measuring tape, do you?</xforms:alert>
</xforms:input>

`<xforms:input id="txtweight " model="enter" ref="/user/weight">`
    `<xforms:label>`Weight</xforms:label>
    `<xforms:hint>`Enter your weight in pounds</xforms:hint>
    `<xforms:alert>`Come on fatso, 'fess up!</xforms:alert>
</xforms:input>

`<xforms:input id="boolimmunization" model="enter" ref="/user/immunization">`
    `<xforms:label>`Have you been immunized against major diseases?</xforms:label>
    `<xforms:hint>`Enter your immunization status</xforms:hint>
    `<xforms:alert>`Don't lie, we have syringes with large needles!</xforms:alert>
</xforms:input>

</body>
</html>

At first glance, the above example looks much like the examples we have discussed so far. But a closer inspection of the model reveals a subtle difference...

`<xforms:model id="enter">`
    `<xforms:instance>`
        <user xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
          `<name xsi:type="xsd:string" />
          `<age xsi:type="xsd:integer" />
          `<dob xsi:type="xsd:date" />
          `<height xsi:type="xsd:float" />
          `<weight xsi:type="xsd:decimal" />
          `<immunizaton xsi:type="xsd:boolean" />
        </user>
    </xforms:instance>
</xforms:model>

For starters, I have two new namespaces, one for XML Schema and the second for the XML Schema instance. Both these are required for the new features that I'm going to introduce in this example.

Next, take a closer look at the definition of the XForms model. You'll see that each element contains a new "xsi:type" attribute. This attribute, which references datatypes from the XML Schema namespace (hence the "xsd:" namespace identifier) makes it possible to easily use those datatypes within XForms. Once datatypes have been defined in this manner, and links to the form controls have been set up via "ref" attributes, the XForms processor will generate an error if the data entered into a field does not match the datatype associated with it in the form model.

How is this error handled? Simple - with the new element, which can be used to display control-specific errors or alerts. Take a look at this snippet, which illustrates:

`<xforms:input id="txtage" model="enter" ref="/user/age">`
    `<xforms:label>`Age</xforms:label>
    `<xforms:hint>`Enter your age in years</xforms:hint>
    `<xforms:alert>`Can't you read? Tell me your age!</xforms:alert>
</xforms:input>

Here, if the user does not enter data consistent with the definition for the control, the message specified in the field will be displayed.

The Number Game

If you have special needs, you can even create your own datatypes using XML Schema, instead of restricting yourself to the pre-defined datatypes. Consider the following example, which illustrates:

`<html xmlns="http://www.w3.org/1999/xhtml"  xmlns:xforms="http://www.w3.org/2002/xforms/cr">`

`<head>`

<!-- form model -->
`<xforms:model id="information" schema="users.xsd">`
    `<xforms:instance>`
        `<user>`
            `<name xsi:type="xsd:string"  />`
            `<email xsi:type="xsd:string" />
            `<age xsi:type="adultsOnly" />
        </user>
    </xforms:instance>
    `<xforms:submission id="submit" ref="/user/name" action="/scripts/save.cgi" method="post" indent="yes" omit-xml-declaration="no" />
</xforms:model>

`<basefont face="Arial">`

</head>

`<body>`

<!-- define interface controls -->
`<table cellspacing="5" cellpadding="5" border="0">`
`<tr>`
`<td>`
    `<xforms:input id="txtname" model="information" ref="/user/name">`
        `<xforms:label>`Name</xforms:label>
        `<xforms:hint>`Enter your name here</xforms:hint>
    </xforms:input>
</td>
</tr>

`<tr>`
`<td>`
    `<xforms:input id="txtemail" model="information" ref="/user/email">`
        `<xforms:label>`Email address</xforms:label>
        `<xforms:hint>`Enter your email address here</xforms:hint>
    </xforms:input>
</td>

`<td>`
    `<xforms:input id="intage" model="information" ref="/user/age">`
        `<xforms:label>`Age</xforms:label>
        `<xforms:hint>`Enter your age here</xforms:hint>
    </xforms:input>
</td>

</table>

`<xforms:submit submission="submit">`
    `<xforms:label>`Save</xforms:label>
</xforms:submit>

</body>
</html>

You'll notice that I've defined a custom type in the form model - something called "adultsOnly". The definition for this type is stored in an XML Schema, and it only allows you to enter values equal to or over 18 into the corresponding field. Take a look:

<?xml version="1.0" encoding="UTF-8"?>
`<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">`
    `<xsd:simpleType name="adultsOnly">`
        `<xsd:restriction base="xsd:integer">`
            `<xsd:minInclusive value="18" />
        </xsd:restriction>
</xsd:simpleType>
</xsd:schema>

And that's about it for today. In this article, I began by discussing the XForms submission process, and explained how to use the <xforms:submission> element to save form input to a local file with the PUT method, and how to pass it to a server-side script for storage in a MySQL database. I also showed you a real-world example of the latter, by using the PHP SAX parser to parse the instance document generated by an XForms submission and convert it into an SQL query string.

Next, I taught you how XForms significantly simplifies the task of input validation, by integrating with XML Schema datatypes and allowing you to validate user input against those datatypes. Here too, I showed you a couple of different examples, one using XML Schema built-in datatypes and the other using my own custom types.

Next week, I'll be delving a little deeper into the XForms specification, explaining binding, form events, and built-in functions. Until then...stay healthy!

Note: Examples are illustrative only, and are not meant for a production environment. Melonfire provides no warranties or support for the source code described in this article. YMMV!

This article was first published on08 Nov 2003.