UNPKG

lu2

Version:

Simple and flexible UI component library based on native HTML and JavaScript

384 lines (339 loc) 12.2 kB
/** * @Select.js * @author zhangxinxu * @version * @Created: 15-06-18 * @edit: 07-06-15 rewrite **/ (function (global, factory) { if (typeof define === 'function' && (define.amd || define.cmd)) { define(factory); } else { global.Select = factory(); } }(this, function (require) { if (typeof require == 'function') { require('common/ui/Enhance'); } /** * 模拟下拉框效果 * 针对所有浏览器进行处理 * 基于原生的<select>元素生成 * */ /* * 支持jQuery API调用 * @example * $('select').selectMatch(); * 如果不依赖seajs,则载入改JS就会页面所有下拉UI初始化 */ // 常量变量 var SELECT = 'select'; var SELECTED = 'selected'; var DISABLED = 'disabled'; var ACTIVE = 'active'; var REVERSE = 'reverse'; var MULTIPLE = 'multiple'; // 默认参数 var prefix = 'ui-'; // 通用前缀 var PREFIX = prefix + SELECT; var joiner = prefix.replace(/[a-z]/gi, ''); // 支持jQuery的包装器调用处理 $.fn.selectMatch = function() { return $(this).each(function() { var sel = $(this); if (!sel.data('select')) { new Select(sel); } else { // 刷新 sel.data('select').refresh(); } }); }; /** * 基于原生下拉框生成的下拉组件 * @param {Object} el 原生select元素 */ var Select = function (el) { if (!el) { if (window.console) { window.console.error('Select实例方法参数缺失'); } return; } if (!el.length) { el = $(el); } var ele = el[0]; // 构造元素 // 1. 得到关联id var id = ele.id; if (!id) { id = ('id_' + Math.random()).replace('0.', ''); } this.id = id; // 2. 是否多选 var multiple = el.prop(MULTIPLE); this.multiple = multiple; // 3. 创建下拉组合框元素 var combobox = $('<div></div>').attr({ role: 'combobox' }); // 4. 创建下拉点击按钮元素 var button = $('<a></a>').attr({ href: 'javascript:', 'data-target': id, // 下面3个aria无障碍访问需要 role: 'button', 'aria-expanded': 'false', 'aria-owns': id }).addClass(PREFIX + joiner + 'button'); // 5. 创建下拉列表元素 var datalist = $('<div></div>').attr({ id: id, // for aria role: 'listbox', 'aria-expanded': 'true' }).addClass(PREFIX + joiner + 'datalist'); // 6. 元素组装 // multiple没有button if (!multiple) { combobox.append(button).append(datalist); el.hide().after(combobox); } else { combobox.append(datalist); // 绝对定位隐藏,以便可以响应键盘focus el.css({ position: 'absolute', zIndex: 1 }).after(combobox); // 视觉列表不参与设置为不可访问 datalist.attr('aria-hidden', 'true'); } // 暴露给其他方法 this.el = { sel: el, combobox: combobox, button: button, datalist: datalist }; // 刷新内容 this.refresh(); // 事件绑定 this.events(); // 存储 el.data('select', this); }; Select.prototype.events = function () { var self = this; // 各个元素 var el = self.el; // 主要的几个元素 var sel = el.sel; var combobox = el.combobox; var button = el.button; var datalist = el.datalist; // 单选下拉框的事件 if (!this.multiple) { // 点击按钮 button.on('click', function () { if (sel.prop(DISABLED)) { return false; } // 显示与隐藏 combobox.toggleClass(ACTIVE); if (combobox.hasClass(ACTIVE)) { // 边界判断 var overflow = datalist.offset().top + datalist.outerHeight() > Math.max($(document.body).height(), $(window).height()); combobox[overflow ? 'addClass' : 'removeClass'](REVERSE); // aria状态 button.attr('aria-expanded', 'true'); // 滚动与定位 var arrData = combobox.data('scrollTop'); var selected = datalist.find('.' + SELECTED); // 严格验证 if (arrData && arrData[1] == selected.attr('data-index') && arrData[2] == selected.text()) { datalist.scrollTop(arrData[0]); // 重置 combobox.removeData('scrollTop'); } } else { combobox.removeClass(REVERSE); // aria状态 button.attr('aria-expanded', 'false'); } }); // 点击列表项 datalist.on('click', 'a[data-index]', function() { // 是否当前点击列表禁用 var isDisabled = $(this).hasClass(DISABLED); // 获取索引 var indexOption = $(this).data('index'); // 存储可能的滚动定位需要的数据 var scrollTop = datalist.scrollTop(); combobox.data('scrollTop', [scrollTop, indexOption, $(this).text()]); // 修改下拉选中项 if (isDisabled == false) { sel.find('option')[indexOption][SELECTED] = true; } // 下拉收起 combobox.removeClass(ACTIVE); button.attr('aria-expanded', 'false'); // focus button.focus().blur(); if (isDisabled == false) { // 更新下拉框 self.refresh(); // 回调处理 sel.trigger('change'); } }); // 点击页面空白要隐藏 $(document).on('mouseup', function(event) { var target = event.target; if (target && combobox.hasClass(ACTIVE) && combobox[0] !== target && combobox[0].contains(target) == false) { combobox.removeClass(ACTIVE).removeClass(REVERSE); } }); } else { // 键盘交互UI同步 sel.on('change', function () { // 更新下拉框 self.refresh(); }); // 滚动同步 sel.on('scroll', function () { datalist.scrollTop(sel.scrollTop()); }); // hover穿透 sel.on('mousedown', function () { sel.data(ACTIVE, 'true'); }).on('mousemove', function (event) { if (sel.data(ACTIVE)) { self.refresh(); } else { var clientY = event.clientY; // 当前坐标元素 // 最好方法是使用 // document.elementsFromPoint // IE10+是document.msElementFromPoint // 但IE8, IE9浏览器并不支持 // 所以这里采用其他方法实现 // 判断列表的y位置和clientY做比较 var elLists = datalist.find('a'); // 所有hover状态先还原 elLists.removeAttr('href'); // 然后开始寻找匹配的列表元素 elLists.each(function () { // 进行比对 var beginY = this.getBoundingClientRect().top; var endY = beginY + this.clientHeight; // 如果在区间范围 if (clientY >= beginY && clientY <= endY) { if ($(this).hasClass(SELECTED) == false) { this.href = 'javascript:'; } // 退出循环 return false; } }); } }).on('mouseout', function () { datalist.find('a').removeAttr('href'); }); $(document).on('mouseup', function() { sel.removeData(ACTIVE); }); } }; /* 把下拉元素变成数据,格式为: * [{ html: '选项1', value: '1', selected: false, className: 'a' }, { html: '选项2', value: '2', selected: true, className: 'b' }] * @return 数组 */ Select.prototype.getData = function () { var self = this; var sel = self.el.sel; var elOptions = sel.find('option'); if (elOptions.length == 0) { return [{ html: '' }]; } return elOptions.map(function () { var option = this; return { html: option.innerHTML, value: option.value, selected: option.selected, disabled: option.disabled, className: option.className }; }); }; /** * 下拉刷新方法 * @param {Array} data 根据数组数据显示下拉内容 * @return {Object} 返回当前实例对象 */ Select.prototype.refresh = function (data) { var self = this; // 实例id var id = self.id; // 是否多选 var multiple = self.multiple; // 各个元素 var el = self.el; // 主要的几个元素 var sel = el.sel; var combobox = el.combobox; var button = el.button; var datalist = el.datalist; // 获取当下下拉框的数据和状态 data = data || self.getData(); // 下拉组合框元素的UI和尺寸 combobox.attr('class', $.trim(sel[0].className + ' ' + PREFIX)).width(sel.outerWidth()); // 多选,高度需要同步,因为选项高度不确定 if (multiple) { combobox.height(sel.outerHeight()); } else { // 按钮元素中的文案 button.html('<span class="' + PREFIX + joiner + 'text">' + (function () { var htmlSelected = ''; $.each(data, function (i, obj) { if (obj.selected) { htmlSelected = obj.html; } }); return htmlSelected || data[0].html; })() + '</span><i class="' + PREFIX + joiner + 'icon" aria-hidden="true"></i>'); } // 列表内容的刷新 datalist.html($.map(data, function (obj, index) { var arrCl = [PREFIX + joiner + 'datalist' + joiner + 'li', obj.className]; if (obj[SELECTED]) { arrCl.push(SELECTED); } if (obj[DISABLED]) { arrCl.push(DISABLED); } // 复选模式列表不参与无障碍访问识别,因此HTML相对简单 if (multiple) { return '<a class="' + arrCl.join(' ') + '" data-index=' + index + '>' + obj.html + '</a>'; } return '<a ' + (obj[DISABLED] ? '' : 'href="javascript:" ') + 'class="' + arrCl.join(' ') + '" data-index=' + index + ' data-target="' + id + '" role="option" aria-selected="' + obj[SELECTED] + '">' + obj.html + '</a>'; }).join('')); return this; }; return Select; }));