Page Pattern
A common problem in web applications is how to model the recurring part on many or all pages, like menu or header and footer, in a fashion that avoids code duplication. This is often solved by additional frameworks like Tiles or with additional constructs.
But in Snippetory no additional features are needed. As Snippetory scales well from simple formatting helpers up to modeling the structure of a page there is no need for another gimmick. As the Snippetory Template Engine uses java (or Scala, Groovy or whatever) as logic engine and doesn't mix template and logic code we have all OO features of Java fully available when writing this logic.
I think here we take inheritance as a convenient way to model such frame structure. While Tiles has to implement its own inheritance, I'll use java inheritance for this example. We don't need a huge additional frame-work with its own language to express inheritance. Java can do this, can't it?
To get into this we need a base class to represent our structure. Let's call it Page. For example the Page could have methods to render a head, left navigation, top navigation and the content of the actual page. The latter method will be abstract, as there is no typical content.
In tiles it was common to implement any of those parts with a single JSP, as JSP is unable to express structure and the view template can't contain any logic. In Snippetory this separation works completely different. As the template is free of logic anyway, we can put the entire view template into a single file and concentrate on separating the logic.
Another problem in the RequestDispatcher pattern was the weakly typed parameter transfer between logic in the Dispatcher (aka Action aka Controller) the JSP. This is solved by the Page Pattern too. It provides an interface that is completely controlled by the application programmer. Just a Java class.
Abstract Page
public abstract class Page {
protected final ResourceBundle msg;
protected final Locale locale;
protected final Category cat;
public Page(Locale l, Category cat) {
this.msg = ResourceBundle.getBundle("messages", l);
this.locale = l;
this.cat = cat;
}
public abstract void renderContent(Template page);
protected void renderNav(Template page) {
page.append("cart_label", msg.getString("cart.show"));
page.append("categories_label", msg.getString("cat.list.heading"));
for (Category childCat : cat.getChildren()) {
page.get("category")
.set("name", childCat.getName())
.set("id", childCat.getId())
.render();
}
}
public void render(Writer out) throws IOException {
Template page = Repo
.readResource("org/jproggy/examples/minishop/Frame.html")
.locale(locale).encoding(Encodings.html).parse();
page.append("title", msg.getString("app.basics.name"));
page.append("imprint", msg.getString("app.basics.imprint"));
page.append("contact", msg.getString("app.basics.contact"));
renderLeftNav(page);
renderContent(page);
page.render(out);
}
}
So far our example is not oversimplified. It's localized and a part is rendered based on a model. However, it could be significantly smaller if we would put if we would use the MessageFormat. But this is (even though highly recommended) not part of the Snippetory Template Engine. Now, let's look at the template:
<html>
<head>
<title>{v:title}</title>
</head>
<body>
<center>
<table width="800" border="3" cellpadding="5" cellspacing="1" >
<tr>
<td align="center"></td>
<td><a href="contact.html">{v:contact}</a></td>
<td><a href="imprint.html">{v:imprint}</a></td>
</tr>
<tr>
<td>
<div><a href="cart.html">{v:cart_label}</a></div>
<h2>{categories_label}</h2>
<div style="padding-left:2mm">
<t:category>
<div><a href="category.html?cat_id={v:id enc='url'}">{v:name}</a></div>
</t:category>
</div>
</td>
<td colspan="2" width="100%">{v:content}</td>
</tr>
</table>
</center>
</body>
</html>
Really simple, but not too stylish ;-)
Concrete Page
Let's look how to implement the cart page. That's really straight forward, too.
Again, we need a class. A concrete one this time:
public class CartPage extends Page {
private final Cart cart;
public CartPage(Locale locale, Category cat, Cart cart) {
super(locale, cat);
this.cart = cart;
}
public void renderContent(Template page) {
if (cart.isEmpty()) {
page.set("content", msg.getString("cart.empty");
return;
}
Template template = Repo
.readResource("org/jproggy/examples/minishop/cart.html")
.locale(locale).encoding(Encodings.html).parse();
renderCart(template);
page.append("content", template);
}
public void renderCart(Template template) {
template.set("cart_desc", msg.getString("cart.cartDesc"));
Snippetory cart = template.get("cart");
cart.set("name", msg.getString("product.name"))
.set("quantity", msg.getString("cart.quantity"))
.set("price", msg.getString("cart.price"))
.set("sum", msg.getString("cart.sum"));
for (CartEntry entry : cart.getEntries()) {
cart.get("cart_entries")
.set("name", entry.getProduct().getName())
.set("product_id", entry.getProduct().getID())
.set("quantity", entry.getQuantity())
.set("price", entry.getPrice())
.set("sum", entry.getSum())
.set("currency", entry.getCurrencySymbol())
.render();
}
}
}
And the cart template:
<p>{v:cart_desc}</p>
<table>
<tr>
<th>{v:name}</th>
<th>{v:quantity}</th>
<th>{v:price}</th>
<th>{v:sum}</th>
<th></th>
</tr>
<t:cart_entries>
<tr>
<td><a href="product.html?id={v:product_id enc=url}">{v:name}</a></td>
<td>{v:quantity}</td>
<td>{v:price}</td>
<td>{v:sum}</td>
<td>{v:currency}</td>
</tr>
</t:cart_entries>
<tr>
<td colspan="3" align="right">{v:total-label}</td>
<td>{v:total}</td>
<td>{v:currency}</td>
</tr>
</table>
I think it's clear how to bind this into a web application, and, may be more important, how to handle additional pages.
Have Fun,