你的位置:首页 > Java教程

[Java教程]自定义select控件开发


目的:select下拉框条目太多(上百),当用户选择具体项时会浪费用户很多时间去寻找,因此需要一个搜索框让用户输入关键字来匹配列表,便于用户选择

示例图:

1、html结构

<div class="custom-select-container" data-name="oilBrand" data-default-value="品牌系列" data-placeholder="品牌系列">  <textarea style="display: none;">    [{"id": "1", "name": "1嘉实多级护全合成油SN级5W-30"}, {"id": "11", "name": "111嘉实多级护全合成油SN级5W-30"}, {"id": "2", "name": "2实多级护全合成油SN级5W-30"}, {"id": "3", "name": "3实多级护全合成油SN级5W-30"}, {"id": "4", "name": "4实多级护全合成油SN级5W-30"}, {"id": "5", "name": "5实多级护全合成油SN级5W-30"}, {"id": "6", "name": "6实多级护全合成油SN级5W-30"}]  </textarea></div>

说明:

初始化容器属性:
data-name: 相当于原始select的name
data-default-value: input文本搜索框的初始化值
data-placeholder: input文本搜索框的占位值

textarea:

里面是一个JONS字符串,保存着自定义select的键值对,注意里面的id才是需要传递给后端接口的,而name只是显示文本

 

2、实现原理

将用户输入的关键字用正则去匹配数据,展示匹配后的数据下拉列表,供用户选择

 

3、重要交互实现点

3.1、用户点击(或鼠标聚焦)搜索框,需要显示所有的数据下拉列表
3.2、用户每次输入文本,即当文本框值有改变时,匹配相应的数据列表并展示
3.3、当用户点击了数据列表某一项时,即当用户选择了
3.4、当用户在指定的列表项按下enter键时,即当用户选择了
3.5、当用户选择了列表项后,再次点击(或聚焦)搜索框,需要展示所有数据列表,并高亮显示所选择的数据项
3.6、当用户在搜索框中用鼠标粘贴了关键字后,需要显示匹配的数据列表并展示(此项较复杂,并兼容了ie7,8)

注:jQuery在处理paste事件时,event参数并没有处理event.clipboardData,即为undefined,因此需要自己处理事件绑定(兼容ie)

 

4、示例

