lu2
Version:
Simple and flexible UI component library based on native HTML and JavaScript
1,672 lines (1,468 loc) • 359 kB
JavaScript
/**
* @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');