| <document> |
| <body> |
| <section name="Basic Example"> |
| |
| <p> |
| This example is based around a NavBar component that generates a set |
| of links to other pages in the applilcation. |
| </p> |
| |
| <subsection name="NavBar.tml"> |
| |
| <source><![CDATA[ |
| <table class="navigation" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd"> |
| |
| <tr> |
| <t:loop source="pageNames" value="pageName"> |
| <td class="${tabClass}"> |
| <t:pagelink page="pageName">${pageName}</t:pagelink> |
| </td> |
| </t:loop> |
| </tr> |
| |
| </table>]]></source> |
| |
| <p> |
| We are assuming that the NavBar component |
| has a pageNames property (possibly a parameter). The Loop will |
| iterate over those page names and store each into its value parameter. |
| </p> |
| |
| |
| </subsection> |
| |
| |
| <subsection name="NavBar.java"> |
| |
| <source><![CDATA[ |
| public class NavBar |
| { |
| @Parameter(defaultPrefix="literal", required=true) |
| private String pages; |
| |
| @Inject |
| private ComponentResources resources; |
| |
| @Property |
| private String _pageName; |
| |
| public String[] getPageNames() |
| { |
| return pages.split(","); |
| } |
| |
| public String getTabClass() |
| { |
| if (pageName.equalsIgnoreCase(resources.getPageName()) |
| return "current"; |
| |
| return null; |
| } |
| } |
| ]]></source> |
| |
| <p> |
| The component converts its pages parameter into the pageNames property |
| by splitting it at the commas. It tracks the current pageName of the loop |
| not just to generate the links, but to calculate the CSS class of each |
| <td> element on the fly. This way we can give the tab corresponding |
| to the current page a special look or highlight. |
| </p> |
| |
| </subsection> |
| |
| </section> |
| |
| <section name="Invisible Instrumentation"> |
| |
| <p>We can fold together the Loop component and the <td> element:</p> |
| |
| <subsection name="NavBar.tml"> |
| |
| <source><![CDATA[ |
| <table class="navigation" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd"> |
| |
| <tr> |
| <td t:type="loop" source="pageNames" value="pageName" class="${tabClass}"> |
| <t:pagelink page="pageName">${pageName}</t:pagelink> |
| </td> |
| </tr> |
| |
| </table>]]></source> |
| |
| <p>Using the |
| <code>t:type="loop"</code> |
| attribute, the other way to identify a template |
| element as a component, allows the Loop component to render the element's tag, |
| the <td> on each iteration, along with informal parameters (the class attribute). This is |
| called<em>invisible instrumentation</em>, and it is more concise and more |
| editor/preview friendly than Tapestry's typical markup. |
| </p> |
| </subsection> |
| </section> |
| |
| <section name="Forms and Loops Example"> |
| |
| <p> |
| Tapestry form control element components (TextField, etc.) work inside loops. However, |
| some additional configuration is needed to make this work efficiently. |
| </p> |
| |
| <p> |
| With no extra configuration, each value object will be serialized into the form (if |
| you view the rendered markup, you'll see a hidden form field containing serialized data needed by |
| Tapestry to process the form). This can become very bloated, or may not work if the objects being |
| iterated |
| are not serializable. |
| </p> |
| |
| <p> |
| The typical case is database driven; you are editting objects from a database and need |
| those objects back when the form is submitted. All that should be stored |
| on the client is the |
| <em>ids</em> |
| of those objects. Thats what the encoder |
| parameter is for. |
| </p> |
| |
| <subsection name="EditOrder.tml"> |
| <source><![CDATA[ |
| <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd"> |
| <body> |
| |
| <h1>Edit Order Quantities</h1> |
| |
| <t:form> |
| |
| <t:errors/> |
| |
| <t:loop source="items" value="item" encoder="encoder"> |
| <div class="line-item"> |
| <t:label for="quantity">${item.product.name}</t:label> |
| <t:textfield t:id="quantity" value="item.quantity"/> |
| </div> |
| </t:loop> |
| |
| <input type="submit" value="Update"/> |
| </t:form> |
| </body> |
| </html>]]></source> |
| |
| <p> |
| The TextField component is rendered multiple times, once |
| for each LineItem in the Order. |
| </p> |
| </subsection> |
| |
| <subsection name="EditOrder.java"> |
| <source><![CDATA[ |
| public class EditOrder |
| { |
| @Inject |
| private OrderDAO orderDAO; |
| |
| @Property |
| private final ValueEncoder<LineItem> encoder = new ValueEncoder<LineItem>() |
| { |
| public String toClient(LineItem value) { return String.valueOf(value.getId()); } |
| |
| public LineItem toValue(String clientValue) |
| { |
| long id = Long.parseLong(clientValue); |
| |
| return orderDAO.getLineItem(id); |
| } |
| }; |
| |
| @Persist |
| private long orderId; |
| |
| @Property |
| private LineItem item; |
| |
| public List<LineItem> getItems() |
| { |
| return orderDAO.getLineItemsForOrder(orderId); |
| } |
| }]]></source> |
| |
| <p> |
| Here, we expect the OrderDAO service to do most of the work, |
| and we create a wrapper around it, in the form of the |
| ValueEncoder instance. |
| </p> |
| |
| <p> |
| We've glossed over a few issues here, including how to handle |
| the case that a particular item has been deleted or changed |
| between the render request and the form submission, as well as how the orderId |
| property gets set in the first place. |
| </p> |
| |
| </subsection> |
| |
| |
| </section> |
| |
| </body> |
| </document> |