Struts2中静态页面生成策略

    利用Struts2生成静态页面其实很灵活,很强大,尤其是利用Struts2对Freemarker较好的支持,充分利用Freemarker的模板功能来生成静态页面。

    基本思路为:利用Struts2对自定义result type的支持,自定义能够生成静态页面的result type,结合模板引擎Freemarker可以实现大批量静态页面的生成。

    参看org.apache.struts2.views.freemarker.FreemarkerResult的代码实现,自定义了自己的生成静态页面的result type。此种方案不单纯用于生成静态页面,其实也可以用于生成诸如wml、xhtml等内容,具体可以参考Struts2缺省提供的各种result type的实现。

1、com.mobilesoft.esales.webapp.action.FreemarkerResult

package com.mobilesoft.esales.webapp.action;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Locale;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.StrutsResultSupport;
import org.apache.struts2.views.freemarker.FreemarkerManager;
import org.apache.struts2.views.util.ResourceUtil;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.LocaleProvider;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ValueStack;

import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;

public class FreemarkerResult extends StrutsResultSupport {

    private static final long serialVersionUID = -3778230771704661631L;

    protected ActionInvocation invocation;
    protected Configuration configuration;
    protected ObjectWrapper wrapper;
    protected FreemarkerManager freemarkerManager;
    private Writer writer;
    protected String location;
    private String pContentType = “text/html”;

    protected String fileName; // 要生成的静态页面名称
    protected String filePath; // 要生成的静态页面的路径
    protected String staticTemplate; // 用于生成静态页面Freemarker模板的路径

    public FreemarkerResult() {
        super();
    }

    public FreemarkerResult(String location) {
        super(location);
    }

    @Inject
    public void setFreemarkerManager(FreemarkerManager mgr) {
        this.freemarkerManager = mgr;
    }

    public void setContentType(String aContentType) {
        pContentType = aContentType;
    }

    public String getContentType() {
        return pContentType;
    }

    public void doExecute(String location, ActionInvocation invocation)
            throws IOException, TemplateException {
        this.location = location;
        this.invocation = invocation;
        this.configuration = getConfiguration();
        this.wrapper = getObjectWrapper();

        this.fileName = (String) conditionalParse(fileName, invocation);
        this.staticTemplate = (String) conditionalParse(staticTemplate, invocation);
        this.filePath = ((String) conditionalParse(filePath, invocation)) == null ? “”
                : ((String) conditionalParse(filePath, invocation));

        if (!location.startsWith(“/”)) {
            ActionContext ctx = invocation.getInvocationContext();
            HttpServletRequest req = (HttpServletRequest) ctx
                    .get(ServletActionContext.HTTP_REQUEST);
            String base = ResourceUtil.getResourceBase(req);
            location = base + “/” + location;
        }

        //生成html页面的模板类
        Template template = configuration.getTemplate(location, deduceLocale());
        // 生成静态页面的的模板类
        Template staticTemplate = configuration.getTemplate(this.staticTemplate,
                deduceLocale());

        TemplateModel model = createModel();
        String path = ServletActionContext.getServletContext().getRealPath(
                filePath)
                + File.separator;
        Writer out = new BufferedWriter(new OutputStreamWriter(
                new FileOutputStream(path + fileName)));

        if (preTemplateProcess(template, model)) {
            try {
                staticTemplate.process(model, out);
                template.process(model, getWriter());
            } finally {
                postTemplateProcess(template, model);
                postTemplateProcess(staticTemplate, model);
            }
        }
    }

    protected Configuration getConfiguration() throws TemplateException {
        return freemarkerManager.getConfiguration(ServletActionContext
                .getServletContext());
    }

    protected ObjectWrapper getObjectWrapper() {
        return configuration.getObjectWrapper();
    }

    public void setWriter(Writer writer) {
        this.writer = writer;
    }

    protected Writer getWriter() throws IOException {
        if (writer != null) {
            return writer;
        }
        return ServletActionContext.getResponse().getWriter();
    }

    protected TemplateModel createModel() throws TemplateModelException {
        ServletContext servletContext = ServletActionContext
                .getServletContext();
        HttpServletRequest request = ServletActionContext.getRequest();
        HttpServletResponse response = ServletActionContext.getResponse();
        ValueStack stack = ServletActionContext.getContext().getValueStack();

        Object action = null;
        if (invocation != null)
            action = invocation.getAction(); // Added for NullPointException
        return freemarkerManager.buildTemplateModel(stack, action,
                servletContext, request, response, wrapper);
    }

    protected Locale deduceLocale() {
        if (invocation.getAction() instanceof LocaleProvider) {
            return ((LocaleProvider) invocation.getAction()).getLocale();
        } else {
            return configuration.getLocale();
        }
    }

    protected void postTemplateProcess(Template template, TemplateModel data)
            throws IOException {
    }

    protected boolean preTemplateProcess(Template template, TemplateModel model)
            throws IOException {
        Object attrContentType = template.getCustomAttribute(“content_type”);

        if (attrContentType != null) {
            ServletActionContext.getResponse().setContentType(
                    attrContentType.toString());
        } else {
            String contentType = getContentType();

            if (contentType == null) {
                contentType = “text/html”;
            }

            String encoding = template.getEncoding();

            if (encoding != null) {
                contentType = contentType + “; charset=” + encoding;
            }

            ServletActionContext.getResponse().setContentType(contentType);
        }

        return true;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public String getFilePath() {
        return filePath;
    }

    public void setFilePath(String filePath) {
        this.filePath = filePath;
    }

    public String getStaticTemplate() {
        return staticTemplate;
    }

    public void setStaticTemplate(String staticTemplate) {
        this.staticTemplate = staticTemplate;
    }
}

 

2、struts.xml

        <action name=”staticViewAction” class=”com.mobilesoft.esales.webapp.action.StaticViewtAction”>
            <result name=”success” type=”staticview”>
                <param name=”location”>test/freemarkertest.ftl</param>
                <param name=”contentType”>text/html</param>
                 <param name=”fileName”>${filename}</param>
                <param name=”staticTemplate”>test/freemarkertest.ftl</param>
                <param name=”filePath”>static</param>
            </result>                   
        </action>

 

Struts2 doubleselect标签中select框缺省selected实现

    在项目中,省市下拉框联动采用的是Struts2的doubleselect标签,需要根据业务需求实现两个下拉框动态的缺省值(selected)。

业务场景:

    在代理商管理中,增加代理商时候选择代理商所属的省市,然后增加代理商的销售人员,但代理商销售人员销售产品,如果客户在客户库中没有相关信息,需要增加客户,此时侯应当缺省根据代理商所属的省市信息,在增加客户时候,客户所在省市的缺省selected的值应当为代理商所在的省市信息。

 

主要实现逻辑如下:

    采用doubleselect标签的value和doublevalue属性,在action中定义两个select框缺省值参数(例子中是defaultItem、doubleDefaultItem)的get、set方法,在action方法中根据业务逻辑(在增加客户时候,客户所在省市缺省为销售员所在省市)调用set方法设定两个select框的缺省值,然后在页面通过value和doublevalue方法获取设定的缺省值。

实现样例如下:

1. Action

package com.mobilesoft.esales.webapp.action;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.Map;

import org.apache.log4j.Logger;

public class DoubleListAction extends BaseAction {

private static final Logger logger = Logger.getLogger(DoubleListAction.class);

private String defaultItem;

private String doubleDefaultItem;

public String execute() {

return SUCCESS;

    }

public String doubleSelectTest(){

       Map map=new HashMap();

       ArrayList list1=new ArrayList();

       list1.add(“11″);

       list1.add(“12″);

       list1.add(“13″);

       map.put(“1″, list1);

       ArrayList list2=new ArrayList();

       list2.add(“21″);

       list2.add(“22″);

       list2.add(“23″);

       map.put(“2″, list2);

       ArrayList list3=new ArrayList();

       list3.add(“31″);

       list3.add(“32″);

       list3.add(“33″);

       map.put(“3″, list3);

       setDefaultItem(“2″);

       setDoubleDefaultItem(“23″);

       getRequest().setAttribute(“defaultItem”, getDefaultItem());

       getRequest().setAttribute(“doubleDefaultItem”, getDoubleDefaultItem());

       getRequest().setAttribute(“map”, map);

return SUCCESS;

    }

public String getDefaultItem() {

return defaultItem;

    }

public void setDefaultItem(String defaultItem) {

this.defaultItem = defaultItem;

    }

public String getDoubleDefaultItem() {

return doubleDefaultItem;

    }

public void setDoubleDefaultItem(String doubleDefaultItem) {

this.doubleDefaultItem = doubleDefaultItem;

    }

}

2. doubleselect.jsp

<%@ taglib prefix=”s” uri=”/struts-tags” %>

<%@ page language=”java” errorPage=”/error.jsp” pageEncoding=”GBK” contentType=”text/html;charset=GBK” %>

<html>

<head>

<title>Struts 2 Cool Tags – &lt;s:doubeselect/ &gt;</title>

<s:head />

</head>

<body>

<h2>Doubleselect 缺省值selected使用数据演示:</h2>

<s:form name=”form1″>

<s:doubleselect label=”缺省值测试”

