Dynamic navigation with XForms

December 29th, 2009 by Henri Bezemer

XForms are a great way for a Java web developer to avoid the use of the JSF framework. Instead of authoring world-wide open standard (X)HTML pages, JSF lets you code JSP pages with JSF tags. JSF forces you from a truly open standard into a less open Java only standard. This alone makes me want to avoid JSF.

XForms is a programming language independent W3C open standard. In this post I will not try to show all the benefits of using XForms. I’ll assume that you’ve already decided that you want to use XForms with Java. XForms is powerful enough to provide most if not all of the presentation logic that your web site requires. In this post I will explain how dynamic navigation can be achieved fully in XForms, thereby stripping the server side Java code of all presentation logic. Dynamic navigation is a mechanism found in the JSF framework. Put very simply, an agent (browser) posts data collected on a page to a server and the outcome of processing this data determines the next page.

Choosing an XForms processor

XForms is an open standard for a few years now. However, in most browsers XForms is not supported natively. You need an XForms processor to make it work. I quickly tried a few open source XForms processors and I decided to stick with XSLTForms because it works quite well. XSLTForms uses XSLT and Ajax on the client side. Download it and install it on a harddrive or web server.

I’ve tested the example with Internet Explorer 8.

An example: login form

We all know the basic login form. After submitting a username and password, you either get forwarded to your personal page or you receive a message telling you that something is wrong with the username or password. Here is the XHTML/XForms code for the login form:

<?xml version="1.0" encoding="iso-8859-1"?>
<?xml-stylesheet href="file:///d:/apps/xsltforms-beta2/xsltforms/xsltforms.xsl" type="text/xsl"?>
<?xsltforms-options debug="yes"?>
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:xf="http://www.w3.org/2002/xforms"
	xmlns:ev="http://www.w3.org/2001/xml-events"
	xmlns:q="http://www.zienit.com/login">

	<head>
	<title>Please Log In</title>

	<xf:model>
	<xf:instance id="main">
		<q:login>
			<q:user/>
			<q:password/>
		</q:login>
	</xf:instance>

	<xf:instance id="outcome">
		<q:outcome/>
	</xf:instance>

	<xf:instance id="nav">
		<nav xmlns="">
			<url outcome="success">my-personal-page.html</url>
			<url outcome="password-expired">change-password.html</url>
			<msg outcome="password-incorrect">Sorry, incorrect login!</msg>
			<msg outcome="password-permanently-blocked">Your account is permanently blocked!</msg>
		</nav>
	</xf:instance>

	<xf:submission id="submit" action=http://localhost:8080/myapp/loginservice method="post" replace="instance" ref="instance('main')" instance="outcome">
		<xf:action ev:event="xforms-submit-done">
			<xf:load ref="instance('nav')/url[@outcome=instance('outcome')]"/>
		</xf:action>
	</xf:submission>

	<xf:bind nodeset="q:user" required="true()"/>
	<xf:bind nodeset="q:password" required="true()"/>

	<xf:bind id="message" nodeset="instance('nav')/msg[@outcome=instance('outcome')]"/>

</xf:model>

</head>
<body>

<xf:input ref="q:user">
	<xf:label>User</xf:label>
</xf:input>

<xf:secret ref="q:password">
	<xf:label>Password</xf:label>
</xf:secret>

<xf:output bind="message"/>

<xf:submit submission="submit">
	<xf:label>OK</xf:label>
</xf:submit>

</body>
</html>

Note that this file must be an XML file. Line 2 contains an important processing instruction. This will instruct the XSLT processor built into the browser to transform the XML as specified by the XSLTForms stylesheet (which I installed in a specific local directory; change this to your specific location). The next line contains another processing instruction. This one is specific to XSLTForms and tells it to display debug info at the bottom of the page. At line 4, the XHTML html root element provides the namespace prefixes that are needed in the rest of the document: the default namespace is used for XHTML itself, the prefix xf is used to denote XForms elements, the prefix ev is used to denote attributes related to XML events and the prefix q is used to denote my specific XML data.

