2013年4月25日 星期四

產生 jQuery UI 日期時間選取器的 PHP 函式

把 Trent Richardson 的 jQuery UI 日期時間選擇器 addon 用法測試一番後, 該寫一個 PHP 函式 get_datetimepicker() 來產生網頁與程式碼, 這樣以後直接呼叫它就萬事 OK 了, 不必再管那些選項設定啦. 設計的目標是 :
  1. 產生我常用的 "2013-04-17 12:10:00" 的格式
  2. 使用預設的滑桿設定時間
  3. 不顯示中間的選取時間 (取消, 還是要顯示為宜, 輸入框有時會被蓋住)
  4. 不顯示下方按扭
  5. 預設繁體中文
這函式主要是由 get_datepicker() 函式增修而得, timepicker 部分是採用  Trent Richardson 的 addon, 請先參考上一篇關於此插件的介紹, 從 Trent Richardson 的網站下載 zip 檔解壓縮後, 將下列檔案複製到專案目錄的 jquery 下 :

jquery-ui-timepicker-addon.js (插件主程式)
jquery-ui-sliderAccess.js (支援按鈕+滑動軸功能)
jquery-ui-timepicker-addon.css (時間選擇器樣式設定)

然後在網頁的 head 中, 除了 jQuery 與 jQuery UI 函式庫外, 還要再匯入這三個檔案. 經過這樣部署後, 在 PHP 檔案中呼叫下列函式就可以產生一個日期時間選擇器了. 注意, 繁體中文化中的時分秒標題已經被我加工過了, 字體加粗並向右移, 這樣版面看起來比較勻稱, 不會離左邊線太近.

<?php echo get_datetimepicker("datetimepicker1") ?>


如何在 jQuery UI 的日期選擇器上附加時間選擇器 timepicker

jQuery UI 的日期選擇器非常容易使用, 但是在專案中我通常要用到 "2013-04-17 12:10:00" 這樣的完整日期時間格式 (例如要設定佈告發布時間, 修改日誌紀錄時間等等), 這樣 jQuery UI 的datepicker 小工具就沒辦法一次到位了, 在官網也找不到時間選擇器, 本來想, 時間部分就用老方法-下拉式選單吧.
上週去受訓時用電腦找了一下, 發現已經有人 (Trent Richardson) 幫 datepicker 加工裝上時間選擇器了. 這個第三方插件 datetimepicker 可在下列網址下載 :

Adding a Timepicker to jQuery UI Datepicker [下載 (zip)]

其實這個 addon 是繼承 jQuery UI 的 datepicker 小工具, 再加上時間選擇器而成, 因此原來的日期選擇器設定方式不變, 但添加了時間選擇器的選項. 下載此插件 zip 檔後解壓縮, 將下列檔案複製到專案目錄的 jquery 下 :

jquery-ui-timepicker-addon.js (插件主程式)
jquery-ui-sliderAccess.js (支援按鈕+滑動軸功能)
jquery-ui-timepicker-addon.css (時間選擇器樣式設定)

然後在網頁的 head 中, 除了 jQuery 與 jQuery UI 函式庫外, 還要再匯入這三個檔案 :
<link href="jquery/jquery-ui-timepicker-addon.css" rel="stylesheet"></link>
<script src="jquery/jquery-ui-timepicker-addon.js" type="text/javascript"></script>
<script src="jquery/jquery-ui-sliderAccess.js" type="text/javascript"></script>

然後如同日期選取器一樣, 放一個 input 文字欄位元素 :

<input id="datetimepicker1" type="text" />

接著呼叫該 input 元素包裹物件元素的 datetimepicker() 方法即可, 如範例 1 所示 :

$('#datetimepicker1').datetimepicker();

測試範例 1 : http://tony1966.xyz/test/jquerytest/datetimepicker_1.htm [看原始碼]

在範例 1 中, 我們並未傳任何參數進去 datetimepicker() 中, 可見預設語言是英文, 預設日期格式 dateFormat 為 "mm/dd/yy", 預設時間格式 timeFormat 為 HH:mm, 兩者以空格串接. 預設時間選擇器類型為 slider (滑動桿), 其滑動步階預設值時分秒均為 1. 當滑動桿移動時, 日曆上的 Time 顯示以及 input 文字欄位中的時或分也會立即改變. 按下日曆左下角的 "Now" 按鈕會填入現在系統的時間, 按下 "Close" 按鈕則關閉日曆.
一般我最常用的完整日期時間格式為 "2013-04-17 12:10:00",  要達到此目標, 日期選擇器部分當然是用選項 {dateFormat: "yy-mm-dd"}, 時間選擇器部分則是需要兩個選項 showSecond 與 timeFormat, 前者預設為 false, 因此不會顯示秒數滑桿, 而後者預設值為 "HH:mm" 不顯示秒數. 在下列範例 2 中, 我們就傳入這三個選項 :

var opt={dateFormat: 'yy-mm-dd',
              showSecond: true,
               timeFormat: 'HH:mm:ss'
               };
$('#datetimepicker1').datetimepicker(opt);

測試範例 2 : http://tony1966.xyz/test/jquerytest/datetimepicker_2.htm [看原始碼]

從範例 2 可知, {showSecond: true} 會讓時間選取器多出秒數滑桿, 而 {timeFormat: 'HH:mm:ss'} 則顯示完整的時分秒格式. 選項 timeFormat 的可用格式如下 :

timeFormat 格式
 H     24 小時制時數, 個位數時前面不補 0
 HH 24 小時制時數 (兩位數), 個位數時前面補 0
 h 12 小時制時數, 個位數時前面不補 0
 hh 12 小時制時數 (兩位數), 個位數時前面補 0
 m 分數, 個位數時前面不補 0
 mm 分數 (兩位數), 個位數時前面補 0
 s 秒數, 個位數時前面不補 0
 ss 秒數 (兩位數), 個位數時前面補 0
 t 早上顯示 "a" 表示 AM, 下午顯示 "p" 表示 PM
 tt 早上顯示 "am" 表示 AM, 下午顯示 "pm" 表示 PM
 T 早上顯示 "A" 表示 AM, 下午顯示 "P" 表示 PM
 TT 早上顯示 "AM" 表示 AM, 下午顯示 "PM" 表示 PM
 z 顯示時區
 l (小寫 L) 毫秒數, 前面補 0
 '' 顯示字串

接下來我們來測試一下不同類型的時間選擇器, datetimepicker 小工具提供三種選擇器類型 (可以利用 controlType 選項客製化), 預設就是上面看到的 slider 滑桿 :

 時間選取器型態 相關選項
 滑動桿 (預設)var opt={stepHour:1, stepMinute:1, stepSecond:1};
 下拉式選單var opt={controlType:"select"};
 滑動桿+按鈕var opt={addSliderAccess:true,sliderAccessArgs:{touchonly:false}};

下列範例 3 我們放了三個日曆來分別測試這三種類型 :


測試範例 3 : http://tony1966.xyz/test/jquerytest/datetimepicker_3.htm [看原始碼]


var opt1={dateFormat: 'yy-mm-dd',
               showSecond: true,
                timeFormat: 'HH:mm:ss',
               stepHour:2, 
               stepMinute:5, 
               stepSecond:10 
               };
var opt2={dateFormat: 'yy-mm-dd',
                showSecond: true,
                timeFormat: 'HH:mm:ss',
                controlType:"select" 
                };
var opt3={dateFormat: 'yy-mm-dd',
               showSecond: true,
               timeFormat: 'HH:mm:ss',
               addSliderAccess:true, 
               sliderAccessArgs:{touchonly:false}  
               };
$('#datetimepicker1').datetimepicker(opt1);
$('#datetimepicker2').datetimepicker(opt2);
$('#datetimepicker3').datetimepicker(opt3);

其中 datetimepicker1 我們使用 stepHour, stepMinute, stepSecond 三個選項來分別設定時分秒三個滑桿滑動時的步階, 秒數每格為 10 秒, 這樣一來就要間隔 10 秒以上按左下角的 "Now" 時, 才會改變 input 內的秒數了. 而 datetimepicker2 則使用 controlType 選項將時間選擇器改為下拉式選單. controlType 預設值為 "slider". 第三個日曆 datetimepicker3 中使用 addSliderAccess 與 sliderAccessArgs 兩個選項, 前者設為 true 表示要加上 +/- 按鈕來控制滑桿, 而後者將 touchonly 設為 false 表示也要顯示滑桿. 我覺得還是預設的單純滑桿簡單好用.

接下來我們要測試四個跟時間預設值有關的選項, hour, minute, second, 以及 defaultValue. 前面三個選項用來設定時間選取器預設值, 而 defaultValue 則是用來設定 input 元素內的時間預設值, 與選擇時間的滑桿無關, 其格式需配合 timeFormat 選項. 這跟日期選取器的 setDate 動作是類似的. 

測試範例 4 : http://tony1966.xyz/test/jquerytest/datetimepicker_4.htm [看原始碼]

功能在範例 4 中我們放置了兩個日曆, 其中 datetimepicker1 以 hour, minute, second 選項設定滑桿的初始值, 而 datetimepicker2 則以 defaultValue 設定 input 元素之初始時間值, 只要一打開日曆, 其值便被設定於 input 欄位內, 但其實這沒甚麼用處. 如果是要修改資料庫中的一個日期欄位, 只要在 PHP 中讀取出來後, 直接放在 input 元素的 value 屬性中就可以了, 沒必要用到 defaultValue, 倒是 hour, minute, second 三個選項比較有用, 可以讓日曆一打開, 滑桿就放在預設位置上了.


var opt1={dateFormat: 'yy-mm-dd',
                 defaultDate: '2013-03-17',
                 showSecond: true,
                 timeFormat: 'HH:mm:ss',
                 hour:13,   
                 minute:25,   
                 second:10  
                 };
var opt2={dateFormat: 'yy-mm-dd',
                defaultDate: '2013-03-17',
                showSecond: true,
                timeFormat: 'HH:mm:ss',
                defaultValue:"13:25:10"  
                };
$('#datetimepicker1').datetimepicker(opt1);
$('#datetimepicker2').datetimepicker(opt2);

