Freemarker Template Language (FTL) : Freemarker Template Language (FTL) : Freemarker Template Language Basic FTL terms, built-in functions and built-in instructions are introduced for quick reference in the development manual (version 2.3.30 is used for demonstration in this paper, please check the official website according to your own project version in actual use).

This article will not list the official apis, but will only demonstrate the syntax if necessary. There is a freemarker API mind map in the code project. Please pick it up at github of the class representative.

1. A FreeMarker is what

Freemarker is a template engine software written in pure Java that can be used to generate all kinds of text, including but not limited to: HTML, E-mail, various source code, etc.

Its main task is to assemble templates and data together to produce a document, a process called Render. The process is shown as follows:

Since most template developers use it to generate HTML pages, this article demonstrates rendering HTML pages based on SpringBoot(2.4.1)+Freemarker(2.3.30)+SpringWeb

2. The simplest template

Suppose I want a simple page to welcome the current user, template code:

<html>
<head>
  <title>index</title>
</head>
<body>
    <p>Hello, ${userName}</p>
</body>
</html>
Copy the code

${userName} is FTL’s interpolation syntax, which will replace the value of userName into the generated HTML, so that according to the current logon, different userName, this value is put by the backend code into the Model, corresponding to the Controlelr code:

@Controller
public class HelloWorld {
    @GetMapping("hello")
    public String hello(Model model) {
        model.addAttribute("userName"."Java Class Representative");
        // Returns the template name
        return "index"; }}Copy the code

Visit page:

The data is passed by the back-end code through the data Model, and the template only cares about how the data is presented (View). The relationship between the two is controlled by the Controller, which is MVC.

3. Data-model

How is the data from the Controller added to the model organized? In this case, we need to understand the data-model of FTL.

The data model of FTL is a tree in structure:

(root)
  |
  +- animals
  |   |
  |   +- mouse
  |   |   |
  |   |   +- size = "small"
  |   |   |
  |   |   +- price = 50
  |   |
  |   +- elephant
  |   |   |
  |   |   +- size = "large"
  |   |   |
  |   |   +- price = 5000
  |   |
  |   +- python
  |       |
  |       +- size = "medium"
  |       |
  |       +- price = 4999
  |
  +- message = "It is a test"
  |
  +- misc
      |
      +- foo = "Something"
Copy the code

Where root can be understood as model in Controller through model.addattribute (“userName”,”Java class representative “); You can add data to the data model.

Variables in the data model that can be expanded like directories, such as root, animals, mouse, elephant, Python, and misc, are called hases. The key of the hash is the name of the variable, and the value is the value that the variable stores. Separated paths can access variable values, such as price for mouse: animes.mouse.price.

A variable such as animes.mouse. price that stores a single value is called a scalar, with four specific types: string, Boolean, date-like, and number;

There is also a variable called a sequence, which is analogous to an array in Java. Each item in a sequence has no name and can be accessed by traversal or subscript (sequence access will be shown later). Its data structure looks like this:

(root)
  |
  +- animals
  |   |
  |   +- (1st)
  |   |   |
  |   |   +- name = "mouse"
  |   |   |
  |   |   +- size = "small"
  |   |   |
  |   |   +- price = 50
  |   |
  |   +- (2nd)
  |   |   |
  |   |   +- name = "elephant"
  |   |   |
  |   |   +- size = "large"
  |   |   |
  |   |   +- price = 5000
  |   |
  |   +- (3rd)
  |       |
  |       +- name = "python"
  |       |
  |       +- size = "medium"
  |       |
  |       +- price = 4999
  |
  +- misc
      |
      +- fruits
          |
          +- (1st) = "orange"
          |
          +- (2nd) = "banana"
Copy the code

There are three commonly used data types in FTL: Hashe, scalar and sequence.

Once you have data, you also need a syntax to organize it. Here is the common syntax in FTL.

4. The FTL syntax

