Designing new components <para> Creating new components using Tapestry is designed to be quite simple. </> <para> Components are typically created through aggregation, that is, by combining existing components using an HTML template and specification. </> <para> You will almost always want to define a short alias for your new component in the application specification. This insulates developers from minor name changes to the component specification, such as moving the component to a different Java package. </> <para> Like pages, components should reset their state back to default values when the page they are contained within is returned to the pool. </> <para> Most components do not have any state. A component which does should implement the <classname>ILifecycle</> interface, and implement the <function>reset()</> method. </> <para> The <function>reset()</> method is invoked from the page's <function>detatch()</> method, which is invoked at the very end of the request cycle, just before the page is returned to the page pool. </> <section id="coding-components.base-class"> <title>Choosing a base class</> <para> There are two basic types of components: those that use an HTML template, and those that don't. </> <para> Nearly all of the base components provided with the Tapestry framework don't use templates. They inherit from the class <classname>com.primix.tapestry.AbstractComponent</>. Such components must implement the <function>render()</> method. </> <para> Components that use templates inherit from a subclass of <classname>AbstractComponent</>: <classname>com.primix.tapestry.BaseComponent</>. They should leave the <function>render()</> method alone (it already does what it should ... load the template if necessary and render itself using the template). </> <para> In some cases, a new component can be written just by combining existing components ( this often involves using inherited bindings). Such a codeless component will consist of just a specification and an HTML template and will use the <classname>BaseComponent</> class. </> </section> <section id="coding-components.parameters"> <title>Parameters and Bindings</> <para> You may create a component that has parameters. Under Tapestry, component parameters are a kind of "named slot" that can be wired up as a source (or sink) of data in a number of ways. This "wiring up" is actually accomplished using binding objects. </> <para> The page loader, the object that converts a component specification into an actual component, is responsible for creating and assigning the bindings. It uses the method <function>setBinding()</> to assign a binding with a name. Your component can retrieve the binding by name using <function>getBinding()</>. </> <para> For example, lets create a component that allows the color of a span of text to be specified using a <classname>java.awt.Color</> object. The component has a required parameter named <varname>color</>. The class's <function>render()</> method is below: </> &start-listing;public void render(IResponseWriter writer, IRequestCycle cycle) throws RequestCycleException { IBinding colorBinding; Color color; String encodedColor; colorBinding = getBinding("color"); color = (Color)colorBinding.getObject("color", Color.class); encodedColor = RequestContext.encodeColor(color); writer.begin("font"); writer.attribute("color", encodedColor); renderWrapped(writer, cycle); writer.end(); }&end-listing; <para> The call to <function>getBinding()</> is relatively expensive, since it involves rummaging around in a <classname>java.util.Map</> and then casting the result from <classname>java.lang.Object</> to <classname>com.primix.tapestry.IBinding</>. </> <para> Because bindings are typically set once and then read frequently by the component, implementing them as private instance variables is much more efficient. Tapestry allows for this as an optimization on frequently used components. </> <para> The <function>setBinding()</> method in <classname>AbstractComponent</> checks to see if there is a read/write JavaBeans property named "<replaceable>name</>Binding" of type <classname>com.primix.tapestry.IBinding</>. In this example, it would look for the methods <function>getColorBinding()</> and <classname>setColorBinding()</>. </> <para> If the methods are found, they are invoked from <function>getBinding()</> and <function>setBinding()></> instead of updating the <classname>Map</>. </> <para> This changes the example to:</> &start-listing;<emphasis>private IBinding colorBinding; public void setColorBinding(IBinding value) { colorBinding = value; } public IBinding getColorBinding() { return colorBinding; } </> public void render(IResponseWriter writer, IRequestCycle cycle) throws RequestCycleException { Color color; String encodedColor; color = (Color)<emphasis>colorBinding</>.getObject("color", Color.class); encodedColor = RequestContext.encodeColor(color); writer.begin("font"); writer.attribute("color", encodedColor); renderWrapped(writer, cycle); writer.end(); }&end-listing; <para> This is a trade off; slightly more code for slightly better performance. There is also a memory bonus; the <classname>Map</> used by <classname>AbstractComponent</> will never be created. </> </section> <section id="coding-components.persistent-state"> <title>Persistent Component State</> <para> As with pages, individual components may have state that persists between request cycles. This is rare for non-page components, but still possible and useful in special circumstances. </> <para> A client that must persist some client state uses its page's <property>changeObserver</>. It simply posts <classname>ObservedChangeEvents</> with itself (not its page) as the source. In practice, it still simply invokes the <function>fireObservedChange()</> method. </> <para> In addition, the component should implement the interface <classname>ILifecycle</>, and implement the method <function>reset()</> and, within that method, reset all instance variables to default values, just as a page does (in its <function>detach()</> method). </> </section> <section id="coding-components.assets"> <title>Component Assets</> <para> Tapestry components are designed for easy re-use. Most components consist of a specification, a Java class and an HTML template. </> <para> Some components may need more; they may have additional image files, sounds, Flash animations, QuickTime movies or whatever. These are collectively called "assets". </> <para> Assets come in three flavors: external, internal and private. </> <itemizedlist> <listitem> <para>An external asset is just a fancy way of packaging a URL at an arbitrary web site. </> </> <listitem> <para>An internal asset represents a file with a URL relative to the web server containing the Tapestry application.</> </> <listitem> <para>A private asset is a file within the class path, that is, packaged with the component in a Java Archive (JAR) file. Obviously, such assets are not normally visible to the web server. </> </> </> <para> Components which use assets don't care what flavor they are; they simply rely on the method <function>buildURL()</> to provide a URL they can incorporate into the HTML they generate. For example, the <classname>Image</> component has an image parameter that is used to build the <varname>src</> attribute of an HTML <sgmltag class=starttag>img</> element. </> <para> Assets figure prominently into three areas: reuse, deployment and localization. </> <para> Internal and private assets may be localized: when needed, a search occurs for a localized version, relative to a base name provided in the component specification. </> <para> Private assets simplify both re-use and deployment. They allow a re-usable Tapestry component, even one with associated images, style sheets (or other assets) to be incorporated into a Tapestry application without any special consideration. For example, the standard exception page makes use of a private asset to access its stylesheet. </> <para> In a traditional web application, private assets would need to be packaged separately from the 'component' code and placed into some pre-defined directory visible to the web server. </> <para> Under Tapestry, the private assets are distributed with the component specification, HTML templates and Java code, within a Java Archive (JAR) file, or within the <filename class=directory>WEB-INF/classes</> directory of a Web Application Archive (WAR) file. The resources are located within the running application's class path. </> <para> The Tapestry framework takes care of making the private assets visible to the client web browser. This occurs in one of two ways: </> <itemizedlist> <listitem> <para>The private assets are copied out of the class path and to a directory visible to the web server. This requires some additional configuration of the Tapestry application's web deployment descriptor. </> </> <listitem> <para> The assets are dynamically accessed from the class path using the asset service. </> </> </> <para> Copying assets out of the class path and onto the web site is the best solution for final deployment, since it allows the assets to be retrieved as static files, an operation most web servers are optimized for. </> <para> Dynamically accessing assets requires additional operations in Java code. These operations are not nearly as efficient as static access. However, dynamic access is much more convenient during development since much less configuration (in this case, copying of assets) is necessary before testing the application. </> <para> As with many things in Tapestry, the components using assets are blind as to how the assets are made visible to the client. </> <para> Finally, every component has an <property>assets</> property that is an unmodifiable <classname>java.util.Map</>. The assets in the <classname>Map</> are accessible as if they were properties of the <classname>Map</>. In other words, the property path <varname>assets.welcome</> is valid, if the component defines an asset named 'welcome'. </> </section> </chapter>