2012年5月29日 星期二

jQuery 練習題:三層式連動下拉選單(無後端整合)

 

這是一個老掉牙的一個功能,但問題就是常常都會有人問起,以往部落格也有針對連動式下拉選單寫了文章,

jQuery 對下拉選單 DropDownList 的操作 - 2:連動下拉選單

因為該文章的內容是有跟 ASP.NET MVC 做整合,所以對於有些沒有接觸過 ASP.NET MVC 的人就會看得模模糊糊的,很多對 Javascript 或是 jQuery 不熟的人做連動式下拉選單,大部分的情況就是直接 Google 然後找一個自己看得懂也改得動的來用,但問題是 …… 有了兩層式連動下拉選單就一定會有三層式連動下拉選單的需求,對於前端程式不是很熟悉而且只會複製貼上、修改的人來說,從兩層式連動換成三層式連動就會不知所措。

講這麼多,所以這邊文章接下來的內容就將會來練習如何做一個三層式連動的下拉選單,沒有 Server-Side 的程式,完全都是前端程式與文字資料的整合,所以看得懂的就看,看不懂的就回頭去好好練習 Javascript 與 jQuery,如果是想要找一個拿來修改就可以用的三層式連動下拉選單,可以就此打住而不必繼續往下,因為拿回去應該也不能用。

 



資料來源

這一個練習的顯示資料來源將會採用台北市政府公開資料的台北旅遊網以及台北市旅館資料庫,

臺北市政府公開資料平台

image

 


 

功能介紹

這個練習題所要做的功能就只是三層下拉選單的連動與選單資料的呈現,所以就沒有必要再去多其他的功能,不然台北市政府公開資料所提供的 XML 裡是還有很多的資料,例如地圖資訊、圖片資訊、景點介紹等,這些都還可以進階的來加以應用,但這就超出這次練習題的範疇。

 

第一層選單:

分為住宿與景點兩個分類

image

 

第二層選單:

依據第一層來決定次分類的資料,例如第一層選擇「住宿」

image

 

第三層選單:

依據第一層分類與第二層次分類來決定第三層下拉選單的資料,

image

 


引用檔案

jQuery-1.7.2

http://docs.jquery.com/Downloading_jQuery

 

jQuery XML to JSON Plugin v1.1

http://www.fyneworks.com/jquery/xml-to-json/

這個套件可以讓我們把 XML 資料轉換為 JSON 格式的資料,畢竟在 Javascript 中操作 JSON 比操作 XML 要來得方便

 

LINQ to JavaScript (JSLINQ) v2.2

http://jslinq.codeplex.com/

這個套件在之前就有寫過文章來介紹,可以讓我們在 Javascript 程式中使用類似於 LINQ 語法的方式來篩選集合資料

前端處理JSON資料與陣列 - 使用 LINQ to JavaScript (1)

前端處理JSON資料與陣列 - 使用 LINQ to JavaScript (2)

 


 

準備資料

category.txt

這是第一個分類下拉選單的資料內容,以 JSON 格式存放在一個文字檔案中,不寫死在 HTML Code 中

image

 

然後依據台北市政府公開資料的內容,景點與住宿的次分類如下,

SubCategory_01.txt  - 景點次分類

image

SubCategory_02.txt - 住宿次分類

image

 

而景點與住宿飯店的資料是直接將台北市政府所提供的 XML 給另存為「scenery.xml」「hotel.xml」,不過以下的兩個 XML 資料檔案在臺北市政府公開資料平台上面已經找不到了,而連結還是有效(可能不久後就會不見),

景點資料:http://www.taipei.gov.tw/public/ogdi/blob/scenery.xml

飯店資料:http://www.taipei.gov.tw/public/ogdi/blob/hotel.xml

 


網頁內容


HTML Code

HTML 的部份相當簡單,

<h1>臺北旅遊網-景點資料 及 台北市旅館資料庫</h1>
 
<div id="DropDownList">
    分類:<select id="SelectCategory"></select> 
    次分類:<select id="SelectSubCategory"></select> 
    <label id="LabelViewpoint"></label><select id="SelectViewpoint"></select> 
</div>


Include Javascript files

加入 jQuery Library 以及上面介紹的兩個 jQuery Plugin

<script type="text/javascript" src="scripts/jquery-1.7.2.min.js"></script>
<script type="text/javascript" src="scripts/jquery.xml2json.js"></script>
<script type="text/javascript" src="scripts/JSLINQ.js"></script>


