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>');