早期 Web 開發常見需求為「點擊大分類展開/收合子分類」的滑動選單。本文展示如何以 PHP 產出 JavaScript 程式碼,動態建構具備互動行為的樹狀選單,並解析其事件委派與 DOM 操作機制。
程式分為三大區塊:
<script for=... event=onclick> 區塊(IE 專屬語法),分別處理大分類(pad)與子分類(bar)的點擊事件,切換顯示狀態並更新預覽區內容。menu(n):以 JavaScript 陣列定義選單結構(大分類代號 0,子分類代號 >0),迴圈產出巢狀 HTML 表格,並將分類代號寫入自訂屬性 v1、v2 供事件處理器讀取。<style>
.menu-table { font-size: 9pt; border-collapse: collapse; width: 120px; }
.menu-table td { height: 20px; cursor: pointer; }
.category-header { background: #d0d0d0; font-weight: bold; }
.sub-item { background: #f0f0f0; }
</style>
<div id="menus"></div>
<div id="view"></div>
<script>
// 選單資料結構:[大分類ID, 子分類ID, 名稱]
const menuData = [
[1, 0, '大類一'], [1, 1, '小類一'], [1, 2, '小類二'],
[2, 0, '大類二'], [2, 1, '小類一'], [2, 2, '小類二'], [2, 3, '小類三'],
[3, 0, '大類三'], [3, 1, '小類一'], [3, 2, '小類二'],
[4, 0, '大類四'], [4, 1, '小類一'], [4, 2, '小類二'], [4, 3, '小類三']
];
function buildMenu(defaultOpen = 4) {
let html = '<table class="menu-table">';
let currentMainId = null;
let subTableOpen = false;
menuData.forEach(([mainId, subId, name]) => {
if (subId === 0) { // 大分類
if (subTableOpen) html += '</table></td></tr>';
const isOpen = mainId === defaultOpen ? 'block' : 'none';
html += `<tr class="category-header" data-main="${mainId}"><td>${name}</td></tr>`;
html += `<tr style="display:${isOpen}"><td><table class="menu-table" width="100%">`;
currentMainId = mainId;
subTableOpen = true;
} else { // 子分類
html += `<tr class="sub-item" data-main="${mainId}" data-sub="${subId}"><td>${name}</td></tr>`;
}
});
html += '</table></td></tr></table>';
return html;
}
// 事件委派:單一監聽器處理所有點擊
document.getElementById('menus').addEventListener('click', e => {
const row = e.target.closest('tr');
if (!row) return;
if (row.classList.contains('category-header')) {
// 大分類:切換下一列顯示/隱藏,並隱藏其他同層子選單
document.querySelectorAll('#menus tr[style*="display: block"]').forEach(el => {
if (el !== row.nextElementSibling) el.style.display = 'none';
});
row.nextElementSibling.style.display =
row.nextElementSibling.style.display === 'none' ? 'block' : 'none';
document.getElementById('view').textContent = `${row.dataset.main}:0`;
} else if (row.classList.contains('sub-item')) {
// 子分類:更新預覽區
document.getElementById('view').textContent =
`${row.dataset.main}:${row.dataset.sub}`;
}
});
// 初始化
document.getElementById('menus').innerHTML = buildMenu(4);
</script>
| 原版寫法 | 現代重構 | 改進說明 |
|---|---|---|
script for=pad event=onclick (IE 專屬) | addEventListener + 事件委派 | 相容所有現代瀏覽器,效能更佳 |
自訂屬性 v1, v2 | HTML5 data-* 屬性 | 符合標準,可透過 dataset 存取 |
| 字串拼接產生 HTML | 模板字面量 + 陣列迭代 | 可讀性高,易維護 |
| 內嵌 style 控制顯示 | CSS 類別切換 | 關注點分離,樣式集中管理 |
此範例展示了「伺服器端語言產出客戶端邏輯」的典型模式。雖原始碼使用已淘汰的 IE 專屬語法,但其「資料驅動視圖、事件委派處理互動」的核心思想仍具參考價值。現代開發建議改用 Vue/React 等框架,或以原生 ES6+ 重構,獲得更佳的維護性與跨瀏覽器相容性。