package org.rendersnake;
import static org.rendersnake.HtmlAttributesFactory.type;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.rendersnake.error.RenderException;
import org.rendersnake.internal.CharactersWriteable;
import org.rendersnake.ext.servlet.HtmlServletCanvas;
/**
* HtmlBufferingCanvas separates the Head and Body section of a HTML page.
* Information for the Head section must be added by HTML element objects.
* Information for the Body section can be added using the HtmlCanvas API. In
* addition, HTML elements that need to be on the bottom of the page must be
* added to the bodyElements set. Finally, Javascript statements that need to be
* executed AFTER loading the page can be added to the onLoadScripts list.
*
* The actual output is written when the _body() is sent.
*
* @author ernest
*/
public class HtmlBufferingCanvas extends HtmlServletCanvas {
// configurable
public static int CANVAS_BODY_BUFFER_LENGTH = 4096;
private Writer output;
private DocType docType = DocType.HTML_4_01_Strict;
private List<String> onLoadScripts;
private HtmlAttributes htmlAttributes;
private HtmlAttributes headAttributes;
private OrderedRenderableSet headElements;
private OrderedRenderableSet bodyElements;
public HtmlBufferingCanvas(HttpServletRequest request, HttpServletResponse response, Writer out) {
super(request, response, out);
this.init();
}
public void init() {
// swap output with new buffer
output = out;
out = new StringWriter(CANVAS_BODY_BUFFER_LENGTH);
}
public HtmlAttributes headAttributes() {
if (headAttributes == null) {
headAttributes = new HtmlAttributes();
}
return headAttributes;
}
public List<String> onLoadScripts() {
if (onLoadScripts == null) {
onLoadScripts = new ArrayList<String>();
}
return onLoadScripts;
}
public OrderedRenderableSet headElements() {
if (headElements == null) {
headElements = new OrderedRenderableSet();
}
return headElements;
}
@Override
public HtmlCanvas _body() throws IOException {
this.renderBodyElements();
this.renderOnLoadScripts();
super._body();
this.copyBufferContentsToOutput();
return this;
}
private void renderBodyElements() throws IOException {
if (bodyElements != null) {
for (Renderable each : bodyElements.getRenderables()) {
this.render(each);
}
}
}
private void renderOnLoadScripts() throws IOException {
if (onLoadScripts != null) {
this.script(type("text/javascript")).cdata();
this.write("function onLoad(){\n", false);
for (String each : onLoadScripts) {
this.write(each, false);
}
this.write("\n};", false);
this._cdata()._script();
}
}
@Override
public HtmlCanvas html() throws IOException {
// no effect
return this;
}
@Override
public HtmlCanvas html(CharactersWriteable attrs) throws IOException {
// no effect
htmlAttributes = (HtmlAttributes)attrs;
return this;
}
@Override
public HtmlCanvas head() throws IOException {
// no effect
return this;
}
@Override
public HtmlCanvas head(CharactersWriteable attrs) throws IOException {
// no effect
headAttributes = (HtmlAttributes)attrs;
return this;
}
@Override
public HtmlCanvas _html() throws IOException {
// no effect
this.checkForClose("</html>");
return this;
}
@Override
public HtmlCanvas _head() throws IOException {
// no effect
this.checkForClose("</head>");
return this;
}
private void checkForClose(String endElement) {
// only check if end tags are on the stack
if (openTagStack.isEmpty())
return;
if (!openTagStack.get(openTagStack.size()-1).equals(endElement))
throw RenderException.unexpectedTag(openTagStack.get(openTagStack.size()-1), endElement);
}
public OrderedRenderableSet bodyElements() {
if (bodyElements == null) {
bodyElements = new OrderedRenderableSet();
}
return bodyElements;
}
/**
* It allows you to send your partially ready HTML response to the browser
* so that the browser can start fetching components while your backend is
* busy with the rest of the HTML page. [source:yahoo]
*
* @return
*/
public HtmlBufferingCanvas flush() throws IOException {
out.flush();
return this;
}
private void copyBufferContentsToOutput() {
try {
HtmlCanvas outCanvas = new HtmlServletCanvas(request, response, output);
outCanvas.render(docType);
outCanvas.html(htmlAttributes);
// add any head elements
outCanvas.head(headAttributes);
if (headElements != null) {
for (Renderable each : headElements.getRenderables()) {
outCanvas.render(each);
}
}
outCanvas._head();
// copy out contents to output
outCanvas.out.write(out.toString());
outCanvas._html();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
@Override
public String toHtml() {
return output.toString();
}
// Tryout
public CanvasMacros<HtmlBufferingCanvas> macros() {
return new CanvasMacros<HtmlBufferingCanvas>(this);
}
}