/*! * mdui v0.2.0 (http://mdui.org) * copyright 2016-2017 zdhxiong * licensed under mit */ /* jshint ignore:start */ ;(function (window, document, undefined) { 'use strict'; /* jshint ignore:end */ var mdui = {}; /** * ============================================================================= * ************ 浏览器兼容性问题修复 ************ * ============================================================================= */ /** * requestanimationframe * cancelanimationframe */ (function () { var lasttime = 0; if (!window.requestanimationframe) { window.requestanimationframe = window.webkitrequestanimationframe; window.cancelanimationframe = window.webkitcancelanimationframe; } if (!window.requestanimationframe) { window.requestanimationframe = function (callback, element) { var currtime = new date().gettime(); var timetocall = math.max(0, 16.7 - (currtime - lasttime)); var id = window.settimeout(function () { callback(currtime + timetocall); }, timetocall); lasttime = currtime + timetocall; return id; }; } if (!window.cancelanimationframe) { window.cancelanimationframe = function (id) { cleartimeout(id); }; } })(); /** * ============================================================================= * ************ javascript 工具库,语法和 jquery 类似 ************ * ============================================================================= */ /* jshint ignore:start */ var $ = (function (window, document, undefined) { 'use strict'; /* jshint ignore:end */ var emptyarray = []; var slice = emptyarray.slice; var concat = emptyarray.concat; var isarray = array.isarray; var documentelement = document.documentelement; /** * 是否是类数组的数据 * @param obj * @returns {boolean} */ function isarraylike(obj) { return typeof obj.length === 'number'; } /** * 循环数组或对象 * @param obj * @param callback * @returns {*} */ function each(obj, callback) { var i; var prop; if (isarraylike(obj)) { for (i = 0; i < obj.length; i++) { if (callback.call(obj[i], i, obj[i]) === false) { return obj; } } } else { for (prop in obj) { if (obj.hasownproperty(prop)) { if (callback.call(obj[prop], prop, obj[prop]) === false) { return obj; } } } } return obj; } function map(elems, callback) { var value; var ret = []; each(elems, function (i, elem) { value = callback(elem, i); if (value !== null && value !== undefined) { ret.push(value); } }); return concat.apply([], ret); } /** * 把对象合并到第一个参数中,并返回第一个参数 * @param first * @param second * @returns {*} */ function merge(first, second) { each(second, function (i, val) { first.push(val); }); return first; } /** * 返回去重后的数组 * @param arr * @returns {array} */ function unique(arr) { var unique = []; for (var i = 0; i < arr.length; i++) { if (unique.indexof(arr[i]) === -1) { unique.push(arr[i]); } } return unique; } /** * 是否是 null * @param obj * @returns {boolean} */ function isnull(obj) { return obj === null; } /** * 判断一个节点名 * @param ele * @param name * @returns {boolean} */ function nodename(ele, name) { return ele.nodename && ele.nodename.tolowercase() === name.tolowercase(); } function isfunction(fn) { return typeof fn === 'function'; } function isstring(obj) { return typeof obj === 'string'; } function isobject(obj) { return typeof obj === 'object'; } /** * 除去 null 后的 object 类型 * @param obj * @returns {*|boolean} */ function isobjectlike(obj) { return isobject(obj) && !isnull(obj); } function iswindow(win) { return win && win === win.window; } function isdocument(doc) { return doc && doc.nodetype === doc.document_node; } var elementdisplay = {}; /** * 获取元素的默认 display 样式值,用于 .show() 方法 * @param nodename * @returns {*} */ function defaultdisplay(nodename) { var element; var display; if (!elementdisplay[nodename]) { element = document.createelement(nodename); document.body.appendchild(element); display = getcomputedstyle(element, '').getpropertyvalue('display'); element.parentnode.removechild(element); if (display === 'none') { display = 'block'; } elementdisplay[nodename] = display; } return elementdisplay[nodename]; } var jq = function (arr) { var _this = this; for (var i = 0; i < arr.length; i++) { _this[i] = arr[i]; } _this.length = arr.length; return this; }; /** * @param selector {string|function|node|window|nodelist|array|jq=} * @returns {jq} */ var $ = function (selector) { var arr = []; var i = 0; if (!selector) { return new jq(arr); } if (selector instanceof jq) { return selector; } if (isstring(selector)) { var els; var tempparent; selector = selector.trim(); // 创建 html 字符串 if (selector[0] === '<' && selector[selector.length - 1] === '>') { // html var tocreate = 'div'; if (selector.indexof(':~]/)) { els = [document.getelementbyid(selector.slice(1))]; } // 其他选择器 else { els = document.queryselectorall(selector); } for (i = 0; i < els.length; i++) { if (els[i]) { arr.push(els[i]); } } } } // function else if (isfunction(selector)) { return $(document).ready(selector); } // node else if (selector.nodetype || selector === window || selector === document) { arr.push(selector); } // nodelist else if (selector.length > 0 && selector[0].nodetype) { for (i = 0; i < selector.length; i++) { arr.push(selector[i]); } } return new jq(arr); }; $.fn = jq.prototype; /** * 扩展函数和原型属性 * @param obj */ $.extend = $.fn.extend = function (obj) { if (obj === undefined) { return this; } var length = arguments.length; var prop; var i; var options; // $.extend(obj) if (length === 1) { for (prop in obj) { if (obj.hasownproperty(prop)) { this[prop] = obj[prop]; } } return this; } // $.extend({}, defaults[, obj]) for (i = 1; i < length; i++) { options = arguments[i]; for (prop in options) { if (options.hasownproperty(prop)) { obj[prop] = options[prop]; } } } return obj; }; $.extend({ /** * 遍历对象 * @param obj {string|array|object} * @param callback {function} * @returns {array|object} */ each: each, /** * 合并两个数组,返回的结果会修改第一个数组的内容 * @param first {array} * @param second {array} * @returns {array} */ merge: merge, /** * 删除数组中重复元素 * @param arr {array} * @returns {array} */ unique: unique, /** * 通过遍历集合中的节点对象,通过函数返回一个新的数组,null 或 undefined 将被过滤掉。 * @param elems * @param callback * @returns {array} */ map: map, /** * 一个 dom 节点是否包含另一个 dom 节点 * @param parent {node} 父节点 * @param node {node} 子节点 * @returns {boolean} */ contains: function (parent, node) { if (parent && !node) { return documentelement.contains(parent); } return parent !== node && parent.contains(node); }, /** * 将数组或对象序列化 * @param obj * @returns {string} */ param: function (obj) { if (!isobjectlike(obj)) { return ''; } var args = []; each(obj, function (key, value) { destructure(key, value); }); return args.join('&'); function destructure(key, value) { var keytmp; if (isobjectlike(value)) { each(value, function (i, v) { if (isarray(value) && !isobjectlike(v)) { keytmp = ''; } else { keytmp = i; } destructure(key + '[' + keytmp + ']', v); }); } else { if (!isnull(value) && value !== '') { keytmp = '=' + encodeuricomponent(value); } else { keytmp = ''; } args.push(encodeuricomponent(key) + keytmp); } } }, }); $.fn.extend({ /** * 遍历对象 * @param callback {function} * @return {jq} */ each: function (callback) { return each(this, callback); }, /** * 通过遍历集合中的节点对象,通过函数返回一个新的对象,null 或 undefined 将被过滤掉。 * @param callback {function} * @returns {jq} */ map: function (callback) { return new jq(map(this, function (el, i) { return callback.call(el, i, el); })); }, /** * 获取指定 dom 元素,没有 index 参数时,获取所有 dom 的数组 * @param index {number=} * @returns {node|array} */ get: function (index) { return index === undefined ? slice.call(this) : this[index >= 0 ? index : index + this.length]; }, /** * array中提取的方法。从start开始,如果end 指出。提取不包含end位置的元素。 * @param argument {start, end} * @returns {jq} */ slice: function (argument) { return new jq(slice.apply(this, arguments)); }, /** * 筛选元素集合 * @param selector {string|jq|node|function} * @returns {jq} */ filter: function (selector) { if (isfunction(selector)) { return this.map(function (index, ele) { return selector.call(ele, index, ele) ? ele : undefined; }); } else { var $selector = $(selector); return this.map(function (index, ele) { return $selector.index(ele) > -1 ? ele : undefined; }); } }, /** * 从元素集合中删除指定的元素 * @param selector {string|node|jq|function} * @return {jq} */ not: function (selector) { var $excludes = this.filter(selector); return this.map(function (index, ele) { return $excludes.index(ele) > -1 ? undefined : ele; }); }, /** * 获取元素相对于 document 的偏移 * @returns {object} */ offset: function () { if (this[0]) { var offset = this[0].getboundingclientrect(); return { left: offset.left + window.pagexoffset, top: offset.top + window.pageyoffset, width: offset.width, height: offset.height, }; } return null; }, /** * 返回最近的用于定位的父元素 * @returns {*|jq} */ offsetparent: function () { return this.map(function () { var offsetparent = this.offsetparent; while (offsetparent && $(offsetparent).css('position') === 'static') { offsetparent = offsetparent.offsetparent; } return offsetparent || documentelement; }); }, /** * 获取元素相对于父元素的偏移 * @return {object} */ position: function () { var _this = this; if (!_this[0]) { return null; } var offsetparent; var offset; var parentoffset = { top: 0, left: 0, }; if (_this.css('position') === 'fixed') { offset = _this[0].getboundingclientrect(); } else { offsetparent = _this.offsetparent(); offset = _this.offset(); if (!nodename(offsetparent[0], 'html')) { parentoffset = offsetparent.offset(); } parentoffset = { top: parentoffset.top + offsetparent.css('bordertopwidth'), left: parentoffset.left + offsetparent.css('borderleftwidth'), }; } return { top: offset.top - parentoffset.top - _this.css('margintop'), left: offset.left - parentoffset.left - _this.css('marginleft'), width: offset.width, height: offset.height, }; }, /** * 显示指定元素 * @returns {jq} */ show: function () { return this.each(function () { if (this.style.display === 'none') { this.style.display = ''; } if (window.getcomputedstyle(this, '').getpropertyvalue('display') === 'none') { this.style.display = defaultdisplay(this.nodename); } }); }, /** * 隐藏指定元素 * @returns {jq} */ hide: function () { return this.each(function () { this.style.display = 'none'; }); }, /** * 切换元素的显示状态 * @returns {jq} */ toggle: function () { return this.each(function () { this.style.display = this.style.display === 'none' ? '' : 'none'; }); }, /** * 是否含有指定的 css 类 * @param classname {string} * @returns {boolean} */ hasclass: function (classname) { if (!this[0] || !classname) { return false; } return this[0].classlist.contains(classname); }, /** * 移除指定属性 * @param attr {string} * @returns {jq} */ removeattr: function (attr) { return this.each(function () { this.removeattribute(attr); }); }, /** * 删除属性值 * @param name {string} * @returns {jq} */ removeprop: function (name) { return this.each(function () { try { delete this[name]; } catch (e) {} }); }, /** * 获取当前对象中第n个元素 * @param index {number} * @returns {jq} */ eq: function (index) { var ret = index === -1 ? this.slice(index) : this.slice(index, +index + 1); return new jq(ret); }, /** * 获取对象中第一个元素 * @returns {jq} */ first: function () { return this.eq(0); }, /** * 获取对象中最后一个元素 * @returns {jq} */ last: function () { return this.eq(-1); }, /** * 获取一个元素的位置。 * 当 ele 参数没有给出时,返回当前元素在兄弟节点中的位置。 * 有给出了 ele 参数时,返回 ele 元素在当前对象中的位置 * @param ele {selector|node=} * @returns {number} */ index: function (ele) { if (!ele) { // 获取当前 jq 对象的第一个元素在同辈元素中的位置 return this.eq(0).parent().children().get().indexof(this[0]); } else if (isstring(ele)) { // 返回当前 jq 对象的第一个元素在指定选择器对应的元素中的位置 return $(ele).eq(0).parent().children().get().indexof(this[0]); } else { // 返回指定元素在当前 jq 对象中的位置 return this.get().indexof(ele); } }, /** * 根据选择器、dom元素或 jq 对象来检测匹配元素集合, * 如果其中至少有一个元素符合这个给定的表达式就返回true * @param selector {string|node|nodelist|array|jq|window} * @returns boolean */ is: function (selector) { var _this = this[0]; if (!_this || selector === undefined || selector === null) { return false; } var $comparewith; var i; if (isstring(selector)) { if (_this === document || _this === window) { return false; } var matchesselector = _this.matches || _this.matchesselector || _this.webkitmatchesselector || _this.mozmatchesselector || _this.omatchesselector || _this.msmatchesselector; return matchesselector.call(_this, selector); } else if (selector === document || selector === window) { return _this === selector; } else { if (selector.nodetype || isarraylike(selector)) { $comparewith = selector.nodetype ? [selector] : selector; for (i = 0; i < $comparewith.length; i++) { if ($comparewith[i] === _this) { return true; } } return false; } return false; } }, /** * 根据 css 选择器找到后代节点的集合 * @param selector {string} * @returns {jq} */ find: function (selector) { var foundelements = []; this.each(function (i, _this) { merge(foundelements, _this.queryselectorall(selector)); }); return new jq(foundelements); }, /** * 找到直接子元素的元素集合 * @param selector {string=} * @returns {jq} */ children: function (selector) { var children = []; this.each(function (i, _this) { each(_this.childnodes, function (i, childnode) { if (childnode.nodetype !== 1) { return true; } if (!selector || (selector && $(childnode).is(selector))) { children.push(childnode); } }); }); return new jq(unique(children)); }, /** * 保留含有指定子元素的元素,去掉不含有指定子元素的元素 * @param selector {string|node|jq|nodelist|array} * @return {jq} */ has: function (selector) { var $targets = isstring(selector) ? this.find(selector) : $(selector); var len = $targets.length; return this.filter(function () { for (var i = 0; i < len; i++) { if ($.contains(this, $targets[i])) { return true; } } }); }, /** * 取得同辈元素的集合 * @param selector {string=} * @returns {jq} */ siblings: function (selector) { return this.prevall(selector).add(this.nextall(selector)); }, /** * 返回首先匹配到的父节点,包含父节点 * @param selector {string} * @returns {jq} */ closest: function (selector) { var _this = this; if (!_this.is(selector)) { _this = _this.parents(selector).eq(0); } return _this; }, /** * 删除所有匹配的元素 * @returns {jq} */ remove: function () { return this.each(function (i, _this) { if (_this.parentnode) { _this.parentnode.removechild(_this); } }); }, /** * 添加匹配的元素到当前对象中 * @param selector {string|jq} * @returns {jq} */ add: function (selector) { return new jq(unique(merge(this.get(), $(selector)))); }, /** * 删除子节点 * @returns {jq} */ empty: function () { return this.each(function () { this.innerhtml = ''; }); }, /** * 通过深度克隆来复制集合中的所有元素。 * (通过原生 clonenode 方法深度克隆来复制集合中的所有元素。此方法不会有数据和事件处理程序复制到新的元素。这点和jquery中利用一个参数来确定是否复制数据和事件处理不相同。) * @returns {jq} */ clone: function () { return this.map(function () { return this.clonenode(true); }); }, /** * 用新元素替换当前元素 * @param newcontent {string|node|nodelist|jq} * @returns {jq} */ replacewith: function (newcontent) { return this.before(newcontent).remove(); }, /** * 将表单元素的值组合成键值对数组 * @returns {array} */ serializearray: function () { var result = []; var $ele; var type; var ele = this[0]; if (!ele || !ele.elements) { return result; } $(slice.call(ele.elements)).each(function () { $ele = $(this); type = $ele.attr('type'); if ( this.nodename.tolowercase() !== 'fieldset' && !this.disabled && ['submit', 'reset', 'button'].indexof(type) === -1 && (['radio', 'checkbox'].indexof(type) === -1 || this.checked) ) { result.push({ name: $ele.attr('name'), value: $ele.val(), }); } }); return result; }, /** * 将表单元素或对象序列化 * @returns {string} */ serialize: function () { var result = []; each(this.serializearray(), function (i, elm) { result.push(encodeuricomponent(elm.name) + '=' + encodeuricomponent(elm.value)); }); return result.join('&'); }, }); /** * val - 获取或设置元素的值 * @param value {string=} * @return {*|jq} */ /** * html - 获取或设置元素的 html * @param value {string=} * @return {*|jq} */ /** * text - 获取或设置元素的内容 * @param value {string=} * @return {*|jq} */ each(['val', 'html', 'text'], function (nameindex, name) { var props = { 0: 'value', 1: 'innerhtml', 2: 'textcontent', }; var defaults = { 0: undefined, 1: undefined, 2: null, }; $.fn[name] = function (value) { if (value === undefined) { // 获取值 return this[0] ? this[0][props[nameindex]] : defaults[nameindex]; } else { // 设置值 return this.each(function (i, ele) { ele[props[nameindex]] = value; }); } }; }); /** * attr - 获取或设置元素的属性值 * @param {name|props|key,value=} * @return {string|jq} */ /** * prop - 获取或设置元素的属性值 * @param {name|props|key,value=} * @return {string|jq} */ /** * css - 获取或设置元素的样式 * @param {name|props|key,value=} * @return {string|jq} */ each(['attr', 'prop', 'css'], function (nameindex, name) { var set = function (ele, key, value) { if (nameindex === 0) { ele.setattribute(key, value); } else if (nameindex === 1) { ele[key] = value; } else { ele.style[key] = value; } }; var get = function (ele, key) { if (!ele) { return undefined; } var value; if (nameindex === 0) { value = ele.getattribute(key); } else if (nameindex === 1) { value = ele[key]; } else { value = window.getcomputedstyle(ele, null).getpropertyvalue(key); } return value; }; $.fn[name] = function (key, value) { var arglength = arguments.length; if (arglength === 1 && isstring(key)) { // 获取值 return get(this[0], key); } else { // 设置值 return this.each(function (i, ele) { if (arglength === 2) { set(ele, key, value); } else { each(key, function (k, v) { set(ele, k, v); }); } }); } }; }); /** * addclass - 添加 css 类,多个类名用空格分割 * @param classname {string} * @return {jq} */ /** * removeclass - 移除 css 类,多个类名用空格分割 * @param classname {string} * @return {jq} */ /** * toggleclass - 切换 css 类名,多个类名用空格分割 * @param classname {string} * @return {jq} */ each(['add', 'remove', 'toggle'], function (nameindex, name) { $.fn[name + 'class'] = function (classname) { if (!classname) { return this; } var classes = classname.split(' '); return this.each(function (i, ele) { each(classes, function (j, cls) { ele.classlist[name](cls); }); }); }; }); /** * width - 获取元素的宽度 * @return {number} */ /** * height - 获取元素的高度 * @return {number} */ each({ width: 'width', height: 'height', }, function (prop, name) { $.fn[name] = function (val) { if (val === undefined) { // 获取 var ele = this[0]; if (iswindow(ele)) { return ele['inner' + prop]; } if (isdocument(ele)) { return ele.documentelement['scroll' + prop]; } var $ele = $(ele); // ie10、ie11 在 box-sizing:border-box 时,不会包含 padding,这里进行修复 var iefixvalue = 0; if ('activexobject' in window) { // 判断是 ie 浏览器 if ($ele.css('box-sizing') === 'border-box') { iefixvalue = parsefloat($ele.css('padding-' + (name === 'width' ? 'left' : 'top'))) + parsefloat($ele.css('padding-' + (name === 'width' ? 'right' : 'bottom'))); } } return parsefloat($(ele).css(name)) + iefixvalue; } else { // 设置 if (!isnan(number(val)) && val !== '') { val += 'px'; } return this.css(name, val); } }; }); /** * innerwidth - 获取元素的宽度,包含内边距 * @return {number} */ /** * innerheight - 获取元素的高度,包含内边距 * @return {number} */ each({ width: 'width', height: 'height', }, function (prop, name) { $.fn['inner' + prop] = function () { var value = this[name](); var $ele = $(this[0]); if ($ele.css('box-sizing') !== 'border-box') { value += parsefloat($ele.css('padding-' + (name === 'width' ? 'left' : 'top'))); value += parsefloat($ele.css('padding-' + (name === 'width' ? 'right' : 'bottom'))); } return value; }; }); var dir = function (nodes, selector, nameindex, node) { var ret = []; var ele; nodes.each(function (j, _this) { ele = _this[node]; while (ele) { if (nameindex === 2) { // prevuntil if (!selector || (selector && $(ele).is(selector))) { break; } ret.push(ele); } else if (nameindex === 0) { // prev if (!selector || (selector && $(ele).is(selector))) { ret.push(ele); } break; } else { // prevall if (!selector || (selector && $(ele).is(selector))) { ret.push(ele); } } ele = ele[node]; } }); return new jq(unique(ret)); }; /** * prev - 取得前一个匹配的元素 * @param selector {string=} * @return {jq} */ /** * prevall - 取得前面所有匹配的元素 * @param selector {string=} * @return {jq} */ /** * prevuntil - 取得前面的所有元素,直到遇到匹配的元素,不包含匹配的元素 * @param selector {string=} * @return {jq} */ each(['', 'all', 'until'], function (nameindex, name) { $.fn['prev' + name] = function (selector) { // prevall、prevuntil 需要把元素的顺序倒序处理,以便和 jquery 的结果一致 var $nodes = nameindex === 0 ? this : $(this.get().reverse()); return dir($nodes, selector, nameindex, 'previouselementsibling'); }; }); /** * next - 取得后一个匹配的元素 * @param selector {string=} * @return {jq} */ /** * nextall - 取得后面所有匹配的元素 * @param selector {string=} * @return {jq} */ /** * nextuntil - 取得后面所有匹配的元素,直到遇到匹配的元素,不包含匹配的元素 * @param selector {string=} * @return {jq} */ each(['', 'all', 'until'], function (nameindex, name) { $.fn['next' + name] = function (selector) { return dir(this, selector, nameindex, 'nextelementsibling'); }; }); /** * parent - 取得匹配的直接父元素 * @param selector {string=} * @return {jq} */ /** * parents - 取得所有匹配的父元素 * @param selector {string=} * @return {jq} */ /** * parentuntil - 取得所有的父元素,直到遇到匹配的元素,不包含匹配的元素 * @param selector {string=} * @return {jq} */ each(['', 's', 'suntil'], function (nameindex, name) { $.fn['parent' + name] = function (selector) { // parents、parentsuntil 需要把元素的顺序反向处理,以便和 jquery 的结果一致 var $nodes = nameindex === 0 ? this : $(this.get().reverse()); return dir($nodes, selector, nameindex, 'parentnode'); }; }); /** * append - 在元素内部追加内容 * @param newchild {string|node|nodelist|jq} * @return {jq} */ /** * prepend - 在元素内部前置内容 * @param newchild {string|node|nodelist|jq} * @return {jq} */ each(['append', 'prepend'], function (nameindex, name) { $.fn[name] = function (newchild) { var newchilds; var copybyclone = this.length > 1; if (isstring(newchild)) { var tempdiv = document.createelement('div'); tempdiv.innerhtml = newchild; newchilds = slice.call(tempdiv.childnodes); } else { newchilds = $(newchild).get(); } if (nameindex === 1) { // prepend newchilds.reverse(); } return this.each(function (i, _this) { each(newchilds, function (j, child) { // 一个元素要同时追加到多个元素中,需要先复制一份,然后追加 if (copybyclone && i > 0) { child = child.clonenode(true); } if (nameindex === 0) { // append _this.appendchild(child); } else { // prepend _this.insertbefore(child, _this.childnodes[0]); } }); }); }; }); /** * insertbefore - 插入到指定元素的前面 * @param selector {string|node|nodelist|jq} * @return {jq} */ /** * insertafter - 插入到指定元素的后面 * @param selector {string|node|nodelist|jq} * @return {jq} */ each(['insertbefore', 'insertafter'], function (nameindex, name) { $.fn[name] = function (selector) { var $ele = $(selector); return this.each(function (i, _this) { $ele.each(function (j, ele) { ele.parentnode.insertbefore( $ele.length === 1 ? _this : _this.clonenode(true), nameindex === 0 ? ele : ele.nextsibling ); }); }); }; }); /** * appendto - 追加到指定元素内容 * @param selector {string|node|nodelist|jq} * @return {jq} */ /** * prependto - 前置到指定元素内部 * @param selector {string|node|nodelist|jq} * @return {jq} */ /** * before - 插入到指定元素前面 * @param selector {string|node|nodelist|jq} * @return {jq} */ /** * after - 插入到指定元素后面 * @param selector {string|node|nodelist|jq} * @return {jq} */ /** * replaceall - 替换掉指定元素 * @param selector {string|node|nodelist|jq} * @return {jq} */ each({ appendto: 'append', prependto: 'prepend', before: 'insertbefore', after: 'insertafter', replaceall: 'replacewith', }, function (name, original) { $.fn[name] = function (selector) { $(selector)[original](this); return this; }; }); (function () { var datans = 'mduielementdatastorage'; $.extend({ /** * 在指定元素上存储数据,或从指定元素上读取数据 * @param ele 必须, dom 元素 * @param key 必须,键名 * @param value 可选,值 */ data: function (ele, key, value) { var data = {}; if (value !== undefined) { // 根据 key、value 设置值 data[key] = value; } else if (isobjectlike(key)) { // 根据键值对设置值 data = key; } else if (key === undefined) { // 获取所有值 var result = {}; each(ele.attributes, function (i, attribute) { var name = attribute.name; if (name.indexof('data-') === 0) { var prop = name.slice(5).replace(/-./g, function (u) { // 横杠转为驼峰法 return u.charat(1).touppercase(); }); result[prop] = attribute.value; } }); if (ele[datans]) { each(ele[datans], function (k, v) { result[k] = v; }); } return result; } else { // 获取指定值 if (ele[datans] && (key in ele[datans])) { return ele[datans][key]; } else { var datakey = ele.getattribute('data-' + key); if (datakey) { return datakey; } else { return undefined; } } } // 设置值 if (!ele[datans]) { ele[datans] = {}; } each(data, function (k, v) { ele[datans][k] = v; }); }, /** * 移除指定元素上存放的数据 * @param ele 必须,dom 元素 * @param key 必须,键名 */ removedata: function (ele, key) { if (ele[datans] && ele[datans][key]) { ele[datans][key] = null; delete ele.mduielementdatastorage[key]; } }, }); $.fn.extend({ /** * 在元素上读取或设置数据 * @param key 必须 * @param value * @returns {*} */ data: function (key, value) { if (value === undefined) { // 获取值 if (this[0]) { return $.data(this[0], key); } else { return undefined; } } else { // 设置值 return this.each(function (i, ele) { $.data(ele, key, value); }); } }, /** * 移除元素上存储的数据 * @param key 必须 * @returns {*} */ removedata: function (key) { return this.each(function (i, ele) { $.removedata(ele, key); }); }, }); })(); (function () { // 存储事件 var handlers = { // i: { // 元素id // j: { // 事件id // e: 事件名 // fn: 事件处理函数 // i: 事件id // proxy: // sel: 选择器 // } // } }; // 元素id var _elementid = 1; var fnfalse = function () { return false; }; $.fn.extend({ /** * dom 加载完毕后调用的函数 * @param callback * @returns {ready} */ ready: function (callback) { if (/complete|loaded|interactive/.test(document.readystate) && document.body) { callback($); } else { document.addeventlistener('domcontentloaded', function () { callback($); }, false); } return this; }, /** * 绑定事件 * * $().on({eventname: fn}, selector, data); * $().on({eventname: fn}, selector) * $().on({eventname: fn}) * $().on(eventname, selector, data, fn); * $().on(eventname, selector, fn); * $().on(eventname, data, fn); * $().on(eventname, fn); * $().on(eventname, false); * * @param eventname * @param selector * @param data * @param callback * @param one 是否是 one 方法,只在 jq 内部使用 * @returns */ on: function (eventname, selector, data, callback, one) { var _this = this; // 默认 // $().on(event, selector, data, callback) // event 使用 事件:函数 键值对 // event = { // 'event1': callback1, // 'event2': callback2 // } // // $().on(event, selector, data) if (eventname && !isstring(eventname)) { each(eventname, function (type, fn) { _this.on(type, selector, data, fn); }); return _this; } // selector 不存在 // $().on(event, data, callback) if (!isstring(selector) && !isfunction(callback) && callback !== false) { callback = data; data = selector; selector = undefined; } // data 不存在 // $().on(event, callback) if (isfunction(data) || data === false) { callback = data; data = undefined; } // callback 为 false // $().on(event, false) if (callback === false) { callback = fnfalse; } if (one === 1) { var origcallback = callback; callback = function () { _this.off(eventname, selector, callback); return origcallback.apply(this, arguments); }; } return this.each(function () { add(this, eventname, callback, data, selector); }); }, /** * 绑定事件,只触发一次 * @param eventname * @param selector * @param data * @param callback */ one: function (eventname, selector, data, callback) { var _this = this; if (!isstring(eventname)) { each(eventname, function (type, fn) { type.split(' ').foreach(function (ename) { _this.on(ename, selector, data, fn, 1); }); }); } else { eventname.split(' ').foreach(function (ename) { _this.on(ename, selector, data, callback, 1); }); } return this; }, /** * 取消绑定事件 * * $().off(eventname, selector); * $().off(eventname, callback); * $().off(eventname, false); * */ off: function (eventname, selector, callback) { var _this = this; // event 使用 事件:函数 键值对 // event = { // 'event1': callback1, // 'event2': callback2 // } // // $().off(event, selector) if (eventname && !isstring(eventname)) { each(eventname, function (type, fn) { _this.off(type, selector, fn); }); return _this; } // selector 不存在 // $().off(event, callback) if (!isstring(selector) && !isfunction(callback) && callback !== false) { callback = selector; selector = undefined; } // callback 为 false // $().off(event, false) if (callback === false) { callback = fnfalse; } return _this.each(function () { remove(this, eventname, callback, selector); }); }, /** * 触发一个事件 * @param eventname * @param data * @returns {*|jq} */ trigger: function (eventname, data) { if (!isstring(eventname)) { return; } var evt; try { evt = new customevent(eventname, { detail: data, bubbles: true, cancelable: true }); } catch (e) { evt = document.createevent('event'); evt.initevent(eventname, true, true); evt.detail = data; } evt._data = data; return this.each(function () { this.dispatchevent(evt); }); }, }); /** * 添加事件监听 * @param element * @param eventname * @param func * @param data * @param selector */ function add(element, eventname, func, data, selector) { var elementid = getelementid(element); if (!handlers[elementid]) { handlers[elementid] = []; } // 传入 data.usecapture 来设置 usecapture: true var usecapture = false; if (isobjectlike(data) && data.usecapture) { usecapture = true; } eventname.split(' ').foreach(function (event) { var handler = { e: event, fn: func, sel: selector, i: handlers[elementid].length, }; var callfn = function (e, ele) { var result = func.apply(ele, e._data === undefined ? [e] : [e].concat(e._data)); if (result === false) { e.preventdefault(); e.stoppropagation(); } }; var proxyfn = handler.proxy = function (e) { e.data = data; // 事件代理 if (selector) { $(element).find(selector).get().reverse().foreach(function (ele) { if (ele === e.target || $.contains(ele, e.target)) { callfn(e, ele); } }); } // 不使用事件代理 else { callfn(e, element); } }; handlers[elementid].push(handler); element.addeventlistener(handler.e, proxyfn, usecapture); }); } /** * 移除事件监听 * @param element * @param eventname * @param func * @param selector */ function remove(element, eventname, func, selector) { (eventname || '').split(' ').foreach(function (event) { gethandlers(element, event, func, selector).foreach(function (handler) { delete handlers[getelementid(element)][handler.i]; element.removeeventlistener(handler.e, handler.proxy, false); }); }); } /** * 为元素赋予一个唯一的id * @param element * @returns {number|*} */ function getelementid(element) { return element._elementid || (element._elementid = _elementid++); } /** * 获取匹配的事件 * @param element * @param eventname * @param func * @param selector * @returns {array.} */ function gethandlers(element, eventname, func, selector) { return (handlers[getelementid(element)] || []).filter(function (handler) { return handler && (!eventname || handler.e === eventname) && (!func || handler.fn.tostring() === func.tostring()) && (!selector || handler.sel === selector); }); } })(); /* jshint ignore:start */ return $; })(window, document); /* jshint ignore:end */ /** * ============================================================================= * ************ 定义全局变量 ************ * ============================================================================= */ var $body = $('body'); var $document = $(document); var $window = $(window); /** * 队列 -- 当前队列的 api 和 jquery 不一样,所以不打包进 mdui.jq 里 */ var queue = {}; (function () { var queuedata = []; /** * 写入队列 * @param queuename 对列名 * @param func 函数名,该参数为空时,返回所有队列 */ queue.queue = function (queuename, func) { if (queuedata[queuename] === undefined) { queuedata[queuename] = []; } if (func === undefined) { return queuedata[queuename]; } queuedata[queuename].push(func); }; /** * 从队列中移除第一个函数,并执行该函数 * @param queuename */ queue.dequeue = function (queuename) { if (queuedata[queuename] !== undefined && queuedata[queuename].length) { (queuedata[queuename].shift())(); } }; })(); /** * touch 事件后的 500ms 内禁用 mousedown 事件 * * 不支持触控的屏幕上事件顺序为 mousedown -> mouseup -> click * 支持触控的屏幕上事件顺序为 touchstart -> touchend -> mousedown -> mouseup -> click */ var touchhandler = { touches: 0, /** * 该事件是否被允许 * 在执行事件前调用该方法判断事件是否可以执行 * @param e * @returns {boolean} */ isallow: function (e) { var allow = true; if ( touchhandler.touches && [ 'mousedown', 'mouseup', 'mousemove', 'click', 'mouseover', 'mouseout', 'mouseenter', 'mouseleave', ].indexof(e.type) > -1 ) { // 触发了 touch 事件后阻止鼠标事件 allow = false; } return allow; }, /** * 在 touchstart 和 touchmove、touchend、touchcancel 事件中调用该方法注册事件 * @param e */ register: function (e) { if (e.type === 'touchstart') { // 触发了 touch 事件 touchhandler.touches += 1; } else if (['touchmove', 'touchend', 'touchcancel'].indexof(e.type) > -1) { // touch 事件结束 500ms 后解除对鼠标事件的阻止 settimeout(function () { if (touchhandler.touches) { touchhandler.touches -= 1; } }, 500); } }, start: 'touchstart mousedown', move: 'touchmove mousemove', end: 'touchend mouseup', cancel: 'touchcancel mouseleave', unlock: 'touchend touchmove touchcancel', }; // 测试事件 // 在每一个事件中都使用 touchhandler.isallow(e) 判断事件是否可执行 // 在 touchstart 和 touchmove、touchend、touchcancel // (function () { // // $document // .on(touchhandler.start, function (e) { // if (!touchhandler.isallow(e)) { // return; // } // touchhandler.register(e); // console.log(e.type); // }) // .on(touchhandler.move, function (e) { // if (!touchhandler.isallow(e)) { // return; // } // console.log(e.type); // }) // .on(touchhandler.end, function (e) { // if (!touchhandler.isallow(e)) { // return; // } // console.log(e.type); // }) // .on(touchhandler.unlock, touchhandler.register); // })(); $(function () { // 避免页面加载完后直接执行css动画 // https://css-tricks.com/transitions-only-after-page-load/ settimeout(function () { $body.addclass('mdui-loaded'); }, 0); }); /** * ============================================================================= * ************ mdui 内部使用的函数 ************ * ============================================================================= */ /** * 解析 data api 的参数 * @param str * @returns {*} */ var parseoptions = function (str) { var options = {}; if (str === null || !str) { return options; } if (typeof str === 'object') { return str; } /* jshint ignore:start */ var start = str.indexof('{'); try { options = (new function('', 'var json = ' + str.substr(start) + '; return json.parse(json.stringify(json));'))(); } catch (e) { } /* jshint ignore:end */ return options; }; /** * 绑定组件的事件 * @param eventname 事件名 * @param pluginname 插件名 * @param inst 插件实例 * @param trigger 在该元素上触发 * @param obj 事件参数 */ var componentevent = function (eventname, pluginname, inst, trigger, obj) { if (!obj) { obj = {}; } obj.inst = inst; var fulleventname = eventname + '.mdui.' + pluginname; // jquery 事件 if (typeof jquery !== 'undefined') { jquery(trigger).trigger(fulleventname, obj); } // jq 事件 $(trigger).trigger(fulleventname, obj); }; /** * ============================================================================= * ************ 开放的常用方法 ************ * ============================================================================= */ $.fn.extend({ /** * 执行强制重绘 */ reflow: function () { return this.each(function () { return this.clientleft; }); }, /** * 设置 transition 时间 * @param duration */ transition: function (duration) { if (typeof duration !== 'string') { duration = duration + 'ms'; } return this.each(function () { this.style.webkittransitionduration = duration; this.style.transitionduration = duration; }); }, /** * transition 动画结束回调 * @param callback * @returns {transitionend} */ transitionend: function (callback) { var events = [ 'webkittransitionend', 'transitionend', ]; var i; var _this = this; function firecallback(e) { if (e.target !== this) { return; } callback.call(this, e); for (i = 0; i < events.length; i++) { _this.off(events[i], firecallback); } } if (callback) { for (i = 0; i < events.length; i++) { _this.on(events[i], firecallback); } } return this; }, /** * 设置 transform-origin 属性 * @param transformorigin */ transformorigin: function (transformorigin) { return this.each(function () { this.style.webkittransformorigin = transformorigin; this.style.transformorigin = transformorigin; }); }, /** * 设置 transform 属性 * @param transform */ transform: function (transform) { return this.each(function () { this.style.webkittransform = transform; this.style.transform = transform; }); }, }); $.extend({ /** * 创建并显示遮罩 * @param zindex 遮罩层的 z-index */ showoverlay: function (zindex) { var $overlay = $('.mdui-overlay'); if ($overlay.length) { $overlay.data('isdeleted', 0); if (zindex !== undefined) { $overlay.css('z-index', zindex); } } else { if (zindex === undefined) { zindex = 2000; } $overlay = $('
') .appendto($body) .reflow() .css('z-index', zindex); } var level = $overlay.data('overlay-level') || 0; return $overlay .data('overlay-level', ++level) .addclass('mdui-overlay-show'); }, /** * 隐藏遮罩层 * @param force 是否强制隐藏遮罩 */ hideoverlay: function (force) { var $overlay = $('.mdui-overlay'); if (!$overlay.length) { return; } var level = force ? 1 : $overlay.data('overlay-level'); if (level > 1) { $overlay.data('overlay-level', --level); return; } $overlay .data('overlay-level', 0) .removeclass('mdui-overlay-show') .data('isdeleted', 1) .transitionend(function () { if ($overlay.data('isdeleted')) { $overlay.remove(); } }); }, /** * 锁定屏幕 */ lockscreen: function () { // 不直接把 body 设为 box-sizing: border-box,避免污染全局样式 var newbodywidth = $body.width(); $body .addclass('mdui-locked') .width(newbodywidth); var level = $body.data('lockscreen-level') || 0; $body.data('lockscreen-level', ++level); }, /** * 解除屏幕锁定 * @param force 是否强制解锁屏幕 */ unlockscreen: function (force) { var level = force ? 1 : $body.data('lockscreen-level'); if (level > 1) { $body.data('lockscreen-level', --level); return; } $body .data('lockscreen-level', 0) .removeclass('mdui-locked') .width(''); }, /** * 函数节流 * @param fn * @param delay * @returns {function} */ throttle: function (fn, delay) { var timer = null; if (!delay || delay < 16) { delay = 16; } return function () { var _this = this; var args = arguments; if (timer === null) { timer = settimeout(function () { fn.apply(_this, args); timer = null; }, delay); } }; }, /** * 生成唯一 id * @param pluginname 插件名,若传入该参数,guid 将以该参数作为前缀 * @returns {string} */ guid: function (pluginname) { function s4() { return math.floor((1 + math.random()) * 0x10000) .tostring(16) .substring(1); } var guid = s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); if (pluginname) { guid = 'mdui-' + pluginname + '-' + guid; } return guid; }, }); /** * ============================================================================= * ************ headroom.js ************ * ============================================================================= */ mdui.headroom = (function () { /** * 默认参数 * @type {{}} */ var default = { tolerance: 5, // 滚动条滚动多少距离开始隐藏或显示元素,{down: num, up: num},或数字 offset: 0, // 在页面顶部多少距离内滚动不会隐藏元素 initialclass: 'mdui-headroom', // 初始化时添加的类 pinnedclass: 'mdui-headroom-pinned-top', // 元素固定时添加的类 unpinnedclass: 'mdui-headroom-unpinned-top', // 元素隐藏时添加的类 }; /** * headroom * @param selector * @param opts * @constructor */ function headroom(selector, opts) { var _this = this; _this.$headroom = $(selector).eq(0); if (!_this.$headroom.length) { return; } // 已通过自定义属性实例化过,不再重复实例化 var oldinst = _this.$headroom.data('mdui.headroom'); if (oldinst) { return oldinst; } _this.options = $.extend({}, default, (opts || {})); // 数值转为 {down: bum, up: num} var tolerance = _this.options.tolerance; if (tolerance !== object(tolerance)) { _this.options.tolerance = { down: tolerance, up: tolerance, }; } _this._init(); } /** * 初始化 * @private */ headroom.prototype._init = function () { var _this = this; _this.state = 'pinned'; _this.$headroom .addclass(_this.options.initialclass) .removeclass(_this.options.pinnedclass + ' ' + _this.options.unpinnedclass); _this.inited = false; _this.lastscrolly = 0; _this._attachevent(); }; /** * 监听滚动事件 * @private */ headroom.prototype._attachevent = function () { var _this = this; if (!_this.inited) { _this.lastscrolly = window.pageyoffset; _this.inited = true; $window.on('scroll', function () { _this._scroll(); }); } }; /** * 滚动时的处理 * @private */ headroom.prototype._scroll = function () { var _this = this; _this.rafid = window.requestanimationframe(function () { var currentscrolly = window.pageyoffset; var direction = currentscrolly > _this.lastscrolly ? 'down' : 'up'; var toleranceexceeded = math.abs(currentscrolly - _this.lastscrolly) >= _this.options.tolerance[direction]; if ( currentscrolly > _this.lastscrolly && currentscrolly >= _this.options.offset && toleranceexceeded) { _this.unpin(); } else if ( (currentscrolly < _this.lastscrolly && toleranceexceeded) || currentscrolly <= _this.options.offset ) { _this.pin(); } _this.lastscrolly = currentscrolly; }); }; /** * 动画结束回调 * @param inst */ var transitionend = function (inst) { if (inst.state === 'pinning') { inst.state = 'pinned'; componentevent('pinned', 'headroom', inst, inst.$headroom); } if (inst.state === 'unpinning') { inst.state = 'unpinned'; componentevent('unpinned', 'headroom', inst, inst.$headroom); } }; /** * 固定住 */ headroom.prototype.pin = function () { var _this = this; if ( _this.state === 'pinning' || _this.state === 'pinned' || !_this.$headroom.hasclass(_this.options.initialclass) ) { return; } componentevent('pin', 'headroom', _this, _this.$headroom); _this.state = 'pinning'; _this.$headroom .removeclass(_this.options.unpinnedclass) .addclass(_this.options.pinnedclass) .transitionend(function () { transitionend(_this); }); }; /** * 不固定住 */ headroom.prototype.unpin = function () { var _this = this; if ( _this.state === 'unpinning' || _this.state === 'unpinned' || !_this.$headroom.hasclass(_this.options.initialclass) ) { return; } componentevent('unpin', 'headroom', _this, _this.$headroom); _this.state = 'unpinning'; _this.$headroom .removeclass(_this.options.pinnedclass) .addclass(_this.options.unpinnedclass) .transitionend(function () { transitionend(_this); }); }; /** * 启用 */ headroom.prototype.enable = function () { var _this = this; if (!_this.inited) { _this._init(); } }; /** * 禁用 */ headroom.prototype.disable = function () { var _this = this; if (_this.inited) { _this.inited = false; _this.$headroom .removeclass([ _this.options.initialclass, _this.options.pinnedclass, _this.options.unpinnedclass, ].join(' ')); $window.off('scroll', function () { _this._scroll(); }); window.cancelanimationframe(_this.rafid); } }; /** * 获取当前状态 pinning | pinned | unpinning | unpinned */ headroom.prototype.getstate = function () { return this.state; }; return headroom; })(); /** * ============================================================================= * ************ headroom 自定义属性 api ************ * ============================================================================= */ $(function () { $('[mdui-headroom]').each(function () { var $this = $(this); var options = parseoptions($this.attr('mdui-headroom')); var inst = $this.data('mdui.headroom'); if (!inst) { inst = new mdui.headroom($this, options); $this.data('mdui.headroom', inst); } }); }); /** * ============================================================================= * ************ 供 collapse、 panel 调用的折叠内容块插件 ************ * ============================================================================= */ var collapseprivate = (function () { /** * 默认参数 */ var default = { accordion: false, // 是否使用手风琴效果 }; // 类名 var class = { item: 'mdui-collapse-item', // item 类名 itemopen: 'mdui-collapse-item-open', // 打开状态的 item header: 'mdui-collapse-item-header', // item 中的 header 类名 body: 'mdui-collapse-item-body', // item 中的 body 类名 }; // 命名空间 var namespace = 'collapse'; /** * 折叠内容块 * @param selector * @param opts * @param classes * @param namespace * @constructor */ function collapse(selector, opts, classes, namespace) { var _this = this; _this.classes = $.extend({}, class, classes || {}); _this.namespace = namespace ? namespace : namespace; // 折叠面板元素 _this.$collapse = $(selector).eq(0); if (!_this.$collapse.length) { return; } // 已通过自定义属性实例化过,不再重复实例化 var oldinst = _this.$collapse.data('mdui.' + _this.namespace); if (oldinst) { return oldinst; } _this.options = $.extend({}, default, (opts || {})); _this.$collapse.on('click', '.' + _this.classes.header, function () { var $item = $(this).parent('.' + _this.classes.item); if (_this.$collapse.children($item).length) { _this.toggle($item); } }); } /** * 指定 item 是否处于打开状态 * @param $item * @returns {boolean} * @private */ collapse.prototype._isopen = function ($item) { return $item.hasclass(this.classes.itemopen); }; /** * 获取指定 item * @param item * @returns {*} * @private */ collapse.prototype._getitem = function (item) { var _this = this; if (parseint(item) === item) { // item 是索引号 return _this.$collapse.children('.' + _this.classes.item).eq(item); } return $(item).eq(0); }; /** * 动画结束回调 * @param inst * @param $content * @param $item */ var transitionend = function (inst, $content, $item) { if (inst._isopen($item)) { $content .transition(0) .height('auto') .reflow() .transition(''); componentevent('opened', inst.namespace, inst, $item[0]); } else { $content.height(''); componentevent('closed', inst.namespace, inst, $item[0]); } }; /** * 打开指定面板项 * @param item 面板项的索引号或 dom 元素或 css 选择器 */ collapse.prototype.open = function (item) { var _this = this; var $item = _this._getitem(item); if (_this._isopen($item)) { return; } // 关闭其他项 if (_this.options.accordion) { _this.$collapse.children('.' + _this.classes.itemopen).each(function () { var $tmpitem = $(this); if ($tmpitem !== $item) { _this.close($tmpitem); } }); } var $content = $item.children('.' + _this.classes.body); $content .height($content[0].scrollheight) .transitionend(function () { transitionend(_this, $content, $item); }); componentevent('open', _this.namespace, _this, $item[0]); $item.addclass(_this.classes.itemopen); }; /** * 关闭指定项 * @param item 面板项的索引号或 dom 元素或 css 选择器 */ collapse.prototype.close = function (item) { var _this = this; var $item = _this._getitem(item); if (!_this._isopen($item)) { return; } var $content = $item.children('.' + _this.classes.body); componentevent('close', _this.namespace, _this, $item[0]); $item.removeclass(_this.classes.itemopen); $content .transition(0) .height($content[0].scrollheight) .reflow() .transition('') .height('') .transitionend(function () { transitionend(_this, $content, $item); }); }; /** * 切换指定项的状态 * @param item 面板项的索引号或 dom 元素或 css 选择器或 jq 对象 */ collapse.prototype.toggle = function (item) { var _this = this; var $item = _this._getitem(item); if (_this._isopen($item)) { _this.close($item); } else { _this.open($item); } }; /** * 打开所有项 */ collapse.prototype.openall = function () { var _this = this; _this.$collapse.children('.' + _this.classes.item).each(function () { var $tmpitem = $(this); if (!_this._isopen($tmpitem)) { _this.open($tmpitem); } }); }; /** * 关闭所有项 */ collapse.prototype.closeall = function () { var _this = this; _this.$collapse.children('.' + _this.classes.item).each(function () { var $tmpitem = $(this); if (_this._isopen($tmpitem)) { _this.close($tmpitem); } }); }; return collapse; })(); /** * ============================================================================= * ************ collapse 折叠内容块插件 ************ * ============================================================================= */ mdui.collapse = (function () { function collapse(selector, opts) { return new collapseprivate(selector, opts); } return collapse; })(); /** * ============================================================================= * ************ collapse 自定义属性 ************ * ============================================================================= */ $(function () { $('[mdui-collapse]').each(function () { var $this = $(this); var options = parseoptions($this.attr('mdui-collapse')); var inst = $this.data('mdui.collapse'); if (!inst) { inst = new mdui.collapse($this, options); $this.data('mdui.collapse', inst); } }); }); /** * ============================================================================= * ************ table 表格 ************ * ============================================================================= */ (function () { /** * 生成 checkbox 的 html 结构 * @param tag * @returns {string} */ var checkboxhtml = function (tag) { return '<' + tag + ' class="mdui-table-cell-checkbox">' + '' + ''; }; /** * table 表格 * @param selector * @constructor */ function table(selector) { var _this = this; _this.$table = $(selector).eq(0); if (!_this.$table.length) { return; } _this.init(); } /** * 初始化 */ table.prototype.init = function () { var _this = this; _this.$throw = _this.$table.find('thead tr'); _this.$tdrows = _this.$table.find('tbody tr'); _this.$tdcheckboxs = $(); _this.selectable = _this.$table.hasclass('mdui-table-selectable'); _this.selectedrow = 0; _this._updatethcheckbox(); _this._updatetdcheckbox(); _this._updatenumericcol(); }; /** * 更新表格行的 checkbox */ table.prototype._updatetdcheckbox = function () { var _this = this; _this.$tdrows.each(function () { var $tdrow = $(this); // 移除旧的 checkbox $tdrow.find('.mdui-table-cell-checkbox').remove(); if (!_this.selectable) { return; } // 创建 dom var $checkbox = $(checkboxhtml('td')) .prependto($tdrow) .find('input[type="checkbox"]'); // 默认选中的行 if ($tdrow.hasclass('mdui-table-row-selected')) { $checkbox[0].checked = true; _this.selectedrow++; } // 所有行都选中后,选中表头;否则,不选中表头 _this.$thcheckbox[0].checked = _this.selectedrow === _this.$tdrows.length; // 绑定事件 $checkbox.on('change', function () { if ($checkbox[0].checked) { $tdrow.addclass('mdui-table-row-selected'); _this.selectedrow++; } else { $tdrow.removeclass('mdui-table-row-selected'); _this.selectedrow--; } // 所有行都选中后,选中表头;否则,不选中表头 _this.$thcheckbox[0].checked = _this.selectedrow === _this.$tdrows.length; }); _this.$tdcheckboxs = _this.$tdcheckboxs.add($checkbox); }); }; /** * 更新表头的 checkbox */ table.prototype._updatethcheckbox = function () { var _this = this; // 移除旧的 checkbox _this.$throw.find('.mdui-table-cell-checkbox').remove(); if (!_this.selectable) { return; } _this.$thcheckbox = $(checkboxhtml('th')) .prependto(_this.$throw) .find('input[type="checkbox"]') .on('change', function () { var ischeckedall = _this.$thcheckbox[0].checked; _this.selectedrow = ischeckedall ? _this.$tdrows.length : 0; _this.$tdcheckboxs.each(function (i, checkbox) { checkbox.checked = ischeckedall; }); _this.$tdrows.each(function (i, row) { $(row)[ischeckedall ? 'addclass' : 'removeclass']('mdui-table-row-selected'); }); }); }; /** * 更新数值列 */ table.prototype._updatenumericcol = function () { var _this = this; var $th; var $tdrow; _this.$throw.find('th').each(function (i, th) { $th = $(th); _this.$tdrows.each(function () { $tdrow = $(this); var method = $th.hasclass('mdui-table-col-numeric') ? 'addclass' : 'removeclass'; $tdrow.find('td').eq(i)[method]('mdui-table-col-numeric'); }); }); }; $(function () { // 实例化表格 $('.mdui-table').each(function () { var $table = $(this); if (!$table.data('mdui.table')) { $table.data('mdui.table', new table($table)); } }); }); /** * 更新表格 */ mdui.updatetables = function () { $(arguments.length ? arguments[0] : '.mdui-table').each(function () { var $table = $(this); var inst = $table.data('mdui.table'); if (inst) { inst.init(); } else { $table.data('mdui.table', new table($table)); } }); }; })(); /** * ============================================================================= * ************ 涟漪 ************ * ============================================================================= * * inspired by https://github.com/nolimits4web/framework7/blob/master/src/js/fast-clicks.js * https://github.com/nolimits4web/framework7/blob/master/license * * inspired by https://github.com/fians/waves */ (function () { var ripple = { /** * 显示涟漪动画 * @param e * @param $ripple */ show: function (e, $ripple) { // 鼠标右键不产生涟漪 if (e.button === 2) { return; } // 点击位置坐标 var tmp; if ('touches' in e && e.touches.length) { tmp = e.touches[0]; } else { tmp = e; } var touchstartx = tmp.pagex; var touchstarty = tmp.pagey; // 涟漪位置 var offset = $ripple.offset(); var center = { x: touchstartx - offset.left, y: touchstarty - offset.top, }; var height = $ripple.innerheight(); var width = $ripple.innerwidth(); var diameter = math.max( math.pow((math.pow(height, 2) + math.pow(width, 2)), 0.5), 48 ); // 涟漪扩散动画 var translate = 'translate3d(' + (-center.x + width / 2) + 'px, ' + (-center.y + height / 2) + 'px, 0) ' + 'scale(1)'; // 涟漪的 dom 结构 $('
' + '
') // 缓存动画效果 .data('translate', translate) .prependto($ripple) .reflow() .transform(translate); }, /** * 隐藏涟漪动画 */ hide: function () { var $ripple = $(this); $ripple.children('.mdui-ripple-wave').each(function () { removeripple($(this)); }); $ripple.off('touchmove touchend touchcancel mousemove mouseup mouseleave', ripple.hide); }, }; /** * 隐藏并移除涟漪 * @param $wave */ function removeripple($wave) { if (!$wave.length || $wave.data('isremoved')) { return; } $wave.data('isremoved', true); var removetimeout = settimeout(function () { $wave.remove(); }, 400); var translate = $wave.data('translate'); $wave .addclass('mdui-ripple-wave-fill') .transform(translate.replace('scale(1)', 'scale(1.01)')) .transitionend(function () { cleartimeout(removetimeout); $wave .addclass('mdui-ripple-wave-out') .transform(translate.replace('scale(1)', 'scale(1.01)')); removetimeout = settimeout(function () { $wave.remove(); }, 700); settimeout(function () { $wave.transitionend(function () { cleartimeout(removetimeout); $wave.remove(); }); }, 0); }); } /** * 显示涟漪,并绑定 touchend 等事件 * @param e */ function showripple(e) { if (!touchhandler.isallow(e)) { return; } touchhandler.register(e); var $ripple; var $target = $(e.target); // 获取含 .mdui-ripple 类的元素 if ($target.hasclass('mdui-ripple')) { $ripple = $target; } else { $ripple = $target.parents('.mdui-ripple').eq(0); } if ($ripple.length) { // 禁用状态的元素上不产生涟漪效果 if ($ripple[0].disabled || $ripple.attr('disabled') !== null) { return; } ripple.show(e, $ripple); $ripple.on('touchmove touchend touchcancel mousemove mouseup mouseleave', ripple.hide); } } // 初始化绑定的事件 $document .on(touchhandler.start, showripple) .on(touchhandler.unlock, touchhandler.register); })(); /** * ============================================================================= * ************ text field 文本框 ************ * ============================================================================= */ (function () { var getprop = function (obj, prop) { return ( typeof obj === 'object' && obj !== null && typeof obj[prop] !== 'undefined' && obj[prop] ) ? obj[prop] : false; }; /** * 输入框事件 * @param e */ var inputevent = function (e) { var input = e.target; var $input = $(input); var event = e.type; var value = $input.val(); // reinit 为 true 时,需要重新初始化文本框 var reinit = getprop(e.detail, 'reinit'); // domloadedevent 为 true 时,为 dom 加载完毕后自动触发的事件 var domloadedevent = getprop(e.detail, 'domloadedevent'); // 文本框类型 var type = $input.attr('type') || ''; if (['checkbox', 'button', 'submit', 'range', 'radio', 'image'].indexof(type) >= 0) { return; } var $textfield = $input.parent('.mdui-textfield'); // 输入框是否聚焦 if (event === 'focus') { $textfield.addclass('mdui-textfield-focus'); } if (event === 'blur') { $textfield.removeclass('mdui-textfield-focus'); } // 输入框是否为空 if (event === 'blur' || event === 'input') { $textfield[(value && value !== '') ? 'addclass' : 'removeclass']('mdui-textfield-not-empty'); } // 输入框是否禁用 $textfield[input.disabled ? 'addclass' : 'removeclass']('mdui-textfield-disabled'); // 表单验证 if ((event === 'input' || event === 'blur') && !domloadedevent) { if (input.validity) { $textfield[input.validity.valid ? 'removeclass' : 'addclass']('mdui-textfield-invalid'); } } // textarea 高度自动调整 if (e.target.nodename.tolowercase() === 'textarea') { $input.height(''); var height = input.offsetheight; var diff = height - input.clientheight; var scrollheight = input.scrollheight; if (scrollheight + diff > height) { var newareaheight = scrollheight + diff; $input.height(newareaheight); } } // 实时字数统计 if (reinit) { $textfield .removeclass('mdui-textfield-has-counter') .find('.mdui-textfield-counter') .remove(); } var maxlength = $input.attr('maxlength'); if (maxlength) { if (reinit || domloadedevent) { $('
' + ' / ' + maxlength + '
').appendto($textfield); // 如果没有 .mdui-textfield-error 作为占位,需要增加 .mdui-textfield 的下边距, // 使 .mdui-textfield-counter 不会覆盖在文本框上 if (!$textfield.find('.mdui-textfield-error').length) { $textfield.addclass('mdui-textfield-has-counter'); } } // 字符长度,确保统计方式和 maxlength 一致 var inputed = value.length + value.split('\n').length - 1; $textfield.find('.mdui-textfield-counter-inputed').text(inputed.tostring()); } }; // 绑定事件 $document.on('input focus blur', '.mdui-textfield-input', { usecapture: true }, inputevent); // 可展开文本框展开 $document.on('click', '.mdui-textfield-expandable .mdui-textfield-icon', function () { $(this) // 展开文本框 .parents('.mdui-textfield') .addclass('mdui-textfield-expanded') // 聚焦到输入框 .find('.mdui-textfield-input')[0].focus(); }); // 可展开文本框关闭 $document.on('click', '.mdui-textfield-expanded .mdui-textfield-close', function () { $(this) // 关闭文本框 .parents('.mdui-textfield') .removeclass('mdui-textfield-expanded') // 清空输入框 .find('.mdui-textfield-input') .val(''); }); /** * 通过 js 更新了表单内容,需要重新进行表单处理 * @param- 如果传入了 .mdui-textfield 所在的 dom 元素,则更新该文本框;否则,更新所有文本框 */ mdui.updatetextfields = function () { $(arguments.length ? arguments[0] : '.mdui-textfield').each(function () { $(this) .find('.mdui-textfield-input') .trigger('input', { reinit: true, }); }); }; $(function () { // dom 加载完后自动执行 $('.mdui-textfield-input').each(function () { $(this).trigger('input', { domloadedevent: true, }); }); }); })(); /** * ============================================================================= * ************ slider 滑块 ************ * ============================================================================= */ (function () { /** * 滑块的值变更后修改滑块样式 * @param $slider */ var updatevaluestyle = function ($slider) { var data = $slider.data(); var $track = data.$track; var $fill = data.$fill; var $thumb = data.$thumb; var $input = data.$input; var min = data.min; var max = data.max; var isdisabled = data.disabled; var isdiscrete = data.discrete; var $thumbtext = data.$thumbtext; var value = $input.val(); var percent = (value - min) / (max - min) * 100; $fill.width(percent + '%'); $track.width((100 - percent) + '%'); if (isdisabled) { $fill.css('padding-right', '6px'); $track.css('padding-left', '6px'); } $thumb.css('left', percent + '%'); if (isdiscrete) { $thumbtext.text(value); } $slider[parsefloat(percent) === 0 ? 'addclass' : 'removeclass']('mdui-slider-zero'); }; /** * 重新初始化 * @param $slider */ var reinit = function ($slider) { var $track = $('
'); var $fill = $('
'); var $thumb = $('
'); var $input = $slider.find('input[type="range"]'); // 禁用状态 var isdisabled = $input[0].disabled; $slider[isdisabled ? 'addclass' : 'removeclass']('mdui-slider-disabled'); // 重新填充 html $slider.find('.mdui-slider-track').remove(); $slider.find('.mdui-slider-fill').remove(); $slider.find('.mdui-slider-thumb').remove(); $slider.append($track).append($fill).append($thumb); // 间续型滑块 var isdiscrete = $slider.hasclass('mdui-slider-discrete'); var $thumbtext; if (isdiscrete) { $thumbtext = $(''); $thumb.empty().append($thumbtext); } $slider.data({ $track: $track, $fill: $fill, $thumb: $thumb, $input: $input, min: $input.attr('min'), // 滑块最小值 max: $input.attr('max'), // 滑块最大值 disabled: isdisabled, // 是否禁用状态 discrete: isdiscrete, // 是否是间续型滑块 $thumbtext: $thumbtext, // 间续型滑块的数值 }); // 设置默认值 updatevaluestyle($slider); }; var rangeselector = '.mdui-slider input[type="range"]'; $document // 滑动滑块事件 .on('input change', rangeselector, function () { var $slider = $(this).parent(); updatevaluestyle($slider); }) // 开始触摸滑块事件 .on(touchhandler.start, rangeselector, function (e) { if (!touchhandler.isallow(e)) { return; } touchhandler.register(e); if (!this.disabled) { var $slider = $(this).parent(); $slider.addclass('mdui-slider-focus'); } }) // 结束触摸滑块事件 .on(touchhandler.end, rangeselector, function (e) { if (!touchhandler.isallow(e)) { return; } if (!this.disabled) { var $slider = $(this).parent(); $slider.removeclass('mdui-slider-focus'); } }) .on(touchhandler.unlock, rangeselector, touchhandler.register); /** * 页面加载完后自动初始化 */ $(function () { $('.mdui-slider').each(function () { reinit($(this)); }); }); /** * 重新初始化滑块 */ mdui.updatesliders = function () { $(arguments.length ? arguments[0] : '.mdui-slider').each(function () { reinit($(this)); }); }; })(); /** * ============================================================================= * ************ fab 浮动操作按钮 ************ * ============================================================================= */ mdui.fab = (function () { /** * 默认参数 * @type {{}} */ var default = { trigger: 'hover', // 触发方式 ['hover', 'click'] }; /** * 浮动操作按钮实例 * @param selector 选择器或 html 字符串或 dom 元素或 jq 对象 * @param opts * @constructor */ function fab(selector, opts) { var _this = this; _this.$fab = $(selector).eq(0); if (!_this.$fab.length) { return; } // 已通过 data 属性实例化过,不再重复实例化 var oldinst = _this.$fab.data('mdui.fab'); if (oldinst) { return oldinst; } _this.options = $.extend({}, default, (opts || {})); _this.state = 'closed'; _this.$btn = _this.$fab.find('.mdui-fab'); _this.$dial = _this.$fab.find('.mdui-fab-dial'); _this.$dialbtns = _this.$dial.find('.mdui-fab'); if (_this.options.trigger === 'hover') { _this.$btn .on('touchstart mouseenter', function () { _this.open(); }); _this.$fab .on('mouseleave', function () { _this.close(); }); } if (_this.options.trigger === 'click') { _this.$btn .on(touchhandler.start, function () { _this.open(); }); } // 触摸屏幕其他地方关闭快速拨号 $document.on(touchhandler.start, function (e) { if (!$(e.target).parents('.mdui-fab-wrapper').length) { _this.close(); } }); } /** * 打开菜单 */ fab.prototype.open = function () { var _this = this; if (_this.state === 'opening' || _this.state === 'opened') { return; } // 为菜单中的按钮添加不同的 transition-delay _this.$dialbtns.each(function (index, btn) { btn.style['transition-delay'] = btn.style['-webkit-transition-delay'] = 15 * (_this.$dialbtns.length - index) + 'ms'; }); _this.$dial.addclass('mdui-fab-dial-show'); // 如果按钮中存在 .mdui-fab-opened 的图标,则进行图标切换 if (_this.$btn.find('.mdui-fab-opened').length) { _this.$btn.addclass('mdui-fab-opened'); } _this.state = 'opening'; componentevent('open', 'fab', _this, _this.$fab); // 打开顺序为从下到上逐个打开,最上面的打开后才表示动画完成 _this.$dialbtns.eq(0).transitionend(function () { if (_this.$btn.hasclass('mdui-fab-opened')) { _this.state = 'opened'; componentevent('opened', 'fab', _this, _this.$fab); } }); }; /** * 关闭菜单 */ fab.prototype.close = function () { var _this = this; if (_this.state === 'closing' || _this.state === 'closed') { return; } // 为菜单中的按钮添加不同的 transition-delay _this.$dialbtns.each(function (index, btn) { btn.style['transition-delay'] = btn.style['-webkit-transition-delay'] = 15 * index + 'ms'; }); _this.$dial.removeclass('mdui-fab-dial-show'); _this.$btn.removeclass('mdui-fab-opened'); _this.state = 'closing'; componentevent('close', 'fab', _this, _this.$fab); // 从上往下依次关闭,最后一个关闭后才表示动画完成 _this.$dialbtns.eq(-1).transitionend(function () { if (!_this.$btn.hasclass('mdui-fab-opened')) { _this.state = 'closed'; componentevent('closed', 'fab', _this, _this.$fab); } }); }; /** * 切换菜单的打开状态 */ fab.prototype.toggle = function () { var _this = this; if (_this.state === 'opening' || _this.state === 'opened') { _this.close(); } else if (_this.state === 'closing' || _this.state === 'closed') { _this.open(); } }; /** * 获取当前菜单状态 * @returns {'opening'|'opened'|'closing'|'closed'} */ fab.prototype.getstate = function () { return this.state; }; /** * 以动画的形式显示浮动操作按钮 */ fab.prototype.show = function () { this.$fab.removeclass('mdui-fab-hide'); }; /** * 以动画的形式隐藏浮动操作按钮 */ fab.prototype.hide = function () { this.$fab.addclass('mdui-fab-hide'); }; return fab; })(); /** * ============================================================================= * ************ fab data api ************ * ============================================================================= */ $(function () { // mouseenter 不冒泡,无法进行事件委托,这里用 mouseover 代替。 // 不管是 click 、 mouseover 还是 touchstart ,都先初始化。 $document.on('touchstart mousedown mouseover', '[mdui-fab]', function (e) { var $this = $(this); var inst = $this.data('mdui.fab'); if (!inst) { var options = parseoptions($this.attr('mdui-fab')); inst = new mdui.fab($this, options); $this.data('mdui.fab', inst); } }); }); /** * ============================================================================= * ************ appbar ************ * ============================================================================= * 滚动时自动隐藏应用栏 * mdui-appbar-scroll-hide * mdui-appbar-scroll-toolbar-hide */ $(function () { // 滚动时隐藏应用栏 $('.mdui-appbar-scroll-hide').each(function () { var $this = $(this); $this.data('mdui.headroom', new mdui.headroom($this)); }); // 滚动时只隐藏应用栏中的工具栏 $('.mdui-appbar-scroll-toolbar-hide').each(function () { var $this = $(this); var inst = new mdui.headroom($this, { pinnedclass: 'mdui-headroom-pinned-toolbar', unpinnedclass: 'mdui-headroom-unpinned-toolbar', }); $this.data('mdui.headroom', inst); }); }); /** * ============================================================================= * ************ tab ************ * ============================================================================= */ mdui.tab = (function () { var default = { trigger: 'click', // 触发方式 click: 鼠标点击切换 hover: 鼠标悬浮切换 //animation: false, // 切换时是否显示动画 loop: false, // 为true时,在最后一个选项卡时调用 next() 方法会回到第一个选项卡 }; // 元素是否已禁用 var isdisabled = function ($ele) { return $ele[0].disabled || $ele.attr('disabled') !== null; }; /** * 选项卡 * @param selector * @param opts * @returns {*} * @constructor */ function tab(selector, opts) { var _this = this; _this.$tab = $(selector).eq(0); if (!_this.$tab.length) { return; } // 已通过自定义属性实例化过,不再重复实例化 var oldinst = _this.$tab.data('mdui.tab'); if (oldinst) { return oldinst; } _this.options = $.extend({}, default, (opts || {})); _this.$tabs = _this.$tab.children('a'); _this.$indicator = $('
').appendto(_this.$tab); _this.activeindex = false; // 根据 url hash 获取默认激活的选项卡 var hash = location.hash; if (hash) { _this.$tabs.each(function (i, tab) { if ($(tab).attr('href') === hash) { _this.activeindex = i; return false; } }); } // 含 mdui-tab-active 的元素默认激活 if (_this.activeindex === false) { _this.$tabs.each(function (i, tab) { if ($(tab).hasclass('mdui-tab-active')) { _this.activeindex = i; return false; } }); } // 默认激活第一个选项卡 if (_this.activeindex === false) { _this.activeindex = 0; } // 设置激活状态选项卡 _this._setactive(); // 监听窗口大小变化事件,调整指示器位置 $window.on('resize', $.throttle(function () { _this._setindicatorposition(); }, 100)); // 监听点击选项卡事件 _this.$tabs.each(function (i, tab) { var $tab = $(tab); // 点击或鼠标移入触发的事件 var clickevent = function (e) { // 禁用状态的选项无法选中 if (isdisabled($tab)) { e.preventdefault(); return; } _this.activeindex = i; _this._setactive(); }; // 无论 trigger 是 click 还是 hover,都会响应 click 事件 $tab.on('click', clickevent); // trigger 为 hover 时,额外响应 mouseenter 事件 if (_this.options.trigger === 'hover') { $tab.on('mouseenter', clickevent); } $tab.on('click', function (e) { // 阻止链接的默认点击动作 if ($tab.attr('href').indexof('#') === 0) { e.preventdefault(); } }); }); } /** * 设置激活状态的选项卡 */ tab.prototype._setactive = function () { var _this = this; _this.$tabs.each(function (i, tab) { var $tab = $(tab); var targetid = $tab.attr('href'); // 设置选项卡激活状态 if (i === _this.activeindex && !isdisabled($tab)) { if (!$tab.hasclass('mdui-tab-active')) { componentevent('change', 'tab', _this, _this.$tab, { index: _this.activeindex, target: tab, }); componentevent('show', 'tab', _this, $tab); $tab.addclass('mdui-tab-active'); } $(targetid).show(); _this._setindicatorposition(); } else { $tab.removeclass('mdui-tab-active'); $(targetid).hide(); } }); }; /** * 设置选项卡指示器的位置 */ tab.prototype._setindicatorposition = function () { var _this = this; var $activetab = _this.$tabs.eq(_this.activeindex); if (isdisabled($activetab)) { return; } var activetaboffset = $activetab.offset(); _this.$indicator.css({ left: activetaboffset.left + _this.$tab[0].scrollleft - _this.$tab[0].getboundingclientrect().left + 'px', width: $activetab.width() + 'px', }); }; /** * 切换到下一个选项卡 */ tab.prototype.next = function () { var _this = this; if (_this.$tabs.length > _this.activeindex + 1) { _this.activeindex++; } else if (_this.options.loop) { _this.activeindex = 0; } _this._setactive(); }; /** * 切换到上一个选项卡 */ tab.prototype.prev = function () { var _this = this; if (_this.activeindex > 0) { _this.activeindex--; } else if (_this.options.loop) { _this.activeindex = _this.$tabs.length - 1; } _this._setactive(); }; /** * 显示指定序号或指定id的选项卡 * @param index 从0开始的序号,或以#开头的id */ tab.prototype.show = function (index) { var _this = this; if (parseint(index) === index) { _this.activeindex = index; } else { _this.$tabs.each(function (i, tab) { if (tab.id === index) { _this.activeindex = i; return false; } }); } _this._setactive(); }; /** * 在父元素的宽度变化时,需要调用该方法重新调整指示器位置 */ tab.prototype.handleupdate = function () { this._setindicatorposition(); }; return tab; })(); /** * ============================================================================= * ************ tab 自定义属性 api ************ * ============================================================================= */ $(function () { $('[mdui-tab]').each(function () { var $this = $(this); var inst = $this.data('mdui.tab'); if (!inst) { inst = new mdui.tab($this, parseoptions($this.attr('mdui-tab'))); $this.data('mdui.tab', inst); } }); }); /** * ============================================================================= * ************ drawer 抽屉栏 ************ * ============================================================================= * * 在桌面设备上默认显示抽屉栏,不显示遮罩层 * 在手机和平板设备上默认不显示抽屉栏,始终显示遮罩层,且覆盖导航栏 */ mdui.drawer = (function () { /** * 默认参数 * @type {{}} */ var default = { // 在桌面设备上是否显示遮罩层。手机和平板不受这个参数影响,始终会显示遮罩层 overlay: false, }; var isdesktop = function () { return $window.width() >= 1024; }; /** * 抽屉栏实例 * @param selector 选择器或 html 字符串或 dom 元素 * @param opts * @constructor */ function drawer(selector, opts) { var _this = this; _this.$drawer = $(selector).eq(0); if (!_this.$drawer.length) { return; } var oldinst = _this.$drawer.data('mdui.drawer'); if (oldinst) { return oldinst; } _this.options = $.extend({}, default, (opts || {})); _this.overlay = false; // 是否显示着遮罩层 _this.position = _this.$drawer.hasclass('mdui-drawer-right') ? 'right' : 'left'; if (_this.$drawer.hasclass('mdui-drawer-close')) { _this.state = 'closed'; } else if (_this.$drawer.hasclass('mdui-drawer-open')) { _this.state = 'opened'; } else if (isdesktop()) { _this.state = 'opened'; } else { _this.state = 'closed'; } // 浏览器窗口大小调整时 $window.on('resize', $.throttle(function () { // 由手机平板切换到桌面时 if (isdesktop()) { // 如果显示着遮罩,则隐藏遮罩 if (_this.overlay && !_this.options.overlay) { $.hideoverlay(); _this.overlay = false; $.unlockscreen(); } // 没有强制关闭,则状态为打开状态 if (!_this.$drawer.hasclass('mdui-drawer-close')) { _this.state = 'opened'; } } // 由桌面切换到手机平板时。如果抽屉栏是打开着的且没有遮罩层,则关闭抽屉栏 else { if (!_this.overlay && _this.state === 'opened') { // 抽屉栏处于强制打开状态,添加遮罩 if (_this.$drawer.hasclass('mdui-drawer-open')) { $.showoverlay(); _this.overlay = true; $.lockscreen(); $('.mdui-overlay').one('click', function () { _this.close(); }); } else { _this.state = 'closed'; } } } }, 100)); // 绑定关闭按钮事件 _this.$drawer.find('[mdui-drawer-close]').each(function () { $(this).on('click', function () { _this.close(); }); }); } /** * 动画结束回调 * @param inst */ var transitionend = function (inst) { if (inst.$drawer.hasclass('mdui-drawer-open')) { inst.state = 'opened'; componentevent('opened', 'drawer', inst, inst.$drawer); } else { inst.state = 'closed'; componentevent('closed', 'drawer', inst, inst.$drawer); } }; /** * 打开抽屉栏 */ drawer.prototype.open = function () { var _this = this; if (_this.state === 'opening' || _this.state === 'opened') { return; } _this.state = 'opening'; componentevent('open', 'drawer', _this, _this.$drawer); if (!_this.options.overlay) { $body.addclass('mdui-drawer-body-' + _this.position); } _this.$drawer .removeclass('mdui-drawer-close') .addclass('mdui-drawer-open') .transitionend(function () { transitionend(_this); }); if (!isdesktop() || _this.options.overlay) { _this.overlay = true; $.showoverlay().one('click', function () { _this.close(); }); $.lockscreen(); } }; /** * 关闭抽屉栏 */ drawer.prototype.close = function () { var _this = this; if (_this.state === 'closing' || _this.state === 'closed') { return; } _this.state = 'closing'; componentevent('close', 'drawer', _this, _this.$drawer); if (!_this.options.overlay) { $body.removeclass('mdui-drawer-body-' + _this.position); } _this.$drawer .addclass('mdui-drawer-close') .removeclass('mdui-drawer-open') .transitionend(function () { transitionend(_this); }); if (_this.overlay) { $.hideoverlay(); _this.overlay = false; $.unlockscreen(); } }; /** * 切换抽屉栏打开/关闭状态 */ drawer.prototype.toggle = function () { var _this = this; if (_this.state === 'opening' || _this.state === 'opened') { _this.close(); } else if (_this.state === 'closing' || _this.state === 'closed') { _this.open(); } }; /** * 获取抽屉栏状态 * @returns {'opening'|'opened'|'closing'|'closed'} */ drawer.prototype.getstate = function () { return this.state; }; return drawer; })(); /** * ============================================================================= * ************ drawer 自定义属性 api ************ * ============================================================================= */ $(function () { $('[mdui-drawer]').each(function () { var $this = $(this); var options = parseoptions($this.attr('mdui-drawer')); var selector = options.target; delete options.target; var $drawer = $(selector).eq(0); var inst = $drawer.data('mdui.drawer'); if (!inst) { inst = new mdui.drawer($drawer, options); $drawer.data('mdui.drawer', inst); } $this.on('click', function () { inst.toggle(); }); }); }); /** * ============================================================================= * ************ dialog 对话框 ************ * ============================================================================= */ mdui.dialog = (function () { /** * 默认参数 */ var default = { history: true, // 监听 hashchange 事件 overlay: true, // 打开对话框时是否显示遮罩 modal: false, // 是否模态化对话框,为 false 时点击对话框外面区域关闭对话框,为 true 时不关闭 closeonesc: true, // 按下 esc 关闭对话框 closeoncancel: true, // 按下取消按钮时关闭对话框 closeonconfirm: true, // 按下确认按钮时关闭对话框 destroyonclosed: false, // 关闭后销毁 }; /** * 遮罩层元素 */ var $overlay; /** * 窗口是否已锁定 */ var islockscreen; /** * 当前对话框实例 */ var currentinst; /** * 队列名 * @type {string} */ var queuename = '__md_dialog'; /** * 窗口宽度变化,或对话框内容变化时,调整对话框位置和对话框内的滚动条 */ var readjust = function () { if (!currentinst) { return; } var $dialog = currentinst.$dialog; var $dialogtitle = $dialog.children('.mdui-dialog-title'); var $dialogcontent = $dialog.children('.mdui-dialog-content'); var $dialogactions = $dialog.children('.mdui-dialog-actions'); // 调整 dialog 的 top 和 height 值 $dialog.height(''); $dialogcontent.height(''); var dialogheight = $dialog.height(); $dialog.css({ top: (($window.height() - dialogheight) / 2) + 'px', height: dialogheight + 'px', }); // 调整 mdui-dialog-content 的高度 $dialogcontent.height( dialogheight - ($dialogtitle.height() || 0) - ($dialogactions.height() || 0) ); }; /** * hashchange 事件触发时关闭对话框 */ var hashchangeevent = function () { if (location.hash.substring(1).indexof('&mdui-dialog') < 0) { currentinst.close(true); } }; /** * 点击遮罩层关闭对话框 * @param e */ var overlayclick = function (e) { if ($(e.target).hasclass('mdui-overlay')) { currentinst.close(); } }; /** * 对话框实例 * @param selector 选择器或 html 字符串或 dom 元素 * @param opts * @constructor */ function dialog(selector, opts) { var _this = this; // 对话框元素 _this.$dialog = $(selector).eq(0); if (!_this.$dialog.length) { return; } // 已通过 data 属性实例化过,不再重复实例化 var oldinst = _this.$dialog.data('mdui.dialog'); if (oldinst) { return oldinst; } // 如果对话框元素没有在当前文档中,则需要添加 if (!$.contains($body[0], _this.$dialog[0])) { _this.append = true; $body.append(_this.$dialog); } _this.options = $.extend({}, default, (opts || {})); _this.state = 'closed'; // 绑定取消按钮事件 _this.$dialog.find('[mdui-dialog-cancel]').each(function () { $(this).on('click', function () { componentevent('cancel', 'dialog', _this, _this.$dialog); if (_this.options.closeoncancel) { _this.close(); } }); }); // 绑定确认按钮事件 _this.$dialog.find('[mdui-dialog-confirm]').each(function () { $(this).on('click', function () { componentevent('confirm', 'dialog', _this, _this.$dialog); if (_this.options.closeonconfirm) { _this.close(); } }); }); // 绑定关闭按钮事件 _this.$dialog.find('[mdui-dialog-close]').each(function () { $(this).on('click', function () { _this.close(); }); }); } /** * 打开指定对话框 * @private */ dialog.prototype._doopen = function () { var _this = this; currentinst = _this; if (!islockscreen) { $.lockscreen(); islockscreen = true; } _this.$dialog.show(); readjust(); $window.on('resize', $.throttle(function () { readjust(); }, 100)); // 打开消息框 _this.state = 'opening'; componentevent('open', 'dialog', _this, _this.$dialog); _this.$dialog .addclass('mdui-dialog-open') .transitionend(function () { if (_this.$dialog.hasclass('mdui-dialog-open')) { _this.state = 'opened'; componentevent('opened', 'dialog', _this, _this.$dialog); } else { _this.state = 'closed'; componentevent('closed', 'dialog', _this, _this.$dialog); } }); // 不存在遮罩层元素时,添加遮罩层 if (!$overlay) { $overlay = $.showoverlay(5100); } $overlay // 点击遮罩层时是否关闭对话框 [_this.options.modal ? 'off' : 'on']('click', overlayclick) // 是否显示遮罩层,不显示时,把遮罩层背景透明 .css('opacity', _this.options.overlay ? '' : 0); if (_this.options.history) { // 如果 hash 中原来就有 &mdui-dialog,先删除,避免后退历史纪录后仍然有 &mdui-dialog 导致无法关闭 var hash = location.hash.substring(1); if (hash.indexof('&mdui-dialog') > -1) { hash = hash.replace(/&mdui-dialog/g, ''); } // 后退按钮关闭对话框 location.hash = hash + '&mdui-dialog'; $window.on('hashchange', hashchangeevent); } }; /** * 打开对话框 */ dialog.prototype.open = function () { var _this = this; if (_this.state === 'opening' || _this.state === 'opened') { return; } // 如果当前有正在打开或已经打开的对话框,或队列不为空,则先加入队列,等旧对话框开始关闭时再打开 if ( (currentinst && (currentinst.state === 'opening' || currentinst.state === 'opened')) || queue.queue(queuename).length ) { queue.queue(queuename, function () { _this._doopen(); }); return; } _this._doopen(); }; /** * 关闭对话框 */ dialog.prototype.close = function () { var _this = this; if (_this.state === 'closing' || _this.state === 'closed') { return; } currentinst = null; _this.state = 'closing'; componentevent('close', 'dialog', _this, _this.$dialog); // 所有对话框都关闭,且当前没有打开的对话框时,隐藏遮罩 if (queue.queue(queuename).length === 0 && $overlay) { $.hideoverlay(); $overlay = null; } _this.$dialog .removeclass('mdui-dialog-open') .transitionend(function () { if (!_this.$dialog.hasclass('mdui-dialog-open')) { _this.state = 'closed'; componentevent('closed', 'dialog', _this, _this.$dialog); _this.$dialog.hide(); // 所有对话框都关闭,且当前没有打开的对话框时,解锁屏幕 if (queue.queue(queuename).length === 0 && !currentinst && islockscreen) { $.unlockscreen(); islockscreen = false; } $window.off('resize', $.throttle(function () { readjust(); }, 100)); if (_this.options.destroyonclosed) { _this.destroy(); } } else { _this.state = 'opened'; componentevent('opened', 'dialog', _this, _this.$dialog); } }); if (_this.options.history && queue.queue(queuename).length === 0) { // 是否需要后退历史纪录,默认为 false。 // 为 false 时是通过 js 关闭,需要后退一个历史记录 // 为 true 时是通过后退按钮关闭,不需要后退历史记录 if (!arguments[0]) { window.history.back(); } $window.off('hashchange', hashchangeevent); } // 关闭旧对话框,打开新对话框。 // 加一点延迟,仅仅为了视觉效果更好。不加延时也不影响功能 settimeout(function () { queue.dequeue(queuename); }, 100); }; /** * 切换对话框打开/关闭状态 */ dialog.prototype.toggle = function () { var _this = this; if (_this.state === 'opening' || _this.state === 'opened') { _this.close(); } else if (_this.state === 'closing' || _this.state === 'closed') { _this.open(); } }; /** * 获取对话框状态 * @returns {'opening'|'opened'|'closing'|'closed'} */ dialog.prototype.getstate = function () { return this.state; }; /** * 销毁对话框 */ dialog.prototype.destroy = function () { var _this = this; if (_this.append) { _this.$dialog.remove(); } _this.$dialog.removedata('mdui.dialog'); if (queue.queue(queuename).length === 0 && !currentinst) { if ($overlay) { $.hideoverlay(); $overlay = null; } if (islockscreen) { $.unlockscreen(); islockscreen = false; } } }; /** * 对话框内容变化时,需要调用该方法来调整对话框位置和滚动条高度 */ dialog.prototype.handleupdate = function () { readjust(); }; // esc 按下时关闭对话框 $document.on('keydown', function (e) { if ( currentinst && currentinst.options.closeonesc && currentinst.state === 'opened' && e.keycode === 27 ) { currentinst.close(); } }); return dialog; })(); /** * ============================================================================= * ************ dialog data api ************ * ============================================================================= */ $(function () { $document.on('click', '[mdui-dialog]', function () { var $this = $(this); var options = parseoptions($this.attr('mdui-dialog')); var selector = options.target; delete options.target; var $dialog = $(selector).eq(0); var inst = $dialog.data('mdui.dialog'); if (!inst) { inst = new mdui.dialog($dialog, options); $dialog.data('mdui.dialog', inst); } inst.open(); }); }); /** * ============================================================================= * ************ mdui.dialog(options) ************ * ============================================================================= */ mdui.dialog = function (options) { /** * 默认参数 */ var default = { title: '', // 标题 content: '', // 文本 buttons: [], // 按钮 stackedbuttons: false, // 垂直排列按钮 cssclass: '', // 在 dialog 上添加的 css 类 history: true, // 监听 hashchange 事件 overlay: true, // 是否显示遮罩 modal: false, // 是否模态化对话框 closeonesc: true, // 按下 esc 时关闭对话框 destroyonclosed: true, // 关闭后销毁 onopen: function () { // 打开动画开始时的回调 }, onopened: function () { // 打开动画结束后的回调 }, onclose: function () { // 关闭动画开始时的回调 }, onclosed: function () { // 关闭动画结束时的回调 }, }; /** * 按钮的默认参数 */ var default_button = { text: '', // 按钮文本 bold: false, // 按钮文本是否加粗 close: true, // 点击按钮后关闭对话框 onclick: function (inst) { // 点击按钮的回调 }, }; // 合并参数 options = $.extend({}, default, (options || {})); $.each(options.buttons, function (i, button) { options.buttons[i] = $.extend({}, default_button, button); }); // 按钮的 html var buttonshtml = ''; if (options.buttons.length) { buttonshtml = '
'; $.each(options.buttons, function (i, button) { buttonshtml += '' + button.text + ''; }); buttonshtml += '
'; } // dialog 的 html var html = '
' + (options.title ? '
' + options.title + '
' : '') + (options.content ? '
' + options.content + '
' : '') + buttonshtml + '
'; // 实例化 dialog var inst = new mdui.dialog(html, { history: options.history, overlay: options.overlay, modal: options.modal, closeonesc: options.closeonesc, destroyonclosed: options.destroyonclosed, }); // 绑定按钮事件 if (options.buttons.length) { inst.$dialog.find('.mdui-dialog-actions .mdui-btn').each(function (i, button) { $(button).on('click', function () { if (typeof options.buttons[i].onclick === 'function') { options.buttons[i].onclick(inst); } if (options.buttons[i].close) { inst.close(); } }); }); } // 绑定打开关闭事件 if (typeof options.onopen === 'function') { inst.$dialog .on('open.mdui.dialog', function () { options.onopen(inst); }) .on('opened.mdui.dialog', function () { options.onopened(inst); }) .on('close.mdui.dialog', function () { options.onclose(inst); }) .on('closed.mdui.dialog', function () { options.onclosed(inst); }); } inst.open(); return inst; }; /** * ============================================================================= * ************ mdui.alert(text, title, onconfirm, options) ************ * ************ mdui.alert(text, onconfirm, options) ************ * ============================================================================= */ mdui.alert = function (text, title, onconfirm, options) { // title 参数可选 if (typeof title === 'function') { title = ''; onconfirm = arguments[1]; options = arguments[2]; } if (onconfirm === undefined) { onconfirm = function () {}; } if (options === undefined) { options = {}; } /** * 默认参数 */ var default = { confirmtext: 'ok', // 按钮上的文本 history: true, // 监听 hashchange 事件 modal: false, // 是否模态化对话框,为 false 时点击对话框外面区域关闭对话框,为 true 时不关闭 closeonesc: true, // 按下 esc 关闭对话框 }; options = $.extend({}, default, options); return mdui.dialog({ title: title, content: text, buttons: [ { text: options.confirmtext, bold: false, close: true, onclick: onconfirm, }, ], cssclass: 'mdui-dialog-alert', history: options.history, modal: options.modal, closeonesc: options.closeonesc, }); }; /** * ============================================================================= * ************ mdui.confirm(text, title, onconfirm, oncancel, options) ************ * ************ mdui.confirm(text, onconfirm, oncancel, options) ************ * ============================================================================= */ mdui.confirm = function (text, title, onconfirm, oncancel, options) { // title 参数可选 if (typeof title === 'function') { title = ''; onconfirm = arguments[1]; oncancel = arguments[2]; options = arguments[3]; } if (onconfirm === undefined) { onconfirm = function () {}; } if (oncancel === undefined) { oncancel = function () {}; } if (options === undefined) { options = {}; } /** * 默认参数 */ var default = { confirmtext: 'ok', // 确认按钮的文本 canceltext: 'cancel', // 取消按钮的文本 history: true, // 监听 hashchange 事件 modal: false, // 是否模态化对话框,为 false 时点击对话框外面区域关闭对话框,为 true 时不关闭 closeonesc: true, // 按下 esc 关闭对话框 }; options = $.extend({}, default, options); return mdui.dialog({ title: title, content: text, buttons: [ { text: options.canceltext, bold: false, close: true, onclick: oncancel, }, { text: options.confirmtext, bold: false, close: true, onclick: onconfirm, }, ], cssclass: 'mdui-dialog-confirm', history: options.history, modal: options.modal, closeonesc: options.closeonesc, }); }; /** * ============================================================================= * ************ mdui.prompt(label, title, onconfirm, oncancel, options) ************ * ************ mdui.prompt(label, onconfirm, oncancel, options) ************ * ============================================================================= */ mdui.prompt = function (label, title, onconfirm, oncancel, options) { // title 参数可选 if (typeof title === 'function') { title = ''; onconfirm = arguments[1]; oncancel = arguments[2]; options = arguments[3]; } if (onconfirm === undefined) { onconfirm = function () {}; } if (oncancel === undefined) { oncancel = function () {}; } if (options === undefined) { options = {}; } /** * 默认参数 */ var default = { confirmtext: 'ok', // 确认按钮的文本 canceltext: 'cancel', // 取消按钮的文本 history: true, // 监听 hashchange 事件 modal: false, // 是否模态化对话框,为 false 时点击对话框外面区域关闭对话框,为 true 时不关闭 closeonesc: true, // 按下 esc 关闭对话框 type: 'text', // 输入框类型,text: 单行文本框 textarea: 多行文本框 maxlength: '', // 最大输入字符数 defaultvalue: '', // 输入框中的默认文本 }; options = $.extend({}, default, options); var content = '
' + (label ? '' : '') + (options.type === 'text' ? '' : '') + (options.type === 'textarea' ? '' : '') + '
'; return mdui.dialog({ title: title, content: content, buttons: [ { text: options.canceltext, bold: false, close: true, onclick: function (inst) { var value = inst.$dialog.find('.mdui-textfield-input').val(); oncancel(value, inst); }, }, { text: options.confirmtext, bold: false, close: true, onclick: function (inst) { var value = inst.$dialog.find('.mdui-textfield-input').val(); onconfirm(value, inst); }, }, ], cssclass: 'mdui-dialog-prompt', history: options.history, modal: options.modal, closeonesc: options.closeonesc, onopen: function (inst) { // 初始化输入框 var $input = inst.$dialog.find('.mdui-textfield-input'); mdui.updatetextfields($input); // 聚焦到输入框 $input[0].focus(); // 如果是多行输入框,监听输入框的 input 事件,更新对话框高度 if (options.type === 'textarea') { $input.on('input', function () { inst.handleupdate(); }); } // 有字符数限制时,加载完文本框后 dom 会变化,需要更新对话框高度 if (options.maxlength) { inst.handleupdate(); } }, }); }; /** * ============================================================================= * ************ tooltip 工具提示 ************ * ============================================================================= */ mdui.tooltip = (function () { /** * 默认参数 */ var default = { position: 'auto', // 提示所在位置 delay: 0, // 延迟,单位毫秒 content: '', // 提示文本,允许包含 html }; /** * 是否是桌面设备 * @returns {boolean} */ var isdesktop = function () { return $window.width() > 1024; }; /** * 设置 tooltip 的位置 * @param inst */ function setposition(inst) { var marginleft; var margintop; var position; // 触发的元素 var targetprops = inst.$target[0].getboundingclientrect(); // 触发的元素和 tooltip 之间的距离 var targetmargin = (isdesktop() ? 14 : 24); // tooltip 的宽度和高度 var tooltipwidth = inst.$tooltip[0].offsetwidth; var tooltipheight = inst.$tooltip[0].offsetheight; // tooltip 的方向 position = inst.options.position; // 自动判断位置,加 2px,使 tooltip 距离窗口边框至少有 2px 的间距 if (['bottom', 'top', 'left', 'right'].indexof(position) === -1) { if ( targetprops.top + targetprops.height + targetmargin + tooltipheight + 2 < $window.height() ) { position = 'bottom'; } else if (targetmargin + tooltipheight + 2 < targetprops.top) { position = 'top'; } else if (targetmargin + tooltipwidth + 2 < targetprops.left) { position = 'left'; } else if ( targetprops.width + targetmargin + tooltipwidth + 2 < $window.width() - targetprops.left ) { position = 'right'; } else { position = 'bottom'; } } // 设置位置 switch (position) { case 'bottom': marginleft = -1 * (tooltipwidth / 2); margintop = (targetprops.height / 2) + targetmargin; inst.$tooltip.transformorigin('top center'); break; case 'top': marginleft = -1 * (tooltipwidth / 2); margintop = -1 * (tooltipheight + (targetprops.height / 2) + targetmargin); inst.$tooltip.transformorigin('bottom center'); break; case 'left': marginleft = -1 * (tooltipwidth + (targetprops.width / 2) + targetmargin); margintop = -1 * (tooltipheight / 2); inst.$tooltip.transformorigin('center right'); break; case 'right': marginleft = (targetprops.width / 2) + targetmargin; margintop = -1 * (tooltipheight / 2); inst.$tooltip.transformorigin('center left'); break; } var targetoffset = inst.$target.offset(); inst.$tooltip.css({ top: targetoffset.top + (targetprops.height / 2) + 'px', left: targetoffset.left + (targetprops.width / 2) + 'px', 'margin-left': marginleft + 'px', 'margin-top': margintop + 'px', }); } /** * tooltip 实例 * @param selector * @param opts * @constructor */ function tooltip(selector, opts) { var _this = this; _this.$target = $(selector).eq(0); if (!_this.$target.length) { return; } // 已通过 data 属性实例化过,不再重复实例化 var oldinst = _this.$target.data('mdui.tooltip'); if (oldinst) { return oldinst; } _this.options = $.extend({}, default, (opts || {})); _this.state = 'closed'; // 创建 tooltip html var guid = $.guid('tooltip'); _this.$tooltip = $( '
' + _this.options.content + '
' ).appendto($body); // 绑定事件 _this.$target .on('touchstart mouseenter', function (e) { if (!touchhandler.isallow(e)) { return; } touchhandler.register(e); _this.open(); }) .on('touchend mouseleave', function (e) { if (!touchhandler.isallow(e)) { return; } _this.close(); }) .on(touchhandler.unlock, touchhandler.register); } /** * 动画结束回调 * @private */ var transitionend = function (inst) { if (inst.$tooltip.hasclass('mdui-tooltip-open')) { inst.state = 'opened'; componentevent('opened', 'tooltip', inst, inst.$target); } else { inst.state = 'closed'; componentevent('closed', 'tooltip', inst, inst.$target); } }; /** * 执行打开 tooltip * @private */ tooltip.prototype._doopen = function () { var _this = this; _this.state = 'opening'; componentevent('open', 'tooltip', _this, _this.$target); _this.$tooltip .addclass('mdui-tooltip-open') .transitionend(function () { transitionend(_this); }); }; /** * 打开 tooltip * @param opts 允许每次打开时设置不同的参数 */ tooltip.prototype.open = function (opts) { var _this = this; if (_this.state === 'opening' || _this.state === 'opened') { return; } var oldopts = _this.options; // 合并 data 属性参数 $.extend(_this.options, parseoptions(_this.$target.attr('mdui-tooltip'))); if (opts) { $.extend(_this.options, opts); } // tooltip 的内容有更新 if (oldopts.content !== _this.options.content) { _this.$tooltip.html(_this.options.content); } setposition(_this); if (_this.options.delay) { _this.timeoutid = settimeout(function () { _this._doopen(); }, _this.options.delay); } else { _this.timeoutid = false; _this._doopen(); } }; /** * 关闭 tooltip */ tooltip.prototype.close = function () { var _this = this; if (_this.timeoutid) { cleartimeout(_this.timeoutid); _this.timeoutid = false; } if (_this.state === 'closing' || _this.state === 'closed') { return; } _this.state = 'closing'; componentevent('close', 'tooltip', _this, _this.$target); _this.$tooltip .removeclass('mdui-tooltip-open') .transitionend(function () { transitionend(_this); }); }; /** * 切换 tooltip 状态 */ tooltip.prototype.toggle = function () { var _this = this; if (_this.state === 'opening' || _this.state === 'opened') { _this.close(); } else if (_this.state === 'closing' || _this.state === 'closed') { _this.open(); } }; /** * 获取 tooltip 状态 * @returns {'opening'|'opened'|'closing'|'closed'} */ tooltip.prototype.getstate = function () { return this.state; }; /** * 销毁 tooltip */ /*tooltip.prototype.destroy = function () { var _this = this; cleartimeout(_this.timeoutid); $.data(_this.target, 'mdui.tooltip', null); $.remove(_this.tooltip); };*/ return tooltip; })(); /** * ============================================================================= * ************ tooltip data api ************ * ============================================================================= */ $(function () { // mouseenter 不能冒泡,所以这里用 mouseover 代替 $document.on('touchstart mouseover', '[mdui-tooltip]', function () { var $this = $(this); var inst = $this.data('mdui.tooltip'); if (!inst) { var options = parseoptions($this.attr('mdui-tooltip')); inst = new mdui.tooltip($this, options); $this.data('mdui.tooltip', inst); } }); }); /** * ============================================================================= * ************ snackbar ************ * ============================================================================= */ (function () { /** * 当前打开着的 snackbar */ var currentinst; /** * 对列名 * @type {string} */ var queuename = '__md_snackbar'; var default = { message: '', // 文本内容 timeout: 4000, // 在用户没有操作时多长时间自动隐藏 buttontext: '', // 按钮的文本 buttoncolor: '', // 按钮的颜色,支持 blue #90caf9 rgba(...) closeonbuttonclick: true, // 点击按钮时关闭 closeonoutsideclick: true, // 触摸或点击屏幕其他地方时关闭 onclick: function () { // 在 snackbar 上点击的回调 }, onbuttonclick: function () { // 点击按钮的回调 }, onclose: function () { // 关闭动画开始时的回调 }, }; /** * 点击 snackbar 外面的区域关闭 * @param e */ var closeonoutsideclick = function (e) { var $target = $(e.target); if (!$target.hasclass('mdui-snackbar') && !$target.parents('.mdui-snackbar').length) { currentinst.close(); } }; /** * snackbar 实例 * @param opts * @constructor */ function snackbar(opts) { var _this = this; _this.options = $.extend({}, default, (opts || {})); // message 参数必须 if (!_this.options.message) { return; } _this.state = 'closed'; _this.timeoutid = false; // 按钮颜色 var buttoncolorstyle = ''; var buttoncolorclass = ''; if ( _this.options.buttoncolor.indexof('#') === 0 || _this.options.buttoncolor.indexof('rgb') === 0 ) { buttoncolorstyle = 'style="color:' + _this.options.buttoncolor + '"'; } else if (_this.options.buttoncolor !== '') { buttoncolorclass = 'mdui-text-color-' + _this.options.buttoncolor; } // 添加 html _this.$snackbar = $( '
' + '
' + _this.options.message + '
' + (_this.options.buttontext ? ('' + _this.options.buttontext + '') : '' ) + '
') .appendto($body); // 设置位置 _this.$snackbar .transform('translatey(' + _this.$snackbar[0].clientheight + 'px)') .css('left', (document.body.clientwidth - _this.$snackbar[0].clientwidth) / 2 + 'px') .addclass('mdui-snackbar-transition'); } /** * 打开 snackbar */ snackbar.prototype.open = function () { var _this = this; if (_this.state === 'opening' || _this.state === 'opened') { return; } // 如果当前有正在显示的 snackbar,则先加入队列,等旧 snackbar 关闭后再打开 if (currentinst) { queue.queue(queuename, function () { _this.open(); }); return; } currentinst = _this; // 开始打开 _this.state = 'opening'; _this.$snackbar .transform('translatey(0)') .transitionend(function () { if (_this.state !== 'opening') { return; } _this.state = 'opened'; // 有按钮时绑定事件 if (_this.options.buttontext) { _this.$snackbar .find('.mdui-snackbar-action') .on('click', function () { _this.options.onbuttonclick(); if (_this.options.closeonbuttonclick) { _this.close(); } }); } // 点击 snackbar 的事件 _this.$snackbar.on('click', function (e) { if (!$(e.target).hasclass('mdui-snackbar-action')) { _this.options.onclick(); } }); // 点击 snackbar 外面的区域关闭 if (_this.options.closeonoutsideclick) { $document.on(touchhandler.start, closeonoutsideclick); } // 超时后自动关闭 _this.timeoutid = settimeout(function () { _this.close(); }, _this.options.timeout); }); }; /** * 关闭 snackbar */ snackbar.prototype.close = function () { var _this = this; if (_this.state === 'closing' || _this.state === 'closed') { return; } if (_this.timeoutid) { cleartimeout(_this.timeoutid); } if (_this.options.closeonoutsideclick) { $document.off(touchhandler.start, closeonoutsideclick); } _this.state = 'closing'; _this.options.onclose(); _this.$snackbar .transform('translatey(' + _this.$snackbar[0].clientheight + 'px)') .transitionend(function () { if (_this.state !== 'closing') { return; } currentinst = null; _this.state = 'closed'; _this.$snackbar.remove(); queue.dequeue(queuename); }); }; /** * 打开 snackbar * @param params */ mdui.snackbar = function (params) { var inst = new snackbar(params); inst.open(); return inst; }; })(); /** * ============================================================================= * ************ bottom navigation 底部导航栏 ************ * ============================================================================= */ (function () { // 切换导航项 $document.on('click', '.mdui-bottom-nav>a', function () { var $this = $(this); var $bottomnav = $this.parent(); var isthis; $bottomnav.children('a').each(function (i, item) { isthis = $this.is(item); if (isthis) { componentevent('change', 'bottomnav', null, $bottomnav, { index: i, }); } $(item)[isthis ? 'addclass' : 'removeclass']('mdui-bottom-nav-active'); }); }); // 滚动时隐藏 mdui-bottom-nav-scroll-hide $('.mdui-bottom-nav-scroll-hide').each(function () { var $this = $(this); var inst = new mdui.headroom($this, { pinnedclass: 'mdui-headroom-pinned-down', unpinnedclass: 'mdui-headroom-unpinned-down', }); $this.data('mdui.headroom', inst); }); })(); /** * ============================================================================= * ************ spinner 圆形进度条 ************ * ============================================================================= */ (function () { /** * layer 的 html 结构 */ var layerhtml = function () { var i = arguments.length ? arguments[0] : false; return '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
' + '
'; }; /** * 填充 html * @param spinner */ var fillhtml = function (spinner) { var $spinner = $(spinner); var layer; if ($spinner.hasclass('mdui-spinner-colorful')) { layer = layerhtml('1') + layerhtml('2') + layerhtml('3') + layerhtml('4'); } else { layer = layerhtml(); } $spinner.html(layer); }; /** * 页面加载完后自动填充 html 结构 */ $(function () { $('.mdui-spinner').each(function () { fillhtml(this); }); }); /** * 更新圆形进度条 */ mdui.updatespinners = function () { $(arguments.length ? arguments[0] : '.mdui-spinner').each(function () { fillhtml(this); }); }; })(); /** * ============================================================================= * ************ expansion panel 可扩展面板 ************ * ============================================================================= */ mdui.panel = (function () { function panel(selector, opts) { return new collapseprivate(selector, opts, { item: 'mdui-panel-item', itemopen: 'mdui-panel-item-open', header: 'mdui-panel-item-header', body: 'mdui-panel-item-body', }, 'panel'); } return panel; })(); /** * ============================================================================= * ************ expansion panel 自定义属性 ************ * ============================================================================= */ $(function () { $('[mdui-panel]').each(function () { var $target = $(this); var inst = $target.data('mdui.panel'); if (!inst) { var options = parseoptions($target.attr('mdui-panel')); inst = new mdui.panel($target, options); $target.data('mdui.panel', inst); } }); }); /** * ============================================================================= * ************ menu 菜单 ************ * ============================================================================= */ mdui.menu = (function () { /** * 默认参数 */ var default = { position: 'auto', // 菜单位置 top、bottom、center、auto align: 'auto', // 菜单和触发它的元素的对齐方式 left、right、center、auto gutter: 16, // 菜单距离窗口边缘的最小距离,单位 px fixed: false, // 是否使菜单固定在窗口,不随滚动条滚动 covered: 'auto', // 菜单是否覆盖在触发它的元素上,true、false。auto 时简单菜单覆盖,级联菜单不覆盖 submenutrigger: 'hover', // 子菜单的触发方式 hover、click submenudelay: 200, // 子菜单的触发延时,仅在 submenutrigger 为 hover 有效 }; /** * 调整主菜单位置 * @param _this 实例 */ var readjust = function (_this) { var menuleft; var menutop; // 菜单位置和方向 var position; var align; // window 窗口的宽度和高度 var windowheight = $window.height(); var windowwidth = $window.width(); // 配置参数 var gutter = _this.options.gutter; var iscovered = _this.iscovered; var isfixed = _this.options.fixed; // 动画方向参数 var transformoriginx; var transformoriginy; // 菜单的原始宽度和高度 var menuwidth = _this.$menu.width(); var menuheight = _this.$menu.height(); var $anchor = _this.$anchor; // 触发菜单的元素在窗口中的位置 var anchortmp = $anchor[0].getboundingclientrect(); var anchortop = anchortmp.top; var anchorleft = anchortmp.left; var anchorheight = anchortmp.height; var anchorwidth = anchortmp.width; var anchorbottom = windowheight - anchortop - anchorheight; var anchorright = windowwidth - anchorleft - anchorwidth; // 触发元素相对其拥有定位属性的父元素的位置 var anchoroffsettop = $anchor[0].offsettop; var anchoroffsetleft = $anchor[0].offsetleft; // =============================== // ================= 自动判断菜单位置 // =============================== if (_this.options.position === 'auto') { // 判断下方是否放得下菜单 if (anchorbottom + (iscovered ? anchorheight : 0) > menuheight + gutter) { position = 'bottom'; } // 判断上方是否放得下菜单 else if (anchortop + (iscovered ? anchorheight : 0) > menuheight + gutter) { position = 'top'; } // 上下都放不下,居中显示 else { position = 'center'; } } else { position = _this.options.position; } // =============================== // ============== 自动判断菜单对齐方式 // =============================== if (_this.options.align === 'auto') { // 判断右侧是否放得下菜单 if (anchorright + anchorwidth > menuwidth + gutter) { align = 'left'; } // 判断左侧是否放得下菜单 else if (anchorleft + anchorwidth > menuwidth + gutter) { align = 'right'; } // 左右都放不下,居中显示 else { align = 'center'; } } else { align = _this.options.align; } // =============================== // ==================== 设置菜单位置 // =============================== if (position === 'bottom') { transformoriginy = '0'; menutop = (iscovered ? 0 : anchorheight) + (isfixed ? anchortop : anchoroffsettop); } else if (position === 'top') { transformoriginy = '100%'; menutop = (iscovered ? anchorheight : 0) + (isfixed ? (anchortop - menuheight) : (anchoroffsettop - menuheight)); } else { transformoriginy = '50%'; // =====================在窗口中居中 // 显示的菜单的高度,简单菜单高度不超过窗口高度,若超过了则在菜单内部显示滚动条 // 级联菜单内部不允许出现滚动条 var menuheighttemp = menuheight; // 简单菜单比窗口高时,限制菜单高度 if (!_this.$menu.hasclass('mdui-menu-cascade')) { if (menuheight + gutter * 2 > windowheight) { menuheighttemp = windowheight - gutter * 2; _this.$menu.height(menuheighttemp); } } menutop = (windowheight - menuheighttemp) / 2 + (isfixed ? 0 : (anchoroffsettop - anchortop)); } _this.$menu.css('top', menutop + 'px'); // =============================== // ================= 设置菜单对齐方式 // =============================== if (align === 'left') { transformoriginx = '0'; menuleft = isfixed ? anchorleft : anchoroffsetleft; } else if (align === 'right') { transformoriginx = '100%'; menuleft = isfixed ? (anchorleft + anchorwidth - menuwidth) : (anchoroffsetleft + anchorwidth - menuwidth); } else { transformoriginx = '50%'; //=======================在窗口中居中 // 显示的菜单的宽度,菜单宽度不能超过窗口宽度 var menuwidthtemp = menuwidth; // 菜单比窗口宽,限制菜单宽度 if (menuwidth + gutter * 2 > windowwidth) { menuwidthtemp = windowwidth - gutter * 2; _this.$menu.width(menuwidthtemp); } menuleft = (windowwidth - menuwidthtemp) / 2 + (isfixed ? 0 : anchoroffsetleft - anchorleft); } _this.$menu.css('left', menuleft + 'px'); // 设置菜单动画方向 _this.$menu.transformorigin(transformoriginx + ' ' + transformoriginy); }; /** * 调整子菜单的位置 * @param $submenu */ var readjustsubmenu = function ($submenu) { var $item = $submenu.parent('.mdui-menu-item'); var submenutop; var submenuleft; // 子菜单位置和方向 var position; // top、bottom var align; // left、right // window 窗口的宽度和高度 var windowheight = $window.height(); var windowwidth = $window.width(); // 动画方向参数 var transformoriginx; var transformoriginy; // 子菜单的原始宽度和高度 var submenuwidth = $submenu.width(); var submenuheight = $submenu.height(); // 触发子菜单的菜单项的宽度高度 var itemtmp = $item[0].getboundingclientrect(); var itemwidth = itemtmp.width; var itemheight = itemtmp.height; var itemleft = itemtmp.left; var itemtop = itemtmp.top; // =================================== // ===================== 判断菜单上下位置 // =================================== // 判断下方是否放得下菜单 if (windowheight - itemtop > submenuheight) { position = 'bottom'; } // 判断上方是否放得下菜单 else if (itemtop + itemheight > submenuheight) { position = 'top'; } // 默认放在下方 else { position = 'bottom'; } // ==================================== // ====================== 判断菜单左右位置 // ==================================== // 判断右侧是否放得下菜单 if (windowwidth - itemleft - itemwidth > submenuwidth) { align = 'left'; } // 判断左侧是否放得下菜单 else if (itemleft > submenuwidth) { align = 'right'; } // 默认放在右侧 else { align = 'left'; } // =================================== // ======================== 设置菜单位置 // =================================== if (position === 'bottom') { transformoriginy = '0'; submenutop = '0'; } else if (position === 'top') { transformoriginy = '100%'; submenutop = -submenuheight + itemheight; } $submenu.css('top', submenutop + 'px'); // =================================== // ===================== 设置菜单对齐方式 // =================================== if (align === 'left') { transformoriginx = '0'; submenuleft = itemwidth; } else if (align === 'right') { transformoriginx = '100%'; submenuleft = -submenuwidth; } $submenu.css('left', submenuleft + 'px'); // 设置菜单动画方向 $submenu.transformorigin(transformoriginx + ' ' + transformoriginy); }; /** * 打开子菜单 * @param $submenu */ var opensubmenu = function ($submenu) { readjustsubmenu($submenu); $submenu .addclass('mdui-menu-open') .parent('.mdui-menu-item') .addclass('mdui-menu-item-active'); }; /** * 关闭子菜单,及其嵌套的子菜单 * @param $submenu */ var closesubmenu = function ($submenu) { // 关闭子菜单 $submenu .removeclass('mdui-menu-open') // 移除激活状态的样式 .parent('.mdui-menu-item') .removeclass('mdui-menu-item-active'); // 循环关闭嵌套的子菜单 $submenu.find('.mdui-menu').each(function () { $(this) .removeclass('mdui-menu-open') .parent('.mdui-menu-item') .removeclass('mdui-menu-item-active'); }); }; /** * 切换子菜单状态 * @param $submenu */ var togglesubmenu = function ($submenu) { if ($submenu.hasclass('mdui-menu-open')) { closesubmenu($submenu); } else { opensubmenu($submenu); } }; /** * 绑定子菜单事件 * @param inst 实例 */ var bindsubmenuevent = function (inst) { // 点击打开子菜单 inst.$menu.on('click', '.mdui-menu-item', function (e) { var $this = $(this); var $target = $(e.target); // 禁用状态菜单不操作 if ($this.attr('disabled') !== null) { return; } // 没有点击在子菜单的菜单项上时,不操作(点在了子菜单的空白区域、或分隔线上) if ($target.is('.mdui-menu') || $target.is('.mdui-divider')) { return; } // 阻止冒泡,点击菜单项时只在最后一级的 mdui-menu-item 上生效,不向上冒泡 if (!$target.parents('.mdui-menu-item').eq(0).is($this)) { return; } // 当前菜单的子菜单 var $submenu = $this.children('.mdui-menu'); // 先关闭除当前子菜单外的所有同级子菜单 $this.parent('.mdui-menu').children('.mdui-menu-item').each(function () { var $tmpsubmenu = $(this).children('.mdui-menu'); if ( $tmpsubmenu.length && (!$submenu.length || !$tmpsubmenu.is($submenu)) ) { closesubmenu($tmpsubmenu); } }); // 切换当前子菜单 if ($submenu.length) { togglesubmenu($submenu); } }); if (inst.options.submenutrigger === 'hover') { // 临时存储 settimeout 对象 var timeout; var timeoutopen; var timeoutclose; inst.$menu.on('mouseover mouseout', '.mdui-menu-item', function (e) { var $this = $(this); var eventtype = e.type; var $relatedtarget = $(e.relatedtarget); // 禁用状态的菜单不操作 if ($this.attr('disabled') !== null) { return; } // 用 mouseover 模拟 mouseenter if (eventtype === 'mouseover') { if (!$this.is($relatedtarget) && $.contains($this[0], $relatedtarget[0])) { return; } } // 用 mouseout 模拟 mouseleave else if (eventtype === 'mouseout') { if ($this.is($relatedtarget) || $.contains($this[0], $relatedtarget[0])) { return; } } // 当前菜单项下的子菜单,未必存在 var $submenu = $this.children('.mdui-menu'); // 鼠标移入菜单项时,显示菜单项下的子菜单 if (eventtype === 'mouseover') { if ($submenu.length) { // 当前子菜单准备打开时,如果当前子菜单正准备着关闭,不用再关闭了 var tmpclose = $submenu.data('timeoutclose.mdui.menu'); if (tmpclose) { cleartimeout(tmpclose); } // 如果当前子菜单已经打开,不操作 if ($submenu.hasclass('mdui-menu-open')) { return; } // 当前子菜单准备打开时,其他准备打开的子菜单不用再打开了 cleartimeout(timeoutopen); // 准备打开当前子菜单 timeout = timeoutopen = settimeout(function () { opensubmenu($submenu); }, inst.options.submenudelay); $submenu.data('timeoutopen.mdui.menu', timeout); } } // 鼠标移出菜单项时,关闭菜单项下的子菜单 else if (eventtype === 'mouseout') { if ($submenu.length) { // 鼠标移出菜单项时,如果当前菜单项下的子菜单正准备打开,不用再打开了 var tmpopen = $submenu.data('timeoutopen.mdui.menu'); if (tmpopen) { cleartimeout(tmpopen); } // 准备关闭当前子菜单 timeout = timeoutclose = settimeout(function () { closesubmenu($submenu); }, inst.options.submenudelay); $submenu.data('timeoutclose.mdui.menu', timeout); } } }); } }; /** * 菜单 * @param anchorselector 点击该元素触发菜单 * @param menuselector 菜单 * @param opts 配置项 * @constructor */ function menu(anchorselector, menuselector, opts) { var _this = this; // 触发菜单的元素 _this.$anchor = $(anchorselector).eq(0); if (!_this.$anchor.length) { return; } // 已通过自定义属性实例化过,不再重复实例化 var oldinst = _this.$anchor.data('mdui.menu'); if (oldinst) { return oldinst; } _this.$menu = $(menuselector).eq(0); // 触发菜单的元素 和 菜单必须是同级的元素,否则菜单可能不能定位 if (!_this.$anchor.siblings(_this.$menu).length) { return; } _this.options = $.extend({}, default, (opts || {})); _this.state = 'closed'; // 是否是级联菜单 _this.iscascade = _this.$menu.hasclass('mdui-menu-cascade'); // covered 参数处理 if (_this.options.covered === 'auto') { _this.iscovered = !_this.iscascade; } else { _this.iscovered = _this.options.covered; } // 点击触发菜单切换 _this.$anchor.on('click', function () { _this.toggle(); }); // 点击菜单外面区域关闭菜单 $document.on('click touchstart', function (e) { var $target = $(e.target); if ( (_this.state === 'opening' || _this.state === 'opened') && !$target.is(_this.$menu) && !$.contains(_this.$menu[0], $target[0]) && !$target.is(_this.$anchor) && !$.contains(_this.$anchor[0], $target[0]) ) { _this.close(); } }); // 点击不含子菜单的菜单条目关闭菜单 $document.on('click', '.mdui-menu-item', function (e) { var $this = $(this); if (!$this.find('.mdui-menu').length && $this.attr('disabled') === null) { _this.close(); } }); // 绑定点击或鼠标移入含子菜单的条目的事件 bindsubmenuevent(_this); // 窗口大小变化时,重新调整菜单位置 $window.on('resize', $.throttle(function () { readjust(_this); }, 100)); } /** * 切换菜单状态 */ menu.prototype.toggle = function () { var _this = this; if (_this.state === 'opening' || _this.state === 'opened') { _this.close(); } else if (_this.state === 'closing' || _this.state === 'closed') { _this.open(); } }; /** * 动画结束回调 * @param inst */ var transitionend = function (inst) { if (inst.state === 'opening') { inst.state = 'opened'; componentevent('opened', 'menu', inst, inst.$menu); } if (inst.state === 'closing') { inst.state = 'closed'; componentevent('closed', 'menu', inst, inst.$menu); // 关闭后,恢复菜单样式到默认状态,并恢复 fixed 定位 inst.$menu.css({ top: '', left: '', width: '', position: 'fixed', }); } }; /** * 打开菜单 */ menu.prototype.open = function () { var _this = this; if (_this.state === 'opening' || _this.state === 'opened') { return; } _this.state = 'opening'; componentevent('open', 'menu', _this, _this.$menu); // 调整菜单位置 readjust(_this); _this.$menu // 菜单隐藏状态使用使用 fixed 定位。 .css('position', _this.options.fixed ? 'fixed' : 'absolute') // 打开菜单 .addclass('mdui-menu-open') // 打开动画完成后 .transitionend(function () { transitionend(_this); }); }; /** * 关闭菜单 */ menu.prototype.close = function () { var _this = this; if (_this.state === 'closing' || _this.state === 'closed') { return; } _this.state = 'closing'; componentevent('close', 'menu', _this, _this.$menu); // 菜单开始关闭时,关闭所有子菜单 _this.$menu.find('.mdui-menu').each(function () { closesubmenu($(this)); }); _this.$menu .removeclass('mdui-menu-open') .transitionend(function () { transitionend(_this); }); }; return menu; })(); /** * ============================================================================= * ************ menu 自定义属性 api ************ * ============================================================================= */ $(function () { $document.on('click', '[mdui-menu]', function () { var $this = $(this); var inst = $this.data('mdui.menu'); if (!inst) { var options = parseoptions($this.attr('mdui-menu')); var menuselector = options.target; delete options.target; inst = new mdui.menu($this, menuselector, options); $this.data('mdui.menu', inst); inst.toggle(); } }); }); /* jshint ignore:start */ mdui.jq = $; window.mdui = mdui; })(window, document); /* jshint ignore:end */