Supersonic Tour of Apache Cocoon

Introduction

Supersonic Tour of Apache Cocoon

All documents and files included in this tour are Copyright (C) 1999-2004 by the Apache Software Foundation.

You can generate a printable single-page version of this tour here (requires an Internet connection for some example pages).

Overview

This supersonic tour of Apache Cocoon will give you a quick overview of what Cocoon is and does.

We won't go deep into any part of Cocoon, but will rather get an overall feel of what's in Cocoon.

The only parts that we will study in some detail are the following Cocoon components, which are becoming the mainstream way of using Cocoon:

Running this application

If you're reading this documentation on paper and would like to study the application directly in Cocoon, you will find it under blocks with samples on the Cocoon samples page.

Studying this application

The complete source code of this tour is found under src/blocks/tour in the Cocoon source code tree. There is one directory with its own sitemap for each part of the tutorial, so that you can easily dig deeper in your areas of interest.

Code excerpts

Note that (according to the lazyness is a virtue rule), most of the sitemap, flowscripts, XSLT transforms and other code excerpts are dynamically inserted into this documentation from the corresponding source files. This avoids having to copy and paste code and ensures that the code you see stays up to date.

Contents

What is Cocoon?

Depending on who you ask, you might get different answers: Cocoon 2 has become a rather big project, or more precisely a rather big collection of modular components, and as such means different things to different people.

Today, more than 40 blocks are available to acquire, process and publish XML data, and to build interactive applications based on XML and java technologies. Cocoon does not contain millions of lines of codes, but rather reuses a lot of Open-Source tools and libraries, combining them in a very modular way.

In the beginning, Cocoon was only a web publishing framework, meant to convert different document types to HTML, PDF and similar formats for publishing.

Today, and especially with the Cocoon Forms and Flow components that we're going to discuss, Cocoon is becoming a full-blown web applications framework, covering a large set of use cases and targeting different styles of developers.

By covering the mainstream aspects of Cocoon as it stands at the beginning of 2004, this supersonic tour will help you get a feel of what Cocoon is about, and help you find your way in this big collection of components.

Use cases

Here's a non exhaustive but typical list of Cocoon use-cases, including some weird ones to show Cocoon's flexiblity...

The combination of components available in Cocoon applies to widely varied types of applications. This explains why more and more companies are making Cocoon a strategic component of their toolset.

See also the list of Cocoon features at http://cocoon.apache.org/2.1/features.html

Facts

Architecture

Cocoon is written in Java and based on the Apache Avalon components framework.

Modular blocks can be enabled by configuration when installing Cocoon, allowing you to have either a small and focused system, or a "kitchen sink" installation with all possible options.

Many of Cocoon's core components and blocks reuse Open-Source libraries, many from other Apache projects and some from outside the Apache Software Foundation.

The caching subsystem uses sophisticated yet very customizable caching algorithms to maximize performance. Although some components or blocks might use a lot of resources (for example when generating PDF documents or bitmapped images), the Cocoon core itself is very efficient and can be tuned to maximize performance under high loads.

Licensing

Cocoon is distributed under the liberal Apache License which maximises its use while still providing protection.

Community

The Cocoon project lives through its contributors and committers, a community which is recognized for being friendly and efficient. Discussions take place on the mailing lists and IRC channels, and conferences or informal mettings called GetTogethers happen in various places.

The current team comprises more than 40 active committers, of which about 20 are regularly active. Those committers and the wider Cocoon community contribute new components, bug fixes, tests and documentation, and provide user support to each other.

Resources

The main Cocoon web site is found at http://cocoon.apache.org and contains links to the project's mailing lists, CVS code repository and other resources.

An important source of information is the http://wiki.apache.org/cocoon site, a Wiki containing lots of information which completes (and often overlaps) the official documentation.

The samples provided with Cocoon will help to explore the functionality of the various blocks. The main website contains some documentation tracks which will help you analyze and understand the samples.

Business layer options

A question which often comes when starting with Cocoon is, "How do I implement my business layer?"

The short answer: it is up to you ;-)

More precisely, you could say that Cocoon leaves a lot of freedom regarding how to access databases and run business processes or apply business rules.

Similarly, people often ask about "best practices" with Cocoon -- that is, "What's the best way to implement X or Y?" While we generally don't have a set list of best practices, we can provide some simple guidance to help you along your way.

Here's a brief discussion of possible options. Some are real today and some are still - as we like to say here - in the pipeline.

Flowscript code

Writing your business layer code inside Flowscript might be tempting after looking at this tour's examples, but please don't!

Flowscript is not meant for more than glue between pages and business code, and its design and future evolution will stay targeted to small glue modules.

Independent Java code

As we'll see in our example, it is very easy to access Java objects from Flowscript code. Such objects do not necessarily need to know about Cocoon or Avalon classes, which means that legacy code could be easily integrated, provided there are no class libraries conflicts.

Working in this way however, prevents you from using any Avalon features like configuration, logging, and monitoring facilities.

As such, this is a suboptimal solution, but might be interesting for small applications where you don't want to learn too much about Avalon and Cocoon.

Avalon-based Java code

The next step would be to write first-class Cocoon components based on the Avalon framework, allowing your components to use all of the Avalon facilities, and if necessary to access Cocoon components directly.

In this case, your build system will be integrated with the Cocoon build system, and you will tailor your build to include only the required Cocoon blocks.

This is the way to go if you don't mind the tighter coupling between Cocoon, Avalon and your application, and if you are ready to learn these technologies in more detail. The benefits are largely worth it for serious applications.

Structuring your code in blocks, as is done inside Cocoon, will make integration easier and should help future migrations to newer versions of Cocoon.

RMI components

If you're worried about coupling, RMI components might be an interesting option: in this case, only a small facade will be integrated in Cocoon, and your application will run in its own process with no risks of class library conflicts.

The downside is added complexity and a possible loss of performance, depending on your application patterns.

REST or SOAP backends

The next step towards decoupling would be to use REST or SOAP backends to communicate with your business layer, leaving you free to choose the language and framework of your choice to implement the backends. Interoperability with other systems can also be a big advantage in this case.

There are some SOAP helper components in Cocoon today, but we don't see a lot of discussions about them on the mailing lists, so we don't know if their use is widespread.

