This article is the first part of Freemarker series. It is aimed at Template developers. It mainly introduces the FTL(Freemarker Template Language) syntax used by Freemarker, and understands the basic concepts of Freemarker. Introducing basic FTL terms and built-in functions, built-in instructions, easy to use as a development manual quick check (the version used for the demonstration in this paper is 2.3.30, in actual use, please check the official website according to your own project version).

This article will not list the official website API, but only demonstrate its syntax when necessary. There is a Freemarker API mind map organized by the class representative in the code project. With this article, you can make the power increase! Please pick it up from the course representative’s github.

1. A FreeMarker is what

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

Its main task is: the template and data assembly together, generate documents, this process is also called Render. The process is 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. Simplest template

Suppose I want a simple page to welcome the current user. The template code is:

< HTML > < head > < title > index < / title > < / head > < body > < p > hello, ${userName} < / p > < / body > < / HTML >

${userName} is the interpolation syntax for FTL. It replaces the value of userName with the generated HTML to display different usernames, depending on who is currently logged in. This value is put into the Model by the back-end code, corresponding to the Controlelr code:

@Controller public class HelloWorld { @GetMapping("hello") public String hello(Model model) { Model. AddatAttribute ("userName","Java class representative "); Return "index"; }}

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. The relationship between the two is controlled by the Controller, which is MVC.

3. Data-model

How is the data added to the model in the Controller organized? This requires an understanding of FTL’s data-model.

The FTL data model is structured as a tree:

(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"

The root of the Controller can be defined as model. AddatAttribute (“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 hashes. The key of the hash is the name of the variable, and the value is the value that the variable stores. A delimited path can access variable values, such as the price of mouse :animals.mouse.price.

Variables that store a single value, such as animals.mouse.price, are called scalars. Scalars have four specific types: string, Boolean, date-like, number;

There is also a variable called a sequence, which can be likened to an array in Java. Each item in a sequence has no name and can be accessed by either traversal or subscript (sequence access will be shown later). The 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"

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

Once you have the data, you also have the syntax to organize the data. Here’s a look at the syntax commonly used in FTL.

4. The FTL syntax

Freemarker only recognizes the following three grammars:

  1. The interpolation: ${… }, Freemarker will replace the inside variable with the actual value
  2. FTL Tags (Tags) : Tags that are similar in structure to HTML are used<>Wrap up, ordinary label with< #Begin with a user-defined tag withThe < @At the beginning, such as<#if true>true thing<#/if>.<@myDirect></@myDirect>

You’ll see two kinds of names: tags and directives. Here’s an example:
<#if></#if>Call tag; Inside the label
ifIs a directive, analogous to a tag in HTML (e.g.
<table></table>) and elements (e.g.
table). However, think of tags and instructions as yes
A synonym forNo problem.

  1. Comments: The Comments in FTL look like this:<#-- Comment out content -->For comments, FTL will automatically skip them, so they will not show up in the generated text (unlike HTML comments)

Note: Everything except the above three syntaxes is treated as plain text by Freemarker, and plain text will be printed as it is

Interpolation is simply replacing the value of a variable. Comments are not much to say. Here’s a look at some of the most common FTL tags (directives) and how to use them in code.

If the instructions

If you can skip a block of code in the template depending on the condition. For example, when the userName value is “Java class representative “or zhengxl5566, it is shown in a special style. The corresponding template code is as follows:

< p > hello, <#if userName == "> <strong>${userName}</strong> <#elseif userName == "zhengxl5566"> <h1>${userName}</h1> <#else> ${userName} </#if> </p>

The list instruction

List is used to traverse a sequence. Its syntax is:

<#list sequence as loopVariable>
    repeatThis
</#list>

So let’s say we put a collection of allUsers in our model in the background

model.addAttribute("allUsers",userService.getAllUser());

${allUsers[0].name} ${allUsers[0].name}

You can also traverse the display directly in the template:

<ol> <#list allUsers as user> <li> Name: ${user.name} Age: ${user.age} </li> </#list> </ol>

The actual rendered HTML:

<ol> <li b> Name: ZXL, age: 18 </li> <li> Name: ls, age: 19 </li> <li> Name: zs, age: 16 </li> </ol>

Note: Assuming allUsers is empty, the rendered page will be
<ol></ol>If you need to circumvent this situation, you can use the items tag

< # list allUsers > < ol > < # items as user > < li > name: ${user. The name}, age: ${user. Age} < / li > < / # items > < / ol > < / # list >

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

The include directive

Include directives can insert the contents of one template into another (the official recommendation is to use import instead, see Best Practices below). FTLH: If we need a footer for every page, we can write a public footer. ftLH template. For the rest of the pages that need footers, we can simply refer to the footer. ftLH template:

<#include "footer.ftlh">

The import command

Import can import 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 (we’ll see how import and include compare later in this article).

/libs/commons.ftl /libs/commons.ftl /libs/commons.ftl /libs/commons.ftl /libs/commons.ftl /libs/commons.ftl /libs/commons.ftl /libs/commons.ftl /libs/commons.ftl

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

If you want to use the copyright method in /libs/commons.ftl, you can use it directly:

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

The assign instructions

Assign can be used to create a new variable and assign a value to it. 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>

For example:

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

Macro instruction

Macro is used to create user-defined directives from templates (the Java backend can be customized by implementing the TemplateDirectivemodel interface, which will be covered in the next article: Freemarker Tutorial 2 – Backend Development Guide).

Macro also creates variables that can be used as user-defined directives, such as the greet directive in the following template:

<#macro greet> <h1>hello </h1> </#macro>

Use GREET command

< @ greet > < / @ greet > or < @ greet / >

Instructions can also have arguments:

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

Passed in the person variable when used:

<@greet person="Java课代表"/> and <@greet person="zhengxl5566"/>

5. Built-in functions

Built-in functions are the built-in methods that Freemarker provides for different data types to make it easier to display data in a template. When you use a built-in function, you only need to use it after a variable, right? Add the corresponding function name. I’m not going to cover all of the built-in functions here, so 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} <#-- Preserve the string after the specified character --> ${myStr? Keep_after ("Java")} <#-- Replace specified character --> ${myStr? replace("Java","Freemarker")}

Example 2: Time-type built-in functions

<#-- fetching the current time (if you pass the data to the data-model, you can pass the Date type)--> <#assign currentDateTime =.now> <#-- display the Date part --> ${currentDateTime? Date}<br> <#-- > ${currentDateTime? ${currentDateTime? ${currentDateTime? ${currentDateTime? ${currentDateTime}<br> <#-- > ${currentDateTime? string("yyyy-MM-dd HH:mm a")}<br>

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? First}<br> <#-- ${mySequence? sort? join(",")}<br>

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

In order to make it easy for you to quickly read the relevant built-in functions and directives, the class representative translated from the official website and made a mind map using Xmind. Each function (instruction) can be clicked in to view the function description and sample, which can greatly improve the efficiency of template development:

The original Xmind file is on the course representative’s GitHub, and readers can pick it up on demand.

Class representative highlights! This mind map is the essence of this article. Be sure to download it and check it out!

6. Namespaces

A namespace is a collection of variables created in the same template using the assign, macro, and function directives. The main purpose of this namespace is to uniquely identify a single variable.

There are two ways to create namespaces:

1. Variables in the same template are in the same namespace.

Take the example of index.ftlh below, where all variables are created in the same namespace, and the values of the variables with the same name are overwritten accordingly

<#assign myName = "Java rep "> <#assign myName = "Java rep "> <#assign myName = "--> ${myName}

2, Variables in different templates can be distinguished by different namespace variables through the import directive

If you want to use variables in template A from template B, you can use the import directive to define A new namespace for the incoming template, which is accessed through 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]">

Index.ftLH: copyright:

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

The Life-Cycle of Namespaces

The namespace is determined by the path in the import directive (the absolute path). If the same path is introduced multiple times, the creation of the corresponding namespace will only be triggered on the first call to import. Subsequent imports with the same template path refer 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}

* /lib/example.ftl import * /lib/example.ftl import * /lib/example.ftl import * /lib/example.ftl import * /lib/example.ftl import * /lib/example.ftl import * /lib/example.ftl import

Output:

[email protected], [email protected], [email protected]
[email protected], [email protected], [email protected]

The difference between an import and an include

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

<#include “lib/example.ftlh”> simply inserts the contents of example.ftlh into the current template and does not affect the namespace

Freemarker’s official advice:

All areas where include is used should be replaced by imports

The benefits of using import are as follows:

  • A template imported by an import is executed only once. Repeating multiple references does not duplicate the template. In the case of include, the template content is executed once per include;
  • Import can create the template’s namespace, and when referring to variables, it can clearly express the source of variables, reducing the probability of naming conflicts.
  • If you have defined a lot of generic methods, you can useauto-importConfigure lazy loading, load whatever is used. whileauto-includeCan not achieve lazy loading, must be full load;
  • An import directive doesn’t have any output, while an include might output characters based on the template content.

7. Best practices

1. Null value processing

Handling of null values for variables

For variables that do not exist and variables that are null, Freemarker assumes that they do not exist, and calls to them will return an error. To avoid this, you can set the default value, for example:

Welcome ${user!” visitor”}!

, the visitor string will be displayed when the user does not exist.

Another way is to use?? After a variable. Expression, such as user?? Returns true if user exists, false otherwise. Example: <#if user?? >

Welcome ${user}!

, where user does not exist, does not display the welcome sign.

Null value processing in List

When traversal a sequence, Freemarker does not directly report an error or display a null value assuming that there is a null value in the sequence. Instead, it searches for the value of a variable with the same name in a higher scope. This behavior may result in incorrect output, for example:

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

The intention is to display the “missing” string when an empty element is encountered in sequence XS, but due to Freemarker’s lookup feature, the null value here 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

The official recommendation is to use import for everything you need to include

The main purpose of the include directive is to insert a section of content into the current template.

Simply encapsulate what needs to be inserted into a custom directive.

For example, let’s define a custom directive myFooter from common.ftlh

<#macro myFooter> <hr> <p> > footer</p> </#macro>

Where you need to use it, introduce common.ftlh and call the myFooter directive

<#import "lib/common.ftlh" as common> <#-- Include with import --> <#--<#include "footer.ftlh">--> < @common.myfooter />

3. Change color between rows

In data display, tables are often used to display data. In order to increase recognition, the odd and even rows are usually distinguished by different colors, which is interrow color

Take a traversal sequence as an example:

<#assign mySequence = [" mySequence "," mySequence "," mySequence "," mySequence "," mySequence "," mySequence "," mySequence "," mySequence "," mySequence "," mySequence "," mySequence "," mySequence "," mySequence "," mySequence "," mySequence "; item_cycle("red","blue")}"> ${name}<br/> </span> </#list>

Item_cycle is a built-in function for the loop variable. For more details on how to use it, I again refer you to the mind map for sorting.

8. To summarize

This paper introduces the basic concepts and basic grammar of Freemarker, aiming to let the new users have a global understanding of Freemarker, and understand the data model, built-in functions and instructions of Freemarker. As long as you can distinguish these concepts, you can use them on the spot in the actual development. Don’t get lost in the massive API at the beginning.

In general, Freemarker is a relatively simple and easy to use templating engine, as long as you have mastered the basic concepts mentioned in this article, it is absolutely no problem to start developing directly.

In the process of writing this paper, the course representative realized the limitations of pure text expression and could not list and translate the API in full text. Therefore, he arranged a mind map and translated all kinds of instructions, built-in functions and examples of Freemarker’s official website into it. In the development process, the development efficiency has been greatly improved. If you need it, please get it from the GitHub of the course representative.


👇 Follow the Java class representatives to get the latest Java articles