Thymeleaf布局踩坑
Contents
Thymeleaf模板引擎第一次使用,之前用过的都是freemarker,结合Spring boot记录一下初次使用时遇到的一些小坑,主要是路径和包名的一些问题。下面将记录使用thymeleaf的layout布局方式展现web内容的整个过程。
Build with Maven
Thymeleaf有两种布局的方式,第一种是使用 th:include 和 th:replace这种的形式,第二种是使用layout:decorate这种。
本文使用第二种,需要引入thymeleaf-layout-dialect这个包,否则layout:decorate这种布局方式不会生效(踩到的第一个坑)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId> nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>
ViewModel
这里对html模板进行了一个封装,使其可以更加灵活方便地使用,就是一个POJO类,对Template中变动的内容进行定义,在提供一个返回ModelAndView对象的方法。
package me.icharm.orange.ViewModel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.springframework.web.servlet.ModelAndView;
import java.util.Map;
/**
* @author elias
* @email [email protected]
* @date 2018/11/21 17:40
*/
@Data
@Slf4j
public class WeuiResultPage {
/**
* 视图模板路径
*/
public String path = "weui/result_fragment";
/**
* 图标
*/
private String icon;
/**
* 标题
*/
private String title;
/**
* 内容
*/
private String content;
/**
* 绿色按钮名称
*/
private String btnPrimary;
/**
* 白色按钮名称
*/
private String btnDefault;
/**
* 绿色按钮事件
*/
private String btnPrimaryAction;
/**
* 白色按钮事件
*/
private String btnDefaultAction;
/**
* This object convert to ModelAndView object.
*
* @return ModelMap
*/
public ModelAndView modelAndView() {
try {
ModelAndView mv = new ModelAndView(this.path);
// Object to map
Map<String, String> map = BeanUtils.describe(this);
mv.addAllObjects(map);
return mv;
} catch (Exception e) {
log.error("WeuiResultPage ViewModel Object convert to map error: " + e);
return null;
}
}
}
</pre>
[/mdx_fold]
## Controller
Controller中很简单,new 一个WeuiResultPage对象,返回其对应的ModeAndView对象。
[mdx_fold title="CommonController.java"]
<!--?prettify linenums=true?-->
<pre class="prettyprint">import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import me.icharm.orange.ViewModel.WeuiResultPage;
/**
* @author elias
* @email [email protected]
* @date 2018/8/20 17:27
*/
@Controller
@RequestMapping("/common")
public class CommonController extends RootController {
@RequestMapping("/result")
public ModelAndView result() {
WeuiResultPage view = new WeuiResultPage();
view.setIcon("success");
view.setTitle("授权成功");
view.setBtnPrimary("确定");
view.setBtnPrimaryAction("wx.closeWindow();"); //使用微信JS-SDK提供的API关闭当前的网页窗口
view.setBtnDefault("取消");
view.setBtnDefaultAction("wx.closeWindow();");
return view.modelAndView();
}
}
View
layout:decorate布局的基本方式:先写一个布局页。
然后在每一个具体页面中通过layout:decorate指定使用的布局页(按照我的理解就是继承),可以选择性重写布局页中的某些fragment(片段)。
<!DOCTYPE html>
<html lang="zh-cmn-Hans"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<!-- 上面xmlns:th、xmlns:layout很重要,分别为下面的th:href, layout:fragment等标签元素指定所属的命名空间 -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0,viewport-fit=cover">
<title>Layout</title>
<!-- 引入Css、js、image等静态资源时,路径是相对于resources/static,而且前面要带上/ -->
<link rel="stylesheet" th:href="@{/style/weui.min.css}"/>
<!--<link rel="stylesheet" href="./example.css"/>-->
</head>
<body ontouchstart>
<div class="weui-toptips weui-toptips_warn js_tooltips">错误提示</div>
<div class="container" id="container">
<!-- 定义一个可以在子页面重写的fragment -->
<div layout:fragment="content"></div>
</div>
<script th:src="@{/js/zepto.min.js}"></script>
<script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script src="https://res.wx.qq.com/open/libs/weuijs/1.0.0/weui.min.js"></script>
<!--<script src="js/example.js"></script>-->
<script>
// 下面是配置微信的JS-SDk,方便在微信里面使用一个特定的Api
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识
timestamp: '', // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名,见附录1
jsApiList: ['closeWindow'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
</script>
</body>
</html>
下面是一个具体的子页面:值得注意的是第四行的layout:decorate的路径写法,和静态资源的写法类似,不过这里是相对于resources/templates, 在thymeleaf3中推荐使用~{}来选择布局页面。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{/weui/layout}">
<head>
<title th:text="${title}">结果页</title>
<!--每页单独引入的文件在这写↓-->
<!--<script th:src="@{../js/alert.js}"></script>-->
</head>
<body>
<div layout:fragment="content">
<!--在这里写入页面内容↓-->
<div id="content">
<div class="page">
<div class="weui-msg">
<div class="weui-msg__icon-area"><i th:class="'weui-icon_msg weui-icon-'+${icon}" ></i></div>
<div class="weui-msg__text-area">
<h2 class="weui-msg__title" th:text="${title}">操作成功</h2>
<p class="weui-msg__desc" th:text="${content}"></p>
</div>
<div class="weui-msg__opr-area">
<p class="weui-btn-area">
<a th:href="'javascript:'+${btnPrimaryAction}" class="weui-btn weui-btn_primary" th:text="${btnPrimary}">确定</a>
<a th:href="'javascript:'+${btnDefaultAction}" class="weui-btn weui-btn_default" th:text="${btnDefault}">取消</a>
</p>
</div>
<div class="weui-msg__extra-area">
<div class="weui-footer">
<p class="weui-footer__links">
<a href="javascript:void(0);" class="weui-footer__link">ICHARM</a>
</p>
<p class="weui-footer__text">Copyright © 2018 icharm.me</p>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
页面效果图:
