2015年9月10日 星期四

jQuery自動完成懶人包

前陣子在專案中用了jQuery AutoComplete Plugin,感覺甚好,我想將來很可能會繼續用在其他專案上,索性做了一個範例懶人包以備未來不時之需,利人也利已。(我預估自己大概下個月就會忘記這次是怎麼寫出來的,所以這個懶人包對我來說也粉重要)
放了一個線上的範例網頁讓大家試敲,你可以輸入電子類股的股票代號、中文/英文股票名稱,網頁會提供提示,選取後會在左邊填入股票代號、右邊填入中文名稱。如下圖剖析,其實它是用<ul><li>去建構清單,而我特別框出selectValue,extra,會呼應到稍後提到findValue()函數中取值的邏輯。
程式的簡單說明如下。
前端HTML中主要透過$("...").autocomplete()的方式在Textbox掛上自動完成功能: 
(註: 原版的jquery.autocomplete.js有中文相容的問題,所以我用的是對岸網友修改過的版本,我自己則加了一個noCache參數。文件上說cacheLength設為1就不會做Cache,但我觀察結果似乎不然,也許是版本不同所致。當資料筆數很多時,Cache在Script Side的做法就會變得不合適,因此我加了一個參數鋸箭解決Cache問題。)
排版顯示純文字
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>jQuery自動輸入完成懶人包</title>
    <script src="jquery-1.3.2.js" type="text/javascript"></script>
    <script src="jquery.autocomplete.js" type="text/javascript"></script>
    <link href="jquery.autocomplete.css" rel="stylesheet" type="text/css" />
    <style type="text/css">
    input 
    {
        width: 60px;
        margin-left: 5px;
    }
    </style>
    <script type="text/javascript">
        $(function() {
//選項說明: http://docs.jquery.com/Plugins/Autocomplete/autocomplete#url_or_dataoptions
            $("#txtSymbol").autocomplete("ACDataSrc.aspx",
            {
                delay: 10,
                width: 120,
                minChars: 1, //至少輸入幾個字元才開始給提示?
                matchSubset: false,
                matchContains: false,
                cacheLength: 0, 
                noCache: true, //黑暗版自訂參數,每次都重新連後端查詢(適用總資料筆數很多時)
                onItemSelect: findValue,
                onFindValue: findValue,
                formatItem: function(row) {
                    return "<div style='height:12px'><div style='float:left'>" + row[0] +
                            "</div><div style='float:right;padding-right:5px;'>" +
                            row[1] + "/" + row[2] + "</div></div>";
                },
                autoFill: false,
                mustMatch: true //是否允許輸入提示清單上沒有的值?
            });
            function findValue(li) {
                if (li == null) return alert("No match!");
                $("#txtSymbol").val(li.extra[0]);
                $("#txtCName").val(li.extra[1]);
            }
        });
    </script>
</head>
<body>
<input type="text" id="txtSymbol" />
<input type="text" id="txtCName" readonly="readonly" style="background-color: #cccccc;" />
</body>
</html>
另外,我寫了一隻ACDataSrc.aspx負責餵資料: (為力求單純,股票資料是用字串列出股票清單,實務上應該會以資料庫查詢取代之)
排版顯示純文字
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;
using System.IO;
 
public partial class jQueryAutoComp_ACDataSrc : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        //使用者目前輸入的文字預設以q傳入
        string q = Request["q"] ?? "";
        if (q.Length > 0)
        {
            DataTable t = getStockData();
            DataView dv = new DataView(t);
            //利用LIKE做查詢
            dv.RowFilter = "Key LIKE '" + q.Replace("'", "''") + "%'";
            dv.Sort = "Key, Symbol";
            List<string> lst = new List<string>();
            lst.Add("");
            foreach (DataRowView drv in dv)
            {
                DataRow r = drv.Row;
                //組裝出前端要用的欄位
                lst.Add(string.Format("{0}|{1}|{2}", r["key"], r["symbol"], r["cname"]));
                if (lst.Count >= 10) break;
            }
            //每筆資料間以換行分隔
            Response.Write(string.Join("\n", lst.ToArray()));
        }
    }
 
    private DataTable getStockData()
    {
        #region 股票代號
        string rawData = @"
1435    中福    C.F.C.Y.CORP.
1437    勤益    GTM
...省略...
8249    菱光    CSI  
9912    偉聯    AIC";
        #endregion
 
        //如果資料量未多到誇張,將DataTable Cached住
        string CACHE_KEY = "StkTable";
        if (Cache[CACHE_KEY] == null)
        {
            DataTable t = new DataTable();
            t.Columns.Add("Key", typeof(string));
            t.Columns.Add("Symbol", typeof(string));
            t.Columns.Add("CName", typeof(string));
            t.Columns.Add("EName", typeof(string));
            //測試時由字串取得資料,實務上會去查DB
            StringReader sr = new StringReader(rawData);
            string line = null;
            while ((line = sr.ReadLine()) != null)
            {
                string[] p = line.Split('\t');
                if (p.Length != 3) continue;
                //分別以Symbol, CName, EName作Key
                //輸入中英文及代號都可以查
                t.Rows.Add(p[0], p[0], p[1], p[2]);
                t.Rows.Add(p[1], p[0], p[1], p[2]);
                t.Rows.Add(p[2], p[0], p[1], p[2]);
            }
            //放入Cache,保存兩小時
            Cache.Add(CACHE_KEY, t, null, DateTime.Now.AddHours(2),
                System.Web.Caching.Cache.NoSlidingExpiration,
                System.Web.Caching.CacheItemPriority.Normal, null);
        }
        return Cache[CACHE_KEY] as DataTable;
    }
}
實作細節請大家看程式碼自行揣摩,應沒什麼深奧的學問在裡面。如果有什麼對程式架構或寫法方面的指教,歡迎提出來大家切磋。
想抓整包回去玩的朋友,請按這裡下載。