2015年5月30日 星期六

Easyui 測試 : Combobox 下拉式選單

今天在修改財工程式時遇到 Combobox 的運用問題, 在實際系統中測試變數較多, 環境較複雜, 不容易掌握問題的原因, 因此花一點時間單獨來測試一下 Combobox.

根據 Easyui 文件, Combobox 可以用兩種 HTML 元件轉化, 一是 SELECT-OPTION 下拉式選單, 二是 INPUT 元件, 只要指定 class 為 easyui-combobox 即可. 用 SELECT-OPTION 當然是最直覺的, 如下列範例 1 所示 :

範例 1 : http://mybidrobot.allalla.com/easyuitest/easyui-combobox-1.htm [看原始碼]

  <select id="category" class="easyui-combobox" name="category" data-options="panelHeight:'auto'">
      <option value="水泥">水泥</option>
      <option value="食品">食品</option>
      <option value="塑膠">塑膠</option>
      <option value="鋼鐵">鋼鐵</option>
      <option value="電子" selected>電子</option>
      <option value="金融">金融</option>
  </select>


此處 combobox 的屬性就放在 data-options 中, 這裡 panelHeight 設為 'auto', 表示會視選項多寡自動調整拉把的高度, 也可以直接指定一個數值 (例如 100 或 '100px', 有 px 須視為字串), 這樣高度就會被固定. 利用 OPTION 的 selected 屬性就可以設定初始值了. 注意, combobox 預設即有 autocomplete 功能, 輸入任何一個字首, 就會顯示過濾後的選項了.

此外也可以用 Javascript 來設定 data-options 中的屬性值, 如範例 2 所示 :

範例 2 : http://mybidrobot.allalla.com/easyuitest/easyui-combobox-2.htm [看原始碼]

  <select id="category" class="easyui-combobox" name="category">
      <option value="水泥">水泥</option>
      <option value="食品">食品</option>
      <option value="塑膠">塑膠</option>
      <option value="鋼鐵">鋼鐵</option>
      <option value="電子" selected>電子</option>
      <option value="金融">金融</option>
  </select>
  <script language="javascript">
    $(document).ready(function(){
      $("#category").combobox({
        panelHeight:'80px',
        panelWidth:150
        });
      });
  </script>


此例我們將 panelHeight 改為 '80px' (注意, 須用引號括起來), 但選項長度超過 80px, 所以會出現垂直滑動桿. 當然也可以全部都用 Javascript 設定, 但這最好使用第二種元件, 也就是 INPUT 來製作, 如範例三所示 :

範例 3 : http://mybidrobot.allalla.com/easyuitest/easyui-combobox-3.htm [看原始碼]

  <input id="category" name="category">
  <script language="javascript">
    $(document).ready(function(){
      $("#category").combobox({
        valueField:'value',
        textField:'text',
        data:[
          {value:'水泥',text:'水泥'},
          {value:'食品',text:'食品'},
          {value:'塑膠',text:'塑膠'},
          {value:'鋼鐵',text:'鋼鐵'},
          {value:'電子',text:'電子',selected:true},
          {value:'金融',text:'金融'}
          ]
        });
      });
  </script>


此例中我們只放置了一個具有 id 屬性的 INPUT 元素, 然後在 combobox 方法中設定 valueField (option 的 value 屬性名稱), textField (option 的顯示字串), 以及 data (option 屬性陣列) 這三個屬性即可. valueField 與 textField 的值是任意定的, 只要能與 data 中的設定相符即可. 預設的選項要用 seleted:true 來指定.

注意, 全部使用 Javascript 設定時, 應該像範例 3 那樣使用 INPUT 元件, 不要用沒有 option 的 select 來做, 否則會變得扁扁的. 將範例 3 的 INPUT 改為如下測試即知 :

<select id="category" name="category"></select>

以上都是使用本地端資料, combobox 也可以由遠端提供動態選項資料, 這必須用到 url 這個屬性, 若需要本地提供參數, 也需要用到 method 與 queryParams 屬性, 如範例 4 所示 :


  <input id="category" name="category">
  <script language="javascript">
    $(document).ready(function(){
      $("#category").combobox({
        panelHeight:'auto',
        valueField:'value',
        textField:'text',
        url:"get_combobox_category.php"
        });
      });
  </script>


此例中我們透過 url 指定由遠端程式 get_combobox_category.php 提供選項資料, 不過須先在後端資料庫建一個 category 資料表, 裡面只有一個欄位 category, 填入上面本地資料的六種類別, 此 category 資料表匯出內容如下 :

--
-- 表的結構 `category`
--