FreeMarker only recognizes three grammars:

  1. The interpolation: ${… }, Freemarker will replace the variables inside with the actual values
  2. FTL tags: Tags that are similar in structure to HTML<>Wrap it up with a regular label< #Start with user-defined tagsThe < @At the beginning, such as<#if true>true thing<#/if>.<@myDirect></@myDirect>

You’ll see two names: tags and directive. For example: <#if>
is called a tag; The if inside the tag is an instruction, which is analogous to HTML tags (e.g.

) and elements (e.g. Table). However, it’s ok to think of labels and directives as synonyms.

  1. Comments: Comments in FTL look like this:<#-- comment out -->For comments, FTL skips them automatically, so they are not displayed in the generated text (unlike HTML comments)

Note: All content other than these three grammars is treated by FreeMarker as normal text, and normal text is printed as is

Interpolation is simply replacing the value of a variable, and annotation is nothing to say. Here are some of the most commonly used FTL tags (instructions) and some code to demonstrate their use.

If the instructions

If userName = “Java class representative “or” zhengxl5566 “; if userName = “Java class representative “or” zhengxl5566 “;

<p><#if userName == "Java class representative "><strong>${userName}</strong>
    <#elseif userName == "zhengxl5566">
        <h1>${userName}</h1>
    <#else>
        ${userName}
    </#if>
</p>
Copy the code

The list instruction

List is used to iterate over a sequence. The syntax is:

<#list sequence as loopVariable>
    repeatThis
</#list>
Copy the code

For example, the background puts a collection of allUsers into the model

model.addAttribute("allUsers",userService.getAllUser());
Copy the code

An element in the collection can be accessed directly using subscripts: ${allUsers[0].name}

You can also iterate through the display directly in the template:

<ol>
<#list allUsers as user>
    <li>Name: ${user.name}, age: ${user.age}</li>
</#list>
</ol>
Copy the code

The actual rendered HTML:

<ol>
  <li>Name: ZXL, age: 18</li>
  <li>Name: LS, age: 19</li>
  <li>Name: ZS, age: 16</li>
</ol>
Copy the code

Note: Assuming allUsers is empty, the rendered page will be < OL >
. If you want to avoid this, you can use the Items tag

<#list allUsers>
    <ol>
        <#items as user>
            <li>Name: ${user.name}, age: ${user.age}</li>
        </#items>
    </ol>
</#list>
Copy the code

At this point, assuming allUsers is empty, the HTML content in the list tag will not be rendered.

The include directive

The include directive inserts the contents of one template into another (the official recommendation is to use import instead, see best practices below). Assuming we need a footer for each page, we can write a common footer. FTLH template, and the rest of the pages that need a footer just reference the footer. FTLH template:

<#include "footer.ftlh">
Copy the code

The import command

Import imports variables defined in a template into the current template for use in the current template. The main difference between import and include is that import encapsulates variables in a new namespace (see how import and include compare later).

For example, a template /libs/commons.ftl contains a number of public methods that can be referenced in other templates.

<#import "/libs/commons.ftl" as com>
Copy the code

To use the copyright method in /libs/commons.ftl, you can use:

<@com.copyright date="1999-2002"/>
Copy the code

The assign instructions

Assign can be used to create new variables and assign values to them. The syntax is as follows:

<#assign name1=value1 name2=value2 ... nameN=valueN> or <#assign name1=value1 name2=value2 ... nameN=valueN in namespacehash> or <#assign name> capture this </#assign> or <#assign name in namespacehash> capture this  </#assign>Copy the code

For example:

< # - create a string - > < # assign myStr = "Java classes is for" > < # - using interpolation grammar shows string - > myStr: ${myStr}Copy the code

Macro instruction

Macro is used to create custom directives from templates. (Java backends can implement custom directives by implementing the TemplateDirectiveModel interface, as described in the next article: Freemarker Tutorial ii – Backend Development Guide.)