接著來看看三個可能會用到的選項 : separator, showTime, 與 showButtonPanel. 前面範例中, 日期與時間是以一個空格串接, 其實這是 separator 選項的預設值為 " " (一個空格) 之故. 其次, showTime 選項是用來控制是否要在日曆中間顯示所選取之時間, 預設是 true. 而 showButtonPanel 則是用來設定是否顯示日曆下方的 "Now" 與 "Close" 兩個按鈕, 預設是 true (其實選項  showButtonPanel 是 datepicker 定義的, 預設是 false 不顯示, 但 datetimepicker 繼承它時改為預設 true). 如果用不到就可以將其設為 false, 這樣日曆版面會比較簡潔. 雖然我認為下方那兩個按鈕不需要, 但日曆中間的時間顯示仍需要保留, 因為有時候日曆會蓋住 input 元素, 導致調整滑桿時不知道現在的調整值, 所以 showTime 還是維持預設 true 較好.

測試範例 5 : http://tony1966.xyz/test/jquerytest/datetimepicker_5.htm [看原始碼]

在範例 5 中, 我們把日期與時間的分隔字元改為 "#", 同時取消中間的時間顯示, 以及下方的兩個按鈕. 這樣日曆看起來就清爽多了吧!

var opt1={dateFormat: 'yy-mm-dd',
                 defaultDate: '2013-03-17',
                 showSecond: true,
                 timeFormat: 'HH:mm:ss',
                 separator: '#',
                 showTime: false,
                 showButtonPanel: false
                };
$('#datetimepicker1').datetimepicker(opt1);

最後來看看時間選擇器的本地化, 這需要以本地語言覆蓋下列 11 個選項的預設值 (英文) : timeOnlyTitle, timeText", hourText, minuteText, secondText, millisecText, timezoneText, currentText, closeText, amNames,  pmNames. 但這 11 個只是 timepicker 部分, 必須同時搭配上半部 datepicker 部分的本地化才完整. 範例 6 為繁體中文化的第一個測試 :

測試範例 6 : http://tony1966.xyz/test/jquerytest/datetimepicker_6.htm [看原始碼]


繁體中文化

var opt={
   //以下為日期選擇器部分
   dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],
   dayNamesMin:["日","一","二","三","四","五","六"],
   monthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],
   monthNamesShort:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],
   prevText:"上月",
   nextText:"次月",
   weekHeader:"週",
   showMonthAfterYear:true,
   dateFormat:"yy-mm-dd",
   //以下為時間選擇器部分
   timeOnlyTitle:"選擇時分秒",
   timeText:"時間",
   hourText:"時",
   minuteText:"分",
   secondText:"秒",
   millisecText:"毫秒",
   timezoneText:"時區",
   currentText:"現在時間",
   closeText:"確定",
   amNames:["上午","AM","A"],
   pmNames:["下午","PM","P"],
   timeFormat:"HH:mm:ss"
   };
$("#datetimepicker1").datetimepicker(opt);


在範例 6 中我們也在 input 元素的 value 屬性中給予初始值, 模擬 PHP 從資料庫提取出來的日期時間欄位 :

<input id="datetimepicker1" type="text" value="2013-04-17 12:10:00"/>

點一下 input 欄位, 日期與時間選擇器都被設定為初始值了.

測試範例 7 : http://tony1966.xyz/test/jquerytest/datetimepicker_7.htm [看原始碼]

上面範例 6 是以直接設定選項方式來本地化, 如果網頁中要用到好幾個 datetimepicker, 每次都要這樣設定就太累了, 因為這個 datetimepicker 也繼承了 setDefault() 方法, 所以可以用 reginal 陣列來儲存本地化選項值, 日期與時間要分別用 datepicker 與 timepicker 設定, 這樣此網頁中的每一個日期時間選擇器都會套用此預設值而本地化 :

繁體中文化 (使用 setDefault)

$.datepicker.regional['zh-TW']={
   dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],
   dayNamesMin:["日","一","二","三","四","五","六"],
   monthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],
   monthNamesShort:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],
   prevText:"上月",
   nextText:"次月",
   weekHeader:"週",
   };
$.timepicker.regional['zh-TW']={
   timeOnlyTitle:"選擇時分秒",
   timeText:"時間",
   hourText:"時",
   minuteText:"分",
   secondText:"秒",
   millisecText:"毫秒",
   timezoneText:"時區",
   currentText:"現在時間",
   closeText:"確定",
   amNames:["上午","AM","A"],
   pmNames:["下午","PM","P"]
   };
$.datepicker.setDefaults($.datepicker.regional["zh-TW"]);
$.timepicker.setDefaults($.timepicker.regional["zh-TW"]);
$("#datetimepicker1").datetimepicker({dateFormat:"yy-mm-dd", 
                                                        timeFormat:"HH:mm:ss",
                                                        showSecond:true
                                                        });

其實 datetimepicker 小工具的作者非常佛心, 早已用上面的 setDefaults() 方法為 timepicker 部分做好許多語言的本地化檔案, 這些都放在解壓縮檔的 localization 目錄下, 例如繁體中文為 jquery-ui-timepicker-zh-TW.js, 韓文則為 jquery-ui-timepicker-ko.js 等等.
我們把本地化 js 檔複製到專案的 jquery 目錄下, 同時在 head 中匯入此本地化檔案, 那麼就不需要自行設定本地化選項了, 連同 datepicker 本身的本地化檔案, 完整的繁體中文的日期時間 widget 需要匯入下列檔案 :

