Thymeleaf 3 (not) building layout sequentially? - spring

I know about existance of layout dialect for Thymeleaf (https://github.com/ultraq/thymeleaf-layout-dialect), but before jumping into said dialect dating pre thymeleaf 3, I wanted to explore newer fragment expressions (introduced in TL3).
So as per docs - I can define base.html that will work almost the same way that the layout dialect allows fragments to be managed.
base.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="base(title, links, content)">
<title th:replace="${title}"></title>
<link rel="stylesheet" type="text/css" media="all" href="/css/main.css" />
<th:block th:replace="${links}"></th:block>
</head>
<body>
<header th:replace="~{header :: header}"></header>
<div th:replace="${content}"></div>
<footer th:replace="~{footer::footer}"></footer>
</body>
</html>
...but then when I use this in my home.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="base::base(~{::title}, ~{::link}, ~{::content})">
<title th:text="#{home.title}"></title>
<link rel="stylesheet" th:href="test">
<link rel="stylesheet" th:href="test1">
<link rel="stylesheet" th:href="test2">
</head>
<body>
<div th:fragment="content">
<span>TEST CONTENT</span>
</div>
</body>
</html>
...it acts like home.html only evaluates itself and arguments it passes to base.html because this is product:
<!DOCTYPE html>
<html>
<head>
<title>My home page</title>
<link rel="stylesheet" type="text/css" media="all" href="/css/main.css" />
<link rel="stylesheet" href="test"><link rel="stylesheet" href="test1"><link rel="stylesheet" href="test2">
</head>
<body>
<div>
<span>TEST CONTENT</span>
</div>
</body>
</html>
As you can see, title is evaluated in home.html and passed to base.html, same goes for 3 links I provided in home.html. Also content gets passed and placed in proper place. What is missing? Everything that is not argument of base.html fragment. Thymeleaf ignores evaluating my header and footer and just removes them.
Let me just note that If I were to place header/footer in home.html inside content they will be evaluated like they should - from header.html with selector header: "~{header :: header}".
Am I missing something crucial as to how this whole thing is supposed to work? What's the point of being able to define fragments that will work as layout if they can't evaluate themselves and need everything passed from "child file (caller)"?

It seems to me you mixed fragments and replacements together. The head replacement works well because you declared it correctly. But why did you declare a th:replace attribute in base.html like
<header th:replace="~{header :: header}"></header>? It is not within a fragment, not a parameter, therefore it must be replaced from somewhere.
As far as I understand you expect fragments in base.html and replacements in home.html. Then make both the header and footer fragments and declare corresponding th:replace tags in home.html.
<div th:replace="${content}"></div>
This also doesn't work because it is not within a th:fragment tag.
The bottom line: fix the hierarchy of tags and the logic of replacement.

Like Xaltotun pointed out - I needed to fix my stuff.
I've placed base(args...) in header so my passed fragments werent visible in body, thus everything failed.
Let me just add some code:
Layout.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:fragment="layout(content, title, meta)">
<head>
<title th:text="${title}">Default Title</title>
<link rel="stylesheet" type="text/css" media="all" href="/css/style.css" />
<th:block th:replace="${meta}" />
</head>
<body>
<div class="viewport">
<header class="website-header">
...
</header>
<div th:replace="${content}"></div>
<th:block th:replace="staticContentTemplate :: testFragment" />
<footer class="website-footer">
...
</footer>
</div>
</body>
</html>
Home.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="layout::layout(~{::content}, #{home.title}, ~{::meta})">
<head>
<th:block th:fragment="meta">
<link rel="stylesheet" th:href="test">
</th:block>
</head>
<body>
<div th:fragment="content">
<span>TEST CONTENT</span>
</div>
</body>
</html>
Result is as expected:
Home.html provides title, content and meta.
Layout.html gives everything else, and also copies additional template from different source staticContentTemplate :: testFragment
Tip for others: notice that I placed args in layout in this order - content since its always present, title that can be optional and meta that might not be needed - this allows us to call layout with just content: layout(~{::content}) (shorter = better).

Related

How can I create a general page, that I can use every where inside the app using Thymleaf and Spring boot?

I'm trying to understand how use Thymeleaf, I have a structure like this:
I have a default.html that work like the most general page, I put there some inclusion like general css, bootstrap and so on.. The I replace using th:replace the footer and a navbar. I use this page as base line for all the other pages.
My problem is: how can I use this page for all the other pages?
For example if I have 2 pages, page A and B and both of them need bootstrap, the app's css and so on, I don't want write the code to include them twice, but only once. So to do that I think I have to put page A inside default.html if I want show A and B in the other case.
At least, I done in this way using JSP.
How can I do it? Is it possible using Thymeleaf?
I tried to do somenthig like that but id doesn't work
DEFAULT.HTML
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<title th:text="#{application_name}"></title>
<link rel="icon" type="image/x-icon" th:href="#{/static/favicon.ico}"/>
<script th:src="#{/webjars/jquery/jquery.min.js}"></script>
<script th:src="#{/webjars/bootstrap/js/bootstrap.min.js}"></script>
<link rel="stylesheet" th:href="#{/webjars/bootstrap/css/bootstrap.min.css}"/>
<link rel="stylesheet" th:href="#{/css/mio.css}"/>
<link rel="stylesheet" th:href="#{/webjars/font-awesome/css/font-awesome.min.css}"/>
</head>
<body>
<div th:replace="~{fragments/header :: header}"></div>
<div class="container">
<div layout:fragment="content">
<p>Your page content goes here</p>
</div>
</div>
<div th:replace="~{fragments/footer :: footer}"></div>
</body>
</html>
It is totally possible!
We have some steps to do it with Thymeleaf:
Configurations:
1 - Include the thymeleaf layout dialect into your project:
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
<version>2.1.2</version>
</dependency>
2 - I do not know how is your webConfig, but with Spring, we have to add this configuration to template engine:
#Bean
public TemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setEnableSpringELCompiler(true);
//your templateResolver
engine.setTemplateResolver(templateResolver());
//here it is!
engine.addDialect(new LayoutDialect());
return engine;
}
The default html:
<!DOCTYPE html>
<html lang="pt-BR"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
<!-- Necessary to thymeleaf layout dialect-->
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<link rel="stylesheet" type="text/css" th:href="#{/layout/stylesheets/vendors/bootstrap.css}" />
</head>
<body>
<header>your Heder here</header>
<!--Here what you want, it gonna find the other html that contains "maincode"-->
<section layout:fragment="maincode"></section>
<footer>yourFooter</footer>
<script th:src="#{/javascript/vendors/jquery-2.2.4.min.js}"></script>
<script th:src="#{/layout/javascripts/bootstrap.min.js}"></script>
<th:block layout:fragment="javascript-extra"></th:block>
</body>
</html>
The other html:
<!DOCTYPE html>
<html lang="pt" xmlns="http://w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
<!--Tell who is the default html-->
layout:decorate="layout/defaultHtml">
<body>
<section layout:fragment="maincode">
bla bla bla
</section>
</body>
</html>
Any question, just ask!

