June 12,2008 11:22

[AJAX] jQuery的多重下拉式選單應用

P.S 2009-04-09 update:有更輕鬆簡單的方法,詳見[AJAX] jQuery的多重下拉式選單應用:Select box manipulation

關於Ajax,我想最棒的地方就是可以於背景呼叫資料庫傳值吧~多重下拉式選單就是一項非常棒的Ajax應用;前幾天Jace丟過來一個國外的網址jQuery.cascade : Cascading values from forms,這篇文章主要是在說明jQuery的cascade,而它就是用來實現多重下拉式選單的功能。

花了一點時間實作了一下,發現非常簡單就能實現!以前我也作過類似的功能,可是花了我非常多的時間...jQuery把它包裝起來,讓一切變的簡單多了;以下是簡單的範例,給有需要的人參考吧:範例是三階層的關聯式多重下拉式選單,分為index.php(呈現頁)、action.php(Ajax後端資料處理頁)、以及jQuery的cascade

index.php:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="jquery.cascade.js"></script>
<script type="text/javascript" src="jquery.cascade.ext.js"></script>
<script type="text/javascript" src="jquery.templating.js"></script>
</head>
<body>
第一項 <select id="myParentSelect">
<option value="">請選擇</option>
<?php
    // 資料庫設定
    $host_sql = "localhost";
    $username_sql = "username";
    $password_sql = "password";

    $link = mysql_connect($host_sql, $username_sql, $password_sql) or die("無法連結資料庫");
    mysql_select_db('target', $link);
    mysql_query("SET NAMES UTF8");

    $query = "SELECT id, name FROM table where lv = 1";
    $result = mysql_query($query, $link);
    while ($row = mysql_fetch_assoc($result)) {
        echo '<option value="' . $row["id"] . '">' . $row["name"] . '</option>' . "\n";
    }

?>
</select> 
第二項 <select id="myFirstChildSelect">
<option value="">請選擇</option>
</select>
第三項 <select id="mySecondChildSelect">
<option value="">請選擇</option>
</select>

<script type="text/javascript">
$(function () {

    // 第一階層對應第二階層
    $('#myFirstChildSelect').cascade('#myParentSelect', {
    ajax: {
        type: "post",
        url:  'action.php',
        data: { act: 'first', val: $('#myParentSelect').val() }
    },
    template: function(item) { return "<option value='" + item.Value + "'>" + item.Text + "</option>"; },
    match: function(selectedValue) { return this.When == selectedValue; }
    });

    // 第二階層對應第三階層
    $('#mySecondChildSelect').cascade('#myFirstChildSelect', {
    ajax: {
        type: "post",
        url:  'action.php',
        data: { act: 'second', val: $('#myFirstChildSelect').val() }
    },
    template: function(item) { return "<option value='" + item.Value + "'>" + item.Text + "</option>"; },
    match: function(selectedValue) { return this.When == selectedValue; }
    });
});
</script>
</body>
</html>

action.php:

<?php

// 資料庫設定
$host_sql = "localhost";
$username_sql = "username";
$password_sql = "password";

$link = mysql_connect($host_sql, $username_sql, $password_sql) or die("無法連結資料庫");
mysql_select_db('target', $link);
mysql_query("SET NAMES UTF8");

if (!empty($_GET['act'])) {
    $action = $_GET['act'];
}

if (!empty($_GET['val'])) {
    $parentId = $_GET['val'];
}

$list = '[';

switch ($action) {
    case 'first':
        $query = "SELECT id, name FROM table where lv = 2 AND parentid= $parentId";
        $result = mysql_query($query, $link);
        while ($row = mysql_fetch_assoc($result)) {
            $list .= '{\'When\':\'' . $parentId . '\',\'Value\':\'' . $row["id"] . '\',\'Text\':\'' . $row["name"] . '\'},';
        }
        break;
    case 'second':
    default :
        $query = "SELECT id, name FROM table where lv = 3 AND parentid = $parentId";
        $result = mysql_query($query, $link);
        while ($row = mysql_fetch_assoc($result)) {
            $list .= '{\'When\':\'' . $parentId . '\',\'Value\':\'' . $row["id"] . '\',\'Text\':\'' . $row["name"] . '\'},';
        }
        break;
}

$list .= ']';
echo $list;

實作的重點是在資料的格式:

list = [{'When':'A1','Value':'W','Text':'SubchildA1a'},
          {'When':'A1','Value':'X','Text':'SubchildA1b'},
         ];

