AutoComplete应该不是很陌生了,网上也有好多开源的js。今天主要的不是研究Autocomplete这个js的实现。今天主要讲的是将这个js做成一插件。那么今天主要用到的
- js插件jquery-UI-AutoComplete ,jquery-1.9.1.js
- C#技术:反射,Attribute。
演示性例子
做好了上面准备工作,那我们来开始我们的代码编写。
1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2: <html xmlns="http://www.w3.org/1999/xhtml">
3: <head>
4: <title></title>
5: <script src="../Scripts/jquery-1.9.1.js" type="text/javascript"></script>
6: <!-- 这是从jquery-UI-官网上下载的-->
7: <script src="../Scripts/jquery-ui.custom.js" type="text/javascript"></script>
8: <link href="../Styles/jquery-ui.css" rel="stylesheet" type="text/css" />
9: <script type="text/javascript">
10: $(function () {
11: var arry = ["cnblogs", "博客园", "hankskfc"];
12: $("#Text1").autocomplete({
13: source: arry
14: });
15: });
16: </script>
17: </head>
18: <body>
19: <input id="Text1" type="text" />
20: </body>
21: </html>
下面是运行结果图:
相信这个应该看着官网的都可以写出来。或者可以参考院里面的一篇文章:jQuery UI Autocomplete 体验
这个自己作为练习倒是可以玩玩的,真正要用到项目中去总显得有点臃肿或者说让人用的不爽。我们希望在适量的配置下,只需要对“<input id="Text1" type="text" />”这个添加少量“属性”就可以自动具备上述功能。
思考与探索
首先我们从“数据”提供方面思考。在此引用下jQuery UI Autocomplete 体验 下的东西。
jQuery UI Autocomplete主要支持字符串Array、JSON两种数据格式。
普通的Array格式没有什么特殊的,如下:
1: ["cnblogs","博客园","囧月"]
对于JSON格式的Array,则要求有:label、value属性,如下:
1: [{label: "博客园", value: "cnblogs"}, {label: "囧月", value: "囧月"}]
其中label属性用于显示在autocomplete弹出菜单,而value属性则是选中后给文本框赋的值。
如果没有指定其中一个属性则用另一个属性替代(即value和label值一样),如下:
1: [{label: "cnblogs"}, {label: "囧月"}]
2: [{value: "cnblogs"}, {value: "囧月"}]
如果label和value都没有指定,则无法用于autocomplete的提示。
另外需要注意,对于从服务器端输出的JSON的key必须用双引号,如下:
1: [{"label": "博客园", "value": "cnblogs"}, {"label": "囧月", "value": "囧月"}]
否则可能会出现parsererror错误。
“囧月”博主总结的很好,很强大。好了我们知道了autocomplete的支持数据类型,联想下实际项目中一般以List<T>形式得到数据库搜索出来的数据或者其他集合形式这里不一一列举了。
那么这里就有个问题怎么样才可以准换成它支持的Json格式呢,你可以每次都自己手动转。这样会用会让人很不爽,有种砸键盘的感觉。想必大家用过mvc这一类框架,里面Model层的话都会在列上打上一个“特定的标签”-Attribute,这里就引出了另一个技术“反射”。那么就呈上源码:
Attribute
1: public class AutoCompleteCustomAttribute : Attribute
2: {
3: public string ColName { get; private set; }
4: public AutoCompleteCustomAttribute(string colName)
5: {
6: ColName = colName;
7: }
8: }
1: public class AutoCompleteLabelAttribute : Attribute
2: {
3: }
1: public class AutoCompleteValueAttribute : Attribute
2: {
3: }
反射实现ToJson
1: public static class ConvertLib
2: {
3: public static string ConvertToAutoJson<T>(IList<T> instance) where T : new()
4: {
5: const string result = "[{0}]";
6: var type = typeof(T);
7: var sb = new StringBuilder();
8: foreach (var ins in instance)
9: {
10: var label = string.Empty;
11: var value = string.Empty;
12: var custom = string.Empty;
13: sb.Append("{");
14: #region Body
15: foreach (var prop in type.GetProperties())
16: {
17: var attrLabels = prop.GetCustomAttributes(typeof(AutoCompleteLabelAttribute), true);
18: var hasLabel = attrLabels.Length > 0;
19: if (hasLabel)
20: {
21: label = prop.GetValue(ins, null).ToString();
22: }
23:
24: var attrValues = prop.GetCustomAttributes(typeof(AutoCompleteValueAttribute), true);
25: var hasValue = attrValues.Length > 0;
26: if (hasValue)
27: {
28: value = prop.GetValue(ins, null).ToString();
29: }
30:
31: var attrCustoms = prop.GetCustomAttributes(typeof(AutoCompleteCustomAttribute), true);
32: var hasCustom = attrCustoms.Length > 0;
33: if (!hasCustom) continue;
34: var attr = attrCustoms[0] as AutoCompleteCustomAttribute;
35: if (attr != null)
36: {
37: custom += """+attr.ColName + "":"" + prop.GetValue(ins, null) + "",";
38: }
39: }
40: sb.Append(""label":"" + label + "",");
41: sb.Append(""value":"" + value + """);
42: if (!string.IsNullOrEmpty(custom))
43: {
44: custom = custom.Remove(custom.Length - 1);
45: sb.Append(", " + custom + "");
46: }
47: #endregion
48: sb.Append("},");
49: }
50: sb = sb.Remove(sb.Length - 1, 1);
51: return string.Format(result, sb);
52: }
53: }
主要思想:遍历集合的每一个T类型的属性,然后看看每个属性是否包含我们设定的“三个特性”,有则按照格式拼接字符串。
(注意:AutoCompleteLabelAttribute,AutoCompleteValueAttribute按照要的json格式只能出现一次,然而AutoCompleteCustomAttribute可以出现多次。还有要注意的是引号问题即40,41,37行,为此我花费了一天查问题郁闷呢!!)
1: //我们就可以这样讲一个集合格式化成我们要的Json字符串。
2: //妈妈再也不用担心转Json了~~
3: var json = ConvertLib.ConvertToAutoJson(T);
问题二
上文中调用转Json中有个问题:你必须知道要转类型即上文的”T”。由于不知道T,我们要么建立好多个一般处理文件,每个一般处理文件都是向上文一样调用。这又让我们用起来不爽了,凭什么每次要对某个业务自动提示就创建一个一般处理程序呢?很不爽呢~~。
那么我们只好在想一些其他的办法,仔细想想MVC Route的添加。都是在一个类似Global.asax文件添加路径,能不能就这个提供一种思路呢?
无论创建多少个一般处理程序,代码结构基本相似,就缺个知道要处理关于那个业务的搜索。那我们可以写个容器来管理我们要求的业务和执行代码的映射,我们只需要在开始开启网站的时候注册下(Global.asax)。
代码如下:
interface
1: public interface IHandleAutoJson
2: {
3: string GetAutoJson(string term);
4: }
容器类
1: public class AutoCompleteConfig
2: {
3: /// <summary>
4: /// 用来保存配置文件的数据字典eg:["nickname":"product"]
5: /// </summary>
6: private static readonly Dictionary<string, Type> AutoCompleteDict = new Dictionary<string, Type>();
7:
8: private static readonly object LockObj = new object();
9:
10: public static Type GetHandler(string nickName)
11: {
12: Type typ = null;
13: if (AutoCompleteDict.ContainsKey(nickName))
14: {
15: typ = AutoCompleteDict[nickName];
16: }
17: return typ;
18: }
19:
20: public static void AddAutoCompleteHandler(string nickName, Type typ)
21: {
22: lock (LockObj)
23: {
24: if (AutoCompleteDict.ContainsKey(nickName))
25: {
26: AutoCompleteDict[nickName] = typ;
27: }
28: else
29: {
30: AutoCompleteDict.Add(nickName, typ);
31: }
32: }
33:
34: }
35:
36: public static void RemoveAutoCompleteHandler(string nickName)
37: {
38: lock (LockObj)
39: {
40: AutoCompleteDict.Remove(nickName);
41: }
42: }
43: }
代码很简单,就是对容器的Add,Remove,Select操作进行简单的封装。
然后是注册代码:
1: AutoCompleteConfig.AddAutoCompleteHandler("product", typeof(AutoCompleteProduct));
然后我们的只要建立一个一般处理程序,要让它和AutoCompleteConfig发生关系只需要一个工厂就行。
1: public class AutoCompleteFactory
2: {
3: public static IHandleAutoJson GetHandler(string nickName)
4: {
5: IHandleAutoJson json = null;
6: var typ = AutoCompleteConfig.GetHandler(nickName);
7: if (typ != null && !typ.IsAbstract && typeof(IHandleAutoJson).IsAssignableFrom(typ))
8: {
9: var obj = Activator.CreateInstance(typ) as IHandleAutoJson;
10: json = obj;
11: }
12: return json;
13: }
14: }
这样我们的调用形式就变成了:
1: var handler = AutoCompleteFactory.GetHandler(nickName);//这里是业务对应别名,也是唯一的。
2: if (handler!=null)
3: {
4: json = handler.GetAutoJson(term);
5: }
到这里数据提供应该算差不多了,接下来就应该是js块了。
Js调用形式思考
我们希望的形式只是在“<input id="Text1" type="text" />”添加少量的属性,那这个该如何实现呢。
我们需要哪些属性呢?马上想到的是nickName,因为上文中提到是对应于某个业务的。还有没有呢,还缺个class=“autoC”(这里的可以随便取)。
然后我们只需要:
1: $(".autoC").each(function () {.....});
将每个需要的text都绑定autocomplete就可以了。
下面就是js代码:
1: //用于动态加载css和javascript
2: $.extend({
3: includePath: '',
4: include: function (file) {
5: var files = typeof file == "string" ? [file] : file;
6: for (var i = 0; i < files.length; i++) {
7: var name = files[i].replace(/^s|s$/g, "");
8: var att = name.split('.');
9: var ext = att[att.length - 1].toLowerCase();
10: var isCSS = ext == "css";
11: var tag = isCSS ? "link" : "script";
12: var attr = isCSS ? " type='text/css' rel='stylesheet' " : " language='javascript' type='text/javascript' ";
13: var link = (isCSS ? "href" : "src") + "='" + $.includePath + name + "'";
14: if ($(tag + "[" + link + "]").length == 0) $(document.head).append("<" + tag + attr + link + "></" + tag + ">");
15: }
16: }
17: });
用于window onload时候动态绑定每一“<input id="Text1" type="text" />”
1: //需要在window.onload时候绑定
2: //每个要添加 textbox需要添加属性 class="autoC" nickName="product"
3: var BindAutoComplete = (function () {
4: var scriptPath = JsConfig.GetConfig("baseUrl") + "Scripts/";
5: var stylePath = JsConfig.GetConfig("baseUrl") + "Styles/";
6: var handlePath = JsConfig.GetConfig("baseUrl") + "Handle/";
7: var bindData = function () {
8: $.include([scriptPath + 'jquery-ui.custom.js', stylePath + 'jquery-ui.css']);
9:
10: $(".autoC").each(function () {
11: var nickName = $(this).attr("nickName");
12: var url = handlePath + 'AutoComplete.ashx?nick=' + nickName;
13: $(this).autocomplete({
14: source: url,
15: minLength: 1,
16: select: function (event, ui) {
17: this.value = ui.item.value;
18: }
19: });
20: });
21: };
22:
23: return {
24: BindData: bindData
25: };
26: })();
Jsconfig:主要发布到IIS上可能有虚拟路径问题,从而添加了这个。
1: //JS配置全局静态类
2: var JsConfig = (function () {
3: var constants = {
4: baseUrl: "http://localhost:3694/"
5: };
6: var returnObj = {
7: GetConfig: function (name) {
8: return constants[name];
9: }
10: };
11: return returnObj;
12: })();
页面调用
1: <script src="../Scripts/jquery-1.9.1.js" type="text/javascript"></script>
2: <script src="../JsConfig.js" type="text/javascript"></script>
3: <script src="../Scripts/AddScript.js" type="text/javascript"></script>
4: <script src="../Scripts/AutoComplete/BindAutoComplete.js" type="text/javascript"></script>
5: <script type="text/javascript">
6: $(function () {
7: BindAutoComplete.BindData();
8: });
9: </script>
好了一个Autocomplete 插件做好了。
源码整理: