In Freemarker, how can I create a template that inherits from a template that itself inherits?
Single inheritance works fine with the <#nested> tag:
File base.ftl:
<#macro layout>
<html lang="en">
<head>...</head>
<body>
<div>... (navigation bar)</div>
<div class="container">
<#nested>
</div>
</body>
</html>
</#macro>
File normalBase.ftl:
<#import "base.ftl" as base>
<#base.layout>
<div class="row">
<div class="col-md-9">
${content.body}
</div>
<div class="col-md-3">
<p>Latest releases</p>
<ul>....</ul>
</div>
</div>
</#base.layout>
How do I turn this into double inheritance where useCaseBase.ftl extends normalBase.ftl which extends base.ftl?
This works like a charm:
File base.ftl:
<#macro layout>
<html lang="en">
<head>...</head>
<body>
... // Shared navigation bar
<div class="container">
<#nested>
</div>
... // Shared footer
</body>
</html>
</#macro>
<#layout>
${content.body}
</#layout>
File normalBase.ftl:
<#import "base.ftl" as parent>
<#macro layout>
<#parent.layout>
<div class="row">
<div class="col-md-9">
<#nested>
</div>
<div class="col-md-3">
... // Shared sidebar
</div>
</div>
</#parent.layout>
</#macro>
<#layout>
${content.body}
</#layout>
File useCaseBase.ftl:
<#import "normalBase.ftl" as parent>
<#parent.layout>
${content.body}
... // Shared content between all use case pages
</#parent.layout>
Now I can create *.adoc pages with jbake-type set to either base, normalBase or useCaseBase and it works.
I tried to implement extends and block like Jinja2.
extends.ftl for defining macros.
<#if !blocks??>
<#assign blocks = {} />
</#if>
<#macro extends ftl>
<#nested />
<#include ftl />
</#macro>
<#macro replace name>
<#local value>
<#nested />
</#local>
<#assign blocks += {name: value} />
</#macro>
<#macro block name>
<#if blocks[name]??>
<!-- replaced ${name} -->
${blocks[name]}
<#else>
<!-- default ${name} -->
<#nested />
</#if>
</#macro>
base.ftl for layout
<#import "extends.ftl" as layout />
<!DOCTYPE html>
<html>
<head>
<title>A demo of FreeMarker extends directive</title>
</head>
<body>
<#layout.block "message">
This is default message in base.ftl
</#layout.block>
</body>
</html>
finally, index.ftl like this:
<#import "extends.ftl" as layout />
<#layout.extends "base.ftl">
<#layout.replace "message">
This is the message from index.ftl
</#layout.replace>
</#layout.extends>
Take a look at https://github.com/emesday/freemarker-extends .
Related
I'm trying to include a letter head in my PDF templates like so:
fragments/header.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Header</title>
</head>
<body>
<div id="header" class="clearfix" th:fragement="header(sender, name, address1, address2, today)">
<div id="header-left">
<img class="logo" src="app/src/main/resources/template/pdf/assets/logo.png"/>
<div id="recipient">
<p th:object="${sender}"><span th:text="*{name}"></span> - <span th:text="*{address1short}"></span> - <span th:text="*{address2}"></span></p>
<p th:text="${name}"></p>
<p>
<span th:text="${address1}"></span><br/>
<span th:text="${address2}"></span>
</p>
</div>
</div>
<div id="header-right" th:object="${sender}">
<p><strong th:text="*{name}"></strong></p>
<p>
<span th:text="*{address1}"></span><br />
<span th:text="*{address2}"></span><br />
<span class="label" th:text="*{phoneLabel} + ':'"></span><span th:text="*{phone}"></span><br />
<span class="label" th:text="*{faxLabel} + ':'"></span><span th:text="*{fax}"></span><br />
<span class="label" th:text="*{emailLabel} + ':'"></span><span th:text="*{email}"></span><br />
<strong><span th:text="*{internetLabel}"></span>: <span th:text="*{internet}"></span></strong>
</p>
<p>
<span th:text="*{court}"></span><br />
<span th:text="*{registryNr}"></span><br />
<span th:text="*{directorLabel}"></span>: <span th:text="*{director}"></span><br />
<span th:text="*{vatId}"></span><br />
</p>
<p><span th:text="*{todayPrefix}"></span> <span th:text="${today}"></span></p>
</div>
</div>
</body>
</html>
template.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Template</title>
<link rel="stylesheet" type="text/css" media="all" href="app/src/main/resources/template/pdf/assets/style.css"/>
</head>
<body>
<div th:replace="app/src/main/resources/template/pdf/fragments/header :: header(${sender}, ${name}, ${address1}, ${address2}, ${today})"></div>
<!-- omitted for brevity -->
</body>
</html>
Everything works fine when I put the header code in the template directly. But when I try to inlude it as a fragment, like above, I get the following Error:
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "app/src/main/resources/template/pdf/fragments/header.html" - line 9, col 6)
template.html is in the app/src/main/resources/template/pdf/ folder. I had to include css and image files with the full path (from the root of the project) to get them to work. I tried including the fragment with the full path (as above) and only fragements/header, but I get the same error each time.
Im using
spring-boot-starter-thymeleaf 2.1.1
thymeleaf 3.0.11
Stacktrace:
[removed bc of character limit]
UPDATE
The correct path for the fragment is (in my case) "template/pfd/fragment/header" (so, full path from resources folder). Also, there was a typo in th:fragement="header".
The fragment reference should not be absolute to your project, but relative to your thymeleaf template root. So instead of (assuming it is app/src/main/resources/template/ whre your thymeleaf templates are located)
th:replace="app/src/main/resources/template/pdf/fragments/header ..."
use
th:replace="pdf/fragments/header ..."
The common use is
<div th:replace="fragments/header :: header"><div>
Using Thymeleaf
<html lang="en">
<head>
<meta charset="UTF-8">
<title> Java, Spring Boot, FreeMarker</title>
<link href="/css/main.css" rel="stylesheet">
</head>
<script>
function myFunction() {
<#test/>
}
</script>
<body>
<h2>Java, Spring Boot, FreeMarker</h2>
<form action="/search" method="post">
Search : <input type="text" name="firstName" onkeyup="myFunction()" id = "fname">
<input type="submit" value="Submit">
</form>
<div style="background-color:lightblue">
<#macro test>
<#list empList as emp>
<div id="emp-data">
<ul>
<li>${emp}</li>
</ul>
</#list>
</div>
</#macro>
<script src="/js/main.js"></script>
</div>
</body>
When I run this code I am getting some errors on the browser console:
(index):60 Uncaught ReferenceError: myFunction is not defined at HTMLInputElement.onkeyup ((index):60) onkeyup # (index):60 – PCS 1 hour ago
Is it possible in FreeMarker to do something like that?
In a sense you can... but it doesn't do what you apparently believe it does. First all FreeMarker instructions, like <#test/>, are resolved on the server, then later the resulting output runs in the browser. So as far as the browser sees, function myFunction() { ... } contains HTML div-s directly in inside the { ... }, which is invalid JavaScript.
Im trying to render a webapge and use the thymleaf attribute "url" added with model.addAttribute but the Attribute is not beeing displayed on the html document.
My document.html file path is here:
/templates/webpage/document.html
#RequestMapping(value = "/webpage/document")
public String document(HttpServletRequest req, Model model) {
model.addAttribute("dialogurl", url);
return "/webpage/document";
}
Here is the html document
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:include="wrapperdialog :: page">
<head>
<title></title>
</head>
<body>
<div th:fragment="content">
<div class="container dialogpage">
<div class="row">
<div class="col-md-12">
<div id="typeform" th:attr="data-url=*{dialogurl}">
</div>
</div>
</div>
</div>
</div>
Please use this expression to bind dialog url: #{${dialogurl}}
<div id="typeform" th:attr="data-url=#{${dialogurl}}">
# prefix is used to specify a link and $ prefix is used to bind your model value.
Use $ to bind data.
<div id="typeform" th:attr="data-url=${dialogurl}">
I've a simple main layout:
<!DOCTYPE html>
<html>
<head>
<title>Title</title>
#yield('style')
</head>
<body>
#include('layouts.frontend.partials.slider')
#yield('javascript')
</body>
</html>
layouts.frontend.partials.slider
#section('style')
<link rel="stylesheet" type="text/css" href="mystyle.css">
#append
#section('javascript')
<script></script>
#append
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide" data-swiper-autoplay="1000">Slide 1</div>
<div class="swiper-slide" data-swiper-autoplay="1000">Slide 2</div>
<div class="swiper-slide" data-swiper-autoplay="1000">Slide 3</div>
</div>
<div class="swiper-pagination"> </div>
<div class="swiper-button-prev"> </div>
<div class="swiper-button-next"> </div>
</div>
The #section('style') will be ignored while the #section('javascript') is working fine within the include file...
I've reduced both files (main and include) to a minimum and swapped the position of style and javascript without any difference
What seems to be working is to change to position from the #yield('style') to the body, like:
<!DOCTYPE html>
<html>
<head>
<title>Title of the document</title>
</head>
<body>
#include('layouts.frontend.partials.slider')
#yield('javascript')
#yield('style')
</body>
Maybe it's not allowed to have #section in an include file?
What i want to archive is to have multiple partial includes with it's own css and javascript includes
Thanks
Here is another way to do, it's good because you have some flexibility.
You create a master template, put your main files
master.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" href="">
#section('css')
<!-- some master css here -->
#show
</head>
<body>
#section('navbar')
#include('common.navbar')
#show
#yield('content')
#include('common.footer')
#section('js')
<!-- some js here -->
#show
</body>
</html>
The others childs extends the master, you'll have all your layout and can customize what you want:
Child blade
#extends('master')
#section('css')
#parent
<!-- more css -->
#endsection
#section('navbar')
#parent
#endsection
#section('content')
<!-- Main content goes here -->
#endsection
#section('js')
<!-- replace js and add my own -->
<!-- others js -->
#endsection
Did you try extending your child blade file to use the master template?
At the top of layouts.frontend.partials.slider did you put #extends('layout.master') (or whatever the path to your master template is?)
It could be an issue caused by the order in which you are including files. Wouldn't a simpler solution be to have a #yield('slider') in your master template and simply wrap the slider content in #section('slider') and drop the #include...?
Is it possible to create a dynamic replace in Thymeleaf?
I have the following controller:
#Controller
public class LoginController {
#RequestMapping("/login")
public String getLogin(Model model){
model.addAttribute("template","login");
return "index";
}
}
And the following view:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" >
<head></head>
<body>
<div th:replace="fragments/${template} :: ${template}"></div>
</body>
</html>
And i'm getting the following error:
Error resolving template "fragments/${template}", template might not exist or might not be accessible by any of the configured Template Resolvers
UPDATE
I tried to preprocess my variables like this:
<div th:replace="fragments/${__#{${template}}__} :: ${__#{${template}}__}"></div>
How ever now ${template} is getting replaced with login i have the following error now:
Exception evaluating SpringEL expression: "??login_en_US??"
Although Joe Essey's solution is working as well i solved with following code:
<div th:replace="#{'fragments/' + ${template}} :: ${template}"></div>
I believe the appropriate method to manage this behavior in thymeleaf is to use layout:fragment tags. Please correct me if I'm wrong. Here is a simple example of my layout page, and the login page which is 'dynamically' loaded:
layout.html
<html xmlns:layout="http://www.w3.org/1999/xhtml" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<title layout:title-pattern="$DECORATOR_TITLE - $CONTENT_TITLE">Layout</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
</head>
<body>
<div>
<div class="app-container">
<div th:fragment="content">
</div>
</div>
</div>
<div th:fragment="script"></div>
</body>
</html>
Then, when login gets loaded, it replaces the th:fragment div with the associated div in the html view which matches the string returned by the controller method, in this case login.html:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.w3.org/1999/xhtml"
layout:decorator="layout">
<head>
<title>Login</title>
</head>
<body>
<div th:fragment="content">
<form th:action="#{/login}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
</div>
</body>
</html>
Now, if you want to load another fragment conditionally, the approach I take is to add replace tags with th:if cases. Here's an example of a Form that displays different questions based on an attribute of the current user:
<div th:if="${foo.type)} == 'type_1'">
<div th:replace="fragments/custom-questions :: type-1-checkboxes"></div>
</div>
<div th:if="${foo.type} == 'type_2'">
<div th:replace="fragments/custom-questions :: type-2-checkboxes"></div>
</div>
Then the associated div gets loaded from the file custom-questions.html:
<div th:fragment="type-1-checkboxes">
//stuff
</div>
<div th:fragment="type-2-checkboxes">
//stuff
</div>
I am just encountering this issue (this is my first time with thymeleaf/spring). This is what solved it for me:
<div class="col-md-12" th:include="__${template}__ :: body" ...
In Thymeleaf 3.0, the following solution has worked for me:
<div th:replace="('fragments/' + ${template}) :: (${template})">
(Note however, that I use it with fixed name of the fragment and dynamic name of the template, so the parantheses around :: (${template}) might be optional.)
The solution is inspired by documentation for Thymeleaf in https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#fragment-specification-syntax
Both templatename and selector in the above examples can be fully-featured expressions (even conditionals!) like:
<div th:insert="footer :: (${user.isAdmin}? #{footer.admin} : #{footer.normaluser})"></div>
Note again how the surrounding ~{...} envelope is optional in th:insert/th:replace
<div th:insert=“${subpage}::fragementName”>
Just change subpage names and you will dynamic behaviour in thymleaf