Spring - thymeleaf-layout-dialect 2버전의 head 태그설정

항목 개발환경 비고
운영체제 Widnwos10(64)
백엔드프레임워크 Spring MVC 4
개발언어 java1.8
WAS Tomcat9
템플릿엔진 Thymeleaf3
템플릿엔진레이아웃 Tymeleaf-laout-dialect2
DB mysql8
ORM mybatis

들어가기

Spring4에 Tymeleaf 3버전과 thymeleaf-layout-dialect 2버전으로 개발중 콘솔에서 아래와 같은 경고가 발생했다.

경고내용

1
WARN : nz.net.ultraq.thymeleaf.fragments.FragmentProcessor - You don't need to put the layout:fragment/data-layout-fragment attribute into the <head> section

발생하는 이유는 내 thymeleaf-layout-dialect 예제 소스가 1버전 소스를 사용하고 있어서 나오는 것이었다. 특히 head 태그 부분인데...

기존 버전에서는 레이아웃.html에 아래처럼 head 태그전용 어트리뷰트를 선언해서 사용했었다.

1
2
<head th:replace="fragments/head :: headFragment">
</head>

그리고 별도의 head fragments 전용 html을 생성했었고, 거기에 컨텐츠.html에서는 아래처럼 head 영역에 자신만의 css 나 js 등의 리소스를 적용했었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 사용자 CSS 추가 -->
<th:block layout:fragment="css">
</th:block>


<!-- 사용자 스크립트 추가 -->
<th:block layout:fragment="script">
<script th:inline="javascript">
/*<![CDATA[*/

alert("여긴 컨텐츠 영역 스크립트!");

/*]]>*/
</script>
</th:block>

하지만 thymeleaf-layout-dialect 2버전으로 올라가면서 이럴필요가 없었고, 이것 때문에 경고문구가 나온 것이다.


해결방법

아래가 레이아웃 페이지다. 보시다 시피 그냥 head 태그를 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<title>Layout page</title>
<script src="common-script.js"></script>
</head>
<body>
<header>
<h1>My website</h1>
</header>
<section layout:fragment="content">
<p>Page content goes here</p>
</section>
<footer>
<p>My footer</p>
<p layout:fragment="custom-footer">Custom footer here</p>
</footer>
</body>
</html>

아래는 컨텐츠 html이다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html layout:decorate="~{layout.html}">
<head>
<title>Content page</title>
<script src="content-script.js"></script>
</head>
<body>
<section layout:fragment="content">
<p>This is a paragraph from the content page</p>
</section>
<footer>
<p layout:fragment="custom-footer">This is some footer content from the content page</p>
</footer>
</body>
</html>

그리고 아래는 둘이 합쳐져 렌더링 된 결과이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<title>Content page</title>
<script src="common-script.js"></script>
<script src="content-script.js"></script>
</head>
<body>
<header>
<h1>My website</h1>
</header>
<section>
<p>This is a paragraph from the content page</p>
</section>
<footer>
<p>My footer</p>
<p>This is some footer content from the content page</p>
</footer>
</body>
</html>

결과적으로 보면 중복되는 title태그는 컨텐츠.html 것으로 교체되고, 레이아웃에 없는 script 태그가 자동으로 추가된다. 전보다는 훨씬 손될 부분이 적어졌다.


참고로 현재 내 예제는 다음과 같다.

1.레이아웃.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<!DOCTYPE html>
<html th:lang = "ko"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">

<head>

<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>MokaBoard</title>
<!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<link rel="stylesheet" th:href="@{/resources/bower_components/bootstrap/dist/css/bootstrap.min.css}">
<!-- Font Awesome -->
<link rel="stylesheet" th:href="@{/resources/bower_components/font-awesome/css/font-awesome.min.css}">
<!-- Ionicons -->
<link rel="stylesheet" th:href="@{/resources/bower_components/Ionicons/css/ionicons.min.css}">
<!-- DataTables -->
<link rel="stylesheet" th:href="@{/resources/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css}">
<!-- Theme style -->
<link rel="stylesheet" th:href="@{/resources/dist/css/AdminLTE.min.css}">
<!-- AdminLTE Skins. We have chosen the skin-blue for this starter
page. However, you can choose any other skin. Make sure you
apply the skin class to the body tag so the changes take effect. -->
<!-- <link rel="stylesheet" th:href="@{/resources/dist/css/skins/skin-blue.min.css}"> -->
<link rel="stylesheet" th:href="@{/resources/dist/css/skins/_all-skins.min.css}">


<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->

<!-- Google Font -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">


<!-- jQuery 3 -->
<script th:src="@{/resources/bower_components/jquery/dist/jquery.min.js}"></script>
<!-- Bootstrap 3.3.7 -->
<script th:src="@{/resources/bower_components/bootstrap/dist/js/bootstrap.min.js}"></script>
<!-- DataTables -->
<script th:src="@{/resources/bower_components/datatables.net/js/jquery.dataTables.min.js}"></script>
<script th:src="@{/resources/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js}"></script>
<!-- SlimScroll -->
<script th:src="@{/resources/bower_components/jquery-slimscroll/jquery.slimscroll.min.js}"></script>
<!-- FastClick -->
<script th:src="@{/resources/bower_components/fastclick/lib/fastclick.js}"></script>
<!-- AdminLTE App -->
<script th:src="@{/resources/dist/js/adminlte.min.js}"></script>
<!-- AdminLTE for demo purposes -->
<script th:src="@{/resources/dist/js/demo.js}"></script>

