-
Notifications
You must be signed in to change notification settings - Fork 18
Bidirectional Templates
RIFE2 provides a unique template engine that is logic-less and bidirectional.
The bidirectionality allows the templates to perform the original intent of templating in the real world, providing patterns and models to be used for further processing and manipulation.
NOTE: it's easy to glance over the fundamental difference between RIFE2's template engine and the majority of other template engines, and to conclude that RIFE2's is too simple and underpowered. The bidirectional nature is unique and fundamentally changes how you design and interact with your templates.
In RIFE2, you manipulate the templates like a puzzle, using Java. Each template provides reusable pieces that can be rearranged and reassembled. There's no need for logic nor data binding in the template engine since you can use any Java language construct. There's also no template execution model, templates are just structured content. This is inspired by how templates behave in visual design tools, they provide pieces than can be reused and rearranged and have placeholders to put content in. In visual design tools, you're usually the one doing the rearranging, templates just don't rearrange themselves. Similarly, in RIFE2, you write the Java language statements to perform these template manipulations.
You can find a number of template examples in the RIFE2 GitHub repository.
RIFE2's templates contain two main concepts:
-
values - that can be filled in with content and data (
v
-tags) -
blocks - that will be stripped away and that provide content building blocks (
b
-tags)
Your Java code will compose the final layout by assigning and appending blocks, and by putting data into values. Let's rewrite one of the previous site examples with a template.
In this example, no template manipulation is done in Java yet.
Instead, it introduces the {{v route:hello/}}
value tag, which will
automatically be replaced with the URL of the route that is available with that
field name in your active Site
.
public class AppSite extends Site {
Route hello = get("/hello", c -> c.print("Hello World"));
Route link = get("/link", c-> c.print(c.template("HelloTemplate")));
// public static void main ...
}
With HelloTemplate.html
being:
<!DOCTYPE html>
<html lang="en">
<body>
<a href="{{v route:hello/}}"><!--v message-->Hello<!--/v--></a>
</body>
</html>
Note that RIFE2 internally transforms your templates into Java classes by generating heavily optimized bytecode.
This happens on-the-fly during development. For production, templates can be pre-compiled, making them incredibly fast.
You'll notice that there's one other value tag: message
. This will by default
contain Hello
(see the default value content section below).
If you want to change its content, this can be done through template
manipulation from Java.
For instance, this will display Bonjour
instead:
var t = c.template("HelloTemplate");
t.setValueEncoded("message", "Bonjour");
c.print(t);
Now, let's change the example some more and create a single route that can respond to
both get
and post
requests.
- the
get
request will display a form with a single button to click. - the
post
request will receive the form's submission and displayHello World
.
public class AppSite extends Site {
Route hello = getPost("/hello", c -> {
var t = c.template("HelloForm");
switch (c.method()) {
case GET -> t.setBlock("content", "form");
case POST -> t.setBlock("content", "text");
}
c.print(t);
});
// public static void main ...
}
With HelloForm.html
being:
<!DOCTYPE html>
<html lang="en">
<body>
<!--v content/-->
<!--b form-->
<form action="{{v route:action:hello/}}" method="post" name="hello">
<!--v route:inputs:hello/-->
<input type="submit" name="Submit">
</form>
<!--/b-->
<!--b text--><p id="greeting">Hello World</p><!--/b-->
</body>
</html>
NOTE: that the
route:
value tag from the above has been split intoroute:action:
androute:inputs:
, generating hidden HTML form inputs for parameters instead of query string parameters.
You can see that the template contains all the pieces to create both pages:
- the value named
content
- the block named
form
- the block named
text
In Java, we simply assign either block to the value, depending on what we want to display.
Another benefit is that RIFE2's template tags can be HTML comments, making them completely invisible. This allows you to work on your HTML design as usual and preview the template file with a regular browser.
Common template manipulation methods include:
t.setValue(String id, Object value); // sets contents to a value
t.setValueEncoded(String id, Object value); // sets the encoded contents to a value
t.setBean(Object bean); // sets bean properties for matching values
t.setBean(Object bean, String prefix); // sets bean properties for prefixed values
t.appendValue(String id, Object value); // appends content to a value
t.removeValue(String id); // remove the constructed content of a value
t.blankValue(String id); // set a value to an empty string
t.clear(); // resets the template to a clean state
t.setBlock(String valueBlockId); // set a block to a value with the same ID
t.setBlock(String valueId, String blockId); // set a block to a value
t.appendBlock(String valueBlockId); // append a block to a value with the same ID
t.appendBlock(String valueId, String blockId); // append a block to a value
t.getBlock(String id); // get the evaluated content of a block
t.getContent(); // get the evaluated content of the template
t.addResourceBundle(ResourceBundle bundle); // adds a resource bundle for localization
t.setAttribute(String name, Object value); // sets an attribute that travels with this template
t.getAttribute(String name); // retrieves an attribute from this template
t.getValueOrAttribute(String name); // retrieves a value or attribute from this template
Value tags can be expressed in a short or a long form, for example:
<!--v value1/-->
<!--v value2-->default content<!--/v-->
When using the long form, any content between the value start and end tags will be considered the default content. Note that you can't nest value tags. If you want more complicated default content, consider using block value tags as described below.
RIFE2 allows you to comment your templates with tags that will be stripped away.
You could for instance change Common.html
to include its purpose as a comment:
<title>Hello World</title>
<!--c head section-->Additional meta tags belong here<!--/c-->
<meta charset="UTF-8">
<meta property="og:type" content="website">
Certain sections of templates are often commonly used. RIFE2 allows you to include other templates and share the content.
For instance, you might want to have a set of meta tags that are included in the
HelloForm.html
head section, and that also apply to other templates.
You could change HelloForm.html
to:
<!DOCTYPE html>
<html lang="en">
<head>
<!--i common/-->
</head>
<body>
<!--v content/-->
<!--b form-->
<form action="{{v route:action:hello/}}" method="post" name="hello">
<!--v route:inputs:hello/-->
<input type="submit" name="Submit">
</form>
<!--/b-->
<!--b text--><p id="greeting">Hello World</p><!--/b-->
</body>
</html>
With common.html
being:
<title>Hello World</title>
<meta charset="UTF-8">
<meta property="og:type" content="website">
When you visit the page, you'll see that Hello World
is now the window title
and when you view the source of the HTML page, you'll notice that those two meta
tags have been added.
It's important to note that all include tags will be processed as the first parsing step. Only after all inclusions are performed, the block and value tags will be evaluated. This allows for some useful constructs with the block value and block append tags that are explained below.
You might have noticed that having Hello World
in the title
tag above might
not be suitable for reuse across many pages, however the title
tag itself is.
Let's change common.html
to make that changeable:
<title><!--v title-->Hello World<!--/v--></title>
<meta charset="UTF-8">
<meta property="og:type" content="website">
This will provide default content for the title
value tag, and also allows
it to be changed.
Now let's create another template that does this, for instance:
<!DOCTYPE html>
<html lang="en">
<head>
<!--i common/-->
<!--bv title-->Welcome Universe<!--/bv-->
</head>
<body>
Welcome Universe!
</body>
</html>
When this template is evaluated, the bv
(block value) tag will define a block
with the name title
. As with any template block, its contents will be stripped
but the bv
nature will automatically set that content to any value tag with
the same name, in this case replacing the title of the page with
Welcome Universe
.
The ba
tag (block append), has a similar behavior but instead appends the
content to a value tag with the same name.
As mentioned above, having all the template inclusions being resolved before
processing value and block tags, allows the bv
and ba
tags to perform
convenient template content manipulation by merely using related names and
separating template into different files.
You might have noticed that there are two ways to write RIFE2 template tags.
- a long one that is an HTML comment:
<!--v id-->default content<!--/v-->
<!--b id-->block content<!--/b-->
- a short one that can be used inside tags without generating invalid XML:
{{v id}}default content{{/v}}
{{b id}}block content{{/b}}
You can freely decide to use either, depending on what makes the most sense or feels the most natural to you.
If RIFE2 can find the template source files in your classpath, those will be checked for changes and be automatically parsed again and reloaded while your app is running.
All that's necessary is to ensure that the templates are accessible through your runtime classpath. When you use a bld created project or the RIFE2 Gradle plugin, this is automatically set up for you.
Next learn more about Filtered Template Tags