Fork me on GitHub

Not all applications are REST based and you might need to generate some HTML.
It is not productive to inline the HTML in strings in your code and concatenate them at request time.
Pippo ships with Freemarker template engine as default and other engines as a builtin alternatives.

To use a template engine is optional and Pippo detects automatically the template engine using ServiceLocator. If you want to use a template engine in your application, you must add pippo-<engine name> as dependency for your project. Other option is to set programmatically the desired template engine using Application#setTemplateEngine(TemplateEngine templateEngine), that in case that you want to create by hand the template engine.

Pippo comes (out of the box) with some template engines:

To use one of these template engines just add a dependency in your project:

<dependency>
    <groupId>ro.pippo</groupId>
    <artifactId>pippo-freemarker</artifactId>
    <version>${pippo.version}</version>
</dependency>

All templates by default are localized in /templates folder (src/main/resources/templates for Maven projects). So, by default the templates are loaded from classpath. See below all templates from pippo-demo-basic project:

$ tree src/main/resources/templates
src/main/resources/templates
├── files.ftl
└── hello.ftl

You can change the location of templates by adding the following line in conf/application.properties:

template.pathPrefix = /

Using above snippet we changed the templates location from /templates to /. Don’t remember that the template will be loaded from classpath as resource.

Below is a code snippet about how you can use a template as response to a request:

GET("/contact/{id}", routeContext -> {
    // retrieve some request's parameters values
    int id = routeContext.getParameter("id").toInt(0);
    String action = routeContext.getParameter("action").toString("new");
    
    // create the template model
    Map<String, Object> model = new HashMap<>();
    model.put("id", id);
    model.put("action", action)
    
    // render the template using data from model
    routeContext.render("contact", model);
});

In above route, Pippo will find a template with name “contact” and will respond with the result of rendering template by the template engine (a String).

Don’t forget that locals variables from a response will be available automatically to all templates for the current request/response cycle. So, maybe the shortest version is:

GET("/contact/{id}", routeContext -> {
    routeContext.setLocal("id", routeContext.getParameter("id").toInt(0));
    routeContext.setLocal("action", routeContext.getParameter("action").toString("new"));
    routeContext.render("contact");
});

From the above code snippets you can see that we call the render method with the template name as parameter but without extension. That means that you can change anytime the template file extension without to change the route handler code. Also, you can change the template engine without to change teh route handler code (for Freemarker template engine you supply a template contact.ftl file, for Pebble template engine you supply a contact.peb file, and so on).

Each template engine has a default file extension but you can change it if you want. For example, PebbleTemplateEngine has peb as default file extension but you can change it in html. The customization can be applied by adding the following line in conf/application.properties:

template.extension = html

For each template engine we expose its configuration. For example Freemarker works with freemarker.template.Configuration and Jade works with de.neuland.jade4j.JadeConfiguration.
In Application#onInit you can create a new instance for a discovered template engine or you can modify its configuration.

public class CrudApplication extends Application {

   @Override
   protected void onInit() {
      FreemarkerTemplateEngine templateEngine = new FreemarkerTemplateEngine();
      try {
         templateEngine.getConfiguration().setDirectoryForTemplateLoading(new File("src/main/resources/templates/"));
      } catch (IOException e) {
         throw new PippoRuntimeException(e);
      }
      setTemplateEngine(templateEngine);

      // add routes
   }

}

or

public class CrudApplication extends Application {

   @Override
   protected void onInit() {
      Configuration configuration = ((FreemarkerTemplateEngine) templateEngine).getConfiguration();
      try {
         configuration.setDirectoryForTemplateLoading(new File("src/main/resources/templates/"));
      } catch (IOException e) {
         throw new PippoRuntimeException(e);
      }

      // add routes
   }

}

You can extend very easy the template engine and add/modify features. See below how you can add a Trimou custom template engine in your application. You can create the MyTrimouTemplateEngine class, like this:

public class MyTrimouTemplateEngine extends TrimouTemplateEngine {
    
    @Override
    protected void init(Application application, Configuration configuration) {
        // your custom logic here
    }
    
}

In your Application/ControllerApplication sub-class, you can do the following:

public class PippoApplication extends ControllerApplication {
    
    @Override
    protected void onInit() {
        setTemplateEngine(new MyTrimouTemplateEngine());
    }

}

If you want how to add a new function Filter(MyUpperFiler) in PebbleTemplateEngine please see this.

In Pippo, each builtin template engine comes with special templates for routing problems and exceptions. See below the special templates that come by default with pippo-freemarker

$ tree src/main/resources/
src/main/resources/
└── templates
    └── pippo
        ├── 000base.ftl
        ├── 400badRequest.ftl
        ├── 401unauthorized.ftl
        ├── 402paymentRequired.ftl
        ├── 403forbidden.ftl
        ├── 404notFound.ftl
        ├── 405methodNotAllowed.ftl
        ├── 409conflict.ftl
        ├── 410gone.ftl
        ├── 500internalError.ftl
        ├── 501notImplemented.ftl
        ├── 502overloaded.ftl
        └── 503serviceUnavailable.ftl

You may override these templates within your own application (put a file with the same name, to same location, in your application classpath). If you feel that is too much to have a template for each error code, you can return the same template for all error code. In this situation you must override DefaultErrorHandler#getTemplateForStatusCode(int statusCode).

By default, all template engines disable the cache in dev and test mode (to speed the development - change template and refresh the page in browser) and enable the cache in prod mode (to improve the performance).

Majority of builtin template engine supplied by Pippo, come with useful functions (extensions) that increase the readability of template:

  • webjarsAt
  • publicAt
  • i18n Read the documentation page allocated to each template engine for more information. The links to individual pages are presented in the start of this page.

If you want to add support for other template engine in your application you must follow below steps:

  • create a new module/project
  • create a class (ex. MyTemplateEngine) that extends AbstractTemplateEngine (or implements TemplateEngine)
  • mark your template engine class as service using one of the methods below
    • automatically via @MetaInfServices annotation (mark your template engine class with this annotation)
    • manually, add file ro.pippo.core.TemplateEngine in src/main/resources/META-INF/services folder with your class name that implements TemplateEngine as content (for Jade the content file is ro.pippo.jade.JadeTemplateEngine).

For more information about how to implement a template engine please see pippo-freemarker and pippo-jade modules.