iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
JavaScript

解決學了JavaScript不知道能做什麼:用DOM操作打造實用網頁效果 系列 第 17 篇

【第十七課】表單與輸入:多層級聯動下拉選單(change 事件與動態選單渲染)

  • 分享至 

  • xImage
  •  

前言

在填寫寄送地址或商品分類搜尋時,「選了縣市後,行政區選單才會跟著更新」是不可或缺的互動功能。如果沒有做好層級連動,不僅使用者體驗差,還容易讓使用者選出「台北市西屯區」這種邏輯不通的無效資料。

核心觀念與語法拆解

  1. change 事件監聽:捕捉 <select> 值的變更,搭配 Key-Value 結構的資料物件 (districtData[cityKey]) 快速撈出對應的子集陣列。
  2. 動態節點重置與啟用控制:每次父層選單變更時,先清空子層的舊 <option>,接著透過 createElement 重建選項,並靈活運用 disabled 屬性控制選單的可用狀態。

觀念與函式深度解析

1. change 事件 vs 資料結構驅動 (Tree Data Structure)

  • 為什麼選用 change 事件?

    下拉選單(<select>)只有在使用者切換並選取了新的選項(<option>)時,才會觸發 change 事件。這與文字輸入框用的 input 事件不同,非常適合用來捕捉選單層級的切換動作。

  • 這個機制在幹嘛?

    當第一層選單改變時,以第一層選取的 value 作為 Key,去查尋樹狀物件資料(JSON / Object),取出對應的陣列並動態重構第二層選單的 <option>。

2. 動態選單重置與 innerHTML = ""

  • 為什麼需要重置?

    每次第一層選單切換時,第二層選單必須先「清空舊選項」,再重新填充新選項。同時,如果第一層切換回預設預設值(例如「請選擇縣市」),第二層選單應同步鎖定(disabled = true)。

<!DOCTYPE html>
<html lang="zh-TW">
<head>
  <meta charset="UTF-8">
  <title>第十七課 - 多層級聯動下拉選單</title>
  <style>
    body { font-family: sans-serif; padding: 40px; background-color: #f8f9fa; }
    .card { max-width: 450px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.08); }

    .form-group { margin-bottom: 20px; }
    label { font-weight: bold; color: #333; display: block; margin-bottom: 8px; }

    /* 下拉選單基本樣式 */
    select {
      width: 100%;
      padding: 10px;
      font-size: 15px;
      border: 2px solid #ced4da;
      border-radius: 6px;
      outline: none;
      background-color: white;
      cursor: pointer;
      transition: border-color 0.2s ease;
    }
    select:focus { border-color: #007bff; }
    select:disabled { background-color: #e9ecef; cursor: not-allowed; }

    /* 結果顯示區 */
    .result-box {
      margin-top: 20px;
      padding: 12px;
      background-color: #e7f5ff;
      border-left: 4px solid #007bff;
      color: #004085;
      font-weight: bold;
      display: none;
    }
    .result-box.show { display: block; }
  </style>
</head>
<body>

  <div class="card">
    <h2>寄送地址選取</h2>
    
    <!-- 第一層:縣市選單 -->
    <div class="form-group">
      <label for="citySelect">選擇縣市</label>
      <select id="citySelect">
        <option value="">-- 請選擇縣市 --</option>
        <option value="taipei">台北市</option>
        <option value="taichung">台中市</option>
        <option value="kaohsiung">高雄市</option>
      </select>
    </div>

    <!-- 第二層:鄉鎮市區選單 (預設停用) -->
    <div class="form-group">
      <label for="districtSelect">選擇行政區</label>
      <select id="districtSelect" disabled>
        <option value="">-- 請先選擇縣市 --</option>
      </select>
    </div>

    <!-- 選擇結果顯示區 -->
    <div id="resultBox" class="result-box"></div>
  </div>

  <script>
    // ----------------------------------------------------
    // JavaScript 邏輯 (多層級聯動與 change 事件)
    // ----------------------------------------------------

    // 1. 定義區域階層資料 (模擬資料庫或 API 傳回的關聯物件)
    const districtData = {
      taipei: ["中正區", "大安區", "信義區", "中山區"],
      taichung: ["西屯區", "北區", "南屯區", "豐原區"],
      kaohsiung: ["新興區", "前鎮區", "左營區", "苓雅區"]
    };

    // 2. 取得 DOM 元素
    const citySelect = document.getElementById("citySelect");
    const districtSelect = document.getElementById("districtSelect");
    const resultBox = document.getElementById("resultBox");

    // 3. 監聽第一層縣市選單的 change 事件
    citySelect.addEventListener("change", function() {
      const selectedCityKey = citySelect.value; // 取得選取的縣市 Key (例如 "taipei")

      // 清空第二層選單的舊內容
      districtSelect.innerHTML = '<option value="">-- 請選擇行政區 --</option>';
      resultBox.classList.remove("show");

      // 判斷是否有選取有效縣市
      if (selectedCityKey && districtData[selectedCityKey]) {
        // 取得對應的行政區陣列
        const districts = districtData[selectedCityKey];

        // 走訪陣列,動態建立第二層 <option> 節點
        districts.forEach(function(districtName) {
          const option = document.createElement("option");
          option.value = districtName;
          option.innerText = districtName;
          districtSelect.appendChild(option);
        });

        // 啟用第二層選單
        districtSelect.disabled = false;
      } else {
        // 若選回預設的空白選項,重置並停用第二層選單
        districtSelect.innerHTML = '<option value="">-- 請先選擇縣市 --</option>';
        districtSelect.disabled = true;
      }
    });

    // 4. 監聽第二層行政區選單的 change 事件
    districtSelect.addEventListener("change", function() {
      const selectedCityName = citySelect.options[citySelect.selectedIndex].text;
      const selectedDistrict = districtSelect.value;

      if (selectedDistrict) {
        resultBox.innerText = `您選擇的區域為:${selectedCityName} ${selectedDistrict}`;
        resultBox.classList.add("show");
      } else {
        resultBox.classList.remove("show");
      }
    });
  </script>
</body>
</html>

總結與實用情境

今天我們掌握了多層級資料對應與選單連動的核心技術。這種「父層改動 ➔ 撈取對應資料 ➔ 清空與重建子層 DOM」的模式,可以輕鬆延伸至三層(縣市 ➔ 行政區 ➔ 路段)、電商多重規格選單(顏色 ➔ 尺寸 ➔ 庫存)等複雜場景!

下集預告

在購物網站瀏覽商品時,常常有「關鍵字搜尋 + 價格區間 + 分類」等多個篩選條件同時作用,該怎麼做?

【第十八課】資料檢索:多條件即時搜尋與篩選器(Array.filter 與複合狀態監控),我們將學習如何運用 JavaScript 陣列高階方法打造強大的前端篩選器。


上一篇
【第十六課】表單與驗證:即時表單驗證與錯誤提示(RegExp 正則表達式與 submit 事件)
系列文
解決學了JavaScript不知道能做什麼:用DOM操作打造實用網頁效果 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言