Macro also creates a variable that can be used as a user – defined greet directive. For example, the following template defines the greet directive:

<#macro greet>
  <h1>Hello class representative</h1>
</#macro>
Copy the code

Use the greet command

< @ greet > < / @ greet > or < @ greet / >Copy the code

Directives can also take arguments:

<#macro greet person>
  <h1>hello ${person}</h1>
</#macro>
Copy the code

Pass the person variable to use:

<@greet person="Java greet "/> and <@greet person="Java greet "/>Copy the code

5. Built-in functions

Built-in functions are the built-in methods FreeMarker provides for different data types to make it easier to display data in templates. When you use a built-in function, you only need to use it after the variable, right? Add the corresponding function name. I’m not going to list all the built-in functions here because of space constraints, but I’ll just pick a few simple examples to show the syntax.

Example 1: Built-in functions for strings

< # - create a string - > < # assign myStr = "Java classes is for" > < # - first letters lowercase - > ${myStr? Uncap_first} <#-- keep string after specified character --> ${myStr? Keep_after ("Java")} <#-- > ${myStr? replace("Java","Freemarker")}Copy the code

Example 2: Time-type built-in functions

<# currentDateTime =.now> <# currentDateTime =.now> date}<br>${currentDateTime? time}<br><#-- show the date and time section --> ${currentDateTime? datetime}<br>${currentDateTime? ${currentDateTime? string("yyyy-MM-dd HH:mm a")}<br>
Copy the code

Example 3: Built-in functions for sequences

< # - sequence type built-in function sample - > < # assign mySequence = [" Java classes is for ", "zhang", "li si", "detective"] > < # - all the elements to specify a delimiter segmentation, output string - > ${mySequence? join(",")}<br>${mySequence? ${mySequence? first}<br>${mySequence? ${mySequence? sort? join(",")}<br>
Copy the code

With the above three simple examples, I believe you have mastered the use of built-in functions, that is, after the variable? The function supported by the addvariable data type. FTL’s built-in functions are extremely rich. The official website lists the supported built-in functions and their usage in detail by data type. You can check the built-in functions on the official website for reference.

For quick access to the related built-in and directives, the class representative has translated from the official website and made a mind map using XMind, into which each function (directive) can be viewed for functional descriptions and samples to greatly improve the efficiency of template development:

The original Xmind files are available on github on behalf of the class, and readers can help themselves on demand.

Class representative highlights! This mind map is the essence of the whole text, be sure to download it!

6. Namespaces

A namespace is a set of variables created using the Assign, Macro, and function directives in the same template. Its main function is to uniquely identify a variable.

There are two ways to create namespaces:

Variables in the same template are in the same namespace.

Take index. FTLH as an example, where variables are created in the same namespace, and values of variables with the same name are overwritten

<#assign myName = "Java "> <#assign myName = "Java ">Copy the code

2. Variables in different templates can be distinguished from namespace variables by the import directive

If you want to use variables from template A and template B, you can use the import directive to define A new namespace for the imported template and access the new namespace using the key specified after as.

For example, the template lib/example.ftlh defines copyright:

<#macro copyright date>
  <p>Copyright (C) ${date} Someone. All rights reserved.</p>
</#macro>

<#assign mail = "[email protected]">
Copy the code

Use copyright in another template, index. FTLH:

<#import "lib/example.ftlh" as e>
<@e.copyright date="1999-2002"/>
${e.mail}
Copy the code

The Life-cycle of namespaces

Namespaces are determined by path (absolute path) in the import directive. If the same path is introduced more than once, the namespace creation is triggered only when import is called for the first time. The import of the same template path refers to the same namespace, for example:

<#import "/lib/example.ftl" as e>
<#import "/lib/example.ftl" as e2>
<#import "/lib/example.ftl" as e3>
${e.mail}, ${e2.mail}, ${e3.mail}
<#assign mail="[email protected]" in e>
${e.mail}, ${e2.mail}, ${e3.mail}
Copy the code