A good example of a REST backend is the XReporter database reporting framework (http://xreporter.cocoondev.org/).

Recent discussions about a possible integration of SOAP with Flowscript are promising. Being able to transparently access SOAP backends directly from Flowscript would make it possible to create forms-based frontends to SOAP services with a minimal amount of code. Stay tuned...

Pipelines

Pipelines overview

The multi-channel publishing subsystem of Cocoon is based on XML processing pipelines.

A pipeline consists of:

The are many types of Generators, Transformers and Serializers, but a typical pipeline could consist of:

The building and configuration of the pipelines is controlled by the sitemap, an XML document used by Cocoon to dynamically instantiate and activate pipelines.

The next pages show several examples using various components.

Basic pipelines

Minimal pipeline

Our first pipeline uses the RequestGenerator to output the request attributes in XML.

Sitemap excerpt

<map:match id="requestPipeline" pattern="dump-request">
<map:generate id="12" type="request"/>
<map:serialize type="xml"/>
</map:match>

Typical output

Shown below is the result of a request to dump-request?param=xyz. The response is XML as specified by the serializer, and describes the request attributes and parameters, converted to XML by the RequestGenerator component.

<h:request target="/demos/release/samples/blocks/tour/print/supersonic-tour.html" sitemap="dump-request" source="">
<h:requestHeaders>
<h:header name="Host"> localhost:11001 </h:header>
<h:header name="User-Agent"> CCBot/1.0 (+http://www.commoncrawl.org/bot.html) </h:header>
<h:header name="Accept"> Accept: application/xhtml+xml,text/html;q=0.9,text/plain; </h:header>
<h:header name="Accept-Language"> en-us,en;q=0.5 </h:header>
<h:header name="Accept-Encoding"> gzip </h:header>
<h:header name="Accept-Charset"> ISO-8859-1,utf-8;q=0.7,*;q=0.7 </h:header>
<h:header name="Cache-Control"> no-cache </h:header>
<h:header name="Pragma"> no-cache </h:header>
<h:header name="Max-Forwards"> 10 </h:header>
<h:header name="X-Forwarded-For"> 38.103.63.59 </h:header>
<h:header name="X-Forwarded-Host"> cocoon.zones.apache.org </h:header>
<h:header name="X-Forwarded-Server"> cocoon.zones.apache.org </h:header>
<h:header name="Connection"> close </h:header>
</h:requestHeaders>
<h:requestParameters>
<h:parameter name="param">
<h:value> xyz </h:value>
</h:parameter>
</h:requestParameters>
<h:configurationParameters/>
<h:remoteUser/>
</h:request>

Converting XML to HTML

Adding a transformer with map:transform and changing the serializer to HTML allows us to generate HTML out of the XML of the previous example.

You can also view the output here: request.html

Sitemap excerpt

<map:match id="requestToHtml" pattern="request.html">
<map:generate type="request"/>
<map:transform src="xsl/request-to-html.xsl"/>
<map:serialize type="html"/>
</map:match>

XSL transformation

<xsl:template name="main" match="/">
<html>
<body>
<h1> Request to host
<xsl:value-of select="//h:header[@name='Host']"/>
</h1>
<h2> Request headers </h2>
<xsl:for-each select="//h:header">
<b>
<xsl:value-of select="@name"/>
</b>
:
<xsl:value-of select="."/>
<br/>
</xsl:for-each>
</body>
</html>
</xsl:template>

Typical output

<html>
<body>
<h1> Request to host localhost:11001 </h1>
<h2> Request headers </h2>
<b> Host </b>
: localhost:11001
<br/>
<b> User-Agent </b>
: CCBot/1.0 (+http://www.commoncrawl.org/bot.html)
<br/>
<b> Accept </b>
: Accept: application/xhtml+xml,text/html;q=0.9,text/plain;
<br/>
<b> Accept-Language </b>
: en-us,en;q=0.5
<br/>
<b> Accept-Encoding </b>
: gzip
<br/>
<b> Accept-Charset </b>
: ISO-8859-1,utf-8;q=0.7,*;q=0.7
<br/>
<b> Cache-Control </b>
: no-cache
<br/>
<b> Pragma </b>
: no-cache
<br/>
<b> Max-Forwards </b>
: 10
<br/>
<b> X-Forwarded-For </b>
: 38.103.63.59
<br/>
<b> X-Forwarded-Host </b>
: cocoon.zones.apache.org
<br/>
<b> X-Forwarded-Server </b>
: cocoon.zones.apache.org
<br/>
<b> Connection </b>
: close
<br/>
</body>
</html>

Adding a touch of style

You can chain several transformers in a pipeline. In this example we add some style to our HTML by adding an additional XSL transform.

View the output in your browser: styled/request.html

Sitemap excerpt

<map:match id="styledHtml" pattern="styled/request.html">
<map:generate type="request"/>
<map:transform src="xsl/request-to-html.xsl"/>
<map:transform src="xsl/html-styling.xsl"/>
<map:serialize type="html"/>
</map:match>

XSL transformation

<xsl:template name="main" match="/">
<html>
<head>
<link rel="stylesheet" href="css/tour.css" type="text/css"/>
<xsl:copy-of select="//head/node()"/>
</head>
<body>
<xsl:copy-of select="//body/node()"/>
<p class="footer"> This footer has been added by html-styling.xsl </p>
</body>
</html>
</xsl:template>

Typical output

<html>
<head>
<link type="text/css" href="css/tour.css" rel="stylesheet"/>
</head>
<body>
<h1> Request to host localhost:11001 </h1>
<h2> Request headers </h2>
<b> Host </b>
: localhost:11001
<br/>
<b> User-Agent </b>
: CCBot/1.0 (+http://www.commoncrawl.org/bot.html)
<br/>
<b> Accept </b>
: Accept: application/xhtml+xml,text/html;q=0.9,text/plain;
<br/>
<b> Accept-Language </b>
: en-us,en;q=0.5
<br/>
<b> Accept-Encoding </b>
: gzip
<br/>
<b> Accept-Charset </b>
: ISO-8859-1,utf-8;q=0.7,*;q=0.7
<br/>
<b> Cache-Control </b>
: no-cache
<br/>
<b> Pragma </b>
: no-cache
<br/>
<b> Max-Forwards </b>
: 10
<br/>
<b> X-Forwarded-For </b>
: 38.103.63.59
<br/>
<b> X-Forwarded-Host </b>
: cocoon.zones.apache.org
<br/>
<b> X-Forwarded-Server </b>
: cocoon.zones.apache.org
<br/>
<b> Connection </b>
: close
<br/>
<p class="footer"> This footer has been added by html-styling.xsl </p>
</body>
</html>

Generating graphics

SVG output

At the end of the pipeline, different Serializers can generate different output formats, provided they're fed the right data (i.e. the required XML elements and namespaces).

Let's generate a simple graph out of of our request parameters. If your have an SVG plugin in your browser you can view the result: red/request.svg or blue/request.svg. If you don't have an SVG plugin see below for the bitmapped version of the same image.

Sitemap excerpt

<map:match id="svgRequest" pattern="*/request.svg">
<map:generate type="request"/>
<map:transform src="xsl/request-to-svg.xsl">
<map:parameter name="fillColor" value="{1}"/>
</map:transform>
<map:serialize type="svgxml"/>
</map:match>

XSL transformation

Here we show only the main template wich generates the SVG skeleton

<xsl:template name="main" match="/">
<svg width="800" height="500">
<defs>
<filter id="blur1">
<feGaussianBlur stdDeviation="3"/>
</filter>
<filter id="blur2">
<feGaussianBlur stdDeviation="1"/>
</filter>
</defs>
<g title="this is a tooltip">
<rect style="{concat('fill:',$fillColor,';stroke:#000000;stroke-width:4;filter:url(#blur1);')}" x="30" y="30" rx="20" ry="20" width="700" height="400"/>
<text style="fill:#FFFFFF;font-size:24;font-family:TrebuchetMS-Bold;filter:url(#blur2);" x="65" y="80">
<xsl:value-of select="concat('color:',$fillColor)"/>
</text>
<xsl:apply-templates select="//h:header[position() < 5]"/>
</g>
</svg>
</xsl:template>

Typical output

<svg height="500" width="800">
<defs>
<filter id="blur1">
<feGaussianBlur stdDeviation="3"/>
</filter>
<filter id="blur2">
<feGaussianBlur stdDeviation="1"/>
</filter>
</defs>
<g title="this is a tooltip">
<rect height="400" width="700" ry="20" rx="20" y="30" x="30" style="fill:red;stroke:#000000;stroke-width:4;filter:url(#blur1);"/>
<text y="80" x="65" style="fill:#FFFFFF;font-size:24;font-family:TrebuchetMS-Bold;filter:url(#blur2);"> color:red </text>
<svg:text y="120" x="65" style="fill:#FFFFFF;font-size:24;font-family:TrebuchetMS-Bold;filter:url(#blur2);"> Host:localhost:11001 </svg:text>
<svg:text y="160" x="65" style="fill:#FFFFFF;font-size:24;font-family:TrebuchetMS-Bold;filter:url(#blur2);"> User-Agent:CCBot/1.0 (+http://www.commoncrawl.org/bot.html) </svg:text>
<svg:text y="200" x="65" style="fill:#FFFFFF;font-size:24;font-family:TrebuchetMS-Bold;filter:url(#blur2);"> Accept:Accept: application/xhtml+xml,text/html;q=0.9,text/plain; </svg:text>
<svg:text y="240" x="65" style="fill:#FFFFFF;font-size:24;font-family:TrebuchetMS-Bold;filter:url(#blur2);"> Accept-Language:en-us,en;q=0.5 </svg:text>
</g>
</svg>

Bitmapped output

Post-processing the SVG output allows us to generate JPEG (cyan/request.jpeg) or PNG (black/request.png) images, simply by configuring a different serializer at the end of the pipeline.

Sitemap excerpts

Note the use of the cocoon:/ protocol to re-use the previous pipeline as input: when a request to blue/request.png is received, the output of the blue/request.svg is used as the input of the first pipeline shown below.

<map:match id="pngRequest" pattern="*/request.png">
<map:generate src="cocoon:/{1}/request.svg"/>
<map:serialize type="svg2png"/>
</map:match>
<map:match id="jpegRequest" pattern="*/request.jpeg">
<map:generate src="cocoon:/{1}/request.svg"/>
<map:serialize type="svg2jpeg"/>
</map:match>

XML news source

Other types of input

Until now our pipelines have all started with the RequestGenerator. Let's see a different example, where we retrieve an external XML document via HTTP.

This example requires access to the http://codeconsult.ch/bertrand/index.rdf XML newsfeed.

View the result in your browser: xmlnews.html

Sitemap excerpt

<map:match id="xmlNews" pattern="xmlnews.html">
<map:generate src="http://codeconsult.ch/bertrand/index.rdf"/>
<map:transform src="xsl/news-to-html.xsl"/>
<map:transform src="xsl/html-styling.xsl"/>
<map:serialize type="html"/>
</map:match>

XSL transform

Here we show only the conversion of rss:item elements

<xsl:template name="rssItem" match="rss:item">
<li>
<a href="{@rdf:about}">
<xsl:value-of select="rss:title"/>
</a>
</li>
</xsl:template>

Typical output

<html>
<head>
<link type="text/css" href="css/tour.css" rel="stylesheet"/>
</head>
<body>
<h1> RSS news in HTML </h1>
<ul/>
<p class="footer"> This footer has been added by html-styling.xsl </p>
</body>
</html>

Multi-channel publishing

Output formats

Through its many Serializers, Cocoon can transform XML data into many output formats, including, but not limited to:

We say not limited to, because we might have forgotten to list some, but also because it is fairly easy for a Java programmer to implement any output format by writing a new Serializer or extending an existing one.

The Hello world samples of Cocoon give simple examples of how these formats are generated.

Our simple example will let you input some text and create a PDF document out of it, using the PDFSerializer based on the Apache FOP project.

PDF example

Dynamic PDF generation

In this example we will generate a PDF document containing text received in the HTTP request parameters.

To run the example, submit this form, after changing the provided text if you want to:

Title
Text

If your browser has problems with PDF coming out of a form submit use this link instead.

The sitemap

The FOPSerializer requires a document in the XSL-FO vocabulary for input, and converts this to PDF, handling fonts, page layout, etc.

Our sitemap will then apply an XSL transform to the output of the RequestGenerator to convert it to XSL-FO, using request parameter values provided by the generator.

Errors in the XSL-FO document can happen when implementing such a transform, and to make debugging easier we use a view in the sitemap, to access the intermediate document before it is processed by the FOPSerializer.

Here are the view and pipeline definitions:

<map:view id="view" name="xsl-fo" from-label="xsl-fo">
<map:serialize type="xml"/>
</map:view>
<map:match id="pdf" pattern="*/*.pdf">
<map:generate type="request"/>
<map:transform label="xsl-fo" src="{1}/{2}.xsl"/>
<map:serialize type="fo2pdf"/>
</map:match>

These definitions allow the intermediate XSL-FO document to be retrieved by adding the cocoon-view=xsl-fo parameter at the end of the URL, for example:
../pdf-example/pdf-example.pdf?title=....&cocoon-view=xsl-fo

XSL transformation

This transformation converts the RequestGenerator output in XSL-FO, inserting the value of the text and title request parameters in the output document:

<xsl:stylesheet id="main" version="1.0">
<xsl:template match="/">
<fo:root>
<fo:layout-master-set>
<fo:simple-page-master master-name="main" page-height="29.7cm" page-width="21cm" margin-top="1cm" margin-bottom="2cm" margin-left="2.5cm" margin-right="2.5cm">
<fo:region-body margin-top="3cm"/>
<fo:region-before extent="3cm"/>
<fo:region-after extent="1.5cm"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence id="page-sequence" master-reference="main">
<fo:flow flow-name="xsl-region-body">
<fo:block font-size="12pt" text-align="right"> Supersonic FOP example </fo:block>
<fo:block font-size="18pt">
<xsl:apply-templates select="//request:parameter[@name='title']"/>
</fo:block>
<fo:block font-size="12pt">
<xsl:apply-templates select="//request:parameter[@name='text']"/>
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
<xsl:template match="request:parameter">
<xsl:value-of select="request:value"/>
</xsl:template>
</xsl:stylesheet>

The Flow subsystem

Flow examples

Through the magic of continuations, the Cocoon Flow subsystem makes it easy to track the user's state when interacting with a web-based application.

A continuation saves the state of execution of the currently running Flowscript, and allows this state to be "resurrected" later on, typically when the user submits an HTML form that was sent with the cocoon.sendPageAndWait instruction.

This makes the typical web applications interactions much easier to manage, as the developer can write "linear" code for page flows, instead of having to keep track of the user's state manually. Our examples will show how little code is used to manage typical interactions.

Having to use JavaScript to write the Flow scripts might seem strange at first, but in practice only a few lines of Flow code will be required for a typical application, so this doesn't matter much. The reason is that JavaScript (through a modified version of the mozilla Rhino interpreter) is currently the only continuations-enabled language that can be distributed with Cocoon.

Number guessing game

Adapted from Tony Collen's GettingStartedWithFlow page on the Cocoon wiki.

In this example, an HTML form is shown repeatedly, until the user guesses the correct number.

Start the game here.

The sitemap

At the sitemap level, Flowscript applications need four things:

The corresponding excerpts of our sitemap are shown below.

Flowscript declaration

Note that this already contains the declaration of the next example's Flowscript.

<map:flow id="flow" language="javascript">
<map:script src="number-guess/guess-number.js"/>
<map:script src="multi-page/multi-page.js"/>
<map:script src="java-shapes/java-shapes.js"/>
</map:flow>

map:call function

Here we use variables to allow any Flowscript function to be called, with a simple security restriction: the function name must start with the public_ prefix.

The maxValue parameter is used by our Flowscript public_startGuessNumber function, but doesn't hurt if it is passed to other functions (like in the next example).

<map:match id="start" pattern="*/start*">
<map:call function="public_start{2}">
<map:parameter name="maxValue" value="10"/>
</map:call>
</map:match>

map:call continue

This activates a Flowscript continuation when an URL ending with a continuation ID and the .continue suffix is received.

<map:match id="continue" pattern="**/*.continue">
<map:call continuation="{2}"/>
</map:match>

View pipeline using JXTemplageGenerator

To be able to include data provided by Flowscript in our forms and views, we use the Flowscript-aware JXTemplateGenerator

Here's the pipeline

<map:match id="views" pattern="*/views/*">
<map:generate src="{1}/{2}.xml" type="file"/>
<map:transform type="jx"/>
<map:call resource="html"/>
</map:match>

Which uses this resource to convert the page to HTML

<map:resource id="html" name="html">
<map:transform src="../intro/presentation/page2html.xsl"/>
<map:serialize type="html"/>
</map:resource>

And here's the JXTemplate component declaration

<map:generators id="generators" default="file">
<map:generator name="jx" src="org.apache.cocoon.generation.JXTemplateGenerator" label="content" logger="sitemap.generator.jx"/>
</map:generators>

Flowscript code

Here's the complete Flowscript code that drives the number-guessing game. (sorry about the lost indentation, the Slop block, which generates these listings, has been improved in the meantime).

guess-number.js
0001 /*
0002 * Licensed to the Apache Software Foundation (ASF) under one or more
0003 * contributor license agreements. See the NOTICE file distributed with
0004 * this work for additional information regarding copyright ownership.
0005 * The ASF licenses this file to You under the Apache License, Version 2.0
0006 * (the "License"); you may not use this file except in compliance with
0007 * the License. You may obtain a copy of the License at
0008 *
0009 * http://www.apache.org/licenses/LICENSE-2.0
0010 *
0011 * Unless required by applicable law or agreed to in writing, software
0012 * distributed under the License is distributed on an "AS IS" BASIS,
0013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014 * See the License for the specific language governing permissions and
0015 * limitations under the License.
0016 */

0018 // simple number guessing game in Flowscript
0019 // based on the Cocoon Flow tutorial:
0020 // http://cocoon.apache.org/2.1/userdocs/flow/tutor.html

0022 function public_startGuessNumber() {
0023 var max = cocoon.parameters["maxValue"];
0024 var toGuess = Math.round(Math.random() * max);
0025 if(toGuess == 0) toGuess = 1;
0026 var hint = "Guess a number between 1 and " + max;
0027 var tries = 0;

0029 // show and process input form, until correct answer is given
0030 while (true) {
0031 cocoon.sendPageAndWait("number-guess/views/guess", {"toGuess" : toGuess, "hint" : hint, "tries" : tries});
0032 var answer = parseInt( cocoon.request.get("answer") );

0034 tries++;

0036 if(answer) {
0037 if(answer > toGuess) {
0038 hint = "The number you entered (" + answer + ") is too big";
0039 } else if(answer < toGuess) {
0040 hint = "The number you entered (" + answer + ") is too small";
0041 } else {
0042 break;
0043 }
0044 }
0045 }

0047 cocoon.sendPage("number-guess/views/success", {"toGuess" : toGuess, "answer" : answer, "tries" : tries} );
0048 }

JXTemplate view

The JXTemplateGenerator makes Flowscript variables (like the toGuess and hint attributes of the sendPageAndWait call above) accessible using a simple substitution syntax.

Here's the page definition for our number-guessing form. JXTemplate codes like ${cocoon.continuation.id}, will by replaced by values provided in the Flowscript sendPageAndWait function call.

<page id="page">
<title> Flow example: Guess a number </title>
<content>
<h2> ${hint} </h2>
<form method="post" action="${cocoon.continuation.id}.continue">
<input type="text" name="answer"/>
<input type="submit"/>
</form>
<p class="footer">
<a href="../docs/index.html"> Flow examples </a>
</p>
</content>
</page>

That's it!

We have now seen the complete code of our Flowscript application:

We didn't have to write much code to implement this, and more importantly all the code that we wrote is application-specific. The Cocoon framework provides all the infrastructure.

Multi-page form

This example uses a simple two-page form to enter data for a simulated email message.

Start the example here.

Although a two-page form does not make sense for three fields when run from a desktop browser, this demonstrates how the Flow makes it easy to keep track of the user's "position" in the application flow. Just imagine you're working on a tiny mobile device for a minute.

We won't use Cocoon Forms here, but simply bind a JavaScript object to our form manually.

Notice how little code is needed to keep state while the user can freely move between the pages without losing data.

The sitemap

There's nothing new in the sitemap, our use of variables allows the exact same sitemap to be reused for both our Flow examples.

The only specific thing is the importing of the multi-page.js Flowscript, but this was already present for the previous example:

<map:flow id="flow" language="javascript">
<map:script src="number-guess/guess-number.js"/>
<map:script src="multi-page/multi-page.js"/>
<map:script src="java-shapes/java-shapes.js"/>
</map:flow>

Flowscript code

Here's the Flowscript which drives the multi-page form example:

multi-page.js
0001 /*
0002 * Licensed to the Apache Software Foundation (ASF) under one or more
0003 * contributor license agreements. See the NOTICE file distributed with
0004 * this work for additional information regarding copyright ownership.
0005 * The ASF licenses this file to You under the Apache License, Version 2.0
0006 * (the "License"); you may not use this file except in compliance with
0007 * the License. You may obtain a copy of the License at
0008 *
0009 * http://www.apache.org/licenses/LICENSE-2.0
0010 *
0011 * Unless required by applicable law or agreed to in writing, software
0012 * distributed under the License is distributed on an "AS IS" BASIS,
0013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014 * See the License for the specific language governing permissions and
0015 * limitations under the License.
0016 */

0018 // Multi-page Flow example
0019 // Simple multi-page form, without using Cocoon Forms

0021 var date = new Packages.java.util.Date();

0023 // simulated email message data
0024 function MessageData() {
0025 this.sender = "you@somewhere.com";
0026 this.subject = "Type the subject here";
0027 this.text = "Type the text of your message here";
0028 }

0030 // page flow
0031 function public_startMultiPage() {
0032 var message = new MessageData();

0034 while(true) {

0036 // decide which page to show based on request parameters
0037 var page = "page1";
0038 if(cocoon.request.getParameter("action_send") != null) {
0039 break;
0040 } else if(cocoon.request.getParameter("action_page2") != null) {
0041 page = "page2";
0042 }

0044 // show form and wait for results
0045 cocoon.sendPageAndWait("multi-page/views/" + page, { "message" : message, "date" : date });

0047 // now for the boring part: copy form data into message
0048 // that's where Forms bindings would help
0049 var tmp = cocoon.request.getParameter("sender");
0050 if(tmp != null) message.sender = tmp;

0052 tmp = cocoon.request.getParameter("subject");
0053 if(tmp != null) message.subject = tmp;

0055 tmp = cocoon.request.getParameter("text");
0056 if(tmp != null) message.text = tmp;
0057 }

0059 // user selected "send", show message contents
0060 cocoon.sendPage("multi-page/views/result", { "message" : message });
0061 }

It is not more complicated than the previous example, but slightly longer due to the (boring) copying of request parameters into the message object. This boring part is where Cocoon Forms will come into play, by making it easier to bind data to values edited in a form.

By copying values into the message object after each submission, we allow the user to freely move between pages without losing data.

JXTemplate view

Nothing special in the form view, this is very similar to the previous example.

We're only using two submit buttons, one to move between pages and one to actually submit the data and go to the result page.

<page id="page">
<title> Flow example: multi-page form </title>
<content>
<h2> Multi-page form: page 1 (${date}) </h2>
<form method="get" action="${continuation.id}.continue">
<table>
<tr>
<td> Sender </td>
<td>
<input type="text" size="40" name="sender" value="${message.sender}"/>
</td>
</tr>
<tr>
<td> Subject </td>
<td>
<input type="text" size="40" name="subject" value="${message.subject}"/>
</td>
</tr>
</table>
<input type="submit" name="action_page2" value="Page 2"/>
<input type="submit" name="action_send" value="Send message"/>
</form>
<p class="footer">
<a href="../docs/index.html"> Flow examples </a>
</p>
</content>
</page>

This is page 1. The page 2 is similar but shows the text field instead of the fields present on page 1.

Java shapes: using Java objects from Flow

In this example, java classes are used to calculate the area and perimeter of shapes. Various Java classes are instantiated and used from Flow.

Start the example here.

The sitemap

There's nothing new in the sitemap, our use of variables allows the exact same sitemap to be reused for both our Flow examples.

The only specific thing is the importing of the java-shapes.js flowscript, but this was already present for the previous example:

<map:flow id="flow" language="javascript">
<map:script src="number-guess/guess-number.js"/>
<map:script src="multi-page/multi-page.js"/>
<map:script src="java-shapes/java-shapes.js"/>
</map:flow>

Java code

The java code (interface Shape, classes Rectangular, Square, Circle) is fairly trivial, and the computations could easily be done in javascript for such a simple case.

However, our goal is to show interactions between Flow and java classes, so you shouldn't pay too much attention to the java code, except to note that it has no knowledge of Avalon - our java objects are just POJOs: Plain Old Java Objects.

Flowscript code

Here's the Flowscript which has three steps.

  1. Get the selected shape from user and prepare the next page.
  2. Get shape's informations (width, height or radius) and instantiate the correct java class for the selected shape.
  3. Use java class to calculate area and perimeter and display the results
java-shapes.js
0001 /*
0002 * Licensed to the Apache Software Foundation (ASF) under one or more
0003 * contributor license agreements. See the NOTICE file distributed with
0004 * this work for additional information regarding copyright ownership.
0005 * The ASF licenses this file to You under the Apache License, Version 2.0
0006 * (the "License"); you may not use this file except in compliance with
0007 * the License. You may obtain a copy of the License at
0008 *
0009 * http://www.apache.org/licenses/LICENSE-2.0
0010 *
0011 * Unless required by applicable law or agreed to in writing, software
0012 * distributed under the License is distributed on an "AS IS" BASIS,
0013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014 * See the License for the specific language governing permissions and
0015 * limitations under the License.
0016 */

0018 // Shape's area and perimeter calculation example.

0020 var calculator = null;

0022 function public_startShape() {
0023 var hint = "Calculate shape's area and perimeter using logic in java. ";

0025 // let user select shape
0026 cocoon.sendPageAndWait("java-shapes/views/select", {"hint" : hint});
0027 var shapeId = cocoon.request.get("shape");

0029 // send shape-specific view
0030 cocoon.sendPageAndWait("java-shapes/views/" + shapeId, {"shapeId" : shapeId});

0032 // get request parameters (of which some are null depending on shape, that's not a problem)
0033 var h = parseInt( cocoon.request.get("h") );
0034 var b = parseInt( cocoon.request.get("b") );
0035 var r = parseInt( cocoon.request.get("r") );

0037 // instantiate appropriate calculator
0038 if(shapeId == "square") {
0039 calculator = new Packages.org.apache.cocoon.samples.tour.shapes.Square(b);
0040 } else if(shapeId=="rectangular") {
0041 calculator = new Packages.org.apache.cocoon.samples.tour.shapes.Rectangular(b,h);
0042 } else if(shapeId=="circle") {
0043 calculator = new Packages.org.apache.cocoon.samples.tour.shapes.Circle(r);
0044 } else {
0045 throw new java.lang.Exception("No calculator found for shape '" + shapeId + "'");
0046 }

0048 // compute results
0049 // (accessing bean-like properties like "getArea()" using property names like "area")
0050 var a = calculator.area;
0051 var p = calculator.perimeter;

0053 cocoon.sendPage("java-shapes/views/results", {"area" : a, "perimeter" : p, "shape" : shapeId} );
0054 }

JXTemplate view

Shape selection

<page id="page">
<title> Flow example: Shapes </title>
<content>
<h2> ${hint} </h2>
<form method="post" action="${continuation.id}.continue">
<p> Select shape </p>
<select name="shape">
<optgroup label="Select shape">
<option value="square"> Square </option>
<option value="rectangular"> Rectangular </option>
<option value="circle"> Circle </option>
</optgroup>
</select>
<input type="submit"/>
</form>
<p class="footer">
<a href="../docs/index.html"> Flow examples </a>
</p>
</content>
</page>

Here the user can select a shape.

Cocoon Forms sample application

The Bean Editor application

The bean editor application uses Cocoon Forms, Pipelines and Flow to edit a simple data structure modeled by Java objects.

As such, it is a good example of how a Cocoon front-end can be used to manipulate business objects implemented in Java.

You will notice that the Java objects have nothing to do with Cocoon in our case. They are completely independent of any Cocoon libraries. This is one of the options for implementing the business layer of your application, useful when you have legacy business layer code to integrate.

Our Java beans are trivial and uninteresting: what we're looking for is an understanding of how Cocoon Pipelines, Forms and Flow play together with Java code, and for this a very simple application is certainly good.

After studying this application, you will see that very little code had to be written to implement an already capable system. As is often the case with a modular system like Cocoon, the hard part is not having to write a lot of code, but rather finding where to write the small amounts of code that are needed.

Use cases

To keep it simple and focused, this application does just three things:

  1. Display a list of tasks, units of work assigned to somebody and containing dated comments
  2. Display a single task with its comments
  3. Edit a single task, allowing comments to be added and removed
New tasks cannot be created, and tasks cannot be deleted.

The application is obviously incomplete, but shows many interesting features of the Cocoon Forms, and their interplay with the Cocoon components that we already know.

Running the application

To run the bean editor application, Cocoon must be able to load the required Java classes.

If you're running this tutorial from the standard Cocoon distribution this should be taken care of already, as the required classes are copied into the webapp/WEB-INF/lib directory automatically during the build.

When you start using your own Java classes with Cocoon, you'll have to take care of this yourself, usually by making sure the required jar files are copied in the right place.

For now, you can start the application from this link.

Java beans

As already mentioned, our Java beans are dead simple and not very robust. The "database" is simply held in memory, loaded with simulated data at startup.

Interfaces

Here's the interface of the DatabaseFacade class, which is used by our Flowscript code to "talk" to the Java beans:

/** access the Database */
public static DatabaseFacade getInstance();

/** get our list of tasks
 *  @return a List of TaskBean objects */
public List getTasks();

/** get a single TaskBean */
public TaskBean getTaskBeanById(int id) throws Exception;

/** get this object's version */
public String getVersion();
            

Here's the interface of the TaskBean class, our main "task" object. It is basically a Java Bean with one read-only and three read-write properties.

public int getId();
public String getTaskName();
public void setTaskName(String m_taskName);
public String getAssignedTo();
public void setAssignedTo(String m_assignedTo);

/** @return a List of TaskCommentBean objects */
public List getComments();

/** @param c a List of TaskCommentBean objects */
public void setComments(List c);
            

Here's the TaskCommentBean interface:

public int getId();
public Date getDate();
public void setDate(Date m_date);
public String getComment();
public void setComment(String m_comment);
            

Access from Flowscript

Here's a code excerpt showing how Flowscript code can access Java classes.

var db = Packages.org.apache.cocoon.samples.tour.beans.DatabaseFacade.getInstance();
...
list = db.getTasks();
        

Simple enough. The "official" way of accessing Java components in a Cocoon application would be to use the Avalon lookup mechanisms, but this wouldn't add much to our example so we took the easy way here.

Tasks list

Let's look at the implementation of the view/allTasks application page, which lists all tasks provided by the DatabaseFacade.

The scenario is the following:

  1. In Flowscript, access and query the DatabaseFacade
  2. Pass the data from Flowscript to a page which uses the JXTemplateGenerator to access it
  3. In the JXTemplate page, iterate over the data elements and display them

Sitemap

Here's the first pipeline definition in sitemap, activated by the view/allTasks request:

<map:match id="view" pattern="view/*">
<map:call function="query_{1}"/>
</map:match>

This causes the above request to call the query_allTasks() Flowscript function (Flowscript listing here).

Later, the Flowscript will call the internal/generate-view/taskList pipeline to generate a view using the JXTemplateGenerator and the views/taskListView.xml page:

<map:match id="genView" pattern="internal/generate-view/*">
<map:generate src="cocoon-app/views/{1}View.xml" type="file"/>
<map:transform type="jx" label="jxDebug"/>
<map:call resource="html"/>
</map:match>

Java beans to JXTemplate page

The following lines of Flowscript code implement the first two steps of our scenario, getting a List of TaskBean objects from the DatabaseFacade and passing it to the taskList pipeline.

0006 // Access java "database" facade object
0007 var db = Packages.org.apache.cocoon.samples.tour.beans.DatabaseFacade.getInstance();
...
0010 function query_allTasks() {
0011    list = db.getTasks();

0013    cocoon.sendPage("internal/generate-view/taskList", {
0014        title : "List of tasks",
0015        task : list,
0016        db : db
0017    });
            
As the taskList pipeline uses a JXTemplate generator, the corresponding page will have access to the variables passed with the sendPage call.

The db object is also passed to the page, but it is only used to access its db.version field.

We're not using continuations here (there's no sendPageAndWait), Flowscript serves only as a thin layer of glue between our Java objects and our JXTemplate view page.

JXTemplate page

Here's the JXtemplate page that generates the taskList view, using the title, task and db variables supplied by the above Flowscript code:

<page id="page">
<title> #{title} </title>
<content>
<table>
<tr>
<td class="legend"> id </td>
<td class="legend"> Assigned to </td>
<td class="legend"> Task name </td>
</tr>
<c:forEach select="#{task}">
<tr class="listRow">
<td>
<a href="singleTask?taskId=#{id}"> #{id} </a>
</td>
<td> #{assignedTo} </td>
<td> #{taskName} </td>
</tr>
</c:forEach>
</table>
<p class="footer"> #{format-number(count(task),'###')} tasks in list - #{db/version}
<br/>
<a href="../../"> Table of contents </a>
</p>
</content>
</page>

Note the use of a <c:forEach> element, from the JXTemplate namespace, to iterate over members of the task collection.

That's it!

Let's summarize what happened here:

  1. A request for view/allTasks comes in
  2. The query_allTasks() Flowscript function is called and uses the Java DatabaseFacade to retrieve a Java List of TaskBean objects.
  3. Flowscript uses the cocoon.sendPage() function to trigger the execution of a sitemap pipeline, passing to it some data as JavaScript variables.
  4. The pipeline uses the JXTemplate generator to dynamically insert data in an XML template document, generating one of our <page> documents.
  5. Our usual XML-to-HTML conversion transformation is used for final presentation.
This might seem complicated when explained in so much detail, but the interesting thing to note is that, once again, very little code must be written to make this work. And the whole thing stays very flexible and customizable.

Single task

The view/singleTask page (for example view/singleTask?taskId=2) is built in a similar way than the tasksList page, without requiring new definitions in the sitemap.

Shown below are the singleTask-specific portions of our code. Refer to the taskList page for the corresponding sitemap excerpts.

Flowscript query and display methods

These methods query the java objects and call the appropriate pipeline for display:

0020 // Query a single TaskBean object and display it
0021 function query_singleTask() {
0022    id = cocoon.request.getParameter("taskId");
0023    bean = db.getTaskBeanById(id);
0024    displayTaskBean(id,bean);
0025 }
...
0038 // Display a single TaskBean
0039 function displayTaskBean(id,bean) {
0040    cocoon.sendPage("internal/generate-view/singleTask", {
0041        title : "Task #" + id,
0042        task : bean,
0043        selectedTaskId : id
0044    });
0045 }
            

Note that no error checking is done, for example for a missing taskId parameter. This would obviously be needed in a production application.

JXTemplate page

<page id="page">
<title> #{title} </title>
<content>
<h2> Task info </h2>
<table>
<tr>
<td class="legend"> Task ID </td>
<td> #{task/id} </td>
</tr>
<tr>
<td class="legend"> Task name </td>
<td class="titleCell"> #{task/taskName} </td>
</tr>
<tr>
<td class="legend"> Assigned to </td>
<td> #{task/assignedTo} </td>
</tr>
</table>
<p>
<a href="../edit/singleTask?taskId=#{task/id}"> Edit this task </a>
</p>
<h2> Comments </h2>
<table>
<tr>
<c:forEach select="#{task/comments}">
<tr class="listRow">
<td>
<c:formatDate value="#{date}" dateStyle="medium"/>
</td>
<td> #{comment} </td>
</tr>
</c:forEach>
</tr>
</table>
<p class="footer">
<a href="../view/allTasks"> Back to the list of tasks </a>
</p>
</content>
</page>

Forms

At this point, our application allows us to navigate in our TaskBean objects, and we're ready to start editing them.

To take advantage of the Cocoon Forms editing features, we need the following:

This seems a lot of work when described in this way, but notice once again how modular things are: each "concern" of our bean editor is clearly defined in its own place.

Note also that, while our example uses static XML files for these definitions, nothing prevents you from generating them dynamically using pipelines and the cocoon:/ protocol. If your application provides a central data dictionary, for example, it would be possible to generate at least simple versions of the required definition files automatically.

Sitemap

Let's look first at the pipeline definition for the edit/singleTask request:

<map:match id="edit" pattern="edit/*">
<map:call function="handleForm">
<map:parameter name="function" value="{1}Editor"/>
<map:parameter name="form-definition" value="cocoon-app/forms/{1}FormModel.xml"/>
<map:parameter name="bindingURI" value="cocoon-app/forms/{1}FormBinding.xml"/>
</map:call>
</map:match>

What this does is to call the handleForm() function of the Form.js Flowscript library, telling it which of our own Flowscript function to call to edit the form, and indicating the locations of the Form Model (form-definition) and Form Binding documents.

Later, the Flowscript function shown below will call this pipeline:

<map:match id="showForm" pattern="internal/show-form/*">
<map:generate src="cocoon-app/forms/{1}FormTemplate.xml"/>
<map:transform type="forms"/>
<map:transform src="context://samples/blocks/forms/resources/forms-samples-styling.xsl"/>
<map:call resource="html"/>
</map:match>

Using the FormsTransformer to generate the appropriate HTML elements for our form's widgets.

Flowscript

Here's our Flowscript form editing function, called by the handleForm library function to edit our form:

0027 // Edit a single TaskBean object using Cocoon Forms
0028 function singleTaskEditor(form) {
0029    id = cocoon.request.getParameter("taskId");
0030    bean = db.getTaskBeanById(id);

0032    form.load(bean);
0033    form.showForm("internal/show-form/singleTask");
0034    form.save(bean);
0035    displayTaskBean(id,bean);
0036 }
            

Notice how simple and readable the code is - the magic happens behind the Cocoon Forms scenes, based on the Forms definitions shown below.

Form Model

Here's the definition of our Form Model.

<fd:form id="form">
<fd:widgets>
<fd:output id="taskId" readonly="true" required="true">
<fd:label> Task ID </fd:label>
<fd:datatype base="integer"/>
</fd:output>
<fd:field id="taskName" required="true">
<fd:label> Task name </fd:label>
<fd:datatype base="string"/>
</fd:field>
<fd:field id="assignedTo" required="true">
<fd:label> Assigned to </fd:label>
<fd:datatype base="string"/>
</fd:field>
<fd:repeater id="comments">
<fd:label> Comments </fd:label>
<fd:widgets>
<fd:output id="id">
<fd:datatype base="integer"/>
</fd:output>
<fd:booleanfield id="select">
<fd:label> Select </fd:label>
</fd:booleanfield>
<fd:field id="date" required="true">
<fd:label> Date </fd:label>
<fd:datatype base="date">
<fd:convertor>
<fd:patterns>
<fd:pattern> dd/MM/yyyy </fd:pattern>
</fd:patterns>
</fd:convertor>
</fd:datatype>
</fd:field>
<fd:field id="comment" required="true">
<fd:label> Comment </fd:label>
<fd:datatype base="string">
<fd:validation>
<fd:length min="5" max="150">
<fd:failmessage> The comment length must be between 5 and 150 characters </fd:failmessage>
</fd:length>
</fd:validation>
</fd:datatype>
</fd:field>
</fd:widgets>
</fd:repeater>
<fd:repeater-action id="addcomment" action-command="add-row" repeater="comments">
<fd:label> Add comment </fd:label>
</fd:repeater-action>
<fd:repeater-action id="removecomment" action-command="delete-rows" repeater="comments" select="select">
<fd:label> Remove selected comments </fd:label>
</fd:repeater-action>
</fd:widgets>
</fd:form>

As you can see, the Forms subsystem uses widgets with labels and datatypes to define a Model.

If you omit the repeater element above, this is a fairly simple data model which represents our form's data. The repeater is used to model the 1-N relationship between our TaskBean and TaskCommentBean objects.

Strongly typed fields are an important features of Cocoon Forms, and provide many standard validation features which make our life easier.

This model is independent from the way the form is going to look in HTML (or WML, or XUL, or whatever), and also independent of the internal structure of our Java beans.

Here we use constants for the widget labels (field names), but the i18n transformer could be added to our pipeline to easily generate views and forms in multiple languages.

Form Binding

<fb:context id="form" path="/">
<fb:value id="taskId" path="id" direction="load"/>
<fb:value id="taskName" path="taskName"/>
<fb:value id="assignedTo" path="assignedTo"/>
<fb:repeater id="comments" parent-path="." row-path="comments">
<fb:identity>
<fb:value id="id" path="@id" direction="load"/>
</fb:identity>
<fb:on-bind>
<fb:value id="date" path="date"/>
<fb:value id="comment" path="comment"/>
</fb:on-bind>
<fb:on-delete-row>
<fb:delete-node/>
</fb:on-delete-row>
<fb:on-insert-row>
<fb:insert-bean classname="org.apache.cocoon.samples.tour.beans.TaskCommentBean" addmethod="addComment"/>
</fb:on-insert-row>
</fb:repeater>
</fb:context>

Here's the binding definition, which allows the Forms subsystem to automatically move data from our Form's internal model to our Java beans.

This looks simple enough: for example, we tell the Forms subsystem that the form's taskId field is mapped in readonly mode to the bean's id field.

The data mapping is triggered by the Flowscript form.load and form.save calls shown above.

For the taskId form field, the readonly flag makes the binding unidirectional. The value of the form field will not be copied to the bean when form.save is called.

Form Template

Here's the template used by the FormsTransformer, which is activated by the above sitemap excerpt.

<page id="page">
<title> TaskBean editing </title>
<content>
<ft:form-template action="#{$continuation/id}.continue" method="POST">
<h2> Task info </h2>
<table>
<tr>
<tr>
<td class="legend">
<ft:widget-label id="taskId"/>
</td>
<td>
<ft:widget id="taskId"/>
</td>
</tr>
<td class="legend">
<ft:widget-label id="taskName"/>
</td>
<td>
<ft:widget id="taskName">
<fi:styling size="60"/>
</ft:widget>
</td>
</tr>
<tr>
<td class="legend">
<ft:widget-label id="assignedTo"/>
</td>
<td>
<ft:widget id="assignedTo">
<fi:styling size="60"/>
</ft:widget>
</td>
</tr>
</table>
<input type="submit" value="Save page"/>
<h2>
<ft:widget-label id="comments"/>
</h2>
<ft:repeater-size id="comments"/>
<table>
<tr>
<th>   </th>
<th>
<ft:repeater-widget-label id="comments" widget-id="date"/>
</th>
<th>
<ft:repeater-widget-label id="comments" widget-id="comment"/>
</th>
</tr>
<ft:repeater-widget id="comments">
<tr>
<td>
<ft:widget id="select"/>
</td>
<td>
<ft:widget id="date">
<fi:styling type="text" size="10"/>
</ft:widget>
</td>
<td>