</head>

<!--
BODY TAG OPTIONS:
=================
Apply one or more of the following classes to get the
desired effect
|---------------------------------------------------------|
| SKINS | skin-blue |
| | skin-black |
| | skin-purple |
| | skin-yellow |
| | skin-red |
| | skin-green |
|---------------------------------------------------------|
|LAYOUT OPTIONS | fixed |
| | layout-boxed |
| | layout-top-nav |
| | sidebar-collapse |
| | sidebar-mini |
|---------------------------------------------------------|
-->
<body class="hold-transition skin-blue sidebar-mini">
<div class="wrapper">

<header th:replace="fragments/header :: headerFragment"></header>

<aside th:replace="samples/fragments/leftAside :: leftAsideFragment"></aside>

<div layout:fragment="content"></div> <!-- 실제 콘텐츠 -->

<footer th:replace="fragments/footer:: footerFragment"></footer>

<div th:replace="fragments/controlDiv :: controlDivFragment"></div> <!-- 모바일 버전에서 하단과 우측에 나오는 사이즈 조정바 -->

</div>
<!-- ./wrapper -->


<!-- Optionally, you can add Slimscroll and FastClick plugins.
Both of these plugins are recommended to enhance the
user experience. -->
</body>
</html>

2.컨텐츠.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
data-layout-decorate="~{samples/layout/sampleLayout}"
>

<head>
<title>게시판예제-페이징</title>
<script th:inline="javascript">

$(function () {

var result =/*[[${msg}]]*/ 'default';

if(result == 'success'){
alert("처리가 완료되었습니다.");
}


});

</script>
</head>


<div layout:fragment="content">

<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
게시판 페이징 적용
<small>advanced tables</small>
</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> Home</a></li>
<li><a href="#">Tables</a></li>
<li class="active">Data tables</li>
</ol>
</section>
<!-- Main content -->
<section class="content">
<div class="row">
<div class="col-xs-12">

<div class="box">
<div class="box-header with-border">
<h3 class="box-title">게시판 + 페이징</h3>

<div class="box-tools">
<div class="input-group input-group-sm" style="width: 150px;">
<input type="text" name="table_search" class="form-control pull-right" placeholder="Search">

<div class="input-group-btn">
<button type="submit" class="btn btn-default"><i class="fa fa-search"></i></button>
</div>
</div>
</div>

</div>
<!-- /.box-header -->
<div class="box-body">
<table class="table table-bordered">
<tr>
<th style="width: 10px">BNO</th>
<th>TITLE</th>
<th style="width: 200px">WRITER</th>
<th style="width: 200px">REGDATE</th>
<th style="width: 40px">VIEWCNT</th>
</tr>

<tr th:each="boardVO : ${list}">
<td th:text="${boardVO.bno}">BNO</td>
<td><a th:href="@{/samplehome/board/read(bno=${boardVO.bno})}" th:text="${boardVO.title}">TITLE</a></td>
<td th:text="${boardVO.writer}">WRITER</td>
<td th:text="${#dates.format(boardVO.regdate, 'yyyy-MM-dd HH:mm')}">REGDATE</td>
<td th:text="${boardVO.viewcnt}">VIEWCNT</td>
</tr>

</table>
</div>
<!-- /.box-body -->

<!-- 게시판 하단의 페이징 버튼 -->
<div class="box-footer clearfix">
<span th:text="${pageMaker.cri.page}"></span>
<ul class="pagination pagination-sm no-margin pull-right">

<li th:if="${pageMaker.prev} == true">
<a th:href="@{/samplehome/board/listPage(page=${pageMaker.startPage}-1)}">&laquo;</a>
</li>

<li th:each="idx,iterStat : ${#numbers.sequence(pageMaker.startPage,pageMaker.endPage)}" th:classappend="${pageMaker.cri.page} == ${idx} ? active : userclass">
<a th:href="@{/samplehome/board/listPage(page=${idx})}" th:text="${idx}"></a>
</li>

<li th:if="${pageMaker.next} == true and ${pageMaker.endPage > 0}">
<a th:href="@{/samplehome/board/listPage(page=${pageMaker.endPage}+1)}">&raquo;</a>
</li>

</ul>

</div>
</div>
<!-- /.box -->
</div>
<!-- /.col -->
</div>
<!-- /.row -->
</section>
<!-- /.content -->
</div>
<!-- /.content-wrapper -->

</div>


</html>

아래 th:inline 어트리뷰트는 스프링 컨트롤러로 부터 전달 받은 값을 javascript영역에 전달하기 위해 사용되었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<head>
<title>게시판예제-페이징</title>
<script th:inline="javascript">

$(function () {

var result =/*[[${msg}]]*/ 'default';

if(result == 'success'){
alert("처리가 완료되었습니다.");
}


});

</script>
</head>

현재 head 태그 자체를 fragment 할수 없어서 레이아웃.html의 head 내용이 엄청 길어졌다. 저 부분을 별도의 config.html 파일로 분리하고 싶은데 조금더 고민해봐야 할것 같다.

Related Posts

"thymeleaf-layout-dialect 깃허브페이지"