网络编程 
首页 > 网络编程 > 浏览文章

JavaScript实现城市选择控件的效果

(编辑:jimmy 日期: 2024/11/26 浏览:3 次 )

在淘宝旅行上看到的城市选择效果,感觉还不错,就自己的理解重新实现一遍,先看效果(有人说IE9下面有BUG,LZ用的是落后的XP,居然装不上IE9,去公司在搞搞好了),然后再细说实现原理,支持鼠标上下键选择城市,支持直接输入城市名称,拼音首字母,全拼,支持IE6遮盖SELECT,压缩后12K。

JavaScript实现城市选择控件的效果

实现的步骤:

一、先用一定的格式罗列出控件所需要的城市以及拼音等,我这里是按照如下格式罗列成一个数组, 如果需要增加城市,直接增加在数组里面即可:

城市我是一个一个手打的。。。

['北京|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对象,在按照相应的事件触发。在生成相应的按照A\B\C\D……分组的时候遇到一个

关于排序的问题,我的对象格式是这样的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'"#">' + 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事件的冒泡,但是这样两个层有同时出现的可能,

// 设置点击文档隐藏弹出的城市选择框 
  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); 
    } 
   }

然后总的JS代码如下:

/* * 
 * ---------------------------------------- * 
 * 城市选择组件 v1.0 
 * Author: VVG 
 * QQ: 83816819 
 * Mail: mysheller@163.com 
 * http://www.cnblogs.com/NNUF/ 
 * ---------------------------------------- * 
 * Date: 2012-07-10 
 * ---------------------------------------- * 
 * */ 
 
/* * 
 * 全局空间 Vcity 
 * */ 
var Vcity = {}; 
/* * 
 * 静态方法集 
 * @name _m 
 * */ 
Vcity._m = { 
 /* 选择元素 */ 
 $:function (arg, context) { 
  var tagAll, n, eles = [], i, sub = arg.substring(1); 
  context = context || document; 
  if (typeof arg == 'string') { 
   switch (arg.charAt(0)) { 
    case '#': 
     return document.getElementById(sub); 
     break; 
    case '.': 
     if (context.getElementsByClassName) return context.getElementsByClassName(sub); 
     tagAll = Vcity._m.$('*', context); 
     n = tagAll.length; 
     for (i = 0; i < n; i++) { 
      if (tagAll[i].className.indexOf(sub) > -1) eles.push(tagAll[i]); 
     } 
     return eles; 
     break; 
    default: 
     return context.getElementsByTagName(arg); 
     break; 
   } 
  } 
 }, 
 
 /* 绑定事件 */ 
 on:function (node, type, handler) { 
  node.addEventListener "(^|\\s+)" + c + "(\\s+|$)", "g"); 
  if(!Vcity._m.hasClass(c,node))return; 
  node.className = reg.test(node.className) "tip">热门城市(支持汉字/拼音)</p>', 
 '<ul>', 
 '<li class="on">热门城市</li>', 
 '<li>ABCDEFGH</li>', 
 '<li>IJKLMNOP</li>', 
 '<li>QRSTUVWXYZ</li>', 
 '</ul>' 
]; 
 
/* * 
 * 城市控件构造函数 
 * @CitySelector 
 * */ 
 
Vcity.CitySelector = function () { 
 this.initialize.apply(this, arguments); 
}; 
 
