In this tutorial, we'll show how to create a reusable component. One common use of components it to create a common "border" for the application that includes basic navigation. We'll be creating a simple, three page application with a navigation bar down the left side.
Navigating to another page results in a similar display:
Each page's content is confined to the silver area in the center. Note that the border adapts itself to each page: the title "Home" or "Credo" is specific to the page, and the current page doesn't have an active link (in the above page, "Credo" is the current page, so only "Home" and "Legal" are usable as navigation links).
The "i" in the gear is the Show Inspector link. It will be described in the next chapter.
Because this tutorial is somewhat large, we'll only be showing excerpts from some of the files. The complete source of the tutorial examples is available seperately, in the tutorial.border package.
Each of the three pages has a similar HTML template:
Figure 6.3. Home.html
<span jwcid="border"> Nothing much doing here on the <b>home</b> page. Visit one of our other fine pages. </span> |
Remember that Tapestry components can wrap around other HTML elements or components. For the border, we have an HTML template where everything on the page is wrapped by the border component.
Note that we don't specify any <html> or <body> tags; those are provided by the Border component (as well as the matching close tags).
This illustrates a key concept within Tapestry: embedding vs. wrapping. The Home page embeds the border component (as we'll see in the Home page's specification). This means that the Home page is implemented using the border component.
However, the border component wraps the content of the Home page, the Home page HTML template indicates the order in which components (and static HTML elements) are renderred. On the Home page, the border component 'bats' first and cleanup.
The construction of the Border component is driven by how it differs from page to page. You'll see that on each page, the title (in the upper left corner) changes. The names of all three pages are displayed, but only two of the three will have links (the third, the current page, is just text). Lastly, each page contains the specific content from its own HTML template.
Figure 6.4. Border.html
![]() | The shell component provides the <html> and <head> elements of the response HTML. |
![]() | The body components provides the <body> element. It also provides support for JavaScript related to Rollover buttons, such as the showInspector component. |
![]() | The e component is a Foreach configured to work through a list of page names (provided by the engine). |
![]() | The link and insertName components provide the inter-page navigation links. |
![]() | The renderBody component provides the actual content for the page. The Border component is used on all three pages, but its a different instance on each page, wrapping around different content specific to the page. |
![]() | The showInspector component provides the button below the page names (the italicized "i" in a circle) and will be explained shortly. |
The Border component is designed to be usable in other Tapestry applications, so it doesn't hard code the list of page names. These must be provided to the component as a parameter. In fact, the application engine provides the list.
Figure 6.5. Border.jwc
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE component-specification PUBLIC "-//Howard Lewis Ship//Tapestry Specification 1.3//EN" "http://tapestry.sf.net/dtd/Tapestry_1_3.dtd"> <component-specification class="tutorial.border.Border" allow-informal-parameters="no"> <parameter name="title" java-type="java.lang.String" required="yes"/> |
![]() | Declares a required parameter for the border, the title that will appear on the page. |
![]() | Declares a parameter to specify the list of page names. We don't specify a particular type because its pretty unbounded; the framework will accept List, Iterator or a Java array. |
![]() | We then provide the shell component with its title parameter; this will be the window title. We use the application's name, with is extracted from the application's specification. |
![]() | The <inherited-binding> element allows a component to share its parameters. Here the Border's title is used as the value parameter of the insertPageTitle component (an Insert). Using these inherited bindings simplifies the process of creating complex components from simple ones. |
![]() | Likewise, the e component (a Foreach) needs as its source the list of pages, which it inherits from the Border component's pages parameter. It has been configured to store each succesive page name into the pageName property of the Border component; this is necessary so that the Border component can determine which page link to disable (it disables the current page since we're already there). |
![]() | The link component creates the link to the other pages. It has a disabled parameter; which, when true, causes the link component to not create the hyperlink (though it still allows the elements it wraps to render). The Java class for the Border component, tutorial.border.Border, provides a method, getDisablePageLink(), that returns true when the pageName instance variable (set by the e component) matches the current page's name. |
![]() | This component will raise the Tapestry Inspector in a new window when clicked. |
So, the specification for the Border component must identify the parameters it needs, but also the components it uses and how they are configured.
Clicking on the button raises a second window that describes the current page in the application (this is used when debugging a Tapestry applicaton). The Inspector is described in the next chapter.
The final mystery is the wrapped component. It is used to render the elements wrapped by the Border on the page containing the Border. Those elements will vary from page to page; running the application shows that they are different on the home, credo and legal pages (different text appears in the central light-grey box). There is no limitation on the elements either: Tapestry is specifically designed to allow components to wrap other components in this way, without any arbitrary limitations.
This means that the different pages could contain forms, images or any set of components at all, not just static HTML text.
The specification for the home page shows how the title and pages parameters are set. The title is static, the literal value "Home" (this isn't the best approach if localization is a concern).
Figure 6.7. Home page specification
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE page-specification PUBLIC "-//Howard Lewis Ship//Tapestry Specification 1.3//EN" "http://tapestry.sf.net/dtd/Tapestry_1_3.dtd"> <page-specification class="org.apache.tapestry.html.BasePage"> <component id="border" type="Border"> <static-binding name="title">Home</static-binding> <binding name="pages" expression="engine.pageNames"/> </component> </page-specification> |
The pages property is retrieved from the application engine, which implements a pageNames JavaBeans property:
Figure 6.8. BorderEngine.java (excerpt)
private static final String[] pageNames = { "Home", "Credo", "Legal" }; public String[] getPageNames() { return pageNames; } |
How did Tapestry know that the type 'Border' corresponded to the specification /tutorial/border/Border.jwc? Only because we defined an alias in the application specification:
Figure 6.9. Border.application (excerpt)
<component-alias type="Border" specification-path="/tutorial/border/Border.jwc"/> |
Had we failed to do this, we would have had to specify the complete resource path, /tutorial/border/Border.jwc, on each page's specification, instead of the short alias 'Border'. There is no magic about the existing Tapestry component types (Insert, Foreach, PageLink, etc. ... they each have an alias pre-registered into every application specification. These short aliases are simply a convienience.