在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動態包含的方式,如果是在菜單中存在復雜邏輯的話不容易實現,因此推薦使用第一和第三中方案。
具體采用哪種方式可以根據各自項目進行選擇,但是實現原理都是相同的。