<link href='jquery/jquery-ui-timepicker-addon.css' rel='stylesheet'>
<script type="text/javascript" src="jquery/jquery-ui-timepicker-addon.js"></script>
<script type='text/javascript' src='jquery/jquery-ui-sliderAccess.js'></script>
<script type="text/javascript" src="jquery/jquery.ui.datepicker-zh-TW.js"></script>
<script type="text/javascript" src="jquery/jquery-ui-timepicker-zh-TW.js"></script>

這樣呼叫 datetimepicker() 時就只要傳入需要的選項即可, 不必再管那些本地化選項了 :

$('#datetimepicker1').datetimepicker({showMonthAfterYear: true,
                                                          dateFormat: 'yy-mm-dd',
                                                          timeFormat: 'HH:mm:ss',
                                                          showSecond: true,
                                                          showButtonPanel: false,
                                                          showTime: false
                                                          });

繁體中文的實際測試詳見範例 8.

測試範例 8 : http://tony1966.xyz/test/jquerytest/datetimepicker_8.htm [看原始碼]

其他語言的本地化就不再一一測試, 僅舉韓語為例如範例 9.

<script type="text/javascript" src="jquery/jquery.ui.datepicker-ko.js"></script>
<script type="text/javascript" src="jquery/jquery-ui-timepicker-ko.js"></script>

測試範例 9 : http://tony1966.xyz/test/jquerytest/datetimepicker_9.htm [看原始碼

http://trentrichardson.com/examples/timepicker/

2013年4月20日 星期六

jQuery UI 的自動完成器 autocomplete 測試

本週對 jQuery UI 的自動完成器進行了功能測試, 所謂的自動完成器 autocomplete 就是一個文字輸入框, 當你輸入第一個字元時 (預設), 它就會去比對資料來源, 並且把資料來源中含有該字元的資料顯示在輸入框下方出現的一個下拉式選單中, 這樣使用者就可以從中挑選要填入的資料, 這是對使用者相當友善的設計. 使用者在文字輸入框中輸入的字串有一個術語叫做 term (字眼).
自動完成器的承載容器是一個 input 元素 :

<input id="auto1" type="text" />

但接下來與其他 widget 不同的是, 自動完成器在呼叫 autocomplete() 函式時, 必須傳入 source 選項提供資料來源才行, 否則將因無資料可比對而無作用. 選項 source 有三種形式: 陣列, 字串, 或回呼函式. 首先介紹陣列形式, 這是資料來源為本地 (即程式本身所提供) 時用的形式. 這個陣列有兩種, 第一種文字陣列, 如範例 1 所示 :

測試範例 1 : http://tony1966.xyz/test/jquerytest/autocomplete_1.htm [看原始碼]

範例 1 中我們放了三個 input 元素, 第一個用來輸入股票名稱, 後面兩個用來輸入股票號碼 (但第三個選項是錯誤的示範) :

var stock_name=["味王", "車王電", "葡萄王", "王品", "花王", "緯創", "創意"]; //正確的
var stock_id=["1203", "1533", "1707", "2727", "8906", "3231", "3443"];  //正確的
var stock_id_number=[1203, 1533, 1707, 2727, 8906, 3231, 3443];  //錯誤的
$("#auto1").autocomplete({source: stock_name});
$("#auto2").autocomplete({source: stock_id});
$("#auto3").autocomplete({source: stock_id_number});

當我們在 auto1 輸入 "王" 時, 自動完成器就會把含有 "王" 的股票名稱找出來作為下拉式選單的選項 (共五項), 輸入 "創" 則會找到兩個選項. 特別注意, 陣列的元素必須是文字, 不可以用數值, 否則選項將不會顯示, 這就是 auto3 測試所呈現的結果.

接著在範例 2 要介紹第二種陣列-物件陣列 (其實就是 JSON 格式之陣列), 陣列的每一個元素必須要有 label 與 value 兩個 key (即屬性), 其中 label 用來顯示於下拉式選單中的選項, 必須是字串; 而 value 則是使用者選取後填入 input 欄位中的值, 可以用數值, 但建議一律使用字串. 其次, 要介紹 delay, minlength 這兩個選項的用法. 選項 delay 是向 source 選項指定之資料來源發出請求或擷取動作之前的等待毫秒數. 預設為 300 毫秒 (0.3 秒). 因為透過網路取得取得非本地端資料來源時速度較慢, 利用 delay 延遲可以讓使用者多輸入幾個字元再送出請求可以縮小資料量. 對於本地資料 (陣列), 可以將此選項設為 0 以加快反應時間. 而 minLength 是設定要輸入多少字元才會觸發向 source 選項指定之資料來源擷取資料的動作. 預設值為 1. 當符合的資料量很多時, 可以利用此選項來限縮資料量至合理範圍. 設為 0 時, 只要隨意輸入一個字元再刪除, 就會觸發資料擷取動作, 顯示全部選項.

