Developer's Programming Guide 1.5

How to produce HTML

  • HtmlCanvas, an object to write HTML elements on a Writer
  • HtmlAttributes, an object to write HTML attributes of an HTML element

How to design HTML components

How to test HTML components

How to integrate with Web frameworks

Available renderSnake tool classes

  • Inspector is a decoration component that renders additional details for a component
  • DebugHtmlCanvas is a special HtmlCanvas that renders each component witht the InspectorWrapper
  • LoggingPageContext is used by DebugHtmlCanvas and traces the modification of PageContext values
  • PrettyWriter is used for component testing and produces a better human-readable HTML format

How to use the HtmlCanvas

HtmlCanvas provides the interface to write HTML elements as part of response to a Web request. Its interface consists of methods that allow you build a HTML page. For example, to write a hyperlink you write:

html.a(href("otherpage.html")).content("Other Page")

<a href="otherpage.html">Other Page</a>

Methods that start with "_" will write the closing tag. HtmlCanvas will verify that you are closing the expected tag. Each method will return the HtmlCanvas object. This allows for a fluent programming style (also known as the Builder coding pattern). Note that format of the source in this example is chosen to be similar to a well-formed HTML page. This is of course not required but is may help to improve readability.

If your component class implements the Renderable interface then it will have the renderOn method that has a HtmlCanvas as its argument. A servlet is responsible for creating a HtmlCanvas and calling the renderOn method of your component. The renderSnake library includes such a servlet (see RenderSnakeServlet) but you can use any other framework that can dispatch incoming requests to components (see JSP, Spring-MVC or Google-Guice).

top

How to write attributes

HtmlAttributes provides the interface to write the HTML attributes for elements. Its interface consists of methods that allow you define the attributes. For example, to write a div with an id and class you write:

html.div(id("me").class_("hidden")).content("Invisible text");

<div id="me" class="hidden">Invisible text</a>

In order to make the attribute methods visible to your component class, you must include a static import:

import static org.rendersnake.HtmlAttributesFactory.*

Each method will return the HtmlAttributes object. This allows for a fluent programming style (also known as the Builder coding pattern). Note that the HtmlAttributes object does not check for duplicate attributes. If the attribute name is acutally a Java reserved word, its corresponding method will have the underscore "_" suffix.

The library also includes the JQueryAttributesFactory and JQueryAttributes classes. These classes has methods for common JQuery(Mobile) attributes.

import static org.rendersnake.ext.jquery.JQueryAttributesFactory.*

If you cannot find the method in either class HtmlAttributes or JQueryAttributes then you can use the generic method.

new HtmlAttributes("key","value").add("another-key","another-value");
top

How to create components

To create a component class for HTML rendering, it must implement the Renderable interface. This requires you to implement the renderOn method that has a HtmlCanvas as its argument. You can use the template below to create your component classes.

import java.io.IOException;
import org.rendersnake.HtmlCanvas;
import org.rendersnake.Renderable;
import static org.rendersnake.HtmlAttributesFactory.*;

public class MyComponent implements Renderable {

    public void renderOn(HtmlCanvas html) throws IOException {
    
    	html.h1().content("My Component");    
    }
}

Browse the components for this site


top

How to decorate components

If your HTML design contains sections that have a similar layout or styling then you can decide to create a decorating component to capture that common look. The typical example is a website having a common layout across the pages containing a header, menubar, searchbar, main and footer. The page itself and each of these sections may be implemented by applying the Decorator or Wrapper design pattern.

To create a Wrapper component you must extend RenderableWrapper and implement the renderOn method. In this method you write the common elements that should appear before the decorated component is rendered. After calling render(this.component) you write the common element that should appear after the component is rendered.

public class SingleTableCellWrapper extends RenderableWrapper {
   
    public SingleTableCellWrapper(Renderable component){
        super(component);
    }
    
    public void renderOn(HtmlCanvas html) throws IOException {
        
        html
          .table()
            .tr()
              .td().render(this.component)._td()
            ._tr()
          ._table();        
    }
}

Browse the layout wrapper for this site


top

How to use the Page context

A PageContext is responsible for holding model data that is used by components to perform their rendering. Each HtmlCanvas has one PageContext. It has an API to set and get values based on a String key.

canvas.getPageContext()
    .withString("title", "Welcome")
    .withInteger("number", 42);
canvas.getPageContext().getInteger("number");
canvas.getPageContext().getString("title", "No Title"); // default value if absent

Values stored in a PageContext are only visible to the component that stored them and all nested components that are going to be rendered. This allows for a safer way to pass on parameters from top to bottom in the component rendering hierarchy. Components with a deeper nesting level can only read values from higher levels ; never change them.