其中When代表上一階層的值,Value是此一階層的值,Test則是下拉式選單顯示的文字;要特別注意JavaScript是大小寫敏感的!實作這個範例途中,曾經被大小寫拖了一段時間...後來才發現~所以這地方要特別注意!希望大家都能輕鬆寫出關聯資料庫的多重下拉式選單囉^^

  • taikobo0 發表於樂多回應(36)引用(0)AJAX編輯本文
    樂多分類:日記/一般切換閱讀版型 │昨日人次:3 │累計人次:24647
     

    引用URL

    http://cgi.blog.roodo.com/trackback/6166625
    回應文章

    請問如果選單(select value) 是中文BIG5時會變成亂碼,請問該如何解決!
    | 檢舉 | Posted by rainchen at July 2,2008 09:42

    如果我建議你全部改成UTF8,會不會被打...^^|||開玩笑的。請問你使用的編輯環境是BIG5嗎?

    如果是,試試看把 index.php meta 的 charset 改成 big5
    | 檢舉 | Posted by 台扣啵 at July 2,2008 10:49

    謝謝回應!是的我的是big5 的編輯環境
    將 charset 改成 big5 果然可以..感謝
    | 檢舉 | Posted by rainchen at July 3,2008 08:48

    請問一下,我照著你上面教的做,是沒有問題
    但是把他加上 form
    submit 後,
    這幾個 下拉選單的 值 並不會傳到 form 的 action page 耶?
    有人遇到這個狀況嗎?
    謝謝。
    | 檢舉 | Posted by sskes at September 1,2008 20:43

    處理 POST 表單中的值,是依據該表單物件中的 name 屬性唷~範例因為只是簡述實作方法,所以並沒有在 select 物件中加入 name 的屬性;如果需要傳值時:

    第一項 <select id="myParentSelect">
    =>
    第一項 <select id="myParentSelect" name="myParentSelect">

    然後在 action page,依據表單所使用的傳值方法;利用 $_GET['myParentSelect'] 或是 $_POST['myParentSelect'] 就可以得到你想要的值囉!另二項也以此類推。
    | 檢舉 | Posted by 台扣啵 at September 2,2008 10:21

    先謝謝您的回答
    select 裡面的 name 我原本有加

    像您這個是多層的,我只要兩層,所以我把第三層拆掉
    我第一層的值傳的回去
    第二層,就是由jquery 到後面您的 action.php 來產生的 option
    在 action page 就沒有第二個 select 的值

    另外,請問一下,有沒有什麼辦法能夠讓他 預設 第一層的值
    並且讓第二層也依第一層的值 去產生第二層的 option
    在頁面一讀進來,第一層都還沒有選的時候?
    謝謝您的回覆。
    | 檢舉 | Posted by sskes at September 2,2008 14:36

    name 有設定還是沒有值嗎?有試著 print_r($_POST);exit; 看看嗎?如果真的沒有的話...這我也不清楚是什麼問題了。

    至於預設,之前我也有遇到這個問題(我也只用二層);我是用很笨的方法:發現第一層已有值時就直接利用第一層的值作 Ajax 產生第二層的值~不太會解釋,總之算是還蠻蠢的方式...不過因為找不到這個外掛使用預設值的相關資料,目前我是先醬處理囉;這樣不知道對你有沒有幫助?抱歉...@@
    | 檢舉 | Posted by 台扣啵 at September 2,2008 15:31

    謝謝您的回答。
    不好意思,我又拿了一次你的 sample code 去改
    action page 有值了,可能是我之前有什麼遺漏
    自己改錯了吧,真是不好意思。

    另外我提到的預設的問題
    我 setValue 或是 用 html 的自己去標 selected
    他都能把第一層的 選出來,當預設值
    但是都沒辦法 trigger 到第二層的動作
    所以第二層都是空的

    以前這種有關聯的下拉選單,我也都是自己用手寫 XMLHttpReuqest
    現在是發現大家都用 jquery,就來研究一下
    剛接觸,所以笨問題很多,不好意思。
    | 檢舉 | Posted by sskes at September 2,2008 16:00

    大家一起研究研究囉,我的所學其實還遠遠不足~

    關於預設的做法(請參考最新一篇文章),已盡我所能的描述...如果有語焉不詳鬼打牆的狀況就請見諒囉...@@
    | 檢舉 | Posted by 台扣啵 at September 3,2008 10:57
    您好,我這串聯已經有做出來了,可是我先進一步結合呼叫xml,例如:我選第一項下來時,他的值是A,我就呼出xml有關a的節點顯示出來,我一直都弄不出來,前輩能幫幫我嗎?謝謝。
    | 檢舉 | Posted by 新菜鳥 at December 27,2008 00:51

    不好意思,我跟 xml 不熟耶...這個文章主要的目的只是在 ajax 與資料庫連結與動態產生下拉式選單;如果需要其他的運用,可能就要麻煩您自己研究囉~沒有能幫上忙真是抱歉^^|||
    | 檢舉 | Posted by 台扣啵 at December 28,2008 21:33
    ajax送出去時val有可能沒有東西我也有遇到,後來才發現在cascade.ext.js裡面val已經被拿來用了,因此ajax{}裡面的data不用再次設定val,cascade會自己去抓,你手動設定val還有可能會出錯喔 @@
    我寫的說明blog:
    http://peachwaneversay.blogspot.com/2008/12/jquery-cascade-select.html
    | 檢舉 | Posted by neversay at December 29,2008 23:17

    感謝 neversay 兄的分享~我當初在使用的時候也覺得很奇怪,傳入的變數一定要是 val 才能 work...原來是 cascade 的原始碼已經寫死了啊~感謝告知囉^^
    | 檢舉 | Posted by 台扣啵 at December 31,2008 21:20

    請問位什麼我在IE環境下會發生錯誤
    但在FIREFOX環境下卻是正常執行??

    感謝!!!
    | 檢舉 | Posted by 路人甲 at January 31,2009 21:03

    哈,感謝路人甲提醒;IE 對 JavaScript 的判定比較嚴謹,所以在有陣列的地方,結尾如果有多的「,」就會有錯誤,like:

    ajax: {
    type: "post",
    url: \'action.php\',
    data: { act: \'first\', val: $(\'#myParentSelect\').val() }, // 結尾的這個逗號就是多餘的
    }

    只要去掉多餘的逗號,IE 應該就可以正常顯示了;應該是這個問題吧,抱歉~^^|||我也會修改文章內容的,謝謝囉
    | 檢舉 | Posted by 台扣啵 at January 31,2009 21:26

    感謝 但是

    // 第一階層對應第二階層
    $('#myFirstChildSelect').cascade('#myParentSelect', {
    ajax: {
    type: "post",
    url: "action.php",
    data: { act: 'first', val:
    $('#myParentSelect').val() }
    dataType: "json",//多加這行就又不行了

    },
    不好意思 因為我對AJAX很陌生所以很多都不懂
    感謝你那麼快就答覆我 ^^
    | 檢舉 | Posted by 路人甲 at February 1,2009 00:55

    如果加上限定回傳的型別 dataType,這樣陣列結尾的元素就不是 data 囉!以這個例子來說,ajax 這個陣列包含 type、url、data、dataType 四個元素,所以要寫成這樣:

    ajax: {
    type: "post",
    url: "action.php",
    data: { act: 'first', val:
    $('#myParentSelect').val() }, // 因為不是陣列最後一個元素所以要逗號,與下一個元素做區隔
    dataType: "json" // 因為是陣列最後一個元素,所以最後的逗號是多餘的要去掉
    }

    原來另外一篇文章也寫錯了(汗),我稍後也會再修改;抱歉^^|||
    | 檢舉 | Posted by 台扣啵 at February 1,2009 11:17

    感謝你這個問題我解決了!!
    我還有個問題就是我有三階層的選單
    如果我想第三階可以接收到第一街和第二階的的值
    我要如何修改??
    | 檢舉 | Posted by 路人甲 at February 1,2009 11:44

    通常階層的 parentId 就是上一階層的 id,如果要知道再上一層的 id,應該就是上一階層的 parentId 吧?這邊跟資料庫結構比較有關係,要看你的資料庫是怎麼設計的。關於這一部分可以運用後端的 PHP 實作,至於做法就要請您自己研究囉!謝謝^^
    | 檢舉 | Posted by 台扣啵 at February 1,2009 16:31
    感謝您的文章, 受益很大..
    有個小問題請教,
    ajax:{
    type: "post",
    url: 'getQry.asp',
    data:{act:'2',val:$('#Select2').val()}
    }

    我傳入兩個值一個是act,一個是Select2的值到後端,都正常,但如果我想把Select1 的值也一起傳入後端,該怎麼寫才好,因為都會出錯 @@''
    | 檢舉 | Posted by Andrew at February 17,2009 15:44

    理論上只要多加一個變數在 ajax 中,應該就能在後端接到值:

    ajax:{
    type: "post",
    url: 'getQry.asp',
    data:{ act: '2', val: $('#Select2').val(), select1Value: $('#Select1').val() }
    }

    後端程式:(Sorry,我不懂 asp,用 php 語法表示...^^|||)
    $select1Value = $_GET['select1Value'];

    可能要再經過測試,不過大概是這種感覺吧~^^|||
    | 檢舉 | Posted by 台扣啵 at February 18,2009 10:51

    各位先進好
    我的問題是 我的第三層一直沒辦法顯示
    http://220.134.190.186/myprogram/auditor/index.php
    我想應該是我的action.php網頁寫錯,但是第一次用jquary真的還不是很懂,我的action.php長這樣

    require_once("conn.php");

    if (!empty($_GET['act'])) {
    $action = $_GET['act'];
    }

    if (!empty($_GET['val'])) {
    $parentId = $_GET['val'];
    }


    $list = '[';

    switch ($action) {
    case 'first':
    $query = "SELECT DISTINCT `dep` FROM `97opencouse` WHERE `semester`=971 and `college`='".$parentId."'";
    $result = mysql_query($query);
    while ($row = mysql_fetch_array($result)) {
    $list .= '{\'When\':\'' . $parentId . '\',\'Value\':\'' . $row["dep"] . '\',\'Text\':\'' . $row["dep"] . '\'},';
    }
    break;
    case 'second':
    default :
    $query = "SELECT * FROM `97opencouse` WHERE `semester`=971 and `college`='".$parentId."'";
    $result = mysql_query($query);
    while ($row = mysql_fetch_array($result)) {
    $list .= '{\'When\':\'' . $parentId . '\',\'Value\':\'' . $row["dep"] . '\',\'Text\':\'' . $row["course"] . '\'},';
    }
    break;
    }

    $list .= ']';
    echo $list;
    | 檢舉 | Posted by rey at February 25,2009 20:47

    從 Firebug 看起來 $list = [],所以應該是 case 'first' 的 query 就沒有從資料庫撈到任何資料,當然第三層也找不到;建議可以先印出 $query,看看是不是 SQL 語法有錯誤~
    | 檢舉 | Posted by 台扣啵 at February 26,2009 16:29

    台扣啵 您好:

    您可以點 http://220.134.190.186/myprogram/auditor/index.php
    我想case 'first'應該有抓到值,因為我有印出$row["dep"] 這是系(所)名稱 ,我的$parentId 是學院
    但是我的第3層 也就是點了系(所)會跳 開設的課程名稱 ,卻沒辦法顯示。 能不能麻煩台大再幫我看一下~~~真的感謝您呀
    | 檢舉 | Posted by rey at February 26,2009 20:20

    台扣啵 您好:

    我剛自己又改了一下query敘述,ok嘍~~3層都可以正常顯示了~~感謝您提供的資料!!3QQQQ
    | 檢舉 | Posted by rey at February 26,2009 20:29

    請問個自獨立且彼此有關聯的動態選單...
    網上都找不到相關的教學或是範例>"<
    譬如..
    你可以B選單先選新店市 而A選單立即帶出台北縣以及C選單帶出所有符合的道路名稱。
    或是C選單選擇道路名稱..A.B選單自動帶出符合的鄉鎮市 以及縣市名稱...諸如此類的選單

    不知有辦法達成嗎?
    | 檢舉 | Posted by kyo at March 11,2009 10:18

    一開始我對第一句話「個自獨立且彼此有關聯」的意思非常困惑啊...是一種偏意複詞嗎?(噗,開個玩笑別就介意^^|||)

    我大概了解你的意思,這裡使用的 jQuery cascade 是一種單向的多重下拉式選單應用,目前我只想到一個方法:個別對 A、B、C 三種選單寫程式判斷;因為先選 A、先選 B 及先選 C 是三種不同的狀況。所以真的要實作起來,你可能需要用到花很多時間在判斷使用者的動作是哪一種,而且各對這三種選單寫三個 cascade...光想起來就是一件非常麻煩的事情!

    俗話說:「對使用者仁慈就是對開發者殘忍!」我個人認為這樣的方式其實對使用者並不會有太大的效益,但是對開發者的時間成本卻是一大損耗;建議是不要採取這種方式啦...僅供參考^^
    | 檢舉 | Posted by 台扣啵 at March 11,2009 11:11

    恩...我有試著完成了...
    不過..的確如同您所說的...
    有幾各選單就寫幾各cascade...
    真的是累人..
    無奈這是主管的吩咐阿...
    不得不想辦法完成...
    | 檢舉 | Posted by kyo at March 12,2009 12:06

    這...您辛苦了!(敬禮)
    | 檢舉 | Posted by 台扣啵 at March 12,2009 14:26

    您好,謝謝你上篇的答覆,真是辛苦您了,
    我現在想把選單改成兩層就好,我試著修改index.js

    程式碼如下:

    $(function () {

    // 判斷是否有預設值
    var defaultValue = false;
    if (0 < $.trim($('#fullIdPath').val()).length) {
    $fullIdPath = $('#fullIdPath').val().split(',');
    defaultValue = true;}

    if (defaultValue) { $('#select1').selectOptions($fullIdPath[0]);
    }
    // 開始產生關聯下拉式選單
    $('#select1').change(function () {
    // 觸發第二階下拉式選單
    $('#select2').removeOption(/.?/).ajaxAddOption(
    'action.php',
    { 'id': $(this).val(), 'lv': 2 },
    false,
    function () {
    // 設定預設選項
    if (defaultValue) {
    $(this).selectOptions($fullIdPath[0]);
    }
    }
    );
    });
    // 全部選擇完畢後,顯示所選擇的選項
    $('#select2').change(function () {
    alert('主機:' + $('#select1 option:selected').text() +
    '/類型:' + $('#select2 option:selected').text()
    );
    });
    });

    以上,跑出來都沒問題,而我遇到的問題是,select1下拉是中文選項,而後第二項下拉帶出來的卻選項都是???而非中文,全都是問號,alert視窗帶出來的也是"主機:Wii 類型:??? ,submit送出之後,測試印出頁一樣會印出select1,select2為中文,所以我想問題是出在index.js這裡面,是不是我哪邊程式碼有漏掉了??
    | 檢舉 | Posted by fate1209 at April 29,2009 01:11

    你好,看起來是因為編碼的關係;請確定 js 檔案文件、php 檔案文件與編輯環境的編碼都必須是 UTF8。(因為範例檔是用 UTF8)

    另外,感謝您的回應;發表文章就是希望分享技術,大家互相討論,教學相長是件好事。但是您近期的提問內容過於基本,而且給我一種「根本還沒嘗試著解決就先發問」的感覺;google 是大家的好朋友,我希望您在發生任何問題請先 google 一下,謝謝
    | 檢舉 | Posted by 台扣啵 at April 29,2009 22:59

    改謝這篇 我稍微弄懂AJAX了 不過對應JQUERY 這功能還不是很熟
    | 檢舉 | Posted by Abow at September 2,2010 12:29

    感謝! 打錯...另問請問有無推薦的jquery的學習書
    | 檢舉 | Posted by Abow at September 2,2010 12:30

    請問這在firefox可以使用嗎
    | 檢舉 | Posted by lol at September 10,2010 05:14
    版主你好,
    按照這些步驟結果沒有成功顯示,
    但是我把jquery版本從1.7.1換成1.3.2竟然就成功了

    這是甚麼問題呢!?
    ---------------------------------------------
    版主回覆:
    你好,cascade 本身已是一個比較久的 plugin,當初搭配的 jQuery 版本是 1.2.6,而 jQuery 已歷經多次改版,舊版本可用的方法可能在新版本中已捨棄,所以才會造成舊版本才能使用的狀態;建議試著使用新一點的方式,如:Select box manipulation,謝謝^^
    | 檢舉 | Posted by jacksyu at April 17,2012 18:02

    請問版主:

    如果要在 action.php 中加入 require() ,要如何執行?

    因為我的DB連結方式寫在另一個檔案中。

    我試過了,action.php 完全無法執行 require() ,不知識怎麼一回事。

    麻煩您了,謝謝!
    ---------------------------------------------
    版主回覆:
    你好,請留意 require() 函式內的路徑是否正確喔,應該不會有無法 require 的問題;可以試著交叉比對:action.php require 其他的檔案,或是用另外一支 php require DB 的檔案,謝謝
    | 檢舉 | Posted by fuyun at July 21,2014 01:35