What type of operation and syntax for them are possible on Thymeleaf 3 fragment expression? - thymeleaf

With Thymeleaf 3 it is possible to pass fragment from page to template via ~{:: selector} syntax.
What kind of operation are possible on that object?
Fragment can be used inside expression:
<div th:fragment="name(arg)">
<div th:replace="${arg} :? _"></div>
</div>
Can I extract only part of fragment inside fragment with something like (following is incorrect syntax!!):
<div th:fragment="name(arg)">
<div th:replace="${arg :: script} :? _"></div>
<div th:replace="${arg}.filter('script'} :? _"></div>
<div th:replace="${xpath(${arg},'script')} :? _"></div>
</div>
UPDATE I introspected to what fragment expression is resolved with:
<th:block th:text="${bodyContent.class}" />
which is org.thymeleaf.standard.expression.Fragment. It has:
<th:block th:text="${bodyContent.templateModel.class}" />
TemplateModel which can be rendered via toString() or write(Writer writer). I don't see easy way to filter Fragment content...

I saw Thymeleaf templates - Is there a way to decorate a template instead of including a template fragment? technique which I tried to employ.
Thymeleaf v2.1 and 3 allows referencing templates/fragment mix to itself.
Lets look to template:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<nav></nav>
<div th:replace="this :: body"/>
</body>
</html>
and to page:
<html lang="en" xmlns:th="http://www.thymeleaf.org"
th:replace="thymeleaf/layout/default :: html">
<body>
XXX
</body>
</html>
Above code produces infinite sequence of <body><nav></nav> as CSS selector referenced from template to body's template.
To move reference to page I added more complicated CSS style selector:
<html lang="en" xmlns:th="http://www.thymeleaf.org" class="htmlFrag">
<body>
<nav></nav>
<div th:replace="this :: html[!class]/body"/>
</body>
</html>
I am not sure how it is possible to have template and page in same scope for selector matching but it works...
Advanced templating with CSS/JS handling can be represented as:
<html lang="en" xmlns:th="http://www.thymeleaf.org" class="htmlFrag">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title th:text="~{::html[!class]/head/title/text()}"></title>
<link rel='stylesheet' href='/webjars/...">
<div th:replace="this :: html[!class]/head/link"/>
<script src="/webjars/..."></script>
<div th:replace="this :: html[!class]/head/script"/>
</head>
<body>
<nav></nav>
<div th:replace="this :: html[!class]/body"/>
</body>
</html>
UPDATE I've got responce from developers https://github.com/thymeleaf/thymeleaf/issues/626
Thymeleaf uses pull-based or fragment-inclusion-based layout architecture or by default.
With Layout Dialect it is possible to use hierarchical layout style and it is preferred to do this.

Related

Define and insert Thymeleaf fragments in TEXT templates

