2011/6/26 功能菜单模块分析
1.Model层 MenuPojo [JavaBean]
public class MenuPojo { public final static String[] STATE_NAME = { "无效", "有效" }; public final static String[] CLASS_NAME = { "功能组", "普通功能" }; public final static String[] ISOPEN_NAME = { "关闭", "打开" }; public final static String[] TOPFLAG_NAME = { "不可以", "可以" }; private String menuId = "";// 菜单ID private String appName = "";// 应用ID private String menuName = "";// 菜单项名称 private String menuDes = "";// 菜单项描述 private int menuClass = 1;// 菜单组 private StaticPojo menuType = new StaticPojo();// 类型 private int state = 1;// 状态 private String jsfun = "";// JS方法 private String imgName = "";// 图标路径 private int isOpen = 1;// 是否打开 private String pmenuId = "";// 上级菜单id private String pmenuName = "无";// 上级菜单名称 private int chlMenuNum = -1;// 下级菜单数量 private String toPage = "";// 去向页面 private int topFlag = 0;// 是否可放置在top菜单里;0:不可;1:可以 private int seq = 0; private int count = 0;// 下级数量 .....getters and setters...... }
2.数据库层 BSMenuDBMang
下文再谈,呵呵呵
3.表现层 BSMenu
(1)功能菜单查看的初始化页面:
public BSObject do_MenuIni(BSObject m_bs) throws Exception { m_bs.setCurPage("system/menu/index.jsp"); SqlExecute sqlHelper = new SqlExecute(); try { // 设置功能菜单树 this._setMenuTree(m_bs, sqlHelper); } catch (Exception ep) { ep.printStackTrace(); throw ep; } finally { sqlHelper.close(); } return m_bs; }
注意点:项目统一命名规范,进入页面的第一个初始化方法的do_之后的命名是首字母大写,内部自己定义的方法以下划线开头,其他的方法do_之后是以小写字母开头
其中调用的方法 _setMenuTree
// 设置角色和权限关系树 public void _setMenuTree(BSObject m_bs, SqlExecute sqlHelper) throws Exception { // 设置组织机构数 VBSTree tree = null; VBSTreeNode rootnode = null; tree = new VBSTree(); tree.setName("MenuTree"); // 设置右键 String tpath = m_bs.getRequest().getContextPath() + "/common/images/toolbar/"; tree.setRightmenu(new VBSRightMenu()); VBSRightMenuArea rma = tree.getRightmenu().addMenuAreaMenu("菜单操作"); VBSRightMenuItem item = rma.addItem("添加功能", -1); item.setImg(tpath + "add.gif"); item.setName("add"); item.setJsFun("addMenu()"); // 根
rootnode = tree.addRootNode(); rootnode.set("root", "功能菜单", "", ""); rootnode.setIsOpen(true); rootnode.setRmAreaIndex(0); // 得到第一层 BSMenuDBMang menuDB = new BSMenuDBMang(sqlHelper, m_bs); ArrayList<MenuPojo> thisNodes = menuDB .getMenuList(" and t.MENU_ID not in (select MENU_ID from T_MENU_R)"); this._setTreeNode(thisNodes, rootnode, menuDB); m_bs.setTagValue(tree); }
从这里可以学会如何使用 Tree控件 和 RightMenu 控件,界面的显示如下
上面的方法中调用了一个递归的方法,用于生成树形菜单
// 递归得到下级树 private void _setTreeNode(ArrayList<MenuPojo> thisNodes, VBSTreeNode pNode, BSMenuDBMang menuDB) throws Exception { VBSTreeNode node = null; MenuPojo oneMenu = null; for (int i = 0, size = thisNodes.size(); i < size; i++) { oneMenu = thisNodes.get(i); //递归得到下级功能菜单 node = pNode.addNode(); node.set(oneMenu.getMenuId(), oneMenu.getMenuName(), "editMenu()", ""); node.setRmAreaIndex(0); node.setTitle(oneMenu.getMenuDes()); // 是否有下级菜单 if (oneMenu.getCount() > 0) { node.setIsOpen(true); ArrayList<MenuPojo> subNodeList = menuDB .getMenuList(" and t.MENU_ID in (select MENU_ID from T_MENU_R where P_MENU_ID='" + node.getName() + "')"); this._setTreeNode(subNodeList, node, menuDB); } } }
(2)功能菜单编辑的初始化页面:
public BSObject do_MenuEditIni(BSObject m_bs) throws Exception { m_bs.setCurPage("system/menu/edit.jsp"); String type = (String) m_bs.getPrivateMap().get("in_type"); String menuId = (String) m_bs.getPrivateMap().get("in_menuid"); // 得到功能树 SqlExecute sqlHelper = new SqlExecute(); try { // 得到一个菜单项 MenuPojo onePojo = new MenuPojo(); if (type != null && type.trim().equals("edit")) { // 编辑菜单 BSMenuDBMang menuDB = new BSMenuDBMang(sqlHelper, m_bs); onePojo = menuDB.getOneMenu(" and t.MENU_ID='" + menuId + "'"); } // 网页面上放置 this._setMenuToWeb(m_bs, onePojo, sqlHelper); } catch (Exception ep) { ep.printStackTrace(); throw ep; } finally { sqlHelper.close(); } return m_bs; }
这里调用了一个很重要的方法,就是将从数据库中得到的一个menu(onepojo)设置到web页面上去,也就是
// 设置编辑页面 private void _setMenuToWeb(BSObject m_bs, MenuPojo onePojo, SqlExecute sqlHelper) { m_bs.setPrivateValue("t_menuid", onePojo.getMenuId()); m_bs.setPrivateValue("t_pmenuid", onePojo.getPmenuId()); m_bs.setPrivateValue("l_pmenuname", onePojo.getPmenuName()); m_bs.setPrivateValue("t_menuname", onePojo.getMenuName()); m_bs.setPrivateValue("t_menudesc", onePojo.getMenuDes()); m_bs.setPrivateValue("t_menujsfun", onePojo.getJsfun()); m_bs.setPrivateValue("t_menuimg", onePojo.getImgName()); m_bs.setPrivateValue("t_menutopage", onePojo.getToPage()); m_bs.setPrivateValue("t_menutseq", String.valueOf(onePojo.getSeq())); m_bs.setPrivateValue("t_menuclass", String.valueOf(onePojo.getMenuClass())); // m_bs.setPrivateValue("t_menutype", String // .valueOf(onePojo.getMenuType())); m_bs.setPrivateValue("t_menustate", String.valueOf(onePojo.getState())); m_bs.setPrivateValue("t_menuisopen", String.valueOf(onePojo.getIsOpen())); m_bs.setPrivateValue("t_menutop", String.valueOf(onePojo.getTopFlag())); // 下拉框 VBSSelect t_menuclass = new VBSSelect(); t_menuclass.setName("t_menuclass"); t_menuclass.setOption("1", "普通功能", 0); t_menuclass.setOption("0", "功能组", 0); m_bs.setTagValue(t_menuclass); VBSSelect t_menustate = new VBSSelect(); t_menustate.setName("t_menustate"); t_menustate.setOption("0", "无效", 0); t_menustate.setOption("1", "有效", 0); m_bs.setTagValue(t_menustate); VBSSelect t_menuisopen = new VBSSelect(); t_menuisopen.setName("t_menuisopen"); t_menuisopen.setOption("0", "关闭", 0); t_menuisopen.setOption("1", "打开", 0); m_bs.setTagValue(t_menuisopen); VBSSelect t_menutop = new VBSSelect(); t_menutop.setName("t_menutop"); t_menutop.setOption("0", "不可做快捷方式", 0); t_menutop.setOption("1", "可做快捷方式", 0); m_bs.setTagValue(t_menutop); }
这里的主要操作就是将onepojo里保存的数据放入到页面相应的显示控件中
例如:edit.jsp中的
<BS:text name="t_menuname" style="100%;" />
这个text控件就是用于存放功能菜单的名称的,后台通过m_bs.setPrivateValue("t_menuname", onePojo.getMenuName()); 来赋值给它
类似的还有其他的一些控件,如:
<BS:textarea rows="3" cols="" value="" name="t_menudesc" style="100%;" />
<BS:select name="t_menuclass" style="100%;" /> //这个是菜单组,菜单是有分组的
(3)提交编辑结果
public BSObject do_commitMenu(BSObject m_bs) throws Exception { String retStr = "操作失败,请稍后再试"; String type = (String) m_bs.getPrivateMap().get("in_type"); VBSTree MenuTree = (VBSTree) m_bs.getTagMap().get("MenuTree"); if (MenuTree != null) { // 得到一个菜单项 MenuPojo onePojo = this._getMenuFormWeb(m_bs); int count = 0; VBSTreeNode node = null; SqlExecute sqlHelper = new SqlExecute(); try { sqlHelper.setAutoCommit(false); BSMenuDBMang menuDB = new BSMenuDBMang(sqlHelper, m_bs); // 得到一个菜单项 if (type != null && type.trim().equals("edit")) { // 保存菜单 count = menuDB.updateMenu(onePojo); node = MenuTree.getNodeByName(onePojo.getMenuId()); if (onePojo != null) { node.set(onePojo.getMenuId(), onePojo.getMenuName(), "editMenu()", ""); node.setRmAreaIndex(0); node.setUpdateFlag(true); MenuTree.getReFreshNodeList().add(node); } } else { // 新增 count = menuDB.insertMenu(onePojo); if (!onePojo.getPmenuId().trim().equals("")) { node = MenuTree.getNodeByName(onePojo.getPmenuId()) .addNode(); } else { node = MenuTree.getRootNode().addNode(); } if (onePojo != null) { node.set(onePojo.getMenuId(), onePojo.getMenuName(), "editMenu()", ""); node.setRmAreaIndex(0); MenuTree.getReFreshNodeList().add(node); } } if (count > 0) { retStr = "T"; m_bs.setTagValue(MenuTree); } else { retStr = "没有更新任何数据!"; } sqlHelper.commit(); } catch (Exception ep) { sqlHelper.rollback(); ep.printStackTrace(); throw ep; } } m_bs.setRetrunObj(retStr); return m_bs; }
提交这里会去获取编辑的那些项的内容,但是有可能是空的或者不合法,所以还要有一个方法来进行判断和解析那些编辑项
private MenuPojo _getMenuFormWeb(BSObject m_bs) { MenuPojo onePojo = new MenuPojo(); if (m_bs.getPrivateMap().get("t_menuid") != null) { onePojo.setMenuId((String) m_bs.getPrivateMap().get("t_menuid")); } if (m_bs.getPrivateMap().get("t_pmenuid") != null) { onePojo.setPmenuId((String) m_bs.getPrivateMap().get("t_pmenuid")); } if (m_bs.getPrivateMap().get("t_menuname") != null) { onePojo.setMenuName((String) m_bs.getPrivateMap().get("t_menuname")); } if (m_bs.getPrivateMap().get("t_menudesc") != null) { onePojo.setMenuDes((String) m_bs.getPrivateMap().get("t_menudesc")); } if (m_bs.getPrivateMap().get("t_menujsfun") != null) { onePojo.setJsfun((String) m_bs.getPrivateMap().get("t_menujsfun")); } if (m_bs.getPrivateMap().get("t_menuimg") != null) { onePojo.setImgName((String) m_bs.getPrivateMap().get("t_menuimg")); } if (m_bs.getPrivateMap().get("t_menutopage") != null) { onePojo.setToPage((String) m_bs.getPrivateMap().get("t_menutopage")); } if (m_bs.getPrivateMap().get("t_menutseq") != null) { onePojo.setSeq(Integer.parseInt((String) m_bs.getPrivateMap().get( "t_menutseq"))); } if (m_bs.getPrivateMap().get("t_menustate") != null) { onePojo.setState(Integer.parseInt((String) m_bs.getPrivateMap() .get("t_menustate"))); } if (m_bs.getPrivateMap().get("t_menuclass") != null) { onePojo.setMenuClass(Integer.parseInt((String) m_bs.getPrivateMap() .get("t_menuclass"))); } if (m_bs.getPrivateMap().get("t_menuisopen") != null) { onePojo.setIsOpen(Integer.parseInt((String) m_bs.getPrivateMap() .get("t_menuisopen"))); } if (m_bs.getPrivateMap().get("t_menutop") != null) { onePojo.setTopFlag(Integer.parseInt((String) m_bs.getPrivateMap() .get("t_menutop"))); } return onePojo; }
(4)重新显示树形结构
public BSObject do_reShowMenu(BSObject m_bs) throws Exception { m_bs.addRefreshTag("MenuTree"); return m_bs; }
只要将要重新显示的控件添加到m_bs中就可以了
4.页面的分析
Index.jsp 这个是功能菜单管理的首页
以下是首页的重要的js
<script type="text/javascript" language="javascript"> var retObj = {}; var fromObj = thisDlg.inObj; function iniPage() { thisDlg.endFun(); } function addMenu() { var node = MenuTree.getSelectNode(); if (node != null) { var tObj = {}; tObj.type = "new"; tObj.pid = node.name; tObj.pname = node.showStr; if (node.id <= 0) { tObj.pid = ""; } p.openParentDlg("MENU_EDIT", "新建功能菜单", 550, 440, tObj, "MENU", "MenuEditIni", "&in_type=" + tObj.type, window, true, true, "editMenuRet", ""); } else { alert("请选择一个功能!"); } } function editMenu() { var node = MenuTree.getSelectNode(); if (node != null) { var tObj = {}; tObj.type = "edit"; tObj.id = node.name; p.openParentDlg("MENU_EDIT", "编辑功能菜单", 550, 440, tObj, "MENU", "MenuEditIni", "&in_type=" + tObj.type + "&in_menuid=" + tObj.id, window, true, true, "editMenuRet", ""); } else { alert("请选择一个功能!"); } } function editMenuRet(r_obj, i_obj) { if (r_obj == "T") { doRefresh("MENU", "reShowMenu", true, "", "", ""); } } </script>
这里的 retObj 就是返回的对象,fromObj 是 来源对象,它是当前的 thisDlg 的 inObj,这里通过给这个对象设置一些值可以使得打开的dialog能够得到这些值,并进行相应的操作
var retObj = {}; var fromObj = thisDlg.inObj;
iniPage方法:thisDlg.endFun(); 这里不理解是什么意思,后文有介绍,主要就是表明打开了新的子窗体
addMenu方法 和 editMenu方法:两者的区别在前者只需要从前面得到操作类型是new还有选中的menu的id[新建一个功能菜单时需要知道该功能菜单的pid],而后者除了需要操作类型是edit还需要选中的的menu的id[修改一个功能菜单需要知道修改的功能菜单的id]
打开一个新的窗口来进行添加和编辑功能菜单
由于功能菜单是多对多的关系,存在着上下级,属于树形结构,所以选择用Tree控件来显示它,如果选择了某项来进行操作,就要有一个node对象,当他不为空时,就可以得到它的
相关信息,再传给打开的窗口
addMenu方法:
p.openParentDlg("MENU_EDIT", "新建功能菜单", 550, 440, tObj, "MENU",
"MenuEditIni", "&in_type=" + tObj.type, window, true, true,
"editMenuRet", "");
editMenu方法:
p.openParentDlg("MENU_EDIT", "编辑功能菜单", 550, 440, tObj, "MENU",
"MenuEditIni", "&in_type=" + tObj.type + "&in_menuid="
+ tObj.id, window, true, true, "editMenuRet", "");
openParentDlg 这个方法来源于 index.js,定义如下:从下面可以看出各个参数分别是什么
这个方法实际上就是根据特定的用户,通过该用户的选择进一步封装一些参数,然后将这些参数传给parentDlg.openDlg()方法,生成另一个窗体来进行下一步的操作,例如添加
或者编辑,最后还是调用了doCommit方法来提交操作,也就是得到了一个新的需要的窗口
function openParentDlg(id, title, width, height, inObj, bsID, fun, paras, cWinName, isMT, isResize, retFun, img, index) { if (isMT) { isResize = false; } else { if (isResize != null && !isResize) { isResize = false; } else { isResize = true; } } // parentDlg.show(); var exStr1 = ":" + $F("pub_tusername"); var exStr2 = ":" + $F("pub_torgname"); var exStr3 = ""; var newDlg = parentDlg.openDlg(parentDlg.name + "_" + id, title, 0, 0, width, height, isMT, isResize, retFun, img, index, exStr1, exStr2, exStr3, true); if (newDlg != null) { newDlg.inObj = inObj; newDlg.win = cWinName; newDlg.startFun(); if (retFun != null && retFun != "") { newDlg.retFun = retFun; } newDlg.returnObj = null; frmBusiness.target = newDlg.target; doCommit(bsID, fun, paras); } }
注意这里的红色部分,后文解释
其中的 retFun 就是在新打开的窗体关闭后调用的方法(回调函数),首先判断用户是否进行了操作,如果没有进行任何必要的操作,那么就不用刷新,反之就要调用deRefresh来进行零刷新操作
function editMenuRet(r_obj, i_obj) { if (r_obj == "T") { doRefresh("MENU", "reShowMenu", true, "", "", ""); } }
首页 index.jsp 的界面就非常的简单,就是一个树形结构的对象<BS:tree ….. /> ,name属性很重要,后台就是通过这个name来进行赋值的(或者给这个树添加节点的)
<body scroll="no" onload="iniPage();"> <form method="POST" name="frmBusiness" action=""> <table style=" 100%; height: 100%;" align="center" class="table_d" border="0" cellpadding="0" cellspacing="0"> <tr> <td style=" 100%; height: 100%;"> <div style=" 100%; height: 100%; overflow: auto"> <BS:tree name="MenuTree" imagesPath="bsweb/images/tree/" canDrag="false" /> </div> </td> </tr> </table> </form> </body>
然后就是编辑页面 edit.jsp
<script type="text/javascript" language="javascript"> var fObj = thisDlg.inObj; var val = new BSValidate(); function iniPage() { if (fObj.type == "new") { $("t_pmenuid").value = fObj.pid; $("p_l_pmenuname").innerHTML = fObj.pname; } thisDlg.endFun(); val.add("t_menuname", "菜单项名称"); val.add("t_menutseq", "序号"); val.add("t_menutseq", "序号", 1); } function MenuCommit() { if (val.doValidate() && confirm("是否提交数据?")) { doRefresh("MENU", "commitMenu", true, "", "&in_type=" + fObj.type, "MenuCommitRet"); } } function MenuCommitRet(retObj, data) { if (data == "T") { thisDlg.returnObj = data; thisDlg.closeDlg(); } else { alert(data); } } </script>
这里的相关解释:fObj是来源obj,它就是父窗体中的fromObj,里面保存了一些数据,(这里是功能菜单的父亲编号pid和父菜单的名称,前者设置为当前页面的一个元素的value,后者设置为一个label的innerHTML),同时还有一个最重要的数据就是 type,当type是 new 时,就表明是新建一个功能菜单
回到上面的红色部分,newDlg.inObj = inObj; 这句话很重要,这句话的意思就是说,将父窗体的inObj(这个是Dialog的一个属性)传给了新建的子窗体
对照一下,inde.jsp的js中
function addMenu() { var node = MenuTree.getSelectNode(); if (node != null) { var tObj = {}; tObj.type = "new"; tObj.pid = node.name; tObj.pname = node.showStr; if (node.id <= 0) { tObj.pid = ""; } p.openParentDlg("MENU_EDIT", "新建功能菜单", 550, 440, tObj, "MENU", "MenuEditIni", "&in_type=" + tObj.type, window, true, true, "editMenuRet", ""); } else { alert("请选择一个功能!"); } }
edit.jsp的js中
var fObj = thisDlg.inObj; function iniPage() { if (fObj.type == "new") { $("t_pmenuid").value = fObj.pid; $("p_l_pmenuname").innerHTML = fObj.pname; } thisDlg.endFun(); val.add("t_menuname", "菜单项名称"); val.add("t_menutseq", "序号"); val.add("t_menutseq", "序号", 1); }
val 是 一个 BSValidate 对象,来源于 /bsweb/js/bsvalidate.js,用于对各种数据进行验证,对于需要验证的对象只要调用val.add(…)方法就可以了
val.add("t_menutseq", "序号"); val.add("t_menutseq", "序号", 1); 前者是验证序号是否是空的,而后者是验证序号是否是只包含数字