Javascript Code

 
    var jsonScenery = [];
    var jsonHotel = [];    
 
 
    $(document).ready(function()
    {
        Page_Init();
 
    });
 
    function Page_Init()
    {
        $.getJSON('data/category.txt', function(data) 
        {
            $('#SelectCategory').empty().append($('<option></option>').val('').text('------'));
 
            $.each(data, function (i, item)
            {
                $('#SelectCategory').append($('<option></option>').val(item.categoryId).text(item.categoryName));
            });                      
        }); 
 
        $('#SelectSubCategory').empty().append($('<option></option>').val('').text('------'));
        $('#SelectViewpoint').empty().append($('<option></option>').val('').text('------'));    
        $('#LabelViewpoint').text('景點');
 
        $('#SelectCategory').change(function(){
            ChangeCategory();
        });
 
        $('#SelectSubCategory').change(function(){
            ChangeSubCategory();
        });                
 
        GetSceneryJsonData();
        GetHotelJsonData();
    }
 
    function GetSceneryJsonData()
    {
        $.get('data/scenery.xml', function(xml) 
        {
            jsonScenery = $.xml2json(xml);
        });
        return jsonScenery;
    }    
 
    function GetHotelJsonData()
    {
        $.get('data/hotel.xml', function(xml) 
        {
            jsonHotel = $.xml2json(xml);
        });
        return jsonHotel;
    }        
 
 

程式說明:

頁面載入網頁完成後,會先載入第一個分類資料的下拉選單「category.txt」,然後清空第二個與第三個下拉選單的資料,#LabelViewpoint 是會依據第一個分類資料做不一樣名稱的顯示,一開始預設顯示景點,如果是選住宿就會顯示為旅館,接下來就分別綁定第一個與第二個下拉選單的 change 事件,讓下拉的時候去執行綁定的 function,再來就是分別預先載入兩個 XML 檔案,因為在 Javascript 裡面要對 XML 的資料做操作真的很瑣碎,所以就使用了 jQuery XML to JSON Plugin 讓 XML 資料轉換為 JSON。

 

    function ChangeCategory()
    {
        //變動第一個下拉選單
 
        $('#SelectSubCategory').empty().append($('<option></option>').val('').text('------'));
        $('#SelectViewpoint').empty().append($('<option></option>').val('').text('------'));            
 
        var categoryId = $.trim($('#SelectCategory option:selected').val());
        if(categoryId.length != 0)
        {
            $.getJSON('data/SubCategory_0'+ categoryId +'.txt', function(data)
            {
                $.each(data, function(i, item){
                    $('#SelectSubCategory').append($('<option></option>').val(item.subCategoryId).text(item.subCategoryName));
                });
            });
 
 
            if(categoryId == '1')
            {
                $('#LabelViewpoint').text("景點");
            }
            else
            {
                $('#LabelViewpoint').text("旅館");
            }                    
        }
    }

程式說明:

第一個分類下拉選單的變動所觸發執行的 funciton,這部分就比較單純,就是依據分類選單所選擇的項目而去載入個別的次分類資料,這邊的次分類資料就只有兩個「SubCategory_01.txt」與「SubCategory_02.txt」

 

    function ChangeSubCategory()
    {
        //變動第二個下拉選單
 
        $('#SelectViewpoint').empty().append($('<option></option>').val('').text('------'));
 
        var categoryId = $.trim($('#SelectCategory option:selected').val());
        var categoryName = $.trim($('#SelectCategory option:selected').text());
        var subCategoryName = $.trim($('#SelectSubCategory option:selected').text());
        
        if(categoryId == '1')
        {
            //景點
            var result = new JSLINQ(this.jsonScenery.Section)
                .Where(function (item) { return item.CAT1 == categoryName && item.CAT2 == subCategoryName; })
                .Select(function (item) { return item; }).ToArray();
 
            $.each(result, function(i, item)
            {
                $('#SelectViewpoint').append($('<option></option>').val(item["SERIAL_NO"]).text(item["stitle"]));
            });
        }
        else if(categoryId == '2')
        {
            //住宿
            var result = new JSLINQ(this.jsonHotel.Section)
                .Where(function (item) { return item.CAT1 == categoryName && item.CAT2 == subCategoryName; })
                .Select(function (item) { return item; }).ToArray();
 
            $.each(result, function(i, item)
            {
             $('#SelectViewpoint').append($('<option></option>').val(item["SERIAL_NO"]).text(item["stitle"]));
            });
        }
    };

