在WEB开发中经常需要进行树形菜单的展示,本例通过不同角度的总结了如下三种实现方式:
- 通过JS的递归实现前端菜单DOM的动态创建
- 通过JSP的include指令结合JSTL表达式语言递归实现菜单的展示
- 通过扩展JSP的标签在后端实现菜单的DOM节点并响应给前端展示
针对第一种方法,可以采用JS的相关组件,或者使用JS的递归调用将服务端相应的数据组装成DOM节点内容,动态添加到菜单的Container中,网上的例子较多,在此不再赘述,本例就后两种方案进行讲解。
通过JSP的include指令结合JSTL表达式语言递归实现菜单的展示
由于JSP中的JSTL不支持递归,又不想在JSP中加入Java脚本,可以采用JSP动态包含的方式组装菜单,当前方案需要服务端将菜单列表组装成树形结构,具体算法如下:
主页面menu.jsp
1 <ul class="nav nav-list">
2 <c:if test="${not empty sessionScope.SESSION_MENUS}">
3 <c:forEach items="${sessionScope.SESSION_MENUS }" var="menu">
4 <%@include file="recursiveMenu.jsp"%>
5 </c:forEach>
6 </c:if>
7 </ul><!-- /.nav-list -->
递归调用页面recursiveMenu.jsp
1 <%@ page language="java" import="java.util.*" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
2 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3 <li>
4 <c:choose>
5 <c:when test="${empty menu.children }">
6 <a href="${menu.url }">
7 <i class="${menu.icon }"></i>
8 <span class="menu-text"> ${menu.name } </span>
9 </a>
10 </c:when>
11 <c:otherwise>
12 <a href="javascript:void(0);" class="dropdown-toggle">
13 <i class="${menu.icon }"></i>
14 <span class="menu-text"> ${menu.name } </span>
15 <b class="arrow icon-angle-down"></b>
16 <ul class="submenu">
17 <c:forEach items="${menu.children }" var="menu">
18 <c:set var="menu" value="${menu}" scope="request"/>
19 <jsp:include page="recursiveMenu.jsp"/>
20 </c:forEach>
21 </ul>
22 </a>
23 </c:otherwise>
24 </c:choose>
25 </li>
通过扩展JSP的标签在后端实现菜单的DOM节点并相应给前端展示
当前方案采用扩展JSP标签的方式实现具体实现方法如下
1、新建标签实现类MenuTa.java
1 package com.luwei.console.mg.tag;
2
3 import java.io.IOException;
4 import java.util.List;
5
6 import javax.servlet.jsp.JspException;
7 import javax.servlet.jsp.tagext.TagSupport;
8
9 import org.apache.commons.lang.StringUtils;
10 import org.slf4j.Logger;
11 import org.slf4j.LoggerFactory;
12
13 import com.luwei.console.mg.constant.KeyConst;
14 import com.luwei.console.mg.entity.ext.MenuExt;
15
16 /**
17 * <Description> 菜单标签<br>
18 *
19 * @author lu.wei<br>
20 * @email 1025742048@qq.com <br>
21 * @date 2016年12月25日 <br>
22 * @since V1.0<br>
23 * @see com.luwei.console.mg.tag <br>
24 */
25 public class MenuTag extends TagSupport {
26 private Logger logger = LoggerFactory.getLogger(this.getClass());
27
28 /**
29 * serialVersionUID <br>
30 */
31 private static final long serialVersionUID = -2755997672501044414L;
32
33 @SuppressWarnings("unchecked")
34 @Override
35 public int doStartTag() throws JspException {
36 Object menusObj = pageContext.getSession().getAttribute(KeyConst.KEY_SESSION_MENUS);
37 if (null != menusObj) {
38 List<MenuExt> menus = (List<MenuExt>) menusObj;
39 String menuStr = assumbleMenuHtml(menus);
40 try {
41 pageContext.getOut().println(menuStr);
42 }
43 catch (IOException e) {
44 logger.error("菜单标签创建菜单出错:{} ", e.getMessage(), e);
45 }
46 }
47 return super.doStartTag();
48 }
49
50 /**
51 * <Description> TODO<br>
52 *
53 * @author lu.wei<br>
54 * @email 1025742048@qq.com <br>
55 * @date 2016年12月25日 上午9:29:26 <br>
56 * @param menus
57 * @return <br>
58 */
59 private String assumbleMenuHtml(List<MenuExt> menus) {
60 StringBuffer sb = new StringBuffer("");
61
62 if (!menus.isEmpty()) {
63 long activeMenuId = 1;
64 Object avtiveMenuIdObj = pageContext.getSession().getAttribute(KeyConst.SESSION_ACTIVE_MENU_ID);
65 if (null != avtiveMenuIdObj) {
66 activeMenuId = (Long) avtiveMenuIdObj;
67 }
68 for (MenuExt menu : menus) {
69
70 if (menu.getId() == activeMenuId) {
71 sb.append("<li class='active'>");
72 }
73 else {
74 sb.append("<li>");
75 }
76
77 boolean hasChild = false;
78 if (null != menu.getChildren() && !menu.getChildren().isEmpty()) {
79 hasChild = true;
80 }
81 if (hasChild) {
82 sb.append("<a href='#' class='dropdown-toggle'> ");
83 }
84 else {
85 String url = menu.getUrl();
86 if (StringUtils.isNotEmpty(url)) {
87 if (url.contains("?")) {
88 url = (url + "&mid=" + menu.getId());
89 }
90 else {
91 url = (url + "?mid=" + menu.getId());
92 }
93 }
94 sb.append("<a href='").append(url).append("'>");
95 }
96
97 if (StringUtils.isNotEmpty(menu.getIcon())) {
98 sb.append("<i class='").append(menu.getIcon()).append("'></i>");
99 }
100 sb.append("<span class='menu-text'>").append(menu.getName()).append("</span>");
101 if (hasChild) {
102 sb.append("<b class='arrow icon-angle-down'></b>");
103 }
104 sb.append("</a>");
105 if (hasChild) {
106 sb.append("<ul class='submenu'>").append(assumbleMenuHtml(menu.getChildren())).append("</ul>");
107 }
108 sb.append("</li>");
109 }
110 }
111 return sb.toString();
112 }
113 }
2、配置标签
在项目的WEB-INFO目录下新建文件treeMenuAssum.tld
1 <?xml version="1.0" encoding="UTF-8" ?>
2 <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
3 " http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
4 <taglib>
5 <tlibversion>1.0</tlibversion>
6 <jspversion>1.2</jspversion>
7 <shortname>menu</shortname>
8 <uri>http://www.luwei.com/web/ext/menus</uri>
9 <tag>
10 <name>menu</name>
11 <tagclass>com.luwei.console.mg.tag.MenuTag</tagclass>
12 </tag>
13 </taglib>
3、使用菜单标签
在JSP页面中直接使用新建标签
<%@ taglib prefix="menu" uri="http://www.luwei.com/web/ext/menus" %>
<menu:menu/>
总结
以上三种方式均能够实现后端服务的配置管理的无限树形菜单在前端显示出来,第一种方法服务端只需要将数据组装成前端需要的格式相应给前端,前端通过空间或者递归的方式动态创建DOM节点就可以了;二后两种方式采用纯Java相关的知识完全由服务端生成树形菜单,服务端会有一定的负载,同时采用JSP动态包含的方式,如果是在菜单中存在复杂逻辑的话不容易实现,因此推荐使用第一和第三中方案。
具体采用哪种方式可以根据各自项目进行选择,但是实现原理都是相同的。