Showing posts with label Enum. Show all posts
Showing posts with label Enum. Show all posts

Tuesday, May 20, 2008

Use of EnumMap


     I have posted a few blogs explaining my idea of using Enum to create FormTagTemplate class which writes HTML tags. In this post, I'll use FormTagTemplate in CustomTagTemplate to sample the use of EnumMap.

     CustomTagTemplate has two custom tags: calendar and number. Calendar tag, as you can see from the image on the left, has one text field, one hidden field, and one image. When you click on the image or select the text field, a calendar will pop up. Number tag has one text field and one hidden field. It formats the number entered when the text field is onblur and unformat the number when the text field is onfocus. The actual raw value, however, is stored in the hidden field for both tags. Both tags require javascripts which I do not cover here. Calendar tag's javascript is my personal modification from Epoch JavaScript Calendar. I also added on a time picker with customizable time interval.

CustomTagTemplate.java

package net.yw.html;

import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.StringUtils;


/**
 * @author KWang
 *
 */
public enum CustomTagTemplate implements HTMLTagTemplate {
    calendar() {
        protected void setDefaults(){
            Map<String, Object> properties = new HashMap<String, Object>();
            properties.put("class", "right-align");
            properties.put("size", 29);
            this.defaults.put(FormTagTemplate.text, properties);
            
            properties = new HashMap<String, Object>();
            properties.put("src", "../images/calendar.gif");
            properties.put("class", "calendar");
            properties.put("alt", "");
            this.defaults.put(FormTagTemplate.img, properties);
        }
        
        public String doEnd() {
            return "";
        }

        public String doStart(Map<String, Object> propertyMap) throws HTMLTagException {
            StringBuffer sb = new StringBuffer();
            String name = propertyMap.get("name").toString();
            
            Map<String, Object> defaults = this.defaults.get(FormTagTemplate.text);
            defaults.putAll(propertyMap);
            if(defaults.containsKey("value"))
                defaults.remove("value");
            defaults.put("name", name + "_input");
            defaults.put("id", name + "_input");
            sb.append(FormTagTemplate.text.doStart(defaults));
            
            sb.append("\n");
            defaults = this.defaults.get(FormTagTemplate.img);
            defaults.put("name", name + "_input_img");
            defaults.put("id", name + "_input_img");
            sb.append(FormTagTemplate.img.doStart(defaults));
            
            sb.append("\n");
            defaults = new HashMap<String, Object>();
            defaults.put("id", name + "_input_fmt");
            sb.append(FormTagTemplate.div.doStart(defaults) + FormTagTemplate.div.doEnd());
            
            sb.append("\n");
            defaults = new HashMap<String, Object>();
            defaults.put("name", name);
            defaults.put("id", name);
            if(propertyMap.containsKey("value"))
                defaults.put("value", propertyMap.get("value"));
            sb.append(FormTagTemplate.hidden.doStart(defaults));
            
            boolean timepicker = propertyMap.containsKey("timepicker") ? Boolean.valueOf(propertyMap.get("timepicker").toString()) : false;
            int interval = propertyMap.containsKey("interval") ? Integer.valueOf(propertyMap.get("interval").toString()) : 15;
            sb.append("<script type=\"text/javascript\">");
            sb.append("epochCalendar('" + name + "', '" + name + "_input', " + timepicker + ", " + interval + ");");
            sb.append("</script>");
            return sb.toString();
        }
    },
    
