Skip to content

Commit d984d0e

Browse files
authored
Merge pull request #587 from jooby-project/thymeleaf
[ViewEngine] Add support for Thymeleaf 3 fix #563
2 parents 7a1fe87 + 0cb921c commit d984d0e

File tree

28 files changed

+977
-21
lines changed

28 files changed

+977
-21
lines changed

coverage-report/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
<source>${project.parent.basedir}/jooby-scanner/src/main/java</source>
8585
<source>${project.parent.basedir}/jooby-csl/src/main/java</source>
8686
<source>${project.parent.basedir}/jooby-unbescape/src/main/java</source>
87+
<source>${project.parent.basedir}/jooby-thymeleaf/src/main/java</source>
8788
</sources>
8889
</configuration>
8990
</execution>
@@ -143,6 +144,7 @@
143144
<source>${project.parent.basedir}/jooby-scanner/src/test/java</source>
144145
<source>${project.parent.basedir}/jooby-csl/src/test/java</source>
145146
<source>${project.parent.basedir}/jooby-unbescape/src/test/java</source>
147+
<source>${project.parent.basedir}/jooby-thymeleaf/src/test/java</source>
146148
</sources>
147149
</configuration>
148150
</execution>
@@ -572,6 +574,12 @@
572574
<version>${project.version}</version>
573575
</dependency>
574576

