Good <user:time-of-day/>!

I've been trying to go deeper into XForms and implement an xsl stylesheet
that transforms a page from XForms markup into HTML. The following are
random thoughts that were coming to me during it and they are accorded by
the result of my try, consisting of a sample page in XForms format, a schema
for instance data and the stylesheet used to display the page. (Just to
remind what is XForms: it's an XML mark up format for the next generation of
HTML forms. See: http://www.w3c.org/MarkUp/XForms)

1. Introduction
A page in XForms format looks like this:
<page>
    <model>
        <instance/>
        <schema/>
        <bind/>*
        <submitInfo/>*
    </model>
    <group/>*
</page>

The 'model' contains: instance data, schema (W3C Schema) for the instance
data, bindings (=constraints) and submit info elements. Usually, all the
parts, except 'instance' data are static, so static XML files can be used
(although, it's possible to use XSP/JSP to generate dynamic forms and
constraints).

2. It is for browsers!
XForms is a client side form representation that should be processed by the
browser, so it's not a format for server side processing. But some ideas
from it can be used to implement server side processing of forms. How XForms
can be implemented in web apps, particularly in Cocoon based applications?
Two steps are required: display a page and handle submitted data using the
same form model.

3. Displaying a form in XForms format.
Usually, the process looks like this:
    (Form description + instance data) * transformer(s) = HTML

Which means: transformer(s) takes the form description and the instance data
then represent it in the target format.

Normally, web applications use JavaBeans (or value objects in terms of
EJB/J2EE) to hold the business data. So, the first task is rather simple:
serialize JavaBeans/Value objects as XML. This can be easily solved with
Castor and proposed CastorTransformer or a logicsheet.

Transformation to HTML is a more complicated thing and requires dynamic
XPath expressions support. I've been thinking of writing some JavaScript
functions that can perform validation and visualization according to
constraints, but that requires full support of XPath in JavaScript and some
tricky things during transformation to place function calls in the needed
places. To implement a simplies XSL for displaying a had to use Xalan
extensions. For real XForms processing a special logicsheet or a transformer
will be needed. In any case, this will require an intermediate markup
format.

4. Processing submitted data.
Procesing the submitted data requires following steps: validate user input
against the Schema and constraints, map values to according JavaBeans/Value
objects, perform the action (update, add, etc.). So, to validate the input
against a Schema, request parameters should be translated to XML. For this
purpose param names must be in XPath format (as it is going to be in XForms
browsers), like this:
    customer/firstname=John
    customer/service[@id='prepaid']/discount=0.10
    customer/address/line[1]=London, UK
etc.

As it were already proposed by Ivelin Ivanov, this XML can be unmarshalled
to corresponding JavaBeans/Value objects using Castor and a special Cocoon
action. After that binding constraints should be checked. And then a
business logic action should be performed.

5. Sample
This samples can be interesting to anybody who is interested in XForms. They
demonstrate how an XForm can be displayed. Also, there is a demonstration of
how 'relevant' and 'required' constraints can be visualized.

To see how display style is changed, add/remove 'required' from bindings or
change the expression in 'relevant' attribute. Also, in singleSelect element
selectUI attribute can have values 'menu', 'listbox', 'radio', 'checkbox'.

6. Conclusion
So, to conclude all the experience, I can state that for server-side XForms
support will be needed some intermediate markup format, a pair of
transformers/logicsheets, a mapping tool (like Castor) and formatting
stylesheet(s).

Any comments?

Btw, is there any activity in ExFormula?

Best regards,

Konstantin Piroumian
Software engineer

Protek Flagship LLC
Phone: + 7 095 795 0520 (add. 1288)
Fax: + 7 095 795 0525
E-mail: [EMAIL PROTECTED]
http://www.protek.com

<?xml version="1.0" encoding="UTF-8"?>
<!--
	XForms processing and displaying stylesheet.	
	Author: Konstantin Piroumian, 21-Feb-2002
	Email: [EMAIL PROTECTED]
-->

<!DOCTYPE html [
<!ENTITY nbsp "&#160;">
]>

<!-- NOTE: Xalan extensions are used to evaluate constraints and dynamic XPath expressions. This is not supported by standart specification. -->
<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
    xmlns:xalan="http://xml.apache.org/xalan";
    exclude-result-prefixes="xalan"
>
	<xsl:variable name="model" select="/page/model" />	
	<xsl:variable name="instance" select="$model/instance" />
	
	<xsl:template match="page">
		<html>
			<head>
				<title>Sample XForm</title>		
				<style type="text/css">
				<![CDATA[
				      h1 { font-size: large; color: #990000 }
				      input { background-color: #FFFFFF; color: #000099 }		
				      table { background-color: #EEEEEE; color: #000099; font-size: x-small; }
        			      select { background-color: #FFFFFF; color: #000099 }
				      
				      .citizenship { color: #990000 } 
				      .table { border: 1px inset #999999;border: 1px inset #999999; width: 400px; }
                          .sub-table { border: none; }
                          .button { background-color: #FFFFFF; color: #000099; border: 1px 
solid #666666; width: 70px; }

                          #US { background-color: #9999FF; color: #FF0000 }
                          #CA { background-color: #FF0000; color: #FFFFFF }
                          #RU { background-color: #FFFFFF; color: #0000FF }
                    ]]>
				</style>
			</head>
			<body>
				<h1>Customer <i>#<xsl:value-of select="$instance/customer/@id" /></i> Personal Details Edit Form</h1>
                    <!-- FIXME (KP): In what case we should generate a form tag and what if several submit elements are present? -->
				<form action="{$model/submitInfo/@action}" method="{$model/submitInfo/@method}">
					<xsl:apply-templates />
				</form>
			</body>
		</html>
	</xsl:template>
	
	<!-- Process an element group -->
	<xsl:template match="group">
		<table>
		    <xsl:call-template name="copy-common-attrs" />
			<xsl:for-each select="*">
			    <tr>
              	        <xsl:if test="name() != 'submit'">
                     	    <td>
                    	        <xsl:value-of select="caption" />
                    	    </td>
              	        </xsl:if>
              	        <xsl:if test="name() != 'submit'">                	    
                	    <td>
                	        <xsl:variable name="is-required">
                	            <xsl:call-template name="is-required" />
                	        </xsl:variable>
                	        <xsl:choose>
                                <xsl:when test="$is-required='true'"> * </xsl:when>
                                <xsl:otherwise>&nbsp;</xsl:otherwise>
                            </xsl:choose>
                	    </td>
              	        </xsl:if>                	    
			         <td>
                            <xsl:if test="name() = 'submit'">
                	            <xsl:attribute name="colspan">3</xsl:attribute>
                	            <xsl:attribute name="align">center</xsl:attribute>
                	        </xsl:if>			         
			             <xsl:apply-templates select="." />
			         </td>
			    </tr>
			</xsl:for-each>
		</table>
	</xsl:template>

    <!-- Process a specific group. Normally, this kind of templates should be in a customized stylesheet. See: Customer.xsl -->	
	<xsl:template match="group[@id='fullname']">
		<div>
		    <xsl:call-template name="copy-common-attrs" />
		    <xsl:for-each select="*">
                	    <xsl:value-of select="caption" /><br/>
                	    <xsl:apply-templates select="." />
                        <br />
			</xsl:for-each>
		</div>
	</xsl:template>	
	
	<xsl:template match="output">
    	     <xsl:call-template name="get-ref-value" />
	</xsl:template>
	
	<xsl:template match="submit">	
	    <xsl:variable name="submitInfo" select="@submitInfo" />
	    <input type="submit" name="{@submitInfo}" value="{caption}">
               <xsl:call-template name="copy-common-attrs" />
     	</input>
	</xsl:template>
	
	<xsl:template match="input">
	    <xsl:variable name="is-relevant">
	        <xsl:call-template name="is-relevant" />
	    </xsl:variable>
		<input name="{@ref}" type="text" title="{hint}">
                <xsl:attribute name="value"><xsl:call-template name="get-ref-value" /></xsl:attribute>
    		      <xsl:call-template name="copy-common-attrs" />
    		      <!-- This is a demonstration 'relevant' constraint usage -->
    		      <xsl:if test="$is-relevant!='true'">
    		          <xsl:attribute name="style">background-color:#CCCCCC; color: #999999</xsl:attribute>
    		      </xsl:if>
          </input>
	</xsl:template>
	
	<xsl:template match="singleSelect[@selectUI='menu']">
	    <xsl:variable name="selected">
	        <xsl:call-template name="get-ref-value" />
	    </xsl:variable>
	     <select name="{@ref}">
   		    <xsl:call-template name="copy-common-attrs" />
              <xsl:for-each select="item">
                    <option value="{value}">                  
                          <xsl:call-template name="copy-common-attrs" />
                          <xsl:if test="$selected=value">
                              <xsl:attribute name="selected">selected</xsl:attribute>
                          </xsl:if>                          
                          <xsl:value-of select="caption" />
                    </option>
              </xsl:for-each>
	     </select>
	</xsl:template>
	
	<xsl:template match="singleSelect[@selectUI='radio']">
        	    <xsl:variable name="selected">
	            <xsl:call-template name="get-ref-value" />
        	    </xsl:variable>	
              <xsl:for-each select="item">
                  <input type="radio" name="{../@ref}" >
             		    <xsl:call-template name="copy-common-attrs" />
                          <xsl:if test="$selected=value">
                              <xsl:attribute name="checked">checked</xsl:attribute>
                          </xsl:if>             		    
                  </input> <xsl:value-of select="caption" /><br/>
              </xsl:for-each>
	</xsl:template>	

	<xsl:template match="singleSelect[@selectUI='listbox']">
     	<xsl:variable name="selected">
              <xsl:call-template name="get-ref-value" />
    	     </xsl:variable>		
	     <select name="{@ref}" size="{count(item)}">
 		    <xsl:call-template name="copy-common-attrs" />
              <xsl:for-each select="item">
                    <option value="{value}">
            		    <xsl:call-template name="copy-common-attrs" />
                          <xsl:if test="$selected=value">
                              <xsl:attribute name="selected">selected</xsl:attribute>
                          </xsl:if>            		    
                        <xsl:value-of select="caption" />
                    </option>
              </xsl:for-each>
	     </select>
	</xsl:template>
	
	<xsl:template match="singleSelect[@selectUI='checkbox']">
             	<xsl:variable name="selected">
                  <xsl:call-template name="get-ref-value" />
    	         </xsl:variable>	
    	         <xsl:variable name="check">
    	             <xsl:call-template name="string-to-boolean">
    	                 <xsl:with-param name="value" select="$selected" />
    	             </xsl:call-template>
    	         </xsl:variable>
              <xsl:for-each select="item">
                  <input type="checkbox" name="{../@ref}" >
              		    <xsl:call-template name="copy-common-attrs" />
                          <xsl:if test="$check='true'">
                              <xsl:attribute name="checked">checked</xsl:attribute>
                          </xsl:if>              		    
                  </input> <xsl:value-of select="caption" /><br/>
              </xsl:for-each>	
	</xsl:template>	
    
    <!-- Helper named templated -->
    
    <!-- This template constructs full path to instance data node for the context node using recursion and values of 'ref' attributes -->
    <xsl:template name="get-ref-path">
        <xsl:param name="path" />
        <xsl:param name="node" select="." />        
        <xsl:choose>
            <xsl:when test="not($node/@ref)">
                <xsl:value-of select="$path" />
            </xsl:when>                
            <xsl:when test="string($node/@ref) and string($path)">
                <xsl:call-template name="get-ref-path">
                    <xsl:with-param name="path" select="concat($node/@ref, '/', $path)" />
                    <xsl:with-param name="node" select="$node/.." />
                </xsl:call-template>
            </xsl:when>        
            <xsl:when test="$node/@ref">
                <xsl:call-template name="get-ref-path">
                    <xsl:with-param name="path" select="@ref" />
                    <xsl:with-param name="node" select="$node/.." />
                </xsl:call-template>
            </xsl:when>            
        </xsl:choose>
    </xsl:template>

	<xsl:template name="get-ref-value">
	    <xsl:variable name="ref">
	        <xsl:call-template name="get-ref-path" />
	    </xsl:variable>	    
	    <xsl:value-of select="xalan:evaluate(concat('/page/model/instance/', $ref))" />
	</xsl:template>
	
	<xsl:template name="get-bind">
	    <xsl:param name="path">
	        <xsl:call-template name="get-ref-path" />
	    </xsl:param>
	    <xsl:value-of select="$model/bind[@ref=$path]" />
	</xsl:template>
	
	<xsl:template name="is-required">
	    <xsl:param name="path">
	        <xsl:call-template name="get-ref-path" />
	    </xsl:param>
	    <xsl:value-of select="$model/bind[@ref=$path]/@required='true'" />	
	</xsl:template>
	
	<xsl:template name="is-relevant">
	    <xsl:param name="path">
	        <xsl:call-template name="get-ref-path" />
	    </xsl:param>
	    <xsl:variable name="expr" select="$model/bind[@ref=$path]/@relevant" />
	    <xsl:choose>
            <xsl:when test="$expr">
                <xsl:value-of select="xalan:evaluate(concat('/page/model/instance/', $expr))" />
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="true()" />
            </xsl:otherwise>
        </xsl:choose>
	</xsl:template>	
	
	<xsl:template name="copy-common-attrs">
	    <xsl:copy-of select="@class | @title | @id" />
	</xsl:template>
	
	<xsl:template name="string-to-boolean">
	     <xsl:param name="value">false</xsl:param>
	     <xsl:value-of select="$value='true' or $value='yes'" />
	</xsl:template>
	
	<xsl:template match="node()"></xsl:template>
	
</xsl:stylesheet>
<?xml version="1.0" encoding="UTF-8"?>
<!-- edited with XML Spy v4.3 U (http://www.xmlspy.com) by Konstantin Piroumian (Protek) -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"; elementFormDefault="qualified" attributeFormDefault="unqualified">
    <xs:element name="customer">
        <xs:annotation>
            <xs:documentation>Customer info</xs:documentation>
        </xs:annotation>
        <xs:complexType>
            <xs:sequence>
                <xs:element name="firstname" type="personName"/>
                <xs:element name="lastname" type="personName"/>
                <xs:element name="citizenship" type="countryCode"/>
                <xs:element name="has-car" type="xs:boolean" default="false"/>
                <xs:element name="car-info" type="car-infoType"/>
            </xs:sequence>
            <xs:attribute name="id" type="xs:ID" use="required"/>
            <xs:attribute name="type" type="xs:ID" use="required"/>
        </xs:complexType>
    </xs:element>
    <xs:simpleType name="personName">
        <xs:restriction base="xs:string">
            <xs:maxLength value="1"/>
            <xs:pattern value="\c{20}"/>
        </xs:restriction>
    </xs:simpleType>
    <xs:simpleType name="countryCode">
        <xs:restriction base="xs:string">
            <xs:pattern value="[A_ZA_Z]"/>
            <xs:enumeration value="RU"/>
            <xs:enumeration value="US"/>
            <xs:enumeration value="CA"/>
            <xs:enumeration value="GB"/>
        </xs:restriction>
    </xs:simpleType>
    <xs:complexType name="car-infoType">
        <xs:sequence>
            <xs:element name="issue-year" type="xs:gYear"/>
            <xs:element name="model" type="xs:string"/>
        </xs:sequence>
    </xs:complexType>
</xs:schema>
<?xml version="1.0" encoding="UTF-8"?>
<!--
	XForms based form description sample.	
	Author: Konstantin Piroumian, 21-Feb-2002
	Email: [EMAIL PROTECTED]
-->

<!-- Root element of the document. Can be in HTML, XHTML or some other namespace -->
<page>
	<!-- Data model used in this form -->
	<model xmlns:xlink="http://foo.com/xlink";>
		<!-- XML Schema for the instance data -->
		<schema xlink:href="XForms.xsd"/>
		
		<!-- Instance data: initial values -->
		<instance>
			<customer id="102038" type="individual">
				<firstname>John</firstname>
				<lastname>Lennon</lastname>
				<citizenship>CA</citizenship>
				<has-car>false</has-car>
				<car-info>
				        <issue-year>1999</issue-year>
				        <model>Volkswagen Passat</model>
				</car-info>
			</customer>
		</instance>
		
		<!-- Data binding with simple constraints -->
		<bind ref="customer/@id" readOnly="true"/>
		<bind ref="customer/firstname" required="true"/>
		<bind ref="customer/lastname" required="true"/>
		<bind ref="customer/citizenship" relevant="customer/@type='individual'"/>
     	<bind ref="customer/car-info/issue-year" relevant="customer/has-car='true'" required="true" />
     	<bind ref="customer/car-info/model" relevant="customer/has-car='true'" required="true" />

		<!-- What and where to submit -->
		<submitInfo action="http://example.com/submit"; method="post" id="s00"/>
	</model>
	
	<!-- The form UI definition itself. Normally, it can be mixed with other markup: HTML, WML or other to provide a customized layout -->
	<group ref="customer" class="table">
		<output ref="@id">
		    <caption>Customer ID</caption>
		</output>
		<input ref="firstname">
			<caption>First name</caption>
			<hint>Your first name</hint>
		</input>
		<input ref="lastname">
			<caption>Last name</caption>
			<hint>Your last name</hint>
		</input>
		<singleSelect ref="citizenship" selectUI="menu" selection="closed" class="citizenship">
			<caption>Citizenship</caption>
			<item id="US">
				<caption>United States</caption>
				<value>US</value>
			</item>
			<item id="CA">
				<caption>Canada</caption>
				<value>CA</value>
			</item>
			<item id="RU">
				<caption>Russian Federation</caption>
				<value>RU</value>4
			</item>
		</singleSelect>
		<singleSelect ref="has-car" selectUI="checkbox">
			<caption>Own a car?</caption>
			<item>
			    <caption>(Yes or No)</caption>
			</item>
		</singleSelect>

          <group ref="car-info" class="sub-table">
        		<input ref="issue-year">
		        	<caption>Car Issue Year</caption>
			    <hint>The year when the car was issued</hint>
		    </input>
        		<input ref="model">
		        	<caption>Car Model</caption>
			    <hint>Model of your car</hint>
		    </input>              
          </group>
		
		<!-- This can be displayed as a submit button, a link or somehow else -->
		<submit submitInfo="s00" class="button">
			<caption>Save</caption>
		</submit>
	</group>
</page>

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, email: [EMAIL PROTECTED]

Reply via email to