UNPKG

lu2

Version:

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

1,672 lines (1,468 loc) 359 kB
/** * @Keyboard.js * @author zhangxinxu * @version * Created: 17-06-13 */ (function (global, factory) { if (typeof define === 'function' && (define.amd || define.cmd)) { define(factory); } else { factory(); } }(this, function () { var doc = document; var win = window; if (typeof win.Keyboard == 'object') { return win.Keyboard; } /* ** HTML accesskey辅助增强脚本 ** 作用包括: ** 1. 统一IE浏览器和其它浏览器的快捷访问行为; ** 2. 增加单独accesskey属性值对应按键按下的focus行为; ** 3. 增加accesskey生效的前置键按下的提示行为,例如window上Alt键提示,Mac下的Alt + control组合提示等; ** 4. 增加shift + '?'(keyCode=191)键的提示行为支持; */ // 操作系统和浏览器设备检测 var ua = navigator.userAgent; var syetem = 'windows'; if (/Mac\sOS\sX/i.test(ua)) { syetem = 'mac'; } // 浏览器判断 var browser = 'chrome'; if (typeof doc.mozFullScreen != 'undefined') { browser = 'moz'; } else if (typeof doc.msHidden != 'undefined' || typeof doc.hidden == 'undefined') { browser = 'ie'; } // 快捷键组合 var keyPrefix = ({ windows: browser == 'moz' ? { ctrlKey: false, altKey: true, shiftKey: true } : { ctrlKey: false, altKey: true, shiftKey: false }, mac: { ctrlKey: true, altKey: true, shiftKey: false } })[syetem]; // 获取字符Unicode值方法 var U = function(a, b) { if (!a) { return ''; } b = b || 'x'; var c = ''; var d = 0; var e; for (d; d < a.length; d += 1) a.charCodeAt(d) >= 55296 && a.charCodeAt(d) <= 56319 ? (e = (65536 + 1024 * (Number(a.charCodeAt(d)) - 55296) + Number(a.charCodeAt(d + 1)) - 56320).toString(16), d += 1) : e = a.charCodeAt(d).toString(16), c += b + e; return c.substr(b.length); }; // 提示当前元素快捷键的方法 var timerTips = null; var tips = function (arrEles) { // 已经显示中,忽略 if (doc.hasTipsShow) { return; } // 页面的滚动高度 var scrollTop = doc.documentElement.scrollTop || doc.body.scrollTop; var scrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft; // 遍历创建提示元素 arrEles.forEach(function (ele) { // 如果元素隐藏,也忽略 if (ele.clientHeight * ele.clientWidth == 0) { return; } var accesskey = ele.getAttribute('accesskey'); var arrAccesskey = []; for (var key in keyPrefix) { if (keyPrefix[key]) { arrAccesskey.push(key); } } arrAccesskey.push(accesskey); // 当前元素相对于文档的偏移 var bounding = ele.getBoundingClientRect(); // 创建tips提示元素 var div = doc.createElement('div'); div.className = 'ui-kbd-tips AK_Tips'; div.setAttribute('style', 'top:' + (bounding.top + scrollTop) + 'px;left:' + (bounding.left + scrollLeft) + 'px;'); div.innerHTML = arrAccesskey.map(function (key) { return '<kbd>' + key.replace('Key', '') + '</kbd>'; }).join('+'); doc.body.appendChild(div); div.fromTarget = ele; }); // 标记,避免重复生成 doc.hasTipsShow = true; // 一段时间隐藏 timerTips = setTimeout(function () { removeTips(); }, 3000); }; // 隐藏tips var removeTips = function () { clearTimeout(timerTips); // 移除所有的快捷键提示 var elesTips = doc.querySelectorAll('.AK_Tips'); [].slice.call(elesTips).forEach(function (ele) { if (ele.fromTarget) { ele.fromTarget.hasTipsShow = null; } doc.body.removeChild(ele); }); doc.hasTipsShow = null; }; if (doc.addEventListener) { // IE9+ doc.addEventListener('keydown', function (event) { // 当前元素是否是可输入的input或者textarea var isTargetInputable = false; var eleTarget = event.target || doc.activeElement; var tagName = eleTarget.tagName.toLowerCase(); if (tagName == 'textarea' || (tagName == 'input' && /checkbox|radio|select|file|button|image|hidden/i.test(eleTarget.type) == false)) { isTargetInputable = true; } // 遍历所有设置了accesskey的符合HTML4.0.1规范的元素 // 包括<a>, <area>, <button>, <input>, <label>, <legend>以及<textarea> var elesOwnAccesskey = doc.querySelectorAll('a[accesskey],area[accesskey],button[accesskey],input[accesskey],label[accesskey],legend[accesskey],textarea[accesskey]'); if (elesOwnAccesskey.length == 0) { return; } // 对象列表转换成数组 var arrElesOwnAccesskey = [].slice.call(elesOwnAccesskey); // 进行遍历 var arrAcceeekey = arrElesOwnAccesskey.map(function (ele) { return ele.getAttribute('accesskey'); }); // windows下图下直接event.key就是按下的键对于的内容,但OS X系统却没有key属性,有的是event.keyIdentifier,表示字符的Unicode值 // 根据这个判断按键是否有对应的匹配 var indexMatch = -1; arrAcceeekey.forEach(function (accesskey, index) { if ((event.key && event.key == accesskey) || (event.keyIdentifier && parseInt(event.keyIdentifier.toLowerCase().replace('u+', ''), 16) == parseInt(U(accesskey), 16))) { indexMatch = index; return false; } }); // 1. 单独按下某个键的匹配支持 if (event.altKey == false && event.shiftKey == false && event.ctrlKey == false) { if (isTargetInputable) { return; } // focus高亮 if (arrElesOwnAccesskey[indexMatch]) { arrElesOwnAccesskey[indexMatch].focus(); // 阻止内容输入 event.preventDefault(); } // 2. shift + '?'(keyCode=191)键的提示行为支持 } else if (event.altKey == false && event.shiftKey == true && event.ctrlKey == false) { if (event.keyCode == 191 && !isTargetInputable) { doc.hasTipsShow ? removeTips() : tips(arrElesOwnAccesskey); } // 3. 增加accesskey生效的前置键按下的提示行为 } else if (event.altKey == keyPrefix.altKey && event.shiftKey == keyPrefix.shiftKey && event.ctrlKey == keyPrefix.ctrlKey) { if (indexMatch == -1) { event.preventDefault(); doc.hasTipsShow ? removeTips() : tips(arrElesOwnAccesskey); } else { removeTips(); } // 4. IE浏览器和其他浏览器行为一致的处理 if (browser == 'ie' && arrElesOwnAccesskey[indexMatch] && !isTargetInputable) { // click行为触发 arrElesOwnAccesskey[indexMatch].click(); } } }); doc.addEventListener('mousedown', function () { removeTips(); }); } /* ** CSS :focus或者JS的focus事件让下拉或浮层元素显示的时候键盘交互支持 ** 基于data-target属性进行关联 */ if (doc.addEventListener) { var keycode = { 37: 'left', 38: 'up', 39: 'right', 40: 'down', 13: 'enter', 9: 'tab', 27: 'esc' }; // 键盘高亮类名 var className = 'ui-outline'; // 高亮类名的添加与删除 var classList = { add: function (ele) { ele.className = ele.className + ' ' + className; }, remove: function (ele) { ele.className = ele.className.split(/\s+/).filter(function (cl) { if (cl != className) { return cl; } }).join(' '); }, removeAll: function () { [].slice.call(doc.querySelectorAll('.' + className)).forEach(function (ele) { classList.remove(ele); }); }, has: function (ele) { if (!ele.className || !ele.className.split) { return false; } return ele.className.split(/\s+/).filter(function (cl) { if (cl == className) { return cl; } }).length > 0; } }; win.isKeyEvent = false; //键盘事件 doc.addEventListener('keydown', function (event) { win.isKeyEvent = true; setTimeout(function () { win.isKeyEvent = false; }, 100); // 是否是上下左右键 var keyName = keycode[event.keyCode]; if (!keyName) { return; } // 当前激活元素 var trigger = doc.activeElement; if (!trigger || /body/i.test(trigger.tagName)) { return; } // 单复选框的增加回车选中,原生是空格键选中 if (/^radio|checkbox$/i.test(trigger.type) && keyName == 'enter') { trigger.click(); // 阻止默认的回车行为,主要是表单提交 event.preventDefault(); return; } // 如果是ESC退出 var eleFirstMatchAttrTarget = null; var eleEscAll = doc.querySelectorAll('.ESC'); if (keyName == 'esc' && eleEscAll.length) { [].slice.call(eleEscAll).forEach(function (eleEsc) { var idEsc = eleEsc.id; eleFirstMatchAttrTarget = idEsc && doc.querySelector('a[data-target="' + idEsc + '"],input[data-target="' + idEsc + '"]'); if (eleFirstMatchAttrTarget && eleEsc.style.display !== 'none' && eleEsc.clientHeight > 0) { eleFirstMatchAttrTarget.click(); } }); } // 对应的面板 // 1. data-target 点击出现的面板 // 2. data-focus focus出现的面板 var attrFocus = trigger.getAttribute('data-focus'); var attrTarget = trigger.getAttribute('data-target'); var target = null; if (!attrFocus && !attrTarget) { return; } if (attrFocus) { target = doc.getElementById(attrFocus); } else if (attrTarget) { target = doc.getElementById(attrTarget); } // 目标元素存在,并且是显示状态 if (!target || (target.clientWidth == 0 && target.clientHeight == 0)) { return; } // 如果是Tab键 if (keyName == 'tab') { classList.removeAll(); var eleFirstFocusable = target.querySelector('a[href], button:not(:disabled), input:not(:disabled)'); if (eleFirstFocusable) { eleFirstFocusable.focus(); event.preventDefault(); } return; } // 如果是回车事件 if (keyName == 'enter') { var eleFocus = target.querySelector('.' + className); if (eleFocus && attrFocus) { // 阻止默认的回车 event.preventDefault(); eleFocus.click(); return; } if (attrTarget) { return; } } // ESC退出 if (keyName == 'esc') { eleFirstMatchAttrTarget = doc.querySelector('a[data-target="' + attrTarget + '"],input[data-target="' + attrTarget + '"]'); if (attrFocus) { trigger.blur(); } else if (eleFirstMatchAttrTarget && /ESC/.test(eleFirstMatchAttrTarget.className) == false) { eleFirstMatchAttrTarget.click(); } return; } // 如果都符合,同时有目标子元素 var arrEleFocusable = [].slice.call(target.querySelectorAll('a[href], button:not(:disabled), input:not(:disabled)')); var index = -1; if (arrEleFocusable.length == 0) { return; } // 计算出当前索引 arrEleFocusable.forEach(function (ele, indexFocus) { if (attrFocus) { if (classList.has(ele)) { index = indexFocus; } } else if (trigger == ele) { index = indexFocus; } // 先全部清除focus态 classList.remove(ele); }); // 阻止默认的上下键滚屏 event.preventDefault(); // 索引加加减减 if (keyName == 'left' || keyName == 'up') { index--; if (index < 0) { index = -1; } } else if (keyName == 'right' || keyName == 'down') { index++; if (index > arrEleFocusable.length - 1) { index = arrEleFocusable.length; } } // 如果有对应的索引元素 if (arrEleFocusable[index]) { // 高亮对应的控件元素 if (attrFocus) { classList.add(arrEleFocusable[index]); } else { arrEleFocusable[index].focus(); } } }); doc.addEventListener('mousedown', function (event) { var target = event.target; if (target && !classList.has(target)) { classList.removeAll(); } }); doc.addEventListener('click', function (event) { var target = event.target; var eleActive = doc.activeElement; var tabindex = target.getAttribute('tabindex') || '-1'; // 单复选框点击不focus // tabindex>=0 元素点击也不focus(避免outline出现) if (target && target == eleActive && (/^radio|checkbox$/i.test(eleActive.type) || tabindex >= 0) && win.isKeyEvent == false) { eleActive.blur(); } }); // 全局对a标签按钮进行无障碍角色设置 doc.addEventListener('focusin', function (event) { var target = event.target; if (!target) { return; } if (/^javascript/.test(target.href) && !target.getAttribute('role')) { target.setAttribute('role', 'button'); } else if (target.type == 'button' || target.type == 'submit') { if (win.isKeyEvent) { classList.add(target); } } }); doc.addEventListener('focusout', function (event) { var target = event.target; if (!target) { return; } if (target.type == 'button' || target.type == 'submit') { classList.remove(target); } }); } win.Keyboard = { ua: ua, browser: browser, keycode: keycode, keyPrefix: keyPrefix }; })); /** * @Follow.js * @author zhangxinxu * @version * @Created: 15-06-25 * @edited: 17-06-19 */ (function (global, factory) { if (typeof define === 'function' && (define.amd || define.cmd)) { define(factory); } else { global.Follow = factory(); } }(this, function (require) { if (typeof require == 'function') { require('common/ui/Enhance'); } /** * 绝对定位元素的定位效果 * 针对所有浏览器 * 自动含边界判断 * 可用在DropDown, Tips等组件上 * 支持链式调用和模块化调用 * @example * $().follow(trigger, options); * new Follow(trigger, target, options); * 文档见:http://www.zhangxinxu.com/wordpress/?p=1328 position()方法 **/ $.fn.follow = function(trigger, options) { var defaults = { offsets: { x: 0, y: 0 }, // trigger-target position: '4-1', // 边缘位置自动调整 edgeAdjust: true }; var params = $.extend({}, defaults, options || {}); return $(this).each(function() { var target = $(this); if (trigger.length == 0) { return; } var pos, triL, triT, tarL, tarT; var triH = 0; var triW = 0; var tarH = target.data('height'); var tarW = target.data('width'); //缓存目标对象高度,宽度,提高鼠标跟随时显示性能,元素隐藏时缓存清除 if (!tarH) { tarH = target.outerHeight(); } if (!tarW) { tarW = target.outerWidth(); } var st = $(window).scrollTop(); var sl = $(window).scrollLeft(); var offX = parseInt(params.offsets.x, 10) || 0; var offY = parseInt(params.offsets.y, 10) || 0; var winWidth = $(window).width(); var winHeight = $(window).height(); var position = params.position; if (typeof position != 'string' && position.length == 2) { var left = position[0] + offX; var top = position[1] + offY; if (params.edgeAdjust == true) { if (left + tarW > winWidth + sl - 5) { left = winWidth + sl - 5 - tarW; } if (top + tarH > winHeight + st - 5) { top = winHeight + st - 5 - tarH; } } //浮动框显示 target.css({ position: 'absolute', left: left, top: top }).attr('data-align', '3-1'); // z-index自动最高 if (target.zIndex) { target.zIndex(); } return; } pos = trigger.offset(); triH = trigger.outerHeight(); triW = trigger.outerWidth(); triL = pos.left; triT = pos.top; // 合法的位置关系数据 var arrLegalPos = ['4-1', '1-4', '5-7', '2-3', '2-1', '6-8', '3-4', '4-3', '8-6', '1-2', '7-5', '3-2']; // 设定的对齐关系 var align = params.position; // 是否对齐匹配的标志量 var alignMatch = false; // 确定定位的方向 var strDirect; // 遍历,以确定设定的对齐是否有匹配 $.each(arrLegalPos, function(i, n) { if (n === align) { alignMatch = true; return false; } }); // 如果没有匹配的对齐方式,使用默认的对齐方式 if (!alignMatch) { align = defaults.position; } // 确定定位方位,是上下左右的哪个 var funDirect = function(a) { var dir = 'bottom'; //确定方向 switch (a) { case '1-4': case '5-7': case '2-3': { dir = 'top'; break; } case '2-1': case '6-8': case '3-4': { dir = 'right'; break; } case '1-2': case '8-6': case '4-3': { dir = 'left'; break; } case '4-1': case '7-5': case '3-2': { dir = 'bottom'; break; } } return dir; }; // 居中判断 var funCenterJudge = function(a) { if (a === '5-7' || a === '6-8' || a === '8-6' || a === '7-5') { return true; } return false; }; // 是否超出边界的判断 var funJudge = function(dir) { var totalHeight = 0; var totalWidth = 0; // 4个方位分别判断 if (dir === 'right') { totalWidth = triL + triW + tarW + offX; if (totalWidth > $(window).width()) { return false; } } else if (dir === 'bottom') { totalHeight = triT + triH + tarH + offY; if (totalHeight > st + $(window).height()) { return false; } } else if (dir === 'top') { totalHeight = tarH + offY; if (totalHeight > triT - st) { return false; } } else if (dir === 'left') { totalWidth = tarW + offX; if (totalWidth > triL) { return false; } } return true; }; //此时的方向 strDirect = funDirect(align); //边缘过界判断 if (params.edgeAdjust) { //根据位置是否溢出显示界面重新判定定位 if (funJudge(strDirect)) { //该方向不溢出 (function() { if (funCenterJudge(align)) { return; } var obj = { top: { right: '2-3', left: '1-4' }, right: { top: '2-1', bottom: '3-4' }, bottom: { right: '3-2', left: '4-1' }, left: { top: '1-2', bottom: '4-3' } }; var o = obj[strDirect]; var name; if (o) { for (name in o) { if (!funJudge(name)) { align = o[name]; } } } })(); } else { //该方向溢出 (function() { if (funCenterJudge(align)) { var center = { '5-7': '7-5', '7-5': '5-7', '6-8': '8-6', '8-6': '6-8' }; align = center[align]; } else { var obj = { top: { left: '3-2', right: '4-1' }, right: { bottom: '1-2', top: '4-3' }, bottom: { left: '2-3', right: '1-4' }, left: { bottom: '2-1', top: '3-4' } }; var o = obj[strDirect]; var arr = []; for (var name in o) { arr.push(name); } if (funJudge(arr[0]) || !funJudge(arr[1])) { align = o[arr[0]]; } else { align = o[arr[1]]; } } })(); } } // 是否变换了方向 var strNewDirect = funDirect(align); var strFirst = align.split('-')[0]; //确定left, top值 switch (strNewDirect) { case 'top': { tarT = triT - tarH; if (strFirst == '1') { tarL = triL; } else if (strFirst === '5') { tarL = triL - (tarW - triW) / 2; } else { tarL = triL - (tarW - triW); } break; } case 'right': { tarL = triL + triW ; if (strFirst == '2') { tarT = triT; } else if (strFirst === '6') { tarT = triT - (tarH - triH) / 2; } else { tarT = triT - (tarH - triH); } break; } case 'bottom': { tarT = triT + triH; if (strFirst == '4') { tarL = triL; } else if (strFirst === '7') { tarL = triL - (tarW - triW) / 2; } else { tarL = triL - (tarW - triW); } break; } case 'left': { tarL = triL - tarW; if (strFirst == '2') { tarT = triT; } else if (strFirst === '6') { tarT = triT - (tarW - triW) / 2; } else { tarT = triT - (tarH - triH); } break; } } if (params.edgeAdjust && funCenterJudge(align)) { // 是居中定位 // 变更的不是方向,而是offset大小 // 偏移处理 if (align == '7-5' || align == '5-7') { // 左右是否超出 if (tarL - sl < 0.5 * winWidth) { // 左半边,判断左边缘 if (tarL - sl < 0) { tarL = sl; } } else if (tarL - sl + tarW > winWidth) { tarL = winWidth + sl - tarW; } // 下面两个else if 判断上下是否超出 } else if (tarT - st < 0.5 * winHeight) { // 左半边,判断左边缘 if (tarT - st < 0) { tarT = st; } } else if (tarT - st + tarH > winHeight) { tarT = winHeight + st - tarH; } } if (strNewDirect == 'top' || strNewDirect == 'left') { tarL = tarL - offX; tarT = tarT - offY; } else { tarL = tarL + offX; tarT = tarT + offY; } //浮动框显示 target.css({ position: 'absolute', left: Math.round(tarL), top: Math.round(tarT) }).attr('data-align', align); // z-index自动最高 if (target.zIndex) { target.zIndex(); } }); }; var Follow = function(trigger, target, options) { target.follow(trigger, options); }; // Follow.prototype.hide = function() { // target.remove(); // }; return Follow; })); /** * @Enhance.js * @author zhangxinxu * @version * @Created: 17-06-14 * @descrption 对整个组件体系进行功能增强,理论上不调用本JS,基本功能不影响,但没有理由不引用本JS */ (function (global, factory) { if (typeof define === 'function' && (define.amd || define.cmd)) { define(factory); } else { global.Refresh = factory(); } }(this, function (require) { // require if (typeof require == 'function') { require('common/ui/Keyboard'); } else if (!window.Keyboard && window.console) { window.console.warn('you may forget include Keyboard.js'); } /** * 动态最大zIndex值 * @return 返回当前jQuery元素 */ $.fn.zIndex = function () { return $(this).each(function() { var target = this; var zIndex = $(this).css('z-index'); var newZIndex = 19; // 只对<body>子元素进行层级最大化计算处理 $('body').children().each(function() { var ele = this; var el = $(ele); var z = el.css('zIndex') * 1; if (z && ele !== target && el.css('display') !== 'none' && el.width() * el.height() > 0) { newZIndex = Math.max(z + 1, newZIndex); } }); if (zIndex != newZIndex) { $(this).css('z-index', newZIndex); } }); }; /** * UI组件刷新使用更好记的统一的API * @return 返回当前jQuery元素 */ $.fn.refresh = function () { return $(this).each(function() { var el = $(this); // 如果是下拉框 if (el.is('select') && el.selectMatch) { el.selectMatch(); // 如果是单复选框 } else if (/^radio|checkbox$/i.test(el.attr('type')) && el.propMatch) { el.propMatch(); } else if (el.attr('placeholder') && el.placeholder) { // 占位符 el.placeholder(); } else if (el.data('pagination')) { // 占位符 el.data('pagination').show(); } }); }; /** * 按钮disabled和enabled控制 * @return 返回当前jQuery元素 */ $.fn.disabled = function () { return $(this).each(function() { var el = $(this); // 如果是下拉框 if (el.is(':input')) { el.attr('disabled', 'disabled'); } else if (el.is('a')) { el.data('href', el.attr('href')); el.removeAttr('href'); el.attr('aria-disabled', 'true'); } else { // span, div这类标签 var tabindex = el.attr('tabindex'); if (tabindex) { // 存储设置的tabindex值 el.data('tabindex', tabindex); el.removeAttr('tabindex'); } } el.addClass('disabled'); }); }; $.fn.enabled = function () { return $(this).each(function() { var el = $(this); // 如果是下拉框 if (el.is(':input')) { el.removeAttr('disabled'); } else if (el.is('a')) { el.attr('href', el.data('href') || 'javascript:'); el.attr('aria-disabled', 'false'); } else { var tabindex = el.data('tabindex'); if (tabindex) { el.attr('tabindex', tabindex); } } el.removeClass('disabled'); }); }; $.fn.isDisabled = function () { var el = $(this).eq(0); // 如果是下拉框 if (el.is(':input')) { return el.prop('disabled'); } return el.hasClass('disabled'); }; return function (el) { el.refresh(); }; })); /** * @Tab.js * @author zhangxinxu * @version * Created: 15-06-12 */ (function (global, factory) { if (typeof define === 'function' && (define.amd || define.cmd)) { define(factory); } else { global.Tab = factory(); } }(this, function () { /** * 定制级极简的选项卡 * 选项卡效果本质上就是单选框切换效果 * 因此,状态类名使用.checked * @使用示例 * new Tab($('#container > a'), { * callback: function() {} * }); * 或者包装器用法 * $('#container > a').tab({ * callback: function() {} * }); */ var STATE = 'checked'; $.fn.eqAttr = function(key) { key = key || 'data-rel'; // 获得属性对应的值 var value = $(this).attr(key); if (!value) return $(); // 设置管理 // 当作id来处理 var target = $('#' + value); if (target.length) { return target; } // 否则当作className处理 return $('.' + value); }; // 根据属性获得对应的元素 $.fn.tab = function(options) { if (!$(this).data('tab')) { $(this).data('tab', new Tab($(this), options)); } }; $.queryString = function(key, value, str) { // 有则替换,无则加勉 var href = (str || location.href).split('#')[0]; var root = ''; var hash = location.hash; // 查询数组 var arrQuery = href.split('?'); var arrMap = []; if (arrQuery.length > 1) { if (arrQuery[1] != '') { arrMap = $.grep(arrQuery[1].split('&'), function(query) { return query.split('=')[0] != key; }); } root = arrQuery[0] + '?' + arrMap.join('&') + '&'; root = root.replace('?&', '?'); } else { root = href + '?'; } return root + key + '=' + value + hash; }; var Tab = function(el, options) { // 选项卡元素,点击主体 el = $(el); // 如果元素没有,打道回府 if (el.length == 0) return; // 下面3语句参数获取 options = options || {}; var defaults = { eventType: 'click', // 'auto' 表示根据选项卡上的状态类名,如.checked获取index值 index: 'auto', // 是否使用HTML5 history在URL上标记当前选项卡位置,for IE10+ history: true, callback: $.noop }; var params = $.extend({}, defaults, options); // 很重要的索引 var indexTab = params.index; // 首先,获得索引值 el.each(function(index) { if (typeof indexTab != 'number' && $(this).hasClass(STATE)) { indexTab = index; } $(this).data('index', index).attr({ role: 'tab', 'aria-selected': 'false' }); $(this).eqAttr().attr('role', 'tabpanel'); }); if (typeof indexTab != 'number') { indexTab = 0; } // 获得选项卡对应的面板元素 // 两种情况 // 1. 直接切换 // 2. ajax并载入 // 目前先第一种 // 则直接就是页面元素 // 事件gogogo el.on(params.eventType, function(event) { if ($(this).hasClass(STATE)) { if (event.isTrigger) { // 一开始加载的时候回调也能正常进行 params.callback.call(this, this, $(), $(), $()); } return; } // 选项卡样式变变变 // 类名变化 var targetTab = $(this).addClass(STATE); // 要移除类名的选项卡 var resetTab = el.eq(indexTab).removeClass(STATE); // 面板的变化 var targetPanel = targetTab.eqAttr().addClass(STATE); var resetPanel = resetTab.eqAttr().removeClass(STATE); // 索引参数变化 indexTab = targetTab.data('index'); // 回调方法 params.callback.call(this, targetTab, resetTab, targetPanel, resetPanel); if (/^(:?javas|#)/.test(this.getAttribute('href'))) { var rel = targetTab.attr('data-rel'); if (params.history && history.replaceState) { history.replaceState(null, document.title, $.queryString('targetTab', rel)); } else if (params.history) { location.hash = '#targetTab=' + rel; } event.preventDefault(); } }); // 默认就来一发 $(function() { el.eq(indexTab).attr({ 'aria-selected': 'true' }).trigger(params.eventType); }); // 暴露的属性或方法,供外部调用 this.el = { tab: el }; this.params = params; if (el.length > 1 && el[0].parentNode == el[1].parentNode) { el.parent().attr('role', 'tablist'); } }; return Tab; })); /** * @Drop.js * @author zhangxinxu * @version * Created: 15-06-30 */ (function (global, factory) { if (typeof define === 'function' && (define.amd || define.cmd)) { define(factory); } else { global.Drop = factory(); } }(this, function (require) { // require if (typeof require == 'function') { require('common/ui/Follow'); } else if (!$().follow) { if (window.console) { window.console.error('need Follow.js'); } return {}; } /* * 元素的下拉显示 */ /* * 支持jQuery API调用和模块化调用两种 * @example * $('trigger').drop(target, options); * or * new Drop(trigger, target, options); */ $.fn.drop = function(target, options) { return $(this).each(function() { var drop = $(this).data('drop'); if (!drop) { $(this).data('drop', new Drop($(this), target, options)); } }); }; /** * 实例方法 * @param {Object} trigger 触发元素,$()包装器元素类型 * @param {Object} target 显示的浮动定位元素,$()包装器元素类型 * @param {Object} options 可选参数 */ var Drop = function(trigger, target, options) { var defaults = { // 触发元素显示的事件, // 'null'直接显示;'hover'是hover方法;'click'是点击显示, // 新增'contextmenu'表示右键显示 eventType: 'null', // 实现点击或hover事件的委托实现 selector: '', offsets: { x: 0, y: 0 }, edgeAdjust: true, position: '7-5', onShow: $.noop, onHide: $.noop }; var params = $.extend({}, defaults, options || {}); var id = target.attr('id'); if (!id) { id = ('id_' + Math.random()).replace('0.', ''); target.attr('id', id); } if (params.selector == '') { trigger.attr({ 'data-target': id, 'aria-expanded': 'false' }); } // 元素暴露给实例 this.el = { trigger: trigger, target: target }; // 偏移 this.offsets = params.offsets; // 回调 this.callback = { show: params.onShow, hide: params.onHide }; // 位置 this.position = params.position; // 边缘调整 this.edgeAdjust = params.edgeAdjust; // 实例的显示状态 this.display = false; var drop = this; switch (params.eventType) { case 'null': { this.show(); break; } case 'hover': { // hover处理需要增加延时 var timerHover; // 同时,从trigger移动到target也需要延时,因为两者可能有间隙,不能单纯移出就隐藏 var timerHold; trigger.delegate(params.selector, 'mouseenter', function() { if (params.selector) { drop.el.trigger = $(this).attr({ 'data-target': id, 'aria-expanded': 'false' }); } // 显示的定时器 timerHover = setTimeout(function() { drop.show(); }, 150); // 去除隐藏的定时器 clearTimeout(timerHold); }); trigger.delegate(params.selector, 'mouseleave', function() { // 清除显示的定时器 clearTimeout(timerHover); // 隐藏的定时器 timerHold = setTimeout(function() { drop.hide(); }, 200); }); if (!target.data('dropHover')) { target.hover(function() { // 去除隐藏的定时器 clearTimeout(timerHold); }, function() { // 隐藏 timerHold = setTimeout(function() { drop.hide(); }, 100); }); target.data('dropHover', true); } // 键盘支持,原本使用focus事件,但并不利于键盘交互 trigger.delegate(params.selector, 'click', function(event) { // window.isKeyEvent表示是否键盘触发,来自Enhance.js if (window.isKeyEvent) { if (params.selector) { drop.el.trigger = $(this).attr({ 'data-target': id, 'aria-expanded': 'false' }); } // 点击即显示 if (drop.display == false) { drop.show(); } else { drop.hide(); } event.preventDefault(); } }); break; } case 'click': case 'contextmenu': { trigger.delegate(params.selector, params.eventType, function(event) { event.preventDefault(); // aria支持 if (params.selector) { drop.el.trigger = $(this).attr({ 'data-target': id, 'aria-expanded': 'false' }); } // 重复右键点击一直显示,非显隐切换 if (params.eventType == 'contextmenu') { drop.position = [event.pageX, event.pageY]; drop.show(); return; } // 点击即显示 if (drop.display == false) { drop.show(); } else { drop.hide(); } }); break; } } // 点击页面空白区域隐藏 $(document).click(function(event) { var clicked = event && event.target; if (!clicked || !drop || drop.display != true) return; var tri = drop.el.trigger.get(0); var tar = drop.el.target.get(0); if (tri && tar && clicked != tri && clicked != tar && tri.contains(clicked) == false && tar.contains(clicked) == false) { drop.hide(); } }); // 窗体尺寸改变生活的重定位 $(window).resize(function() { drop.follow(); }); return drop; }; /** * 下拉定位处理 * @return {Object} 返回当前实例对象 */ Drop.prototype.follow = function() { var target = this.el.target; var trigger = this.el.trigger; // 下拉必须是显示状态才执行定位处理 if (this.display == true && trigger.css('display') != 'none') { target.follow(trigger, { offsets: this.offsets, position: this.position, edgeAdjust: this.edgeAdjust }); } return this; }; /** * 下拉的显示处理 * @return {Object} 返回当前实例对象 */ Drop.prototype.show = function() { // target需要在页面中 var target = this.el.target; var trigger = this.el.trigger; // 如果target在内存中,append到页面上 if (target && $.contains(document.body, target.get(0)) == false) { $('body').append(target); } // 改变显示标志量 this.display = true; // 进行定位 target.css({ position: 'absolute', display: 'inline' }).addClass('ESC'); // aria trigger.attr({ 'aria-expanded': 'true' }); // 定位 this.follow(); // 显示回调处理 if ($.isFunction(this.callback.show)) { this.callback.show.call(this, trigger, target); } return this; }; /** * 下拉的隐藏处理 * @return {Object} 返回当前实例对象 */ Drop.prototype.hide = function() { var target = this.el.target; var trigger = this.el.trigger; // 隐藏下拉面板 target.hide().removeClass('ESC'); // aria trigger.attr({ 'aria-expanded': 'false' }); if (window.isKeyEvent) { trigger.focus(); } // 更改显示标志量 this.display = false; // 隐藏回调处理 if ($.isFunction(this.callback.hide)) { this.callback.hide.call(this, trigger, target); } return this; }; return Drop; })); /** * @DropList.js * @author xinxuzhang * @version * Created: 15-06-30 */ (function (global, factory) { if (typeof define === 'function' && (define.amd || define.cmd)) { define(factory); } else { global.DropList = factory(); } }(this, function (require) { // require if (typeof require == 'function') { require('common/ui/Drop'); } else if (!$().drop) { if (window.console) { window.console.error('need Drop.js'); } return {}; } // 一些变量 var prefixDropList = 'ui-droplist-'; var SELECTED = 'selected'; var DISABLED = 'disabled'; // 支持jQuery调用的语法处理 $.fn.dropList = function (data, options) { return $(this).each(function() { var dropList = $(this).data('dropList'); if (!dropList) { $(this).data('dropList', new DropList($(this), data, options)); } }); }; /** * 下拉列表 * @trigger 触发下拉的按钮元素 * @data * 也可以是Function函数,表示数据是动态呈现的 * @param {Object} trigger jQuery包装器元素 * @param {Array|Function} data * 下拉列表数据,数组,例如: [{ id: 1, value: '所有评论', selected: true }, { id: 2, value: '未审核评论', disabled: true }, { id: 3, value: '通过评论' }] 也可以是Function函数,表示数据是动态呈现的。 * @param {Object} options 可选参数 */ var DropList = function(trigger, data, options) { var defaults = { // 触发元素显示的事件,‘null’直接显示;‘hover’是hover方法;‘click’是点击显示;其他为手动显示与隐藏。 eventType: 'click', offsets: { x: 0, y: 0 }, position: '4-1', selector: '', width: '', onShow: $.noop, onHide: $.noop, // this为当前点击的列表元素,支持两个参数,第一个参数为列表元素对应的数据(纯对象),第二个是当前实例对象 onSelect: $.noop }; var params = $.extend({}, defaults, options || {}); // 列表元素 var target = $('<div></div>').addClass(prefixDropList + 'x').css('width', params.width).attr({ role: 'listbox' }); if (trigger.length == 0) { return; } // 下拉三角 var arrow = trigger.find('.' + prefixDropList + 'arrow'); // 下拉三角元素的HTML代码 var htmlArrow = arrow.length ? arrow.get(0).outerHTML : ''; // trigger除去三角以外的HTML代码 var htmlLeft = $.trim(trigger.html().replace(htmlArrow, '')); // 创建列表 // 不管怎样,data需要是个有数据的数组 // 如果不符合条件,我们只能反馈没有数据 if ($.isArray(data) && data.length == 0) { data = [{ value: '没有数据', disabled: true }]; } else if ($.isArray(data) && params.selector == '') { // 1. 如果列表数据含有selected: true, 则自动重置trigger里面的内容 // 2. 如果列表数据没有selected: true, 则根据trigger的内容信息确定哪个数据是selected var hasSelected = false; var matchIndex = -1; // 遍历数据 $.each(data, function(index, obj) { if (obj.selected) { trigger.html(obj.value + htmlArrow); hasSelected = true; } if ($.trim(obj.value) == htmlLeft) { matchIndex = index; } }); // 选中项标示 if (hasSelected == false && matchIndex != -1) { data[matchIndex].selected = true; } } // 根据data组装列表 var _get = function(arr) { var html = ''; $.each(arr, function(index, obj) { // 选中列表的类名 var clSelected = ''; if (obj[SELECTED]) { clSelected = ' ' + SELECTED; } // 禁用态和非禁用使用标签区分 if (obj[DISABLED] != true) { html = html + '<a href="' + (obj.href || 'javascript:') + '" class="' + prefixDropList + 'li' + clSelected + '" data-index="' + index + '" role="option" aria-selected="' + (obj[SELECTED] || 'false') + '" data-target="' + target.attr('id') + '">' + obj.value + '</a>'; } else { html = html + '<span class="' + prefixDropList + 'li">' + obj.value + '</span>'; } }); return html; }; // 下拉面板 trigger.drop(target, { eventType: params.eventType, offsets: params.offsets, selector: params.selector, position: params.position, onShow: function() { var _data = this.data; if ($.isFunction(data)) { _data = data(); // 更新 drop.data = _data; } target.html(_get(_data)); params.onShow.apply(this, [trigger, target]); }, onHide: params.onHide }); var drop = trigger.data('drop');