Custom Action

(史帝芬, idealist@gcn.net.tw, 2002/11/11, JSP 1.1, JDK 1.4.0)

 

1.      Abstract

Custom action又稱為custom tagJSP制定這項規格的用意在於提供一個可使java codehtml儘可能分開的機制,避免在網頁中因為混雜了java codehtml,而使網頁難以撰寫和維護。

Custom action其實只是一種特殊的bean,這個特殊的bean有個正式的名稱,稱為tag handler class,如果要實作tag handler class一定要繼承TagSupportBodyTagSupport這兩個類別。繼承這兩個類別的tag的差別在於繼承TagSupporttag沒有body,繼承BodyTagSupporttagbody

 

1 TagSupportBodyTagSupport的繼承圖

 

2.      Simple custom action

2 繼承TagSupporttag

 

現在我們寫一個最簡單的custom action,它只會根據傳入的參數傳回西元或民國的時間,這個custom action沒有body所以只要繼承TabSupport就好。使用這個custom actionJSP寫法如下…

 

<%@ page contentType="text/html; charset=BIG5" %>

<html>

<head>

<title>Now Tag Example</title>

</head>

<body>

 

<H1>Now Tag Example</H1>

<%@ taglib uri="/WEB-INF/tlds/now.tld" prefix="idealist" %>

 

現在時間: <idealist:now calendar="民國" />

 

</body>

</html>

 

3 Now Tag執行結果

 

JSP中引入的now.tldcustom action的定義檔,透過這個定義檔JSP container可以順利的找到相關的custom action類別檔。定義檔除了定義類別檔外,也定義了版本編號、prefix所用名稱(shortname)tag名稱、還有attribute…因為這個custom action是繼承TagSupport,所以bodycontent設為empty。如下所示…

 

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE taglib

        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"

        "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">

       

<taglib>

 

<tlibversion>1.0</tlibversion>

<jspversion>1.1</jspversion>

<shortname>idealist</shortname>

<info>

        A tag library from Steven Shi,

        http://www.idealist.idv.tw/

</info>

 

<tag>

        <name>now</name>

        <tagclass>tw.idv.idealist.ctl.Now</tagclass>

        <bodycontent>empty</bodycontent>

        <attribute>

                <name>calendar</name>

                <required>true</required>

        </attribute>

</tag>

 

</taglib>

 

沒有bodytag類別繼承的是TagSupportTagSupport提供的method雖然相當多,但是對程式員來說,比較重要的只有doStartTagdoEndTag。如圖2所示,tag一開始執行就會載入attribute的值,然後就會執行doStartTag,在這裡程式員可以做一些初始化的動作,當JSP container遇到結尾標籤時,會呼叫doEndTag,大多數tag handler都會改寫doEndTag來進行實際的工作。

        doStartTagdoEndTag都要傳回值,傳回的值如下表所示…因為這個custom action沒有body,所以doStartTag一定是傳回SKIP_BODY,表示不用處理body

 

Method

傳回值

doStartTag

SKIP_BODY

EVAL_BODY_BUFFERED

doEndTag

EVAL_PAGE

SKIP_PAGE

 

以下為tag handler class程式碼…

 

package tw.idv.idealist.ctl;

 

import javax.servlet.jsp.tagext.TagSupport;

import javax.servlet.jsp.*;

import javax.servlet.http.*;

import java.io.*;

import java.util.Calendar;

import java.text.DecimalFormat;

 

public class Now extends TagSupport {

  private String m_strC = null;

 

  public int doStartTag() throws JspException {

    return SKIP_BODY;

  }

 

