跟同事討論到下拉選單連動(最常見的經典應用是縣市、行政區下拉選單連動,選取縣市後自動換成該縣市的行政區清單),這才發現針對這門必修課,我只寫過KO版範例,沒寫過NG版,趕緊補上。

我寫了一個三層式下拉選單連動範例,在ViewModel中安排Level1、Level2、Level3三個屬性保存下拉選單選取結果,另外用L1Options、L2Options、L3Options分別存放Level1-3的下拉選單選項。透過$scope.$watch(),在Level1變動時更新第二層選項,在Level1或Level2變動時更新第三層選項。更新選項時,若Level2/Level3的值不在選項中,則自動切到第一個選項。

為驗證反向操作,我還做了一個修改Level1、Level2、Level3值的按鈕,測試修改資料後下拉選單是否能正確對應。

<!DOCTYPE html>
<html ng-app="app">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>linked dropdowns</title>
  <style>
    body { font-size: 9pt; }
    div { padding: 6px; }
  </style>
</head>
<body ng-controller="main">
    <div>
        <select ng-model="m.Level1" ng-options="o as o for o in m.L1Options"></select>
        <select ng-model="m.Level2" ng-options="o as o for o in m.L2Options"></select>
        <select ng-model="m.Level3" ng-options="o as o for o in m.L3Options"></select>
    </div>
    <div>
    L1 = {{m.Level1}}, L2 = {{m.Level2}}, L3 = {{m.Level3}}
    </div>
    <div>
        <select ng-model="m.Path" ng-options="o as o for o in m.PathOptions"></select> 
        <button ng-click="m.SetLevels()">Set Levels</button>
    </div>
    <script src="https://code.jquery.com/jquery-3.0.0.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
    <script>
      function myViewModel(scope) {
        var self = this;
        self.Level1 = null;
        self.Level2 = null;
        self.Level3 = null;
        
        //模擬資料
        var data = self.Data = {
          "台北": {
            "文山": [ "政大" ],
            "大安": [ "台大", "台科大" ]
          },
          "新竹": {
            "東區": [ "交大", "清大" ]
          },
          "台南": {
            "東區": [ "成大" ],
            "官田": [ "南藝" ]
          }
        };
        
        //各Level對應的選項集合
        self.L1Options = Object.keys(self.Data);
        self.Level1 = self.L1Options[0];
        self.L2Options = [];
        self.L3Options = [];
        
        //Level1變更時連動L2Options
        scope.$watch("m.Level1", function() {
            self.L2Options = data[self.Level1] ? Object.keys(data[self.Level1]) : [];
            //檢查Level2是否在選項中,若無將Level2設定第一筆選項
            var idx = $.inArray(self.Level2, self.L2Options);
            if (idx == -1) self.Level2 = self.L2Options[0];
        });
        //Level1或Level2變更時連動L3Options
        scope.$watch("m.Level1+'/'+m.Level2", function() {
            self.L3Options = 
                data[self.Level1] && data[self.Level1][self.Level2] ?
                data[self.Level1][self.Level2] :
                [];
            //檢查Level3是否在選項中,若無將Level3設定第一筆選項
            var idx = $.inArray(self.Level3, self.L3Options);
            if (idx == -1 ) self.Level3 = self.L3Options[0];
        });
        
        //產生單層資料,形成下拉選單,用來測試更動Level1/Level2/Level3後連動是否正確
        var list = [];
        self.L1Options.forEach(function(city) {
            Object.keys(data[city]).forEach(function(area) {
                data[city][area].forEach(function(school) {
                    list.push(city + "/" + area + "/" + school);
                });
            });
        });
        self.Path = "";
        self.PathOptions = list;
        
        //按鈕後修改Level1/Level2/Level3
        self.SetLevels = function() {
            var p = self.Path.split('/');
            self.Level1 = p[0];
            self.Level2 = p[1];
            self.Level3 = p[2];
        };
        
      }      
      
      angular.module("app", [])
      .controller("main", function ($scope) {
        $scope.m = new myViewModel($scope);
      });
    </script>
</body>
</html>

在實務上,選項可能需要透過AJAX方式取回,此時將兩個$watch()函式改為AJAX查詢邏輯即可。JSBin上有Live Demo,大家可以動手玩玩。

[NG系列]

http://www.darkthread.net/kolab/labs/default.aspx?m=post&t=angularjs

Comments

# by 機密

如果是包在物件內,像是這樣的概念L1{L2[{L3[]}]} 那也可以用filter的方式進行過濾 缺點是第一次下來的清單會比較大 優點是寫的比較少XDDD

# by Alison

版主您好: 冒昧打擾了~~我是松崗圖書的編輯黃小姐。在網路上看到您的網站上有許多分享技術的文章。不曉得您是否有想要撰寫書籍的想法呢?若您對於撰寫書籍有興趣,但是有其他比較偏好的主題,都歡迎分享看法。alisonhuang@kingsinfo.com.tw 希望能有機會合作書籍。 靜待佳音

# by Neal

請問如果是四層連動, 模擬資料那邊, 格式要怎麼寫? 謝謝!

# by Jeffrey

to Neal, 差不多像這樣吧:https://gist.github.com/darkthread/630f2a777a97748edff0b4412bae61ef

# by Neal

非常感謝您的回覆! 我用您的code來做這個功能, 但不知道問題出在哪? http://jsbin.com/soqalamenu/edit?html,js,output 麻煩您解惑了

# by Jeffrey

to Neal, Check it out! http://jsbin.com/tokoxul/1/edit?html,js,output

# by Neal

太感謝了!

# by Neal

不好意思, 我看了很久還是不知道, 我哪邊寫不對...

# by Jeffrey

to Neal, 不確定你是否有貼錯連結,你的版本只有第一個TD放了SELECT,後三個都是{{m.Level*}}。而資料連動的部分錯在self.L3Options = data[self.Level1] && data[self.Level1][self.Level2] ? Object.keys(data[self.Level1][self.Level2]) : []; 取第三層時仍要用Object.keys而不是直接當成陣列取用。

# by Neal

沒有貼錯連結, 其實我是想要做這種功能 http://jsbin.com/kakevoguki/edit?html,js,output 以連結來說 第一欄位的 第一個對應西瓜 第二個對應火龍果 第三個對應蘋果 第二欄位可以隨著select改變, 把值show在第三第四第五欄位 只是現在變成全部都西瓜...

# by Neal

http://jsbin.com/bugoqudojo/edit?html,js,output 後來我改用別種方式 只是要預設顯示option第一個 這個就難倒我了

# by Jeffrey

to Neal, 若是第二欄位隨select改變, 值show在第三第四第五欄位,資料設三層就好,第三層放陣列。至於最後的寫法,預設option未選第一個因為存成物件屬性走(key,value)的限制多,我會改成物件陣列:http://jsbin.com/gutural/1/edit?html,js,output

# by Neal

請問像您這樣寫 http://jsbin.com/gutural/1/edit?html,js,output 要抓值的話, 是要怎麼設定值?

Post a comment