JavaScript打造城市选择控件(1)
在淘宝旅行上看到的城市选择效果,感觉还不错,就自己的理解重新实现一遍,先看效果(有人说IE9下面有BUG,LZ用的是落后的XP,居然装不上IE9,去公司在搞搞好了),然后再细说实现原理,支持鼠标上下键选择城市,支持直接输入城市名称,拼音首字母,全拼,支持IE6遮盖SELECT,压缩后12K,
我实现的步骤:
一、先用一定的格式罗列出控件所需要的城市以及拼音等,我这里是按照如下格式罗列成一个数组, 如果需要增加城市,直接增加在数组里面即可:
城市我是一个一个手打的。。。
- ['北京|beijing|bj','上海|shanghai|sh', '重庆|chongqing|cq']
二、因为控件的城市分组按好几类划分,比如:按首字母HOT 、ABCDEFH 、 IJKLMNOP 、 QRSTUVWXYZ 四组划分,
而划分了四组后又按照了首字母划分,所以我用正则表达式和循环把数组重新格式化为一个分组对象,热门城市取前16条。
对象格式如下:
- {HOT:{hot:[]},ABCDEFGH:{a:[1,2,3],b:[1,2,3]},IJKLMNOP:{i:[1.2.3],j:[1,2,3]},QRSTUVWXYZ:{}}
所用代码如下:
- /* *
- * 格式化城市数组为对象oCity,按照a-h,i-p,q-z,hot热门城市分组:
- * {HOT:{hot:[]},ABCDEFGH:{a:[1,2,3],b:[1,2,3]},IJKLMNOP:{i:[1.2.3],j:[1,2,3]},QRSTUVWXYZ:{}}
- * */
- (function () {
- var citys = Vcity.allCity, match, letter,
- regEx = Vcity.regEx,
- reg2 = /^[a-h]$/i, reg3 = /^[i-p]$/i, reg4 = /^[q-z]$/i;
- if (!Vcity.oCity) {
- Vcity.oCity = {hot:{},ABCDEFGH:{}, IJKLMNOP:{}, QRSTUVWXYZ:{}};
- //console.log(citys.length);
- for (var i = 0, n = citys.length; i < n; i++) {
- match = regEx.exec(citys[i]);
- letter = match[3].toUpperCase();
- if (reg2.test(letter)) {
- if (!Vcity.oCity.ABCDEFGH[letter]) Vcity.oCity.ABCDEFGH[letter] = [];
- Vcity.oCity.ABCDEFGH[letter].push(match[1]);
- } else if (reg3.test(letter)) {
- if (!Vcity.oCity.IJKLMNOP[letter]) Vcity.oCity.IJKLMNOP[letter] = [];
- Vcity.oCity.IJKLMNOP[letter].push(match[1]);
- } else if (reg4.test(letter)) {
- if (!Vcity.oCity.QRSTUVWXYZ[letter]) Vcity.oCity.QRSTUVWXYZ[letter] = [];
- Vcity.oCity.QRSTUVWXYZ[letter].push(match[1]);
- }
- /* 热门城市 前16条 */
- if(i<16){
- if(!Vcity.oCity.hot['hot']) Vcity.oCity.hot['hot'] = [];
- Vcity.oCity.hot['hot'].push(match[1]);
- }
- }
- }
- })();
三、然后先照着淘宝旅行里面的样子弄出HTML与CSS;这里略过。
四、然后开始建立CitySelector构造函数,根据城市对象,构建生成DOM对象,在按照相应的事件触发。在生成相应的按照ABCD……分组的时候遇到一个
关于排序的问题,我的对象格式是这样的ABCDEFGH:{a:[1,2,3],b:[1,2,3],c:[1,2,3]},里面的小数组要按照字母的顺序排序,但是我用for……in循环生成
出来是乱的,咨询了群里的高人后,处理方法如下:这里单独把KEY拿出来组成一个数组,然后排序后,在根据数组的值作为KEY值,来读取对象!
- sortKey=[];
- for(ckey in oCity[key]){
- sortKey.push(ckey);
- // ckey按照ABCDEDG顺序排序
- sortKey.sort();
- }
- for(var j=0,k = sortKey.length;j<k;j++){
- odl = document.createElement('dl');
- odt = document.createElement('dt');
- odd = document.createElement('dd');
- odt.innerHTML = sortKey[j] == 'hot'?' ':sortKey[j];
- odda = [];
- for(var i=0,n=oCity[key][sortKey[j]].length;i<n;i++){
- str = '<a href="#">' + oCity[key][sortKey[j]][i] + '</a>';
- odda.push(str);
- }
五、鼠标上下键移动选择城市的处理方法:在城市弹出后记录一个this.count = 0;然后再获取上下键的按键事件中分别对count值加一或者减一,
当然count的最大值不能大于筛选出来的城市数组的长度,超过长度后归0,小于0后赋值最大值,然后把this.count的值,来作为数组的标获取相应的城市
项:
- switch(keycode){
- case 40: //向下箭头↓
- this.count++;
- if(this.count > len-1) this.count = 0;
- for(var i=0;i<len;i++){
- Vcity._m.removeClass('on',lis[i]);
- }
- Vcity._m.addClass('on',lis[this.count]);
- break;
- case 38: //向上箭头↑
- this.count--;
- if(this.count<0) this.count = len-1;
- for(i=0;i<len;i++){
- Vcity._m.removeClass('on',lis[i]);
- }
- Vcity._m.addClass('on',lis[this.count]);
- break;
- case 13: // enter键
- this.input.value = Vcity.regExChiese.exec(lis[this.count].innerHTML)[0];
- Vcity._m.addClass('hide',this.ul);
- Vcity._m.addClass('hide',this.ul);
- /* IE6 */
- Vcity._m.addClass('hide',this.myIframe);
- break;
- default:
- break;
- }
六、IE中对SELECT的遮挡也是一个增加代码的地方,因为城市弹出框的大小是变化的,然后下拉的城市列也是根据筛选出来的值而变化,所以得每操作一个变化的
地方的时候就重新给iframe设置长度和宽度,苦逼的处理方法啊,所以就多了这样一个方法,然后在改变尺寸的时候,应用一下就可以了。
- /* IE6的改变遮罩SELECT 的 IFRAME尺寸大小 */
- changeIframe:function(){
- if(!this.isIE6)return;
- this.myIframe.style.width = this.rootDiv.offsetWidth + 'px';
- this.myIframe.style.height = this.rootDiv.offsetHeight + 'px';
- },
7、弹出框的取消问题,这个问题最开始我是设置document的click事件关闭层,然后再弹出的层上阻止click事件的冒泡,但是这样两个层有同时出现的可能,
如这个反例:http://www.cnblogs.com/NNUF/archive/2012/06/24/2560557.html ; 这里要感谢一下JS丛林群里面的‘搞搞破鞋’同学的方法,取消INPUT对
click的冒泡,然后关键是如下红色字体:
- // 设置点击文档隐藏弹出的城市选择框
- Vcity._m.on(document, 'click', function (event) {
- event = Vcity._m.getEvent(event);
- var target = Vcity._m.getTarget(event);
- if(target == that.input) return false;
- //console.log(target.className);
- if (that.cityBox)Vcity._m.addClass('hide', that.cityBox);
- if (that.ul)Vcity._m.addClass('hide', that.ul);
- if(that.myIframe)Vcity._m.addClass('hide',that.myIframe);
- });
8、输入框输入拼音或者文字或者拼音首字母筛选城市,这个就是直接用正则表达式在最开始的数组里面筛选数据即可:
- var reg = new RegExp("^" + value + "||" + value, 'gi');
- var searchResult = [];
- for (var i = 0, n = Vcity.allCity.length; i < n; i++) {
- if (reg.test(Vcity.allCity[i])) {
- var match = Vcity.regEx.exec(Vcity.allCity[i]);
- if (searchResult.length !== 0) {
- str = '<li><b class="cityname">' + match[1] + '</b><b class="cityspell">' + match[2] + '</b></li>';
- } else {
- str = '<li class="on"><b class="cityname">' + match[1] + '</b><b class="cityspell">' + match[2] + '</b></li>';
- }
- searchResult.push(str);
- }
- }







