Thymeleaf.rb is a Ruby adaptation of Thymeleaf, a natural templating engine.
It allows to create natural templates (HTML that can be correctly displayed in browsers and also work as static prototypes) and render with a data set to provide them functionality.
This is a Work In Progress Project. This gem is not ready for production use
A Thymeleaf.rb template looks like:
<table>
<thead>
<tr>
<th data-th-text="${headers.name}">Name</th>
<th data-th-text="${headers.price}">Price</th>
</tr>
</thead>
<tbody>
<tr data-th-each="prod : ${all_products}">
<td data-th-text="${prod.name}">Oranges</td>
<td data-th-text="${prod.price}">0.99</td>
</tr>
</tbody>
</table>Add the gem to your Gemfile:
gem install thymeleafand then execute:
$ bundleOr install it yourself as:
$ gem install thymeleafTo use a template, simply:
Thymeleaf::Template.new(template_content, context).renderWhere template_content is the template as HTML code and context is a hash with template values.
You can also specify a file:
Thymeleaf::Template.new('path/to/template', context).render_fileThymeleaf::Template.new('<p data-th-text="Hello, ${user}">Hello, world</p>', { :user => 'John' }).renderThe parsed result of the above:
<p>Hello, John</p>Thymeleaf.rb offers quite a few options through the Configure module:
Thymeleaf.configure do |config|
# Template prefix. Used in render_file method and include/replace
config.template.prefix = "path/to/my/templates/"
# Template suffix. Used in render_file method and include/replace
config.template.suffix = '.th.html'
# Changes encoding from UTF-8 (default) to 'EUC-JP'
config.parser.encoding='EUC-JP'
endThe config var is an instance of Configuration, which has some methods to configure the engine:
template.prefix='': specify a template prefix. Used inrender_filemethod and include/replace processors.template.suffix='': specify a template suffix. Used inrender_filemethod and include/replace processors.config.parser.encoding='': overwrites the default document encoding (UTF-8).add_dialect(Dialect): registers a dialect into the engine.clear_dialects: clears all registered dialects (including standard dialect).
Context sent to the engine can be:
- A hash dictionary with the variables you want to be accessibles within the template.
- An
OpenStructclass.
Context values can be any kind of data. Thymeleaf.rb converts the passed context to a class similar to OpenStruct, so the limitations about the identifiers are the same as it.
Default Dialect is included by default in Thymeleaf.rb, and offers a set of processors and utilities to render templates.
Default dialect's attributes have the data-th- preffix (which is HTML5 friendly), so any attribute starting with it will be processed:
<h1 data-th-if="${true_var}" data-th-text="Welcome, user">Greetings</h1>The result of the above:
<h1>Welcome, user</h1>Default Dialect supports some syntax variants as attribute values:
- Literals, which is any text value:
"3 + 2" - Output: "3 + 2"
- Expression evaluator, which is an evaluable Ruby expression inside
${and}symbols which includes context vars access:
"${3 + 2}" - Output: "5"
- Selector evaluator, which evaluates a method or property of a previous defined variable:
"*{name}" - instead of ${user.name})
These values can be used together inside an attribute value:
<div data-th-text="*{name} is ${20 + 5} years old"></div>Node content can be overwritten with data-th-text attribute:
<p data-th-text="${surname}, ${name}"></p>If we need text won't be escaped can use the data-th-utext variant:
<pre data-th-text="${unescaped_text}"></pre>Please, be careful when using unescaped text from the end-user.
Conditionals allows you to show or delete a node by evaluating a variable:
<div data-th-if="${true_var}">Hi, baby</p>
<div data-th-if="${false_var}">I'll not be shown after Thymeleaf processed me :(</p>There is the until conditional too:
<div data-th-unless="${true_var}">Hi, baby</p>
<div data-th-unless="${false_var}">Revenge is a dish best served cold >:)</p>switch-case works as it expected:
<div data-th-switch="${user.role}">
<p data-th-case="admin">Admin user</p>
<p data-th-case="manager">Manager user</p>
</div>There are two notes about it:
- All
casetags are evaluated, independent of a previous tag was matched. - There is not a default
case.
Evaluation of conditional attributes to true/false values is done by applying the following rule:
an evaluation is true unless its string value is one of these words (case insensitive):
- false
- f
- nil
- no
- n
- 0
- -0
each iterator accepts an iterable element as parameter (list, set...) and repeats the node fragment for each element.
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
</tr>
<tr data-th-each="prod : ${prods}">
<td data-th-text="${prod.name}">Onions</td>
<td data-th-text="${prod.price}">2.41</td>
</tr>
</table>In the fragment above:
${prods}is the iterated expression or iterated variable.prodis the iteration variable.
Note that the prod variable will only be available inside the <tr> element (including inner tags like <td>).
There is an alternative syntax for each:
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
</tr>
<tr data-th-each="prod, stat : ${prods}">
<td data-th-text="${prod.name}">Onions</td>
<td data-th-text="${prod.price}">2.41</td>
</tr>
</table>Where stat is a status variable defined within a data-th-each attribute that contain the following data:
index: the current iteration index, starting with 0.count: current iteration index, starting with 1.size: the total amount of elements in the iterated variable..current: The iter variable for each iteration.even:truewhether the current iteration is even orfalseif not.oddtruewhether the current iteration is even orfalseif not.first:truewhether the current iteration is the first one orfalseif not.last:truewhether the current iteration is the last one orfalseif not.
Since status variable contains the iteration variable value, we can rewrite the previous example:
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
</tr>
<tr data-th-each="_, stat : ${prods}">
<td data-th-text="${stat.current.name}">Onions</td>
<td data-th-text="${stat.current.price}">2.41</td>
</tr>
</table>The data-th-remove attribute allows to delete document nodes:
<table>
<tr>
<th>NAME</th>
<th>PRICE</th>
</tr>
<tr data-th-each="_, stat : ${prods}">
<td data-th-text="${stat.current.name}">Onions</td>
<td data-th-text="${stat.current.price}">2.41</td>
</tr>
<tr class="odd" data-th-remove="all">
<td>Blue Lettuce</td>
<td>9.55</td>
</tr>
<tr data-th-remove="all">
<td>Mild Cinnamon</td>
<td>1.99</td>
</tr>
</table>data-th-remove can behave in different ways, depending on its value:
all: Remove both the containing tag and all its children.body: Do not remove the containing tag, but remove all its children.tag: Remove the containing tag, but do not remove its children.all-but-first: Remove all children of the containing tag except the first one. It will let us save somedata-th-remove="all"when prototyping.none: Do nothing. This value is useful for dynamic evaluation.
We can assign a value to be used as the context value of selection operator with the data-th-object attribute as follow:
<div data-th-block="${user}">
<h1 data-th-text="Hello, *{name}">Greetings</h1>
...
</div>Which is a shorted way to write:
<div>
<h1 data-th-text="Hello, ${user.name}">Greetings</h1>
...
</div>Fragment expressions are an easy way to represent fragments of markup and move them around templates. This allows to replicate them, pass them to other templates as arguments, etc.
Fragments are defined by data-th-fragment attribute:
<footer data-th-fragment="myfooter">
<span data-th-text="Copyright ${date.year} Trabe">
Copyright 2016 My Company
</span>
</footer>data-th-include and data-th-replace are used to insert:
- Other templates into the current one.
- Other fragments of the current template.
- Fragments of other templates.
Their syntax is as follow:
template :: fragmentWhere template is the name of template to include and fragment is the name of declared fragment (with data-th-fragment) inside the previous template. To indicate the current template, you can use the this identificator. When any option is not required, it can be omitted:
templateortemplate ::to insert a template::fragmentto insert any current template fragment. It is equivalent tothis::fragment.
For example, to include the myfooter fragment from the previous example defined in the same template:
<div data-th-insert="myfooter">...</div>
<!-- Or... -->
<div data-th-insert="this::myfooter">...</div>The difference between data-th-include and data-th-replace is, while include inserts the fragment or template inside the container node, replace replaces the container node:
<div data-th-insert="myfooter" class="inserted">...</div>
<div data-th-replace="myfooter" class="replaced">...</div>The result of the above:
<div class="inserted">
<footer>
<span>Copyright 2016 Trabe</span>
</footer>
</div>
<footer>
<span>Copyright 2016 Trabe</span>
</footer>Fragment inclusion can be done by CSS selector, instead of fragments definition. For example, we can insert the tag with id="footerid" with:
<div data-th-insert="#footerid" class="inserted">...</div>We can insert all divs on a document, too:
<footer data-th-insert="div" class="inserted">...</footer>Basically, when a fragment is invoked in a data-th-include or data-th-replace attribute, Thymeleaf.rb searchs first for any declared fragment with data-th-fragment and, if there is not any result, searchs on document by CSS selector.
If you'd like to help improve Thymeleaf.rb, feel free to submit a pull request. If you found something we should know about, please submit an issue.
Thymeleaf.rb is released under the MIT license.