    number(){
        protected void setDefaults(){
            Map<String, Object> properties = new HashMap<String, Object>();
            properties.put("class", "right-align");
            properties.put("size", 29);
            properties.put("maxlength", 11);
            this.defaults.put(FormTagTemplate.text, properties);
        }
        
        public String doEnd() {
            return "";
        }

        public String doStart(Map<String, Object> propertyMap) throws HTMLTagException {
            StringBuffer sb = new StringBuffer();
            String name = propertyMap.get("name").toString();
            String format = propertyMap.get("format").toString();
            
            Map<String, Object> defaults = this.defaults.get(FormTagTemplate.text);
            defaults.putAll(propertyMap);
            if(defaults.containsKey("value"))
                defaults.remove("value");
            defaults.put("name", name + "_input");
            defaults.put("id", name + "_input");
            defaults.put("onblur", "javascript:formatNumberFunc(this,'" + name + "','" + format + "');");
            defaults.put("onfocus", "javascript:unformatNumber(this,'" + format + "');");
            sb.append(FormTagTemplate.text.doStart(defaults));
            
            sb.append("\n");
            defaults = new HashMap<String, Object>();
            defaults.put("name", name);
            defaults.put("id", name);
            Object value = "0";
            if(propertyMap.containsKey("value"))
                value = propertyMap.get("value");
            if(value == null || StringUtils.isBlank(value.toString()) || !StringUtils.isNumeric(value.toString()))
                value = "0";
            defaults.put("value", value);
            sb.append(FormTagTemplate.hidden.doStart(defaults));
            
            sb.append("\n");
            sb.append("<script type=\"text/javascript\">");
            sb.append("$('" + name + "_input').value = formatNumberFunction(" + value + ", " + format + ");");
            sb.append("</script>");
            return sb.toString();
        }
    };
    
    protected EnumMap<FormTagTemplate, Map<String, Object>> defaults = new EnumMap<FormTagTemplate, Map<String, Object>>(FormTagTemplate.class);
    
    private CustomTagTemplate(){
        this.setDefaults();
    }
    
    protected abstract void setDefaults();

    public abstract String doEnd();

    public abstract String doStart(Map<String, Object> propertyMap) throws HTMLTagException;
}


     Of course you can add a calendar and a number function to the Utility class and create tag files in the same way as I did for input and select tag. However, it will be much easier if you just write it in pure tag file. Then you might wonder why go through all the trouble to write CustomTagTemplate? The answer is to use it for AJAX calls, which will be covered soon.

HTML Tag Template - Tag File

     Now that we've done the Java part, let's get down to Tag File. First of all, we need to add value, input, and select functions from the Utility class to a tld file and place it under WEB-INF.

html.tld

<?xml version="1.0" encoding="UTF-8"?>
<taglib 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_0.xsd" version="2.0">
<tlib-version>1.0</tlib-version>
<short-name>HTMLFunc</short-name>
<function>
<name>value</name>
<function-class>net.yw.html.HTMLTagUtils</function-class>
<function-signature>java.lang.String value(java.lang.Object, java.lang.String, boolean)</function-signature>
</function>
<function>
<name>input</name>
<function-class>net.yw.html.HTMLTagUtils</function-class>
<function-signature>java.lang.String input(java.lang.Object, java.lang.String, java.lang.String, java.lang.String, java.util.Map)</function-signature>
</function>
<function>
<name>select</name>
<function-class>net.yw.html.HTMLTagUtils</function-class>
<function-signature>java.lang.String select(java.lang.Object, java.lang.String, java.lang.String, java.util.Map, java.lang.Object, java.lang.String, java.lang.String, java.lang.String)</function-signature>
</function>
</taglib>


     Also, you'll need to create a tags folder under WEB-INF which will holds all the tag files. In addition, an include.jsp which holds all the tag definition is imported in each tag file.

includes.jsp

<%--  To use Servlet 2.4, the following setting should have a "/jsp" before "/jstl" --%>
<%-- JSTL Tags --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn"  uri="http://java.sun.com/jsp/jstl/functions" %>

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>

<%-- Other Tags --%>
<%@ taglib uri="/WEB-INF/html.tld" prefix="html" %>
<%@ taglib prefix="tags" tagdir="/WEB-INF/tags"%>


     Assume that form and error information is hold in request, and a default error style -- 'errorField' exists, we now have two standard tag files.

input.tag

<%@ tag dynamic-attributes="attrMap" %>
<%@ attribute name="type" required="true" rtexprvalue="true" %>
<%@ attribute name="property" required="true" rtexprvalue="true" %>
<%@ attribute name="styleClass" required="false" rtexprvalue="true" %>
<%@ attribute name="errorStyleClass" required="false" rtexprvalue="true" %>

<%@ include file="/WEB-INF/jsp/common/includes.jsp"%>

