Interactive Application</> <para> Now it's time to build a real, interactive application. We'll still use just a single page, but it will demonstrate many of the more interesting features of Tapestry, including maintenance of server side page state. </> <para> Our Adder application allows the user to sum up a list of numbers. </> <figure> <title>Adder Application</> <mediaobject> <imageobject> <imagedata fileref="images/adder.jpg" format="jpg"> </imageobject> </> </figure> <para> The user enters a number into the value field and clicks "Add to list". The number is added to the list of items and factored into the total. </> <para> A <classname>Form</> component containing a <classname>TextField</> component will be used to collect information from the user. A <classname>Foreach</> component will be used to run though the list of items, and <classname>Insert</> components will be used to present each item in the list, as well as the total. </> <para> If the user enter in a non-number, then an error message is displayed. </> <para> As with the previous examples, the servlet and application objects are simple variations on the previous two sets (they are ommited here). </> <para> The application specification is, likewise, a variation on the prior example. </> <para> The code for this section is in the <classname>tutorial.adder</> package. </> <para> We'll start with the HTML template for the home page: </> <figure> <title>Home.html</> <programlisting><![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Adder Tutorial

Value:
Items


]]> Again, Tapestry takes care of most of the details. The form component will turn into an HTML form element, and the correct URL is automatically generated. The inputNewValue component will become an input type=text, with the necessary smarts to collect the value submitted by the user and provide it to the page. The e component is type Foreach, used for running through a list of elements (supplied as a List, Iterator or an array of Java objects). We've already see the Insert component. Next we have the specification:

