自定义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、当用户鼠标移动在数据下拉列表上时,可以通过键盘up,down上下键来选择
3.6、当用户选择了列表项后,再次点击(或聚焦)搜索框,需要展示所有数据列表,并高亮显示所选择的数据项
3.7、当用户在搜索框中用鼠标粘贴了关键字后,需要显示匹配的数据列表并展示(此项较复杂,并兼容了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 class="custom-select-container" data-name="carBrand" data-default-value="品牌系列" data-placeholder="品牌系列">
* <textarea style="display: none;">[{"id": "1", "name": "宝马"}, {"id": "2", "name": "奥迪"}]</textarea>
* </div>
* <div class="custom-select-container" data-name="carPrice" data-default-value="价格区间" data-placeholder="价格区间">
* <textarea style="display: none;">[{"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 class="custom-select-input-wrap">' +
'<input type="text" class="custom-select-input" value="' + (this.$container.data('default-value')) +
'" placeholder="' + (this.$container.data('placeholder')) + '">' +
'<div class="list-toggle-trigger"><i></i></div>' +
'</div>' +
'<div class="custom-select-list"></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>
5、重难点实现
5.1、如何隐藏数据下拉列表(失去焦点)
试过很多种实现方式,如结合focus,blur,mouseenter,mouseleave等事件处理,都很难处理数据下拉列表的隐藏,最终决定在
body上注册事件处理,判断当前元素是否在容器上,如果不是,则隐藏。
5.2、粘贴事件处理的考虑
粘贴事件处理需要判断用户是在搜索框的起始,中间,末尾粘贴文本,这样才能正确的处理用户输入的关键字搜索
PS:插件为是一个构造函数,这里只是一个例子,你也可以将其改造为一个模块(seajs模块),转载请注明出处 博客园杨君华
自定义select控件开发的更多相关文章
- Winform自定义键盘控件开发及使用
最近有学员提出项目中要使用键盘控件,系统自带的osk.exe不好用,于是就有了下面的内容: 首先是进行自定义键盘控件的开发,其实核心大家都知道,就是利用SendKeys.Send发送相应 的字符,但是 ...
- C#自定义工业控件开发
由于工作需要,调研过一段时间的工业控制方面的“组态软件”(SCADA)的开发,组态软件常用于自动化工业控制领域,其中包括实时数据采集.数据储存.设备控制和数据展现等功能.其中工控组件的界面展现的实现类 ...
- 在IE中点击转跳,并打开chorme浏览器继续浏览指定页面,IE自定义ocx控件开发
因项目需要,需要开发一个功能:在IE中点击转跳,并打开chorme浏览器继续浏览指定页面. 分析需求后,参考了: https://www.cnblogs.com/ffjiang/p/7908025.h ...
- Yii 控制dropdownlist / select 控件的宽度和 option 的宽度
默认情况下, option的宽度会由options中最宽的元素决定,并且同时决定着select控件的宽度 在Yii中,如果需要自定义select控件的宽度,可以用 htmlOptions定义,如下: ...
- 使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)
概述 最近,有客户向我们请求开发一个前端下拉控件,需求是显示了一个列表,其中包含可由用户单独选择的项目控件,该控件将在下拉列表中显示多选TreeView(树形图). 如今WijmoJS已经实现了该控件 ...
- iOS开发UI篇—Quartz2D(自定义UIImageView控件)
iOS开发UI篇—Quartz2D(自定义UIImageView控件) 一.实现思路 Quartz2D最大的用途在于自定义View(自定义UI控件),当系统的View不能满足我们使用需求的时候,自定义 ...
- 【Android开发日记】之入门篇(十四)——Button控件+自定义Button控件
好久不见,又是一个新的学期开始了,为什么我感觉好惆怅啊!这一周也发生了不少事情,节假日放了三天的假(好久没有这么悠闲过了),实习公司那边被组长半强制性的要求去解决一个后台登陆的问题,结果就是把 ...
- Android开发之自定义组合控件
自定义组合控件的步骤1.自定义一个View,继承ViewGroup,比如RelativeLayout2.编写组合控件的布局文件,在自定义的view中加载(使用View.inflate())3.自定义属 ...
- Android开发学习笔记-自定义组合控件的过程
自定义组合控件的过程 1.自定义一个View 一般来说,继承相对布局,或者线性布局 ViewGroup:2.实现父类的构造方法.一般来说,需要在构造方法里初始化自定义的布局文件:3.根据一些需要或者需 ...
随机推荐
- Bzoj1449 [JSOI2009]球队收益
Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 741 Solved: 423 Description Input Output 一个整数表示联盟里所有球 ...
- js事件委托的方式绑定详解
js事件绑定 事件绑定,这里使用了冒泡的原理,从点击的元素开始,递归方式的向父元素传播事件,这样做的好处是对于大量要处理的元素,不必为每个元素都绑定事件,只需要在他们的父元素上绑定一次即可,提高性能. ...
- 《C陷阱与缺陷》杂记
第一章 词法"陷阱" 1.4整型常量 如果一个整型常量的第一个字符是数字0,那么该常量将被视作八进制数.因此,10与010的含义截然不同.需要注意这种情况,有时候在上下文为了格式& ...
- python中join和split函数
一个是分割,一个是连接. 惯例,先看内部帮助文档 Help on method_descriptor: join(...) S.join(iterable) -> string Return a ...
- NOIp2010 关押罪犯
二分+2-SAT 先预处理出所有的v,然后离散化一下,在那个的基础上二分,对于每次二分出的值约束边权超过所二分出的边权的两点. //OJ 1322 //by Cydiater //2015.8.26 ...
- HDU1698Just a Hook(线段树 + 区间修改 + 求和)
题目链接 分析:1-N区间内初始都是1,然后q个询问,每个询问修改区间[a,b]的值为2或3或者1,统计最后整个区间的和 本来想刷刷手速,结果还是写了一个小时,第一个超时,因为输出的时候去每个区间查找 ...
- python的正则表达式 re-------可以在字符串前加上 r 这个前缀来避免部分疑惑,因为 r 开头的python字符串是 raw 字符串,所以里面的所有字符都不会被转义
正则表达式使用反斜杆(\)来转义特殊字符,使其可以匹配字符本身,而不是指定其他特殊的含义.这可能会和python字面意义上的字符串转义相冲突,这也许有些令人费解.比如,要匹配一个反斜杆本身,你也许要用 ...
- css中关于居中的那点事儿
css中关于居中的那点事儿 关于居中,无论是水平居中,还是垂直居中都有很多方式,下面我来介绍一些常用的. 第一部分:水平居中 1.实现行内元素的居中.方法:在行内元素外面的块元素的样式中添加:text ...
- SaltStack项目实战(六)
SaltStack项目实战 系统架构图 一.初始化 1.salt环境配置,定义基础环境.生产环境(base.prod) vim /etc/salt/master 修改file_roots file_r ...
- PHP 生成图片缩略图函数
<?php /** * 生成缩略图函数(支持图片格式:gif.jpeg.png和bmp) * @author ruxing.li * @param string $src 源图片路径 * @pa ...