<c:set var="type" value="${fn:toLowerCase(type)}"/>
<c:set var="class" value="${styleClass == null ? 'right-align' : styleClass}"/>
<c:if test="${not empty request.errors.messages[property]}">
<c:set var="class" value="${errorStyleClass == null ? 'errorField' : errorStyleClass}"/>
</c:if>
${html:input(request.form, property, class, type, attrMap)}


select.tag

<%@ tag dynamic-attributes="attrMap" %>
<%@ attribute name="property" required="true" rtexprvalue="true" %>
<%@ attribute name="collections" required="false" rtexprvalue="true" type="java.lang.Object"%>
<%@ attribute name="valueProperty" required="false" rtexprvalue="true" %>
<%@ attribute name="labelProperty" required="false" rtexprvalue="true" %>
<%@ attribute name="styleClass" required="false" rtexprvalue="true" %>
<%@ attribute name="errorStyleClass" required="false" rtexprvalue="true" %>

<%@ include file="/WEB-INF/jsp/common/includes.jsp"%>

<c:set var="class" value="${styleClass == null ? '' : styleClass}"/>
<c:if test="${not empty request.errors.messages[property]}">
<c:set var="class" value="${errorStyleClass == null ? 'errorField' : errorStyleClass}"/>
</c:if>
<c:set var="optionString"><jsp:doBody /></c:set>
${html:select(request.form, property, class, attrMap, collections, valueProperty, labelProperty, optionString)}


     Note there is an attribute called attrMap which is a dynamic-attribute. With attrMap in place, you can add any extra attribute to the tag besides what have been defined in the tag file. And these extra attributes will be passed as the HTML Tag's atrribute values. It is safe to do so as long as the property filter process in FormTagTemplate.java is in place.

     For select tag, the user has multiple ways to specify options. You can write the options manually between the select tag. Or, you can pass in a collection or an array and specify the value and label property. Also, you can pass in a map which's key is the value property and value is the label property. There is also a blank option indicating adding a blank option on top of the option list when it's true.

     Here is some example:

<tags:input type="text" property="stockNumber" size="29" styleClass="left-align" tabindex="1" errorStyleClass="errorClass" maxlength="20"/>

<tags:select property="typeId" collections="${request.form.optionMap}" blank="true" tabindex="2" />

<tags:select property="selectedName" style="width: 80px" onchange="javascript:doSubmit();">
    <c:forEach var="name" items="${request.form.availableNames}">
        <c:choose>
            <c:when test="${name[0] == request.form.selectedName}">
                <option value="${name[0]}" selected>
            </c:when>
            <c:otherwise>
                <option value="${name[0]}">
            </c:otherwise>
        </c:choose>
        <c:catch var="error">
            <tags:label key="${name[1]}" />
        </c:catch>
        </option>
    </c:forEach>
</tags:select>

<%-- request.form.styles is an array --%>
<tags:select property="style" collections="${request.form.styles}" valueProperty="0" labelProperty="1" tabindex="4" blank="false" style="width:178px" />


     Coming up next, more on custom HTML Tag and use of HTMLTagTemplate in AJAX.

HTML Tag Template - Utility

     In my previous post, I wrote a FormTagTemplate which can write form tags. However, since I don't use TagSupport, I'll need a little utility class in order to actually use it to write tags in jsp files.

HTMLTagUtils.java

package net.yw.html;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;

/**
 * @author KWang
 * 
 */
public class HTMLTagUtils {

    // ResponseUtils.filter(String)
    public static String filter(String value) {
        if (value == null || value.length() == 0)
            return value;
        StringBuffer result = null;
        String filtered = null;
        for (int i = 0; i < value.length(); i++) {
            filtered = null;
            switch (value.charAt(i)) {
                case '<':
                    filtered = "&lt;";
                    break;
                case '>':
                    filtered = "&gt;";
                    break;
                case '&':
                    filtered = "&amp;";
                    break;
                case '"':
                    filtered = "&quot;";
                    break;
                case '\'':
                    filtered = "&#39;";
                    break;
            }

            if (result == null) {
                if (filtered != null) {
                    result = new StringBuffer(value.length() + 50);
                    if (i > 0) {
                        result.append(value.substring(0, i));
                    }
                    result.append(filtered);
                }
            } else {
                if (filtered == null) {
                    result.append(value.charAt(i));
                } else {
                    result.append(filtered);
                }
            }
        }

        return result == null ? value : result.toString();
    }

