/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jooby.thymeleaf;
import static java.util.Objects.requireNonNull;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.jooby.Env;
import org.jooby.Jooby;
import org.jooby.Renderer;
import org.jooby.View;
import org.thymeleaf.ITemplateEngine;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import com.google.inject.Binder;
import com.google.inject.multibindings.Multibinder;
import com.typesafe.config.Config;
/**
* <h1>thymeleaf</h1>
* <p>
* <a href="http://www.thymeleaf.org">Thymeleaf</a> is a modern server-side Java template engine for
* both web and standalone environments.
* </p>
*
* <h2>exports</h2>
* <ul>
* <li>{@link TemplateEngine}</li>
* <li>{@link View.Engine}</li>
* </ul>
*
* <h2>usage</h2>
*
* <pre>{@code
* {
* use(new Thl());
*
* get("/", () -> {
* return Results.html("index")
* .put("model", new MyModel());
* });
*
* // Or Thymeleaf API:
* get("/thymeleaf-api", () -> {
* TemplateEngine engine = require(TemplateEngine.class);
* engine.processs("template", ...);
* });
* }
* }</pre>
*
* <p>
* Templates are loaded from root of classpath: <code>/</code> and must end with: <code>.html</code>
* file extension. Example:
* </p>
*
* <p>
* public/index.html
* </p>
* <pre>{@code
* <!DOCTYPE html>
* <html xmlns:th="http://www.thymeleaf.org">
* <body>
* <p>
* Hello <span th:text="${model.name}">World</span>!!!
* </p>
* </body>
* </html>
* }</pre>
*
* <h2>template loader</h2>
* <p>
* Templates are loaded from the <code>root</code> of classpath and must end with
* <code>.html</code>. You can change the default template location and/or extensions:
* </p>
*
* <pre>{@code
* {
* use(new Thl("templates", ".thl"));
* }
* }</pre>
*
* <h2>request locals</h2>
* <p>
* A template engine has access to request locals (a.k.a attributes). Here is an example:
* </p>
*
* <pre>{@code
* {
* use(new Thl());
*
* get("*", req -> {
* req.set("foo", bar);
* });
* }
* }</pre>
*
* <p>
* Then from template:
* </p>
*
* <pre>{@code
* <span th:text="${who}">World</span>
* }</pre>
*
* <h2>template cache</h2>
* <p>
* Cache is OFF when <code>env=dev</code> (useful for template reloading), otherwise is ON.
* </p>
*
* <h2>advanced configuration</h2>
* <p>
* Advanced configuration if provided by callback:
* </p>
*
* <pre>{@code
* {
* use(new Thl().doWith(engine -> {
* engine.addDialect(...);
* }));
* }
* }</pre>
*
* @author edgar
*/
public class Thl implements Jooby.Module {
private final String prefix;
private final String suffix;
private BiConsumer<TemplateEngine, Config> callback;
/**
* Creates a new thymeleaf module.
*
* @param prefix Template prefix.
* @param suffix Template suffix.
*/
public Thl(final String prefix, final String suffix) {
this.prefix = Objects.requireNonNull(prefix, "Prefix required.");
this.suffix = Objects.requireNonNull(suffix, "Suffix required.");
}
/**
* Creates a new thymeleaf module.
*/
public Thl() {
this("/", ".html");
}
/**
* Set a configuration callback.
*
* <pre>{@code
* {
* use(new Thl().doWith(engine -> {
* ...
* }));
* }
* }</pre>
*
* @param callback Callback.
* @return This module.
*/
public Thl doWith(final Consumer<TemplateEngine> callback) {
requireNonNull(callback, "Callback required.");
return doWith((e, c) -> callback.accept(e));
}
/**
* Set a configuration callback.
*
* <pre>{@code
* {
* use(new Thl().doWith(engine -> {
* ...
* }));
* }
* }</pre>
*
* @param callback Callback.
* @return This module.
*/
public Thl doWith(final BiConsumer<TemplateEngine, Config> callback) {
this.callback = requireNonNull(callback, "Callback required.");
return this;
}
@Override
public void configure(final Env env, final Config conf, final Binder binder) throws Throwable {
ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
boolean cacheable = !env.name().equals("dev");
/** Defaults: */
resolver.setCacheable(cacheable);
resolver.setPrefix(prefix);
resolver.setSuffix(suffix);
resolver.setTemplateMode(TemplateMode.HTML);
TemplateEngine engine = new TemplateEngine();
engine.setTemplateResolver(resolver);
if (callback != null) {
callback.accept(engine, conf);
}
binder.bind(TemplateEngine.class).toInstance(engine);
binder.bind(ITemplateEngine.class).toInstance(engine);
Multibinder.newSetBinder(binder, Renderer.class)
.addBinding()
.toInstance(new ThlEngine(engine, env));
}
}