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