Home.jwc</> <programlisting><![CDATA[ <?xml version="1.0"?> <!DOCTYPE specification PUBLIC "-//Primix Solutions//Tapestry Specification 1.0//EN" "http://tapestry.sourceforge.net/dtd/Tapestry_1_0.dtd"> <specification> <class>tutorial.adder.Home</class> <components> <component> <id>ifError</id> <type>Conditional</type> <bindings> <binding> <name>condition</name> <property-path>error</property-path> </binding> </bindings> </component> <component> <id>insertError</id> <type>Insert</type> <bindings> <binding> <name>value</name> <property-path>error</property-path> </binding> </bindings> </component> <component> <id>form</id> <type>Form</type> <bindings> <binding> <name>listener</name> <property-path>formListener</property-path> </binding> </bindings> </component> <component> <id>inputNewValue</id> <type>TextField</type> <bindings> <binding> <name>text</name> <property-path>newValue</property-path> </binding> </bindings> </component> <component> <id>e</id> <type>Foreach</type> <bindings> <binding> <name>source</name> <property-path>items</property-path> </binding> </bindings> </component> <component> <id>insertCurrentValue</id> <type>Insert</type> <bindings> <binding> <name>value</name> <property-path>components.e.value</property-path> </binding> </bindings> </component> <component> <id>insertTotal</id> <type>Insert</type> <bindings> <binding> <name>value</name> <property-path>total</property-path> </binding> </bindings> </component> </components> </specification>]]></programlisting></figure> <para> We only want to display the error message if there is one, so the <varname>ifText</> is conditional on there being a non-null error message (the <classname>Conditional</> component treats <literal>null</> as <literal>false</>). </> <para> For the <varname>form</> component, all we have to do is supply a <varname>listener</>, an object that is informed when the form is submitted. </> <para> For the <varname>inputNewValue</> component, we provide a <varname>text</> parameter that provides the default value for the <sgmltag class=starttag>input</> element. The same property is updated with the value submitted with the form. </> <para> The property must be of type <classname>java.lang.String</>, so we need to do a little translation (in our Java class), since internally we want to store the value as a double. </> <para> For the <varname>e</> component, we supply a binding for the <varname>source</> parameter. For each item in the source list, it will update its own <varname>value</> property, which is later accessed by the <varname>insertCurrentValue</> component. The property path <varname>components.e.value</> accomplishes this: the page has a <varname>components</> property, which is a <classname>Map</> of the components on the page. <varname>e</> is the id of a component, and a key in the <classname>Map</>. It has a property named <varname>value</>, which is the current item from the source list. </> <para> A <classname>Foreach</> component also has a parameter named <varname>value</>. By creating a binding for this parameter, the <classname>Foreach</> can update a property of the page, or some other component. This is more commonly used when the items in the list are business objects and the application needs to invoke business methods on them. </> <para> Finally, the Java code for the home page puts everything together: </> <figure> <title>Home.java</> <programlisting><![CDATA[package tutorial.adder; import com.primix.tapestry.*; import com.primix.tapestry.components.*; import java.util.*; public class Home extends BasePage { private List items; private String newValue; private String error; public List getItems() { return items; } public void setItems(List value) { items = value; fireObservedChange("items", value); } public void setNewValue(String value) { newValue = value; } public String getNewValue() { return newValue; } public void detach() { items = null; newValue = null; error = null; super.detach(); } public void addItem(double value) { if (items == null) { items = new ArrayList(); fireObservedChange("items", items); } items.add(new Double(value)); fireObservedChange(); } public double getTotal() { Iterator i; Double item; double result = 0.0; if (items != null) { i = items.iterator(); while (i.hasNext()) { item = (Double)i.next(); result += item.doubleValue(); } } return result; } public IActionListener getFormListener() { return new IActionListener() { public void actionTriggered(IComponent component, IRequestCycle cycle) { try { double item = Double.parseDouble(newValue); addItem(item); newValue = null; } catch (NumberFormatException e) { error = "Please enter a valid number."; } } }; } public String getError() { return error; } }]]></programlisting></figure> <para> That may seem like a lot of code for what we're doing, but in reality, very much is going that we don't have to write: </> <itemizedlist> <listitem> <para> Processing the submitted form </> </> <listitem> <para> Storing the List of items persistently between request cycles </> </> <listitem> <para> Encoding and decoding URLs </> </> <listitem> <para> Very robust exception support </> </> </> <para> Tapestry components, using JavaBeans properties, take care of moving data to and from the HTML form. Our application merely has to supply the logic to properly respond when the form is submitted. In this case, converting the text into a double that can be added to the list. </> <para> Because we let Tapestry set the names of our form elements, there's no possibility of mismatched names between the Java code (setting defaults and interpreting the posted request) and the HTML template. </> <para> Enter a few values into the text field to see how the application works, adding them together into an ever larger list. </> <section id="interactive.listeners"> <title>Adding Interactivity using Listeners</> <para> To understand the relationship between the home page specification, the home page class and the components used by the home page, it is necessary to understand the JavaBeans properties provided by the home page class. </> <para> We implement several JavaBeans properties on this page: </> <table> <title>JavaBeans Properties</> <tgroup cols=4> <thead> <row> <entry>Property name</> <entry>Type</> <entry>R / W</> <entry>Description</> </row> </> <tbody> <row> <entry>newItem</> <entry>String</> <entry>R / W</> <entry>Stores the string entered into the form.</> </row> <row> <entry>items</> <entry>List (of Double)</> <entry>R / W</> <entry>Items in the list. Persists between request cycles.</> </> <row> <entry>formListener</> <entry>IActionListener</> <entry>Read Only</> <entry>Informed when form is submitted.</> </> <row> <entry>total</> <entry>double</> <entry>Read Only</> <entry>Total of items; computed on the fly.</> </row> </tbody> </tgroup> </table> <para> This example demonstrates how to provide interactivity to an application. For Tapestry, interactivity is defined as a request cycle initiated by a user clicking on a hyperlink or submitting a form. </> <para> In our case, we want to know when the form containing the text field is submitted so that we can provide application specific behavior: adding the value enterred by the user to the list of items. </> <para> This is accomplished using a listener, an object that implements the Java interface <classname>IActionListener</>. This interface defines a single method, <function>actionTriggered()</>. When the form is submitted, all the components wrapped by the form (in this case, the <classname>TextField</>) are given a chance to retrieve their values from the request and update properties of the application (the <classname>TextField</> sets the <varname>currentItem</> property). The form then gets its listener and invokes the <function>actionTriggered()</> method. </> <para> In the specification, the listener parameter was bound to the <varname>formListener</> property of the page. The code in the <function>getFormListener()</> method creates an anonymous inner class and returns it. </> <para> Inner classes have access to the private fields and methods of the class. In this case, the inner class invokes the <function>addItem()</> method to add the <varname>currentItem</> (with a value provided by the <classname>TextField</> component) to the items <classname>List</>. </> <para> A listener is free to do anything it wants. It can change the state of the application, or can retrieve other pages (by name) from the request cycle object, and can change properties of those pages. It can even chose a different page to render, by invoking <function>setPage()</> on the request cycle. </> </section> <section id="interactive.pooling"> <title>Persistant Page State and Page Pooling</> <para> The Home page of this application uses a persistant page property, a <classname>List</> that contains <classname>java.lang.Double</>s, the items in the list. </> <para> Persistent page state is one of the most important concepts in Tapestry. Each page in the application (and in fact, even components within the page) may have some properties that should persist between requests. This can be values such as the user's name and address, or (in this case) the list of numbers enterred so far. </> <para> In traditional JavaServer Pages or servlet applications, a good chunk of code must be written to manage this. The values must be encoded in cookies, as hidden form fields, as named attributes of the <classname>HttpSession</>, or stored into a server-side flat file or database. Each servlet (or JSP) is directly responsible for managing access to these values which leads to many half realized, ad- hoc solutions and an avalanche of bugs, and even security holes. </> <para> With Tapestry, the framework takes care of these persistence issues. When a persistent property of a page is changed the accessor method also invokes the method <function>fireObservedChange()</>. This method informs a special object, the page's recorder, about the property and its new value. </> <para> When the page is next used, the value is restored automatically. This may not seem natural; an obvious question is: why wasn't the page in the same state? Then answer is that that page instance is shared, and may be used by a different client in the interrum. </> <para> Within the Tapestry framework, all of these pages, components, specifications and templates are converted into Java objects. Assembling a page is somewhat expensive: it involves reading all those specifications and templates , creating and initializating component objects, creating binding objects for the components, and organizing the components into a hierarchy. </> <para> Creating a page object for just one request cycle only to discard it is simply unacceptible. Pages should be kept around as long as they are needed; they should be re-used in subsequent request cycles, both for the same client session, or for other sessions. </> <para> The Tapestry framework accomplishes this by pooling instances of page objects; there could concievably be a handful of different instances being shared by thousands of client sessions. This is a kind of shell game that is important to maintain scalability. </> <para> What this means for the developer is some minor extra work. On each request cycle, a different instance of the page object may be used to handle the request. This means that data can't simply be stored in the instance variables of the page between request cycles. </> <para> Tapestry isolates the persistent state of a page from the actual page objects. The state is stored seperately, making use of the page recorder objects. When needed, a page can be created or reclaimed from the page pool and have all of its persistant properties set by the page recorder. </> <para> The developer has three responsibilities when coding a page with persistant state: </> <itemizedlist> <listitem> <para> The property must be serializable; this includes Java scalar types (boolean, int, double, etc.), <classname>String</>s, common collection classes ( <classname>ArrayList</>, <classname>HashMap</>, etc.) and other classes that implement <classname>java.io.Serializable</>. </> </> <listitem> <para> When the value of the property changes, the <function>fireObservedChange()</> method must be invoked, to inform the page recorder about the change. </> </> <listitem> <para> When the request cycle ends and the page is returned to the pool, the persistant state must be reset to its initial value (as if the page object was newly instantiated). This is done in the <function>detach()</> method. </> </> </> </section> <section id="interactive.dynamic"> <title>Dynamic Page State</> <para> This page has a bit of dynamic state; state that changes as the page is being renderred. The <varname>value</> property of the <classname>Foreach</> component takes on different values from the <varname>items</> <classname>List</> as the page is renderred. Dynamic state is easier to handle than persistant state; for completeness, it must also be reset in the <function>detach()</> method. </> </section> </chapter>