程式說明:

第二個次分類下拉選單的變動所觸發執行的 function,這個地方就比較有趣,因為我們原本的景點資料與住宿旅館資料來源都是 XML,而這兩份 XML 資料是把所有的景點、住宿資料都塞在一起,為了要做篩選的處理,所以我們一開始就把 XML 載入並轉成 JSON 資料,而 JSON 資料的篩選處理就用了 JSLINQ 這個套件,用近似於 C# LINQ 的查詢語法來做資料的篩選,是個相當方便套件,我們可以依據第一個分類與第二個次分類下拉選單的選取值來做篩選過濾的條件,如此一來就可以得到第三層下拉選單的資料內容了。

 


觀察 Firebug

藉由觀察 Firebug 的資訊來了解運作情況,一開始的初始狀況與載入情形

image

 

變動第一個分類下拉選單

image

 

變動第二個次分類選單

image

image

image

 


這邊文章只有文字內容與截圖,可能有些人還是搞不太懂,沒關係!就直接放在線上讓各位看看、操作。

線上測試網頁:

http://mrkt.myweb.hinet.net/cascade/cascade.html

image

因為 hinet 的網頁空間對於純文字檔案的載入會有限制,所以前面 Javascript 程式中載入 category.txt, SubCategory_01.txt, SubCategory_02.txt,這三個檔案就不載入,這三個檔案的資料內容就直接寫入到 Javascript 的程式中,想要看看修改後的原始檔,可以參考以下的 Code 內容,基本上改變不大。

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <h1>臺北旅遊網-景點資料 及 台北市旅館資料庫</h1>
 
        <div id="DropDownList">
            分類:<select id="SelectCategory"></select> 
            次分類:<select id="SelectSubCategory"></select> 
            <label id="LabelViewpoint"></label>:<select id="SelectViewpoint"></select> 
        </div>
 
 
        <div id="map" style="width:800px; height:600px"></div>
    
        <script type="text/javascript" src="scripts/jquery-1.7.2.min.js"></script>
        <script type="text/javascript" src="scripts/jquery.xml2json.js"></script>
        <script type="text/javascript" src="scripts/JSLINQ.js"></script>
        <script type="text/javascript">
        <!--
            var jsonScenery = [];
            var jsonHotel = [];    
 
 
            $(document).ready(function()
            {
                Page_Init();
            });
 
            function Page_Init()
            {
                var jsonData =
                [
                    {
                        "categoryId": "2",
                        "categoryName": "住宿"
                    },
                    {
                        "categoryId": "1",
                        "categoryName": "景點"
                    }
                ];
 
                $('#SelectCategory').empty().append($('<option></option>').val('').text('------'));
 
                $.each(jsonData, function (i, item)
                {
                    $('#SelectCategory').append($('<option></option>').val(item.categoryId).text(item.categoryName));
                });    
 
                $('#SelectSubCategory').empty().append($('<option></option>').val('').text('------'));
                $('#SelectViewpoint').empty().append($('<option></option>').val('').text('------'));    
                $('#LabelViewpoint').text('景點');
 
                $('#SelectCategory').change(function(){
                    ChangeCategory();
                });
 
                $('#SelectSubCategory').change(function(){
                    ChangeSubCategory();
                });                
 
                GetSceneryJsonData();
                GetHotelJsonData();
            }
 
            function GetSceneryJsonData()
            {
                $.get('data/scenery.xml', function(xml) 
                {
                    jsonScenery = $.xml2json(xml);
                });
                return jsonScenery;
            }    
 
            function GetHotelJsonData()
            {
                $.get('data/hotel.xml', function(xml) 
                {
                    jsonHotel = $.xml2json(xml);
                });
                return jsonHotel;
            }
 
            function ChangeCategory()
            {
                //變動第一個下拉選單
 
                $('#SelectSubCategory').empty().append($('<option></option>').val('').text('------'));
                $('#SelectViewpoint').empty().append($('<option></option>').val('').text('------'));            
 
                var categoryId = $.trim($('#SelectCategory option:selected').val());
                
                var jsonData = [];
                
                if(categoryId == '1')
                {
                    jsonData =
                    [
                        {
                            "subCategoryId": "S01",
                            "subCategoryName": "博物館"
                        },
                        {
                            "subCategoryId": "S02",
                            "subCategoryName": "歷史建築"
                        },
                        {
                            "subCategoryId": "S03",
                            "subCategoryName": "廟宇"
                        },
                        {
                            "subCategoryId": "S04",
                            "subCategoryName": "單車遊蹤"
                        },
                        {
                            "subCategoryId": "S05",
                            "subCategoryName": "城市公園"
                        },
                        {
                            "subCategoryId": "S06",
                            "subCategoryName": "親山健行"
                        },
                        {
                            "subCategoryId": "S07",
                            "subCategoryName": "藍色公路"
                        },
                        {
                            "subCategoryId": "S08",
                            "subCategoryName": "公共藝術"
                        },
                        {
                            "subCategoryId": "S09",
                            "subCategoryName": "展演會館"
                        },
                        {
                            "subCategoryId": "S10",
                            "subCategoryName": "教堂"
                        },
                        {
                            "subCategoryId": "S11",
                            "subCategoryName": "圖書館"
                        },
                        {
                            "subCategoryId": "S12",
                            "subCategoryName": "親子共遊"
                        },
                        {
                            "subCategoryId": "S13",
                            "subCategoryName": "養生溫泉"
                        },
                        {
                            "subCategoryId": "S14",
                            "subCategoryName": "其它"
                        }
                    ];
                }
                else if(categoryId == '2')
                {
                    jsonData =
                    [
                        {
                            "subCategoryId": "H01",
                            "subCategoryName": "國際觀光旅館"
                        },
                        {
                            "subCategoryId": "H02",
                            "subCategoryName": "一般觀光旅館"
                        },
                        {
                            "subCategoryId": "H03",
                            "subCategoryName": "一般旅館"
                        }
                    ];                
                }
                
                
                if(categoryId.length != 0)
                {
                    $.each(jsonData , function(i, item){
                        $('#SelectSubCategory').append($('<option></option>').val(item.subCategoryId).text(item.subCategoryName));
                    });
 
 
                    if(categoryId == '1')
                    {
                        $('#LabelViewpoint').text("景點");
                    }
                    else
                    {
                        $('#LabelViewpoint').text("旅館");
                    }                    
                }
            }
 
            function ChangeSubCategory()
            {
                //變動第二個下拉選單
 
                $('#SelectViewpoint').empty().append($('<option></option>').val('').text('------'));
 
                var categoryId = $.trim($('#SelectCategory option:selected').val());
                var categoryName = $.trim($('#SelectCategory option:selected').text());
                var subCategoryName = $.trim($('#SelectSubCategory option:selected').text());
                
                if(categoryId == '1')
                {
                    //景點
                    var result = new JSLINQ(this.jsonScenery.Section)
                        .Where(function (item) { return item.CAT1 == categoryName && item.CAT2 == subCategoryName; })
                        .Select(function (item) { return item; }).ToArray();
 
                    $.each(result, function(i, item)
                    {
                        $('#SelectViewpoint').append($('<option></option>').val(item["SERIAL_NO"]).text(item["stitle"]));
                    });
                }
                else if(categoryId == '2')
                {
                    //住宿
                    var result = new JSLINQ(this.jsonHotel.Section)
                        .Where(function (item) { return item.CAT1 == categoryName && item.CAT2 == subCategoryName; })
                        .Select(function (item) { return item; }).ToArray();
 
                    $.each(result, function(i, item)
                    {
                     $('#SelectViewpoint').append($('<option></option>').val(item["SERIAL_NO"]).text(item["stitle"]));
                    });
                }
            };
        -->
        </script>
        
    </body>
</html>

 


原本最後的線上展示是想要放在 jsFiddle 上面,但是 jsFiddle 要讀取外部文字資料、XML 資料似乎是要做另外的設定還是不支援,最後就乾脆直接放到 hinet 所提供用戶的網頁空間中,但也遇到了這網頁空間似乎對於純文字檔案有限制的問題,所以就改成了把分類與次分類資料由原本文字檔案中改放到 Javascript  的內容當中,…… 總之,這不是重點。

連動式的下拉選單並不是一個很困難的功能,但就是會常常有人會卡住,很多人所遇到的問題不外乎就是:

  1. 對 jQuery 不熟
  2. 不知道該用下拉選單的哪個事件觸發
  3. 觸發 change 事件後不知道怎麼載入資料
  4. 不知道怎麼做來源資料(給下拉選單建立 option 之用)
  5. ASP.NET WebForm 開發人員習慣用 UpdatePanel 而對 jQuery, JSON, Javascript 完全不認識

 

雖然這篇練習題不算是一個很完整的教學說明,而我將資料來源從原本有後端語言執行(ASP.NET or PHP or ASP … etc)轉變為沒有後端語言執行的方式,這目的就是要讓大家知道連動變換下拉選單資料來源的組成並不是問題,只要能夠可以給 option 一個 value 以及 一個 text 就可以。

