is-mui
Version:
本项目已经不再维护! 请知悉!
1,983 lines (1,899 loc) • 264 kB
JavaScript
/*!
* =====================================================
* Mui v3.7.2 (http://dev.dcloud.net.cn/mui)
* =====================================================
*/
/**
* MUI核心JS
* @type _L4.$|Function
*/
var mui = (function(document, undefined) {
var readyRE = /complete|loaded|interactive/;
var idSelectorRE = /^#([\w-]+)$/;
var classSelectorRE = /^\.([\w-]+)$/;
var tagSelectorRE = /^[\w-]+$/;
var translateRE = /translate(?:3d)?\((.+?)\)/;
var translateMatrixRE = /matrix(3d)?\((.+?)\)/;
var $ = function(selector, context) {
context = context || document;
if (!selector)
return wrap();
if (typeof selector === 'object')
if ($.isArrayLike(selector)) {
return wrap($.slice.call(selector), null);
} else {
return wrap([selector], null);
}
if (typeof selector === 'function')
return $.ready(selector);
if (typeof selector === 'string') {
try {
selector = selector.trim();
if (idSelectorRE.test(selector)) {
var found = document.getElementById(RegExp.$1);
return wrap(found ? [found] : []);
}
return wrap($.qsa(selector, context), selector);
} catch (e) {}
}
return wrap();
};
var wrap = function(dom, selector) {
dom = dom || [];
Object.setPrototypeOf(dom, $.fn);
dom.selector = selector || '';
return dom;
};
$.uuid = 0;
$.data = {};
/**
* extend(simple)
* @param {type} target
* @param {type} source
* @param {type} deep
* @returns {unresolved}
*/
$.extend = function() { //from jquery2
var options, name, src, copy, copyIsArray, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
if (typeof target === "boolean") {
deep = target;
target = arguments[i] || {};
i++;
}
if (typeof target !== "object" && !$.isFunction(target)) {
target = {};
}
if (i === length) {
target = this;
i--;
}
for (; i < length; i++) {
if ((options = arguments[i]) != null) {
for (name in options) {
src = target[name];
copy = options[name];
if (target === copy) {
continue;
}
if (deep && copy && ($.isPlainObject(copy) || (copyIsArray = $.isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && $.isArray(src) ? src : [];
} else {
clone = src && $.isPlainObject(src) ? src : {};
}
target[name] = $.extend(deep, clone, copy);
} else if (copy !== undefined) {
target[name] = copy;
}
}
}
}
return target;
};
/**
* mui noop(function)
*/
$.noop = function() {};
/**
* mui slice(array)
*/
$.slice = [].slice;
/**
* mui filter(array)
*/
$.filter = [].filter;
$.type = function(obj) {
return obj == null ? String(obj) : class2type[{}.toString.call(obj)] || "object";
};
/**
* mui isArray
*/
$.isArray = Array.isArray ||
function(object) {
return object instanceof Array;
};
/**
* mui isArrayLike
* @param {Object} obj
*/
$.isArrayLike = function(obj) {
var length = !!obj && "length" in obj && obj.length;
var type = $.type(obj);
if (type === "function" || $.isWindow(obj)) {
return false;
}
return type === "array" || length === 0 ||
typeof length === "number" && length > 0 && (length - 1) in obj;
};
/**
* mui isWindow(需考虑obj为undefined的情况)
*/
$.isWindow = function(obj) {
return obj != null && obj === obj.window;
};
/**
* mui isObject
*/
$.isObject = function(obj) {
return $.type(obj) === "object";
};
/**
* mui isPlainObject
*/
$.isPlainObject = function(obj) {
return $.isObject(obj) && !$.isWindow(obj) && Object.getPrototypeOf(obj) === Object.prototype;
};
/**
* mui isEmptyObject
* @param {Object} o
*/
$.isEmptyObject = function(o) {
for (var p in o) {
if (p !== undefined) {
return false;
}
}
return true;
};
/**
* mui isFunction
*/
$.isFunction = function(value) {
return $.type(value) === "function";
};
/**
* mui querySelectorAll
* @param {type} selector
* @param {type} context
* @returns {Array}
*/
$.qsa = function(selector, context) {
context = context || document;
return $.slice.call(classSelectorRE.test(selector) ? context.getElementsByClassName(RegExp.$1) : tagSelectorRE.test(selector) ? context.getElementsByTagName(selector) : context.querySelectorAll(selector));
};
/**
* ready(DOMContentLoaded)
* @param {type} callback
* @returns {_L6.$}
*/
$.ready = function(callback) {
if (readyRE.test(document.readyState)) {
callback($);
} else {
document.addEventListener('DOMContentLoaded', function() {
callback($);
}, false);
}
return this;
};
/**
* 将 fn 缓存一段时间后, 再被调用执行
* 此方法为了避免在 ms 段时间内, 执行 fn 多次. 常用于 resize , scroll , mousemove 等连续性事件中;
* 当 ms 设置为 -1, 表示立即执行 fn, 即和直接调用 fn 一样;
* 调用返回函数的 stop 停止最后一次的 buffer 效果
* @param {Object} fn
* @param {Object} ms
* @param {Object} context
*/
$.buffer = function(fn, ms, context) {
var timer;
var lastStart = 0;
var lastEnd = 0;
var ms = ms || 150;
function run() {
if (timer) {
timer.cancel();
timer = 0;
}
lastStart = $.now();
fn.apply(context || this, arguments);
lastEnd = $.now();
}
return $.extend(function() {
if (
(!lastStart) || // 从未运行过
(lastEnd >= lastStart && $.now() - lastEnd > ms) || // 上次运行成功后已经超过ms毫秒
(lastEnd < lastStart && $.now() - lastStart > ms * 8) // 上次运行或未完成,后8*ms毫秒
) {
run.apply(this, arguments);
} else {
if (timer) {
timer.cancel();
}
timer = $.later(run, ms, null, $.slice.call(arguments));
}
}, {
stop: function() {
if (timer) {
timer.cancel();
timer = 0;
}
}
});
};
/**
* each
* @param {type} elements
* @param {type} callback
* @returns {_L8.$}
*/
$.each = function(elements, callback, hasOwnProperty) {
if (!elements) {
return this;
}
if (typeof elements.length === 'number') {
[].every.call(elements, function(el, idx) {
return callback.call(el, idx, el) !== false;
});
} else {
for (var key in elements) {
if (hasOwnProperty) {
if (elements.hasOwnProperty(key)) {
if (callback.call(elements[key], key, elements[key]) === false) return elements;
}
} else {
if (callback.call(elements[key], key, elements[key]) === false) return elements;
}
}
}
return this;
};
$.focus = function(element) {
if ($.os.ios) {
setTimeout(function() {
element.focus();
}, 10);
} else {
element.focus();
}
};
/**
* trigger event
* @param {type} element
* @param {type} eventType
* @param {type} eventData
* @returns {_L8.$}
*/
$.trigger = function(element, eventType, eventData) {
element.dispatchEvent(new CustomEvent(eventType, {
detail: eventData,
bubbles: true,
cancelable: true
}));
return this;
};
/**
* getStyles
* @param {type} element
* @param {type} property
* @returns {styles}
*/
$.getStyles = function(element, property) {
var styles = element.ownerDocument.defaultView.getComputedStyle(element, null);
if (property) {
return styles.getPropertyValue(property) || styles[property];
}
return styles;
};
/**
* parseTranslate
* @param {type} translateString
* @param {type} position
* @returns {Object}
*/
$.parseTranslate = function(translateString, position) {
var result = translateString.match(translateRE || '');
if (!result || !result[1]) {
result = ['', '0,0,0'];
}
result = result[1].split(",");
result = {
x: parseFloat(result[0]),
y: parseFloat(result[1]),
z: parseFloat(result[2])
};
if (position && result.hasOwnProperty(position)) {
return result[position];
}
return result;
};
/**
* parseTranslateMatrix
* @param {type} translateString
* @param {type} position
* @returns {Object}
*/
$.parseTranslateMatrix = function(translateString, position) {
var matrix = translateString.match(translateMatrixRE);
var is3D = matrix && matrix[1];
if (matrix) {
matrix = matrix[2].split(",");
if (is3D === "3d")
matrix = matrix.slice(12, 15);
else {
matrix.push(0);
matrix = matrix.slice(4, 7);
}
} else {
matrix = [0, 0, 0];
}
var result = {
x: parseFloat(matrix[0]),
y: parseFloat(matrix[1]),
z: parseFloat(matrix[2])
};
if (position && result.hasOwnProperty(position)) {
return result[position];
}
return result;
};
$.hooks = {};
$.addAction = function(type, hook) {
var hooks = $.hooks[type];
if (!hooks) {
hooks = [];
}
hook.index = hook.index || 1000;
hooks.push(hook);
hooks.sort(function(a, b) {
return a.index - b.index;
});
$.hooks[type] = hooks;
return $.hooks[type];
};
$.doAction = function(type, callback) {
if ($.isFunction(callback)) { //指定了callback
$.each($.hooks[type], callback);
} else { //未指定callback,直接执行
$.each($.hooks[type], function(index, hook) {
return !hook.handle();
});
}
};
/**
* setTimeout封装
* @param {Object} fn
* @param {Object} when
* @param {Object} context
* @param {Object} data
*/
$.later = function(fn, when, context, data) {
when = when || 0;
var m = fn;
var d = data;
var f;
var r;
if (typeof fn === 'string') {
m = context[fn];
}
f = function() {
m.apply(context, $.isArray(d) ? d : [d]);
};
r = setTimeout(f, when);
return {
id: r,
cancel: function() {
clearTimeout(r);
}
};
};
$.now = Date.now || function() {
return +new Date();
};
var class2type = {};
$.each(['Boolean', 'Number', 'String', 'Function', 'Array', 'Date', 'RegExp', 'Object', 'Error'], function(i, name) {
class2type["[object " + name + "]"] = name.toLowerCase();
});
if (window.JSON) {
$.parseJSON = JSON.parse;
}
/**
* $.fn
*/
$.fn = {
each: function(callback) {
[].every.call(this, function(el, idx) {
return callback.call(el, idx, el) !== false;
});
return this;
}
};
/**
* 兼容 AMD 模块
**/
if (typeof define === 'function' && define.amd) {
define('mui', [], function() {
return $;
});
}
return $;
})(document);
//window.mui = mui;
//'$' in window || (window.$ = mui);
/**
* $.os
* @param {type} $
* @returns {undefined}
*/
(function($, window) {
function detect(ua) {
this.os = {};
var funcs = [
function() { //wechat
var wechat = ua.match(/(MicroMessenger)\/([\d\.]+)/i);
if (wechat) { //wechat
this.os.wechat = {
version: wechat[2].replace(/_/g, '.')
};
}
return false;
},
function() { //android
var android = ua.match(/(Android);?[\s\/]+([\d.]+)?/);
if (android) {
this.os.android = true;
this.os.version = android[2];
this.os.isBadAndroid = !(/Chrome\/\d/.test(window.navigator.appVersion));
}
return this.os.android === true;
},
function() { //ios
var iphone = ua.match(/(iPhone\sOS)\s([\d_]+)/);
if (iphone) { //iphone
this.os.ios = this.os.iphone = true;
this.os.version = iphone[2].replace(/_/g, '.');
} else {
var ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
if (ipad) { //ipad
this.os.ios = this.os.ipad = true;
this.os.version = ipad[2].replace(/_/g, '.');
}
}
return this.os.ios === true;
}
];
[].every.call(funcs, function(func) {
return !func.call($);
});
}
detect.call($, navigator.userAgent);
})(mui, window);
/**
* $.os.plus
* @param {type} $
* @returns {undefined}
*/
(function($, document) {
function detect(ua) {
this.os = this.os || {};
var plus = ua.match(/Html5Plus/i); //TODO 5\+Browser?
if (plus) {
this.os.plus = true;
$(function() {
document.body.classList.add('mui-plus');
});
if (ua.match(/StreamApp/i)) { //TODO 最好有流应用自己的标识
this.os.stream = true;
$(function() {
document.body.classList.add('mui-plus-stream');
});
}
}
}
detect.call($, navigator.userAgent);
})(mui, document);
/**
* 仅提供简单的on,off(仅支持事件委托,不支持当前元素绑定,当前元素绑定请直接使用addEventListener,removeEventListener)
* @param {Object} $
*/
(function($) {
if ('ontouchstart' in window) {
$.isTouchable = true;
$.EVENT_START = 'touchstart';
$.EVENT_MOVE = 'touchmove';
$.EVENT_END = 'touchend';
} else {
$.isTouchable = false;
$.EVENT_START = 'mousedown';
$.EVENT_MOVE = 'mousemove';
$.EVENT_END = 'mouseup';
}
$.EVENT_CANCEL = 'touchcancel';
$.EVENT_CLICK = 'click';
var _mid = 1;
var delegates = {};
//需要wrap的函数
var eventMethods = {
preventDefault: 'isDefaultPrevented',
stopImmediatePropagation: 'isImmediatePropagationStopped',
stopPropagation: 'isPropagationStopped'
};
//默认true返回函数
var returnTrue = function() {
return true
};
//默认false返回函数
var returnFalse = function() {
return false
};
//wrap浏览器事件
var compatible = function(event, target) {
if (!event.detail) {
event.detail = {
currentTarget: target
};
} else {
event.detail.currentTarget = target;
}
$.each(eventMethods, function(name, predicate) {
var sourceMethod = event[name];
event[name] = function() {
this[predicate] = returnTrue;
return sourceMethod && sourceMethod.apply(event, arguments)
}
event[predicate] = returnFalse;
}, true);
return event;
};
//简单的wrap对象_mid
var mid = function(obj) {
return obj && (obj._mid || (obj._mid = _mid++));
};
//事件委托对象绑定的事件回调列表
var delegateFns = {};
//返回事件委托的wrap事件回调
var delegateFn = function(element, event, selector, callback) {
return function(e) {
//same event
var callbackObjs = delegates[element._mid][event];
var handlerQueue = [];
var target = e.target;
var selectorAlls = {};
for (; target && target !== document; target = target.parentNode) {
if (target === element) {
break;
}
if (~['click', 'tap', 'doubletap', 'longtap', 'hold'].indexOf(event) && (target.disabled || target.classList.contains('mui-disabled'))) {
break;
}
var matches = {};
$.each(callbackObjs, function(selector, callbacks) { //same selector
selectorAlls[selector] || (selectorAlls[selector] = $.qsa(selector, element));
if (selectorAlls[selector] && ~(selectorAlls[selector]).indexOf(target)) {
if (!matches[selector]) {
matches[selector] = callbacks;
}
}
}, true);
if (!$.isEmptyObject(matches)) {
handlerQueue.push({
element: target,
handlers: matches
});
}
}
selectorAlls = null;
e = compatible(e); //compatible event
$.each(handlerQueue, function(index, handler) {
target = handler.element;
var tagName = target.tagName;
if (event === 'tap' && (tagName !== 'INPUT' && tagName !== 'TEXTAREA' && tagName !== 'SELECT')) {
e.preventDefault();
e.detail && e.detail.gesture && e.detail.gesture.preventDefault();
}
$.each(handler.handlers, function(index, handler) {
$.each(handler, function(index, callback) {
if (callback.call(target, e) === false) {
e.preventDefault();
e.stopPropagation();
}
}, true);
}, true)
if (e.isPropagationStopped()) {
return false;
}
}, true);
};
};
var findDelegateFn = function(element, event) {
var delegateCallbacks = delegateFns[mid(element)];
var result = [];
if (delegateCallbacks) {
result = [];
if (event) {
var filterFn = function(fn) {
return fn.type === event;
}
return delegateCallbacks.filter(filterFn);
} else {
result = delegateCallbacks;
}
}
return result;
};
var preventDefaultException = /^(INPUT|TEXTAREA|BUTTON|SELECT)$/;
/**
* mui delegate events
* @param {type} event
* @param {type} selector
* @param {type} callback
* @returns {undefined}
*/
$.fn.on = function(event, selector, callback) { //仅支持简单的事件委托,主要是tap事件使用,类似mouse,focus之类暂不封装支持
return this.each(function() {
var element = this;
mid(element);
mid(callback);
var isAddEventListener = false;
var delegateEvents = delegates[element._mid] || (delegates[element._mid] = {});
var delegateCallbackObjs = delegateEvents[event] || ((delegateEvents[event] = {}));
if ($.isEmptyObject(delegateCallbackObjs)) {
isAddEventListener = true;
}
var delegateCallbacks = delegateCallbackObjs[selector] || (delegateCallbackObjs[selector] = []);
delegateCallbacks.push(callback);
if (isAddEventListener) {
var delegateFnArray = delegateFns[mid(element)];
if (!delegateFnArray) {
delegateFnArray = [];
}
var delegateCallback = delegateFn(element, event, selector, callback);
delegateFnArray.push(delegateCallback);
delegateCallback.i = delegateFnArray.length - 1;
delegateCallback.type = event;
delegateFns[mid(element)] = delegateFnArray;
element.addEventListener(event, delegateCallback);
if (event === 'tap') { //TODO 需要找个更好的解决方案
element.addEventListener('click', function(e) {
if (e.target) {
var tagName = e.target.tagName;
if (!preventDefaultException.test(tagName)) {
if (tagName === 'A') {
var href = e.target.href;
if (!(href && ~href.indexOf('tel:'))) {
e.preventDefault();
}
} else {
e.preventDefault();
}
}
}
});
}
}
});
};
$.fn.off = function(event, selector, callback) {
return this.each(function() {
var _mid = mid(this);
if (!event) { //mui(selector).off();
delegates[_mid] && delete delegates[_mid];
} else if (!selector) { //mui(selector).off(event);
delegates[_mid] && delete delegates[_mid][event];
} else if (!callback) { //mui(selector).off(event,selector);
delegates[_mid] && delegates[_mid][event] && delete delegates[_mid][event][selector];
} else { //mui(selector).off(event,selector,callback);
var delegateCallbacks = delegates[_mid] && delegates[_mid][event] && delegates[_mid][event][selector];
$.each(delegateCallbacks, function(index, delegateCallback) {
if (mid(delegateCallback) === mid(callback)) {
delegateCallbacks.splice(index, 1);
return false;
}
}, true);
}
if (delegates[_mid]) {
//如果off掉了所有当前element的指定的event事件,则remove掉当前element的delegate回调
if ((!delegates[_mid][event] || $.isEmptyObject(delegates[_mid][event]))) {
findDelegateFn(this, event).forEach(function(fn) {
this.removeEventListener(fn.type, fn);
delete delegateFns[_mid][fn.i];
}.bind(this));
}
} else {
//如果delegates[_mid]已不存在,删除所有
findDelegateFn(this).forEach(function(fn) {
this.removeEventListener(fn.type, fn);
delete delegateFns[_mid][fn.i];
}.bind(this));
}
});
};
})(mui);
/**
* mui target(action>popover>modal>tab>toggle)
*/
(function($, window, document) {
/**
* targets
*/
$.targets = {};
/**
* target handles
*/
$.targetHandles = [];
/**
* register target
* @param {type} target
* @returns {$.targets}
*/
$.registerTarget = function(target) {
target.index = target.index || 1000;
$.targetHandles.push(target);
$.targetHandles.sort(function(a, b) {
return a.index - b.index;
});
return $.targetHandles;
};
window.addEventListener($.EVENT_START, function(event) {
var target = event.target;
var founds = {};
for (; target && target !== document; target = target.parentNode) {
var isFound = false;
$.each($.targetHandles, function(index, targetHandle) {
var name = targetHandle.name;
if (!isFound && !founds[name] && targetHandle.hasOwnProperty('handle')) {
$.targets[name] = targetHandle.handle(event, target);
if ($.targets[name]) {
founds[name] = true;
if (targetHandle.isContinue !== true) {
isFound = true;
}
}
} else {
if (!founds[name]) {
if (targetHandle.isReset !== false)
$.targets[name] = false;
}
}
});
if (isFound) {
break;
}
}
});
window.addEventListener('click', function(event) { //解决touch与click的target不一致的问题(比如链接边缘点击时,touch的target为html,而click的target为A)
var target = event.target;
var isFound = false;
for (; target && target !== document; target = target.parentNode) {
if (target.tagName === 'A') {
$.each($.targetHandles, function(index, targetHandle) {
var name = targetHandle.name;
if (targetHandle.hasOwnProperty('handle')) {
if (targetHandle.handle(event, target)) {
isFound = true;
event.preventDefault();
return false;
}
}
});
if (isFound) {
break;
}
}
}
});
})(mui, window, document);
/**
* fixed trim
* @param {type} undefined
* @returns {undefined}
*/
(function(undefined) {
if (String.prototype.trim === undefined) { // fix for iOS 3.2
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, '');
};
}
Object.setPrototypeOf = Object.setPrototypeOf || function(obj, proto) {
obj['__proto__'] = proto;
return obj;
};
})();
/**
* fixed CustomEvent
*/
(function() {
if (typeof window.CustomEvent === 'undefined') {
function CustomEvent(event, params) {
params = params || {
bubbles: false,
cancelable: false,
detail: undefined
};
var evt = document.createEvent('Events');
var bubbles = true;
for (var name in params) {
(name === 'bubbles') ? (bubbles = !!params[name]) : (evt[name] = params[name]);
}
evt.initEvent(event, bubbles, true);
return evt;
};
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
}
})();
/*
A shim for non ES5 supporting browsers.
Adds function bind to Function prototype, so that you can do partial application.
Works even with the nasty thing, where the first word is the opposite of extranet, the second one is the profession of Columbus, and the version number is 9, flipped 180 degrees.
*/
Function.prototype.bind = Function.prototype.bind || function(to) {
// Make an array of our arguments, starting from second argument
var partial = Array.prototype.splice.call(arguments, 1),
// We'll need the original function.
fn = this;
var bound = function() {
// Join the already applied arguments to the now called ones (after converting to an array again).
var args = partial.concat(Array.prototype.splice.call(arguments, 0));
// If not being called as a constructor
if (!(this instanceof bound)) {
// return the result of the function called bound to target and partially applied.
return fn.apply(to, args);
}
// If being called as a constructor, apply the function bound to self.
fn.apply(this, args);
}
// Attach the prototype of the function to our newly created function.
bound.prototype = fn.prototype;
return bound;
};
/**
* mui fixed classList
* @param {type} document
* @returns {undefined}
*/
(function(document) {
if (!("classList" in document.documentElement) && Object.defineProperty && typeof HTMLElement !== 'undefined') {
Object.defineProperty(HTMLElement.prototype, 'classList', {
get: function() {
var self = this;
function update(fn) {
return function(value) {
var classes = self.className.split(/\s+/),
index = classes.indexOf(value);
fn(classes, index, value);
self.className = classes.join(" ");
};
}
var ret = {
add: update(function(classes, index, value) {
~index || classes.push(value);
}),
remove: update(function(classes, index) {
~index && classes.splice(index, 1);
}),
toggle: update(function(classes, index, value) {
~index ? classes.splice(index, 1) : classes.push(value);
}),
contains: function(value) {
return !!~self.className.split(/\s+/).indexOf(value);
},
item: function(i) {
return self.className.split(/\s+/)[i] || null;
}
};
Object.defineProperty(ret, 'length', {
get: function() {
return self.className.split(/\s+/).length;
}
});
return ret;
}
});
}
})(document);
/**
* mui fixed requestAnimationFrame
* @param {type} window
* @returns {undefined}
*/
(function(window) {
if (!window.requestAnimationFrame) {
var lastTime = 0;
window.requestAnimationFrame = window.webkitRequestAnimationFrame || 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;
};
window.cancelAnimationFrame = window.webkitCancelAnimationFrame || window.webkitCancelRequestAnimationFrame || function(id) {
clearTimeout(id);
};
};
}(window));
/**
* fastclick(only for radio,checkbox)
*/
(function($, window, name) {
if (!$.os.android && !$.os.ios) { //目前仅识别android和ios
return;
}
if (window.FastClick) {
return;
}
var handle = function(event, target) {
if (target.tagName === 'LABEL') {
if (target.parentNode) {
target = target.parentNode.querySelector('input');
}
}
if (target && (target.type === 'radio' || target.type === 'checkbox')) {
if (!target.disabled) { //disabled
return target;
}
}
return false;
};
$.registerTarget({
name: name,
index: 40,
handle: handle,
target: false
});
var dispatchEvent = function(event) {
var targetElement = $.targets.click;
if (targetElement) {
var clickEvent, touch;
// On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect
if (document.activeElement && document.activeElement !== targetElement) {
document.activeElement.blur();
}
touch = event.detail.gesture.changedTouches[0];
// Synthesise a click event, with an extra attribute so it can be tracked
clickEvent = document.createEvent('MouseEvents');
clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
clickEvent.forwardedTouchEvent = true;
targetElement.dispatchEvent(clickEvent);
event.detail && event.detail.gesture.preventDefault();
}
};
window.addEventListener('tap', dispatchEvent);
window.addEventListener('doubletap', dispatchEvent);
//捕获
window.addEventListener('click', function(event) {
if ($.targets.click) {
if (!event.forwardedTouchEvent) { //stop click
if (event.stopImmediatePropagation) {
event.stopImmediatePropagation();
} else {
// Part of the hack for browsers that don't support Event#stopImmediatePropagation
event.propagationStopped = true;
}
event.stopPropagation();
event.preventDefault();
return false;
}
}
}, true);
})(mui, window, 'click');
(function($, document) {
$(function() {
if (!$.os.ios) {
return;
}
var CLASS_FOCUSIN = 'mui-focusin';
var CLASS_BAR_TAB = 'mui-bar-tab';
var CLASS_BAR_FOOTER = 'mui-bar-footer';
var CLASS_BAR_FOOTER_SECONDARY = 'mui-bar-footer-secondary';
var CLASS_BAR_FOOTER_SECONDARY_TAB = 'mui-bar-footer-secondary-tab';
// var content = document.querySelector('.' + CLASS_CONTENT);
// if (content) {
// document.body.insertBefore(content, document.body.firstElementChild);
// }
document.addEventListener('focusin', function(e) {
if ($.os.plus) { //在父webview里边不fix
if (window.plus) {
if (plus.webview.currentWebview().children().length > 0) {
return;
}
}
}
var target = e.target;
//TODO 需考虑所有键盘弹起的情况
if (target.tagName && (target.tagName === 'TEXTAREA' || (target.tagName === 'INPUT' && (target.type === 'text' || target.type === 'search' || target.type === 'number')))) {
if (target.disabled || target.readOnly) {
return;
}
document.body.classList.add(CLASS_FOCUSIN);
var isFooter = false;
for (; target && target !== document; target = target.parentNode) {
var classList = target.classList;
if (classList && classList.contains(CLASS_BAR_TAB) || classList.contains(CLASS_BAR_FOOTER) || classList.contains(CLASS_BAR_FOOTER_SECONDARY) || classList.contains(CLASS_BAR_FOOTER_SECONDARY_TAB)) {
isFooter = true;
break;
}
}
if (isFooter) {
var scrollTop = document.body.scrollHeight;
var scrollLeft = document.body.scrollLeft;
setTimeout(function() {
window.scrollTo(scrollLeft, scrollTop);
}, 20);
}
}
});
document.addEventListener('focusout', function(e) {
var classList = document.body.classList;
if (classList.contains(CLASS_FOCUSIN)) {
classList.remove(CLASS_FOCUSIN);
setTimeout(function() {
window.scrollTo(document.body.scrollLeft, document.body.scrollTop);
}, 20);
}
});
});
})(mui, document);
/**
* mui namespace(optimization)
* @param {type} $
* @returns {undefined}
*/
(function($) {
$.namespace = 'mui';
$.classNamePrefix = $.namespace + '-';
$.classSelectorPrefix = '.' + $.classNamePrefix;
/**
* 返回正确的className
* @param {type} className
* @returns {String}
*/
$.className = function(className) {
return $.classNamePrefix + className;
};
/**
* 返回正确的classSelector
* @param {type} classSelector
* @returns {String}
*/
$.classSelector = function(classSelector) {
return classSelector.replace(/\./g, $.classSelectorPrefix);
};
/**
* 返回正确的eventName
* @param {type} event
* @param {type} module
* @returns {String}
*/
$.eventName = function(event, module) {
return event + ($.namespace ? ('.' + $.namespace) : '') + ( module ? ('.' + module) : '');
};
})(mui);
/**
* mui gestures
* @param {type} $
* @param {type} window
* @returns {undefined}
*/
(function($, window) {
$.gestures = {
session: {}
};
/**
* Gesture preventDefault
* @param {type} e
* @returns {undefined}
*/
$.preventDefault = function(e) {
e.preventDefault();
};
/**
* Gesture stopPropagation
* @param {type} e
* @returns {undefined}
*/
$.stopPropagation = function(e) {
e.stopPropagation();
};
/**
* register gesture
* @param {type} gesture
* @returns {$.gestures}
*/
$.addGesture = function(gesture) {
return $.addAction('gestures', gesture);
};
var round = Math.round;
var abs = Math.abs;
var sqrt = Math.sqrt;
var atan = Math.atan;
var atan2 = Math.atan2;
/**
* distance
* @param {type} p1
* @param {type} p2
* @returns {Number}
*/
var getDistance = function(p1, p2, props) {
if(!props) {
props = ['x', 'y'];
}
var x = p2[props[0]] - p1[props[0]];
var y = p2[props[1]] - p1[props[1]];
return sqrt((x * x) + (y * y));
};
/**
* scale
* @param {Object} starts
* @param {Object} moves
*/
var getScale = function(starts, moves) {
if(starts.length >= 2 && moves.length >= 2) {
var props = ['pageX', 'pageY'];
return getDistance(moves[1], moves[0], props) / getDistance(starts[1], starts[0], props);
}
return 1;
};
/**
* angle
* @param {type} p1
* @param {type} p2
* @returns {Number}
*/
var getAngle = function(p1, p2, props) {
if(!props) {
props = ['x', 'y'];
}
var x = p2[props[0]] - p1[props[0]];
var y = p2[props[1]] - p1[props[1]];
return atan2(y, x) * 180 / Math.PI;
};
/**
* direction
* @param {Object} x
* @param {Object} y
*/
var getDirection = function(x, y) {
if(x === y) {
return '';
}
if(abs(x) >= abs(y)) {
return x > 0 ? 'left' : 'right';
}
return y > 0 ? 'up' : 'down';
};
/**
* rotation
* @param {Object} start
* @param {Object} end
*/
var getRotation = function(start, end) {
var props = ['pageX', 'pageY'];
return getAngle(end[1], end[0], props) - getAngle(start[1], start[0], props);
};
/**
* px per ms
* @param {Object} deltaTime
* @param {Object} x
* @param {Object} y
*/
var getVelocity = function(deltaTime, x, y) {
return {
x: x / deltaTime || 0,
y: y / deltaTime || 0
};
};
/**
* detect gestures
* @param {type} event
* @param {type} touch
* @returns {undefined}
*/
var detect = function(event, touch) {
if($.gestures.stoped) {
return;
}
$.doAction('gestures', function(index, gesture) {
if(!$.gestures.stoped) {
if($.options.gestureConfig[gesture.name] !== false) {
gesture.handle(event, touch);
}
}
});
};
/**
* 暂时无用
* @param {Object} node
* @param {Object} parent
*/
var hasParent = function(node, parent) {
while(node) {
if(node == parent) {
return true;
}
node = node.parentNode;
}
return false;
};
var uniqueArray = function(src, key, sort) {
var results = [];
var values = [];
var i = 0;
while(i < src.length) {
var val = key ? src[i][key] : src[i];
if(values.indexOf(val) < 0) {
results.push(src[i]);
}
values[i] = val;
i++;
}
if(sort) {
if(!key) {
results = results.sort();
} else {
results = results.sort(function sortUniqueArray(a, b) {
return a[key] > b[key];
});
}
}
return results;
};
var getMultiCenter = function(touches) {
var touchesLength = touches.length;
if(touchesLength === 1) {
return {
x: round(touches[0].pageX),
y: round(touches[0].pageY)
};
}
var x = 0;
var y = 0;
var i = 0;
while(i < touchesLength) {
x += touches[i].pageX;
y += touches[i].pageY;
i++;
}
return {
x: round(x / touchesLength),
y: round(y / touchesLength)
};
};
var multiTouch = function() {
return $.options.gestureConfig.pinch;
};
var copySimpleTouchData = function(touch) {
var touches = [];
var i = 0;
while(i < touch.touches.length) {
touches[i] = {
pageX: round(touch.touches[i].pageX),
pageY: round(touch.touches[i].pageY)
};
i++;
}
return {
timestamp: $.now(),
gesture: touch.gesture,
touches: touches,
center: getMultiCenter(touch.touches),
deltaX: touch.deltaX,
deltaY: touch.deltaY
};
};
var calDelta = function(touch) {
var session = $.gestures.session;
var center = touch.center;
var offset = session.offsetDelta || {};
var prevDelta = session.prevDelta || {};
var prevTouch = session.prevTouch || {};
if(touch.gesture.type === $.EVENT_START || touch.gesture.type === $.EVENT_END) {
prevDelta = session.prevDelta = {
x: prevTouch.deltaX || 0,
y: prevTouch.deltaY || 0
};
offset = session.offsetDelta = {
x: center.x,
y: center.y
};
}
touch.deltaX = prevDelta.x + (center.x - offset.x);
touch.deltaY = prevDelta.y + (center.y - offset.y);
};
var calTouchData = function(touch) {
var session = $.gestures.session;
var touches = touch.touches;
var touchesLength = touches.length;
if(!session.firstTouch) {
session.firstTouch = copySimpleTouchData(touch);
}
if(multiTouch() && touchesLength > 1 && !session.firstMultiTouch) {
session.firstMultiTouch = copySimpleTouchData(touch);
} else if(touchesLength === 1) {
session.firstMultiTouch = false;
}
var firstTouch = session.firstTouch;
var firstMultiTouch = session.firstMultiTouch;
var offsetCenter = firstMultiTouch ? firstMultiTouch.center : firstTouch.center;
var center = touch.center = getMultiCenter(touches);
touch.timestamp = $.now();
touch.deltaTime = touch.timestamp - firstTouch.timestamp;
touch.angle = getAngle(offsetCenter, center);
touch.distance = getDistance(offsetCenter, center);
calDelta(touch);
touch.offsetDirection = getDirection(touch.deltaX, touch.deltaY);
touch.scale = firstMultiTouch ? getScale(firstMultiTouch.touches, touches) : 1;
touch.rotation = firstMultiTouch ? getRotation(firstMultiTouch.touches, touches) : 0;
calIntervalTouchData(touch);
};
var CAL_INTERVAL = 25;
var calIntervalTouchData = function(touch) {
var session = $.gestures.session;
var last = session.lastInterval || touch;
var deltaTime = touch.timestamp - last.timestamp;
var velocity;
var velocityX;
var velocityY;
var direction;
if(touch.gesture.type != $.EVENT_CANCEL && (deltaTime > CAL_INTERVAL || last.velocity === undefined)) {
var deltaX = last.deltaX - touch.deltaX;
var deltaY = last.deltaY - touch.deltaY;
var v = getVelocity(deltaTime, deltaX, deltaY);
velocityX = v.x;
velocityY = v.y;
velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
direction = getDirection(deltaX, deltaY) || last.direction;
session.lastInterval = touch;
} else {
velocity = last.velocity;
velocityX = last.velocityX;
velocityY = last.velocityY;
direction = last.direction;
}
touch.velocity = velocity;
touch.velocityX = velocityX;
touch.velocityY = velocityY;
touch.direction = direction;
};
var targetIds = {};
var convertTouches = function(touches) {
for(var i = 0; i < touches.length; i++) {
!touches['identifier'] && (touches['identifier'] = 0);
}
return touches;
};
var getTouches = function(event, touch) {
var allTouches = convertTouches($.slice.call(event.touches || [event]));
var type = event.type;
var targetTouches = [];
var changedTargetTouches = [];
//当touchstart或touchmove且touches长度为1,直接获得all和changed
if((type === $.EVENT_START || type === $.EVENT_MOVE) && allTouches.length === 1) {
targetIds[allTouches[0].identifier] = true;
targetTouches = allTouches;
changedTargetTouches = allTouches;
touch.target = event.target;
} else {
var i = 0;
var targetTouches = [];
var changedTargetTouches = [];
var changedTouches = convertTouches($.slice.call(event.changedTouches || [event]));
touch.target = event.target;
var sessionTarget = $.gestures.session.target || event.target;
targetTouches = allTouches.filter(function(touch) {
return hasParent(touch.target, sessionTarget);
});
if(type === $.EVENT_START) {
i = 0;
while(i < targetTouches.length) {
targetIds[targetTouches[i].identifier] = true;
i++;
}
}
i = 0;
while(i < changedTouches.length) {
if(targetIds[changedTouches[i].identifier]) {
changedTargetTouches.push(changedTouches[i]);
}
if(type === $.EVENT_END || type === $.EVENT_CANCEL) {
delete targetIds[changedTouches[i].identifier];
}
i++;
}
if(!changedTargetTouches.length) {
return false;
}
}
targetTouches = uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true);
var touchesLength = targetTouches.length;
var changedTouchesLength = changedTargetTouches.length;
if(type === $.EVENT_START && touchesLength - changedTouchesLength === 0) { //first
touch.isFirst = true;
$.gestures.touch = $.gestures.session = {
target: event.target
};
}
touch.isFinal = ((type === $.EVENT_END || type === $.EVENT_CANCEL) && (touchesLength - changedTouchesLength === 0));
touch.touches = targetTouches;
touch.changedTouches = changedTargetTouches;
return true;
};
var handleTouchEvent = function(event) {
var touch = {
gesture: event
};
var touches = getTouches(event, touch);
if(!touches) {
return;
}
calTouchData(touch);
detect(event, touch);
$.gestures.session.prevTouch = touch;
if(event.type === $.EVENT_END && !$.isTouchable) {
$.gestures.touch = $.gestures.session = {};
}
};
var supportsPassive = (function checkPassiveListener() {
var supportsPassive = false;
try {
var opts = Object.defineProperty({}, 'passive', {
get: function get() {
supportsPassive = true;
},
});
window.addEventListener('testPassiveListener', null, opts);
} catch(e) {
// No support
}
return supportsPassive;
}())
window.addEventListener($.EVENT_START, handleTouchEvent);
window.addEventListener($.EVENT_MOVE, handleTouchEvent, supportsPassive ? {
passive: false,
capture: false
} : false);
window.addEventListener($.EVENT_END, handleTouchEvent);
window.addEventListener($.EVENT_CANCEL, handleTouchEvent);
//fixed hashchange(android)
window.addEventListener($.EVENT_CLICK, function(e) {
//TODO 应该判断当前target是不是在targets.popover内部,而不是非要相等
if(($.os.android || $.os.ios) && (($.targets.popover && e.target === $.targets.popover) || ($.targets.tab) || $.targets.offcanvas || $.targets.modal)) {
e.preventDefault();
}
}, true);
//增加原生滚动识别
$.isScrolling = false;
var scrollingTimeout = null;
window.addEventListener('scroll', function() {
$.isScrolling = true;
scrollingTimeout && clearTimeout(scrollingTimeout);
scrollingTimeout = setTimeout(function() {
$.isScrolling = false;
}, 250);
});
})(mui, window);
/**
* mui gesture flick[left|right|up|down]
* @param {type} $
* @param {type} name
* @returns {undefined}
*/
(function($, name) {
var flickStartTime = 0;
var handle = function(event, touch) {
var session = $.gestures.session;
var options = this.options;
var now = $.now();
switch (event.type) {
case $.EVENT_MOVE:
if (now - flickStartTime > 300) {
flickStartTime = now;
session.flickStart = touch.center;
}
break;
case $.EVENT_END:
case $.EVENT_CANCEL:
touch.flick = false;
if (session.flickStart && options.flickMaxTime > (now - flickStartTime) && touch.distance > options.flickMinDistince) {
touch.flick = true;
touch.flickTime = now - flickStartTime;
touch.flickDistanceX = touch.center.x - session.flickStart.x;
touch.flickDistanceY = touch.center.y - session.flickStart.y;
$.trigger(session.target, name, touch);
$.trigger(session.target, name + touch.direction, touch);
}
break;
}
};
/**
* mui gesture flick
*/
$.addGesture({
name: name,
index: 5,
handle: handle,
options: {
flickMaxTime: 200,
flickMinDistince: 10
}
});
})(mui, 'flick');
/**
* mui gesture swipe[left|right|up|down]
* @param {type} $
* @param {type} name
* @returns {undefined}
*/
(function($, name) {
var handle = function(event, touch) {
var session = $.gestures.session;
if (event.type === $.EVENT_END || event.type === $.EVENT_CANCEL) {
var options = this.options;
touch.swipe = false;
//TODO 后续根据velocity计算
if (touch.direction && options.swipeMaxTime > touch.deltaTime && touch.distance > options.swipeMinDistince) {
touch.swipe = true;
$.trigger(session.target, name, touch);
$.trigger(session.target, name + touch.direction, touch);
}
}
};
/**
* mui gesture swipe
*/
$.addGesture({
name: name,
index: 10,
handle: handle,
options: {
swipeMaxTime: 300,
swipeMinDistince: 18
}
});
})(mui, 'swipe');
/**
* mui gesture drag[start|left|right|up|down|end]
* @param {type} $
* @param {type} name
* @returns {undefined}
*/
(function($, name) {
var handle = function(event, touch) {
var session = $.gestures.session;
switch (event.type) {
case $.EVENT_START:
break;
case $.EVENT_MOVE:
if (!touch.direction || !session.target) {
return;
}
//修正direction,可在session期间自行锁定拖拽方向,方便开发scroll类不同方向拖拽插件嵌套
if (session.lockDirection && session.startDirection) {
if (session.startDirection && session.startDirection !== touch.direction) {
if (session.startDirection === 'up' || session.startDirection === 'down') {
touch.direction = touch.deltaY < 0 ? 'up' : 'down';
} else {
touch.direction = touch.deltaX < 0 ? 'left' : 'right';
}
}
}
if (!session.drag) {
session.drag = true;
$.trigger(session.target, name + 'start', touch);
}
$.trigger(session.target, name, touch);
$.trigger(session.target, name + touch.direction, touch);
break;
case $.EVENT_END:
case $.EVENT_CANCEL:
if (session.drag && touch.isFinal) {
$.trigger(session.target, name + 'end', touch);
}
break;
}
};
/**
* mui gesture drag
*/
$.addGesture({
name: name,
index: 20,
handle: handle,
options: {
fingers: 1
}
});
})(mui, 'drag');
/**
* mui gesture tap and doubleTap
* @param {type} $
* @param {type} name
* @returns {undefined}
*/
(function($, name) {
var lastTarget;
var lastTapTime;
var handle = function(event, touch) {
var session = $.gestures.session;
var options = this.options;
switch (event.type) {
case $.EVENT_END:
if (!touch.isFinal) {
return;
}
var target = session.target;
if (!target || (target.disabled || (target.classList && target.classList.contains('mui-disabled')))) {
return;
}
if (touch.distance < options.tapMaxDistance && touch.deltaTime < options.tapMaxTime) {
if ($.options.gestureConfig.doubletap && lastTarget && (lastTarget === target)) { //same target
if (lastTapTime && (touch.timestamp - lastTapTime) < options.tapMaxInterval) {
$.trigger(target, 'doubletap', touch);
lastTapTime = $.now();
lastTarget = target;
return;
}
}
$.trigger(target, name, touch);
lastTapTime = $.now();
lastTarget = target;
}
break;
}
};
/**
* mui gesture tap
*/
$.addGesture({
name: name,
index: 30,
handle: handle,
options: {
fingers: 1,
tapMaxInterval: 300,
tapMaxDistance: 5,
tapMaxTime: 250
}
});
})(mui, 'tap');
/**
* mui gesture longtap
* @param {type} $
* @param {type} name
* @returns {undefined}
*/
(function($, name) {
var timer;
var handle = function(event, touch) {
var session = $.gestures.session;
var options = this.options;
switch (event.type) {
case $.EVENT_START:
clearTimeout(timer);
timer = setTimeout(function() {
$.trigger(session.target, name, touch);
}, options.holdTimeout);
break;
case $.EVENT_MOVE:
if (touch.distance > options.holdThreshold) {
clearTimeout(timer);
}
break;
case $.EVENT_END:
case $.EVENT_CANCEL:
clearTimeout(timer);
break;
}
};
/**
* mui gesture longtap
*/
$.addGesture({
name: name,
index: 10,
handle: handle,
options: {
fingers: 1,
holdTimeout: 500,
holdThreshold: 2
}
});
})(mui, 'longtap');
/**
* mui gesture hold
* @param {type} $
* @param {type} name
* @returns {undefined}
*/
(function($, name) {
var timer;
var handle = function(event, touch) {
var session = $.gestures.session;
var options = this.options;
switch (event.type) {
case $.EVENT_START:
if ($.options.gestureConfig.hold) {
timer && clearTimeout(timer);
timer = setTimeout(function() {
touch.hold = true;
$.trigger(session.target, name, touch);
}, options.holdTimeout);
}
break;
case $.EVENT_MOVE:
break;
case $.EVENT_END:
case $.EVENT_CANCEL:
if (timer) {
clearTimeout(timer) && (timer = null);
$.trigger(session.target, 'release', touch);
}
break;
}
};
/**
* mui gesture hold
*/
$.addGesture({
name: name,
index: 10,
handle: handle,
options: {
fingers: 1,
holdTimeout: 0
}
});
})(mui, 'hold');
/**
* mui gesture pinch
* @param {type} $
* @param {type} name
* @returns {undefined}
*/
(function($, name) {
var handle = function(event, touch) {
var options = this.options;
var session = $.gestures.session;
switch (event.type) {
case $.EVENT_START:
break;
case $.EVENT_MOVE:
if ($.options.gestureConfig.pinch) {
if (touch.touches.length < 2) {
return;
}
if (!session.pinch) { //start
session.pinch = true;
$.trigger(session.target, name + 'start', touch);
}
$.trigger(session.target, name, touch);
var scale = touch.scale;
var rotation = touch.rotation;
var lastScale = typeof touch.lastScale === 'undefined' ? 1 : touch.lastScale;
var scaleDiff = 0.000000000001; //防止scale与lastScale相等,不触发事件的情况。
if (scale > lastScale) { //out
lastScale = scale - scaleDiff;
$.trigger(session.target, name + 'out', touch);
} //in
else if (scale < lastScale) {
lastScale = scale + scaleDiff;
$.trigger(session.target, name + 'in', touch);
}
if (Math.abs(rotation) > options.minRotationAngle) {
$.trigger(session.target, 'rotate', touch);
}
}
break;
case $.EVENT_END:
case $.EVENT_CANCEL:
if ($.options.gestureConfig.pinch && session.pinch && touch.touches.length === 2) {
session.pinch = false;
$.trigger(session.target, name + 'end', touch);
}
break;
}
};
/**
* mui gesture pinch
*/
$.addGesture({
name: name,
index: 10,
handle: handle,
options: {
minRotationAngle: 0
}
});
})(mui, 'pinch');
/**
* mui.init
* @param {type} $
* @returns {undefined}
*/
(function($) {
$.global = $.options = {
gestureConfig: {
tap: true,
doubletap: false,
longtap: false,
hold: false,
flick: true,
swipe: true,
drag: true,
pinch: false
}
};
/**
*
* @param {type} options
* @returns {undefined}
*/
$.initGlobal = function(options) {
$.options = $.extend(true, $.global, options);
return this;
};
var inits = {};
/**
* 单页配置 初始化
* @param {object} options
*/
$.init = function(options) {
$.options = $.extend(true, $.global, options || {});
$.ready(function() {
$.doAction('inits', function(index, init) {
var isInit = !!(!inits[init.name] || init.repeat);
if (isInit) {
init.handle.call($);
inits[init.name] = true;
}
});
});
return this;
};
/**
* 增加初始化执行流程
* @param {function} init
*/
$.addInit = function(init) {
return $.addAction('inits', init);
};
/**
* 处理html5版本subpages
*/
$.addInit({
name: 'iframe',
index: 100,
handle: function() {
var options = $.options;
var subpages = options.subpages || [];
if (!$.os.plus && subpages.length) {
//暂时只处理单个subpage。后续可以考虑支持多个subpage
createIframe(subpages[0]);
}
}
});
var createIframe = function(options) {
var wrapper = document.createElement('div');
wrapper.className = 'mui-iframe-wrapper';
var styles = options.styles || {};
if (typeof styles.top !== 'string') {
styles.top = '0px';
}
if (typeof styles.bottom !== 's