这次我们学习灵活的布局,或者说提高代码的复用性。
无论你浏览什么网页,都会(基本上)看到它——没错,它就是导航栏。
以上列举了 4 个网站的导航栏 ,在网站内部跳转时,它们基本上不变,或只是变更少数内容。
要为网站的每个页面都添上导航栏,必定会造成重复的代码,一个好的模板怎么会容忍这种事情发生?!于是, Thymeleaf 马上就提供了 th:fragment 属性来避免这种事的发生,现在我们来试试
假如有一个简易(过于简陋了,将来会解决这个问题)的导航栏:用来在网站内跳转不同的页面。
我们要在网站的每个页面都添加上(例如:主页面2、订阅点这里、主页面 的跳转链接)
先单独创建一个 menu.html 文件,专门写导航栏的代码:(也可以直接写在某一个页面内)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!DOCTYPE html > <html xmlns:th ="http://www.thymeleaf.org" > <head > <title > Good Thymes Virtual Grocery</title > </head > <body > <menu th:fragment ="menu-copy" > <p > menu</p > <a href ="/home2.html" th:href ="@{/home2}" > 主页面2</a > <a href ="/subscribe" th:href ="@{/subscribe}" > 订阅点这里</a > <a href ="/" th:href ="@{/}" > 主页面</a > </menu > </body > </html >
并给主要标签加上 th:fragment 属性,值随便取一个,就叫 menu-copy 。
接着在 home.html(主页 html)、home2.html(主页2 html)、subscribe.html(订阅界面 html),文件中加入:
1 <div th:insert ="~{menu :: menu-copy}" > </div >
或:
1 <div th:replace ="~{menu :: menu-copy}" > </div >
或:
1 <div th:include ="~{menu :: menu-copy}" > </div >
也可以把 ~{} 去掉,但是 th:include 属性在 Thymeleaf 3.0之后就不推荐使用了,那它们的区别是什么?
th:insert 是最简单的,它将目标标签作为自己本地标签的子标签插入进来,带目标标签的属性 。
th:replace 直接把本地标签换成了目标标签,带属性 。
th:include 只是将目标标签的内容(例如它的子标签)插入到本地标签来,不带属性 。
所以上述代码结果分别为:
1 2 3 4 5 6 7 8 9 10 11 <div > <menu th:fragment ="menu-copy" > <p > menu</p > <a href ="/home2.html" th:href ="@{/home2}" > 主页面2</a > <a href ="/subscribe" th:href ="@{/subscribe}" > 订阅点这里</a > <a href ="/" th:href ="@{/}" > 主页面</a > </menu > </div >
and
1 2 3 4 5 6 7 8 9 <menu th:fragment ="menu-copy" > <p > menu</p > <a href ="/home2.html" th:href ="@{/home2}" > 主页面2</a > <a href ="/subscribe" th:href ="@{/subscribe}" > 订阅点这里</a > <a href ="/" th:href ="@{/}" > 主页面</a > </menu >
and
1 2 3 4 5 6 7 8 9 <div > <p > menu</p > <a href ="/home2.html" th:href ="@{/home2}" > 主页面2</a > <a href ="/subscribe" th:href ="@{/subscribe}" > 订阅点这里</a > <a href ="/" th:href ="@{/}" > 主页面</a > </div >
它们也有共同的部分:片段表达式
· ~{templatename::selector} :templatename 是 html 文件名,selector 是文件中目标片段的 th:fragment 属性值。
· ~{templatename}:表示复制整个模板。
· ~{::selector} or ~{this::selector} :表示在当前模板中用 selector 匹配目标片段。
注意到 templatename 必须要能够被当前模板引擎使用的模板解析器正确解析 ,否则如果 selector 没有匹配到片段,会报异常,~{} 依然可以被省略。
Both templatename and selector can be fully-featured expressions(even conditionals!):
1 <div th:insert ="menu :: (${page.home}? #{menu.home}:#{menu.common})" > </div >
在上面的例子中,主页面会加载一个特有的导航栏,前提是 menu 文件中有该目标片段。
(没有主页面跳转链接的导航栏,本来就不需要φ(* ̄0 ̄))
🙋♀️:直接在主页面的模板中指定用主页面导航栏不就行了?需要判断吗?
🤦♂️:好像也是φ(゜▽゜*)♪,咳咳~这就是一个说明可以用条件式的例子罢了(🤷♂️我不管~~)
id 属性可以代替 th:fragment ,片段被复制到新的模板中之后也可以引用模板中的变量,there is a example
1 2 3 4 5 6 7 8 9 10 @GetMapping("/") public String home (Model model) { String enname = "TCJ" ; String cnname = "田超杰" ; String nname = null ; boolean admin = true ; model.addAttribute("user" ,new users(enname,cnname,nname,admin)); model.addAttribute("webs" ,webs); return "home" ; }
首先,在主页控制器往模型中添加 user 数据
然后,特制 导航栏
1 2 3 4 <menu id ="houtai-copy" th:if ="${user.admin}" > <a href ="/houtai.html" th:href ="@{/hhhhhhhoutai}" > 管理员后台</a > </menu >
最后,在主页模板中进行复制
1 <div th:insert ="menu :: #houtai-copy" > </div >
只有管理员访问网站时才会看到 管理员后台 的跳转链接。
为了进一步提高代码的复用性,Thymeleaf 提供了可参数化 的片段签名,例如 Java 的方法、C 和 Python 的函数等,那 Thymeleaf 的 “函数” 的语法是什么呢?
构造语法 1 2 3 4 <div th:fragment ="frag(onevar,twovar)" > <p th:text ="${onevar} + 'and' + ${twovar}" > ...</p > </div >
和编程语言中的构造语法基本一致。
调用 1 2 <div th:insert ="templatename::frag(${value1},${value2})" > ...</div > <div th:replace ="templatename::frag(onevar=${value1},twovar=${value2})" > ...</div >
第二个语法中变量赋值语句可以乱序,例如
1 <div th:replace ="::frag (twovar=${value2},onevar=${value1})" > ...</div >
即使在构造的时候没有声明参数,也可以在调用的时候加入 1 2 <div th:fragment ="frag" > ... </div >
1 2 <div th:replace ="::frag(onevar=${value1},towvar=${value2})" > ...</div >
1 2 <div th:replace ="::frag" th:with ="onevar=${value1},twovar=${value2}" > ...</div >
和编程语言不同的是,被调用的片段可以使用该模板的上下文变量,而 Java 只允许使用同一个类中的全局变量和传进来的参数。
现在,扩展一下语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <head th:fragment ="common_header(title,links)" > <title th:replace ="${title}" > The shopping application</title > <link rel ="stylesheet" type ="text/css" media ="all" th:href ="@{/css/shoppingapp.css}" > <link rel ="shortcut icon" th:href ="@{/images/favicon.ico}" > <script type ="text/javascript" th:src ="@{/sh/scripts/codebase.js}" > </script > <th:block th:replace ="${links}" /> </head >
1 2 3 4 5 6 7 8 9 10 11 ...<head th:replace ="base :: common_header(~{::title},~{::link})" > <title > Shopping - Main</title > <link rel ="stylesheet" th:href ="@{/css/bootstrap.min.css}" > <link rel ="stylesheet" th:href ="@{/themes/smoothness/jquery-ui.css}" > </head > ...
运行它并且结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...<head > <title > Shopping - Main</title > <link rel ="stylesheet" type ="text/css" media ="all" href ="/sho/css/shoppingapp.css" > <link rel ="shortcut icon" href ="/sho/images/favicon.ico" > <script type ="text/javascript" src ="/sho/sh/scripts/codebase.js" > </script > <link rel ="stylesheet" href ="/sho/css/bootstrap.min.css" > <link rel ="stylesheet" href ="/sho/themes/smoothness/jquery-ui.css" > </head > ...
再稍改一下
1 2 3 4 <head th:replace ="base :: common_header(~{::title},~{})" > <title > Shopping - Main</title > </head > ...
结果是:
1 2 3 4 5 6 7 8 9 10 11 12 ...<head > <title > Shopping - Main</title > <link rel ="stylesheet" type ="text/css" media ="all" href ="/sho/css/Shoppingapp.css" > <link rel ="shortcut icon" href ="/sho/images/favicon.ico" > <script type ="text/javascript" src ="/sho/sh/scripts/codebase.js" > </script > </head > ...
使用默认值的语法 _
1 2 3 4 5 6 7 8 9 10 ...<head th:replace ="base :: common_header(_,~{::link})" > <title > Shopping - Main</title > <link rel ="stylesheet" th:href ="@{/css/bootstrap.min.css}" > <link rel ="stylesheet" th:href ="@{/themes/smoothness/jquery-ui.css}" > </head > ...
'_' results in current part of the fragment not being executed at all( title = no-operation),so the result is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ...<head > <title > The shopping application</title > <link rel ="stylesheet" type ="text/css" media ="all" href ="/sho/css/shoppingapp.css" > <link rel ="shortcut icon" href ="/sho/images/favicon.ico" > <script type ="text/javascript" src ="/sho/sh/scripts/codebase.js" > </script > <link rel ="stylesheet" href ="/sho/css/bootstrap.min.css" > <link rel ="stylesheet" href ="/sho/themes/smoothness/jquery-ui.css" > </head > ...
th:insert 的表达式也可以是conditionals:
1 2 3 ...<div th:insert ="${user.isAdmin()} ? ~{common :: adminhead} : ~{}" > ...</div > ...
又或者:
1 2 3 ...<div th:insert ="${user.isAdmin()} ? ~{common :: adminhead} : _" > ...</div > ...
之前学习的
1 <div th:insert ="~{templatename :: selector}" > </div >
在 templatename or selector 未被正确解析或匹配到目标片段时会抛出异常,th:assert 属性与此类似,它通过创建一个表达式列表,并且当列表中的值均为 true 或等效与 true 时才会执行代码,否则会抛出异常:
1 <header th:fragment ="contentheader(title)" th:assert ="${!#strings.isEmpty(title)}" > ...</header >
可以用它来检验参数。
除此之外,我们可以通过模板解析器来检查模板资源是否存在,即通过它们的 checkExistence 标志。我们也可以把片段是否存在作为一个默认的条件:
1 2 3 4 5 6 7 ... <div th:insert ="~{common :: salutation} ?: _" > Welcome [[${user.name}]], click <a th:href ="@{/support}" > here</a > for help-desk support.</div > ...
简言之就是若该模板和目标片段都存在,该语法和 th:insert="~{templatename :: selector}" 基本一样;如果不存在,返回 null ,它不会报异常。
模板继承 把 th:fragment 属性移到 <html> 标签中完成构造,then 在另一个模板的 <html> 标签中加入 th:replace 属性完成调用,切不可用 th:insert 否则会造成双重 <html> 标签。