public class AccountsListUI implements Renderable {

	public void renderOn(HtmlCanvas html) throws IOException {
		
		List<Account> accounts = (List<Account>)html.getPageContext().getObject("accounts");
		for (Account each : accounts) {
			this.renderAccountOn(each,html);
		}
	}
}

A PageContext is just a variable container with a Map interface. There is no way to ensure that a variable exists or is of the correct type. A component may need to perform null checks and, for non-primitive types, perform a Class casting.

Each HtmlCanvas instance has its own PageContext instance that is a container of key->value attributes. Upon its creation, the PageContext will be initialized with request related values which are stored in special variables. These values are accessible using the helper class RequestUtils.

Request Parameters

ContextMap<?> parameterMap = RequestUtils.getParameters(html);
String parameter = RequestUtils.getParameter(html,"username");

Session Attributes

ContextMap<?> sessionMap = RequestUtils.getSession(html);

Headers

ContextMap<?> headerMap = RequestUtils.getHeaders(html);
String header = RequestUtils.getHeadersValue(html,"Host");

Cookies

ContextMap<?> cookieMap = RequestUtils.getCookies(html);

Another way to access all the details of the HttpServletRequest or HttpServletResponse is to use the ServletUtils class. This obviously only works if you are using Jsp, Spring MVC or Guice in a J2E Servlet container (Tomcat,JBoss,Jetty,...).

HttpServletRequest request = ServletUtils.requestFor(html);
HttpServletResponse response = ServletUtils.responseFor(html);
HttpSession session = ServletUtils.sessionFor(html);
top

How to include static content

Sometimes, it is more convenient to store and maintain larger fixed texts in separate files. To include its contents in e.g. a paragraph, you can use the StringResource component. On default, the text content is cached in memory (static field of the class).

Either use it directly:
html.write(StringResource.get("/content/cms.html"),NO_ESCAPE);
html.write(StringResource.get("/js/myfunctions.js"),NO_ESCAPE);
or use as a Renderable component:
html.render(new StringResource("/content/cms.html",NO_ESCAPE));
html.render(new StringResource("/content/plain.txt",NO_ESCAPE));
NO_ESCAPE is a boolean constant of HtmlCanvas with value "false".

The contents from the file (such as cms.html from the example) is cached by the StringResource class and is typically stored in /src/main/resources top

Test components with JUnit

You can create a HtmlCanvas without a HttpRequest or HttpResponse. The rendering of a component or page will happen on an internal PrettyWriter that procudes a more readable, indented HTML format. This enables you to write unit tests for each component. Validation of the output is partly done by HtmlCanvas because it verifies that all opened tags are closed in the right order. If you need more validation (W3C) then you can use jTidy (see below).

public void testPersonalPage() throws IOException {
    HtmlCanvas html = new HtmlCanvas(new PrettyWriter());
    
    // prepare your component and render it
    // the canvas will preform a runtime structure validation (did you close all the tags?)    
    html.render(new ProductDetails());
    
    // simply dump the result
	System.out.println(html.toHtml());        
}
top

Validate a Page using jTidy

Tidy is an external library that can parse HTML content and perform full validation. You can use Tidy to validate Page components. RenderSnake does not provide any additional tooling to support this. For more information how to use jTidy goto jTidy.

public void testPageRender() throws IOException {
    HtmlCanvas html = new HtmlCanvas();
    html.render(new PersonalPage());
    
    Tidy tidy = new Tidy();
    tidy.setMessageListener(new TidyMessageCheck());
    tidy.setXHTML(true); 
    tidy.setDocType("loose");
    tidy.parse(new ByteArrayInputStream(html.toHtml().getBytes()), System.out);        
}

On default, the TidyMessageCheck will throw an Assertion failed if it encounters an error. By setting its field collectMessages to true, your test can collect all errors and warnings to inspect later in the testcase.

Use the following Maven dependency it you want Tidy to validate your components.

<!-- for HTML validation -->
<dependency>
	<groupId>net.sf.jtidy</groupId>
	<artifactId>jtidy</artifactId>
	<version>r938</version>
	<scope>test</scope>
</dependency>

Source for the class TidyMessageCheck can be downloaded here. top

Test the rendering of an individual component

When developing components it may be convenient if you could render only the component you are working on at the moment. One solution could be to create a PreviewPage class for each component or one that renders all components at once, see the example.

When using Spring-MVC (see below) you can setup a Controller for this purpose. The testComponent implementation creates your component and wraps it using your decoration (here HomeLayoutWrapper) for rendering. In the request mapping annotation you tell the controller to accept urls such as /test/org.rendersnake.site.components.Logo

@RequestMapping("/test/{componentClassName}")
@ResponseBody
public void testComponent(HtmlCanvas html, @PathVariable("componentClassName") String componentClassName) throws IOException {
    try {
        Class<?> uiClass = Class.forName(componentClassName);
        Renderable uiComponent = (Renderable)uiClass.newInstance();
        HomeLayoutWrapper wrapper = new HomeLayoutWrapper(uiComponent);                    
        html.render(wrapper);
	} catch (Exception ex) {
        html.render(RenderException.caught(ex));
    }
}
top

How to use renderSnake from JSP

You can combine JSP technology and renderSnake to implement the MVC pattern. In this approach the JSP (servlet) will act as the controller to which a request is dispatched. The contents of a JSP file will be a scriplet where you can fetch domain data to prepare the rendering. Next, you create a new HtmlCanvas to render your component(s) using the domain data.

<%@page import="org.rendersnake.ext.servlet.HtmlServletCanvas"%>
<%@page import="com.company.html.AccountListPage"%>

<% 
// fetch accounts
...
new HtmlServletCanvas(request,response,out).render(new AccountListPage(accounts));
%>
top

How to use renderSnake from Spring-MVC

You can choose from 2 implementation strategies to render a HTML page using renderSnake from a Spring Annotated Controller. The first one uses a ViewResolver that fits into the chain of resolvers to map a view name to the component that does the actual rendering. The second does not use a ViewResolver and allows you to start the rendering of a component directly from the Controller.

A working example application is available for download

Solution 1: Using a ViewResolver

In your Spring configuration (e.g. servlet-config.xml) include


<bean class="org.rendersnake.ext.spring.RenderableViewResolver" />

In ExampleController define

@RequestMapping("/example.html")
public String example(Model model) {
        
        model.addAttribute("today", new Date());                

        return "examplePage";
}

Then define ExamplePage as

@Component("examplePage")
public class ExamplePage implements Renderable {

        @Override
        public void renderOn(HtmlCanvas html) throws IOException {
                
                html.render(new SiteLayoutWrapper(new ExampleContents())));
        }
}

Solution 2: Direct from the Controller

In your Spring configuration (e.g. servlet-config.xml)


<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
        <property name="customArgumentResolver">
                <bean class="org.rendersnake.ext.spring.HtmlCanvasArgumentResolver" />
        </property>
</bean>

In ExampleController

@RequestMapping("/example.html")
@ResponseBody
public void renderExample(HtmlCanvas html) throws IOException {
        
        // get your domain objects using services available in the application context
        // and stick them into the page context
        html.getPageContext().withObject("today", new Date());
        
        html.render(new SiteLayoutWrapper(new ExampleContents())));
}

If you want to use Spring MVC and renderSnake then you could start with the example Quickstart Spring Example.

top

How to use renderSnake using Google Guice

First, you have to know how to use Guice in a servlet container

Define the application WebModule ; e.g. class ZeshoekWebModule

public class ZeshoekWebModule extends AbstractModule {

        @Override
        protected void configure() {
                install(new ServletModule() {
                          @Override
                          protected void configureServlets() {
                             serve("/*").with(org.rendersnake.ext.guice.GuiceComponentServlet.class);
                             bind(HomeAction.class);
                          }
                        });
        }
}

Define an Action component ; e.g. class HomeAction

@Singleton @Named("/")
public class HomeAction implements Renderable {

        @Override
        public void renderOn(HtmlCanvas html) throws IOException {
                html.render(new HomePage());
        }
}

Define a Page component ; e.g. class HomePage

public class HomePage implements Renderable {

        @Override
        public void renderOn(HtmlCanvas html) throws IOException {
                html.html().body().h1().content("renderSnake rocks!")._body()._html();
        }
}

Define a Login component ; e.g. class LoginForm

@Singleton @Named("/login")
public class LoginAction implements PostHandler {

    @Inject
    UserService userService;
    
	public void handlePost(HttpServletRequest request,
			HttpServletResponse response) {
        
        String enteredUser = request.getParameter("name");
        String enteredPassword = request.getParameter("password");
        
        if (this.userService.authenticate(enteredUser, enteredPassword)) {
            request.getSession().setAttribute("authenticated", true);
            request.getSession().setAttribute("user", enteredUser);
        }
        
        response.sendRedirect("./");
    }
}

If you want to use Guice and renderSnake then you could start with the example Quickstart Guice Example.

top