在创建一个自定义样式的select(1)中我们成功的为select隐去其原来的样式,创造了伪装起来的外观,并且看上去还过得去,现在我们将真正为些自定义select绑定行为。
首先,我们需要一个构造函数,以生成伪装的select样式。
我们将其命名为BB.select,然后我们需要四个方法:
init() 负责生成基本的HTML结构,绑定事件,初始化整个select的行为。
click() 模拟点击select元素时弹出列表的行为
liClick() 模拟option上的点击事件,并返回值到真正的select元素内
hideList() options列表弹出时,用户在除options以外的地方点击鼠标时,则options列表要收回
在最底部我们还在document对像上绑定了click和scroll事件,用于检测并激活hideList()的行为。
基本结构如下:
var BB = {};
BB.select = function(oObj){
this.oObj = oObj; //select
this.width = oObj.offsetWidth; //select宽度
this.tObjText; //select中显示的文字
this.relUL; //与select相关联的ulList列表id
var that = this;
if (typeof BB.select._initialized == 'undefined') {
BB.select.prototype.init = function(){
//初始化模拟出来的ul列表,绑定click事件
};
BB.select.prototype.click = function(){
//模拟select的点击事件
};
BB.select.prototype.liClick = function(){
//列表项的点击事件,返回值到select中
};
BB.select.prototype.hideList = function(e){
//在列表外位置点击或者滚动页面时,隐藏列表
};
(function(){
//在document对像上绑定mousedown和scroll事件,以实现点击其它区域时select消失,只需绑定一次
BB.EVENT.addEvent('mousedown', that.hideList);
BB.EVENT.addEvent('scroll', that.hideList);
})();
BB.select._initialized = 1;
}
this.init();
}
先是init()函数:
为了生成下图的这种结构:
我们并不需要在每个select外面手工的写入,我们只需要在init()函数里加入如下代码:
var nSelectIndex = 0;
for (var i = this.oObj.options.length - 1; i >= 0; i--) {
if (this.oObj.options[i].selected) {
nSelectIndex = i;
}
}
this.tObjText = this.oObj.options[nSelectIndex].innerHTML;
//对应处于选中状态的option里的文字内容
//外层套入两个span模拟select的外观效果
var selectIn, selectOut, textNode;
selectIn = document.createElement('span');
selectOut = document.createElement('span');
textNode = document.createElement('span');
selectOut.className = 'selectOut';
selectIn.className = 'selectIn';
selectIn.style.width = this.width - 25 + 'px';
//根据select框的宽度对应生成
selectIn.style.padding = '0 24px 0 6px';
textNode.innerHTML = this.tObjText;
selectIn.appendChild(textNode);
selectOut.appendChild(selectIn);
this.oObj.parentNode.insertBefore(selectOut, this.oObj);
selectIn.appendChild(this.oObj);
this.oObj.className = 'noShow';
接下来,我们将根据select对应的options对像的内容生成一ul>li列表来模拟options
//生成一个名为oObj.id + 'List'的ul列表
var oDiv = document.createElement('div');
oDiv.id = 'ulListFor' + this.oObj.id;
oDiv.className = 'selectUlList';
oDiv.style.width = this.width - 5 + 'px';
oDiv.style.height = '130px';
var oUl = document.createElement('ul');
var nOptionLength = oObj.options.length;
for (var i = 0; i < nOptionLength; i++) {
var oLi = document.createElement('li');
oLi.setAttribute("onmouseover", "this.className = 'active'");
oLi.setAttribute("onmouseout", "this.className = ''");
//oLi.setAttribute('value', oObj.options[i].value);
oLi.appendChild(document.createTextNode(this.oObj.options[i].innerHTML));
oUl.appendChild(oLi);
BTO.EVENT.addEvent('click', this.liClick(), oLi);
}
oDiv.appendChild(oUl);
我们再将此ul列表写入到对应select的位置,并在selectOut元素上绑定click()事件
//追加此ulList到页面中
selectOut.parentNode.appendChild(oDiv);
this.relUL = oDiv; //与select相关联的ulList列表id
//为其绑定onclick事件
BTO.EVENT.addEvent('click', this.click(), selectOut);
//在document对像上绑定mousedown和scroll事件,以实现点击其它区域时select消失
if (!this.onceEvent) {
BTO.EVENT.addEvent('mousedown', that.hideList);
BTO.EVENT.addEvent('scroll', that.hideList);
}
而click事件则非常简单:
- 根据当前点击的selectOut算出坐标
- 把对应的ul列表显示在对应的位置
BB.select.prototype.click = function(){
//模拟select的点击事件
var that = this;
return function(){
if (that.relUL.style.display == '') {
var absOffset = getAbsPos(that.oObj);
//到到绝对坐标
that.relUL.style.top = absOffset.y + 'px';
that.relUL.style.left = absOffset.x - 5 + 'px';
that.relUL.style.zIndex = '99';
that.relUL.style.display = 'block';
} else {
that.relUL.style.display = '';
}
}
};
而在li的点击事件,则需要如下做:
- 遍历与ul列表对应的select.options对像
- 找到与之相等的option
- 将此option标记为已选中
- 将其它option的已选中状态去掉(因option可以存在多选)
- 更新显示的文字
- 隐藏ul列表
BB.select.prototype.liClick = function(){
//列表项的点击事件,返回值到select中
var that = this;
return function(e){
var e = e || window.event;
var obj = e.srcElement || e.target;
var oOption = that.oObj.options;
for (var i = oOption.length - 1; i >= 0; i--) {
if (obj.innerHTML == oOption[i].innerHTML) {
//设置选中项
oOption[i].setAttribute('selected', 1);
//设置selectIn中的文字内容
obj.innerHTML = that.oObj.parentNode.getElementsByTagName('span')[0].innerHTML = oOption[i].innerHTML;
that.relUL.style.display = '';
} else {
oOption[i].removeAttribute('selected');
}
}
}
};
最后的事件是hideList,它的逻辑也很简单:
- 检测document对像内的所有click和scroll事件
- 查找其所有父级对像
- 如果所有父级对像的class均不为selectUlList(说明是在事件ul列表外部产生)
- 隐藏所有class为selectUlList的列表
BB.select.prototype.hideList = function(e){
//在列表外位置点击或者滚动页面时,隐藏列表
var e = e || window.event;
var obj = e.srcElement || e.target;
var clickInner = 0; //是否点击在内部
if (obj.parentNode) {
while (obj.parentNode) {
if (obj.className == 'selectUlList') {
clickInner = 1;
break;
}
obj = obj.parentNode;
}
}
if (!clickInner) {
if (document.getElementsByClassName) {
//DOM5 方法(ff/other)
var aDiv = document.getElementsByClassName('selectUlList');
for (var i = aDiv.length - 1; i >= 0; i--) {
aDiv[i].style.display = '';
}
} else {
//ie
var aDiv = document.getElementsByTagName('div');
for (var i = aDiv.length - 1; i >= 0; i--) {
if (aDiv[i].id.indexOf('ulListFor') != -1) {
aDiv[i].style.display = '';
}
}
}
}
};
代码已经基本完成,我们来看一下所有的代码是什么样子。
HTML部分:
BB.select = function(oObj){
this.oObj = oObj; //select
this.width = oObj.offsetWidth; //select宽度
this.tObjText; //select中显示的文字
this.relUL; //与select相关联的ulList列表id
var that = this;
if (typeof BB.select._initialized == 'undefined') {
BB.select.prototype.init = function(){
var nSelectIndex = 0;
for (var i = this.oObj.options.length - 1; i >= 0; i--) {
if (this.oObj.options[i].selected) {
nSelectIndex = i;
}
}
this.tObjText = this.oObj.options[nSelectIndex].innerHTML;
//外层套入两个span模拟select的外观效果
var selectIn, selectOut, textNode;
selectIn = document.createElement('span');
selectOut = document.createElement('span');
textNode = document.createElement('span');
selectOut.className = 'selectOut';
selectIn.className = 'selectIn';
selectIn.style.width = this.width - 25 + 'px';
selectIn.style.padding = '0 24px 0 6px';
textNode.innerHTML = this.tObjText;
selectIn.appendChild(textNode);
selectOut.appendChild(selectIn);
this.oObj.parentNode.insertBefore(selectOut, this.oObj);
selectIn.appendChild(this.oObj);
this.oObj.className = 'noShow';
//oObj.replaceNode(selectIn.appendChild(oObj));
//生成一个名为oObj.id + 'List'的ul列表
var oDiv = document.createElement('div');
oDiv.id = 'ulListFor' + this.oObj.id;
oDiv.className = 'selectUlList';
oDiv.style.width = this.width - 5 + 'px';
oDiv.style.height = '130px';
var oUl = document.createElement('ul');
var nOptionLength = oObj.options.length;
for (var i = 0; i < nOptionLength; i++) {
var oLi = document.createElement('li');
oLi.setAttribute("onmouseover", "this.className = 'active'");
oLi.setAttribute("onmouseout", "this.className = ''");
//oLi.setAttribute('value', oObj.options[i].value);
oLi.appendChild(document.createTextNode(this.oObj.options[i].innerHTML));
oUl.appendChild(oLi);
BTO.EVENT.addEvent('click', this.liClick(), oLi);
}
oDiv.appendChild(oUl);
//追加此ulList到页面中
selectOut.parentNode.appendChild(oDiv);
this.relUL = oDiv; //与select相关联的ulList列表id
//为其绑定onclick事件
BTO.EVENT.addEvent('click', this.click(), selectOut);
//在document对像上绑定mousedown和scroll事件,以实现点击其它区域时select消失
if (!this.onceEvent) {
BTO.EVENT.addEvent('mousedown', that.hideList);
BTO.EVENT.addEvent('scroll', that.hideList);
}
};
BB.select.prototype.click = function(){
//模拟select的点击事件
var that = this;
return function(){
if (that.relUL.style.display == '') {
var absOffset = getAbsPos(that.oObj);
that.relUL.style.top = absOffset.y + 'px';
that.relUL.style.left = absOffset.x - 5 + 'px';
that.relUL.style.zIndex = '99';
that.relUL.style.display = 'block';
} else {
that.relUL.style.display = '';
}
}
};
BB.select.prototype.liClick = function(){
//列表项的点击事件,返回值到select中
var that = this;
return function(e){
var e = e || window.event;
var obj = e.srcElement || e.target;
var oOption = that.oObj.options;
for (var i = oOption.length - 1; i >= 0; i--) {
if (obj.innerHTML == oOption[i].innerHTML) {
//设置选中项
oOption[i].setAttribute('selected', 1);
//设置selectIn中的文字内容
obj.innerHTML = that.oObj.parentNode.getElementsByTagName('span')[0].innerHTML = oOption[i].innerHTML;
that.relUL.style.display = '';
} else {
oOption[i].removeAttribute('selected');
}
}
}
};
BB.select.prototype.hideList = function(e){
//在列表外位置点击或者滚动页面时,隐藏列表
var e = e || window.event;
var obj = e.srcElement || e.target;
var clickInner = 0; //是否点击在内部
if (obj.parentNode) {
while (obj.parentNode) {
if (obj.className == 'selectUlList') {
clickInner = 1;
break;
}
obj = obj.parentNode;
}
}
if (!clickInner) {
if (document.getElementsByClassName) {
//DOM5 方法(ff/other)
var aDiv = document.getElementsByClassName('selectUlList');
for (var i = aDiv.length - 1; i >= 0; i--) {
aDiv[i].style.display = '';
}
} else {
//ie
var aDiv = document.getElementsByTagName('div');
for (var i = aDiv.length - 1; i >= 0; i--) {
if (aDiv[i].id.indexOf('ulListFor') != -1) {
aDiv[i].style.display = '';
}
}
}
}
};
(function(){
//在document对像上绑定mousedown和scroll事件,以实现点击其它区域时select消失,只需绑定一次
BTO.EVENT.addEvent('mousedown', that.hideList);
BTO.EVENT.addEvent('scroll', that.hideList);
})();
BB.select._initialized = 1;
}
this.init();
}
我们只需这样调用:
new BB.select($('selectId'));
这就可以生成一个有着漂亮外观的select样式列表了。
您可以在这儿下载或预览效果。
思考:虽然我们的效果达到了,但是我们注意到hideList()、click()、liClick()方法都暴露在外,有没有办法将其变成私有方法呢?提示是:闭包!