The PHP XForms library: What's next?
At this point you have a PHP library that's mostly functional, yet primarily untested. Now it is time to improve the look of the output, validate the inputs to the functions (errors from the XForms preprocessor aren't always as helpful), and see a demonstration of the library in action.
So, let's go ahead and do all of this.
With the core of the library complete, you're in good shape to build some enhancements into the PHP XForms library, such as some simple exception throwing and handling, which is useful since not all inputs can acceptably be set, or errors will inevitably result in the resultant XHTML document. In addition, you need a few convenience functions that will help output the XHTML to the Web browser, and another to help format the data with correct indentations. Let's start with error checking.
By adding error checking to the library in this section, you'll reduce programming time by having fewer errors to debug through the XForms preprocessor. That will be a big help when all is said and done. You'll perform error checking in two ways:
- First, for XHTML tags that allow subelements like the final four (action, model, trigger, and repeat) you added in Part 1, you'll make sure that only the allowed XHTML tags are added as children.
- Lastly, you'll perform error checks on inputs to the individual functions that require it, making sure that certain conditions never occur, and, if they do, an Exception will be thrown, caught, and displayed on the browser.
To begin, go to the list of variables in the xforms_lib.php class file, and add two more variables, as shown in Listing 1.
Listing 1. New variables
...
var $namespaceEvents;
var $allowed;
var $tag;
function xforms_lib($ns, $nsxforms, $nsevents){
...
$this->tag = '';
$this->allowed =
array('action' => array('dispatch', 'insert',
'setvalue', 'load'),
'model' => array('instance', 'submission',
'bind', 'action'),
'trigger' => array('label', 'action'),
'root' => array('trigger', 'submit', 'select1',
'repeat', 'input', 'output',
'label', 'model'));
...
|
The new variables are the $allowed and $tag variables. In the constructor you'll initialize them both. You'll learn more about the $tag variable later in this section, but suffice it to say that it holds the name of the tag currently open. Every value in Listing 1 is a top-level XForms XHTML tag. Root is used when none is specified (repeat and root are the same, and so when a repeat tag is open, root is used for this type of error checking). For example, take a look at 'action': Only dispatch, insert, setvalue, and load XHTML tags are allowed as children. Each tag is different, and so a different subset of children are allowed.
Next you'll modify the dispatchTag method to take advantage of this new functionality, as shown in Listing 2.
Listing 2. Modifying the
dispatchTag function
function dispatchTag($name, $target){
$this->check('dispatch');
$xml = '<xforms:dispatch';
if($name != '')
$xml .= ' name="'.$name.'"';
if($target != '')
$xml .= ' target="'.$target.'"';
$xml .= " />";
}
|
Notice what's new here. You're passing the name of the tag being created to the new function check (which you'll define in a moment). The rest should be the same.
Now you'll define the check function that performs the actual checks, as shown in Listing 3.
Listing 3. The
check function
function check($newTag, $action=''){
$openTag = $this->tag;
if($openTag == '')
$openTag = 'root';
if($action == 'close' && ($openTag != $newTag || $openTag == 'root'))
throw new Exception("Cannot close $openTag with a $newTag ".
"\"close tag\"statement!");
else if($action == 'close' && $openTag == $newTag)
return;
else if(!in_array($newTag, $this->allowed["$openTag"]))
throw new Exception("$newTag is not allowed within $openTag!");
}
|
- First
ifstatement: Note that if the empty string ('') is the current value of the $tag class variable, then the $openTag parameter defaults to 'root'. - Second
ifstatement: If the opened tag specified in $openTag is being closed, and the opened tag ($openTag) is not the same as the tag ($newTag) being closed (or is the root tag since the root tag can't be closed), then an error will be thrown. - First
else ifstatement: Otherwise, if the tag ($newTag) being closed is equal to the open tag ($openTag), then no error results. - Second
else ifstatement: However, if the tag being opened/created ($newTag) is not one of the allowed tags being opened within the currently opened tag ($openTag) -- a repeat tag trying to be created within an action tag, for example -- then an error will be thrown. Such errors will be caught and displayed to the user.
You'll now use the check function by adding check statements to the rest of the functions, with a few shown to you as guides in Listing 4 (you can find the rest in the source code download).
Listing 4. Adding
check statements
function loadTag($resource = '', $ref = '', $show='replace'){
$this->check('load');
$xml = '<xforms:load show="'.$show.'"';
...
}
function triggerTagOpen($ref, $submission = '', $label = 'default'){
$this->check('trigger', 'open');
$xml = '<xforms:trigger ref="'.$ref.'"';
if($submission != '')
$xml .= ' submission="'.$submission.'"';
$xml .= ' >';
}
function triggerTagClose(){
$this->check('trigger', 'close');
$xml = '</xforms:trigger>';
}
|
Here you need to be sure to add to a call to the $this->check() fucntion to each function that creates an XHTML tag. Note the difference in calling the check function in the triggerTagOpen and triggerTagClose functions where a second parameter is passed ('open' or 'close'), depending on whether or not the tag is being opened or closed.
Now you'll create the function that sets the currently open tag ($this->tag), which is currently being referenced in Listing 3. You'll use this function to set the value of the currently open tag, as shown in Listing 5.
Listing 5. The
setTag function
function setTag($t){
$this->tag = $t;
}
|
You'll use this function to set the tag of the highest element. For example, after opening a model tag, you'll call this function as follows: setTag('model'). After closing the tag, you'll no longer be operating under an XForms XHTML tag, and so you'll call it like this: setTag('root'), resetting the currently open tag to root.
Now it's time to validate inputs by throwing exceptions when violations are found. Only five functions require this error-checking capability: submission, bind, load, select1 and instance. Start with submission, as shown in Listing 6.
Listing 6. The
submissionTag function: Throwing exceptions on input violations
function submissionTag($id, $action, $method = 'post', $ref='',
$instance = '', $replace = ''){
$this->check('submission');
if(($instance != '' || $replace != '') &&
$replace != '' && $instance != '')
throw new Exception("instance or replace is set, but not both ".
"in a submission tag!");
$xml = '<xforms:submission id="'.$id.'" action="'.$action.
'" method="'.$method.'"';
...
}
|
For the submission tag, the $instance or $replace parameters can be set individually, but they cannot both be set at the same time. When using this function an exception will be thrown and displayed to the user.
The idea is similar in the other four functions, as shown in Listing 7.
Listing 7. More functions throwing exceptions on input violations
function bindTag($nodeset, $relevant = '', $calculate = '',
$required = ''){
$this->check('bind');
if($relevant == '' && $calculate == '' && $required == '')
throw new Exception("Must set one of: relevant, calculate or ".
"required in a bind tag!");
$xml = '<xforms:bind nodeset="'.$nodeset.'"';
...
}
function loadTag($resource = '', $ref = '', $show='replace'){
$this->check('load');
if($resource != '' && $ref != '')
throw new Exception("Cannot specify both a resource and ref ".
"for the load tag!");
else if($resource == '' && $ref == '')
throw new Exception("Must specify one of: resource or ref ".
"for the load tag!");
$xml = '<xforms:load show="'.$show.'"';
...
}
function select1Tag($ref, $label, $itemArray, $itemset){
$this->check('select1');
if(is_array($itemArray) && is_array($itemset))
throw new Exception("Cannot specify both a list of items ".
"and an itemset for a select1 element!");
else if(!is_array($itemArray) && !is_array($itemset))
throw new Exception("Must specify one of: itemArray or itemset ".
"as arrays in the select1 tag!");
$xml = '<xforms:select1 ref="'.$ref.'">';
$xml .= '<xforms:label>'.$label.'</xforms:label>';
...
}
function instanceTag($id = '', $instanceXML = '', $src = ''){
$this->check('instance');
if($instanceXML != '' && $src != '')
throw new Exception("Must define instance data or a src URL ".
"for the instance tag!");
$xml = '<xforms:instance';
...
}
|
Note for the bindTag function that one of the $required, $calculate or $required inputs must be set. For the loadTag function, both the $resource and $ref inputs cannot be set at the same time, but one of the two must be set. The select1Tag function requires that one of $itemArray and $itemset be set but not both. Lastly, the instanceTag function requires that both $instanceXML and $src cannot be set.
This ends the section on error handling. Later in the testing section you'll see how to handle thrown errors and display the error message to the user.
To top off the error handling you'll add several convenience functions that will assist developers in their XForms development using PHP.
First you'll define a few more class variables, as shown in Listing 8.
Listing 8. More class variables
var $allowed;
var $print;
var $indentation;
var $indentationValue;
function xforms_lib($ns, $nsxforms, $nsevents){
...
$this->namespaceEvents = $nsevents;
$this->print = 0;
$this->indentation = 0;
$this->indentationValue = " ";
$this->allowed =
...
}
|
Three more variables are created here to help format the XHTML output. The $print variable specifies whether or not to output XHTML (using echo statements) to the browser directly within class functions using the printData function (defined later). The $indentation variable specifies how many levels of indentation to output when the indentation function is called (defined later), and the $indentationValue variable specifies what to display as indentation (four spaces are used as the $indentationValue here).
Now you'll create a few functions to set the value of $print, and increment or decrement the $indentation count, as shown in Listing 9.
Listing 9. Functions to modify $indentation and $print
function incrementIndentation(){
$this->indentation++;
}
function decrementIndentation(){
$this->indentation--;
}
function setPrint($p){
if($p)
$this->print = 1;
else
$this->print = 0;
}
|
The first two functions either increment or decrement the $indentation class variable initialized to zero in the constructor, and the third one sets $print to 1 if the passed in variable equates to true; otherwise, $print is set to 0.
This next function displays the indentation, shown in Listing 10.
Listing 10. Displaying the indentation
function indentation(){
$xml = '';
for($i = $this->indentation; $i > 0; $i--)
$xml .= $this->indentationValue;
return $xml;
}
|
This function iterates as many times as the value of the $indentation class variable, and for each iteration it adds the $indentationValue class variable to $xml, and returns it. So if $indentation were 2, then eight spaces would accumulate in $xml before being returned. This function, along with the decrementIndentation and incrementIndentation functions, make the XHTML code come out formatted with proper indenting.
Listing 11 shows how to display XHTML using the printData function.
Listing 11. Displaying XHTML
function printData($xml){
if($this->print){
echo $this->indentation();
echo $xml."\r\n";
return 0;
}
return 1;
}
|
The end of each function calls the printData function, and based on the value of the $print class variable the XHTML data will either be returned from the function or displayed to the browser. Add it to all tag creation functions, as shown by a few examples in Listing 12. Add the incrementIndentation function to all tagOpen functions and decrementIndentation to all tagClose functions.
Listing 12. Using the
printData, incrementIndentation and decrementIndentation functions
function submissionTag($id, $action, $method = 'post', $ref='',
$instance = '', $replace = ''){
$this->check('submission');
...
$xml .= ' replace="'.$replace.'"';
$xml .= " />";
$this->printData($xml);
return $xml;
}
function triggerTagOpen($ref, $submission = '', $label = 'default'){
$this->check('trigger', 'open');
$xml = '<xforms:trigger ref="'.$ref.'"';
if($submission != '')
$xml .= ' submission="'.$submission.'"';
$xml .= ' >';
$this->incrementIndentation();
$this->printData($xml);
return $xml;
}
function triggerTagClose(){
$this->check('trigger', 'close');
$xml = '</xforms:trigger>';
$this->decrementIndentation();
$this->printData($xml);
return $xml;
}
|
Here you see the use of $this->printData function in each of the above functions, in Listing 12. Note that even if the data is already displayed to the browser with the call to printData, the XHTML data still gets returned to the statement calling the function. Note also how the triggerTagOpen function uses the incrementIndentation function. Thus, everything after the trigger tag is opened will have extra indentation until the trigger tag is closed with the call to triggerTagClose. This decreases the indentation back to the original amount before the triggerTagOpen function was called.
Add $this->printData($xml); statements to the rest of the functions in the appropriate places (you can use the source code download as a guide). Also add $this->decrementIndentation(); statements to all tagClose statements and $this->incrementIndentation(); statements to all tagOpen statements using Listing 12 and the source code download as guides.
Finally, there are just a few more convenience functions to help make your use of this PHP XForms library a little easier, as shown in Listing 13. You can use these functions in the PHP file to help with the display of necessary regular HTML tags.
Listing 13. Convenience functions
function doctypeTag(){
$xml = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
$this->printData($xml);
return $xml;
}
function headTitle($title){
$xml = "<head><title>$title</title>";
$this->printData($xml);
return $xml;
}
function closeHeadOpenBody(){
$xml = "</head><body>";
$this->printData($xml);
return $xml;
}
function closeBodyCloseHtml(){
$xml = "</body></html>";
$this->printData($xml);
return $xml;
}
function outputXHTMLheader(){
header("Content-Type: application/xhtml+xml; charset=UTF-8");
}
|
You'll use each of the above functions in the next section as you formulate the form that you'll create to test the XForms PHP library. They simply create the tags, and output the headers required to be XHTML compliant.
Creating a form using the PHP XForms library
It's time to see in action just how useful this XForms PHP library can be. In this section several XForms widgets will be instantiated and shown to be working using the Firefox XForms plugin.
First take a look at the beginning of the new index page, as shown in Listing 14.
Listing 14: The start of the new index page
<?php
include('lib/xforms_lib.php');
$xformsDoc = new xforms_lib("http://www.w3.org/1999/xhtml",
"http://www.w3.org/2002/xforms",
"http://www.w3.org/2001/xml-events");
$xformsDoc->setPrint(1);
$xformsDoc->outputXHTMLheader();
$xformsDoc->doctypeTag();
$xformsDoc->htmlTag();
$xformsDoc->incrementIndentation();
$xformsDoc->headTitle('XForms served via PHP');
...
?>
|
The first line includes the library into your PHP file, where you instantiate a new xforms_lib class and pass in the three namespaces. Then you set the $print class variable to 1, specifying to the library to output XHTML directly. The XHTML document is then set up by outputing the appropriate headers, the doctype, the HTML tag, and the title.
Next, you'll see how to create an XForms model using the library (see Listing 15).
Listing 15. Creating an XForms model
...
// display model here
$model1 = 'model';
$instance1 = 'instance';
$instance1data = '<root xmlns=""><data1>../here</data1><data2/>'.
'<data3/><data4/><data5/></root>';
$instance2 = 'instance2';
$instance2data = '<root xmlns=""><data><datum>data1</datum>'.
'<datum>d2</datum><datum>third data</datum></data>
</root>';
$submitButton1 = 'submit1';
$submitButton2 = 'submit2';
$xformsDoc->setTag('root');
$xformsDoc->modelTagOpen($model1);
$xformsDoc->setTag('model');
$xformsDoc->incrementIndentation();
$xformsDoc->instanceTag($instance1, $instance1data);
$xformsDoc->instanceTag($instance2, $instance2data);
$xformsDoc->submissionTag($submitButton1,
"receive.php", 'post', "instance('$instance1')");
$xformsDoc->submissionTag($submitButton2,
"receive.php", 'post', "instance('$instance2')");
$xformsDoc->bindTag("instance('$instance1')/data4",
"instance('$instance1')/data3='1'",
"instance('$instance1')/data2");
$xformsDoc->modelTagClose();
$xformsDoc->setTag('root');
$xformsDoc->decrementIndentation();
...
|
The first several lines are the PHP data variables that contain model, instance, and submission names. Two instance document XML data are also defined. Then the $tag class variable is initialized to 'root' with a call to setTag.
You're now ready to open the model tag, which is what's accomplished on the following line. The $tag variable is then set again to 'model' this time, as the model tag was just opened and root is no longer the top-level tag. This also means that the indentation needs to be incremented, hence the call to incrementIndentation.
Next the two instance documents are instantiated, followed by two submission tags. They will both send one of the two instance documents sent using POST to the receive.php page you created in Part 1. A bind tag is also created that binds data4 (ref) to data2 (calculate) using calculate, and data3 (relevant) makes data4 relevant. Finally, the model tag is closed, $tag gets reset to 'root' and the indentation is decremented.
You can now begin creating the XHTML body, as shown in Listing 16.
Listing 16. Using
inputTag and outputTag
...
$xformsDoc->closeHeadOpenBody();
$xformsDoc->incrementIndentation();
// display form here
$xformsDoc->inputTag("instance('$instance1')//data1",
'data1 (new page input): ');
echo "<br />";
$xformsDoc->inputTag("instance('$instance1')//data2",
'data2 (data4 is bound to this field): ');
echo "<br />";
$xformsDoc->inputTag("instance('$instance1')//data3",
'data3 (1 makes data4 relevant): ');
echo "<br />data4 (bound to data2): ";
$xformsDoc->outputTag("instance('$instance1')//data4");
echo "<br />data5 (value of select menu shown here): ";
$xformsDoc->outputTag("instance('$instance1')//data5");
echo "<br />";
...
|
First the XHTML head tag gets closed, and the body tag opened. Then three input tags are created, each referencing to data1, data2 ,and data3, respectively, of $instance1, followed by creating two output tags with each referencing data4 and data5, respectively, also of $instance1.
You can verify in the form that playing with these values will cause the bind element created above in Listing 15 to work as expected.
Next you'll define a select1 tag, as shown in Listing 17.
Listing 17. Defining a select1 tag
...
$itemset['nodeset'] = "instance('$instance2')//datum";
$itemset['label'] = '.';
$itemset['value'] = '.';
$xformsDoc->select1Tag("instance('$instance1')//data5", 'select me',
'', $itemset);
echo "<br />";
...
|
First you set up the $itemset array with a nodeset containing all the datum elements in $instance2. The select1 tag is then instantiated and referenced to data5 of $instance1. Note also that playing with the select1 tag will cause the value of the second output tag defined in Listing 16 to be changed accordingly.
Next define the two submit buttons. These two buttons will allow you to see the actual XML of each instance. See Listing 18.
Listing 18. Defining submit buttons
...
$xformsDoc->submitTag($submitButton1, 'SubmitTextBoxes');
echo "<br />";
$xformsDoc->submitTag($submitButton2, 'SubmitSelect');
echo "<br />";
...
|
Labeled appropriately, you can play with the values in the text boxes and select1 menu box, and then click each of the submit buttons to see what the values in your XML are.
Now you'll test out a trigger tag, as shown in Listing 19.
Listing 19. Instantiating a trigger tag
...
$xformsDoc->triggerTagOpen("instance('$instance1')//data1", '', 'new page');
echo "<br />";
$xformsDoc->setTag('trigger');
$xformsDoc->actionTagOpen("DOMActivate");
$xformsDoc->setTag('action');
$xformsDoc->loadTag('', "instance('$instance1')/data1",
'new');
$xformsDoc->actionTagClose();
$xformsDoc->setTag('trigger');
$xformsDoc->triggerTagClose();
$xformsDoc->setTag('root');
...
|
Here you create a trigger tag whose label is 'new page.' Once open, you set the $tag variable to 'trigger' and within the trigger tag you create an action tag, activated on the DOMActivate event. Once within this tag you set $tag to 'action,' create a load tag that loads a new page based on the value of data1 (feel free to play with the values of data1 and pressing this trigger). Lastly, you close both the action and trigger tags, and reset $tag to root.
The last tag you test is repeat, as shown in Listing 20.
Listing 20. Creating a repeat
...
echo "<br/>repeat data:";
$xformsDoc->repeatTagOpen("instance('$instance2')//datum", 'id1');
$xformsDoc->setTag('root');
$xformsDoc->outputTag(".");
$xformsDoc->setTag('repeat');
$xformsDoc->repeatTagClose();
$xformsDoc->setTag('root');
$xformsDoc->decrementIndentation();
$xformsDoc->decrementIndentation();
$xformsDoc->closeBodyCloseHtml();
?>
|
Here you specify the contents of the repeat as all the datum children in $instance2. Because repeat is equivalent to root when it comes to adding new elements, you set $tag to 'root.' You then display an output tag that displays the contents of the current XML contents of datum. $tag is then set to 'repeat' before closing the repeat tag, and after closing, $tag is reset to root.
After all the XHTML XForms tags are created, the XHTML document is closed off by decrementing the indentation value twice and closing both the body and html tags.
You can preview the form in Figure 1.
Figure 1. The resulting form
Be sure to right click the XHTML page and click view source, this allows you to verify the XHTML output of your PHP (see Figure 2).
Figure 2. Formatting working
Be sure to play with the form, and with the PHP-generated XForms widgets you created.
Congratulations. You now have a fairly robust PHP XForms library that you can use and continue to develop and extend. Note that the library and its defined XForms XHTML tags isn't necessarily complete, and there is still room for extensions to this library and its error-checking capabilities. For example, you can define new functions that define new XForms XHTML tags, etc. Essentially, the possiblities are endless and an entire open source project could be centered on an endeaver such as what you've begun to create in this two-part series. Enjoy your journey with PHP and XForms, and good luck!
| Description | Name | Size | Download method |
|---|---|---|---|
| Part 2 sample code | code_part2.zip | 4KB | HTTP |
Information about download methods
Learn
-
To learn more about integrating XForms and graphics using SVG see the article: Creating an XForms-based logo generator (developerWorks, ).
-
Get a basic introduction to XForms in Introduction to XForms, Part 1: The new Web standard for forms (Chris Herborth, developerWorks, September 2006).
-
Get the The PHP Manual.
-
Visit PHP.NET for PHP documentation.
-
For tutorials on learning to program with PHP, check out the developerWorks Learning PHP series.
-
Visit IBM developerWorks' PHP project resources to learn more about PHP.
-
Learn more about XForms submission events in the XForms tip: Using form submission events (Nicholas Chase, developerWorks, November 2006).
-
Find out how to accept XForms data in Java (Nicholas Chase, developerWorks, October 2006), Perl (Tyler Anderson, developerWorks, October 2006), and PHP (Nicholas Chase, developerWorks, October 2006).
-
IBM XML certification: Find out how you can become an IBM-Certified Developer in XML and related technologies.
-
XML technical library: See the developerWorks XML Zone for a wide range of technical articles and tips, tutorials, standards, and IBM Redbooks.
-
developerWorks technical events and webcasts: Stay current with technology in these sessions.
-
Learn more about XForms in the IBM developerWorks XML zone.
Get products and technologies
-
Get PHP V 5.2.3.
-
Get the Firefox XForms plugin.
-
The XForms Recommendation is maintained by the W3C.
Discuss
Comments (Undergoing maintenance)