    public static String value(Object bean, String property, boolean escapeXml) throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {
        String value = BeanUtils.getProperty(bean, property);
        if(value == null)
            return "";
        return escapeXml?filter(value):value;
    }

    @SuppressWarnings("unchecked")
    public static String select(Object bean, String property, String styleClass, Map<String, Object> properties,
            Object collections, String valueProperty, String labelProperty, String optionString) throws  HTMLTagException {
        if(StringUtils.isBlank(property))
            throw new HTMLTagException("property is missing");
        FormTagTemplate select = FormTagTemplate.select;
        properties.put("name", property);
        if(!properties.containsKey("id"))
            properties.put("id", properties.get("name"));
        if(StringUtils.isNotBlank(styleClass))
            properties.put("class", styleClass);
        
        StringBuffer sb = new StringBuffer(select.doStart(properties));
        
        //options
        FormTagTemplate option = FormTagTemplate.option;
        if(properties.containsKey("blank")){
            boolean blank = Boolean.parseBoolean(properties.get("blank").toString());
            if(blank){
                Map<String, Object> blankOp = new HashMap<String, Object>();
                blankOp.put("value", "");
                blankOp.put("label", "");
                sb.append(option.doStart(blankOp) + option.doEnd());
            }
        }
        if(collections != null){
            try {
                String value = bean == null ? "":value(bean, property, false);
                Map<String, Object> optionProp = new HashMap<String, Object>();
                if(collections instanceof Map){
                    for(Map.Entry<Object, Object> entry:((Map<Object, Object>)collections).entrySet()){
                        optionProp.put("value", entry.getKey().toString());
                        optionProp.put("label", entry.getValue().toString());
                        optionProp.put("selected", StringUtils.equals(value, entry.getKey().toString()));
                        sb.append(option.doStart(optionProp) + optionProp.get("label") + option.doEnd());
                    }
                }else if(collections instanceof Collection){
                    for(Object object:((Collection)collections)){
                        optionProp.put("value", StringUtils.isBlank(valueProperty)?object.toString():value(object, valueProperty, false));
                        optionProp.put("label", StringUtils.isBlank(labelProperty)?object.toString():value(object, labelProperty, false));
                        optionProp.put("selected", StringUtils.equals(value, optionProp.get("value").toString()));
                        sb.append(option.doStart(optionProp) + optionProp.get("label") + option.doEnd());
                    }
                }else if(collections.getClass().isArray()){
                    for(Object object:Arrays.asList((Object[])collections)){
                        if(object.getClass().isArray()){
                            int valueIndex = StringUtils.isNumeric(valueProperty)?Integer.valueOf(valueProperty):0;
                            int labelIndex = StringUtils.isNumeric(labelProperty)?Integer.valueOf(labelProperty):0;
                            optionProp.put("value", ((Object[])object)[valueIndex]);
                            optionProp.put("label", ((Object[])object)[labelIndex]);
                            optionProp.put("selected", StringUtils.equals(value, optionProp.get("value").toString()));
                            sb.append(option.doStart(optionProp) + optionProp.get("label") + option.doEnd());
                        }else{
                            optionProp.put("value", StringUtils.isBlank(valueProperty)?object.toString():value(object, valueProperty, false));
                            optionProp.put("label", StringUtils.isBlank(labelProperty)?object.toString():value(object, labelProperty, false));
                            optionProp.put("selected", StringUtils.equals(value, optionProp.get("value").toString()));
                            sb.append(option.doStart(optionProp) + optionProp.get("label") + option.doEnd());
                        }
                    }
                }
            } catch (IllegalAccessException e) {
                throw new HTMLTagException("Cannot get value of " + property + " from " + bean );
            } catch (InvocationTargetException e) {
                throw new HTMLTagException("Cannot get value of " + property + " from " + bean );
            } catch (NoSuchMethodException e) {
                throw new HTMLTagException("Cannot get value of " + property + " from " + bean );
            }
        }
        
        if(StringUtils.isNotBlank(optionString))
            sb.append(optionString);
        sb.append(select.doEnd());
        return sb.toString();
    }
    
