| <chapter id="components"> |
| <title>Creating Components</title> |
| <para> |
| One of the major features of Tapestry, that makes complex web development easier, is the ability to |
| create custom components that can be reused across the application (or in fact, in other applications, |
| via the use of Libraries). </para> |
| |
| <para>Components are similar to Pages, in that they can have an HTML template, |
| a specification and a Java object. However there is a crucial difference - components can expose properties, whereas Pages cannot. |
| Other than this, components take the same form. They consist of an HTML template (optional), component specification and java object. |
| The java object "backing" the component typically derives from AbstractComponent. AbstractComponent |
| in turn implements IRender, which simply renders all components that are declared within the component specification. |
| </para> |
| |
| <para>You will find all code for this example within |
| the 'components' package of the tutorial.</para> |
| |
| <section id="components-example"> |
| <title>Building an Example</title> |
| <para> |
| The sample below is for a tutorial component, called <emphasis>ArrayViewer</emphasis>. |
| This component can take any Java array, |
| and render it as an HTML table. In real applications, the same can be done using alternative components that |
| exist in the framework (such as <varname>TableView</varname>). This example exists to show simply how easy it is to build a component.</para> |
| |
| <para> |
| By the end of this example, you should have learnt the basics of building a component with various properties. |
| </para> |
| |
| <para>First, we shall define the HTML template:</para> |
| <figure> |
| <title>HTML for example component</title> |
| <programlisting> |
| <span jwcid="$content$"> |
| <table border=1 width="95%"> |
| <tr><td align="center"> |
| |
| <table> |
| <tr><th> |
| <span jwcid="heading"/> |
| </th></tr> |
| </table> |
| |
| </td></tr> |
| |
| <tr><td> |
| |
| <table border=1 width="100%"> |
| <span jwcid="elements"> |
| <tr> |
| <span jwcid="cells"> |
| <td> |
| <span jwcid="cell"/> |
| </td> |
| </span> |
| </tr> |
| </span> |
| </table> |
| |
| </td></tr> |
| </table> |
| </span> |
| </programlisting> |
| </figure> |
| |
| <para>This template defines a table skeleton, where the intention is to enable the rendering of headings |
| and row data by the component itself. Here, we intend to show a single row at the top of the component |
| as a heading <footnote><para>At this point, we are not going to consider aspects such as custom renderers</para></footnote> |
| , and then some number of rows (the <varname> |
| elements</varname> component) of data. Each row of data is rendered using another &Foreach; component - <varname> |
| cells</varname>. Finally we output the value of each cell using the <varname>cell</varname>, which you |
| will see below defined as being of type &InsertText;. |
| </para> |
| |
| <para>You may have noticed the span element with a jwcid of <emphasis>$content$</emphasis>. When reading the |
| template, Tapestry will ignore anything <emphasis>outside</emphasis> of the <span jwcid="$content$"> element. |
| For example, it is then possible to write a template like this:</para> |
| |
| <figure> |
| <title>Sample usage of $content$</title> |
| <programlisting> |
| <html> |
| <head><title>Your Component</title></head> |
| <body> |
| <span jwcid="$content$"> |
| <table border=1 width="95%"> |
| ... other stuff here ... |
| </table> |
| </span> |
| </body> |
| </html> |
| </programlisting> |
| </figure> |
| |
| <para>and still have it work properly in Tapestry (that is, without the other elements that exist outside of the content span tag). |
| Now for the component specification:</para> |
| |
| <figure> |
| <title>Component Specfication for example component</title> |
| <programlisting> |
| <component-specification class="tutorial.components.ArrayViewer" allow-body="no" allow-informal-parameters="yes"> |
| |
| <parameter |
| name="source" |
| java-type="java.lang.Object" |
| direction="in" |
| required="yes"> |
| </parameter> |
| |
| <parameter |
| name="heading" |
| java-type="java.lang.String" |
| direction="in" |
| required="no"> |
| </parameter> |
| |
| <component id="elements" type="Foreach"> |
| <inherited-binding name="source" parameter-name="source"/> |
| </component> |
| |
| <component id="cells" type="Foreach"> |
| <binding name="source" expression="components.elements.value"/> |
| </component> |
| |
| <component id="heading" type="InsertText"> |
| <binding name="value" expression="getHeading()"/> |
| </component> |
| |
| <component id="cell" type="InsertText"> |
| <binding name="value" expression="components.cells.value"/> |
| </component> |
| |
| </component-specification> |
| </programlisting> |
| </figure> |
| |
| <para>The first thing to note is that this specification is named <filename>ArrayViewer.jwc</filename> and |
| not <filename>ArrayViewer.page</filename>. In Tapestry, all component specifications end with the |
| <filename>.jwc</filename> suffix.</para> |
| |
| <para>At the top, you will see two new parameters <varname>allow-body</varname> and <varname> |
| allow-informal-parameters</varname>. The former controls whether or not Tapestry will allow the |
| component to have a body. If you specify <varname>no</varname> here, then Tapestry will strip |
| any content inserted into the body of the element when the component is rendered. If you specify |
| <varname>yes</varname> then Tapestry will process the body, including any contained comonents</para> |
| |
| <para>The <varname>parameter</varname> declarations define the source data (which is required) as well as an |
| optional <varname>heading</varname> for the generated table. |
| If a value for <varname>heading </varname> is omitted, the <varname>getHeading()</varname> method will |
| return a default heading value</para> |
| |
| <para>The source data is expected to be some kind of collection that a &Foreach; component |
| can handle. This is not a Tapestry requirement. The only reason is that this example uses the |
| <varname>source</varname> value directly within a &Foreach; component itself. |
| In this example, we will pass a multi-dimensional array. The example is intentionally simple and |
| the component does not check to see if each "row" of data has the same width.</para> |
| |
| <para>The component code itself can be very simple, since we have bound everything |
| apart from the header text using OGNL expressions. The <varname>inherited-binding</varname> |
| is used to refer to parameters specified in the parameter specification (top of the file). |
| This type of binding links the values passed by the user of the component to values |
| used inside the component.</para> |
| |
| <para>Here is the associated Java code for the component:</para> |
| |
| <figure> |
| <title>ArrayViewer Java Code</title> |
| <programlisting> |
| public class ArrayViewer extends BaseComponent { |
| /** |
| * Return the bound heading if there is one, else return a static default heading |
| */ |
| public String getHeading() { |
| IBinding binding = (IBinding)getBinding("heading"); |
| if(binding.getObject() != null) { |
| return binding.getObject().toString(); |
| } |
| return heading; |
| } |
| |
| /** |
| * Sets the heading. |
| * @param heading The heading to set |
| */ |
| public void setHeading(String heading) { |
| this.heading = heading; |
| } |
| |
| /** |
| * @see net.sf.tapestry.AbstractComponent#cleanupAfterRender(IRequestCycle) |
| */ |
| protected void cleanupAfterRender(IRequestCycle cycle) { |
| source = null; |
| heading = "Array Viewer"; |
| super.cleanupAfterRender(cycle); |
| } |
| |
| /** |
| * Returns the source. |
| * @return Object |
| */ |
| public Object getSource() { |
| return source; |
| } |
| |
| /** |
| * Sets the source. |
| * @param source The source to set |
| */ |
| public void setSource(Object source) { |
| this.source = source; |
| } |
| |
| private String heading = "Array Viewer"; |
| private Object source; |
| } |
| </programlisting> |
| </figure> |
| |
| <para>Tapestry requires that accessors be available for the bindings as they have been implemented here. |
| The <varname>set/getSource()</varname> and <varname>set/getHeading()</varname> are called by Tapestry |
| at the appropriate time. Notice that we have added some code within <varname>getHeading()</varname> to |
| ensure that if a value for the binding <emphasis>has</emphasis> been set, then this is used.</para> |
| |
| <para>Finally, we add to the application specification, to make the component available using a shorter name:</para> |
| <figure> |
| <title>Modified Application Specification</title> |
| <programlisting> |
| <application name="Components Tutorial" engine-class="net.sf.tapestry.engine.SimpleEngine"> |
| <page name="Home" specification-path="/tutorial/components/Home.page"/> |
| <emphasis role="bold"><component-alias type="ArrayViewer" specification-path="/tutorial/components/ArrayViewer.jwc" /></emphasis> |
| </application> |
| </programlisting> |
| </figure> |
| |
| <para>As you can see, the implementation of a component is very simple. Lastly, we show how to use |
| this newly defined component. Here is the HTML, page specification, and associated java code</para> |
| |
| <figure> |
| <title>HTML for component usage</title> |
| <programlisting> |
| <body> |
| |
| <h1>ArrayViewer example</h1> |
| This pag contains a single instance of ArrayViewer with some sample static data. |
| |
| <hr> |
| <span jwcid="viewer">Viewer Will Go here</span> |
| </hr> |
| |
| </body> |
| </programlisting> |
| </figure> |
| |
| <figure> |
| <title>Page specification for component usage</title> |
| <programlisting> |
| <page-specification class="tutorial.components.Home"> |
| |
| <component id="viewer" type="ArrayViewer"> |
| <static-binding name="heading">Array Viewer Example!</static-binding> |
| <binding name="source" expression="getArraySource()"/> |
| </component> |
| |
| </page-specification> |
| </programlisting> |
| </figure> |
| |
| <figure> |
| <title>JavaCode to return the data to display</title> |
| <programlisting> |
| /** |
| * Example code for the "creating components" section of the tutorial |
| * @author neil clayton |
| */ |
| public class Home extends BasePage { |
| public Object[] getArraySource() { |
| return new Object[] { |
| new Object[] { "This is", "a test", "of the array viewer" }, |
| new Object[] { "There should be nothing in the next two columns", null, null }, |
| new Object[] { new Integer(1234), Boolean.TRUE, this } |
| }; |
| } |
| } </programlisting> |
| </figure> |
| |
| <para>And finally, this is what you should see when running the turorial example:</para> |
| <figure> |
| <title>Simple Component - The result</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/components-result.gif" format="GIF"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| |
| <para>Note that in the component specification, we are able to use a shortened name for |
| the new component. This is because in this tutorial, the Appliation specification had added to it a component alias. |
| If this was not the case, the full package path to the component would also have to be specified.</para> |
| </section> |
| </chapter> |