出家如初,成佛有余

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

Posted in Uncategorized by chuanliang on 2008/04/03

  在项目中,有大量的诸如“产品类型”这样的下拉选择框,为了保证项目后期的可维护性,除了相对固化的类别可以在代码中直接写死外,这些下拉选择框应当尽量从系统的数据字典表中进行动态读取和展现。但在展现时候存在一个问题,如果分类存在多级时候,一种方案是采用几个下拉框进行级联操作,也即所谓的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 标签: ,,,
Tagged with: , , ,

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: