Browser-Side Templating
From Ajax Patterns
| Revision as of 19:53, 17 August 2007 83.236.145.99 (Talk | contribs) The Problem of Embedded Code in Templates ← Previous diff |
Revision as of 19:57, 17 August 2007 83.236.145.99 (Talk | contribs) The Problem of Embedded Code in Templates Next diff → |
||
| Line 369: | Line 369: | ||
| == The Problem of Embedded Code in Templates == | == The Problem of Embedded Code in Templates == | ||
| - | You may have noticed that, throughout the illustrations shown above, a considerable amount of JavaScript source code is embedded, verbatim, ''inside'' the templates. The looping, and other similar behaviors, is literally being accomplished by executable code that is contained within the template. One solution to this is the twoBirds library that has 100% distinct separation between code and design ( HTML, CSS, JS are loaded separately by coding design). | + | You may have noticed that, throughout the illustrations shown above, a considerable amount of JavaScript source code is embedded, verbatim, ''inside'' the templates. The looping, and other similar behaviors, is literally being accomplished by executable code that is contained within the template. One solution to this is the [http://blog.phpbuero.de/?page=10 twoBirds] library that has 100% distinct separation between code and design ( HTML, CSS, JS are loaded separately by coding design) [http://system.dlv.phpb002.de twoBirds prototype]. |
| While this technique is inarguably ''powerful,'' it also creates a ''functional dependency'' between the code in the main JavaScript application and the code that is scattered throughout the (potentially hundreds of...) individual templates. It could easily be argued that this does not maintain "separation of presentation from logic." It ''could'' certainly become a maintenance issue as your application continues its lifetime of service. (On the other hand, it could work splendidly and present no practical problems at all!) | While this technique is inarguably ''powerful,'' it also creates a ''functional dependency'' between the code in the main JavaScript application and the code that is scattered throughout the (potentially hundreds of...) individual templates. It could easily be argued that this does not maintain "separation of presentation from logic." It ''could'' certainly become a maintenance issue as your application continues its lifetime of service. (On the other hand, it could work splendidly and present no practical problems at all!) | ||
Revision as of 19:57, 17 August 2007
There's an archived version of this pattern available, taken from the Ajax Pattern book draft, showing roughly how it appeared before the page became publicly editable.
Evidence: 1/3
Tags: Pages Presentation Render Template Transform View
Contents |
|
In A Blink
Diagram A little template code
Technical Story
Devi has just received a request to add more details to the user profile. The original version takes an XML document about the user and renders it all using a jungle of Javascript code, a function that accumulates a big HTML string amid if-then conditions and loops. Devi decides to refactor the Javascript to use a Browser-Side Template, isolating the HTML generation. As a result, introducing the new content becomes trivial.
Problem
How can you separate presentation from logic?
Forces
- Generating HTML in the browser is a good thing if you wish to isolate all presentation logic in the one tier.
- Often, the nature of the HTML is dynamic, depending on browser state and incoming responses from the server.
- To render context-specific information within the browser, you need to rely on a dynamic mechanism. It's not feasible to just set an element's innerHTML property to point to a static HTML page.
- Code gets complex and error-prone when you mix HTML generation with application logic. Javascript is not well-suited to programmatically building up HTML strings.
Solution
Produce browser-side templates and call on a suitable browser-side framework to render them as HTML. The template contains standard HTML and allows context variables to be substituted in at rendering time, which gives it more flexibility than a static HTML page. In addition, you can also intersperse Javascript code. For instance, you could generate an HTML table by running a loop, one iteration for each row.
The templating idea has been used for a long time on the web, with tools like Perl's HTML::Mason, PHP, Active Server Pages (ASPs), Java Server Pages (JSPs). All of them are syntactic sugar of the "killer app" variety. That is, they technically add no new functionality, but they make life a whole lot easier for web developers. In the Java world, the standard alternative to JSPs is servlets. Here's how you'd write a message in a servlet:
package com.example.hello;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>Hi Everyboddeee!</title></head>");
out.println("<body>");
out.println("<h1>Howdy</h1>");
out.println(" Your name is \"" + request.getAttribute("name") + "\".");
out.println("</body></html>");
out.close();
}
}
Not pretty. There are printing commands all over the place, escaping special characters, quote marks, error-handling. What happened to the simplicity of HTML? Likewise, the Javascript logic is obscured by the heavy presence of HTML generation. In short, code like this is undesirable because it mixes presentation with logic.
The following JSP, using the standard JSTL library, achieves the same thing in a more lucid, maintainable, style.
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<html>
<head><title>Hi Everyboddeee!</title></head>
<body>
You're name is <c:out value="$name" />.
</body>
</html>
With many Ajax applications, it's deja vu. Often, the browser receives an XMLHttpRequest Call response from a Semantic Service, in the form of a complex data structure - XML Message, JSON Message, or customised format. At this point, it's over to the browser to render it as HTML, so server-side templates are not an option. A common approach is to manually traverse the structure programmatically and create some HTML to be injected via some element's innerHTML property. The old servlet-style "bucket of print statements" recurs: Javascript is used to build up a big chunk of HTML, as messy as it was on the server-side.
The solution here recapitulates in Javascript what all those server-server side templating frameworks offer. A templating framework, while not immensely difficult to implement, is probably not the sort of thing you'd want to write yourself. This pattern, then, is only made possibly by the fact that frameworks already exist. The frameworks are discussed in the examples below, but here is a quick example of a template. The following Javascript template - in Ajax Pages format, will run a loop:
<ul>
<%
for (i=0; i<context.names.length; i++) {
var name = conext.names[i];
%>
<li> <%= name %> </li>
<%
}
%>
</ul>
The Javascript template is in similar format to the JSP above, and follows standard conventions of templates. That is:
- Any code is contained inside <% and %> tags.
- Any expression to be substituted in the HTML is contained inside <%= and %> tags.
To apply the template, you create a processor and pass it a context.
var ajp = new AjaxPages();
ajp.load("template.ajp");
var processor = ajp.getProcessor();
element.innerHTML = processor(context);
Decisions
Will the template include any code? How much?
Code in templates is somewhat frowned upon because it defeats the purpose of templates as a place to isolate the presentation. Certainly, more complex calculation are best performed in plain Javascript or on the server-side, but there are occasions when template code can be useful. Examples include:
- Loops: A collection is passed in to the template. The template outputs the entire collection by looping over the collection. It's much better for the template to perform the looping, rather than the calling Javascript, because there is usually some HTML before and after the loop. If the Javascript was to output that HTML, there would be too much coupling between the Javascript and the template.
- Conditionals: if-then conditions and switch statements are sometimes better performed by the template too. However, if the body of each branch is long, you might prefer to include a different template for each.
How to prepare the template's context?
In the case of Ajax Pages, you're allowed to pass in a context variable at rendering time. That's usually a good thing, as it provides a good alternative to the template using global variables. The most obvious thing to do is pass some existing objects to the template, but sometimes the calling code can prepare some extra information too. The aim is to perform as much processing in the Javascript code, so as to avoid any complex logic being performed within the template. For example, sometimes the template contains a simple if-then condition like this:
<% if (context.credits == 0) { %>
You need more credits!
<% } else { %>
You have <%=context.credits%> credits!
<% } %>
The logic isn't especially complex, but scaling up with this approach can be problematic. As an alternative, let the Javascript do some processing:
var creditsMessage = credits ? "You need more credits!" : "You have " + credits + " credits!";
And the template then gives you a better idea of the outputted HTML:
<%= context.creditsMessage %>
Real-World Examples
Ajax Pages Framework
Gustavo Ribeiro Amigo's Ajax Pages is an open-source templating framework. Basic usage is illustrated in the Solution above, and it's also included in the Refactoring Illustration below. Also, there's a basic blog demo on the project homepage.
Authenteo
Authenteo is an integrated JavaScript framework and content management system. Authenteo uses browser-side templating to dynamically generate HTML from the client accessible data domain model. The Authenteo templating engine allows you to create templates that generate HTML from the persisted objects of the domain model. The following is an example of a template:
<h2 logic="text" field="content.#name"></h2>
<div class="contents" logic="display" field="content.#body"></div>
<div logic="each" field="content.#items" class="listItem"> <!-- iterate through the items -->
<a logic="hyperlink">
<span logic="text" field="content.#name"></span>
</a>
<div logic="display" field="content.#description"></div>
</div>
The logic attribute defines the action, and the field attribute defines what value to grab. The # symbol denotes persisted fields should be accessed from the objects.
Javascript Templates Framework
JavaScript Templates (JST) is an open-source templating framework from TrimPath. JST offers a richer set of functionality at this time, including:
- Expression modifiers. The "capitalize" modifier, for instance, occurs in expressions like ${name|capitalize}.
- A special syntax for loops and conditions.
- Macros.
These features may be useful if you're relying heavily on templating, but there's also an argument that Javascript alone is sufficient, so there's no need to add the burden of learning a second set of similar syntax.
Here's how a JST template looks:
{for p in products}
<tr>
<td>${p.name|capitalize}</td><td>${p.desc}</td>
<td>$${p.price}</td>
<td>${p.quantity} : ${p.alert|default:""|capitalize}</td>
</tr>
{/for}
With the string in a DOM element on the web page, you can apply the template with a one-liner:
element.innerHTML = TrimPath.processDOMTemplate("templateElement", data);
Jemplate
http://search.cpan.org/dist/Jemplate/
Allows you to use Perl's Template Toolkit syntax in javascript.
var data = Ajax.get('url/data.json');
var elem = document.getElementById('some-div');
elem.innerHTML = Jemplate.process('my-template.html', data);
prototype.js
The ever-popular "prototype.js" framework now contains some templating capabilities: http://www.prototypejs.org/api/template
django (a Python framework)
It's important to bear in mind that you are not limited to JavaScript in your search for good ideas! Django, an open-source web framework implemented in the Python programming language (http://www.djangoproject.com), implements some very innovative templating ideas, which can be easily adapted to the world of JavaScript. (And it may well have already been done.)
For example, in Django, a template is effectively compiled, into an internal data-structure, which is then traversed to generate the output. It is, in other words, a process that is considerably more sophisticated and flexible than simple pattern-matching.
Django also provides the concept of a "template tag," which causes Python code to be executed but which does not, itself, consist of Python code. This allows the template to specify behaviors that can only be accomplished by means of executing a programming-language subroutine, without embedding "live" source-code into the template (as the AJP-based illustrations in this article do).
.. and more!
In short... the "real-world examples" listed here are only a representative sample of the myriad possibilities that exist, both in the world of JavaScript and otherwise. Client-side templating is such a generally-useful concept that a great many implementations of it have been built, in languages of all kinds.
These implementations are, categorically speaking, generally very sophisticated, yet they can be obtained as components which you can simply "drop in" to your projects.
Before embarking on your project, therefore, spend some time researching the various possibilities that are available to you "today." The illustrations in this article, or in any article, are necessarily just that: illustrations.
Refactoring Illustration
Templating Drilldown Demo
Initial Version
In the Drilldown Demo, the Drilldown menu itself is a bunch of HTML generated within Javascript. The script picks up an XML file containing a specification for the current level, and traversed it with standard Javascript and XML access. See Drilldown for details, but as a quick summary, the XML file looks like:
<category name="Overviews" parent="All Categories">
<items>
<link>
<url>http://en.wikipedia.org/wiki/AJAX</url>
<name>Wikipedia Article</name>
</link>
<link>
<url>http://www.adaptivepath.com/publications/essays/archives/000385.php</url>
<name>First Ajax</name>
</link>
<category name="Podcast Overviews" parent="Overviews" />
</items>
</category>
And the manual HTML generation looks like this:
function onDrilldownResponse(xml) {
var category = xml.getElementsByTagName("category")[0];
var html="";
var categoryName = category.getAttribute("name");
html+="<div id='categoryName'>" + categoryName + "</div>";
....
*** Much more appending to html ***
...
$("drilldown").innerHTML = html;
}
Refactored To Render from a Template
For all the reasons described earlier, the HTML generation here is a messy approach. The present example refactors the application to use a template for the drilldown menu. Instead of the lengthy HTML generation in onDrilldownResponse(), the method becomes a simple application of an AjaxPages template.
function onDrilldownResponse(xml) {
var ajp = new AjaxPages();
ajp.load("category.ajp");
var processor = ajp.getProcessor();
$("drilldown").innerHTML = processor( {xml: xml} );
}
So we're passing the entire xml string into the template, and the template's converting it to HTML. Let's see how that template ("category.ajp") looks. First, it performs a little pre-processing on the XML string.
<%
var category = context.xml.getElementsByTagName("category")[0];
var categoryName = category.getAttribute("name");
var parent = category.getAttribute("parent");
var items = category.getElementsByTagName("items")[0].childNodes;
%>
Then, it outputs the HTML. Note that looping and conditions are implemented with regular Javascript, a reasonable time to include Javascript in a template.
<div id='categoryName'><%=categoryName%></div>
<%
if (parent && parent.length > 0) {
%>
<div id='parent' onclick="retrieveCategory('<%=parent%>')">Back to <br/>
<%=parent%></div>
<% } %>
<%
for (i=0; i<items.length; i++) {
var item = items[i];
if (item.nodeName=="link") {
var name = item.getElementsByTagName("name")[0].firstChild.nodeValue;
var url = item.getElementsByTagName("url")[0].firstChild.nodeValue;
%>
<div class="link"><a href="<%=url%>"><%= name %></a></div>
<%
} else if (item.nodeName=="category") {
var name = item.getAttribute("name");
%>
<div class='category'
onclick="retrieveCategory('<%=name%>')"><%=name%></div>
<%
}
}
%>
We now have exactly the same external behaviour as before, but the templating approach has helped separate presentation from logic.
Refactored to Improve Template Context
In the refactored version, the context consisted of only one thing: the entire XML string. As mentioned in the Decisions above, it's sometimes worthwhile doing preparing work before passing the context over to the template. In this case, the template begins by extracting out a few convenience variables from the XML. That's arguably a good approach, because it means the XML format is coupled only to the template, and not the Javascript. However, there's also an argument that the Javascript should simplify the template's work by passing in a richer context. This further refactoring explores that avenue.
The change is quite small. The xml callback function now passes in a more detailed context:
function onDrilldownResponse(xml) {
var ajp = new AjaxPages();
ajp.load("category.ajp");
var processor = ajp.getProcessor();
var category = xml.getElementsByTagName("category")[0];
$("drilldown").innerHTML = processor({
categoryName: category.getAttribute("name"),
parent: category.getAttribute("parent"),
items: category.getElementsByTagName("items")[0].childNodes
});
}
The template no longer includes the first few lines of creating convenience variables. References to those variables have been changed throughout to refer to context:
<%
if (context.parent && context.parent.length > 0) {
%>
<div id='parent' onclick="retrieveCategory('<%=context.parent%>')">Back to
<br/>
<%=context.parent%></div>
...
Alternatives (and Issues)
Browser-Side XSLT
In many cases, the browser receives an XML response. Where the browser is converting XML to HTML, Browser-Side XSLT is a very direct alternative to this Browser-Side Templating. Templating simplifies presentation, but still requires parsing of the XML, which can be cumbersome. XSLT simplifies the parsing as well, being designed specifically for the purpose of transforming XML.
The Problem of Embedded Code in Templates
You may have noticed that, throughout the illustrations shown above, a considerable amount of JavaScript source code is embedded, verbatim, inside the templates. The looping, and other similar behaviors, is literally being accomplished by executable code that is contained within the template. One solution to this is the twoBirds library that has 100% distinct separation between code and design ( HTML, CSS, JS are loaded separately by coding design) twoBirds prototype.
While this technique is inarguably powerful, it also creates a functional dependency between the code in the main JavaScript application and the code that is scattered throughout the (potentially hundreds of...) individual templates. It could easily be argued that this does not maintain "separation of presentation from logic." It could certainly become a maintenance issue as your application continues its lifetime of service. (On the other hand, it could work splendidly and present no practical problems at all!)
Many templating systems exist, for JavaScript and for other languages, and they all have different ways to implement the features and behaviors that designers have found useful. Every one of these implementations constitutes some combination of trade-offs. Consider your options prudently.
The "Weight" That You Place on the Client-Side
A full-featured templating system can place a considerable burden upon the browser, and it may expose limitations or implementation-differences from one browser type to another, to which a different approach might not be susceptible. It is important to carefully consider the range of browser-types that you must support, and the potential age of those browsers. The critical importance of testing also cannot be over-emphasized.
Related Patterns
Semantic Response
The importance of templating mostly arises from the need to deal with data structures passed back from Semantic Responses.
XML Message
Templating is well-suited to transforming an XML Message into HTML.
JSON Message
Templating is well-suied to transforming an object from a JSON Message into HTML.
Visual Metaphor
Think of a phsical template - a document with most content already present, with a few blanks to populate with current values.
Time your website with
WebWait - from the creator of AjaxPatterns.org
