Sometimes, you may want to specify a component parameter in tapestry's markup language (TML) and instead of rendering the HTML directly to the response output stream, you may wish to use it for some other purpose (eg use it in a javascript).
Example 1
The code below demonstrates a component that does the following:
- Accepts a count parameter and a RenderCommand parameter
- Adds a wrapper div to the markup writer
- Loops through each count and updates a the "current" property which may be referenced in the RenderCommand parameter
- Renders the RenderCommand for each iteration through the loop
- Uses @AfterRender to get the resultant HTML from the wrapper div
- Removes the wrapper div from the markup so that it is not rendered to the response directly
Page.tml
<t:tmlToString t:count="5" t:id="tmlToString"> <p:renderMe> <div>foo ${tmlToString.current} bar</div> </p:renderMe> </t:tmlToString>
Page.java
@InjectComponent @Property private TmlToString tmlToString;
TmlToString.java
public class TmlToString { @Parameter @Property private RenderCommand renderMe; @Property @Parameter(defaultPrefix=BindingConstants.LITERAL, required=true) private int count; @Property private int current; @Inject private JavaScriptSupport javaScriptSupport; private Element wrappingDiv; @BeginRender RenderCommand beginRender() { return new RenderCommand() { public void render(MarkupWriter writer, RenderQueue queue) { wrappingDiv = writer.element("div"); List<RenderCommand> commands = new ArrayList<RenderCommand>(); for (int i = 0; i < count; ++ i) { final int finalI = i; commands.add(new RenderCommand() { public void render(MarkupWriter writer2, RenderQueue queue2) { current = finalI; queue2.push(renderMe); } }); } Collections.reverse(commands); // render commands are pushed to the front of the queue for (RenderCommand command : commands) { queue.push(command); } } }; } @AfterRender void afterRender(MarkupWriter writer) { writer.end(); String html = wrappingDiv.getChildMarkup(); wrappingDiv.remove(); javaScriptSupport.addScript("alert('%s')", html); } }
Result
alert('<div>foo 0 bar</div><div>foo 1 bar</div><div>foo 2 bar</div><div>foo 3 bar</div><div>foo 4 bar</div>');
Example 2
Here is a slightly different example which uses the component's body block instead of a render command parameter. Note that tapestry's TypeCoercer can coerce between Block and RenderCommand
Page.tml
<t:alertBody t:count="5" t:id="alertBody"> <div>${alertBody.current}</div> </t:alertBody>
Page.java
@InjectComponent @Property private AlertBody alertBody;
AlertBody.java
public class AlertBody { @Property @Parameter(defaultPrefix=BindingConstants.LITERAL, required=true) private int count; @Property private int current; @Inject private JavaScriptSupport javaScriptSupport; @Inject private ComponentResources componentResources; @Inject private TypeCoercer typeCoercer; private Element wrappingDiv; @BeginRender RenderCommand beginRender() { return new RenderCommand() { public void render(MarkupWriter writer, RenderQueue queue) { wrappingDiv = writer.element("div"); List<RenderCommand> commands = new ArrayList<RenderCommand>(); for (int i = 0; i < count; ++ i) { final int finalI = i; commands.add(new RenderCommand() { public void render(MarkupWriter writer2, RenderQueue queue2) { current = finalI; Block body = componentResources.getBody(); RenderCommand bodyRenderCommand = typeCoercer.coerce(body, RenderCommand.class); queue2.push(bodyRenderCommand); } }); } // commands are pushed to the front of the queue Collections.reverse(commands); for (RenderCommand command : commands) { queue.push(command); } } }; } @BeforeRenderBody boolean beforeRenderBody() { // we are explicitly rendering the body in beginRender so do not render it implicitly return false; } @AfterRender void afterRender(MarkupWriter writer) { writer.end(); String html = wrappingDiv.getChildMarkup(); wrappingDiv.remove(); javaScriptSupport.addScript("alert('%s')", html); } }
Result
alert('<div>0</div><div>1</div><div>2</div><div>3</div><div>4</div>');