範例 2 中我們放置了兩個 input 元素 : auto1 與 auto2, 兩個自動完成器的物件陣列之 label 與 value 剛好顛倒, auto1 的 delay 與 minlength 都設為 0, 所以反應時間很快; 而 auto2 的 delay 設為 1000 (1 秒), 因此雖然是本地資料, 感覺反應較慢, 其 minLength 設為 2, 須輸入兩個字元才會觸發自動完成動作. 請注意, 如果是本地端提供的資料, 兩個屬性名稱有沒有加引號都可以, 亦即 label 與 value 或 "label" 與 "value" 均可. 但是, 如果資料是由遠端伺服器提供, 則屬性名稱務必加上引號. 請參考下面的範例 5.

測試範例 2 : http://tony1966.xyz/test/jquerytest/autocomplete_2.htm [看原始碼]

$(document).ready(function(){
var stock_name=[{"label":"味王", "value":1203},
                           {label:"車王電", value:"1533"},
                           {label:"葡萄王", value:"1707"},
                           {label:"王品", value:"2727"},
                           {label:"花王", value:"8906"},
                           {label:"緯創", value:"3231"},
                           {label:"創意", value:"3443"}];
var stock_id=[{label:"1101", value:"台泥"},
                     {label:"1102", value:"亞泥"},
                     {label:"1103", value:"嘉泥"},
                     {label:"2204", value:"中華"},
                     {label:"2207", value:"和泰車"},
                     {label:"2227", value:"裕日車"},
                     {label:"2206", value:"裕隆"}];
$("#auto1").autocomplete({source: stock_name, delay: 0, minLength: 0});
$("#auto2").autocomplete({source: stock_id, delay: 1000, minLength: 2});

從上面兩個範例可知, 字串陣列事實上是物件陣列的一種特例, 也就是說字串陣列是 label=value 的物件陣列, 亦即下列兩個是等效的  :

var stock_name=["味王", "車王電"];
var stock_name=[{label:"味王", value:"味王"}, {label:"車王電", value:"車王電"}];

其實物件陣列不一定同時需要 lable 與 value 這兩個屬性, 單獨一個屬性也是可以的, 只有 label 時, 表示 value 等於 label, 反之亦然, 總之, 其中一個屬性沒提供的話, 就會用另一個代替, 下列範例 3 我們就來測試看看 :

測試範例 3 : http://tony1966.xyz/test/jquerytest/autocomplete_3.htm [看原始碼]


在範例 3 中, 我們放了兩個 input 元素, 第一個 auto1 的物件陣列中, 只有一個屬性 label, 選取後之值就是 label; 而第二個 auto2 則只有一個屬性 value, 顯示的 label 就是 value.

var stock_name=[{label:"味王"},
                             {label:"車王電"},
                             {label:"葡萄王"},
                             {label:"王品"},
                             {label:"花王"},
                             {label:"緯創"},
                             {label:"創意"}];
var stock_id=[{value:"1101"},
                       {value:"1102"},
                       {value:"1103"},
                       {value:"2204"},
                       {value:"2207"},
                       {value:"2227"},
                       {value:"2206"}];
$("#auto1").autocomplete({source: stock_name});
$("#auto2").autocomplete({source: stock_id});

所以跟範例 1 效果一樣, 也就是說, 如果顯示用的 label 與設值的 value 一樣時, 其實用單純的文字陣列就可以了, 沒必要用物件陣列, 只有 "表裡不一" 時才需要用物件陣列, 例如下拉式選單顯示 "2330 (台積電)", 但選取後設定在輸入框的是 2330 (後端資料庫搜尋時的 key).

接著來介紹 source 選項為字串的用法. 這個字串是一個指向遠端伺服器的 URL 字串, 也就是說, 自動完成器內部對於 source 為 URL 的情況內建了一個 Ajax 機制, 向遠端伺服器要求提供資料作為下拉式選單選項的資料來源. 自動完成器會將使用者輸入的字串以名稱為 term 的網頁參數傳給伺服器, 後端的 PHP 程式可以用 $_GET["term"] 或 $_REQUEST["term"] 取得使用者輸入的字眼. 程式傳回的資料格式必須是 JSON, 以 PHP 而言, 可以將回應資料先存入字串陣列中, 最後再呼叫 json_encode() 函式即可轉成 JSON 格式.

在下面的範例 4 我們要測試從遠端資料庫中擷取股票名稱或代號, 顯示於自動完成器的下拉式選單中. 所以要先在 MySQL 中建立一個資料表 stock_list, 它只有兩個文字欄位 : stock_id 與 stock_name, 分別用來儲存股票代號與名稱. 建立此資料表的 SQL 指令如下 :

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;

然後把資料 INSERT 進去, 由於資料很多, 指令僅擷取片段說明 :

INSERT INTO `stocks_list` (`stock_id`, `stock_name`) VALUES
('0015', '富邦'),
('0050', '台灣50'),
('0051', '中100'),
.....

('9955', '佳龍'),
('9958', '世紀鋼');

完整的 SQL 指令檔可從下列 URL 下載 :
stocks_list.sql http://tony1966.xyz/test/jquerytest/stocks_list.sql
只要將此 SQL 指令檔灌進 (載入/輸入) MySQL 資料庫即可.

後端資料庫安排妥當後, 接下來看前端網頁與程式. 在網頁中我們放置了兩個 input 元素 : auto1 用來輸入股名; auto2 用來輸入股號 :

股名 : <input gt="" id="auto1" p="" type="text" />
股號 : <input gt="" id="auto2" p="" type="text" />

Javascript 程式部分, 我們設定兩個自動完成器的 source 屬性為遠端的 PHP 程式, 這兩個程式必須傳回 JSON 格式的回應 :

var url_1="http://tony1966.xyz/test/jquerytest/get_stocks_name.php";
var url_2="http://tony1966.xyz/test/jquerytest/get_stocks_id.php";
$("#auto1").autocomplete({source: url_1});
$("#auto2").autocomplete({source: url_2});

現在剩下兩個後端 PHP 程式了, 先來看一下 get_stocks_name.php, 先連線資料庫, 並取得自動完成器傳出之 term 參數 (即使用者輸入的字串) 以製作 SQL 查詢指令. 注意, 這裡用 LIKE '%大%' 條件來查詢 stock_name 欄位值含有 '大' 者. 然後呼叫 PHP 的 mysql_query 取得記錄集, 再呼叫 mysql_fetch_array() 函式來取得每一列, 再把其中的 stock_name 欄位值儲存在陣列中, 最後呼叫 json_encode() 函式把股名陣列轉成 JSON 格式字串傳回.

<?php
header('Content-Type: text/html;charset=UTF-8');
$host="mysql.1freehosting.com"; //MySQL 主機位址
$username="tony1966_test"; //MySQL 使用者名稱
$password="123456"; //MySQL 使用者密碼
$database="testdb"; //資料庫名稱
$conn=mysql_connect($host, $username, $password); //建立連線
mysql_query("SET NAMES 'utf8'"); //設定查詢所用之字元集為 utf-8
mysql_select_db($database, $conn); //開啟資料庫
$term=$_GET['term'];  //擷取 jQueryUI 傳出參數 'term'
$SQL="SELECT * FROM `stocks_list` WHERE `stock_name` LIKE '%".$term."%'";
$result=mysql_query($SQL, $conn); //執行 SQL 指令
$arr=Array(); //用來儲存 stock_name 欄位值之陣列
for ($i=0; $i < $row=mysql_fetch_array($result); //取得列陣列
     $arr[]=$row["stock_name"]; //放入陣列
     } //end of for
echo json_encode($arr);  //將陣列轉成 JSON 資料格式傳回
?>

另一個 PHP 程式 get_stocks_id.php 只要將 stock_name 改為 stock_id 即可.

測試範例 4 :  http://tony1966.xyz/test/jquerytest/autocomplete_4.htm [看原始碼]

上述範例 4 是股票名稱與代號分開, 現在想要合而為一, 只需要一個文字框, 使用者可以輸入股名或股號, 都會觸發自動完成器的 Ajax 查詢動作, 而且選單會顯示例如 "2330 (台積電)", 選取後文字框會自動填入代號 2330, 這要怎麼做? 其實只要修改一下網頁與後端 PHP 查詢程式即可. 在下列範例 5 中, 網頁中只放置一個 input 元素 :


股名或股號 : <input gt="" id="auto1" p="" type="text" />

而後端 PHP 查詢程式則改為 get_stocks_list.php 如下  :



<?php
header('Content-Type: text/html;charset=UTF-8');
$host="mysql.1freehosting.com"; //MySQL 主機位址
$username="tony1966_test"; //MySQL 使用者名稱
$password="123456"; //MySQL 使用者密碼
$database="testdb"; //資料庫名稱
$conn=mysql_connect($host, $username, $password); //建立連線
mysql_query("SET NAMES 'utf8'"); //設定查詢所用之字元集為 utf-8
mysql_select_db($database, $conn); //開啟資料庫
$term=$_GET['term'];  //擷取 jQueryUI 傳出參數 'term'
$SQL="SELECT * FROM `stocks_list` WHERE `stock_id` LIKE '".$term. 
            "%' OR `stock_name` LIKE '".$term."%'";
$result=mysql_query($SQL, $conn); //執行 SQL 指令
$arr=Array(); //用來儲存 stock_id 欄位值之陣列
for ($i=0; $i < mysql_numrows($result); $i++) { //走訪紀錄集 (列)
     $row=mysql_fetch_array($result); //取得列陣列
     $label=$row["stock_id"]." (".$row["stock_name"].")";
     $value=$row["stock_id"];
     $stock=Array("label" => $label, "value" => $value);
     $arr[]=$stock; //放入陣列
     } //end of for
echo json_encode($arr);  //將陣列轉成 JSON 資料格式傳回
?>

與前面範例 4 不同的是, 此處的 $SQL 指令在搜尋 stock_id 與 
stock_name 時都是比對前面字元符合者, 例如 LIKE '2%' 會搜尋股號以 2 開頭者, 而 LIKE '%大%' 會搜尋股名中以 "大" 開頭者. 
其次, 因為 label 與 value 要求 "表裡不一", 顯示 "2330 (台積電)", 選取後只填入 2330 為值, 因此 source 必須為物件陣列. 這樣在後端程式就必須以二維陣列來儲存 label 與 value, 第一維是索引式陣列, 第二維是關聯式陣列, 以 "label" 與 "value" 當作索引. 這樣在呼叫 json_encode() 函式之後就會轉換成物件陣列了. 上面曾提過, 如果資料來源為遠端伺服器, 則物件陣列的屬性必須加上雙引號 (亦即要用 "label" 與 "value", 而非 label 與 value 或 'label' 與 'value'), 否則會出現 Parse Error 而沒效果, 其實若是呼叫 json_encode(), 這部分不用擔心, 因為我們用關聯式陣列以字串做索引, 呼叫 json_encode() 時就會轉成字串屬性名稱, 而且是用雙引號. 例如搜尋股名中含有 "創" 者, 後端程式 get_stocks_list.php 會傳回如下結果 (共 2 筆) :

http://tony1966.xyz/test/jquerytest/get_stocks_list.php?term=%E5%89%B5

[{"label":"2451 (\u5275\u898b)","value":"2451"},{"label":"3443 (\u5275\u610f)","value":"3443"}]

可見 json_encode() 會把中文轉成字元實體. 關於 json_encode() 的使用, Tsung's Blog 有一篇文章可參考 : "PHP 讓 json_encode() 指定回傳格式".

測試範例 5 :  http://tony1966.xyz/test/jquerytest/autocomplete_5.htm [看原始碼]


但一定要用 json_encode() 嗎? 那倒不一定, 其實土法煉鋼, 自己輸出符合要求的格式字串也行. 我們把上述範例 5 的後端程式 get_stocks_list.php 的後半部修改為下列, 存成 get_stocks_list_2.php 試試看 :


$arr=Array(); //用來儲存 stock_id 欄位值之陣列
for ($i=0;  $i < mysql_numrows($result); $i++) { //走訪紀錄集 (列)
     $row=mysql_fetch_array($result); //取得列陣列
     $label=$row["stock_id"]." (".$row["stock_name"].")";
     $value=$row["stock_id"];
     $arr[]='{"label":"'.$label.'","value":"'.$value.'"}'; //放入陣列
     } //end of for
echo "[".join(",", $arr)."]"; //輸出 JSON 格式字串

同樣向 get_stocks_list_2.php 查詢股名以 "創" 字開頭者, 它的回應如下 :

http://tony1966.xyz/test/jquerytest/get_stocks_list_2.php?term=%E5%89%B5

[{"label":"2451 (創見)","value":"2451"},{"label":"3443 (創意)","value":"3443"}]

這樣也是 OK 的, 請參考範例 6.

測試範例 6 :  http://tony1966.xyz/test/jquerytest/autocomplete_6.htm [看原始碼]

當然如果想要像 json_encode() 那樣把中文改成字元實體也是可以的. 特別注意, 輸出 JSON 格式時, key 的名稱務必用雙引號, 如果把它改為單引號, 那就不行了, 我們只要改範例 6 的後端程式 get_stocks_list_2.php 的倒數第三行為如下, 存成 get_stocks_list_3.php  :

$arr[]="{'label':'".$label."','value':'".$value."'}"; //放入陣列

這時向 get_stocks_list_3.php 查詢股名以 "創" 字開頭者, 可以發現回應的 key 都改為單引號了 :

http://tony1966.xyz/test/jquerytest/get_stocks_list_3.php?term=%E5%89%B5

[{'label':'2451 (創見)','value':'2451'},{'label':'3443 (創意)','value':'3443'}]

以這個回應做成範例 7 可知, 這樣是不行的 :

測試範例 7 :  http://ㄒ/jquerytest/autocomplete_7.htm [看原始碼] (無效)

哇, 光是測試 source=url 就花了這麼多口水, 不過通過這些測試也了解了自動完成器的一些脾氣, 這些在官網卻沒提到. 總結一下 : source 是遠端伺服器時, 回應的 JSON 物件陣列中, 屬性名稱 label 與 value 務必要用雙引號括起來.

下面介紹 source 的第三種類型 : 回呼函式. 據書上說這是自動完成器最強的部份, 因為它的彈性最大, 可以透過回呼函式操控輸出結果. 這個回呼函式有兩個傳入參數 request 與 response, 使用者輸入的字眼 (term) 是透過 request 物件唯一的 term 屬性取得的 :

var term=request.term; //取得使用者輸入的查詢字串

然後函式要產生一個結果陣列 result, 此陣列可以是上述所說的文字陣列或 JSON 物件陣列. 然後呼叫 response() 把結果陣列傳回去就搞定了 :

response(result);

下面範例 8 我們就用一個超簡單功能來測試一下. 從以上測試結果可知, 若資料來源為本地陣列, 比對的方式是 "contains", 亦即陣列元素的 label 是否包含使用者輸入的字眼, 這相當於 SQL 的 LIKE '%字眼%' 條件. 如果要比對起始字串是否符合該怎麼辦呢呢? 哪是有這款的症頭, 不是打 "控八控控" 喔, 而是要把陣列交給回呼函式處理一下.

測試範例 8 :  http://tony1966.xyz/test/jquerytest/autocomplete_8.htm [看原始碼]

範例 8 我們對股名與股號分別放了 input 元素 :


股名 (輸入 "中","台","創") : <input id="auto1" type="text" />
股號 (輸入 "1","2","3") : <input id="auto2" type="text" />

程式部分, 我們只列出 auto1 部分, auto2 的只要把 stock_name 改成 stock_id 即可. 選項 source 設為回呼函式, 並傳入兩個參數 request 與 response. 但這裡的重點是利用正規表達式物件 RegExt() 來過濾原始資料來源, 其中 "^" 是表示以後面的字串起頭之意.  注意, 因為正規式範本含有變數 term, 所以一定要用 RegExp() 產生範本物件, 沒辦法使用 var reg=/^ ... /i 方式. 


var stock_name=["中華電","中碳","台新金","台積電","台塑","創見","創意"];

$("#auto1").autocomplete({source: 

  function(request, response) {
    var term=request.term; //取得使用者輸入字串

    var result=new Array();
    var reg=new RegExp("^" + term,"i"); //以 term 開頭者 

    for (var i=0; i<stock_name.length; i++) {

         var match=reg.test(stock_name[i]);
         if (match) {result.push(stock_name[i]);}               

         }

    response(result);

    }    
  });

接下來我們把範例 8 合而為一改成範例 9, 只用一個 input 元素, 搜尋股名或股號均可 : 

股名或股號 : <input gt="" id="auto1" p="" type="text" />

程式部分, 原始資料我們用物件陣列的方式呈現, 重點是比對符合後, 我們把新的 label (股號+股名) 以及 value 分別設定為物件屬性值, 再推入陣列中傳回 : 

var data=[{label:"中華電", value:"2412"},
                {label:"中碳", value:"1723"},
                {label:"台新金", value:"3045"},   
                {label:"台積電", value:"2330"},       
                {label:"台塑", value:"1301"},           
                {label:"創見", value:"2451"},
                {label:"創意", value:"3443"}];

$("#auto1").autocomplete({source: 

  function(request, response) {

    var term=request.term;

    var result=new Array();

    var reg=new RegExp("^" + term,"i"); //以 term 開頭者 

    for (var i=0; i<data.length; i++) {      
          var match=reg.test(data[i].label) || reg.test(data[i].value);
          if (match) {
            var obj=new Object();
            obj.label=data[i].label + ' (' + data[i].value + ')'; 
            obj.value=data[i].value; 
    result.push(obj);
    }  //end of if           
         } //end of for
    response(result);
    }  //end of function
  });


測試範例 9 :  http://tony1966.xyz/test/jquerytest/autocomplete_9.htm [看原始碼]

以上是以回呼函式處理 (過濾) 本地資料來源的方法. 當然也可以在回呼函式中用 Ajax 從遠端伺服器擷取資料回來後再處理. 不過這樣似乎有點多此一舉, 既然要從遠端伺服器取得資料, 何不將過濾處理的工作全部由伺服器一手包呢 (例如範例 4 我們就將 stock_id 以 term 起頭的過濾作業利用 SQL 完成)? 話是沒錯啦, 如果伺服器程式是自己寫的當然沒問題, 如果不是的話, 例如資料來自於公共資料庫, 你沒辦法要求它回應你要的格式時, 想要呈現客制化格式就要用回呼函式處理了. 
我們用範例 10 來測試在回呼函式中用 Ajax 從遠端伺服器擷取資料回來後再處理的作法. 在範例 6 中, 後端 PHP 程式 get_stocks_list_2.php 會回應如下 JSON 資料 :

[{"label":"2451 (創見)","value":"2451"},{"label":"3443 (創意)","value":"3443"}]

現在假定後端程式只能回應如下資料 :

[{"label":"創見","value":"2451"},{"label":"創意","value":"3443"}]

亦即 label 中沒有附帶股號, 而且這是公用資料庫, 回應格式就是固定這樣沒法改, 我們該如何把取得之資料喬成跟上面有附股號的一樣呢? 先把 get_stocks_list_2.php 修改一下變成 get_stocks_list_4.php, 讓它只回應無股號之 JSON 資料 :

$term=$_GET['term'];  //擷取 jQueryUI 傳出參數 'term'
$SQL="SELECT * FROM `stocks_list` WHERE `stock_id` LIKE '".$term.
            "%' OR `stock_name` LIKE '".$term."%'";
$result=mysql_query($SQL, $conn); //執行 SQL 指令
$arr=Array(); //用來儲存 stock_id 欄位值之陣列
for ($i=0; $i < mysql_numrows($result); $i++) { //走訪紀錄集 (列)
     $row=mysql_fetch_array($result); //取得列陣列
     $label=$row["stock_name"];
     $value=$row["stock_id"];
     $arr[]='{"label":"'.$label.'","value":"'.$value.'"}'; //放入陣列
     } //end of for
echo "[".join(",", $arr)."]";


而前端網頁程式部份, 我們就在選項 source 的回呼函式中, 以 Ajax 擷取 get_stocks_list_4.php 的回應資料加以處理, 如範例 10 所示 :

測試範例 10 :  http://tony1966.xyz/test/jquerytest/autocomplete_10.htm [看原始碼]

未完待續 ...