Lines 13-18 outlines the primary instance data, or put differently the data that is being collected by the form. This instance data is identified as main. Line 20-22 defines the instance data that will contain the response from the server after submission of the primary data. Lines 24-30 contain a third instance, which will be used as a table. It’s entries are either url’s or messages. All entries are identified by an attribute called outcome.

Line 33-37 define what will happen on submission of the form. This part is crucial. The data collected in the instance main (ref=”instance(’main’)”) will be posted (method=”post”) to a servlet (action=”…”) and the response of the servlet will be assigned to instance outcome (replace=”instance” in conjunction with instance=”outcome”). Important to note is that the XForm is “still there” even after receiving the response of the servlet. This is quite different from normal HTML forms. Line 34 defines an event handler that responds to the self explanatory event xforms-submit-done. It will execute the action load, which will “load” a different url into the browser window (in other words: it navigates to some url). The attribute ref contains an XPath expression that selects a url from the table (line 24-30) based on the response of the servlet. Note that in certain cases the XPath expression yields no result, in which case the load action will do nothing (doing nothing means the form stays visible in the browser).

Line 39-40 state which elements in the primary instance are required (both user and password). Line 42 is an interest one. It binds the name message to a value taken from the table using a XPath expression closely resembling the expression on line 35. Note that this XPath expression may yield zero or one message.

Line 41-61 define the controls - two input fields, one output field and the submit button - that make up the form. In the real world, you would also add a bit of CSS to get a more visually pleasing result.

A bit of Java

For the server side I’ve provided the following old-school Java servlet:

package com.zienit.xforms;

import java.io.IOException;
import java.util.Iterator;

import javax.servlet.ServletException;
import javax.servlet.http.*;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;

public class LoginServlet extends HttpServlet {

	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		try {
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			dbf.setNamespaceAware(true);

			DocumentBuilder parser = dbf.newDocumentBuilder();
			Document document = parser.parse(request.getInputStream());

			XPathFactory xf = XPathFactory.newInstance();
			XPath xpath = xf.newXPath();
			xpath.setNamespaceContext(new NamespaceContext() {
				public String getNamespaceURI(String prefix) {
			        if (prefix == null) throw new NullPointerException("Null prefix");
			        else if ("q".equals(prefix)) return "http://www.zienit.com/login";
			        else if ("xml".equals(prefix)) return XMLConstants.XML_NS_URI;
			        return XMLConstants.NULL_NS_URI;
				}
				public String getPrefix(String namespaceURI) {
					throw new UnsupportedOperationException();
				}
				public Iterator getPrefixes(String namespaceURI) {
					throw new UnsupportedOperationException();
				}
			});
			String user = xpath.evaluate("/q:login/q:user", document);
			String password = xpath.evaluate("/q:login/q:password", document);
			String outcome = "password-incorrect";
			if ("expire".equals(password)) {
				outcome = "password-expired";
			} else if ("hacker".equals(user)) {
				outcome = "password-permanently-blocked";
			} else if (user.equals(password)) {
				outcome = "success";
			}
			response.setContentType("application/xml");
			response.getWriter().println("<?xml version='1.0' encoding='iso-8859-1'?><outcome xmlns='http://www.zienit.com/login'>" + outcome + "</outcome>");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Instead of this servlet I could have provided a RESTful service, but I decided to use old-school Java technology so that the focus remains with the XForms part. The servlet parses the XML it receives into a DOM tree and uses XPath to extract the username and password. Then it employs certain “business logic” and returns the outcome of the process to the client. Notice that no presentation logic is part of this servlet!

In this post I’ve showed you a hint of how you can put your presentation logic into your “static” XHTML/XForms and out of your Java code. I’m planning another post about XForms in which I’ll show its exceptional power.

2 Comments

1

[...] Dit blogartikel was vermeld op Twitter door Alain Couthures, Henri Bezemer. Henri Bezemer heeft gezegd: Posted a new blog entry: Dynamic navigation with XForms: http://bit.ly/6JZWff. All presentation logic moved from Java to XForms. Yeah! [...]

December 29, 2009 at 11:30 am Dominique Rabeuf

2

Very fine and useful sample