I am about to re-write a web-platform, and I am using Spring Boot/Spring MVC. A major part of the platform is the website. I am struggling deciding which template-engine to use. Thymeleaf seems to be recommended, while JSP discouraged. I am not sure if my requirements are unusual, at least they do not sound like that to me:
I do not want to repeat myself in the different templates, they should all be displayed inside a "master template/layout"
The master template/layout will have navigation and footer, which have dynamic content (eg. it is not only the main content that is dynamic)
1) With Thymeleaf, from what I have been able to understand, using Layouts would be the recommended (only?) approach. However, it looks to me like all dynamic content much still be generated in each template (where it flows into the layout using the layout:fragment attribute). This sounds less than ideal, as it would mean I would still have to generate the dynamic part of the layout in each template. Is there no way to include dynamic content in Thymeleaf layouts, where the content (menu, footer, twitter-feed etc) is generated separately from the actual content-template?
2) JSP seems to be able to solve this rather easily, using a custom tag for the layout, that has <jsp:include> -tags for the dynamic content and a <jsp:doBody> -tag for the actual content-template. However, from reading the Spring Boot documentation, I somehow got the impression that it is encouraged to use a different template-engine that JSP. The approach described above would however let me define a header.jsp, navigation.jsp, footer.jsp and twitterFeed.jsp that generates the content dynamically (based on database content, logged in user etc), while the actual content-template purely focuses on the content to display. Is there something I am missing in my comparison between Thymeleaf and JSP here, why would I not chose JSP as my project's template engine?
3) With the approach meantioned in 2), would I be limited to putting all my Java logic in the JSPs for the templates included in the main layout (header, navigation, footer, twitter-feed), or is there a better way to back these stubs up with a controller-like class?
4) Are there any other template engines that integrate well with Spring MVC / Spring Boot, that would be a better choice that any of the above mentioned ones?
Use can use Thymeleaf Ultraq Layout to create a base template which will act as a decorator for your other templates as shown below:
base-template.html:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<title layout:title-pattern="$CONTENT_TITLE - $LAYOUT_TITLE">Sample</title>
<meta name="description" content=""/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<!-- all CSS links here -->
</head>
<body>
<div class="container">
<div class="content">
<div layout:fragment="page_content">
<!-- Content from other pages which decorate using this template -->
</div>
</div>
</div>
<!-- /.container -->
<!-- All script tags here -->
<th:block layout:fragment="scripts">
<!-- If you have any page specific scripts -->
</th:block>
</body>
</html>
Then the other pages will use the above template as a decorator as shown below:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{base-template}">
<head>
<title>This page title</title>
</head>
<div layout:fragment="page_content">
<!-- content for this page -->
</div>
<th:block layout:fragment="scripts">
<!-- add any scripts related to this page -->
</th:block>
</html>
The syntax ~{base-template} is used with Thymeleaf 3 onward.
You can proceed with the above approach and not repeat the navigations, headers and footers on your other pages.
Related
i'm using thymeleaf as the template engine on a Java - Spring web application which is already a completed website, what i'm working now is to introduce some <meta> tags in the header to optimize the interaction with social network platforms.
My goal is to accomplish this modifying as little as possible of the templates code, and to have some flexibility on which values goes into the tags content.
Right now the templates are structured as follows:
Layout.html which contains a couple fragments for head and body.
<head th:fragment="common_head(title, links, scripts)" th:assert="${!#strings.isEmpty(title)}">
...
</head>
<body th:fragment="common_body(content, body_end)">
...
</body>
And the templates for the actual pages are something like this.
<head th:replace="common/layout :: common_head(~{:: head/title}, ~{}, ~{:: head/script})">
<title>My page title</title>
<script>
console.log('some page specific JS code');
</script>
...
</head>
<body th:replace="common/layout :: common_body(~{ :: body/content }, ~{ :: body/bottom })">
<div class="wrapper" th:fragment="content">
some content here
</div>
<th:block th:fragment="bottom">
more content here
</div>
</body>
I know i can just add another parameter to common_head fragment and pass the <meta> tags to it the same way i'm doing with title, scripts, etc. but i was thinking on another approach which will lead to less repetition of code to inject the values into the header.
In the layout common_head fragment i have this:
<meta property="og:title" th:with="og_value = ~{this :: %og_title/text()}" th:content="${og_value ne null ? og_value : 'Lorem ipsum'}">
<meta property="og:description" th:with="og_value = ~{this :: %og_description/text()}" th:content="${og_value ne null ? og_value : 'Lorem ipsum'}">
<meta property="og:image" th:with="og_value = ~{this :: %og_image}" th:content="${og_value ne null ? og_value : 'path-to-default-img'}">
The idea is to use fragment selectors to pick the right content to inject from the page template, this way in a page i can just mark a tag (maybe a div, a paragraph, something relevant for that template) with th:ref="og_description" and it's value will become the content of the <meta> tag in the head.
This works really well for og_title and og_description tags, but the problem arises with the og:image meta tag, in which i need to inject the value of the src attribute of an <img> tag marked with th:ref="og_image".
I couldn't find any way to read an attributes value from the fragment, is there a way to do this?.
I can see that the selected fragment is actually an instance of org.thymeleaf.standard.expression.Fragment but i don't see any method i can use to access the html attributes in it.
If this is not technically possible, is there a better approach to this use case?
I'd like to add additional content to a fragment using Thymeleaf 3 layouts, but cannot figure out how to do it. For example, I'd like to have a fragment named layout that looks like:
<head th:fragment="head(title)">
<title th:include="${title}">My App: </title>
</head>
Then have a template that uses the fragment above using:
<head th:include="layout :: head(title=~{::title})">
<title>Please Login</title>
</head>
The content renders like:
<head>
<title>Please Login</title>
</head>
However, I would like to modify the templates so that it renders like the following and placing My App: in the layout template (I don't want to have to duplicate it).
<head>
<title>My App: Please Login</title>
</head>
I can get this to work using the following:
<head th:fragment="head(title)">
<title th:include="${title}">My App: <th:block th:include="${title}"></th:block></title>
</head>
However, the Thymeleaf discourages the use of th:include. From the reference:
And what is the difference between th:insert and th:replace (and
th:include, not recommended since 3.0)?
Can someone tell me how to fix my templates so that it renders as shown above using best practices (As mentioned the reference implies this means not using th:include)?
The complexity here comes from the fact that you don't want your <title> tag to go directly into your fragment (which would be easy with your ~{::title} and a th:replace at the fragment's <title> tag). Instead, here as you explain you are actually enriching your fragment's <title> with textual content coming from the including template.
The key here would be to use the /text() modifier in your markup selector, which means "select text contents of this tag", like:
<head th:include="layout :: head(title=~{::title/text()})">
<title>Please Login</title>
</head>
(see http://www.attoparser.org/apidocs/attoparser/2.0.0.RELEASE/org/attoparser/select/package-summary.html for the complete reference of the markup selector syntax)
That would make your title variable contain a Fragment object consisting of a single node/event, an IText containing the text "Please Login".
As you mention, th:include is now discouraged (to be deprecated in 3.1) and instead th:insert and th:replace are the preferred options. The reason is th:include's mechanism seemed to be not completely immediate and commonly provoked misunderstandings (basically, a lot of people thought it did what now th:insert does, which is much simpler). Besides, th:include added some unwanted computational complexity.
th:replace does exactly the same as in 2.1, this is, actually replace the host tag with the fragment. th:insert will insert the fragment into the body of the host tag. A set of simpler options, IMHO.
Back to your code, I would therefore evolve it towards using th:replace:
<head th:replace="layout :: head(title=~{::title/text()})">
<title>Please Login</title>
</head>
And as for your fragment, in your case I'd go for inlining, probably the simplest option here:
<head th:fragment="head(title)">
<title>My App: [[${title}]]</title>
</head>
Note that in this case we are using a Fragment (i.e. the result of a fragment expression, in this case ~{::title/text()}), and we are simply outputting it via inlining (equivalent to th:text) as if instead of a fragment the title variable contained a mere String. But that's part of the flexibility of the new fragment expressions in v3.0.
If you don't like inlining, you could go for something like:
<head th:fragment="head(title)">
<title th:text="|My App: ${title}|">My App</title>
</head>
And if there is the possibility that the including template has no <title> tag and you want to check that possibility and just use the My App text as title if there is no title being sent to the fragment, you could use the also-new no-op token (_):
<head th:fragment="head(title)">
<title th:text="${title} ? |My App: ${title}| : _">My App</title>
</head>
Disclaimer, per StackOverflow rules: I'm Thymeleaf's project lead.
I'm planning my website structure as following:
header.scala.html
XXX
footer.scala.html
now, instead of "xxx" there should be a specific page (i.e. "UsersView.scala.html").
what I need is to include (like with well-known languages) the source of the footer and the
header into the the middle page's code.
so my questions are:
How do you include a page in another with scala templating?
Do you think it's a good paradigm for Play! framework based website?
Just call another template like a method. If you want to include footer.scala.html:
#footer()
A common pattern is to create a template that contains the boilerplate, and takes a parameter of type HTML. Let's say:
main.scala.html
#(content: HTML)
#header
// boilerplate
#content
// more boilerplate
#footer
In fact, you don't really need to separate out header and footer with this approach.
Your UsersView.scala.html then looks like this:
#main {
// all your users page html here.
}
You're wrapping the UsersView with main by passing it in as a parameter.
You can see examples of this in the samples
My usual main template is a little more involved and looks roughly like this:
#(title: String)(headInsert: Html = Html.empty)(content: Html)(implicit user: Option[User] = None)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>#title</title>
// bootstrap stuff here
#headInsert
</head>
<body>
#menu(user)
<div id="mainContainer" class="container">
#content
</div>
</body>
</html>
This way a template can pass in a head insert and title, and make a user available, as well as content of course.
Play provide a very convenient way to help implement that!
Layout part from official docs:
First we have a base.html (that's we call in django -_-)
// views/main.scala.html
#(title: String)(content: Html)
<!DOCTYPE html>
<html>
<head>
<title>#title</title>
</head>
<body>
<section class="content">#content</section>
</body>
</html>
How to use the base.html?
#main(title = "Home") {
<h1>Home page</h1>
}
More information here
I've just installed grails rendering plugin and would like to use it for generating PDF files. I've created simple template, but it is exported without any css styles. If I simply render template from grails, then page appears with all styles in my web browser.
So, my question is - how to correctly include CSS file during PDF generation process?
My template:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<link rel="stylesheet" href="${resource(dir:'css',file:'main.css')}" />
<link rel="stylesheet" href="${resource(dir:'css',file:'webui.css')}" />
<r:layoutResources/>
<title>Report</title>
</head>
<body>
<div id="content">
<div id="center-container">
<h1><g:message code="default.list.label" args="[entityName]" /></h1>
<table>
<thead>
<tr>
<th class="trip">trip</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>
${tip}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>
And I have style .odd in my webui.css, but it is not applied on the row.
Any help would be appreciated.
Edit1: I found out that styles are fetched, if I do it in the following way:
<link rel="stylesheet" href="my_appname${resource(dir:'css',file:'main.css')}" />
But I don't want to hardcode application name (this is also a base context path). Is there a better way to generate proper link to a css file?
It may be late with my answer but I wanted to share my experience here with rendering pdf in grails. I followed the below steps and failed over to the next until I get a pdf:
Used the resource plugin in the template gsp to grab a module where css was bundled.
For example:
Test.gsp
<html>
<head>
<r:require modules="bootstapApp"/>
<r:layoutResources/>
</head>
<body>
....
<r:layoutResources/>
</body>
</html>
The above worked fine but the styles were not used in the pdf after rendering. I had to fall over to step 2.
2.Started using tag as mentioned above in the problem statement.
Result: No change. I wasn't able to get the styles in the pdf.
Failed over to step 3
3.Added the styles inline in the template gsp. And then I was able to apply them to the pdf. Point to note here is that if you follow step 3 and you have css like bootstrap.css then inlining them in the template will be cumbersome. Even if we add them, do not forget to put them inside the media tag. For me the below worked perfectly fine:
<style type="text/css">
#media all {
//CSS styles goes here
}
</style>
Try setting grails.serverURL in Config.groovy to the app url (ex. grails.serverURL=http://localhost:8080/appname). The plugin resolves all relative links via this setting
I shared my answer elsewhere, but I ended up just embedding the external file contents into the gsp for pdf rendering:
https://stackoverflow.com/a/32767378/1599616
I want to create a master template that every other view page will inherit.
So the master template will have:
HEADER
--CONTENT--
FOOTER
the header will optionally show (if the user is logged in), the username and other user object properties.
the --CONTENT-- is a placeholder that other 'inheriting' view pages will inject their content into.
So my questions are, is this possible with freemarker? If so, any guidance?
How would I pass the user object to the header from my controller actions? ideally the object would be passed in somewhere OTHER than each and every view page (to avoid having to maintain this code on each and every view page).
Yes, it's possible. In our applications things like the user object exist in session scope, but this could be any scope freemarker has access to:
<#if Session.the_user?? && Session.the_user.loggedIn>
<#-- header code -->
</#if>
You can omit the Session. and Freemarker will search the various scopes for the given variable name.
To inject the content, include this at the point in the master template where you'd like the view page to put its content:
<#nested>
The view pages then declare their use of the master template as follows:
<#import "/WEB-INF/ftl/path/to/template/master.ftl" as com>
<#com.template>
View page content
</#com.template>
I made Freemarker template inheritance - https://github.com/kwon37xi/freemarker-template-inheritance
I think it's what you want. It is tested on freemarker 2.3.19.
I implemented something like this:
base.ftl
<#macro page_head>
<title>Page title!</title>
</#macro>
<#macro page_body></#macro>
<#macro display_page>
<!DOCTYPE html>
<html lang="en">
<head>
<#page_head/>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<#page_body/>
</body>
</html>
</#macro>
then index.ftl will inherit the boilerplate templates as:
<#include "base.ftl">
<#macro page_head>
<title>Welcome studs!</title>
</#macro>
<#macro page_body>
<h1> Welcome user</h1>
</#macro>
<#display_page/>
this site was helpful for the above code reference
https://nickfun.github.io/posts/2014/freemarker-template-inheritance.html
In newer Freemarker versions, the <#nested> element is extremely useful:
base.ftl:
<#macro layout>
<html>
<body>
<p>OptaPlanner AI</p>
<#nested>
</body>
</html>
</#macro>
baseWithDownloadButton.ftl:
<#import "base.ftl" as base>
<#base.layout>
${content.body}<#-- Only applicable for jbake -->
<p>Download button</p>
</#base.layout>