<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- We need a doctype to allow us to use special characters like
We use a "strict" DTD to make IE follow the alignment rules. -->
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd">
<body class="container">
<h3>MarkupWriter</h3>
Instead of rendering from a <em>template</em>, a page or component can render a DOM directly with
Tapestry's <em>MarkupWriter</em> as described <a href="http://tapestry.apache.org/dom.html">here</a>.<br/>
For components that need a lot of control over the sequence of output it can be easier to use than a template.<br/><br/>
A really simple example is Tapestry's
<a href="http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/TextField.java?view=markup">
TextField</a> component, but its superclass
<a href="http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/corelib/base/AbstractTextField.java?view=markup">
AbstractTextField</a> does most of the work.<br/><br/>
Two more examples are JumpStart's SourceCodeDisplay (used by SourceCodeTab) and JodaTimeOutput components. Their source is below.<br/><br/>
References:
<a href="http://tapestry.apache.org/dom.html">DOM</a>,
<a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/MarkupWriter.html">MarkupWriter</a>.<br/><br/>
<t:pagelink page="Index">Home</t:pagelink><br/><br/>
<t:tabgroup>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/component/MarkupWriter.tml"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/component/MarkupWriter.java"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/components/SourceCodeDisplay.java"/>
<t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/sourcecodedisplay.css"/>
<t:sourcecodetab src="/web/src/main/java/jumpstart/web/components/JodaTimeOutput.java"/>
</t:tabgroup>
</body>
</html>
package jumpstart.web.pages.examples.component;
public class MarkupWriter {
}
package jumpstart.web.components;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.Context;
@Import(stylesheet = "css/sourcecodedisplay.css")
public class SourceCodeDisplay {
static private String LINE_SEPARATOR = System.getProperty("line.separator");
static private String STYLE_SOURCECODEDISPLAY = "sourcecodedisplay";
static private String STYLE_SOURCE = "source";
static private String STYLE_NOT_FOUND = "not-found";
static private int TAB_STOPS_WIDTH = 4;
// The source file path from the project root eg. "/web/src/main/jumpstart/web/pages/Start.java"
@Parameter(required = true, defaultPrefix = BindingConstants.LITERAL)
private String src;
@Inject
private Context context;
boolean beginRender(MarkupWriter writer) {
// Print start of the source block
writer.write(LINE_SEPARATOR);
writer.writeRaw("<!-- Start of source code inserted by SourceCodeDisplay component. -->");
writer.write(LINE_SEPARATOR);
writer.write(LINE_SEPARATOR);
// Print a div with style info to make a pretty block
writer.element("div", "class", STYLE_SOURCECODEDISPLAY);
{
// Print the source
if (src != null) {
printSourceFromInputStream(writer, src, "/WEB-INF/sourcecode" + src);
}
// Print end of div
}
writer.end();
// Print end of source block
writer.write(LINE_SEPARATOR);
writer.write(LINE_SEPARATOR);
writer.writeRaw("<!-- End of source code inserted by SourceCodeDisplay component. -->");
writer.write(LINE_SEPARATOR);
return true;
}
private void printSourceFromInputStream(MarkupWriter writer, String title, String givenPath) {
if (givenPath != null) {
URL url = context.getResource(givenPath);
try {
if (url != null) {
InputStream templateStream = url.openStream();
if (templateStream != null) {
BufferedReader templateReader = new BufferedReader(new InputStreamReader(templateStream));
printSource(writer, templateReader);
}
else {
printResourceNotFound(writer, givenPath);
}
}
else {
printResourceNotFound(writer, givenPath);
}
}
catch (IOException e) {
printResourceNotFound(writer, givenPath);
}
}
}
private void printSource(MarkupWriter writer, BufferedReader sourceReader) {
writer.element("div", "class", STYLE_SOURCE);
{
writer.element("pre");
{
writer.write(LINE_SEPARATOR);
writer.element("code");
{
writer.write(LINE_SEPARATOR);
String s;
try {
while ((s = sourceReader.readLine()) != null) {
s = replaceTabsWithSpaces(s);
writer.write(s);
writer.write(LINE_SEPARATOR);
}
}
catch (IOException e) {
writer.write("Error reading .....?");
e.printStackTrace();
}
}
writer.end();
writer.write(LINE_SEPARATOR);
}
writer.end();
}
writer.end();
}
private void printResourceNotFound(MarkupWriter writer, String resourcePath) {
writer.element("div", "class", STYLE_NOT_FOUND);
{
writer.write("The file was not found. Path given was " + resourcePath);
}
writer.end();
}
private String replaceTabsWithSpaces(String s) {
StringBuilder sb = new StringBuilder();
char c;
int column = 1;
for (int i = 0; i < s.length(); i++, column++) {
if ((c = s.charAt(i)) == '\t') {
sb.append(' ');
while (column % TAB_STOPS_WIDTH != 0) {
sb.append(' ');
column++;
}
}
else {
sb.append(c);
}
}
return sb.toString();
}
}
.sourcecodedisplay .box {
margin: 10px 0px 0px 0px;
background: #adffd6;
padding: 8px;
border: 1px solid #ddd;
border-radius: 8px;
-webkit-border-radius: 8px;
-moz-border-radius: 8px;
}
.sourcecodedisplay .source pre {
font-size: 11px;
font-weight: normal;
}
.sourcecodedisplay .not-found {
margin: 10px 0;
font-family: Arial, Helvetica, sans-serif;
font-size: 12px;
font-weight: normal;
text-align: left;
color: red;
}
// This is based on Tapestry's Output component.
package jumpstart.web.components;
import java.text.Format;
import java.util.Locale;
import org.apache.tapestry5.Binding;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.annotations.SupportsInformalParameters;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.services.ComponentDefaultProvider;
import org.joda.time.base.AbstractInstant;
import org.joda.time.base.AbstractPartial;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
/**
* A component for formatting for output of JodaTime objects. It supports subclasses of AbstractInstant and
* AbstractPartial. If the component is represented in the template using an element, then the element (plus any
* informal parameters) will be output around the formatted value.
*/
@SupportsInformalParameters
public class JodaTimeOutput {
// Parameters
/**
* The value to be output (before formatting). If the formatted value is blank, no output is produced.
*/
@Parameter(required = true)
private Object value;
/** The format to be applied to the object. */
@Parameter(required = false)
private DateTimeFormatter formatter;
/** The format to be applied to the object. */
@Parameter(required = false, defaultPrefix = BindingConstants.LITERAL)
private String style;
/** The format to be applied to the object. */
@Parameter(required = false, defaultPrefix = BindingConstants.LITERAL)
private String pattern;
/** This is declared so we catch slip-ups - an error will point the developer to formatter instead. */
@Parameter(required = false, defaultPrefix = BindingConstants.LITERAL)
private Format format;
/**
* The element name, derived from the component template. This can even be overridden manually if desired (for
* example, to sometimes render a surrounding element and other times not).
*/
@Parameter("componentResources.elementName")
private String elementName;
// Useful bits and pieces
@Inject
private ComponentDefaultProvider defaultProvider;
@Inject
private ComponentResources componentResources;
@Inject
private Locale locale;
// The code
Binding defaultValue() {
return defaultProvider.defaultBinding("value", componentResources);
}
void setupRender() {
if (format != null) {
throw new IllegalArgumentException(
"JodaTimeOutput does not allow \"format\" parameter. Valid parameters are \"style\", \"formatter\", and \"pattern\". Formatter type is DateTimeFormatter.");
}
int formatParams = 0;
if (style != null) {
formatParams += 1;
}
if (formatter != null) {
formatParams += 1;
}
if (pattern != null) {
formatParams += 1;
}
if (formatParams > 1) {
throw new IllegalArgumentException(
"JodaTimeOutput can optionally receive \"style\" parameter, \"formatter\" parameter, or \"pattern\" parameter, but no more than one of them. Received "
+ formatParams + " of them.");
}
}
boolean beginRender(MarkupWriter writer) {
String formatted = (value == null ? "" : format(value));
if (InternalUtils.isNonBlank(formatted)) {
if (elementName != null) {
writer.element(elementName);
componentResources.renderInformalParameters(writer);
}
writer.write(formatted);
if (elementName != null)
writer.end();
}
return false;
}
private String format(Object value) {
String formatted = "";
if (value != null) {
// If value is an AbstractInstant - includes DateTime and DateMidnight
if (value instanceof AbstractInstant) {
AbstractInstant ai = ((AbstractInstant) value);
if (style != null) {
formatted = DateTimeFormat.forStyle(style).withLocale(locale).print(ai);
}
else if (formatter != null) {
formatted = ai.toString(formatter);
}
else if (pattern != null) {
formatted = DateTimeFormat.forPattern(pattern).print(ai);
}
else {
formatted = value.toString();
}
}
// Else if value is an AbstractPartial - includes LocalDate, LocalTime,
// LocalDateTime, YearMonthDay, and TimeOfDay
else if (value instanceof AbstractPartial) {
AbstractPartial ap = ((AbstractPartial) value);
if (style != null) {
formatted = DateTimeFormat.forStyle(style).withLocale(locale).print(ap);
}
else if (formatter != null) {
formatted = ap.toString(formatter);
}
else if (pattern != null) {
formatted = DateTimeFormat.forPattern(pattern).print(ap);
}
else {
formatted = value.toString();
}
}
// Else value is an unsupported type
else {
throw new IllegalArgumentException(
"JodaTimeOutput received a value of the wrong type. Supported types are subclasses of AbstractInstant and AbstractPartial. Type found is "
+ value.getClass().getName() + ".");
}
}
return formatted;
}
// For testing.
void setup(Object value, String style, DateTimeFormatter formatter, String pattern, String elementName,
ComponentResources componentResources) {
this.value = value;
this.style = style;
this.formatter = formatter;
this.pattern = pattern;
this.elementName = elementName;
this.componentResources = componentResources;
}
}