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?
Related
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.
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.
Let me explain my requirement. Currently we are using Scala template which has some html code and Scala attributes.
Example test.scala.html
#(hostName: String, token: String, protocol: String, supportEmail: String)
#import helper._
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<table>
<tr>
<td> </td>
<td style="color: #626262;">
<p style="font-size:14px;">
<br/>
Our app will let you access your company intranet.<br/><br/>
Your activation code is <b>#token</b><br/><br/>
If you have any questions, please contact our support team anytime.
<br/><br/>
Best regards,<br/>
The Support Team
</p>
</td>
</tr>
</table>
</body>
If you see the above code #token and #supportEmail is dynamic we are passing to Scala.
Till now looks good. The requirement changes, the customer now wants us to read the content from the database. They want to save the actual html content (table ... end of table).
So I took the code and stored it in a database column. I am able to pass this content to my view function by using #Html.
Now my new Scala view function looks like the following:
#(hostName: String, token:String, protocol: String, supportEmail: String, content: String)
#import helper._
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
#Html(content)
Bu I am not able pass #token and #supportEmail dynamically. It is treated as a plain string. After rendering it looks like the following :
Our app will let you access your company intranet, files and SaaS services from your device.
Your activation code is #token
If you have any questions, please contact our support team #supportEmail anytime.
Best regards,
The support Team
Can anyone explain me whats wrong with #Html, can I parse the dynamic content or not. If not is there any alternative.
The #Html function allows you to insert a fragment of HTML from a variable without escaping special characters like < or >. However, it works only with static HTML and unfortunately doesn't parse additional Scala code inside it.
In your case, the easiest way to achieve your requirements is to prepare the content in your controller before passing it to the view. Code in controller's method would look more or less like this:
public Result showContent() {
//fetching all your data here
content = content.replace("#token", token);
content = content.replace("#supportEmail", supportEmail);
return ok(views.html.test.render(hostName, protocol, content));
}
Basically, you aren't forced to use Twirl placeholders inside the table content. The decision how to mark replaceable parameters in the content is up to you. Just make sure that replace calls matches you patterns.
I assumed that token and supportEmail aren't used in other places of the view, hence I removed them from the header of the view.
#(hostName: String, protocol: String, content: String)
Simple solutions are usually the best solutions :)
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 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>