  public int doEndTag() throws JspException {

    HttpServletResponse response = (HttpServletResponse) pageContext.getResponse();

    DecimalFormat df2 = new DecimalFormat("00");

    Calendar c = Calendar.getInstance();

    int nYear = 0;

    if (m_strC.equals("民國"))

      nYear = 1911;

    else

      m_strC = "西元";

    try {

      JspWriter out = pageContext.getOut();

      out.print(m_strC + (c.get(Calendar.YEAR)-nYear) + "/" + df2.format(c.get(Calendar.MONTH)+1) + "/" + df2.format(c.get(Calendar.DAY_OF_MONTH)) + " " + df2.format(c.get(Calendar.HOUR_OF_DAY)) + ":" + df2.format(c.get(Calendar.MINUTE)) + ":" + df2.format(c.get(Calendar.SECOND)));

    }

    catch (IOException e) {}

 

    return EVAL_PAGE;

  }

 

  public void setCalendar(String strC) {

    m_strC = strC;

  }

}

 

3.      Custom action with body

4 繼承BodyTagSupportTag

 

第二個範例我們將寫一個會將html特殊符號編碼的cuatom action,讓寫在body中的文字可以以html的原始碼輸出,因為這個custom action會用到body,所以要繼承BodyTagSupport,底下我們先看JSP怎麼寫,還有它的輸出…

 

<%@ page contentType="text/html; charset=BIG5" %>

<html>

<head>

<title>

encodeHTMLExample

</title>

</head>

<body>

<H1>encodeHTML Tag Example</H1>

<%@ taglib uri="/WEB-INF/tlds/encodeHTML.tld" prefix="idealist" %>

 

底下的文字是經過編碼的…

<idealist:encodeHTML>

<html>

<body>

這是測試…

<body>

</html>

</idealist:encodeHTML>

 

</body>

</html>

 

5 encodeHTML Tag執行結果

 

底下為設定檔,特別注意bodycontent不再是empty而是JSP,因為這個custom actionbody是需要填東西的。

 

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE taglib

        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"

        "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">

       

<taglib>

 

<tlibversion>1.0</tlibversion>

<jspversion>1.1</jspversion>

<shortname>idealist</shortname>

<info>

        A tag library from Steven Shi,

        http://www.idealist.idv.tw/

</info>

 

<tag>

        <name>encodeHTML</name>

        <tagclass>tw.idv.idealist.ctl.encodeHTML</tagclass>

        <bodycontent>JSP</bodycontent>

</tag>

 

</taglib>

 

繼承了BodyTagSupportcustom action提供了比較多的method,如圖4,比繼承TagSupportcuatom action多了setBodyContentdoInitBodydoAfterBody,由名稱不難了解其功用都在於控制Body。底下為各method的傳回值,要特別注意的是,如果程式要處理body,則在doStartTag中要傳回EVAL_BODY_BUFFERED,還有doAfterBody因為只處理一次body,不重複處理,所以傳回SKIP_BODY

 

Method

傳回值

doStartTag

SKIP_BODY

EVAL_BODY_BUFFERED

doAfterBody

SKIP_BODY

EVAL_BODY_AGAIN

doEndTag

EVAL_PAGE

SKIP_PAGE

 

以下為tag handler class程式碼…

package tw.idv.idealist.ctl;

 

import javax.servlet.jsp.tagext.*;

import javax.servlet.jsp.*;

import java.io.*;

 

public class encodeHTML extends BodyTagSupport {

  public int doAfterBody() throws JspException {

    BodyContent bc = getBodyContent();

    JspWriter out = getPreviousOut();

    try {

      out.write(toHTMLString(bc.getString()));

    } catch (IOException e) { }

 

    return SKIP_BODY;

  }

 

  private String toHTMLString(String strT) {

    String[] strSpecial = { "&", "<", ">", "\"" };

    String[] strMark = { "&amp;", "&lt;", "&gt;", "&quot;" };

    StringBuffer bstrT = new StringBuffer(strT);

 

    int nStart;

    for(int i=0; i<strSpecial.length; i++) {

      while ((nStart = bstrT.indexOf(strSpecial[i])) != -1) {

        bstrT = bstrT.replace(nStart, nStart+1, strMark[i]);

      }

    }

 

    return bstrT.toString();

  }

}

 

4.      Looping custom action

第三個範例我們將寫一個迴圈的custom action,迴圈的custom action和一般的custom action的差別只在於doAfterBody這個method傳回值不同,如果要繼續繞迴圈,就傳回EVAL_BODY_AGAIN,否則傳回SKIP_BODY

底下為JSP

<%@ page contentType="text/html; charset=BIG5" %>

<%@ taglib uri="/web-inf/tlds/loop.tld" prefix="idealist" %>

 

<html>

<head>

<title>

LoopExample

</title>

</head>

<body>

 

<idealist:loop reps="10">

  Current value

</idealist:loop>

 

</body>

</html>

 

6 Loop Tag執行結果

 

底下為設定檔,需特別注意有個屬性reps

 

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE taglib

        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"

        "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">

       

<taglib>

 

<tlibversion>1.0</tlibversion>

<jspversion>1.1</jspversion>

<shortname>idealist</shortname>

<info>

        A tag library from Steven Shi,

        http://www.idealist.idv.tw/

</info>

 

<tag>

        <name>loop</name>

        <tagclass>tw.idv.idealist.ctl.LoopTag</tagclass>

        <bodycontent>JSP</bodycontent>

        <attribute>

                <name>reps</name>

                <required>true</required>

        </attribute>

</tag>

 

</taglib>

 

底下為t ag handler class程式碼…

package tw.idv.idealist.ctl;

 

import javax.servlet.jsp.tagext.*;

import javax.servlet.jsp.*;

import java.io.*;

 

public class LoopTag extends BodyTagSupport {

  private int reps;

 

  public void setReps(String repeats) {

    try {

      reps = Integer.parseInt(repeats);

    }

    catch(NumberFormatException e) {

      reps = -1;

    }

  }

 

  public int doAfterBody() {

    if (reps-- >= 1) {

      BodyContent body = getBodyContent();

      try {

        JspWriter out = body.getEnclosingWriter();

        out.println(body.getString() + ": " + reps + "<br>");

        body.clearBody();

      } catch (IOException e) {

        System.out.println("Error in LoopTag: " + e);

      }

      return EVAL_BODY_AGAIN;

    }

    else {

      return SKIP_BODY;

    }

  }

}

 

5.      Nested custom action

第四個範例我們將寫一個巢狀的custom action,巢狀的custom action和一般的custom action並沒有什麼差別,需注意的只有findAncestorWithClass這個method,這個method是內層的tag用來尋找外層的tag用的,它會由內而外找,直到找到為止,如果到了最外層仍然找不到,這個method將傳回null

底下為JSP

 

<%@ page contentType="text/html; charset=BIG5" %>

<html>

<head>

<title>If Tag Example</title>

</head>

<body>

 

<H1>If Tag Example</H1>

<%@ taglib uri="/WEB-INF/tlds/if.tld" prefix="idealist" %>

 

<idealist:if>

  <idealist:condition>true</idealist:condition>

  <idealist:then>show then</idealist:then>

  <idealist:else>show else</idealist:else>

</idealist:if>

 

</body>

</html>

 

7 If tag執行結果

 

底下為設定檔…

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE taglib

        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"

        "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">

       

<taglib>

 

<tlibversion>1.0</tlibversion>

<jspversion>1.1</jspversion>

<shortname>idealist</shortname>

<info>

        A tag library from Steven Shi,

        http://www.idealist.idv.tw/

</info>

 

<tag>

        <name>if</name>

        <tagclass>tw.idv.idealist.ctl.IfTag</tagclass>

        <bodycontent>JSP</bodycontent>

</tag>

 

<tag>

        <name>condition</name>

        <tagclass>tw.idv.idealist.ctl.IfConditionTag</tagclass>

        <bodycontent>JSP</bodycontent>

</tag>

 

<tag>

        <name>then</name>

        <tagclass>tw.idv.idealist.ctl.IfThenTag</tagclass>

        <bodycontent>JSP</bodycontent>

</tag>

 

<tag>

        <name>else</name>

        <tagclass>tw.idv.idealist.ctl.IfElseTag</tagclass>

        <bodycontent>JSP</bodycontent>

</tag>

 

</taglib>

 

底下為t ag handler class程式碼…

package tw.idv.idealist.ctl;

 

import javax.servlet.jsp.tagext.*;

import javax.servlet.jsp.*;

import javax.servlet.*;

import java.io.*;

 

public class IfTag extends TagSupport {

  private boolean condition;

  private boolean hasCondition = false;

 

  public void setCondition(boolean condition) {

    this.condition = condition;

    hasCondition = true;

  }

 

  public boolean getCondition() {

    return condition;

  }

 

  public void setHasCondition(boolean flag) {

    this.hasCondition = flag;

  }

 

  public boolean hasCondition() {

    return hasCondition;

  }

 

  public int doStartTag() {

    return EVAL_BODY_INCLUDE;

  }

}

 

 

package tw.idv.idealist.ctl;

 

import javax.servlet.jsp.tagext.*;

import javax.servlet.jsp.*;

import javax.servlet.*;

import java.io.*;

 

public class IfConditionTag extends BodyTagSupport {

  public int doStartTag() throws JspTagException {

    IfTag parent = (IfTag) findAncestorWithClass(this, IfTag.class);

    if (parent == null) {

      throw new JspTagException("condition not inside if");

    }

    return (EVAL_BODY_BUFFERED);

  }

 

  public int doAfterBody() {

    IfTag parent = (IfTag) findAncestorWithClass(this, IfTag.class);

    String bodyString = getBodyContent().getString();

    if (bodyString.trim().equals("true")) {

      parent.setCondition(true);

    }

    else {

      parent.setCondition(false);

    }

    return (SKIP_BODY);

  }

}

 

package tw.idv.idealist.ctl;

 

import javax.servlet.jsp.tagext.*;

import javax.servlet.jsp.*;

import javax.servlet.*;

import java.io.*;

 

public class IfThenTag extends BodyTagSupport {

  public int doStartTag() throws JspTagException {

    IfTag parent = (IfTag) findAncestorWithClass(this, IfTag.class);

    if (parent ==null) {

      throw new JspTagException("then not inside if");

    } else if (!parent.hasCondition()) {

      String warning = "condition tag must come before then tag";

      throw new JspTagException(warning);

    }

    return EVAL_BODY_BUFFERED;

  }

 

  public int doAfterBody() {

    IfTag parent = (IfTag) findAncestorWithClass(this, IfTag.class);

    if (parent.getCondition()) {

      try {

        BodyContent body = getBodyContent();

        JspWriter out = body.getEnclosingWriter();

        out.print(body.getString());

      }

      catch (IOException ioe) {

        System.out.println("Error in IfThenTag: " + ioe);

      }

    }

    return SKIP_BODY;

  }

}

package tw.idv.idealist.ctl;

 

import javax.servlet.jsp.tagext.*;

import javax.servlet.jsp.tagext.BodyTag.*;

import javax.servlet.jsp.*;

import javax.servlet.*;

import java.io.*;

 

public class IfElseTag extends BodyTagSupport {

  public int doStartTag() throws JspTagException {

    IfTag parent = (IfTag) findAncestorWithClass(this, IfTag.class);

    if (parent == null) {

      throw new JspTagException("else not inside if");

    } else if (!parent.hasCondition()) {

      String warning = "condition tag must come before else tag";

      throw new JspTagException(warning);

    }

    return EVAL_BODY_BUFFERED;

  }

 

  public int doAfterBody() {

    IfTag parent = (IfTag) findAncestorWithClass(this, IfTag.class);

    if (!parent.getCondition()) {

      try {

        BodyContent body = getBodyContent();

        JspWriter out = body.getEnclosingWriter();

        out.print(body.getString());

      }

      catch (IOException ioe) {

        System.out.println("Error in IfElseTag: " + ioe);

      }

    }

    return SKIP_BODY;

  }

}