    public static String input(Object bean, String property, String styleClass, String type, Map<String, Object> properties) throws HTMLTagException{
        if(StringUtils.isBlank(property))
            throw new HTMLTagException("property is missing");
        FormTagTemplate tag = FormTagTemplate.valueOf(type);
        if(tag == null)
            throw new HTMLTagException("tag " + type + " not found!");
        else if(tag.equals(FormTagTemplate.select) || tag.equals(FormTagTemplate.option))
            throw new HTMLTagException("Please use select for select/option type tag!");
        else if(tag.equals(FormTagTemplate.div))
            throw new HTMLTagException("Please use div tag directly in page!");

        properties.put("name", property);
        if(!properties.containsKey("id"))
            properties.put("id", properties.get("name"));
        if(StringUtils.isNotBlank(styleClass))
            properties.put("class", styleClass);

        try {
            String value = bean == null || tag.equals(FormTagTemplate.file) ? "":value(bean, property, false);
            switch(tag){
                case radio:
                    for(String key:properties.keySet()){
                        if(StringUtils.equalsIgnoreCase(key, "value")){
                            String compareValue = properties.get(key).toString();
                            properties.put("checked", Boolean.valueOf(StringUtils.equals(value, compareValue)));
                            break;
                        }
                    }
                    break;
                case checkbox:
                    if(!properties.containsKey("value"))
                        properties.put("value", "on");
                    properties.put("checked", Boolean.valueOf(value));
                    break;
                default:
                    properties.put("value", value);
                    break;
            }
            
            StringBuffer sb = new StringBuffer(tag.doStart(properties));
            if(tag.equals(FormTagTemplate.textarea) || tag.equals(FormTagTemplate.div))
                sb.append(value + tag.doEnd());
            return sb.toString();
        } catch (IllegalAccessException e) {
            throw new HTMLTagException("Cannot get value of " + property + " from " + bean );
        } catch (InvocationTargetException e) {
            throw new HTMLTagException("Cannot get value of " + property + " from " + bean );
        } catch (NoSuchMethodException e) {
            throw new HTMLTagException("Cannot get value of " + property + " from " + bean );
        }
    }
}


     The utility only has four functions. filter() is identical to ResponseUtils.filter() in Struts. value() retrieves the String value of a property in a bean. select() and input() is used in select and input tag files in my next post.

HTML Tag Template - Enum

     In my previous post, I created an interface for HTML Tags. Now,I'll create FormTagTemplate implementing HTMLTagTemplate which is for general form tags:

FormTagTemplate.java

package net.yw.html;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;

/**
 * @author KWang
 * 
 */
public enum FormTagTemplate implements HTMLTagTemplate {

    button("input") {
        protected void additionalSetting() {
            this.standardAttributes.addAll(Arrays.asList("tabindex", "accesskey"));
            this.requiredAttributes.add("name");
            this.optionalAttributes.addAll(Arrays.asList("disabled", "size", "value"));
            this.eventAttributes.addAll(Arrays.asList("onfocus", "onblur", "onselect", "onchange"));
        }
    },

    checkbox("input") {
        protected void additionalSetting() {
            this.standardAttributes.addAll(Arrays.asList("tabindex", "accesskey"));
            this.requiredAttributes.addAll(Arrays.asList("name", "value"));
            this.optionalAttributes.addAll(Arrays.asList("checked", "disabled", "size"));
            this.eventAttributes.addAll(Arrays.asList("onfocus", "onblur", "onselect", "onchange"));
        }
    },

    file("input") {
        protected void additionalSetting() {
            this.standardAttributes.addAll(Arrays.asList("tabindex", "accesskey"));
            this.requiredAttributes.add("name");
            this.optionalAttributes.addAll(Arrays.asList("accept", "size"));
            this.eventAttributes.addAll(Arrays.asList("onfocus", "onblur", "onselect", "onchange"));
        }
    },

    hidden("input") {
        protected void additionalSetting() {
            this.standardAttributes.addAll(Arrays.asList("tabindex", "accesskey"));
            this.requiredAttributes.add("name");
            this.optionalAttributes.add("value");
            this.eventAttributes.addAll(Arrays.asList("onfocus", "onblur", "onselect", "onchange"));
        }
    },

