advice on freemarker templating, want to create a master template - java

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>

Related

Read an html attribute value from a Thymeleaf fragment

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?

Recommended template engine to reduce redundancy of dynamic content (Spring Boot)

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.

How to add additional content to fragment in Thymeleaf 3

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.

Thymeleaf not displaying values from spring controller

I am practising thymeleaf template engine for the first time. I have followed the tutorial and so on but I have no idea where I am going wrong.
My controller:
public String mainPage(Model model){
model.addAttribute("data", "Hello Thymeleaf");
return "main";
}
and my html is as follows:
<!DOCTYPE html >
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>th:text="${data}"</h1>
</body>
</html>
When I hit localhost it displays th:text="${data}" instead of Hello Thymeleaf
<h1>"${data}"</h1>
doesn't work either. View resolver config must be correct as it resolves main to main.html. I am using spring4 SpringTemplateEngine and spring4 thymeleaf view resolver.
thanks in advance
You have to use th:text
<h1 th:text="${data}"></h1>
or if you don't want to use the th:text attribute, then you have to use th:inline="text" and make the thymeleaf render the context inside the tag. But make sure you put the variable inside [[ and ]]
<h1 th:inline="text">[[${data}]]</h1>
Thymeleaf isn't Velocity or Freemarker and doesn't replace expressions blindly. You need the expression in an appropriate tag attribute, such as
<h1 data-th-text="${data}" />
remove quotes to "${data}" and just use ${data}. I also agree with #Faraj Farook

Play! framework. template "include"

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

Categories