I'd like to draw attention to a pleasant way to implement the view
in MVC web apps that
is emerging in suitably reflective and dynamic frameworks.
It doesn't
seem to have a name yet - I'd suggest "slim builder" and/or
"untangled template view".
The technique combines the strengths of two traditionally
distinct approaches - builders, and server page templates - and
thereby solves some of their usual problems. I'll illustrate it through an
example drawn from Rails, although you don't need to know the details
of this framework or the underlying Ruby language to get the point.
If you do know Rails and its canon then the
example will be very familiar: it's the bookstore index page from the
"depot" tutorial in
Agile
Web Development with Rails,
as of the end of section B.
(As context for the rest of you,
here's how the page renders
once its style is wired up. It's simple, but will suffice for our purposes. The approaches described
all extend smoothly to more complicated layouts, Ajax updates, and so forth.)
Templates - mark-up and code entangled in a server page
The traditional template view works by embedding executable code fragments into
concrete mark-up. The default Rails view system uses rhtml
templates processed by ERb.
Here's how we'd implement the store page view. In the tutorial - as in real Rails apps
- the page gets split into different modular parts (layouts, partials, etc.) For the
sake of this example I'll inline everything into one template:
<?xhtml version="1.0" encoding="UTF-8"?>
<html>
<head>
<title>Pragprog Books Online Store</title>
<%= stylesheet_link_tag "depot", :media => "all" %>
</head>
<body id="store">
<div id="banner">
<img src="/images/logo.png" alt="Logo"/>
<%= @page_title || "Pragmatic Bookshelf" %>
</div>
<div id="columns">
<div id="side">
<a href="http://www....">Home</a><br />
<a href="http://www..../faq">Questions</a><br />
<a href="http://www..../news">News</a><br />
<a href="http://www..../contact">Contact</a><br />
</div>
<div id="main">
<h1>Your Pragmatic Store</h1>
<% for product in @products -%>
<div class="entry">
<img src="<%= product.image_url %>" alt="Cover" />
<h3><%= h(product.title) %></h3>
<%= product.description %>
<span class="price"><%= number_to_currency(product.price) %></span>
<%= button_to "Add to Cart", :action => :add_to_cart, :id => product %>
</div>
<% end %>
</div>
</div>
</body>
</html>
To my eyes, this kind of JSP-inspired template is
rather ugly - it is the dirtiest plate in the default Rails stack. Angle-bracketed
web mark-up is not pretty to start with, and mixing code into it under the protection of
even more punctuation does not help matters, forming uglies such as <%,
<%=, and -%>. Further, the resulting template is neither
fish nor flesh; it isn't validating mark-up, nor is it directly compilable code. Two worlds
have been messily ent-angled.
To be fair, if you need to directly mix the two, then having the mark-up around
the code is probably a better choice than the converse. Templates rightly ousted
sequences of print statements that cobbled together strings of explicit mark-up.
Traditional entangled server page templates have a sufficient similarity to straight HTML that
tag-aware tools will do a decent job of pretty-printing and coloring them. Similarly tag-aware
but non-programming web folk can work with the template directly, especially when helper
objects have been used to minimize the size of the scary code fragments and tuck the details
safely away.
Still, it's messy. Please do better.
Builders - code that casts a mark-up shadow
When I first ran across the Template View pattern in Martin Fowler's
Patterns
of Enterprise Application Architecture I imagined it to cover exactly the case above
and no more; template views were server pages.
But a refinement of the "cobbling" procedure described above brings us to the builder,
which inverts the server page relationship between the tags and the code. By encapsulating all the opening
and closing of tags within helper methods, we can hide the actual mark-up whilst persisting its structure in
that of the program that knows how to build it, so that the code itself looks something like a template.
Fowler's account of the view template shows he considers builders to be a valid variant; the veteran builder
from those days was Perl's CGI.pm, which is referenced (albeit briefly) in the pattern.
The CGI.pm builder brews tag soup by recursively nesting expressions each of which
evaluate to strings. A set of helper methods that are named after the tags they build
take a hash of options followed by further string-returning expressions to concatenate
together and enclose. Glossing over a few Perl/Rails gaps, here's how we can get CGI.pm
to build up and print an expression that evaluates to the store page.
#!/usr/local/bin/perl
use CGI ':standard';
print(header,
start_html("Pragprog Books Online Store"),
# I couldn't figure out how to supply the stylesheet
div({-id=>'banner'},
img({-src=>"http://localhost:3000/images/logo.png", -alt=>"Logo"}),
$page_title || "Pragmatic Bookshelf"
),
div({-id=>'columns'},
div({-id=>'side'},
a({-href=>"http://www...."}, "Home"), br,
a({-href=>"http://www..../faq"}, "Questions"), br,
a({-href=>"http://www..../news"}, "News"), br,
a({-href=>"http://www..../contact"}, "Contact"), br
),
div({-id=>'main'},
h1("Your Pragmatic Store"),
join '', map {
$product = $_;
div({-id=>entry},
img({-src=>$product->{image_url}, -alt=>"Cover"},
h3($product->{title}),
$product->{description},
span({-id=>'price'}, number_to_currency($product->{price})),
button({-action=>"add_to_cart/$product->{id}"}, "Add to Cart")
)
)
} (@products)
)
),
end_html
);
This builder is very Perlish. It achieves high-level brevity at the cost of some low-level heavy
punctuation, as well as dragging its helpers into the global scope. We got to use some semi-functional
programming cuteness to iterate over the products within the innards of a huge expression.
This is charming if, like me, you have an old affection for the language; but even if you find Perl
distasteful I hope you'll agree CGI.pm seems to have hit upon a good idea.
The Ruby version most directly descended from CGI.pm is XmlBuilder, and it's the
default builder within Rails. One change in approach is that rather than building a page as
one big expression that nests method calls and string concatenation, instead we let the building
occur as a side effect and hence write a regular sequential program with guards and iterators.
Here's the rxml template to get Rails to render the store page:
xml.instruct! :xhtml, :version=>"1.0", :encoding=>"UTF-8"
xml.html do
xml.head do
xml.title("Pragprog Books Online Store")
xml<<(stylesheet_link_tag "depot", :media => "all")
end
xml.body :id=>"store" do
xml.div :id=>"banner" do
xml.img :src=>"/images/logo.png", :alt=>"Logo"
xml.text!(@page_title || "Pragmatic Bookshelf")
end
xml.div :id=> "columns" do
xml.div :id=>"side" do
xml.a("Home", :href=>"http://www...."); xml.br
xml.a("Questions", :href=>"http://www..../faq"); xml.br
xml.a("News", :href=>"http://www..../news"); xml.br
xml.a("Contact", :href=>"http://www..../contact"); xml.br
end
xml.div :id=>"main" do
xml.h1("Your Pragmatic Store")
@products.each do |product|
xml.div :class=>"entry" do
xml.img :src=>product.image_url, :alt=>"Cover"
xml.h3(product.title)
xml<<(product.description)
xml.span(number_to_currency(product.price), :class=>"price")
xml<<(button_to "Add to Cart", :action => :add_to_cart, :id => product)
end
end
end
end
end
end
This is looking OK, but it isn't nice enough to move me from rhtml - it's
still a bit hard to read, just from a different angle. We haven't yet reached a
DSL for writing pages
- web people probably couldn't work with this directly. My DRY eyes are offended by some remaining duplication.
What with introducing the xml builder object to scope its helpers,
this is actually more verbose as the server page.
And, there's too much punctuation (although I confess I've not suppressed everything
I could - I'm leaving some tricks for our big finish...)
Still too massive. Let there be light.
Slim builder untangles the template
What happens if we throw further Ruby magic at the problem so as to slim down the
builder?
Markaby happens:
xhtml_strict {
head {
title "Pragprog Books Online Store"
stylesheet_link_tag "depot", :media => "all"
}
body.store! {
div.banner! {
img :src=>"/images/logo.png", :alt=>"Logo"
text @page_title || "Pragmatic Bookshelf"
}
div.columns! {
div.side! {
a "Home", :href=>"http://www...."; br
a "Questions", :href=>"http://www..../faq"; br
a "News", :href=>"http://www..../news"; br
a "Contact", :href=>"http://www..../contact"; br
}
div.main! {
h1 "Your Pragmatic Store"
@products.each do |product|
div.entry {
img :src=>product.image_url, :alt=>"Cover"
h3 product.title
text product.description
span.price number_to_currency(product.price)
button_to "Add to Cart", :action => :add_to_cart, :id => product
}
end
}
}
}
}
Markaby is "mark-up as Ruby." It's real pretty.
Punctuation noise is minimal. As a matter of style we've used curlies rather than
"do ... end" for the tag blocks, and managed to suppress almost
every bracket. This is about as clean as the structure representation could be whilst
remaining executable code. (HAML
gets briefer still, including Pythonesque semantic indentation, but it only
manages this through going back to the being a template that needs
pre-processing - using a better rhtml like this is an interesting alternative, though.)
There's much handling of method_missing so that method invocations can be used to
build the basic CSS classes and
ids without needing hashes. (Like many
builders Markaby is also using further reflection to more cheaply create the tag helper
methods that these modify.)
The builder is made implicit again by using instance_eval to carry
the object's context through to its inner blocks - this avoids scope pollution without
incurring a character tax. (XmlBuilder used to turn this trick too, but because it builds
general XML the chance of confusion was considered too high and the technique was retired. As an HTML builder
Markaby suffers less, and we also get to enforce a restricted builder context so as to control
which models can be referenced by the view.)
The code flow has been designed so that natural idioms tend to do the right thing;
appropriate encodings are implicitly applied to appropriate string arguments.
The inner details can be mysterious, but why
would you expect helpful magic to be otherwise?
Talking of doing the right thing by default... Markaby was the only one of the
builders I tried that generated completely (i.e. W3C)
validating mark-up out the box without any tuning. Further, it also complains if you
attempt to build such things as elements with duplicate ids, or tags that
aren't allowed to self-close - this validation is a nice benefit of builders. Once armed
with a decent Ruby-aware editor working in Markaby can provide plenty of warning of
mistakes in either code or tags - the best of both worlds, keeping programmer and web designer happy.
Conclusion
I think this is the right approach for view building. To repeat my proposed
terminologies:
-
It's an untangled template view - all the flexibility of your regular server page
view template, but less of the mess. We've disent
angled the template
by collapsing mark-up into the code and outlawed all distressing pointy brackets.
-
And it's a slim builder - all the power of your regular builder but less of the mass.
The result is a DSL for authoring web pages.
What are the slimmest builders in other languages and frameworks?
Tags: rails, software_development
Current Mood:
impressed