CREATE TABLE IF NOT EXISTS `category` (
  `category` varchar(255) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

--
-- 轉存資料表中的資料 `category`
--

INSERT INTO `category` (`category`) VALUES
('水泥'),
('食品'),
('塑膠'),
('鋼鐵'),
('電子'),
('金融'),
('金融');

然後就可以撰寫輸出此 category 資料的程式 get_combobox_category.php 如下 :  

<?php
header('Content-Type: text/html;charset=UTF-8');
$host="mysql.1freehosting.com"; 
$username="easyui_test";
$password="blabla";
$database="easyui_test";
$conn=mysql_connect($host, $username, $password); //建立連線
mysql_query("SET NAMES 'utf8'"); //設定查詢所用之字元集為 utf-8
mysql_select_db($database, $conn); //開啟資料庫
$SQL="SELECT * FROM `category`"; 
$result=mysql_query($SQL, $conn); //執行 SQL 指令
$arr=array(); 
for ($i=0; $i<mysql_numrows($result); $i++) { //走訪紀錄集 (列)
     $row=mysql_fetch_array($result); //取得列陣列
     if ($i==4) { //指定第 5 個為預設值
       $arr[$i]=array("value" => $row["category"],
                      "text" => $row["category"],
                      "selected" => true);
       }
     else {
       $arr[$i]=array("value" => $row["category"],
                      "text" => $row["category"]);
       }
     } 
echo json_encode($arr);  //將陣列轉成 JSON 資料格式傳回
?>

此程式會輸出一個 JSON 物件陣列 :

[{"value":"\u6c34\u6ce5","text":"\u6c34\u6ce5"},{"value":"\u98df\u54c1","text":"\u98df\u54c1"},{"value":"\u5851\u81a0","text":"\u5851\u81a0"},{"value":"\u92fc\u9435","text":"\u92fc\u9435"},{"value":"\u96fb\u5b50","text":"\u96fb\u5b50","selected":true}, {"value":"\u91d1\u878d","text":"\u91d1\u878d"},{"value":"\u91d1\u878d","text":"\u91d1\u878d"}]

此處第五項多了一個 selected 屬性, 設為 true 即為預設值. 好了, combobox 就暫時測試到這裡, 總算釐清 combobox 中文傳回值可正常運作無疑, 要回頭檢視財工程式到底哪裡弄錯以至於無法顯示選項, 以後有空再繼續整理測試結果. 

# 2015-05-30 21:50:50 註 :

晚上在鄉下測試好久, 終於找到原因了, 原來問題出在 mode 這個屬性沒有設, 預設是 local, 這樣 combobox 只會在載入的資料的 text 中 (不是 value), 搜尋以輸入字開頭的選項. 參閱 Easyui 文件 combobox 的 mode 屬性說明 :

"Defines how to load list data when text changed. Set to 'remote' if the combobox loads from server. When set to 'remote' mode, what the user types will be sent as the http request parameter named 'q' to server to retrieve the new data."

也就是說, 當 mode 為 remote 時, 輸入查詢字時會以 http 要求將此查詢字串用 q 這個參數傳給後端伺服器. 為了說明這個問題, 我先準備了一個含有台股股票代號與公司名稱的資料表 stocks_list :

--
-- 表的結構 `stocks_list`
--

CREATE TABLE IF NOT EXISTS `stocks_list` (
  `stock_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `stock_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`stock_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

--
-- 轉存資料表中的資料 `stocks_list`
--

INSERT INTO `stocks_list` (`stock_id`, `stock_name`) VALUES
('0015', '富邦'),
('0050', '台灣50'),
('0051', '中100'),
('0052', 'FB科技'),
('0053', '寶電子'),
('0054', '台商50'),
('0055', '寶金融'),
....
('9958', '世紀鋼'),
('2342', '茂矽'),
('2364', '倫飛'),
('2816', '旺旺保');

然後我希望在 combobox 中, 輸入股票代號的第一個字元時就顯示以其為首的所有股票; 或者當輸入公司名稱的任一字元時, 就顯示所有含有此字元的股票. 亦即, 前者在 SQL 搜尋條件是 "$q%"; 而後者是 "%$q%", 如下面範例 5 所示 :

範例 5 : http://mybidrobot.allalla.com/easyuitest/easyui-combobox-5.htm [看原始碼]

  <input id="stock" name="stock">
  <script language="javascript">
    $(document).ready(function(){
      $("#stock").combobox({
        panelHeight:150,
        valueField:'value',
        textField:'text',
        url:"get_combobox_stocks_list.php"
        });
      });
  </script>

此例中我們僅指定資料來源為遠端程式 get_combobox_stocks_list.php, 其內容如下 :

<?php
header('Content-Type: text/html;charset=UTF-8');
$host="mysql.1freehosting.com"; 
$username="easyui_test";
$password="blabla";
$database="easyui_test";
$conn=mysql_connect($host, $username, $password); //建立連線
mysql_query("SET NAMES 'utf8'"); //設定查詢所用之字元集為 utf-8
mysql_select_db($database, $conn); //開啟資料庫
$q=$_REQUEST["q"]; //combobox 送出的查詢參數
if (isset($q)) { //有搜尋
  $where=" WHERE `stock_id` LIKE '".$q."%' OR ".
         "`stock_name` LIKE '%".$q."%'";
  }
else {$where="";} //沒有搜尋,顯示全部
$SQL="SELECT stock_id,stock_name FROM stocks_list".$where;
$result=mysql_query($SQL, $conn); //執行 SQL 指令
$arr=array();
for ($i=0; $i<mysql_numrows($result); $i++) { //走訪紀錄集 (列)
  $row=mysql_fetch_array($result); //取得列陣列
  $value=$row["stock_id"];
  $text=$row["stock_id"]." (".$row["stock_name"].")";
  $arr[$i]=array("value" => $value,"text" => $text);
  }
echo json_encode($arr);
?>

此程式回應的 value 屬性為股票代號, 而顯示的選項文字是像這樣 : 2330 (台積電). 輸入 2 會顯示所有股票代號開頭為 2 之選項, 但是當輸入股票名稱, 像 "台" 時, 卻沒有半個選項. 原因就是如上所言, combobox 的 mode 屬性預設是 "local", 它只會在載入的選項資料中, 從開頭去比對 text 的內容, 因為傳回的 text 屬性都是以代號開頭, 因此輸入名稱就不會有任何匹配的選項了.

解決之道就是加上 mode:"remote" 設定即可, 如範例 6 所示 :

範例 6 : http://mybidrobot.allalla.com/easyuitest/easyui-combobox-6.htm [看原始碼]

  <input id="stock" name="stock">
  <script language="javascript">
    $(document).ready(function(){
      $("#stock").combobox({
        panelHeight:150,
        valueField:'value',
        textField:'text',
        url:"get_combobox_stocks_list.php",
        mode:'remote'
        });
      });
  </script>


哈哈, 終於搞定這個困擾我許久的問題了. 原以為 easyui 的 combobox 處理中文有 bug, 前面範例 1~4 破除了這個疑慮, 沒想到是設定上的問題.

不過這種 remote 方式會增加許多 request 次數, 例如輸入一個 "台" 字就會產生三次 http 要求, 如下圖所示, 第一個是輸入 "ㄊ" 時傳出 q 參數, 第二個是輸入 "ㄞ" 時傳出 q="ㄊㄞ" 給後端, 最後一個是傳出 q="台" :


最後, 也最重要的是, 當使用者選定選項後必須要去執行一項工作 (不然是選好玩的嗎), 這就要用到 onSelect 這個屬性, 其值是一個回呼函數, combobox 會傳入一個選項參數物件 item (不一定用此名稱, 我們可任意命名傳入參數, 例如 option), 利用 valueField 與 textField 所定義的值與顯示文字欄位名稱, 就可取得被選取項目的 value 與 text. 例如 valueField 與 textField 分別定為 "value" 與 "text", 則 item.value 與 item.text 就是被選取項目的值與顯示文字, 如範例 7 所示 :

範例 7 : http://mybidrobot.allalla.com/easyuitest/easyui-combobox-7.htm [看原始碼]

  <input id="stock" name="stock">
  <script language="javascript">
    $(document).ready(function(){
      $("#stock").combobox({
        panelHeight:150,
        valueField:'value',
        textField:'text',
        url:'get_combobox_stocks_list.php',
        mode:'remote',
        onSelect:function(item){
          $.messager.alert("Info", item.value + " " + item.text);
          }
        });
      });
  </script>


實際運用上可能是去更新 datagrid 的內容, 例如 :

      onSelect:function(item){
        $('#daily').datagrid("load",{
          op:"list_daily",
          stock_id:item.value
          });
        }

此處就是傳出 op 與 stock_id 這兩個參數去載入 id 為 daily 的 datagrid 內容.

參考 :

EasyUI的ComBoBox怎么设定默认值?


4 則留言 :

shanhua 提到...

請教一下,你combobox你加的參數mode:'remote'
是適用於各家瀏覽器嗎(輸入文字自動完成近似的選項)?

我以你的方式測試結果如后:
適用:PC版Chrome(未設定時也正常)、IE瀏覽器
不適用:PC版firfox、手機版chrome、iphone 6S plus Safari瀏覽器

小狐狸事務所 提到...

感謝您的回應, 我用 firefox 測試確實設 remote 無作用, 檢查網路發現它在輸入中文時不會傳出參數 q, 而 Chrome/IE 就會. 我一直都僅使用 Chrome, 因此未曾發現此怪現象. 我原以為是當時下載的 easyui 版本太舊, 但將範例 6 改為 CDN 使用官網最新版本問題依舊.

http://mybidrobot.allalla.com/easyuitest/easyui-combobox-6-1.htm

原因有可能是 EASYUI 在跨瀏覽器方面的 BUG, 有待進一步測試.

Unknown 提到...

原始碼失效了>"<

小狐狸事務所 提到...

謝謝留言,我的範例網站原先放在 1freehosting, 這種免費主機三不五時就以濫用免費資源名義關掉我的帳號, 總之, 免費的 ... 最貴. 所幸我有備份, 找時間放到我的付費網站上面. 我已將測試網站重新發布到 tony1966.16mb.com 網域, 不過我打算用 cu.cc 設定轉址後再去改 blogger 文章中的 url, 這需要一點時間, 若您現在要看到這些範例也是 ok 的, 只要將文章中原來的 http://mybidrobot.allalla.com 網域改成 tony1966.16mb.com 即可, 其餘檔案路徑都一樣. 但使用到資料庫部分不會 work, 因為我尚未重建資料表.