[Web] 建立樹狀選單


Posted by mike-hsieh on 2023-10-19

本文網址: https://mike.coderbridge.io/2023/10/19/how-to-create-tree-menu-in-javascript/

Web中最最基本的架構就是[樹狀選單],以現在前端框架氾濫的年代,多數都已經有Tree的Component,而最需要的就是組選單的部分。

有些專案可能用寫死的,但是基本上都需要從資料庫取資料,且有些還有複雜的權限機制。

以下就簡單紀錄一個選單的樹狀選單實作,附上jsfiddle傳送門: 連結

1. 建立假資料

// [全域變數] 假資料
const mockData = [
    { id: '1', parentId : '0', label: '基本功能', url: 'homepage/index' },
    { id: '2', parentId : '1', label: '使用者管理', url: 'subpage1/index' },
    { id: '3', parentId : '1', label: '權限管理', url: 'subpage2/index' },
    { id: '4', parentId : '0', label: '進階功能', url: 'homepage/index' },
    { id: '5', parentId : '4', label: '債務試算', url: 'subpage1/index' },
    { id: '6', parentId : '4', label: '報表', url: 'subpage2/index' },
    { id: '7', parentId : '6', label: '列印', url: 'subpage2/index' },
];

2. 建立 [全域變數] Menu

//  [全域變數] Menu
let globalMenuList = [];

3. 建立 產生選單的方法

/* [方法] 建立Menu */
function buildMenuTree(flatData, includeRoot = true) {
    // 定義根節點
    const root = {
        label: '目錄清單',
        id: 0,
        parentId: null,
        children: []
    };

    // 創建一個物件,以id為鍵來存放每一個節點,方便後續直接查找節點
    const nodesById = {};

    // 一次遍歷來構建nodesById和初始化節點
    flatData.forEach(item => {
        nodesById[item.id] = {
            ...item,
            // 這邊可以客製化自己的屬性
            routUrl: `Controller/${item.url}`,
            children: []
        };
    });

    // 遍歷所有節點,將它們放到適當的父節點或根節點下
    flatData.forEach(item => {
        const node = nodesById[item.id];

        if (item.parentId === '0') {
            root.children.push(node);
        } else if (nodesById[item.parentId]) {
            nodesById[item.parentId].children.push(node);
        }
    });

    // 若includeRoot為true,回傳包含根節點的陣列;否則僅回傳根節點的子節點
    return includeRoot ? [root] : root.children;
}

4. 建立 產生HTML清單的方法

// [方法] 遞迴函數來建立HTML清單
function createMenuList(items) {
  const ul = document.createElement('ul');

  items.forEach(item => {
    const li = document.createElement('li');
    const a = document.createElement('a');
    a.href = item.url;
    a.textContent = item.label;
    li.appendChild(a);

    if (item.children && item.children.length > 0) {
      const childUl = createMenuList(item.children);
      li.appendChild(childUl);
    }

    ul.appendChild(li);
  });

  return ul;
}

6. 正式產生HTML清單

// 取得不包含 根結點 的選單
globalMenuList = buildMenuTree(mockData, false)
// 取得不包含 根結點 的選單
globalMenuList = buildMenuTree(mockData, true)


// 顯示Html結果
document.getElementById('menu').appendChild(createMenuList(globalMenuList));

7. 樹狀結構資料

[
  {
    "children": [
      {
        "children": [],
        "id": "2",
        "label": "使用者管理",
        "parentId": "1",
        "url": "subpage1/index"
      },
      {
        "children": [],
        "id": "3",
        "label": "權限管理",
        "parentId": "1",
        "url": "subpage2/index"
      }
    ],
    "id": "1",
    "label": "基本功能",
    "parentId": "0",
    "url": "homepage/index"
  },
  {
    "children": [
      {
        "children": [],
        "id": "5",
        "label": "債務試算",
        "parentId": "4",
        "url": "subpage1/index"
      },
      {
        "children": [
          {
            "children": [],
            "id": "7",
            "label": "列印",
            "parentId": "6",
            "url": "subpage2/index"
          }
        ],
        "id": "6",
        "label": "報表",
        "parentId": "4",
        "url": "subpage2/index"
      }
    ],
    "id": "4",
    "label": "進階功能",
    "parentId": "0",
    "url": "homepage/index"
  }
]


#javascript #tree #Menu







Related Posts

一些後端相關的名詞

一些後端相關的名詞

希望是最淺顯易懂的 RxJS 教學

希望是最淺顯易懂的 RxJS 教學

MTR04_1001

MTR04_1001


Comments