577+
<dependency>
578+
<groupId>org.jooby</groupId>
579+
<artifactId>jooby-thymeleaf</artifactId>
580+
<version>${project.version}</version>
581+
</dependency>
582+
575583
<dependency>
576584
<groupId>org.avaje</groupId>
577585
<artifactId>avaje-agentloader</artifactId>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.jooby.issues;
2+
3+
import org.jooby.Results;
4+
import org.jooby.test.ServerFeature;
5+
import org.jooby.thymeleaf.Thl;
6+
import org.junit.Test;
7+
8+
public class Issue563 extends ServerFeature {
9+
10+
{
11+
use(new Thl());
12+
13+
get("/", req -> Results.html("org/jooby/thl/index").put("model", req.param("model").value()));
14+
}
15+
16+
@Test
17+
public void thl() throws Exception {
18+
request()
19+
.get("/?model=jooby")
20+
.expect(
21+
"<!DOCTYPE html>\n" +
22+
"<html>\n" +
23+
"<body>\n" +
24+
"<p>\n" +
25+
" Hello <span>jooby</span>!!!\n" +
26+
"</p>\n" +
27+
"</body>\n" +
28+
"</html>\n" +
29+
"");
30+
}
31+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!DOCTYPE html>
2+
<html xmlns:th="http://www.thymeleaf.org">
3+
<body>
4+
<p>
5+
Hello <span th:text="${model}">World</span>!!!
6+
</p>
7+
</body>
8+
</html>

jooby-thymeleaf/pom.xml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4+
5+
<parent>
6+
<groupId>org.jooby</groupId>
7+
<artifactId>jooby-project</artifactId>
8+
<version>1.0.1-SNAPSHOT</version>
9+
</parent>
10+
11+
<modelVersion>4.0.0</modelVersion>
12+
<artifactId>jooby-thymeleaf</artifactId>
13+
14+
<name>thymeleaf module</name>
15+
16+
<build>
17+
<plugins>
18+
<!-- sure-fire -->
19+
<plugin>
20+
<groupId>org.apache.maven.plugins</groupId>
21+
<artifactId>maven-surefire-plugin</artifactId>
22+
<configuration>
23+
<includes>
24+
<include>**/*Test.java</include>
25+
<include>**/*Feature.java</include>
26+
<include>**/Issue*.java</include>
27+
</includes>
28+
</configuration>
29+
</plugin>
30+
31+
</plugins>
32+
</build>
33+
34+
<dependencies>
35+
<!-- Jooby -->
36+
<dependency>
37+
<groupId>org.jooby</groupId>
38+
<artifactId>jooby</artifactId>
39+
<version>${project.version}</version>
40+
</dependency>
41+
42+
<!-- Thymeleaf -->
43+
<dependency>
44+
<groupId>org.thymeleaf</groupId>
45+
<artifactId>thymeleaf</artifactId>
46+
</dependency>
47+
48+
<!-- Test dependencies -->
49+
<dependency>
50+
<groupId>org.jooby</groupId>
51+
<artifactId>jooby</artifactId>
52+
<version>${project.version}</version>
53+
<scope>test</scope>
54+
<classifier>tests</classifier>
55+
</dependency>
56+
57+
<dependency>
58+
<groupId>junit</groupId>
59+
<artifactId>junit</artifactId>
60+
<scope>test</scope>
61+
</dependency>
62+
63+
<dependency>
64+
<groupId>org.easymock</groupId>
65+
<artifactId>easymock</artifactId>
66+
<scope>test</scope>
67+
</dependency>
68+
69+
<dependency>
70+
<groupId>org.powermock</groupId>
71+
<artifactId>powermock-api-easymock</artifactId>
72+
<scope>test</scope>
73+
</dependency>
74+
75+
<dependency>
76+
<groupId>org.powermock</groupId>
77+
<artifactId>powermock-module-junit4</artifactId>
78+
<scope>test</scope>
79+
</dependency>
80+
81+
<dependency>
82+
<groupId>org.jacoco</groupId>
83+
<artifactId>org.jacoco.agent</artifactId>
84+
<classifier>runtime</classifier>
85+
<scope>test</scope>
86+
</dependency>
87+
88+
<dependency>
89+
<groupId>org.jooby</groupId>
90+
<artifactId>jooby-netty</artifactId>
91+
<version>${project.version}</version>
92+
<scope>test</scope>
93+
</dependency>
94+
</dependencies>
95+
96+
</project>
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package org.jooby.thymeleaf;
2+
3+
import static java.util.Objects.requireNonNull;
4+
5+
import java.util.Objects;
6+
import java.util.function.BiConsumer;
7+
import java.util.function.Consumer;
8+
9+
import org.jooby.Env;
10+
import org.jooby.Jooby;
11+
import org.jooby.Renderer;
12+
import org.jooby.View;
13+
import org.thymeleaf.ITemplateEngine;
14+
import org.thymeleaf.TemplateEngine;
15+
import org.thymeleaf.templatemode.TemplateMode;
16+
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
17+
18+
import com.google.inject.Binder;
19+
import com.google.inject.multibindings.Multibinder;
20+
import com.typesafe.config.Config;
21+
22+
/**
23+
* <h1>thymeleaf</h1>
24+
* <p>
25+
* <a href="http://www.thymeleaf.org">Thymeleaf</a> is a modern server-side Java template engine for
26+
* both web and standalone environments.
27+
* </p>
28+
*
29+
* <h2>exports</h2>
30+
* <ul>
31+
* <li>{@link TemplateEngine}</li>
32+
* <li>{@link View.Engine}</li>
33+
* </ul>
34+
*
35+
* <h2>usage</h2>
36+
*
37+
* <pre>{@code
38+
* {
39+
* use(new Thl());
40+
*
41+
* get("/", () -> {
42+
* return Results.html("index")
43+
* .put("model", new MyModel());
44+
* });
45+
*
46+
* // Or Thymeleaf API:
47+
* get("/thymeleaf-api", () -> {
48+
* TemplateEngine engine = require(TemplateEngine.class);
49+
* engine.processs("template", ...);
50+
* });
51+
* }
52+
* }</pre>
53+
*
54+
* <p>
55+
* Templates are loaded from root of classpath: <code>/</code> and must end with: <code>.html</code>
56+
* file extension. Example:
57+
* </p>
58+
*
59+
* <p>
60+
* public/index.html
61+
* </p>
62+
* <pre>{@code
63+
* <!DOCTYPE html>
64+
* <html xmlns:th="http://www.thymeleaf.org">
65+
* <body>
66+
* <p>
67+
* Hello <span th:text="${model.name}">World</span>!!!
68+
* </p>
69+
* </body>
70+
* </html>
71+
* }</pre>
72+
*
73+
* <h2>template loader</h2>
74+
* <p>
75+
* Templates are loaded from the <code>root</code> of classpath and must end with
76+
* <code>.html</code>. You can change the default template location and/or extensions:
77+
* </p>
78+
*
79+
* <pre>{@code
80+
* {
81+
* use(new Thl("templates", ".thl"));
82+
* }
83+
* }</pre>
84+
*
85+
* <h2>request locals</h2>
86+
* <p>
87+
* A template engine has access to request locals (a.k.a attributes). Here is an example:
88+
* </p>
89+
*
90+
* <pre>{@code
91+
* {
92+
* use(new Thl());
93+
*
94+
* get("*", req -> {
95+
* req.set("foo", bar);
96+
* });
97+
* }
98+
* }</pre>
99+
*
100+
* <p>
101+
* Then from template:
102+
* </p>
103+
*
104+
* <pre>{@code
105+
* <span th:text="${who}">World</span>
106+
* }</pre>
107+
*
108+
* <h2>template cache</h2>
109+
* <p>
110+
* Cache is OFF when <code>env=dev</code> (useful for template reloading), otherwise is ON.
111+
* </p>
112+
*
113+
* <h2>advanced configuration</h2>
114+
* <p>
115+
* Advanced configuration if provided by callback:
116+
* </p>
117+
*
118+
* <pre>{@code
119+
* {
120+
* use(new Thl().doWith(engine -> {
121+
* engine.addDialect(...);
122+
* }));
123+
* }
124+
* }</pre>
125+
*
126+
* @author edgar
127+
*/
128+
public class Thl implements Jooby.Module {
129+
130+
private final String prefix;
131+
132+
private final String suffix;
133+
134+
private BiConsumer<TemplateEngine, Config> callback;
135+
136+
/**
137+
* Creates a new thymeleaf module.
138+
*
139+
* @param prefix Template prefix.
140+
* @param suffix Template suffix.
141+
*/
142+
public Thl(final String prefix, final String suffix) {
143+
this.prefix = Objects.requireNonNull(prefix, "Prefix required.");
144+
this.suffix = Objects.requireNonNull(suffix, "Suffix required.");
145+
}
146+
147+
/**
148+
* Creates a new thymeleaf module.
149+
*/
150+
public Thl() {
151+
this("/", ".html");
152+
}
153+
154+
/**
155+
* Set a configuration callback.
156+
*
157+
* <pre>{@code
158+
* {
159+
* use(new Thl().doWith(engine -> {
160+
* ...
161+
* }));
162+
* }
163+
* }</pre>
164+
*
165+
* @param callback Callback.
166+
* @return This module.
167+
*/
168+
public Thl doWith(final Consumer<TemplateEngine> callback) {
169+
requireNonNull(callback, "Callback required.");
170+
return doWith((e, c) -> callback.accept(e));
171+
}
172+
173+
/**
174+
* Set a configuration callback.
175+
*
176+
* <pre>{@code
177+
* {
178+
* use(new Thl().doWith(engine -> {
179+
* ...
180+
* }));
181+
* }
182+
* }</pre>
183+
*
184+
* @param callback Callback.
185+
* @return This module.
186+
*/
187+
public Thl doWith(final BiConsumer<TemplateEngine, Config> callback) {
188+
this.callback = requireNonNull(callback, "Callback required.");
189+
return this;
190+
}
191+
192+
@Override
193+
public void configure(final Env env, final Config conf, final Binder binder) throws Throwable {
194+
ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
195+
boolean cacheable = !env.name().equals("dev");
196+
/** Defaults: */
197+
resolver.setCacheable(cacheable);
198+
resolver.setPrefix(prefix);
199+
resolver.setSuffix(suffix);
200+
resolver.setTemplateMode(TemplateMode.HTML);
201+
202+
TemplateEngine engine = new TemplateEngine();
203+
engine.setTemplateResolver(resolver);
204+
205+
if (callback != null) {
206+
callback.accept(engine, conf);
207+
}
208+
209+
binder.bind(TemplateEngine.class).toInstance(engine);
210+
binder.bind(ITemplateEngine.class).toInstance(engine);
211+
212+
Multibinder.newSetBinder(binder, Renderer.class)
213+
.addBinding()
214+
.toInstance(new ThlEngine(engine, env));
215+
}
216+
}

0 commit comments

Comments
 (0)