FTL import /lib/example. FTL import to e,e2,e3

Output:

[email protected], [email protected], [email protected]
[email protected], [email protected], [email protected]
Copy the code

The difference between import and include

<#import “lib/example.ftlh” as e> creates a new namespace and encapsulates the variables defined in lib/example.ftlh into the new namespace to provide access, such as: < @ e.c. with our fabrication: opyright date = “1999-2002” / >.

<#include “lib/example.ftlh”> simply inserts example.ftlh into the current template without affecting the namespace

Freemarker official advice:

All places where include is used should be replaced by import

The benefits of using import are as follows:

  • Import an imported template is executed only once. Repeating a reference more than once does not repeat the template execution. For include, template content is executed once per include;
  • Import creates the namespace of the template. When referencing variables, the source of variables can be clearly expressed, reducing the probability of naming conflicts.
  • If you define a number of generic methods, you can use theauto-importConfigure lazy loading and load whichever is used. whileauto-includeLazy loading cannot be achieved, and full loading must be achieved;
  • Import directives have no output, while include may output characters based on template content.

Best practices

1. Null value processing

Handling of null values of variables

For non-existent variables and variables with a value of null, Freemarker uniformly treats them as non-existent values and calls to them will report an error. To avoid this, you can set defaults, for example:

Welcome ${user!” visitor”}!

, the visitor string is displayed when the user does not exist.

Another way is to use?? After the variable. An expression such as user?? Return true if user exists, false otherwise, as in <#if user?? >

Welcome ${user}!

, the welcome banner is not displayed when the user does not exist.

Null value handling in list

When traversing a sequence assuming empty values, freemarker does not directly report an error or display a null value, but searches for a variable of the same name up the scope. This behavior can result in incorrect output, for example:

<#assign x = 20> <#list xs as x> ${x! 'Missing'} </#list>Copy the code

The intention is to display the “missing” string when an empty element in the sequence XS is encountered, but due to freemarker’s upward-looking nature, the empty value will be displayed as 20. To shut down this feature, can be in the server configuration: configuration. SetFallbackOnNullLoopVariable (false);

2. Use import instead of include

As mentioned earlier in the introduction to include, the official recommendation is to import everything you need to include

The include directive is usually used to insert content into the current template. How to implement the include directive with import?

It’s as simple as encapsulating what needs to be inserted into a custom directive.

For example, we define a custom directive myFooter from common.ftlh

<#macro myFooter>
    <hr>
    <p>Here is the footer</p>
</#macro>
Copy the code

Where needed, introduce common.ftlh and call the myFooter directive

<#import "lib/common.ftlh" as common> <#-- include "footer.ftlh">--> < @common.myfooter />Copy the code

3. Alternate color

In data presentation, tables are often used to display data. In order to increase the identification, odd lines and even lines are usually distinguished by different colors, which is called interline discoloration

Here is an example of traversing a sequence:

<#list mySequence = name <#list mySequence = name><span style="color:  ${name?item_cycle("red","blue")} ">   ${name}<br/> </span>
</#list>
    
Copy the code

Item_cycle is a built-in function of the loop variable. For detailed usage, I recommend you to look at the mind map for organizing.

8. To summarize

This article introduces the basic concepts and syntax of Freemarker. It is intended to give newcomers an overview of Freemarker’s data model, built-in functions, and instructions. As long as you can distinguish between these concepts, you can use them as you go, and don’t get lost in a sea of apis at first.

Overall, Freemarker is a relatively simple, easy-to-use template engine, and once you’ve mastered the basic concepts mentioned in this article, it’s perfectly fine to start development directly.

In the process of writing this article, the class representative realized the limitations of pure text expression and the inability to list and translate the API in full, so he put together a mind map that translated the various Freemarker instructions, built-in functions and official website examples. During the normal development process, the development efficiency has been greatly improved. If you need it, please go to github for help.

Finally, attach the mind map town building: