blob: c4c1d986d8a878c9bb75f24f318cf11bbefcb43a [file] [log] [blame]
qiaoweif044a742019-07-10 16:04:20 +08001/**
2* jQuery WeUI V1.2.1
3* By 言川
4* http://lihongxun945.github.io/jquery-weui/
5 */
6/* global $:true */
7/* global WebKitCSSMatrix:true */
8
9(function($) {
10 "use strict";
11
12 $.fn.transitionEnd = function(callback) {
13 var events = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'],
14 i, dom = this;
15
16 function fireCallBack(e) {
17 /*jshint validthis:true */
18 if (e.target !== this) return;
19 callback.call(this, e);
20 for (i = 0; i < events.length; i++) {
21 dom.off(events[i], fireCallBack);
22 }
23 }
24 if (callback) {
25 for (i = 0; i < events.length; i++) {
26 dom.on(events[i], fireCallBack);
27 }
28 }
29 return this;
30 };
31
32 $.support = (function() {
33 var support = {
34 touch: !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch)
35 };
36 return support;
37 })();
38
39 $.touchEvents = {
40 start: $.support.touch ? 'touchstart' : 'mousedown',
41 move: $.support.touch ? 'touchmove' : 'mousemove',
42 end: $.support.touch ? 'touchend' : 'mouseup'
43 };
44
45 $.getTouchPosition = function(e) {
46 e = e.originalEvent || e; //jquery wrap the originevent
47 if(e.type === 'touchstart' || e.type === 'touchmove' || e.type === 'touchend') {
48 return {
49 x: e.targetTouches[0].pageX,
50 y: e.targetTouches[0].pageY
51 };
52 } else {
53 return {
54 x: e.pageX,
55 y: e.pageY
56 };
57 }
58 };
59
60 $.fn.scrollHeight = function() {
61 return this[0].scrollHeight;
62 };
63
64 $.fn.transform = function(transform) {
65 for (var i = 0; i < this.length; i++) {
66 var elStyle = this[i].style;
67 elStyle.webkitTransform = elStyle.MsTransform = elStyle.msTransform = elStyle.MozTransform = elStyle.OTransform = elStyle.transform = transform;
68 }
69 return this;
70 };
71 $.fn.transition = function(duration) {
72 if (typeof duration !== 'string') {
73 duration = duration + 'ms';
74 }
75 for (var i = 0; i < this.length; i++) {
76 var elStyle = this[i].style;
77 elStyle.webkitTransitionDuration = elStyle.MsTransitionDuration = elStyle.msTransitionDuration = elStyle.MozTransitionDuration = elStyle.OTransitionDuration = elStyle.transitionDuration = duration;
78 }
79 return this;
80 };
81
82 $.getTranslate = function (el, axis) {
83 var matrix, curTransform, curStyle, transformMatrix;
84
85 // automatic axis detection
86 if (typeof axis === 'undefined') {
87 axis = 'x';
88 }
89
90 curStyle = window.getComputedStyle(el, null);
91 if (window.WebKitCSSMatrix) {
92 // Some old versions of Webkit choke when 'none' is passed; pass
93 // empty string instead in this case
94 transformMatrix = new WebKitCSSMatrix(curStyle.webkitTransform === 'none' ? '' : curStyle.webkitTransform);
95 }
96 else {
97 transformMatrix = curStyle.MozTransform || curStyle.OTransform || curStyle.MsTransform || curStyle.msTransform || curStyle.transform || curStyle.getPropertyValue('transform').replace('translate(', 'matrix(1, 0, 0, 1,');
98 matrix = transformMatrix.toString().split(',');
99 }
100
101 if (axis === 'x') {
102 //Latest Chrome and webkits Fix
103 if (window.WebKitCSSMatrix)
104 curTransform = transformMatrix.m41;
105 //Crazy IE10 Matrix
106 else if (matrix.length === 16)
107 curTransform = parseFloat(matrix[12]);
108 //Normal Browsers
109 else
110 curTransform = parseFloat(matrix[4]);
111 }
112 if (axis === 'y') {
113 //Latest Chrome and webkits Fix
114 if (window.WebKitCSSMatrix)
115 curTransform = transformMatrix.m42;
116 //Crazy IE10 Matrix
117 else if (matrix.length === 16)
118 curTransform = parseFloat(matrix[13]);
119 //Normal Browsers
120 else
121 curTransform = parseFloat(matrix[5]);
122 }
123
124 return curTransform || 0;
125 };
126 $.requestAnimationFrame = function (callback) {
127 if (window.requestAnimationFrame) return window.requestAnimationFrame(callback);
128 else if (window.webkitRequestAnimationFrame) return window.webkitRequestAnimationFrame(callback);
129 else if (window.mozRequestAnimationFrame) return window.mozRequestAnimationFrame(callback);
130 else {
131 return window.setTimeout(callback, 1000 / 60);
132 }
133 };
134
135 $.cancelAnimationFrame = function (id) {
136 if (window.cancelAnimationFrame) return window.cancelAnimationFrame(id);
137 else if (window.webkitCancelAnimationFrame) return window.webkitCancelAnimationFrame(id);
138 else if (window.mozCancelAnimationFrame) return window.mozCancelAnimationFrame(id);
139 else {
140 return window.clearTimeout(id);
141 }
142 };
143
144 $.fn.join = function(arg) {
145 return this.toArray().join(arg);
146 }
147})($);
148
149/*===========================
150 Template7 Template engine
151 ===========================*/
152/* global $:true */
153/* jshint unused:false */
154/* jshint forin:false */
155+function ($) {
156 "use strict";
157 $.Template7 = $.t7 = (function () {
158 function isArray(arr) {
159 return Object.prototype.toString.apply(arr) === '[object Array]';
160 }
161 function isObject(obj) {
162 return obj instanceof Object;
163 }
164 function isFunction(func) {
165 return typeof func === 'function';
166 }
167 var cache = {};
168 function helperToSlices(string) {
169 var helperParts = string.replace(/[{}#}]/g, '').split(' ');
170 var slices = [];
171 var shiftIndex, i, j;
172 for (i = 0; i < helperParts.length; i++) {
173 var part = helperParts[i];
174 if (i === 0) slices.push(part);
175 else {
176 if (part.indexOf('"') === 0) {
177 // Plain String
178 if (part.match(/"/g).length === 2) {
179 // One word string
180 slices.push(part);
181 }
182 else {
183 // Find closed Index
184 shiftIndex = 0;
185 for (j = i + 1; j < helperParts.length; j++) {
186 part += ' ' + helperParts[j];
187 if (helperParts[j].indexOf('"') >= 0) {
188 shiftIndex = j;
189 slices.push(part);
190 break;
191 }
192 }
193 if (shiftIndex) i = shiftIndex;
194 }
195 }
196 else {
197 if (part.indexOf('=') > 0) {
198 // Hash
199 var hashParts = part.split('=');
200 var hashName = hashParts[0];
201 var hashContent = hashParts[1];
202 if (hashContent.match(/"/g).length !== 2) {
203 shiftIndex = 0;
204 for (j = i + 1; j < helperParts.length; j++) {
205 hashContent += ' ' + helperParts[j];
206 if (helperParts[j].indexOf('"') >= 0) {
207 shiftIndex = j;
208 break;
209 }
210 }
211 if (shiftIndex) i = shiftIndex;
212 }
213 var hash = [hashName, hashContent.replace(/"/g,'')];
214 slices.push(hash);
215 }
216 else {
217 // Plain variable
218 slices.push(part);
219 }
220 }
221 }
222 }
223 return slices;
224 }
225 function stringToBlocks(string) {
226 var blocks = [], i, j, k;
227 if (!string) return [];
228 var _blocks = string.split(/({{[^{^}]*}})/);
229 for (i = 0; i < _blocks.length; i++) {
230 var block = _blocks[i];
231 if (block === '') continue;
232 if (block.indexOf('{{') < 0) {
233 blocks.push({
234 type: 'plain',
235 content: block
236 });
237 }
238 else {
239 if (block.indexOf('{/') >= 0) {
240 continue;
241 }
242 if (block.indexOf('{#') < 0 && block.indexOf(' ') < 0 && block.indexOf('else') < 0) {
243 // Simple variable
244 blocks.push({
245 type: 'variable',
246 contextName: block.replace(/[{}]/g, '')
247 });
248 continue;
249 }
250 // Helpers
251 var helperSlices = helperToSlices(block);
252 var helperName = helperSlices[0];
253 var helperContext = [];
254 var helperHash = {};
255 for (j = 1; j < helperSlices.length; j++) {
256 var slice = helperSlices[j];
257 if (isArray(slice)) {
258 // Hash
259 helperHash[slice[0]] = slice[1] === 'false' ? false : slice[1];
260 }
261 else {
262 helperContext.push(slice);
263 }
264 }
265
266 if (block.indexOf('{#') >= 0) {
267 // Condition/Helper
268 var helperStartIndex = i;
269 var helperContent = '';
270 var elseContent = '';
271 var toSkip = 0;
272 var shiftIndex;
273 var foundClosed = false, foundElse = false, foundClosedElse = false, depth = 0;
274 for (j = i + 1; j < _blocks.length; j++) {
275 if (_blocks[j].indexOf('{{#') >= 0) {
276 depth ++;
277 }
278 if (_blocks[j].indexOf('{{/') >= 0) {
279 depth --;
280 }
281 if (_blocks[j].indexOf('{{#' + helperName) >= 0) {
282 helperContent += _blocks[j];
283 if (foundElse) elseContent += _blocks[j];
284 toSkip ++;
285 }
286 else if (_blocks[j].indexOf('{{/' + helperName) >= 0) {
287 if (toSkip > 0) {
288 toSkip--;
289 helperContent += _blocks[j];
290 if (foundElse) elseContent += _blocks[j];
291 }
292 else {
293 shiftIndex = j;
294 foundClosed = true;
295 break;
296 }
297 }
298 else if (_blocks[j].indexOf('else') >= 0 && depth === 0) {
299 foundElse = true;
300 }
301 else {
302 if (!foundElse) helperContent += _blocks[j];
303 if (foundElse) elseContent += _blocks[j];
304 }
305
306 }
307 if (foundClosed) {
308 if (shiftIndex) i = shiftIndex;
309 blocks.push({
310 type: 'helper',
311 helperName: helperName,
312 contextName: helperContext,
313 content: helperContent,
314 inverseContent: elseContent,
315 hash: helperHash
316 });
317 }
318 }
319 else if (block.indexOf(' ') > 0) {
320 blocks.push({
321 type: 'helper',
322 helperName: helperName,
323 contextName: helperContext,
324 hash: helperHash
325 });
326 }
327 }
328 }
329 return blocks;
330 }
331 var Template7 = function (template) {
332 var t = this;
333 t.template = template;
334
335 function getCompileFn(block, depth) {
336 if (block.content) return compile(block.content, depth);
337 else return function () {return ''; };
338 }
339 function getCompileInverse(block, depth) {
340 if (block.inverseContent) return compile(block.inverseContent, depth);
341 else return function () {return ''; };
342 }
343 function getCompileVar(name, ctx) {
344 var variable, parts, levelsUp = 0, initialCtx = ctx;
345 if (name.indexOf('../') === 0) {
346 levelsUp = name.split('../').length - 1;
347 var newDepth = ctx.split('_')[1] - levelsUp;
348 ctx = 'ctx_' + (newDepth >= 1 ? newDepth : 1);
349 parts = name.split('../')[levelsUp].split('.');
350 }
351 else if (name.indexOf('@global') === 0) {
352 ctx = '$.Template7.global';
353 parts = name.split('@global.')[1].split('.');
354 }
355 else if (name.indexOf('@root') === 0) {
356 ctx = 'ctx_1';
357 parts = name.split('@root.')[1].split('.');
358 }
359 else {
360 parts = name.split('.');
361 }
362 variable = ctx;
363 for (var i = 0; i < parts.length; i++) {
364 var part = parts[i];
365 if (part.indexOf('@') === 0) {
366 if (i > 0) {
367 variable += '[(data && data.' + part.replace('@', '') + ')]';
368 }
369 else {
370 variable = '(data && data.' + name.replace('@', '') + ')';
371 }
372 }
373 else {
374 if (isFinite(part)) {
375 variable += '[' + part + ']';
376 }
377 else {
378 if (part.indexOf('this') === 0) {
379 variable = part.replace('this', ctx);
380 }
381 else {
382 variable += '.' + part;
383 }
384 }
385 }
386 }
387
388 return variable;
389 }
390 function getCompiledArguments(contextArray, ctx) {
391 var arr = [];
392 for (var i = 0; i < contextArray.length; i++) {
393 if (contextArray[i].indexOf('"') === 0) arr.push(contextArray[i]);
394 else {
395 arr.push(getCompileVar(contextArray[i], ctx));
396 }
397 }
398 return arr.join(', ');
399 }
400 function compile(template, depth) {
401 depth = depth || 1;
402 template = template || t.template;
403 if (typeof template !== 'string') {
404 throw new Error('Template7: Template must be a string');
405 }
406 var blocks = stringToBlocks(template);
407 if (blocks.length === 0) {
408 return function () { return ''; };
409 }
410 var ctx = 'ctx_' + depth;
411 var resultString = '(function (' + ctx + ', data) {\n';
412 if (depth === 1) {
413 resultString += 'function isArray(arr){return Object.prototype.toString.apply(arr) === \'[object Array]\';}\n';
414 resultString += 'function isFunction(func){return (typeof func === \'function\');}\n';
415 resultString += 'function c(val, ctx) {if (typeof val !== "undefined") {if (isFunction(val)) {return val.call(ctx);} else return val;} else return "";}\n';
416 }
417 resultString += 'var r = \'\';\n';
418 var i, j, context;
419 for (i = 0; i < blocks.length; i++) {
420 var block = blocks[i];
421 // Plain block
422 if (block.type === 'plain') {
423 resultString += 'r +=\'' + (block.content).replace(/\r/g, '\\r').replace(/\n/g, '\\n').replace(/'/g, '\\' + '\'') + '\';';
424 continue;
425 }
426 var variable, compiledArguments;
427 // Variable block
428 if (block.type === 'variable') {
429 variable = getCompileVar(block.contextName, ctx);
430 resultString += 'r += c(' + variable + ', ' + ctx + ');';
431 }
432 // Helpers block
433 if (block.type === 'helper') {
434 if (block.helperName in t.helpers) {
435 compiledArguments = getCompiledArguments(block.contextName, ctx);
436 resultString += 'r += ($.Template7.helpers.' + block.helperName + ').call(' + ctx + ', ' + (compiledArguments && (compiledArguments + ', ')) +'{hash:' + JSON.stringify(block.hash) + ', data: data || {}, fn: ' + getCompileFn(block, depth+1) + ', inverse: ' + getCompileInverse(block, depth+1) + ', root: ctx_1});';
437 }
438 else {
439 if (block.contextName.length > 0) {
440 throw new Error('Template7: Missing helper: "' + block.helperName + '"');
441 }
442 else {
443 variable = getCompileVar(block.helperName, ctx);
444 resultString += 'if (' + variable + ') {';
445 resultString += 'if (isArray(' + variable + ')) {';
446 resultString += 'r += ($.Template7.helpers.each).call(' + ctx + ', ' + variable + ', {hash:' + JSON.stringify(block.hash) + ', data: data || {}, fn: ' + getCompileFn(block, depth+1) + ', inverse: ' + getCompileInverse(block, depth+1) + ', root: ctx_1});';
447 resultString += '}else {';
448 resultString += 'r += ($.Template7.helpers.with).call(' + ctx + ', ' + variable + ', {hash:' + JSON.stringify(block.hash) + ', data: data || {}, fn: ' + getCompileFn(block, depth+1) + ', inverse: ' + getCompileInverse(block, depth+1) + ', root: ctx_1});';
449 resultString += '}}';
450 }
451 }
452 }
453 }
454 resultString += '\nreturn r;})';
455 return eval.call(window, resultString);
456 }
457 t.compile = function (template) {
458 if (!t.compiled) {
459 t.compiled = compile(template);
460 }
461 return t.compiled;
462 };
463 };
464 Template7.prototype = {
465 options: {},
466 helpers: {
467 'if': function (context, options) {
468 if (isFunction(context)) { context = context.call(this); }
469 if (context) {
470 return options.fn(this, options.data);
471 }
472 else {
473 return options.inverse(this, options.data);
474 }
475 },
476 'unless': function (context, options) {
477 if (isFunction(context)) { context = context.call(this); }
478 if (!context) {
479 return options.fn(this, options.data);
480 }
481 else {
482 return options.inverse(this, options.data);
483 }
484 },
485 'each': function (context, options) {
486 var ret = '', i = 0;
487 if (isFunction(context)) { context = context.call(this); }
488 if (isArray(context)) {
489 if (options.hash.reverse) {
490 context = context.reverse();
491 }
492 for (i = 0; i < context.length; i++) {
493 ret += options.fn(context[i], {first: i === 0, last: i === context.length - 1, index: i});
494 }
495 if (options.hash.reverse) {
496 context = context.reverse();
497 }
498 }
499 else {
500 for (var key in context) {
501 i++;
502 ret += options.fn(context[key], {key: key});
503 }
504 }
505 if (i > 0) return ret;
506 else return options.inverse(this);
507 },
508 'with': function (context, options) {
509 if (isFunction(context)) { context = context.call(this); }
510 return options.fn(context);
511 },
512 'join': function (context, options) {
513 if (isFunction(context)) { context = context.call(this); }
514 return context.join(options.hash.delimiter || options.hash.delimeter);
515 },
516 'js': function (expression, options) {
517 var func;
518 if (expression.indexOf('return')>=0) {
519 func = '(function(){'+expression+'})';
520 }
521 else {
522 func = '(function(){return ('+expression+')})';
523 }
524 return eval.call(this, func).call(this);
525 },
526 'js_compare': function (expression, options) {
527 var func;
528 if (expression.indexOf('return')>=0) {
529 func = '(function(){'+expression+'})';
530 }
531 else {
532 func = '(function(){return ('+expression+')})';
533 }
534 var condition = eval.call(this, func).call(this);
535 if (condition) {
536 return options.fn(this, options.data);
537 }
538 else {
539 return options.inverse(this, options.data);
540 }
541 }
542 }
543 };
544 var t7 = function (template, data) {
545 if (arguments.length === 2) {
546 var instance = new Template7(template);
547 var rendered = instance.compile()(data);
548 instance = null;
549 return (rendered);
550 }
551 else return new Template7(template);
552 };
553 t7.registerHelper = function (name, fn) {
554 Template7.prototype.helpers[name] = fn;
555 };
556 t7.unregisterHelper = function (name) {
557 Template7.prototype.helpers[name] = undefined;
558 delete Template7.prototype.helpers[name];
559 };
560
561 t7.compile = function (template, options) {
562 var instance = new Template7(template, options);
563 return instance.compile();
564 };
565
566 t7.options = Template7.prototype.options;
567 t7.helpers = Template7.prototype.helpers;
568 return t7;
569 })();
570}($);
571
572/*! Hammer.JS - v2.0.8 - 2016-04-23
573 * http://hammerjs.github.io/
574 *
575 * Copyright (c) 2016 Jorik Tangelder;
576 * Licensed under the MIT license */
577(function(window, document, exportName, undefined) {
578 'use strict';
579
580var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
581var TEST_ELEMENT = document.createElement('div');
582
583var TYPE_FUNCTION = 'function';
584
585var round = Math.round;
586var abs = Math.abs;
587var now = Date.now;
588
589/**
590 * set a timeout with a given scope
591 * @param {Function} fn
592 * @param {Number} timeout
593 * @param {Object} context
594 * @returns {number}
595 */
596function setTimeoutContext(fn, timeout, context) {
597 return setTimeout(bindFn(fn, context), timeout);
598}
599
600/**
601 * if the argument is an array, we want to execute the fn on each entry
602 * if it aint an array we don't want to do a thing.
603 * this is used by all the methods that accept a single and array argument.
604 * @param {*|Array} arg
605 * @param {String} fn
606 * @param {Object} [context]
607 * @returns {Boolean}
608 */
609function invokeArrayArg(arg, fn, context) {
610 if (Array.isArray(arg)) {
611 each(arg, context[fn], context);
612 return true;
613 }
614 return false;
615}
616
617/**
618 * walk objects and arrays
619 * @param {Object} obj
620 * @param {Function} iterator
621 * @param {Object} context
622 */
623function each(obj, iterator, context) {
624 var i;
625
626 if (!obj) {
627 return;
628 }
629
630 if (obj.forEach) {
631 obj.forEach(iterator, context);
632 } else if (obj.length !== undefined) {
633 i = 0;
634 while (i < obj.length) {
635 iterator.call(context, obj[i], i, obj);
636 i++;
637 }
638 } else {
639 for (i in obj) {
640 obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
641 }
642 }
643}
644
645/**
646 * wrap a method with a deprecation warning and stack trace
647 * @param {Function} method
648 * @param {String} name
649 * @param {String} message
650 * @returns {Function} A new function wrapping the supplied method.
651 */
652function deprecate(method, name, message) {
653 var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
654 return function() {
655 var e = new Error('get-stack-trace');
656 var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
657 .replace(/^\s+at\s+/gm, '')
658 .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
659
660 var log = window.console && (window.console.warn || window.console.log);
661 if (log) {
662 log.call(window.console, deprecationMessage, stack);
663 }
664 return method.apply(this, arguments);
665 };
666}
667
668/**
669 * extend object.
670 * means that properties in dest will be overwritten by the ones in src.
671 * @param {Object} target
672 * @param {...Object} objects_to_assign
673 * @returns {Object} target
674 */
675var assign;
676if (typeof Object.assign !== 'function') {
677 assign = function assign(target) {
678 if (target === undefined || target === null) {
679 throw new TypeError('Cannot convert undefined or null to object');
680 }
681
682 var output = Object(target);
683 for (var index = 1; index < arguments.length; index++) {
684 var source = arguments[index];
685 if (source !== undefined && source !== null) {
686 for (var nextKey in source) {
687 if (source.hasOwnProperty(nextKey)) {
688 output[nextKey] = source[nextKey];
689 }
690 }
691 }
692 }
693 return output;
694 };
695} else {
696 assign = Object.assign;
697}
698
699/**
700 * extend object.
701 * means that properties in dest will be overwritten by the ones in src.
702 * @param {Object} dest
703 * @param {Object} src
704 * @param {Boolean} [merge=false]
705 * @returns {Object} dest
706 */
707var extend = deprecate(function extend(dest, src, merge) {
708 var keys = Object.keys(src);
709 var i = 0;
710 while (i < keys.length) {
711 if (!merge || (merge && dest[keys[i]] === undefined)) {
712 dest[keys[i]] = src[keys[i]];
713 }
714 i++;
715 }
716 return dest;
717}, 'extend', 'Use `assign`.');
718
719/**
720 * merge the values from src in the dest.
721 * means that properties that exist in dest will not be overwritten by src
722 * @param {Object} dest
723 * @param {Object} src
724 * @returns {Object} dest
725 */
726var merge = deprecate(function merge(dest, src) {
727 return extend(dest, src, true);
728}, 'merge', 'Use `assign`.');
729
730/**
731 * simple class inheritance
732 * @param {Function} child
733 * @param {Function} base
734 * @param {Object} [properties]
735 */
736function inherit(child, base, properties) {
737 var baseP = base.prototype,
738 childP;
739
740 childP = child.prototype = Object.create(baseP);
741 childP.constructor = child;
742 childP._super = baseP;
743
744 if (properties) {
745 assign(childP, properties);
746 }
747}
748
749/**
750 * simple function bind
751 * @param {Function} fn
752 * @param {Object} context
753 * @returns {Function}
754 */
755function bindFn(fn, context) {
756 return function boundFn() {
757 return fn.apply(context, arguments);
758 };
759}
760
761/**
762 * let a boolean value also be a function that must return a boolean
763 * this first item in args will be used as the context
764 * @param {Boolean|Function} val
765 * @param {Array} [args]
766 * @returns {Boolean}
767 */
768function boolOrFn(val, args) {
769 if (typeof val == TYPE_FUNCTION) {
770 return val.apply(args ? args[0] || undefined : undefined, args);
771 }
772 return val;
773}
774
775/**
776 * use the val2 when val1 is undefined
777 * @param {*} val1
778 * @param {*} val2
779 * @returns {*}
780 */
781function ifUndefined(val1, val2) {
782 return (val1 === undefined) ? val2 : val1;
783}
784
785/**
786 * addEventListener with multiple events at once
787 * @param {EventTarget} target
788 * @param {String} types
789 * @param {Function} handler
790 */
791function addEventListeners(target, types, handler) {
792 each(splitStr(types), function(type) {
793 target.addEventListener(type, handler, false);
794 });
795}
796
797/**
798 * removeEventListener with multiple events at once
799 * @param {EventTarget} target
800 * @param {String} types
801 * @param {Function} handler
802 */
803function removeEventListeners(target, types, handler) {
804 each(splitStr(types), function(type) {
805 target.removeEventListener(type, handler, false);
806 });
807}
808
809/**
810 * find if a node is in the given parent
811 * @method hasParent
812 * @param {HTMLElement} node
813 * @param {HTMLElement} parent
814 * @return {Boolean} found
815 */
816function hasParent(node, parent) {
817 while (node) {
818 if (node == parent) {
819 return true;
820 }
821 node = node.parentNode;
822 }
823 return false;
824}
825
826/**
827 * small indexOf wrapper
828 * @param {String} str
829 * @param {String} find
830 * @returns {Boolean} found
831 */
832function inStr(str, find) {
833 return str.indexOf(find) > -1;
834}
835
836/**
837 * split string on whitespace
838 * @param {String} str
839 * @returns {Array} words
840 */
841function splitStr(str) {
842 return str.trim().split(/\s+/g);
843}
844
845/**
846 * find if a array contains the object using indexOf or a simple polyFill
847 * @param {Array} src
848 * @param {String} find
849 * @param {String} [findByKey]
850 * @return {Boolean|Number} false when not found, or the index
851 */
852function inArray(src, find, findByKey) {
853 if (src.indexOf && !findByKey) {
854 return src.indexOf(find);
855 } else {
856 var i = 0;
857 while (i < src.length) {
858 if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
859 return i;
860 }
861 i++;
862 }
863 return -1;
864 }
865}
866
867/**
868 * convert array-like objects to real arrays
869 * @param {Object} obj
870 * @returns {Array}
871 */
872function toArray(obj) {
873 return Array.prototype.slice.call(obj, 0);
874}
875
876/**
877 * unique array with objects based on a key (like 'id') or just by the array's value
878 * @param {Array} src [{id:1},{id:2},{id:1}]
879 * @param {String} [key]
880 * @param {Boolean} [sort=False]
881 * @returns {Array} [{id:1},{id:2}]
882 */
883function uniqueArray(src, key, sort) {
884 var results = [];
885 var values = [];
886 var i = 0;
887
888 while (i < src.length) {
889 var val = key ? src[i][key] : src[i];
890 if (inArray(values, val) < 0) {
891 results.push(src[i]);
892 }
893 values[i] = val;
894 i++;
895 }
896
897 if (sort) {
898 if (!key) {
899 results = results.sort();
900 } else {
901 results = results.sort(function sortUniqueArray(a, b) {
902 return a[key] > b[key];
903 });
904 }
905 }
906
907 return results;
908}
909
910/**
911 * get the prefixed property
912 * @param {Object} obj
913 * @param {String} property
914 * @returns {String|Undefined} prefixed
915 */
916function prefixed(obj, property) {
917 var prefix, prop;
918 var camelProp = property[0].toUpperCase() + property.slice(1);
919
920 var i = 0;
921 while (i < VENDOR_PREFIXES.length) {
922 prefix = VENDOR_PREFIXES[i];
923 prop = (prefix) ? prefix + camelProp : property;
924
925 if (prop in obj) {
926 return prop;
927 }
928 i++;
929 }
930 return undefined;
931}
932
933/**
934 * get a unique id
935 * @returns {number} uniqueId
936 */
937var _uniqueId = 1;
938function uniqueId() {
939 return _uniqueId++;
940}
941
942/**
943 * get the window object of an element
944 * @param {HTMLElement} element
945 * @returns {DocumentView|Window}
946 */
947function getWindowForElement(element) {
948 var doc = element.ownerDocument || element;
949 return (doc.defaultView || doc.parentWindow || window);
950}
951
952var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
953
954var SUPPORT_TOUCH = ('ontouchstart' in window);
955var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
956var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
957
958var INPUT_TYPE_TOUCH = 'touch';
959var INPUT_TYPE_PEN = 'pen';
960var INPUT_TYPE_MOUSE = 'mouse';
961var INPUT_TYPE_KINECT = 'kinect';
962
963var COMPUTE_INTERVAL = 25;
964
965var INPUT_START = 1;
966var INPUT_MOVE = 2;
967var INPUT_END = 4;
968var INPUT_CANCEL = 8;
969
970var DIRECTION_NONE = 1;
971var DIRECTION_LEFT = 2;
972var DIRECTION_RIGHT = 4;
973var DIRECTION_UP = 8;
974var DIRECTION_DOWN = 16;
975
976var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
977var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
978var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
979
980var PROPS_XY = ['x', 'y'];
981var PROPS_CLIENT_XY = ['clientX', 'clientY'];
982
983/**
984 * create new input type manager
985 * @param {Manager} manager
986 * @param {Function} callback
987 * @returns {Input}
988 * @constructor
989 */
990function Input(manager, callback) {
991 var self = this;
992 this.manager = manager;
993 this.callback = callback;
994 this.element = manager.element;
995 this.target = manager.options.inputTarget;
996
997 // smaller wrapper around the handler, for the scope and the enabled state of the manager,
998 // so when disabled the input events are completely bypassed.
999 this.domHandler = function(ev) {
1000 if (boolOrFn(manager.options.enable, [manager])) {
1001 self.handler(ev);
1002 }
1003 };
1004
1005 this.init();
1006
1007}
1008
1009Input.prototype = {
1010 /**
1011 * should handle the inputEvent data and trigger the callback
1012 * @virtual
1013 */
1014 handler: function() { },
1015
1016 /**
1017 * bind the events
1018 */
1019 init: function() {
1020 this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
1021 this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
1022 this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
1023 },
1024
1025 /**
1026 * unbind the events
1027 */
1028 destroy: function() {
1029 this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
1030 this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
1031 this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
1032 }
1033};
1034
1035/**
1036 * create new input type manager
1037 * called by the Manager constructor
1038 * @param {Hammer} manager
1039 * @returns {Input}
1040 */
1041function createInputInstance(manager) {
1042 var Type;
1043 var inputClass = manager.options.inputClass;
1044
1045 if (inputClass) {
1046 Type = inputClass;
1047 } else if (SUPPORT_POINTER_EVENTS) {
1048 Type = PointerEventInput;
1049 } else if (SUPPORT_ONLY_TOUCH) {
1050 Type = TouchInput;
1051 } else if (!SUPPORT_TOUCH) {
1052 Type = MouseInput;
1053 } else {
1054 Type = TouchMouseInput;
1055 }
1056 return new (Type)(manager, inputHandler);
1057}
1058
1059/**
1060 * handle input events
1061 * @param {Manager} manager
1062 * @param {String} eventType
1063 * @param {Object} input
1064 */
1065function inputHandler(manager, eventType, input) {
1066 var pointersLen = input.pointers.length;
1067 var changedPointersLen = input.changedPointers.length;
1068 var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
1069 var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
1070
1071 input.isFirst = !!isFirst;
1072 input.isFinal = !!isFinal;
1073
1074 if (isFirst) {
1075 manager.session = {};
1076 }
1077
1078 // source event is the normalized value of the domEvents
1079 // like 'touchstart, mouseup, pointerdown'
1080 input.eventType = eventType;
1081
1082 // compute scale, rotation etc
1083 computeInputData(manager, input);
1084
1085 // emit secret event
1086 manager.emit('hammer.input', input);
1087
1088 manager.recognize(input);
1089 manager.session.prevInput = input;
1090}
1091
1092/**
1093 * extend the data with some usable properties like scale, rotate, velocity etc
1094 * @param {Object} manager
1095 * @param {Object} input
1096 */
1097function computeInputData(manager, input) {
1098 var session = manager.session;
1099 var pointers = input.pointers;
1100 var pointersLength = pointers.length;
1101
1102 // store the first input to calculate the distance and direction
1103 if (!session.firstInput) {
1104 session.firstInput = simpleCloneInputData(input);
1105 }
1106
1107 // to compute scale and rotation we need to store the multiple touches
1108 if (pointersLength > 1 && !session.firstMultiple) {
1109 session.firstMultiple = simpleCloneInputData(input);
1110 } else if (pointersLength === 1) {
1111 session.firstMultiple = false;
1112 }
1113
1114 var firstInput = session.firstInput;
1115 var firstMultiple = session.firstMultiple;
1116 var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
1117
1118 var center = input.center = getCenter(pointers);
1119 input.timeStamp = now();
1120 input.deltaTime = input.timeStamp - firstInput.timeStamp;
1121
1122 input.angle = getAngle(offsetCenter, center);
1123 input.distance = getDistance(offsetCenter, center);
1124
1125 computeDeltaXY(session, input);
1126 input.offsetDirection = getDirection(input.deltaX, input.deltaY);
1127
1128 var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
1129 input.overallVelocityX = overallVelocity.x;
1130 input.overallVelocityY = overallVelocity.y;
1131 input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
1132
1133 input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
1134 input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
1135
1136 input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
1137 session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
1138
1139 computeIntervalInputData(session, input);
1140
1141 // find the correct target
1142 var target = manager.element;
1143 if (hasParent(input.srcEvent.target, target)) {
1144 target = input.srcEvent.target;
1145 }
1146 input.target = target;
1147}
1148
1149function computeDeltaXY(session, input) {
1150 var center = input.center;
1151 var offset = session.offsetDelta || {};
1152 var prevDelta = session.prevDelta || {};
1153 var prevInput = session.prevInput || {};
1154
1155 if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
1156 prevDelta = session.prevDelta = {
1157 x: prevInput.deltaX || 0,
1158 y: prevInput.deltaY || 0
1159 };
1160
1161 offset = session.offsetDelta = {
1162 x: center.x,
1163 y: center.y
1164 };
1165 }
1166
1167 input.deltaX = prevDelta.x + (center.x - offset.x);
1168 input.deltaY = prevDelta.y + (center.y - offset.y);
1169}
1170
1171/**
1172 * velocity is calculated every x ms
1173 * @param {Object} session
1174 * @param {Object} input
1175 */
1176function computeIntervalInputData(session, input) {
1177 var last = session.lastInterval || input,
1178 deltaTime = input.timeStamp - last.timeStamp,
1179 velocity, velocityX, velocityY, direction;
1180
1181 if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
1182 var deltaX = input.deltaX - last.deltaX;
1183 var deltaY = input.deltaY - last.deltaY;
1184
1185 var v = getVelocity(deltaTime, deltaX, deltaY);
1186 velocityX = v.x;
1187 velocityY = v.y;
1188 velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
1189 direction = getDirection(deltaX, deltaY);
1190
1191 session.lastInterval = input;
1192 } else {
1193 // use latest velocity info if it doesn't overtake a minimum period
1194 velocity = last.velocity;
1195 velocityX = last.velocityX;
1196 velocityY = last.velocityY;
1197 direction = last.direction;
1198 }
1199
1200 input.velocity = velocity;
1201 input.velocityX = velocityX;
1202 input.velocityY = velocityY;
1203 input.direction = direction;
1204}
1205
1206/**
1207 * create a simple clone from the input used for storage of firstInput and firstMultiple
1208 * @param {Object} input
1209 * @returns {Object} clonedInputData
1210 */
1211function simpleCloneInputData(input) {
1212 // make a simple copy of the pointers because we will get a reference if we don't
1213 // we only need clientXY for the calculations
1214 var pointers = [];
1215 var i = 0;
1216 while (i < input.pointers.length) {
1217 pointers[i] = {
1218 clientX: round(input.pointers[i].clientX),
1219 clientY: round(input.pointers[i].clientY)
1220 };
1221 i++;
1222 }
1223
1224 return {
1225 timeStamp: now(),
1226 pointers: pointers,
1227 center: getCenter(pointers),
1228 deltaX: input.deltaX,
1229 deltaY: input.deltaY
1230 };
1231}
1232
1233/**
1234 * get the center of all the pointers
1235 * @param {Array} pointers
1236 * @return {Object} center contains `x` and `y` properties
1237 */
1238function getCenter(pointers) {
1239 var pointersLength = pointers.length;
1240
1241 // no need to loop when only one touch
1242 if (pointersLength === 1) {
1243 return {
1244 x: round(pointers[0].clientX),
1245 y: round(pointers[0].clientY)
1246 };
1247 }
1248
1249 var x = 0, y = 0, i = 0;
1250 while (i < pointersLength) {
1251 x += pointers[i].clientX;
1252 y += pointers[i].clientY;
1253 i++;
1254 }
1255
1256 return {
1257 x: round(x / pointersLength),
1258 y: round(y / pointersLength)
1259 };
1260}
1261
1262/**
1263 * calculate the velocity between two points. unit is in px per ms.
1264 * @param {Number} deltaTime
1265 * @param {Number} x
1266 * @param {Number} y
1267 * @return {Object} velocity `x` and `y`
1268 */
1269function getVelocity(deltaTime, x, y) {
1270 return {
1271 x: x / deltaTime || 0,
1272 y: y / deltaTime || 0
1273 };
1274}
1275
1276/**
1277 * get the direction between two points
1278 * @param {Number} x
1279 * @param {Number} y
1280 * @return {Number} direction
1281 */
1282function getDirection(x, y) {
1283 if (x === y) {
1284 return DIRECTION_NONE;
1285 }
1286
1287 if (abs(x) >= abs(y)) {
1288 return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
1289 }
1290 return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
1291}
1292
1293/**
1294 * calculate the absolute distance between two points
1295 * @param {Object} p1 {x, y}
1296 * @param {Object} p2 {x, y}
1297 * @param {Array} [props] containing x and y keys
1298 * @return {Number} distance
1299 */
1300function getDistance(p1, p2, props) {
1301 if (!props) {
1302 props = PROPS_XY;
1303 }
1304 var x = p2[props[0]] - p1[props[0]],
1305 y = p2[props[1]] - p1[props[1]];
1306
1307 return Math.sqrt((x * x) + (y * y));
1308}
1309
1310/**
1311 * calculate the angle between two coordinates
1312 * @param {Object} p1
1313 * @param {Object} p2
1314 * @param {Array} [props] containing x and y keys
1315 * @return {Number} angle
1316 */
1317function getAngle(p1, p2, props) {
1318 if (!props) {
1319 props = PROPS_XY;
1320 }
1321 var x = p2[props[0]] - p1[props[0]],
1322 y = p2[props[1]] - p1[props[1]];
1323 return Math.atan2(y, x) * 180 / Math.PI;
1324}
1325
1326/**
1327 * calculate the rotation degrees between two pointersets
1328 * @param {Array} start array of pointers
1329 * @param {Array} end array of pointers
1330 * @return {Number} rotation
1331 */
1332function getRotation(start, end) {
1333 return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
1334}
1335
1336/**
1337 * calculate the scale factor between two pointersets
1338 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
1339 * @param {Array} start array of pointers
1340 * @param {Array} end array of pointers
1341 * @return {Number} scale
1342 */
1343function getScale(start, end) {
1344 return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
1345}
1346
1347var MOUSE_INPUT_MAP = {
1348 mousedown: INPUT_START,
1349 mousemove: INPUT_MOVE,
1350 mouseup: INPUT_END
1351};
1352
1353var MOUSE_ELEMENT_EVENTS = 'mousedown';
1354var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
1355
1356/**
1357 * Mouse events input
1358 * @constructor
1359 * @extends Input
1360 */
1361function MouseInput() {
1362 this.evEl = MOUSE_ELEMENT_EVENTS;
1363 this.evWin = MOUSE_WINDOW_EVENTS;
1364
1365 this.pressed = false; // mousedown state
1366
1367 Input.apply(this, arguments);
1368}
1369
1370inherit(MouseInput, Input, {
1371 /**
1372 * handle mouse events
1373 * @param {Object} ev
1374 */
1375 handler: function MEhandler(ev) {
1376 var eventType = MOUSE_INPUT_MAP[ev.type];
1377
1378 // on start we want to have the left mouse button down
1379 if (eventType & INPUT_START && ev.button === 0) {
1380 this.pressed = true;
1381 }
1382
1383 if (eventType & INPUT_MOVE && ev.which !== 1) {
1384 eventType = INPUT_END;
1385 }
1386
1387 // mouse must be down
1388 if (!this.pressed) {
1389 return;
1390 }
1391
1392 if (eventType & INPUT_END) {
1393 this.pressed = false;
1394 }
1395
1396 this.callback(this.manager, eventType, {
1397 pointers: [ev],
1398 changedPointers: [ev],
1399 pointerType: INPUT_TYPE_MOUSE,
1400 srcEvent: ev
1401 });
1402 }
1403});
1404
1405var POINTER_INPUT_MAP = {
1406 pointerdown: INPUT_START,
1407 pointermove: INPUT_MOVE,
1408 pointerup: INPUT_END,
1409 pointercancel: INPUT_CANCEL,
1410 pointerout: INPUT_CANCEL
1411};
1412
1413// in IE10 the pointer types is defined as an enum
1414var IE10_POINTER_TYPE_ENUM = {
1415 2: INPUT_TYPE_TOUCH,
1416 3: INPUT_TYPE_PEN,
1417 4: INPUT_TYPE_MOUSE,
1418 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
1419};
1420
1421var POINTER_ELEMENT_EVENTS = 'pointerdown';
1422var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
1423
1424// IE10 has prefixed support, and case-sensitive
1425if (window.MSPointerEvent && !window.PointerEvent) {
1426 POINTER_ELEMENT_EVENTS = 'MSPointerDown';
1427 POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
1428}
1429
1430/**
1431 * Pointer events input
1432 * @constructor
1433 * @extends Input
1434 */
1435function PointerEventInput() {
1436 this.evEl = POINTER_ELEMENT_EVENTS;
1437 this.evWin = POINTER_WINDOW_EVENTS;
1438
1439 Input.apply(this, arguments);
1440
1441 this.store = (this.manager.session.pointerEvents = []);
1442}
1443
1444inherit(PointerEventInput, Input, {
1445 /**
1446 * handle mouse events
1447 * @param {Object} ev
1448 */
1449 handler: function PEhandler(ev) {
1450 var store = this.store;
1451 var removePointer = false;
1452
1453 var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
1454 var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
1455 var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
1456
1457 var isTouch = (pointerType == INPUT_TYPE_TOUCH);
1458
1459 // get index of the event in the store
1460 var storeIndex = inArray(store, ev.pointerId, 'pointerId');
1461
1462 // start and mouse must be down
1463 if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
1464 if (storeIndex < 0) {
1465 store.push(ev);
1466 storeIndex = store.length - 1;
1467 }
1468 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
1469 removePointer = true;
1470 }
1471
1472 // it not found, so the pointer hasn't been down (so it's probably a hover)
1473 if (storeIndex < 0) {
1474 return;
1475 }
1476
1477 // update the event in the store
1478 store[storeIndex] = ev;
1479
1480 this.callback(this.manager, eventType, {
1481 pointers: store,
1482 changedPointers: [ev],
1483 pointerType: pointerType,
1484 srcEvent: ev
1485 });
1486
1487 if (removePointer) {
1488 // remove from the store
1489 store.splice(storeIndex, 1);
1490 }
1491 }
1492});
1493
1494var SINGLE_TOUCH_INPUT_MAP = {
1495 touchstart: INPUT_START,
1496 touchmove: INPUT_MOVE,
1497 touchend: INPUT_END,
1498 touchcancel: INPUT_CANCEL
1499};
1500
1501var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
1502var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
1503
1504/**
1505 * Touch events input
1506 * @constructor
1507 * @extends Input
1508 */
1509function SingleTouchInput() {
1510 this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
1511 this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
1512 this.started = false;
1513
1514 Input.apply(this, arguments);
1515}
1516
1517inherit(SingleTouchInput, Input, {
1518 handler: function TEhandler(ev) {
1519 var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
1520
1521 // should we handle the touch events?
1522 if (type === INPUT_START) {
1523 this.started = true;
1524 }
1525
1526 if (!this.started) {
1527 return;
1528 }
1529
1530 var touches = normalizeSingleTouches.call(this, ev, type);
1531
1532 // when done, reset the started state
1533 if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
1534 this.started = false;
1535 }
1536
1537 this.callback(this.manager, type, {
1538 pointers: touches[0],
1539 changedPointers: touches[1],
1540 pointerType: INPUT_TYPE_TOUCH,
1541 srcEvent: ev
1542 });
1543 }
1544});
1545
1546/**
1547 * @this {TouchInput}
1548 * @param {Object} ev
1549 * @param {Number} type flag
1550 * @returns {undefined|Array} [all, changed]
1551 */
1552function normalizeSingleTouches(ev, type) {
1553 var all = toArray(ev.touches);
1554 var changed = toArray(ev.changedTouches);
1555
1556 if (type & (INPUT_END | INPUT_CANCEL)) {
1557 all = uniqueArray(all.concat(changed), 'identifier', true);
1558 }
1559
1560 return [all, changed];
1561}
1562
1563var TOUCH_INPUT_MAP = {
1564 touchstart: INPUT_START,
1565 touchmove: INPUT_MOVE,
1566 touchend: INPUT_END,
1567 touchcancel: INPUT_CANCEL
1568};
1569
1570var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
1571
1572/**
1573 * Multi-user touch events input
1574 * @constructor
1575 * @extends Input
1576 */
1577function TouchInput() {
1578 this.evTarget = TOUCH_TARGET_EVENTS;
1579 this.targetIds = {};
1580
1581 Input.apply(this, arguments);
1582}
1583
1584inherit(TouchInput, Input, {
1585 handler: function MTEhandler(ev) {
1586 var type = TOUCH_INPUT_MAP[ev.type];
1587 var touches = getTouches.call(this, ev, type);
1588 if (!touches) {
1589 return;
1590 }
1591
1592 this.callback(this.manager, type, {
1593 pointers: touches[0],
1594 changedPointers: touches[1],
1595 pointerType: INPUT_TYPE_TOUCH,
1596 srcEvent: ev
1597 });
1598 }
1599});
1600
1601/**
1602 * @this {TouchInput}
1603 * @param {Object} ev
1604 * @param {Number} type flag
1605 * @returns {undefined|Array} [all, changed]
1606 */
1607function getTouches(ev, type) {
1608 var allTouches = toArray(ev.touches);
1609 var targetIds = this.targetIds;
1610
1611 // when there is only one touch, the process can be simplified
1612 if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
1613 targetIds[allTouches[0].identifier] = true;
1614 return [allTouches, allTouches];
1615 }
1616
1617 var i,
1618 targetTouches,
1619 changedTouches = toArray(ev.changedTouches),
1620 changedTargetTouches = [],
1621 target = this.target;
1622
1623 // get target touches from touches
1624 targetTouches = allTouches.filter(function(touch) {
1625 return hasParent(touch.target, target);
1626 });
1627
1628 // collect touches
1629 if (type === INPUT_START) {
1630 i = 0;
1631 while (i < targetTouches.length) {
1632 targetIds[targetTouches[i].identifier] = true;
1633 i++;
1634 }
1635 }
1636
1637 // filter changed touches to only contain touches that exist in the collected target ids
1638 i = 0;
1639 while (i < changedTouches.length) {
1640 if (targetIds[changedTouches[i].identifier]) {
1641 changedTargetTouches.push(changedTouches[i]);
1642 }
1643
1644 // cleanup removed touches
1645 if (type & (INPUT_END | INPUT_CANCEL)) {
1646 delete targetIds[changedTouches[i].identifier];
1647 }
1648 i++;
1649 }
1650
1651 if (!changedTargetTouches.length) {
1652 return;
1653 }
1654
1655 return [
1656 // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
1657 uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
1658 changedTargetTouches
1659 ];
1660}
1661
1662/**
1663 * Combined touch and mouse input
1664 *
1665 * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
1666 * This because touch devices also emit mouse events while doing a touch.
1667 *
1668 * @constructor
1669 * @extends Input
1670 */
1671
1672var DEDUP_TIMEOUT = 2500;
1673var DEDUP_DISTANCE = 25;
1674
1675function TouchMouseInput() {
1676 Input.apply(this, arguments);
1677
1678 var handler = bindFn(this.handler, this);
1679 this.touch = new TouchInput(this.manager, handler);
1680 this.mouse = new MouseInput(this.manager, handler);
1681
1682 this.primaryTouch = null;
1683 this.lastTouches = [];
1684}
1685
1686inherit(TouchMouseInput, Input, {
1687 /**
1688 * handle mouse and touch events
1689 * @param {Hammer} manager
1690 * @param {String} inputEvent
1691 * @param {Object} inputData
1692 */
1693 handler: function TMEhandler(manager, inputEvent, inputData) {
1694 var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
1695 isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
1696
1697 if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
1698 return;
1699 }
1700
1701 // when we're in a touch event, record touches to de-dupe synthetic mouse event
1702 if (isTouch) {
1703 recordTouches.call(this, inputEvent, inputData);
1704 } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
1705 return;
1706 }
1707
1708 this.callback(manager, inputEvent, inputData);
1709 },
1710
1711 /**
1712 * remove the event listeners
1713 */
1714 destroy: function destroy() {
1715 this.touch.destroy();
1716 this.mouse.destroy();
1717 }
1718});
1719
1720function recordTouches(eventType, eventData) {
1721 if (eventType & INPUT_START) {
1722 this.primaryTouch = eventData.changedPointers[0].identifier;
1723 setLastTouch.call(this, eventData);
1724 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
1725 setLastTouch.call(this, eventData);
1726 }
1727}
1728
1729function setLastTouch(eventData) {
1730 var touch = eventData.changedPointers[0];
1731
1732 if (touch.identifier === this.primaryTouch) {
1733 var lastTouch = {x: touch.clientX, y: touch.clientY};
1734 this.lastTouches.push(lastTouch);
1735 var lts = this.lastTouches;
1736 var removeLastTouch = function() {
1737 var i = lts.indexOf(lastTouch);
1738 if (i > -1) {
1739 lts.splice(i, 1);
1740 }
1741 };
1742 setTimeout(removeLastTouch, DEDUP_TIMEOUT);
1743 }
1744}
1745
1746function isSyntheticEvent(eventData) {
1747 var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
1748 for (var i = 0; i < this.lastTouches.length; i++) {
1749 var t = this.lastTouches[i];
1750 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
1751 if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
1752 return true;
1753 }
1754 }
1755 return false;
1756}
1757
1758var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
1759var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
1760
1761// magical touchAction value
1762var TOUCH_ACTION_COMPUTE = 'compute';
1763var TOUCH_ACTION_AUTO = 'auto';
1764var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
1765var TOUCH_ACTION_NONE = 'none';
1766var TOUCH_ACTION_PAN_X = 'pan-x';
1767var TOUCH_ACTION_PAN_Y = 'pan-y';
1768var TOUCH_ACTION_MAP = getTouchActionProps();
1769
1770/**
1771 * Touch Action
1772 * sets the touchAction property or uses the js alternative
1773 * @param {Manager} manager
1774 * @param {String} value
1775 * @constructor
1776 */
1777function TouchAction(manager, value) {
1778 this.manager = manager;
1779 this.set(value);
1780}
1781
1782TouchAction.prototype = {
1783 /**
1784 * set the touchAction value on the element or enable the polyfill
1785 * @param {String} value
1786 */
1787 set: function(value) {
1788 // find out the touch-action by the event handlers
1789 if (value == TOUCH_ACTION_COMPUTE) {
1790 value = this.compute();
1791 }
1792
1793 if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
1794 this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
1795 }
1796 this.actions = value.toLowerCase().trim();
1797 },
1798
1799 /**
1800 * just re-set the touchAction value
1801 */
1802 update: function() {
1803 this.set(this.manager.options.touchAction);
1804 },
1805
1806 /**
1807 * compute the value for the touchAction property based on the recognizer's settings
1808 * @returns {String} value
1809 */
1810 compute: function() {
1811 var actions = [];
1812 each(this.manager.recognizers, function(recognizer) {
1813 if (boolOrFn(recognizer.options.enable, [recognizer])) {
1814 actions = actions.concat(recognizer.getTouchAction());
1815 }
1816 });
1817 return cleanTouchActions(actions.join(' '));
1818 },
1819
1820 /**
1821 * this method is called on each input cycle and provides the preventing of the browser behavior
1822 * @param {Object} input
1823 */
1824 preventDefaults: function(input) {
1825 var srcEvent = input.srcEvent;
1826 var direction = input.offsetDirection;
1827
1828 // if the touch action did prevented once this session
1829 if (this.manager.session.prevented) {
1830 srcEvent.preventDefault();
1831 return;
1832 }
1833
1834 var actions = this.actions;
1835 var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
1836 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
1837 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
1838
1839 if (hasNone) {
1840 //do not prevent defaults if this is a tap gesture
1841
1842 var isTapPointer = input.pointers.length === 1;
1843 var isTapMovement = input.distance < 2;
1844 var isTapTouchTime = input.deltaTime < 250;
1845
1846 if (isTapPointer && isTapMovement && isTapTouchTime) {
1847 return;
1848 }
1849 }
1850
1851 if (hasPanX && hasPanY) {
1852 // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
1853 return;
1854 }
1855
1856 if (hasNone ||
1857 (hasPanY && direction & DIRECTION_HORIZONTAL) ||
1858 (hasPanX && direction & DIRECTION_VERTICAL)) {
1859 return this.preventSrc(srcEvent);
1860 }
1861 },
1862
1863 /**
1864 * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
1865 * @param {Object} srcEvent
1866 */
1867 preventSrc: function(srcEvent) {
1868 this.manager.session.prevented = true;
1869 srcEvent.preventDefault();
1870 }
1871};
1872
1873/**
1874 * when the touchActions are collected they are not a valid value, so we need to clean things up. *
1875 * @param {String} actions
1876 * @returns {*}
1877 */
1878function cleanTouchActions(actions) {
1879 // none
1880 if (inStr(actions, TOUCH_ACTION_NONE)) {
1881 return TOUCH_ACTION_NONE;
1882 }
1883
1884 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
1885 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
1886
1887 // if both pan-x and pan-y are set (different recognizers
1888 // for different directions, e.g. horizontal pan but vertical swipe?)
1889 // we need none (as otherwise with pan-x pan-y combined none of these
1890 // recognizers will work, since the browser would handle all panning
1891 if (hasPanX && hasPanY) {
1892 return TOUCH_ACTION_NONE;
1893 }
1894
1895 // pan-x OR pan-y
1896 if (hasPanX || hasPanY) {
1897 return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
1898 }
1899
1900 // manipulation
1901 if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
1902 return TOUCH_ACTION_MANIPULATION;
1903 }
1904
1905 return TOUCH_ACTION_AUTO;
1906}
1907
1908function getTouchActionProps() {
1909 if (!NATIVE_TOUCH_ACTION) {
1910 return false;
1911 }
1912 var touchMap = {};
1913 var cssSupports = window.CSS && window.CSS.supports;
1914 ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
1915
1916 // If css.supports is not supported but there is native touch-action assume it supports
1917 // all values. This is the case for IE 10 and 11.
1918 touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
1919 });
1920 return touchMap;
1921}
1922
1923/**
1924 * Recognizer flow explained; *
1925 * All recognizers have the initial state of POSSIBLE when a input session starts.
1926 * The definition of a input session is from the first input until the last input, with all it's movement in it. *
1927 * Example session for mouse-input: mousedown -> mousemove -> mouseup
1928 *
1929 * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
1930 * which determines with state it should be.
1931 *
1932 * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
1933 * POSSIBLE to give it another change on the next cycle.
1934 *
1935 * Possible
1936 * |
1937 * +-----+---------------+
1938 * | |
1939 * +-----+-----+ |
1940 * | | |
1941 * Failed Cancelled |
1942 * +-------+------+
1943 * | |
1944 * Recognized Began
1945 * |
1946 * Changed
1947 * |
1948 * Ended/Recognized
1949 */
1950var STATE_POSSIBLE = 1;
1951var STATE_BEGAN = 2;
1952var STATE_CHANGED = 4;
1953var STATE_ENDED = 8;
1954var STATE_RECOGNIZED = STATE_ENDED;
1955var STATE_CANCELLED = 16;
1956var STATE_FAILED = 32;
1957
1958/**
1959 * Recognizer
1960 * Every recognizer needs to extend from this class.
1961 * @constructor
1962 * @param {Object} options
1963 */
1964function Recognizer(options) {
1965 this.options = assign({}, this.defaults, options || {});
1966
1967 this.id = uniqueId();
1968
1969 this.manager = null;
1970
1971 // default is enable true
1972 this.options.enable = ifUndefined(this.options.enable, true);
1973
1974 this.state = STATE_POSSIBLE;
1975
1976 this.simultaneous = {};
1977 this.requireFail = [];
1978}
1979
1980Recognizer.prototype = {
1981 /**
1982 * @virtual
1983 * @type {Object}
1984 */
1985 defaults: {},
1986
1987 /**
1988 * set options
1989 * @param {Object} options
1990 * @return {Recognizer}
1991 */
1992 set: function(options) {
1993 assign(this.options, options);
1994
1995 // also update the touchAction, in case something changed about the directions/enabled state
1996 this.manager && this.manager.touchAction.update();
1997 return this;
1998 },
1999
2000 /**
2001 * recognize simultaneous with an other recognizer.
2002 * @param {Recognizer} otherRecognizer
2003 * @returns {Recognizer} this
2004 */
2005 recognizeWith: function(otherRecognizer) {
2006 if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
2007 return this;
2008 }
2009
2010 var simultaneous = this.simultaneous;
2011 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
2012 if (!simultaneous[otherRecognizer.id]) {
2013 simultaneous[otherRecognizer.id] = otherRecognizer;
2014 otherRecognizer.recognizeWith(this);
2015 }
2016 return this;
2017 },
2018
2019 /**
2020 * drop the simultaneous link. it doesnt remove the link on the other recognizer.
2021 * @param {Recognizer} otherRecognizer
2022 * @returns {Recognizer} this
2023 */
2024 dropRecognizeWith: function(otherRecognizer) {
2025 if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
2026 return this;
2027 }
2028
2029 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
2030 delete this.simultaneous[otherRecognizer.id];
2031 return this;
2032 },
2033
2034 /**
2035 * recognizer can only run when an other is failing
2036 * @param {Recognizer} otherRecognizer
2037 * @returns {Recognizer} this
2038 */
2039 requireFailure: function(otherRecognizer) {
2040 if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
2041 return this;
2042 }
2043
2044 var requireFail = this.requireFail;
2045 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
2046 if (inArray(requireFail, otherRecognizer) === -1) {
2047 requireFail.push(otherRecognizer);
2048 otherRecognizer.requireFailure(this);
2049 }
2050 return this;
2051 },
2052
2053 /**
2054 * drop the requireFailure link. it does not remove the link on the other recognizer.
2055 * @param {Recognizer} otherRecognizer
2056 * @returns {Recognizer} this
2057 */
2058 dropRequireFailure: function(otherRecognizer) {
2059 if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
2060 return this;
2061 }
2062
2063 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
2064 var index = inArray(this.requireFail, otherRecognizer);
2065 if (index > -1) {
2066 this.requireFail.splice(index, 1);
2067 }
2068 return this;
2069 },
2070
2071 /**
2072 * has require failures boolean
2073 * @returns {boolean}
2074 */
2075 hasRequireFailures: function() {
2076 return this.requireFail.length > 0;
2077 },
2078
2079 /**
2080 * if the recognizer can recognize simultaneous with an other recognizer
2081 * @param {Recognizer} otherRecognizer
2082 * @returns {Boolean}
2083 */
2084 canRecognizeWith: function(otherRecognizer) {
2085 return !!this.simultaneous[otherRecognizer.id];
2086 },
2087
2088 /**
2089 * You should use `tryEmit` instead of `emit` directly to check
2090 * that all the needed recognizers has failed before emitting.
2091 * @param {Object} input
2092 */
2093 emit: function(input) {
2094 var self = this;
2095 var state = this.state;
2096
2097 function emit(event) {
2098 self.manager.emit(event, input);
2099 }
2100
2101 // 'panstart' and 'panmove'
2102 if (state < STATE_ENDED) {
2103 emit(self.options.event + stateStr(state));
2104 }
2105
2106 emit(self.options.event); // simple 'eventName' events
2107
2108 if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
2109 emit(input.additionalEvent);
2110 }
2111
2112 // panend and pancancel
2113 if (state >= STATE_ENDED) {
2114 emit(self.options.event + stateStr(state));
2115 }
2116 },
2117
2118 /**
2119 * Check that all the require failure recognizers has failed,
2120 * if true, it emits a gesture event,
2121 * otherwise, setup the state to FAILED.
2122 * @param {Object} input
2123 */
2124 tryEmit: function(input) {
2125 if (this.canEmit()) {
2126 return this.emit(input);
2127 }
2128 // it's failing anyway
2129 this.state = STATE_FAILED;
2130 },
2131
2132 /**
2133 * can we emit?
2134 * @returns {boolean}
2135 */
2136 canEmit: function() {
2137 var i = 0;
2138 while (i < this.requireFail.length) {
2139 if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
2140 return false;
2141 }
2142 i++;
2143 }
2144 return true;
2145 },
2146
2147 /**
2148 * update the recognizer
2149 * @param {Object} inputData
2150 */
2151 recognize: function(inputData) {
2152 // make a new copy of the inputData
2153 // so we can change the inputData without messing up the other recognizers
2154 var inputDataClone = assign({}, inputData);
2155
2156 // is is enabled and allow recognizing?
2157 if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
2158 this.reset();
2159 this.state = STATE_FAILED;
2160 return;
2161 }
2162
2163 // reset when we've reached the end
2164 if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
2165 this.state = STATE_POSSIBLE;
2166 }
2167
2168 this.state = this.process(inputDataClone);
2169
2170 // the recognizer has recognized a gesture
2171 // so trigger an event
2172 if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
2173 this.tryEmit(inputDataClone);
2174 }
2175 },
2176
2177 /**
2178 * return the state of the recognizer
2179 * the actual recognizing happens in this method
2180 * @virtual
2181 * @param {Object} inputData
2182 * @returns {Const} STATE
2183 */
2184 process: function(inputData) { }, // jshint ignore:line
2185
2186 /**
2187 * return the preferred touch-action
2188 * @virtual
2189 * @returns {Array}
2190 */
2191 getTouchAction: function() { },
2192
2193 /**
2194 * called when the gesture isn't allowed to recognize
2195 * like when another is being recognized or it is disabled
2196 * @virtual
2197 */
2198 reset: function() { }
2199};
2200
2201/**
2202 * get a usable string, used as event postfix
2203 * @param {Const} state
2204 * @returns {String} state
2205 */
2206function stateStr(state) {
2207 if (state & STATE_CANCELLED) {
2208 return 'cancel';
2209 } else if (state & STATE_ENDED) {
2210 return 'end';
2211 } else if (state & STATE_CHANGED) {
2212 return 'move';
2213 } else if (state & STATE_BEGAN) {
2214 return 'start';
2215 }
2216 return '';
2217}
2218
2219/**
2220 * direction cons to string
2221 * @param {Const} direction
2222 * @returns {String}
2223 */
2224function directionStr(direction) {
2225 if (direction == DIRECTION_DOWN) {
2226 return 'down';
2227 } else if (direction == DIRECTION_UP) {
2228 return 'up';
2229 } else if (direction == DIRECTION_LEFT) {
2230 return 'left';
2231 } else if (direction == DIRECTION_RIGHT) {
2232 return 'right';
2233 }
2234 return '';
2235}
2236
2237/**
2238 * get a recognizer by name if it is bound to a manager
2239 * @param {Recognizer|String} otherRecognizer
2240 * @param {Recognizer} recognizer
2241 * @returns {Recognizer}
2242 */
2243function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
2244 var manager = recognizer.manager;
2245 if (manager) {
2246 return manager.get(otherRecognizer);
2247 }
2248 return otherRecognizer;
2249}
2250
2251/**
2252 * This recognizer is just used as a base for the simple attribute recognizers.
2253 * @constructor
2254 * @extends Recognizer
2255 */
2256function AttrRecognizer() {
2257 Recognizer.apply(this, arguments);
2258}
2259
2260inherit(AttrRecognizer, Recognizer, {
2261 /**
2262 * @namespace
2263 * @memberof AttrRecognizer
2264 */
2265 defaults: {
2266 /**
2267 * @type {Number}
2268 * @default 1
2269 */
2270 pointers: 1
2271 },
2272
2273 /**
2274 * Used to check if it the recognizer receives valid input, like input.distance > 10.
2275 * @memberof AttrRecognizer
2276 * @param {Object} input
2277 * @returns {Boolean} recognized
2278 */
2279 attrTest: function(input) {
2280 var optionPointers = this.options.pointers;
2281 return optionPointers === 0 || input.pointers.length === optionPointers;
2282 },
2283
2284 /**
2285 * Process the input and return the state for the recognizer
2286 * @memberof AttrRecognizer
2287 * @param {Object} input
2288 * @returns {*} State
2289 */
2290 process: function(input) {
2291 var state = this.state;
2292 var eventType = input.eventType;
2293
2294 var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
2295 var isValid = this.attrTest(input);
2296
2297 // on cancel input and we've recognized before, return STATE_CANCELLED
2298 if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
2299 return state | STATE_CANCELLED;
2300 } else if (isRecognized || isValid) {
2301 if (eventType & INPUT_END) {
2302 return state | STATE_ENDED;
2303 } else if (!(state & STATE_BEGAN)) {
2304 return STATE_BEGAN;
2305 }
2306 return state | STATE_CHANGED;
2307 }
2308 return STATE_FAILED;
2309 }
2310});
2311
2312/**
2313 * Pan
2314 * Recognized when the pointer is down and moved in the allowed direction.
2315 * @constructor
2316 * @extends AttrRecognizer
2317 */
2318function PanRecognizer() {
2319 AttrRecognizer.apply(this, arguments);
2320
2321 this.pX = null;
2322 this.pY = null;
2323}
2324
2325inherit(PanRecognizer, AttrRecognizer, {
2326 /**
2327 * @namespace
2328 * @memberof PanRecognizer
2329 */
2330 defaults: {
2331 event: 'pan',
2332 threshold: 10,
2333 pointers: 1,
2334 direction: DIRECTION_ALL
2335 },
2336
2337 getTouchAction: function() {
2338 var direction = this.options.direction;
2339 var actions = [];
2340 if (direction & DIRECTION_HORIZONTAL) {
2341 actions.push(TOUCH_ACTION_PAN_Y);
2342 }
2343 if (direction & DIRECTION_VERTICAL) {
2344 actions.push(TOUCH_ACTION_PAN_X);
2345 }
2346 return actions;
2347 },
2348
2349 directionTest: function(input) {
2350 var options = this.options;
2351 var hasMoved = true;
2352 var distance = input.distance;
2353 var direction = input.direction;
2354 var x = input.deltaX;
2355 var y = input.deltaY;
2356
2357 // lock to axis?
2358 if (!(direction & options.direction)) {
2359 if (options.direction & DIRECTION_HORIZONTAL) {
2360 direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
2361 hasMoved = x != this.pX;
2362 distance = Math.abs(input.deltaX);
2363 } else {
2364 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
2365 hasMoved = y != this.pY;
2366 distance = Math.abs(input.deltaY);
2367 }
2368 }
2369 input.direction = direction;
2370 return hasMoved && distance > options.threshold && direction & options.direction;
2371 },
2372
2373 attrTest: function(input) {
2374 return AttrRecognizer.prototype.attrTest.call(this, input) &&
2375 (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
2376 },
2377
2378 emit: function(input) {
2379
2380 this.pX = input.deltaX;
2381 this.pY = input.deltaY;
2382
2383 var direction = directionStr(input.direction);
2384
2385 if (direction) {
2386 input.additionalEvent = this.options.event + direction;
2387 }
2388 this._super.emit.call(this, input);
2389 }
2390});
2391
2392/**
2393 * Pinch
2394 * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
2395 * @constructor
2396 * @extends AttrRecognizer
2397 */
2398function PinchRecognizer() {
2399 AttrRecognizer.apply(this, arguments);
2400}
2401
2402inherit(PinchRecognizer, AttrRecognizer, {
2403 /**
2404 * @namespace
2405 * @memberof PinchRecognizer
2406 */
2407 defaults: {
2408 event: 'pinch',
2409 threshold: 0,
2410 pointers: 2
2411 },
2412
2413 getTouchAction: function() {
2414 return [TOUCH_ACTION_NONE];
2415 },
2416
2417 attrTest: function(input) {
2418 return this._super.attrTest.call(this, input) &&
2419 (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
2420 },
2421
2422 emit: function(input) {
2423 if (input.scale !== 1) {
2424 var inOut = input.scale < 1 ? 'in' : 'out';
2425 input.additionalEvent = this.options.event + inOut;
2426 }
2427 this._super.emit.call(this, input);
2428 }
2429});
2430
2431/**
2432 * Press
2433 * Recognized when the pointer is down for x ms without any movement.
2434 * @constructor
2435 * @extends Recognizer
2436 */
2437function PressRecognizer() {
2438 Recognizer.apply(this, arguments);
2439
2440 this._timer = null;
2441 this._input = null;
2442}
2443
2444inherit(PressRecognizer, Recognizer, {
2445 /**
2446 * @namespace
2447 * @memberof PressRecognizer
2448 */
2449 defaults: {
2450 event: 'press',
2451 pointers: 1,
2452 time: 251, // minimal time of the pointer to be pressed
2453 threshold: 9 // a minimal movement is ok, but keep it low
2454 },
2455
2456 getTouchAction: function() {
2457 return [TOUCH_ACTION_AUTO];
2458 },
2459
2460 process: function(input) {
2461 var options = this.options;
2462 var validPointers = input.pointers.length === options.pointers;
2463 var validMovement = input.distance < options.threshold;
2464 var validTime = input.deltaTime > options.time;
2465
2466 this._input = input;
2467
2468 // we only allow little movement
2469 // and we've reached an end event, so a tap is possible
2470 if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
2471 this.reset();
2472 } else if (input.eventType & INPUT_START) {
2473 this.reset();
2474 this._timer = setTimeoutContext(function() {
2475 this.state = STATE_RECOGNIZED;
2476 this.tryEmit();
2477 }, options.time, this);
2478 } else if (input.eventType & INPUT_END) {
2479 return STATE_RECOGNIZED;
2480 }
2481 return STATE_FAILED;
2482 },
2483
2484 reset: function() {
2485 clearTimeout(this._timer);
2486 },
2487
2488 emit: function(input) {
2489 if (this.state !== STATE_RECOGNIZED) {
2490 return;
2491 }
2492
2493 if (input && (input.eventType & INPUT_END)) {
2494 this.manager.emit(this.options.event + 'up', input);
2495 } else {
2496 this._input.timeStamp = now();
2497 this.manager.emit(this.options.event, this._input);
2498 }
2499 }
2500});
2501
2502/**
2503 * Rotate
2504 * Recognized when two or more pointer are moving in a circular motion.
2505 * @constructor
2506 * @extends AttrRecognizer
2507 */
2508function RotateRecognizer() {
2509 AttrRecognizer.apply(this, arguments);
2510}
2511
2512inherit(RotateRecognizer, AttrRecognizer, {
2513 /**
2514 * @namespace
2515 * @memberof RotateRecognizer
2516 */
2517 defaults: {
2518 event: 'rotate',
2519 threshold: 0,
2520 pointers: 2
2521 },
2522
2523 getTouchAction: function() {
2524 return [TOUCH_ACTION_NONE];
2525 },
2526
2527 attrTest: function(input) {
2528 return this._super.attrTest.call(this, input) &&
2529 (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
2530 }
2531});
2532
2533/**
2534 * Swipe
2535 * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
2536 * @constructor
2537 * @extends AttrRecognizer
2538 */
2539function SwipeRecognizer() {
2540 AttrRecognizer.apply(this, arguments);
2541}
2542
2543inherit(SwipeRecognizer, AttrRecognizer, {
2544 /**
2545 * @namespace
2546 * @memberof SwipeRecognizer
2547 */
2548 defaults: {
2549 event: 'swipe',
2550 threshold: 10,
2551 velocity: 0.3,
2552 direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
2553 pointers: 1
2554 },
2555
2556 getTouchAction: function() {
2557 return PanRecognizer.prototype.getTouchAction.call(this);
2558 },
2559
2560 attrTest: function(input) {
2561 var direction = this.options.direction;
2562 var velocity;
2563
2564 if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
2565 velocity = input.overallVelocity;
2566 } else if (direction & DIRECTION_HORIZONTAL) {
2567 velocity = input.overallVelocityX;
2568 } else if (direction & DIRECTION_VERTICAL) {
2569 velocity = input.overallVelocityY;
2570 }
2571
2572 return this._super.attrTest.call(this, input) &&
2573 direction & input.offsetDirection &&
2574 input.distance > this.options.threshold &&
2575 input.maxPointers == this.options.pointers &&
2576 abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
2577 },
2578
2579 emit: function(input) {
2580 var direction = directionStr(input.offsetDirection);
2581 if (direction) {
2582 this.manager.emit(this.options.event + direction, input);
2583 }
2584
2585 this.manager.emit(this.options.event, input);
2586 }
2587});
2588
2589/**
2590 * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
2591 * between the given interval and position. The delay option can be used to recognize multi-taps without firing
2592 * a single tap.
2593 *
2594 * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
2595 * multi-taps being recognized.
2596 * @constructor
2597 * @extends Recognizer
2598 */
2599function TapRecognizer() {
2600 Recognizer.apply(this, arguments);
2601
2602 // previous time and center,
2603 // used for tap counting
2604 this.pTime = false;
2605 this.pCenter = false;
2606
2607 this._timer = null;
2608 this._input = null;
2609 this.count = 0;
2610}
2611
2612inherit(TapRecognizer, Recognizer, {
2613 /**
2614 * @namespace
2615 * @memberof PinchRecognizer
2616 */
2617 defaults: {
2618 event: 'tap',
2619 pointers: 1,
2620 taps: 1,
2621 interval: 300, // max time between the multi-tap taps
2622 time: 250, // max time of the pointer to be down (like finger on the screen)
2623 threshold: 9, // a minimal movement is ok, but keep it low
2624 posThreshold: 10 // a multi-tap can be a bit off the initial position
2625 },
2626
2627 getTouchAction: function() {
2628 return [TOUCH_ACTION_MANIPULATION];
2629 },
2630
2631 process: function(input) {
2632 var options = this.options;
2633
2634 var validPointers = input.pointers.length === options.pointers;
2635 var validMovement = input.distance < options.threshold;
2636 var validTouchTime = input.deltaTime < options.time;
2637
2638 this.reset();
2639
2640 if ((input.eventType & INPUT_START) && (this.count === 0)) {
2641 return this.failTimeout();
2642 }
2643
2644 // we only allow little movement
2645 // and we've reached an end event, so a tap is possible
2646 if (validMovement && validTouchTime && validPointers) {
2647 if (input.eventType != INPUT_END) {
2648 return this.failTimeout();
2649 }
2650
2651 var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
2652 var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
2653
2654 this.pTime = input.timeStamp;
2655 this.pCenter = input.center;
2656
2657 if (!validMultiTap || !validInterval) {
2658 this.count = 1;
2659 } else {
2660 this.count += 1;
2661 }
2662
2663 this._input = input;
2664
2665 // if tap count matches we have recognized it,
2666 // else it has began recognizing...
2667 var tapCount = this.count % options.taps;
2668 if (tapCount === 0) {
2669 // no failing requirements, immediately trigger the tap event
2670 // or wait as long as the multitap interval to trigger
2671 if (!this.hasRequireFailures()) {
2672 return STATE_RECOGNIZED;
2673 } else {
2674 this._timer = setTimeoutContext(function() {
2675 this.state = STATE_RECOGNIZED;
2676 this.tryEmit();
2677 }, options.interval, this);
2678 return STATE_BEGAN;
2679 }
2680 }
2681 }
2682 return STATE_FAILED;
2683 },
2684
2685 failTimeout: function() {
2686 this._timer = setTimeoutContext(function() {
2687 this.state = STATE_FAILED;
2688 }, this.options.interval, this);
2689 return STATE_FAILED;
2690 },
2691
2692 reset: function() {
2693 clearTimeout(this._timer);
2694 },
2695
2696 emit: function() {
2697 if (this.state == STATE_RECOGNIZED) {
2698 this._input.tapCount = this.count;
2699 this.manager.emit(this.options.event, this._input);
2700 }
2701 }
2702});
2703
2704/**
2705 * Simple way to create a manager with a default set of recognizers.
2706 * @param {HTMLElement} element
2707 * @param {Object} [options]
2708 * @constructor
2709 */
2710function Hammer(element, options) {
2711 options = options || {};
2712 options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);
2713 return new Manager(element, options);
2714}
2715
2716/**
2717 * @const {string}
2718 */
2719Hammer.VERSION = '2.0.8';
2720
2721/**
2722 * default settings
2723 * @namespace
2724 */
2725Hammer.defaults = {
2726 /**
2727 * set if DOM events are being triggered.
2728 * But this is slower and unused by simple implementations, so disabled by default.
2729 * @type {Boolean}
2730 * @default false
2731 */
2732 domEvents: false,
2733
2734 /**
2735 * The value for the touchAction property/fallback.
2736 * When set to `compute` it will magically set the correct value based on the added recognizers.
2737 * @type {String}
2738 * @default compute
2739 */
2740 touchAction: TOUCH_ACTION_COMPUTE,
2741
2742 /**
2743 * @type {Boolean}
2744 * @default true
2745 */
2746 enable: true,
2747
2748 /**
2749 * EXPERIMENTAL FEATURE -- can be removed/changed
2750 * Change the parent input target element.
2751 * If Null, then it is being set the to main element.
2752 * @type {Null|EventTarget}
2753 * @default null
2754 */
2755 inputTarget: null,
2756
2757 /**
2758 * force an input class
2759 * @type {Null|Function}
2760 * @default null
2761 */
2762 inputClass: null,
2763
2764 /**
2765 * Default recognizer setup when calling `Hammer()`
2766 * When creating a new Manager these will be skipped.
2767 * @type {Array}
2768 */
2769 preset: [
2770 // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
2771 [RotateRecognizer, {enable: false}],
2772 [PinchRecognizer, {enable: false}, ['rotate']],
2773 [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
2774 [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
2775 [TapRecognizer],
2776 [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
2777 [PressRecognizer]
2778 ],
2779
2780 /**
2781 * Some CSS properties can be used to improve the working of Hammer.
2782 * Add them to this method and they will be set when creating a new Manager.
2783 * @namespace
2784 */
2785 cssProps: {
2786 /**
2787 * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
2788 * @type {String}
2789 * @default 'none'
2790 */
2791 userSelect: 'none',
2792
2793 /**
2794 * Disable the Windows Phone grippers when pressing an element.
2795 * @type {String}
2796 * @default 'none'
2797 */
2798 touchSelect: 'none',
2799
2800 /**
2801 * Disables the default callout shown when you touch and hold a touch target.
2802 * On iOS, when you touch and hold a touch target such as a link, Safari displays
2803 * a callout containing information about the link. This property allows you to disable that callout.
2804 * @type {String}
2805 * @default 'none'
2806 */
2807 touchCallout: 'none',
2808
2809 /**
2810 * Specifies whether zooming is enabled. Used by IE10>
2811 * @type {String}
2812 * @default 'none'
2813 */
2814 contentZooming: 'none',
2815
2816 /**
2817 * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
2818 * @type {String}
2819 * @default 'none'
2820 */
2821 userDrag: 'none',
2822
2823 /**
2824 * Overrides the highlight color shown when the user taps a link or a JavaScript
2825 * clickable element in iOS. This property obeys the alpha value, if specified.
2826 * @type {String}
2827 * @default 'rgba(0,0,0,0)'
2828 */
2829 tapHighlightColor: 'rgba(0,0,0,0)'
2830 }
2831};
2832
2833var STOP = 1;
2834var FORCED_STOP = 2;
2835
2836/**
2837 * Manager
2838 * @param {HTMLElement} element
2839 * @param {Object} [options]
2840 * @constructor
2841 */
2842function Manager(element, options) {
2843 this.options = assign({}, Hammer.defaults, options || {});
2844
2845 this.options.inputTarget = this.options.inputTarget || element;
2846
2847 this.handlers = {};
2848 this.session = {};
2849 this.recognizers = [];
2850 this.oldCssProps = {};
2851
2852 this.element = element;
2853 this.input = createInputInstance(this);
2854 this.touchAction = new TouchAction(this, this.options.touchAction);
2855
2856 toggleCssProps(this, true);
2857
2858 each(this.options.recognizers, function(item) {
2859 var recognizer = this.add(new (item[0])(item[1]));
2860 item[2] && recognizer.recognizeWith(item[2]);
2861 item[3] && recognizer.requireFailure(item[3]);
2862 }, this);
2863}
2864
2865Manager.prototype = {
2866 /**
2867 * set options
2868 * @param {Object} options
2869 * @returns {Manager}
2870 */
2871 set: function(options) {
2872 assign(this.options, options);
2873
2874 // Options that need a little more setup
2875 if (options.touchAction) {
2876 this.touchAction.update();
2877 }
2878 if (options.inputTarget) {
2879 // Clean up existing event listeners and reinitialize
2880 this.input.destroy();
2881 this.input.target = options.inputTarget;
2882 this.input.init();
2883 }
2884 return this;
2885 },
2886
2887 /**
2888 * stop recognizing for this session.
2889 * This session will be discarded, when a new [input]start event is fired.
2890 * When forced, the recognizer cycle is stopped immediately.
2891 * @param {Boolean} [force]
2892 */
2893 stop: function(force) {
2894 this.session.stopped = force ? FORCED_STOP : STOP;
2895 },
2896
2897 /**
2898 * run the recognizers!
2899 * called by the inputHandler function on every movement of the pointers (touches)
2900 * it walks through all the recognizers and tries to detect the gesture that is being made
2901 * @param {Object} inputData
2902 */
2903 recognize: function(inputData) {
2904 var session = this.session;
2905 if (session.stopped) {
2906 return;
2907 }
2908
2909 // run the touch-action polyfill
2910 this.touchAction.preventDefaults(inputData);
2911
2912 var recognizer;
2913 var recognizers = this.recognizers;
2914
2915 // this holds the recognizer that is being recognized.
2916 // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
2917 // if no recognizer is detecting a thing, it is set to `null`
2918 var curRecognizer = session.curRecognizer;
2919
2920 // reset when the last recognizer is recognized
2921 // or when we're in a new session
2922 if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
2923 curRecognizer = session.curRecognizer = null;
2924 }
2925
2926 var i = 0;
2927 while (i < recognizers.length) {
2928 recognizer = recognizers[i];
2929
2930 // find out if we are allowed try to recognize the input for this one.
2931 // 1. allow if the session is NOT forced stopped (see the .stop() method)
2932 // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
2933 // that is being recognized.
2934 // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
2935 // this can be setup with the `recognizeWith()` method on the recognizer.
2936 if (session.stopped !== FORCED_STOP && ( // 1
2937 !curRecognizer || recognizer == curRecognizer || // 2
2938 recognizer.canRecognizeWith(curRecognizer))) { // 3
2939 recognizer.recognize(inputData);
2940 } else {
2941 recognizer.reset();
2942 }
2943
2944 // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
2945 // current active recognizer. but only if we don't already have an active recognizer
2946 if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
2947 curRecognizer = session.curRecognizer = recognizer;
2948 }
2949 i++;
2950 }
2951 },
2952
2953 /**
2954 * get a recognizer by its event name.
2955 * @param {Recognizer|String} recognizer
2956 * @returns {Recognizer|Null}
2957 */
2958 get: function(recognizer) {
2959 if (recognizer instanceof Recognizer) {
2960 return recognizer;
2961 }
2962
2963 var recognizers = this.recognizers;
2964 for (var i = 0; i < recognizers.length; i++) {
2965 if (recognizers[i].options.event == recognizer) {
2966 return recognizers[i];
2967 }
2968 }
2969 return null;
2970 },
2971
2972 /**
2973 * add a recognizer to the manager
2974 * existing recognizers with the same event name will be removed
2975 * @param {Recognizer} recognizer
2976 * @returns {Recognizer|Manager}
2977 */
2978 add: function(recognizer) {
2979 if (invokeArrayArg(recognizer, 'add', this)) {
2980 return this;
2981 }
2982
2983 // remove existing
2984 var existing = this.get(recognizer.options.event);
2985 if (existing) {
2986 this.remove(existing);
2987 }
2988
2989 this.recognizers.push(recognizer);
2990 recognizer.manager = this;
2991
2992 this.touchAction.update();
2993 return recognizer;
2994 },
2995
2996 /**
2997 * remove a recognizer by name or instance
2998 * @param {Recognizer|String} recognizer
2999 * @returns {Manager}
3000 */
3001 remove: function(recognizer) {
3002 if (invokeArrayArg(recognizer, 'remove', this)) {
3003 return this;
3004 }
3005
3006 recognizer = this.get(recognizer);
3007
3008 // let's make sure this recognizer exists
3009 if (recognizer) {
3010 var recognizers = this.recognizers;
3011 var index = inArray(recognizers, recognizer);
3012
3013 if (index !== -1) {
3014 recognizers.splice(index, 1);
3015 this.touchAction.update();
3016 }
3017 }
3018
3019 return this;
3020 },
3021
3022 /**
3023 * bind event
3024 * @param {String} events
3025 * @param {Function} handler
3026 * @returns {EventEmitter} this
3027 */
3028 on: function(events, handler) {
3029 if (events === undefined) {
3030 return;
3031 }
3032 if (handler === undefined) {
3033 return;
3034 }
3035
3036 var handlers = this.handlers;
3037 each(splitStr(events), function(event) {
3038 handlers[event] = handlers[event] || [];
3039 handlers[event].push(handler);
3040 });
3041 return this;
3042 },
3043
3044 /**
3045 * unbind event, leave emit blank to remove all handlers
3046 * @param {String} events
3047 * @param {Function} [handler]
3048 * @returns {EventEmitter} this
3049 */
3050 off: function(events, handler) {
3051 if (events === undefined) {
3052 return;
3053 }
3054
3055 var handlers = this.handlers;
3056 each(splitStr(events), function(event) {
3057 if (!handler) {
3058 delete handlers[event];
3059 } else {
3060 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
3061 }
3062 });
3063 return this;
3064 },
3065
3066 /**
3067 * emit event to the listeners
3068 * @param {String} event
3069 * @param {Object} data
3070 */
3071 emit: function(event, data) {
3072 // we also want to trigger dom events
3073 if (this.options.domEvents) {
3074 triggerDomEvent(event, data);
3075 }
3076
3077 // no handlers, so skip it all
3078 var handlers = this.handlers[event] && this.handlers[event].slice();
3079 if (!handlers || !handlers.length) {
3080 return;
3081 }
3082
3083 data.type = event;
3084 data.preventDefault = function() {
3085 data.srcEvent.preventDefault();
3086 };
3087
3088 var i = 0;
3089 while (i < handlers.length) {
3090 handlers[i](data);
3091 i++;
3092 }
3093 },
3094
3095 /**
3096 * destroy the manager and unbinds all events
3097 * it doesn't unbind dom events, that is the user own responsibility
3098 */
3099 destroy: function() {
3100 this.element && toggleCssProps(this, false);
3101
3102 this.handlers = {};
3103 this.session = {};
3104 this.input.destroy();
3105 this.element = null;
3106 }
3107};
3108
3109/**
3110 * add/remove the css properties as defined in manager.options.cssProps
3111 * @param {Manager} manager
3112 * @param {Boolean} add
3113 */
3114function toggleCssProps(manager, add) {
3115 var element = manager.element;
3116 if (!element.style) {
3117 return;
3118 }
3119 var prop;
3120 each(manager.options.cssProps, function(value, name) {
3121 prop = prefixed(element.style, name);
3122 if (add) {
3123 manager.oldCssProps[prop] = element.style[prop];
3124 element.style[prop] = value;
3125 } else {
3126 element.style[prop] = manager.oldCssProps[prop] || '';
3127 }
3128 });
3129 if (!add) {
3130 manager.oldCssProps = {};
3131 }
3132}
3133
3134/**
3135 * trigger dom event
3136 * @param {String} event
3137 * @param {Object} data
3138 */
3139function triggerDomEvent(event, data) {
3140 var gestureEvent = document.createEvent('Event');
3141 gestureEvent.initEvent(event, true, true);
3142 gestureEvent.gesture = data;
3143 data.target.dispatchEvent(gestureEvent);
3144}
3145
3146assign(Hammer, {
3147 INPUT_START: INPUT_START,
3148 INPUT_MOVE: INPUT_MOVE,
3149 INPUT_END: INPUT_END,
3150 INPUT_CANCEL: INPUT_CANCEL,
3151
3152 STATE_POSSIBLE: STATE_POSSIBLE,
3153 STATE_BEGAN: STATE_BEGAN,
3154 STATE_CHANGED: STATE_CHANGED,
3155 STATE_ENDED: STATE_ENDED,
3156 STATE_RECOGNIZED: STATE_RECOGNIZED,
3157 STATE_CANCELLED: STATE_CANCELLED,
3158 STATE_FAILED: STATE_FAILED,
3159
3160 DIRECTION_NONE: DIRECTION_NONE,
3161 DIRECTION_LEFT: DIRECTION_LEFT,
3162 DIRECTION_RIGHT: DIRECTION_RIGHT,
3163 DIRECTION_UP: DIRECTION_UP,
3164 DIRECTION_DOWN: DIRECTION_DOWN,
3165 DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
3166 DIRECTION_VERTICAL: DIRECTION_VERTICAL,
3167 DIRECTION_ALL: DIRECTION_ALL,
3168
3169 Manager: Manager,
3170 Input: Input,
3171 TouchAction: TouchAction,
3172
3173 TouchInput: TouchInput,
3174 MouseInput: MouseInput,
3175 PointerEventInput: PointerEventInput,
3176 TouchMouseInput: TouchMouseInput,
3177 SingleTouchInput: SingleTouchInput,
3178
3179 Recognizer: Recognizer,
3180 AttrRecognizer: AttrRecognizer,
3181 Tap: TapRecognizer,
3182 Pan: PanRecognizer,
3183 Swipe: SwipeRecognizer,
3184 Pinch: PinchRecognizer,
3185 Rotate: RotateRecognizer,
3186 Press: PressRecognizer,
3187
3188 on: addEventListeners,
3189 off: removeEventListeners,
3190 each: each,
3191 merge: merge,
3192 extend: extend,
3193 assign: assign,
3194 inherit: inherit,
3195 bindFn: bindFn,
3196 prefixed: prefixed
3197});
3198
3199// this prevents errors when Hammer is loaded in the presence of an AMD
3200// style loader but by script tag, not by the loader.
3201var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
3202freeGlobal.Hammer = Hammer;
3203
3204if (typeof define === 'function' && define.amd) {
3205 define(function() {
3206 return Hammer;
3207 });
3208} else if (typeof module != 'undefined' && module.exports) {
3209 module.exports = Hammer;
3210} else {
3211 window[exportName] = Hammer;
3212}
3213
3214})(window, document, 'Hammer');
3215
3216+ function($) {
3217 "use strict";
3218
3219 var defaults;
3220
3221 $.modal = function(params, onOpen) {
3222 params = $.extend({}, defaults, params);
3223
3224
3225 var buttons = params.buttons;
3226
3227 var buttonsHtml = buttons.map(function(d, i) {
3228 return '<a href="javascript:;" class="weui-dialog__btn ' + (d.className || "") + '">' + d.text + '</a>';
3229 }).join("");
3230
3231 var tpl = '<div class="weui-dialog">' +
3232 '<div class="weui-dialog__hd"><strong class="weui-dialog__title">' + params.title + '</strong></div>' +
3233 ( params.text ? '<div class="weui-dialog__bd">'+params.text+'</div>' : '')+
3234 '<div class="weui-dialog__ft">' + buttonsHtml + '</div>' +
3235 '</div>';
3236
3237 var dialog = $.openModal(tpl, onOpen);
3238
3239 dialog.find(".weui-dialog__btn").each(function(i, e) {
3240 var el = $(e);
3241 el.click(function() {
3242 //先关闭对话框,再调用回调函数
3243 if(params.autoClose) $.closeModal();
3244
3245 if(buttons[i].onClick) {
3246 buttons[i].onClick.call(dialog);
3247 }
3248 });
3249 });
3250
3251 return dialog;
3252 };
3253
3254 $.openModal = function(tpl, onOpen) {
3255 var mask = $("<div class='weui-mask'></div>").appendTo(document.body);
3256 mask.show();
3257
3258 var dialog = $(tpl).appendTo(document.body);
3259
3260 if (onOpen) {
3261 dialog.transitionEnd(function () {
3262 onOpen.call(dialog);
3263 });
3264 }
3265
3266 dialog.show();
3267 mask.addClass("weui-mask--visible");
3268 dialog.addClass("weui-dialog--visible");
3269
3270
3271 return dialog;
3272 }
3273
3274 $.closeModal = function() {
3275 $(".weui-mask--visible").removeClass("weui-mask--visible").transitionEnd(function() {
3276 $(this).remove();
3277 });
3278 $(".weui-dialog--visible").removeClass("weui-dialog--visible").transitionEnd(function() {
3279 $(this).remove();
3280 });
3281 };
3282
3283 $.alert = function(text, title, onOK) {
3284 var config;
3285 if (typeof text === 'object') {
3286 config = text;
3287 } else {
3288 if (typeof title === 'function') {
3289 onOK = arguments[1];
3290 title = undefined;
3291 }
3292
3293 config = {
3294 text: text,
3295 title: title,
3296 onOK: onOK
3297 }
3298 }
3299 return $.modal({
3300 text: config.text,
3301 title: config.title,
3302 buttons: [{
3303 text: defaults.buttonOK,
3304 className: "primary",
3305 onClick: config.onOK
3306 }]
3307 });
3308 }
3309
3310 $.confirm = function(text, title, onOK, onCancel) {
3311 var config;
3312 if (typeof text === 'object') {
3313 config = text
3314 } else {
3315 if (typeof title === 'function') {
3316 onCancel = arguments[2];
3317 onOK = arguments[1];
3318 title = undefined;
3319 }
3320
3321 config = {
3322 text: text,
3323 title: title,
3324 onOK: onOK,
3325 onCancel: onCancel
3326 }
3327 }
3328 return $.modal({
3329 text: config.text,
3330 title: config.title,
3331 buttons: [
3332 {
3333 text: defaults.buttonCancel,
3334 className: "default",
3335 onClick: config.onCancel
3336 },
3337 {
3338 text: defaults.buttonOK,
3339 className: "primary",
3340 onClick: config.onOK
3341 }]
3342 });
3343 };
3344
3345 //如果参数过多,建议通过 config 对象进行配置,而不是传入多个参数。
3346 $.prompt = function(text, title, onOK, onCancel, input) {
3347 var config;
3348 if (typeof text === 'object') {
3349 config = text;
3350 } else {
3351 if (typeof title === 'function') {
3352 input = arguments[3];
3353 onCancel = arguments[2];
3354 onOK = arguments[1];
3355 title = undefined;
3356 }
3357 config = {
3358 text: text,
3359 title: title,
3360 input: input,
3361 onOK: onOK,
3362 onCancel: onCancel,
3363 empty: false //allow empty
3364 }
3365 }
3366
3367 var modal = $.modal({
3368 text: '<p class="weui-prompt-text">'+(config.text || '')+'</p><input type="text" class="weui-input weui-prompt-input" id="weui-prompt-input" value="' + (config.input || '') + '" />',
3369 title: config.title,
3370 autoClose: false,
3371 buttons: [
3372 {
3373 text: defaults.buttonCancel,
3374 className: "default",
3375 onClick: function () {
3376 $.closeModal();
3377 config.onCancel && config.onCancel.call(modal);
3378 }
3379 },
3380 {
3381 text: defaults.buttonOK,
3382 className: "primary",
3383 onClick: function() {
3384 var input = $("#weui-prompt-input").val();
3385 if (!config.empty && (input === "" || input === null)) {
3386 modal.find('.weui-prompt-input').focus()[0].select();
3387 return false;
3388 }
3389 $.closeModal();
3390 config.onOK && config.onOK.call(modal, input);
3391 }
3392 }]
3393 }, function () {
3394 this.find('.weui-prompt-input').focus()[0].select();
3395 });
3396
3397 return modal;
3398 };
3399
3400 //如果参数过多,建议通过 config 对象进行配置,而不是传入多个参数。
3401 $.login = function(text, title, onOK, onCancel, username, password) {
3402 var config;
3403 if (typeof text === 'object') {
3404 config = text;
3405 } else {
3406 if (typeof title === 'function') {
3407 password = arguments[4];
3408 username = arguments[3];
3409 onCancel = arguments[2];
3410 onOK = arguments[1];
3411 title = undefined;
3412 }
3413 config = {
3414 text: text,
3415 title: title,
3416 username: username,
3417 password: password,
3418 onOK: onOK,
3419 onCancel: onCancel
3420 }
3421 }
3422
3423 var modal = $.modal({
3424 text: '<p class="weui-prompt-text">'+(config.text || '')+'</p>' +
3425 '<input type="text" class="weui-input weui-prompt-input" id="weui-prompt-username" value="' + (config.username || '') + '" placeholder="输入用户名" />' +
3426 '<input type="password" class="weui-input weui-prompt-input" id="weui-prompt-password" value="' + (config.password || '') + '" placeholder="输入密码" />',
3427 title: config.title,
3428 autoClose: false,
3429 buttons: [
3430 {
3431 text: defaults.buttonCancel,
3432 className: "default",
3433 onClick: function () {
3434 $.closeModal();
3435 config.onCancel && config.onCancel.call(modal);
3436 }
3437 }, {
3438 text: defaults.buttonOK,
3439 className: "primary",
3440 onClick: function() {
3441 var username = $("#weui-prompt-username").val();
3442 var password = $("#weui-prompt-password").val();
3443 if (!config.empty && (username === "" || username === null)) {
3444 modal.find('#weui-prompt-username').focus()[0].select();
3445 return false;
3446 }
3447 if (!config.empty && (password === "" || password === null)) {
3448 modal.find('#weui-prompt-password').focus()[0].select();
3449 return false;
3450 }
3451 $.closeModal();
3452 config.onOK && config.onOK.call(modal, username, password);
3453 }
3454 }]
3455 }, function () {
3456 this.find('#weui-prompt-username').focus()[0].select();
3457 });
3458
3459 return modal;
3460 };
3461
3462 defaults = $.modal.prototype.defaults = {
3463 title: "提示",
3464 text: undefined,
3465 buttonOK: "确定",
3466 buttonCancel: "取消",
3467 buttons: [{
3468 text: "确定",
3469 className: "primary"
3470 }],
3471 autoClose: true //点击按钮自动关闭对话框,如果你不希望点击按钮就关闭对话框,可以把这个设置为false
3472 };
3473
3474}($);
3475
3476+ function($) {
3477 "use strict";
3478
3479 var defaults;
3480
3481 var show = function(html, className) {
3482 className = className || "";
3483 var mask = $("<div class='weui-mask_transparent'></div>").appendTo(document.body);
3484
3485 var tpl = '<div class="weui-toast ' + className + '">' + html + '</div>';
3486 var dialog = $(tpl).appendTo(document.body);
3487
3488 dialog.addClass("weui-toast--visible");
3489 dialog.show();
3490 };
3491
3492 var hide = function(callback) {
3493 $(".weui-mask_transparent").remove();
3494 var done = false;
3495 var $el = $(".weui-toast--visible").removeClass("weui-toast--visible").transitionEnd(function() {
3496 var $this = $(this);
3497 $this.remove();
3498 callback && callback();
3499 done = true
3500 });
3501
3502 setTimeout(function () {
3503 if (!done) {
3504 $el.remove()
3505 callback && callback();
3506 }
3507 }, 1000)
3508 }
3509
3510 $.toast = function(text, style, callback) {
3511 if(typeof style === "function") {
3512 callback = style;
3513 }
3514 var className, iconClassName = 'weui-icon-success-no-circle';
3515 var duration = toastDefaults.duration;
3516 if(style == "cancel") {
3517 className = "weui-toast_cancel";
3518 iconClassName = 'weui-icon-cancel'
3519 } else if(style == "forbidden") {
3520 className = "weui-toast--forbidden";
3521 iconClassName = 'weui-icon-warn'
3522 } else if(style == "text") {
3523 className = "weui-toast--text";
3524 } else if(typeof style === typeof 1) {
3525 duration = style
3526 }
3527 show('<i class="' + iconClassName + ' weui-icon_toast"></i><p class="weui-toast_content">' + (text || "已经完成") + '</p>', className);
3528
3529 setTimeout(function() {
3530 hide(callback);
3531 }, duration);
3532 }
3533
3534 $.showLoading = function(text) {
3535 var html = '<div class="weui_loading">';
3536 html += '<i class="weui-loading weui-icon_toast"></i>';
3537 html += '</div>';
3538 html += '<p class="weui-toast_content">' + (text || "数据加载中") + '</p>';
3539 show(html, 'weui_loading_toast');
3540 }
3541
3542 $.hideLoading = function() {
3543 hide();
3544 }
3545
3546 var toastDefaults = $.toast.prototype.defaults = {
3547 duration: 2500
3548 }
3549
3550}($);
3551
3552+ function($) {
3553 "use strict";
3554
3555 var defaults;
3556
3557 var show = function(params) {
3558
3559 var mask = $("<div class='weui-mask weui-actions_mask'></div>").appendTo(document.body);
3560
3561 var actions = params.actions || [];
3562
3563 var actionsHtml = actions.map(function(d, i) {
3564 return '<div class="weui-actionsheet__cell ' + (d.className || "") + '">' + d.text + '</div>';
3565 }).join("");
3566
3567 var titleHtml = "";
3568
3569 if (params.title) {
3570 titleHtml = '<div class="weui-actionsheet__title"><p class="weui-actionsheet__title-text">' + params.title + '</p></div>';
3571 }
3572
3573 var tpl = '<div class="weui-actionsheet " id="weui-actionsheet">'+
3574 titleHtml +
3575 '<div class="weui-actionsheet__menu">'+
3576 actionsHtml +
3577 '</div>'+
3578 '<div class="weui-actionsheet__action">'+
3579 '<div class="weui-actionsheet__cell weui-actionsheet_cancel">取消</div>'+
3580 '</div>'+
3581 '</div>';
3582 var dialog = $(tpl).appendTo(document.body);
3583
3584 dialog.find(".weui-actionsheet__menu .weui-actionsheet__cell, .weui-actionsheet__action .weui-actionsheet__cell").each(function(i, e) {
3585 $(e).click(function() {
3586 $.closeActions();
3587 params.onClose && params.onClose();
3588 if(actions[i] && actions[i].onClick) {
3589 actions[i].onClick();
3590 }
3591 })
3592 });
3593
3594 mask.show();
3595 dialog.show();
3596 mask.addClass("weui-mask--visible");
3597 dialog.addClass("weui-actionsheet_toggle");
3598 };
3599
3600 var hide = function() {
3601 $(".weui-mask").removeClass("weui-mask--visible").transitionEnd(function() {
3602 $(this).remove();
3603 });
3604 $(".weui-actionsheet").removeClass("weui-actionsheet_toggle").transitionEnd(function() {
3605 $(this).remove();
3606 });
3607 }
3608
3609 $.actions = function(params) {
3610 params = $.extend({}, defaults, params);
3611 show(params);
3612 }
3613
3614 $.closeActions = function() {
3615 hide();
3616 }
3617
3618 $(document).on("click", ".weui-actions_mask", function() {
3619 $.closeActions();
3620 });
3621
3622 var defaults = $.actions.prototype.defaults = {
3623 title: undefined,
3624 onClose: undefined,
3625 /*actions: [{
3626 text: "菜单",
3627 className: "color-danger",
3628 onClick: function() {
3629 console.log(1);
3630 }
3631 },{
3632 text: "菜单2",
3633 className: "color-success",
3634 onClick: function() {
3635 console.log(2);
3636 }
3637 }]*/
3638 }
3639
3640}($);
3641
3642/* ===============================================================================
3643************ Pull to refreh ************
3644=============================================================================== */
3645/* global $:true */
3646
3647+function ($) {
3648 "use strict";
3649
3650 var PTR = function(el, opt) {
3651 if (typeof opt === typeof function () {}) {
3652 opt = {
3653 onRefresh: opt
3654 }
3655 }
3656 if (typeof opt === typeof 'a') {
3657 opt = undefined
3658 }
3659 this.opt = $.extend(PTR.defaults, opt || {});
3660 this.container = $(el);
3661 this.attachEvents();
3662 }
3663
3664 PTR.defaults = {
3665 distance: 50,
3666 onRefresh: undefined,
3667 onPull: undefined
3668 }
3669
3670 PTR.prototype.touchStart = function(e) {
3671 if(this.container.hasClass("refreshing")) return;
3672 var p = $.getTouchPosition(e);
3673 this.start = p;
3674 this.diffX = this.diffY = 0;
3675 };
3676
3677 PTR.prototype.touchMove= function(e) {
3678 if(this.container.hasClass("refreshing")) return;
3679 if(!this.start) return false;
3680 if(this.container.scrollTop() > 0) return;
3681 var p = $.getTouchPosition(e);
3682 this.diffX = p.x - this.start.x;
3683 this.diffY = p.y - this.start.y;
3684 if (Math.abs(this.diffX) > Math.abs(this.diffY)) return true; // 说明是左右方向的拖动
3685 if(this.diffY < 0) return;
3686 this.container.addClass("touching");
3687 e.preventDefault();
3688 e.stopPropagation();
3689 this.diffY = Math.pow(this.diffY, 0.75);
3690 this.container.css("transform", "translate3d(0, "+this.diffY+"px, 0)");
3691 this.triggerPull(this.diffY)
3692 };
3693 PTR.prototype.touchEnd = function() {
3694 this.start = false;
3695 if(this.diffY <= 0 || this.container.hasClass("refreshing")) return;
3696 this.container.removeClass("touching");
3697 this.container.removeClass("pull-down pull-up");
3698 this.container.css("transform", "");
3699 if(Math.abs(this.diffY) <= this.opt.distance) {
3700 } else {
3701 this.triggerPullToRefresh();
3702 }
3703 };
3704
3705 PTR.prototype.triggerPullToRefresh = function() {
3706 this.triggerPull(this.opt.distance)
3707 this.container.removeClass('pull-up').addClass("refreshing");
3708 if (this.opt.onRefresh) {
3709 this.opt.onRefresh.call(this)
3710 }
3711 this.container.trigger("pull-to-refresh");
3712 }
3713
3714 PTR.prototype.triggerPull = function(diffY) {
3715
3716 if(diffY < this.opt.distance) {
3717 this.container.removeClass("pull-up").addClass("pull-down");
3718 } else {
3719 this.container.removeClass("pull-down").addClass("pull-up");
3720 }
3721
3722 if (this.opt.onPull) {
3723 this.opt.onPull.call(this, Math.floor(diffY / this.opt.distance * 100))
3724 }
3725 this.container.trigger("pull");
3726 }
3727
3728 PTR.prototype.pullToRefreshDone = function() {
3729 this.container.removeClass("refreshing");
3730 }
3731
3732 PTR.prototype.attachEvents = function() {
3733 var el = this.container;
3734 el.addClass("weui-pull-to-refresh");
3735 el.on($.touchEvents.start, $.proxy(this.touchStart, this));
3736 el.on($.touchEvents.move, $.proxy(this.touchMove, this));
3737 el.on($.touchEvents.end, $.proxy(this.touchEnd, this));
3738 };
3739
3740 var pullToRefreshDone = function(el) {
3741 $(el).removeClass("refreshing");
3742 }
3743
3744 $.fn.pullToRefresh = function(opt) {
3745 return this.each(function() {
3746 var $this = $(this)
3747 var ptr = $this.data('ptr')
3748 if (!ptr) $this.data('ptr', ptr = new PTR(this, opt))
3749 if (typeof opt === typeof 'a') {
3750 ptr[opt].call(ptr)
3751 }
3752 });
3753 }
3754
3755 $.fn.pullToRefreshDone = function() {
3756 return this.each(function() {
3757 pullToRefreshDone(this);
3758 });
3759 }
3760
3761}($);
3762
3763/* ===============================================================================
3764************ Infinite ************
3765=============================================================================== */
3766/* global $:true */
3767+function ($) {
3768 "use strict";
3769
3770 // fix https://github.com/lihongxun945/jquery-weui/issues/442
3771 // chrome will always return 0, when use document.body.scrollTop
3772 // https://stackoverflow.com/questions/43717316/google-chrome-document-body-scrolltop-always-returns-0
3773 var getOffset = function (container) {
3774 var tagName = container[0].tagName.toUpperCase()
3775 var scrollTop
3776 if (tagName === 'BODY' || tagName === 'HTML') {
3777 scrollTop = container.scrollTop() || $(window).scrollTop()
3778 } else {
3779 scrollTop = container.scrollTop()
3780 }
3781 var offset = container.scrollHeight() - ($(window).height() + scrollTop)
3782 console.log(offset)
3783 return offset
3784 }
3785
3786 var Infinite = function(el, distance) {
3787 this.container = $(el);
3788 this.container.data("infinite", this);
3789 this.distance = distance || 50;
3790 this.attachEvents();
3791 }
3792
3793 Infinite.prototype.scroll = function() {
3794 var container = this.container;
3795 this._check();
3796 }
3797
3798 Infinite.prototype.attachEvents = function(off) {
3799 var el = this.container;
3800 var scrollContainer = (el[0].tagName.toUpperCase() === "BODY" ? $(document) : el);
3801 scrollContainer[off ? "off" : "on"]("scroll", $.proxy(this.scroll, this));
3802 };
3803 Infinite.prototype.detachEvents = function(off) {
3804 this.attachEvents(true);
3805 }
3806 Infinite.prototype._check = function() {
3807 var offset = getOffset(this.container);
3808 if(Math.abs(offset) <= this.distance) {
3809 this.container.trigger("infinite");
3810 }
3811 }
3812
3813 var infinite = function(el) {
3814 attachEvents(el);
3815 }
3816
3817 $.fn.infinite = function(distance) {
3818 return this.each(function() {
3819 new Infinite(this, distance);
3820 });
3821 }
3822 $.fn.destroyInfinite = function() {
3823 return this.each(function() {
3824 var infinite = $(this).data("infinite");
3825 if(infinite && infinite.detachEvents) infinite.detachEvents();
3826 });
3827 }
3828
3829}($);
3830
3831/* global $:true */
3832+function ($) {
3833 "use strict";
3834
3835 var ITEM_ON = "weui-bar__item--on";
3836
3837 var showTab = function(a) {
3838 var $a = $(a);
3839 if($a.hasClass(ITEM_ON)) return;
3840 var href = $a.attr("href");
3841
3842 if(!/^#/.test(href)) return ;
3843
3844 $a.parent().find("."+ITEM_ON).removeClass(ITEM_ON);
3845 $a.addClass(ITEM_ON);
3846
3847 var bd = $a.parents(".weui-tab").find(".weui-tab__bd");
3848
3849 bd.find(".weui-tab__bd-item--active").removeClass("weui-tab__bd-item--active");
3850
3851 $(href).addClass("weui-tab__bd-item--active");
3852 }
3853
3854 $.showTab = showTab;
3855
3856 $(document).on("click", ".weui-navbar__item, .weui-tabbar__item", function(e) {
3857 var $a = $(e.currentTarget);
3858 var href = $a.attr("href");
3859 if($a.hasClass(ITEM_ON)) return;
3860 if(!/^#/.test(href)) return;
3861
3862 e.preventDefault();
3863
3864 showTab($a);
3865 });
3866
3867}($);
3868
3869/* global $:true */
3870+ function($) {
3871 "use strict";
3872
3873 $(document).on("click touchstart", ".weui-search-bar__label", function(e) {
3874 $(e.target).parents(".weui-search-bar").addClass("weui-search-bar_focusing").find('input').focus();
3875 })
3876 /*
3877 .on("blur", ".weui-search-bar__input", function(e) {
3878 var $input = $(e.target);
3879 if(!$input.val()) $input.parents(".weui-search-bar").removeClass("weui-search-bar_focusing");
3880 })
3881 */
3882 .on("click", ".weui-search-bar__cancel-btn", function(e) {
3883 var $input = $(e.target).parents(".weui-search-bar").removeClass("weui-search-bar_focusing").find(".weui-search-bar__input").val("").blur();
3884 })
3885 .on("click", ".weui-icon-clear", function(e) {
3886 var $input = $(e.target).parents(".weui-search-bar").find(".weui-search-bar__input").val("").focus();
3887 });
3888
3889}($);
3890
3891/*===========================
3892Device/OS Detection
3893===========================*/
3894/* global $:true */
3895;(function ($) {
3896 "use strict";
3897 var device = {};
3898 var ua = navigator.userAgent;
3899
3900 var android = ua.match(/(Android);?[\s\/]+([\d.]+)?/);
3901 var ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
3902 var ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/);
3903 var iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/);
3904
3905 device.ios = device.android = device.iphone = device.ipad = device.androidChrome = false;
3906
3907 // Android
3908 if (android) {
3909 device.os = 'android';
3910 device.osVersion = android[2];
3911 device.android = true;
3912 device.androidChrome = ua.toLowerCase().indexOf('chrome') >= 0;
3913 }
3914 if (ipad || iphone || ipod) {
3915 device.os = 'ios';
3916 device.ios = true;
3917 }
3918 // iOS
3919 if (iphone && !ipod) {
3920 device.osVersion = iphone[2].replace(/_/g, '.');
3921 device.iphone = true;
3922 }
3923 if (ipad) {
3924 device.osVersion = ipad[2].replace(/_/g, '.');
3925 device.ipad = true;
3926 }
3927 if (ipod) {
3928 device.osVersion = ipod[3] ? ipod[3].replace(/_/g, '.') : null;
3929 device.iphone = true;
3930 }
3931 // iOS 8+ changed UA
3932 if (device.ios && device.osVersion && ua.indexOf('Version/') >= 0) {
3933 if (device.osVersion.split('.')[0] === '10') {
3934 device.osVersion = ua.toLowerCase().split('version/')[1].split(' ')[0];
3935 }
3936 }
3937
3938 // Webview
3939 device.webView = (iphone || ipad || ipod) && ua.match(/.*AppleWebKit(?!.*Safari)/i);
3940
3941 // Minimal UI
3942 if (device.os && device.os === 'ios') {
3943 var osVersionArr = device.osVersion.split('.');
3944 device.minimalUi = !device.webView &&
3945 (ipod || iphone) &&
3946 (osVersionArr[0] * 1 === 7 ? osVersionArr[1] * 1 >= 1 : osVersionArr[0] * 1 > 7) &&
3947 $('meta[name="viewport"]').length > 0 && $('meta[name="viewport"]').attr('content').indexOf('minimal-ui') >= 0;
3948 }
3949
3950 // Check for status bar and fullscreen app mode
3951 var windowWidth = $(window).width();
3952 var windowHeight = $(window).height();
3953 device.statusBar = false;
3954 if (device.webView && (windowWidth * windowHeight === screen.width * screen.height)) {
3955 device.statusBar = true;
3956 }
3957 else {
3958 device.statusBar = false;
3959 }
3960
3961 // Classes
3962 var classNames = [];
3963
3964 // Pixel Ratio
3965 device.pixelRatio = window.devicePixelRatio || 1;
3966 classNames.push('pixel-ratio-' + Math.floor(device.pixelRatio));
3967 if (device.pixelRatio >= 2) {
3968 classNames.push('retina');
3969 }
3970
3971 // OS classes
3972 if (device.os) {
3973 classNames.push(device.os, device.os + '-' + device.osVersion.split('.')[0], device.os + '-' + device.osVersion.replace(/\./g, '-'));
3974 if (device.os === 'ios') {
3975 var major = parseInt(device.osVersion.split('.')[0], 10);
3976 for (var i = major - 1; i >= 6; i--) {
3977 classNames.push('ios-gt-' + i);
3978 }
3979 }
3980
3981 }
3982 // Status bar classes
3983 if (device.statusBar) {
3984 classNames.push('with-statusbar-overlay');
3985 }
3986 else {
3987 $('html').removeClass('with-statusbar-overlay');
3988 }
3989
3990 // Add html classes
3991 if (classNames.length > 0) $('html').addClass(classNames.join(' '));
3992
3993 $.device = device;
3994})($);
3995
3996/*======================================================
3997************ Picker ************
3998======================================================*/
3999/* global $:true */
4000/* jshint unused:false */
4001/* jshint multistr:true */
4002+ function($) {
4003 "use strict";
4004 var Picker = function (params) {
4005 var p = this;
4006 var defaults = {
4007 updateValuesOnMomentum: false,
4008 updateValuesOnTouchmove: true,
4009 rotateEffect: false,
4010 momentumRatio: 7,
4011 freeMode: false,
4012 // Common settings
4013 scrollToInput: true,
4014 inputReadOnly: true,
4015 toolbar: true,
4016 toolbarCloseText: '完成',
4017 title: '请选择',
4018 toolbarTemplate: '<div class="toolbar">\
4019 <div class="toolbar-inner">\
4020 <a href="javascript:;" class="picker-button close-picker">{{closeText}}</a>\
4021 <h1 class="title">{{title}}</h1>\
4022 </div>\
4023 </div>',
4024 };
4025 params = params || {};
4026 for (var def in defaults) {
4027 if (typeof params[def] === 'undefined') {
4028 params[def] = defaults[def];
4029 }
4030 }
4031 p.params = params;
4032 p.cols = [];
4033 p.initialized = false;
4034
4035 // Inline flag
4036 p.inline = p.params.container ? true : false;
4037
4038 // 3D Transforms origin bug, only on safari
4039 var originBug = $.device.ios || (navigator.userAgent.toLowerCase().indexOf('safari') >= 0 && navigator.userAgent.toLowerCase().indexOf('chrome') < 0) && !$.device.android;
4040
4041 // Should be converted to popover
4042 function isPopover() {
4043 var toPopover = false;
4044 if (!p.params.convertToPopover && !p.params.onlyInPopover) return toPopover;
4045 if (!p.inline && p.params.input) {
4046 if (p.params.onlyInPopover) toPopover = true;
4047 else {
4048 if ($.device.ios) {
4049 toPopover = $.device.ipad ? true : false;
4050 }
4051 else {
4052 if ($(window).width() >= 768) toPopover = true;
4053 }
4054 }
4055 }
4056 return toPopover;
4057 }
4058 function inPopover() {
4059 if (p.opened && p.container && p.container.length > 0 && p.container.parents('.popover').length > 0) return true;
4060 else return false;
4061 }
4062
4063 // Value
4064 p.setValue = function (arrValues, transition) {
4065 var valueIndex = 0;
4066 for (var i = 0; i < p.cols.length; i++) {
4067 if (p.cols[i] && !p.cols[i].divider) {
4068 p.cols[i].setValue(arrValues[valueIndex], transition);
4069 valueIndex++;
4070 }
4071 }
4072 };
4073 p.updateValue = function () {
4074 var newValue = [];
4075 var newDisplayValue = [];
4076 for (var i = 0; i < p.cols.length; i++) {
4077 if (!p.cols[i].divider) {
4078 newValue.push(p.cols[i].value);
4079 newDisplayValue.push(p.cols[i].displayValue);
4080 }
4081 }
4082 if (newValue.indexOf(undefined) >= 0) {
4083 return;
4084 }
4085 p.value = newValue;
4086 p.displayValue = newDisplayValue;
4087 if (p.params.onChange) {
4088 p.params.onChange(p, p.value, p.displayValue);
4089 }
4090 if (p.input && p.input.length > 0) {
4091 $(p.input).val(p.params.formatValue ? p.params.formatValue(p, p.value, p.displayValue) : p.value.join(' '));
4092 $(p.input).trigger('change');
4093 }
4094 };
4095
4096 // Columns Handlers
4097 p.initPickerCol = function (colElement, updateItems) {
4098 var colContainer = $(colElement);
4099 var colIndex = colContainer.index();
4100 var col = p.cols[colIndex];
4101 if (col.divider) return;
4102 col.container = colContainer;
4103 col.wrapper = col.container.find('.picker-items-col-wrapper');
4104 col.items = col.wrapper.find('.picker-item');
4105
4106 var i, j;
4107 var wrapperHeight, itemHeight, itemsHeight, minTranslate, maxTranslate;
4108 col.replaceValues = function (values, displayValues) {
4109 col.destroyEvents();
4110 col.values = values;
4111 col.displayValues = displayValues;
4112 var newItemsHTML = p.columnHTML(col, true);
4113 col.wrapper.html(newItemsHTML);
4114 col.items = col.wrapper.find('.picker-item');
4115 col.calcSize();
4116 col.setValue(col.values[0] || '', 0, true);
4117 col.initEvents();
4118 };
4119 col.calcSize = function () {
4120 if (!col.values.length) return;
4121 if (p.params.rotateEffect) {
4122 col.container.removeClass('picker-items-col-absolute');
4123 if (!col.width) col.container.css({width:''});
4124 }
4125 var colWidth, colHeight;
4126 colWidth = 0;
4127 colHeight = col.container[0].offsetHeight;
4128 wrapperHeight = col.wrapper[0].offsetHeight;
4129 itemHeight = col.items[0].offsetHeight;
4130 itemsHeight = itemHeight * col.items.length;
4131 minTranslate = colHeight / 2 - itemsHeight + itemHeight / 2;
4132 maxTranslate = colHeight / 2 - itemHeight / 2;
4133 if (col.width) {
4134 colWidth = col.width;
4135 if (parseInt(colWidth, 10) === colWidth) colWidth = colWidth + 'px';
4136 col.container.css({width: colWidth});
4137 }
4138 if (p.params.rotateEffect) {
4139 if (!col.width) {
4140 col.items.each(function () {
4141 var item = $(this);
4142 item.css({width:'auto'});
4143 colWidth = Math.max(colWidth, item[0].offsetWidth);
4144 item.css({width:''});
4145 });
4146 col.container.css({width: (colWidth + 2) + 'px'});
4147 }
4148 col.container.addClass('picker-items-col-absolute');
4149 }
4150 };
4151 col.calcSize();
4152
4153 col.wrapper.transform('translate3d(0,' + maxTranslate + 'px,0)').transition(0);
4154
4155
4156 var activeIndex = 0;
4157 var animationFrameId;
4158
4159 // Set Value Function
4160 col.setValue = function (newValue, transition, valueCallbacks) {
4161 if (typeof transition === 'undefined') transition = '';
4162 var newActiveIndex = col.wrapper.find('.picker-item[data-picker-value="' + newValue + '"]').index();
4163 if(typeof newActiveIndex === 'undefined' || newActiveIndex === -1) {
4164 col.value = col.displayValue = newValue;
4165 return;
4166 }
4167 var newTranslate = -newActiveIndex * itemHeight + maxTranslate;
4168 // Update wrapper
4169 col.wrapper.transition(transition);
4170 col.wrapper.transform('translate3d(0,' + (newTranslate) + 'px,0)');
4171
4172 // Watch items
4173 if (p.params.updateValuesOnMomentum && col.activeIndex && col.activeIndex !== newActiveIndex ) {
4174 $.cancelAnimationFrame(animationFrameId);
4175 col.wrapper.transitionEnd(function(){
4176 $.cancelAnimationFrame(animationFrameId);
4177 });
4178 updateDuringScroll();
4179 }
4180
4181 // Update items
4182 col.updateItems(newActiveIndex, newTranslate, transition, valueCallbacks);
4183 };
4184
4185 col.updateItems = function (activeIndex, translate, transition, valueCallbacks) {
4186 if (typeof translate === 'undefined') {
4187 translate = $.getTranslate(col.wrapper[0], 'y');
4188 }
4189 if(typeof activeIndex === 'undefined') activeIndex = -Math.round((translate - maxTranslate)/itemHeight);
4190 if (activeIndex < 0) activeIndex = 0;
4191 if (activeIndex >= col.items.length) activeIndex = col.items.length - 1;
4192 var previousActiveIndex = col.activeIndex;
4193 col.activeIndex = activeIndex;
4194 /*
4195 col.wrapper.find('.picker-selected, .picker-after-selected, .picker-before-selected').removeClass('picker-selected picker-after-selected picker-before-selected');
4196
4197 col.items.transition(transition);
4198 var selectedItem = col.items.eq(activeIndex).addClass('picker-selected').transform('');
4199 var prevItems = selectedItem.prevAll().addClass('picker-before-selected');
4200 var nextItems = selectedItem.nextAll().addClass('picker-after-selected');
4201 */
4202 //去掉 .picker-after-selected, .picker-before-selected 以提高性能
4203 col.wrapper.find('.picker-selected').removeClass('picker-selected');
4204 if (p.params.rotateEffect) {
4205 col.items.transition(transition);
4206 }
4207 var selectedItem = col.items.eq(activeIndex).addClass('picker-selected').transform('');
4208
4209 if (valueCallbacks || typeof valueCallbacks === 'undefined') {
4210 // Update values
4211 col.value = selectedItem.attr('data-picker-value');
4212 col.displayValue = col.displayValues ? col.displayValues[activeIndex] : col.value;
4213 // On change callback
4214 if (previousActiveIndex !== activeIndex) {
4215 if (col.onChange) {
4216 col.onChange(p, col.value, col.displayValue);
4217 }
4218 p.updateValue();
4219 }
4220 }
4221
4222 // Set 3D rotate effect
4223 if (!p.params.rotateEffect) {
4224 return;
4225 }
4226 var percentage = (translate - (Math.floor((translate - maxTranslate)/itemHeight) * itemHeight + maxTranslate)) / itemHeight;
4227
4228 col.items.each(function () {
4229 var item = $(this);
4230 var itemOffsetTop = item.index() * itemHeight;
4231 var translateOffset = maxTranslate - translate;
4232 var itemOffset = itemOffsetTop - translateOffset;
4233 var percentage = itemOffset / itemHeight;
4234
4235 var itemsFit = Math.ceil(col.height / itemHeight / 2) + 1;
4236
4237 var angle = (-18*percentage);
4238 if (angle > 180) angle = 180;
4239 if (angle < -180) angle = -180;
4240 // Far class
4241 if (Math.abs(percentage) > itemsFit) item.addClass('picker-item-far');
4242 else item.removeClass('picker-item-far');
4243 // Set transform
4244 item.transform('translate3d(0, ' + (-translate + maxTranslate) + 'px, ' + (originBug ? -110 : 0) + 'px) rotateX(' + angle + 'deg)');
4245 });
4246 };
4247
4248 function updateDuringScroll() {
4249 animationFrameId = $.requestAnimationFrame(function () {
4250 col.updateItems(undefined, undefined, 0);
4251 updateDuringScroll();
4252 });
4253 }
4254
4255 // Update items on init
4256 if (updateItems) col.updateItems(0, maxTranslate, 0);
4257
4258 var allowItemClick = true;
4259 var isTouched, isMoved, touchStartY, touchCurrentY, touchStartTime, touchEndTime, startTranslate, returnTo, currentTranslate, prevTranslate, velocityTranslate, velocityTime;
4260 function handleTouchStart (e) {
4261 if (isMoved || isTouched) return;
4262 e.preventDefault();
4263 isTouched = true;
4264 var position = $.getTouchPosition(e);
4265 touchStartY = touchCurrentY = position.y;
4266 touchStartTime = (new Date()).getTime();
4267
4268 allowItemClick = true;
4269 startTranslate = currentTranslate = $.getTranslate(col.wrapper[0], 'y');
4270 }
4271 function handleTouchMove (e) {
4272 if (!isTouched) return;
4273 e.preventDefault();
4274 allowItemClick = false;
4275 var position = $.getTouchPosition(e);
4276 touchCurrentY = position.y;
4277 if (!isMoved) {
4278 // First move
4279 $.cancelAnimationFrame(animationFrameId);
4280 isMoved = true;
4281 startTranslate = currentTranslate = $.getTranslate(col.wrapper[0], 'y');
4282 col.wrapper.transition(0);
4283 }
4284 e.preventDefault();
4285
4286 var diff = touchCurrentY - touchStartY;
4287 currentTranslate = startTranslate + diff;
4288 returnTo = undefined;
4289
4290 // Normalize translate
4291 if (currentTranslate < minTranslate) {
4292 currentTranslate = minTranslate - Math.pow(minTranslate - currentTranslate, 0.8);
4293 returnTo = 'min';
4294 }
4295 if (currentTranslate > maxTranslate) {
4296 currentTranslate = maxTranslate + Math.pow(currentTranslate - maxTranslate, 0.8);
4297 returnTo = 'max';
4298 }
4299 // Transform wrapper
4300 col.wrapper.transform('translate3d(0,' + currentTranslate + 'px,0)');
4301
4302 // Update items
4303 col.updateItems(undefined, currentTranslate, 0, p.params.updateValuesOnTouchmove);
4304
4305 // Calc velocity
4306 velocityTranslate = currentTranslate - prevTranslate || currentTranslate;
4307 velocityTime = (new Date()).getTime();
4308 prevTranslate = currentTranslate;
4309 }
4310 function handleTouchEnd (e) {
4311 if (!isTouched || !isMoved) {
4312 isTouched = isMoved = false;
4313 return;
4314 }
4315 isTouched = isMoved = false;
4316 col.wrapper.transition('');
4317 if (returnTo) {
4318 if (returnTo === 'min') {
4319 col.wrapper.transform('translate3d(0,' + minTranslate + 'px,0)');
4320 }
4321 else col.wrapper.transform('translate3d(0,' + maxTranslate + 'px,0)');
4322 }
4323 touchEndTime = new Date().getTime();
4324 var velocity, newTranslate;
4325 if (touchEndTime - touchStartTime > 300) {
4326 newTranslate = currentTranslate;
4327 }
4328 else {
4329 velocity = Math.abs(velocityTranslate / (touchEndTime - velocityTime));
4330 newTranslate = currentTranslate + velocityTranslate * p.params.momentumRatio;
4331 }
4332
4333 newTranslate = Math.max(Math.min(newTranslate, maxTranslate), minTranslate);
4334
4335 // Active Index
4336 var activeIndex = -Math.floor((newTranslate - maxTranslate)/itemHeight);
4337
4338 // Normalize translate
4339 if (!p.params.freeMode) newTranslate = -activeIndex * itemHeight + maxTranslate;
4340
4341 // Transform wrapper
4342 col.wrapper.transform('translate3d(0,' + (parseInt(newTranslate,10)) + 'px,0)');
4343
4344 // Update items
4345 col.updateItems(activeIndex, newTranslate, '', true);
4346
4347 // Watch items
4348 if (p.params.updateValuesOnMomentum) {
4349 updateDuringScroll();
4350 col.wrapper.transitionEnd(function(){
4351 $.cancelAnimationFrame(animationFrameId);
4352 });
4353 }
4354
4355 // Allow click
4356 setTimeout(function () {
4357 allowItemClick = true;
4358 }, 100);
4359 }
4360
4361 function handleClick(e) {
4362 if (!allowItemClick) return;
4363 $.cancelAnimationFrame(animationFrameId);
4364 /*jshint validthis:true */
4365 var value = $(this).attr('data-picker-value');
4366 col.setValue(value);
4367 }
4368
4369 col.initEvents = function (detach) {
4370 var method = detach ? 'off' : 'on';
4371 col.container[method]($.touchEvents.start, handleTouchStart);
4372 col.container[method]($.touchEvents.move, handleTouchMove);
4373 col.container[method]($.touchEvents.end, handleTouchEnd);
4374 col.items[method]('click', handleClick);
4375 };
4376 col.destroyEvents = function () {
4377 col.initEvents(true);
4378 };
4379
4380 col.container[0].f7DestroyPickerCol = function () {
4381 col.destroyEvents();
4382 };
4383
4384 col.initEvents();
4385
4386 };
4387 p.destroyPickerCol = function (colContainer) {
4388 colContainer = $(colContainer);
4389 if ('f7DestroyPickerCol' in colContainer[0]) colContainer[0].f7DestroyPickerCol();
4390 };
4391 // Resize cols
4392 function resizeCols() {
4393 if (!p.opened) return;
4394 for (var i = 0; i < p.cols.length; i++) {
4395 if (!p.cols[i].divider) {
4396 p.cols[i].calcSize();
4397 p.cols[i].setValue(p.cols[i].value, 0, false);
4398 }
4399 }
4400 }
4401 $(window).on('resize', resizeCols);
4402
4403 // HTML Layout
4404 p.columnHTML = function (col, onlyItems) {
4405 var columnItemsHTML = '';
4406 var columnHTML = '';
4407 if (col.divider) {
4408 columnHTML += '<div class="picker-items-col picker-items-col-divider ' + (col.textAlign ? 'picker-items-col-' + col.textAlign : '') + ' ' + (col.cssClass || '') + '">' + col.content + '</div>';
4409 }
4410 else {
4411 for (var j = 0; j < col.values.length; j++) {
4412 columnItemsHTML += '<div class="picker-item" data-picker-value="' + col.values[j] + '">' + (col.displayValues ? col.displayValues[j] : col.values[j]) + '</div>';
4413 }
4414 columnHTML += '<div class="picker-items-col ' + (col.textAlign ? 'picker-items-col-' + col.textAlign : '') + ' ' + (col.cssClass || '') + '"><div class="picker-items-col-wrapper">' + columnItemsHTML + '</div></div>';
4415 }
4416 return onlyItems ? columnItemsHTML : columnHTML;
4417 };
4418 p.layout = function () {
4419 var pickerHTML = '';
4420 var pickerClass = '';
4421 var i;
4422 p.cols = [];
4423 var colsHTML = '';
4424 for (i = 0; i < p.params.cols.length; i++) {
4425 var col = p.params.cols[i];
4426 colsHTML += p.columnHTML(p.params.cols[i]);
4427 p.cols.push(col);
4428 }
4429 pickerClass = 'weui-picker-modal picker-columns ' + (p.params.cssClass || '') + (p.params.rotateEffect ? ' picker-3d' : '') + (p.params.cols.length === 1 ? ' picker-columns-single' : '');
4430 pickerHTML =
4431 '<div class="' + (pickerClass) + '">' +
4432 (p.params.toolbar ? p.params.toolbarTemplate.replace(/{{closeText}}/g, p.params.toolbarCloseText).replace(/{{title}}/g, p.params.title) : '') +
4433 '<div class="picker-modal-inner picker-items">' +
4434 colsHTML +
4435 '<div class="picker-center-highlight"></div>' +
4436 '</div>' +
4437 '</div>';
4438
4439 p.pickerHTML = pickerHTML;
4440 };
4441
4442 // Input Events
4443 function openOnInput(e) {
4444 e.preventDefault();
4445 if (p.opened) return;
4446 p.open();
4447 if (p.params.scrollToInput && !isPopover()) {
4448 var pageContent = p.input.parents('.content');
4449 if (pageContent.length === 0) return;
4450
4451 var paddingTop = parseInt(pageContent.css('padding-top'), 10),
4452 paddingBottom = parseInt(pageContent.css('padding-bottom'), 10),
4453 pageHeight = pageContent[0].offsetHeight - paddingTop - p.container.height(),
4454 pageScrollHeight = pageContent[0].scrollHeight - paddingTop - p.container.height(),
4455 newPaddingBottom;
4456 var inputTop = p.input.offset().top - paddingTop + p.input[0].offsetHeight;
4457 if (inputTop > pageHeight) {
4458 var scrollTop = pageContent.scrollTop() + inputTop - pageHeight;
4459 if (scrollTop + pageHeight > pageScrollHeight) {
4460 newPaddingBottom = scrollTop + pageHeight - pageScrollHeight + paddingBottom;
4461 if (pageHeight === pageScrollHeight) {
4462 newPaddingBottom = p.container.height();
4463 }
4464 pageContent.css({'padding-bottom': (newPaddingBottom) + 'px'});
4465 }
4466 pageContent.scrollTop(scrollTop, 300);
4467 }
4468 }
4469 }
4470 function closeOnHTMLClick(e) {
4471 if (inPopover()) return;
4472 if (p.input && p.input.length > 0) {
4473 if (e.target !== p.input[0] && $(e.target).parents('.weui-picker-modal').length === 0) p.close();
4474 }
4475 else {
4476 if ($(e.target).parents('.weui-picker-modal').length === 0) p.close();
4477 }
4478 }
4479
4480 if (p.params.input) {
4481 p.input = $(p.params.input);
4482 if (p.input.length > 0) {
4483 if (p.params.inputReadOnly) p.input.prop('readOnly', true);
4484 if (!p.inline) {
4485 p.input.on('click', openOnInput);
4486 }
4487 if (p.params.inputReadOnly) {
4488 p.input.on('focus mousedown', function (e) {
4489 e.preventDefault();
4490 });
4491 }
4492 }
4493
4494 }
4495
4496 if (!p.inline) $('html').on('click', closeOnHTMLClick);
4497
4498 // Open
4499 function onPickerClose() {
4500 p.opened = false;
4501 if (p.input && p.input.length > 0) p.input.parents('.page-content').css({'padding-bottom': ''});
4502 if (p.params.onClose) p.params.onClose(p);
4503
4504 // Destroy events
4505 p.container.find('.picker-items-col').each(function () {
4506 p.destroyPickerCol(this);
4507 });
4508 }
4509
4510 p.opened = false;
4511 p.open = function () {
4512 var toPopover = isPopover();
4513
4514 if (!p.opened) {
4515
4516 // Layout
4517 p.layout();
4518
4519 // Append
4520 if (toPopover) {
4521 p.pickerHTML = '<div class="popover popover-picker-columns"><div class="popover-inner">' + p.pickerHTML + '</div></div>';
4522 p.popover = $.popover(p.pickerHTML, p.params.input, true);
4523 p.container = $(p.popover).find('.weui-picker-modal');
4524 $(p.popover).on('close', function () {
4525 onPickerClose();
4526 });
4527 }
4528 else if (p.inline) {
4529 p.container = $(p.pickerHTML);
4530 p.container.addClass('picker-modal-inline');
4531 $(p.params.container).append(p.container);
4532 }
4533 else {
4534 p.container = $($.openPicker(p.pickerHTML));
4535 $(p.container)
4536 .on('close', function () {
4537 onPickerClose();
4538 });
4539 }
4540
4541 // Store picker instance
4542 p.container[0].f7Picker = p;
4543
4544 // Init Events
4545 p.container.find('.picker-items-col').each(function () {
4546 var updateItems = true;
4547 if ((!p.initialized && p.params.value) || (p.initialized && p.value)) updateItems = false;
4548 p.initPickerCol(this, updateItems);
4549 });
4550
4551 // Set value
4552 if (!p.initialized) {
4553 if (p.params.value) {
4554 p.setValue(p.params.value, 0);
4555 }
4556 }
4557 else {
4558 if (p.value) p.setValue(p.value, 0);
4559 }
4560 }
4561
4562 // Set flag
4563 p.opened = true;
4564 p.initialized = true;
4565
4566 if (p.params.onOpen) p.params.onOpen(p);
4567 };
4568
4569 // Close
4570 p.close = function (force) {
4571 if (!p.opened || p.inline) return;
4572 if (inPopover()) {
4573 $.closePicker(p.popover);
4574 return;
4575 }
4576 else {
4577 $.closePicker(p.container);
4578 return;
4579 }
4580 };
4581
4582 // Destroy
4583 p.destroy = function () {
4584 p.close();
4585 if (p.params.input && p.input.length > 0) {
4586 p.input.off('click focus', openOnInput);
4587 $(p.input).data('picker', null);
4588 }
4589 $('html').off('click', closeOnHTMLClick);
4590 $(window).off('resize', resizeCols);
4591 };
4592
4593 if (p.inline) {
4594 p.open();
4595 }
4596
4597 return p;
4598 };
4599
4600 $(document).on("click", ".close-picker", function() {
4601 var pickerToClose = $('.weui-picker-modal.weui-picker-modal-visible');
4602 if (pickerToClose.length > 0) {
4603 $.closePicker(pickerToClose);
4604 }
4605 });
4606
4607 //修复picker会滚动页面的bug
4608 $(document).on($.touchEvents.move, ".picker-modal-inner", function(e) {
4609 e.preventDefault();
4610 });
4611
4612
4613 $.openPicker = function(tpl, className, callback) {
4614
4615 if(typeof className === "function") {
4616 callback = className;
4617 className = undefined;
4618 }
4619
4620 $.closePicker();
4621
4622 var container = $("<div class='weui-picker-container "+ (className || "") + "'></div>").appendTo(document.body);
4623 container.show();
4624
4625 container.addClass("weui-picker-container-visible");
4626
4627 //关于布局的问题,如果直接放在body上,则做动画的时候会撑开body高度而导致滚动条变化。
4628 var dialog = $(tpl).appendTo(container);
4629
4630 dialog.width(); //通过取一次CSS值,强制浏览器不能把上下两行代码合并执行,因为合并之后会导致无法出现动画。
4631
4632 dialog.addClass("weui-picker-modal-visible");
4633
4634 callback && container.on("close", callback);
4635
4636 return dialog;
4637 }
4638
4639 $.updatePicker = function(tpl) {
4640 var container = $(".weui-picker-container-visible");
4641 if(!container[0]) return false;
4642
4643 container.html("");
4644
4645 var dialog = $(tpl).appendTo(container);
4646
4647 dialog.addClass("weui-picker-modal-visible");
4648
4649 return dialog;
4650 }
4651
4652 $.closePicker = function(container, callback) {
4653 if(typeof container === "function") callback = container;
4654 $(".weui-picker-modal-visible").removeClass("weui-picker-modal-visible").transitionEnd(function() {
4655 $(this).parent().remove();
4656 callback && callback();
4657 }).trigger("close");
4658 };
4659
4660 $.fn.picker = function(params) {
4661 var args = arguments;
4662 return this.each(function() {
4663 if(!this) return;
4664 var $this = $(this);
4665
4666 var picker = $this.data("picker");
4667 if(!picker) {
4668 params = $.extend({ input: this }, params || {}) // https://github.com/lihongxun945/jquery-weui/issues/432
4669 var inputValue = $this.val();
4670 if(params.value === undefined && inputValue !== "") {
4671 params.value = (params.cols && params.cols.length > 1) ? inputValue.split(" ") : [inputValue];
4672 }
4673 var p = $.extend({input: this}, params);
4674 picker = new Picker(p);
4675 $this.data("picker", picker);
4676 }
4677 if(typeof params === typeof "a") {
4678 picker[params].apply(picker, Array.prototype.slice.call(args, 1));
4679 }
4680 });
4681 };
4682}($);
4683
4684/* global $:true */
4685+ function($) {
4686 "use strict";
4687
4688 var defaults;
4689
4690 var selects = [];
4691
4692 var Select = function(input, config) {
4693
4694 var self = this;
4695 this.config = config;
4696
4697 //init empty data
4698 this.data = {
4699 values: '',
4700 titles: '',
4701 origins: [],
4702 length: 0
4703 };
4704
4705 this.$input = $(input);
4706 this.$input.prop("readOnly", true);
4707
4708 this.initConfig();
4709
4710 config = this.config;
4711
4712 this.$input.click($.proxy(this.open, this));
4713 selects.push(this)
4714 }
4715
4716 Select.prototype.initConfig = function() {
4717 this.config = $.extend({}, defaults, this.config);
4718
4719 var config = this.config;
4720
4721 if(!config.items || !config.items.length) return;
4722
4723 config.items = config.items.map(function(d, i) {
4724 if(typeof d == typeof "a") {
4725 return {
4726 title: d,
4727 value: d
4728 };
4729 }
4730
4731 return d;
4732 });
4733
4734
4735 this.tpl = $.t7.compile("<div class='weui-picker-modal weui-select-modal'>" + config.toolbarTemplate + (config.multi ? config.checkboxTemplate : config.radioTemplate) + "</div>");
4736
4737 if(config.input !== undefined) this.$input.val(config.input);
4738
4739 this.parseInitValue();
4740
4741 this._init = true;
4742 }
4743
4744 Select.prototype.updateInputValue = function(values, titles) {
4745 var v, t;
4746 if(this.config.multi) {
4747 v = values.join(this.config.split);
4748 t = titles.join(this.config.split);
4749 } else {
4750 v = values[0];
4751 t = titles[0];
4752 }
4753
4754 //caculate origin data
4755 var origins = [];
4756
4757 this.config.items.forEach(function(d) {
4758 values.each(function(i, dd) {
4759 if(d.value == dd) origins.push(d);
4760 });
4761 });
4762
4763 this.$input.val(t).data("values", v);
4764 this.$input.attr("value", t).attr("data-values", v);
4765
4766 var data = {
4767 values: v,
4768 titles: t,
4769 valuesArray: values,
4770 titlesArray: titles,
4771 origins: origins,
4772 length: origins.length
4773 };
4774 this.data = data;
4775 this.$input.trigger("change", data);
4776 this.config.onChange && this.config.onChange.call(this, data);
4777 }
4778
4779 Select.prototype.parseInitValue = function() {
4780 var value = this.$input.val();
4781 var items = this.config.items;
4782
4783 //如果input为空,只有在第一次初始化的时候才保留默认选择。因为后来就是用户自己取消了全部选择,不能再为他选中默认值。
4784 if( !this._init && (value === undefined || value == null || value === "")) return;
4785
4786 var titles = this.config.multi ? value.split(this.config.split) : [value];
4787 for(var i=0;i<items.length;i++) {
4788 items[i].checked = false;
4789 for(var j=0;j<titles.length;j++) {
4790 if(items[i].title === titles[j]) {
4791 items[i].checked = true;
4792 }
4793 }
4794 }
4795 }
4796
4797 Select.prototype._bind = function(dialog) {
4798 var self = this,
4799 config = this.config;
4800 dialog.on("change", function(e) {
4801 var checked = dialog.find("input:checked");
4802 var values = checked.map(function() {
4803 return $(this).val();
4804 });
4805 var titles = checked.map(function() {
4806 return $(this).data("title");
4807 });
4808 self.updateInputValue(values, titles);
4809
4810 if(config.autoClose && !config.multi) self.close();
4811 })
4812 .trigger('change')
4813 .on("click", ".close-select", function() {
4814 self.close();
4815 });
4816 }
4817
4818 //更新数据
4819 Select.prototype.update = function(config) {
4820 this.config = $.extend({}, this.config, config);
4821 this.initConfig();
4822 if(this._open) {
4823 this._bind($.updatePicker(this.getHTML()));
4824 }
4825 }
4826
4827 Select.prototype.open = function(values, titles) {
4828
4829 if(this._open) return;
4830
4831 // open picker 会默认关掉其他的,但是 onClose 不会被调用,所以这里先关掉其他select
4832 for (var i = 0; i < selects.length; i++ ) {
4833 var s = selects[i];
4834 if (s === this) continue;
4835 if (s._open) {
4836 if(!s.close()) return false; // 其他的select由于某些条件限制关闭失败。
4837 }
4838 }
4839
4840 this.parseInitValue();
4841
4842 var config = this.config;
4843
4844 var dialog = this.dialog = $.openPicker(this.getHTML());
4845
4846 this._bind(dialog);
4847
4848 this._open = true;
4849 if(config.onOpen) config.onOpen(this);
4850 }
4851
4852 Select.prototype.close = function(callback, force) {
4853 if (!this._open) return false;
4854 var self = this,
4855 beforeClose = this.config.beforeClose;
4856
4857 if(typeof callback === typeof true) {
4858 force === callback;
4859 }
4860 if(!force) {
4861 if(beforeClose && typeof beforeClose === 'function' && beforeClose.call(this, this.data.values, this.data.titles) === false) {
4862 return false
4863 }
4864 if(this.config.multi) {
4865 if(this.config.min !== undefined && this.data.length < this.config.min) {
4866 $.toast("请至少选择"+this.config.min+"个", "text");
4867 return false
4868 }
4869 if(this.config.max !== undefined && this.data.length > this.config.max) {
4870 $.toast("最多只能选择"+this.config.max+"个", "text");
4871 return false
4872 }
4873 }
4874 }
4875 $.closePicker(function() {
4876 self.onClose();
4877 callback && callback();
4878 });
4879
4880 return true
4881 }
4882
4883 Select.prototype.onClose = function() {
4884 this._open = false;
4885 if(this.config.onClose) this.config.onClose(this);
4886 }
4887
4888 Select.prototype.getHTML = function(callback) {
4889 var config = this.config;
4890 return this.tpl({
4891 items: config.items,
4892 title: config.title,
4893 closeText: config.closeText
4894 })
4895 }
4896
4897
4898 $.fn.select = function(params, args) {
4899
4900 return this.each(function() {
4901 var $this = $(this);
4902 if(!$this.data("weui-select")) $this.data("weui-select", new Select(this, params));
4903
4904 var select = $this.data("weui-select");
4905
4906 if(typeof params === typeof "a") select[params].call(select, args);
4907
4908 return select;
4909 });
4910 }
4911
4912 defaults = $.fn.select.prototype.defaults = {
4913 items: [],
4914 input: undefined, //输入框的初始值
4915 title: "请选择",
4916 multi: false,
4917 closeText: "确定",
4918 autoClose: true, //是否选择完成后自动关闭,只有单选模式下才有效
4919 onChange: undefined, //function
4920 beforeClose: undefined, // function 关闭之前,如果返回false则阻止关闭
4921 onClose: undefined, //function
4922 onOpen: undefined, //function
4923 split: ",", //多选模式下的分隔符
4924 min: undefined, //多选模式下可用,最少选择数
4925 max: undefined, //单选模式下可用,最多选择数
4926 toolbarTemplate: '<div class="toolbar">\
4927 <div class="toolbar-inner">\
4928 <a href="javascript:;" class="picker-button close-select">{{closeText}}</a>\
4929 <h1 class="title">{{title}}</h1>\
4930 </div>\
4931 </div>',
4932 radioTemplate:
4933 '<div class="weui-cells weui-cells_radio">\
4934 {{#items}}\
4935 <label class="weui-cell weui-check_label" for="weui-select-id-{{this.title}}">\
4936 <div class="weui-cell__bd weui-cell_primary">\
4937 <p>{{this.title}}</p>\
4938 </div>\
4939 <div class="weui-cell__ft">\
4940 <input type="radio" class="weui-check" name="weui-select" id="weui-select-id-{{this.title}}" value="{{this.value}}" {{#if this.checked}}checked="checked"{{/if}} data-title="{{this.title}}">\
4941 <span class="weui-icon-checked"></span>\
4942 </div>\
4943 </label>\
4944 {{/items}}\
4945 </div>',
4946 checkboxTemplate:
4947 '<div class="weui-cells weui-cells_checkbox">\
4948 {{#items}}\
4949 <label class="weui-cell weui-check_label" for="weui-select-id-{{this.title}}">\
4950 <div class="weui-cell__bd weui-cell_primary">\
4951 <p>{{this.title}}</p>\
4952 </div>\
4953 <div class="weui-cell__ft">\
4954 <input type="checkbox" class="weui-check" name="weui-select" id="weui-select-id-{{this.title}}" value="{{this.value}}" {{#if this.checked}}checked="checked"{{/if}} data-title="{{this.title}}" >\
4955 <span class="weui-icon-checked"></span>\
4956 </div>\
4957 </label>\
4958 {{/items}}\
4959 </div>',
4960 }
4961
4962}($);
4963
4964/*======================================================
4965************ Calendar ************
4966======================================================*/
4967/* global $:true */
4968/*jshint unused: false*/
4969+function ($) {
4970 "use strict";
4971 var rtl = false;
4972 var defaults;
4973 var isSameDate = function (a, b) {
4974 var a = new Date(a),
4975 b = new Date(b);
4976 return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()
4977 }
4978 var Calendar = function (params) {
4979 var p = this;
4980 params = params || {};
4981 for (var def in defaults) {
4982 if (typeof params[def] === 'undefined') {
4983 params[def] = defaults[def];
4984 }
4985 }
4986 p.params = params;
4987 p.initialized = false;
4988
4989 // Inline flag
4990 p.inline = p.params.container ? true : false;
4991
4992 // Is horizontal
4993 p.isH = p.params.direction === 'horizontal';
4994
4995 // RTL inverter
4996 var inverter = p.isH ? (rtl ? -1 : 1) : 1;
4997
4998 // Animating flag
4999 p.animating = false;
5000
5001 // Should be converted to popover
5002 function isPopover() {
5003 var toPopover = false;
5004 if (!p.params.convertToPopover && !p.params.onlyInPopover) return toPopover;
5005 if (!p.inline && p.params.input) {
5006 if (p.params.onlyInPopover) toPopover = true;
5007 else {
5008 if ($.device.ios) {
5009 toPopover = $.device.ipad ? true : false;
5010 }
5011 else {
5012 if ($(window).width() >= 768) toPopover = true;
5013 }
5014 }
5015 }
5016 return toPopover;
5017 }
5018 function inPopover() {
5019 if (p.opened && p.container && p.container.length > 0 && p.container.parents('.popover').length > 0) return true;
5020 else return false;
5021 }
5022
5023 // Format date
5024 function formatDate(date) {
5025 date = new Date(date);
5026 var year = date.getFullYear();
5027 var month = date.getMonth();
5028 var month1 = month + 1;
5029 var day = date.getDate();
5030 var weekDay = date.getDay();
5031 return p.params.dateFormat
5032 .replace(/yyyy/g, year)
5033 .replace(/yy/g, (year + '').substring(2))
5034 .replace(/mm/g, month1 < 10 ? '0' + month1 : month1)
5035 .replace(/m/g, month1)
5036 .replace(/MM/g, p.params.monthNames[month])
5037 .replace(/M/g, p.params.monthNamesShort[month])
5038 .replace(/dd/g, day < 10 ? '0' + day : day)
5039 .replace(/d/g, day)
5040 .replace(/DD/g, p.params.dayNames[weekDay])
5041 .replace(/D/g, p.params.dayNamesShort[weekDay]);
5042 }
5043
5044
5045 // Value
5046 p.addValue = function (value) {
5047 if (p.params.multiple) {
5048 if (!p.value) p.value = [];
5049 var inValuesIndex;
5050 for (var i = 0; i < p.value.length; i++) {
5051 if (isSameDate(value, p.value[i])) {
5052 inValuesIndex = i;
5053 }
5054 }
5055 if (typeof inValuesIndex === 'undefined') {
5056 p.value.push(value);
5057 }
5058 else {
5059 p.value.splice(inValuesIndex, 1);
5060 }
5061 p.updateValue();
5062 }
5063 else {
5064 p.value = [value];
5065 p.updateValue();
5066 }
5067 };
5068 p.setValue = function (arrValues) {
5069 var date = new Date(arrValues[0]);
5070 p.setYearMonth(date.getFullYear(), date.getMonth());
5071 p.addValue(+ date);
5072 };
5073 p.updateValue = function () {
5074 p.wrapper.find('.picker-calendar-day-selected').removeClass('picker-calendar-day-selected');
5075 var i, inputValue;
5076 for (i = 0; i < p.value.length; i++) {
5077 var valueDate = new Date(p.value[i]);
5078 p.wrapper.find('.picker-calendar-day[data-date="' + valueDate.getFullYear() + '-' + valueDate.getMonth() + '-' + valueDate.getDate() + '"]').addClass('picker-calendar-day-selected');
5079 }
5080 if (p.params.onChange) {
5081 p.params.onChange(p, p.value.map(formatDate), p.value.map(function (d) {
5082 return + new Date(typeof d === typeof 'a' ? d.split(/\D/).filter(function (a) { return !!a; }).join("-") : d);
5083 }));
5084 }
5085 if (p.input && p.input.length > 0) {
5086 if (p.params.formatValue) inputValue = p.params.formatValue(p, p.value);
5087 else {
5088 inputValue = [];
5089 for (i = 0; i < p.value.length; i++) {
5090 inputValue.push(formatDate(p.value[i]));
5091 }
5092 inputValue = inputValue.join(', ');
5093 }
5094 $(p.input).val(inputValue);
5095 $(p.input).trigger('change');
5096 }
5097 };
5098
5099 // Columns Handlers
5100 p.initCalendarEvents = function () {
5101 var col;
5102 var allowItemClick = true;
5103 var isTouched, isMoved, touchStartX, touchStartY, touchCurrentX, touchCurrentY, touchStartTime, touchEndTime, startTranslate, currentTranslate, wrapperWidth, wrapperHeight, percentage, touchesDiff, isScrolling;
5104 function handleTouchStart (e) {
5105 if (isMoved || isTouched) return;
5106 // e.preventDefault();
5107 isTouched = true;
5108 var position = $.getTouchPosition(e);
5109 touchStartX = touchCurrentY = position.x;
5110 touchStartY = touchCurrentY = position.y;
5111 touchStartTime = (new Date()).getTime();
5112 percentage = 0;
5113 allowItemClick = true;
5114 isScrolling = undefined;
5115 startTranslate = currentTranslate = p.monthsTranslate;
5116 }
5117 function handleTouchMove (e) {
5118 if (!isTouched) return;
5119 var position = $.getTouchPosition(e);
5120 touchCurrentX = position.x;
5121 touchCurrentY = position.y;
5122 if (typeof isScrolling === 'undefined') {
5123 isScrolling = !!(isScrolling || Math.abs(touchCurrentY - touchStartY) > Math.abs(touchCurrentX - touchStartX));
5124 }
5125 if (p.isH && isScrolling) {
5126 isTouched = false;
5127 return;
5128 }
5129 e.preventDefault();
5130 if (p.animating) {
5131 isTouched = false;
5132 return;
5133 }
5134 allowItemClick = false;
5135 if (!isMoved) {
5136 // First move
5137 isMoved = true;
5138 wrapperWidth = p.wrapper[0].offsetWidth;
5139 wrapperHeight = p.wrapper[0].offsetHeight;
5140 p.wrapper.transition(0);
5141 }
5142 e.preventDefault();
5143
5144 touchesDiff = p.isH ? touchCurrentX - touchStartX : touchCurrentY - touchStartY;
5145 percentage = touchesDiff/(p.isH ? wrapperWidth : wrapperHeight);
5146 currentTranslate = (p.monthsTranslate * inverter + percentage) * 100;
5147
5148 // Transform wrapper
5149 p.wrapper.transform('translate3d(' + (p.isH ? currentTranslate : 0) + '%, ' + (p.isH ? 0 : currentTranslate) + '%, 0)');
5150
5151 }
5152 function handleTouchEnd (e) {
5153 if (!isTouched || !isMoved) {
5154 isTouched = isMoved = false;
5155 return;
5156 }
5157 isTouched = isMoved = false;
5158
5159 touchEndTime = new Date().getTime();
5160 if (touchEndTime - touchStartTime < 300) {
5161 if (Math.abs(touchesDiff) < 10) {
5162 p.resetMonth();
5163 }
5164 else if (touchesDiff >= 10) {
5165 if (rtl) p.nextMonth();
5166 else p.prevMonth();
5167 }
5168 else {
5169 if (rtl) p.prevMonth();
5170 else p.nextMonth();
5171 }
5172 }
5173 else {
5174 if (percentage <= -0.5) {
5175 if (rtl) p.prevMonth();
5176 else p.nextMonth();
5177 }
5178 else if (percentage >= 0.5) {
5179 if (rtl) p.nextMonth();
5180 else p.prevMonth();
5181 }
5182 else {
5183 p.resetMonth();
5184 }
5185 }
5186
5187 // Allow click
5188 setTimeout(function () {
5189 allowItemClick = true;
5190 }, 100);
5191 }
5192
5193 function handleDayClick(e) {
5194 if (!allowItemClick) return;
5195 var day = $(e.target).parents('.picker-calendar-day');
5196 if (day.length === 0 && $(e.target).hasClass('picker-calendar-day')) {
5197 day = $(e.target);
5198 }
5199 if (day.length === 0) return;
5200 // if (day.hasClass('picker-calendar-day-selected') && !p.params.multiple) return;
5201 if (day.hasClass('picker-calendar-day-disabled')) return;
5202 if (day.hasClass('picker-calendar-day-next')) p.nextMonth();
5203 if (day.hasClass('picker-calendar-day-prev')) p.prevMonth();
5204 var dateYear = day.attr('data-year');
5205 var dateMonth = day.attr('data-month');
5206 var dateDay = day.attr('data-day');
5207 if (p.params.onDayClick) {
5208 p.params.onDayClick(p, day[0], dateYear, dateMonth, dateDay);
5209 }
5210 p.addValue(new Date(dateYear, dateMonth, dateDay).getTime());
5211 if (p.params.closeOnSelect && !p.params.multiple) p.close();
5212 }
5213
5214 p.container.find('.picker-calendar-prev-month').on('click', p.prevMonth);
5215 p.container.find('.picker-calendar-next-month').on('click', p.nextMonth);
5216 p.container.find('.picker-calendar-prev-year').on('click', p.prevYear);
5217 p.container.find('.picker-calendar-next-year').on('click', p.nextYear);
5218 p.wrapper.on('click', handleDayClick);
5219 if (p.params.touchMove) {
5220 p.wrapper.on($.touchEvents.start, handleTouchStart);
5221 p.wrapper.on($.touchEvents.move, handleTouchMove);
5222 p.wrapper.on($.touchEvents.end, handleTouchEnd);
5223 }
5224
5225 p.container[0].f7DestroyCalendarEvents = function () {
5226 p.container.find('.picker-calendar-prev-month').off('click', p.prevMonth);
5227 p.container.find('.picker-calendar-next-month').off('click', p.nextMonth);
5228 p.container.find('.picker-calendar-prev-year').off('click', p.prevYear);
5229 p.container.find('.picker-calendar-next-year').off('click', p.nextYear);
5230 p.wrapper.off('click', handleDayClick);
5231 if (p.params.touchMove) {
5232 p.wrapper.off($.touchEvents.start, handleTouchStart);
5233 p.wrapper.off($.touchEvents.move, handleTouchMove);
5234 p.wrapper.off($.touchEvents.end, handleTouchEnd);
5235 }
5236 };
5237
5238
5239 };
5240 p.destroyCalendarEvents = function (colContainer) {
5241 if ('f7DestroyCalendarEvents' in p.container[0]) p.container[0].f7DestroyCalendarEvents();
5242 };
5243
5244 // Calendar Methods
5245 p.daysInMonth = function (date) {
5246 var d = new Date(date);
5247 return new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate();
5248 };
5249 p.monthHTML = function (date, offset) {
5250 date = new Date(date);
5251 var year = date.getFullYear(),
5252 month = date.getMonth(),
5253 day = date.getDate();
5254 if (offset === 'next') {
5255 if (month === 11) date = new Date(year + 1, 0);
5256 else date = new Date(year, month + 1, 1);
5257 }
5258 if (offset === 'prev') {
5259 if (month === 0) date = new Date(year - 1, 11);
5260 else date = new Date(year, month - 1, 1);
5261 }
5262 if (offset === 'next' || offset === 'prev') {
5263 month = date.getMonth();
5264 year = date.getFullYear();
5265 }
5266 var daysInPrevMonth = p.daysInMonth(new Date(date.getFullYear(), date.getMonth()).getTime() - 10 * 24 * 60 * 60 * 1000),
5267 daysInMonth = p.daysInMonth(date),
5268 firstDayOfMonthIndex = new Date(date.getFullYear(), date.getMonth()).getDay();
5269 if (firstDayOfMonthIndex === 0) firstDayOfMonthIndex = 7;
5270
5271 var dayDate, currentValues = [], i, j,
5272 rows = 6, cols = 7,
5273 monthHTML = '',
5274 dayIndex = 0 + (p.params.firstDay - 1),
5275 today = new Date().setHours(0,0,0,0),
5276 minDate = p.params.minDate ? new Date(p.params.minDate).getTime() : null,
5277 maxDate = p.params.maxDate ? new Date(p.params.maxDate).getTime() : null;
5278
5279 if (p.value && p.value.length) {
5280 for (i = 0; i < p.value.length; i++) {
5281 currentValues.push(new Date(p.value[i]).setHours(0,0,0,0));
5282 }
5283 }
5284
5285 for (i = 1; i <= rows; i++) {
5286 var rowHTML = '';
5287 var row = i;
5288 for (j = 1; j <= cols; j++) {
5289 var col = j;
5290 dayIndex ++;
5291 var dayNumber = dayIndex - firstDayOfMonthIndex;
5292 var addClass = '';
5293 if (dayNumber < 0) {
5294 dayNumber = daysInPrevMonth + dayNumber + 1;
5295 addClass += ' picker-calendar-day-prev';
5296 dayDate = new Date(month - 1 < 0 ? year - 1 : year, month - 1 < 0 ? 11 : month - 1, dayNumber).getTime();
5297 }
5298 else {
5299 dayNumber = dayNumber + 1;
5300 if (dayNumber > daysInMonth) {
5301 dayNumber = dayNumber - daysInMonth;
5302 addClass += ' picker-calendar-day-next';
5303 dayDate = new Date(month + 1 > 11 ? year + 1 : year, month + 1 > 11 ? 0 : month + 1, dayNumber).getTime();
5304 }
5305 else {
5306 dayDate = new Date(year, month, dayNumber).getTime();
5307 }
5308 }
5309 // Today
5310 if (dayDate === today) addClass += ' picker-calendar-day-today';
5311 // Selected
5312 if (currentValues.indexOf(dayDate) >= 0) addClass += ' picker-calendar-day-selected';
5313 // Weekend
5314 if (p.params.weekendDays.indexOf(col - 1) >= 0) {
5315 addClass += ' picker-calendar-day-weekend';
5316 }
5317 // Disabled
5318 if ((minDate && dayDate < minDate) || (maxDate && dayDate > maxDate)) {
5319 addClass += ' picker-calendar-day-disabled';
5320 }
5321
5322 dayDate = new Date(dayDate);
5323 var dayYear = dayDate.getFullYear();
5324 var dayMonth = dayDate.getMonth();
5325 rowHTML += '<div data-year="' + dayYear + '" data-month="' + dayMonth + '" data-day="' + dayNumber + '" class="picker-calendar-day' + (addClass) + '" data-date="' + (dayYear + '-' + dayMonth + '-' + dayNumber) + '"><span>'+dayNumber+'</span></div>';
5326 }
5327 monthHTML += '<div class="picker-calendar-row">' + rowHTML + '</div>';
5328 }
5329 monthHTML = '<div class="picker-calendar-month" data-year="' + year + '" data-month="' + month + '">' + monthHTML + '</div>';
5330 return monthHTML;
5331 };
5332 p.animating = false;
5333 p.updateCurrentMonthYear = function (dir) {
5334 if (typeof dir === 'undefined') {
5335 p.currentMonth = parseInt(p.months.eq(1).attr('data-month'), 10);
5336 p.currentYear = parseInt(p.months.eq(1).attr('data-year'), 10);
5337 }
5338 else {
5339 p.currentMonth = parseInt(p.months.eq(dir === 'next' ? (p.months.length - 1) : 0).attr('data-month'), 10);
5340 p.currentYear = parseInt(p.months.eq(dir === 'next' ? (p.months.length - 1) : 0).attr('data-year'), 10);
5341 }
5342 p.container.find('.current-month-value').text(p.params.monthNames[p.currentMonth]);
5343 p.container.find('.current-year-value').text(p.currentYear);
5344
5345 };
5346 p.onMonthChangeStart = function (dir) {
5347 p.updateCurrentMonthYear(dir);
5348 p.months.removeClass('picker-calendar-month-current picker-calendar-month-prev picker-calendar-month-next');
5349 var currentIndex = dir === 'next' ? p.months.length - 1 : 0;
5350
5351 p.months.eq(currentIndex).addClass('picker-calendar-month-current');
5352 p.months.eq(dir === 'next' ? currentIndex - 1 : currentIndex + 1).addClass(dir === 'next' ? 'picker-calendar-month-prev' : 'picker-calendar-month-next');
5353
5354 if (p.params.onMonthYearChangeStart) {
5355 p.params.onMonthYearChangeStart(p, p.currentYear, p.currentMonth);
5356 }
5357 };
5358 p.onMonthChangeEnd = function (dir, rebuildBoth) {
5359 p.animating = false;
5360 var nextMonthHTML, prevMonthHTML, newMonthHTML;
5361 p.wrapper.find('.picker-calendar-month:not(.picker-calendar-month-prev):not(.picker-calendar-month-current):not(.picker-calendar-month-next)').remove();
5362
5363 if (typeof dir === 'undefined') {
5364 dir = 'next';
5365 rebuildBoth = true;
5366 }
5367 if (!rebuildBoth) {
5368 newMonthHTML = p.monthHTML(new Date(p.currentYear, p.currentMonth), dir);
5369 }
5370 else {
5371 p.wrapper.find('.picker-calendar-month-next, .picker-calendar-month-prev').remove();
5372 prevMonthHTML = p.monthHTML(new Date(p.currentYear, p.currentMonth), 'prev');
5373 nextMonthHTML = p.monthHTML(new Date(p.currentYear, p.currentMonth), 'next');
5374 }
5375 if (dir === 'next' || rebuildBoth) {
5376 p.wrapper.append(newMonthHTML || nextMonthHTML);
5377 }
5378 if (dir === 'prev' || rebuildBoth) {
5379 p.wrapper.prepend(newMonthHTML || prevMonthHTML);
5380 }
5381 p.months = p.wrapper.find('.picker-calendar-month');
5382 p.setMonthsTranslate(p.monthsTranslate);
5383 if (p.params.onMonthAdd) {
5384 p.params.onMonthAdd(p, dir === 'next' ? p.months.eq(p.months.length - 1)[0] : p.months.eq(0)[0]);
5385 }
5386 if (p.params.onMonthYearChangeEnd) {
5387 p.params.onMonthYearChangeEnd(p, p.currentYear, p.currentMonth);
5388 }
5389 };
5390 p.setMonthsTranslate = function (translate) {
5391 translate = translate || p.monthsTranslate || 0;
5392 if (typeof p.monthsTranslate === 'undefined') p.monthsTranslate = translate;
5393 p.months.removeClass('picker-calendar-month-current picker-calendar-month-prev picker-calendar-month-next');
5394 var prevMonthTranslate = -(translate + 1) * 100 * inverter;
5395 var currentMonthTranslate = -translate * 100 * inverter;
5396 var nextMonthTranslate = -(translate - 1) * 100 * inverter;
5397 p.months.eq(0).transform('translate3d(' + (p.isH ? prevMonthTranslate : 0) + '%, ' + (p.isH ? 0 : prevMonthTranslate) + '%, 0)').addClass('picker-calendar-month-prev');
5398 p.months.eq(1).transform('translate3d(' + (p.isH ? currentMonthTranslate : 0) + '%, ' + (p.isH ? 0 : currentMonthTranslate) + '%, 0)').addClass('picker-calendar-month-current');
5399 p.months.eq(2).transform('translate3d(' + (p.isH ? nextMonthTranslate : 0) + '%, ' + (p.isH ? 0 : nextMonthTranslate) + '%, 0)').addClass('picker-calendar-month-next');
5400 };
5401 p.nextMonth = function (transition) {
5402 if (typeof transition === 'undefined' || typeof transition === 'object') {
5403 transition = '';
5404 if (!p.params.animate) transition = 0;
5405 }
5406 var nextMonth = parseInt(p.months.eq(p.months.length - 1).attr('data-month'), 10);
5407 var nextYear = parseInt(p.months.eq(p.months.length - 1).attr('data-year'), 10);
5408 var nextDate = new Date(nextYear, nextMonth);
5409 var nextDateTime = nextDate.getTime();
5410 var transitionEndCallback = p.animating ? false : true;
5411 if (p.params.maxDate) {
5412 if (nextDateTime > new Date(p.params.maxDate).getTime()) {
5413 return p.resetMonth();
5414 }
5415 }
5416 p.monthsTranslate --;
5417 if (nextMonth === p.currentMonth) {
5418 var nextMonthTranslate = -(p.monthsTranslate) * 100 * inverter;
5419 var nextMonthHTML = $(p.monthHTML(nextDateTime, 'next')).transform('translate3d(' + (p.isH ? nextMonthTranslate : 0) + '%, ' + (p.isH ? 0 : nextMonthTranslate) + '%, 0)').addClass('picker-calendar-month-next');
5420 p.wrapper.append(nextMonthHTML[0]);
5421 p.months = p.wrapper.find('.picker-calendar-month');
5422 if (p.params.onMonthAdd) {
5423 p.params.onMonthAdd(p, p.months.eq(p.months.length - 1)[0]);
5424 }
5425 }
5426 p.animating = true;
5427 p.onMonthChangeStart('next');
5428 var translate = (p.monthsTranslate * 100) * inverter;
5429
5430 p.wrapper.transition(transition).transform('translate3d(' + (p.isH ? translate : 0) + '%, ' + (p.isH ? 0 : translate) + '%, 0)');
5431 if (transitionEndCallback) {
5432 p.wrapper.transitionEnd(function () {
5433 p.onMonthChangeEnd('next');
5434 });
5435 }
5436 if (!p.params.animate) {
5437 p.onMonthChangeEnd('next');
5438 }
5439 };
5440 p.prevMonth = function (transition) {
5441 if (typeof transition === 'undefined' || typeof transition === 'object') {
5442 transition = '';
5443 if (!p.params.animate) transition = 0;
5444 }
5445 var prevMonth = parseInt(p.months.eq(0).attr('data-month'), 10);
5446 var prevYear = parseInt(p.months.eq(0).attr('data-year'), 10);
5447 var prevDate = new Date(prevYear, prevMonth + 1, -1);
5448 var prevDateTime = prevDate.getTime();
5449 var transitionEndCallback = p.animating ? false : true;
5450 if (p.params.minDate) {
5451 if (prevDateTime < new Date(p.params.minDate).getTime()) {
5452 return p.resetMonth();
5453 }
5454 }
5455 p.monthsTranslate ++;
5456 if (prevMonth === p.currentMonth) {
5457 var prevMonthTranslate = -(p.monthsTranslate) * 100 * inverter;
5458 var prevMonthHTML = $(p.monthHTML(prevDateTime, 'prev')).transform('translate3d(' + (p.isH ? prevMonthTranslate : 0) + '%, ' + (p.isH ? 0 : prevMonthTranslate) + '%, 0)').addClass('picker-calendar-month-prev');
5459 p.wrapper.prepend(prevMonthHTML[0]);
5460 p.months = p.wrapper.find('.picker-calendar-month');
5461 if (p.params.onMonthAdd) {
5462 p.params.onMonthAdd(p, p.months.eq(0)[0]);
5463 }
5464 }
5465 p.animating = true;
5466 p.onMonthChangeStart('prev');
5467 var translate = (p.monthsTranslate * 100) * inverter;
5468 p.wrapper.transition(transition).transform('translate3d(' + (p.isH ? translate : 0) + '%, ' + (p.isH ? 0 : translate) + '%, 0)');
5469 if (transitionEndCallback) {
5470 p.wrapper.transitionEnd(function () {
5471 p.onMonthChangeEnd('prev');
5472 });
5473 }
5474 if (!p.params.animate) {
5475 p.onMonthChangeEnd('prev');
5476 }
5477 };
5478 p.resetMonth = function (transition) {
5479 if (typeof transition === 'undefined') transition = '';
5480 var translate = (p.monthsTranslate * 100) * inverter;
5481 p.wrapper.transition(transition).transform('translate3d(' + (p.isH ? translate : 0) + '%, ' + (p.isH ? 0 : translate) + '%, 0)');
5482 };
5483 p.setYearMonth = function (year, month, transition) {
5484 if (typeof year === 'undefined') year = p.currentYear;
5485 if (typeof month === 'undefined') month = p.currentMonth;
5486 if (typeof transition === 'undefined' || typeof transition === 'object') {
5487 transition = '';
5488 if (!p.params.animate) transition = 0;
5489 }
5490 var targetDate;
5491 if (year < p.currentYear) {
5492 targetDate = new Date(year, month + 1, -1).getTime();
5493 }
5494 else {
5495 targetDate = new Date(year, month).getTime();
5496 }
5497 if (p.params.maxDate && targetDate > new Date(p.params.maxDate).getTime()) {
5498 return false;
5499 }
5500 if (p.params.minDate && targetDate < new Date(p.params.minDate).getTime()) {
5501 return false;
5502 }
5503 var currentDate = new Date(p.currentYear, p.currentMonth).getTime();
5504 var dir = targetDate > currentDate ? 'next' : 'prev';
5505 var newMonthHTML = p.monthHTML(new Date(year, month));
5506 p.monthsTranslate = p.monthsTranslate || 0;
5507 var prevTranslate = p.monthsTranslate;
5508 var monthTranslate, wrapperTranslate;
5509 var transitionEndCallback = p.animating ? false : true;
5510 if (targetDate > currentDate) {
5511 // To next
5512 p.monthsTranslate --;
5513 if (!p.animating) p.months.eq(p.months.length - 1).remove();
5514 p.wrapper.append(newMonthHTML);
5515 p.months = p.wrapper.find('.picker-calendar-month');
5516 monthTranslate = -(prevTranslate - 1) * 100 * inverter;
5517 p.months.eq(p.months.length - 1).transform('translate3d(' + (p.isH ? monthTranslate : 0) + '%, ' + (p.isH ? 0 : monthTranslate) + '%, 0)').addClass('picker-calendar-month-next');
5518 }
5519 else {
5520 // To prev
5521 p.monthsTranslate ++;
5522 if (!p.animating) p.months.eq(0).remove();
5523 p.wrapper.prepend(newMonthHTML);
5524 p.months = p.wrapper.find('.picker-calendar-month');
5525 monthTranslate = -(prevTranslate + 1) * 100 * inverter;
5526 p.months.eq(0).transform('translate3d(' + (p.isH ? monthTranslate : 0) + '%, ' + (p.isH ? 0 : monthTranslate) + '%, 0)').addClass('picker-calendar-month-prev');
5527 }
5528 if (p.params.onMonthAdd) {
5529 p.params.onMonthAdd(p, dir === 'next' ? p.months.eq(p.months.length - 1)[0] : p.months.eq(0)[0]);
5530 }
5531 p.animating = true;
5532 p.onMonthChangeStart(dir);
5533 wrapperTranslate = (p.monthsTranslate * 100) * inverter;
5534 p.wrapper.transition(transition).transform('translate3d(' + (p.isH ? wrapperTranslate : 0) + '%, ' + (p.isH ? 0 : wrapperTranslate) + '%, 0)');
5535 if (transitionEndCallback) {
5536 p.wrapper.transitionEnd(function () {
5537 p.onMonthChangeEnd(dir, true);
5538 });
5539 }
5540 if (!p.params.animate) {
5541 p.onMonthChangeEnd(dir);
5542 }
5543 };
5544 p.nextYear = function () {
5545 p.setYearMonth(p.currentYear + 1);
5546 };
5547 p.prevYear = function () {
5548 p.setYearMonth(p.currentYear - 1);
5549 };
5550
5551
5552 // HTML Layout
5553 p.layout = function () {
5554 var pickerHTML = '';
5555 var pickerClass = '';
5556 var i;
5557
5558 var layoutDate = p.value && p.value.length ? p.value[0] : new Date().setHours(0,0,0,0);
5559 var prevMonthHTML = p.monthHTML(layoutDate, 'prev');
5560 var currentMonthHTML = p.monthHTML(layoutDate);
5561 var nextMonthHTML = p.monthHTML(layoutDate, 'next');
5562 var monthsHTML = '<div class="picker-calendar-months"><div class="picker-calendar-months-wrapper">' + (prevMonthHTML + currentMonthHTML + nextMonthHTML) + '</div></div>';
5563 // Week days header
5564 var weekHeaderHTML = '';
5565 if (p.params.weekHeader) {
5566 for (i = 0; i < 7; i++) {
5567 var weekDayIndex = (i + p.params.firstDay > 6) ? (i - 7 + p.params.firstDay) : (i + p.params.firstDay);
5568 var dayName = p.params.dayNamesShort[weekDayIndex];
5569 weekHeaderHTML += '<div class="picker-calendar-week-day ' + ((p.params.weekendDays.indexOf(weekDayIndex) >= 0) ? 'picker-calendar-week-day-weekend' : '') + '"> ' + dayName + '</div>';
5570
5571 }
5572 weekHeaderHTML = '<div class="picker-calendar-week-days">' + weekHeaderHTML + '</div>';
5573 }
5574 pickerClass = 'weui-picker-calendar ' + (p.params.cssClass || '');
5575 if(!p.inline) pickerClass = 'weui-picker-modal ' + pickerClass;
5576 var toolbarHTML = p.params.toolbar ? p.params.toolbarTemplate.replace(/{{closeText}}/g, p.params.toolbarCloseText) : '';
5577 if (p.params.toolbar) {
5578 toolbarHTML = p.params.toolbarTemplate
5579 .replace(/{{closeText}}/g, p.params.toolbarCloseText)
5580 .replace(/{{monthPicker}}/g, (p.params.monthPicker ? p.params.monthPickerTemplate : ''))
5581 .replace(/{{yearPicker}}/g, (p.params.yearPicker ? p.params.yearPickerTemplate : ''));
5582 }
5583
5584 pickerHTML =
5585 '<div class="' + (pickerClass) + '">' +
5586 toolbarHTML +
5587 '<div class="picker-modal-inner">' +
5588 weekHeaderHTML +
5589 monthsHTML +
5590 '</div>' +
5591 '</div>';
5592
5593
5594 p.pickerHTML = pickerHTML;
5595 };
5596
5597 // Input Events
5598 function openOnInput(e) {
5599 e.preventDefault();
5600 if (p.opened) return;
5601 p.open();
5602 if (p.params.scrollToInput && !isPopover()) {
5603 var pageContent = p.input.parents('.page-content');
5604 if (pageContent.length === 0) return;
5605
5606 var paddingTop = parseInt(pageContent.css('padding-top'), 10),
5607 paddingBottom = parseInt(pageContent.css('padding-bottom'), 10),
5608 pageHeight = pageContent[0].offsetHeight - paddingTop - p.container.height(),
5609 pageScrollHeight = pageContent[0].scrollHeight - paddingTop - p.container.height(),
5610 newPaddingBottom;
5611
5612 var inputTop = p.input.offset().top - paddingTop + p.input[0].offsetHeight;
5613 if (inputTop > pageHeight) {
5614 var scrollTop = pageContent.scrollTop() + inputTop - pageHeight;
5615 if (scrollTop + pageHeight > pageScrollHeight) {
5616 newPaddingBottom = scrollTop + pageHeight - pageScrollHeight + paddingBottom;
5617 if (pageHeight === pageScrollHeight) {
5618 newPaddingBottom = p.container.height();
5619 }
5620 pageContent.css({'padding-bottom': (newPaddingBottom) + 'px'});
5621 }
5622 pageContent.scrollTop(scrollTop, 300);
5623 }
5624 }
5625 }
5626 function closeOnHTMLClick(e) {
5627 if (inPopover()) return;
5628 if (p.input && p.input.length > 0) {
5629 if (e.target !== p.input[0] && $(e.target).parents('.weui-picker-modal').length === 0) p.close();
5630 }
5631 else {
5632 if ($(e.target).parents('.weui-picker-modal').length === 0) p.close();
5633 }
5634 }
5635
5636 if (p.params.input) {
5637 p.input = $(p.params.input);
5638 if (p.input.length > 0) {
5639 if (p.params.inputReadOnly) p.input.prop('readOnly', true);
5640 if (!p.inline) {
5641 p.input.on('click', openOnInput);
5642 }
5643 if (p.params.inputReadOnly) {
5644 p.input.on('focus mousedown', function (e) {
5645 e.preventDefault();
5646 });
5647 }
5648 }
5649
5650 }
5651
5652 //iphone 上无法正确触发 click,会导致点击外面无法关闭
5653 if (!p.inline) $(document).on('click touchend', closeOnHTMLClick);
5654
5655 // Open
5656 function onPickerClose() {
5657 p.opened = false;
5658 if (p.input && p.input.length > 0) p.input.parents('.page-content').css({'padding-bottom': ''});
5659 if (p.params.onClose) p.params.onClose(p);
5660
5661 // Destroy events
5662 p.destroyCalendarEvents();
5663 }
5664
5665 p.opened = false;
5666 p.open = function () {
5667 var toPopover = isPopover() && false;
5668 var updateValue = false;
5669 if (!p.opened) {
5670 // Set date value
5671 if (!p.value) {
5672 if (p.params.value) {
5673 p.value = p.params.value;
5674 updateValue = true;
5675 }
5676 }
5677
5678 // Layout
5679 p.layout();
5680
5681 // Append
5682 if (toPopover) {
5683 p.pickerHTML = '<div class="popover popover-picker-calendar"><div class="popover-inner">' + p.pickerHTML + '</div></div>';
5684 p.popover = $.popover(p.pickerHTML, p.params.input, true);
5685 p.container = $(p.popover).find('.weui-picker-modal');
5686 $(p.popover).on('close', function () {
5687 onPickerClose();
5688 });
5689 }
5690 else if (p.inline) {
5691 p.container = $(p.pickerHTML);
5692 p.container.addClass('picker-modal-inline');
5693 $(p.params.container).append(p.container);
5694 }
5695 else {
5696 p.container = $($.openPicker(p.pickerHTML));
5697 $(p.container)
5698 .on('close', function () {
5699 onPickerClose();
5700 });
5701 }
5702
5703 // Store calendar instance
5704 p.container[0].f7Calendar = p;
5705 p.wrapper = p.container.find('.picker-calendar-months-wrapper');
5706
5707 // Months
5708 p.months = p.wrapper.find('.picker-calendar-month');
5709
5710 // Update current month and year
5711 p.updateCurrentMonthYear();
5712
5713 // Set initial translate
5714 p.monthsTranslate = 0;
5715 p.setMonthsTranslate();
5716
5717 // Init events
5718 p.initCalendarEvents();
5719
5720 // Update input value
5721 if (updateValue) p.updateValue();
5722
5723 }
5724
5725 // Set flag
5726 p.opened = true;
5727 p.initialized = true;
5728 if (p.params.onMonthAdd) {
5729 p.months.each(function () {
5730 p.params.onMonthAdd(p, this);
5731 });
5732 }
5733 if (p.params.onOpen) p.params.onOpen(p);
5734 };
5735
5736 // Close
5737 p.close = function () {
5738 if (!p.opened || p.inline) return;
5739 p.animating = false; //有可能还有动画没做完,因此animating设置还没改。
5740 if (inPopover()) {
5741 $.closePicker(p.popover);
5742 return;
5743 }
5744 else {
5745 $.closePicker(p.container);
5746 return;
5747 }
5748 };
5749
5750 // Destroy
5751 p.destroy = function () {
5752 p.close();
5753 if (p.params.input && p.input.length > 0) {
5754 p.input.off('click focus', openOnInput);
5755 p.input.data("calendar", null);
5756 }
5757 $('html').off('click', closeOnHTMLClick);
5758 };
5759
5760 if (p.inline) {
5761 p.open();
5762 }
5763
5764 return p;
5765 };
5766
5767 var format = function(d) {
5768 return d < 10 ? "0"+d : d;
5769 }
5770
5771
5772 $.fn.calendar = function (params, args) {
5773 params = params || {};
5774 return this.each(function() {
5775 var $this = $(this);
5776 if(!$this[0]) return;
5777 var p = {};
5778 if($this[0].tagName.toUpperCase() === "INPUT") {
5779 p.input = $this;
5780 } else {
5781 p.container = $this;
5782 }
5783
5784 var calendar = $this.data("calendar");
5785
5786 if(!calendar) {
5787 if(typeof params === typeof "a") {
5788 } else {
5789 if(!params.value && $this.val()) params.value = [$this.val()];
5790 //默认显示今天
5791 if(!params.value) {
5792 var today = new Date();
5793 params.value = [today.getFullYear() + "/" + format(today.getMonth() + 1) + "/" + format(today.getDate())];
5794 }
5795 calendar = $this.data("calendar", new Calendar($.extend(p, params)));
5796 }
5797 }
5798
5799 if(typeof params === typeof "a") {
5800 calendar[params].call(calendar, args);
5801 }
5802 });
5803 };
5804
5805 defaults = $.fn.calendar.prototype.defaults = {
5806 value: undefined, // 通过JS赋值,注意是数组
5807 monthNames: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
5808 monthNamesShort: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
5809 dayNames: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
5810 dayNamesShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
5811 firstDay: 1, // First day of the week, Monday
5812 weekendDays: [0, 6], // Sunday and Saturday
5813 multiple: false,
5814 dateFormat: 'yyyy/mm/dd',
5815 direction: 'horizontal', // or 'vertical'
5816 minDate: null,
5817 maxDate: null,
5818 touchMove: true,
5819 animate: true,
5820 closeOnSelect: true,
5821 monthPicker: true,
5822 monthPickerTemplate:
5823 '<div class="picker-calendar-month-picker">' +
5824 '<a href="javascript:;" class="link icon-only picker-calendar-prev-month"><i class="icon icon-prev"></i></a>' +
5825 '<div class="current-month-value"></div>' +
5826 '<a href="javascript:;" class="link icon-only picker-calendar-next-month"><i class="icon icon-next"></i></a>' +
5827 '</div>',
5828 yearPicker: true,
5829 yearPickerTemplate:
5830 '<div class="picker-calendar-year-picker">' +
5831 '<a href="javascript:;" class="link icon-only picker-calendar-prev-year"><i class="icon icon-prev"></i></a>' +
5832 '<span class="current-year-value"></span>' +
5833 '<a href="javascript:;" class="link icon-only picker-calendar-next-year"><i class="icon icon-next"></i></a>' +
5834 '</div>',
5835 weekHeader: true,
5836 // Common settings
5837 scrollToInput: true,
5838 inputReadOnly: true,
5839 convertToPopover: true,
5840 onlyInPopover: false,
5841 toolbar: true,
5842 toolbarCloseText: 'Done',
5843 toolbarTemplate:
5844 '<div class="toolbar">' +
5845 '<div class="toolbar-inner">' +
5846 '{{yearPicker}}' +
5847 '{{monthPicker}}' +
5848 // '<a href="#" class="link close-picker">{{closeText}}</a>' +
5849 '</div>' +
5850 '</div>',
5851 /* Callbacks
5852 onMonthAdd
5853 onChange
5854 onOpen
5855 onClose
5856 onDayClick
5857 onMonthYearChangeStart
5858 onMonthYearChangeEnd
5859 */
5860 };
5861
5862}($);
5863
5864/* global $:true */
5865/* jshint unused:false*/
5866
5867+ function($) {
5868 "use strict";
5869
5870
5871 var defaults;
5872
5873 var formatNumber = function (n) {
5874 return n < 10 ? "0" + n : n;
5875 }
5876
5877 var Datetime = function(input, params) {
5878 this.input = $(input);
5879 this.params = params || {};
5880
5881 this.initMonthes = params.monthes
5882
5883 this.initYears = params.years
5884
5885 var p = $.extend({}, params, this.getConfig());
5886 $(this.input).picker(p);
5887 }
5888
5889 Datetime.prototype = {
5890 getDays : function(max) {
5891 var days = [];
5892 for(var i=1; i<= (max||31);i++) {
5893 days.push(i < 10 ? "0"+i : i);
5894 }
5895 return days;
5896 },
5897
5898 getDaysByMonthAndYear : function(month, year) {
5899 var int_d = new Date(year, parseInt(month)+1-1, 1);
5900 var d = new Date(int_d - 1);
5901 return this.getDays(d.getDate());
5902 },
5903 getConfig: function() {
5904 var today = new Date(),
5905 params = this.params,
5906 self = this,
5907 lastValidValues;
5908
5909 var config = {
5910 rotateEffect: false, //为了性能
5911 cssClass: 'datetime-picker',
5912
5913 value: [today.getFullYear(), formatNumber(today.getMonth()+1), formatNumber(today.getDate()), formatNumber(today.getHours()), (formatNumber(today.getMinutes()))],
5914
5915 onChange: function (picker, values, displayValues) {
5916 var cols = picker.cols;
5917 var days = self.getDaysByMonthAndYear(values[1], values[0]);
5918 var currentValue = values[2];
5919 if(currentValue > days.length) currentValue = days.length;
5920 picker.cols[4].setValue(currentValue);
5921
5922 //check min and max
5923 var current = new Date(values[0]+'-'+values[1]+'-'+values[2]);
5924 var valid = true;
5925 if(params.min) {
5926 var min = new Date(typeof params.min === "function" ? params.min() : params.min);
5927
5928 if(current < +min) {
5929 picker.setValue(lastValidValues);
5930 valid = false;
5931 }
5932 }
5933 if(params.max) {
5934 var max = new Date(typeof params.max === "function" ? params.max() : params.max);
5935 if(current > +max) {
5936 picker.setValue(lastValidValues);
5937 valid = false;
5938 }
5939 }
5940
5941 valid && (lastValidValues = values);
5942
5943 if (self.params.onChange) {
5944 self.params.onChange.apply(this, arguments);
5945 }
5946 },
5947
5948 formatValue: function (p, values, displayValues) {
5949 return self.params.format(p, values, displayValues);
5950 },
5951
5952 cols: [
5953 {
5954 values: this.initYears
5955 },
5956 {
5957 divider: true, // 这是一个分隔符
5958 content: params.yearSplit
5959 },
5960 {
5961 values: this.initMonthes
5962 },
5963 {
5964 divider: true, // 这是一个分隔符
5965 content: params.monthSplit
5966 },
5967 {
5968 values: (function () {
5969 var dates = [];
5970 for (var i=1; i<=31; i++) dates.push(formatNumber(i));
5971 return dates;
5972 })()
5973 },
5974
5975 ]
5976 }
5977
5978 if (params.dateSplit) {
5979 config.cols.push({
5980 divider: true,
5981 content: params.dateSplit
5982 })
5983 }
5984
5985 config.cols.push({
5986 divider: true,
5987 content: params.datetimeSplit
5988 })
5989
5990 var times = self.params.times();
5991 if (times && times.length) {
5992 config.cols = config.cols.concat(times);
5993 }
5994
5995 var inputValue = this.input.val();
5996 if(inputValue) config.value = params.parse(inputValue);
5997 if(this.params.value) {
5998 this.input.val(this.params.value);
5999 config.value = params.parse(this.params.value);
6000 }
6001
6002 return config;
6003 }
6004 }
6005
6006 $.fn.datetimePicker = function(params) {
6007 params = $.extend({}, defaults, params);
6008 return this.each(function() {
6009 if(!this) return;
6010 var $this = $(this);
6011 var datetime = $this.data("datetime");
6012 if(!datetime) $this.data("datetime", new Datetime(this, params));
6013 return datetime;
6014 });
6015 };
6016
6017 defaults = $.fn.datetimePicker.prototype.defaults = {
6018 input: undefined, // 默认值
6019 min: undefined, // YYYY-MM-DD 最大最小值只比较年月日,不比较时分秒
6020 max: undefined, // YYYY-MM-DD
6021 yearSplit: '-',
6022 monthSplit: '-',
6023 dateSplit: '', // 默认为空
6024 datetimeSplit: ' ', // 日期和时间之间的分隔符,不可为空
6025 monthes: ('01 02 03 04 05 06 07 08 09 10 11 12').split(' '),
6026 years: (function () {
6027 var arr = [];
6028 for (var i = 1950; i <= 2030; i++) { arr.push(i); }
6029 return arr;
6030 })(),
6031 times: function () {
6032 return [ // 自定义的时间
6033 {
6034 values: (function () {
6035 var hours = [];
6036 for (var i=0; i<24; i++) hours.push(formatNumber(i));
6037 return hours;
6038 })()
6039 },
6040 {
6041 divider: true, // 这是一个分隔符
6042 content: ':'
6043 },
6044 {
6045 values: (function () {
6046 var minutes = [];
6047 for (var i=0; i<60; i++) minutes.push(formatNumber(i));
6048 return minutes;
6049 })()
6050 }
6051 ];
6052 },
6053 format: function (p, values) { // 数组转换成字符串
6054 return p.cols.map(function (col) {
6055 return col.value || col.content;
6056 }).join('');
6057 },
6058 parse: function (str) {
6059 // 把字符串转换成数组,用来解析初始值
6060 // 如果你的定制的初始值格式无法被这个默认函数解析,请自定义这个函数。比如你的时间是 '子时' 那么默认情况这个'时'会被当做分隔符而导致错误,所以你需要自己定义parse函数
6061 // 默认兼容的分隔符
6062 var t = str.split(this.datetimeSplit);
6063 return t[0].split(/\D/).concat(t[1].split(/:|时|分|秒/)).filter(function (d) {
6064 return !!d;
6065 })
6066 }
6067 }
6068
6069}($);
6070
6071/*======================================================
6072************ Picker ************
6073======================================================*/
6074/* global $:true */
6075
6076+ function($) {
6077 "use strict";
6078
6079
6080 //Popup 和 picker 之类的不要共用一个弹出方法,因为这样会导致 在 popup 中再弹出 picker 的时候会有问题。
6081
6082 $.openPopup = function(popup, className) {
6083
6084 $.closePopup();
6085
6086 popup = $(popup);
6087 popup.show();
6088 popup.width();
6089 popup.addClass("weui-popup__container--visible");
6090 var modal = popup.find(".weui-popup__modal");
6091 modal.width();
6092 modal.transitionEnd(function() {
6093 modal.trigger("open");
6094 });
6095 }
6096
6097
6098 $.closePopup = function(container, remove) {
6099 container = $(container || ".weui-popup__container--visible");
6100 container.find('.weui-popup__modal').transitionEnd(function() {
6101 var $this = $(this);
6102 $this.trigger("close");
6103 container.hide();
6104 remove && container.remove();
6105 })
6106 container.removeClass("weui-popup__container--visible")
6107 };
6108
6109
6110 $(document).on("click", ".close-popup, .weui-popup__overlay", function() {
6111 $.closePopup();
6112 })
6113 .on("click", ".open-popup", function() {
6114 $($(this).data("target")).popup();
6115 })
6116 .on("click", ".weui-popup__container", function(e) {
6117 if($(e.target).hasClass("weui-popup__container")) $.closePopup();
6118 })
6119
6120 $.fn.popup = function() {
6121 return this.each(function() {
6122 $.openPopup(this);
6123 });
6124 };
6125
6126}($);
6127
6128/* ===============================================================================
6129************ Notification ************
6130=============================================================================== */
6131/* global $:true */
6132+function ($) {
6133 "use strict";
6134
6135 var noti, defaults, timeout, start, diffX, diffY;
6136
6137 var touchStart = function(e) {
6138 var p = $.getTouchPosition(e);
6139 start = p;
6140 diffX = diffY = 0;
6141 noti.addClass("touching");
6142 };
6143 var touchMove = function(e) {
6144 if(!start) return false;
6145 e.preventDefault();
6146 e.stopPropagation();
6147 var p = $.getTouchPosition(e);
6148 diffX = p.x - start.x;
6149 diffY = p.y - start.y;
6150 if(diffY > 0) {
6151 diffY = Math.sqrt(diffY);
6152 }
6153
6154 noti.css("transform", "translate3d(0, "+diffY+"px, 0)");
6155 };
6156 var touchEnd = function() {
6157 noti.removeClass("touching");
6158 noti.attr("style", "");
6159 if(diffY < 0 && (Math.abs(diffY) > noti.height()*0.38)) {
6160 $.closeNotification();
6161 }
6162
6163 if(Math.abs(diffX) <= 1 && Math.abs(diffY) <= 1) {
6164 noti.trigger("noti-click");
6165 }
6166
6167 start = false;
6168 };
6169
6170 var attachEvents = function(el) {
6171 el.on($.touchEvents.start, touchStart);
6172 el.on($.touchEvents.move, touchMove);
6173 el.on($.touchEvents.end, touchEnd);
6174 };
6175
6176 $.notification = $.noti = function(params) {
6177 params = $.extend({}, defaults, params);
6178 noti = $(".weui-notification");
6179 if(!noti[0]) { // create a new notification
6180 noti = $('<div class="weui-notification"></div>').appendTo(document.body);
6181 attachEvents(noti);
6182 }
6183
6184 noti.off("noti-click"); //the click event is not correct sometime: it will trigger when user is draging.
6185 if(params.onClick) noti.on("noti-click", function() {
6186 params.onClick(params.data);
6187 });
6188
6189 noti.html($.t7.compile(params.tpl)(params));
6190
6191 noti.show();
6192
6193 noti.addClass("weui-notification--in");
6194 noti.data("params", params);
6195
6196 var startTimeout = function() {
6197 if(timeout) {
6198 clearTimeout(timeout);
6199 timeout = null;
6200 }
6201
6202 timeout = setTimeout(function() {
6203 if(noti.hasClass("weui-notification--touching")) {
6204 startTimeout();
6205 } else {
6206 $.closeNotification();
6207 }
6208 }, params.time);
6209 };
6210
6211 startTimeout();
6212
6213 };
6214
6215 $.closeNotification = function() {
6216 timeout && clearTimeout(timeout);
6217 timeout = null;
6218 var noti = $(".weui-notification").removeClass("weui-notification--in").transitionEnd(function() {
6219 $(this).remove();
6220 });
6221
6222 if(noti[0]) {
6223 var params = $(".weui-notification").data("params");
6224 if(params && params.onClose) {
6225 params.onClose(params.data);
6226 }
6227 }
6228 };
6229
6230 defaults = $.noti.prototype.defaults = {
6231 title: undefined,
6232 text: undefined,
6233 media: undefined,
6234 time: 4000,
6235 onClick: undefined,
6236 onClose: undefined,
6237 data: undefined,
6238 tpl: '<div class="weui-notification__inner">' +
6239 '{{#if media}}<div class="weui-notification__media">{{media}}</div>{{/if}}' +
6240 '<div class="weui-notification__content">' +
6241 '{{#if title}}<div class="weui-notification__title">{{title}}</div>{{/if}}' +
6242 '{{#if text}}<div class="weui-notification__text">{{text}}</div>{{/if}}' +
6243 '</div>' +
6244 '<div class="weui-notification__handle-bar"></div>' +
6245 '</div>'
6246 };
6247
6248}($);
6249
6250+ function($) {
6251 "use strict";
6252
6253 var timeout;
6254
6255 $.toptip = function(text, duration, type) {
6256 if(!text) return;
6257 if(typeof duration === typeof "a") {
6258 type = duration;
6259 duration = undefined;
6260 }
6261 duration = duration || 3000;
6262 var className = type ? 'bg-' + type : 'bg-danger';
6263 var $t = $('.weui-toptips').remove();
6264 $t = $('<div class="weui-toptips"></div>').appendTo(document.body);
6265 $t.html(text);
6266 $t[0].className = 'weui-toptips ' + className
6267
6268 clearTimeout(timeout);
6269
6270 if(!$t.hasClass('weui-toptips_visible')) {
6271 $t.show().width();
6272 $t.addClass('weui-toptips_visible');
6273 }
6274
6275 timeout = setTimeout(function() {
6276 $t.removeClass('weui-toptips_visible').transitionEnd(function() {
6277 $t.remove();
6278 });
6279 }, duration);
6280 }
6281}($);
6282
6283/* global $:true */
6284+ function($) {
6285 "use strict";
6286 var Slider = function (container, arg) {
6287 this.container = $(container);
6288 this.handler = this.container.find('.weui-slider__handler')
6289 this.track = this.container.find('.weui-slider__track')
6290 this.value = this.container.find('.weui-slider-box__value')
6291 this.bind()
6292 if (typeof arg === 'function') {
6293 this.callback = arg
6294 }
6295 }
6296
6297 Slider.prototype.bind = function () {
6298 this.container
6299 .on($.touchEvents.start, $.proxy(this.touchStart, this))
6300 .on($.touchEvents.end, $.proxy(this.touchEnd, this));
6301 $(document.body).on($.touchEvents.move, $.proxy(this.touchMove, this)) // move even outside container
6302 }
6303
6304 Slider.prototype.touchStart = function (e) {
6305 e.preventDefault()
6306 this.start = $.getTouchPosition(e)
6307 this.width = this.container.find('.weui-slider__inner').width()
6308 this.left = parseInt(this.container.find('.weui-slider__handler').css('left'))
6309 this.touching = true
6310 }
6311
6312 Slider.prototype.touchMove = function (e) {
6313 if (!this.touching) return true
6314 var p = $.getTouchPosition(e)
6315 var distance = p.x - this.start.x
6316 var left = distance + this.left
6317 var per = parseInt(left / this.width * 100)
6318 if (per < 0) per = 0
6319 if (per > 100) per = 100
6320 this.handler.css('left', per + '%')
6321 this.track.css('width', per + '%')
6322 this.value.text(per)
6323 this.callback && this.callback.call(this, per)
6324 this.container.trigger('change', per)
6325 }
6326
6327 Slider.prototype.touchEnd = function (e) {
6328 this.touching = false
6329 }
6330
6331 $.fn.slider = function (arg) {
6332 this.each(function () {
6333 var $this = $(this)
6334 var slider = $this.data('slider')
6335 if (slider) return slider;
6336 else $this.data('slider', new Slider(this, arg))
6337 })
6338 }
6339}($);
6340
6341/* ===============================================================================
6342************ Swipeout ************
6343=============================================================================== */
6344/* global $:true */
6345
6346+function ($) {
6347 "use strict";
6348
6349 var cache = [];
6350 var TOUCHING = 'swipeout-touching'
6351
6352 var Swipeout = function(el) {
6353 this.container = $(el);
6354 this.mover = this.container.find('>.weui-cell__bd')
6355 this.attachEvents();
6356 cache.push(this)
6357 }
6358
6359 Swipeout.prototype.touchStart = function(e) {
6360 var p = $.getTouchPosition(e);
6361 this.container.addClass(TOUCHING);
6362 this.start = p;
6363 this.startX = 0;
6364 this.startTime = + new Date;
6365 var transform = this.mover.css('transform').match(/-?[\d\.]+/g)
6366 if (transform && transform.length) this.startX = parseInt(transform[4])
6367 this.diffX = this.diffY = 0;
6368 this._closeOthers()
6369 this.limit = this.container.find('>.weui-cell__ft').width() || 68; // 因为有的时候初始化的时候元素是隐藏的(比如在对话框内),所以在touchstart的时候计算宽度而不是初始化的时候
6370 };
6371
6372 Swipeout.prototype.touchMove= function(e) {
6373 if(!this.start) return true;
6374 var p = $.getTouchPosition(e);
6375 this.diffX = p.x - this.start.x;
6376 this.diffY = p.y - this.start.y;
6377 if (Math.abs(this.diffX) < Math.abs(this.diffY)) { // 说明是上下方向在拖动
6378 this.close()
6379 this.start = false
6380 return true;
6381 }
6382 e.preventDefault();
6383 e.stopPropagation();
6384 var x = this.diffX + this.startX
6385 if (x > 0) x = 0;
6386 if (Math.abs(x) > this.limit) x = - (Math.pow(-(x+this.limit), .7) + this.limit)
6387 this.mover.css("transform", "translate3d("+x+"px, 0, 0)");
6388 };
6389 Swipeout.prototype.touchEnd = function() {
6390 if (!this.start) return true;
6391 this.start = false;
6392 var x = this.diffX + this.startX
6393 var t = new Date - this.startTime;
6394 if (this.diffX < -5 && t < 200) { // 向左快速滑动,则打开
6395 this.open()
6396 } else if (this.diffX >= 0 && t < 200) { // 向右快速滑动,或者单击,则关闭
6397 this.close()
6398 } else if (x > 0 || -x <= this.limit / 2) {
6399 this.close()
6400 } else {
6401 this.open()
6402 }
6403 };
6404
6405
6406 Swipeout.prototype.close = function() {
6407 this.container.removeClass(TOUCHING);
6408 this.mover.css("transform", "translate3d(0, 0, 0)");
6409 this.container.trigger('swipeout-close');
6410 }
6411
6412 Swipeout.prototype.open = function() {
6413 this.container.removeClass(TOUCHING);
6414 this._closeOthers()
6415 this.mover.css("transform", "translate3d(" + (-this.limit) + "px, 0, 0)");
6416 this.container.trigger('swipeout-open');
6417 }
6418
6419 Swipeout.prototype.attachEvents = function() {
6420 var el = this.mover;
6421 el.on($.touchEvents.start, $.proxy(this.touchStart, this));
6422 el.on($.touchEvents.move, $.proxy(this.touchMove, this));
6423 el.on($.touchEvents.end, $.proxy(this.touchEnd, this));
6424 }
6425 Swipeout.prototype._closeOthers = function() {
6426 //close others
6427 var self = this
6428 cache.forEach(function (s) {
6429 if (s !== self) s.close()
6430 })
6431 }
6432
6433 var swipeout = function(el) {
6434 return new Swipeout(el);
6435 };
6436
6437 $.fn.swipeout = function (arg) {
6438 return this.each(function() {
6439 var $this = $(this)
6440 var s = $this.data('swipeout') || swipeout(this);
6441 $this.data('swipeout', s);
6442
6443 if (typeof arg === typeof 'a') {
6444 s[arg]()
6445 }
6446 });
6447 }
6448
6449 $('.weui-cell_swiped').swipeout() // auto init
6450}($);