ultraq / thymeleaf-layout-dialect

A dialect for Thymeleaf that lets you build layouts and reusable templates in order to improve code reuse

Home Page:https://ultraq.github.io/thymeleaf-layout-dialect/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Nested decorations issue

AustinLin07 opened this issue · comments

The nested decorate source code setup is as following:

theme1.html:

  <html xmlns:th="https://www.thymeleaf.org"
	  xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	  layout:decorate="~{theme2.html}">
    <head></head>
    <body>
    <section layout:fragment="theme1-content">
	<p>The content provided by theme1</p>
    </section>
    </body>
  </html>

theme2.html:

	<html xmlns:th="https://www.thymeleaf.org"
	  xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
		layout:decorate="~{base.html}">
	<head></head>
	<body>
	
	<section layout:fragment="theme1-content">
		<p>Some content to be provide by theme1</p>
	</section>
	<section layout:fragment="base-content">
		<p>Custom content for base</p>
	</section>

	</body>
	</html>

base.html:

	<html xmlns:th="https://www.thymeleaf.org"
			xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
	<head></head>
	<body>
	<section layout:fragment="base-content">
		<p>The custom content to be provided here</p>
	</section>
	</body>
	</html>

The entry view is theme1.html, when browser open theme1.html, the expected result is:

The content provided by theme1
Custom content for base

But the actual (issue) result is:

Custom content for base

I have tried thymeleaf-layout-dialect version 2.4.1, 2.3.0, 2.1.2, all have same result.
The expect render process (flow) is:
(1) theme2 decorate theme1:

	The content provided by theme1
	<section layout:fragment="base-content">
		<p>Custom content for base</p>
	</section>

then (2) base decorate theme2:

	The content provided by theme1
	Custom content for base

If change base.html as following:

(v2) base.html:

	<html xmlns:th="https://www.thymeleaf.org"
			xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
	<head></head>
	<body>
	<section layout:fragment="theme1-content">
		<p>(base) Some content to be provide by theme1</p>
	</section>
	<section layout:fragment="base-content">
		<p>The custom content to be provided here</p>
	</section>
	</body>
	</html>

The render result is:

Some content to be provide by theme1
Custom content for base

The "theme1-content" still not replace by theme1's.
Any suggestions for this use case? Thanks in advance.

I too wonder why one cannot nest decorations in this way.
Looking at django templates, this is absolutely possible and a nice way for reusing existing code or extending upon existing code.

@AustinLin07 Looking at the existing test Decorate-DeepHierarchy.thtest having a deep hierarchy should actually work.

I just tested this and it works just fine with multi level layout template hierarchies.

page.html

<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{derived}">
<head>
    <title>Page title</title>
    <script src="child-script.js"></script>
</head>
<body>
<div layout:fragment="content">
    <p>This is a paragraph from the child page</p>
</div>
<div layout:fragment="section2">
    <p>da bottom up</p>
</div>
</body>
</html>

derived.html

<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{base}">
<head>
    <script src="parent-script.js"></script>
</head>
<body>
<section layout:fragment="section">
    <header>
        <h1>My website</h1>
    </header>
    <div layout:fragment="content">
        <p>Page content goes here</p>
    </div>
    <footer>
        <p>My footer</p>
    </footer>
</section>
</body>
</html>

base.html

<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
    <script src="grandparent-script.js"></script>
</head>
<body>
<section layout:fragment="section">
    <p>Middle layout section goes here</p>
</section>
<section layout:fragment="section2">
    <p>Bottom layout section</p>
</section>
</body>
</html>

output

<!DOCTYPE html>
<html>
<head>
    <title>Page title</title>
    <script src="grandparent-script.js"></script>
<script src="parent-script.js"></script>
<script src="child-script.js"></script>
</head>
<body>
<section>
    <header>
        <h1>My website</h1>
    </header>
    <div>
    <p>This is a paragraph from the child page</p>
</div>
    <footer>
        <p>My footer</p>
    </footer>
</section>
<div>
    <p>da bottom up</p>
</div>
</body>
</html>

What is not working, though, is when you do it like you presented in your example code.

I got it, I will give it a try. The "decorate" seems be reversed in derived.html's layout:decorate="~{base}". Put it simply,

page.html  layout:decorate="~{derived}":
     derived decorate page
derived.html  layout:decorate="~{base}":
     base is decorated by derived (that is, 'base' is 
     for the purpose of put actual content)

The decorate skeleton is falling on first decorate statement, layout:decorate="~{derived}".
Above is my roughly explanation, it would be appreciated if there are some official docs instructing this nested usage and the behavior. :-)

I fiddled around with this for a while and even debugged the code.
Looking at other template engines such as django or jinja, they all behave this way.
I guess that it is nearly impossible to automatically merge content from templates further down in the inheritance hierarchy into their respective layout base templates.
One has to declare required layout fragments in the top level template and can increase the level of complexity as is shown in the existing test case Decorate-DeepHierarchy.thtest.

I found a minor issue though, with a template in the hierarchy redeclaring a layout fragment that was already declared in the decorated base template and with the content template once again declaring the same layout fragment. In this case, the fragment declared by the decorated template will be used instead of that from the content template.

I will open a new issue for this, even though it is a fringe case but newbies like me might stumble over it and it should at least be documented or fixed in code.

@silkentrance #197 (comment) It works properly, thanks.

Correcting previous comment #197 (comment), the render flow is:
'base' --> replace content by 'derived' --> replace content by 'page'