<!DOCTYPE html><html><head>  <script src="http://apps.bdimg.com/libs/jquery/1.11.1/jquery.min.js"></script>  <meta charset="utf-8">  <title>custom select</title>  <style>    * {margin: 0; padding: 0;}    /*customSelect*/    .custom-select-container {      width: 150px;      position: relative;      display: inline-block;      vertical-align: top;      margin-right: 5px;      /*兼容IE6, 7*/      *display: inline;      *zoom: 1;      margin: 100px 0 0 100px;     }    .custom-select-input {      width: 120px;      padding-right: 28px;      height: 30px;      line-height: 30px;        font-size: 14px;      text-indent: 5px;      *margin-left: -5px;      border: none 0;      outline: none;    }    .custom-select-input-wrap {      position: relative;      width: 148px;      height: 30px;      overflow: hidden;      border: 1px solid #aaa;    }    .list-toggle-trigger {      position: absolute;      right: 0;      top: 0;      padding: 10px;      background-color: #fff;    }    .list-toggle-trigger i {      display: block;      width: 0;      height: 0;      border-width: 8px 5px 5px;      border-style: solid;      border-color: #aaa transparent transparent transparent;    }    .list-toggle-trigger.active {      padding-top: 4px;    }    .list-toggle-trigger.active i {      border-width: 5px 5px 8px;      border-color: transparent transparent #aaa transparent;      }    .custom-select-list {      min-width: 120px;      max-height: 400px;      overflow-y: auto;        border: 1px solid #006ed5;      position: absolute;      left: 0;      top: 32px;      z-index: 100;      background-color: #FFF;      display: none;    }    .custom-select-list span {      display: block;      height: 24px;      line-height: 24px;      color: #000;      text-indent: 5px;      white-space: nowrap;      /*padding-right: 25px;*/    }    .custom-select-list span.hover {      color: #FFF;      background-color: #006ed5;      cursor: default;    }  </style></head><body>  <div class="custom-select-container" data-name="oilBrand" data-default-value="品牌系列" data-placeholder="品牌系列">    <textarea style="display: none;">      [{"id": "1", "name": "1嘉实多级护全合成油SN级5W-30"}, {"id": "11", "name": "111嘉实多级护全合成油SN级5W-30"}, {"id": "2", "name": "2实多级护全合成油SN级5W-30"}, {"id": "3", "name": "3实多级护全合成油SN级5W-30"}, {"id": "4", "name": "4实多级护全合成油SN级5W-30"}, {"id": "5", "name": "5实多级护全合成油SN级5W-30"}, {"id": "6", "name": "6实多级护全合成油SN级5W-30"}]    </textarea>  </div>  <script>    (function($){      var jsonParse = window.JSON && JSON.parse ? JSON.parse : eval;      var addEvent;      if (document.body.addEventListener) {        addEvent = function(elem, type, eventHandler) {          elem.addEventListener(type, eventHandler);        };      } else if (document.body.attachEvent) {        addEvent = function(elem, type, eventHandler) {          elem.attachEvent('on' + type, eventHandler);        };      } else {        addEvent = function(elem, type, eventHandler) {          elem['on' + type] = eventHandler;        };      }      /**       * author: yangjunhua       * email: 1025357509@qq.com       * constructor:       *   CustomSelect       * params:       *   options = {       *     container: selector,    // init container       *     change: function(value) {} // it means select change handler       *   }       * example:       *   html:       *     <div data-name="carBrand" data-default-value="品牌系列" data-placeholder="品牌系列">       *       <textarea >[{"id": "1", "name": "宝马"}, {"id": "2", "name": "奥迪"}]</textarea>       *     </div>       *     <div data-name="carPrice" data-default-value="价格区间" data-placeholder="价格区间">       *       <textarea >[{"id": "1", "name": "30-100万"}, {"id": "2", "name": "100-300万"}]</textarea>       *     </div>       *   js:       *     $('.custom-select-container').each(function() {       *       new CustomSelect({       *         container: this,       *         change: function(value) {       *           // value it means id       *           // query data ...       *         }       *       });       *     });       *       */      function CustomSelect(options) {        this.options = $.extend({}, options || {});        this.init();      }      // 原型      CustomSelect.prototype = {        constructor: CustomSelect,        keywords: '',        init: function() {          if (!this.options || !this.options.container) return;          this.initContainer();          this.listenFocus();          this.listenBlur();          this.listenSearch();          this.listenTrigger();          this.listenSelect();          this.listenMouseenter();          this.listenBodyClick();          this.listenPaste();        },        initContainer: function() {          this.$container = $(this.options.container).addClass('custom-select-container');          var tpl = '<div >' +            '<input type="text" value="' + (this.$container.data('default-value')) +            '" placeholder="' + (this.$container.data('placeholder')) + '">' +            '<div ><i></i></div>' +            '</div>' +            '<div ></div>';          this.dataList = jsonParse(this.$container.find('textarea')[0].value);          this.$container.html(tpl);          this.$input = this.$container.find('.custom-select-input');          this.$list = this.$container.find('.custom-select-list');          this.$filterList = $();          this.$trigger = this.$container.find('.list-toggle-trigger');          this.defaltValue = this.$container.data('default-value');          this.$container.data({            'customSelect': this,            'value': ''          });        },        _isRended: false,        _isResetSize: false,        _highlightIndex: -1,        _seletedIndex: -1,        highlight: function(idx) {          idx = idx !== undefined && idx > -1 ? idx : this._highlightIndex;          idx >= 0 && this.$filterList.children().removeClass('hover').eq(idx).addClass('hover');        },        renderList: function(list) {          var listTpl = '',            len = list.length;          if (len > 0) {            for (var i = 0; i < len; i++) {              listTpl += '<span data-value="' + list[i].id + '">' + list[i].name + '</span>';            }            this.$list.html(listTpl).slideDown('fast');          } else {            this.$list.html(listTpl).hide();          }          this.filterDataList = list;          this._isRended = true;          if (!this._isResetSize) {            this._isResetSize = true;            this.$list.css({              width: this.$list[0].scrollWidth + 25 + 'px'            });          }        },        search: function() {          if (this.keywords === '' || this.keywords === this.defaltValue) {            this.$input.val('');            this.renderList(this.dataList);            this.$filterList = this.$list;            return;          }          var searchList = [];          var len = this.dataList.length;          var reg = new RegExp(this.keywords, 'i');          for (var i = 0; i < len; i++) {            var dataItem = this.dataList[i];            dataItem.name.match(reg) && (searchList.push(dataItem));            this.$filterList = this.$filterList.add(this.$list.eq(i));          }          this.renderList(searchList);        },        listenFocus: function() {          var self = this;          this.$input.on('focus', function() {            if (self._isRended && self.filterDataList.length > 0) {              self.highlight(self._seletedIndex);              self.$list.slideDown('fast');              self.keywords === '' && self.$input.val('');              return;            }            self.search();          });        },        listenBlur: function() {          var self = this;          this.$input.on('blur', function() {            if (self.filterDataList.length === 0) {              self.$input.val(self.defaltValue);              self.keywords = '';            } else if ($.trim(self.$input.val()) === '') {              self.$input.val(self.defaltValue);            }          });        },        keyboardSelect: function(code) {          if (code === 38) {            this._highlightIndex--;            this._highlightIndex = this._highlightIndex < 0 ? 0 : this._highlightIndex;            this.highlight();          } else if (code === 40) {            this._highlightIndex++;            this._highlightIndex = this._highlightIndex > (this.filterDataList.length - 1) ? (this.filterDataList.length - 1) : this._highlightIndex;            this.highlight();          }          this._seletedIndex = this._highlightIndex;        },        listenSearch: function() {          var self = this;          this.$input.on('keyup', function(e) {            var code = e.keyCode || e.which;            self.keywords = $.trim(self.$input.val());            if (code === 38 || code === 40) { // up down select              self.keyboardSelect(code);            } else if (code === 13 && self._highlightIndex >= 0) { // enter              var selectObj = self.filterDataList[self._highlightIndex];              self.$input.val(selectObj.name);              self.$container.data('value', selectObj.id);              self.options.change && self.options.change(self.$container.data('value'));              self.$list.hide();              self.$input.trigger('blur');            } else {              self.search();            }          });        },        listenTrigger: function() {          var self = this;          this.$trigger.on('click', function() {            var $this = $(this);            if (self._isRended && self.filterDataList.length > 0) {              self.$list.slideToggle('fast');            } else {              self.$input.trigger('focus');            }          });        },        listenSelect: function() {          var self = this;          this.$container.on('click', '[data-value]', function() {            var $this = $(this),              value = $this.data('value');            self.$input.val($this.text());            self.keywords = $this.text();            self.$list.hide();            self.$container.data('value', value);            self.options.change && self.options.change(value);            self._seletedIndex = self.$filterList.children().index(this);          });        },        listenMouseenter: function() {          var self = this;          this.$container            .on('mouseenter', '[data-value]', function() {              var $childs = self.$filterList.children();              var i = self._highlightIndex = $childs.index(this);              $childs.removeClass('hover').eq(i).addClass('hover');            });        },        listenBodyClick: function() {          var self = this;          $('body').on('click', function(e) {            if ($(e.target).parents('.custom-select-container')[0] !== self.$container[0]) {              self.$list.hide();            }          });        },        listenPaste: function() {          var self = this;          addEvent(this.$input[0], 'paste', function(e) {            var clipboardData = e.clipboardData || window.clipboardData;            var clipValue = clipboardData.getData('text');            self.keywords = self.getValueAsPaste(clipValue);            self.search();          });        },        getValueAsPaste: function(pasteText) {          var existingVal = this.$input.val();          var length = existingVal.length;          var start = this.getSelectionStart(this.$input[0]);          var value = '';          if (start === undefined) return existingVal;          if (start > 0) {            if (start < length) {              value = existingVal.substring(0, start) + pasteText + existingVal.substring(start, length);            } else if (start === length) {              value = existingVal.substring(0, start) + pasteText;            }          } else {            value = pasteText + existingVal.substring(0, length);          }          return value;        },        getSelectionStart: function(el) {          if (el.selectionStart) {            return el.selectionStart;          } else if (document.selection) {            el.focus();            var r = document.selection.createRange();            if (!r) return 0;            var re = el.createTextRange(),              rc = re.duplicate();            re.moveToBookmark(r.getBookmark());            rc.setEndPoint('EndToStart', re);            return rc.text.length;          }          return 0;        }      };      window.CustomSelect = CustomSelect;          }(jQuery));    $('.custom-select-container').each(function() {      new CustomSelect({        container: this,        change: function(value) {          // value it means id          // query data ...          // test code          alert(value);        }      });    });  </script></body></html>

View Code

 

5、重难点实现

5.1、如何隐藏数据下拉列表(失去焦点)

试过很多种实现方式,如结合focus,blur,mouseenter,mouseleave等事件处理,都很难处理数据下拉列表的隐藏,最终决定在
body上注册事件处理,判断当前元素是否在容器上,如果不是,则隐藏。

5.2、粘贴事件处理的考虑

粘贴事件处理需要判断用户是在搜索框的起始,中间,末尾粘贴文本,这样才能正确的处理用户输入的关键字搜索

 

PS:插件为是一个构造函数,这里只是一个例子,你也可以将其改造为一个模块(seajs模块),转载请注明出处 博客园杨君华