最新完整的源码在: http://code.taobao.org/p/bigfoot_v2/src/tags/。
首先声明 Tag File 是门老技术,好用之余知道的人却不多!
以前我们抽取一段JSP代码,整合到完整的页面中,一般使用 include 指令(例如<%@include file="public/nav.jsp"%>),这比较简单的说。而今天要介绍的是 include 的“高级版”——Tag Files。它着实十分强大,不仅可以完全替代 include,而且还可以创建高级的可复用标签库,使得快速开发和维护动态网页比以前更加容易,甚至网页作者无须学习 Java 程序语言本身,就能开发出全新的动态网页。学习过程中,发现以下两点比较有趣,而且都是往事:
只恨俺当年有眼不识泰山、相见恨晚呀~呵呵~闲话休提,速速进入 Tag Files 之旅吧!
首先说说怎么使用 Tag File。拿一个简单的例子。第一步创建被应用的 HTML 片段,假设当前是 Hello.tag,将它放置在 WEB-INF/tags/ 目录下。你可以在 Tag File 里直接使用 JSP 的语法来制作标签。标签文件的扩展名必须是“.tag”。
<%
out.println("Hello from tag file.");
%>
然后在页面中使用自定义标签时,需要先导入标签库,再使用标签。具体在 JSP 网页使用 Hello.tag 的方法如下:
<%@ taglib prefix="myTag" tagdir="/WEB-INF/tags" %>
其中,prefix 用于确定标签前缀;而tagdir标签库路径下存放很多 Tag File,每 Tag File 对应一个标签。最后执行的结果如下:
Hello from tag file.
十分简单是吧?其实,再复杂的 Tag Files,也要比原生写 SimpleTag、写 Java 代码来得简单。所有 JSP 里面能做的事情,几乎在 Tag Files 里面都可以做的,包括模板语言 JSTL——不过我就没有推荐使用 JSTL,而是直接 Java if/for 来控制某些页面逻辑,也就是 <% ...Java code...%>。出于学习成本的原因,我不想再重复学习类似的东西。当然,EL 表达式我是推荐使用的,如果能够使用 EL 表达式的,尽量使用,能避免 Java Code <%%> 的尽量避免。
该小节小结如下:
导入格式为 <%@taglib prefix="test" tagdir="/WEB-INF/tags"%>
使用:
include 指令有个缺点,就是不能对被包含的页面片段进行参数的传递。你可能会想到使用
虽然可以传参数,但
<%@tag description="header 內容" pageEncoding="UTF-8"%>
<%@attribute name="title" type="java.lang.String" description="标题" require="true"%>
${title}
本体(本体的描述乃相对于被包含的 Tag File 而言)调用方式:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="html" tagdir="/WEB-INF/tags" %>
Foo...
像“title="首页"”这样就完成了 title “参数”的传递!当然准确说是 Attribute 属性。并且声明该项属性不能不填(require="true")。而且要求是 String 类型,别的不行哦。还带有 description 说明呢。另外请注意 type 这里不支持泛型,所以没有 Map
你可能会问,像字符串的类型就可以 title="首页" 这样传,但其他类型呢?你可以直接输入值,如 array="<%=homeService.getMsg()%>";也可以把其他类型保存在 request.setAttribute("foo", Object) 中,然后通过 title="${foo}" 传就可以了。另外对于 Tag File 里面的值获取,EL 表达式也是通用的。
该小节小结如下:
tag 指令如同 JSP 网页的 page 指令,用来设定标签文件 <%@tag display-name="" body-content="" dynamic-attributes="" small-icon="" large-icon="" description="" example=""language="" import="" pageEncoding="" isELIgnored="">
支持 HTML 片段传递是 Tag Files 的一大特点,简直令笔者心灵神往。心想,模板机制能做到这样,非常不错!什么标签的继承,都不在话下!
我们把上面的例子改改,变成 Tag:
<%@tag description="HTML 懶人標籤" pageEncoding="UTF-8"%>
<%@attribute name="title"%>
${title}
注意这次 Tag 变成一个完整的 HTML 页面,而且里面有个
这样的话,那么本体是这样的:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="html" tagdir="/WEB-INF/tags" %>
哈哈~不知你看到没有,
更妙的是,
<%@include file="public/nav.jsp"%>
如此一来即可规避 body-content="scriptless" 不可出现 <% %>、<%= %> 或 <%! %> 之问题。
更妙的是,两套模板之间还可以相互嵌套的,请看:
<%@tag pageEncoding="UTF-8" description="呼叫客户端组件"%>
<%@attribute fragment="true" name="button" required="false" description="按钮"%>
<%@taglib prefix="dhtml" tagdir="/WEB-INF/tags/common/dhtml"%>
一个 doBoady 是不够的,——来多几个怎么样?回答是绝对肯定的!只要我们把 Attibute 声明为 fragment="true" 即可。例如下面撰写一个 table.tag:
<%@attribute name="frag1" fragment="true"%>
<%@attribute name="frag2" fragment="true"%>
frag1
frag2
在这个 Tag File 中,将 attribute 的属性设定为 Fragment,然后想取得指定的 Fragment 的话,就可以使用
<%@taglib prefix="caterpillar" tagdir="/WEB-INF/tags/"%>
Fragment 1 here
Fragment 2 here
JSP 网页中,同样的是使用
frag1
Fragment 1 here
frag2
Fragment 2 here
该小节小结如下:
这个指令用来设定自定义标签的属性。其中 name 表示属性的名字:<%@attribute name="" required="" fragment="" rtexprvalue="" type="" description=""%>
Tag File 运算过的结果,都可以返回给本体,灰常强大是吧!?没错哦~的确可以。我们通过 <%@variable%> 指令完成。
注意下面的例子, doBody 多了 var="code":
<%@attribute name="preserve" fragment="true" %>
<%@variable name-given="code" scope="NESTED" %>
本体文件:
<%@taglib prefix="caterpillar" tagdir="/WEB-INF/tags"%>
${ code }
PROGRAM MAIN
PRINT 'HELLO'
END
也许大家会问,这个所谓“强大”的功能有什么用呢?——很简单,你想想,把逻辑封装起来里,让变化的只是标签,也就是本体里面的——实际上也是如此,通常变化的都是 HTML 标签。那么要输出的值便是这些变量了,返回给本体,让本体去控制显示。标签这个特性在制作“标签迭代器”的时候十分适用,且看例子:
<%@tag pageEncoding="UTF-8" description="文章功能模块" import="java.util.Map, com.ajaxjs.core.Util"%>
<%@attribute name="array" type="Map[]" required="true" description="要迭代的数据,是一个Map数组"%>
<%@attribute name="itemTag" fragment="true" required="true" description="文章列表"%>
<%@variable name-given="current" %>
<%
// 列出栏目
if(Util.isNotNull(array)){
for(Map item : array){
jspContext.setAttribute("current", item);
%>
-
<%
}
}
%>
本体:
设置 name-from-attribute 还可以指定传递变量的名称!由主体来定义而不是 Tag File 来定义,也就是说,把上述例子的 current 改为你喜欢的!
该小节小结如下:
这个指令用来设定标签文件的变量,其中 name-given 表示直接指定变量的名称: <%@variable name-given="" name-from-attribute="" alias="" variable-class="" declare="" scope="" desription="">
Tag File 是自定义标签的简化。事实上,就如同 JSP 文件会编译成 Servlet 一样, TagFile 也会编译成 Tag 处理类,自定义标签的后台依然由标签处理类完成,而这个过程由容器完成。关于编译,参见:
前面提過Tag File會被容器轉譯,實際上是轉譯為javax.servlet.jsp.tagext.SimpleTagSupport的子類別。以Tomcat為例,Errors.tag轉譯後的類別原始碼名稱是Errors_tag.java。在Tag File中可以使用out、config、request、response、session、application、jspContext等隱含物件,其中jspContext在轉譯之後,實際上則是javax.servlet.jsp.JspContext物件。
所以,Tag File在JSP中,並不是靜態包含或動態包含,在Tag File中撰寫Scriplet的話,其中的區域變數也不可能與JSP中Scriptlet溝通。
JspContext是PageContext的父類別,JspContext上定義的API不像PageContext有使用到Servlet API,原本在設計上希望JSP的相關實現可以不依賴特定技術(例如Servlet),所以才會有JspContext這個父類別的存在。
附加一个例子:用 TagFile 完成一个迭代器:
<%@tag pageEncoding="UTF-8" description="文章功能模块" import="java.util.Map, com.ajaxjs.util.Util"%>
<%@tag trimDirectiveWhitespaces="true"%>
<%@attribute name="array" type="Map[]" required="true" description="要迭代的数据,是一个Map数组"%>
<%@attribute name="itemTag" fragment="true" required="true" description="文章列表"%>
<%@attribute name="isOutterInclude" type="Boolean" required="false" description="是否不包括外层标签,外层标签通常指的是 ul"%>
<%@attribute name="isOutterItemInclude" type="Boolean" required="false" description="是否不包括外层 item 标签,外层标签通常指的是 li"%>
<%@variable name-given="current"%>
${isOutterInclude? '' : ''}
<%
// 列出栏目
if(Util.isNotNull(array)) {
for(Map, ?> item : array) {
jspContext.setAttribute("current", item);
%>
${isOutterItemInclude? '' : '- '}
${isOutterItemInclude? '' : ' '}
<%
}
}else{
System.out.println("iterator.tag 列表控件输出没有数据");
%>
没有记录
<%
}
%>${isOutterInclude? '' : '
'}
参见资源: