[erlang-questions] [ANN] Brim - HTML templating library
Per Melin
per.melin@REDACTED
Thu Mar 15 11:40:22 CET 2012
Brim is inspired by Enlive for Clojure, meaning that the HTML templates are completely free of the embedded code, annotations, special attributes, etc that we usually see. It also uses CSS selectors to reference the parts of the HTML we want to transform with Erlang.
https://github.com/permelin/brim
It is version 0.0.1 and while usable it's too early to be very useful yet. I'm announcing it anyway because I really want input on the API, which I am struggling with.
An example (since there is no documentation): A web page that shows a table with the applications running on our Erlang node. Here is the template, filled with some dummy text so that we can test what it will look like in a browser.
<table id="apps">
<caption>node name will go here</caption>
<tbody>
<tr>
<td>application</td>
<td>yada yada yada</td>
<td>version</td>
</tr>
</tbody>
</table>
Then the code that will transform the above HTML and fill it with data.
-module(running_apps).
-export([html/0]).
html() ->
% Read the template
Doc0 = brim:document("priv/apps.html"),
% Set the caption to the output from node()
Doc1 = brim:content(Doc0, "#apps > caption", node()),
% Fill the table tbody -- see rows/1 below
Doc2 = brim:content(Doc1, "#apps > tbody", rows(Doc1)),
% And to really show off the CSS selector support we'll add the
% class "pointless" on all <tr> whose id does not start with "ker"
Doc3 = brim:add_class(Doc2, "tr:not([id^=ker])", "pointless"),
% Print to screen -- use brim:render/1 instead to get io list
brim:print(Doc3).
rows(Doc) ->
% Create one clone of the <tr> for each item returned from
% application:which_application() and transform them as specified
% by the fun.
brim:map(
Doc, "#apps tr", application:which_applications(),
fun(TR0, {App, Description, Version}) ->
TR1 = brim:content(TR0, "td:first-child", App),
TR2 = brim:content(TR1, "td:nth-child(2)", Description),
TR3 = brim:content(TR2, "td:last-child", Version),
% And finally set the id of each <tr> to the name of the
% corresponding application, for no good reason
brim:id(TR3, "tr", atom_to_list(App))
end).
And here is the output (formatted for readability):
<table id="apps">
<caption>nonode@REDACTED</caption>
<tbody>
<tr id="inets" class="pointless">
<td>inets</td>
<td>INETS CXC 138 49</td>
<td>5.7.1</td>
</tr>
<tr id="stdlib" class="pointless">
<td>stdlib</td>
<td>ERTS CXC 138 10</td>
<td>1.17.5</td>
</tr>
<tr id="kernel">
<td>kernel</td>
<td>ERTS CXC 138 10</td>
<td>2.14.5</td>
</tr>
</tbody>
</table>
As you can see, there is a lot of explicit state threading (Doc0, Doc1, Doc2, Doc3, TR0, TR1, TR2, TR3) which I fear can get annoying when dealing with larger templates. I'm trying out different ways to minimize that (and verbosity in general). The most obvious (to me) is to move the state argument to last position to facilitate chaining.
html() ->
Doc = brim:document("priv/apps.html"),
brim:print(
brim:add_class("tr:not([id^=ker])", "pointless",
brim:content("#apps > caption", node(),
brim:content("#apps > tbody", app_rows(Doc), Doc)))).
Another alternative below. But I think it has too many limitations to be feasible.
html() ->
Doc = brim:document("priv/apps.html"),
brim:do(Doc,
[brim:content("#apps > caption", node()),
brim:content("#apps > tbody", app_rows(Doc)),
brim:add_class("tr:not([id^=ker])", "pointless"),
brim:print()]).
As I said, all input is appreciated.
More information about the erlang-questions
mailing list