Vcity.CitySelector.prototype = { 
 
 constructor:Vcity.CitySelector, 
 
 /* 初始化 */ 
 
 initialize :function (options) { 
  var input = options.input; 
  this.input = Vcity._m.$('#'+ input); 
  this.inputEvent(); 
 }, 
 
 /* * 
  * @createWarp 
  * 创建城市BOX HTML 框架 
  * */ 
 
 createWarp:function(){ 
  var inputPos = Vcity._m.getPos(this.input); 
  var div = this.rootDiv = document.createElement('div'); 
  var that = this; 
 
  // 设置DIV阻止冒泡 
  Vcity._m.on(this.rootDiv,'click',function(event){ 
   Vcity._m.stopPropagation(event); 
  }); 
 
  // 设置点击文档隐藏弹出的城市选择框 
  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); 
  }); 
  div.className = 'citySelector'; 
  div.style.position = 'absolute'; 
  div.style.left = inputPos.left + 'px'; 
  div.style.top = inputPos.bottom + 'px'; 
  div.style.zIndex = 999999; 
 
  // 判断是否IE6,如果是IE6需要添加iframe才能遮住SELECT框 
  var isIe = (document.all) "javascript:">' + oCity[key][sortKey[j]][i] + '</a>'; 
     odda.push(str); 
    } 
    odd.innerHTML = odda.join(''); 
    odl.appendChild(odt); 
    odl.appendChild(odd); 
    odiv.appendChild(odl); 
   } 
 
   // 移除热门城市的隐藏CSS 
   Vcity._m.removeClass('hide',this.hot); 
   this.hotCity.appendChild(odiv); 
  } 
  document.body.appendChild(this.rootDiv); 
  /* IE6 */ 
  this.changeIframe(); 
 
  this.tabChange(); 
  this.linkEvent(); 
 }, 
 
 /* * 
  * tab按字母顺序切换 
  * @ tabChange 
  * */ 
 
 tabChange:function(){ 
  var lis = Vcity._m.$('li',this.cityBox); 
  var divs = Vcity._m.$('div',this.hotCity); 
  var that = this; 
  for(var i=0,n=lis.length;i<n;i++){ 
   lis[i].index = i; 
   lis[i].onclick = function(){ 
    for(var j=0;j<n;j++){ 
     Vcity._m.removeClass('on',lis[j]); 
     Vcity._m.addClass('hide',divs[j]); 
    } 
    Vcity._m.addClass('on',this); 
    Vcity._m.removeClass('hide',divs[this.index]); 
    /* IE6 改变TAB的时候 改变Iframe 大小*/ 
    that.changeIframe(); 
   }; 
  } 
 }, 
 
 /* * 
  * 城市LINK事件 
  * @linkEvent 
  * */ 
 
 linkEvent:function(){ 
  var links = Vcity._m.$('a',this.hotCity); 
  var that = this; 
  for(var i=0,n=links.length;i<n;i++){ 
   links[i].onclick = function(){ 
    that.input.value = this.innerHTML; 
    Vcity._m.addClass('hide',that.cityBox); 
    /* 点击城市名的时候隐藏myIframe */ 
    Vcity._m.addClass('hide',that.myIframe); 
   } 
  } 
 }, 
 
 /* * 
  * INPUT城市输入框事件 
  * @inputEvent 
  * */ 
 
 inputEvent:function(){ 
  var that = this; 
  Vcity._m.on(this.input,'click',function(event){ 
   event = event || window.event; 
   if(!that.cityBox){ 
    that.createWarp(); 
   }else if(!!that.cityBox && Vcity._m.hasClass('hide',that.cityBox)){ 
    // slideul 不存在或者 slideul存在但是是隐藏的时候 两者不能共存 
    if(!that.ul || (that.ul && Vcity._m.hasClass('hide',that.ul))){ 
     Vcity._m.removeClass('hide',that.cityBox); 
 
     /* IE6 移除iframe 的hide 样式 */ 
     //alert('click'); 
     Vcity._m.removeClass('hide',that.myIframe); 
     that.changeIframe(); 
    } 
   } 
  }); 
  Vcity._m.on(this.input,'focus',function(){ 
   that.input.select(); 
   if(that.input.value == '城市名') that.input.value = ''; 
  }); 
  Vcity._m.on(this.input,'blur',function(){ 
   if(that.input.value == '') that.input.value = '城市名'; 
  }); 
  Vcity._m.on(this.input,'keyup',function(event){ 
   event = event || window.event; 
   var keycode = event.keyCode; 
   Vcity._m.addClass('hide',that.cityBox); 
   that.createUl(); 
 
   /* 移除iframe 的hide 样式 */ 
   Vcity._m.removeClass('hide',that.myIframe); 
 
   // 下拉菜单显示的时候捕捉按键事件 
   if(that.ul && !Vcity._m.hasClass('hide',that.ul) && !that.isEmpty){ 
    that.KeyboardEvent(event,keycode); 
   } 
  }); 
 }, 
 
 /* * 
  * 生成下拉选择列表 
  * @ createUl 
  * */ 
 
 createUl:function () { 
  //console.log('createUL'); 
  var str; 
  var value = Vcity._m.trim(this.input.value); 
  // 当value不等于空的时候执行 
  if (value !== '') { 
   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); 
    } 
   } 
   this.isEmpty = false; 
   // 如果搜索数据为空 
   if (searchResult.length == 0) { 
    this.isEmpty = true; 
    str = '<li class="empty">对不起,没有找到数据 "<em>' + value + '</em>"</li>'; 
    searchResult.push(str); 
   } 
   // 如果slideul不存在则添加ul 
   if (!this.ul) { 
    var ul = this.ul = document.createElement('ul'); 
    ul.className = 'cityslide'; 
    this.rootDiv && this.rootDiv.appendChild(ul); 
    // 记录按键次数,方向键 
    this.count = 0; 
   } else if (this.ul && Vcity._m.hasClass('hide', this.ul)) { 
    this.count = 0; 
    Vcity._m.removeClass('hide', this.ul); 
   } 
   this.ul.innerHTML = searchResult.join(''); 
 
   /* IE6 */ 
   this.changeIframe(); 
 
   // 绑定Li事件 
   this.liEvent(); 
  }else{ 
   Vcity._m.addClass('hide',this.ul); 
   Vcity._m.removeClass('hide',this.cityBox); 
 
   Vcity._m.removeClass('hide',this.myIframe); 
 
   this.changeIframe(); 
  } 
 }, 
 
 /* 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'; 
 }, 
 
 /* * 
  * 特定键盘事件,上、下、Enter键 
  * @ KeyboardEvent 
  * */ 
 
 KeyboardEvent:function(event,keycode){ 
  var lis = Vcity._m.$('li',this.ul); 
  var len = lis.length; 
  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; 
  } 
 }, 
 
 /* * 
  * 下拉列表的li事件 
  * @ liEvent 
  * */ 
 
 liEvent:function(){ 
  var that = this; 
  var lis = Vcity._m.$('li',this.ul); 
  for(var i = 0,n = lis.length;i < n;i++){ 
   Vcity._m.on(lis[i],'click',function(event){ 
    event = Vcity._m.getEvent(event); 
    var target = Vcity._m.getTarget(event); 
    that.input.value = Vcity.regExChiese.exec(target.innerHTML)[0]; 
    Vcity._m.addClass('hide',that.ul); 
    /* IE6 下拉菜单点击事件 */ 
    Vcity._m.addClass('hide',that.myIframe); 
   }); 
   Vcity._m.on(lis[i],'mouseover',function(event){ 
    event = Vcity._m.getEvent(event); 
    var target = Vcity._m.getTarget(event); 
    Vcity._m.addClass('on',target); 
   }); 
   Vcity._m.on(lis[i],'mouseout',function(event){ 
    event = Vcity._m.getEvent(event); 
    var target = Vcity._m.getTarget(event); 
    Vcity._m.removeClass('on',target); 
   }) 
  } 
 } 
}; 

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

上一篇:JSP技术实现RSS订阅功能的示例
下一篇:JSP技术实现动态页面到静态页面的方法
一句话新闻
一文看懂荣耀MagicBook Pro 16
荣耀猎人回归!七大亮点看懂不只是轻薄本,更是游戏本的MagicBook Pro 16.
人们对于笔记本电脑有一个固有印象:要么轻薄但性能一般,要么性能强劲但笨重臃肿。然而,今年荣耀新推出的MagicBook Pro 16刷新了人们的认知——发布会上,荣耀宣布猎人游戏本正式回归,称其继承了荣耀 HUNTER 基因,并自信地为其打出“轻薄本,更是游戏本”的口号。
众所周知,寻求轻薄本的用户普遍更看重便携性、外观造型、静谧性和打字办公等用机体验,而寻求游戏本的用户则普遍更看重硬件配置、性能释放等硬核指标。把两个看似难以相干的产品融合到一起,我们不禁对它产生了强烈的好奇:作为代表荣耀猎人游戏本的跨界新物种,它究竟做了哪些平衡以兼顾不同人群的各类需求呢?
友情链接:杰晶网络 DDR爱好者之家 南强小屋 黑松山资源网 白云城资源网 网站地图 SiteMap