In this guide we will create two XChain commands that will output html select and option elements. This example assumes that the reader understands the SAX event model and is familar with the org.xml.sax.* classes. If you are not, those interfaces can be found in the Java SE 1.4+ javadocs and information about the SAX event model can be found at http://www.saxproject.org/.
This example will define a set of elements that look like this:
<xhtml:select id="id" name="number"> <xhtml:option value="">--select one--</xhtml:option> <xhtml:option value="one">One</xhtml:option> <xhtml:option value="two">Two</xhtml:option> <xhtml:option value="three">Three</xhtml:option> </xhtml:select>
First, we need a package for our classes if we do not already have one. To do this, we will create a directory for the package and then place a package-info.java file in the package, that defines the namespace for our commands.
/** * <p>The Guide command package.</p> */ @org.xchain.annotations.Namespace(uri="http://www.xchain.org/guide/xhtml") package org.xchain.example.namespaces.xhtml;
Next, we need to create an abstract class that implements the org.xchain.Chain interface. This will allow other xchains to be nested inside of our select class.
The class will also need access to a content handler for the output nodes. We will do this by asking the org.xchain.namespaces.sax.Pipeline command for the configuration tied to the current thread. This configuration object provides access to the org.xml.sax.ContentHandler object being used by the current execution. Passing an html fragment to this handler will allow us to write an html fragment to the output using sax events.
This is the code to access the content handler:
protected CommandHandler getContentHandler() { return ((CommandXmlReader) PipelineCommand.getPipelineConfig().getXmlReader()).getCommandHandler(); }
The complete code for the <xhtml:select/> element:
package org.xchain.example.namespaces.xhtml; import javax.servlet.http.HttpServletRequest; import javax.xml.namespace.QName; import org.apache.commons.jxpath.JXPathContext; import org.xchain.Filter; import org.xchain.impl.ChainImpl; import org.xchain.annotations.Element; import org.xchain.annotations.Attribute; import org.xchain.annotations.AttributeType; import org.xchain.annotations.PrefixMapping; import org.xchain.framework.jxpath.Scope; import org.xchain.framework.jxpath.ScopedQNameVariables; import org.xchain.framework.sax.CommandXmlReader; import org.xchain.framework.sax.CommandHandler; import org.xchain.namespaces.sax.PipelineCommand; import org.xml.sax.ContentHandler; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; @Element(localName="select") public abstract class SelectCommand extends ChainImpl implements Filter { public static final QName SELECTED_VARIABLE_NAME = QName.valueOf("{http://www.xchain.org/guide/xhtml}selected"); public static final String SELECT_LOCAL_NAME = "select"; public static final String ID_LOCAL_NAME = "id"; public static final String NAME_LOCAL_NAME = "name"; public static final String XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; @Attribute( localName = "id", type = AttributeType.ATTRIBUTE_VALUE_TEMPLATE ) public abstract String getId(JXPathContext context); public abstract boolean hasId(); @Attribute( localName = "name", type = AttributeType.ATTRIBUTE_VALUE_TEMPLATE ) public abstract String getName(JXPathContext context); @Attribute( localName = "request", type = AttributeType.JXPATH_VALUE, defaultValue = "$servlet:request", defaultPrefixMappings = {@PrefixMapping(uri="http://www.xchain.org/servlet/1.0", prefix="servlet")} ) public abstract HttpServletRequest getRequest( JXPathContext context ); public boolean execute(JXPathContext context) throws Exception { // get the request and the name from the context. HttpServletRequest request = getRequest(context); String name = getName(context); // get the original value of the name. String currentValue = request.getParameter(name); // get the xhtml prefix and make sure that there is a mapping for it. String xhtmlPrefix = context.getPrefix(XHTML_NAMESPACE); if( xhtmlPrefix == null ) { throw new IllegalStateException("There is no mapping defined for "+XHTML_NAMESPACE); } // create the attributes for the element we are going to output. AttributesImpl attributes = new AttributesImpl(); if( hasId() ) { attributes.addAttribute("", ID_LOCAL_NAME, ID_LOCAL_NAME, "CDATA", getId(context)); } attributes.addAttribute("", NAME_LOCAL_NAME, NAME_LOCAL_NAME, "CDATA", getName(context)); ((ScopedQNameVariables)context.getVariables()).declareVariable(SELECTED_VARIABLE_NAME, currentValue, Scope.execution); ContentHandler handler = getContentHandler(); handler.startElement(XHTML_NAMESPACE, SELECT_LOCAL_NAME, qNameString(xhtmlPrefix, SELECT_LOCAL_NAME), attributes); boolean result = false; Exception exception = null; // execute the children and catch any exceptions. try { result = super.execute(context); } catch (Exception e) { exception = e; } // if there was not an exception, or the exception is not a sax exception, then we need to finish the output. if( exception == null || !(exception instanceof SAXException) ) { handler.endElement(XHTML_NAMESPACE, SELECT_LOCAL_NAME, qNameString(xhtmlPrefix, SELECT_LOCAL_NAME)); } // rethrow the exception. if( exception != null ) { throw exception; } return result; } public boolean postProcess( JXPathContext context, Exception e ) { ((ScopedQNameVariables)context).undeclareVariable(SELECTED_VARIABLE_NAME); return false; } protected CommandHandler getContentHandler() { return ((CommandXmlReader) PipelineCommand.getPipelineConfig().getXmlReader()).getCommandHandler(); } protected String qNameString( String prefix, String localName ) { return (prefix == null || prefix.equals("")) ? localName : prefix+":"+localName; } }
Now that the select command is defined, we need an option command. This command will look at the selected variable set by the select command and create the proper option attributes in the output document.
package org.xchain.example.namespaces.xhtml; import org.apache.commons.jxpath.JXPathContext; import org.xchain.impl.ChainImpl; import org.xchain.annotations.Attribute; import org.xchain.annotations.AttributeType; import org.xchain.annotations.Element; import org.xchain.framework.jxpath.Scope; import org.xchain.framework.jxpath.ScopedQNameVariables; import org.xchain.framework.sax.CommandXmlReader; import org.xchain.framework.sax.CommandHandler; import org.xchain.namespaces.sax.PipelineCommand; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; @Element(localName="option") public abstract class OptionCommand extends ChainImpl { public static final String OPTION_LOCAL_NAME = "option"; public static final String SELECTED_LOCAL_NAME = "selected"; public static final String SELECTED_VALUE = "selected"; public static final String VALUE_LOCAL_NAME = "value"; public static final String XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; @Attribute(localName = "value", type = AttributeType.ATTRIBUTE_VALUE_TEMPLATE) public abstract String getValue(JXPathContext context); public boolean execute(JXPathContext context) throws Exception { String value = getValue(context); String currentValue = (String)((ScopedQNameVariables) context.getVariables()).getVariable(SelectCommand.SELECTED_VARIABLE_NAME, Scope.execution); // get the xhtml prefix and make sure that there is a mapping for it. String xhtmlPrefix = context.getPrefix(XHTML_NAMESPACE); if( xhtmlPrefix == null ) { throw new IllegalStateException("There is no mapping defined for "+XHTML_NAMESPACE); } AttributesImpl attributes = new AttributesImpl(); if( value != null ) { attributes.addAttribute("", VALUE_LOCAL_NAME, VALUE_LOCAL_NAME, "CDATA", value); } if( value != null && value.equals(currentValue) ) { attributes.addAttribute("", SELECTED_LOCAL_NAME, SELECTED_LOCAL_NAME, "CDATA", SELECTED_VALUE); } ContentHandler handler = getContentHandler(); handler.startElement(XHTML_NAMESPACE, OPTION_LOCAL_NAME, qNameString(xhtmlPrefix, OPTION_LOCAL_NAME), attributes); boolean result = false; Exception exception = null; try { result = super.execute(context); } catch (Exception e) { exception = e; } if( exception == null || !(exception instanceof SAXException) ) { handler.endElement(XHTML_NAMESPACE, OPTION_LOCAL_NAME, qNameString(xhtmlPrefix, OPTION_LOCAL_NAME)); } if( exception != null ) { throw exception; } return result; } protected CommandHandler getContentHandler() { return ((CommandXmlReader) PipelineCommand.getPipelineConfig().getXmlReader()).getCommandHandler(); } protected String qNameString( String prefix, String localName ) { return (prefix == null || prefix.equals("")) ? localName : prefix+":"+localName; } }
Finally, we need to put our page into action. For this example, we will just post back to ourself with a GET request. After submitting the form, the page should redisplay with the correct value selected in the dropdown box.
<?xml version="1.0" encoding="UTF-8"?> <?xchain-stylesheet system-id="resource://context-class-loader/org/xchain/namespaces/servlet/xhtml.xsl"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:jsl="http://www.xchain.org/jsl/1.0" xmlns:xhtml="http://www.xchain.org/guide/xhtml"> <head> <title>SAX Events Example</title> </head> <body> <form method="GET"> <xhtml:select name="number" id="number"> <xhtml:option value="">- -Select A Number- -</xhtml:option> <xhtml:option value="one">One</xhtml:option> <xhtml:option value="two">Two</xhtml:option> <xhtml:option value="three">Three</xhtml:option> </xhtml:select> <input type="submit"/> </form> </body> </html>