preface

In development, we often encounter: navigation menus, department menus, permission trees, comments, etc.

These features all have common features:

  1. Father and son
  2. It can recurse indefinitely

Let’s take the example of the navigation menu, which we set to be dynamic, that is, to load the menu data dynamically.

Database design

The design for database storage is as follows:

Menus' (' id 'int primary key auto_increment,' name 'varchar(20) comment' menu ', 'pid' int default 0 comment 'ID ',' order 'int comment' ID ')Copy the code

The front-end rendering

For the front end, we generally need this effect:

Menu configuration page:

Corresponding navigation menu:

Commonly used Tree display plug-ins are: JsTree, zTree, Layui Tree, Bootstrap Tree View, etc.

These plug-ins generally require these two formats:

Basic format:

[{" id ": 1," name ":" rights management ", "pid" : 0, "order" : 1}, {" id ": 2," name ":" user management ", "pid" : 1, the "order" : 2}, {" id ": 3, "name" : "the role management", "pid" : 1, the "order" : 3}, {" id ": 4," name ":" rights management ", "pid" : 1, the "order" : 4}]Copy the code

Tree format:

[{" id ": 1," name ":" rights management ", "pid" : 0, "order" : 1, "children" : [{" id ": 2," name ":" user management ", "pid" : 1, the "order" : 2, "children" : []}, {" id ": 3," name ":" role management ", "pid" : 1, the "order" : 3, "children" : []}, {" id ": 4," name ": "Rights management", "pid" : 1, "order" : 4, "children" : []}}]]Copy the code

Some plug-ins support both formats, while others only support tree structure, but our database query results are often common structure, we need to convert the common format to tree format.

This conversion is usually done on the server side (because front-end plug-ins mostly request a URL in the background to receive JSON data, and do not provide post-loading and pre-rendering events, the conversion cannot be done on the front end).

Data conversion

First, there are Java entity classes:

Public class Menu {private int id, private String name, private int pid // getterCopy the code

SQL > select * from List;

List<Menu> menus = xxxMapper.selectXXX();
Copy the code

We then need to convert the List to a tree structure by first defining a VO class with a tree structure:

public class MenuTreeVO { private int id, private String name, private int pid, private List<MenuVo> children, // getsetter omitted}Copy the code

Conversion utility class:

package im.zhaojun.util; import im.zhaojun.model.vo.MenuTreeVO; import java.util.ArrayList; import java.util.List; Private static List<MenuTreeVO> all = null; private static List<MenuTreeVO> all = null; Public static list <MenuTreeVO> toTree(list <MenuTreeVO> list) {// All = new ArrayList<>(list); List<MenuTreeVO> roots = new ArrayList<>(); for (MenuTreeVO menuTreeVO : list) { if (menuTreeVO.getParentId() == 0) { roots.add(menuTreeVO); }} // removeAll top-level menus from the "standby menu list" all.removeall (roots); for (MenuTreeVO menuTreeVO : roots) { menuTreeVO.setChildren(getCurrentNodeChildren(menuTreeVO));; } return roots; } /** * recursive function * recursive purpose: get the child node * recursive termination condition: No child node * @param parent Parent node * @return child node */ private static List<MenuTreeVO> getCurrentNodeChildren(MenuTreeVO parent) { // Check whether the current node has child nodes. If no, create an empty List. If yes, use all existing child nodes. List<MenuTreeVO> childList = parent.getChildren() == null ? new ArrayList<>() : parent.getChildren(); For (MenuTreeVO child: all) { if (parent.getMenuId().equals(child.getParentId())) { childList.add(child); }} // removeAll children of the current node from the "standby menu list" all.removeall (childList); // All the children find their own children. childList) { menuTreeVO.setChildren(getCurrentNodeChildren(menuTreeVO)); } return childList; }}Copy the code

Call method:

// Get List<Menu> Menus = xxxmapper.selectxxx (); // Menu to MenuTreeVO List<MenuTreeVO> menuTreeVOS = new ArrayList<>(); for (Menu menu : menus) { MenuTreeVO menuTreeVO = new MenuTreeVO(); BeanUtils.copyProperties(menu, menuTreeVO); menuTreeVOS.add(menuTreeVO); } // call the conversion method xxxutil.totree (menuTreeVOS); // return to foreground via Json or ModelAndView.Copy the code

Attached: template engine rendering

Sometimes we use a template engine to render menus, but because menus are tree-shaped, it is impossible to render infiniband menus using for in a template engine.

Here’s a novel approach, using the Thymeleaf engine as an example:

The navigation section of index.html:

<div class="left-nav">
    <div id="side-nav">
        <ul id="nav">
            <th:block th:include="public::menu(${menus})"/>
        </ul>
    </div>
</div>
Copy the code

Public.html Public template section:

<th:block th:fragment="menu(menus)"> <li th:each="menu:${menus}"> <a href="javascript:;" > < I class = "iconfont" >  < / I > < cite th: text = "${menu. MenuName}" > system management < cite > < I class = "iconfont nav_right" >  < / I > < / a > < ul class="sub-menu"> <li th:each="child:${menu.children}"> <a th:if="${#lists.isEmpty(child.children)}" Data - th - _href = "${child. Url}" _href = "users" > < I class = "iconfont" >  < / I > < cite th: text = "${child. MenuName}" > user management < cite > < / a >  <th:block th:unless="${#lists.isEmpty(child.children)}" th:include="this::menu(${child})" /> </li> </ul> </li> </th:block>Copy the code

The basic logic is to use include to refer to the template, which all template engines do, and then determine if the current node has children, and if so, the template file refers to itself to complete the recursion.

conclusion

The above code is the development of a Shiro permission management background while some ideas and code, the complete code can be seen: github.com/zhaojun1998…