另外這次也介紹了一個好用的 jQuery Plugin「jQuery XML to JSON Plugin」,可以讓我們在 Javascript 操作 XML 資料更加方便,不過對於結構太過於複雜的 XML 就不適合使用這個 jQuery Plugin 來轉換為 JSON,大家可以透過 jQuery XML to JSON Plugin 的範例來做初步的了解。

http://www.fyneworks.com/jquery/xml-to-json/#tab-Examples

http://www.fyneworks.com/jquery/xml-to-json/#tab-Examples-RSS

 

以上

10 則留言:

  1. 感謝教學!正好在研究相關的部分,看了這篇有很大的幫助!謝謝~

    回覆刪除
  2. 大大可以請教一下
    如果選單選完,下方想要跳出對應的<div資料
    應該怎麼改寫?
    我是美術設計人員><html ok....但script完全不行
    最近在做一個靜態網站...剛好要用這個範例(前端搜尋區域)
    翻遍google..大部份都是講解到連動選單
    但我連動選單選完後,下方想出現對應的門市地點
    :'(
    希望大大能看見啊.....

    回覆刪除
    回覆
    1. Hello 你好,
      有關選完下拉選單的選項之後要把相應的資料給顯示到某一個 ID 的 div 容器下,
      我建議你可以參考 jQuery 的官方 API 文件,如下:
      http://api.jquery.com/html/
      http://api.jquery.com/text/

      刪除
  3. 路過,感謝您的資訊。雖然還沒學會,但分享資訊是偉大的

    回覆刪除
  4. 不好意思打擾了 我目前使用過了三個版本的JSLINQ
    在2.10版的 在程式執行時他會顯示JSLINQ.js裡迴圈式子的length有問題
    在2.2.2.0 跟2.2.0.0版本的 他在var result = new JSLINQ(this.jsonScenery.Section) 說找不到JSLINQ
    於是我把名稱從JSLINQ改成linq.js(2.2.2.0版本的檔案)裡面的Enumerable
    他會變成.Select(function (item) { return item; }).ToArray();
    這行 .Select找不到
    雖小弟才疏學淺 但也是看了程式碼很久都還是沒辦法找到解答...
    還請版主不吝指教 感恩

    回覆刪除
    回覆
    1. 因為我不清楚你所使用的資料內容與格式為何,因為我這邊所執行的都是正常(JSLINQ.js),
      而有出現問題的應該是使用 IE 瀏覽器(這個請記得在問問題的時候,請務必把你的測試背景資料給說清楚),
      然後是直接執行檔案系統下的 html 檔案才會發生,
      我會建議,在前端的 javascript 可以不必使用 JSLINQ.js 或 linq.js 這些套件,
      我之所以會用,這是因為寫文章的當時正在 try 前端這些類似 .NET 的 LINQ 套件功能,
      其實這種過濾條件取得資料的功能,使用基本的 javascript 或 jQuery 也是可以做到,
      如果對於 .NET 的 LINQ 並不了解,或是也根本不會用到 .NET 的技術,
      那麼就不要管 JSLINQ.js 了,就用最基本的方式來處理,
      而至於怎麼改用所謂的最基本的 javascript 或 jquery 的方式來......
      我想我就不提供做法,就讓大家自己去練習吧(自己的功課自己寫)

      刪除
  5. 您好:
    真的非常抱歉,想請問要如何連結至指定的檔案呢?是否有完整的語法,不好意思,是否可以麻煩拜託請您教教我呢?千萬拜託,感激不盡,謝謝您喔!

    回覆刪除
    回覆
    1. 啊?為什麼同樣的問題要連貼三篇文章,而且跟文章主題不一樣呀

      刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。