Disclaimer: The examples shown here do not use Spring.
All of the code used below is available here on GitHub.
Thymeleaf supports inline expression processing for JavaScript and CSS. So, you can use code like this in your HTML page:
|
|
In this example we have an inline CSS expression introduced by <style th:inline="css">
:
|
|
And we have a similar inline JavaScript expression, introduced by <script th:inline="javascript">
:
|
|
In both cases, the Thymeleaf expression is surrounded by /*...*/
comment delimiters - and followed by a valid CSS and JavaScript value. This means that the CSS and JavaScript fragments are valid, despite the inclusion of (otherwise invalid) Thymeleaf expressions.
The fragments use default values, blueviolet
and []
, which are ignored when these lines are processed by Thymeleaf - but which are used normally outside of Thymeleaf processing. This is referred to as natural templating.
It is also worth noting that the ${dataList}
expression in the JavaScript example can be a plain Java List
- and Thymeleaf will automatically translate this Java list into an equivalent
JavaScript list. The same is also true for other Java objects such as maps and arrays.
That is all very powerful and flexible.
But this assumes you have placed everything you need (HTML, CSS and JS) directly in your HTML tempalte file - and that every Thymeleaf expression is therefore in that HTML file.
This certainly works - but as you build more Thymeleaf templates, you may find yourself creating increasingly fragmented JavaScript and CSS snippets, placed throughout your templates
What if you want to use external CSS and JS files - and put your Thymeleaf expressions in those files, instead of in your HTML template?
What if your HTML <head>
looks more like this:
|
|
How can you render your HTML template and, at the same time, also render one or more of these JavaScript and CSS external files?
There are plenty of tutorials and examples which show how to approach this task using Spring. One excellent (but maybe a bit dated) example is here:
Thymeleaf Template Modes Example
That’s all I’ll say about Spring, here. Instead I will focus on a Thymeleaf-without-Spring approach.
For this walkthrough I will use Javalin as the web application framework. There are a couple of specific points which are relevant to Javalin, since it has built-in support for Thymeleaf and other templating solutions. But, more generally, this is a container-agnostic approach (Javalin uses embedded Jetty as its web container).
We start with a simple Thymeleaf HTML template:
The test.html
file:
|
|
The my_script.js
file:
|
|
The main.css
file:
|
|
My Javalin set-up involves creating a public
folder from which my static content is served:
|
|
This includes sub-folders for CSS and JS files.
In Javalin, you define route handlers as follows:
|
|
Here, TEST
represents a functional interface (Handler
), where we can place our handler logic:
|
|
From the Javalin documentation:
Javalin looks for template files in
src/resources
, and uses the correct rendering engine based on the extension of the provided template.
I therefore create the resources
folder and place my test.html
there.
This means, apart from including the Thymeleaf library (in my pom.xml
, in my case), there is nothing more I need to do to configure Thymeleaf in Javalin.
Using the above approach, when a user browses to
http://localhost:7001/test
there is not yet any Thymeleaf handling provided for our JS and CSS files - therefore, they are processed in the normal way by the browser. This is where the default values we provided in our JS and CSS files will be used - and the end result will be:
In order to handle JS and CSS files, I will depart from Javalin’s out-of-the-box support for Thymeleaf (and other rendering libraries). Instead, I will provide a set of custom Thymeleaf template resolvers, and then integrate those into my Javalin route handlers.
The basic approach is this:
For page requests, we will continue to use Javalin’s standard approach for HTML rendering:
|
|
But for JavaScript and CSS, we will use Thymeleaf to render each of these files to a Java InputStream
and then provide that stream as a response to the browser as part of each page request. Javalin’s role will be to intercept these requests, using standard routes, and pass the requests to Thymeleaf for processing.
We will use different Thymeleaf template modes for this:
TemplateMode.HTML
TemplateMode.JAVASCRIPT
TemplateMode.CSS
One more change we will make is to move our Thymeleaf templates to a new location. Instead of placing every HTML tempalte file in src/resources
, we will create a new subfolder src/resources/thymeleaf
, to keep our template files clearly separated from other (public) resources. We can also create additional subfolders as needed.
Javalin can be given a custom Thymeleaf rendering engine:
|
|
The ThymeleafConfig
class mentioned in the above code snippet is our custom class in which we provide all the necessary Thymeleaf configuration we will need.
Here is that class in full:
|
|
This class could use some refactoring - but for demonstration purposes (simplicity and clarity) I have left the class in this basic format.
Because I am using Javalin, all my Thymeleaf template resolvers use ClassLoaderTemplateResolver
- but for different types of web application (for example, a traditional Tomcat webapp), different resolvers could be used - most notably, the ServletContextTemplateResolver
.
My class has three template resolvers, one each for HTML, CSS, and JS files.
The CSS and JS resolvers also have methods which return their results as input streams.
To use these custom Thymeleaf resolvers, we add two new routes to our Javalin application:
|
|
These routes take advantag of the fact that all our JS and CSS resources are public - and are accessed by URLs from within the HTML file:
|
|
So now we are intercepting those requests.
The route handlers (including the HTML handler) need to use our custom Thymeleaf renderers:
|
|
The TEST
route will use the Thymeleaf HTML renderer automatically, without us needing to make any explicit changes. This is because ctx.render()
simply delegates to a Javalin HTML handler.
But for the CSS
and JS
routes, we do not use that. Instead we use ctx.result()
which simply returns the result (in our case, as an input stream) to the browser.
We do have to remember to set the result content types, as well. This ensures the correct MIME types are used by the browser when handling each response.
So, the processing flow is:
TEST
route handler.CSS
and JS
handlers.The end result is:
And now we see that the Thymeleaf expressions in our external CSS and JS files have been applied in the rendered HTML.
All of the code used above is available here on GitHub - with some refactoring applied - so some of the details will be different from the code shown in this post.
One significant downside to this approach is that the application has explicitly taken over the handling of your custom static JavaScript and CSS resources. This means you are also responsible for related error handling - for example:
You want the browser to know when a “404” missing file has been thrown, and when a “500” internal server error has occurred.
How you handle this is somewhat dependent on your web framework and the container you are using.
The GitHub version of my code does include some basic error handling for 404 and 500 errors relating to these resources.
In our JS and CSS Thymeleaf expressions, we used the double square bracket syntax:
[[...]]
This ensures that all values generated by Thymeleaf expressions are escaped, which is essential when handling user-provided (and therefore untrusted) input data - especially for use in HTML pages.
However, there can be some situations where this causes a problem.
One such situation is whentrying to render a hexadecimal color code into a CSS file:
In the CSS file:
|
|
In our Java code to populate the model:
|
|
Here we are using #bb9534
instead of the word goldenrod
.
This will be rendered as follows by Thymeleaf:
|
|
That extra backslash in front of the #
is Thymeleaf’s escaping process in action - but here it causes the CSS to be invalid.
To solve this, you can use Thymeleaf’s unescaped syntax:
[(...)]
So, in the CSS file, the Thymeleaf expression becomes:
background-color: /*[(${backgroundColor})]*/ pink;
Use this with extreme caution. Make sure you only ever place expressions inside [( )]
which are provided directly by your application and never by user-provided input, or by data from any external source.
Better yet, just use [[ ]]
with color names, or with one of the other syntaxes for specifying a color in a CSS file.