テンプレートレイアウトで内容の切り替え
ヘッダとかフッタが決まってて本文的な部分だけ切り替えたい時のやり方。
すぐ忘れるので記録。
なお掲載コードはSpringBootを前提としている。
やり方
「レイアウト」を決めているテンプレートに「本文」を断片(fragment)を取り込む。
それだけ。
「本文」の方に「レイアウト」を読み込もうとするのは筋が悪い。
HTMLテンプレート
「_layout.html」で『th:include』するところでプリプロセスで読み込み先のファイル名を解決させてる。
「include/content1」と「include/content2」は直接表示しないのでサブフォルダ切ってそっちに入れている。
ディレクトリ階層が変わるとテンプレート単独で見ての作業がやりにくい事もあるので区別が付くなら適当な命名ルールでもいいかも。
includeされる断片側の変数の名前空間はinclude元でModelに直接入れたオブジェクトも見えちゃうので、混乱するのが嫌なら「必ずセットされる名前」なんかは資料化しておくと無用なトラブルがなくせる。
<!DOCTYPE html>
<html data-th-object="${bean}">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../static/css/page.css" data-th-href="@{/css/page.css}">
<title data-th-text="*{title}">TITLE TEXT</title>
</head>
<body>
<header class="header">
<section>
<div class="header-title">HEADER TEXT;HEADER TEXT;HEADER TEXT;</div>
</section>
<nav class="header-menu">
<ul class="header-menu__list">
<li class="header-menu__list__element"><a href="#">TOP</a></li>
<li class="header-menu__list__element"><a href="#">WHAT US</a></li>
<li class="header-menu__list__element"><a href="#">NEWS</a></li>
<li class="header-menu__list__element"><a href="#">DOCS</a></li>
<li class="header-menu__list__element"><a href="#">FORUM</a></li>
<li class="header-menu__list__element"><a href="#">SUPPORT</a></li>
<li class="header-menu__list__element"><a href="#">LINKS</a></li>
</ul>
</nav>
</header>
<article class="content" data-th-include="~{include/__*{viewName}__ :: content ( ${bean} )}">
</article>
<footer class="footer">
<section>
<div class="footer-text">FOOTER TEXT;FOOTER TEXT;FOOTER TEXT;</div>
</section>
</footer>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../../static/css/page.css">
<title>TITLE TEXT</title>
</head>
<body>
<article class="content" data-th-fragment="content ( model )" data-th-object="${model}">
<div>
<span data-th-text="*{text}">TEXT</span>
</div>
</article>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../../static/css/page.css">
<title>TITLE TEXT</title>
</head>
<body>
<article class="content" data-th-fragment="content ( model )" data-th-object="${model}">
<div>
<span data-th-text="*{text}">TEXT</span>
<p data-th-text="*{body}"></p>
</div>
</article>
</body>
</html>
モデルクラス
ModelAttributeとして使うBeanは以下のような感じ。
必須属性があるならinterfaceやabstract classで実装を強いると間違いがなくなる。
Thymeleafでは表示用の項目はgetterしか必要ない(setterはなくてもいい)
面倒くさがりはLombokしてもいいよ。
public class Bean1 {
private String viewName;
private String title;
private String text;
/* getter/setter は省略 */
}
public class Bean2 {
private String viewName;
private String title;
private String text;
private String body;
/* getter/setter は省略 */
}
コントローラ
コントローラはこんな感じになる。どうせほぼ同じなので片方だけ。
View名はレイアウトを使う都合で固定化するのでStringじゃなくてModelAndViewで返す方がちょっとスマートな気がする。
(PathVariableの部分のあるなしでオーバーロードしてもちゃんと呼び分けられる。みんな知ってるね)
@Controller
public class Controller2 {
@ModelAttribute
Bean2 modelAttribute() {
return new Bean2();
}
@RequestMapping("content2")
ModelAndView content2(Bean2 bean) {
bean.setViewName("content2");
bean.setTitle("PLACTICE2");
bean.setText("PLACTICE2 CALLED!");
bean.setBody("BODY TEXT!");
return useLayout(bean);
}
@RequestMapping("content2/{title}")
ModelAndView content2(Bean2 bean, @PathVariable("title") String title) {
bean.setViewName("content2");
bean.setTitle(title);
bean.setText("PLACTICE2 CALLED!");
bean.setBody("BODY TEXT! [" + title + "]");
return useLayout(bean);
}
private ModelAndView useLayout(Bean2 bean) {
ModelAndView mav = new ModelAndView();
mav.addObject("bean", bean);
mav.setViewName("_layout");
return mav;
}
}
以上、解散!