说在前头

  由于原生的select比较丑陋,而且在不同浏览器下的表现不尽相同;所以为了保持与实际项目中的ul风格保持一致,需要开发一款类似的组件。

  目前该组件已基本开发结束(还会用到上一篇博客的Widget抽象类,同时新增了几个方法);但仍有一个问题,我也是刚发现的,目前还没有比较好的方案,后续找到好的方案会已博客形式指出。


重点

css代码:

.u-select{
	width:120px;
	height: 30px;
	line-height: 30px;
	border: 1px #DBDBDB solid;
}
.u-selector{
	position: absolute;
	top: 30px;
	width: 120px;
	padding: 0px;
	border: 1px #DBDBDB solid;
	display: none;
}
.u-opt{
	list-style: none;
	height: 20px;
  	line-height: 20px;
  	cursor: pointer;
}
.u-opt:hover{background:#DBDBDB; }
.selectorContainer,.selectorContainer2{  display: inline-block;  position: relative;margin: 0px 10px;}//父元素请使用相对定位
.selector-icon{
	background: url(../img/down-icon.png) center center;
	width: 9px;
	height: 5px;
	display: inline-block;
   	right: 10px;
   	position: absolute;
   	top: 15px;
}


插件主要js代码:

/**
 *Uselect组件   create by jiaxiangjun   2015-9-27 
 *
 */
(function (w) {
  function Uselect() {
    if(this instanceof Uselect) {
      this.config = {
        container:null,//组件的父容器
        skin:null,//皮肤
        readOnly: true,//输入框是否只读
        maxCount:5,//最大展示数量,超过时,出现滚动条
        selectHandler:null,//选中回调
        dataType:0,//数据类型0,1
        data:"",//数据,如果自定义拼装,请使用li标签
        top:15,//下拉距顶位置,主要用于微调
        width:120,//input,以及下拉的宽度
        liHeight:30//下拉li的高度
      };
      this.isShow = false;//局部变量,仅限组件内部使用
    } else {
      return new Uselect();
    }
  }

    Uselect.prototype = new Widget();
    //隐藏组件
    AutoComplete.prototype.hide = function(config) {
      config.container && config.container.hide();
    }
    //销毁组件
    AutoComplete.prototype.destroy = function(config) {
      config.container && config.container.empty();
    }
   Uselect.prototype.show = function(config) {
     var CFG = $.extend(this.config, config);//合并对象
     if(!CFG.container) {
      consle.log("组件的父容器为空");
      return;
     }
     this.renderUI(CFG);
      this.syncUI(CFG);
     this.bindUI(CFG);
     return this;
   };
  Uselect.prototype.renderUI= function(CFG) {
    var listStr = constrStr(CFG);
    CFG.container.append(listStr);
    var selector = CFG.container.find(".u-selector");
    CFG.container.find("input").css("width", CFG.width);
    selector.find("li").css({"height":CFG.liHeight,"lineHeight":CFG.liHeight + "px"});
     "" !== CFG.skin && selector.addClass(CFG.skin);//皮肤
    
    var maxHeight = "none";
    var overflowY = "hidden";
    //如果设置了最大展示数量,滚动显示
    if(CFG.maxCount) {
      var liList = selector.find("li");
      var liLen = liList.length;
      if (liLen > CFG.maxCount) {//如果设置的最大数量大于结果集的总数量,无限下拉
         var liHeight = liList.eq(0).height();
         maxHeight = liHeight*CFG.maxCount;
         overflowY = "scroll";
      }           
    }
    selector.css({
      "top":CFG.top,
      "width":CFG.width,
      "maxHeight":maxHeight,
      "overflowY":overflowY
    });
  }
    Uselect.prototype.syncUI= function(CFG) {
        var selector = CFG.container.find(".u-selector");
        CFG.container.find("input").css("width", CFG.width);
        selector.find("li").css({"height":CFG.liHeight,"lineHeight":CFG.liHeight + "px"});
         "" !== CFG.skin && selector.addClass(CFG.skin);//皮肤
        
        var maxHeight = "none";
        var overflowY = "hidden";
        //如果设置了最大展示数量,滚动显示
        if(CFG.maxCount) {
          var liList = selector.find("li");
          var liLen = liList.length;
          if (liLen > CFG.maxCount) {//如果设置的最大数量大于结果集的总数量,无限下拉
             var liHeight = liList.eq(0).height();
             maxHeight = liHeight*CFG.maxCount;
             overflowY = "scroll";
          }           
        }
        selector.css({
          "top":CFG.top,
          "width":CFG.width,
          "maxHeight":maxHeight,
          "overflowY":overflowY
        });
  
  Uselect.prototype.bindUI = function(CFG) {
    var that = this;
    //选择下拉选项
    CFG.container.find(".u-selector li").on("click", function(event) {
      if(CFG.selectHandler) {
        CFG.selectHandler();
      }
      that.fire("select", $(this));
      CFG.container.find(".u-selector").hide();
    });
    CFG.container.blur(function() {
      console.log("blur");
    })
    $("body").on("click", function(event) {
      var e = event || window.event;
      var target = e.target || e.srcElement;
      //点击输入框区域,下拉收缩展开效果
      if(CFG.container[0].contains($(target)[0])) {//很多人不知道这个方法,主要是组件被一个页面多处使用时,冲突
        if(!that.isShow) {
          if(CFG.clickHandler) {
            CFG.clickHandler();
          }
          that.fire("click", $(this));
          CFG.container.find(".u-selector").show();
          that.isShow = true;
          return;
        }
        CFG.container.find(".u-selector").hide();
        that.isShow = false;
        return;
      }
      //点击时,如果isshow为true,收缩下拉
      if(that.isShow) {
        CFG.container.find(".u-selector").hide();
        that.isShow = false;
      }
    });
  }

  function constrStr(CFG) {
    var data = CFG.data;
    var listStr = '<input type="text" class="u-select"'+ (CFG.readOnly ? "readonly" :"") +' index=""><i class="selector-icon"></i><ul class="u-selector">';
    if(0 === CFG.dataType) {
      listStr += CFG.data;
      return listStr + "<ul>";
    }
    if(1 === CFG.dataType) {
      var len = data.length;
      for (var i = 0; i < len; i++){
        listStr += '<li class="u-opt" index="' + data[i].index + '">' + data[i].value + '</li>';
      }
    }

    
    return listStr + "<ul>";;
  }
  w.Uselect = Uselect;
})(window);


公用抽象类widget新增了一个方法

/**
 *Widget抽象类   create by jiaxiangjun   2015-9-20 
 *               modify by jiaxiangjun   2015-9-27 
 * @return {[Widget抽象类]}
 */
(function(w){

  function Widget() {
    if(this instanceof Widget) {
        this.handlers = {};
    } else {
      return new Widget();
    }
  }

  Widget.prototype = 
  {
    //绑定自定义事件
    on:function(type,handler) {
      if ("undefined" === typeof this.handlers[type]) {
        this.handlers[type] = [];
      }
      this.handlers[type].push(handler);
      return this;
    },
    //依次触发自定义事件
    fire:function(type, data) {
      if ("[object Array]" === Object.prototype.toString.call(this.handlers[type])){
        var handlers = this.handlers[type];
        var len = handlers.length;
        for (var i = 0; i < len; i++) {
          handlers[i](data);
        }
        return this;
      }
    },
    //销毁函数
    destroy:function(argument) {
      // to do smething
    },
    //隐藏函数
    hide:function(argument) {
      // to do smething
    },
    //dom操作相关
    renderUI:function(argument) {
      // to do smething
    },
    //设置组件属性相关
    syncUI:function(argument) {
      // to do smething
    },
    //事件绑定相关
    bindUI:function(argument) {
      // to do smething
    }
  }

	w.Widget = Widget;
})(window);

使用示例:

html代码

<div class="selectorContainer">
</div>
<div class="selectorContainer2">
</div>

js代码

$(function() {
  var config = {
    container:$(".selectorContainer"),
    dataType:1,
    data:[{index:1,value:"scott"},
       {index:2,value:"Jhon"},
       {index:3,value:"Tom"},
       {index:4,value:"scott4"},
       {index:5,value:"scott5"},
       {index:6,value:"scott6"},
       {index:7,value:"scott7"},
       {index:8,value:"scott8"},
       {index:9,value:"scott9"}
     ],
    readOnly:false,
    top:17
  };

  var uselect = (new window.Uselect()).show(config);
  uselect.on("select", function(data) {
   // console.log(data);
   alert("select");
  }).on("click", function(data) {
   // console.log(data);
   alert("click");
  });

    var config2 = {
    container:$(".selectorContainer2"),
    dataType:1,
    data:[{index:1,value:"scott2"},{index:2,value:"Jhon2"}, {index:3,value:"Tom2"}],
    readOnly:true,
    width:240,
    top:17
  };

  var uselect2 = (new window.Uselect()).show(config2);
  uselect2.on("select", function(data) {
   // console.log(data);
   alert("select2");
  }).on("click", function(data) {
    //console.log(data);
    alert("click2");
  });

});

说在最后

开发过程中发一下了一个严重的问题:

如果一个页面不知一处使用该组件;将会出问题的。

  之前,在绑定事件和设置css时,我没有考虑到这一点,在本次开发中我特意在绑定事件和设置css等时,使用CFG.container来隔离多个相同组件的冲突问题。

但是,很不幸,又遇到了一个问题,我们这个地方使用了on,fire来绑定和执行自定义事件;但是由于多个相同组件的相同事件的type相同(比如2个组件的click自定义事件的type都为click;但在抽象类widget中执行时,一个组件的click事件会执行两次)

现在还没有想到比较好的解决方案。

后面想到了,会告诉大家的。敬请期待!



还有之前一篇博客的联想输入插件还有很多优化空间;有兴趣的可以优化,并告知我,相互进步,谢谢。