Sunday, July 8, 2012

JSP Custom Tag: Nested Tags


Introduction

In the previous post (http://ben-bai.blogspot.tw/2012/07/jsp-custom-tag-body-tag.html), we have implemented a body tag 'fadeoutBlock', in this post we will try the advanced body tag - 'Nested Tags' and implement a tag set 'tabbox, tabpanel'.

Pre-define

1. A tabbox can contain several tabpanels.
2. You can set the 'width' and 'height' of a tabbox.
3. A tabpanel can contains any JSP body content.
4. You can set the 'header' of a tabpanel.

The Program

Tabbox.java

package test.tag.custom;

import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.TagSupport;

/**
 * Simple tabbox with poor look and feel 
 */
public class Tabbox extends BodyTagSupport {
    private int _panelCnt = 0;
    private int _width = 100;
    private int _height = 100;
    private StringBuilder _headerContent = new StringBuilder("");
    private StringBuilder _bodyContent = new StringBuilder("");

    public void setWidth (int width) {
        _width = width;
    }
    public void setHeight (int height) {
        _height = height;
    }
    public int doStartTag() throws JspException {
        try {
            // output the out most area
            JspWriter out = pageContext.getOut();
            out.print("<div");
            out.print(" style=\"overflow: auto; width: "+_width+"px; height: "+_height+"px; border: 1px solid #CCCCCC;\"");
            out.print(">");
        } catch (Exception e) {
            throw new JspException("Error: IOException while writing to client");
        }

        // evaluate body content and output it directly
        return EVAL_BODY_INCLUDE;
    }

    public int doEndTag() throws JspException {
        try {
            JspWriter out = pageContext.getOut();
            // output the header/body of tabpanels
            out.print("<div>"+_headerContent.toString()+"</div>");
            out.print("<div style=\"margin: 10px; border: 1px solid #3648AE;\">"+_bodyContent.toString()+"</div></div>");
        } catch (Exception ex) {
            throw new JspException(ex.getMessage());
        }
        release();
        // continue evaluate page
        return EVAL_PAGE;
        
    }

    public void release() {
        // have to reset all field values since container may reuse this instance
        _panelCnt = 0;
        _width = 100;
        _height = 100;
        _headerContent.setLength(0);
        _bodyContent.setLength(0);
    }

    public void addHeaderContent (String content) {
        // called by Tabpanel, add header content
        String style = _panelCnt == 0? "background-color: gray;" : "background-color: transparent;";
        style += " margin-right: 5px; border: 1px solid #CCCCCC; border-bottom: 0px; cursor: pointer;";
        _headerContent.append("<span onclick=\""+showMatchedPanel()+"\" style=\""+style+"\">")
            .append(content)
            .append("</span>");
    }
    public void addBodyContent (String content) {
        // called by Tabpanel, add body contents
        String style = _panelCnt == 0? "" : "display: none;";
        _bodyContent.append("<div style=\""+style+"\">")
            .append(content)
            .append("</div>");
    }

    public void increaseCnt () {
        // called by Tabpanel, tell Tabbox the number of tabpanel is increased
        _panelCnt++;
    }

    // its better provided by a .js file
    private String showMatchedPanel () {
        // the javascript that executes while tabpanel's header clicked
        StringBuilder cmd = new StringBuilder();

        cmd.append("var headerContainer = this.parentNode,")
            .append("    headerArray = headerContainer.childNodes,")
            .append("    bodyContainer = headerContainer.nextSibling,")
            .append("    bodyArray = bodyContainer.childNodes,")
            .append("    ele, i, idx;")
            .append("for (i = 0; i < headerArray.length; i++) {")
            .append("    if ((ele = headerArray[i]) == this) {")
            .append("        ele.style.backgroundColor = 'gray';")
            .append("        idx = i;")
            .append("    } else")
            .append("        ele.style.backgroundColor = 'transparent';")
            .append("}")
            .append("for (i = 0; i < bodyArray.length; i++) {")
            .append("    if (i == idx)")
            .append("        bodyArray[i].style.display = 'block';")
            .append("    else")
            .append("        bodyArray[i].style.display = 'none';")
            .append("}");
        return cmd.toString();
    }
}

Tabbox provide the API's for tabpanels to add their header/body content and then output them properly with the appropriate javascript action while header clicked.

Tabpanel.java

package test.tag.custom;

import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

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


/**
 * Simple tabpanel with poor look and feel
 */
public class Tabpanel extends BodyTagSupport {
    private String _header = new String("tab header");

    public void setHeader (String header) {
        _header = header;
    }

    public int doStartTag() throws JspException {
        // denotes evaluate body but do not output it, store it in buffer
        return EVAL_BODY_BUFFERED;
    }
    
    public int doEndTag() throws JspException {
        // find the parent tabbox
        Tabbox parent = (Tabbox)findAncestorWithClass(this, Tabbox.class);
        // get the buffered body content
        String body = getBodyContent().getString();
        // parent should not be null
        if(parent == null)
            throw new JspException("Tabpanel.doStartTag(): " + "No Tabbox ancestor");

        // fix empty body
        if (body == null || body.isEmpty())
            body = "&nbsp;"; // at least a space char
        // fix empty header
        if (_header == null || _header.isEmpty())
            _header = "&nbsp;"; // at least a space char
        // add header content to parent tabbox
        parent.addHeaderContent(_header);
        // add body content to parent tabbox
        parent.addBodyContent(body);
        parent.increaseCnt();
        release();

        return EVAL_PAGE;
        
    }
    
    public void release() {
        // have to reset all field values since container may reuse this instance
        _header = null;
    }
    
}

Tabpanel will store its body content in a buffer then pass it and header to Tabbox to render.

The tag definition

Add the fragment below to the tld file which already created from previous post (http://ben-bai.blogspot.tw/2012/06/jsp-custom-tag-simple-tag.html) then export jar as described in the previous post.


<tag>
    <!-- tag name -->
    <name>tabbox</name>
    <!-- tag class path -->
    <tagclass>test.tag.custom.Tabbox</tagclass>
    <!-- denotes the tag has JSP body content -->
    <bodycontent>JSP</bodycontent>
    <attribute>
        <!-- attribute name -->
        <name>width</name>
        <!-- required or not -->
        <required>false</required>
        <!-- el enable or not (true denotes can be eval at runtime) -->
        <rtexprvalue>false</rtexprvalue>
    </attribute>
    <attribute>
        <!-- attribute name -->
        <name>height</name>
        <!-- required or not -->
        <required>false</required>
        <!-- el enable or not (true denotes can be eval at runtime) -->
        <rtexprvalue>false</rtexprvalue>
    </attribute>
</tag>
<tag>
    <!-- tag name -->
    <name>tabpanel</name>
    <!-- tag class path -->
    <tagclass>test.tag.custom.Tabpanel</tagclass>
    <!-- denotes the tag has JSP body content -->
    <bodycontent>JSP</bodycontent>
    <attribute>
        <!-- attribute name -->
        <name>header</name>
        <!-- required or not -->
        <required>false</required>
        <!-- el enable or not (true denotes can be eval at runtime) -->
        <rtexprvalue>false</rtexprvalue>
    </attribute>
</tag>

Test Page

tabboxTest.jsp

<%@ page isErrorPage="true" language="java"
    contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page isELIgnored ="false" %>
<!-- use the custom taglib with prefix ct -->
<%@taglib prefix="ct" uri="http://test.tag.custom/jsp/impl/taglib"%>
<html>
    <head>
        <meta http-equiv="Content-Type" 
            content="text/html; charset=UTF-8"/>
        <title>EL Math Practice</title>
    </head>
    <body>
        <!-- the tabbox -->
        <ct:tabbox width="500" height="300">
            <!-- child tabpanels -->
            <ct:tabpanel header="Tab 1">
                this is the first panel of the tabbox
                <div style="height: 150px; width: 150px; background-color: red;"></div>
            </ct:tabpanel>
            <ct:tabpanel header="Tab 2">
                <div style="height: 200px; width: 200px; background-color: green;">
                    second panel
                </div>
            </ct:tabpanel>
            <ct:tabpanel header="Tab 3">
                the third panel
            </ct:tabpanel>
        </ct:tabbox>
    </body>
</html>


The Result

View the demo flash on line
http://screencast.com/t/e5QqxXQ1O

You can find the flash file at github:
https://github.com/benbai123/JSP_Servlet_Practice/blob/master/demo_src/JSP/Custom_tag/tabbox_test.swf



Reference
http://java.sun.com/j2ee/tutorial/1_3-fcs/doc/JSPTags.html


Download

The full project is at github
https://github.com/benbai123/JSP_Servlet_Practice/tree/master/Practice/CustomTagPractice

No comments:

Post a Comment