ManTL is a lightweight & type-safe template engine directly integrated with the Java compiler using Manifold. It supports the full Java language, type-safe arguments to templates, type-safe inclusion of other templates, shared layouts for templates and custom base classes for application-specific logic, among other features.
Templates compile directly in your build as if Java source files without a separate code generation build step, therefore your Java source code can reference and use your template files by name directly as Java classes. This level of integration and type-safety promotes higher levels of integrity and performance. It also enables tooling like the Manifold IntelliJ plugin to provide deterministic code completion, navigation, usage searching, and refactoring. Additionally the IntelliJ plugin enables incremental compilation and hot swap debugging, allowing you to make template changes in a running application.
Clone the Manifold sample Web App project to quickly begin experimenting with ManTL templates using the Manifold IntelliJ plugin.
Use ManTL in your project simply by adding the manifold-template
dependency.
Maven:
<dependency>
<groupId>systems.manifold</groupId>
<artifactId>manifold-templates</artifactId>
<!-- it is best to use the latest release -->
<version>0.71-alpha</version>
</dependency>
Gradle:
compile group: 'systems.manifold', name: 'manifold-templates', version: 'RELEASE'
Note prebuilt binaries are also available for non-Maven/Gradle projects.
Once you have installed ManTL, you can begin using it by placing a new file with the mtl
extension in your resources
directory (nb: not in your source directory). The file can have any sort of string content, as well as dynamic content
and directives that change how the template behaves.
Note it is helpful, but not required, to include the file extension of the target content in the template file name. For instance, a template that produces HTML as output is named MyTemplate.html.mtl
Consider the following template named HelloWorld.txt.mtl
, located in the resources/templates
directory:
Hello World!
This template can be used directly from your Java code:
import templates.HelloWorld;
public class Demo {
public static void main(String[] args) {
System.out.println(HelloWorld.render());
}
}
This prints Hello World
to your console.
If you want to add a parameter to the template, you can change the template to use the params
directive:
<%@ params(String name) %>
Hello ${name}!
Note you can make changes to your templates in IntelliJ while debugging your application. The Manifold plugin for IntelliJ hot swaps incremental compilation changes into your running application.
You can call this parameterized template with a String
argument:
import templates.HelloWorld;
public class Demo {
public static void main(String[] args) {
System.out.println(HelloWorld.render("ManTL"));
}
}
Which prints Hello ManTL!
to your console.
If you do not wish to materialize the template as a string, you can use the renderInto()
method to render templates
into any Appendable
object. The renderInto()
method is similar to render()
but defines an additional Appendable
parameter and returns void
.
As with most template languages, a ManTL template consists of regular textual content interspersed with language constructs such as statements, expressions, comments, and directives.
ManTL lets you control output of a template with Java language statements. You inline statements or statement fragments in a template using this syntax:
<% java-statement-parts %>
ManTL supports all Java language statements including variable and method declarations and control structures. For
example, the if
statement:
<% if(total >= 90) { %>
Grade: A
<% } %>
results in the following output if total
is 90
or greater:
Grade: A
otherwise, the statement has no effect on the output.
Notice the statement is fragmented between two sets of <% %>
delimiters. You can leverage many of Java’s
statements in this way to control the output, including if-else
, switch
, for
, while
, and do-while
.
This example demonstrates how a simple for
statement can repeat a section of the template’s content:
<% for(String brand: Arrays.asList("Maserati", "Alfa Romeo", "Abarth") { %>
Fiat brand: ${brand}
<% } %>
Renders as:
Fiat brand: Maserati
Fiat brand: Alfa Romeo
Fiat brand: Abarth
You can achieve the same result using a Java lambda expression:
<% Arrays.asList("Maserati", "Alfa Romeo", "Abarth").forEach(brand -> { %>
Fiat brand: ${brand}
<% }); %>
A ManTL expression contains a Java language expression, it is evaluated, coerced to a String
, and
inserted where the expression appears in the ManTL file.
Use expressions with this syntax:
<%= java-expression %>
Additionally, the following shorthand syntax is also valid:
${ java-expression }
For example, this template:
<html>
<head><title>Expression Example</title></head>
<body>
<% int y = 10; %>
<p style="font-size: ${y}"> The font size of this paragraph is ${y}. </p>
</body>
</html>
generates the following HTML:
<html>
<head><title>Expression Example</title></head>
<body>
<p style="font-size: 10"> The font size of this paragraph is 10. </p>
</body>
</html>
Note the statement declaring the y
variable does not directly contribute to the resulting content. This is because a
statement does not produce a value to display, instead a statement controls what displays. By contrast an expression
produces a value, thus it directly renders as part of the template’s resulting content, hence both ${y}
expressions
render 10
in the output.
Comments are blocks that delimit areas of the template the compiler ignores; they do not contribute to the template’s output. Use them to make comments and to temporarily mask off sections of a template as you like.
Note template comments in no way affect the generated Java code, they are exclusively template file comments.
The syntax of a comment is as follows:
<%-- This is a comment --%>
Directives are commands you use to control the compilation and resulting structure of a template.
Directives have the following syntax:
<%@ directive-name [options] %>
Here is a summary of all the ManTL directives. More detailed descriptions follow.
Directive | Syntax | Description |
---|---|---|
import | <%@ import type-name %> |
Imports Java types for use in template directives, statements, and expressions |
extends | <%@ extends class-name %> |
Extends a base class having features suitable for the template file |
params | <%@ params(parameter-list) %> |
Parameters for the template, arguments passed via the render(arg-list) method |
include | <%@ include template-name[(parameter-list)] [if <condition>]%> |
Include a separate template in the template |
section | <%@ section section-name(parameter-list) %> |
Creates a sub-template within the template, that can be called from other templates |
layout | <%@ layout template-name %> |
Specifies the template in which the declaring template nests its content |
content | <%@ content %> |
Used in a layout template, denotes where the content of a nested template renders |
import
Use the import
directive as you would a Java import
statement so you can use Java classes without
having to qualify them with package names.
The syntax of the import
directive:
<%@ import type-name %>
This example imports the java.util.HashSet
class and uses it to declare the myHashSet
variable:
<html>
<%@ import java.util.HashSet %>
<head><title>Import Example</title></head>
<body>
<% int y = 10;
HashSet<Integer> myHashSet = new HashSet<>();
myHashSet.add(y);
myHashSet.add(15);
for(Integer a: myHashSet) { %>
<p> myHashSet contains ${a}. </p>
<% } %>
</body>
</html>
The above template produces the following HTML:
<html>
<head><title>Import Example</title></head>
<body>
<p> myHashSet contains 10. </p>
<p> myHashSet contains 15. </p>
</body>
</html>
Note
import
directives must precede all other directives in your template.
extends
Use the extends
directive to make a template extend a custom base class, which you can use to provide
additional application specific functionality e.g., Request
and Response
objects in a web application.
A practical example of the extends
directive:
package demo;
import model.Contact;
import manifold.templates.rt.runtime.BaseTemplate;
public class ExampleTemplate extends BaseTemplate {
public String displayContact(Contact c) {
if(c.hasName()) {
return c.getName();
} else {
return c.getEmail();
}
}
}
This allows the developer to render a clean template:
<%@ import model.Contact %>
<%@ extends demo.ExampleTemplate %>
<%@ params(Contact c)%>
<div>
<div>
Contact
</div>
<div>
${displayContact(c)}
</div>
</div>
And easily callable:
get("/contact/:id", (req, resp) -> ShowContact.render(Contact.find(req.getParam("id")));
params
Use the params
directive to declare parameters in a template, similar to declaring parameters for a method.
The syntax of the params
directive is as follows:
<%@ params(parameter-list) %>
For example, you can create the template NameDisplay.html.mtl
as the following:
<%@ params(String name) %>
<p>Your name is: ${myName}</p>
You can then include it in another template as follows:
<html>
<head><title>PARAMS Example</title></head>
<body>
<%@ include NameDisplay("Robert") %>
<%@ include NameDisplay("Scott") %>
</body>
</html>
Then, the following HTML will be generated:
<html>
<head><title>PARAMS Example</title></head>
<body>
<p>Your name is: Robert </p>
<p>Your name is: Scott </p>
</body>
</html>
include
The include
directive allows users to insert other templates inside of the given template in a type
safe manner.
The syntax looks like this:
<%@ include [template-name] %>
For example, consider the following template, MyTemplate.html.mtl
:
<% int fontSize = 0; %>
<html>
<head><title>WHILE LOOP Example</title></head>
<body>
<% while (fontSize <= 3) { %>
<font color = "green" size = "<%= fontSize %>">
ManTL Tutorial
</font><br />
<%fontSize++;%>
<%}%>
</body>
</html>
We can then include it from another template as such:
<%@ include MyTemplate %>
Both statements will result in the following HTML code:
<html>
<head><title>WHILE LOOP Example</title></head>
<body>
<font color = "green" size = "0">
ManTL Tutorial
</font><
<font color = "green" size = "1">
ManTL Tutorial
</font><br />
<font color = "green" size = "2">
ManTL Tutorial
</font><br />
<font color = "green" size = "3">
ManTL Tutorial
</font><br />
</body>
</html>
ManTL supports shorthand for conditional inclusion of templates. The following syntax:
<% if (condition) { %>
<%@ include MyTemplate %>
<% } %>
Can be condensed to the following:
<%@ include MyTemplate if(condition) %>
(Note: In the above, parentheses are optional.)
section
The section
directive creates a subsection of the current template that can be added using the include
directive in
other templates.
The syntax of a section
block:
<%@ section section-name[(symbols-used-in-section)] %>
SECTION CONTENT HERE
<%@ end section %>
Note the corresponding <%@ end section %>
directive must be used to complete the section, otherwise
a compile error results.
For example, you can create the template NestedImport.html.mtl
as the following:
<%@ import java.util.* %>
<h1>Defines a section</h1>
<%@ section mySection %>
<% HashSet<Integer> myHashSet = new HashSet<>();
myHashSet.add(1);
myHashSet.add(2);
myHashSet.add(3);
for(Integer a: myHashSet) { %>
<h2 style="font-size: ${a}">Font size: ${a}</h2>
<% } %>
<%@ end section %>
<p> The End </p>
The above code will generate the following HTML:
<h1>Defines a section</h1>
<h2 style="font-size: 1">Font size: 1</h2>
<h2 style="font-size: 2">Font size: 2</h2>
<h2 style="font-size: 3">Font size: 3</h2>
<p> The End </p>
Then, you can include mySection
in a separate template:
<%@ include NestedImport.mySection %>
Which will result in the following HTML:
<h2 style="font-size: 1">Font size: 1</h2>
<h2 style="font-size: 2">Font size: 2</h2>
<h2 style="font-size: 3">Font size: 3</h2>
layout
Layouts can be made and used with the content
and layout
directives respectively.
The content
directive splits the current template into the header and footer of a layout.
The layout
directive makes the header and footer of the layout frame the current template.
The current template renders at the location of the content
directive.
Both the content
directive and layout
directive are only valid in the outermost class
(not within sections) and can only appear once in a template.
The params
directive is not yet supported for a template that contains the content
directive.
The syntax of a layout template is as follows:
HEADER CONTENT HERE
<%@ content %>
FOOTER CONTENT HERE
For example, you can create the template LayoutEx.html.mtl
:
</html>
</body>
<%@ content %>
</body>
</html>
And use the layout in the following template:
<%@ layout LayoutEx %>
<h1>This is a template that uses a layout.</h1>
<h2>The layout directive can appear anywhere in the template.</h2>
The above code will generate the following HTML:
</html>
</body>
<h1>This is a template that uses a layout.</h1>
<h2>The directive can appear anywhere in the template.</h2>
</body>
</html>
ManTL also supports the ability to set default layouts for templates in a given package via the
ManifoldTemplates.java
configuration class:
// Sets default template for all templates
ManifoldTemplates.setDefaultLayout(MyLayout.asLayout());
// Sets default templates for all templates in "some.package"
ManifoldTemplates.setDefaultLayout("some.package", AnotherLayout.asLayout());
By default, more specific layout declarations take precedence over less specific ones. For example, templates with a
declared layout (using the layout
directive) override the default layout.
Note the generated asLayout()
static method on layout template classes. This is useful when you override
layouts, as specified below.
Sometimes you may want to manually override the layout of a given template in code,
or render a template with no layout. ManTL classes include two fluent helper methods:
withoutLayout()
and withLayout(ILayout)
to assist in these cases:
// Renders the template with no layout, regardless of the configuration
MyTemplate.withoutLayout().render();
// Renders MyTemplate with the MyLayout layout, regardless of other configuration
MyTemplate.withLayout(MyLayout.asLayout()).render();
ManTL language constructs are silent with respect to the template’s output. That is to say, contiguous whitespace characters leading and trailing a language construct are omitted from the template’s generated content. Whitespace characters include spaces, tabs, and new lines.
<%@ import java.util.ArrayList %>
<% if(true) { <%>
Hi
<% } %>
The above template renders just one line of text consisting of the two characters in the word Hi
; none of the
whitespace immediately preceding or following the language constructs are included.
ManTL is designed with web frameworks like Spark in mind.
A simple “Hello World!” Spark application making use of ManTL:
package app;
import manifold.templates.rt.ManifoldTemplates;
import views.Index;
import views.layout.DefaultLayout;
import static spark.Spark.*;
public class WebApp {
public static void main(String[] args) {
// Set up the default layout for the application
ManifoldTemplates.setDefaultLayout(DefaultLayout.asLayout());
// Enable tracing
ManifoldTemplates.trace();
// Render the Index template
get("/", (req, resp) -> Index.render("Hello World!"));
}
}
There are two templates in the resources
directory: views/Index.html.mtl
and views/layouts/DefaultLayout.html.mtl
.
Here the code references the Index
template directly as a Java class. This is a powerful aspect of ManTL – the
compiler verifies your links are never broken and you can fully leverage the strength of IntelliJ for deterministic
code completion, usage searching, refactoring, navigation, incremental compilation, and hot swap.
Note the code takes advantage of the type-safe parameters available in ManTL and no Spark “TemplateEngine” is needed.
Manifold provides base class manifold.templates.rt.sparkjava.SparkTemplate
for use with the extends
directive
in your templates (or, more commonly, you extend this class and add more of your own application functionality). This
class provides various convenience methods to get the HTTP Request
, Response
, etc. and it also automatically escapes
all string content for HTML, to help prevent malicious user input from causing a security issue in your application.
If you wish, you can output raw HTML in a template that extends manifold.templates.rt.sparkjava.SparkTemplate
using the
raw()
function:
${raw("<h1>Some Raw HTML</h1>")}
ManTL supports performance tracing with the following syntax:
ManifoldTemplates.trace();
After invoking the trace()
method, every following render()
call prints the following to the console:
- Template template-name rendered in time-to-render ms
A sample Spark application is available here: