📜 ⬆️ ⬇️

Freemarker templates

Apache FreeMarker is a template mechanism: a Java library for generating text output (HTML pages, xml, configuration files, source code, etc.) The input is a template, for example html in which there are special expressions, data corresponding to this expression is prepared, and Freemarker dynamically inserts this data and it turns out a dynamically filled document.

image

In the FreeMarker article
Spring boot
Macros
REST API

Those. a simple expression on a freemarker is for example $ {name}, expressions support calculations, comparison operations, conditions, cycles, lists, built-in functions, macros, and many others. Example html with $ {name} expression (template test.ftl):
')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>${name}!</title> </head> <body> <h2>Hello ${name}!</h2> </body> </html> 

If we now create a data model in java:

 import freemarker.template.Configuration; import freemarker.template.Template; ... //  Configuration cfg = new Configuration(Configuration.VERSION_2_3_27); //   Map<String, Object> root = new HashMap<>(); root.put("name", "Freemarker"); //  Template temp = cfg.getTemplate("test.ftl"); //      Writer out = new OutputStreamWriter(System.out); //    temp.process(root, out); 

then we get an html document with the filled name.

If the list needs to be processed, the #list construction is used, for example for the html list:

 <ul> <#list father as item> <li>${item}</li> </#list> </ul> 

In java, you can submit a list to the data model

 Map<String, Object> root = new HashMap<>(); .... root.put("father", Arrays.asList("Alexander", "Petrov", 47)); 

Let's go to Spring


Spring boot has support for Freemarker. On the SPRING INITIALIZR site you can get the pom project file

pom file
 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>demoFreeMarker</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demoFreeMarker</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 


DemoFreeMarkerApplication class
 @SpringBootApplication public class DemoFreeMarkerApplication { public static void main(String[] args) { SpringApplication.run(DemoFreeMarkerApplication.class, args); } } 


In Spring, there is an already prepared configuration component Configuration for freemarker. For the example of a console application, I will take the spring interface for processing the command line (CommandLineRunner) and prepare the data model for the following ftl template (hello_test.ftl):

Hello_test.ftl template
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hello ${name}!</title> </head> <body> <input type="text" placeholder="${name}"> <table> <#list persons as row> <tr> <#list row as field> <td>${field}</td> </#list> </tr> </#list> </table> </body> </html> 


Java code for the hello_test.ftl template data model:

CommandLine class and data model
 @Component public class CommandLine implements CommandLineRunner { @Autowired private Configuration configuration; public void run(String... args) { Map<String, Object> root = new HashMap<>(); //  ${name} root.put("name", "Fremarker"); //  <#list persons List<List> persons = new ArrayList<>(); persons.add(Arrays.asList("Alexander", "Petrov", 47)); persons.add(Arrays.asList("Slava", "Petrov", 13)); root.put("persons", persons); try { Template template = configuration.getTemplate("hello_test.ftl"); Writer out = new OutputStreamWriter(System.out); try { template.process(root, out); } catch (TemplateException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } } } 


After processing, we get an html document:

Output html
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hello Fremarker!</title> </head> <body> <input type="text" placeholder="Fremarker"> <table> <tr> <td>Alexander</td> <td>Petrov</td> <td>47</td> </tr> <tr> <td>Slava</td> <td>Petrov</td> <td>13</td> </tr> </table> </body> 


Macros


In freemarker there is support for macros, this is a very convenient and strong side of it, and it is simply necessary to use it.

A simple example:

 <#macro textInput id value=""> <input type="text" id="${id}" value="${value}"> </#macro> 

This is a macro with the name textInput and the parameters id (it is mandatory) and value (it is optional, because it has a default value). Next is his body and the use of input parameters. In the template, the file with macros is connected as follows:

 <#import "ui.ftl" as ui/> 

From the template, the macro is called like this:

 <@ui.textInput id="name" value="${name}"/> 

Where ui is the alias that was specified when connecting, the $ {name} variable in the model, then through the alias we refer to the name of the textInput macro and specify its parameters, which are at least mandatory. I will prepare simple macros for html Input and Table:

ui.ftl macro file
 <#-- textInput macro for html input --> <#macro textInput id placeholder="" value=""> <input type="text" id="${id}" placeholder="${placeholder}" value="${value}"> </#macro> <#-- table macro for html table --> <#macro table id rows> <table id="${id}"> <#list rows as row> <tr> <td>${row?index + 1}</td> <#list row as field> <td>${field}</td> </#list> </tr> </#list> </table> </#macro> 


$ {row? index + 1} is built-in support for the index of the list item, there are many similar built-in functions. If we now change the previous main template and replace the input and table in it with macros, we get the following document:

Hello.ftl template
 <#import "ui.ftl" as ui/> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hello ${name}!</title> </head> <body> <@ui.textInput id="name" placeholder="Enter name" value="${name}"/> <@ui.table id="table1" rows=persons/> </body> </html> 


REST


Of course, this model is convenient to use in a web application. I connect dependence in pom:

  <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> 

Adding a REST Controller:

DemoController.java
 @Controller public class DemoController { @Autowired private RepositoryService repositoryService; @GetMapping("/") public String index() { return "persons"; } @RequestMapping(value = "/search", method = RequestMethod.POST) public String hello(Model model, @RequestParam(defaultValue = "") String searchName) { List<List<String>> persons = repositoryService.getRepository(); List<List<String>> filterList = persons.stream() .filter(p -> p.get(0).contains(searchName)) .collect(Collectors.toList()); model.addAttribute("persons", filterList); model.addAttribute("lastSearch", searchName); return "persons"; } @RequestMapping(value = "/save", method = RequestMethod.POST) public String save(Model model, @ModelAttribute("person") Person person) { List<List<String>> persons = repositoryService.addPerson(person); model.addAttribute("persons", persons); return "persons"; } } 


Service repository for individuals:

RepositoryService.java
 @Service public class RepositoryService { private static List<List<String>> repository = new ArrayList<>(); public List<List<String>> getRepository() { return repository; } public List<List<String>> addPerson(Person person) { repository.add(Arrays.asList(person.getFirstName(), person.getAge().toString())); return repository; } } 


Class face:

Person.java
 public class Person { public Person(String firstName, Integer age) { this.firstName = firstName; this.age = age; } private String firstName; private Integer age; public String getFirstName() { return firstName; } public Integer getAge() { return age; } } 


Macro template:

ui.ftl
 <#macro formInput id name label type="text" value=""> <label for="${id}">${label}</label> <input type="${type}" id="${id}" name="${name}" value="${value}"> </#macro> <#macro table id rows> <table id="${id}" border="1px" cellspacing="2" border="1" cellpadding="5"> <#list rows as row> <tr> <td>${row?index + 1}</td> <#list row as field> <td>${field}</td> </#list> </tr> </#list> </table> </#macro> 


Main template:

persons.ftl
 <#import "ui.ftl" as ui/> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Person</title> <link href="style/my.css" rel="stylesheet"> </head> <body> <div> <fieldset> <legend> </legend> <form name="person" action="save" method="POST"> <@ui.formInput id="t1" name="firstName" label=""/> <br/> <@ui.formInput id="t2" name="age" label=""/> <br/> <input type="submit" value="Save" /> </form> </fieldset> </div> <div> <fieldset> <legend></legend> <form name="searchForm" action="search" method="POST"> <@ui.formInput id="t3" name="searchName" label=""/> <br/> <input type="submit" value="Search" /> </form> </fieldset> </div> <p><#if lastSearch??> : ${lastSearch}<#else></#if></p> <@ui.table id="table1" rows=persons![]/> </body> </html> 


Project structure:

image

The application will process two commands “save” and “search” of a person (see controller). All work on processing (mapping) input parameters takes Spring.

Some explanations for the template.

 <#if lastSearch??> : ${lastSearch}<#else></#if> 

it is checked here, if the parameter is set, then print the phrase “Search for: ..”, otherwise nothing:

 <@ui.table id="table1" rows=persons![]/> 

there is also a check that the list of persons is present, otherwise it is empty. These checks are important when you first open the page, otherwise you would have to initialize them in the controller’s index ().

Application operation

image

Materials:

→ Apache FreeMarker Manual

Source: https://habr.com/ru/post/420549/


All Articles