You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
274 lines
8.4 KiB
274 lines
8.4 KiB
[[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 |
|
https://www.jcp.org/en/jsr/detail?id=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] |https://kotlinlang.org/[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",role="primary"] |
|
---- |
|
@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",role="secondary"] |
|
---- |
|
@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",role="primary"] |
|
---- |
|
@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",role="secondary"] |
|
---- |
|
@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 |
|
{api-spring-framework}/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",role="primary"] |
|
---- |
|
@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",role="secondary"] |
|
---- |
|
@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-main-code}/spring-webmvc/src/test/java/org/springframework/web/servlet/view/script[Java], and |
|
{spring-framework-main-code}/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/script[resources], |
|
for more configuration examples. |
|
|
|
|
|
|
|
|
|
|