I'm trying to create email templates in both plain text and HTML with Thymeleaf. Because I don't want to duplicate the common parts I want to define these parts separately and insert them into the more specific templates.
It works for HTML, but for plain text variables in the common parts are not replaced:
HTML
common.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div th:fragment="header">
<p>
Hello, [( ${name} )]
</p>
</div>
<div th:fragment="footer">
<p>
Bye.
</p>
</div>
</body>
</html>
specific.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div th:replace="html/common::header"></div>
<p>
<a th:href="${myLink}">[( ${myLink} )]</a>
</p>
<div th:replace="html/common::footer"></div>
</body>
</html>
Plain text
header.txt
Hello ${name}
footer.txt
Bye
specific.txt
[( ~{text/header} )]
[( ${myLink} )]
[( ~{text/footer} )]
Result
It all works well for HTML but for the plain text version the ${name} variable from the inserted header.txt template is not replaced:
Hello, [#th:block th:utext="${name}"][/th:block]
http://example.com
Bye.
The HTML result looks correct:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div>
<p>
Hello, name-value
</p>
</div>
<p>
http://example.com
</p>
<div>
<p>
Bye.
</p>
</div>
</body>
</html>
My questions
Is there an elegant solution for the plain text version?
Is there a way to define and use fragments also for textual Thymeleaf templates?
Any general recommendations, as I'm only starting to use Thymeleaf?
Variables in the Plain Text Version
For the plain text issue, you can use the [#th:block] syntax.
Specifically, instead of using this in your specific.txt:
[( ~{text/header} )]
you can use this:
[#th:block th:replace="text/header"][/th:block]
Also, in the header.txt file, instead of using this:
Hello ${name}
you need to use this:
Hello [( ${name} )]
This is expression inlining - which you have already used - and is presented here, for reference.
Some additional examples of the [#th:block] syntax are presented here.
Defining and Using Fragments for Text
You might think that the [#th:block] syntax would now allow us to use fragments, in a similar way to the HTML approach. For example, something like this:
DOES NOT WORK:
[#th:block th:replace="text/common :: header"][/th:block]
together with a common.txt fragment like this:
ALSO DOES NOT WORK:
[#th:block th:fragment="header"]
Hello, [( ${name} )]
[/th:block]
If you try this, you will get the following error:
java.lang.IllegalArgumentException: Template selectors cannot be specified for a template using a TEXT template mode: template insertion operations must be always performed on whole template files, not fragments
General Comments
The only other thing I would mention here, if you have not already seen or used it, is parameterized fragments. They can make HTML fragments more flexible and re-usable.

Angular 2 with ASP.NET Core duplicating <html><body>

I have setup Angular 2 with ASP.NET core, using:
dotnet new --install Microsoft.AspNetCore.SpaTemplates::*
I followed the standard setup but the App.component.html is duplicating the html and body tags.
When Pressing Control U (to view source) the html looks like:
<html>
<head>
....
</head>
<body>
<app>
<html>
<head>
<style>
...
</style>
</head>
</app>
</body>
</html>
</app>
...
</body>
</html>
redacted
None of my components have html or body tags in them. the only one that has html or body tag is the initial page
My _Layout.cshtml html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>#ViewData["Title"] - HotSnail</title>
<base href="/" />
<script src="~/content/modules/jquery/jquery-3.2.1.min.js"></script>
<script src="~/content/modules/bootstrap-3.3.7-dist/js/bootstrap.js"></script>
<link rel="stylesheet" href="~/dist/vendor.css" asp-append-version="true" />
<link href="~/content/modules/font-awesome-4.7.0/css/font-awesome.css" rel="stylesheet" />
<link href="~/content/modules/animate/animate.css" rel="stylesheet" />
<link href="~/content/global.css" rel="stylesheet" />
</head>
<body>
#RenderBody()
#RenderSection("scripts", required: false)
</body>
</html>
My Index.cshtml
<script src="~/dist/vendor.js" asp-append-version="true"></script>
#section scripts {
<script src="~/dist/main-client.js" asp-append-version="true"></script>
}
The above code is what should be served, now the first bit of angular should be the app.component
app.component.html
<ribbon></ribbon>
<div id="content">
<router-outlet></router-outlet>
</div>
ribbon.component.html:
<div id="ribbon">
<span class="ribbon-button-alignment">
<span id="refresh" class="btn btn-ribbon" data-action="resetWidgets" data-title="refresh" rel="tooltip" data-placement="bottom" data-original-title="<i class='text-warning fa fa-warning'></i> Warning! This will reset all your widget settings." data-html="true" data-reset-msg="Would you like to RESET all your saved widgets and clear LocalStorage?"><i class="fa fa-refresh"></i></span>
</span>
<!-- breadcrumb -->
<ol class="breadcrumb"><li>Home</li><li>Outlook</li><li>Inbox</li></ol>
</div>
This is all the components that should be injected on the first page
UPDATE*
So I have done some more research and it seems to be a known bug. As it says here:
https://github.com/aspnet/JavaScriptServices/issues/662
I cant quiet figure out their hacky solution
Here is hacky code that fixes this:
return requestZone.run>(() =>
platform.serializeModule(AppModule)).then(html => { resolve({ html:
html.slice(27, html.length - 14).replace('', '') }); },
reject);
Also in index.cshtml I advice changing app to div because we already
have app tag returned from Universal.
Loading...
I'm not sure where that code would go..
UPDATE
This also happens in the DEFAULT project created when doing
dotnet new angular
Does anyone know who maintains these templates as I'd like to post a bug with the development team
Same problem for angular 5.
I fixed it like in suggestion from last comment on https://github.com/aspnet/JavaScriptServices/issues/662 (last comment by salarcode):
boot.server.ts file change the code to this:
resolve({
html: (() => {
let apphtml = state.renderToString();
apphtml = apphtml.replace('<html><head><style>', '<style>').replace('</head><body><app', '<app').replace('</app></body></html>', '</app>');
return apphtml;
})()});

Overloading blocks with dust.js for nested templates

I have a layout called layouts/master.dust
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{+title /}</title>
<link rel="stylesheet" href="css/app.css" />
</head>
<body>
<div id="wrapper">
<h3>This is the master header</h3>
{+body /}
</div>
</body>
</html>
I then have a template called index.dust:
{>"layouts/master" /}
{<body}
<h1>{#pre type="content" key="greeting"/}</h1>
{/body}
Predictably, that outputs
This is the master header
Hello, Alex!
(when supplied with the relevant json)
My issue is, I'm creating another "nested" or "child" template: layouts/admin.dust that looks like:
{>"layouts/master" /}
<h4>This is an admin header</h4>
{+body /}
and a template adminIndex.dust that uses layouts/admin/dust like this:
{>"layouts/admin" /}
{<body}
<p>This is some stuff for admin</p>
{/body}
My problem, is the output is:
This is the master header
This is some stuff for admin
This is an admin header
This is some stuff for admin
So, my {+body} block is being called twice, and not overridden by my nested template
Is there a way to do that?
Edit
As per my comment, a more 'realistic' example of admin.dust would be:
{>"layouts/master" /}
<h4>This is an admin header</h4>
<div style="float: left; width: 200px;">
<strong>left nav</strong>
<ul>
<li>Link one</li>
<li>Link two</li>
</ul>
</div>
<div style="float: right">
{+ body /}
</div>
Where by in templates that use the admin layout, their body is in a div floated right (for example)
Your {+body} block is being overridden by your {<body} inline partial in your adminIndex.dust template. The contents of an inline partial (in your case <p>This is some stuff for admin</p>) will be inserted wherever a block is found in your template. If you are hoping to have the master header be replaced by the admin header, you would need your templates to look like this:
layouts/master.dust
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{+title /}</title>
<link rel="stylesheet" href="css/app.css" />
</head>
<body>
<div id="wrapper">
{+header}<h3>This is the master header</h3>{/header}
{+body /}
</div>
</body>
</html>
layouts/admin.dust
{>"layouts/master" /}
{+header}<h4>This is an admin header</h4>{/header}
adminIndex.dust requires no change.
Note: I have added a {+header} block to layouts/master.dust with a default value, and added a {header} inline partial and removed the {+body} block from layouts/admin.dust. This will output:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title></title>
<link rel="stylesheet" href="css/app.css" />
</head>
<body>
<div id="wrapper">
<h3>This is the admin header</h3>
<p>This is some stuff for admin</p>
</div>
</body>
</html>

Grail partial view for ajax request

<g:if test="${!request?.xhr}">
<!doctype html>
<html>
<head>
<meta name="layout" content="home">
</head>
<body>
<div class="row-fluid">
</g:if>
AJAX
<g:if test="${!request?.xhr}">
</div>
</body>
</html>
</g:if>
I get error: Grails tag [sitemesh:captureBody] was not closed.
In Config.groovy I set grails.views.gsp.sitemesh.preprocess = false but this doesn't help.
What way to use partial view with if statement.
A better way to handle this in grails is to wrap a template containing the main content. For example:
//_body.gsp
AJAX
//view.gsp
<!doctype html>
<html>
<head>
<meta name="layout" content="home">
</head>
<body>
<div class="row-fluid">
<g:render template="body">
</div>
</body>
</html>
Then your controller can render the whole view on a regular request, or just the body on AJAX.
You can check request.xhr in the controller, and determine to render a template or a string based on the results of that if statement.

using 3 layouts on same gsp page

this is edit of my question that was first how to apply 2 layouts in the same gsp page but now i got problem with 3 layouts :) :
I am fairly new to all that css and layout stuff and i'm using grails 2.0 version
i have the following moduls in my problem:
1. main.gsp layout which basically have a nice header with company logo for all pages.
2. mainTabPanle.gsp layouts which basically contain some main tabs all pages should have
3. reportTab.gsp layout which basically contain nice report tabs and short javascript code to manipulate chosen tab color that all reports gsp pages should have.
what i am trying to do is to use this reportTab layout in all the reports gsp pages.
so this is what i got so far:
main.gsp:
<!doctype html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}"type="text/css">
<g:layoutHead/>
<r:layoutResources />
</head>
<body style="height:100%">
<div>some nice header in here </div>
<g:layoutBody/>
<r:layoutResources />
</body>
</html>
mainTabPanle.gsp (also located in layout folder)
<g:applyLayout name="main">
<!doctype html>
<html>
<head>
<g:layoutHead/>
<r:layoutResources />
</head>
<body>
<div>some main tabs here </div>
<g:layoutBody/>
</body>
<script type="text/javascript">
//script to manipulate main tabs
</script>
<r:layoutResources />
</body>
</html>
</g:applyLayout>
reportTabPanel.gsp:
<g:applyLayout name="mainTabPanel">
<!doctype html>
<html>
<head>
<g:layoutHead/>
<r:layoutResources />
</head>
<body>
<div>some reports tab panel </div>
<g:layoutBody/>
</body>
<script type="text/javascript">
//some script to manipulate report tab item
</script>
<r:layoutResources />
</body>
</html>
</g:applyLayout>
and now im using in moneyreport.gsp header the following line:
<meta name="layout" content="reportTabPanel" />
what i want to see is the nice header and the maintabsPanel and the reportTabPanel but all i see is the body of moneyreport.gsp
the weird thing is that if i use this:
<meta name="layout" content="mainTabPanel" />
inside moneyreport.gsp i see mainTab and the body of moneyreport.gsp as expected.
what am i doing wrong? i cannot use 3 layout on the same page?
thanks for your help guys ...
You can apply 2 layouts on the same page. In order to apply a different layout in a layout file, you need to use the applyLayout tag. Your reportTab should be something like this:
<g:applyLayout name="main">
<!doctype html>
<head>
<g:layoutHead/>
<r:layoutResources />
</head>
<body>
<div> some nice tabs here </div>
<g:layoutBody/>
</body>
<script type="text/javascript">
few line script handling chosen tab color in here
</script>
<r:layoutResources />
</body>
</html>
</g:applyLayout>
The best way is using templates because you can use as many as you want. I have this main HTML where I want to include different templates, like a menu and a generic content page:
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>
<g:layoutTitle default="Loto Tasks"/>
</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<g:layoutHead/>
</head>
<!-- Including menu -->
<g:render template="/templates/menu" />
<!-- Including generic content page -->
<g:render template="/templates/genericcontent" />
<g:layoutBody/>
</body>
</html>
I have a package named templates inside a views package, and inside templates I have two files, _menu.gsp and _genericcontent.gsp. The _genericcontent.gsp file looks like the following simple code. I could see that this is a little confusing if I insert a <head> with imports to other files, but you can import in the main file and it works:
_genericcontent.gsp
<div id="mainSearchPanel" class="searchPanel">
×
Filter panel here
</div>
<div id="search-icon" class="animate__animated animate__heartBeat animate__infinite animate__slower">
<i class="fas fa-search fa-2x"></i>
</div>
first thanks for your reply Anuj !
it kind of work but in a wrong way cuz i was getting weird html source:
i was getting 2 headers and 2 body tags so basically grails just copy paste all the layout
together and that wasn't good html page even that the browser display it right!
i found what i needed and that is simply using templates!
for example i have file called "_mainHeader.gsp" which look like this:
<!-- this is my main header for all gsp pages -->
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>IntentIQ Management System</title>
<link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}"type="text/css">
<link rel="shortcut icon" href="${resource(dir:'images/myImg',file:'favicon.ico')}" type="image/x-icon">
and from all pages i can use those lines like for example in page1.gsp:
<g:render template="/templates/mainHeader" />
</head>
<body>
<h1> this is page1 with header from mainHeader.gsp template </h1>
</body>
</html>
pay attention for who ever who read this to:
1.temmplate file name are with '_' character
2.the closing body tag in "page1.gsp" is closing the body tag started at "_mainHeader.gsp" file.
so basically this tempaltes stuff is kind of copy paste of parts of gsp pages and its working great!
thanks for your reply never the less!

Resources