Boot Thymeleaf高级教程
注意:本页面内容为W3xue原创,未经授权禁止转载,违者必究!
来源:W3xue 发布时间:2019/9/29 12:31:24
上一节中我们已经简单介绍了Spring Boot 中 Thymeleaf的基本配置和使用,本节让我们来看看其更加详尽的用法。本节课程主要分为以下几部分:
一、标准表达式语法
标准表达式分一共为四类:
1.变量表达式($)
2.选择或星号表达式(*)
3.文字国际化表达式(#)
4.URL 表达式(@)
下面我们分别来介绍。
1.变量表达式
变量表达式即 OGNL 表达式或 Spring EL 表达式(在 Spring 术语中也叫 model attributes)。如下所示:
- ${session.user.name}
其中点号,表明它是对象的成员,是上下级关系。我们来看一个例子。在“lucky.html”底部,增加2个标签:
- <p th:text="${student1.name}"></p>
- <li th:each="stu : ${students}"><span th:text="${stu.name}"></span></li>
当然,这里的student1和students都是预设的标签名字,我们还没有在后端绑定数据给它。此外,这里出现了一个新语法“th:each”,我们后面也会讲到。我们现在就来给这2个标签绑定数据,首先需要先在MainController类的顶部,添加自动装配的代码:
- @Autowired
- private MainDao mainDao;
然后我们在MainController类中的getlucky路由(也是方法名)中,添加如下的绑定:
- MainBean mbstudent1=mainDao.getOne(1); //取得ID为1的学生对象
- mv.addObject("student1",mbstudent1); //将其绑定到student1标签上
- List<MainBean> mbStudents=mainDao.getStudentByName(""); //没有关键词,将会选择全部学生
- mv.addObject("students",mbStudents);//将其绑定到students标签上
你会发现,访问“http://localhost:8080/jiaocheng/getlucky”时会多出如下HTML代码,这些都是取得的结果:
- <p>张三</p>
- <li><span>张三</span></li>
- <li><span>李四</span></li>
- <li><span>王麻子</span></li>
2.选择或星号表达式(*)
星号表达式,提供了一种容器,这个容器里的标签,不必在重复写对象的名字,只需要写对象的成员变量名即可。我们来看一个例子,在“lucky.html”底部,增加1个标签即可,这里我们借用上一小节绑定的student1标签:
- <p th:object="${student1}">
- 姓名:<span th:text="*{name}"></span>,年龄:<span th:text="*{age}"></span>,年级:<span th:text="*{grade}"></span>
- </p>
这里,我们使用了“th:object”标签,表明th标签里将包含对象。
其实以上代码完全等价于:
- <p th:object="${student1}">
- 姓名:<span th:text="${student1.name}"></span>,年龄:<span th:text="${student1.age}"></span>,年级:<span th:text="${student1.grade}"></span>
- </p>
可以发现,星号表达式是省去了选择上文的代码,它可以与美元符号混合使用。
重新运行程序后,访问“http://localhost:8080/jiaocheng/getlucky”时会多出如下HTML代码:
- <p>
- 姓名:<span>张三</span>,年龄:<span>8</span>,年级:<span>二年级</span>
- </p>
3.文字国际化表达式(#)
文字国际化表达式作用是,从另外一个.properties的外部文件获取某个标签的值。这个.properties文件,和application.yml(或默认的application.properties)一样,存放在“src/main/resources”文件夹。文字国际化表达式和引用application.yml的键值类似,这里不再详细介绍。
4.URL 表达式(@)
URL 表达式指的是把一个有用的上下文或会话信息添加到 URL,这个过程经常被叫做 URL 重写。比如:
- @{/getlucky}
还可以设置参数:
- @{/getlucky(id=${studentId})}
或者这样表示相对路径:
- @{../hello}
在“lucky.html”页面底部,添加下面的代码:
- <a href="https://www.w3xue.com/" th:href="@{/hello}">链接到哪里</a>
这个链接会最终指向“http://localhost:8080/jiaocheng/hello”。
二、表达式支持的语法
我们仍然有许多疑问,比如,如果想在th标签里直接表示文本,或者使用if判断,还有上文提到的循环each语法怎么办?我们一起来看看Thymeleaf标签支持的语法:
1、字面(Literals)
2、文本操作(Text operations)
3、算术运算(Arithmetic operations)
4、布尔操作(Boolean operations)
5、比较和等价(Comparisons and equality)
6、条件运算符(Conditional operators)
7、循环运算符
下面分别来一一介绍。
1、字面(Literals)
字面语法是最常用的语法,它包含以下具体类型:
·文本文字(Text literals): 'one text', 'Another one!',…
·数字文本(Number literals): 0, 34, 3.0, 12.3,…
·布尔文本(Boolean literals):true, false
·空(Null literal):null
·文字标记(Literal tokens):one, sometext, main,…
比如,我想要在th标签中,显示原始的文本,就需要用到单引号,然后让其与标签混合。我们可以在“lucky.html”页面底部,添加下面的代码:
- <p th:text="'学生的姓名是:'+${student1.name}"></p>
我们会发现,最终结果是:
- <p>学生的姓名是:张三</p>
数字文本常常会和运算符(如加减乘除)一起使用,布尔文本、null常常与if一起使用,后面会提到运算符和if语法。
2、文本操作(Text operations)
文本操作在上文中我们已经用过,即拼接原始文本和标签的+号。文本操作包括:
·字符串连接(String concatenation):+
·文本替换(Literal substitutions):|姓名是: ${name}|
单竖线允许原始文本和标签混用而不用使用单引号和+号连接,例如,上一个例子中使用单引号与下面的例子完全等价:
- <p th:text="|学生的姓名是:${student1.name}|"></p>
3、算术运算(Arithmetic operations)
算术运算主要用于数字的操作,包括下面2中类型:
·二元运算符(Binary operators):+, -, *, /, %
·减号(单目运算符)Minus sign (unary operator):-
例如,二元运算符可以将下面代码中该学生的年龄加10岁:
- <p th:text="${student1.age}+10"></p>
将显示为:
- <p>18</p>
但是,二元运算符中的+号却不能与文本操作符(+|)还有单引号配合使用,否则,将不进行计算,将被视为字符串连接的+号。
- <p th:text="'学生10年后的年龄是:'+${student1.age}+10"></p>
将显示为:
- <p>学生10年后的年龄是:810</p>
但是除法却可以混用,例如:
- <p th:text="'学生年龄的一半是:'+${student1.age}/2"></p>
将显示为:
- <p>学生年龄的一半是:4</p>
4、布尔操作(Boolean operations)
布尔操作语法主要包括2类:
·二元运算符(Binary operators):and, or
·布尔否定(一元运算符)Boolean negation (unary operator):!, not
在“lucky.html”页面底部,添加下面的代码:
- <p th:text="${student1.age}==8 and ${student1.name}=='李四'"></p>
- <p th:text="${student1.age}==8 or ${student1.name}=='李四'"></p>
- <p th:text="!${student1.age}==8">张三的年龄为8岁,但是加上否定前缀后,这里将显示false</p>
- <p th:text="not ${student1.age}==8">张三的年龄为8岁,但是加上否定前缀后,这里将显示false</p>
将显示如下的结果:
- <p>false</p>
- <p>true</p>
- <p>false</p>
- <p>false</p>
5、比较和等价(Comparisons and equality)
比较运算符在上一个例子中已经使用了一个(==,即一般程序语言中的是否相等判断),主要分为以下2类:
·比较(Comparators):>, <, >=, <= (gt, lt, ge, le)
·等值运算符(Equality operators):==, != (eq, ne)
这跟大多数程序语言的用法相同,这里不再赘述。
6、条件运算符(Conditional operators)
条件运算符对应一般程序语言中的if-else分支结构,可以分为以下几类:
·If/unless-then结构:(if/unless) ? (then)
·If-then-else结构(三目运算符):(if) ? (then) : (else)
·Default结构: (表达式或值 ?: defaultvalue)
·switch结构:switch-case
我们来看第一种结构,在“lucky.html”页面底部,添加下面的代码:
- <p th:if="${student1.age==8}" >学生的年龄是8</p>
- <p th:unless="${student1.age==8}" >学生的年龄不是8</p>
第一行是说,如果这个学生的年龄是8,就显示该段落。第二行正好相反,如果年龄是8则不显示该段落。
结果自然是这样,第二段没有显示出来:
- <p >学生的年龄是8</p>
第二种结构实际上是一种三目运算符,而第三种Default结构,它是一种判断表达式或值是否有效的结构,如果表达式或值有效,就使用该值,否则,就使用“defaultvalue”:
- <p th:text="( ${student1.age}==8 ? '是的' : '不是' )"></p>
- <p th:text="( ${student1.age}==9 ? '是的' : ( ${student1.age} ?: '未知' ) )"></p>
上面的代码运行结果为:
- <p>是的</p>
- <p>8</p>
这个例子表明,所有这些Thymeleaf支持的语法可以任意的进行组合。
第四种,switch结构,使用方法如下:
- <div th:switch="${student1.age}">
- <p th:case="7">年龄为7岁</p>
- <p th:case="8">年龄为8岁</p>
- <p th:case="*">年龄默认值</p>
- </div>
其结果如下:
- <div>
- <p>年龄为8岁</p>
- </div>
7、循环运算符
最后我们来看看循环运算符,之前我们就看到了一个例子:
- <li th:each="stu : ${students}"><span th:text="${stu.name}"></span></li>
这个例子中,会把students标签包含的所有成员,都拿出来循环打印一遍。而students标签包含的单个成员,在这里被代指为“stu”。
实际上,我们还可以带入一个“状态变量”,例如,我们把上一个例子改成如下的形态:
- <li th:each="stu,i: ${students}"><span th:text="${i.count}+','"></span><span th:text="${stu.name}"></span></li>
这里,在“stu”后面多了一个i,这里被用来作为计数器了,后面用到了${i.count}。上面的代码结果如下:
- <li><span>1,</span><span>张三</span></li>
- <li><span>2,</span><span>李四</span></li>
- <li><span>3,</span><span>王麻子</span></li>
i这个状态变量,除了“count”计数器属性,还有其他的属性:
·index:当前迭代对象的 index(从0开始计算)
·count: 当前迭代对象的 index(从1开始计算)
·size:被迭代对象的大小
·current:当前迭代变量
·even/odd:布尔值,当前循环是否是偶数/奇数(从0开始计算)
·first:布尔值,当前循环是否是第一个
·last:布尔值,当前循环是否是最后一个
三、常用th标签
我们在上面已经使用了th:text,th:utext,th:href,th:inline,th:object,th:each,th:if,th:unless等标签,其实,Thymeleaf还有其他更多的标签,如下表所示:
关键字 | 功能介绍 | 案例 |
---|---|---|
th:id | 替换id | <input th:id="'xxx' + ${collect.id}"/> |
th:text | 文本替换 | <p th:text="${collect.description}">description</p> |
th:utext | 支持html的文本替换 | <p th:utext="${htmlcontent}">conten</p> |
th:object | 替换对象 | <div th:object="${session.user}"> |
th:value | 属性赋值 | <input th:value="${user.name}" /> |
th:with | 变量赋值运算 | <div th:with="isEven=${prodStat.count}%2==0"></div> |
th:style | 设置样式 | th:style="'display:' + @{(${sitrue} ? 'none' : 'inline-block')} + ''" |
th:onclick | 点击事件 | th:onclick="'getCollect()'" |
th:each | 属性赋值 | tr th:each="user,userStat:${users}"> |
th:if | 判断条件 | <a th:if="${userId == collect.userId}" > |
th:unless | 和th:if判断相反 | <a th:href="@{/login}" th:unless=${session.user != null}>Login</a> |
th:href | 链接地址 | <a th:href="@{/login}" th:unless=${session.user != null}>Login</a> /> |
th:switch | 多路选择 配合th:case 使用 | <div th:switch="${user.role}"> |
th:case | th:switch的一个分支 | <p th:case="'admin'">User is an administrator</p> |
th:fragment | 布局标签,定义一个代码片段,方便其它地方引用 | <div th:fragment="alert"> |
th:include | 布局标签,替换内容到引入的文件 | <head th:include="layout :: htmlhead" th:with="title='xx'"></head> /> |
th:replace | 布局标签,替换整个标签到引入的文件 | <div th:replace="fragments/header :: title"></div> |
th:insert | 布局标签,插入整个标签到引入的文件 | <div th:insert="fragments/header :: title"></div> |
th:selected | selected选择框 选中 | th:selected="(${xxx.id} == ${configObj.dd})" |
th:src | 图片类地址引入 | <img class="img-responsive" alt="App Logo" th:src="@{/img/logo.png}" /> |
th:inline | 定义js脚本可以使用变量 | <script type="text/javascript" th:inline="javascript"> |
th:action | 表单提交的地址 | <form action="subscribe.html" th:action="@{/subscribe}"> |
th:remove | 删除某个属性 | <tr th:remove="all"> 1.all:删除包含标签和所有的孩子。2.body:不包含标记删除,但删除其所有的孩子。3.tag:包含标记的删除,但不删除它的孩子。4.all-but-first:删除所有包含标签的孩子,除了第一个。5.none:什么也不做。这个值是有用的动态评估。 |
th:attr | 设置标签属性,多个属性可以用逗号分隔 | 比如th:attr="src=@{/image/aa.jpg},title=#{logo}",此标签不太优雅,一般用的比较少。 |
th:attrappend | 追加标签属性 | 比如<p class="btn" th:attrappend="class=${' ' + classname}" />,会在class上追加一个后台绑定的classname值 |
th:block | 跟HTML标签平行的标签,而不作为HTML标签的属性使用,在结果中会消失 | 比如<th:block><p>这里是文本</p></th:block>,只会显示为<p>这里是文本</p>,一般用在布局上 |
还有非常多的标签,这里只列出最常用的几个,由于一个标签内可以包含多个th:x属性,其生效的优先级顺序为:
include,each,if/unless/switch/case,with,attr/attrprepend/attrappend,value/href,src ,etc,text/utext,fragment,remove。
例如,我们可以2个标签,让学生的名字链接到其详细信息页:
- <a th:text="${student1.name}+'的信息'" th:href="@{/stuinfo(id=${student1.id})}"></a>
结果为:
- <a href="/jiaocheng/stuinfo?id=1">张三的信息</a>
四、Thymeleaf 的布局
在引入布局之前,让我们先了解三个标签:th:insert、th:replace、th:include。我们先来看一个例子,在“lucky.html”页面底部,添加下面的代码:
- <p id="someEle">我是一个模板文本框</p>
- <p th:insert="this::#someEle"></p>
- <p th:replace="this::#someEle"></p>
- <p th:include="this::#someEle"></p>
你会得到如下结果:
- <p id="someEle">我是一个模板文本框</p>
- <p><p id="someEle">我是一个模板文本框</p></p>
- <p id="someEle">我是一个模板文本框</p>
- <p>我是一个模板文本框</p>
第一个是原始的结果,底下三行可以看出三个标签的区别,th:insert标签是在原有控件内部插入被引用的控件,th:replace是拿被引用的控件替换原有控件,th:include是拿被引用的控件的内容插入到原有控件中。Thymeleaf 3.0+版本不推荐使用th:include,所以我们应当避免使用(感谢网友“周”QQ67****330提醒原有错误,奖励5元现金红包)。网上其他教程说th:replace将被抛弃,这种说法属于以讹传讹,是错误的。
我们可以在其他的页面上引用这个控件,语法是“文件名::#控件ID”。首先,我们在“templates”文件夹,新建一个“greeting.html”文件,然后放入标签引用:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Title</title>
- </head>
- <body>
- <p th:insert="lucky::#someEle"></p>
- <p th:replace="lucky::#someEle"></p>
- <p th:include="lucky::#someEle"></p>
- </body>
- </html>
然后,在MainController类,申明如下路由和方法:
- @RequestMapping(value = "/greeting")
- public ModelAndView greeting(ModelAndView mv) {
- mv.setViewName("/greeting");
- return mv;
- }
最后访问“http://localhost:8080/jiaocheng/greeting”,得到如下结果(body中的主要内容):
- <p><p id="someEle">我是一个模板文本框</p></p>
- <p id="someEle">我是一个模板文本框</p>
- <p>我是一个模板文本框</p>
现在我们来看看布局,我们在“lucky.html”页面底部,添加下面的代码:
- <th:block th:fragment="commonFooter">
- <footer>这里是页脚<br />然后是一堆备案、版权的文字</footer>
- </th:block>
注意,这里使用的是“th:block”标签,这个标签比较特殊,不同于其他标签作为HTML控件的属性,它和HTML控件是平行的,但是最终它会被删掉,但是它注册的布局片段却可以起作用。
然后在“greeting.html”底部加上这样的代码:
- <th:block th:insert="lucky::commonFooter"></th:block>
这里跟刚才的插入控件的语法有一点不同,那就是没有“#”号,直接使用布局片段的名字就可以了。
以后,不管是什么模板文件,只要引用“<th:block th:insert="lucky::commonFooter"></th:block>”这样的代码,就会自动插入lucky.html中定义的这个代码段,一旦修改lucky.html中的这个代码段,其他页面显示的内容会自动更改,非常适合页眉、页脚这种高重复使用的代码段。
当然,建议把这种高度重复的代码,放到专门的模板文件里,方便其他页面集中引用。同时,引用还可以附带上参数。比如,我们在“templates”文件夹,再建一个“iframe.html”:
- <!DOCTYPE html>
- <html lang="en">
- <th:block th:fragment="customHeader(title,keywords)">
- <head>
- <meta charset="UTF-8">
- <title th:text="${title}"></title>
- <meta name="keywords" th:content="${keywords}" />
- </head>
- </th:block>
- <body>
- <th:block th:fragment="commonFooter">
- <footer>这里是页脚<br />然后是一堆备案、版权的文字</footer>
- </th:block>
- </body>
- </html>
这个“iframe.html”无需定义任何路由,因为它只是用来存放一些模板控件。
然后,我们把“greeting.html”改成引用这个文件的head和footer:
- <!DOCTYPE html>
- <html lang="en">
- <th:block th:insert="iframe::customHeader('你好','关键词1,关键词2')"></th:block>
- <body>
- <p th:insert="lucky::#someEle"></p>
- <p th:replace="lucky::#someEle"></p>
- <p th:include="lucky::#someEle"></p>
- <th:block th:insert="iframe::commonFooter"></th:block>
- </body>
- </html>
访问“http://localhost:8080/jiaocheng/greeting”,得到的结果如下:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>你好</title>
- <meta name="keywords" content="关键词1,关键词2" />
- </head>
- <body>
- <p><p id="someEle">我是一个模板文本框</p></p>
- <p id="someEle">我是一个模板文本框</p>
- <p>我是一个模板文本框</p>
- <footer>这里是页脚<br />然后是一堆备案、版权的文字</footer>
- </body>
- </html>
可以看到,页面标题和关键词已经被替换成了带入的文本。
你也可以在后端定义变量。例如,把“greeting.html”的head引用改为如下代码:
- <th:block th:insert="iframe::customHeader(${title},${keyword1}+','+${keyword2})"></th:block>
然后在“greeting”路由中,加入如下绑定:
- mv.addObject("title","标题:你好");
- mv.addObject("keyword1","天空");
- mv.addObject("keyword2","大海");
重新运行项目后,访问“http://localhost:8080/jiaocheng/greeting”,head部分会变成后端定义的值:
- <head>
- <meta charset="UTF-8">
- <title>标题:你好</title>
- <meta name="keywords" content="天空,大海" />
- </head>
注意:本页面内容为W3xue原创,未经授权禁止转载,违者必究!
来源:W3xue 发布时间:2019/9/29 12:31:24