创建一个自定义样式的select(2)

创建一个自定义样式的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()函数:

为了生成下图的这种结构:

image

我们并不需要在每个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事件则非常简单:

  1. 根据当前点击的selectOut算出坐标
  2. 把对应的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的点击事件,则需要如下做:

  1. 遍历与ul列表对应的select.options对像
  2. 找到与之相等的option
  3. 将此option标记为已选中
  4. 将其它option的已选中状态去掉(因option可以存在多选)
  5. 更新显示的文字
  6. 隐藏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,它的逻辑也很简单:

  1. 检测document对像内的所有click和scroll事件
  2. 查找其所有父级对像
  3. 如果所有父级对像的class均不为selectUlList(说明是在事件ul列表外部产生)
  4. 隐藏所有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()方法都暴露在外,有没有办法将其变成私有方法呢?提示是:闭包!

关于 江波

UI设计师、关注前端及交互技术,期望能做出创造性的产品!
此条目发表在 学习笔记 分类目录,贴了 , , 标签。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>