一直在用flex做前端,都习惯面向对象的组件模式装配ui,最近想做一些基于网页的以信息展示为主的网站系统,我简称信息发布内核,内核两字表明我只是想做基本功能,不是做一个大而全的内容发布系统。首先得考虑的就是页面的布局模式。页面的布局模式与所选用的页面技术相关,初步计划选择thymeleaf,源于它的natural理念。
thymeleaf的eclipse插件:https://github.com/thymeleaf/thymeleaf-extras-eclipse-plugin
页面布局分为包含布局和层次布局,包含布局,一般通过th:include以及th:replace来实现,include和replace不一样的是一个包含在host tag里面,一个是指替换host tag,thymeleaf的包含布局和jsp的include不同的方面在于,thymeleaf可以包含某个文件的某一个部分,而jsp的必须包含整个文件。比如:<div th:replace="fragments/header :: header">...</div>,fragments/header是指被包含的模板文件,::header的header指被包含模板文件中的被包含部分。可以用this:header或者::header都是指包含本页面的部分。被包含的文件的被包含部分需要加上属性:th:fragment="header"
thymeleaf可以基于dom selector来处理包含,而不用显示地调用th:fragment,比如:<div th:include="http://www.thymeleaf.org :: p.notice" >...</div>,那么将会调用tag p且.class="notice“的片段,这个最大的好处就是包含别的网站的网页部分。以前的做法有用ajax的,有用iframe的,还有用javabean获取后传给前端的。thymeleaf这种处理方式相对合理。采用dom这种方式,需要templateEngine.addTemplateResolver(urlTemplateResolver());
包含语法的模板文件和片段都可以通过表达式来指定,比如<div th:replace="fragments/footer :: ${#authentication.principal.isAdmin()} ? 'footer-admin' : 'footer'">。
thymeleaf包含模板也支持参数包含,比如:
<div th:fragment="alert (type, message)"
class="alert alert-dismissable" th:classappend="'alert-' + ${type}">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<span th:text="${message}">Test</span>
</div>
表示alert这个片段有两个参数:type和message,那么调用的时候:
<div th:replace="fragments/alert :: alert (type='danger', message=${errorMessage})">...</div>
参数化片段提高片段的可重用性.
题外话,我当年特喜欢infoglue的设计理念,事过好多年,依稀记得slot和param binding两个让我一直很喜欢的理念,现在的thymeleaf都可以做到。继续...
能够从spring controller返回片段,比如:
if (AjaxUtils.isAjaxRequest(requestedWith)) {
return SIGNUP_VIEW_NAME.concat(" :: signupForm");
}
return SIGNUP_VIEW_NAME;
当用ajax请求的时候,后端返回的视图为片段的内容。
包含布局,由于是在每个页面包含公共代码,因此natural特性没有影响,不过如果一旦需要切换包含另外的公共部分或者改变统一页面布局模式,那么包含布局就显得力不从心。层次布局,目前流行的有Tiles和sitemesh,一般是将布局等公用部分放在parent里面,显示时将每个子页面的具体内容融合到parent里面来对外展现,优点是更好的维护性,缺点是natural不够。
本次信息发布内核采用层次布局模式。两层模板展现,父级模板负责布局展现,子级模板负责内容展现。
针对spring mvc和thymeleaf做一下扩展:
1、定义注释:layout注释可以用在类和方法上。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Layout {
String value() default "";
}
2、定义interceptor:
public class ThymeleafLayoutInterceptor extends HandlerInterceptorAdapter {
private static final String DEFAULT_LAYOUT = "layouts/default";
private static final String DEFAULT_VIEW_ATTRIBUTE_NAME = "view";
private String defaultLayout = DEFAULT_LAYOUT;
private String viewAttributeName = DEFAULT_VIEW_ATTRIBUTE_NAME;
public void setDefaultLayout(String defaultLayout) {
Assert.hasLength(defaultLayout);
this.defaultLayout = defaultLayout;
}
public void setViewAttributeName(String viewAttributeName) {
Assert.hasLength(defaultLayout);
this.viewAttributeName = viewAttributeName;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (!modelAndView.hasView()) {
return;
}
String originalViewName = modelAndView.getViewName();
if (isRedirectOrForward(originalViewName)) {
return;
}
String layoutName = getLayoutName(handler);
modelAndView.setViewName(layoutName);
modelAndView.addObject(this.viewAttributeName, originalViewName);
}
private boolean isRedirectOrForward(String viewName) {
return viewName.startsWith("redirect:") || viewName.startsWith("forward:");
}
private String getLayoutName(Object handler) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Layout layout = getMethodOrTypeAnnotation(handlerMethod);
if (layout == null) {
return this.defaultLayout;
} else {
return layout.value();
}
}
private Layout getMethodOrTypeAnnotation(HandlerMethod handlerMethod) {
Layout layout = handlerMethod.getMethodAnnotation(Layout.class);
if (layout == null) {
return handlerMethod.getBeanType().getAnnotation(Layout.class);
}
return layout;
}
}
3:配置interceptor:
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ThymeleafLayoutInterceptor());
}
}
4:测试类:
@Controller
class SigninController {
@Layout(value = "layouts/blank")
@RequestMapping(value = "signin")
String signin() {
return "signin/signin";
}
}
5:测试布局模板页面:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>...</head>
<body>
<div th:raplace="fragments/header :: header">
Header
</div>
<div th:replace="${view} :: content">
Content
</div>
<div th:replace="fragments/footer :: footer">
Footer
</div>
</body>
</html>
6:测试内容模板页面:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>...</head>
<body>
<div class="container" th:fragment="content">
<!-- /* Handle the flash message */-->
<th:block th:if="${message != null}">
<div th:replace="fragments/alert :: alert (type=${#strings.toLowerCase(message.type)}, message=${message.message})"> </div>
</th:block>
<p>
Hello <span th:text="${#authentication.name}">User</span>!
Welcome to the Spring MVC Quickstart application!
</p>
</div>
</body>
</html>
posted on 2014-02-10 14:00
不做浮躁的人 阅读(7689)
评论(1) 编辑 收藏