275 lines
8.3 KiB
Plaintext
275 lines
8.3 KiB
Plaintext
[[mvc-view-script]]
|
|
= Script Views
|
|
|
|
[.small]#xref:web/webflux-view.adoc#webflux-view-script[See equivalent in the Reactive stack]#
|
|
|
|
The Spring Framework has a built-in integration for using Spring MVC with any
|
|
templating library that can run on top of the
|
|
{JSR}223[JSR-223] Java scripting engine. We have tested the following
|
|
templating libraries on different script engines:
|
|
|
|
[%header]
|
|
|===
|
|
|Scripting Library |Scripting Engine
|
|
|https://handlebarsjs.com/[Handlebars] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
|
|https://mustache.github.io/[Mustache] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
|
|https://facebook.github.io/react/[React] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
|
|https://www.embeddedjs.com/[EJS] |https://openjdk.java.net/projects/nashorn/[Nashorn]
|
|
|https://www.stuartellis.name/articles/erb/[ERB] |https://www.jruby.org[JRuby]
|
|
|https://docs.python.org/2/library/string.html#template-strings[String templates] |https://www.jython.org/[Jython]
|
|
|https://github.com/sdeleuze/kotlin-script-templating[Kotlin Script templating] |{kotlin-site}[Kotlin]
|
|
|===
|
|
|
|
TIP: The basic rule for integrating any other script engine is that it must implement the
|
|
`ScriptEngine` and `Invocable` interfaces.
|
|
|
|
|
|
|
|
[[mvc-view-script-dependencies]]
|
|
== Requirements
|
|
[.small]#xref:web/webflux-view.adoc#webflux-view-script-dependencies[See equivalent in the Reactive stack]#
|
|
|
|
You need to have the script engine on your classpath, the details of which vary by script engine:
|
|
|
|
* The https://openjdk.java.net/projects/nashorn/[Nashorn] JavaScript engine is provided with
|
|
Java 8+. Using the latest update release available is highly recommended.
|
|
* https://www.jruby.org[JRuby] should be added as a dependency for Ruby support.
|
|
* https://www.jython.org[Jython] should be added as a dependency for Python support.
|
|
* `org.jetbrains.kotlin:kotlin-script-util` dependency and a `META-INF/services/javax.script.ScriptEngineFactory`
|
|
file containing a `org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory`
|
|
line should be added for Kotlin script support. See
|
|
https://github.com/sdeleuze/kotlin-script-templating[this example] for more details.
|
|
|
|
You need to have the script templating library. One way to do that for JavaScript is
|
|
through https://www.webjars.org/[WebJars].
|
|
|
|
|
|
|
|
[[mvc-view-script-integrate]]
|
|
== Script Templates
|
|
[.small]#xref:web/webflux-view.adoc#webflux-view-script[See equivalent in the Reactive stack]#
|
|
|
|
You can declare a `ScriptTemplateConfigurer` bean to specify the script engine to use,
|
|
the script files to load, what function to call to render templates, and so on.
|
|
The following example uses Mustache templates and the Nashorn JavaScript engine:
|
|
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
@Configuration
|
|
@EnableWebMvc
|
|
public class WebConfig implements WebMvcConfigurer {
|
|
|
|
@Override
|
|
public void configureViewResolvers(ViewResolverRegistry registry) {
|
|
registry.scriptTemplate();
|
|
}
|
|
|
|
@Bean
|
|
public ScriptTemplateConfigurer configurer() {
|
|
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
|
configurer.setEngineName("nashorn");
|
|
configurer.setScripts("mustache.js");
|
|
configurer.setRenderObject("Mustache");
|
|
configurer.setRenderFunction("render");
|
|
return configurer;
|
|
}
|
|
}
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
@Configuration
|
|
@EnableWebMvc
|
|
class WebConfig : WebMvcConfigurer {
|
|
|
|
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
|
registry.scriptTemplate()
|
|
}
|
|
|
|
@Bean
|
|
fun configurer() = ScriptTemplateConfigurer().apply {
|
|
engineName = "nashorn"
|
|
setScripts("mustache.js")
|
|
renderObject = "Mustache"
|
|
renderFunction = "render"
|
|
}
|
|
}
|
|
----
|
|
======
|
|
|
|
The following example shows the same arrangement in XML:
|
|
|
|
[source,xml,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
<mvc:annotation-driven/>
|
|
|
|
<mvc:view-resolvers>
|
|
<mvc:script-template/>
|
|
</mvc:view-resolvers>
|
|
|
|
<mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render">
|
|
<mvc:script location="mustache.js"/>
|
|
</mvc:script-template-configurer>
|
|
----
|
|
|
|
The controller would look no different for the Java and XML configurations, as the following example shows:
|
|
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
@Controller
|
|
public class SampleController {
|
|
|
|
@GetMapping("/sample")
|
|
public String test(Model model) {
|
|
model.addAttribute("title", "Sample title");
|
|
model.addAttribute("body", "Sample body");
|
|
return "template";
|
|
}
|
|
}
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
@Controller
|
|
class SampleController {
|
|
|
|
@GetMapping("/sample")
|
|
fun test(model: Model): String {
|
|
model["title"] = "Sample title"
|
|
model["body"] = "Sample body"
|
|
return "template"
|
|
}
|
|
}
|
|
----
|
|
======
|
|
|
|
The following example shows the Mustache template:
|
|
|
|
[source,html,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
<html>
|
|
<head>
|
|
<title>{{title}}</title>
|
|
</head>
|
|
<body>
|
|
<p>{{body}}</p>
|
|
</body>
|
|
</html>
|
|
----
|
|
|
|
The render function is called with the following parameters:
|
|
|
|
* `String template`: The template content
|
|
* `Map model`: The view model
|
|
* `RenderingContext renderingContext`: The
|
|
{spring-framework-api}/web/servlet/view/script/RenderingContext.html[`RenderingContext`]
|
|
that gives access to the application context, the locale, the template loader, and the
|
|
URL (since 5.0)
|
|
|
|
`Mustache.render()` is natively compatible with this signature, so you can call it directly.
|
|
|
|
If your templating technology requires some customization, you can provide a script that
|
|
implements a custom render function. For example, https://handlebarsjs.com[Handlerbars]
|
|
needs to compile templates before using them and requires a
|
|
https://en.wikipedia.org/wiki/Polyfill[polyfill] to emulate some
|
|
browser facilities that are not available in the server-side script engine.
|
|
|
|
The following example shows how to do so:
|
|
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
@Configuration
|
|
@EnableWebMvc
|
|
public class WebConfig implements WebMvcConfigurer {
|
|
|
|
@Override
|
|
public void configureViewResolvers(ViewResolverRegistry registry) {
|
|
registry.scriptTemplate();
|
|
}
|
|
|
|
@Bean
|
|
public ScriptTemplateConfigurer configurer() {
|
|
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
|
|
configurer.setEngineName("nashorn");
|
|
configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
|
|
configurer.setRenderFunction("render");
|
|
configurer.setSharedEngine(false);
|
|
return configurer;
|
|
}
|
|
}
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
@Configuration
|
|
@EnableWebMvc
|
|
class WebConfig : WebMvcConfigurer {
|
|
|
|
override fun configureViewResolvers(registry: ViewResolverRegistry) {
|
|
registry.scriptTemplate()
|
|
}
|
|
|
|
@Bean
|
|
fun configurer() = ScriptTemplateConfigurer().apply {
|
|
engineName = "nashorn"
|
|
setScripts("polyfill.js", "handlebars.js", "render.js")
|
|
renderFunction = "render"
|
|
isSharedEngine = false
|
|
}
|
|
}
|
|
----
|
|
======
|
|
|
|
NOTE: Setting the `sharedEngine` property to `false` is required when using non-thread-safe
|
|
script engines with templating libraries not designed for concurrency, such as Handlebars or
|
|
React running on Nashorn. In that case, Java SE 8 update 60 is required, due to
|
|
https://bugs.openjdk.java.net/browse/JDK-8076099[this bug], but it is generally
|
|
recommended to use a recent Java SE patch release in any case.
|
|
|
|
`polyfill.js` defines only the `window` object needed by Handlebars to run properly, as follows:
|
|
|
|
[source,javascript,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
var window = {};
|
|
----
|
|
|
|
This basic `render.js` implementation compiles the template before using it. A production-ready
|
|
implementation should also store any reused cached templates or pre-compiled templates.
|
|
You can do so on the script side (and handle any customization you need -- managing
|
|
template engine configuration, for example). The following example shows how to do so:
|
|
|
|
[source,javascript,indent=0,subs="verbatim,quotes"]
|
|
----
|
|
function render(template, model) {
|
|
var compiledTemplate = Handlebars.compile(template);
|
|
return compiledTemplate(model);
|
|
}
|
|
----
|
|
|
|
Check out the Spring Framework unit tests,
|
|
{spring-framework-code}/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script[Java], and
|
|
{spring-framework-code}/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/script[resources],
|
|
for more configuration examples.
|
|
|
|
|
|
|
|
|