laravel #include with #section in <head>

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...?

local bootstrap with laravel 4.2

I have a default blade file that yields other contents this is where im calling the link to style sheet and javascript i downloaded bootstrap 3.3.5 and created a link to it here is my default blade
<!DOCTYPE html>
<html>
<head>
<title></title>
<link rel="stylesheet" type="text/css" href="bootstrap335/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="//cdn.datatables.net/plug- ins/1.10.7/integration/bootstrap/3/dataTables.bo‌​otstrap.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="//cdn.datatables.net/1.10.7/js/jquery.dataTables.min.js"> </script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="//cdn.datatables.net/1.10.7/js/jquery.dataTables.min.js"></script>
</head>
<body>
<div class="container">
#yield('content')
</div>
</body>
</html>
The bootstrap is working properly in my views except one. i don't know the problem because it works fine on the others and it is extends the default blade file

Laravel 5.1 linking style sheet not working

I am probably missing something very simple but, when I link my style sheet I get:
<link media="all" type="text/css" rel="stylesheet" href="http://laravel.dev:8000/public/css/masterCss.css">
showing on the webpage rather then linking to my css file.
My Html looks like:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>The Nub Life</title>
{{ Html::style('/public/css/masterCss.css') }}
</head>
<body>
#yield('content')
</body>
</html>
Why is this happening and how can I fix it?
the stylesheet href url, should not contain the word public. this is how you can generate url for your css asset files
<link media="all" type="text/css" rel="stylesheet" href="{{ URL::asset('css/masterCss.css') }}">
Suppose you have a .css file in your public/your_project_name/css/style.css
Simply write
<link href="{{ URL::asset('your_project_name/css/style.css') }}" rel="stylesheet">

Using ruby gem coderay gets me inline text in *.html.erb file

I am focused on the wrong layer of abstraction here, but can't figure out where.
I have this file views/pages/overview.html.erb
<%= stylesheet_link_tag "cust/coderay"%>
<h1>Overview</h1>
<hr>
Here's my code test:
<%= html = CodeRay.scan("puts 'Hello, world!'", :ruby).div(:line_numbers => :table)%>
<hr>
Back <%=link_to "home", "home"%>.
<hr>
It took <%="%.3f" %(Time.now-#start_time)%> seconds to generate this page.
To my surprise, the pages renders like so:
When I view source on the page I get:
<!DOCTYPE html>
<html>
<head>
<title>Dash</title>
<link href="/assets/application-all.css?body=1" media="all" rel="stylesheet" type="text/css" />
<link href="/assets/all/pages.css?body=1" media="all" rel="stylesheet" type="text/css" />
<script src="/assets/jquery.js?body=1" type="text/javascript"></script>
<script src="/assets/jquery_ujs.js?body=1" type="text/javascript"></script>
<script src="/assets/pages.js?body=1" type="text/javascript"></script>
<script src="/assets/application.js?body=1" type="text/javascript"></script>
<meta content="authenticity_token" name="csrf-param" />
<meta content="SydEiDhSNHuEE6vCfr4rajIksxBbqnm89sddC08msjs=" name="csrf-token" />
</head>
<body>
<h1>Overview</h1>
<hr>
Here's my code test:
<table class="CodeRay"><tr>
<td class="line-numbers" title="double click to toggle" ondblclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>
</pre></td>
<td class="code"><pre>puts <span style="background-color:hsla(0,100%,50%,0.05)"><span style="color:#710">'</span><span style="color:#D20">Hello, world!</span><span style="color:#710">'</span></span></pre></td>
</tr></table>
<hr>
Back home.
<hr>
It took 0.006 seconds to generate this page.
</body>
</html>
Why is the bracketed css displaying as inline text? What should my usage of coderay look like here?
Many thanks -
Rails escapes your HTML by default in ERB templates. You need to turn off HTML escaping like so:
<%=raw CodeRay.scan("puts 'Hello, world!'", :ruby).div(:line_numbers => :table) %>
See more at this question and these release notes.

Resources