自定义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.根据一些需要或者需 ...
随机推荐
- xcoj1062
题意:给出一个闭合折线上的一堆点(不按顺序),然后再给一个点P,要求判断P是否在闭合折线内 sol attempt1:一开始觉得是个模板题的,后来发现不对劲: 给出的点并不按照顺序.这样模板大法就不行 ...
- Linux之:Ubuntu速学笔记(2)
撰写日期:2016-7-3 18:20:39 基本内容包括:Flash player安装.编译安装PHP.写个简单的PHP程序:Java程序(Java需要使用“javac”命令编译一下才能执行) 一. ...
- 捉襟见肘之UIScrollView 【一】
参考地址:http://segmentfault.com/a/1190000002412930 另一个优秀的UIScrollView实践文章地址:http://tech.glowing.com/cn/ ...
- Codeforces Round #346 (Div. 2)E - New Reform(DFS + 好题)
E. New Reform time limit per test 1 second memory limit per test 256 megabytes input standard input ...
- UVALive 3989Ladies' Choice(稳定婚姻问题)
题目链接 题意:n个男生和女生,先是n行n个数,表示每一个女生对男生的好感值排序,然后是n行n列式每一个男生的好感值排序,输出N行,即每个女生在最好情况下的男生的编号 分析:如果是求女生的最好情况下, ...
- centos搭建https协议的tomcat和apache服务器以及nginx服务器,mysql php
Apache HTTP Server(简称 Apache)是 Apache 软件基金会的一个开放源码的网页服务器,可以在大多数计算机操作系统中运行,由于其多平台和安全性被广泛使用,是最流行的 Web ...
- 解决Bootstrap模态框切换时页面抖动 or页面滚动条
Bootstrap为了让所有的页面(这里指内容溢出和不溢出)显示效果一样,采取的方法如下: 当Modal显示时,设置body -- overflow:hidden;margin-right:15px; ...
- php爬虫 phpspider
<?php /** * Created by PhpStorm. * User: brady * Date: 2016/12/9 * Time: 17:32 */ ini_set("m ...
- _mysql.c(42) : fatal error C1083: Cannot open include file: 'config-win.h':问题的解决 mysql安装python
在win7下安装了Python后,想安装python-MySQL,使用pip安装出现如下问题: >pip install MySQL-python _mysql.c(42) : fatal er ...
- MacPorts安装32位动态库
http://superuser.com/questions/63198/install-32-bits-ports-on-snow-leopard