    list=”#request.map.keySet()”        doubleList=”#request.map[top]”

name=”doubleselect1″                doubleName=”doubleselect2″

value=”#request.defaultItem”       doubleValue=”#request.doubleDefaultItem”

    formName=”form1″

/>

</s:form>

</body>

</html>

3. struts.xml

<action name=”doubleSelectTest” method=”doubleSelectTest” class=”com.mobilesoft.esales.webapp.action.DoubleListAction”>

<result name=”success”>test/doubleselect.jsp</result>

</action>

 

Technorati 标签: ,,,

Struts2中Validation和Type Conversion

    在Struts2中,采用Validation Interceptor来完成校验支持,在struts-default.xml中,先调用conversionError interceptor进行type conversion操作,然后调用params 、prepare、validation interceptor完成对字段校验,interceptor的执行顺序是

  1. 型别转换 (type conversion)
  2. 获取参数信心(params )
  3. 为验证等作准备(prepare)
  4. 参数验证 (validation)

    如果在进行type conversion时候发生错误,struts2对同样的字段就不会进行验证,会抛出异常,例如页面一输入字段希望进行是否为整数验证,由于页面输入值缺省都为String类型,因此会在后台抛出类似的错误:

ERROR (com.opensymphony.xwork2.interceptor.ParametersInterceptor:204) – ParametersInterceptor – [setParameters]: Unexpected Exception caught setting ‘integerTest’ on ‘class com.mobilesoft.esales.webapp.action.MyValidationAction: Error setting expression ‘integerTest’ with value ‘[Ljava.lang.String;@11e170c'

    而在验证页面,会抛出如下的错误信息:

    Invalid field value for field "integerTest"

   怎么把"Invalid field value for ..." 这样的信息转化成我们指定的信息,方法有几个:

1、自定义转换类

将页面非String 类型的property转换成指定的类型,这样进行validation验证

  参看http://struts.apache.org/2.0.11.1/docs/type-conversion.html

  对于页面输入有意义的validator实际上只有required、date、int几个有意义,其它的意义都不大;同时由于要做单独的类型转换类,比较麻烦,不采用此种方式

2、采用struts2的i18n支持

又有几种方法

2.1、在资源文件中设定全局的xwork.default.invalid.fieldvalue 属性

xwork.default.invalid.fieldvalue=数据格式不正确

2.2、在资源文件中设定invalid.fieldvalue.字段名称属性

invalid.fieldvalue.integerTest=数据格式不正确

2.3、定义针对每一个Action的properties文件

例如在MyValidationAction.properties(与MyValidationAction放在同一目录中,而不是在classes下)中定义

invalid.fieldvalue.integerTest=数据格式不正确

注意:

  • 目前在架构中,资源文件命名为ApplicationResources.properties,ApplicationResources_zh.properties,在web.xml中定义的:

<context-param>
    <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
    <param-value>ApplicationResources</param-value>
</context-param>

  • struts2缺省提供了一些type conversion 类,能够完成一些基本的转换操作:

String

boolean / Boolean

char / Character

int / Integer, float / Float, long / Long, double / Double

dates - uses the SHORT format for the Locale associated with the current request

arrays - assuming the individual strings can be coverted to the individual items

collections - if not object type can be determined, it is assumed to be a String and a new ArrayList is created

  • 要完成客户端校验,注意在<s:form >上添加validate="true"

  <s:form action ="myValidationAction" validate="true" >

 

3、例子

3.1、validationInput.jsp

<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page language="java" errorPage="/error.jsp" pageEncoding="GBK" contentType="text/html;charset=GBK" %>

<html>
<head>
    <title> Validation测试 </title>
    <s:head />
</head>
<body>
    <s:form action ="myValidationAction" validate="true" >           
        <s:textfield name ="strTest" label ="String类型测试" />
        <s:textfield name ="integerTest" label ="Integer类型测试" />
        <s:textfield name ="emailTest" label ="email类型测试" />
        <s:submit />
    </s:form>   
</body>
</html>

3.2、validationOutput.jsp

<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page language="java" errorPage="/error.jsp" pageEncoding="GBK" contentType="text/html;charset=GBK" %>

<html>
<head>
    <title> Hello World </title>
</head>
<body>
    String类型测试: <s:property value ="strTest" />   <br/>
    Integer类型测试: <s:property value ="integerTest" />  <br/> 
    email类型测试: <s:property value ="emailTest" />    <br/>
</body>
</html>

 

3.3、MyValidationAction-validation.xml

<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0//EN"
    "http://www.opensymphony.com/xwork/xwork-validator-1.0.dtd">
<validators>
    <field name="strTest">
        <field-validator type="requiredstring">
            <message>字段不能为空</message>
        </field-validator>
    </field>

  <field name="integerTest">
<field-validator type="conversion"> 
     <message>conversion</message> 
</field-validator>
      <field-validator type="required">
          <message>字段不能为空</message>
      </field-validator>
      <field-validator type="int">
          <param name="min">6</param>
          <param name="max">10</param>
          <message>值必须在 [ ${min}, ${max}], 当前的值是: ${integerTest}.</message>
      </field-validator>
  </field>
    <field name=”emailTest”>
          <field-validator type=”required”>
          <message>字段不能为空</message>
      </field-validator>
        <field-validator type=”email”>
            <message>字段必须为邮件地址</message>
        </field-validator>
    </field>
</validators>

3.4、MyValidationAction.java

package com.mobilesoft.esales.webapp.action;

public class MyValidationAction extends BaseAction {
    private String strTest;
    private Integer integerTest;
    private String emailTest;
    public String execute(){
        return SUCCESS;
    }
    public String getStrTest() {
        return strTest;
    }
    public void setStrTest(String strTest) {
        this.strTest = strTest;
    }
    public Integer getIntegerTest() {
        return integerTest;
    }
    public void setIntegerTest(Integer integerTest) {
        this.integerTest = integerTest;
    }
    public String getEmailTest() {
        return emailTest;
    }
    public void setEmailTest(String emailTest) {
        this.emailTest = emailTest;
    }

}

 

3.5、struts.xml

<action name=”myValidationAction” method=”execute” class=”com.mobilesoft.esales.webapp.action.MyValidationAction”>
    <result name=”success”>validationOutput.jsp</result>
    <result name=”input”>validationInput.jsp</result>
</action>    

4、参考文档

http://struts.apache.org/2.x/docs/validation.html

http://struts.apache.org/2.0.11.1/docs/type-conversion.html

http://struts.apache.org/2.x/docs/localizing-output.html

 

Struts2 中下拉框中采用树形结构实现

  在项目中,有大量的诸如“产品类型”这样的下拉选择框,为了保证项目后期的可维护性,除了相对固化的类别可以在代码中直接写死外,这些下拉选择框应当尽量从系统的数据字典表中进行动态读取和展现。但在展现时候存在一个问题,如果分类存在多级时候,一种方案是采用几个下拉框进行级联操作,也即所谓的DoubleSelect,但这种情况只适用与两级级联菜单的情况,在处理上也不具有通用性。例如典型的需求:

对于产品类别,结构如下:

产品类别(顶级节点:root,类型:product_type):

 |-娱乐(产品大类1:product_type1,类型:product_type)

   |-音乐(产品分类11,product_type11)

     |-流行音乐(子分类111,product_type111)

     |-摇滚音乐(子分类112,product_type112)

   |-产品分类12

 |-商务(产品大类2)

 


1、数据库存储策略

 

鉴于此,在项目中,对于这样的需求的实现方法如下:

对于一种类别(例如对于产品类别product_type)的子类别在oss_category(类别表)中存储方案如下:

1、在oss_category表中插入一条记录用于标识“产品类别”对应的顶级节点,其

category_id=1,parent_id=-1,category_type=”product_type”,category_name=”root”

2、在oss_category表中插入一条记录用于标识“product_type11”2级节点,其

category_id=2,parent_id=1,category_type=”product_type”,category_name=”product_type1”

3、在oss_category表中插入一条记录用于标识“product_type11”3级节点,其

category_id=3,parent_id=2,category_type=”product_type”,category_name=”product_type11”

整个数据库脚本如下:

CREATE TABLE `oss_category` (

`category_id` int(11) NOT NULL auto_increment,

`parent_id` int(11) default ‘-1′,

`level` smallint(6) default NULL,

`is_leaf` tinyint(1) default NULL,

`category_title` varchar(100) default NULL,

`category_name` varchar(100) default NULL,

`category_code` varchar(100) default NULL,

`category_type` varchar(30) default NULL,

`image` varchar(255) default NULL,

`status` varchar(20) default NULL,

`creator` varchar(50) default NULL,

`create_date` datetime default NULL,

`modify_user` varchar(50) default NULL,

`modify_date` datetime default NULL,

`description` text,

PRIMARY KEY(`category_id`)

) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=gbk;

INSERT INTO `oss_category` VALUES (’1′, ‘-1′, ’1′, ’0′, ‘root’, ‘root’, ‘root’, ‘product_type’, null, ’1′, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (’2′, ’1′, ’2′, ’0′, ‘<input type=\’checkbox\’ name=\’product_type1\’id=\’product_type1\’/><b>product_type1</b>’, ‘product_type1′, ‘product_type1′, ‘product_type’, null, ’1′, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (’3′, ’1′, ’2′, ’0′, ‘<input type=\’checkbox\’ name=\’product_type2\’ id=\’product_type2\’class=\’treeCheckBox\’/><b>product_typ’, ‘product_type2′, ‘product_type2′, ‘product_type’, null, ’1′, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (’4′, ’3′, ’3′, ’0′, ‘<input type=\’checkbox\’ name=\’product_type21\’id=\’product_type21\’/><b>product_type21</b>’, ‘product_type21′, ‘product_type21′, ‘product_type’, null, ’1′, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (’5′, ’2′, ’3′, ’0′, ‘<input type=\’checkbox\’ name=\’product_type11\’id=\’product_type11\’/><b>product_type11</b>’, ‘product_type11′, ‘product_type11′, ‘product_type’, null, ’1′, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (’6′, ’5′, ’4′, ’1′, ‘<input type=\’checkbox\’ name=\’product_type111\’id=\’product_type111\’/><b>product_type111</b>’, ‘product_type111′, ‘product_type111′, ‘product_type’, null, ’1′, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (’7′, ’3′, ’2′, ’0′, ‘<input type=\’checkbox\’ name=\’product_type22\’ id=\’product_type22\’/><b>product_type22</b>’, ‘product_type22′, ‘product_type22′, ‘product_type’, null, ’1′, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (’8′, ’2′, ’3′, ’0′, ‘<input type=\’checkbox\’ name=\’product_type12\’id=\’product_type12\’/><b>product_type12</b>’, ‘product_type12′, ‘product_type12′, ‘product_type’, null, ’1′, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (’9′, ’4′, ’4′, ’1′, ‘<input type=\’checkbox\’ name=\’product_type211\’id=\’product_type211\’/><b>product_type211</b>’, ‘product_type211′, ‘product_type211′, ‘product_type’, null, ’1′, null, null, null, null, null);

INSERT INTO `oss_category` VALUES (’10′, ’7′, ’4′, ’1′, ‘<input type=\’checkbox\’ name=\’product_type221\’id=\’product_type221\’/><b>product_type221</b>’, ‘product_type221′, ‘product_type221′, ‘product_type’, null, ’1′, null, null, null, null, null);


2、下拉框展现树形结构策略

编辑段落

为了保证跨浏览器的兼容性,不采用任何控件来实现在下拉框中展现树形结构,而采用最为原始的拼凑出如下形式的select 框

<select>

<option value=”1″>1</option>

<option value=”11″>&nbsp;&nbsp;|-11</option>

<option value=”12″>&nbsp;&nbsp;|-12</option>

<option value=”2″>2</option>

<option value=”21″>&nbsp;&nbsp;|-21</option>

<option value=”22″>&nbsp;&nbsp;|-22</option>

</select>

也即用&nbsp;来实现缩进来达到树形结构的效果,在实现上只需要在程序中拼凑出这样的字符串到界面展现即可。

在Struts2中,采用标签展现结果时候,注意property的选项escape=”false”,让Struts2不要对字符串进行转义。

<s:property value=”#request.selectResult” escape=”false”/>


3、代码实现

编辑段落

代码基本上沿用了原来的树形结构的代码,由于原来与OssCategory及ossCategoryDAO存在冲突,拷贝OssCategory.hbm.xml为Tree.hbm.xml。


3.1、Model层

编辑段落

package com.mobilesoft.framework.tree.model;

import java.util.Date;

import java.util.List;

import java.util.Set;

import com.mobilesoft.framework.common.model.BaseObject;

/**

* OssCategory entity.

*

* @author MyEclipse Persistence Tools

*/

public class Tree extends BaseObject

implements java.io.Serializable {

// Fields

private Integer categoryId;

private Integer parentId;

private Short level;

private Byte isLeaf;

private String categoryTitle;

private String categoryName;

private String categoryCode;

private String categoryType;

private String image;

private String status;

private String creator;

private Date createDate;

private String modifyUser;

private Date modifyDate;

private String description;

private Tree[] childCategories;

// Constructors

/** default constructor */

public Tree() {

}

/** full constructor */

public Tree(Integer parentId, Short level, Byte isLeaf,

String categoryTitle, String categoryName, String categoryCode,

String categoryType, String image, String status, String creator,

Date createDate, String modifyUser, Date modifyDate,

String description) {

this.parentId = parentId;

this.level = level;

this.isLeaf = isLeaf;

this.categoryTitle = categoryTitle;

this.categoryName = categoryName;

this.categoryCode = categoryCode;

this.categoryType = categoryType;

this.image = image;

this.status = status;

this.creator = creator;

this.createDate = createDate;

this.modifyUser = modifyUser;

this.modifyDate = modifyDate;

this.description = description;

}

// Property accessors

public Integer getCategoryId() {

return this.categoryId;

}

public void setCategoryId(Integer categoryId) {

this.categoryId = categoryId;

}

public Integer getParentId() {

return this.parentId;

}

public void setParentId(Integer parentId) {

this.parentId = parentId;

}

public Short getLevel() {

return this.level;

}

public void setLevel(Short level) {

this.level = level;

}

public Byte getIsLeaf() {

return this.isLeaf;

}

public void setIsLeaf(Byte isLeaf) {

this.isLeaf = isLeaf;

}

public String getCategoryTitle() {

return this.categoryTitle;

}

public void setCategoryTitle(String categoryTitle) {

this.categoryTitle = categoryTitle;

}

public String getCategoryName() {

return this.categoryName;

}

public void setCategoryName(String categoryName) {

this.categoryName = categoryName;

}

public String getCategoryCode() {

return this.categoryCode;

}

public void setCategoryCode(String categoryCode) {

this.categoryCode = categoryCode;

}

public String getCategoryType() {

return this.categoryType;

}

public void setCategoryType(String categoryType) {

this.categoryType = categoryType;

}

public String getImage() {

return this.image;

}

public void setImage(String image) {

this.image = image;

}

public String getStatus() {

return this.status;

}

public void setStatus(String status) {

this.status = status;

}

public String getCreator() {

return this.creator;

}

public void setCreator(String creator) {

this.creator = creator;

}

public Date getCreateDate() {

return this.createDate;

}

public void setCreateDate(Date createDate) {

this.createDate = createDate;

}

public String getModifyUser() {

return this.modifyUser;

}

public void setModifyUser(String modifyUser) {

this.modifyUser = modifyUser;

}

public Date getModifyDate() {

return this.modifyDate;

}

public void setModifyDate(Date modifyDate) {

this.modifyDate = modifyDate;

}

public String getDescription() {

return this.description;

}

public void setDescription(String description) {

this.description = description;

}

public Tree[] getChildCategories() {

return childCategories;

}

public void setChildCategories(Tree[] childCategories) {

this.childCategories = childCategories;

}

}


3.2、DAO层

编辑段落

package com.mobilesoft.framework.tree.dao.hibernate;

import org.apache.log4j.Logger;

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.hibernate.LockMode;

import org.springframework.context.ApplicationContext;

import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.mobilesoft.framework.tree.model.Tree;

/**

* A data access object (DAO) providing persistence and search support for

* Tree entities. Transaction control of the save(), update() and

* delete() operations can directly support Spring container-managed

* transactions or they can be augmented to handle user-managed Spring

* transactions. Each of these methods provides additional information for how

* to configure it for the desired type of transaction control.

*

* @see com.mobilesoft.esales.model.Tree

* @author MyEclipse Persistence Tools

*/

public class TreeDAO extends HibernateDaoSupport {

/**

* Logger for this class

*/

private static final Logger logger = Logger.getLogger(TreeDAO.class);

private static final Log log = LogFactory.getLog(TreeDAO.class);

// property constants

public static final String PARENT_ID = “parentId”;

public static final String LEVEL = “level”;

public static final String IS_LEAF = “isLeaf”;

public static final String tree_TITLE = “categoryTitle”;

public static final String tree_NAME = “categoryName”;

public static final String tree_CODE = “categoryCode”;

public static final String tree_TYPE = “categoryType”;

public static final String IMAGE = “image”;

public static final String STATUS = “status”;

public static final String CREATOR = “creator”;

public static final String MODIFY_USER = “modifyUser”;

public static final String DESCRIPTION = “description”;

public String delimiter;

protected void initDao() {

// do nothing

}

/*

省略掉其他方法

*/

public static TreeDAO getFromApplicationContext(

ApplicationContext ctx) {

return (TreeDAO) ctx.getBean(“treeDAO”);

}

public Tree[] getAlltree() {

ArrayList<Tree> resultList=new ArrayList<Tree>();

String queryString=”from Tree as tree where tree.parentId=1″;

List<Tree> queryList =getHibernateTemplate().find(queryString);

Iterator iterator=queryList.iterator();

while(iterator.hasNext()){

Tree tree=(Tree)iterator.next();

Tree[] childrenArray=getChildCategoriesById(tree.getCategoryId());

logger.debug(“getAlltree() – Tree[] childrenList=” + childrenArray.length);

tree.setChildCategories(childrenArray);

resultList.add(tree);

}

Tree[] resultArray=(Tree[])resultList.toArray(new Tree[resultList.size()]);

return resultArray;

}

public String getChildrenByType(String type,boolean includeRoot,String delimiter ){

this.delimiter=delimiter;

StringBuffer resultBuffer=new StringBuffer();

resultBuffer.append(“<select>”);

StringBuffer queryBuffer=new StringBuffer(“from Tree as tree where tree.categoryType=’”);

queryBuffer.append(type);

queryBuffer.append(“‘ order by tree.categoryId asc “);

logger.fatal(queryBuffer);

List<Tree> queryList =getHibernateTemplate().find(queryBuffer.toString());

if(queryList.size()>0){

Tree[] treeArray=(Tree[])queryList.toArray(new Tree[queryList.size()]);

int start;

if(includeRoot)

start=0;

else

start=1;

resultBuffer.append(“<option value=’”);

resultBuffer.append(treeArray[start].getCategoryId());

resultBuffer.append(“‘>”);

resultBuffer.append(treeArray[start].getCategoryName());

resultBuffer.append(“</option>”);

String childrenStr=getChildCategories4Select(treeArray[start].getCategoryId(),treeArray[start].getLevel());

logger.debug(“getChildrenByType( String ) – start String=” + start +”,level is :”+treeArray[start].getLevel());

resultBuffer.append(childrenStr);

logger.fatal(“getChildrenByType(String) – StringBuffer resultBuffer11111=” + resultBuffer);

resultBuffer.append(“</select>”);

return resultBuffer.toString();

}else{

return “</select>”;

}

}

public Tree[] getChildCategoriesById(int treeId) {

String queryString=”from Tree as tree where tree.parentId=”+treeId;

List<Tree> queryList =getHibernateTemplate().find(queryString);

logger.debug(“getChildCategoriesById(int) – List<Tree> queryList=” + queryList.size());

ArrayList<Tree> resultList=new ArrayList<Tree>();

Iterator iterator=queryList.iterator();

while(iterator.hasNext()){

Tree tree=(Tree)iterator.next();

Tree[] childrenList=getChildCategoriesById(tree.getCategoryId());

logger.debug(“getChildCategoriesById(int) – Tree[] childrenList=” + childrenList+”parentid is “+treeId);

tree.setChildCategories(childrenList);

resultList.add(tree);

}

Tree[] resultArray=(Tree[])resultList.toArray(new Tree[resultList.size()]);

return resultArray;

}

/*

* @return 要拼凑出如下形式的select 框

* <select>

* <option value=”1″>1</option>

* <option value=”11″>&nbsp;&nbsp;|-11</option>

* <option value=”12″>&nbsp;&nbsp;|-12</option>

* <option value=”2″>2</option>

* <option value=”21″>&nbsp;&nbsp;|-21</option>

* <option value=”22″>&nbsp;&nbsp;|-22</option>

* </select>

*

* @param treeId节点的id号

* @param rootLevel所在类别子树根节点在整棵树中的层级

*/

public String getChildCategories4Select(int treeId,int rootLevel) {

StringBuffer childBuffer=new StringBuffer();

String queryString=”from Tree as tree where tree.parentId=”+treeId;

List<Tree> queryList =getHibernateTemplate().find(queryString);

logger.debug(“getChildCategories4Select(int,int) – List<Tree> queryList=” + queryList.size());

ArrayList<Tree> resultList=new ArrayList<Tree>();

Iterator iterator=queryList.iterator();

while(iterator.hasNext()){

Tree tree=(Tree)iterator.next();

int childLevel=tree.getLevel();

childBuffer.append(“<option value=’”);

childBuffer.append(tree.getCategoryId());

childBuffer.append(” ‘>”);

for(int i=0;i<=childLevel-rootLevel;i++)

childBuffer.append(“&nbsp;&nbsp;”);

childBuffer.append(this.delimiter+tree.getCategoryName());

childBuffer.append(“</option>”);

String childrenStr=getChildCategories4Select(tree.getCategoryId(),rootLevel);

childBuffer.append(childrenStr);

logger.debug(“getChildCategories4Select(int,int) – childBuffer=” + childBuffer);

}

return childBuffer.toString();

}

}


3.3、Service层

编辑段落

TreeService.java

package com.mobilesoft.framework.tree.service;

import java.util.List;

import java.util.Set;

import com.mobilesoft.framework.tree.model.Tree;

public interface TreeService {

/**

* @return 获取下级子节点

*/

//public Set getChildren(int rootId);

/**

* @return 递归指定级别的所有子节点

*

*/

//public Set getChildrenByLevel(int rootId,int level);

/**

* 用于获取数据字典中指定类别的所有类型的节点,

* 类别表节点的树形结构

* |– ROOT 根类别(-1)

*  |–类别类型:套餐类别 bundle_type

*   |–3 套餐类别1

*   |–4 套餐类别2

* |–类别类型:产品类别 product_type

*   |–产品大类1

*    |–产品大类11

*     |—产品大类别111

*     |—产品大类别112

*    |–产品大类12

* @return 获取指定类型的所有子节点

* 在实现上,对于诸如”产品类别“等下拉框可能为树形结构的元素,

* 为了避免由于浏览器兼容性问题,在形式上是树形结构,

* 实际上就是从字典表(目前树形结构仍然采用oss_category作为字典表)动态取出数据,然后拼凑成字符串形式展现,例如:

* <select>

* <option value=”1″>1</option>

*<option value=”11″>&nbsp;&nbsp;|-11</option>

*<option value=”12″>&nbsp;&nbsp;|-12</option>

*<option value=”2″>2</option>

*<option value=”21″>&nbsp;&nbsp;|-21</option>

*<option value=”22″>&nbsp;&nbsp;|-22</option>

*</select>

* @param type:节点的类型

* @param levle:节点相对于所属类型的节点的级别 -1表示获取所属类型的根节点的所有级别的子节点

*/

public String getChildrenByType( String type);

public String getChildrenByType( String type,boolean includeRoot);

public String getChildrenByType( String type,boolean includeRoot,String delimiter);

/**

* @return 获取指定根节点的所有子节点

*/

public Tree[]getAllTree();

}

TreeServiceImpl.java

package com.mobilesoft.framework.tree.service.impl;

import java.util.List;

import java.util.Set;

import com.mobilesoft.framework.tree.dao.hibernate.TreeDAO;

import com.mobilesoft.framework.tree.model.Tree;

import com.mobilesoft.framework.tree.service.TreeService;

public class TreeServiceImpl implements TreeService {

TreeDAO treeDAO;

String delimiter;

public Set getChildren(int rootId) {

// TODO Auto-generated method stub

return null;

}

public Set getLevelChildren(int rootId, int level) {

// TODO Auto-generated method stub

return null;

}

public String getChildrenByType( String type){

return treeDAO.getChildrenByType(type,true,this.delimiter);

}

public String getChildrenByType( String type,boolean includeRoot){

return treeDAO.getChildrenByType(type,includeRoot,this.delimiter);

}

public String getChildrenByType( String type,boolean includeRoot,String delimiter){

return treeDAO.getChildrenByType(type,includeRoot,delimiter);

}

public Tree[]getAllTree(){

return treeDAO.getAlltree();

}

public TreeDAO gettreeDAO() {

return treeDAO;

}

public void settreeDAO(TreeDAO treeDAO) {

this.treeDAO = treeDAO;

}

public String getDelimiter() {

return delimiter;

}

public void setDelimiter(String delimiter) {

this.delimiter = delimiter;

}

}


3.4、调用方法

编辑段落

以Action中为例:

private TreeService treeService;

public String getChildrenByType(){

String selectResult=treeService.getChildrenByType(“product_type”);

getRequest().setAttribute(“selectResult”, selectResult);

returnSUCCESS;

}

public TreeService getTreeService() {

returntreeService;

}

publicvoid setTreeService(TreeService treeService) {

this.treeService = treeService;

}

注意:getChildrenByType方法有三个重载(overload)方法,

public String getChildrenByType( String type);

public String getChildrenByType( String type,boolean includeRoot);

public String getChildrenByType( String type,boolean includeRoot,String delimiter);

但实际上最后还是调用的是getChildrenByType( String type,boolean includeRoot,String delimiter),

参数说明:

type: 类别的类型,例如product_type

includeRoot:是否包含所在类别的根节点,例如查询product_type是否,是否需要导出所在类别的root节点

delimiter:在下拉框选择时候,分隔符类型,缺省为“|-”,可以在applicationContext-service.xml中修改或调用时候传递参数。


3.5、配置文件

编辑段落

applicationContext-resources.xml

<property name=”mappingResources”>

<list>

<value>com/mobilesoft/framework/tree/model/Tree.hbm.xml</value>

</list>

</property>

applicationContext-dao.xml

<bean id=”treeDAO”

class=”com.mobilesoft.framework.tree.dao.hibernate.TreeDAO”>

<property name=”sessionFactory”>

<ref bean=”sessionFactory” />

</property>

</bean>

applicationContext-service.xml

<bean id=”treeService”

class=”com.mobilesoft.framework.tree.service.impl.TreeServiceImpl”>

<property name=”treeDAO”>

<ref bean=”treeDAO” />

</property>

<property name=”delimiter” value=”|-”/>

</bean>

action-servlet.xml

<bean id=”treeAction” scope=”prototype”

class=”com.mobilesoft.esales.webapp.action.TreeAction”>

<property name=”treeService”>

<ref bean=”treeService” />

</property>

</bean>

struts.xml

<action name=”treeAction” method=”execute” class=”treeAction”>

<result name=”success”>test/treetest.jsp</result>

</action>

 

Technorati 标签: ,,,

Struts2 OptionTransferSelect标签使用

 

有时候需要在两个Select框中双向挪动数据,此种在Struts2叫OptionTransferSelect,效果如下:

optiontransferselect

1. Action层

TransferSelect.java

package com.mobilesoft.esales.webapp.action;

import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

import java.util.Map;

import java.util.Set;

import org.apache.log4j.Logger;

public class TransferSelect extends BaseAction {

private static final Logger logger = Logger.getLogger(DoubleListAction.class);

    String[] leftRoles;

    String[] rightRoles;

public String execute(){   

return SUCCESS;

    }

public String transferSelect(){

       ArrayList<Roles> leftList=new ArrayList<Roles> ();     

       Roles leftRoles1=new Roles();

       leftRoles1.setRoleId(1);

       leftRoles1.setRoleName(“角色1″);

       Roles leftRoles2=new Roles();

       leftRoles2.setRoleId(2);

       leftRoles2.setRoleName(“角色2″);

       Roles leftRoles3=new Roles();

       leftRoles3.setRoleId(3);

       leftRoles3.setRoleName(“角色3″);

       Roles leftRoles4=new Roles();

       leftRoles4.setRoleId(4);

       leftRoles4.setRoleName(“角色4″);

       leftList.add(leftRoles1);

       leftList.add(leftRoles2);

       leftList.add(leftRoles3);

       leftList.add(leftRoles4);

       ArrayList<Roles>  rightList=new ArrayList();     

       Roles rightRoles1=new Roles();

       rightRoles1.setRoleId(1);

       rightRoles1.setRoleName(“角色5″);

       Roles rightRoles2=new Roles();

       rightRoles2.setRoleId(2);

       rightRoles2.setRoleName(“角色6″);

       Roles rightRoles3=new Roles();

       rightRoles3.setRoleId(3);

       rightRoles3.setRoleName(“角色7″);

       Roles rightRoles4=new Roles();

       rightRoles4.setRoleId(4);

       rightRoles4.setRoleName(“角色8″);

       rightList.add(rightRoles1);

       rightList.add(rightRoles2);

       rightList.add(rightRoles3);

       rightList.add(rightRoles4);    

       getRequest().setAttribute(“rightList”, rightList);

       getRequest().setAttribute(“leftList”, leftList);

return SUCCESS;

    }

public String transferSelectPost(){

       Map map=getRequest().getParameterMap();

       Set set=map.entrySet();

       Iterator iterator=set.iterator();

while(iterator.hasNext()){

           Map.Entry mapEntry=(Map.Entry)iterator.next();

if(mapEntry.getValue() instanceof String[]){

              String[] selectValues=(String[])mapEntry.getValue();

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

logger.fatal(“For map test,The key is: “+mapEntry.getKey()+”,value is :”+selectValues[i]); 

              }

           }

       }

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

logger.fatal(“For Array test,The leftRoles roleName is: “+leftRoles[i]); 

       }

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

logger.fatal(“For Array test,The rightRoles roleName is: “+rightRoles[i]);  

       }     

       getRequest().setAttribute(“map”, map);

return SUCCESS;

    }

public void setLeftRoles(String[] leftRoles) {

this.leftRoles = leftRoles;

    }

public void setRightRoles(String[] rightRoles) {

this.rightRoles = rightRoles;

    }  

}

Roles.java

package com.mobilesoft.esales.webapp.action;

public class Roles {
    private Integer roleId;
    private String roleName;
    public Roles(){
    }
    public Integer getRoleId() {
        return roleId;
    }
    public void setRoleId(Integer roleId) {
        this.roleId = roleId;
    }
    public String getRoleName() {
        return roleName;
    }
    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }
}

2. Web层

transferselect.jsp

<%@ taglib prefix=”s” uri=”/struts-tags” %>

<%@ page language=”java” errorPage=”/error.jsp” pageEncoding=”GBK” contentType=”text/html;charset=GBK” %>

<html>

<head>

<title>Struts 2 Cool Tags – OptionTransferSelect</title>

<s:head theme=”ajax”/>

</head>

<body>

<h2>用户角色授权</h2>

<s:form action=”transferSelectPost” name=”form1″ >

<s :o ptiontransferselect

label=”测试-Action数据”

name=”leftRoles”

            leftTitle=”用户已授权角色”

            list=”#request.leftList”

            listKey=”roleId”

            listValue=”roleName”

multiple=”true”

            headerKey=”headerKey”

            headerValue=”— 请选择用户角色 —”

            emptyOption=”false”

            allowUpDownOnLeft=”false”

            cssStyle=”width:200px;height:300px;”

            rightTitle=”用户未授权的角色”

            doubleList=”#request.rightList”

            doubleListKey=”roleId”

            doubleListValue=”roleName”

            doubleName=”rightRoles”

            doubleHeaderKey=”doubleHeaderKey”

            doubleHeaderValue=”— 请选择用户角色 —”

            doubleEmptyOption=”false”

            doubleMultiple=”true”

            allowUpDownOnRight=”false”

            doubleCssStyle=”width:200px;height:300px;”

/>

<s:submit align=”left”/>

</s:form>

</body>

</html>

transferselectpost.jsp

<%@ taglib prefix=”s” uri=”/struts-tags” %>

<%@ page language=”java” errorPage=”/error.jsp” pageEncoding=”GBK” contentType=”text/html;charset=GBK” %>

<html>

<head>

<title>Struts 2 Cool Tags – OptionTransferSelect</title>

<s:head />

</head>

<body>

<h2>选择结果</h2>

<s:form action=”transferSelect” name=”form1″ >

<s:iterator value=”#request.map” status=”mystatus”>

<tr>

<td>

<s:property value=”key” />:<s:property value=”value” />,             

</td>

</tr>

</s:iterator>

<s:submit align=”left”/>

</s:form>

</body>

</html>

3. struts.xml

<action name=”transferSelect” method=”transferSelect” class=”com.mobilesoft.esales.webapp.action.TransferSelect”>

<result name=”success”>test/transferselect.jsp</result>

</action>

<action name=”transferSelectPost” method=”transferSelectPost” class=”com.mobilesoft.esales.webapp.action.TransferSelect”>

<result name=”success”>test/transferselectpost.jsp</result>

</action>

 

Struts2 DoubleSelect标签的使用

Struts2本身提供了级联下拉选择框标签,这对简化诸如省份/城市这样的典型需求的操作是大有好处的,同时也有利于架构的统一,没有必要引入一大堆的javascript代码,使用方法如下:

1、Action层

DoubleListAction.java

package com.mobilesoft.esales.webapp.action;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import com.mobilesoft.framework.tree.model.FileWrapper;
import com.mobilesoft.framework.tree.model.OssCategory;
import com.mobilesoft.framework.tree.service.TreeService;

public class DoubleListAction extends BaseAction {
    /**
     * Logger for this class
     */
    private static final Logger logger = Logger.getLogger(DoubleListAction.class);

    public String execute() {
        Map map=new HashMap();
        ArrayList list1=new ArrayList();
        list1.add("11");
        list1.add("12");
        list1.add("13");
        map.put("1", list1);
        ArrayList list2=new ArrayList();
        list2.add("21");
        list2.add("22");
        list2.add("23");
        map.put("2", list2);
        ArrayList list3=new ArrayList();
        list3.add("31");
        list3.add("32");
        list3.add("33");
        map.put("3", list3);
        getRequest().setAttribute("map", map);
        return SUCCESS;
    }

    public String doubleSelectTest(){
        Map map=new HashMap();
        ArrayList list1=new ArrayList();
        list1.add("11");
        list1.add("12");
        list1.add("13");
        map.put("1", list1);
        ArrayList list2=new ArrayList();
        list2.add("21");
        list2.add("22");
        list2.add("23");
        map.put("2", list2);
        ArrayList list3=new ArrayList();
        list3.add("31");
        list3.add("32");
        list3.add("33");
        map.put("3", list3);
        getRequest().setAttribute("map", map);
        return SUCCESS;
    }
    public String doubleSelectPost(){

        Map map=getRequest().getParameterMap();
        Set set=map.entrySet();
        Iterator iterator=set.iterator();
        while(iterator.hasNext()){
            Map.Entry mapEntry=(Map.Entry)iterator.next();
            logger.fatal("The key is: "+mapEntry.getKey()+",value is :"+mapEntry.getValue());
        }
        getRequest().setAttribute("map", map);
        return SUCCESS;
    }

}

2、页面

doubleslect.jsp

<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page language="java" errorPage="/error.jsp" pageEncoding="GBK" contentType="text/html;charset=GBK" %>
<html>
<head>
    <title>Struts 2 Cool Tags - <s:doubeselect/ ></title>
    <s:head />
</head>
<body>
<h2>Doubleselect Tag Example</h2>
<s:form action="doubleSelectPost" name="form1">
Doubleselect演示1
<s:set name="technology"
     value="#{
         'Java': {'Spring', 'Hibernate', 'Struts 2'},
         '.Net': {'Linq', ' ASP.NET 2.0'},
         'Database': {'Oracle', 'SQL Server', 'DB2', 'MySQL'}
         }" />
<s:doubleselect label="技术选择"
list="#technology.keySet()"
name="doubleselect1"
doubleName="techdetail"
formName="form1"
doubleList="#technology[top]" />
<br/><br/>
Doubleselect演示数据演示2:
<s:set name="mymap" value="#request.map"/>
<s:doubleselect list="#request.map.keySet()"
                doubleName="mymap1"
                name="doubleselect2"
                formName="form1"
                doubleList="#request.map[top]"
                label="Map Test" />

<s:submit/>
</s:form>
</body>
</html>

doubleselectpost.jsp

<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page language="java" errorPage="/error.jsp" pageEncoding="GBK" contentType="text/html;charset=GBK" %>
<html>
<head>
    <title>Struts 2 Cool Tags - <s:doubeselect/ ></title>
    <s:head />
</head>
<body>
<h2>Doubleselect Tag Example</h2>
<s:form name="form1">
<table border="0" cellspacing="0" cellpadding="1">
<tr>
  <th>参数值</th>
</tr>
<s:iterator value="#request.map.keySet()" status="keys">
    <tr>
        <td><s:property/></td>
    </tr>
</s:iterator>
</s:form>
</body>
</html>

3、struts.xml配置文件

<action name="doubleSelectTest" method="doubleSelectTest" class="com.mobilesoft.esales.webapp.action.DoubleListAction">
    <result name="success">test/doubleselect.jsp</result>
</action>
<action name="doubleSelectPost" method="doubleSelectPost" class="com.mobilesoft.esales.webapp.action.DoubleListAction">
    <result name="success">test/doubleselectpost.jsp</result>
</action>

4、级联下拉框间的换行<br >问题

缺省情况下,两个级联下拉框间是换行的,Struts2采用Freemarker来做模板,可以通过修改模板来定制标签缺省的树形。参看“Struts2中Datetimepicker控件的中文问题”中的方法,也即:

  • 修改struts.mxl,增加如下内容。

<constant name=”struts.serve.static” value=”false” />

<constant name=”struts.ui.templateDir” value=”struts” />

注意在struts2-core-2.0.11.jar/org/apache/struts2/default.properties中有几个与Freemarker对应的配置参数:

struts.ui.theme=xhtml
struts.ui.templateDir=template
#sets the default template type. Either ftl, vm, or jsp
struts.ui.templateSuffix=ftl

  • 覆盖缺省的静态文件

在resource目录(与WEB-INF同级或WEB-INF下)创建struts目录,并:

解压struts2-core-2.0.11.jar:/org/apache/struts2/static/ to /struts/

解压struts2-core-2.0.11.jar:/template/simple/ to /struts/simple/

解压struts2-core-2.0.11.jar:/template/xhtml/ to /struts/xhtml/

解压struts2-core-2.0.11.jar:/template/css_xhtml/ to /struts/css_xhtml/

解压struts2-core-2.0.11.jar:/template/ajax/ to /struts/ajax/

解压struts2-core-2.0.11.jar:/template/archive/ to /struts/archive

最后的目录结构如下:

struts/
- ajax/
- archive/
- css_xhtml/
- dojo/
- nls/
- src/
- animation/
- …
- xml/
- struts/
- widget/
- widgets/
- niftycorners/
- simple/
- xhtml/

  • 修改doubleselect的模板doubleselect.ftl

修改struts/simple/doubleselect.ftl去除其中的<br />

Technorati 标签: ,,,

Struts2 Tree标签的使用

  在项目中有很多地方使用了树形结构,主要有几个地方:素材和软件等软件资源表,人员权限的权限资源表、人员权限的角色,套餐及产品包,现以类别表为例,说明树形结构的数据库存储、利用Struts2 中dojo的Tree标签来完成前端展现。

1、 数据库结构

CREATE TABLE `oss_category` (
  `category_id` int(11) NOT NULL auto_increment,
  `parent_id` int(11) default '-1',
  `level` smallint(6) default NULL ,
  `is_leaf` tinyint(1) default NULL,
  `category_title` varchar(100) default NULL,
  `category_name` varchar(100) default NULL,
  `category_code` varchar(100) default NULL,
  `category_type` varchar(30) default NULL,
  `image` varchar(255) default NULL,
  `status` varchar(20) default NULL,
  `creator` varchar(50) default NULL,
  `create_date` datetime default NULL,
  `modify_user` varchar(50) default NULL,
  `modify_date` datetime default NULL,
  `description` text,
  PRIMARY KEY  (`category_id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=gbk;

测试数据:

INSERT INTO `oss_category` VALUES ('1', '-1', '1', '0', 'root', 'root', 'root', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('2', '1', '2', '0', '<input type=\'checkbox\' name=\'product_type1\'  id=\'product_type1\'  /><b>product_type1</b>', 'product_type1', 'product_type1', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('3', '1', '2', '0', '<input type=\'checkbox\' name=\'product_type2\' id=\'product_type2\'  class=\'treeCheckBox\'/><b>product_typ', 'product_type2', 'product_type2', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('4', '3', '3', '0', '<input type=\'checkbox\' name=\'product_type21\'  id=\'product_type21\'  /><b>product_type21</b>', 'product_type21', 'product_type21', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('5', '2', '3', '0', '<input type=\'checkbox\' name=\'product_type11\'  id=\'product_type11\'  /><b>product_type11</b>', 'product_type11', 'product_type11', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('6', '5', '4', '1', '<input type=\'checkbox\' name=\'product_type111\'  id=\'product_type111\'  /><b>product_type111</b>', 'product_type111', 'product_type111', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('7', '3', '2', '0', '<input type=\'checkbox\' name=\'product_type22\'  id=\'product_type22\'  /><b>product_type22</b>', 'product_type22', 'product_type22', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('8', '2', '3', '0', '<input type=\'checkbox\' name=\'product_type12\'  id=\'product_type12\'  /><b>product_type12</b>', 'product_type12', 'product_type12', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('9', '4', '4', '1', '<input type=\'checkbox\' name=\'product_type211\'  id=\'product_type211\'  /><b>product_type211</b>', 'product_type211', 'product_type211', 'product_type', null, '1', null, null, null, null, null);
INSERT INTO `oss_category` VALUES ('10', '7', '4', '1', '<input type=\'checkbox\' name=\'product_type221\'  id=\'product_type221\'  /><b>product_type221</b>', 'product_type221', 'product_type221', 'product_type', null, '1', null, null, null, null, null);

 

注意:

  • 为了支持在树形结构的节点前有checkbox框,需要在树形结构的title(对应s:tree中的nodeTitleProperty)前增加checkbox树形,因此在数据库中category_title字段的格式采用如上的形式,例如:

<input type=’checkbox’ name=’product_type1′  id=’product_type1′  /><b>product_type1</b>

  • 对于有序的树形结构,为了标识left和right,可以增加ordering字段,或者采用类似嵌套树的结构,例如
CREATE TABLE nested_category (
 category_id INT AUTO_INCREMENT PRIMARY KEY,
 name VARCHAR(20) NOT NULL,
 lft INT NOT NULL,
 rgt INT NOT NULL
);
但采用嵌套树的方式,在变动一个节点时候需要对相关的节点进行更新,不是很适合。但效率较高。可以参看:
http://dev.mysql.com/tech-resources/articles/hierarchical-data.html
http://www.sitepoint.com/print/hierarchical-data-database
  • mysql对树形结构没有原生的支持,Oracle和DB2可以支持,Oracle通过CONNECT BY,DB2通过With方式。

2、Model层

OssCategory.java

直接用myeclipse生成后,增加了如下内容:

    private OssCategory[] childCategories;
    private OssCategory parentCategory;

    public OssCategory[] getChildCategories() {
        return childCategories;
    }

    public void setChildCategories(OssCategory[] childCategories) {
        this.childCategories = childCategories;
    }

    public OssCategory getParentCategory() {
        return parentCategory;
    }

    public void setParentCategory(OssCategory parentCategory) {
        this.parentCategory = parentCategory;
    }

3、DAO层

OssCategoryDAO.java

直接用myeclipse生成,没有调优,增加如下内容:

    public OssCategory[] getAllCategory() {
        ArrayList<OssCategory> resultList=new ArrayList<OssCategory>();
        String queryString="from OssCategory as category where category.parentId=1";
        List<OssCategory> queryList =getHibernateTemplate().find(queryString);
        Iterator iterator=queryList.iterator();
        while(iterator.hasNext()){
            OssCategory ossCategory=(OssCategory)iterator.next();
            OssCategory[] childrenArray=getChildCategoriesById(ossCategory.getCategoryId());
            logger.debug("getAllCategory() - OssCategory[] childrenList=" + childrenArray.length);
            ossCategory.setChildCategories(childrenArray);
            resultList.add(ossCategory);

        }

        OssCategory[] resultArray=(OssCategory[])resultList.toArray(new OssCategory[resultList.size()]);
        return resultArray;

    }

    public OssCategory[] getChildCategoriesById(int categoryId) {
        String queryString="from OssCategory as category where category.parentId="+categoryId;
        List<OssCategory> queryList =getHibernateTemplate().find(queryString);
        logger.debug("getChildCategoriesById(int) - List<OssCategory> queryList=" + queryList.size());
        ArrayList<OssCategory> resultList=new ArrayList<OssCategory>();
        Iterator iterator=queryList.iterator();
        while(iterator.hasNext()){
            OssCategory ossCategory=(OssCategory)iterator.next();
            OssCategory[] childrenList=getChildCategoriesById(ossCategory.getCategoryId());
            logger.debug("getChildCategoriesById(int) - OssCategory[] childrenList=" + childrenList+"parentid is "+categoryId); //$NON-NLS-1$
            ossCategory.setChildCategories(childrenList);
            resultList.add(ossCategory);

        }
        OssCategory[] resultArray=(OssCategory[])resultList.toArray(new OssCategory[resultList.size()]);
        return resultArray;
    }
注意:
  •  getAllCategory和getChildCategoriesById采用了递归调用方式,应该可以一次性取出指定节点的所有子节点到结果集合List,然后对List在内存中进行遍历,但比较麻烦,先采用此种方式。
  •  struts2的tree应该支持List,而不一定要采用数组方式,只不过没有测试
 

4、Service层

TreeService.java

有些方法尚未实现

package com.mobilesoft.framework.tree.service;

import java.util.List;
import java.util.Set;

import com.mobilesoft.framework.tree.model.OssCategory;

public interface TreeService {

    /**
     * @return 获取下级子节点
     */
  //  public Set getChildren(int rootId);

    /**
     * @return 递归指定级别的所有子节点
     *      
     */
  //  public Set getChildrenByLevel(int rootId,int level);

    /**
     * @return 获取指定类型的所有子节点
     * @param type:节点的类型
     * @param levle:节点相对于所属类型的节点的级别 -1表示获取所属类型的根节点的所有级别的子节点
     */
 //   public Set getChildrenByType( String type,int level);

    /**
     * @return 获取指定根节点的所有子节点
     */
    public OssCategory[]  getAllCategory();

}

TreeServiceImpl.java

 

package com.mobilesoft.framework.tree.service.impl;

import java.util.List;
import java.util.Set;

import com.mobilesoft.framework.tree.dao.hibernate.OssCategoryDAO;
import com.mobilesoft.framework.tree.model.OssCategory;
import com.mobilesoft.framework.tree.service.TreeService;

public class TreeServiceImpl implements TreeService {
    OssCategoryDAO ossCategoryDAO;
    public Set getChildren(int rootId) {
        // TODO Auto-generated method stub
        return null;
    }

    public Set getLevelChildren(int rootId, int level) {
        // TODO Auto-generated method stub
        return null;
    }

    public OssCategory[]   getAllCategory(){
        return ossCategoryDAO.getAllCategory();
    }
    public OssCategoryDAO getOssCategoryDAO() {
        return ossCategoryDAO;
    }

    public void setOssCategoryDAO(OssCategoryDAO ossCategoryDAO) {
        this.ossCategoryDAO = ossCategoryDAO;
    }

}

5、Action层

package com.mobilesoft.esales.webapp.action;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import com.mobilesoft.framework.tree.model.FileWrapper;
import com.mobilesoft.framework.tree.model.OssCategory;
import com.mobilesoft.framework.tree.service.TreeService;

public class TreeAction extends BaseAction {
    /**
     * Logger for this class
     */
    private static final Logger logger = Logger.getLogger(TreeAction.class);

    private OssCategory root;
    private TreeService treeService;
    private FileWrapper fileroot;

    public String execute() {
        root = new OssCategory();
        root.setCategoryId(new Integer(1));
        root.setParentId(-1);
        root.setCategoryTitle("Root");
        root.setCategoryName("Root");
        OssCategory[] childCategories = treeService.getAllCategory();

        root.setChildCategories(childCategories);
        visitTree(childCategories);
        getRequest().setAttribute("root", root);
        // fileroot = new FileWrapper(getSession().getServletContext().getRealPath("/"));
        return SUCCESS;
    }

    public String treeWithCheckbox() {
        Map map = getRequest().getParameterMap();
        logger.fatal("the tree map");
        Set set = map.entrySet();
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();

            logger.fatal("the tree key is: " + entry.getKey() + " ,and value is: "
                    + entry.getValue());
        }

        return SUCCESS;
    }

    public TreeService getTreeService() {
        return treeService;
    }

    public void setTreeService(TreeService treeService) {
        this.treeService = treeService;
    }

    public OssCategory getRoot() {
        return root;
    }

    public void setRoot(OssCategory root) {
        this.root = root;
    }

    public void visitTree(OssCategory[] tree) {

        for (int i = 0; i < tree.length; i++) {
            OssCategory node = (OssCategory) tree[i];
            logger.fatal("visitTree:The treenode parentid is :"
                    + node.getParentId() + " ,treenode id is "
                    + node.getCategoryId() + ",and the treenode name is "
                    + node.getCategoryName());
            visitTree(node.getChildCategories());
        }

    }
}

6、页面

treetest.jsp

<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ page language="java" errorPage="/error.jsp" pageEncoding="GBK" contentType="text/html;charset=GBK" %>
<html>
<head>
    <title>tag list</title>
    <%@ include file="/common/meta.jsp" %>

<style>
    .treeCheckBox {
        height: 14px;
        margin: 0px;
        padding: 0px;
        border: 1px;
        vertical-align: middle;
    }
</style>
<s:head theme="ajax" debug="true" />
</head>
<body >

<script type="text/javascript">
function treeNodeSelected(arg) {
    //alert("id["+arg.source.widgetId+"], name["+ arg.source.title+ "] selected");
}

function treeNodeExpanded(arg) {
    //alert("id["+arg.source.widgetId+"], name["+ arg.source.title+ "] expanded");
}

function treeNodeCollapsed(arg) {
    //alert("id["+arg.source.widgetId+"], name["+ arg.source.title+ "] collapsed");
}

dojo.addOnLoad(function() {
    var t = dojo.widget.byId('root');
    dojo.event.topic.subscribe(t.eventNames.expand, treeNodeExpanded);
    dojo.event.topic.subscribe(t.eventNames.collapse, treeNodeCollapsed);

    var s = t.selector;   

});

</script>

<s:form name="form1" action="treeWithCheckbox">
<div style="float:left; margin-right: 50px;" >
<s:tree
    theme="ajax"
    id="root"
    rootNode="#request.root"
    childCollectionProperty="childCategories"
    nodeIdProperty="categoryId"
    nodeTitleProperty="categoryTitle"
    treeSelectedTopic="treeSelected">
</s:tree>
</div>     

<div style="float:left; margin-right: 50px;">
<s:submit/>
</div>
</s:form>

</body>
</html>
 

注意:

  • 在s:tree中不能使用label标签,使用后树形结构出不来。
  • childCollectionProperty=”childCategories”对应Category.java中的
    private OssCategory[] childCategories;
    
    public OssCategory[] getChildCategories() {
        return childCategories;
    } 
    
    public void setChildCategories(OssCategory[] childCategories) {
        this.childCategories = childCategories;
    }
    
  • nodeIdProperty=”categoryId”和nodeTitleProperty=”categoryTitle” 对应OssCategory[] childCategories中每一个Category的categoryId和categoryTitle

  • rootNode=”#request.root”,采用rootNode=”root”方式好像取不到数据,按理说应该可以通过ognl自动调用getRoot方法得到

  • 结果提交后,根据对应checkbox的name的on属性来判断是否选中,因此在数据库中对节点的命名需要有一定规则,以便方便选择

尚需要完善的地方:

  • 增加对下拉框树形结构实现的支持,以处理项目中大量诸如产品类型、资源类型等类型。

在实现上,为了避免由于浏览器兼容性问题,在形式上是树形结构,实际上就是从字典表(目前树形结构仍然采用oss_category作为字典表)动态取出数据,然后拼凑成字符串形式展现,例如:

<select>
    <option value="1">1</option>
    <option value="11">&nbsp;&nbsp;|-11</option>
    <option value="12">&nbsp;&nbsp;|-12</option>
    <option value="2">2</option>
    <option value="21">&nbsp;&nbsp;|-21</option>
    <option value="22">&nbsp;&nbsp;|-22</option>
</select>
  • 进一步优化递归算法,一次性从数据库取出需要的数据,在内存中完成树形结构的构造,避免对数据库的查询,在目前情况下,采用递归方式问题也不大。
  • 由于Struts2.0采用dojo来实现其标签,尽管dojo与jquery相比较很重,但由于与Struts2.0较好支持,在架构统一上和使用上还是较为方便的,因此有空需要研究一下dojo库。

7、参考资料

http://www.ajaxtree.com/

http://struts.apache.org/2.x/docs/tree.html

http://ait.web.psi.ch/js/dojo/tests/widget/tree/

http://exjava.spaces.live.com/blog/cns!A43FBDAF9245A0BC!199.entry

http://www.stack.be/~roel/blog/archives/the-dojo-tree-control-for-beginners-part-1/

http://www.stack.be/~roel/blog/archives/the-dojo-tree-control-for-beginners-part-2

http://www.codepencil.com/index.php/struts2-dojo-dynamic-tree/

http://dojotoolkit.org/book/dojo-book-0-9/part-2-dijit/advanced-editing-and-display/tree

 

Technorati 标签: ,,,,

下一页 »