與父標籤溝通


如〈了解生命週期與架構〉中提過,若是巢狀標籤中的內層標籤,則還會呼叫標籤處理器的 setParent() 方法,並傳入外層標籤處理器的實例。

同樣地,在這邊以開發 <f:choose><f:when><f:otherwise> 作為示範。首先是標籤處理器的開發:

package cc.openhome;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;

public class ChooseTag extends TagSupport {
    private boolean matched;

    @Override
    public int doStartTag() throws JspException {
        matched = false;
        return EVAL_BODY_INCLUDE;
    }

    public boolean isMatched() {
        return matched;
    }

    public void setMatched(boolean matched) {
        this.matched = matched;
    }
}  

ChooseTag 基本上什麼都不作,之所以要重新定義 doStartTag(),因為 TagSupportdoStartTag() 方法預設傳回 SKIP_BODY,因為 <f:choose> 用來包括內層標籤,你不能忽略本體內容,所以必須傳回 EVAL_BODY_INCLUDE。另一方面,記得 Tag 的實 例會在不使用時放回標籤池,所以若標籤上一次執行過後有狀態存在,下次再度從標籤池中取出時,必須考慮進行狀態重置的動作,這個動作我們放在 doStartTag() 中完成。

package cc.openhome;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.JspTag;
import javax.servlet.jsp.tagext.Tag;

public class WhenTag extends BodyTagSupport {
    private boolean test;

    @Override
    public int doStartTag() throws JspException {
        JspTag parent = null;
        if (!((parent = getParent()) instanceof ChooseTag)) {
            throw new JspTagException("必須置於choose標籤中");
        }

        if (((ChooseTag) parent).isMatched() || !test) {
            return SKIP_BODY;
        }

        ((ChooseTag) parent).setMatched(true);
        return EVAL_BODY_INCLUDE;
    }

    public void setTest(boolean test) {
        this.test = test;
    }
} 

在這邊,doStartTag() 判斷是否包括在 <f:choose> 標籤中,判斷先前的 <f:when> 是否曾經通過測試,以決定是否要執行或忽略自己的本體內容。

package cc.openhome;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.tagext.JspTag;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;

public class OtherwiseTag extends TagSupport {
    @Override
    public int doStartTag() throws JspException {
        JspTag parent = null;
        if (!((parent = getParent()) instanceof ChooseTag)) {
            throw new JspTagException("必須置於choose標籤中");
        }

        if (((ChooseTag) parent).isMatched()) {
            return SKIP_BODY;
        }

        return EVAL_BODY_INCLUDE;
    }
} 

基本上,OtherwiseTagdoStartTag()WhenTag 是類似的,只不過不用檢查 test`屬性。記得在TLD檔案中加入標籤定義:

f.tld

<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
   web-jsptaglibrary_2_1.xsd">
    <tlib-version>1.0</tlib-version>
    <short-name>f</short-name>
    <uri>https://openhome.cc/jstl/fake</uri>
    // 略...
    <tag>
        <name>choose</name>
        <tag-class>cc.openhome.ChooseTag</tag-class>
        <body-content>JSP</body-content>
    </tag>
    <tag>
        <name>when</name>
        <tag-class>cc.openhome.WhenTag</tag-class>
        <body-content>JSP</body-content>
        <attribute>
            <name>test</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
            <type>boolean</type>
        </attribute>
    </tag>
    <tag>
        <name>otherwise</name>
        <tag-class>cc.openhome.OtherwiseTag</tag-class>
        <body-content>JSP</body-content>
    </tag>
</taglib>