在淘宝旅行上看到的城市选择效果,感觉还不错,就自己的理解重新实现一遍,先看效果(有人说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对象,在按照相应的事件触发。在生成相应的按照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);
})
}
}
};
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