    image("input") {
        protected void additionalSetting() {
            this.standardAttributes.addAll(Arrays.asList("tabindex", "accesskey"));
            this.requiredAttributes.add("name");
            this.optionalAttributes.addAll(Arrays.asList("align", "alt", "disabled", "size", "src",
                    "value"));
            this.eventAttributes.addAll(Arrays.asList("onfocus", "onblur", "onselect", "onchange"));
        }
    },

    password("input") {
        protected void additionalSetting() {
            this.standardAttributes.addAll(Arrays.asList("tabindex", "accesskey"));
            this.requiredAttributes.add("name");
            this.optionalAttributes.addAll(Arrays.asList("disabled", "size", "value"));
            this.eventAttributes.addAll(Arrays.asList("onfocus", "onblur", "onselect", "onchange" ));
        }
    },

    radio("input") {
        protected void additionalSetting() {
            this.standardAttributes.addAll(Arrays.asList("tabindex", "accesskey"));
            this.requiredAttributes.addAll(Arrays.asList("name", "value"));
            this.optionalAttributes.addAll(Arrays.asList("checked", "disabled", "size"));
            this.eventAttributes.addAll(Arrays.asList("onfocus", "onblur", "onselect", "onchange"));
        }
    },

    reset("input") {
        protected void additionalSetting() {
            this.standardAttributes.addAll(Arrays.asList("tabindex", "accesskey"));
            this.optionalAttributes.addAll(Arrays.asList("disabled", "name", "size", "value"));
            this.eventAttributes.addAll(Arrays.asList("onfocus", "onblur", "onselect", "onchange"));
        }
    },

    submit("input") {
        protected void additionalSetting() {
            this.standardAttributes.addAll(Arrays.asList("tabindex", "accesskey"));
            this.optionalAttributes.addAll(Arrays.asList("disabled", "name", "size", "value"));
            this.eventAttributes.addAll(Arrays.asList("onfocus", "onblur", "onselect", "onchange"));
        }
    },

    text("input") {
        protected void additionalSetting() {
            this.standardAttributes.addAll(Arrays.asList("tabindex", "accesskey"));
            this.requiredAttributes.add("name");
            this.optionalAttributes.addAll(Arrays.asList("disabled", "maxlength", "readonly", "size",
                    "value"));
            this.eventAttributes.addAll(Arrays.asList("onfocus", "onblur", "onselect", "onchange"));
        }
    },

    select("select") {
        protected void additionalSetting() {
            this.standardAttributes.addAll(Arrays.asList("tabindex", "accesskey"));
            this.optionalAttributes.addAll(Arrays.asList("disabled", "multiple", "name", "size"));
            this.eventAttributes.addAll(Arrays.asList("onfocus", "onblur", "onchange"));
        }
    },

    option("option") {
        protected void additionalSetting() {
            this.optionalAttributes.addAll(Arrays.asList("disabled", "label", "selected", "value"));
        }
    },

    textarea("textarea") {
        protected void additionalSetting() {
            this.standardAttributes.addAll(Arrays.asList("tabindex", "accesskey"));
            this.requiredAttributes.addAll(Arrays.asList("cols", "rows"));
            this.optionalAttributes.addAll(Arrays.asList("disabled", "name", "readonly"));
            this.eventAttributes.addAll(Arrays.asList("onfocus", "onblur", "onselect", "onchange"));
        }
    },

    div("div") {
        protected void additionalSetting() {
            this.optionalAttributes.add("align");
        }
    },

    img("img") {
        protected void additionalSetting() {
            this.requiredAttributes.addAll(Arrays.asList("alt", "src"));
            this.optionalAttributes.addAll(Arrays.asList("align", "border", "height", "hspace", "ismap",
                    "longdesc", "usemap", "vspace", "width"));
        }
    };

    protected String tagName;

    protected String type;

    protected List<String> standardAttributes = new ArrayList<String>(Arrays.asList("id", "class",
            "title", "style", "dir", "lang"));

    protected List<String> requiredAttributes = new ArrayList<String>();

    protected List<String> optionalAttributes = new ArrayList<String>();

    protected List<String> eventAttributes = new ArrayList<String>(Arrays.asList("onclick", "ondbclick",
            "onhelp", "onmousedown", "onmouseup", "onmouseover", "onmousemove", "onmouseout", "onkeypress",
            "onkeydown", "onkeyup"));

    private FormTagTemplate(String tagName) {
        this.tagName = tagName.toLowerCase();
        this.type = this.toString().equals(this.tagName) ? null : this.toString();
        this.additionalSetting();
    }

    protected abstract void additionalSetting();

    public List<String> getAcceptableAttributes() {
        List<String> acceptables = new ArrayList<String>(this.standardAttributes);
        acceptables.addAll(this.requiredAttributes);
        acceptables.addAll(this.optionalAttributes);
        acceptables.addAll(this.eventAttributes);
        return Collections.unmodifiableList(acceptables);
    }

    private Map<String, String> filterProperties(Map<String, Object> propertyMap) {
        Map<String, String> properties = new HashMap<String, String>();
        if (propertyMap != null && !propertyMap.isEmpty()) {
            List<String> list = this.getAcceptableAttributes();
            for (Map.Entry<String, Object> entry : propertyMap.entrySet()) {
                String key = entry.getKey().toLowerCase();
                if (list.contains(key)) {
                    Object value = entry.getValue();
                    if (value instanceof Boolean) {
                        if (((Boolean) value).booleanValue())
                            properties.put(key, key);
                    } else
                        properties.put(key, HTMLTagUtils.filter(value.toString()));
                }
            }
        }
        return properties;
    }

    public String doStart(Map<String, Object> propertyMap) throws HTMLTagException {
        Map<String, String> properties = filterProperties(propertyMap);
        if (!properties.keySet().containsAll(this.requiredAttributes))
            throw new HTMLTagException(this.toString() + ": One or more required field(s) is/are missing: "
                    + this.requiredAttributes.toString());

        StringBuffer sb = new StringBuffer("<" + this.tagName + " ");
        if (StringUtils.isNotBlank(this.type))
            sb.append("type=\"" + this.type + "\" ");
        for (Map.Entry<String, String> entry : properties.entrySet())
            sb.append(entry.getKey() + "=\"" + entry.getValue() + "\" ");
        switch (this) {
            case select:
            case option:
            case textarea:
            case div:
                sb.append(">");
                break;

            default:
                sb.append("/>");
                break;
        }
        return sb.toString();
    }

    public String doEnd() {
        switch (this) {
            case select:
            case option:
            case textarea:
            case div:
                return "</" + this.tagName + ">";

            default:
                return "";
        }
    }
}


     As you can see from the code above, each tag's properties will be filtered so that only acceptable properties will be written. And each tag's unique property set is done by overwritten additionalSetting(). doStart() and doEnd() take advantage of the Enum by using switch to customize how to write the tag.

     In my next post, I'll talk about the utility I use in Tag File.

Monday, May 19, 2008

HTML Tag Template - Interface

     This is my first try out of Enum since Java5 came out. The trigger of this idea is that we outsourced the use of Struts along with all it's tags. So there is no easy way for us to write forms in jsp files. I won't say that my code is competitive with Struts' or any other open/share source's tags, but it will come in handy in many ways, at least it is for me.

     I started out with a simple interface.

HTMLTagTemplate.java

package net.yw.html;

import java.util.Map;

/**
 * @author KWang
 *
 */
public interface HTMLTagTemplate {
    
    public String doStart(Map<String, Object> propertyMap) throws HTMLTagException;
    
    public String doEnd();
}


HTMLTagException.java

package net.yw.html;

/**
 * @author KWang
 *
 */
public class HTMLTagException extends Exception {

    /**
     * 
     */
    private static final long serialVersionUID = -1L;

    /**
     * 
     */
    public HTMLTagException() {
    }

    /**
     * @param message
     */
    public HTMLTagException(String message) {
        super(message);
    }

    /**
     * @param cause
     */
    public HTMLTagException(Throwable cause) {
        super(cause);
    }

    /**
     * @param message
     * @param cause
     */
    public HTMLTagException(String message, Throwable cause) {
        super(message, cause);
    }

}


     In my next post, I'll talk about how to implement HTMLTagTemplate for general form tag use.