MarkupWriter

Instead of rendering from a template, a page or component can render a DOM directly with Tapestry's MarkupWriter as described here.
For components that need a lot of control over the sequence of output it can be easier to use than a template.

A really simple example is Tapestry's TextField component, but its superclass AbstractTextField does most of the work.

Two more examples are JumpStart's SourceCodeDisplay (used by SourceCodeTab) and JodaTimeOutput components. Their source is below.

References: DOM, MarkupWriter.

Home


<!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 &nbsp; 
     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;
    }
}