commonroutesServer/app/www/lib/ionic/js/ionic.js
app/www/lib/ionic/js/ionic.jsRaw
1/*!
2 * Copyright 2015 Drifty Co.
3 * http://drifty.com/
4 *
5 * Ionic, v1.3.1
6 * A powerful HTML5 mobile app framework.
7 * http://ionicframework.com/
8 *
9 * By @maxlynch, @benjsperry, @adamdbradley <3
10 *
11 * Licensed under the MIT license. Please see LICENSE for more information.
12 *
13 */
14
15(function() {
16
17// Create global ionic obj and its namespaces
18// build processes may have already created an ionic obj
19window.ionic = window.ionic || {};
20window.ionic.views = {};
21window.ionic.version = '1.3.1';
22
23(function (ionic) {
24
25 ionic.DelegateService = function(methodNames) {
26
27 if (methodNames.indexOf('$getByHandle') > -1) {
28 throw new Error("Method '$getByHandle' is implicitly added to each delegate service. Do not list it as a method.");
29 }
30
31 function trueFn() { return true; }
32
33 return ['$log', function($log) {
34
35 /*
36 * Creates a new object that will have all the methodNames given,
37 * and call them on the given the controller instance matching given
38 * handle.
39 * The reason we don't just let $getByHandle return the controller instance
40 * itself is that the controller instance might not exist yet.
41 *
42 * We want people to be able to do
43 * `var instance = $ionicScrollDelegate.$getByHandle('foo')` on controller
44 * instantiation, but on controller instantiation a child directive
45 * may not have been compiled yet!
46 *
47 * So this is our way of solving this problem: we create an object
48 * that will only try to fetch the controller with given handle
49 * once the methods are actually called.
50 */
51 function DelegateInstance(instances, handle) {
52 this._instances = instances;
53 this.handle = handle;
54 }
55 methodNames.forEach(function(methodName) {
56 DelegateInstance.prototype[methodName] = instanceMethodCaller(methodName);
57 });
58
59
60 /**
61 * The delegate service (eg $ionicNavBarDelegate) is just an instance
62 * with a non-defined handle, a couple extra methods for registering
63 * and narrowing down to a specific handle.
64 */
65 function DelegateService() {
66 this._instances = [];
67 }
68 DelegateService.prototype = DelegateInstance.prototype;
69 DelegateService.prototype._registerInstance = function(instance, handle, filterFn) {
70 var instances = this._instances;
71 instance.$$delegateHandle = handle;
72 instance.$$filterFn = filterFn || trueFn;
73 instances.push(instance);
74
75 return function deregister() {
76 var index = instances.indexOf(instance);
77 if (index !== -1) {
78 instances.splice(index, 1);
79 }
80 };
81 };
82 DelegateService.prototype.$getByHandle = function(handle) {
83 return new DelegateInstance(this._instances, handle);
84 };
85
86 return new DelegateService();
87
88 function instanceMethodCaller(methodName) {
89 return function caller() {
90 var handle = this.handle;
91 var args = arguments;
92 var foundInstancesCount = 0;
93 var returnValue;
94
95 this._instances.forEach(function(instance) {
96 if ((!handle || handle == instance.$$delegateHandle) && instance.$$filterFn(instance)) {
97 foundInstancesCount++;
98 var ret = instance[methodName].apply(instance, args);
99 //Only return the value from the first call
100 if (foundInstancesCount === 1) {
101 returnValue = ret;
102 }
103 }
104 });
105
106 if (!foundInstancesCount && handle) {
107 return $log.warn(
108 'Delegate for handle "' + handle + '" could not find a ' +
109 'corresponding element with delegate-handle="' + handle + '"! ' +
110 methodName + '() was not called!\n' +
111 'Possible cause: If you are calling ' + methodName + '() immediately, and ' +
112 'your element with delegate-handle="' + handle + '" is a child of your ' +
113 'controller, then your element may not be compiled yet. Put a $timeout ' +
114 'around your call to ' + methodName + '() and try again.'
115 );
116 }
117 return returnValue;
118 };
119 }
120
121 }];
122 };
123
124})(window.ionic);
125
126(function(window, document, ionic) {
127
128 var readyCallbacks = [];
129 var isDomReady = document.readyState === 'complete' || document.readyState === 'interactive';
130
131 function domReady() {
132 isDomReady = true;
133 for (var x = 0; x < readyCallbacks.length; x++) {
134 ionic.requestAnimationFrame(readyCallbacks[x]);
135 }
136 readyCallbacks = [];
137 document.removeEventListener('DOMContentLoaded', domReady);
138 }
139 if (!isDomReady) {
140 document.addEventListener('DOMContentLoaded', domReady);
141 }
142
143
144 // From the man himself, Mr. Paul Irish.
145 // The requestAnimationFrame polyfill
146 // Put it on window just to preserve its context
147 // without having to use .call
148 window._rAF = (function() {
149 return window.requestAnimationFrame ||
150 window.webkitRequestAnimationFrame ||
151 window.mozRequestAnimationFrame ||
152 function(callback) {
153 window.setTimeout(callback, 16);
154 };
155 })();
156
157 var cancelAnimationFrame = window.cancelAnimationFrame ||
158 window.webkitCancelAnimationFrame ||
159 window.mozCancelAnimationFrame ||
160 window.webkitCancelRequestAnimationFrame;
161
162 /**
163 * @ngdoc utility
164 * @name ionic.DomUtil
165 * @module ionic
166 */
167 ionic.DomUtil = {
168 //Call with proper context
169 /**
170 * @ngdoc method
171 * @name ionic.DomUtil#requestAnimationFrame
172 * @alias ionic.requestAnimationFrame
173 * @description Calls [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame), or a polyfill if not available.
174 * @param {function} callback The function to call when the next frame
175 * happens.
176 */
177 requestAnimationFrame: function(cb) {
178 return window._rAF(cb);
179 },
180
181 cancelAnimationFrame: function(requestId) {
182 cancelAnimationFrame(requestId);
183 },
184
185 /**
186 * @ngdoc method
187 * @name ionic.DomUtil#animationFrameThrottle
188 * @alias ionic.animationFrameThrottle
189 * @description
190 * When given a callback, if that callback is called 100 times between
191 * animation frames, adding Throttle will make it only run the last of
192 * the 100 calls.
193 *
194 * @param {function} callback a function which will be throttled to
195 * requestAnimationFrame
196 * @returns {function} A function which will then call the passed in callback.
197 * The passed in callback will receive the context the returned function is
198 * called with.
199 */
200 animationFrameThrottle: function(cb) {
201 var args, isQueued, context;
202 return function() {
203 args = arguments;
204 context = this;
205 if (!isQueued) {
206 isQueued = true;
207 ionic.requestAnimationFrame(function() {
208 cb.apply(context, args);
209 isQueued = false;
210 });
211 }
212 };
213 },
214
215 contains: function(parentNode, otherNode) {
216 var current = otherNode;
217 while (current) {
218 if (current === parentNode) return true;
219 current = current.parentNode;
220 }
221 },
222
223 /**
224 * @ngdoc method
225 * @name ionic.DomUtil#getPositionInParent
226 * @description
227 * Find an element's scroll offset within its container.
228 * @param {DOMElement} element The element to find the offset of.
229 * @returns {object} A position object with the following properties:
230 * - `{number}` `left` The left offset of the element.
231 * - `{number}` `top` The top offset of the element.
232 */
233 getPositionInParent: function(el) {
234 return {
235 left: el.offsetLeft,
236 top: el.offsetTop
237 };
238 },
239
240 getOffsetTop: function(el) {
241 var curtop = 0;
242 if (el.offsetParent) {
243 do {
244 curtop += el.offsetTop;
245 el = el.offsetParent;
246 } while (el)
247 return curtop;
248 }
249 },
250
251 /**
252 * @ngdoc method
253 * @name ionic.DomUtil#ready
254 * @description
255 * Call a function when the DOM is ready, or if it is already ready
256 * call the function immediately.
257 * @param {function} callback The function to be called.
258 */
259 ready: function(cb) {
260 if (isDomReady) {
261 ionic.requestAnimationFrame(cb);
262 } else {
263 readyCallbacks.push(cb);
264 }
265 },
266
267 /**
268 * @ngdoc method
269 * @name ionic.DomUtil#getTextBounds
270 * @description
271 * Get a rect representing the bounds of the given textNode.
272 * @param {DOMElement} textNode The textNode to find the bounds of.
273 * @returns {object} An object representing the bounds of the node. Properties:
274 * - `{number}` `left` The left position of the textNode.
275 * - `{number}` `right` The right position of the textNode.
276 * - `{number}` `top` The top position of the textNode.
277 * - `{number}` `bottom` The bottom position of the textNode.
278 * - `{number}` `width` The width of the textNode.
279 * - `{number}` `height` The height of the textNode.
280 */
281 getTextBounds: function(textNode) {
282 if (document.createRange) {
283 var range = document.createRange();
284 range.selectNodeContents(textNode);
285 if (range.getBoundingClientRect) {
286 var rect = range.getBoundingClientRect();
287 if (rect) {
288 var sx = window.scrollX;
289 var sy = window.scrollY;
290
291 return {
292 top: rect.top + sy,
293 left: rect.left + sx,
294 right: rect.left + sx + rect.width,
295 bottom: rect.top + sy + rect.height,
296 width: rect.width,
297 height: rect.height
298 };
299 }
300 }
301 }
302 return null;
303 },
304
305 /**
306 * @ngdoc method
307 * @name ionic.DomUtil#getChildIndex
308 * @description
309 * Get the first index of a child node within the given element of the
310 * specified type.
311 * @param {DOMElement} element The element to find the index of.
312 * @param {string} type The nodeName to match children of element against.
313 * @returns {number} The index, or -1, of a child with nodeName matching type.
314 */
315 getChildIndex: function(element, type) {
316 if (type) {
317 var ch = element.parentNode.children;
318 var c;
319 for (var i = 0, k = 0, j = ch.length; i < j; i++) {
320 c = ch[i];
321 if (c.nodeName && c.nodeName.toLowerCase() == type) {
322 if (c == element) {
323 return k;
324 }
325 k++;
326 }
327 }
328 }
329 return Array.prototype.slice.call(element.parentNode.children).indexOf(element);
330 },
331
332 /**
333 * @private
334 */
335 swapNodes: function(src, dest) {
336 dest.parentNode.insertBefore(src, dest);
337 },
338
339 elementIsDescendant: function(el, parent, stopAt) {
340 var current = el;
341 do {
342 if (current === parent) return true;
343 current = current.parentNode;
344 } while (current && current !== stopAt);
345 return false;
346 },
347
348 /**
349 * @ngdoc method
350 * @name ionic.DomUtil#getParentWithClass
351 * @param {DOMElement} element
352 * @param {string} className
353 * @returns {DOMElement} The closest parent of element matching the
354 * className, or null.
355 */
356 getParentWithClass: function(e, className, depth) {
357 depth = depth || 10;
358 while (e.parentNode && depth--) {
359 if (e.parentNode.classList && e.parentNode.classList.contains(className)) {
360 return e.parentNode;
361 }
362 e = e.parentNode;
363 }
364 return null;
365 },
366 /**
367 * @ngdoc method
368 * @name ionic.DomUtil#getParentOrSelfWithClass
369 * @param {DOMElement} element
370 * @param {string} className
371 * @returns {DOMElement} The closest parent or self matching the
372 * className, or null.
373 */
374 getParentOrSelfWithClass: function(e, className, depth) {
375 depth = depth || 10;
376 while (e && depth--) {
377 if (e.classList && e.classList.contains(className)) {
378 return e;
379 }
380 e = e.parentNode;
381 }
382 return null;
383 },
384
385 /**
386 * @ngdoc method
387 * @name ionic.DomUtil#rectContains
388 * @param {number} x
389 * @param {number} y
390 * @param {number} x1
391 * @param {number} y1
392 * @param {number} x2
393 * @param {number} y2
394 * @returns {boolean} Whether {x,y} fits within the rectangle defined by
395 * {x1,y1,x2,y2}.
396 */
397 rectContains: function(x, y, x1, y1, x2, y2) {
398 if (x < x1 || x > x2) return false;
399 if (y < y1 || y > y2) return false;
400 return true;
401 },
402
403 /**
404 * @ngdoc method
405 * @name ionic.DomUtil#blurAll
406 * @description
407 * Blurs any currently focused input element
408 * @returns {DOMElement} The element blurred or null
409 */
410 blurAll: function() {
411 if (document.activeElement && document.activeElement != document.body) {
412 document.activeElement.blur();
413 return document.activeElement;
414 }
415 return null;
416 },
417
418 cachedAttr: function(ele, key, value) {
419 ele = ele && ele.length && ele[0] || ele;
420 if (ele && ele.setAttribute) {
421 var dataKey = '$attr-' + key;
422 if (arguments.length > 2) {
423 if (ele[dataKey] !== value) {
424 ele.setAttribute(key, value);
425 ele[dataKey] = value;
426 }
427 } else if (typeof ele[dataKey] == 'undefined') {
428 ele[dataKey] = ele.getAttribute(key);
429 }
430 return ele[dataKey];
431 }
432 },
433
434 cachedStyles: function(ele, styles) {
435 ele = ele && ele.length && ele[0] || ele;
436 if (ele && ele.style) {
437 for (var prop in styles) {
438 if (ele['$style-' + prop] !== styles[prop]) {
439 ele.style[prop] = ele['$style-' + prop] = styles[prop];
440 }
441 }
442 }
443 }
444
445 };
446
447 //Shortcuts
448 ionic.requestAnimationFrame = ionic.DomUtil.requestAnimationFrame;
449 ionic.cancelAnimationFrame = ionic.DomUtil.cancelAnimationFrame;
450 ionic.animationFrameThrottle = ionic.DomUtil.animationFrameThrottle;
451
452})(window, document, ionic);
453
454/**
455 * ion-events.js
456 *
457 * Author: Max Lynch <max@drifty.com>
458 *
459 * Framework events handles various mobile browser events, and
460 * detects special events like tap/swipe/etc. and emits them
461 * as custom events that can be used in an app.
462 *
463 * Portions lovingly adapted from github.com/maker/ratchet and github.com/alexgibson/tap.js - thanks guys!
464 */
465
466(function(ionic) {
467
468 // Custom event polyfill
469 ionic.CustomEvent = (function() {
470 if( typeof window.CustomEvent === 'function' ) return CustomEvent;
471
472 var customEvent = function(event, params) {
473 var evt;
474 params = params || {
475 bubbles: false,
476 cancelable: false,
477 detail: undefined
478 };
479 try {
480 evt = document.createEvent("CustomEvent");
481 evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
482 } catch (error) {
483 // fallback for browsers that don't support createEvent('CustomEvent')
484 evt = document.createEvent("Event");
485 for (var param in params) {
486 evt[param] = params[param];
487 }
488 evt.initEvent(event, params.bubbles, params.cancelable);
489 }
490 return evt;
491 };
492 customEvent.prototype = window.Event.prototype;
493 return customEvent;
494 })();
495
496
497 /**
498 * @ngdoc utility
499 * @name ionic.EventController
500 * @module ionic
501 */
502 ionic.EventController = {
503 VIRTUALIZED_EVENTS: ['tap', 'swipe', 'swiperight', 'swipeleft', 'drag', 'hold', 'release'],
504
505 /**
506 * @ngdoc method
507 * @name ionic.EventController#trigger
508 * @alias ionic.trigger
509 * @param {string} eventType The event to trigger.
510 * @param {object} data The data for the event. Hint: pass in
511 * `{target: targetElement}`
512 * @param {boolean=} bubbles Whether the event should bubble up the DOM.
513 * @param {boolean=} cancelable Whether the event should be cancelable.
514 */
515 // Trigger a new event
516 trigger: function(eventType, data, bubbles, cancelable) {
517 var event = new ionic.CustomEvent(eventType, {
518 detail: data,
519 bubbles: !!bubbles,
520 cancelable: !!cancelable
521 });
522
523 // Make sure to trigger the event on the given target, or dispatch it from
524 // the window if we don't have an event target
525 data && data.target && data.target.dispatchEvent && data.target.dispatchEvent(event) || window.dispatchEvent(event);
526 },
527
528 /**
529 * @ngdoc method
530 * @name ionic.EventController#on
531 * @alias ionic.on
532 * @description Listen to an event on an element.
533 * @param {string} type The event to listen for.
534 * @param {function} callback The listener to be called.
535 * @param {DOMElement} element The element to listen for the event on.
536 */
537 on: function(type, callback, element) {
538 var e = element || window;
539
540 // Bind a gesture if it's a virtual event
541 for(var i = 0, j = this.VIRTUALIZED_EVENTS.length; i < j; i++) {
542 if(type == this.VIRTUALIZED_EVENTS[i]) {
543 var gesture = new ionic.Gesture(element);
544 gesture.on(type, callback);
545 return gesture;
546 }
547 }
548
549 // Otherwise bind a normal event
550 e.addEventListener(type, callback);
551 },
552
553 /**
554 * @ngdoc method
555 * @name ionic.EventController#off
556 * @alias ionic.off
557 * @description Remove an event listener.
558 * @param {string} type
559 * @param {function} callback
560 * @param {DOMElement} element
561 */
562 off: function(type, callback, element) {
563 element.removeEventListener(type, callback);
564 },
565
566 /**
567 * @ngdoc method
568 * @name ionic.EventController#onGesture
569 * @alias ionic.onGesture
570 * @description Add an event listener for a gesture on an element.
571 *
572 * Available eventTypes (from [hammer.js](http://eightmedia.github.io/hammer.js/)):
573 *
574 * `hold`, `tap`, `doubletap`, `drag`, `dragstart`, `dragend`, `dragup`, `dragdown`, <br/>
575 * `dragleft`, `dragright`, `swipe`, `swipeup`, `swipedown`, `swipeleft`, `swiperight`, <br/>
576 * `transform`, `transformstart`, `transformend`, `rotate`, `pinch`, `pinchin`, `pinchout`, <br/>
577 * `touch`, `release`
578 *
579 * @param {string} eventType The gesture event to listen for.
580 * @param {function(e)} callback The function to call when the gesture
581 * happens.
582 * @param {DOMElement} element The angular element to listen for the event on.
583 * @param {object} options object.
584 * @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on).
585 */
586 onGesture: function(type, callback, element, options) {
587 var gesture = new ionic.Gesture(element, options);
588 gesture.on(type, callback);
589 return gesture;
590 },
591
592 /**
593 * @ngdoc method
594 * @name ionic.EventController#offGesture
595 * @alias ionic.offGesture
596 * @description Remove an event listener for a gesture created on an element.
597 * @param {ionic.Gesture} gesture The gesture that should be removed.
598 * @param {string} eventType The gesture event to remove the listener for.
599 * @param {function(e)} callback The listener to remove.
600
601 */
602 offGesture: function(gesture, type, callback) {
603 gesture && gesture.off(type, callback);
604 },
605
606 handlePopState: function() {}
607 };
608
609
610 // Map some convenient top-level functions for event handling
611 ionic.on = function() { ionic.EventController.on.apply(ionic.EventController, arguments); };
612 ionic.off = function() { ionic.EventController.off.apply(ionic.EventController, arguments); };
613 ionic.trigger = ionic.EventController.trigger;//function() { ionic.EventController.trigger.apply(ionic.EventController.trigger, arguments); };
614 ionic.onGesture = function() { return ionic.EventController.onGesture.apply(ionic.EventController.onGesture, arguments); };
615 ionic.offGesture = function() { return ionic.EventController.offGesture.apply(ionic.EventController.offGesture, arguments); };
616
617})(window.ionic);
618
619/* eslint camelcase:0 */
620/**
621 * Simple gesture controllers with some common gestures that emit
622 * gesture events.
623 *
624 * Ported from github.com/EightMedia/hammer.js Gestures - thanks!
625 */
626(function(ionic) {
627
628 /**
629 * ionic.Gestures
630 * use this to create instances
631 * @param {HTMLElement} element
632 * @param {Object} options
633 * @returns {ionic.Gestures.Instance}
634 * @constructor
635 */
636 ionic.Gesture = function(element, options) {
637 return new ionic.Gestures.Instance(element, options || {});
638 };
639
640 ionic.Gestures = {};
641
642 // default settings
643 ionic.Gestures.defaults = {
644 // add css to the element to prevent the browser from doing
645 // its native behavior. this doesnt prevent the scrolling,
646 // but cancels the contextmenu, tap highlighting etc
647 // set to false to disable this
648 stop_browser_behavior: 'disable-user-behavior'
649 };
650
651 // detect touchevents
652 ionic.Gestures.HAS_POINTEREVENTS = window.navigator.pointerEnabled || window.navigator.msPointerEnabled;
653 ionic.Gestures.HAS_TOUCHEVENTS = ('ontouchstart' in window);
654
655 // dont use mouseevents on mobile devices
656 ionic.Gestures.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android|silk/i;
657 ionic.Gestures.NO_MOUSEEVENTS = ionic.Gestures.HAS_TOUCHEVENTS && window.navigator.userAgent.match(ionic.Gestures.MOBILE_REGEX);
658
659 // eventtypes per touchevent (start, move, end)
660 // are filled by ionic.Gestures.event.determineEventTypes on setup
661 ionic.Gestures.EVENT_TYPES = {};
662
663 // direction defines
664 ionic.Gestures.DIRECTION_DOWN = 'down';
665 ionic.Gestures.DIRECTION_LEFT = 'left';
666 ionic.Gestures.DIRECTION_UP = 'up';
667 ionic.Gestures.DIRECTION_RIGHT = 'right';
668
669 // pointer type
670 ionic.Gestures.POINTER_MOUSE = 'mouse';
671 ionic.Gestures.POINTER_TOUCH = 'touch';
672 ionic.Gestures.POINTER_PEN = 'pen';
673
674 // touch event defines
675 ionic.Gestures.EVENT_START = 'start';
676 ionic.Gestures.EVENT_MOVE = 'move';
677 ionic.Gestures.EVENT_END = 'end';
678
679 // hammer document where the base events are added at
680 ionic.Gestures.DOCUMENT = window.document;
681
682 // plugins namespace
683 ionic.Gestures.plugins = {};
684
685 // if the window events are set...
686 ionic.Gestures.READY = false;
687
688 /**
689 * setup events to detect gestures on the document
690 */
691 function setup() {
692 if(ionic.Gestures.READY) {
693 return;
694 }
695
696 // find what eventtypes we add listeners to
697 ionic.Gestures.event.determineEventTypes();
698
699 // Register all gestures inside ionic.Gestures.gestures
700 for(var name in ionic.Gestures.gestures) {
701 if(ionic.Gestures.gestures.hasOwnProperty(name)) {
702 ionic.Gestures.detection.register(ionic.Gestures.gestures[name]);
703 }
704 }
705
706 // Add touch events on the document
707 ionic.Gestures.event.onTouch(ionic.Gestures.DOCUMENT, ionic.Gestures.EVENT_MOVE, ionic.Gestures.detection.detect);
708 ionic.Gestures.event.onTouch(ionic.Gestures.DOCUMENT, ionic.Gestures.EVENT_END, ionic.Gestures.detection.detect);
709
710 // ionic.Gestures is ready...!
711 ionic.Gestures.READY = true;
712 }
713
714 /**
715 * create new hammer instance
716 * all methods should return the instance itself, so it is chainable.
717 * @param {HTMLElement} element
718 * @param {Object} [options={}]
719 * @returns {ionic.Gestures.Instance}
720 * @name Gesture.Instance
721 * @constructor
722 */
723 ionic.Gestures.Instance = function(element, options) {
724 var self = this;
725
726 // A null element was passed into the instance, which means
727 // whatever lookup was done to find this element failed to find it
728 // so we can't listen for events on it.
729 if(element === null) {
730 void 0;
731 return this;
732 }
733
734 // setup ionic.GesturesJS window events and register all gestures
735 // this also sets up the default options
736 setup();
737
738 this.element = element;
739
740 // start/stop detection option
741 this.enabled = true;
742
743 // merge options
744 this.options = ionic.Gestures.utils.extend(
745 ionic.Gestures.utils.extend({}, ionic.Gestures.defaults),
746 options || {});
747
748 // add some css to the element to prevent the browser from doing its native behavoir
749 if(this.options.stop_browser_behavior) {
750 ionic.Gestures.utils.stopDefaultBrowserBehavior(this.element, this.options.stop_browser_behavior);
751 }
752
753 // start detection on touchstart
754 ionic.Gestures.event.onTouch(element, ionic.Gestures.EVENT_START, function(ev) {
755 if(self.enabled) {
756 ionic.Gestures.detection.startDetect(self, ev);
757 }
758 });
759
760 // return instance
761 return this;
762 };
763
764
765 ionic.Gestures.Instance.prototype = {
766 /**
767 * bind events to the instance
768 * @param {String} gesture
769 * @param {Function} handler
770 * @returns {ionic.Gestures.Instance}
771 */
772 on: function onEvent(gesture, handler){
773 var gestures = gesture.split(' ');
774 for(var t = 0; t < gestures.length; t++) {
775 this.element.addEventListener(gestures[t], handler, false);
776 }
777 return this;
778 },
779
780
781 /**
782 * unbind events to the instance
783 * @param {String} gesture
784 * @param {Function} handler
785 * @returns {ionic.Gestures.Instance}
786 */
787 off: function offEvent(gesture, handler){
788 var gestures = gesture.split(' ');
789 for(var t = 0; t < gestures.length; t++) {
790 this.element.removeEventListener(gestures[t], handler, false);
791 }
792 return this;
793 },
794
795
796 /**
797 * trigger gesture event
798 * @param {String} gesture
799 * @param {Object} eventData
800 * @returns {ionic.Gestures.Instance}
801 */
802 trigger: function triggerEvent(gesture, eventData){
803 // create DOM event
804 var event = ionic.Gestures.DOCUMENT.createEvent('Event');
805 event.initEvent(gesture, true, true);
806 event.gesture = eventData;
807
808 // trigger on the target if it is in the instance element,
809 // this is for event delegation tricks
810 var element = this.element;
811 if(ionic.Gestures.utils.hasParent(eventData.target, element)) {
812 element = eventData.target;
813 }
814
815 element.dispatchEvent(event);
816 return this;
817 },
818
819
820 /**
821 * enable of disable hammer.js detection
822 * @param {Boolean} state
823 * @returns {ionic.Gestures.Instance}
824 */
825 enable: function enable(state) {
826 this.enabled = state;
827 return this;
828 }
829 };
830
831 /**
832 * this holds the last move event,
833 * used to fix empty touchend issue
834 * see the onTouch event for an explanation
835 * type {Object}
836 */
837 var last_move_event = null;
838
839
840 /**
841 * when the mouse is hold down, this is true
842 * type {Boolean}
843 */
844 var enable_detect = false;
845
846
847 /**
848 * when touch events have been fired, this is true
849 * type {Boolean}
850 */
851 var touch_triggered = false;
852
853
854 ionic.Gestures.event = {
855 /**
856 * simple addEventListener
857 * @param {HTMLElement} element
858 * @param {String} type
859 * @param {Function} handler
860 */
861 bindDom: function(element, type, handler) {
862 var types = type.split(' ');
863 for(var t = 0; t < types.length; t++) {
864 element.addEventListener(types[t], handler, false);
865 }
866 },
867
868
869 /**
870 * touch events with mouse fallback
871 * @param {HTMLElement} element
872 * @param {String} eventType like ionic.Gestures.EVENT_MOVE
873 * @param {Function} handler
874 */
875 onTouch: function onTouch(element, eventType, handler) {
876 var self = this;
877
878 this.bindDom(element, ionic.Gestures.EVENT_TYPES[eventType], function bindDomOnTouch(ev) {
879 var sourceEventType = ev.type.toLowerCase();
880
881 // onmouseup, but when touchend has been fired we do nothing.
882 // this is for touchdevices which also fire a mouseup on touchend
883 if(sourceEventType.match(/mouse/) && touch_triggered) {
884 return;
885 }
886
887 // mousebutton must be down or a touch event
888 else if( sourceEventType.match(/touch/) || // touch events are always on screen
889 sourceEventType.match(/pointerdown/) || // pointerevents touch
890 (sourceEventType.match(/mouse/) && ev.which === 1) // mouse is pressed
891 ){
892 enable_detect = true;
893 }
894
895 // mouse isn't pressed
896 else if(sourceEventType.match(/mouse/) && ev.which !== 1) {
897 enable_detect = false;
898 }
899
900
901 // we are in a touch event, set the touch triggered bool to true,
902 // this for the conflicts that may occur on ios and android
903 if(sourceEventType.match(/touch|pointer/)) {
904 touch_triggered = true;
905 }
906
907 // count the total touches on the screen
908 var count_touches = 0;
909
910 // when touch has been triggered in this detection session
911 // and we are now handling a mouse event, we stop that to prevent conflicts
912 if(enable_detect) {
913 // update pointerevent
914 if(ionic.Gestures.HAS_POINTEREVENTS && eventType != ionic.Gestures.EVENT_END) {
915 count_touches = ionic.Gestures.PointerEvent.updatePointer(eventType, ev);
916 }
917 // touch
918 else if(sourceEventType.match(/touch/)) {
919 count_touches = ev.touches.length;
920 }
921 // mouse
922 else if(!touch_triggered) {
923 count_touches = sourceEventType.match(/up/) ? 0 : 1;
924 }
925
926 // if we are in a end event, but when we remove one touch and
927 // we still have enough, set eventType to move
928 if(count_touches > 0 && eventType == ionic.Gestures.EVENT_END) {
929 eventType = ionic.Gestures.EVENT_MOVE;
930 }
931 // no touches, force the end event
932 else if(!count_touches) {
933 eventType = ionic.Gestures.EVENT_END;
934 }
935
936 // store the last move event
937 if(count_touches || last_move_event === null) {
938 last_move_event = ev;
939 }
940
941 // trigger the handler
942 handler.call(ionic.Gestures.detection, self.collectEventData(element, eventType, self.getTouchList(last_move_event, eventType), ev));
943
944 // remove pointerevent from list
945 if(ionic.Gestures.HAS_POINTEREVENTS && eventType == ionic.Gestures.EVENT_END) {
946 count_touches = ionic.Gestures.PointerEvent.updatePointer(eventType, ev);
947 }
948 }
949
950 //debug(sourceEventType +" "+ eventType);
951
952 // on the end we reset everything
953 if(!count_touches) {
954 last_move_event = null;
955 enable_detect = false;
956 touch_triggered = false;
957 ionic.Gestures.PointerEvent.reset();
958 }
959 });
960 },
961
962
963 /**
964 * we have different events for each device/browser
965 * determine what we need and set them in the ionic.Gestures.EVENT_TYPES constant
966 */
967 determineEventTypes: function determineEventTypes() {
968 // determine the eventtype we want to set
969 var types;
970
971 // pointerEvents magic
972 if(ionic.Gestures.HAS_POINTEREVENTS) {
973 types = ionic.Gestures.PointerEvent.getEvents();
974 }
975 // on Android, iOS, blackberry, windows mobile we dont want any mouseevents
976 else if(ionic.Gestures.NO_MOUSEEVENTS) {
977 types = [
978 'touchstart',
979 'touchmove',
980 'touchend touchcancel'];
981 }
982 // for non pointer events browsers and mixed browsers,
983 // like chrome on windows8 touch laptop
984 else {
985 types = [
986 'touchstart mousedown',
987 'touchmove mousemove',
988 'touchend touchcancel mouseup'];
989 }
990
991 ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_START] = types[0];
992 ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_MOVE] = types[1];
993 ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_END] = types[2];
994 },
995
996
997 /**
998 * create touchlist depending on the event
999 * @param {Object} ev
1000 * @param {String} eventType used by the fakemultitouch plugin
1001 */
1002 getTouchList: function getTouchList(ev/*, eventType*/) {
1003 // get the fake pointerEvent touchlist
1004 if(ionic.Gestures.HAS_POINTEREVENTS) {
1005 return ionic.Gestures.PointerEvent.getTouchList();
1006 }
1007 // get the touchlist
1008 else if(ev.touches) {
1009 return ev.touches;
1010 }
1011 // make fake touchlist from mouse position
1012 else {
1013 ev.identifier = 1;
1014 return [ev];
1015 }
1016 },
1017
1018
1019 /**
1020 * collect event data for ionic.Gestures js
1021 * @param {HTMLElement} element
1022 * @param {String} eventType like ionic.Gestures.EVENT_MOVE
1023 * @param {Object} eventData
1024 */
1025 collectEventData: function collectEventData(element, eventType, touches, ev) {
1026
1027 // find out pointerType
1028 var pointerType = ionic.Gestures.POINTER_TOUCH;
1029 if(ev.type.match(/mouse/) || ionic.Gestures.PointerEvent.matchType(ionic.Gestures.POINTER_MOUSE, ev)) {
1030 pointerType = ionic.Gestures.POINTER_MOUSE;
1031 }
1032
1033 return {
1034 center: ionic.Gestures.utils.getCenter(touches),
1035 timeStamp: new Date().getTime(),
1036 target: ev.target,
1037 touches: touches,
1038 eventType: eventType,
1039 pointerType: pointerType,
1040 srcEvent: ev,
1041
1042 /**
1043 * prevent the browser default actions
1044 * mostly used to disable scrolling of the browser
1045 */
1046 preventDefault: function() {
1047 if(this.srcEvent.preventManipulation) {
1048 this.srcEvent.preventManipulation();
1049 }
1050
1051 if(this.srcEvent.preventDefault) {
1052 // this.srcEvent.preventDefault();
1053 }
1054 },
1055
1056 /**
1057 * stop bubbling the event up to its parents
1058 */
1059 stopPropagation: function() {
1060 this.srcEvent.stopPropagation();
1061 },
1062
1063 /**
1064 * immediately stop gesture detection
1065 * might be useful after a swipe was detected
1066 * @return {*}
1067 */
1068 stopDetect: function() {
1069 return ionic.Gestures.detection.stopDetect();
1070 }
1071 };
1072 }
1073 };
1074
1075 ionic.Gestures.PointerEvent = {
1076 /**
1077 * holds all pointers
1078 * type {Object}
1079 */
1080 pointers: {},
1081
1082 /**
1083 * get a list of pointers
1084 * @returns {Array} touchlist
1085 */
1086 getTouchList: function() {
1087 var self = this;
1088 var touchlist = [];
1089
1090 // we can use forEach since pointerEvents only is in IE10
1091 Object.keys(self.pointers).sort().forEach(function(id) {
1092 touchlist.push(self.pointers[id]);
1093 });
1094 return touchlist;
1095 },
1096
1097 /**
1098 * update the position of a pointer
1099 * @param {String} type ionic.Gestures.EVENT_END
1100 * @param {Object} pointerEvent
1101 */
1102 updatePointer: function(type, pointerEvent) {
1103 if(type == ionic.Gestures.EVENT_END) {
1104 this.pointers = {};
1105 }
1106 else {
1107 pointerEvent.identifier = pointerEvent.pointerId;
1108 this.pointers[pointerEvent.pointerId] = pointerEvent;
1109 }
1110
1111 return Object.keys(this.pointers).length;
1112 },
1113
1114 /**
1115 * check if ev matches pointertype
1116 * @param {String} pointerType ionic.Gestures.POINTER_MOUSE
1117 * @param {PointerEvent} ev
1118 */
1119 matchType: function(pointerType, ev) {
1120 if(!ev.pointerType) {
1121 return false;
1122 }
1123
1124 var types = {};
1125 types[ionic.Gestures.POINTER_MOUSE] = (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE || ev.pointerType == ionic.Gestures.POINTER_MOUSE);
1126 types[ionic.Gestures.POINTER_TOUCH] = (ev.pointerType == ev.MSPOINTER_TYPE_TOUCH || ev.pointerType == ionic.Gestures.POINTER_TOUCH);
1127 types[ionic.Gestures.POINTER_PEN] = (ev.pointerType == ev.MSPOINTER_TYPE_PEN || ev.pointerType == ionic.Gestures.POINTER_PEN);
1128 return types[pointerType];
1129 },
1130
1131
1132 /**
1133 * get events
1134 */
1135 getEvents: function() {
1136 return [
1137 'pointerdown MSPointerDown',
1138 'pointermove MSPointerMove',
1139 'pointerup pointercancel MSPointerUp MSPointerCancel'
1140 ];
1141 },
1142
1143 /**
1144 * reset the list
1145 */
1146 reset: function() {
1147 this.pointers = {};
1148 }
1149 };
1150
1151
1152 ionic.Gestures.utils = {
1153 /**
1154 * extend method,
1155 * also used for cloning when dest is an empty object
1156 * @param {Object} dest
1157 * @param {Object} src
1158 * @param {Boolean} merge do a merge
1159 * @returns {Object} dest
1160 */
1161 extend: function extend(dest, src, merge) {
1162 for (var key in src) {
1163 if(dest[key] !== undefined && merge) {
1164 continue;
1165 }
1166 dest[key] = src[key];
1167 }
1168 return dest;
1169 },
1170
1171
1172 /**
1173 * find if a node is in the given parent
1174 * used for event delegation tricks
1175 * @param {HTMLElement} node
1176 * @param {HTMLElement} parent
1177 * @returns {boolean} has_parent
1178 */
1179 hasParent: function(node, parent) {
1180 while(node){
1181 if(node == parent) {
1182 return true;
1183 }
1184 node = node.parentNode;
1185 }
1186 return false;
1187 },
1188
1189
1190 /**
1191 * get the center of all the touches
1192 * @param {Array} touches
1193 * @returns {Object} center
1194 */
1195 getCenter: function getCenter(touches) {
1196 var valuesX = [], valuesY = [];
1197
1198 for(var t = 0, len = touches.length; t < len; t++) {
1199 valuesX.push(touches[t].pageX);
1200 valuesY.push(touches[t].pageY);
1201 }
1202
1203 return {
1204 pageX: ((Math.min.apply(Math, valuesX) + Math.max.apply(Math, valuesX)) / 2),
1205 pageY: ((Math.min.apply(Math, valuesY) + Math.max.apply(Math, valuesY)) / 2)
1206 };
1207 },
1208
1209
1210 /**
1211 * calculate the velocity between two points
1212 * @param {Number} delta_time
1213 * @param {Number} delta_x
1214 * @param {Number} delta_y
1215 * @returns {Object} velocity
1216 */
1217 getVelocity: function getVelocity(delta_time, delta_x, delta_y) {
1218 return {
1219 x: Math.abs(delta_x / delta_time) || 0,
1220 y: Math.abs(delta_y / delta_time) || 0
1221 };
1222 },
1223
1224
1225 /**
1226 * calculate the angle between two coordinates
1227 * @param {Touch} touch1
1228 * @param {Touch} touch2
1229 * @returns {Number} angle
1230 */
1231 getAngle: function getAngle(touch1, touch2) {
1232 var y = touch2.pageY - touch1.pageY,
1233 x = touch2.pageX - touch1.pageX;
1234 return Math.atan2(y, x) * 180 / Math.PI;
1235 },
1236
1237
1238 /**
1239 * angle to direction define
1240 * @param {Touch} touch1
1241 * @param {Touch} touch2
1242 * @returns {String} direction constant, like ionic.Gestures.DIRECTION_LEFT
1243 */
1244 getDirection: function getDirection(touch1, touch2) {
1245 var x = Math.abs(touch1.pageX - touch2.pageX),
1246 y = Math.abs(touch1.pageY - touch2.pageY);
1247
1248 if(x >= y) {
1249 return touch1.pageX - touch2.pageX > 0 ? ionic.Gestures.DIRECTION_LEFT : ionic.Gestures.DIRECTION_RIGHT;
1250 }
1251 else {
1252 return touch1.pageY - touch2.pageY > 0 ? ionic.Gestures.DIRECTION_UP : ionic.Gestures.DIRECTION_DOWN;
1253 }
1254 },
1255
1256
1257 /**
1258 * calculate the distance between two touches
1259 * @param {Touch} touch1
1260 * @param {Touch} touch2
1261 * @returns {Number} distance
1262 */
1263 getDistance: function getDistance(touch1, touch2) {
1264 var x = touch2.pageX - touch1.pageX,
1265 y = touch2.pageY - touch1.pageY;
1266 return Math.sqrt((x * x) + (y * y));
1267 },
1268
1269
1270 /**
1271 * calculate the scale factor between two touchLists (fingers)
1272 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
1273 * @param {Array} start
1274 * @param {Array} end
1275 * @returns {Number} scale
1276 */
1277 getScale: function getScale(start, end) {
1278 // need two fingers...
1279 if(start.length >= 2 && end.length >= 2) {
1280 return this.getDistance(end[0], end[1]) /
1281 this.getDistance(start[0], start[1]);
1282 }
1283 return 1;
1284 },
1285
1286
1287 /**
1288 * calculate the rotation degrees between two touchLists (fingers)
1289 * @param {Array} start
1290 * @param {Array} end
1291 * @returns {Number} rotation
1292 */
1293 getRotation: function getRotation(start, end) {
1294 // need two fingers
1295 if(start.length >= 2 && end.length >= 2) {
1296 return this.getAngle(end[1], end[0]) -
1297 this.getAngle(start[1], start[0]);
1298 }
1299 return 0;
1300 },
1301
1302
1303 /**
1304 * boolean if the direction is vertical
1305 * @param {String} direction
1306 * @returns {Boolean} is_vertical
1307 */
1308 isVertical: function isVertical(direction) {
1309 return (direction == ionic.Gestures.DIRECTION_UP || direction == ionic.Gestures.DIRECTION_DOWN);
1310 },
1311
1312
1313 /**
1314 * stop browser default behavior with css class
1315 * @param {HtmlElement} element
1316 * @param {Object} css_class
1317 */
1318 stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_class) {
1319 // changed from making many style changes to just adding a preset classname
1320 // less DOM manipulations, less code, and easier to control in the CSS side of things
1321 // hammer.js doesn't come with CSS, but ionic does, which is why we prefer this method
1322 if(element && element.classList) {
1323 element.classList.add(css_class);
1324 element.onselectstart = function() {
1325 return false;
1326 };
1327 }
1328 }
1329 };
1330
1331
1332 ionic.Gestures.detection = {
1333 // contains all registred ionic.Gestures.gestures in the correct order
1334 gestures: [],
1335
1336 // data of the current ionic.Gestures.gesture detection session
1337 current: null,
1338
1339 // the previous ionic.Gestures.gesture session data
1340 // is a full clone of the previous gesture.current object
1341 previous: null,
1342
1343 // when this becomes true, no gestures are fired
1344 stopped: false,
1345
1346
1347 /**
1348 * start ionic.Gestures.gesture detection
1349 * @param {ionic.Gestures.Instance} inst
1350 * @param {Object} eventData
1351 */
1352 startDetect: function startDetect(inst, eventData) {
1353 // already busy with a ionic.Gestures.gesture detection on an element
1354 if(this.current) {
1355 return;
1356 }
1357
1358 this.stopped = false;
1359
1360 this.current = {
1361 inst: inst, // reference to ionic.GesturesInstance we're working for
1362 startEvent: ionic.Gestures.utils.extend({}, eventData), // start eventData for distances, timing etc
1363 lastEvent: false, // last eventData
1364 name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
1365 };
1366
1367 this.detect(eventData);
1368 },
1369
1370
1371 /**
1372 * ionic.Gestures.gesture detection
1373 * @param {Object} eventData
1374 */
1375 detect: function detect(eventData) {
1376 if(!this.current || this.stopped) {
1377 return null;
1378 }
1379
1380 // extend event data with calculations about scale, distance etc
1381 eventData = this.extendEventData(eventData);
1382
1383 // instance options
1384 var inst_options = this.current.inst.options;
1385
1386 // call ionic.Gestures.gesture handlers
1387 for(var g = 0, len = this.gestures.length; g < len; g++) {
1388 var gesture = this.gestures[g];
1389
1390 // only when the instance options have enabled this gesture
1391 if(!this.stopped && inst_options[gesture.name] !== false) {
1392 // if a handler returns false, we stop with the detection
1393 if(gesture.handler.call(gesture, eventData, this.current.inst) === false) {
1394 this.stopDetect();
1395 break;
1396 }
1397 }
1398 }
1399
1400 // store as previous event event
1401 if(this.current) {
1402 this.current.lastEvent = eventData;
1403 }
1404
1405 // endevent, but not the last touch, so dont stop
1406 if(eventData.eventType == ionic.Gestures.EVENT_END && !eventData.touches.length - 1) {
1407 this.stopDetect();
1408 }
1409
1410 return eventData;
1411 },
1412
1413
1414 /**
1415 * clear the ionic.Gestures.gesture vars
1416 * this is called on endDetect, but can also be used when a final ionic.Gestures.gesture has been detected
1417 * to stop other ionic.Gestures.gestures from being fired
1418 */
1419 stopDetect: function stopDetect() {
1420 // clone current data to the store as the previous gesture
1421 // used for the double tap gesture, since this is an other gesture detect session
1422 this.previous = ionic.Gestures.utils.extend({}, this.current);
1423
1424 // reset the current
1425 this.current = null;
1426
1427 // stopped!
1428 this.stopped = true;
1429 },
1430
1431
1432 /**
1433 * extend eventData for ionic.Gestures.gestures
1434 * @param {Object} ev
1435 * @returns {Object} ev
1436 */
1437 extendEventData: function extendEventData(ev) {
1438 var startEv = this.current.startEvent;
1439
1440 // if the touches change, set the new touches over the startEvent touches
1441 // this because touchevents don't have all the touches on touchstart, or the
1442 // user must place his fingers at the EXACT same time on the screen, which is not realistic
1443 // but, sometimes it happens that both fingers are touching at the EXACT same time
1444 if(startEv && (ev.touches.length != startEv.touches.length || ev.touches === startEv.touches)) {
1445 // extend 1 level deep to get the touchlist with the touch objects
1446 startEv.touches = [];
1447 for(var i = 0, len = ev.touches.length; i < len; i++) {
1448 startEv.touches.push(ionic.Gestures.utils.extend({}, ev.touches[i]));
1449 }
1450 }
1451
1452 var delta_time = ev.timeStamp - startEv.timeStamp,
1453 delta_x = ev.center.pageX - startEv.center.pageX,
1454 delta_y = ev.center.pageY - startEv.center.pageY,
1455 velocity = ionic.Gestures.utils.getVelocity(delta_time, delta_x, delta_y);
1456
1457 ionic.Gestures.utils.extend(ev, {
1458 deltaTime: delta_time,
1459 deltaX: delta_x,
1460 deltaY: delta_y,
1461
1462 velocityX: velocity.x,
1463 velocityY: velocity.y,
1464
1465 distance: ionic.Gestures.utils.getDistance(startEv.center, ev.center),
1466 angle: ionic.Gestures.utils.getAngle(startEv.center, ev.center),
1467 direction: ionic.Gestures.utils.getDirection(startEv.center, ev.center),
1468
1469 scale: ionic.Gestures.utils.getScale(startEv.touches, ev.touches),
1470 rotation: ionic.Gestures.utils.getRotation(startEv.touches, ev.touches),
1471
1472 startEvent: startEv
1473 });
1474
1475 return ev;
1476 },
1477
1478
1479 /**
1480 * register new gesture
1481 * @param {Object} gesture object, see gestures.js for documentation
1482 * @returns {Array} gestures
1483 */
1484 register: function register(gesture) {
1485 // add an enable gesture options if there is no given
1486 var options = gesture.defaults || {};
1487 if(options[gesture.name] === undefined) {
1488 options[gesture.name] = true;
1489 }
1490
1491 // extend ionic.Gestures default options with the ionic.Gestures.gesture options
1492 ionic.Gestures.utils.extend(ionic.Gestures.defaults, options, true);
1493
1494 // set its index
1495 gesture.index = gesture.index || 1000;
1496
1497 // add ionic.Gestures.gesture to the list
1498 this.gestures.push(gesture);
1499
1500 // sort the list by index
1501 this.gestures.sort(function(a, b) {
1502 if (a.index < b.index) {
1503 return -1;
1504 }
1505 if (a.index > b.index) {
1506 return 1;
1507 }
1508 return 0;
1509 });
1510
1511 return this.gestures;
1512 }
1513 };
1514
1515
1516 ionic.Gestures.gestures = ionic.Gestures.gestures || {};
1517
1518 /**
1519 * Custom gestures
1520 * ==============================
1521 *
1522 * Gesture object
1523 * --------------------
1524 * The object structure of a gesture:
1525 *
1526 * { name: 'mygesture',
1527 * index: 1337,
1528 * defaults: {
1529 * mygesture_option: true
1530 * }
1531 * handler: function(type, ev, inst) {
1532 * // trigger gesture event
1533 * inst.trigger(this.name, ev);
1534 * }
1535 * }
1536
1537 * @param {String} name
1538 * this should be the name of the gesture, lowercase
1539 * it is also being used to disable/enable the gesture per instance config.
1540 *
1541 * @param {Number} [index=1000]
1542 * the index of the gesture, where it is going to be in the stack of gestures detection
1543 * like when you build an gesture that depends on the drag gesture, it is a good
1544 * idea to place it after the index of the drag gesture.
1545 *
1546 * @param {Object} [defaults={}]
1547 * the default settings of the gesture. these are added to the instance settings,
1548 * and can be overruled per instance. you can also add the name of the gesture,
1549 * but this is also added by default (and set to true).
1550 *
1551 * @param {Function} handler
1552 * this handles the gesture detection of your custom gesture and receives the
1553 * following arguments:
1554 *
1555 * @param {Object} eventData
1556 * event data containing the following properties:
1557 * timeStamp {Number} time the event occurred
1558 * target {HTMLElement} target element
1559 * touches {Array} touches (fingers, pointers, mouse) on the screen
1560 * pointerType {String} kind of pointer that was used. matches ionic.Gestures.POINTER_MOUSE|TOUCH
1561 * center {Object} center position of the touches. contains pageX and pageY
1562 * deltaTime {Number} the total time of the touches in the screen
1563 * deltaX {Number} the delta on x axis we haved moved
1564 * deltaY {Number} the delta on y axis we haved moved
1565 * velocityX {Number} the velocity on the x
1566 * velocityY {Number} the velocity on y
1567 * angle {Number} the angle we are moving
1568 * direction {String} the direction we are moving. matches ionic.Gestures.DIRECTION_UP|DOWN|LEFT|RIGHT
1569 * distance {Number} the distance we haved moved
1570 * scale {Number} scaling of the touches, needs 2 touches
1571 * rotation {Number} rotation of the touches, needs 2 touches *
1572 * eventType {String} matches ionic.Gestures.EVENT_START|MOVE|END
1573 * srcEvent {Object} the source event, like TouchStart or MouseDown *
1574 * startEvent {Object} contains the same properties as above,
1575 * but from the first touch. this is used to calculate
1576 * distances, deltaTime, scaling etc
1577 *
1578 * @param {ionic.Gestures.Instance} inst
1579 * the instance we are doing the detection for. you can get the options from
1580 * the inst.options object and trigger the gesture event by calling inst.trigger
1581 *
1582 *
1583 * Handle gestures
1584 * --------------------
1585 * inside the handler you can get/set ionic.Gestures.detectionic.current. This is the current
1586 * detection sessionic. It has the following properties
1587 * @param {String} name
1588 * contains the name of the gesture we have detected. it has not a real function,
1589 * only to check in other gestures if something is detected.
1590 * like in the drag gesture we set it to 'drag' and in the swipe gesture we can
1591 * check if the current gesture is 'drag' by accessing ionic.Gestures.detectionic.current.name
1592 *
1593 * readonly
1594 * @param {ionic.Gestures.Instance} inst
1595 * the instance we do the detection for
1596 *
1597 * readonly
1598 * @param {Object} startEvent
1599 * contains the properties of the first gesture detection in this sessionic.
1600 * Used for calculations about timing, distance, etc.
1601 *
1602 * readonly
1603 * @param {Object} lastEvent
1604 * contains all the properties of the last gesture detect in this sessionic.
1605 *
1606 * after the gesture detection session has been completed (user has released the screen)
1607 * the ionic.Gestures.detectionic.current object is copied into ionic.Gestures.detectionic.previous,
1608 * this is usefull for gestures like doubletap, where you need to know if the
1609 * previous gesture was a tap
1610 *
1611 * options that have been set by the instance can be received by calling inst.options
1612 *
1613 * You can trigger a gesture event by calling inst.trigger("mygesture", event).
1614 * The first param is the name of your gesture, the second the event argument
1615 *
1616 *
1617 * Register gestures
1618 * --------------------
1619 * When an gesture is added to the ionic.Gestures.gestures object, it is auto registered
1620 * at the setup of the first ionic.Gestures instance. You can also call ionic.Gestures.detectionic.register
1621 * manually and pass your gesture object as a param
1622 *
1623 */
1624
1625 /**
1626 * Hold
1627 * Touch stays at the same place for x time
1628 * events hold
1629 */
1630 ionic.Gestures.gestures.Hold = {
1631 name: 'hold',
1632 index: 10,
1633 defaults: {
1634 hold_timeout: 500,
1635 hold_threshold: 9
1636 },
1637 timer: null,
1638 handler: function holdGesture(ev, inst) {
1639 switch(ev.eventType) {
1640 case ionic.Gestures.EVENT_START:
1641 // clear any running timers
1642 clearTimeout(this.timer);
1643
1644 // set the gesture so we can check in the timeout if it still is
1645 ionic.Gestures.detection.current.name = this.name;
1646
1647 // set timer and if after the timeout it still is hold,
1648 // we trigger the hold event
1649 this.timer = setTimeout(function() {
1650 if(ionic.Gestures.detection.current.name == 'hold') {
1651 ionic.tap.cancelClick();
1652 inst.trigger('hold', ev);
1653 }
1654 }, inst.options.hold_timeout);
1655 break;
1656
1657 // when you move or end we clear the timer
1658 case ionic.Gestures.EVENT_MOVE:
1659 if(ev.distance > inst.options.hold_threshold) {
1660 clearTimeout(this.timer);
1661 }
1662 break;
1663
1664 case ionic.Gestures.EVENT_END:
1665 clearTimeout(this.timer);
1666 break;
1667 }
1668 }
1669 };
1670
1671
1672 /**
1673 * Tap/DoubleTap
1674 * Quick touch at a place or double at the same place
1675 * events tap, doubletap
1676 */
1677 ionic.Gestures.gestures.Tap = {
1678 name: 'tap',
1679 index: 100,
1680 defaults: {
1681 tap_max_touchtime: 250,
1682 tap_max_distance: 10,
1683 tap_always: true,
1684 doubletap_distance: 20,
1685 doubletap_interval: 300
1686 },
1687 handler: function tapGesture(ev, inst) {
1688 if(ev.eventType == ionic.Gestures.EVENT_END && ev.srcEvent.type != 'touchcancel') {
1689 // previous gesture, for the double tap since these are two different gesture detections
1690 var prev = ionic.Gestures.detection.previous,
1691 did_doubletap = false;
1692
1693 // when the touchtime is higher then the max touch time
1694 // or when the moving distance is too much
1695 if(ev.deltaTime > inst.options.tap_max_touchtime ||
1696 ev.distance > inst.options.tap_max_distance) {
1697 return;
1698 }
1699
1700 // check if double tap
1701 if(prev && prev.name == 'tap' &&
1702 (ev.timeStamp - prev.lastEvent.timeStamp) < inst.options.doubletap_interval &&
1703 ev.distance < inst.options.doubletap_distance) {
1704 inst.trigger('doubletap', ev);
1705 did_doubletap = true;
1706 }
1707
1708 // do a single tap
1709 if(!did_doubletap || inst.options.tap_always) {
1710 ionic.Gestures.detection.current.name = 'tap';
1711 inst.trigger('tap', ev);
1712 }
1713 }
1714 }
1715 };
1716
1717
1718 /**
1719 * Swipe
1720 * triggers swipe events when the end velocity is above the threshold
1721 * events swipe, swipeleft, swiperight, swipeup, swipedown
1722 */
1723 ionic.Gestures.gestures.Swipe = {
1724 name: 'swipe',
1725 index: 40,
1726 defaults: {
1727 // set 0 for unlimited, but this can conflict with transform
1728 swipe_max_touches: 1,
1729 swipe_velocity: 0.4
1730 },
1731 handler: function swipeGesture(ev, inst) {
1732 if(ev.eventType == ionic.Gestures.EVENT_END) {
1733 // max touches
1734 if(inst.options.swipe_max_touches > 0 &&
1735 ev.touches.length > inst.options.swipe_max_touches) {
1736 return;
1737 }
1738
1739 // when the distance we moved is too small we skip this gesture
1740 // or we can be already in dragging
1741 if(ev.velocityX > inst.options.swipe_velocity ||
1742 ev.velocityY > inst.options.swipe_velocity) {
1743 // trigger swipe events
1744 inst.trigger(this.name, ev);
1745 inst.trigger(this.name + ev.direction, ev);
1746 }
1747 }
1748 }
1749 };
1750
1751
1752 /**
1753 * Drag
1754 * Move with x fingers (default 1) around on the page. Blocking the scrolling when
1755 * moving left and right is a good practice. When all the drag events are blocking
1756 * you disable scrolling on that area.
1757 * events drag, drapleft, dragright, dragup, dragdown
1758 */
1759 ionic.Gestures.gestures.Drag = {
1760 name: 'drag',
1761 index: 50,
1762 defaults: {
1763 drag_min_distance: 10,
1764 // Set correct_for_drag_min_distance to true to make the starting point of the drag
1765 // be calculated from where the drag was triggered, not from where the touch started.
1766 // Useful to avoid a jerk-starting drag, which can make fine-adjustments
1767 // through dragging difficult, and be visually unappealing.
1768 correct_for_drag_min_distance: true,
1769 // set 0 for unlimited, but this can conflict with transform
1770 drag_max_touches: 1,
1771 // prevent default browser behavior when dragging occurs
1772 // be careful with it, it makes the element a blocking element
1773 // when you are using the drag gesture, it is a good practice to set this true
1774 drag_block_horizontal: true,
1775 drag_block_vertical: true,
1776 // drag_lock_to_axis keeps the drag gesture on the axis that it started on,
1777 // It disallows vertical directions if the initial direction was horizontal, and vice versa.
1778 drag_lock_to_axis: false,
1779 // drag lock only kicks in when distance > drag_lock_min_distance
1780 // This way, locking occurs only when the distance has become large enough to reliably determine the direction
1781 drag_lock_min_distance: 25,
1782 // prevent default if the gesture is going the given direction
1783 prevent_default_directions: []
1784 },
1785 triggered: false,
1786 handler: function dragGesture(ev, inst) {
1787 if (ev.srcEvent.type == 'touchstart' || ev.srcEvent.type == 'touchend') {
1788 this.preventedFirstMove = false;
1789
1790 } else if (!this.preventedFirstMove && ev.srcEvent.type == 'touchmove') {
1791 // Prevent gestures that are not intended for this event handler from firing subsequent times
1792 if (inst.options.prevent_default_directions.length > 0
1793 && inst.options.prevent_default_directions.indexOf(ev.direction) != -1) {
1794 ev.srcEvent.preventDefault();
1795 }
1796 this.preventedFirstMove = true;
1797 }
1798
1799 // current gesture isnt drag, but dragged is true
1800 // this means an other gesture is busy. now call dragend
1801 if(ionic.Gestures.detection.current.name != this.name && this.triggered) {
1802 inst.trigger(this.name + 'end', ev);
1803 this.triggered = false;
1804 return;
1805 }
1806
1807 // max touches
1808 if(inst.options.drag_max_touches > 0 &&
1809 ev.touches.length > inst.options.drag_max_touches) {
1810 return;
1811 }
1812
1813 switch(ev.eventType) {
1814 case ionic.Gestures.EVENT_START:
1815 this.triggered = false;
1816 break;
1817
1818 case ionic.Gestures.EVENT_MOVE:
1819 // when the distance we moved is too small we skip this gesture
1820 // or we can be already in dragging
1821 if(ev.distance < inst.options.drag_min_distance &&
1822 ionic.Gestures.detection.current.name != this.name) {
1823 return;
1824 }
1825
1826 // we are dragging!
1827 if(ionic.Gestures.detection.current.name != this.name) {
1828 ionic.Gestures.detection.current.name = this.name;
1829 if (inst.options.correct_for_drag_min_distance) {
1830 // When a drag is triggered, set the event center to drag_min_distance pixels from the original event center.
1831 // Without this correction, the dragged distance would jumpstart at drag_min_distance pixels instead of at 0.
1832 // It might be useful to save the original start point somewhere
1833 var factor = Math.abs(inst.options.drag_min_distance / ev.distance);
1834 ionic.Gestures.detection.current.startEvent.center.pageX += ev.deltaX * factor;
1835 ionic.Gestures.detection.current.startEvent.center.pageY += ev.deltaY * factor;
1836
1837 // recalculate event data using new start point
1838 ev = ionic.Gestures.detection.extendEventData(ev);
1839 }
1840 }
1841
1842 // lock drag to axis?
1843 if(ionic.Gestures.detection.current.lastEvent.drag_locked_to_axis || (inst.options.drag_lock_to_axis && inst.options.drag_lock_min_distance <= ev.distance)) {
1844 ev.drag_locked_to_axis = true;
1845 }
1846 var last_direction = ionic.Gestures.detection.current.lastEvent.direction;
1847 if(ev.drag_locked_to_axis && last_direction !== ev.direction) {
1848 // keep direction on the axis that the drag gesture started on
1849 if(ionic.Gestures.utils.isVertical(last_direction)) {
1850 ev.direction = (ev.deltaY < 0) ? ionic.Gestures.DIRECTION_UP : ionic.Gestures.DIRECTION_DOWN;
1851 }
1852 else {
1853 ev.direction = (ev.deltaX < 0) ? ionic.Gestures.DIRECTION_LEFT : ionic.Gestures.DIRECTION_RIGHT;
1854 }
1855 }
1856
1857 // first time, trigger dragstart event
1858 if(!this.triggered) {
1859 inst.trigger(this.name + 'start', ev);
1860 this.triggered = true;
1861 }
1862
1863 // trigger normal event
1864 inst.trigger(this.name, ev);
1865
1866 // direction event, like dragdown
1867 inst.trigger(this.name + ev.direction, ev);
1868
1869 // block the browser events
1870 if( (inst.options.drag_block_vertical && ionic.Gestures.utils.isVertical(ev.direction)) ||
1871 (inst.options.drag_block_horizontal && !ionic.Gestures.utils.isVertical(ev.direction))) {
1872 ev.preventDefault();
1873 }
1874 break;
1875
1876 case ionic.Gestures.EVENT_END:
1877 // trigger dragend
1878 if(this.triggered) {
1879 inst.trigger(this.name + 'end', ev);
1880 }
1881
1882 this.triggered = false;
1883 break;
1884 }
1885 }
1886 };
1887
1888
1889 /**
1890 * Transform
1891 * User want to scale or rotate with 2 fingers
1892 * events transform, pinch, pinchin, pinchout, rotate
1893 */
1894 ionic.Gestures.gestures.Transform = {
1895 name: 'transform',
1896 index: 45,
1897 defaults: {
1898 // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
1899 transform_min_scale: 0.01,
1900 // rotation in degrees
1901 transform_min_rotation: 1,
1902 // prevent default browser behavior when two touches are on the screen
1903 // but it makes the element a blocking element
1904 // when you are using the transform gesture, it is a good practice to set this true
1905 transform_always_block: false
1906 },
1907 triggered: false,
1908 handler: function transformGesture(ev, inst) {
1909 // current gesture isnt drag, but dragged is true
1910 // this means an other gesture is busy. now call dragend
1911 if(ionic.Gestures.detection.current.name != this.name && this.triggered) {
1912 inst.trigger(this.name + 'end', ev);
1913 this.triggered = false;
1914 return;
1915 }
1916
1917 // atleast multitouch
1918 if(ev.touches.length < 2) {
1919 return;
1920 }
1921
1922 // prevent default when two fingers are on the screen
1923 if(inst.options.transform_always_block) {
1924 ev.preventDefault();
1925 }
1926
1927 switch(ev.eventType) {
1928 case ionic.Gestures.EVENT_START:
1929 this.triggered = false;
1930 break;
1931
1932 case ionic.Gestures.EVENT_MOVE:
1933 var scale_threshold = Math.abs(1 - ev.scale);
1934 var rotation_threshold = Math.abs(ev.rotation);
1935
1936 // when the distance we moved is too small we skip this gesture
1937 // or we can be already in dragging
1938 if(scale_threshold < inst.options.transform_min_scale &&
1939 rotation_threshold < inst.options.transform_min_rotation) {
1940 return;
1941 }
1942
1943 // we are transforming!
1944 ionic.Gestures.detection.current.name = this.name;
1945
1946 // first time, trigger dragstart event
1947 if(!this.triggered) {
1948 inst.trigger(this.name + 'start', ev);
1949 this.triggered = true;
1950 }
1951
1952 inst.trigger(this.name, ev); // basic transform event
1953
1954 // trigger rotate event
1955 if(rotation_threshold > inst.options.transform_min_rotation) {
1956 inst.trigger('rotate', ev);
1957 }
1958
1959 // trigger pinch event
1960 if(scale_threshold > inst.options.transform_min_scale) {
1961 inst.trigger('pinch', ev);
1962 inst.trigger('pinch' + ((ev.scale < 1) ? 'in' : 'out'), ev);
1963 }
1964 break;
1965
1966 case ionic.Gestures.EVENT_END:
1967 // trigger dragend
1968 if(this.triggered) {
1969 inst.trigger(this.name + 'end', ev);
1970 }
1971
1972 this.triggered = false;
1973 break;
1974 }
1975 }
1976 };
1977
1978
1979 /**
1980 * Touch
1981 * Called as first, tells the user has touched the screen
1982 * events touch
1983 */
1984 ionic.Gestures.gestures.Touch = {
1985 name: 'touch',
1986 index: -Infinity,
1987 defaults: {
1988 // call preventDefault at touchstart, and makes the element blocking by
1989 // disabling the scrolling of the page, but it improves gestures like
1990 // transforming and dragging.
1991 // be careful with using this, it can be very annoying for users to be stuck
1992 // on the page
1993 prevent_default: false,
1994
1995 // disable mouse events, so only touch (or pen!) input triggers events
1996 prevent_mouseevents: false
1997 },
1998 handler: function touchGesture(ev, inst) {
1999 if(inst.options.prevent_mouseevents && ev.pointerType == ionic.Gestures.POINTER_MOUSE) {
2000 ev.stopDetect();
2001 return;
2002 }
2003
2004 if(inst.options.prevent_default) {
2005 ev.preventDefault();
2006 }
2007
2008 if(ev.eventType == ionic.Gestures.EVENT_START) {
2009 inst.trigger(this.name, ev);
2010 }
2011 }
2012 };
2013
2014
2015 /**
2016 * Release
2017 * Called as last, tells the user has released the screen
2018 * events release
2019 */
2020 ionic.Gestures.gestures.Release = {
2021 name: 'release',
2022 index: Infinity,
2023 handler: function releaseGesture(ev, inst) {
2024 if(ev.eventType == ionic.Gestures.EVENT_END) {
2025 inst.trigger(this.name, ev);
2026 }
2027 }
2028 };
2029})(window.ionic);
2030
2031(function(window, document, ionic) {
2032
2033 function getParameterByName(name) {
2034 name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
2035 var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
2036 results = regex.exec(location.search);
2037 return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
2038 }
2039
2040 var IOS = 'ios';
2041 var ANDROID = 'android';
2042 var WINDOWS_PHONE = 'windowsphone';
2043 var EDGE = 'edge';
2044 var CROSSWALK = 'crosswalk';
2045 var requestAnimationFrame = ionic.requestAnimationFrame;
2046
2047 /**
2048 * @ngdoc utility
2049 * @name ionic.Platform
2050 * @module ionic
2051 * @description
2052 * A set of utility methods that can be used to retrieve the device ready state and
2053 * various other information such as what kind of platform the app is currently installed on.
2054 *
2055 * @usage
2056 * ```js
2057 * angular.module('PlatformApp', ['ionic'])
2058 * .controller('PlatformCtrl', function($scope) {
2059 *
2060 * ionic.Platform.ready(function(){
2061 * // will execute when device is ready, or immediately if the device is already ready.
2062 * });
2063 *
2064 * var deviceInformation = ionic.Platform.device();
2065 *
2066 * var isWebView = ionic.Platform.isWebView();
2067 * var isIPad = ionic.Platform.isIPad();
2068 * var isIOS = ionic.Platform.isIOS();
2069 * var isAndroid = ionic.Platform.isAndroid();
2070 * var isWindowsPhone = ionic.Platform.isWindowsPhone();
2071 *
2072 * var currentPlatform = ionic.Platform.platform();
2073 * var currentPlatformVersion = ionic.Platform.version();
2074 *
2075 * ionic.Platform.exitApp(); // stops the app
2076 * });
2077 * ```
2078 */
2079 var self = ionic.Platform = {
2080
2081 // Put navigator on platform so it can be mocked and set
2082 // the browser does not allow window.navigator to be set
2083 navigator: window.navigator,
2084
2085 /**
2086 * @ngdoc property
2087 * @name ionic.Platform#isReady
2088 * @returns {boolean} Whether the device is ready.
2089 */
2090 isReady: false,
2091 /**
2092 * @ngdoc property
2093 * @name ionic.Platform#isFullScreen
2094 * @returns {boolean} Whether the device is fullscreen.
2095 */
2096 isFullScreen: false,
2097 /**
2098 * @ngdoc property
2099 * @name ionic.Platform#platforms
2100 * @returns {Array(string)} An array of all platforms found.
2101 */
2102 platforms: null,
2103 /**
2104 * @ngdoc property
2105 * @name ionic.Platform#grade
2106 * @returns {string} What grade the current platform is.
2107 */
2108 grade: null,
2109 /**
2110 * @ngdoc property
2111 * @name ionic.Platform#ua
2112 * @returns {string} What User Agent is.
2113 */
2114 ua: navigator.userAgent,
2115
2116 /**
2117 * @ngdoc method
2118 * @name ionic.Platform#ready
2119 * @description
2120 * Trigger a callback once the device is ready, or immediately
2121 * if the device is already ready. This method can be run from
2122 * anywhere and does not need to be wrapped by any additonal methods.
2123 * When the app is within a WebView (Cordova), it'll fire
2124 * the callback once the device is ready. If the app is within
2125 * a web browser, it'll fire the callback after `window.load`.
2126 * Please remember that Cordova features (Camera, FileSystem, etc) still
2127 * will not work in a web browser.
2128 * @param {function} callback The function to call.
2129 */
2130 ready: function(cb) {
2131 // run through tasks to complete now that the device is ready
2132 if (self.isReady) {
2133 cb();
2134 } else {
2135 // the platform isn't ready yet, add it to this array
2136 // which will be called once the platform is ready
2137 readyCallbacks.push(cb);
2138 }
2139 },
2140
2141 /**
2142 * @private
2143 */
2144 detect: function() {
2145 self._checkPlatforms();
2146
2147 requestAnimationFrame(function() {
2148 // only add to the body class if we got platform info
2149 for (var i = 0; i < self.platforms.length; i++) {
2150 document.body.classList.add('platform-' + self.platforms[i]);
2151 }
2152 });
2153 },
2154
2155 /**
2156 * @ngdoc method
2157 * @name ionic.Platform#setGrade
2158 * @description Set the grade of the device: 'a', 'b', or 'c'. 'a' is the best
2159 * (most css features enabled), 'c' is the worst. By default, sets the grade
2160 * depending on the current device.
2161 * @param {string} grade The new grade to set.
2162 */
2163 setGrade: function(grade) {
2164 var oldGrade = self.grade;
2165 self.grade = grade;
2166 requestAnimationFrame(function() {
2167 if (oldGrade) {
2168 document.body.classList.remove('grade-' + oldGrade);
2169 }
2170 document.body.classList.add('grade-' + grade);
2171 });
2172 },
2173
2174 /**
2175 * @ngdoc method
2176 * @name ionic.Platform#device
2177 * @description Return the current device (given by cordova).
2178 * @returns {object} The device object.
2179 */
2180 device: function() {
2181 return window.device || {};
2182 },
2183
2184 _checkPlatforms: function() {
2185 self.platforms = [];
2186 var grade = 'a';
2187
2188 if (self.isWebView()) {
2189 self.platforms.push('webview');
2190 if (!(!window.cordova && !window.PhoneGap && !window.phonegap)) {
2191 self.platforms.push('cordova');
2192 } else if (typeof window.forge === 'object') {
2193 self.platforms.push('trigger');
2194 }
2195 } else {
2196 self.platforms.push('browser');
2197 }
2198 if (self.isIPad()) self.platforms.push('ipad');
2199
2200 var platform = self.platform();
2201 if (platform) {
2202 self.platforms.push(platform);
2203
2204 var version = self.version();
2205 if (version) {
2206 var v = version.toString();
2207 if (v.indexOf('.') > 0) {
2208 v = v.replace('.', '_');
2209 } else {
2210 v += '_0';
2211 }
2212 self.platforms.push(platform + v.split('_')[0]);
2213 self.platforms.push(platform + v);
2214
2215 if (self.isAndroid() && version < 4.4) {
2216 grade = (version < 4 ? 'c' : 'b');
2217 } else if (self.isWindowsPhone()) {
2218 grade = 'b';
2219 }
2220 }
2221 }
2222
2223 self.setGrade(grade);
2224 },
2225
2226 /**
2227 * @ngdoc method
2228 * @name ionic.Platform#isWebView
2229 * @returns {boolean} Check if we are running within a WebView (such as Cordova).
2230 */
2231 isWebView: function() {
2232 return !(!window.cordova && !window.PhoneGap && !window.phonegap && window.forge !== 'object');
2233 },
2234 /**
2235 * @ngdoc method
2236 * @name ionic.Platform#isIPad
2237 * @returns {boolean} Whether we are running on iPad.
2238 */
2239 isIPad: function() {
2240 if (/iPad/i.test(self.navigator.platform)) {
2241 return true;
2242 }
2243 return /iPad/i.test(self.ua);
2244 },
2245 /**
2246 * @ngdoc method
2247 * @name ionic.Platform#isIOS
2248 * @returns {boolean} Whether we are running on iOS.
2249 */
2250 isIOS: function() {
2251 return self.is(IOS);
2252 },
2253 /**
2254 * @ngdoc method
2255 * @name ionic.Platform#isAndroid
2256 * @returns {boolean} Whether we are running on Android.
2257 */
2258 isAndroid: function() {
2259 return self.is(ANDROID);
2260 },
2261 /**
2262 * @ngdoc method
2263 * @name ionic.Platform#isWindowsPhone
2264 * @returns {boolean} Whether we are running on Windows Phone.
2265 */
2266 isWindowsPhone: function() {
2267 return self.is(WINDOWS_PHONE);
2268 },
2269 /**
2270 * @ngdoc method
2271 * @name ionic.Platform#isEdge
2272 * @returns {boolean} Whether we are running on MS Edge/Windows 10 (inc. Phone)
2273 */
2274 isEdge: function() {
2275 return self.is(EDGE);
2276 },
2277
2278 isCrosswalk: function() {
2279 return self.is(CROSSWALK);
2280 },
2281
2282 /**
2283 * @ngdoc method
2284 * @name ionic.Platform#platform
2285 * @returns {string} The name of the current platform.
2286 */
2287 platform: function() {
2288 // singleton to get the platform name
2289 if (platformName === null) self.setPlatform(self.device().platform);
2290 return platformName;
2291 },
2292
2293 /**
2294 * @private
2295 */
2296 setPlatform: function(n) {
2297 if (typeof n != 'undefined' && n !== null && n.length) {
2298 platformName = n.toLowerCase();
2299 } else if (getParameterByName('ionicplatform')) {
2300 platformName = getParameterByName('ionicplatform');
2301 } else if (self.ua.indexOf('Edge') > -1) {
2302 platformName = EDGE;
2303 } else if (self.ua.indexOf('Windows Phone') > -1) {
2304 platformName = WINDOWS_PHONE;
2305 } else if (self.ua.indexOf('Android') > 0) {
2306 platformName = ANDROID;
2307 } else if (/iPhone|iPad|iPod/.test(self.ua)) {
2308 platformName = IOS;
2309 } else {
2310 platformName = self.navigator.platform && navigator.platform.toLowerCase().split(' ')[0] || '';
2311 }
2312 },
2313
2314 /**
2315 * @ngdoc method
2316 * @name ionic.Platform#version
2317 * @returns {number} The version of the current device platform.
2318 */
2319 version: function() {
2320 // singleton to get the platform version
2321 if (platformVersion === null) self.setVersion(self.device().version);
2322 return platformVersion;
2323 },
2324
2325 /**
2326 * @private
2327 */
2328 setVersion: function(v) {
2329 if (typeof v != 'undefined' && v !== null) {
2330 v = v.split('.');
2331 v = parseFloat(v[0] + '.' + (v.length > 1 ? v[1] : 0));
2332 if (!isNaN(v)) {
2333 platformVersion = v;
2334 return;
2335 }
2336 }
2337
2338 platformVersion = 0;
2339
2340 // fallback to user-agent checking
2341 var pName = self.platform();
2342 var versionMatch = {
2343 'android': /Android (\d+).(\d+)?/,
2344 'ios': /OS (\d+)_(\d+)?/,
2345 'windowsphone': /Windows Phone (\d+).(\d+)?/
2346 };
2347 if (versionMatch[pName]) {
2348 v = self.ua.match(versionMatch[pName]);
2349 if (v && v.length > 2) {
2350 platformVersion = parseFloat(v[1] + '.' + v[2]);
2351 }
2352 }
2353 },
2354
2355 /**
2356 * @ngdoc method
2357 * @name ionic.Platform#is
2358 * @param {string} Platform name.
2359 * @returns {boolean} Whether the platform name provided is detected.
2360 */
2361 is: function(type) {
2362 type = type.toLowerCase();
2363 // check if it has an array of platforms
2364 if (self.platforms) {
2365 for (var x = 0; x < self.platforms.length; x++) {
2366 if (self.platforms[x] === type) return true;
2367 }
2368 }
2369 // exact match
2370 var pName = self.platform();
2371 if (pName) {
2372 return pName === type.toLowerCase();
2373 }
2374
2375 // A quick hack for to check userAgent
2376 return self.ua.toLowerCase().indexOf(type) >= 0;
2377 },
2378
2379 /**
2380 * @ngdoc method
2381 * @name ionic.Platform#exitApp
2382 * @description Exit the app.
2383 */
2384 exitApp: function() {
2385 self.ready(function() {
2386 navigator.app && navigator.app.exitApp && navigator.app.exitApp();
2387 });
2388 },
2389
2390 /**
2391 * @ngdoc method
2392 * @name ionic.Platform#showStatusBar
2393 * @description Shows or hides the device status bar (in Cordova). Requires `cordova plugin add org.apache.cordova.statusbar`
2394 * @param {boolean} shouldShow Whether or not to show the status bar.
2395 */
2396 showStatusBar: function(val) {
2397 // Only useful when run within cordova
2398 self._showStatusBar = val;
2399 self.ready(function() {
2400 // run this only when or if the platform (cordova) is ready
2401 requestAnimationFrame(function() {
2402 if (self._showStatusBar) {
2403 // they do not want it to be full screen
2404 window.StatusBar && window.StatusBar.show();
2405 document.body.classList.remove('status-bar-hide');
2406 } else {
2407 // it should be full screen
2408 window.StatusBar && window.StatusBar.hide();
2409 document.body.classList.add('status-bar-hide');
2410 }
2411 });
2412 });
2413 },
2414
2415 /**
2416 * @ngdoc method
2417 * @name ionic.Platform#fullScreen
2418 * @description
2419 * Sets whether the app is fullscreen or not (in Cordova).
2420 * @param {boolean=} showFullScreen Whether or not to set the app to fullscreen. Defaults to true. Requires `cordova plugin add org.apache.cordova.statusbar`
2421 * @param {boolean=} showStatusBar Whether or not to show the device's status bar. Defaults to false.
2422 */
2423 fullScreen: function(showFullScreen, showStatusBar) {
2424 // showFullScreen: default is true if no param provided
2425 self.isFullScreen = (showFullScreen !== false);
2426
2427 // add/remove the fullscreen classname to the body
2428 ionic.DomUtil.ready(function() {
2429 // run this only when or if the DOM is ready
2430 requestAnimationFrame(function() {
2431 if (self.isFullScreen) {
2432 document.body.classList.add('fullscreen');
2433 } else {
2434 document.body.classList.remove('fullscreen');
2435 }
2436 });
2437 // showStatusBar: default is false if no param provided
2438 self.showStatusBar((showStatusBar === true));
2439 });
2440 }
2441
2442 };
2443
2444 var platformName = null, // just the name, like iOS or Android
2445 platformVersion = null, // a float of the major and minor, like 7.1
2446 readyCallbacks = [],
2447 windowLoadListenderAttached,
2448 platformReadyTimer = 2000; // How long to wait for platform ready before emitting a warning
2449
2450 verifyPlatformReady();
2451
2452 // Warn the user if deviceready did not fire in a reasonable amount of time, and how to fix it.
2453 function verifyPlatformReady() {
2454 setTimeout(function() {
2455 if(!self.isReady && self.isWebView()) {
2456 void 0;
2457 }
2458 }, platformReadyTimer);
2459 }
2460
2461 // setup listeners to know when the device is ready to go
2462 function onWindowLoad() {
2463 if (self.isWebView()) {
2464 // the window and scripts are fully loaded, and a cordova/phonegap
2465 // object exists then let's listen for the deviceready
2466 document.addEventListener("deviceready", onPlatformReady, false);
2467 } else {
2468 // the window and scripts are fully loaded, but the window object doesn't have the
2469 // cordova/phonegap object, so its just a browser, not a webview wrapped w/ cordova
2470 onPlatformReady();
2471 }
2472 if (windowLoadListenderAttached) {
2473 window.removeEventListener("load", onWindowLoad, false);
2474 }
2475 }
2476 if (document.readyState === 'complete') {
2477 onWindowLoad();
2478 } else {
2479 windowLoadListenderAttached = true;
2480 window.addEventListener("load", onWindowLoad, false);
2481 }
2482
2483 function onPlatformReady() {
2484 // the device is all set to go, init our own stuff then fire off our event
2485 self.isReady = true;
2486 self.detect();
2487 for (var x = 0; x < readyCallbacks.length; x++) {
2488 // fire off all the callbacks that were added before the platform was ready
2489 readyCallbacks[x]();
2490 }
2491 readyCallbacks = [];
2492 ionic.trigger('platformready', { target: document });
2493
2494 requestAnimationFrame(function() {
2495 document.body.classList.add('platform-ready');
2496 });
2497 }
2498
2499})(window, document, ionic);
2500
2501(function(document, ionic) {
2502 'use strict';
2503
2504 // Ionic CSS polyfills
2505 ionic.CSS = {};
2506 ionic.CSS.TRANSITION = [];
2507 ionic.CSS.TRANSFORM = [];
2508
2509 ionic.EVENTS = {};
2510
2511 (function() {
2512
2513 // transform
2514 var i, keys = ['webkitTransform', 'transform', '-webkit-transform', 'webkit-transform',
2515 '-moz-transform', 'moz-transform', 'MozTransform', 'mozTransform', 'msTransform'];
2516
2517 for (i = 0; i < keys.length; i++) {
2518 if (document.documentElement.style[keys[i]] !== undefined) {
2519 ionic.CSS.TRANSFORM = keys[i];
2520 break;
2521 }
2522 }
2523
2524 // transition
2525 keys = ['webkitTransition', 'mozTransition', 'msTransition', 'transition'];
2526 for (i = 0; i < keys.length; i++) {
2527 if (document.documentElement.style[keys[i]] !== undefined) {
2528 ionic.CSS.TRANSITION = keys[i];
2529 break;
2530 }
2531 }
2532
2533 // Fallback in case the keys don't exist at all
2534 ionic.CSS.TRANSITION = ionic.CSS.TRANSITION || 'transition';
2535
2536 // The only prefix we care about is webkit for transitions.
2537 var isWebkit = ionic.CSS.TRANSITION.indexOf('webkit') > -1;
2538
2539 // transition duration
2540 ionic.CSS.TRANSITION_DURATION = (isWebkit ? '-webkit-' : '') + 'transition-duration';
2541
2542 // To be sure transitionend works everywhere, include *both* the webkit and non-webkit events
2543 ionic.CSS.TRANSITIONEND = (isWebkit ? 'webkitTransitionEnd ' : '') + 'transitionend';
2544 })();
2545
2546 (function() {
2547 var touchStartEvent = 'touchstart';
2548 var touchMoveEvent = 'touchmove';
2549 var touchEndEvent = 'touchend';
2550 var touchCancelEvent = 'touchcancel';
2551
2552 if (window.navigator.pointerEnabled) {
2553 touchStartEvent = 'pointerdown';
2554 touchMoveEvent = 'pointermove';
2555 touchEndEvent = 'pointerup';
2556 touchCancelEvent = 'pointercancel';
2557 } else if (window.navigator.msPointerEnabled) {
2558 touchStartEvent = 'MSPointerDown';
2559 touchMoveEvent = 'MSPointerMove';
2560 touchEndEvent = 'MSPointerUp';
2561 touchCancelEvent = 'MSPointerCancel';
2562 }
2563
2564 ionic.EVENTS.touchstart = touchStartEvent;
2565 ionic.EVENTS.touchmove = touchMoveEvent;
2566 ionic.EVENTS.touchend = touchEndEvent;
2567 ionic.EVENTS.touchcancel = touchCancelEvent;
2568 })();
2569
2570 // classList polyfill for them older Androids
2571 // https://gist.github.com/devongovett/1381839
2572 if (!("classList" in document.documentElement) && Object.defineProperty && typeof HTMLElement !== 'undefined') {
2573 Object.defineProperty(HTMLElement.prototype, 'classList', {
2574 get: function() {
2575 var self = this;
2576 function update(fn) {
2577 return function() {
2578 var x, classes = self.className.split(/\s+/);
2579
2580 for (x = 0; x < arguments.length; x++) {
2581 fn(classes, classes.indexOf(arguments[x]), arguments[x]);
2582 }
2583
2584 self.className = classes.join(" ");
2585 };
2586 }
2587
2588 return {
2589 add: update(function(classes, index, value) {
2590 ~index || classes.push(value);
2591 }),
2592
2593 remove: update(function(classes, index) {
2594 ~index && classes.splice(index, 1);
2595 }),
2596
2597 toggle: update(function(classes, index, value) {
2598 ~index ? classes.splice(index, 1) : classes.push(value);
2599 }),
2600
2601 contains: function(value) {
2602 return !!~self.className.split(/\s+/).indexOf(value);
2603 },
2604
2605 item: function(i) {
2606 return self.className.split(/\s+/)[i] || null;
2607 }
2608 };
2609
2610 }
2611 });
2612 }
2613
2614})(document, ionic);
2615
2616
2617/**
2618 * @ngdoc page
2619 * @name tap
2620 * @module ionic
2621 * @description
2622 * On touch devices such as a phone or tablet, some browsers implement a 300ms delay between
2623 * the time the user stops touching the display and the moment the browser executes the
2624 * click. This delay was initially introduced so the browser can know whether the user wants to
2625 * double-tap to zoom in on the webpage. Basically, the browser waits roughly 300ms to see if
2626 * the user is double-tapping, or just tapping on the display once.
2627 *
2628 * Out of the box, Ionic automatically removes the 300ms delay in order to make Ionic apps
2629 * feel more "native" like. Resultingly, other solutions such as
2630 * [fastclick](https://github.com/ftlabs/fastclick) and Angular's
2631 * [ngTouch](https://docs.angularjs.org/api/ngTouch) should not be included, to avoid conflicts.
2632 *
2633 * Some browsers already remove the delay with certain settings, such as the CSS property
2634 * `touch-events: none` or with specific meta tag viewport values. However, each of these
2635 * browsers still handle clicks differently, such as when to fire off or cancel the event
2636 * (like scrolling when the target is a button, or holding a button down).
2637 * For browsers that already remove the 300ms delay, consider Ionic's tap system as a way to
2638 * normalize how clicks are handled across the various devices so there's an expected response
2639 * no matter what the device, platform or version. Additionally, Ionic will prevent
2640 * ghostclicks which even browsers that remove the delay still experience.
2641 *
2642 * In some cases, third-party libraries may also be working with touch events which can interfere
2643 * with the tap system. For example, mapping libraries like Google or Leaflet Maps often implement
2644 * a touch detection system which conflicts with Ionic's tap system.
2645 *
2646 * ### Disabling the tap system
2647 *
2648 * To disable the tap for an element and all of its children elements,
2649 * add the attribute `data-tap-disabled="true"`.
2650 *
2651 * ```html
2652 * <div data-tap-disabled="true">
2653 * <div id="google-map"></div>
2654 * </div>
2655 * ```
2656 *
2657 * ### Additional Notes:
2658 *
2659 * - Ionic tap works with Ionic's JavaScript scrolling
2660 * - Elements can come and go from the DOM and Ionic tap doesn't keep adding and removing
2661 * listeners
2662 * - No "tap delay" after the first "tap" (you can tap as fast as you want, they all click)
2663 * - Minimal events listeners, only being added to document
2664 * - Correct focus in/out on each input type (select, textearea, range) on each platform/device
2665 * - Shows and hides virtual keyboard correctly for each platform/device
2666 * - Works with labels surrounding inputs
2667 * - Does not fire off a click if the user moves the pointer too far
2668 * - Adds and removes an 'activated' css class
2669 * - Multiple [unit tests](https://github.com/driftyco/ionic/blob/master/test/unit/utils/tap.unit.js) for each scenario
2670 *
2671 */
2672/*
2673
2674 IONIC TAP
2675 ---------------
2676 - Both touch and mouse events are added to the document.body on DOM ready
2677 - If a touch event happens, it does not use mouse event listeners
2678 - On touchend, if the distance between start and end was small, trigger a click
2679 - In the triggered click event, add a 'isIonicTap' property
2680 - The triggered click receives the same x,y coordinates as as the end event
2681 - On document.body click listener (with useCapture=true), only allow clicks with 'isIonicTap'
2682 - Triggering clicks with mouse events work the same as touch, except with mousedown/mouseup
2683 - Tapping inputs is disabled during scrolling
2684*/
2685
2686var tapDoc; // the element which the listeners are on (document.body)
2687var tapActiveEle; // the element which is active (probably has focus)
2688var tapEnabledTouchEvents;
2689var tapMouseResetTimer;
2690var tapPointerMoved;
2691var tapPointerStart;
2692var tapTouchFocusedInput;
2693var tapLastTouchTarget;
2694var tapTouchMoveListener = 'touchmove';
2695
2696// how much the coordinates can be off between start/end, but still a click
2697var TAP_RELEASE_TOLERANCE = 12; // default tolerance
2698var TAP_RELEASE_BUTTON_TOLERANCE = 50; // button elements should have a larger tolerance
2699
2700var tapEventListeners = {
2701 'click': tapClickGateKeeper,
2702
2703 'mousedown': tapMouseDown,
2704 'mouseup': tapMouseUp,
2705 'mousemove': tapMouseMove,
2706
2707 'touchstart': tapTouchStart,
2708 'touchend': tapTouchEnd,
2709 'touchcancel': tapTouchCancel,
2710 'touchmove': tapTouchMove,
2711
2712 'pointerdown': tapTouchStart,
2713 'pointerup': tapTouchEnd,
2714 'pointercancel': tapTouchCancel,
2715 'pointermove': tapTouchMove,
2716
2717 'MSPointerDown': tapTouchStart,
2718 'MSPointerUp': tapTouchEnd,
2719 'MSPointerCancel': tapTouchCancel,
2720 'MSPointerMove': tapTouchMove,
2721
2722 'focusin': tapFocusIn,
2723 'focusout': tapFocusOut
2724};
2725
2726ionic.tap = {
2727
2728 register: function(ele) {
2729 tapDoc = ele;
2730
2731 tapEventListener('click', true, true);
2732 tapEventListener('mouseup');
2733 tapEventListener('mousedown');
2734
2735 if (window.navigator.pointerEnabled) {
2736 tapEventListener('pointerdown');
2737 tapEventListener('pointerup');
2738 tapEventListener('pointercancel');
2739 tapTouchMoveListener = 'pointermove';
2740
2741 } else if (window.navigator.msPointerEnabled) {
2742 tapEventListener('MSPointerDown');
2743 tapEventListener('MSPointerUp');
2744 tapEventListener('MSPointerCancel');
2745 tapTouchMoveListener = 'MSPointerMove';
2746
2747 } else {
2748 tapEventListener('touchstart');
2749 tapEventListener('touchend');
2750 tapEventListener('touchcancel');
2751 }
2752
2753 tapEventListener('focusin');
2754 tapEventListener('focusout');
2755
2756 return function() {
2757 for (var type in tapEventListeners) {
2758 tapEventListener(type, false);
2759 }
2760 tapDoc = null;
2761 tapActiveEle = null;
2762 tapEnabledTouchEvents = false;
2763 tapPointerMoved = false;
2764 tapPointerStart = null;
2765 };
2766 },
2767
2768 ignoreScrollStart: function(e) {
2769 return (e.defaultPrevented) || // defaultPrevented has been assigned by another component handling the event
2770 (/^(file|range)$/i).test(e.target.type) ||
2771 (e.target.dataset ? e.target.dataset.preventScroll : e.target.getAttribute('data-prevent-scroll')) == 'true' || // manually set within an elements attributes
2772 (!!(/^(object|embed)$/i).test(e.target.tagName)) || // flash/movie/object touches should not try to scroll
2773 ionic.tap.isElementTapDisabled(e.target); // check if this element, or an ancestor, has `data-tap-disabled` attribute
2774 },
2775
2776 isTextInput: function(ele) {
2777 return !!ele &&
2778 (ele.tagName == 'TEXTAREA' ||
2779 ele.contentEditable === 'true' ||
2780 (ele.tagName == 'INPUT' && !(/^(radio|checkbox|range|file|submit|reset|color|image|button)$/i).test(ele.type)));
2781 },
2782
2783 isDateInput: function(ele) {
2784 return !!ele &&
2785 (ele.tagName == 'INPUT' && (/^(date|time|datetime-local|month|week)$/i).test(ele.type));
2786 },
2787
2788 isVideo: function(ele) {
2789 return !!ele &&
2790 (ele.tagName == 'VIDEO');
2791 },
2792
2793 isKeyboardElement: function(ele) {
2794 if ( !ionic.Platform.isIOS() || ionic.Platform.isIPad() ) {
2795 return ionic.tap.isTextInput(ele) && !ionic.tap.isDateInput(ele);
2796 } else {
2797 return ionic.tap.isTextInput(ele) || ( !!ele && ele.tagName == "SELECT");
2798 }
2799 },
2800
2801 isLabelWithTextInput: function(ele) {
2802 var container = tapContainingElement(ele, false);
2803
2804 return !!container &&
2805 ionic.tap.isTextInput(tapTargetElement(container));
2806 },
2807
2808 containsOrIsTextInput: function(ele) {
2809 return ionic.tap.isTextInput(ele) || ionic.tap.isLabelWithTextInput(ele);
2810 },
2811
2812 cloneFocusedInput: function(container) {
2813 if (ionic.tap.hasCheckedClone) return;
2814 ionic.tap.hasCheckedClone = true;
2815
2816 ionic.requestAnimationFrame(function() {
2817 var focusInput = container.querySelector(':focus');
2818 if (ionic.tap.isTextInput(focusInput) && !ionic.tap.isDateInput(focusInput)) {
2819 var clonedInput = focusInput.cloneNode(true);
2820
2821 clonedInput.value = focusInput.value;
2822 clonedInput.classList.add('cloned-text-input');
2823 clonedInput.readOnly = true;
2824 if (focusInput.isContentEditable) {
2825 clonedInput.contentEditable = focusInput.contentEditable;
2826 clonedInput.innerHTML = focusInput.innerHTML;
2827 }
2828 focusInput.parentElement.insertBefore(clonedInput, focusInput);
2829 focusInput.classList.add('previous-input-focus');
2830
2831 clonedInput.scrollTop = focusInput.scrollTop;
2832 }
2833 });
2834 },
2835
2836 hasCheckedClone: false,
2837
2838 removeClonedInputs: function(container) {
2839 ionic.tap.hasCheckedClone = false;
2840
2841 ionic.requestAnimationFrame(function() {
2842 var clonedInputs = container.querySelectorAll('.cloned-text-input');
2843 var previousInputFocus = container.querySelectorAll('.previous-input-focus');
2844 var x;
2845
2846 for (x = 0; x < clonedInputs.length; x++) {
2847 clonedInputs[x].parentElement.removeChild(clonedInputs[x]);
2848 }
2849
2850 for (x = 0; x < previousInputFocus.length; x++) {
2851 previousInputFocus[x].classList.remove('previous-input-focus');
2852 previousInputFocus[x].style.top = '';
2853 if ( ionic.keyboard.isOpen && !ionic.keyboard.isClosing ) previousInputFocus[x].focus();
2854 }
2855 });
2856 },
2857
2858 requiresNativeClick: function(ele) {
2859 if (ionic.Platform.isWindowsPhone() && (ele.tagName == 'A' || ele.tagName == 'BUTTON' || ele.hasAttribute('ng-click') || (ele.tagName == 'INPUT' && (ele.type == 'button' || ele.type == 'submit')))) {
2860 return true; //Windows Phone edge case, prevent ng-click (and similar) events from firing twice on this platform
2861 }
2862 if (!ele || ele.disabled || (/^(file|range)$/i).test(ele.type) || (/^(object|video)$/i).test(ele.tagName) || ionic.tap.isLabelContainingFileInput(ele)) {
2863 return true;
2864 }
2865 return ionic.tap.isElementTapDisabled(ele);
2866 },
2867
2868 isLabelContainingFileInput: function(ele) {
2869 var lbl = tapContainingElement(ele);
2870 if (lbl.tagName !== 'LABEL') return false;
2871 var fileInput = lbl.querySelector('input[type=file]');
2872 if (fileInput && fileInput.disabled === false) return true;
2873 return false;
2874 },
2875
2876 isElementTapDisabled: function(ele) {
2877 if (ele && ele.nodeType === 1) {
2878 var element = ele;
2879 while (element) {
2880 if (element.getAttribute && element.getAttribute('data-tap-disabled') == 'true') {
2881 return true;
2882 }
2883 element = element.parentElement;
2884 }
2885 }
2886 return false;
2887 },
2888
2889 setTolerance: function(releaseTolerance, releaseButtonTolerance) {
2890 TAP_RELEASE_TOLERANCE = releaseTolerance;
2891 TAP_RELEASE_BUTTON_TOLERANCE = releaseButtonTolerance;
2892 },
2893
2894 cancelClick: function() {
2895 // used to cancel any simulated clicks which may happen on a touchend/mouseup
2896 // gestures uses this method within its tap and hold events
2897 tapPointerMoved = true;
2898 },
2899
2900 pointerCoord: function(event) {
2901 // This method can get coordinates for both a mouse click
2902 // or a touch depending on the given event
2903 var c = { x: 0, y: 0 };
2904 if (event) {
2905 var touches = event.touches && event.touches.length ? event.touches : [event];
2906 var e = (event.changedTouches && event.changedTouches[0]) || touches[0];
2907 if (e) {
2908 c.x = e.clientX || e.pageX || 0;
2909 c.y = e.clientY || e.pageY || 0;
2910 }
2911 }
2912 return c;
2913 }
2914
2915};
2916
2917function tapEventListener(type, enable, useCapture) {
2918 if (enable !== false) {
2919 tapDoc.addEventListener(type, tapEventListeners[type], useCapture);
2920 } else {
2921 tapDoc.removeEventListener(type, tapEventListeners[type]);
2922 }
2923}
2924
2925function tapClick(e) {
2926 // simulate a normal click by running the element's click method then focus on it
2927 var container = tapContainingElement(e.target);
2928 var ele = tapTargetElement(container);
2929
2930 if (ionic.tap.requiresNativeClick(ele) || tapPointerMoved) return false;
2931
2932 var c = ionic.tap.pointerCoord(e);
2933
2934 //console.log('tapClick', e.type, ele.tagName, '('+c.x+','+c.y+')');
2935 triggerMouseEvent('click', ele, c.x, c.y);
2936
2937 // if it's an input, focus in on the target, otherwise blur
2938 tapHandleFocus(ele);
2939}
2940
2941function triggerMouseEvent(type, ele, x, y) {
2942 // using initMouseEvent instead of MouseEvent for our Android friends
2943 var clickEvent = document.createEvent("MouseEvents");
2944 clickEvent.initMouseEvent(type, true, true, window, 1, 0, 0, x, y, false, false, false, false, 0, null);
2945 clickEvent.isIonicTap = true;
2946 ele.dispatchEvent(clickEvent);
2947}
2948
2949function tapClickGateKeeper(e) {
2950 //console.log('click ' + Date.now() + ' isIonicTap: ' + (e.isIonicTap ? true : false));
2951 if (e.target.type == 'submit' && e.detail === 0) {
2952 // do not prevent click if it came from an "Enter" or "Go" keypress submit
2953 return null;
2954 }
2955
2956 // do not allow through any click events that were not created by ionic.tap
2957 if ((ionic.scroll.isScrolling && ionic.tap.containsOrIsTextInput(e.target)) ||
2958 (!e.isIonicTap && !ionic.tap.requiresNativeClick(e.target))) {
2959 //console.log('clickPrevent', e.target.tagName);
2960 e.stopPropagation();
2961
2962 if (!ionic.tap.isLabelWithTextInput(e.target)) {
2963 // labels clicks from native should not preventDefault othersize keyboard will not show on input focus
2964 e.preventDefault();
2965 }
2966 return false;
2967 }
2968}
2969
2970// MOUSE
2971function tapMouseDown(e) {
2972 //console.log('mousedown ' + Date.now());
2973 if (e.isIonicTap || tapIgnoreEvent(e)) return null;
2974
2975 if (tapEnabledTouchEvents) {
2976 //console.log('mousedown', 'stop event');
2977 e.stopPropagation();
2978
2979 if (!ionic.Platform.isEdge() && (!ionic.tap.isTextInput(e.target) || tapLastTouchTarget !== e.target) &&
2980 !isSelectOrOption(e.target.tagName) && !ionic.tap.isVideo(e.target)) {
2981 // If you preventDefault on a text input then you cannot move its text caret/cursor.
2982 // Allow through only the text input default. However, without preventDefault on an
2983 // input the 300ms delay can change focus on inputs after the keyboard shows up.
2984 // The focusin event handles the chance of focus changing after the keyboard shows.
2985 // Windows Phone - if you preventDefault on a video element then you cannot operate
2986 // its native controls.
2987 e.preventDefault();
2988 }
2989
2990 return false;
2991 }
2992
2993 tapPointerMoved = false;
2994 tapPointerStart = ionic.tap.pointerCoord(e);
2995
2996 tapEventListener('mousemove');
2997 ionic.activator.start(e);
2998}
2999
3000function tapMouseUp(e) {
3001 //console.log("mouseup " + Date.now());
3002 if (tapEnabledTouchEvents) {
3003 e.stopPropagation();
3004 e.preventDefault();
3005 return false;
3006 }
3007
3008 if (tapIgnoreEvent(e) || isSelectOrOption(e.target.tagName)) return false;
3009
3010 if (!tapHasPointerMoved(e)) {
3011 tapClick(e);
3012 }
3013 tapEventListener('mousemove', false);
3014 ionic.activator.end();
3015 tapPointerMoved = false;
3016}
3017
3018function tapMouseMove(e) {
3019 if (tapHasPointerMoved(e)) {
3020 tapEventListener('mousemove', false);
3021 ionic.activator.end();
3022 tapPointerMoved = true;
3023 return false;
3024 }
3025}
3026
3027
3028// TOUCH
3029function tapTouchStart(e) {
3030 //console.log("touchstart " + Date.now());
3031 if (tapIgnoreEvent(e)) return;
3032
3033 tapPointerMoved = false;
3034
3035 tapEnableTouchEvents();
3036 tapPointerStart = ionic.tap.pointerCoord(e);
3037
3038 tapEventListener(tapTouchMoveListener);
3039 ionic.activator.start(e);
3040
3041 if (ionic.Platform.isIOS() && ionic.tap.isLabelWithTextInput(e.target)) {
3042 // if the tapped element is a label, which has a child input
3043 // then preventDefault so iOS doesn't ugly auto scroll to the input
3044 // but do not prevent default on Android or else you cannot move the text caret
3045 // and do not prevent default on Android or else no virtual keyboard shows up
3046
3047 var textInput = tapTargetElement(tapContainingElement(e.target));
3048 if (textInput !== tapActiveEle) {
3049 // don't preventDefault on an already focused input or else iOS's text caret isn't usable
3050 //console.log('Would prevent default here');
3051 e.preventDefault();
3052 }
3053 }
3054}
3055
3056function tapTouchEnd(e) {
3057 //console.log('touchend ' + Date.now());
3058 if (tapIgnoreEvent(e)) return;
3059
3060 tapEnableTouchEvents();
3061 if (!tapHasPointerMoved(e)) {
3062 tapClick(e);
3063
3064 if (isSelectOrOption(e.target.tagName)) {
3065 e.preventDefault();
3066 }
3067 }
3068
3069 tapLastTouchTarget = e.target;
3070 tapTouchCancel();
3071}
3072
3073function tapTouchMove(e) {
3074 if (tapHasPointerMoved(e)) {
3075 tapPointerMoved = true;
3076 tapEventListener(tapTouchMoveListener, false);
3077 ionic.activator.end();
3078 return false;
3079 }
3080}
3081
3082function tapTouchCancel() {
3083 tapEventListener(tapTouchMoveListener, false);
3084 ionic.activator.end();
3085 tapPointerMoved = false;
3086}
3087
3088function tapEnableTouchEvents() {
3089 tapEnabledTouchEvents = true;
3090 clearTimeout(tapMouseResetTimer);
3091 tapMouseResetTimer = setTimeout(function() {
3092 tapEnabledTouchEvents = false;
3093 }, 600);
3094}
3095
3096function tapIgnoreEvent(e) {
3097 if (e.isTapHandled) return true;
3098 e.isTapHandled = true;
3099
3100 if(ionic.tap.isElementTapDisabled(e.target)) {
3101 return true;
3102 }
3103
3104 if (ionic.scroll.isScrolling && ionic.tap.containsOrIsTextInput(e.target)) {
3105 e.preventDefault();
3106 return true;
3107 }
3108}
3109
3110function tapHandleFocus(ele) {
3111 tapTouchFocusedInput = null;
3112
3113 var triggerFocusIn = false;
3114
3115 if (ele.tagName == 'SELECT') {
3116 // trick to force Android options to show up
3117 triggerMouseEvent('mousedown', ele, 0, 0);
3118 ele.focus && ele.focus();
3119 triggerFocusIn = true;
3120
3121 } else if (tapActiveElement() === ele) {
3122 // already is the active element and has focus
3123 triggerFocusIn = true;
3124
3125 } else if ((/^(input|textarea|ion-label)$/i).test(ele.tagName) || ele.isContentEditable) {
3126 triggerFocusIn = true;
3127 ele.focus && ele.focus();
3128 ele.value = ele.value;
3129 if (tapEnabledTouchEvents) {
3130 tapTouchFocusedInput = ele;
3131 }
3132
3133 } else {
3134 tapFocusOutActive();
3135 }
3136
3137 if (triggerFocusIn) {
3138 tapActiveElement(ele);
3139 ionic.trigger('ionic.focusin', {
3140 target: ele
3141 }, true);
3142 }
3143}
3144
3145function tapFocusOutActive() {
3146 var ele = tapActiveElement();
3147 if (ele && ((/^(input|textarea|select)$/i).test(ele.tagName) || ele.isContentEditable)) {
3148 //console.log('tapFocusOutActive', ele.tagName);
3149 ele.blur();
3150 }
3151 tapActiveElement(null);
3152}
3153
3154function tapFocusIn(e) {
3155 //console.log('focusin ' + Date.now());
3156 // Because a text input doesn't preventDefault (so the caret still works) there's a chance
3157 // that its mousedown event 300ms later will change the focus to another element after
3158 // the keyboard shows up.
3159
3160 if (tapEnabledTouchEvents &&
3161 ionic.tap.isTextInput(tapActiveElement()) &&
3162 ionic.tap.isTextInput(tapTouchFocusedInput) &&
3163 tapTouchFocusedInput !== e.target) {
3164
3165 // 1) The pointer is from touch events
3166 // 2) There is an active element which is a text input
3167 // 3) A text input was just set to be focused on by a touch event
3168 // 4) A new focus has been set, however the target isn't the one the touch event wanted
3169 //console.log('focusin', 'tapTouchFocusedInput');
3170 tapTouchFocusedInput.focus();
3171 tapTouchFocusedInput = null;
3172 }
3173 ionic.scroll.isScrolling = false;
3174}
3175
3176function tapFocusOut() {
3177 //console.log("focusout");
3178 tapActiveElement(null);
3179}
3180
3181function tapActiveElement(ele) {
3182 if (arguments.length) {
3183 tapActiveEle = ele;
3184 }
3185 return tapActiveEle || document.activeElement;
3186}
3187
3188function tapHasPointerMoved(endEvent) {
3189 if (!endEvent || endEvent.target.nodeType !== 1 || !tapPointerStart || (tapPointerStart.x === 0 && tapPointerStart.y === 0)) {
3190 return false;
3191 }
3192 var endCoordinates = ionic.tap.pointerCoord(endEvent);
3193
3194 var hasClassList = !!(endEvent.target.classList && endEvent.target.classList.contains &&
3195 typeof endEvent.target.classList.contains === 'function');
3196 var releaseTolerance = hasClassList && endEvent.target.classList.contains('button') ?
3197 TAP_RELEASE_BUTTON_TOLERANCE :
3198 TAP_RELEASE_TOLERANCE;
3199
3200 return Math.abs(tapPointerStart.x - endCoordinates.x) > releaseTolerance ||
3201 Math.abs(tapPointerStart.y - endCoordinates.y) > releaseTolerance;
3202}
3203
3204function tapContainingElement(ele, allowSelf) {
3205 var climbEle = ele;
3206 for (var x = 0; x < 6; x++) {
3207 if (!climbEle) break;
3208 if (climbEle.tagName === 'LABEL') return climbEle;
3209 climbEle = climbEle.parentElement;
3210 }
3211 if (allowSelf !== false) return ele;
3212}
3213
3214function tapTargetElement(ele) {
3215 if (ele && ele.tagName === 'LABEL') {
3216 if (ele.control) return ele.control;
3217
3218 // older devices do not support the "control" property
3219 if (ele.querySelector) {
3220 var control = ele.querySelector('input,textarea,select');
3221 if (control) return control;
3222 }
3223 }
3224 return ele;
3225}
3226
3227function isSelectOrOption(tagName){
3228 return (/^(select|option)$/i).test(tagName);
3229}
3230
3231ionic.DomUtil.ready(function() {
3232 var ng = typeof angular !== 'undefined' ? angular : null;
3233 //do nothing for e2e tests
3234 if (!ng || (ng && !ng.scenario)) {
3235 ionic.tap.register(document);
3236 }
3237});
3238
3239(function(document, ionic) {
3240 'use strict';
3241
3242 var queueElements = {}; // elements that should get an active state in XX milliseconds
3243 var activeElements = {}; // elements that are currently active
3244 var keyId = 0; // a counter for unique keys for the above ojects
3245 var ACTIVATED_CLASS = 'activated';
3246
3247 ionic.activator = {
3248
3249 start: function(e) {
3250 var hitX = ionic.tap.pointerCoord(e).x;
3251 if (hitX > 0 && hitX < 30) {
3252 return;
3253 }
3254
3255 // when an element is touched/clicked, it climbs up a few
3256 // parents to see if it is an .item or .button element
3257 ionic.requestAnimationFrame(function() {
3258 if ((ionic.scroll && ionic.scroll.isScrolling) || ionic.tap.requiresNativeClick(e.target)) return;
3259 var ele = e.target;
3260 var eleToActivate;
3261
3262 for (var x = 0; x < 6; x++) {
3263 if (!ele || ele.nodeType !== 1) break;
3264 if (eleToActivate && ele.classList && ele.classList.contains('item')) {
3265 eleToActivate = ele;
3266 break;
3267 }
3268 if (ele.tagName == 'A' || ele.tagName == 'BUTTON' || ele.hasAttribute('ng-click')) {
3269 eleToActivate = ele;
3270 break;
3271 }
3272 if (ele.classList && ele.classList.contains('button')) {
3273 eleToActivate = ele;
3274 break;
3275 }
3276 // no sense climbing past these
3277 if (ele.tagName == 'ION-CONTENT' || (ele.classList && ele.classList.contains('pane')) || ele.tagName == 'BODY') {
3278 break;
3279 }
3280 ele = ele.parentElement;
3281 }
3282
3283 if (eleToActivate) {
3284 // queue that this element should be set to active
3285 queueElements[keyId] = eleToActivate;
3286
3287 // on the next frame, set the queued elements to active
3288 ionic.requestAnimationFrame(activateElements);
3289
3290 keyId = (keyId > 29 ? 0 : keyId + 1);
3291 }
3292
3293 });
3294 },
3295
3296 end: function() {
3297 // clear out any active/queued elements after XX milliseconds
3298 setTimeout(clear, 200);
3299 }
3300
3301 };
3302
3303 function clear() {
3304 // clear out any elements that are queued to be set to active
3305 queueElements = {};
3306
3307 // in the next frame, remove the active class from all active elements
3308 ionic.requestAnimationFrame(deactivateElements);
3309 }
3310
3311 function activateElements() {
3312 // activate all elements in the queue
3313 for (var key in queueElements) {
3314 if (queueElements[key]) {
3315 queueElements[key].classList.add(ACTIVATED_CLASS);
3316 activeElements[key] = queueElements[key];
3317 }
3318 }
3319 queueElements = {};
3320 }
3321
3322 function deactivateElements() {
3323 if (ionic.transition && ionic.transition.isActive) {
3324 setTimeout(deactivateElements, 400);
3325 return;
3326 }
3327
3328 for (var key in activeElements) {
3329 if (activeElements[key]) {
3330 activeElements[key].classList.remove(ACTIVATED_CLASS);
3331 delete activeElements[key];
3332 }
3333 }
3334 }
3335
3336})(document, ionic);
3337
3338(function(ionic) {
3339 /* for nextUid function below */
3340 var nextId = 0;
3341
3342 /**
3343 * Various utilities used throughout Ionic
3344 *
3345 * Some of these are adopted from underscore.js and backbone.js, both also MIT licensed.
3346 */
3347 ionic.Utils = {
3348
3349 arrayMove: function(arr, oldIndex, newIndex) {
3350 if (newIndex >= arr.length) {
3351 var k = newIndex - arr.length;
3352 while ((k--) + 1) {
3353 arr.push(undefined);
3354 }
3355 }
3356 arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0]);
3357 return arr;
3358 },
3359
3360 /**
3361 * Return a function that will be called with the given context
3362 */
3363 proxy: function(func, context) {
3364 var args = Array.prototype.slice.call(arguments, 2);
3365 return function() {
3366 return func.apply(context, args.concat(Array.prototype.slice.call(arguments)));
3367 };
3368 },
3369
3370 /**
3371 * Only call a function once in the given interval.
3372 *
3373 * @param func {Function} the function to call
3374 * @param wait {int} how long to wait before/after to allow function calls
3375 * @param immediate {boolean} whether to call immediately or after the wait interval
3376 */
3377 debounce: function(func, wait, immediate) {
3378 var timeout, args, context, timestamp, result;
3379 return function() {
3380 context = this;
3381 args = arguments;
3382 timestamp = new Date();
3383 var later = function() {
3384 var last = (new Date()) - timestamp;
3385 if (last < wait) {
3386 timeout = setTimeout(later, wait - last);
3387 } else {
3388 timeout = null;
3389 if (!immediate) result = func.apply(context, args);
3390 }
3391 };
3392 var callNow = immediate && !timeout;
3393 if (!timeout) {
3394 timeout = setTimeout(later, wait);
3395 }
3396 if (callNow) result = func.apply(context, args);
3397 return result;
3398 };
3399 },
3400
3401 /**
3402 * Throttle the given fun, only allowing it to be
3403 * called at most every `wait` ms.
3404 */
3405 throttle: function(func, wait, options) {
3406 var context, args, result;
3407 var timeout = null;
3408 var previous = 0;
3409 options || (options = {});
3410 var later = function() {
3411 previous = options.leading === false ? 0 : Date.now();
3412 timeout = null;
3413 result = func.apply(context, args);
3414 };
3415 return function() {
3416 var now = Date.now();
3417 if (!previous && options.leading === false) previous = now;
3418 var remaining = wait - (now - previous);
3419 context = this;
3420 args = arguments;
3421 if (remaining <= 0) {
3422 clearTimeout(timeout);
3423 timeout = null;
3424 previous = now;
3425 result = func.apply(context, args);
3426 } else if (!timeout && options.trailing !== false) {
3427 timeout = setTimeout(later, remaining);
3428 }
3429 return result;
3430 };
3431 },
3432 // Borrowed from Backbone.js's extend
3433 // Helper function to correctly set up the prototype chain, for subclasses.
3434 // Similar to `goog.inherits`, but uses a hash of prototype properties and
3435 // class properties to be extended.
3436 inherit: function(protoProps, staticProps) {
3437 var parent = this;
3438 var child;
3439
3440 // The constructor function for the new subclass is either defined by you
3441 // (the "constructor" property in your `extend` definition), or defaulted
3442 // by us to simply call the parent's constructor.
3443 if (protoProps && protoProps.hasOwnProperty('constructor')) {
3444 child = protoProps.constructor;
3445 } else {
3446 child = function() { return parent.apply(this, arguments); };
3447 }
3448
3449 // Add static properties to the constructor function, if supplied.
3450 ionic.extend(child, parent, staticProps);
3451
3452 // Set the prototype chain to inherit from `parent`, without calling
3453 // `parent`'s constructor function.
3454 var Surrogate = function() { this.constructor = child; };
3455 Surrogate.prototype = parent.prototype;
3456 child.prototype = new Surrogate();
3457
3458 // Add prototype properties (instance properties) to the subclass,
3459 // if supplied.
3460 if (protoProps) ionic.extend(child.prototype, protoProps);
3461
3462 // Set a convenience property in case the parent's prototype is needed
3463 // later.
3464 child.__super__ = parent.prototype;
3465
3466 return child;
3467 },
3468
3469 // Extend adapted from Underscore.js
3470 extend: function(obj) {
3471 var args = Array.prototype.slice.call(arguments, 1);
3472 for (var i = 0; i < args.length; i++) {
3473 var source = args[i];
3474 if (source) {
3475 for (var prop in source) {
3476 obj[prop] = source[prop];
3477 }
3478 }
3479 }
3480 return obj;
3481 },
3482
3483 nextUid: function() {
3484 return 'ion' + (nextId++);
3485 },
3486
3487 disconnectScope: function disconnectScope(scope) {
3488 if (!scope) return;
3489
3490 if (scope.$root === scope) {
3491 return; // we can't disconnect the root node;
3492 }
3493 var parent = scope.$parent;
3494 scope.$$disconnected = true;
3495 scope.$broadcast('$ionic.disconnectScope', scope);
3496
3497 // See Scope.$destroy
3498 if (parent.$$childHead === scope) {
3499 parent.$$childHead = scope.$$nextSibling;
3500 }
3501 if (parent.$$childTail === scope) {
3502 parent.$$childTail = scope.$$prevSibling;
3503 }
3504 if (scope.$$prevSibling) {
3505 scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;
3506 }
3507 if (scope.$$nextSibling) {
3508 scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;
3509 }
3510 scope.$$nextSibling = scope.$$prevSibling = null;
3511 },
3512
3513 reconnectScope: function reconnectScope(scope) {
3514 if (!scope) return;
3515
3516 if (scope.$root === scope) {
3517 return; // we can't disconnect the root node;
3518 }
3519 if (!scope.$$disconnected) {
3520 return;
3521 }
3522 var parent = scope.$parent;
3523 scope.$$disconnected = false;
3524 scope.$broadcast('$ionic.reconnectScope', scope);
3525 // See Scope.$new for this logic...
3526 scope.$$prevSibling = parent.$$childTail;
3527 if (parent.$$childHead) {
3528 parent.$$childTail.$$nextSibling = scope;
3529 parent.$$childTail = scope;
3530 } else {
3531 parent.$$childHead = parent.$$childTail = scope;
3532 }
3533 },
3534
3535 isScopeDisconnected: function(scope) {
3536 var climbScope = scope;
3537 while (climbScope) {
3538 if (climbScope.$$disconnected) return true;
3539 climbScope = climbScope.$parent;
3540 }
3541 return false;
3542 }
3543 };
3544
3545 // Bind a few of the most useful functions to the ionic scope
3546 ionic.inherit = ionic.Utils.inherit;
3547 ionic.extend = ionic.Utils.extend;
3548 ionic.throttle = ionic.Utils.throttle;
3549 ionic.proxy = ionic.Utils.proxy;
3550 ionic.debounce = ionic.Utils.debounce;
3551
3552})(window.ionic);
3553
3554/**
3555 * @ngdoc page
3556 * @name keyboard
3557 * @module ionic
3558 * @description
3559 * On both Android and iOS, Ionic will attempt to prevent the keyboard from
3560 * obscuring inputs and focusable elements when it appears by scrolling them
3561 * into view. In order for this to work, any focusable elements must be within
3562 * a [Scroll View](http://ionicframework.com/docs/api/directive/ionScroll/)
3563 * or a directive such as [Content](http://ionicframework.com/docs/api/directive/ionContent/)
3564 * that has a Scroll View.
3565 *
3566 * It will also attempt to prevent the native overflow scrolling on focus,
3567 * which can cause layout issues such as pushing headers up and out of view.
3568 *
3569 * The keyboard fixes work best in conjunction with the
3570 * [Ionic Keyboard Plugin](https://github.com/driftyco/ionic-plugins-keyboard),
3571 * although it will perform reasonably well without. However, if you are using
3572 * Cordova there is no reason not to use the plugin.
3573 *
3574 * ### Hide when keyboard shows
3575 *
3576 * To hide an element when the keyboard is open, add the class `hide-on-keyboard-open`.
3577 *
3578 * ```html
3579 * <div class="hide-on-keyboard-open">
3580 * <div id="google-map"></div>
3581 * </div>
3582 * ```
3583 *
3584 * Note: For performance reasons, elements will not be hidden for 400ms after the start of the `native.keyboardshow` event
3585 * from the Ionic Keyboard plugin. If you would like them to disappear immediately, you could do something
3586 * like:
3587 *
3588 * ```js
3589 * window.addEventListener('native.keyboardshow', function(){
3590 * document.body.classList.add('keyboard-open');
3591 * });
3592 * ```
3593 * This adds the same `keyboard-open` class that is normally added by Ionic 400ms after the keyboard
3594 * opens. However, bear in mind that adding this class to the body immediately may cause jank in any
3595 * animations on Android that occur when the keyboard opens (for example, scrolling any obscured inputs into view).
3596 *
3597 * ----------
3598 *
3599 * ### Plugin Usage
3600 * Information on using the plugin can be found at
3601 * [https://github.com/driftyco/ionic-plugins-keyboard](https://github.com/driftyco/ionic-plugins-keyboard).
3602 *
3603 * ----------
3604 *
3605 * ### Android Notes
3606 * - If your app is running in fullscreen, i.e. you have
3607 * `<preference name="Fullscreen" value="true" />` in your `config.xml` file
3608 * you will need to set `ionic.Platform.isFullScreen = true` manually.
3609 *
3610 * - You can configure the behavior of the web view when the keyboard shows by setting
3611 * [android:windowSoftInputMode](http://developer.android.com/reference/android/R.attr.html#windowSoftInputMode)
3612 * to either `adjustPan`, `adjustResize` or `adjustNothing` in your app's
3613 * activity in `AndroidManifest.xml`. `adjustResize` is the recommended setting
3614 * for Ionic, but if for some reason you do use `adjustPan` you will need to
3615 * set `ionic.Platform.isFullScreen = true`.
3616 *
3617 * ```xml
3618 * <activity android:windowSoftInputMode="adjustResize">
3619 *
3620 * ```
3621 *
3622 * ### iOS Notes
3623 * - If the content of your app (including the header) is being pushed up and
3624 * out of view on input focus, try setting `cordova.plugins.Keyboard.disableScroll(true)`.
3625 * This does **not** disable scrolling in the Ionic scroll view, rather it
3626 * disables the native overflow scrolling that happens automatically as a
3627 * result of focusing on inputs below the keyboard.
3628 *
3629 */
3630
3631/**
3632 * The current viewport height.
3633 */
3634var keyboardCurrentViewportHeight = 0;
3635
3636/**
3637 * The viewport height when in portrait orientation.
3638 */
3639var keyboardPortraitViewportHeight = 0;
3640
3641/**
3642 * The viewport height when in landscape orientation.
3643 */
3644var keyboardLandscapeViewportHeight = 0;
3645
3646/**
3647 * The currently focused input.
3648 */
3649var keyboardActiveElement;
3650
3651/**
3652 * The previously focused input used to reset keyboard after focusing on a
3653 * new non-keyboard element
3654 */
3655var lastKeyboardActiveElement;
3656
3657/**
3658 * The scroll view containing the currently focused input.
3659 */
3660var scrollView;
3661
3662/**
3663 * Timer for the setInterval that polls window.innerHeight to determine whether
3664 * the layout has updated for the keyboard showing/hiding.
3665 */
3666var waitForResizeTimer;
3667
3668/**
3669 * Sometimes when switching inputs or orientations, focusout will fire before
3670 * focusin, so this timer is for the small setTimeout to determine if we should
3671 * really focusout/hide the keyboard.
3672 */
3673var keyboardFocusOutTimer;
3674
3675/**
3676 * on Android, orientationchange will fire before the keyboard plugin notifies
3677 * the browser that the keyboard will show/is showing, so this flag indicates
3678 * to nativeShow that there was an orientationChange and we should update
3679 * the viewport height with an accurate keyboard height value
3680 */
3681var wasOrientationChange = false;
3682
3683/**
3684 * CSS class added to the body indicating the keyboard is open.
3685 */
3686var KEYBOARD_OPEN_CSS = 'keyboard-open';
3687
3688/**
3689 * CSS class that indicates a scroll container.
3690 */
3691var SCROLL_CONTAINER_CSS = 'scroll-content';
3692
3693/**
3694 * Debounced keyboardFocusIn function
3695 */
3696var debouncedKeyboardFocusIn = ionic.debounce(keyboardFocusIn, 200, true);
3697
3698/**
3699 * Debounced keyboardNativeShow function
3700 */
3701var debouncedKeyboardNativeShow = ionic.debounce(keyboardNativeShow, 100, true);
3702
3703/**
3704 * Ionic keyboard namespace.
3705 * @namespace keyboard
3706 */
3707ionic.keyboard = {
3708
3709 /**
3710 * Whether the keyboard is open or not.
3711 */
3712 isOpen: false,
3713
3714 /**
3715 * Whether the keyboard is closing or not.
3716 */
3717 isClosing: false,
3718
3719 /**
3720 * Whether the keyboard is opening or not.
3721 */
3722 isOpening: false,
3723
3724 /**
3725 * The height of the keyboard in pixels, as reported by the keyboard plugin.
3726 * If the plugin is not available, calculated as the difference in
3727 * window.innerHeight after the keyboard has shown.
3728 */
3729 height: 0,
3730
3731 /**
3732 * Whether the device is in landscape orientation or not.
3733 */
3734 isLandscape: false,
3735
3736 /**
3737 * Whether the keyboard event listeners have been added or not
3738 */
3739 isInitialized: false,
3740
3741 /**
3742 * Hide the keyboard, if it is open.
3743 */
3744 hide: function() {
3745 if (keyboardHasPlugin()) {
3746 cordova.plugins.Keyboard.close();
3747 }
3748 keyboardActiveElement && keyboardActiveElement.blur();
3749 },
3750
3751 /**
3752 * An alias for cordova.plugins.Keyboard.show(). If the keyboard plugin
3753 * is installed, show the keyboard.
3754 */
3755 show: function() {
3756 if (keyboardHasPlugin()) {
3757 cordova.plugins.Keyboard.show();
3758 }
3759 },
3760
3761 /**
3762 * Remove all keyboard related event listeners, effectively disabling Ionic's
3763 * keyboard adjustments.
3764 */
3765 disable: function() {
3766 if (keyboardHasPlugin()) {
3767 window.removeEventListener('native.keyboardshow', debouncedKeyboardNativeShow );
3768 window.removeEventListener('native.keyboardhide', keyboardFocusOut);
3769 } else {
3770 document.body.removeEventListener('focusout', keyboardFocusOut);
3771 }
3772
3773 document.body.removeEventListener('ionic.focusin', debouncedKeyboardFocusIn);
3774 document.body.removeEventListener('focusin', debouncedKeyboardFocusIn);
3775
3776 window.removeEventListener('orientationchange', keyboardOrientationChange);
3777
3778 if ( window.navigator.msPointerEnabled ) {
3779 document.removeEventListener("MSPointerDown", keyboardInit);
3780 } else {
3781 document.removeEventListener('touchstart', keyboardInit);
3782 }
3783 ionic.keyboard.isInitialized = false;
3784 },
3785
3786 /**
3787 * Alias for keyboardInit, initialize all keyboard related event listeners.
3788 */
3789 enable: function() {
3790 keyboardInit();
3791 }
3792};
3793
3794// Initialize the viewport height (after ionic.keyboard.height has been
3795// defined).
3796keyboardCurrentViewportHeight = getViewportHeight();
3797
3798
3799 /* Event handlers */
3800/* ------------------------------------------------------------------------- */
3801
3802/**
3803 * Event handler for first touch event, initializes all event listeners
3804 * for keyboard related events. Also aliased by ionic.keyboard.enable.
3805 */
3806function keyboardInit() {
3807
3808 if (ionic.keyboard.isInitialized) return;
3809
3810 if (keyboardHasPlugin()) {
3811 window.addEventListener('native.keyboardshow', debouncedKeyboardNativeShow);
3812 window.addEventListener('native.keyboardhide', keyboardFocusOut);
3813 } else {
3814 document.body.addEventListener('focusout', keyboardFocusOut);
3815 }
3816
3817 document.body.addEventListener('ionic.focusin', debouncedKeyboardFocusIn);
3818 document.body.addEventListener('focusin', debouncedKeyboardFocusIn);
3819
3820 if (window.navigator.msPointerEnabled) {
3821 document.removeEventListener("MSPointerDown", keyboardInit);
3822 } else {
3823 document.removeEventListener('touchstart', keyboardInit);
3824 }
3825
3826 ionic.keyboard.isInitialized = true;
3827}
3828
3829/**
3830 * Event handler for 'native.keyboardshow' event, sets keyboard.height to the
3831 * reported height and keyboard.isOpening to true. Then calls
3832 * keyboardWaitForResize with keyboardShow or keyboardUpdateViewportHeight as
3833 * the callback depending on whether the event was triggered by a focusin or
3834 * an orientationchange.
3835 */
3836function keyboardNativeShow(e) {
3837 clearTimeout(keyboardFocusOutTimer);
3838 //console.log("keyboardNativeShow fired at: " + Date.now());
3839 //console.log("keyboardNativeshow window.innerHeight: " + window.innerHeight);
3840
3841 if (!ionic.keyboard.isOpen || ionic.keyboard.isClosing) {
3842 ionic.keyboard.isOpening = true;
3843 ionic.keyboard.isClosing = false;
3844 }
3845
3846 ionic.keyboard.height = e.keyboardHeight;
3847 //console.log('nativeshow keyboard height:' + e.keyboardHeight);
3848
3849 if (wasOrientationChange) {
3850 keyboardWaitForResize(keyboardUpdateViewportHeight, true);
3851 } else {
3852 keyboardWaitForResize(keyboardShow, true);
3853 }
3854}
3855
3856/**
3857 * Event handler for 'focusin' and 'ionic.focusin' events. Initializes
3858 * keyboard state (keyboardActiveElement and keyboard.isOpening) for the
3859 * appropriate adjustments once the window has resized. If not using the
3860 * keyboard plugin, calls keyboardWaitForResize with keyboardShow as the
3861 * callback or keyboardShow right away if the keyboard is already open. If
3862 * using the keyboard plugin does nothing and lets keyboardNativeShow handle
3863 * adjustments with a more accurate keyboard height.
3864 */
3865function keyboardFocusIn(e) {
3866 clearTimeout(keyboardFocusOutTimer);
3867 //console.log("keyboardFocusIn from: " + e.type + " at: " + Date.now());
3868
3869 if (!e.target ||
3870 e.target.readOnly ||
3871 !ionic.tap.isKeyboardElement(e.target) ||
3872 !(scrollView = ionic.DomUtil.getParentWithClass(e.target, SCROLL_CONTAINER_CSS))) {
3873 if (keyboardActiveElement) {
3874 lastKeyboardActiveElement = keyboardActiveElement;
3875 }
3876 keyboardActiveElement = null;
3877 return;
3878 }
3879
3880 keyboardActiveElement = e.target;
3881
3882 // if using JS scrolling, undo the effects of native overflow scroll so the
3883 // scroll view is positioned correctly
3884 if (!scrollView.classList.contains("overflow-scroll")) {
3885 document.body.scrollTop = 0;
3886 scrollView.scrollTop = 0;
3887 ionic.requestAnimationFrame(function(){
3888 document.body.scrollTop = 0;
3889 scrollView.scrollTop = 0;
3890 });
3891
3892 // any showing part of the document that isn't within the scroll the user
3893 // could touchmove and cause some ugly changes to the app, so disable
3894 // any touchmove events while the keyboard is open using e.preventDefault()
3895 if (window.navigator.msPointerEnabled) {
3896 document.addEventListener("MSPointerMove", keyboardPreventDefault, false);
3897 } else {
3898 document.addEventListener('touchmove', keyboardPreventDefault, false);
3899 }
3900 }
3901
3902 if (!ionic.keyboard.isOpen || ionic.keyboard.isClosing) {
3903 ionic.keyboard.isOpening = true;
3904 ionic.keyboard.isClosing = false;
3905 }
3906
3907 // attempt to prevent browser from natively scrolling input into view while
3908 // we are trying to do the same (while we are scrolling) if the user taps the
3909 // keyboard
3910 document.addEventListener('keydown', keyboardOnKeyDown, false);
3911
3912
3913
3914 // if we aren't using the plugin and the keyboard isn't open yet, wait for the
3915 // window to resize so we can get an accurate estimate of the keyboard size,
3916 // otherwise we do nothing and let nativeShow call keyboardShow once we have
3917 // an exact keyboard height
3918 // if the keyboard is already open, go ahead and scroll the input into view
3919 // if necessary
3920 if (!ionic.keyboard.isOpen && !keyboardHasPlugin()) {
3921 keyboardWaitForResize(keyboardShow, true);
3922
3923 } else if (ionic.keyboard.isOpen) {
3924 keyboardShow();
3925 }
3926}
3927
3928/**
3929 * Event handler for 'focusout' events. Sets keyboard.isClosing to true and
3930 * calls keyboardWaitForResize with keyboardHide as the callback after a small
3931 * timeout.
3932 */
3933function keyboardFocusOut() {
3934 clearTimeout(keyboardFocusOutTimer);
3935 //console.log("keyboardFocusOut fired at: " + Date.now());
3936 //console.log("keyboardFocusOut event type: " + e.type);
3937
3938 if (ionic.keyboard.isOpen || ionic.keyboard.isOpening) {
3939 ionic.keyboard.isClosing = true;
3940 ionic.keyboard.isOpening = false;
3941 }
3942
3943 // Call keyboardHide with a slight delay because sometimes on focus or
3944 // orientation change focusin is called immediately after, so we give it time
3945 // to cancel keyboardHide
3946 keyboardFocusOutTimer = setTimeout(function() {
3947 ionic.requestAnimationFrame(function() {
3948 // focusOut during or right after an orientationchange, so we didn't get
3949 // a chance to update the viewport height yet, do it and keyboardHide
3950 //console.log("focusOut, wasOrientationChange: " + wasOrientationChange);
3951 if (wasOrientationChange) {
3952 keyboardWaitForResize(function(){
3953 keyboardUpdateViewportHeight();
3954 keyboardHide();
3955 }, false);
3956 } else {
3957 keyboardWaitForResize(keyboardHide, false);
3958 }
3959 });
3960 }, 50);
3961}
3962
3963/**
3964 * Event handler for 'orientationchange' events. If using the keyboard plugin
3965 * and the keyboard is open on Android, sets wasOrientationChange to true so
3966 * nativeShow can update the viewport height with an accurate keyboard height.
3967 * If the keyboard isn't open or keyboard plugin isn't being used,
3968 * waits for the window to resize before updating the viewport height.
3969 *
3970 * On iOS, where orientationchange fires after the keyboard has already shown,
3971 * updates the viewport immediately, regardless of if the keyboard is already
3972 * open.
3973 */
3974function keyboardOrientationChange() {
3975 //console.log("orientationchange fired at: " + Date.now());
3976 //console.log("orientation was: " + (ionic.keyboard.isLandscape ? "landscape" : "portrait"));
3977
3978 // toggle orientation
3979 ionic.keyboard.isLandscape = !ionic.keyboard.isLandscape;
3980 // //console.log("now orientation is: " + (ionic.keyboard.isLandscape ? "landscape" : "portrait"));
3981
3982 // no need to wait for resizing on iOS, and orientationchange always fires
3983 // after the keyboard has opened, so it doesn't matter if it's open or not
3984 if (ionic.Platform.isIOS()) {
3985 keyboardUpdateViewportHeight();
3986 }
3987
3988 // On Android, if the keyboard isn't open or we aren't using the keyboard
3989 // plugin, update the viewport height once everything has resized. If the
3990 // keyboard is open and we are using the keyboard plugin do nothing and let
3991 // nativeShow handle it using an accurate keyboard height.
3992 if ( ionic.Platform.isAndroid()) {
3993 if (!ionic.keyboard.isOpen || !keyboardHasPlugin()) {
3994 keyboardWaitForResize(keyboardUpdateViewportHeight, false);
3995 } else {
3996 wasOrientationChange = true;
3997 }
3998 }
3999}
4000
4001/**
4002 * Event handler for 'keydown' event. Tries to prevent browser from natively
4003 * scrolling an input into view when a user taps the keyboard while we are
4004 * scrolling the input into view ourselves with JS.
4005 */
4006function keyboardOnKeyDown(e) {
4007 if (ionic.scroll.isScrolling) {
4008 keyboardPreventDefault(e);
4009 }
4010}
4011
4012/**
4013 * Event for 'touchmove' or 'MSPointerMove'. Prevents native scrolling on
4014 * elements outside the scroll view while the keyboard is open.
4015 */
4016function keyboardPreventDefault(e) {
4017 if (e.target.tagName !== 'TEXTAREA') {
4018 e.preventDefault();
4019 }
4020}
4021
4022 /* Private API */
4023/* -------------------------------------------------------------------------- */
4024
4025/**
4026 * Polls window.innerHeight until it has updated to an expected value (or
4027 * sufficient time has passed) before calling the specified callback function.
4028 * Only necessary for non-fullscreen Android which sometimes reports multiple
4029 * window.innerHeight values during interim layouts while it is resizing.
4030 *
4031 * On iOS, the window.innerHeight will already be updated, but we use the 50ms
4032 * delay as essentially a timeout so that scroll view adjustments happen after
4033 * the keyboard has shown so there isn't a white flash from us resizing too
4034 * quickly.
4035 *
4036 * @param {Function} callback the function to call once the window has resized
4037 * @param {boolean} isOpening whether the resize is from the keyboard opening
4038 * or not
4039 */
4040function keyboardWaitForResize(callback, isOpening) {
4041 clearInterval(waitForResizeTimer);
4042 var count = 0;
4043 var maxCount;
4044 var initialHeight = getViewportHeight();
4045 var viewportHeight = initialHeight;
4046
4047 //console.log("waitForResize initial viewport height: " + viewportHeight);
4048 //var start = Date.now();
4049 //console.log("start: " + start);
4050
4051 // want to fail relatively quickly on modern android devices, since it's much
4052 // more likely we just have a bad keyboard height
4053 if (ionic.Platform.isAndroid() && ionic.Platform.version() < 4.4) {
4054 maxCount = 30;
4055 } else if (ionic.Platform.isAndroid()) {
4056 maxCount = 10;
4057 } else {
4058 maxCount = 1;
4059 }
4060
4061 // poll timer
4062 waitForResizeTimer = setInterval(function(){
4063 viewportHeight = getViewportHeight();
4064
4065 // height hasn't updated yet, try again in 50ms
4066 // if not using plugin, wait for maxCount to ensure we have waited long enough
4067 // to get an accurate keyboard height
4068 if (++count < maxCount &&
4069 ((!isPortraitViewportHeight(viewportHeight) &&
4070 !isLandscapeViewportHeight(viewportHeight)) ||
4071 !ionic.keyboard.height)) {
4072 return;
4073 }
4074
4075 // infer the keyboard height from the resize if not using the keyboard plugin
4076 if (!keyboardHasPlugin()) {
4077 ionic.keyboard.height = Math.abs(initialHeight - window.innerHeight);
4078 }
4079
4080 // set to true if we were waiting for the keyboard to open
4081 ionic.keyboard.isOpen = isOpening;
4082
4083 clearInterval(waitForResizeTimer);
4084 //var end = Date.now();
4085 //console.log("waitForResize count: " + count);
4086 //console.log("end: " + end);
4087 //console.log("difference: " + ( end - start ) + "ms");
4088
4089 //console.log("callback: " + callback.name);
4090 callback();
4091
4092 }, 50);
4093
4094 return maxCount; //for tests
4095}
4096
4097/**
4098 * On keyboard close sets keyboard state to closed, resets the scroll view,
4099 * removes CSS from body indicating keyboard was open, removes any event
4100 * listeners for when the keyboard is open and on Android blurs the active
4101 * element (which in some cases will still have focus even if the keyboard
4102 * is closed and can cause it to reappear on subsequent taps).
4103 */
4104function keyboardHide() {
4105 clearTimeout(keyboardFocusOutTimer);
4106 //console.log("keyboardHide");
4107
4108 ionic.keyboard.isOpen = false;
4109 ionic.keyboard.isClosing = false;
4110
4111 if (keyboardActiveElement || lastKeyboardActiveElement) {
4112 ionic.trigger('resetScrollView', {
4113 target: keyboardActiveElement || lastKeyboardActiveElement
4114 }, true);
4115 }
4116
4117 ionic.requestAnimationFrame(function(){
4118 document.body.classList.remove(KEYBOARD_OPEN_CSS);
4119 });
4120
4121 // the keyboard is gone now, remove the touchmove that disables native scroll
4122 if (window.navigator.msPointerEnabled) {
4123 document.removeEventListener("MSPointerMove", keyboardPreventDefault);
4124 } else {
4125 document.removeEventListener('touchmove', keyboardPreventDefault);
4126 }
4127 document.removeEventListener('keydown', keyboardOnKeyDown);
4128
4129 if (ionic.Platform.isAndroid()) {
4130 // on android closing the keyboard with the back/dismiss button won't remove
4131 // focus and keyboard can re-appear on subsequent taps (like scrolling)
4132 if (keyboardHasPlugin()) cordova.plugins.Keyboard.close();
4133 keyboardActiveElement && keyboardActiveElement.blur();
4134 }
4135
4136 keyboardActiveElement = null;
4137 lastKeyboardActiveElement = null;
4138}
4139
4140/**
4141 * On keyboard open sets keyboard state to open, adds CSS to the body
4142 * indicating the keyboard is open and tells the scroll view to resize and
4143 * the currently focused input into view if necessary.
4144 */
4145function keyboardShow() {
4146
4147 ionic.keyboard.isOpen = true;
4148 ionic.keyboard.isOpening = false;
4149
4150 var details = {
4151 keyboardHeight: keyboardGetHeight(),
4152 viewportHeight: keyboardCurrentViewportHeight
4153 };
4154
4155 if (keyboardActiveElement) {
4156 details.target = keyboardActiveElement;
4157
4158 var elementBounds = keyboardActiveElement.getBoundingClientRect();
4159
4160 details.elementTop = Math.round(elementBounds.top);
4161 details.elementBottom = Math.round(elementBounds.bottom);
4162
4163 details.windowHeight = details.viewportHeight - details.keyboardHeight;
4164 //console.log("keyboardShow viewportHeight: " + details.viewportHeight +
4165 //", windowHeight: " + details.windowHeight +
4166 //", keyboardHeight: " + details.keyboardHeight);
4167
4168 // figure out if the element is under the keyboard
4169 details.isElementUnderKeyboard = (details.elementBottom > details.windowHeight);
4170 //console.log("isUnderKeyboard: " + details.isElementUnderKeyboard);
4171 //console.log("elementBottom: " + details.elementBottom);
4172
4173 // send event so the scroll view adjusts
4174 ionic.trigger('scrollChildIntoView', details, true);
4175 }
4176
4177 setTimeout(function(){
4178 document.body.classList.add(KEYBOARD_OPEN_CSS);
4179 }, 400);
4180
4181 return details; //for testing
4182}
4183
4184/* eslint no-unused-vars:0 */
4185function keyboardGetHeight() {
4186 // check if we already have a keyboard height from the plugin or resize calculations
4187 if (ionic.keyboard.height) {
4188 return ionic.keyboard.height;
4189 }
4190
4191 if (ionic.Platform.isAndroid()) {
4192 // should be using the plugin, no way to know how big the keyboard is, so guess
4193 if ( ionic.Platform.isFullScreen ) {
4194 return 275;
4195 }
4196 // otherwise just calculate it
4197 var contentHeight = window.innerHeight;
4198 if (contentHeight < keyboardCurrentViewportHeight) {
4199 return keyboardCurrentViewportHeight - contentHeight;
4200 } else {
4201 return 0;
4202 }
4203 }
4204
4205 // fallback for when it's the webview without the plugin
4206 // or for just the standard web browser
4207 // TODO: have these be based on device
4208 if (ionic.Platform.isIOS()) {
4209 if (ionic.keyboard.isLandscape) {
4210 return 206;
4211 }
4212
4213 if (!ionic.Platform.isWebView()) {
4214 return 216;
4215 }
4216
4217 return 260;
4218 }
4219
4220 // safe guess
4221 return 275;
4222}
4223
4224function isPortraitViewportHeight(viewportHeight) {
4225 return !!(!ionic.keyboard.isLandscape &&
4226 keyboardPortraitViewportHeight &&
4227 (Math.abs(keyboardPortraitViewportHeight - viewportHeight) < 2));
4228}
4229
4230function isLandscapeViewportHeight(viewportHeight) {
4231 return !!(ionic.keyboard.isLandscape &&
4232 keyboardLandscapeViewportHeight &&
4233 (Math.abs(keyboardLandscapeViewportHeight - viewportHeight) < 2));
4234}
4235
4236function keyboardUpdateViewportHeight() {
4237 wasOrientationChange = false;
4238 keyboardCurrentViewportHeight = getViewportHeight();
4239
4240 if (ionic.keyboard.isLandscape && !keyboardLandscapeViewportHeight) {
4241 //console.log("saved landscape: " + keyboardCurrentViewportHeight);
4242 keyboardLandscapeViewportHeight = keyboardCurrentViewportHeight;
4243
4244 } else if (!ionic.keyboard.isLandscape && !keyboardPortraitViewportHeight) {
4245 //console.log("saved portrait: " + keyboardCurrentViewportHeight);
4246 keyboardPortraitViewportHeight = keyboardCurrentViewportHeight;
4247 }
4248
4249 if (keyboardActiveElement) {
4250 ionic.trigger('resetScrollView', {
4251 target: keyboardActiveElement
4252 }, true);
4253 }
4254
4255 if (ionic.keyboard.isOpen && ionic.tap.isTextInput(keyboardActiveElement)) {
4256 keyboardShow();
4257 }
4258}
4259
4260function keyboardInitViewportHeight() {
4261 var viewportHeight = getViewportHeight();
4262 //console.log("Keyboard init VP: " + viewportHeight + " " + window.innerWidth);
4263 // can't just use window.innerHeight in case the keyboard is opened immediately
4264 if ((viewportHeight / window.innerWidth) < 1) {
4265 ionic.keyboard.isLandscape = true;
4266 }
4267 //console.log("ionic.keyboard.isLandscape is: " + ionic.keyboard.isLandscape);
4268
4269 // initialize or update the current viewport height values
4270 keyboardCurrentViewportHeight = viewportHeight;
4271 if (ionic.keyboard.isLandscape && !keyboardLandscapeViewportHeight) {
4272 keyboardLandscapeViewportHeight = keyboardCurrentViewportHeight;
4273 } else if (!ionic.keyboard.isLandscape && !keyboardPortraitViewportHeight) {
4274 keyboardPortraitViewportHeight = keyboardCurrentViewportHeight;
4275 }
4276}
4277
4278function getViewportHeight() {
4279 var windowHeight = window.innerHeight;
4280 //console.log('window.innerHeight is: ' + windowHeight);
4281 //console.log('kb height is: ' + ionic.keyboard.height);
4282 //console.log('kb isOpen: ' + ionic.keyboard.isOpen);
4283
4284 //TODO: add iPad undocked/split kb once kb plugin supports it
4285 // the keyboard overlays the window on Android fullscreen
4286 if (!(ionic.Platform.isAndroid() && ionic.Platform.isFullScreen) &&
4287 (ionic.keyboard.isOpen || ionic.keyboard.isOpening) &&
4288 !ionic.keyboard.isClosing) {
4289
4290 return windowHeight + keyboardGetHeight();
4291 }
4292 return windowHeight;
4293}
4294
4295function keyboardHasPlugin() {
4296 return !!(window.cordova && cordova.plugins && cordova.plugins.Keyboard);
4297}
4298
4299ionic.Platform.ready(function() {
4300 keyboardInitViewportHeight();
4301
4302 window.addEventListener('orientationchange', keyboardOrientationChange);
4303
4304 // if orientation changes while app is in background, update on resuming
4305 /*
4306 if ( ionic.Platform.isWebView() ) {
4307 document.addEventListener('resume', keyboardInitViewportHeight);
4308
4309 if (ionic.Platform.isAndroid()) {
4310 //TODO: onbackpressed to detect keyboard close without focusout or plugin
4311 }
4312 }
4313 */
4314
4315 // if orientation changes while app is in background, update on resuming
4316/* if ( ionic.Platform.isWebView() ) {
4317 document.addEventListener('pause', function() {
4318 window.removeEventListener('orientationchange', keyboardOrientationChange);
4319 })
4320 document.addEventListener('resume', function() {
4321 keyboardInitViewportHeight();
4322 window.addEventListener('orientationchange', keyboardOrientationChange)
4323 });
4324 }*/
4325
4326 // Android sometimes reports bad innerHeight on window.load
4327 // try it again in a lil bit to play it safe
4328 setTimeout(keyboardInitViewportHeight, 999);
4329
4330 // only initialize the adjustments for the virtual keyboard
4331 // if a touchstart event happens
4332 if (window.navigator.msPointerEnabled) {
4333 document.addEventListener("MSPointerDown", keyboardInit, false);
4334 } else {
4335 document.addEventListener('touchstart', keyboardInit, false);
4336 }
4337});
4338
4339
4340
4341var viewportTag;
4342var viewportProperties = {};
4343
4344ionic.viewport = {
4345 orientation: function() {
4346 // 0 = Portrait
4347 // 90 = Landscape
4348 // not using window.orientation because each device has a different implementation
4349 return (window.innerWidth > window.innerHeight ? 90 : 0);
4350 }
4351};
4352
4353function viewportLoadTag() {
4354 var x;
4355
4356 for (x = 0; x < document.head.children.length; x++) {
4357 if (document.head.children[x].name == 'viewport') {
4358 viewportTag = document.head.children[x];
4359 break;
4360 }
4361 }
4362
4363 if (viewportTag) {
4364 var props = viewportTag.content.toLowerCase().replace(/\s+/g, '').split(',');
4365 var keyValue;
4366 for (x = 0; x < props.length; x++) {
4367 if (props[x]) {
4368 keyValue = props[x].split('=');
4369 viewportProperties[ keyValue[0] ] = (keyValue.length > 1 ? keyValue[1] : '_');
4370 }
4371 }
4372 viewportUpdate();
4373 }
4374}
4375
4376function viewportUpdate() {
4377 // unit tests in viewport.unit.js
4378
4379 var initWidth = viewportProperties.width;
4380 var initHeight = viewportProperties.height;
4381 var p = ionic.Platform;
4382 var version = p.version();
4383 var DEVICE_WIDTH = 'device-width';
4384 var DEVICE_HEIGHT = 'device-height';
4385 var orientation = ionic.viewport.orientation();
4386
4387 // Most times we're removing the height and adding the width
4388 // So this is the default to start with, then modify per platform/version/oreintation
4389 delete viewportProperties.height;
4390 viewportProperties.width = DEVICE_WIDTH;
4391
4392 if (p.isIPad()) {
4393 // iPad
4394
4395 if (version > 7) {
4396 // iPad >= 7.1
4397 // https://issues.apache.org/jira/browse/CB-4323
4398 delete viewportProperties.width;
4399
4400 } else {
4401 // iPad <= 7.0
4402
4403 if (p.isWebView()) {
4404 // iPad <= 7.0 WebView
4405
4406 if (orientation == 90) {
4407 // iPad <= 7.0 WebView Landscape
4408 viewportProperties.height = '0';
4409
4410 } else if (version == 7) {
4411 // iPad <= 7.0 WebView Portait
4412 viewportProperties.height = DEVICE_HEIGHT;
4413 }
4414 } else {
4415 // iPad <= 6.1 Browser
4416 if (version < 7) {
4417 viewportProperties.height = '0';
4418 }
4419 }
4420 }
4421
4422 } else if (p.isIOS()) {
4423 // iPhone
4424
4425 if (p.isWebView()) {
4426 // iPhone WebView
4427
4428 if (version > 7) {
4429 // iPhone >= 7.1 WebView
4430 delete viewportProperties.width;
4431
4432 } else if (version < 7) {
4433 // iPhone <= 6.1 WebView
4434 // if height was set it needs to get removed with this hack for <= 6.1
4435 if (initHeight) viewportProperties.height = '0';
4436
4437 } else if (version == 7) {
4438 //iPhone == 7.0 WebView
4439 viewportProperties.height = DEVICE_HEIGHT;
4440 }
4441
4442 } else {
4443 // iPhone Browser
4444
4445 if (version < 7) {
4446 // iPhone <= 6.1 Browser
4447 // if height was set it needs to get removed with this hack for <= 6.1
4448 if (initHeight) viewportProperties.height = '0';
4449 }
4450 }
4451
4452 }
4453
4454 // only update the viewport tag if there was a change
4455 if (initWidth !== viewportProperties.width || initHeight !== viewportProperties.height) {
4456 viewportTagUpdate();
4457 }
4458}
4459
4460function viewportTagUpdate() {
4461 var key, props = [];
4462 for (key in viewportProperties) {
4463 if (viewportProperties[key]) {
4464 props.push(key + (viewportProperties[key] == '_' ? '' : '=' + viewportProperties[key]));
4465 }
4466 }
4467
4468 viewportTag.content = props.join(', ');
4469}
4470
4471ionic.Platform.ready(function() {
4472 viewportLoadTag();
4473
4474 window.addEventListener("orientationchange", function() {
4475 setTimeout(viewportUpdate, 1000);
4476 }, false);
4477});
4478
4479(function(ionic) {
4480'use strict';
4481 ionic.views.View = function() {
4482 this.initialize.apply(this, arguments);
4483 };
4484
4485 ionic.views.View.inherit = ionic.inherit;
4486
4487 ionic.extend(ionic.views.View.prototype, {
4488 initialize: function() {}
4489 });
4490
4491})(window.ionic);
4492
4493/*
4494 * Scroller
4495 * http://github.com/zynga/scroller
4496 *
4497 * Copyright 2011, Zynga Inc.
4498 * Licensed under the MIT License.
4499 * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt
4500 *
4501 * Based on the work of: Unify Project (unify-project.org)
4502 * http://unify-project.org
4503 * Copyright 2011, Deutsche Telekom AG
4504 * License: MIT + Apache (V2)
4505 */
4506
4507/* jshint eqnull: true */
4508
4509/**
4510 * Generic animation class with support for dropped frames both optional easing and duration.
4511 *
4512 * Optional duration is useful when the lifetime is defined by another condition than time
4513 * e.g. speed of an animating object, etc.
4514 *
4515 * Dropped frame logic allows to keep using the same updater logic independent from the actual
4516 * rendering. This eases a lot of cases where it might be pretty complex to break down a state
4517 * based on the pure time difference.
4518 */
4519var zyngaCore = { effect: {} };
4520(function(global) {
4521 var time = Date.now || function() {
4522 return +new Date();
4523 };
4524 var desiredFrames = 60;
4525 var millisecondsPerSecond = 1000;
4526 var running = {};
4527 var counter = 1;
4528
4529 zyngaCore.effect.Animate = {
4530
4531 /**
4532 * A requestAnimationFrame wrapper / polyfill.
4533 *
4534 * @param callback {Function} The callback to be invoked before the next repaint.
4535 * @param root {HTMLElement} The root element for the repaint
4536 */
4537 requestAnimationFrame: (function() {
4538
4539 // Check for request animation Frame support
4540 var requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame;
4541 var isNative = !!requestFrame;
4542
4543 if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) {
4544 isNative = false;
4545 }
4546
4547 if (isNative) {
4548 return function(callback, root) {
4549 requestFrame(callback, root);
4550 };
4551 }
4552
4553 var TARGET_FPS = 60;
4554 var requests = {};
4555 var requestCount = 0;
4556 var rafHandle = 1;
4557 var intervalHandle = null;
4558 var lastActive = +new Date();
4559
4560 return function(callback) {
4561 var callbackHandle = rafHandle++;
4562
4563 // Store callback
4564 requests[callbackHandle] = callback;
4565 requestCount++;
4566
4567 // Create timeout at first request
4568 if (intervalHandle === null) {
4569
4570 intervalHandle = setInterval(function() {
4571
4572 var time = +new Date();
4573 var currentRequests = requests;
4574
4575 // Reset data structure before executing callbacks
4576 requests = {};
4577 requestCount = 0;
4578
4579 for(var key in currentRequests) {
4580 if (currentRequests.hasOwnProperty(key)) {
4581 currentRequests[key](time);
4582 lastActive = time;
4583 }
4584 }
4585
4586 // Disable the timeout when nothing happens for a certain
4587 // period of time
4588 if (time - lastActive > 2500) {
4589 clearInterval(intervalHandle);
4590 intervalHandle = null;
4591 }
4592
4593 }, 1000 / TARGET_FPS);
4594 }
4595
4596 return callbackHandle;
4597 };
4598
4599 })(),
4600
4601
4602 /**
4603 * Stops the given animation.
4604 *
4605 * @param id {Integer} Unique animation ID
4606 * @return {Boolean} Whether the animation was stopped (aka, was running before)
4607 */
4608 stop: function(id) {
4609 var cleared = running[id] != null;
4610 if (cleared) {
4611 running[id] = null;
4612 }
4613
4614 return cleared;
4615 },
4616
4617
4618 /**
4619 * Whether the given animation is still running.
4620 *
4621 * @param id {Integer} Unique animation ID
4622 * @return {Boolean} Whether the animation is still running
4623 */
4624 isRunning: function(id) {
4625 return running[id] != null;
4626 },
4627
4628
4629 /**
4630 * Start the animation.
4631 *
4632 * @param stepCallback {Function} Pointer to function which is executed on every step.
4633 * Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }`
4634 * @param verifyCallback {Function} Executed before every animation step.
4635 * Signature of the method should be `function() { return continueWithAnimation; }`
4636 * @param completedCallback {Function}
4637 * Signature of the method should be `function(droppedFrames, finishedAnimation) {}`
4638 * @param duration {Integer} Milliseconds to run the animation
4639 * @param easingMethod {Function} Pointer to easing function
4640 * Signature of the method should be `function(percent) { return modifiedValue; }`
4641 * @param root {Element} Render root, when available. Used for internal
4642 * usage of requestAnimationFrame.
4643 * @return {Integer} Identifier of animation. Can be used to stop it any time.
4644 */
4645 start: function(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {
4646
4647 var start = time();
4648 var lastFrame = start;
4649 var percent = 0;
4650 var dropCounter = 0;
4651 var id = counter++;
4652
4653 if (!root) {
4654 root = document.body;
4655 }
4656
4657 // Compacting running db automatically every few new animations
4658 if (id % 20 === 0) {
4659 var newRunning = {};
4660 for (var usedId in running) {
4661 newRunning[usedId] = true;
4662 }
4663 running = newRunning;
4664 }
4665
4666 // This is the internal step method which is called every few milliseconds
4667 var step = function(virtual) {
4668
4669 // Normalize virtual value
4670 var render = virtual !== true;
4671
4672 // Get current time
4673 var now = time();
4674
4675 // Verification is executed before next animation step
4676 if (!running[id] || (verifyCallback && !verifyCallback(id))) {
4677
4678 running[id] = null;
4679 completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false);
4680 return;
4681
4682 }
4683
4684 // For the current rendering to apply let's update omitted steps in memory.
4685 // This is important to bring internal state variables up-to-date with progress in time.
4686 if (render) {
4687
4688 var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1;
4689 for (var j = 0; j < Math.min(droppedFrames, 4); j++) {
4690 step(true);
4691 dropCounter++;
4692 }
4693
4694 }
4695
4696 // Compute percent value
4697 if (duration) {
4698 percent = (now - start) / duration;
4699 if (percent > 1) {
4700 percent = 1;
4701 }
4702 }
4703
4704 // Execute step callback, then...
4705 var value = easingMethod ? easingMethod(percent) : percent;
4706 if ((stepCallback(value, now, render) === false || percent === 1) && render) {
4707 running[id] = null;
4708 completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration == null);
4709 } else if (render) {
4710 lastFrame = now;
4711 zyngaCore.effect.Animate.requestAnimationFrame(step, root);
4712 }
4713 };
4714
4715 // Mark as running
4716 running[id] = true;
4717
4718 // Init first step
4719 zyngaCore.effect.Animate.requestAnimationFrame(step, root);
4720
4721 // Return unique animation ID
4722 return id;
4723 }
4724 };
4725})(window);
4726
4727/*
4728 * Scroller
4729 * http://github.com/zynga/scroller
4730 *
4731 * Copyright 2011, Zynga Inc.
4732 * Licensed under the MIT License.
4733 * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt
4734 *
4735 * Based on the work of: Unify Project (unify-project.org)
4736 * http://unify-project.org
4737 * Copyright 2011, Deutsche Telekom AG
4738 * License: MIT + Apache (V2)
4739 */
4740
4741(function(ionic) {
4742 var NOOP = function(){};
4743
4744 // Easing Equations (c) 2003 Robert Penner, all rights reserved.
4745 // Open source under the BSD License.
4746
4747 /**
4748 * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)
4749 **/
4750 var easeOutCubic = function(pos) {
4751 return (Math.pow((pos - 1), 3) + 1);
4752 };
4753
4754 /**
4755 * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)
4756 **/
4757 var easeInOutCubic = function(pos) {
4758 if ((pos /= 0.5) < 1) {
4759 return 0.5 * Math.pow(pos, 3);
4760 }
4761
4762 return 0.5 * (Math.pow((pos - 2), 3) + 2);
4763 };
4764
4765
4766/**
4767 * ionic.views.Scroll
4768 * A powerful scroll view with support for bouncing, pull to refresh, and paging.
4769 * @param {Object} options options for the scroll view
4770 * @class A scroll view system
4771 * @memberof ionic.views
4772 */
4773ionic.views.Scroll = ionic.views.View.inherit({
4774 initialize: function(options) {
4775 var self = this;
4776
4777 self.__container = options.el;
4778 self.__content = options.el.firstElementChild;
4779
4780 //Remove any scrollTop attached to these elements; they are virtual scroll now
4781 //This also stops on-load-scroll-to-window.location.hash that the browser does
4782 setTimeout(function() {
4783 if (self.__container && self.__content) {
4784 self.__container.scrollTop = 0;
4785 self.__content.scrollTop = 0;
4786 }
4787 });
4788
4789 self.options = {
4790
4791 /** Disable scrolling on x-axis by default */
4792 scrollingX: false,
4793 scrollbarX: true,
4794
4795 /** Enable scrolling on y-axis */
4796 scrollingY: true,
4797 scrollbarY: true,
4798
4799 startX: 0,
4800 startY: 0,
4801
4802 /** The amount to dampen mousewheel events */
4803 wheelDampen: 6,
4804
4805 /** The minimum size the scrollbars scale to while scrolling */
4806 minScrollbarSizeX: 5,
4807 minScrollbarSizeY: 5,
4808
4809 /** Scrollbar fading after scrolling */
4810 scrollbarsFade: true,
4811 scrollbarFadeDelay: 300,
4812 /** The initial fade delay when the pane is resized or initialized */
4813 scrollbarResizeFadeDelay: 1000,
4814
4815 /** Enable animations for deceleration, snap back, zooming and scrolling */
4816 animating: true,
4817
4818 /** duration for animations triggered by scrollTo/zoomTo */
4819 animationDuration: 250,
4820
4821 /** The velocity required to make the scroll view "slide" after touchend */
4822 decelVelocityThreshold: 4,
4823
4824 /** The velocity required to make the scroll view "slide" after touchend when using paging */
4825 decelVelocityThresholdPaging: 4,
4826
4827 /** Enable bouncing (content can be slowly moved outside and jumps back after releasing) */
4828 bouncing: true,
4829
4830 /** Enable locking to the main axis if user moves only slightly on one of them at start */
4831 locking: true,
4832
4833 /** Enable pagination mode (switching between full page content panes) */
4834 paging: false,
4835
4836 /** Enable snapping of content to a configured pixel grid */
4837 snapping: false,
4838
4839 /** Enable zooming of content via API, fingers and mouse wheel */
4840 zooming: false,
4841
4842 /** Minimum zoom level */
4843 minZoom: 0.5,
4844
4845 /** Maximum zoom level */
4846 maxZoom: 3,
4847
4848 /** Multiply or decrease scrolling speed **/
4849 speedMultiplier: 1,
4850
4851 deceleration: 0.97,
4852
4853 /** Whether to prevent default on a scroll operation to capture drag events **/
4854 preventDefault: false,
4855
4856 /** Callback that is fired on the later of touch end or deceleration end,
4857 provided that another scrolling action has not begun. Used to know
4858 when to fade out a scrollbar. */
4859 scrollingComplete: NOOP,
4860
4861 /** This configures the amount of change applied to deceleration when reaching boundaries **/
4862 penetrationDeceleration: 0.03,
4863
4864 /** This configures the amount of change applied to acceleration when reaching boundaries **/
4865 penetrationAcceleration: 0.08,
4866
4867 // The ms interval for triggering scroll events
4868 scrollEventInterval: 10,
4869
4870 freeze: false,
4871
4872 getContentWidth: function() {
4873 return Math.max(self.__content.scrollWidth, self.__content.offsetWidth);
4874 },
4875 getContentHeight: function() {
4876 return Math.max(self.__content.scrollHeight, self.__content.offsetHeight + (self.__content.offsetTop * 2));
4877 }
4878 };
4879
4880 for (var key in options) {
4881 self.options[key] = options[key];
4882 }
4883
4884 self.hintResize = ionic.debounce(function() {
4885 self.resize();
4886 }, 1000, true);
4887
4888 self.onScroll = function() {
4889
4890 if (!ionic.scroll.isScrolling) {
4891 setTimeout(self.setScrollStart, 50);
4892 } else {
4893 clearTimeout(self.scrollTimer);
4894 self.scrollTimer = setTimeout(self.setScrollStop, 80);
4895 }
4896
4897 };
4898
4899 self.freeze = function(shouldFreeze) {
4900 if (arguments.length) {
4901 self.options.freeze = shouldFreeze;
4902 }
4903 return self.options.freeze;
4904 };
4905
4906 // We can just use the standard freeze pop in our mouth
4907 self.freezeShut = self.freeze;
4908
4909 self.setScrollStart = function() {
4910 ionic.scroll.isScrolling = Math.abs(ionic.scroll.lastTop - self.__scrollTop) > 1;
4911 clearTimeout(self.scrollTimer);
4912 self.scrollTimer = setTimeout(self.setScrollStop, 80);
4913 };
4914
4915 self.setScrollStop = function() {
4916 ionic.scroll.isScrolling = false;
4917 ionic.scroll.lastTop = self.__scrollTop;
4918 };
4919
4920 self.triggerScrollEvent = ionic.throttle(function() {
4921 self.onScroll();
4922 ionic.trigger('scroll', {
4923 scrollTop: self.__scrollTop,
4924 scrollLeft: self.__scrollLeft,
4925 target: self.__container
4926 });
4927 }, self.options.scrollEventInterval);
4928
4929 self.triggerScrollEndEvent = function() {
4930 ionic.trigger('scrollend', {
4931 scrollTop: self.__scrollTop,
4932 scrollLeft: self.__scrollLeft,
4933 target: self.__container
4934 });
4935 };
4936
4937 self.__scrollLeft = self.options.startX;
4938 self.__scrollTop = self.options.startY;
4939
4940 // Get the render update function, initialize event handlers,
4941 // and calculate the size of the scroll container
4942 self.__callback = self.getRenderFn();
4943 self.__initEventHandlers();
4944 self.__createScrollbars();
4945
4946 },
4947
4948 run: function() {
4949 this.resize();
4950
4951 // Fade them out
4952 this.__fadeScrollbars('out', this.options.scrollbarResizeFadeDelay);
4953 },
4954
4955
4956
4957 /*
4958 ---------------------------------------------------------------------------
4959 INTERNAL FIELDS :: STATUS
4960 ---------------------------------------------------------------------------
4961 */
4962
4963 /** Whether only a single finger is used in touch handling */
4964 __isSingleTouch: false,
4965
4966 /** Whether a touch event sequence is in progress */
4967 __isTracking: false,
4968
4969 /** Whether a deceleration animation went to completion. */
4970 __didDecelerationComplete: false,
4971
4972 /**
4973 * Whether a gesture zoom/rotate event is in progress. Activates when
4974 * a gesturestart event happens. This has higher priority than dragging.
4975 */
4976 __isGesturing: false,
4977
4978 /**
4979 * Whether the user has moved by such a distance that we have enabled
4980 * dragging mode. Hint: It's only enabled after some pixels of movement to
4981 * not interrupt with clicks etc.
4982 */
4983 __isDragging: false,
4984
4985 /**
4986 * Not touching and dragging anymore, and smoothly animating the
4987 * touch sequence using deceleration.
4988 */
4989 __isDecelerating: false,
4990
4991 /**
4992 * Smoothly animating the currently configured change
4993 */
4994 __isAnimating: false,
4995
4996
4997
4998 /*
4999 ---------------------------------------------------------------------------
5000 INTERNAL FIELDS :: DIMENSIONS
5001 ---------------------------------------------------------------------------
5002 */
5003
5004 /** Available outer left position (from document perspective) */
5005 __clientLeft: 0,
5006
5007 /** Available outer top position (from document perspective) */
5008 __clientTop: 0,
5009
5010 /** Available outer width */
5011 __clientWidth: 0,
5012
5013 /** Available outer height */
5014 __clientHeight: 0,
5015
5016 /** Outer width of content */
5017 __contentWidth: 0,
5018
5019 /** Outer height of content */
5020 __contentHeight: 0,
5021
5022 /** Snapping width for content */
5023 __snapWidth: 100,
5024
5025 /** Snapping height for content */
5026 __snapHeight: 100,
5027
5028 /** Height to assign to refresh area */
5029 __refreshHeight: null,
5030
5031 /** Whether the refresh process is enabled when the event is released now */
5032 __refreshActive: false,
5033
5034 /** Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release */
5035 __refreshActivate: null,
5036
5037 /** Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled */
5038 __refreshDeactivate: null,
5039
5040 /** Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */
5041 __refreshStart: null,
5042
5043 /** Zoom level */
5044 __zoomLevel: 1,
5045
5046 /** Scroll position on x-axis */
5047 __scrollLeft: 0,
5048
5049 /** Scroll position on y-axis */
5050 __scrollTop: 0,
5051
5052 /** Maximum allowed scroll position on x-axis */
5053 __maxScrollLeft: 0,
5054
5055 /** Maximum allowed scroll position on y-axis */
5056 __maxScrollTop: 0,
5057
5058 /* Scheduled left position (final position when animating) */
5059 __scheduledLeft: 0,
5060
5061 /* Scheduled top position (final position when animating) */
5062 __scheduledTop: 0,
5063
5064 /* Scheduled zoom level (final scale when animating) */
5065 __scheduledZoom: 0,
5066
5067
5068
5069 /*
5070 ---------------------------------------------------------------------------
5071 INTERNAL FIELDS :: LAST POSITIONS
5072 ---------------------------------------------------------------------------
5073 */
5074
5075 /** Left position of finger at start */
5076 __lastTouchLeft: null,
5077
5078 /** Top position of finger at start */
5079 __lastTouchTop: null,
5080
5081 /** Timestamp of last move of finger. Used to limit tracking range for deceleration speed. */
5082 __lastTouchMove: null,
5083
5084 /** List of positions, uses three indexes for each state: left, top, timestamp */
5085 __positions: null,
5086
5087
5088
5089 /*
5090 ---------------------------------------------------------------------------
5091 INTERNAL FIELDS :: DECELERATION SUPPORT
5092 ---------------------------------------------------------------------------
5093 */
5094
5095 /** Minimum left scroll position during deceleration */
5096 __minDecelerationScrollLeft: null,
5097
5098 /** Minimum top scroll position during deceleration */
5099 __minDecelerationScrollTop: null,
5100
5101 /** Maximum left scroll position during deceleration */
5102 __maxDecelerationScrollLeft: null,
5103
5104 /** Maximum top scroll position during deceleration */
5105 __maxDecelerationScrollTop: null,
5106
5107 /** Current factor to modify horizontal scroll position with on every step */
5108 __decelerationVelocityX: null,
5109
5110 /** Current factor to modify vertical scroll position with on every step */
5111 __decelerationVelocityY: null,
5112
5113
5114 /** the browser-specific property to use for transforms */
5115 __transformProperty: null,
5116 __perspectiveProperty: null,
5117
5118 /** scrollbar indicators */
5119 __indicatorX: null,
5120 __indicatorY: null,
5121
5122 /** Timeout for scrollbar fading */
5123 __scrollbarFadeTimeout: null,
5124
5125 /** whether we've tried to wait for size already */
5126 __didWaitForSize: null,
5127 __sizerTimeout: null,
5128
5129 __initEventHandlers: function() {
5130 var self = this;
5131
5132 // Event Handler
5133 var container = self.__container;
5134
5135 // save height when scroll view is shrunk so we don't need to reflow
5136 var scrollViewOffsetHeight;
5137
5138 /**
5139 * Shrink the scroll view when the keyboard is up if necessary and if the
5140 * focused input is below the bottom of the shrunk scroll view, scroll it
5141 * into view.
5142 */
5143 self.scrollChildIntoView = function(e) {
5144 //console.log("scrollChildIntoView at: " + Date.now());
5145
5146 // D
5147 var scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
5148 // D - A
5149 scrollViewOffsetHeight = container.offsetHeight;
5150 var alreadyShrunk = self.isShrunkForKeyboard;
5151
5152 var isModal = container.parentNode.classList.contains('modal');
5153 // 680px is when the media query for 60% modal width kicks in
5154 var isInsetModal = isModal && window.innerWidth >= 680;
5155
5156 /*
5157 * _______
5158 * |---A---| <- top of scroll view
5159 * | |
5160 * |---B---| <- keyboard
5161 * | C | <- input
5162 * |---D---| <- initial bottom of scroll view
5163 * |___E___| <- bottom of viewport
5164 *
5165 * All commented calculations relative to the top of the viewport (ie E
5166 * is the viewport height, not 0)
5167 */
5168 if (!alreadyShrunk) {
5169 // shrink scrollview so we can actually scroll if the input is hidden
5170 // if it isn't shrink so we can scroll to inputs under the keyboard
5171 // inset modals won't shrink on Android on their own when the keyboard appears
5172 if ( ionic.Platform.isIOS() || ionic.Platform.isFullScreen || isInsetModal ) {
5173 // if there are things below the scroll view account for them and
5174 // subtract them from the keyboard height when resizing
5175 // E - D E D
5176 var scrollBottomOffsetToBottom = e.detail.viewportHeight - scrollBottomOffsetToTop;
5177
5178 // 0 or D - B if D > B E - B E - D
5179 var keyboardOffset = Math.max(0, e.detail.keyboardHeight - scrollBottomOffsetToBottom);
5180
5181 ionic.requestAnimationFrame(function(){
5182 // D - A or B - A if D > B D - A max(0, D - B)
5183 scrollViewOffsetHeight = scrollViewOffsetHeight - keyboardOffset;
5184 container.style.height = scrollViewOffsetHeight + "px";
5185 container.style.overflow = "visible";
5186
5187 //update scroll view
5188 self.resize();
5189 });
5190 }
5191
5192 self.isShrunkForKeyboard = true;
5193 }
5194
5195 /*
5196 * _______
5197 * |---A---| <- top of scroll view
5198 * | * | <- where we want to scroll to
5199 * |--B-D--| <- keyboard, bottom of scroll view
5200 * | C | <- input
5201 * | |
5202 * |___E___| <- bottom of viewport
5203 *
5204 * All commented calculations relative to the top of the viewport (ie E
5205 * is the viewport height, not 0)
5206 */
5207 // if the element is positioned under the keyboard scroll it into view
5208 if (e.detail.isElementUnderKeyboard) {
5209
5210 ionic.requestAnimationFrame(function(){
5211 container.scrollTop = 0;
5212 // update D if we shrunk
5213 if (self.isShrunkForKeyboard && !alreadyShrunk) {
5214 scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
5215 }
5216
5217 // middle of the scrollview, this is where we want to scroll to
5218 // (D - A) / 2
5219 var scrollMidpointOffset = scrollViewOffsetHeight * 0.5;
5220 //console.log("container.offsetHeight: " + scrollViewOffsetHeight);
5221
5222 // middle of the input we want to scroll into view
5223 // C
5224 var inputMidpoint = ((e.detail.elementBottom + e.detail.elementTop) / 2);
5225
5226 // distance from middle of input to the bottom of the scroll view
5227 // C - D C D
5228 var inputMidpointOffsetToScrollBottom = inputMidpoint - scrollBottomOffsetToTop;
5229
5230 //C - D + (D - A)/2 C - D (D - A)/ 2
5231 var scrollTop = inputMidpointOffsetToScrollBottom + scrollMidpointOffset;
5232
5233 if ( scrollTop > 0) {
5234 if (ionic.Platform.isIOS()) ionic.tap.cloneFocusedInput(container, self);
5235 self.scrollBy(0, scrollTop, true);
5236 self.onScroll();
5237 }
5238 });
5239 }
5240
5241 // Only the first scrollView parent of the element that broadcasted this event
5242 // (the active element that needs to be shown) should receive this event
5243 e.stopPropagation();
5244 };
5245
5246 self.resetScrollView = function() {
5247 //return scrollview to original height once keyboard has hidden
5248 if ( self.isShrunkForKeyboard ) {
5249 self.isShrunkForKeyboard = false;
5250 container.style.height = "";
5251 container.style.overflow = "";
5252 }
5253 self.resize();
5254 };
5255
5256 //Broadcasted when keyboard is shown on some platforms.
5257 //See js/utils/keyboard.js
5258 container.addEventListener('scrollChildIntoView', self.scrollChildIntoView);
5259
5260 // Listen on document because container may not have had the last
5261 // keyboardActiveElement, for example after closing a modal with a focused
5262 // input and returning to a previously resized scroll view in an ion-content.
5263 // Since we can only resize scroll views that are currently visible, just resize
5264 // the current scroll view when the keyboard is closed.
5265 document.addEventListener('resetScrollView', self.resetScrollView);
5266
5267 function getEventTouches(e) {
5268 return e.touches && e.touches.length ? e.touches : [{
5269 pageX: e.pageX,
5270 pageY: e.pageY
5271 }];
5272 }
5273
5274 self.touchStart = function(e) {
5275 self.startCoordinates = ionic.tap.pointerCoord(e);
5276
5277 if ( ionic.tap.ignoreScrollStart(e) ) {
5278 return;
5279 }
5280
5281 self.__isDown = true;
5282
5283 if ( ionic.tap.containsOrIsTextInput(e.target) || e.target.tagName === 'SELECT' ) {
5284 // do not start if the target is a text input
5285 // if there is a touchmove on this input, then we can start the scroll
5286 self.__hasStarted = false;
5287 return;
5288 }
5289
5290 self.__isSelectable = true;
5291 self.__enableScrollY = true;
5292 self.__hasStarted = true;
5293 self.doTouchStart(getEventTouches(e), e.timeStamp);
5294 e.preventDefault();
5295 };
5296
5297 self.touchMove = function(e) {
5298 if (self.options.freeze || !self.__isDown ||
5299 (!self.__isDown && e.defaultPrevented) ||
5300 (e.target.tagName === 'TEXTAREA' && e.target.parentElement.querySelector(':focus')) ) {
5301 return;
5302 }
5303
5304 if ( !self.__hasStarted && ( ionic.tap.containsOrIsTextInput(e.target) || e.target.tagName === 'SELECT' ) ) {
5305 // the target is a text input and scroll has started
5306 // since the text input doesn't start on touchStart, do it here
5307 self.__hasStarted = true;
5308 self.doTouchStart(getEventTouches(e), e.timeStamp);
5309 e.preventDefault();
5310 return;
5311 }
5312
5313 if (self.startCoordinates) {
5314 // we have start coordinates, so get this touch move's current coordinates
5315 var currentCoordinates = ionic.tap.pointerCoord(e);
5316
5317 if ( self.__isSelectable &&
5318 ionic.tap.isTextInput(e.target) &&
5319 Math.abs(self.startCoordinates.x - currentCoordinates.x) > 20 ) {
5320 // user slid the text input's caret on its x axis, disable any future y scrolling
5321 self.__enableScrollY = false;
5322 self.__isSelectable = true;
5323 }
5324
5325 if ( self.__enableScrollY && Math.abs(self.startCoordinates.y - currentCoordinates.y) > 10 ) {
5326 // user scrolled the entire view on the y axis
5327 // disabled being able to select text on an input
5328 // hide the input which has focus, and show a cloned one that doesn't have focus
5329 self.__isSelectable = false;
5330 ionic.tap.cloneFocusedInput(container, self);
5331 }
5332 }
5333
5334 self.doTouchMove(getEventTouches(e), e.timeStamp, e.scale);
5335 self.__isDown = true;
5336 };
5337
5338 self.touchMoveBubble = function(e) {
5339 if(self.__isDown && self.options.preventDefault) {
5340 e.preventDefault();
5341 }
5342 };
5343
5344 self.touchEnd = function(e) {
5345 if (!self.__isDown) return;
5346
5347 self.doTouchEnd(e, e.timeStamp);
5348 self.__isDown = false;
5349 self.__hasStarted = false;
5350 self.__isSelectable = true;
5351 self.__enableScrollY = true;
5352
5353 if ( !self.__isDragging && !self.__isDecelerating && !self.__isAnimating ) {
5354 ionic.tap.removeClonedInputs(container, self);
5355 }
5356 };
5357
5358 self.mouseWheel = ionic.animationFrameThrottle(function(e) {
5359 var scrollParent = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'ionic-scroll');
5360 if (!self.options.freeze && scrollParent === self.__container) {
5361
5362 self.hintResize();
5363 self.scrollBy(
5364 (e.wheelDeltaX || e.deltaX || 0) / self.options.wheelDampen,
5365 (-e.wheelDeltaY || e.deltaY || 0) / self.options.wheelDampen
5366 );
5367
5368 self.__fadeScrollbars('in');
5369 clearTimeout(self.__wheelHideBarTimeout);
5370 self.__wheelHideBarTimeout = setTimeout(function() {
5371 self.__fadeScrollbars('out');
5372 }, 100);
5373 }
5374 });
5375
5376 if ('ontouchstart' in window) {
5377 // Touch Events
5378 container.addEventListener("touchstart", self.touchStart, false);
5379 if(self.options.preventDefault) container.addEventListener("touchmove", self.touchMoveBubble, false);
5380 document.addEventListener("touchmove", self.touchMove, false);
5381 document.addEventListener("touchend", self.touchEnd, false);
5382 document.addEventListener("touchcancel", self.touchEnd, false);
5383 document.addEventListener("wheel", self.mouseWheel, false);
5384
5385 } else if (window.navigator.pointerEnabled) {
5386 // Pointer Events
5387 container.addEventListener("pointerdown", self.touchStart, false);
5388 if(self.options.preventDefault) container.addEventListener("pointermove", self.touchMoveBubble, false);
5389 document.addEventListener("pointermove", self.touchMove, false);
5390 document.addEventListener("pointerup", self.touchEnd, false);
5391 document.addEventListener("pointercancel", self.touchEnd, false);
5392 document.addEventListener("wheel", self.mouseWheel, false);
5393
5394 } else if (window.navigator.msPointerEnabled) {
5395 // IE10, WP8 (Pointer Events)
5396 container.addEventListener("MSPointerDown", self.touchStart, false);
5397 if(self.options.preventDefault) container.addEventListener("MSPointerMove", self.touchMoveBubble, false);
5398 document.addEventListener("MSPointerMove", self.touchMove, false);
5399 document.addEventListener("MSPointerUp", self.touchEnd, false);
5400 document.addEventListener("MSPointerCancel", self.touchEnd, false);
5401 document.addEventListener("wheel", self.mouseWheel, false);
5402
5403 } else {
5404 // Mouse Events
5405 var mousedown = false;
5406
5407 self.mouseDown = function(e) {
5408 if ( ionic.tap.ignoreScrollStart(e) || e.target.tagName === 'SELECT' ) {
5409 return;
5410 }
5411 self.doTouchStart(getEventTouches(e), e.timeStamp);
5412
5413 if ( !ionic.tap.isTextInput(e.target) ) {
5414 e.preventDefault();
5415 }
5416 mousedown = true;
5417 };
5418
5419 self.mouseMove = function(e) {
5420 if (self.options.freeze || !mousedown || (!mousedown && e.defaultPrevented)) {
5421 return;
5422 }
5423
5424 self.doTouchMove(getEventTouches(e), e.timeStamp);
5425
5426 mousedown = true;
5427 };
5428
5429 self.mouseMoveBubble = function(e) {
5430 if (mousedown && self.options.preventDefault) {
5431 e.preventDefault();
5432 }
5433 };
5434
5435 self.mouseUp = function(e) {
5436 if (!mousedown) {
5437 return;
5438 }
5439
5440 self.doTouchEnd(e, e.timeStamp);
5441
5442 mousedown = false;
5443 };
5444
5445 container.addEventListener("mousedown", self.mouseDown, false);
5446 if(self.options.preventDefault) container.addEventListener("mousemove", self.mouseMoveBubble, false);
5447 document.addEventListener("mousemove", self.mouseMove, false);
5448 document.addEventListener("mouseup", self.mouseUp, false);
5449 document.addEventListener('mousewheel', self.mouseWheel, false);
5450 document.addEventListener('wheel', self.mouseWheel, false);
5451 }
5452 },
5453
5454 __cleanup: function() {
5455 var self = this;
5456 var container = self.__container;
5457
5458 container.removeEventListener('touchstart', self.touchStart);
5459 container.removeEventListener('touchmove', self.touchMoveBubble);
5460 document.removeEventListener('touchmove', self.touchMove);
5461 document.removeEventListener('touchend', self.touchEnd);
5462 document.removeEventListener('touchcancel', self.touchEnd);
5463
5464 container.removeEventListener("pointerdown", self.touchStart);
5465 container.removeEventListener("pointermove", self.touchMoveBubble);
5466 document.removeEventListener("pointermove", self.touchMove);
5467 document.removeEventListener("pointerup", self.touchEnd);
5468 document.removeEventListener("pointercancel", self.touchEnd);
5469
5470 container.removeEventListener("MSPointerDown", self.touchStart);
5471 container.removeEventListener("MSPointerMove", self.touchMoveBubble);
5472 document.removeEventListener("MSPointerMove", self.touchMove);
5473 document.removeEventListener("MSPointerUp", self.touchEnd);
5474 document.removeEventListener("MSPointerCancel", self.touchEnd);
5475
5476 container.removeEventListener("mousedown", self.mouseDown);
5477 container.removeEventListener("mousemove", self.mouseMoveBubble);
5478 document.removeEventListener("mousemove", self.mouseMove);
5479 document.removeEventListener("mouseup", self.mouseUp);
5480 document.removeEventListener('mousewheel', self.mouseWheel);
5481 document.removeEventListener('wheel', self.mouseWheel);
5482
5483 container.removeEventListener('scrollChildIntoView', self.scrollChildIntoView);
5484 document.removeEventListener('resetScrollView', self.resetScrollView);
5485
5486 ionic.tap.removeClonedInputs(container, self);
5487
5488 delete self.__container;
5489 delete self.__content;
5490 delete self.__indicatorX;
5491 delete self.__indicatorY;
5492 delete self.options.el;
5493
5494 self.__callback = self.scrollChildIntoView = self.resetScrollView = NOOP;
5495
5496 self.mouseMove = self.mouseDown = self.mouseUp = self.mouseWheel =
5497 self.touchStart = self.touchMove = self.touchEnd = self.touchCancel = NOOP;
5498
5499 self.resize = self.scrollTo = self.zoomTo =
5500 self.__scrollingComplete = NOOP;
5501 container = null;
5502 },
5503
5504 /** Create a scroll bar div with the given direction **/
5505 __createScrollbar: function(direction) {
5506 var bar = document.createElement('div'),
5507 indicator = document.createElement('div');
5508
5509 indicator.className = 'scroll-bar-indicator scroll-bar-fade-out';
5510
5511 if (direction == 'h') {
5512 bar.className = 'scroll-bar scroll-bar-h';
5513 } else {
5514 bar.className = 'scroll-bar scroll-bar-v';
5515 }
5516
5517 bar.appendChild(indicator);
5518 return bar;
5519 },
5520
5521 __createScrollbars: function() {
5522 var self = this;
5523 var indicatorX, indicatorY;
5524
5525 if (self.options.scrollingX) {
5526 indicatorX = {
5527 el: self.__createScrollbar('h'),
5528 sizeRatio: 1
5529 };
5530 indicatorX.indicator = indicatorX.el.children[0];
5531
5532 if (self.options.scrollbarX) {
5533 self.__container.appendChild(indicatorX.el);
5534 }
5535 self.__indicatorX = indicatorX;
5536 }
5537
5538 if (self.options.scrollingY) {
5539 indicatorY = {
5540 el: self.__createScrollbar('v'),
5541 sizeRatio: 1
5542 };
5543 indicatorY.indicator = indicatorY.el.children[0];
5544
5545 if (self.options.scrollbarY) {
5546 self.__container.appendChild(indicatorY.el);
5547 }
5548 self.__indicatorY = indicatorY;
5549 }
5550 },
5551
5552 __resizeScrollbars: function() {
5553 var self = this;
5554
5555 // Update horiz bar
5556 if (self.__indicatorX) {
5557 var width = Math.max(Math.round(self.__clientWidth * self.__clientWidth / (self.__contentWidth)), 20);
5558 if (width > self.__contentWidth) {
5559 width = 0;
5560 }
5561 if (width !== self.__indicatorX.size) {
5562 ionic.requestAnimationFrame(function(){
5563 self.__indicatorX.indicator.style.width = width + 'px';
5564 });
5565 }
5566 self.__indicatorX.size = width;
5567 self.__indicatorX.minScale = self.options.minScrollbarSizeX / width;
5568 self.__indicatorX.maxPos = self.__clientWidth - width;
5569 self.__indicatorX.sizeRatio = self.__maxScrollLeft ? self.__indicatorX.maxPos / self.__maxScrollLeft : 1;
5570 }
5571
5572 // Update vert bar
5573 if (self.__indicatorY) {
5574 var height = Math.max(Math.round(self.__clientHeight * self.__clientHeight / (self.__contentHeight)), 20);
5575 if (height > self.__contentHeight) {
5576 height = 0;
5577 }
5578 if (height !== self.__indicatorY.size) {
5579 ionic.requestAnimationFrame(function(){
5580 self.__indicatorY && (self.__indicatorY.indicator.style.height = height + 'px');
5581 });
5582 }
5583 self.__indicatorY.size = height;
5584 self.__indicatorY.minScale = self.options.minScrollbarSizeY / height;
5585 self.__indicatorY.maxPos = self.__clientHeight - height;
5586 self.__indicatorY.sizeRatio = self.__maxScrollTop ? self.__indicatorY.maxPos / self.__maxScrollTop : 1;
5587 }
5588 },
5589
5590 /**
5591 * Move and scale the scrollbars as the page scrolls.
5592 */
5593 __repositionScrollbars: function() {
5594 var self = this,
5595 heightScale, widthScale,
5596 widthDiff, heightDiff,
5597 x, y,
5598 xstop = 0, ystop = 0;
5599
5600 if (self.__indicatorX) {
5601 // Handle the X scrollbar
5602
5603 // Don't go all the way to the right if we have a vertical scrollbar as well
5604 if (self.__indicatorY) xstop = 10;
5605
5606 x = Math.round(self.__indicatorX.sizeRatio * self.__scrollLeft) || 0;
5607
5608 // The the difference between the last content X position, and our overscrolled one
5609 widthDiff = self.__scrollLeft - (self.__maxScrollLeft - xstop);
5610
5611 if (self.__scrollLeft < 0) {
5612
5613 widthScale = Math.max(self.__indicatorX.minScale,
5614 (self.__indicatorX.size - Math.abs(self.__scrollLeft)) / self.__indicatorX.size);
5615
5616 // Stay at left
5617 x = 0;
5618
5619 // Make sure scale is transformed from the left/center origin point
5620 self.__indicatorX.indicator.style[self.__transformOriginProperty] = 'left center';
5621 } else if (widthDiff > 0) {
5622
5623 widthScale = Math.max(self.__indicatorX.minScale,
5624 (self.__indicatorX.size - widthDiff) / self.__indicatorX.size);
5625
5626 // Stay at the furthest x for the scrollable viewport
5627 x = self.__indicatorX.maxPos - xstop;
5628
5629 // Make sure scale is transformed from the right/center origin point
5630 self.__indicatorX.indicator.style[self.__transformOriginProperty] = 'right center';
5631
5632 } else {
5633
5634 // Normal motion
5635 x = Math.min(self.__maxScrollLeft, Math.max(0, x));
5636 widthScale = 1;
5637
5638 }
5639
5640 var translate3dX = 'translate3d(' + x + 'px, 0, 0) scaleX(' + widthScale + ')';
5641 if (self.__indicatorX.transformProp !== translate3dX) {
5642 self.__indicatorX.indicator.style[self.__transformProperty] = translate3dX;
5643 self.__indicatorX.transformProp = translate3dX;
5644 }
5645 }
5646
5647 if (self.__indicatorY) {
5648
5649 y = Math.round(self.__indicatorY.sizeRatio * self.__scrollTop) || 0;
5650
5651 // Don't go all the way to the right if we have a vertical scrollbar as well
5652 if (self.__indicatorX) ystop = 10;
5653
5654 heightDiff = self.__scrollTop - (self.__maxScrollTop - ystop);
5655
5656 if (self.__scrollTop < 0) {
5657
5658 heightScale = Math.max(self.__indicatorY.minScale, (self.__indicatorY.size - Math.abs(self.__scrollTop)) / self.__indicatorY.size);
5659
5660 // Stay at top
5661 y = 0;
5662
5663 // Make sure scale is transformed from the center/top origin point
5664 if (self.__indicatorY.originProp !== 'center top') {
5665 self.__indicatorY.indicator.style[self.__transformOriginProperty] = 'center top';
5666 self.__indicatorY.originProp = 'center top';
5667 }
5668
5669 } else if (heightDiff > 0) {
5670
5671 heightScale = Math.max(self.__indicatorY.minScale, (self.__indicatorY.size - heightDiff) / self.__indicatorY.size);
5672
5673 // Stay at bottom of scrollable viewport
5674 y = self.__indicatorY.maxPos - ystop;
5675
5676 // Make sure scale is transformed from the center/bottom origin point
5677 if (self.__indicatorY.originProp !== 'center bottom') {
5678 self.__indicatorY.indicator.style[self.__transformOriginProperty] = 'center bottom';
5679 self.__indicatorY.originProp = 'center bottom';
5680 }
5681
5682 } else {
5683
5684 // Normal motion
5685 y = Math.min(self.__maxScrollTop, Math.max(0, y));
5686 heightScale = 1;
5687
5688 }
5689
5690 var translate3dY = 'translate3d(0,' + y + 'px, 0) scaleY(' + heightScale + ')';
5691 if (self.__indicatorY.transformProp !== translate3dY) {
5692 self.__indicatorY.indicator.style[self.__transformProperty] = translate3dY;
5693 self.__indicatorY.transformProp = translate3dY;
5694 }
5695 }
5696 },
5697
5698 __fadeScrollbars: function(direction, delay) {
5699 var self = this;
5700
5701 if (!self.options.scrollbarsFade) {
5702 return;
5703 }
5704
5705 var className = 'scroll-bar-fade-out';
5706
5707 if (self.options.scrollbarsFade === true) {
5708 clearTimeout(self.__scrollbarFadeTimeout);
5709
5710 if (direction == 'in') {
5711 if (self.__indicatorX) { self.__indicatorX.indicator.classList.remove(className); }
5712 if (self.__indicatorY) { self.__indicatorY.indicator.classList.remove(className); }
5713 } else {
5714 self.__scrollbarFadeTimeout = setTimeout(function() {
5715 if (self.__indicatorX) { self.__indicatorX.indicator.classList.add(className); }
5716 if (self.__indicatorY) { self.__indicatorY.indicator.classList.add(className); }
5717 }, delay || self.options.scrollbarFadeDelay);
5718 }
5719 }
5720 },
5721
5722 __scrollingComplete: function() {
5723 this.options.scrollingComplete();
5724 ionic.tap.removeClonedInputs(this.__container, this);
5725 this.__fadeScrollbars('out');
5726 },
5727
5728 resize: function(continueScrolling) {
5729 var self = this;
5730 if (!self.__container || !self.options) return;
5731
5732 // Update Scroller dimensions for changed content
5733 // Add padding to bottom of content
5734 self.setDimensions(
5735 self.__container.clientWidth,
5736 self.__container.clientHeight,
5737 self.options.getContentWidth(),
5738 self.options.getContentHeight(),
5739 continueScrolling
5740 );
5741 },
5742 /*
5743 ---------------------------------------------------------------------------
5744 PUBLIC API
5745 ---------------------------------------------------------------------------
5746 */
5747
5748 getRenderFn: function() {
5749 var self = this;
5750
5751 var content = self.__content;
5752
5753 var docStyle = document.documentElement.style;
5754
5755 var engine;
5756 if ('MozAppearance' in docStyle) {
5757 engine = 'gecko';
5758 } else if ('WebkitAppearance' in docStyle) {
5759 engine = 'webkit';
5760 } else if (typeof navigator.cpuClass === 'string') {
5761 engine = 'trident';
5762 }
5763
5764 var vendorPrefix = {
5765 trident: 'ms',
5766 gecko: 'Moz',
5767 webkit: 'Webkit',
5768 presto: 'O'
5769 }[engine];
5770
5771 var helperElem = document.createElement("div");
5772 var undef;
5773
5774 var perspectiveProperty = vendorPrefix + "Perspective";
5775 var transformProperty = vendorPrefix + "Transform";
5776 var transformOriginProperty = vendorPrefix + 'TransformOrigin';
5777
5778 self.__perspectiveProperty = transformProperty;
5779 self.__transformProperty = transformProperty;
5780 self.__transformOriginProperty = transformOriginProperty;
5781
5782 if (helperElem.style[perspectiveProperty] !== undef) {
5783
5784 return function(left, top, zoom, wasResize) {
5785 var translate3d = 'translate3d(' + (-left) + 'px,' + (-top) + 'px,0) scale(' + zoom + ')';
5786 if (translate3d !== self.contentTransform) {
5787 content.style[transformProperty] = translate3d;
5788 self.contentTransform = translate3d;
5789 }
5790 self.__repositionScrollbars();
5791 if (!wasResize) {
5792 self.triggerScrollEvent();
5793 }
5794 };
5795
5796 } else if (helperElem.style[transformProperty] !== undef) {
5797
5798 return function(left, top, zoom, wasResize) {
5799 content.style[transformProperty] = 'translate(' + (-left) + 'px,' + (-top) + 'px) scale(' + zoom + ')';
5800 self.__repositionScrollbars();
5801 if (!wasResize) {
5802 self.triggerScrollEvent();
5803 }
5804 };
5805
5806 } else {
5807
5808 return function(left, top, zoom, wasResize) {
5809 content.style.marginLeft = left ? (-left / zoom) + 'px' : '';
5810 content.style.marginTop = top ? (-top / zoom) + 'px' : '';
5811 content.style.zoom = zoom || '';
5812 self.__repositionScrollbars();
5813 if (!wasResize) {
5814 self.triggerScrollEvent();
5815 }
5816 };
5817
5818 }
5819 },
5820
5821
5822 /**
5823 * Configures the dimensions of the client (outer) and content (inner) elements.
5824 * Requires the available space for the outer element and the outer size of the inner element.
5825 * All values which are falsy (null or zero etc.) are ignored and the old value is kept.
5826 *
5827 * @param clientWidth {Integer} Inner width of outer element
5828 * @param clientHeight {Integer} Inner height of outer element
5829 * @param contentWidth {Integer} Outer width of inner element
5830 * @param contentHeight {Integer} Outer height of inner element
5831 */
5832 setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight, continueScrolling) {
5833 var self = this;
5834
5835 if (!clientWidth && !clientHeight && !contentWidth && !contentHeight) {
5836 // this scrollview isn't rendered, don't bother
5837 return;
5838 }
5839
5840 // Only update values which are defined
5841 if (clientWidth === +clientWidth) {
5842 self.__clientWidth = clientWidth;
5843 }
5844
5845 if (clientHeight === +clientHeight) {
5846 self.__clientHeight = clientHeight;
5847 }
5848
5849 if (contentWidth === +contentWidth) {
5850 self.__contentWidth = contentWidth;
5851 }
5852
5853 if (contentHeight === +contentHeight) {
5854 self.__contentHeight = contentHeight;
5855 }
5856
5857 // Refresh maximums
5858 self.__computeScrollMax();
5859 self.__resizeScrollbars();
5860
5861 // Refresh scroll position
5862 if (!continueScrolling) {
5863 self.scrollTo(self.__scrollLeft, self.__scrollTop, true, null, true);
5864 }
5865
5866 },
5867
5868
5869 /**
5870 * Sets the client coordinates in relation to the document.
5871 *
5872 * @param left {Integer} Left position of outer element
5873 * @param top {Integer} Top position of outer element
5874 */
5875 setPosition: function(left, top) {
5876 this.__clientLeft = left || 0;
5877 this.__clientTop = top || 0;
5878 },
5879
5880
5881 /**
5882 * Configures the snapping (when snapping is active)
5883 *
5884 * @param width {Integer} Snapping width
5885 * @param height {Integer} Snapping height
5886 */
5887 setSnapSize: function(width, height) {
5888 this.__snapWidth = width;
5889 this.__snapHeight = height;
5890 },
5891
5892
5893 /**
5894 * Activates pull-to-refresh. A special zone on the top of the list to start a list refresh whenever
5895 * the user event is released during visibility of this zone. This was introduced by some apps on iOS like
5896 * the official Twitter client.
5897 *
5898 * @param height {Integer} Height of pull-to-refresh zone on top of rendered list
5899 * @param activateCallback {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release.
5900 * @param deactivateCallback {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled.
5901 * @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh.
5902 * @param showCallback {Function} Callback to execute when the refresher should be shown. This is for showing the refresher during a negative scrollTop.
5903 * @param hideCallback {Function} Callback to execute when the refresher should be hidden. This is for hiding the refresher when it's behind the nav bar.
5904 * @param tailCallback {Function} Callback to execute just before the refresher returns to it's original state. This is for zooming out the refresher.
5905 * @param pullProgressCallback Callback to state the progress while pulling to refresh
5906 */
5907 activatePullToRefresh: function(height, refresherMethods) {
5908 var self = this;
5909
5910 self.__refreshHeight = height;
5911 self.__refreshActivate = function() { ionic.requestAnimationFrame(refresherMethods.activate); };
5912 self.__refreshDeactivate = function() { ionic.requestAnimationFrame(refresherMethods.deactivate); };
5913 self.__refreshStart = function() { ionic.requestAnimationFrame(refresherMethods.start); };
5914 self.__refreshShow = function() { ionic.requestAnimationFrame(refresherMethods.show); };
5915 self.__refreshHide = function() { ionic.requestAnimationFrame(refresherMethods.hide); };
5916 self.__refreshTail = function() { ionic.requestAnimationFrame(refresherMethods.tail); };
5917 self.__refreshTailTime = 100;
5918 self.__minSpinTime = 600;
5919 },
5920
5921
5922 /**
5923 * Starts pull-to-refresh manually.
5924 */
5925 triggerPullToRefresh: function() {
5926 // Use publish instead of scrollTo to allow scrolling to out of boundary position
5927 // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled
5928 this.__publish(this.__scrollLeft, -this.__refreshHeight, this.__zoomLevel, true);
5929
5930 var d = new Date();
5931 this.refreshStartTime = d.getTime();
5932
5933 if (this.__refreshStart) {
5934 this.__refreshStart();
5935 }
5936 },
5937
5938
5939 /**
5940 * Signalizes that pull-to-refresh is finished.
5941 */
5942 finishPullToRefresh: function() {
5943 var self = this;
5944 // delay to make sure the spinner has a chance to spin for a split second before it's dismissed
5945 var d = new Date();
5946 var delay = 0;
5947 if (self.refreshStartTime + self.__minSpinTime > d.getTime()) {
5948 delay = self.refreshStartTime + self.__minSpinTime - d.getTime();
5949 }
5950 setTimeout(function() {
5951 if (self.__refreshTail) {
5952 self.__refreshTail();
5953 }
5954 setTimeout(function() {
5955 self.__refreshActive = false;
5956 if (self.__refreshDeactivate) {
5957 self.__refreshDeactivate();
5958 }
5959 if (self.__refreshHide) {
5960 self.__refreshHide();
5961 }
5962
5963 self.scrollTo(self.__scrollLeft, self.__scrollTop, true);
5964 }, self.__refreshTailTime);
5965 }, delay);
5966 },
5967
5968
5969 /**
5970 * Returns the scroll position and zooming values
5971 *
5972 * @return {Map} `left` and `top` scroll position and `zoom` level
5973 */
5974 getValues: function() {
5975 return {
5976 left: this.__scrollLeft,
5977 top: this.__scrollTop,
5978 zoom: this.__zoomLevel
5979 };
5980 },
5981
5982
5983 /**
5984 * Returns the maximum scroll values
5985 *
5986 * @return {Map} `left` and `top` maximum scroll values
5987 */
5988 getScrollMax: function() {
5989 return {
5990 left: this.__maxScrollLeft,
5991 top: this.__maxScrollTop
5992 };
5993 },
5994
5995
5996 /**
5997 * Zooms to the given level. Supports optional animation. Zooms
5998 * the center when no coordinates are given.
5999 *
6000 * @param level {Number} Level to zoom to
6001 * @param animate {Boolean} Whether to use animation
6002 * @param originLeft {Number} Zoom in at given left coordinate
6003 * @param originTop {Number} Zoom in at given top coordinate
6004 */
6005 zoomTo: function(level, animate, originLeft, originTop) {
6006 var self = this;
6007
6008 if (!self.options.zooming) {
6009 throw new Error("Zooming is not enabled!");
6010 }
6011
6012 // Stop deceleration
6013 if (self.__isDecelerating) {
6014 zyngaCore.effect.Animate.stop(self.__isDecelerating);
6015 self.__isDecelerating = false;
6016 }
6017
6018 var oldLevel = self.__zoomLevel;
6019
6020 // Normalize input origin to center of viewport if not defined
6021 if (originLeft == null) {
6022 originLeft = self.__clientWidth / 2;
6023 }
6024
6025 if (originTop == null) {
6026 originTop = self.__clientHeight / 2;
6027 }
6028
6029 // Limit level according to configuration
6030 level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);
6031
6032 // Recompute maximum values while temporary tweaking maximum scroll ranges
6033 self.__computeScrollMax(level);
6034
6035 // Recompute left and top coordinates based on new zoom level
6036 var left = ((originLeft + self.__scrollLeft) * level / oldLevel) - originLeft;
6037 var top = ((originTop + self.__scrollTop) * level / oldLevel) - originTop;
6038
6039 // Limit x-axis
6040 if (left > self.__maxScrollLeft) {
6041 left = self.__maxScrollLeft;
6042 } else if (left < 0) {
6043 left = 0;
6044 }
6045
6046 // Limit y-axis
6047 if (top > self.__maxScrollTop) {
6048 top = self.__maxScrollTop;
6049 } else if (top < 0) {
6050 top = 0;
6051 }
6052
6053 // Push values out
6054 self.__publish(left, top, level, animate);
6055
6056 },
6057
6058
6059 /**
6060 * Zooms the content by the given factor.
6061 *
6062 * @param factor {Number} Zoom by given factor
6063 * @param animate {Boolean} Whether to use animation
6064 * @param originLeft {Number} Zoom in at given left coordinate
6065 * @param originTop {Number} Zoom in at given top coordinate
6066 */
6067 zoomBy: function(factor, animate, originLeft, originTop) {
6068 this.zoomTo(this.__zoomLevel * factor, animate, originLeft, originTop);
6069 },
6070
6071
6072 /**
6073 * Scrolls to the given position. Respect limitations and snapping automatically.
6074 *
6075 * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
6076 * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
6077 * @param animate {Boolean} Whether the scrolling should happen using an animation
6078 * @param zoom {Number} Zoom level to go to
6079 */
6080 scrollTo: function(left, top, animate, zoom, wasResize) {
6081 var self = this;
6082
6083 // Stop deceleration
6084 if (self.__isDecelerating) {
6085 zyngaCore.effect.Animate.stop(self.__isDecelerating);
6086 self.__isDecelerating = false;
6087 }
6088
6089 // Correct coordinates based on new zoom level
6090 if (zoom != null && zoom !== self.__zoomLevel) {
6091
6092 if (!self.options.zooming) {
6093 throw new Error("Zooming is not enabled!");
6094 }
6095
6096 left *= zoom;
6097 top *= zoom;
6098
6099 // Recompute maximum values while temporary tweaking maximum scroll ranges
6100 self.__computeScrollMax(zoom);
6101
6102 } else {
6103
6104 // Keep zoom when not defined
6105 zoom = self.__zoomLevel;
6106
6107 }
6108
6109 if (!self.options.scrollingX) {
6110
6111 left = self.__scrollLeft;
6112
6113 } else {
6114
6115 if (self.options.paging) {
6116 left = Math.round(left / self.__clientWidth) * self.__clientWidth;
6117 } else if (self.options.snapping) {
6118 left = Math.round(left / self.__snapWidth) * self.__snapWidth;
6119 }
6120
6121 }
6122
6123 if (!self.options.scrollingY) {
6124
6125 top = self.__scrollTop;
6126
6127 } else {
6128
6129 if (self.options.paging) {
6130 top = Math.round(top / self.__clientHeight) * self.__clientHeight;
6131 } else if (self.options.snapping) {
6132 top = Math.round(top / self.__snapHeight) * self.__snapHeight;
6133 }
6134
6135 }
6136
6137 // Limit for allowed ranges
6138 left = Math.max(Math.min(self.__maxScrollLeft, left), 0);
6139 top = Math.max(Math.min(self.__maxScrollTop, top), 0);
6140
6141 // Don't animate when no change detected, still call publish to make sure
6142 // that rendered position is really in-sync with internal data
6143 if (left === self.__scrollLeft && top === self.__scrollTop) {
6144 animate = false;
6145 }
6146
6147 // Publish new values
6148 self.__publish(left, top, zoom, animate, wasResize);
6149
6150 },
6151
6152
6153 /**
6154 * Scroll by the given offset
6155 *
6156 * @param left {Number} Scroll x-axis by given offset
6157 * @param top {Number} Scroll y-axis by given offset
6158 * @param animate {Boolean} Whether to animate the given change
6159 */
6160 scrollBy: function(left, top, animate) {
6161 var self = this;
6162
6163 var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft;
6164 var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop;
6165
6166 self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate);
6167 },
6168
6169
6170
6171 /*
6172 ---------------------------------------------------------------------------
6173 EVENT CALLBACKS
6174 ---------------------------------------------------------------------------
6175 */
6176
6177 /**
6178 * Mouse wheel handler for zooming support
6179 */
6180 doMouseZoom: function(wheelDelta, timeStamp, pageX, pageY) {
6181 var change = wheelDelta > 0 ? 0.97 : 1.03;
6182 return this.zoomTo(this.__zoomLevel * change, false, pageX - this.__clientLeft, pageY - this.__clientTop);
6183 },
6184
6185 /**
6186 * Touch start handler for scrolling support
6187 */
6188 doTouchStart: function(touches, timeStamp) {
6189 var self = this;
6190
6191 // remember if the deceleration was just stopped
6192 self.__decStopped = !!(self.__isDecelerating || self.__isAnimating);
6193
6194 self.hintResize();
6195
6196 if (timeStamp instanceof Date) {
6197 timeStamp = timeStamp.valueOf();
6198 }
6199 if (typeof timeStamp !== "number") {
6200 timeStamp = Date.now();
6201 }
6202
6203 // Reset interruptedAnimation flag
6204 self.__interruptedAnimation = true;
6205
6206 // Stop deceleration
6207 if (self.__isDecelerating) {
6208 zyngaCore.effect.Animate.stop(self.__isDecelerating);
6209 self.__isDecelerating = false;
6210 self.__interruptedAnimation = true;
6211 }
6212
6213 // Stop animation
6214 if (self.__isAnimating) {
6215 zyngaCore.effect.Animate.stop(self.__isAnimating);
6216 self.__isAnimating = false;
6217 self.__interruptedAnimation = true;
6218 }
6219
6220 // Use center point when dealing with two fingers
6221 var currentTouchLeft, currentTouchTop;
6222 var isSingleTouch = touches.length === 1;
6223 if (isSingleTouch) {
6224 currentTouchLeft = touches[0].pageX;
6225 currentTouchTop = touches[0].pageY;
6226 } else {
6227 currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;
6228 currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;
6229 }
6230
6231 // Store initial positions
6232 self.__initialTouchLeft = currentTouchLeft;
6233 self.__initialTouchTop = currentTouchTop;
6234
6235 // Store initial touchList for scale calculation
6236 self.__initialTouches = touches;
6237
6238 // Store current zoom level
6239 self.__zoomLevelStart = self.__zoomLevel;
6240
6241 // Store initial touch positions
6242 self.__lastTouchLeft = currentTouchLeft;
6243 self.__lastTouchTop = currentTouchTop;
6244
6245 // Store initial move time stamp
6246 self.__lastTouchMove = timeStamp;
6247
6248 // Reset initial scale
6249 self.__lastScale = 1;
6250
6251 // Reset locking flags
6252 self.__enableScrollX = !isSingleTouch && self.options.scrollingX;
6253 self.__enableScrollY = !isSingleTouch && self.options.scrollingY;
6254
6255 // Reset tracking flag
6256 self.__isTracking = true;
6257
6258 // Reset deceleration complete flag
6259 self.__didDecelerationComplete = false;
6260
6261 // Dragging starts directly with two fingers, otherwise lazy with an offset
6262 self.__isDragging = !isSingleTouch;
6263
6264 // Some features are disabled in multi touch scenarios
6265 self.__isSingleTouch = isSingleTouch;
6266
6267 // Clearing data structure
6268 self.__positions = [];
6269
6270 },
6271
6272
6273 /**
6274 * Touch move handler for scrolling support
6275 */
6276 doTouchMove: function(touches, timeStamp, scale) {
6277 if (timeStamp instanceof Date) {
6278 timeStamp = timeStamp.valueOf();
6279 }
6280 if (typeof timeStamp !== "number") {
6281 timeStamp = Date.now();
6282 }
6283
6284 var self = this;
6285
6286 // Ignore event when tracking is not enabled (event might be outside of element)
6287 if (!self.__isTracking) {
6288 return;
6289 }
6290
6291 var currentTouchLeft, currentTouchTop;
6292
6293 // Compute move based around of center of fingers
6294 if (touches.length === 2) {
6295 currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;
6296 currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;
6297
6298 // Calculate scale when not present and only when touches are used
6299 if (!scale && self.options.zooming) {
6300 scale = self.__getScale(self.__initialTouches, touches);
6301 }
6302 } else {
6303 currentTouchLeft = touches[0].pageX;
6304 currentTouchTop = touches[0].pageY;
6305 }
6306
6307 var positions = self.__positions;
6308
6309 // Are we already is dragging mode?
6310 if (self.__isDragging) {
6311 self.__decStopped = false;
6312
6313 // Compute move distance
6314 var moveX = currentTouchLeft - self.__lastTouchLeft;
6315 var moveY = currentTouchTop - self.__lastTouchTop;
6316
6317 // Read previous scroll position and zooming
6318 var scrollLeft = self.__scrollLeft;
6319 var scrollTop = self.__scrollTop;
6320 var level = self.__zoomLevel;
6321
6322 // Work with scaling
6323 if (scale != null && self.options.zooming) {
6324
6325 var oldLevel = level;
6326
6327 // Recompute level based on previous scale and new scale
6328 level = level / self.__lastScale * scale;
6329
6330 // Limit level according to configuration
6331 level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);
6332
6333 // Only do further compution when change happened
6334 if (oldLevel !== level) {
6335
6336 // Compute relative event position to container
6337 var currentTouchLeftRel = currentTouchLeft - self.__clientLeft;
6338 var currentTouchTopRel = currentTouchTop - self.__clientTop;
6339
6340 // Recompute left and top coordinates based on new zoom level
6341 scrollLeft = ((currentTouchLeftRel + scrollLeft) * level / oldLevel) - currentTouchLeftRel;
6342 scrollTop = ((currentTouchTopRel + scrollTop) * level / oldLevel) - currentTouchTopRel;
6343
6344 // Recompute max scroll values
6345 self.__computeScrollMax(level);
6346
6347 }
6348 }
6349
6350 if (self.__enableScrollX) {
6351
6352 scrollLeft -= moveX * self.options.speedMultiplier;
6353 var maxScrollLeft = self.__maxScrollLeft;
6354
6355 if (scrollLeft > maxScrollLeft || scrollLeft < 0) {
6356
6357 // Slow down on the edges
6358 if (self.options.bouncing) {
6359
6360 scrollLeft += (moveX / 2 * self.options.speedMultiplier);
6361
6362 } else if (scrollLeft > maxScrollLeft) {
6363
6364 scrollLeft = maxScrollLeft;
6365
6366 } else {
6367
6368 scrollLeft = 0;
6369
6370 }
6371 }
6372 }
6373
6374 // Compute new vertical scroll position
6375 if (self.__enableScrollY) {
6376
6377 scrollTop -= moveY * self.options.speedMultiplier;
6378 var maxScrollTop = self.__maxScrollTop;
6379
6380 if (scrollTop > maxScrollTop || scrollTop < 0) {
6381
6382 // Slow down on the edges
6383 if (self.options.bouncing || (self.__refreshHeight && scrollTop < 0)) {
6384
6385 scrollTop += (moveY / 2 * self.options.speedMultiplier);
6386
6387 // Support pull-to-refresh (only when only y is scrollable)
6388 if (!self.__enableScrollX && self.__refreshHeight != null) {
6389
6390 // hide the refresher when it's behind the header bar in case of header transparency
6391 if (scrollTop < 0) {
6392 self.__refreshHidden = false;
6393 self.__refreshShow();
6394 } else {
6395 self.__refreshHide();
6396 self.__refreshHidden = true;
6397 }
6398
6399 if (!self.__refreshActive && scrollTop <= -self.__refreshHeight) {
6400
6401 self.__refreshActive = true;
6402 if (self.__refreshActivate) {
6403 self.__refreshActivate();
6404 }
6405
6406 } else if (self.__refreshActive && scrollTop > -self.__refreshHeight) {
6407
6408 self.__refreshActive = false;
6409 if (self.__refreshDeactivate) {
6410 self.__refreshDeactivate();
6411 }
6412
6413 }
6414 }
6415
6416 } else if (scrollTop > maxScrollTop) {
6417
6418 scrollTop = maxScrollTop;
6419
6420 } else {
6421
6422 scrollTop = 0;
6423
6424 }
6425 } else if (self.__refreshHeight && !self.__refreshHidden) {
6426 // if a positive scroll value and the refresher is still not hidden, hide it
6427 self.__refreshHide();
6428 self.__refreshHidden = true;
6429 }
6430 }
6431
6432 // Keep list from growing infinitely (holding min 10, max 20 measure points)
6433 if (positions.length > 60) {
6434 positions.splice(0, 30);
6435 }
6436
6437 // Track scroll movement for decleration
6438 positions.push(scrollLeft, scrollTop, timeStamp);
6439
6440 // Sync scroll position
6441 self.__publish(scrollLeft, scrollTop, level);
6442
6443 // Otherwise figure out whether we are switching into dragging mode now.
6444 } else {
6445
6446 var minimumTrackingForScroll = self.options.locking ? 3 : 0;
6447 var minimumTrackingForDrag = 5;
6448
6449 var distanceX = Math.abs(currentTouchLeft - self.__initialTouchLeft);
6450 var distanceY = Math.abs(currentTouchTop - self.__initialTouchTop);
6451
6452 self.__enableScrollX = self.options.scrollingX && distanceX >= minimumTrackingForScroll;
6453 self.__enableScrollY = self.options.scrollingY && distanceY >= minimumTrackingForScroll;
6454
6455 positions.push(self.__scrollLeft, self.__scrollTop, timeStamp);
6456
6457 self.__isDragging = (self.__enableScrollX || self.__enableScrollY) && (distanceX >= minimumTrackingForDrag || distanceY >= minimumTrackingForDrag);
6458 if (self.__isDragging) {
6459 self.__interruptedAnimation = false;
6460 self.__fadeScrollbars('in');
6461 }
6462
6463 }
6464
6465 // Update last touch positions and time stamp for next event
6466 self.__lastTouchLeft = currentTouchLeft;
6467 self.__lastTouchTop = currentTouchTop;
6468 self.__lastTouchMove = timeStamp;
6469 self.__lastScale = scale;
6470
6471 },
6472
6473
6474 /**
6475 * Touch end handler for scrolling support
6476 */
6477 doTouchEnd: function(e, timeStamp) {
6478 if (timeStamp instanceof Date) {
6479 timeStamp = timeStamp.valueOf();
6480 }
6481 if (typeof timeStamp !== "number") {
6482 timeStamp = Date.now();
6483 }
6484
6485 var self = this;
6486
6487 // Ignore event when tracking is not enabled (no touchstart event on element)
6488 // This is required as this listener ('touchmove') sits on the document and not on the element itself.
6489 if (!self.__isTracking) {
6490 return;
6491 }
6492
6493 // Not touching anymore (when two finger hit the screen there are two touch end events)
6494 self.__isTracking = false;
6495
6496 // Be sure to reset the dragging flag now. Here we also detect whether
6497 // the finger has moved fast enough to switch into a deceleration animation.
6498 if (self.__isDragging) {
6499
6500 // Reset dragging flag
6501 self.__isDragging = false;
6502
6503 // Start deceleration
6504 // Verify that the last move detected was in some relevant time frame
6505 if (self.__isSingleTouch && self.options.animating && (timeStamp - self.__lastTouchMove) <= 100) {
6506
6507 // Then figure out what the scroll position was about 100ms ago
6508 var positions = self.__positions;
6509 var endPos = positions.length - 1;
6510 var startPos = endPos;
6511
6512 // Move pointer to position measured 100ms ago
6513 for (var i = endPos; i > 0 && positions[i] > (self.__lastTouchMove - 100); i -= 3) {
6514 startPos = i;
6515 }
6516
6517 // If start and stop position is identical in a 100ms timeframe,
6518 // we cannot compute any useful deceleration.
6519 if (startPos !== endPos) {
6520
6521 // Compute relative movement between these two points
6522 var timeOffset = positions[endPos] - positions[startPos];
6523 var movedLeft = self.__scrollLeft - positions[startPos - 2];
6524 var movedTop = self.__scrollTop - positions[startPos - 1];
6525
6526 // Based on 50ms compute the movement to apply for each render step
6527 self.__decelerationVelocityX = movedLeft / timeOffset * (1000 / 60);
6528 self.__decelerationVelocityY = movedTop / timeOffset * (1000 / 60);
6529
6530 // How much velocity is required to start the deceleration
6531 var minVelocityToStartDeceleration = self.options.paging || self.options.snapping ? self.options.decelVelocityThresholdPaging : self.options.decelVelocityThreshold;
6532
6533 // Verify that we have enough velocity to start deceleration
6534 if (Math.abs(self.__decelerationVelocityX) > minVelocityToStartDeceleration || Math.abs(self.__decelerationVelocityY) > minVelocityToStartDeceleration) {
6535
6536 // Deactivate pull-to-refresh when decelerating
6537 if (!self.__refreshActive) {
6538 self.__startDeceleration(timeStamp);
6539 }
6540 }
6541 } else {
6542 self.__scrollingComplete();
6543 }
6544 } else if ((timeStamp - self.__lastTouchMove) > 100) {
6545 self.__scrollingComplete();
6546 }
6547
6548 } else if (self.__decStopped) {
6549 // the deceleration was stopped
6550 // user flicked the scroll fast, and stop dragging, then did a touchstart to stop the srolling
6551 // tell the touchend event code to do nothing, we don't want to actually send a click
6552 e.isTapHandled = true;
6553 self.__decStopped = false;
6554 }
6555
6556 // If this was a slower move it is per default non decelerated, but this
6557 // still means that we want snap back to the bounds which is done here.
6558 // This is placed outside the condition above to improve edge case stability
6559 // e.g. touchend fired without enabled dragging. This should normally do not
6560 // have modified the scroll positions or even showed the scrollbars though.
6561 if (!self.__isDecelerating) {
6562
6563 if (self.__refreshActive && self.__refreshStart) {
6564
6565 // Use publish instead of scrollTo to allow scrolling to out of boundary position
6566 // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled
6567 self.__publish(self.__scrollLeft, -self.__refreshHeight, self.__zoomLevel, true);
6568
6569 var d = new Date();
6570 self.refreshStartTime = d.getTime();
6571
6572 if (self.__refreshStart) {
6573 self.__refreshStart();
6574 }
6575 // for iOS-ey style scrolling
6576 if (!ionic.Platform.isAndroid())self.__startDeceleration();
6577 } else {
6578
6579 if (self.__interruptedAnimation || self.__isDragging) {
6580 self.__scrollingComplete();
6581 }
6582 self.scrollTo(self.__scrollLeft, self.__scrollTop, true, self.__zoomLevel);
6583
6584 // Directly signalize deactivation (nothing todo on refresh?)
6585 if (self.__refreshActive) {
6586
6587 self.__refreshActive = false;
6588 if (self.__refreshDeactivate) {
6589 self.__refreshDeactivate();
6590 }
6591
6592 }
6593 }
6594 }
6595
6596 // Fully cleanup list
6597 self.__positions.length = 0;
6598
6599 },
6600
6601
6602
6603 /*
6604 ---------------------------------------------------------------------------
6605 PRIVATE API
6606 ---------------------------------------------------------------------------
6607 */
6608
6609 /**
6610 * Applies the scroll position to the content element
6611 *
6612 * @param left {Number} Left scroll position
6613 * @param top {Number} Top scroll position
6614 * @param animate {Boolean} Whether animation should be used to move to the new coordinates
6615 */
6616 __publish: function(left, top, zoom, animate, wasResize) {
6617
6618 var self = this;
6619
6620 // Remember whether we had an animation, then we try to continue based on the current "drive" of the animation
6621 var wasAnimating = self.__isAnimating;
6622 if (wasAnimating) {
6623 zyngaCore.effect.Animate.stop(wasAnimating);
6624 self.__isAnimating = false;
6625 }
6626
6627 if (animate && self.options.animating) {
6628
6629 // Keep scheduled positions for scrollBy/zoomBy functionality
6630 self.__scheduledLeft = left;
6631 self.__scheduledTop = top;
6632 self.__scheduledZoom = zoom;
6633
6634 var oldLeft = self.__scrollLeft;
6635 var oldTop = self.__scrollTop;
6636 var oldZoom = self.__zoomLevel;
6637
6638 var diffLeft = left - oldLeft;
6639 var diffTop = top - oldTop;
6640 var diffZoom = zoom - oldZoom;
6641
6642 var step = function(percent, now, render) {
6643
6644 if (render) {
6645
6646 self.__scrollLeft = oldLeft + (diffLeft * percent);
6647 self.__scrollTop = oldTop + (diffTop * percent);
6648 self.__zoomLevel = oldZoom + (diffZoom * percent);
6649
6650 // Push values out
6651 if (self.__callback) {
6652 self.__callback(self.__scrollLeft, self.__scrollTop, self.__zoomLevel, wasResize);
6653 }
6654
6655 }
6656 };
6657
6658 var verify = function(id) {
6659 return self.__isAnimating === id;
6660 };
6661
6662 var completed = function(renderedFramesPerSecond, animationId, wasFinished) {
6663 if (animationId === self.__isAnimating) {
6664 self.__isAnimating = false;
6665 }
6666 if (self.__didDecelerationComplete || wasFinished) {
6667 self.__scrollingComplete();
6668 }
6669
6670 if (self.options.zooming) {
6671 self.__computeScrollMax();
6672 }
6673 };
6674
6675 // When continuing based on previous animation we choose an ease-out animation instead of ease-in-out
6676 self.__isAnimating = zyngaCore.effect.Animate.start(step, verify, completed, self.options.animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic);
6677
6678 } else {
6679
6680 self.__scheduledLeft = self.__scrollLeft = left;
6681 self.__scheduledTop = self.__scrollTop = top;
6682 self.__scheduledZoom = self.__zoomLevel = zoom;
6683
6684 // Push values out
6685 if (self.__callback) {
6686 self.__callback(left, top, zoom, wasResize);
6687 }
6688
6689 // Fix max scroll ranges
6690 if (self.options.zooming) {
6691 self.__computeScrollMax();
6692 }
6693 }
6694 },
6695
6696
6697 /**
6698 * Recomputes scroll minimum values based on client dimensions and content dimensions.
6699 */
6700 __computeScrollMax: function(zoomLevel) {
6701 var self = this;
6702
6703 if (zoomLevel == null) {
6704 zoomLevel = self.__zoomLevel;
6705 }
6706
6707 self.__maxScrollLeft = Math.max((self.__contentWidth * zoomLevel) - self.__clientWidth, 0);
6708 self.__maxScrollTop = Math.max((self.__contentHeight * zoomLevel) - self.__clientHeight, 0);
6709
6710 if (!self.__didWaitForSize && !self.__maxScrollLeft && !self.__maxScrollTop) {
6711 self.__didWaitForSize = true;
6712 self.__waitForSize();
6713 }
6714 },
6715
6716
6717 /**
6718 * If the scroll view isn't sized correctly on start, wait until we have at least some size
6719 */
6720 __waitForSize: function() {
6721 var self = this;
6722
6723 clearTimeout(self.__sizerTimeout);
6724
6725 var sizer = function() {
6726 self.resize(true);
6727 };
6728
6729 sizer();
6730 self.__sizerTimeout = setTimeout(sizer, 500);
6731 },
6732
6733 /*
6734 ---------------------------------------------------------------------------
6735 ANIMATION (DECELERATION) SUPPORT
6736 ---------------------------------------------------------------------------
6737 */
6738
6739 /**
6740 * Called when a touch sequence end and the speed of the finger was high enough
6741 * to switch into deceleration mode.
6742 */
6743 __startDeceleration: function() {
6744 var self = this;
6745
6746 if (self.options.paging) {
6747
6748 var scrollLeft = Math.max(Math.min(self.__scrollLeft, self.__maxScrollLeft), 0);
6749 var scrollTop = Math.max(Math.min(self.__scrollTop, self.__maxScrollTop), 0);
6750 var clientWidth = self.__clientWidth;
6751 var clientHeight = self.__clientHeight;
6752
6753 // We limit deceleration not to the min/max values of the allowed range, but to the size of the visible client area.
6754 // Each page should have exactly the size of the client area.
6755 self.__minDecelerationScrollLeft = Math.floor(scrollLeft / clientWidth) * clientWidth;
6756 self.__minDecelerationScrollTop = Math.floor(scrollTop / clientHeight) * clientHeight;
6757 self.__maxDecelerationScrollLeft = Math.ceil(scrollLeft / clientWidth) * clientWidth;
6758 self.__maxDecelerationScrollTop = Math.ceil(scrollTop / clientHeight) * clientHeight;
6759
6760 } else {
6761
6762 self.__minDecelerationScrollLeft = 0;
6763 self.__minDecelerationScrollTop = 0;
6764 self.__maxDecelerationScrollLeft = self.__maxScrollLeft;
6765 self.__maxDecelerationScrollTop = self.__maxScrollTop;
6766 if (self.__refreshActive) self.__minDecelerationScrollTop = self.__refreshHeight * -1;
6767 }
6768
6769 // Wrap class method
6770 var step = function(percent, now, render) {
6771 self.__stepThroughDeceleration(render);
6772 };
6773
6774 // How much velocity is required to keep the deceleration running
6775 self.__minVelocityToKeepDecelerating = self.options.snapping ? 4 : 0.1;
6776
6777 // Detect whether it's still worth to continue animating steps
6778 // If we are already slow enough to not being user perceivable anymore, we stop the whole process here.
6779 var verify = function() {
6780 var shouldContinue = Math.abs(self.__decelerationVelocityX) >= self.__minVelocityToKeepDecelerating ||
6781 Math.abs(self.__decelerationVelocityY) >= self.__minVelocityToKeepDecelerating;
6782 if (!shouldContinue) {
6783 self.__didDecelerationComplete = true;
6784
6785 //Make sure the scroll values are within the boundaries after a bounce,
6786 //not below 0 or above maximum
6787 if (self.options.bouncing && !self.__refreshActive) {
6788 self.scrollTo(
6789 Math.min( Math.max(self.__scrollLeft, 0), self.__maxScrollLeft ),
6790 Math.min( Math.max(self.__scrollTop, 0), self.__maxScrollTop ),
6791 self.__refreshActive
6792 );
6793 }
6794 }
6795 return shouldContinue;
6796 };
6797
6798 var completed = function() {
6799 self.__isDecelerating = false;
6800 if (self.__didDecelerationComplete) {
6801 self.__scrollingComplete();
6802 }
6803
6804 // Animate to grid when snapping is active, otherwise just fix out-of-boundary positions
6805 if (self.options.paging) {
6806 self.scrollTo(self.__scrollLeft, self.__scrollTop, self.options.snapping);
6807 }
6808 };
6809
6810 // Start animation and switch on flag
6811 self.__isDecelerating = zyngaCore.effect.Animate.start(step, verify, completed);
6812
6813 },
6814
6815
6816 /**
6817 * Called on every step of the animation
6818 *
6819 * @param inMemory {Boolean} Whether to not render the current step, but keep it in memory only. Used internally only!
6820 */
6821 __stepThroughDeceleration: function(render) {
6822 var self = this;
6823
6824
6825 //
6826 // COMPUTE NEXT SCROLL POSITION
6827 //
6828
6829 // Add deceleration to scroll position
6830 var scrollLeft = self.__scrollLeft + self.__decelerationVelocityX;// * self.options.deceleration);
6831 var scrollTop = self.__scrollTop + self.__decelerationVelocityY;// * self.options.deceleration);
6832
6833
6834 //
6835 // HARD LIMIT SCROLL POSITION FOR NON BOUNCING MODE
6836 //
6837
6838 if (!self.options.bouncing) {
6839
6840 var scrollLeftFixed = Math.max(Math.min(self.__maxDecelerationScrollLeft, scrollLeft), self.__minDecelerationScrollLeft);
6841 if (scrollLeftFixed !== scrollLeft) {
6842 scrollLeft = scrollLeftFixed;
6843 self.__decelerationVelocityX = 0;
6844 }
6845
6846 var scrollTopFixed = Math.max(Math.min(self.__maxDecelerationScrollTop, scrollTop), self.__minDecelerationScrollTop);
6847 if (scrollTopFixed !== scrollTop) {
6848 scrollTop = scrollTopFixed;
6849 self.__decelerationVelocityY = 0;
6850 }
6851
6852 }
6853
6854
6855 //
6856 // UPDATE SCROLL POSITION
6857 //
6858
6859 if (render) {
6860
6861 self.__publish(scrollLeft, scrollTop, self.__zoomLevel);
6862
6863 } else {
6864
6865 self.__scrollLeft = scrollLeft;
6866 self.__scrollTop = scrollTop;
6867
6868 }
6869
6870
6871 //
6872 // SLOW DOWN
6873 //
6874
6875 // Slow down velocity on every iteration
6876 if (!self.options.paging) {
6877
6878 // This is the factor applied to every iteration of the animation
6879 // to slow down the process. This should emulate natural behavior where
6880 // objects slow down when the initiator of the movement is removed
6881 var frictionFactor = self.options.deceleration;
6882
6883 self.__decelerationVelocityX *= frictionFactor;
6884 self.__decelerationVelocityY *= frictionFactor;
6885
6886 }
6887
6888
6889 //
6890 // BOUNCING SUPPORT
6891 //
6892
6893 if (self.options.bouncing) {
6894
6895 var scrollOutsideX = 0;
6896 var scrollOutsideY = 0;
6897
6898 // This configures the amount of change applied to deceleration/acceleration when reaching boundaries
6899 var penetrationDeceleration = self.options.penetrationDeceleration;
6900 var penetrationAcceleration = self.options.penetrationAcceleration;
6901
6902 // Check limits
6903 if (scrollLeft < self.__minDecelerationScrollLeft) {
6904 scrollOutsideX = self.__minDecelerationScrollLeft - scrollLeft;
6905 } else if (scrollLeft > self.__maxDecelerationScrollLeft) {
6906 scrollOutsideX = self.__maxDecelerationScrollLeft - scrollLeft;
6907 }
6908
6909 if (scrollTop < self.__minDecelerationScrollTop) {
6910 scrollOutsideY = self.__minDecelerationScrollTop - scrollTop;
6911 } else if (scrollTop > self.__maxDecelerationScrollTop) {
6912 scrollOutsideY = self.__maxDecelerationScrollTop - scrollTop;
6913 }
6914
6915 // Slow down until slow enough, then flip back to snap position
6916 if (scrollOutsideX !== 0) {
6917 var isHeadingOutwardsX = scrollOutsideX * self.__decelerationVelocityX <= self.__minDecelerationScrollLeft;
6918 if (isHeadingOutwardsX) {
6919 self.__decelerationVelocityX += scrollOutsideX * penetrationDeceleration;
6920 }
6921 var isStoppedX = Math.abs(self.__decelerationVelocityX) <= self.__minVelocityToKeepDecelerating;
6922 //If we're not heading outwards, or if the above statement got us below minDeceleration, go back towards bounds
6923 if (!isHeadingOutwardsX || isStoppedX) {
6924 self.__decelerationVelocityX = scrollOutsideX * penetrationAcceleration;
6925 }
6926 }
6927
6928 if (scrollOutsideY !== 0) {
6929 var isHeadingOutwardsY = scrollOutsideY * self.__decelerationVelocityY <= self.__minDecelerationScrollTop;
6930 if (isHeadingOutwardsY) {
6931 self.__decelerationVelocityY += scrollOutsideY * penetrationDeceleration;
6932 }
6933 var isStoppedY = Math.abs(self.__decelerationVelocityY) <= self.__minVelocityToKeepDecelerating;
6934 //If we're not heading outwards, or if the above statement got us below minDeceleration, go back towards bounds
6935 if (!isHeadingOutwardsY || isStoppedY) {
6936 self.__decelerationVelocityY = scrollOutsideY * penetrationAcceleration;
6937 }
6938 }
6939 }
6940 },
6941
6942
6943 /**
6944 * calculate the distance between two touches
6945 * @param {Touch} touch1
6946 * @param {Touch} touch2
6947 * @returns {Number} distance
6948 */
6949 __getDistance: function getDistance(touch1, touch2) {
6950 var x = touch2.pageX - touch1.pageX,
6951 y = touch2.pageY - touch1.pageY;
6952 return Math.sqrt((x * x) + (y * y));
6953 },
6954
6955
6956 /**
6957 * calculate the scale factor between two touchLists (fingers)
6958 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
6959 * @param {Array} start
6960 * @param {Array} end
6961 * @returns {Number} scale
6962 */
6963 __getScale: function getScale(start, end) {
6964 // need two fingers...
6965 if (start.length >= 2 && end.length >= 2) {
6966 return this.__getDistance(end[0], end[1]) /
6967 this.__getDistance(start[0], start[1]);
6968 }
6969 return 1;
6970 }
6971});
6972
6973ionic.scroll = {
6974 isScrolling: false,
6975 lastTop: 0
6976};
6977
6978})(ionic);
6979
6980(function(ionic) {
6981 var NOOP = function() {};
6982 var deprecated = function(name) {
6983 void 0;
6984 };
6985 ionic.views.ScrollNative = ionic.views.View.inherit({
6986
6987 initialize: function(options) {
6988 var self = this;
6989 self.__container = self.el = options.el;
6990 self.__content = options.el.firstElementChild;
6991 // Whether scrolling is frozen or not
6992 self.__frozen = false;
6993 self.isNative = true;
6994
6995 self.__scrollTop = self.el.scrollTop;
6996 self.__scrollLeft = self.el.scrollLeft;
6997 self.__clientHeight = self.__content.clientHeight;
6998 self.__clientWidth = self.__content.clientWidth;
6999 self.__maxScrollTop = Math.max((self.__contentHeight) - self.__clientHeight, 0);
7000 self.__maxScrollLeft = Math.max((self.__contentWidth) - self.__clientWidth, 0);
7001
7002 if(options.startY >= 0 || options.startX >= 0) {
7003 ionic.requestAnimationFrame(function() {
7004 self.el.scrollTop = options.startY || 0;
7005 self.el.scrollLeft = options.startX || 0;
7006
7007 self.__scrollTop = self.el.scrollTop;
7008 self.__scrollLeft = self.el.scrollLeft;
7009 });
7010 }
7011
7012 self.options = {
7013
7014 freeze: false,
7015
7016 getContentWidth: function() {
7017 return Math.max(self.__content.scrollWidth, self.__content.offsetWidth);
7018 },
7019
7020 getContentHeight: function() {
7021 return Math.max(self.__content.scrollHeight, self.__content.offsetHeight + (self.__content.offsetTop * 2));
7022 }
7023
7024 };
7025
7026 for (var key in options) {
7027 self.options[key] = options[key];
7028 }
7029
7030 /**
7031 * Sets isScrolling to true, and automatically deactivates if not called again in 80ms.
7032 */
7033 self.onScroll = function() {
7034 if (!ionic.scroll.isScrolling) {
7035 ionic.scroll.isScrolling = true;
7036 }
7037
7038 clearTimeout(self.scrollTimer);
7039 self.scrollTimer = setTimeout(function() {
7040 ionic.scroll.isScrolling = false;
7041 }, 80);
7042 };
7043
7044 self.freeze = function(shouldFreeze) {
7045 self.__frozen = shouldFreeze;
7046 };
7047 // A more powerful freeze pop that dominates all other freeze pops
7048 self.freezeShut = function(shouldFreezeShut) {
7049 self.__frozenShut = shouldFreezeShut;
7050 };
7051
7052 self.__initEventHandlers();
7053 },
7054
7055 /** Methods not used in native scrolling */
7056 __callback: function() { deprecated('__callback'); },
7057 zoomTo: function() { deprecated('zoomTo'); },
7058 zoomBy: function() { deprecated('zoomBy'); },
7059 activatePullToRefresh: function() { deprecated('activatePullToRefresh'); },
7060
7061 /**
7062 * Returns the scroll position and zooming values
7063 *
7064 * @return {Map} `left` and `top` scroll position and `zoom` level
7065 */
7066 resize: function(continueScrolling) {
7067 var self = this;
7068 if (!self.__container || !self.options) return;
7069
7070 // Update Scroller dimensions for changed content
7071 // Add padding to bottom of content
7072 self.setDimensions(
7073 self.__container.clientWidth,
7074 self.__container.clientHeight,
7075 self.options.getContentWidth(),
7076 self.options.getContentHeight(),
7077 continueScrolling
7078 );
7079 },
7080
7081 /**
7082 * Initialize the scrollview
7083 * In native scrolling, this only means we need to gather size information
7084 */
7085 run: function() {
7086 this.resize();
7087 },
7088
7089 /**
7090 * Returns the scroll position and zooming values
7091 *
7092 * @return {Map} `left` and `top` scroll position and `zoom` level
7093 */
7094 getValues: function() {
7095 var self = this;
7096 self.update();
7097 return {
7098 left: self.__scrollLeft,
7099 top: self.__scrollTop,
7100 zoom: 1
7101 };
7102 },
7103
7104 /**
7105 * Updates the __scrollLeft and __scrollTop values to el's current value
7106 */
7107 update: function() {
7108 var self = this;
7109 self.__scrollLeft = self.el.scrollLeft;
7110 self.__scrollTop = self.el.scrollTop;
7111 },
7112
7113 /**
7114 * Configures the dimensions of the client (outer) and content (inner) elements.
7115 * Requires the available space for the outer element and the outer size of the inner element.
7116 * All values which are falsy (null or zero etc.) are ignored and the old value is kept.
7117 *
7118 * @param clientWidth {Integer} Inner width of outer element
7119 * @param clientHeight {Integer} Inner height of outer element
7120 * @param contentWidth {Integer} Outer width of inner element
7121 * @param contentHeight {Integer} Outer height of inner element
7122 */
7123 setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight) {
7124 var self = this;
7125
7126 if (!clientWidth && !clientHeight && !contentWidth && !contentHeight) {
7127 // this scrollview isn't rendered, don't bother
7128 return;
7129 }
7130
7131 // Only update values which are defined
7132 if (clientWidth === +clientWidth) {
7133 self.__clientWidth = clientWidth;
7134 }
7135
7136 if (clientHeight === +clientHeight) {
7137 self.__clientHeight = clientHeight;
7138 }
7139
7140 if (contentWidth === +contentWidth) {
7141 self.__contentWidth = contentWidth;
7142 }
7143
7144 if (contentHeight === +contentHeight) {
7145 self.__contentHeight = contentHeight;
7146 }
7147
7148 // Refresh maximums
7149 self.__computeScrollMax();
7150 },
7151
7152 /**
7153 * Returns the maximum scroll values
7154 *
7155 * @return {Map} `left` and `top` maximum scroll values
7156 */
7157 getScrollMax: function() {
7158 return {
7159 left: this.__maxScrollLeft,
7160 top: this.__maxScrollTop
7161 };
7162 },
7163
7164 /**
7165 * Scrolls by the given amount in px.
7166 *
7167 * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
7168 * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
7169 * @param animate {Boolean} Whether the scrolling should happen using an animation
7170 */
7171
7172 scrollBy: function(left, top, animate) {
7173 var self = this;
7174
7175 // update scroll vars before refferencing them
7176 self.update();
7177
7178 var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft;
7179 var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop;
7180
7181 self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate);
7182 },
7183
7184 /**
7185 * Scrolls to the given position in px.
7186 *
7187 * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
7188 * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
7189 * @param animate {Boolean} Whether the scrolling should happen using an animation
7190 */
7191 scrollTo: function(left, top, animate) {
7192 var self = this;
7193 if (!animate) {
7194 self.el.scrollTop = top;
7195 self.el.scrollLeft = left;
7196 self.resize();
7197 return;
7198 }
7199
7200 var oldOverflowX = self.el.style.overflowX;
7201 var oldOverflowY = self.el.style.overflowY;
7202
7203 clearTimeout(self.__scrollToCleanupTimeout);
7204 self.__scrollToCleanupTimeout = setTimeout(function() {
7205 self.el.style.overflowX = oldOverflowX;
7206 self.el.style.overflowY = oldOverflowY;
7207 }, 500);
7208
7209 self.el.style.overflowY = 'hidden';
7210 self.el.style.overflowX = 'hidden';
7211
7212 animateScroll(top, left);
7213
7214 function animateScroll(Y, X) {
7215 // scroll animation loop w/ easing
7216 // credit https://gist.github.com/dezinezync/5487119
7217 var start = Date.now(),
7218 duration = 250, //milliseconds
7219 fromY = self.el.scrollTop,
7220 fromX = self.el.scrollLeft;
7221
7222 if (fromY === Y && fromX === X) {
7223 self.el.style.overflowX = oldOverflowX;
7224 self.el.style.overflowY = oldOverflowY;
7225 self.resize();
7226 return; /* Prevent scrolling to the Y point if already there */
7227 }
7228
7229 // decelerating to zero velocity
7230 function easeOutCubic(t) {
7231 return (--t) * t * t + 1;
7232 }
7233
7234 // scroll loop
7235 function animateScrollStep() {
7236 var currentTime = Date.now(),
7237 time = Math.min(1, ((currentTime - start) / duration)),
7238 // where .5 would be 50% of time on a linear scale easedT gives a
7239 // fraction based on the easing method
7240 easedT = easeOutCubic(time);
7241
7242 if (fromY != Y) {
7243 self.el.scrollTop = parseInt((easedT * (Y - fromY)) + fromY, 10);
7244 }
7245 if (fromX != X) {
7246 self.el.scrollLeft = parseInt((easedT * (X - fromX)) + fromX, 10);
7247 }
7248
7249 if (time < 1) {
7250 ionic.requestAnimationFrame(animateScrollStep);
7251
7252 } else {
7253 // done
7254 ionic.tap.removeClonedInputs(self.__container, self);
7255 self.el.style.overflowX = oldOverflowX;
7256 self.el.style.overflowY = oldOverflowY;
7257 self.resize();
7258 }
7259 }
7260
7261 // start scroll loop
7262 ionic.requestAnimationFrame(animateScrollStep);
7263 }
7264 },
7265
7266
7267
7268 /*
7269 ---------------------------------------------------------------------------
7270 PRIVATE API
7271 ---------------------------------------------------------------------------
7272 */
7273
7274 /**
7275 * If the scroll view isn't sized correctly on start, wait until we have at least some size
7276 */
7277 __waitForSize: function() {
7278 var self = this;
7279
7280 clearTimeout(self.__sizerTimeout);
7281
7282 var sizer = function() {
7283 self.resize(true);
7284 };
7285
7286 sizer();
7287 self.__sizerTimeout = setTimeout(sizer, 500);
7288 },
7289
7290
7291 /**
7292 * Recomputes scroll minimum values based on client dimensions and content dimensions.
7293 */
7294 __computeScrollMax: function() {
7295 var self = this;
7296
7297 self.__maxScrollLeft = Math.max((self.__contentWidth) - self.__clientWidth, 0);
7298 self.__maxScrollTop = Math.max((self.__contentHeight) - self.__clientHeight, 0);
7299
7300 if (!self.__didWaitForSize && !self.__maxScrollLeft && !self.__maxScrollTop) {
7301 self.__didWaitForSize = true;
7302 self.__waitForSize();
7303 }
7304 },
7305
7306 __initEventHandlers: function() {
7307 var self = this;
7308
7309 // Event Handler
7310 var container = self.__container;
7311 // save height when scroll view is shrunk so we don't need to reflow
7312 var scrollViewOffsetHeight;
7313
7314 var lastKeyboardHeight;
7315
7316 /**
7317 * Shrink the scroll view when the keyboard is up if necessary and if the
7318 * focused input is below the bottom of the shrunk scroll view, scroll it
7319 * into view.
7320 */
7321 self.scrollChildIntoView = function(e) {
7322 var rect = container.getBoundingClientRect();
7323 if(!self.__originalContainerHeight) {
7324 self.__originalContainerHeight = rect.height;
7325 }
7326
7327 // D
7328 //var scrollBottomOffsetToTop = rect.bottom;
7329 // D - A
7330 scrollViewOffsetHeight = self.__originalContainerHeight;
7331 //console.log('Scroll view offset height', scrollViewOffsetHeight);
7332 //console.dir(container);
7333 var alreadyShrunk = self.isShrunkForKeyboard;
7334
7335 var isModal = container.parentNode.classList.contains('modal');
7336 var isPopover = container.parentNode.classList.contains('popover');
7337 // 680px is when the media query for 60% modal width kicks in
7338 var isInsetModal = isModal && window.innerWidth >= 680;
7339
7340 /*
7341 * _______
7342 * |---A---| <- top of scroll view
7343 * | |
7344 * |---B---| <- keyboard
7345 * | C | <- input
7346 * |---D---| <- initial bottom of scroll view
7347 * |___E___| <- bottom of viewport
7348 *
7349 * All commented calculations relative to the top of the viewport (ie E
7350 * is the viewport height, not 0)
7351 */
7352
7353
7354 var changedKeyboardHeight = lastKeyboardHeight && (lastKeyboardHeight !== e.detail.keyboardHeight);
7355
7356 if (!alreadyShrunk || changedKeyboardHeight) {
7357 // shrink scrollview so we can actually scroll if the input is hidden
7358 // if it isn't shrink so we can scroll to inputs under the keyboard
7359 // inset modals won't shrink on Android on their own when the keyboard appears
7360 if ( !isPopover && (ionic.Platform.isIOS() || ionic.Platform.isFullScreen || isInsetModal) ) {
7361 // if there are things below the scroll view account for them and
7362 // subtract them from the keyboard height when resizing
7363 // E - D E D
7364 //var scrollBottomOffsetToBottom = e.detail.viewportHeight - scrollBottomOffsetToTop;
7365
7366 // 0 or D - B if D > B E - B E - D
7367 //var keyboardOffset = e.detail.keyboardHeight - scrollBottomOffsetToBottom;
7368
7369 ionic.requestAnimationFrame(function(){
7370 // D - A or B - A if D > B D - A max(0, D - B)
7371 scrollViewOffsetHeight = Math.max(0, Math.min(self.__originalContainerHeight, self.__originalContainerHeight - (e.detail.keyboardHeight - 43)));//keyboardOffset >= 0 ? scrollViewOffsetHeight - keyboardOffset : scrollViewOffsetHeight + keyboardOffset;
7372
7373 //console.log('Old container height', self.__originalContainerHeight, 'New container height', scrollViewOffsetHeight, 'Keyboard height', e.detail.keyboardHeight);
7374
7375 container.style.height = scrollViewOffsetHeight + "px";
7376
7377 /*
7378 if (ionic.Platform.isIOS()) {
7379 // Force redraw to avoid disappearing content
7380 var disp = container.style.display;
7381 container.style.display = 'none';
7382 var trick = container.offsetHeight;
7383 container.style.display = disp;
7384 }
7385 */
7386 container.classList.add('keyboard-up');
7387 //update scroll view
7388 self.resize();
7389 });
7390 }
7391
7392 self.isShrunkForKeyboard = true;
7393 }
7394
7395 lastKeyboardHeight = e.detail.keyboardHeight;
7396
7397 /*
7398 * _______
7399 * |---A---| <- top of scroll view
7400 * | * | <- where we want to scroll to
7401 * |--B-D--| <- keyboard, bottom of scroll view
7402 * | C | <- input
7403 * | |
7404 * |___E___| <- bottom of viewport
7405 *
7406 * All commented calculations relative to the top of the viewport (ie E
7407 * is the viewport height, not 0)
7408 */
7409 // if the element is positioned under the keyboard scroll it into view
7410 if (e.detail.isElementUnderKeyboard) {
7411
7412 ionic.requestAnimationFrame(function(){
7413 var pos = ionic.DomUtil.getOffsetTop(e.detail.target);
7414 setTimeout(function() {
7415 if (ionic.Platform.isIOS()) {
7416 ionic.tap.cloneFocusedInput(container, self);
7417 }
7418 // Scroll the input into view, with a 100px buffer
7419 self.scrollTo(0, pos - (rect.top + 100), true);
7420 self.onScroll();
7421 }, 32);
7422
7423 /*
7424 // update D if we shrunk
7425 if (self.isShrunkForKeyboard && !alreadyShrunk) {
7426 scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
7427 console.log('Scroll bottom', scrollBottomOffsetToTop);
7428 }
7429
7430 // middle of the scrollview, this is where we want to scroll to
7431 // (D - A) / 2
7432 var scrollMidpointOffset = scrollViewOffsetHeight * 0.5;
7433 console.log('Midpoint', scrollMidpointOffset);
7434 //console.log("container.offsetHeight: " + scrollViewOffsetHeight);
7435
7436 // middle of the input we want to scroll into view
7437 // C
7438 var inputMidpoint = ((e.detail.elementBottom + e.detail.elementTop) / 2);
7439 console.log('Input midpoint');
7440
7441 // distance from middle of input to the bottom of the scroll view
7442 // C - D C D
7443 var inputMidpointOffsetToScrollBottom = inputMidpoint - scrollBottomOffsetToTop;
7444 console.log('Input midpoint offset', inputMidpointOffsetToScrollBottom);
7445
7446 //C - D + (D - A)/2 C - D (D - A)/ 2
7447 var scrollTop = inputMidpointOffsetToScrollBottom + scrollMidpointOffset;
7448 console.log('Scroll top', scrollTop);
7449
7450 if ( scrollTop > 0) {
7451 if (ionic.Platform.isIOS()) {
7452 //just shrank scroll view, give it some breathing room before scrolling
7453 setTimeout(function(){
7454 ionic.tap.cloneFocusedInput(container, self);
7455 self.scrollBy(0, scrollTop, true);
7456 self.onScroll();
7457 }, 32);
7458 } else {
7459 self.scrollBy(0, scrollTop, true);
7460 self.onScroll();
7461 }
7462 }
7463 */
7464 });
7465 }
7466
7467 // Only the first scrollView parent of the element that broadcasted this event
7468 // (the active element that needs to be shown) should receive this event
7469 e.stopPropagation();
7470 };
7471
7472 self.resetScrollView = function() {
7473 //return scrollview to original height once keyboard has hidden
7474 if (self.isShrunkForKeyboard) {
7475 self.isShrunkForKeyboard = false;
7476 container.style.height = "";
7477
7478 /*
7479 if (ionic.Platform.isIOS()) {
7480 // Force redraw to avoid disappearing content
7481 var disp = container.style.display;
7482 container.style.display = 'none';
7483 var trick = container.offsetHeight;
7484 container.style.display = disp;
7485 }
7486 */
7487
7488 self.__originalContainerHeight = container.getBoundingClientRect().height;
7489
7490 if (ionic.Platform.isIOS()) {
7491 ionic.requestAnimationFrame(function() {
7492 container.classList.remove('keyboard-up');
7493 });
7494 }
7495
7496 }
7497 self.resize();
7498 };
7499
7500 self.handleTouchMove = function(e) {
7501 if (self.__frozenShut) {
7502 e.preventDefault();
7503 e.stopPropagation();
7504 return false;
7505
7506 } else if ( self.__frozen ){
7507 e.preventDefault();
7508 // let it propagate so other events such as drag events can happen,
7509 // but don't let it actually scroll
7510 return false;
7511 }
7512 return true;
7513 };
7514
7515 container.addEventListener('scroll', self.onScroll);
7516
7517 //Broadcasted when keyboard is shown on some platforms.
7518 //See js/utils/keyboard.js
7519 container.addEventListener('scrollChildIntoView', self.scrollChildIntoView);
7520
7521 container.addEventListener(ionic.EVENTS.touchstart, self.handleTouchMove);
7522 container.addEventListener(ionic.EVENTS.touchmove, self.handleTouchMove);
7523
7524 // Listen on document because container may not have had the last
7525 // keyboardActiveElement, for example after closing a modal with a focused
7526 // input and returning to a previously resized scroll view in an ion-content.
7527 // Since we can only resize scroll views that are currently visible, just resize
7528 // the current scroll view when the keyboard is closed.
7529 document.addEventListener('resetScrollView', self.resetScrollView);
7530 },
7531
7532 __cleanup: function() {
7533 var self = this;
7534 var container = self.__container;
7535
7536 container.removeEventListener('resetScrollView', self.resetScrollView);
7537 container.removeEventListener('scroll', self.onScroll);
7538
7539 container.removeEventListener('scrollChildIntoView', self.scrollChildIntoView);
7540 container.removeEventListener('resetScrollView', self.resetScrollView);
7541
7542 container.removeEventListener(ionic.EVENTS.touchstart, self.handleTouchMove);
7543 container.removeEventListener(ionic.EVENTS.touchmove, self.handleTouchMove);
7544
7545 ionic.tap.removeClonedInputs(container, self);
7546
7547 delete self.__container;
7548 delete self.__content;
7549 delete self.__indicatorX;
7550 delete self.__indicatorY;
7551 delete self.options.el;
7552
7553 self.resize = self.scrollTo = self.onScroll = self.resetScrollView = NOOP;
7554 self.scrollChildIntoView = NOOP;
7555 container = null;
7556 }
7557 });
7558
7559})(ionic);
7560
7561(function(ionic) {
7562'use strict';
7563
7564 var ITEM_CLASS = 'item';
7565 var ITEM_CONTENT_CLASS = 'item-content';
7566 var ITEM_SLIDING_CLASS = 'item-sliding';
7567 var ITEM_OPTIONS_CLASS = 'item-options';
7568 var ITEM_PLACEHOLDER_CLASS = 'item-placeholder';
7569 var ITEM_REORDERING_CLASS = 'item-reordering';
7570 var ITEM_REORDER_BTN_CLASS = 'item-reorder';
7571
7572 var DragOp = function() {};
7573 DragOp.prototype = {
7574 start: function(){},
7575 drag: function(){},
7576 end: function(){},
7577 isSameItem: function() {
7578 return false;
7579 }
7580 };
7581
7582 var SlideDrag = function(opts) {
7583 this.dragThresholdX = opts.dragThresholdX || 10;
7584 this.el = opts.el;
7585 this.item = opts.item;
7586 this.canSwipe = opts.canSwipe;
7587 };
7588
7589 SlideDrag.prototype = new DragOp();
7590
7591 SlideDrag.prototype.start = function(e) {
7592 var content, buttons, offsetX, buttonsWidth;
7593
7594 if (!this.canSwipe()) {
7595 return;
7596 }
7597
7598 if (e.target.classList.contains(ITEM_CONTENT_CLASS)) {
7599 content = e.target;
7600 } else if (e.target.classList.contains(ITEM_CLASS)) {
7601 content = e.target.querySelector('.' + ITEM_CONTENT_CLASS);
7602 } else {
7603 content = ionic.DomUtil.getParentWithClass(e.target, ITEM_CONTENT_CLASS);
7604 }
7605
7606 // If we don't have a content area as one of our children (or ourselves), skip
7607 if (!content) {
7608 return;
7609 }
7610
7611 // Make sure we aren't animating as we slide
7612 content.classList.remove(ITEM_SLIDING_CLASS);
7613
7614 // Grab the starting X point for the item (for example, so we can tell whether it is open or closed to start)
7615 offsetX = parseFloat(content.style[ionic.CSS.TRANSFORM].replace('translate3d(', '').split(',')[0]) || 0;
7616
7617 // Grab the buttons
7618 buttons = content.parentNode.querySelector('.' + ITEM_OPTIONS_CLASS);
7619 if (!buttons) {
7620 return;
7621 }
7622 buttons.classList.remove('invisible');
7623
7624 buttonsWidth = buttons.offsetWidth;
7625
7626 this._currentDrag = {
7627 buttons: buttons,
7628 buttonsWidth: buttonsWidth,
7629 content: content,
7630 startOffsetX: offsetX
7631 };
7632 };
7633
7634 /**
7635 * Check if this is the same item that was previously dragged.
7636 */
7637 SlideDrag.prototype.isSameItem = function(op) {
7638 if (op._lastDrag && this._currentDrag) {
7639 return this._currentDrag.content == op._lastDrag.content;
7640 }
7641 return false;
7642 };
7643
7644 SlideDrag.prototype.clean = function(isInstant) {
7645 var lastDrag = this._lastDrag;
7646
7647 if (!lastDrag || !lastDrag.content) return;
7648
7649 lastDrag.content.style[ionic.CSS.TRANSITION] = '';
7650 lastDrag.content.style[ionic.CSS.TRANSFORM] = '';
7651 if (isInstant) {
7652 lastDrag.content.style[ionic.CSS.TRANSITION] = 'none';
7653 makeInvisible();
7654 ionic.requestAnimationFrame(function() {
7655 lastDrag.content.style[ionic.CSS.TRANSITION] = '';
7656 });
7657 } else {
7658 ionic.requestAnimationFrame(function() {
7659 setTimeout(makeInvisible, 250);
7660 });
7661 }
7662 function makeInvisible() {
7663 lastDrag.buttons && lastDrag.buttons.classList.add('invisible');
7664 }
7665 };
7666
7667 SlideDrag.prototype.drag = ionic.animationFrameThrottle(function(e) {
7668 var buttonsWidth;
7669
7670 // We really aren't dragging
7671 if (!this._currentDrag) {
7672 return;
7673 }
7674
7675 // Check if we should start dragging. Check if we've dragged past the threshold,
7676 // or we are starting from the open state.
7677 if (!this._isDragging &&
7678 ((Math.abs(e.gesture.deltaX) > this.dragThresholdX) ||
7679 (Math.abs(this._currentDrag.startOffsetX) > 0))) {
7680 this._isDragging = true;
7681 }
7682
7683 if (this._isDragging) {
7684 buttonsWidth = this._currentDrag.buttonsWidth;
7685
7686 // Grab the new X point, capping it at zero
7687 var newX = Math.min(0, this._currentDrag.startOffsetX + e.gesture.deltaX);
7688
7689 // If the new X position is past the buttons, we need to slow down the drag (rubber band style)
7690 if (newX < -buttonsWidth) {
7691 // Calculate the new X position, capped at the top of the buttons
7692 newX = Math.min(-buttonsWidth, -buttonsWidth + (((e.gesture.deltaX + buttonsWidth) * 0.4)));
7693 }
7694
7695 this._currentDrag.content.$$ionicOptionsOpen = newX !== 0;
7696
7697 this._currentDrag.content.style[ionic.CSS.TRANSFORM] = 'translate3d(' + newX + 'px, 0, 0)';
7698 this._currentDrag.content.style[ionic.CSS.TRANSITION] = 'none';
7699 }
7700 });
7701
7702 SlideDrag.prototype.end = function(e, doneCallback) {
7703 var self = this;
7704
7705 // There is no drag, just end immediately
7706 if (!self._currentDrag) {
7707 doneCallback && doneCallback();
7708 return;
7709 }
7710
7711 // If we are currently dragging, we want to snap back into place
7712 // The final resting point X will be the width of the exposed buttons
7713 var restingPoint = -self._currentDrag.buttonsWidth;
7714
7715 // Check if the drag didn't clear the buttons mid-point
7716 // and we aren't moving fast enough to swipe open
7717 if (e.gesture.deltaX > -(self._currentDrag.buttonsWidth / 2)) {
7718
7719 // If we are going left but too slow, or going right, go back to resting
7720 if (e.gesture.direction == "left" && Math.abs(e.gesture.velocityX) < 0.3) {
7721 restingPoint = 0;
7722
7723 } else if (e.gesture.direction == "right") {
7724 restingPoint = 0;
7725 }
7726
7727 }
7728
7729 ionic.requestAnimationFrame(function() {
7730 if (restingPoint === 0) {
7731 self._currentDrag.content.style[ionic.CSS.TRANSFORM] = '';
7732 var buttons = self._currentDrag.buttons;
7733 setTimeout(function() {
7734 buttons && buttons.classList.add('invisible');
7735 }, 250);
7736 } else {
7737 self._currentDrag.content.style[ionic.CSS.TRANSFORM] = 'translate3d(' + restingPoint + 'px,0,0)';
7738 }
7739 self._currentDrag.content.style[ionic.CSS.TRANSITION] = '';
7740
7741
7742 // Kill the current drag
7743 if (!self._lastDrag) {
7744 self._lastDrag = {};
7745 }
7746 ionic.extend(self._lastDrag, self._currentDrag);
7747 if (self._currentDrag) {
7748 self._currentDrag.buttons = null;
7749 self._currentDrag.content = null;
7750 }
7751 self._currentDrag = null;
7752
7753 // We are done, notify caller
7754 doneCallback && doneCallback();
7755 });
7756 };
7757
7758 var ReorderDrag = function(opts) {
7759 var self = this;
7760
7761 self.dragThresholdY = opts.dragThresholdY || 0;
7762 self.onReorder = opts.onReorder;
7763 self.listEl = opts.listEl;
7764 self.el = self.item = opts.el;
7765 self.scrollEl = opts.scrollEl;
7766 self.scrollView = opts.scrollView;
7767 // Get the True Top of the list el http://www.quirksmode.org/js/findpos.html
7768 self.listElTrueTop = 0;
7769 if (self.listEl.offsetParent) {
7770 var obj = self.listEl;
7771 do {
7772 self.listElTrueTop += obj.offsetTop;
7773 obj = obj.offsetParent;
7774 } while (obj);
7775 }
7776 };
7777
7778 ReorderDrag.prototype = new DragOp();
7779
7780 ReorderDrag.prototype._moveElement = function(e) {
7781 var y = e.gesture.center.pageY +
7782 this.scrollView.getValues().top -
7783 (this._currentDrag.elementHeight / 2) -
7784 this.listElTrueTop;
7785 this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(0, ' + y + 'px, 0)';
7786 };
7787
7788 ReorderDrag.prototype.deregister = function() {
7789 this.listEl = this.el = this.scrollEl = this.scrollView = null;
7790 };
7791
7792 ReorderDrag.prototype.start = function(e) {
7793
7794 var startIndex = ionic.DomUtil.getChildIndex(this.el, this.el.nodeName.toLowerCase());
7795 var elementHeight = this.el.scrollHeight;
7796 var placeholder = this.el.cloneNode(true);
7797
7798 placeholder.classList.add(ITEM_PLACEHOLDER_CLASS);
7799
7800 this.el.parentNode.insertBefore(placeholder, this.el);
7801 this.el.classList.add(ITEM_REORDERING_CLASS);
7802
7803 this._currentDrag = {
7804 elementHeight: elementHeight,
7805 startIndex: startIndex,
7806 placeholder: placeholder,
7807 scrollHeight: scroll,
7808 list: placeholder.parentNode
7809 };
7810
7811 this._moveElement(e);
7812 };
7813
7814 ReorderDrag.prototype.drag = ionic.animationFrameThrottle(function(e) {
7815 // We really aren't dragging
7816 var self = this;
7817 if (!this._currentDrag) {
7818 return;
7819 }
7820
7821 var scrollY = 0;
7822 var pageY = e.gesture.center.pageY;
7823 var offset = this.listElTrueTop;
7824
7825 //If we have a scrollView, check scroll boundaries for dragged element and scroll if necessary
7826 if (this.scrollView) {
7827
7828 var container = this.scrollView.__container;
7829 scrollY = this.scrollView.getValues().top;
7830
7831 var containerTop = container.offsetTop;
7832 var pixelsPastTop = containerTop - pageY + this._currentDrag.elementHeight / 2;
7833 var pixelsPastBottom = pageY + this._currentDrag.elementHeight / 2 - containerTop - container.offsetHeight;
7834
7835 if (e.gesture.deltaY < 0 && pixelsPastTop > 0 && scrollY > 0) {
7836 this.scrollView.scrollBy(null, -pixelsPastTop);
7837 //Trigger another drag so the scrolling keeps going
7838 ionic.requestAnimationFrame(function() {
7839 self.drag(e);
7840 });
7841 }
7842 if (e.gesture.deltaY > 0 && pixelsPastBottom > 0) {
7843 if (scrollY < this.scrollView.getScrollMax().top) {
7844 this.scrollView.scrollBy(null, pixelsPastBottom);
7845 //Trigger another drag so the scrolling keeps going
7846 ionic.requestAnimationFrame(function() {
7847 self.drag(e);
7848 });
7849 }
7850 }
7851 }
7852
7853 // Check if we should start dragging. Check if we've dragged past the threshold,
7854 // or we are starting from the open state.
7855 if (!this._isDragging && Math.abs(e.gesture.deltaY) > this.dragThresholdY) {
7856 this._isDragging = true;
7857 }
7858
7859 if (this._isDragging) {
7860 this._moveElement(e);
7861
7862 this._currentDrag.currentY = scrollY + pageY - offset;
7863
7864 // this._reorderItems();
7865 }
7866 });
7867
7868 // When an item is dragged, we need to reorder any items for sorting purposes
7869 ReorderDrag.prototype._getReorderIndex = function() {
7870 var self = this;
7871
7872 var siblings = Array.prototype.slice.call(self._currentDrag.placeholder.parentNode.children)
7873 .filter(function(el) {
7874 return el.nodeName === self.el.nodeName && el !== self.el;
7875 });
7876
7877 var dragOffsetTop = self._currentDrag.currentY;
7878 var el;
7879 for (var i = 0, len = siblings.length; i < len; i++) {
7880 el = siblings[i];
7881 if (i === len - 1) {
7882 if (dragOffsetTop > el.offsetTop) {
7883 return i;
7884 }
7885 } else if (i === 0) {
7886 if (dragOffsetTop < el.offsetTop + el.offsetHeight) {
7887 return i;
7888 }
7889 } else if (dragOffsetTop > el.offsetTop - el.offsetHeight / 2 &&
7890 dragOffsetTop < el.offsetTop + el.offsetHeight) {
7891 return i;
7892 }
7893 }
7894 return self._currentDrag.startIndex;
7895 };
7896
7897 ReorderDrag.prototype.end = function(e, doneCallback) {
7898 if (!this._currentDrag) {
7899 doneCallback && doneCallback();
7900 return;
7901 }
7902
7903 var placeholder = this._currentDrag.placeholder;
7904 var finalIndex = this._getReorderIndex();
7905
7906 // Reposition the element
7907 this.el.classList.remove(ITEM_REORDERING_CLASS);
7908 this.el.style[ionic.CSS.TRANSFORM] = '';
7909
7910 placeholder.parentNode.insertBefore(this.el, placeholder);
7911 placeholder.parentNode.removeChild(placeholder);
7912
7913 this.onReorder && this.onReorder(this.el, this._currentDrag.startIndex, finalIndex);
7914
7915 this._currentDrag = {
7916 placeholder: null,
7917 content: null
7918 };
7919 this._currentDrag = null;
7920 doneCallback && doneCallback();
7921 };
7922
7923
7924
7925 /**
7926 * The ListView handles a list of items. It will process drag animations, edit mode,
7927 * and other operations that are common on mobile lists or table views.
7928 */
7929 ionic.views.ListView = ionic.views.View.inherit({
7930 initialize: function(opts) {
7931 var self = this;
7932
7933 opts = ionic.extend({
7934 onReorder: function() {},
7935 virtualRemoveThreshold: -200,
7936 virtualAddThreshold: 200,
7937 canSwipe: function() {
7938 return true;
7939 }
7940 }, opts);
7941
7942 ionic.extend(self, opts);
7943
7944 if (!self.itemHeight && self.listEl) {
7945 self.itemHeight = self.listEl.children[0] && parseInt(self.listEl.children[0].style.height, 10);
7946 }
7947
7948 self.onRefresh = opts.onRefresh || function() {};
7949 self.onRefreshOpening = opts.onRefreshOpening || function() {};
7950 self.onRefreshHolding = opts.onRefreshHolding || function() {};
7951
7952 var gestureOpts = {};
7953 // don't prevent native scrolling
7954 if (ionic.DomUtil.getParentOrSelfWithClass(self.el, 'overflow-scroll')) {
7955 gestureOpts.prevent_default_directions = ['left', 'right'];
7956 }
7957
7958 window.ionic.onGesture('release', function(e) {
7959 self._handleEndDrag(e);
7960 }, self.el, gestureOpts);
7961
7962 window.ionic.onGesture('drag', function(e) {
7963 self._handleDrag(e);
7964 }, self.el, gestureOpts);
7965 // Start the drag states
7966 self._initDrag();
7967 },
7968
7969 /**
7970 * Be sure to cleanup references.
7971 */
7972 deregister: function() {
7973 this.el = this.listEl = this.scrollEl = this.scrollView = null;
7974
7975 // ensure no scrolls have been left frozen
7976 if (this.isScrollFreeze) {
7977 self.scrollView.freeze(false);
7978 }
7979 },
7980
7981 /**
7982 * Called to tell the list to stop refreshing. This is useful
7983 * if you are refreshing the list and are done with refreshing.
7984 */
7985 stopRefreshing: function() {
7986 var refresher = this.el.querySelector('.list-refresher');
7987 refresher.style.height = '0';
7988 },
7989
7990 /**
7991 * If we scrolled and have virtual mode enabled, compute the window
7992 * of active elements in order to figure out the viewport to render.
7993 */
7994 didScroll: function(e) {
7995 var self = this;
7996
7997 if (self.isVirtual) {
7998 var itemHeight = self.itemHeight;
7999
8000 // Grab the total height of the list
8001 var scrollHeight = e.target.scrollHeight;
8002
8003 // Get the viewport height
8004 var viewportHeight = self.el.parentNode.offsetHeight;
8005
8006 // High water is the pixel position of the first element to include (everything before
8007 // that will be removed)
8008 var highWater = Math.max(0, e.scrollTop + self.virtualRemoveThreshold);
8009
8010 // Low water is the pixel position of the last element to include (everything after
8011 // that will be removed)
8012 var lowWater = Math.min(scrollHeight, Math.abs(e.scrollTop) + viewportHeight + self.virtualAddThreshold);
8013
8014 // Get the first and last elements in the list based on how many can fit
8015 // between the pixel range of lowWater and highWater
8016 var first = parseInt(Math.abs(highWater / itemHeight), 10);
8017 var last = parseInt(Math.abs(lowWater / itemHeight), 10);
8018
8019 // Get the items we need to remove
8020 self._virtualItemsToRemove = Array.prototype.slice.call(self.listEl.children, 0, first);
8021
8022 self.renderViewport && self.renderViewport(highWater, lowWater, first, last);
8023 }
8024 },
8025
8026 didStopScrolling: function() {
8027 if (this.isVirtual) {
8028 for (var i = 0; i < this._virtualItemsToRemove.length; i++) {
8029 //el.parentNode.removeChild(el);
8030 this.didHideItem && this.didHideItem(i);
8031 }
8032 // Once scrolling stops, check if we need to remove old items
8033
8034 }
8035 },
8036
8037 /**
8038 * Clear any active drag effects on the list.
8039 */
8040 clearDragEffects: function(isInstant) {
8041 if (this._lastDragOp) {
8042 this._lastDragOp.clean && this._lastDragOp.clean(isInstant);
8043 this._lastDragOp.deregister && this._lastDragOp.deregister();
8044 this._lastDragOp = null;
8045 }
8046 },
8047
8048 _initDrag: function() {
8049 // Store the last one
8050 if (this._lastDragOp) {
8051 this._lastDragOp.deregister && this._lastDragOp.deregister();
8052 }
8053 this._lastDragOp = this._dragOp;
8054
8055 this._dragOp = null;
8056 },
8057
8058 // Return the list item from the given target
8059 _getItem: function(target) {
8060 while (target) {
8061 if (target.classList && target.classList.contains(ITEM_CLASS)) {
8062 return target;
8063 }
8064 target = target.parentNode;
8065 }
8066 return null;
8067 },
8068
8069
8070 _startDrag: function(e) {
8071 var self = this;
8072
8073 self._isDragging = false;
8074
8075 var lastDragOp = self._lastDragOp;
8076 var item;
8077
8078 // If we have an open SlideDrag and we're scrolling the list. Clear it.
8079 if (self._didDragUpOrDown && lastDragOp instanceof SlideDrag) {
8080 lastDragOp.clean && lastDragOp.clean();
8081 }
8082
8083 // Check if this is a reorder drag
8084 if (ionic.DomUtil.getParentOrSelfWithClass(e.target, ITEM_REORDER_BTN_CLASS) && (e.gesture.direction == 'up' || e.gesture.direction == 'down')) {
8085 item = self._getItem(e.target);
8086
8087 if (item) {
8088 self._dragOp = new ReorderDrag({
8089 listEl: self.el,
8090 el: item,
8091 scrollEl: self.scrollEl,
8092 scrollView: self.scrollView,
8093 onReorder: function(el, start, end) {
8094 self.onReorder && self.onReorder(el, start, end);
8095 }
8096 });
8097 self._dragOp.start(e);
8098 e.preventDefault();
8099 }
8100 }
8101
8102 // Or check if this is a swipe to the side drag
8103 else if (!self._didDragUpOrDown && (e.gesture.direction == 'left' || e.gesture.direction == 'right') && Math.abs(e.gesture.deltaX) > 5) {
8104
8105 // Make sure this is an item with buttons
8106 item = self._getItem(e.target);
8107 if (item && item.querySelector('.item-options')) {
8108 self._dragOp = new SlideDrag({
8109 el: self.el,
8110 item: item,
8111 canSwipe: self.canSwipe
8112 });
8113 self._dragOp.start(e);
8114 e.preventDefault();
8115 self.isScrollFreeze = self.scrollView.freeze(true);
8116 }
8117 }
8118
8119 // If we had a last drag operation and this is a new one on a different item, clean that last one
8120 if (lastDragOp && self._dragOp && !self._dragOp.isSameItem(lastDragOp) && e.defaultPrevented) {
8121 lastDragOp.clean && lastDragOp.clean();
8122 }
8123 },
8124
8125
8126 _handleEndDrag: function(e) {
8127 var self = this;
8128
8129 if (self.scrollView) {
8130 self.isScrollFreeze = self.scrollView.freeze(false);
8131 }
8132
8133 self._didDragUpOrDown = false;
8134
8135 if (!self._dragOp) {
8136 return;
8137 }
8138
8139 self._dragOp.end(e, function() {
8140 self._initDrag();
8141 });
8142 },
8143
8144 /**
8145 * Process the drag event to move the item to the left or right.
8146 */
8147 _handleDrag: function(e) {
8148 var self = this;
8149
8150 if (Math.abs(e.gesture.deltaY) > 5) {
8151 self._didDragUpOrDown = true;
8152 }
8153
8154 // If we get a drag event, make sure we aren't in another drag, then check if we should
8155 // start one
8156 if (!self.isDragging && !self._dragOp) {
8157 self._startDrag(e);
8158 }
8159
8160 // No drag still, pass it up
8161 if (!self._dragOp) {
8162 return;
8163 }
8164
8165 e.gesture.srcEvent.preventDefault();
8166 self._dragOp.drag(e);
8167 }
8168
8169 });
8170
8171})(ionic);
8172
8173(function(ionic) {
8174'use strict';
8175
8176 ionic.views.Modal = ionic.views.View.inherit({
8177 initialize: function(opts) {
8178 opts = ionic.extend({
8179 focusFirstInput: false,
8180 unfocusOnHide: true,
8181 focusFirstDelay: 600,
8182 backdropClickToClose: true,
8183 hardwareBackButtonClose: true,
8184 }, opts);
8185
8186 ionic.extend(this, opts);
8187
8188 this.el = opts.el;
8189 },
8190 show: function() {
8191 var self = this;
8192
8193 if(self.focusFirstInput) {
8194 // Let any animations run first
8195 window.setTimeout(function() {
8196 var input = self.el.querySelector('input, textarea');
8197 input && input.focus && input.focus();
8198 }, self.focusFirstDelay);
8199 }
8200 },
8201 hide: function() {
8202 // Unfocus all elements
8203 if(this.unfocusOnHide) {
8204 var inputs = this.el.querySelectorAll('input, textarea');
8205 // Let any animations run first
8206 window.setTimeout(function() {
8207 for(var i = 0; i < inputs.length; i++) {
8208 inputs[i].blur && inputs[i].blur();
8209 }
8210 });
8211 }
8212 }
8213 });
8214
8215})(ionic);
8216
8217(function(ionic) {
8218'use strict';
8219
8220 /**
8221 * The side menu view handles one of the side menu's in a Side Menu Controller
8222 * configuration.
8223 * It takes a DOM reference to that side menu element.
8224 */
8225 ionic.views.SideMenu = ionic.views.View.inherit({
8226 initialize: function(opts) {
8227 this.el = opts.el;
8228 this.isEnabled = (typeof opts.isEnabled === 'undefined') ? true : opts.isEnabled;
8229 this.setWidth(opts.width);
8230 },
8231 getFullWidth: function() {
8232 return this.width;
8233 },
8234 setWidth: function(width) {
8235 this.width = width;
8236 this.el.style.width = width + 'px';
8237 },
8238 setIsEnabled: function(isEnabled) {
8239 this.isEnabled = isEnabled;
8240 },
8241 bringUp: function() {
8242 if(this.el.style.zIndex !== '0') {
8243 this.el.style.zIndex = '0';
8244 }
8245 },
8246 pushDown: function() {
8247 if(this.el.style.zIndex !== '-1') {
8248 this.el.style.zIndex = '-1';
8249 }
8250 }
8251 });
8252
8253 ionic.views.SideMenuContent = ionic.views.View.inherit({
8254 initialize: function(opts) {
8255 ionic.extend(this, {
8256 animationClass: 'menu-animated',
8257 onDrag: function() {},
8258 onEndDrag: function() {}
8259 }, opts);
8260
8261 ionic.onGesture('drag', ionic.proxy(this._onDrag, this), this.el);
8262 ionic.onGesture('release', ionic.proxy(this._onEndDrag, this), this.el);
8263 },
8264 _onDrag: function(e) {
8265 this.onDrag && this.onDrag(e);
8266 },
8267 _onEndDrag: function(e) {
8268 this.onEndDrag && this.onEndDrag(e);
8269 },
8270 disableAnimation: function() {
8271 this.el.classList.remove(this.animationClass);
8272 },
8273 enableAnimation: function() {
8274 this.el.classList.add(this.animationClass);
8275 },
8276 getTranslateX: function() {
8277 return parseFloat(this.el.style[ionic.CSS.TRANSFORM].replace('translate3d(', '').split(',')[0]);
8278 },
8279 setTranslateX: ionic.animationFrameThrottle(function(x) {
8280 this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + x + 'px, 0, 0)';
8281 })
8282 });
8283
8284})(ionic);
8285
8286/*
8287 * Adapted from Swipe.js 2.0
8288 *
8289 * Brad Birdsall
8290 * Copyright 2013, MIT License
8291 *
8292*/
8293
8294(function(ionic) {
8295'use strict';
8296
8297ionic.views.Slider = ionic.views.View.inherit({
8298 initialize: function (options) {
8299 var slider = this;
8300
8301 var touchStartEvent, touchMoveEvent, touchEndEvent;
8302 if (window.navigator.pointerEnabled) {
8303 touchStartEvent = 'pointerdown';
8304 touchMoveEvent = 'pointermove';
8305 touchEndEvent = 'pointerup';
8306 } else if (window.navigator.msPointerEnabled) {
8307 touchStartEvent = 'MSPointerDown';
8308 touchMoveEvent = 'MSPointerMove';
8309 touchEndEvent = 'MSPointerUp';
8310 } else {
8311 touchStartEvent = 'touchstart';
8312 touchMoveEvent = 'touchmove';
8313 touchEndEvent = 'touchend';
8314 }
8315
8316 var mouseStartEvent = 'mousedown';
8317 var mouseMoveEvent = 'mousemove';
8318 var mouseEndEvent = 'mouseup';
8319
8320 // utilities
8321 var noop = function() {}; // simple no operation function
8322 var offloadFn = function(fn) { setTimeout(fn || noop, 0); }; // offload a functions execution
8323
8324 // check browser capabilities
8325 var browser = {
8326 addEventListener: !!window.addEventListener,
8327 transitions: (function(temp) {
8328 var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];
8329 for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true;
8330 return false;
8331 })(document.createElement('swipe'))
8332 };
8333
8334
8335 var container = options.el;
8336
8337 // quit if no root element
8338 if (!container) return;
8339 var element = container.children[0];
8340 var slides, slidePos, width, length;
8341 options = options || {};
8342 var index = parseInt(options.startSlide, 10) || 0;
8343 var speed = options.speed || 300;
8344 options.continuous = options.continuous !== undefined ? options.continuous : true;
8345
8346 function setup() {
8347
8348 // do not setup if the container has no width
8349 if (!container.offsetWidth) {
8350 return;
8351 }
8352
8353 // cache slides
8354 slides = element.children;
8355 length = slides.length;
8356
8357 // set continuous to false if only one slide
8358 if (slides.length < 2) options.continuous = false;
8359
8360 //special case if two slides
8361 if (browser.transitions && options.continuous && slides.length < 3) {
8362 element.appendChild(slides[0].cloneNode(true));
8363 element.appendChild(element.children[1].cloneNode(true));
8364 slides = element.children;
8365 }
8366
8367 // create an array to store current positions of each slide
8368 slidePos = new Array(slides.length);
8369
8370 // determine width of each slide
8371 width = container.offsetWidth || container.getBoundingClientRect().width;
8372
8373 element.style.width = (slides.length * width) + 'px';
8374
8375 // stack elements
8376 var pos = slides.length;
8377 while(pos--) {
8378
8379 var slide = slides[pos];
8380
8381 slide.style.width = width + 'px';
8382 slide.setAttribute('data-index', pos);
8383
8384 if (browser.transitions) {
8385 slide.style.left = (pos * -width) + 'px';
8386 move(pos, index > pos ? -width : (index < pos ? width : 0), 0);
8387 }
8388
8389 }
8390
8391 // reposition elements before and after index
8392 if (options.continuous && browser.transitions) {
8393 move(circle(index - 1), -width, 0);
8394 move(circle(index + 1), width, 0);
8395 }
8396
8397 if (!browser.transitions) element.style.left = (index * -width) + 'px';
8398
8399 container.style.visibility = 'visible';
8400
8401 options.slidesChanged && options.slidesChanged();
8402 }
8403
8404 function prev(slideSpeed) {
8405
8406 if (options.continuous) slide(index - 1, slideSpeed);
8407 else if (index) slide(index - 1, slideSpeed);
8408
8409 }
8410
8411 function next(slideSpeed) {
8412
8413 if (options.continuous) slide(index + 1, slideSpeed);
8414 else if (index < slides.length - 1) slide(index + 1, slideSpeed);
8415
8416 }
8417
8418 function circle(index) {
8419
8420 // a simple positive modulo using slides.length
8421 return (slides.length + (index % slides.length)) % slides.length;
8422
8423 }
8424
8425 function slide(to, slideSpeed) {
8426
8427 // do nothing if already on requested slide
8428 if (index == to) return;
8429
8430 if (!slides) {
8431 index = to;
8432 return;
8433 }
8434
8435 if (browser.transitions) {
8436
8437 var direction = Math.abs(index - to) / (index - to); // 1: backward, -1: forward
8438
8439 // get the actual position of the slide
8440 if (options.continuous) {
8441 var naturalDirection = direction;
8442 direction = -slidePos[circle(to)] / width;
8443
8444 // if going forward but to < index, use to = slides.length + to
8445 // if going backward but to > index, use to = -slides.length + to
8446 if (direction !== naturalDirection) to = -direction * slides.length + to;
8447
8448 }
8449
8450 var diff = Math.abs(index - to) - 1;
8451
8452 // move all the slides between index and to in the right direction
8453 while (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0);
8454
8455 to = circle(to);
8456
8457 move(index, width * direction, slideSpeed || speed);
8458 move(to, 0, slideSpeed || speed);
8459
8460 if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place
8461
8462 } else {
8463
8464 to = circle(to);
8465 animate(index * -width, to * -width, slideSpeed || speed);
8466 //no fallback for a circular continuous if the browser does not accept transitions
8467 }
8468
8469 index = to;
8470 offloadFn(options.callback && options.callback(index, slides[index]));
8471 }
8472
8473 function move(index, dist, speed) {
8474
8475 translate(index, dist, speed);
8476 slidePos[index] = dist;
8477
8478 }
8479
8480 function translate(index, dist, speed) {
8481
8482 var slide = slides[index];
8483 var style = slide && slide.style;
8484
8485 if (!style) return;
8486
8487 style.webkitTransitionDuration =
8488 style.MozTransitionDuration =
8489 style.msTransitionDuration =
8490 style.OTransitionDuration =
8491 style.transitionDuration = speed + 'ms';
8492
8493 style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)';
8494 style.msTransform =
8495 style.MozTransform =
8496 style.OTransform = 'translateX(' + dist + 'px)';
8497
8498 }
8499
8500 function animate(from, to, speed) {
8501
8502 // if not an animation, just reposition
8503 if (!speed) {
8504
8505 element.style.left = to + 'px';
8506 return;
8507
8508 }
8509
8510 var start = +new Date();
8511
8512 var timer = setInterval(function() {
8513
8514 var timeElap = +new Date() - start;
8515
8516 if (timeElap > speed) {
8517
8518 element.style.left = to + 'px';
8519
8520 if (delay) begin();
8521
8522 options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
8523
8524 clearInterval(timer);
8525 return;
8526
8527 }
8528
8529 element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px';
8530
8531 }, 4);
8532
8533 }
8534
8535 // setup auto slideshow
8536 var delay = options.auto || 0;
8537 var interval;
8538
8539 function begin() {
8540
8541 interval = setTimeout(next, delay);
8542
8543 }
8544
8545 function stop() {
8546
8547 delay = options.auto || 0;
8548 clearTimeout(interval);
8549
8550 }
8551
8552
8553 // setup initial vars
8554 var start = {};
8555 var delta = {};
8556 var isScrolling;
8557
8558 // setup event capturing
8559 var events = {
8560
8561 handleEvent: function(event) {
8562 if(!event.touches && event.pageX && event.pageY) {
8563 event.touches = [{
8564 pageX: event.pageX,
8565 pageY: event.pageY
8566 }];
8567 }
8568
8569 switch (event.type) {
8570 case touchStartEvent: this.start(event); break;
8571 case mouseStartEvent: this.start(event); break;
8572 case touchMoveEvent: this.touchmove(event); break;
8573 case mouseMoveEvent: this.touchmove(event); break;
8574 case touchEndEvent: offloadFn(this.end(event)); break;
8575 case mouseEndEvent: offloadFn(this.end(event)); break;
8576 case 'webkitTransitionEnd':
8577 case 'msTransitionEnd':
8578 case 'oTransitionEnd':
8579 case 'otransitionend':
8580 case 'transitionend': offloadFn(this.transitionEnd(event)); break;
8581 case 'resize': offloadFn(setup); break;
8582 }
8583
8584 if (options.stopPropagation) event.stopPropagation();
8585
8586 },
8587 start: function(event) {
8588
8589 // prevent to start if there is no valid event
8590 if (!event.touches) {
8591 return;
8592 }
8593
8594 var touches = event.touches[0];
8595
8596 // measure start values
8597 start = {
8598
8599 // get initial touch coords
8600 x: touches.pageX,
8601 y: touches.pageY,
8602
8603 // store time to determine touch duration
8604 time: +new Date()
8605
8606 };
8607
8608 // used for testing first move event
8609 isScrolling = undefined;
8610
8611 // reset delta and end measurements
8612 delta = {};
8613
8614 // attach touchmove and touchend listeners
8615 element.addEventListener(touchMoveEvent, this, false);
8616 element.addEventListener(mouseMoveEvent, this, false);
8617
8618 element.addEventListener(touchEndEvent, this, false);
8619 element.addEventListener(mouseEndEvent, this, false);
8620
8621 document.addEventListener(touchEndEvent, this, false);
8622 document.addEventListener(mouseEndEvent, this, false);
8623 },
8624 touchmove: function(event) {
8625
8626 // ensure there is a valid event
8627 // ensure swiping with one touch and not pinching
8628 // ensure sliding is enabled
8629 if (!event.touches ||
8630 event.touches.length > 1 ||
8631 event.scale && event.scale !== 1 ||
8632 slider.slideIsDisabled) {
8633 return;
8634 }
8635
8636 if (options.disableScroll) event.preventDefault();
8637
8638 var touches = event.touches[0];
8639
8640 // measure change in x and y
8641 delta = {
8642 x: touches.pageX - start.x,
8643 y: touches.pageY - start.y
8644 };
8645
8646 // determine if scrolling test has run - one time test
8647 if ( typeof isScrolling == 'undefined') {
8648 isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) );
8649 }
8650
8651 // if user is not trying to scroll vertically
8652 if (!isScrolling) {
8653
8654 // prevent native scrolling
8655 event.preventDefault();
8656
8657 // stop slideshow
8658 stop();
8659
8660 // increase resistance if first or last slide
8661 if (options.continuous) { // we don't add resistance at the end
8662
8663 translate(circle(index - 1), delta.x + slidePos[circle(index - 1)], 0);
8664 translate(index, delta.x + slidePos[index], 0);
8665 translate(circle(index + 1), delta.x + slidePos[circle(index + 1)], 0);
8666
8667 } else {
8668 // If the slider bounces, do the bounce!
8669 if(options.bouncing) {
8670 delta.x =
8671 delta.x /
8672 ( (!index && delta.x > 0 || // if first slide and sliding left
8673 index == slides.length - 1 && // or if last slide and sliding right
8674 delta.x < 0 // and if sliding at all
8675 ) ?
8676 ( Math.abs(delta.x) / width + 1 ) // determine resistance level
8677 : 1 ); // no resistance if false
8678 } else {
8679 if(width * index - delta.x < 0) { //We are trying scroll past left boundary
8680 delta.x = Math.min(delta.x, width * index); //Set delta.x so we don't go past left screen
8681 }
8682 if(Math.abs(delta.x) > width * (slides.length - index - 1)){ //We are trying to scroll past right bondary
8683 delta.x = Math.max( -width * (slides.length - index - 1), delta.x); //Set delta.x so we don't go past right screen
8684 }
8685 }
8686
8687 // translate 1:1
8688 translate(index - 1, delta.x + slidePos[index - 1], 0);
8689 translate(index, delta.x + slidePos[index], 0);
8690 translate(index + 1, delta.x + slidePos[index + 1], 0);
8691 }
8692
8693 options.onDrag && options.onDrag();
8694 }
8695
8696 },
8697 end: function() {
8698
8699 // measure duration
8700 var duration = +new Date() - start.time;
8701
8702 // determine if slide attempt triggers next/prev slide
8703 var isValidSlide =
8704 Number(duration) < 250 && // if slide duration is less than 250ms
8705 Math.abs(delta.x) > 20 || // and if slide amt is greater than 20px
8706 Math.abs(delta.x) > width / 2; // or if slide amt is greater than half the width
8707
8708 // determine if slide attempt is past start and end
8709 var isPastBounds = (!index && delta.x > 0) || // if first slide and slide amt is greater than 0
8710 (index == slides.length - 1 && delta.x < 0); // or if last slide and slide amt is less than 0
8711
8712 if (options.continuous) isPastBounds = false;
8713
8714 // determine direction of swipe (true:right, false:left)
8715 var direction = delta.x < 0;
8716
8717 // if not scrolling vertically
8718 if (!isScrolling) {
8719
8720 if (isValidSlide && !isPastBounds) {
8721
8722 if (direction) {
8723
8724 if (options.continuous) { // we need to get the next in this direction in place
8725
8726 move(circle(index - 1), -width, 0);
8727 move(circle(index + 2), width, 0);
8728
8729 } else {
8730 move(index - 1, -width, 0);
8731 }
8732
8733 move(index, slidePos[index] - width, speed);
8734 move(circle(index + 1), slidePos[circle(index + 1)] - width, speed);
8735 index = circle(index + 1);
8736
8737 } else {
8738 if (options.continuous) { // we need to get the next in this direction in place
8739
8740 move(circle(index + 1), width, 0);
8741 move(circle(index - 2), -width, 0);
8742
8743 } else {
8744 move(index + 1, width, 0);
8745 }
8746
8747 move(index, slidePos[index] + width, speed);
8748 move(circle(index - 1), slidePos[circle(index - 1)] + width, speed);
8749 index = circle(index - 1);
8750
8751 }
8752
8753 options.callback && options.callback(index, slides[index]);
8754
8755 } else {
8756
8757 if (options.continuous) {
8758
8759 move(circle(index - 1), -width, speed);
8760 move(index, 0, speed);
8761 move(circle(index + 1), width, speed);
8762
8763 } else {
8764
8765 move(index - 1, -width, speed);
8766 move(index, 0, speed);
8767 move(index + 1, width, speed);
8768 }
8769
8770 }
8771
8772 }
8773
8774 // kill touchmove and touchend event listeners until touchstart called again
8775 element.removeEventListener(touchMoveEvent, events, false);
8776 element.removeEventListener(mouseMoveEvent, events, false);
8777
8778 element.removeEventListener(touchEndEvent, events, false);
8779 element.removeEventListener(mouseEndEvent, events, false);
8780
8781 document.removeEventListener(touchEndEvent, events, false);
8782 document.removeEventListener(mouseEndEvent, events, false);
8783
8784 options.onDragEnd && options.onDragEnd();
8785 },
8786 transitionEnd: function(event) {
8787
8788 if (parseInt(event.target.getAttribute('data-index'), 10) == index) {
8789
8790 if (delay) begin();
8791
8792 options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
8793
8794 }
8795
8796 }
8797
8798 };
8799
8800 // Public API
8801 this.update = function() {
8802 setTimeout(setup);
8803 };
8804 this.setup = function() {
8805 setup();
8806 };
8807
8808 this.loop = function(value) {
8809 if (arguments.length) options.continuous = !!value;
8810 return options.continuous;
8811 };
8812
8813 this.enableSlide = function(shouldEnable) {
8814 if (arguments.length) {
8815 this.slideIsDisabled = !shouldEnable;
8816 }
8817 return !this.slideIsDisabled;
8818 };
8819
8820 this.slide = this.select = function(to, speed) {
8821 // cancel slideshow
8822 stop();
8823
8824 slide(to, speed);
8825 };
8826
8827 this.prev = this.previous = function() {
8828 // cancel slideshow
8829 stop();
8830
8831 prev();
8832 };
8833
8834 this.next = function() {
8835 // cancel slideshow
8836 stop();
8837
8838 next();
8839 };
8840
8841 this.stop = function() {
8842 // cancel slideshow
8843 stop();
8844 };
8845
8846 this.start = function() {
8847 begin();
8848 };
8849
8850 this.autoPlay = function(newDelay) {
8851 if (!delay || delay < 0) {
8852 stop();
8853 } else {
8854 delay = newDelay;
8855 begin();
8856 }
8857 };
8858
8859 this.currentIndex = this.selected = function() {
8860 // return current index position
8861 return index;
8862 };
8863
8864 this.slidesCount = this.count = function() {
8865 // return total number of slides
8866 return length;
8867 };
8868
8869 this.kill = function() {
8870 // cancel slideshow
8871 stop();
8872
8873 // reset element
8874 element.style.width = '';
8875 element.style.left = '';
8876
8877 // reset slides so no refs are held on to
8878 slides && (slides = []);
8879
8880 // removed event listeners
8881 if (browser.addEventListener) {
8882
8883 // remove current event listeners
8884 element.removeEventListener(touchStartEvent, events, false);
8885 element.removeEventListener(mouseStartEvent, events, false);
8886 element.removeEventListener('webkitTransitionEnd', events, false);
8887 element.removeEventListener('msTransitionEnd', events, false);
8888 element.removeEventListener('oTransitionEnd', events, false);
8889 element.removeEventListener('otransitionend', events, false);
8890 element.removeEventListener('transitionend', events, false);
8891 window.removeEventListener('resize', events, false);
8892
8893 }
8894 else {
8895
8896 window.onresize = null;
8897
8898 }
8899 };
8900
8901 this.load = function() {
8902 // trigger setup
8903 setup();
8904
8905 // start auto slideshow if applicable
8906 if (delay) begin();
8907
8908
8909 // add event listeners
8910 if (browser.addEventListener) {
8911
8912 // set touchstart event on element
8913 element.addEventListener(touchStartEvent, events, false);
8914 element.addEventListener(mouseStartEvent, events, false);
8915
8916 if (browser.transitions) {
8917 element.addEventListener('webkitTransitionEnd', events, false);
8918 element.addEventListener('msTransitionEnd', events, false);
8919 element.addEventListener('oTransitionEnd', events, false);
8920 element.addEventListener('otransitionend', events, false);
8921 element.addEventListener('transitionend', events, false);
8922 }
8923
8924 // set resize event on window
8925 window.addEventListener('resize', events, false);
8926
8927 } else {
8928
8929 window.onresize = function () { setup(); }; // to play nice with old IE
8930
8931 }
8932 };
8933
8934 }
8935});
8936
8937})(ionic);
8938
8939/*eslint space-after-keywords: 0*/
8940
8941/**
8942 * Swiper 3.2.7
8943 * Most modern mobile touch slider and framework with hardware accelerated transitions
8944 *
8945 * http://www.idangero.us/swiper/
8946 *
8947 * Copyright 2015, Vladimir Kharlampidi
8948 * The iDangero.us
8949 * http://www.idangero.us/
8950 *
8951 * Licensed under MIT
8952 *
8953 * Released on: December 7, 2015
8954 */
8955(function () {
8956 'use strict';
8957 var $;
8958 /*===========================
8959 Swiper
8960 ===========================*/
8961 var Swiper = function (container, params, _scope, $compile) {
8962
8963 if (!(this instanceof Swiper)) return new Swiper(container, params);
8964
8965 var defaults = {
8966 direction: 'horizontal',
8967 touchEventsTarget: 'container',
8968 initialSlide: 0,
8969 speed: 300,
8970 // autoplay
8971 autoplay: false,
8972 autoplayDisableOnInteraction: true,
8973 // To support iOS's swipe-to-go-back gesture (when being used in-app, with UIWebView).
8974 iOSEdgeSwipeDetection: false,
8975 iOSEdgeSwipeThreshold: 20,
8976 // Free mode
8977 freeMode: false,
8978 freeModeMomentum: true,
8979 freeModeMomentumRatio: 1,
8980 freeModeMomentumBounce: true,
8981 freeModeMomentumBounceRatio: 1,
8982 freeModeSticky: false,
8983 freeModeMinimumVelocity: 0.02,
8984 // Autoheight
8985 autoHeight: false,
8986 // Set wrapper width
8987 setWrapperSize: false,
8988 // Virtual Translate
8989 virtualTranslate: false,
8990 // Effects
8991 effect: 'slide', // 'slide' or 'fade' or 'cube' or 'coverflow'
8992 coverflow: {
8993 rotate: 50,
8994 stretch: 0,
8995 depth: 100,
8996 modifier: 1,
8997 slideShadows : true
8998 },
8999 cube: {
9000 slideShadows: true,
9001 shadow: true,
9002 shadowOffset: 20,
9003 shadowScale: 0.94
9004 },
9005 fade: {
9006 crossFade: false
9007 },
9008 // Parallax
9009 parallax: false,
9010 // Scrollbar
9011 scrollbar: null,
9012 scrollbarHide: true,
9013 scrollbarDraggable: false,
9014 scrollbarSnapOnRelease: false,
9015 // Keyboard Mousewheel
9016 keyboardControl: false,
9017 mousewheelControl: false,
9018 mousewheelReleaseOnEdges: false,
9019 mousewheelInvert: false,
9020 mousewheelForceToAxis: false,
9021 mousewheelSensitivity: 1,
9022 // Hash Navigation
9023 hashnav: false,
9024 // Breakpoints
9025 breakpoints: undefined,
9026 // Slides grid
9027 spaceBetween: 0,
9028 slidesPerView: 1,
9029 slidesPerColumn: 1,
9030 slidesPerColumnFill: 'column',
9031 slidesPerGroup: 1,
9032 centeredSlides: false,
9033 slidesOffsetBefore: 0, // in px
9034 slidesOffsetAfter: 0, // in px
9035 // Round length
9036 roundLengths: false,
9037 // Touches
9038 touchRatio: 1,
9039 touchAngle: 45,
9040 simulateTouch: true,
9041 shortSwipes: true,
9042 longSwipes: true,
9043 longSwipesRatio: 0.5,
9044 longSwipesMs: 300,
9045 followFinger: true,
9046 onlyExternal: false,
9047 threshold: 0,
9048 touchMoveStopPropagation: true,
9049 // Pagination
9050 pagination: null,
9051 paginationElement: 'span',
9052 paginationClickable: false,
9053 paginationHide: false,
9054 paginationBulletRender: null,
9055 // Resistance
9056 resistance: true,
9057 resistanceRatio: 0.85,
9058 // Next/prev buttons
9059 nextButton: null,
9060 prevButton: null,
9061 // Progress
9062 watchSlidesProgress: false,
9063 watchSlidesVisibility: false,
9064 // Cursor
9065 grabCursor: false,
9066 // Clicks
9067 preventClicks: true,
9068 preventClicksPropagation: true,
9069 slideToClickedSlide: false,
9070 // Lazy Loading
9071 lazyLoading: false,
9072 lazyLoadingInPrevNext: false,
9073 lazyLoadingOnTransitionStart: false,
9074 // Images
9075 preloadImages: true,
9076 updateOnImagesReady: true,
9077 // loop
9078 loop: false,
9079 loopAdditionalSlides: 0,
9080 loopedSlides: null,
9081 // Control
9082 control: undefined,
9083 controlInverse: false,
9084 controlBy: 'slide', //or 'container'
9085 // Swiping/no swiping
9086 allowSwipeToPrev: true,
9087 allowSwipeToNext: true,
9088 swipeHandler: null, //'.swipe-handler',
9089 noSwiping: true,
9090 noSwipingClass: 'swiper-no-swiping',
9091 // NS
9092 slideClass: 'swiper-slide',
9093 slideActiveClass: 'swiper-slide-active',
9094 slideVisibleClass: 'swiper-slide-visible',
9095 slideDuplicateClass: 'swiper-slide-duplicate',
9096 slideNextClass: 'swiper-slide-next',
9097 slidePrevClass: 'swiper-slide-prev',
9098 wrapperClass: 'swiper-wrapper',
9099 bulletClass: 'swiper-pagination-bullet',
9100 bulletActiveClass: 'swiper-pagination-bullet-active',
9101 buttonDisabledClass: 'swiper-button-disabled',
9102 paginationHiddenClass: 'swiper-pagination-hidden',
9103 // Observer
9104 observer: false,
9105 observeParents: false,
9106 // Accessibility
9107 a11y: false,
9108 prevSlideMessage: 'Previous slide',
9109 nextSlideMessage: 'Next slide',
9110 firstSlideMessage: 'This is the first slide',
9111 lastSlideMessage: 'This is the last slide',
9112 paginationBulletMessage: 'Go to slide {{index}}',
9113 // Callbacks
9114 runCallbacksOnInit: true
9115 /*
9116 Callbacks:
9117 onInit: function (swiper)
9118 onDestroy: function (swiper)
9119 onClick: function (swiper, e)
9120 onTap: function (swiper, e)
9121 onDoubleTap: function (swiper, e)
9122 onSliderMove: function (swiper, e)
9123 onSlideChangeStart: function (swiper)
9124 onSlideChangeEnd: function (swiper)
9125 onTransitionStart: function (swiper)
9126 onTransitionEnd: function (swiper)
9127 onImagesReady: function (swiper)
9128 onProgress: function (swiper, progress)
9129 onTouchStart: function (swiper, e)
9130 onTouchMove: function (swiper, e)
9131 onTouchMoveOpposite: function (swiper, e)
9132 onTouchEnd: function (swiper, e)
9133 onReachBeginning: function (swiper)
9134 onReachEnd: function (swiper)
9135 onSetTransition: function (swiper, duration)
9136 onSetTranslate: function (swiper, translate)
9137 onAutoplayStart: function (swiper)
9138 onAutoplayStop: function (swiper),
9139 onLazyImageLoad: function (swiper, slide, image)
9140 onLazyImageReady: function (swiper, slide, image)
9141 */
9142
9143 };
9144 var initialVirtualTranslate = params && params.virtualTranslate;
9145
9146 params = params || {};
9147 var originalParams = {};
9148 for (var param in params) {
9149 if (typeof params[param] === 'object' && !(params[param].nodeType || params[param] === window || params[param] === document || (typeof Dom7 !== 'undefined' && params[param] instanceof Dom7) || (typeof jQuery !== 'undefined' && params[param] instanceof jQuery))) {
9150 originalParams[param] = {};
9151 for (var deepParam in params[param]) {
9152 originalParams[param][deepParam] = params[param][deepParam];
9153 }
9154 }
9155 else {
9156 originalParams[param] = params[param];
9157 }
9158 }
9159 for (var def in defaults) {
9160 if (typeof params[def] === 'undefined') {
9161 params[def] = defaults[def];
9162 }
9163 else if (typeof params[def] === 'object') {
9164 for (var deepDef in defaults[def]) {
9165 if (typeof params[def][deepDef] === 'undefined') {
9166 params[def][deepDef] = defaults[def][deepDef];
9167 }
9168 }
9169 }
9170 }
9171
9172 // Swiper
9173 var s = this;
9174
9175 // Params
9176 s.params = params;
9177 s.originalParams = originalParams;
9178
9179 // Classname
9180 s.classNames = [];
9181 /*=========================
9182 Dom Library and plugins
9183 ===========================*/
9184 if (typeof $ !== 'undefined' && typeof Dom7 !== 'undefined'){
9185 $ = Dom7;
9186 }
9187 if (typeof $ === 'undefined') {
9188 if (typeof Dom7 === 'undefined') {
9189 $ = window.Dom7 || window.Zepto || window.jQuery;
9190 }
9191 else {
9192 $ = Dom7;
9193 }
9194 if (!$) return;
9195 }
9196 // Export it to Swiper instance
9197 s.$ = $;
9198
9199 /*=========================
9200 Breakpoints
9201 ===========================*/
9202 s.currentBreakpoint = undefined;
9203 s.getActiveBreakpoint = function () {
9204 //Get breakpoint for window width
9205 if (!s.params.breakpoints) return false;
9206 var breakpoint = false;
9207 var points = [], point;
9208 for ( point in s.params.breakpoints ) {
9209 if (s.params.breakpoints.hasOwnProperty(point)) {
9210 points.push(point);
9211 }
9212 }
9213 points.sort(function (a, b) {
9214 return parseInt(a, 10) > parseInt(b, 10);
9215 });
9216 for (var i = 0; i < points.length; i++) {
9217 point = points[i];
9218 if (point >= window.innerWidth && !breakpoint) {
9219 breakpoint = point;
9220 }
9221 }
9222 return breakpoint || 'max';
9223 };
9224 s.setBreakpoint = function () {
9225 //Set breakpoint for window width and update parameters
9226 var breakpoint = s.getActiveBreakpoint();
9227 if (breakpoint && s.currentBreakpoint !== breakpoint) {
9228 var breakPointsParams = breakpoint in s.params.breakpoints ? s.params.breakpoints[breakpoint] : s.originalParams;
9229 for ( var param in breakPointsParams ) {
9230 s.params[param] = breakPointsParams[param];
9231 }
9232 s.currentBreakpoint = breakpoint;
9233 }
9234 };
9235 // Set breakpoint on load
9236 if (s.params.breakpoints) {
9237 s.setBreakpoint();
9238 }
9239
9240 /*=========================
9241 Preparation - Define Container, Wrapper and Pagination
9242 ===========================*/
9243 s.container = $(container);
9244 if (s.container.length === 0) return;
9245 if (s.container.length > 1) {
9246 s.container.each(function () {
9247 new Swiper(this, params);
9248 });
9249 return;
9250 }
9251
9252 // Save instance in container HTML Element and in data
9253 s.container[0].swiper = s;
9254 s.container.data('swiper', s);
9255
9256 s.classNames.push('swiper-container-' + s.params.direction);
9257
9258 if (s.params.freeMode) {
9259 s.classNames.push('swiper-container-free-mode');
9260 }
9261 if (!s.support.flexbox) {
9262 s.classNames.push('swiper-container-no-flexbox');
9263 s.params.slidesPerColumn = 1;
9264 }
9265 if (s.params.autoHeight) {
9266 s.classNames.push('swiper-container-autoheight');
9267 }
9268 // Enable slides progress when required
9269 if (s.params.parallax || s.params.watchSlidesVisibility) {
9270 s.params.watchSlidesProgress = true;
9271 }
9272 // Coverflow / 3D
9273 if (['cube', 'coverflow'].indexOf(s.params.effect) >= 0) {
9274 if (s.support.transforms3d) {
9275 s.params.watchSlidesProgress = true;
9276 s.classNames.push('swiper-container-3d');
9277 }
9278 else {
9279 s.params.effect = 'slide';
9280 }
9281 }
9282 if (s.params.effect !== 'slide') {
9283 s.classNames.push('swiper-container-' + s.params.effect);
9284 }
9285 if (s.params.effect === 'cube') {
9286 s.params.resistanceRatio = 0;
9287 s.params.slidesPerView = 1;
9288 s.params.slidesPerColumn = 1;
9289 s.params.slidesPerGroup = 1;
9290 s.params.centeredSlides = false;
9291 s.params.spaceBetween = 0;
9292 s.params.virtualTranslate = true;
9293 s.params.setWrapperSize = false;
9294 }
9295 if (s.params.effect === 'fade') {
9296 s.params.slidesPerView = 1;
9297 s.params.slidesPerColumn = 1;
9298 s.params.slidesPerGroup = 1;
9299 s.params.watchSlidesProgress = true;
9300 s.params.spaceBetween = 0;
9301 if (typeof initialVirtualTranslate === 'undefined') {
9302 s.params.virtualTranslate = true;
9303 }
9304 }
9305
9306 // Grab Cursor
9307 if (s.params.grabCursor && s.support.touch) {
9308 s.params.grabCursor = false;
9309 }
9310
9311 // Wrapper
9312 s.wrapper = s.container.children('.' + s.params.wrapperClass);
9313
9314 // Pagination
9315 if (s.params.pagination) {
9316 s.paginationContainer = $(s.params.pagination);
9317 if (s.params.paginationClickable) {
9318 s.paginationContainer.addClass('swiper-pagination-clickable');
9319 }
9320 }
9321
9322 // Is Horizontal
9323 function isH() {
9324 return s.params.direction === 'horizontal';
9325 }
9326
9327 // RTL
9328 s.rtl = isH() && (s.container[0].dir.toLowerCase() === 'rtl' || s.container.css('direction') === 'rtl');
9329 if (s.rtl) {
9330 s.classNames.push('swiper-container-rtl');
9331 }
9332
9333 // Wrong RTL support
9334 if (s.rtl) {
9335 s.wrongRTL = s.wrapper.css('display') === '-webkit-box';
9336 }
9337
9338 // Columns
9339 if (s.params.slidesPerColumn > 1) {
9340 s.classNames.push('swiper-container-multirow');
9341 }
9342
9343 // Check for Android
9344 if (s.device.android) {
9345 s.classNames.push('swiper-container-android');
9346 }
9347
9348 // Add classes
9349 s.container.addClass(s.classNames.join(' '));
9350
9351 // Translate
9352 s.translate = 0;
9353
9354 // Progress
9355 s.progress = 0;
9356
9357 // Velocity
9358 s.velocity = 0;
9359
9360 /*=========================
9361 Locks, unlocks
9362 ===========================*/
9363 s.lockSwipeToNext = function () {
9364 s.params.allowSwipeToNext = false;
9365 };
9366 s.lockSwipeToPrev = function () {
9367 s.params.allowSwipeToPrev = false;
9368 };
9369 s.lockSwipes = function () {
9370 s.params.allowSwipeToNext = s.params.allowSwipeToPrev = false;
9371 };
9372 s.unlockSwipeToNext = function () {
9373 s.params.allowSwipeToNext = true;
9374 };
9375 s.unlockSwipeToPrev = function () {
9376 s.params.allowSwipeToPrev = true;
9377 };
9378 s.unlockSwipes = function () {
9379 s.params.allowSwipeToNext = s.params.allowSwipeToPrev = true;
9380 };
9381
9382 /*=========================
9383 Round helper
9384 ===========================*/
9385 function round(a) {
9386 return Math.floor(a);
9387 }
9388 /*=========================
9389 Set grab cursor
9390 ===========================*/
9391 if (s.params.grabCursor) {
9392 s.container[0].style.cursor = 'move';
9393 s.container[0].style.cursor = '-webkit-grab';
9394 s.container[0].style.cursor = '-moz-grab';
9395 s.container[0].style.cursor = 'grab';
9396 }
9397 /*=========================
9398 Update on Images Ready
9399 ===========================*/
9400 s.imagesToLoad = [];
9401 s.imagesLoaded = 0;
9402
9403 s.loadImage = function (imgElement, src, srcset, checkForComplete, callback) {
9404 var image;
9405 function onReady () {
9406 if (callback) callback();
9407 }
9408 if (!imgElement.complete || !checkForComplete) {
9409 if (src) {
9410 image = new window.Image();
9411 image.onload = onReady;
9412 image.onerror = onReady;
9413 if (srcset) {
9414 image.srcset = srcset;
9415 }
9416 if (src) {
9417 image.src = src;
9418 }
9419 } else {
9420 onReady();
9421 }
9422
9423 } else {//image already loaded...
9424 onReady();
9425 }
9426 };
9427 s.preloadImages = function () {
9428 s.imagesToLoad = s.container.find('img');
9429 function _onReady() {
9430 if (typeof s === 'undefined' || s === null) return;
9431 if (s.imagesLoaded !== undefined) s.imagesLoaded++;
9432 if (s.imagesLoaded === s.imagesToLoad.length) {
9433 if (s.params.updateOnImagesReady) s.update();
9434 s.emit('onImagesReady', s);
9435 }
9436 }
9437 for (var i = 0; i < s.imagesToLoad.length; i++) {
9438 s.loadImage(s.imagesToLoad[i], (s.imagesToLoad[i].currentSrc || s.imagesToLoad[i].getAttribute('src')), (s.imagesToLoad[i].srcset || s.imagesToLoad[i].getAttribute('srcset')), true, _onReady);
9439 }
9440 };
9441
9442 /*=========================
9443 Autoplay
9444 ===========================*/
9445 s.autoplayTimeoutId = undefined;
9446 s.autoplaying = false;
9447 s.autoplayPaused = false;
9448 function autoplay() {
9449 s.autoplayTimeoutId = setTimeout(function () {
9450 if (s.params.loop) {
9451 s.fixLoop();
9452 s._slideNext();
9453 }
9454 else {
9455 if (!s.isEnd) {
9456 s._slideNext();
9457 }
9458 else {
9459 if (!params.autoplayStopOnLast) {
9460 s._slideTo(0);
9461 }
9462 else {
9463 s.stopAutoplay();
9464 }
9465 }
9466 }
9467 }, s.params.autoplay);
9468 }
9469 s.startAutoplay = function () {
9470 if (typeof s.autoplayTimeoutId !== 'undefined') return false;
9471 if (!s.params.autoplay) return false;
9472 if (s.autoplaying) return false;
9473 s.autoplaying = true;
9474 s.emit('onAutoplayStart', s);
9475 autoplay();
9476 };
9477 s.stopAutoplay = function (internal) {
9478 if (!s.autoplayTimeoutId) return;
9479 if (s.autoplayTimeoutId) clearTimeout(s.autoplayTimeoutId);
9480 s.autoplaying = false;
9481 s.autoplayTimeoutId = undefined;
9482 s.emit('onAutoplayStop', s);
9483 };
9484 s.pauseAutoplay = function (speed) {
9485 if (s.autoplayPaused) return;
9486 if (s.autoplayTimeoutId) clearTimeout(s.autoplayTimeoutId);
9487 s.autoplayPaused = true;
9488 if (speed === 0) {
9489 s.autoplayPaused = false;
9490 autoplay();
9491 }
9492 else {
9493 s.wrapper.transitionEnd(function () {
9494 if (!s) return;
9495 s.autoplayPaused = false;
9496 if (!s.autoplaying) {
9497 s.stopAutoplay();
9498 }
9499 else {
9500 autoplay();
9501 }
9502 });
9503 }
9504 };
9505 /*=========================
9506 Min/Max Translate
9507 ===========================*/
9508 s.minTranslate = function () {
9509 return (-s.snapGrid[0]);
9510 };
9511 s.maxTranslate = function () {
9512 return (-s.snapGrid[s.snapGrid.length - 1]);
9513 };
9514 /*=========================
9515 Slider/slides sizes
9516 ===========================*/
9517 s.updateAutoHeight = function () {
9518 // Update Height
9519 var newHeight = s.slides.eq(s.activeIndex)[0].offsetHeight;
9520 if (newHeight) s.wrapper.css('height', s.slides.eq(s.activeIndex)[0].offsetHeight + 'px');
9521 };
9522 s.updateContainerSize = function () {
9523 var width, height;
9524 if (typeof s.params.width !== 'undefined') {
9525 width = s.params.width;
9526 }
9527 else {
9528 width = s.container[0].clientWidth;
9529 }
9530 if (typeof s.params.height !== 'undefined') {
9531 height = s.params.height;
9532 }
9533 else {
9534 height = s.container[0].clientHeight;
9535 }
9536 if (width === 0 && isH() || height === 0 && !isH()) {
9537 return;
9538 }
9539
9540 //Subtract paddings
9541 width = width - parseInt(s.container.css('padding-left'), 10) - parseInt(s.container.css('padding-right'), 10);
9542 height = height - parseInt(s.container.css('padding-top'), 10) - parseInt(s.container.css('padding-bottom'), 10);
9543
9544 // Store values
9545 s.width = width;
9546 s.height = height;
9547 s.size = isH() ? s.width : s.height;
9548 };
9549
9550 s.updateSlidesSize = function () {
9551 s.slides = s.wrapper.children('.' + s.params.slideClass);
9552 s.snapGrid = [];
9553 s.slidesGrid = [];
9554 s.slidesSizesGrid = [];
9555
9556 var spaceBetween = s.params.spaceBetween,
9557 slidePosition = -s.params.slidesOffsetBefore,
9558 i,
9559 prevSlideSize = 0,
9560 index = 0;
9561 if (typeof spaceBetween === 'string' && spaceBetween.indexOf('%') >= 0) {
9562 spaceBetween = parseFloat(spaceBetween.replace('%', '')) / 100 * s.size;
9563 }
9564
9565 s.virtualSize = -spaceBetween;
9566 // reset margins
9567 if (s.rtl) s.slides.css({marginLeft: '', marginTop: ''});
9568 else s.slides.css({marginRight: '', marginBottom: ''});
9569
9570 var slidesNumberEvenToRows;
9571 if (s.params.slidesPerColumn > 1) {
9572 if (Math.floor(s.slides.length / s.params.slidesPerColumn) === s.slides.length / s.params.slidesPerColumn) {
9573 slidesNumberEvenToRows = s.slides.length;
9574 }
9575 else {
9576 slidesNumberEvenToRows = Math.ceil(s.slides.length / s.params.slidesPerColumn) * s.params.slidesPerColumn;
9577 }
9578 if (s.params.slidesPerView !== 'auto' && s.params.slidesPerColumnFill === 'row') {
9579 slidesNumberEvenToRows = Math.max(slidesNumberEvenToRows, s.params.slidesPerView * s.params.slidesPerColumn);
9580 }
9581 }
9582
9583 // Calc slides
9584 var slideSize;
9585 var slidesPerColumn = s.params.slidesPerColumn;
9586 var slidesPerRow = slidesNumberEvenToRows / slidesPerColumn;
9587 var numFullColumns = slidesPerRow - (s.params.slidesPerColumn * slidesPerRow - s.slides.length);
9588 for (i = 0; i < s.slides.length; i++) {
9589 slideSize = 0;
9590 var slide = s.slides.eq(i);
9591 if (s.params.slidesPerColumn > 1) {
9592 // Set slides order
9593 var newSlideOrderIndex;
9594 var column, row;
9595 if (s.params.slidesPerColumnFill === 'column') {
9596 column = Math.floor(i / slidesPerColumn);
9597 row = i - column * slidesPerColumn;
9598 if (column > numFullColumns || (column === numFullColumns && row === slidesPerColumn-1)) {
9599 if (++row >= slidesPerColumn) {
9600 row = 0;
9601 column++;
9602 }
9603 }
9604 newSlideOrderIndex = column + row * slidesNumberEvenToRows / slidesPerColumn;
9605 slide
9606 .css({
9607 '-webkit-box-ordinal-group': newSlideOrderIndex,
9608 '-moz-box-ordinal-group': newSlideOrderIndex,
9609 '-ms-flex-order': newSlideOrderIndex,
9610 '-webkit-order': newSlideOrderIndex,
9611 'order': newSlideOrderIndex
9612 });
9613 }
9614 else {
9615 row = Math.floor(i / slidesPerRow);
9616 column = i - row * slidesPerRow;
9617 }
9618 slide
9619 .css({
9620 'margin-top': (row !== 0 && s.params.spaceBetween) && (s.params.spaceBetween + 'px')
9621 })
9622 .attr('data-swiper-column', column)
9623 .attr('data-swiper-row', row);
9624
9625 }
9626 if (slide.css('display') === 'none') continue;
9627 if (s.params.slidesPerView === 'auto') {
9628 slideSize = isH() ? slide.outerWidth(true) : slide.outerHeight(true);
9629 if (s.params.roundLengths) slideSize = round(slideSize);
9630 }
9631 else {
9632 slideSize = (s.size - (s.params.slidesPerView - 1) * spaceBetween) / s.params.slidesPerView;
9633 if (s.params.roundLengths) slideSize = round(slideSize);
9634
9635 if (isH()) {
9636 s.slides[i].style.width = slideSize + 'px';
9637 }
9638 else {
9639 s.slides[i].style.height = slideSize + 'px';
9640 }
9641 }
9642 s.slides[i].swiperSlideSize = slideSize;
9643 s.slidesSizesGrid.push(slideSize);
9644
9645
9646 if (s.params.centeredSlides) {
9647 slidePosition = slidePosition + slideSize / 2 + prevSlideSize / 2 + spaceBetween;
9648 if (i === 0) slidePosition = slidePosition - s.size / 2 - spaceBetween;
9649 if (Math.abs(slidePosition) < 1 / 1000) slidePosition = 0;
9650 if ((index) % s.params.slidesPerGroup === 0) s.snapGrid.push(slidePosition);
9651 s.slidesGrid.push(slidePosition);
9652 }
9653 else {
9654 if ((index) % s.params.slidesPerGroup === 0) s.snapGrid.push(slidePosition);
9655 s.slidesGrid.push(slidePosition);
9656 slidePosition = slidePosition + slideSize + spaceBetween;
9657 }
9658
9659 s.virtualSize += slideSize + spaceBetween;
9660
9661 prevSlideSize = slideSize;
9662
9663 index ++;
9664 }
9665 s.virtualSize = Math.max(s.virtualSize, s.size) + s.params.slidesOffsetAfter;
9666 var newSlidesGrid;
9667
9668 if (
9669 s.rtl && s.wrongRTL && (s.params.effect === 'slide' || s.params.effect === 'coverflow')) {
9670 s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'});
9671 }
9672 if (!s.support.flexbox || s.params.setWrapperSize) {
9673 if (isH()) s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'});
9674 else s.wrapper.css({height: s.virtualSize + s.params.spaceBetween + 'px'});
9675 }
9676
9677 if (s.params.slidesPerColumn > 1) {
9678 s.virtualSize = (slideSize + s.params.spaceBetween) * slidesNumberEvenToRows;
9679 s.virtualSize = Math.ceil(s.virtualSize / s.params.slidesPerColumn) - s.params.spaceBetween;
9680 s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'});
9681 if (s.params.centeredSlides) {
9682 newSlidesGrid = [];
9683 for (i = 0; i < s.snapGrid.length; i++) {
9684 if (s.snapGrid[i] < s.virtualSize + s.snapGrid[0]) newSlidesGrid.push(s.snapGrid[i]);
9685 }
9686 s.snapGrid = newSlidesGrid;
9687 }
9688 }
9689
9690 // Remove last grid elements depending on width
9691 if (!s.params.centeredSlides) {
9692 newSlidesGrid = [];
9693 for (i = 0; i < s.snapGrid.length; i++) {
9694 if (s.snapGrid[i] <= s.virtualSize - s.size) {
9695 newSlidesGrid.push(s.snapGrid[i]);
9696 }
9697 }
9698 s.snapGrid = newSlidesGrid;
9699 if (Math.floor(s.virtualSize - s.size) > Math.floor(s.snapGrid[s.snapGrid.length - 1])) {
9700 s.snapGrid.push(s.virtualSize - s.size);
9701 }
9702 }
9703 if (s.snapGrid.length === 0) s.snapGrid = [0];
9704
9705 if (s.params.spaceBetween !== 0) {
9706 if (isH()) {
9707 if (s.rtl) s.slides.css({marginLeft: spaceBetween + 'px'});
9708 else s.slides.css({marginRight: spaceBetween + 'px'});
9709 }
9710 else s.slides.css({marginBottom: spaceBetween + 'px'});
9711 }
9712 if (s.params.watchSlidesProgress) {
9713 s.updateSlidesOffset();
9714 }
9715 };
9716 s.updateSlidesOffset = function () {
9717 for (var i = 0; i < s.slides.length; i++) {
9718 s.slides[i].swiperSlideOffset = isH() ? s.slides[i].offsetLeft : s.slides[i].offsetTop;
9719 }
9720 };
9721
9722 /*=========================
9723 Slider/slides progress
9724 ===========================*/
9725 s.updateSlidesProgress = function (translate) {
9726 if (typeof translate === 'undefined') {
9727 translate = s.translate || 0;
9728 }
9729 if (s.slides.length === 0) return;
9730 if (typeof s.slides[0].swiperSlideOffset === 'undefined') s.updateSlidesOffset();
9731
9732 var offsetCenter = -translate;
9733 if (s.rtl) offsetCenter = translate;
9734
9735 // Visible Slides
9736 s.slides.removeClass(s.params.slideVisibleClass);
9737 for (var i = 0; i < s.slides.length; i++) {
9738 var slide = s.slides[i];
9739 var slideProgress = (offsetCenter - slide.swiperSlideOffset) / (slide.swiperSlideSize + s.params.spaceBetween);
9740 if (s.params.watchSlidesVisibility) {
9741 var slideBefore = -(offsetCenter - slide.swiperSlideOffset);
9742 var slideAfter = slideBefore + s.slidesSizesGrid[i];
9743 var isVisible =
9744 (slideBefore >= 0 && slideBefore < s.size) ||
9745 (slideAfter > 0 && slideAfter <= s.size) ||
9746 (slideBefore <= 0 && slideAfter >= s.size);
9747 if (isVisible) {
9748 s.slides.eq(i).addClass(s.params.slideVisibleClass);
9749 }
9750 }
9751 slide.progress = s.rtl ? -slideProgress : slideProgress;
9752 }
9753 };
9754 s.updateProgress = function (translate) {
9755 if (typeof translate === 'undefined') {
9756 translate = s.translate || 0;
9757 }
9758 var translatesDiff = s.maxTranslate() - s.minTranslate();
9759 var wasBeginning = s.isBeginning;
9760 var wasEnd = s.isEnd;
9761 if (translatesDiff === 0) {
9762 s.progress = 0;
9763 s.isBeginning = s.isEnd = true;
9764 }
9765 else {
9766 s.progress = (translate - s.minTranslate()) / (translatesDiff);
9767 s.isBeginning = s.progress <= 0;
9768 s.isEnd = s.progress >= 1;
9769 }
9770 if (s.isBeginning && !wasBeginning) s.emit('onReachBeginning', s);
9771 if (s.isEnd && !wasEnd) s.emit('onReachEnd', s);
9772
9773 if (s.params.watchSlidesProgress) s.updateSlidesProgress(translate);
9774 s.emit('onProgress', s, s.progress);
9775 };
9776 s.updateActiveIndex = function () {
9777 var translate = s.rtl ? s.translate : -s.translate;
9778 var newActiveIndex, i, snapIndex;
9779 for (i = 0; i < s.slidesGrid.length; i ++) {
9780 if (typeof s.slidesGrid[i + 1] !== 'undefined') {
9781 if (translate >= s.slidesGrid[i] && translate < s.slidesGrid[i + 1] - (s.slidesGrid[i + 1] - s.slidesGrid[i]) / 2) {
9782 newActiveIndex = i;
9783 }
9784 else if (translate >= s.slidesGrid[i] && translate < s.slidesGrid[i + 1]) {
9785 newActiveIndex = i + 1;
9786 }
9787 }
9788 else {
9789 if (translate >= s.slidesGrid[i]) {
9790 newActiveIndex = i;
9791 }
9792 }
9793 }
9794 // Normalize slideIndex
9795 if (newActiveIndex < 0 || typeof newActiveIndex === 'undefined') newActiveIndex = 0;
9796 // for (i = 0; i < s.slidesGrid.length; i++) {
9797 // if (- translate >= s.slidesGrid[i]) {
9798 // newActiveIndex = i;
9799 // }
9800 // }
9801 snapIndex = Math.floor(newActiveIndex / s.params.slidesPerGroup);
9802 if (snapIndex >= s.snapGrid.length) snapIndex = s.snapGrid.length - 1;
9803
9804 if (newActiveIndex === s.activeIndex) {
9805 return;
9806 }
9807 s.snapIndex = snapIndex;
9808 s.previousIndex = s.activeIndex;
9809 s.activeIndex = newActiveIndex;
9810 s.updateClasses();
9811 };
9812
9813 /*=========================
9814 Classes
9815 ===========================*/
9816 s.updateClasses = function () {
9817 s.slides.removeClass(s.params.slideActiveClass + ' ' + s.params.slideNextClass + ' ' + s.params.slidePrevClass);
9818 var activeSlide = s.slides.eq(s.activeIndex);
9819 // Active classes
9820 activeSlide.addClass(s.params.slideActiveClass);
9821 activeSlide.next('.' + s.params.slideClass).addClass(s.params.slideNextClass);
9822 activeSlide.prev('.' + s.params.slideClass).addClass(s.params.slidePrevClass);
9823
9824 // Pagination
9825 if (s.bullets && s.bullets.length > 0) {
9826 s.bullets.removeClass(s.params.bulletActiveClass);
9827 var bulletIndex;
9828 if (s.params.loop) {
9829 bulletIndex = Math.ceil(s.activeIndex - s.loopedSlides)/s.params.slidesPerGroup;
9830 if (bulletIndex > s.slides.length - 1 - s.loopedSlides * 2) {
9831 bulletIndex = bulletIndex - (s.slides.length - s.loopedSlides * 2);
9832 }
9833 if (bulletIndex > s.bullets.length - 1) bulletIndex = bulletIndex - s.bullets.length;
9834 }
9835 else {
9836 if (typeof s.snapIndex !== 'undefined') {
9837 bulletIndex = s.snapIndex;
9838 }
9839 else {
9840 bulletIndex = s.activeIndex || 0;
9841 }
9842 }
9843 if (s.paginationContainer.length > 1) {
9844 s.bullets.each(function () {
9845 if ($(this).index() === bulletIndex) $(this).addClass(s.params.bulletActiveClass);
9846 });
9847 }
9848 else {
9849 s.bullets.eq(bulletIndex).addClass(s.params.bulletActiveClass);
9850 }
9851 }
9852
9853 // Next/active buttons
9854 if (!s.params.loop) {
9855 if (s.params.prevButton) {
9856 if (s.isBeginning) {
9857 $(s.params.prevButton).addClass(s.params.buttonDisabledClass);
9858 if (s.params.a11y && s.a11y) s.a11y.disable($(s.params.prevButton));
9859 }
9860 else {
9861 $(s.params.prevButton).removeClass(s.params.buttonDisabledClass);
9862 if (s.params.a11y && s.a11y) s.a11y.enable($(s.params.prevButton));
9863 }
9864 }
9865 if (s.params.nextButton) {
9866 if (s.isEnd) {
9867 $(s.params.nextButton).addClass(s.params.buttonDisabledClass);
9868 if (s.params.a11y && s.a11y) s.a11y.disable($(s.params.nextButton));
9869 }
9870 else {
9871 $(s.params.nextButton).removeClass(s.params.buttonDisabledClass);
9872 if (s.params.a11y && s.a11y) s.a11y.enable($(s.params.nextButton));
9873 }
9874 }
9875 }
9876 };
9877
9878 /*=========================
9879 Pagination
9880 ===========================*/
9881 s.updatePagination = function () {
9882 if (!s.params.pagination) return;
9883 if (s.paginationContainer && s.paginationContainer.length > 0) {
9884 var bulletsHTML = '';
9885 var numberOfBullets = s.params.loop ? Math.ceil((s.slides.length - s.loopedSlides * 2) / s.params.slidesPerGroup) : s.snapGrid.length;
9886 for (var i = 0; i < numberOfBullets; i++) {
9887 if (s.params.paginationBulletRender) {
9888 bulletsHTML += s.params.paginationBulletRender(i, s.params.bulletClass);
9889 }
9890 else {
9891 bulletsHTML += '<' + s.params.paginationElement+' class="' + s.params.bulletClass + '"></' + s.params.paginationElement + '>';
9892 }
9893 }
9894 s.paginationContainer.html(bulletsHTML);
9895 s.bullets = s.paginationContainer.find('.' + s.params.bulletClass);
9896 if (s.params.paginationClickable && s.params.a11y && s.a11y) {
9897 s.a11y.initPagination();
9898 }
9899 }
9900 };
9901 /*=========================
9902 Common update method
9903 ===========================*/
9904 s.update = function (updateTranslate) {
9905 s.updateContainerSize();
9906 s.updateSlidesSize();
9907 s.updateProgress();
9908 s.updatePagination();
9909 s.updateClasses();
9910 if (s.params.scrollbar && s.scrollbar) {
9911 s.scrollbar.set();
9912 }
9913 function forceSetTranslate() {
9914 newTranslate = Math.min(Math.max(s.translate, s.maxTranslate()), s.minTranslate());
9915 s.setWrapperTranslate(newTranslate);
9916 s.updateActiveIndex();
9917 s.updateClasses();
9918 }
9919 if (updateTranslate) {
9920 var translated, newTranslate;
9921 if (s.controller && s.controller.spline) {
9922 s.controller.spline = undefined;
9923 }
9924 if (s.params.freeMode) {
9925 forceSetTranslate();
9926 if (s.params.autoHeight) {
9927 s.updateAutoHeight();
9928 }
9929 }
9930 else {
9931 if ((s.params.slidesPerView === 'auto' || s.params.slidesPerView > 1) && s.isEnd && !s.params.centeredSlides) {
9932 translated = s.slideTo(s.slides.length - 1, 0, false, true);
9933 }
9934 else {
9935 translated = s.slideTo(s.activeIndex, 0, false, true);
9936 }
9937 if (!translated) {
9938 forceSetTranslate();
9939 }
9940 }
9941 }
9942 else if (s.params.autoHeight) {
9943 s.updateAutoHeight();
9944 }
9945 };
9946
9947 /*=========================
9948 Resize Handler
9949 ===========================*/
9950 s.onResize = function (forceUpdatePagination) {
9951 //Breakpoints
9952 if (s.params.breakpoints) {
9953 s.setBreakpoint();
9954 }
9955
9956 // Disable locks on resize
9957 var allowSwipeToPrev = s.params.allowSwipeToPrev;
9958 var allowSwipeToNext = s.params.allowSwipeToNext;
9959 s.params.allowSwipeToPrev = s.params.allowSwipeToNext = true;
9960
9961 s.updateContainerSize();
9962 s.updateSlidesSize();
9963 if (s.params.slidesPerView === 'auto' || s.params.freeMode || forceUpdatePagination) s.updatePagination();
9964 if (s.params.scrollbar && s.scrollbar) {
9965 s.scrollbar.set();
9966 }
9967 if (s.controller && s.controller.spline) {
9968 s.controller.spline = undefined;
9969 }
9970 if (s.params.freeMode) {
9971 var newTranslate = Math.min(Math.max(s.translate, s.maxTranslate()), s.minTranslate());
9972 s.setWrapperTranslate(newTranslate);
9973 s.updateActiveIndex();
9974 s.updateClasses();
9975
9976 if (s.params.autoHeight) {
9977 s.updateAutoHeight();
9978 }
9979 }
9980 else {
9981 s.updateClasses();
9982 if ((s.params.slidesPerView === 'auto' || s.params.slidesPerView > 1) && s.isEnd && !s.params.centeredSlides) {
9983 s.slideTo(s.slides.length - 1, 0, false, true);
9984 }
9985 else {
9986 s.slideTo(s.activeIndex, 0, false, true);
9987 }
9988 }
9989 // Return locks after resize
9990 s.params.allowSwipeToPrev = allowSwipeToPrev;
9991 s.params.allowSwipeToNext = allowSwipeToNext;
9992 };
9993
9994 /*=========================
9995 Events
9996 ===========================*/
9997
9998 //Define Touch Events
9999 var desktopEvents = ['mousedown', 'mousemove', 'mouseup'];
10000 if (window.navigator.pointerEnabled) desktopEvents = ['pointerdown', 'pointermove', 'pointerup'];
10001 else if (window.navigator.msPointerEnabled) desktopEvents = ['MSPointerDown', 'MSPointerMove', 'MSPointerUp'];
10002 s.touchEvents = {
10003 start : s.support.touch || !s.params.simulateTouch ? 'touchstart' : desktopEvents[0],
10004 move : s.support.touch || !s.params.simulateTouch ? 'touchmove' : desktopEvents[1],
10005 end : s.support.touch || !s.params.simulateTouch ? 'touchend' : desktopEvents[2]
10006 };
10007
10008
10009 // WP8 Touch Events Fix
10010 if (window.navigator.pointerEnabled || window.navigator.msPointerEnabled) {
10011 (s.params.touchEventsTarget === 'container' ? s.container : s.wrapper).addClass('swiper-wp8-' + s.params.direction);
10012 }
10013
10014 // Attach/detach events
10015 s.initEvents = function (detach) {
10016 var actionDom = detach ? 'off' : 'on';
10017 var action = detach ? 'removeEventListener' : 'addEventListener';
10018 var touchEventsTarget = s.params.touchEventsTarget === 'container' ? s.container[0] : s.wrapper[0];
10019 var target = s.support.touch ? touchEventsTarget : document;
10020
10021 var moveCapture = s.params.nested ? true : false;
10022
10023 //Touch Events
10024 if (s.browser.ie) {
10025 touchEventsTarget[action](s.touchEvents.start, s.onTouchStart, false);
10026 target[action](s.touchEvents.move, s.onTouchMove, moveCapture);
10027 target[action](s.touchEvents.end, s.onTouchEnd, false);
10028 }
10029 else {
10030 if (s.support.touch) {
10031 touchEventsTarget[action](s.touchEvents.start, s.onTouchStart, false);
10032 touchEventsTarget[action](s.touchEvents.move, s.onTouchMove, moveCapture);
10033 touchEventsTarget[action](s.touchEvents.end, s.onTouchEnd, false);
10034 }
10035 if (params.simulateTouch && !s.device.ios && !s.device.android) {
10036 touchEventsTarget[action]('mousedown', s.onTouchStart, false);
10037 document[action]('mousemove', s.onTouchMove, moveCapture);
10038 document[action]('mouseup', s.onTouchEnd, false);
10039 }
10040 }
10041 window[action]('resize', s.onResize);
10042
10043 // Next, Prev, Index
10044 if (s.params.nextButton) {
10045 $(s.params.nextButton)[actionDom]('click', s.onClickNext);
10046 if (s.params.a11y && s.a11y) $(s.params.nextButton)[actionDom]('keydown', s.a11y.onEnterKey);
10047 }
10048 if (s.params.prevButton) {
10049 $(s.params.prevButton)[actionDom]('click', s.onClickPrev);
10050 if (s.params.a11y && s.a11y) $(s.params.prevButton)[actionDom]('keydown', s.a11y.onEnterKey);
10051 }
10052 if (s.params.pagination && s.params.paginationClickable) {
10053 $(s.paginationContainer)[actionDom]('click', '.' + s.params.bulletClass, s.onClickIndex);
10054 if (s.params.a11y && s.a11y) $(s.paginationContainer)[actionDom]('keydown', '.' + s.params.bulletClass, s.a11y.onEnterKey);
10055 }
10056
10057 // Prevent Links Clicks
10058 if (s.params.preventClicks || s.params.preventClicksPropagation) touchEventsTarget[action]('click', s.preventClicks, true);
10059 };
10060 s.attachEvents = function (detach) {
10061 s.initEvents();
10062 };
10063 s.detachEvents = function () {
10064 s.initEvents(true);
10065 };
10066
10067 /*=========================
10068 Handle Clicks
10069 ===========================*/
10070 // Prevent Clicks
10071 s.allowClick = true;
10072 s.preventClicks = function (e) {
10073 if (!s.allowClick) {
10074 if (s.params.preventClicks) e.preventDefault();
10075 if (s.params.preventClicksPropagation && s.animating) {
10076 e.stopPropagation();
10077 e.stopImmediatePropagation();
10078 }
10079 }
10080 };
10081 // Clicks
10082 s.onClickNext = function (e) {
10083 e.preventDefault();
10084 if (s.isEnd && !s.params.loop) return;
10085 s.slideNext();
10086 };
10087 s.onClickPrev = function (e) {
10088 e.preventDefault();
10089 if (s.isBeginning && !s.params.loop) return;
10090 s.slidePrev();
10091 };
10092 s.onClickIndex = function (e) {
10093 e.preventDefault();
10094 var index = $(this).index() * s.params.slidesPerGroup;
10095 if (s.params.loop) index = index + s.loopedSlides;
10096 s.slideTo(index);
10097 };
10098
10099 /*=========================
10100 Handle Touches
10101 ===========================*/
10102 function findElementInEvent(e, selector) {
10103 var el = $(e.target);
10104 if (!el.is(selector)) {
10105 if (typeof selector === 'string') {
10106 el = el.parents(selector);
10107 }
10108 else if (selector.nodeType) {
10109 var found;
10110 el.parents().each(function (index, _el) {
10111 if (_el === selector) found = selector;
10112 });
10113 if (!found) return undefined;
10114 else return selector;
10115 }
10116 }
10117 if (el.length === 0) {
10118 return undefined;
10119 }
10120 return el[0];
10121 }
10122 s.updateClickedSlide = function (e) {
10123 var slide = findElementInEvent(e, '.' + s.params.slideClass);
10124 var slideFound = false;
10125 if (slide) {
10126 for (var i = 0; i < s.slides.length; i++) {
10127 if (s.slides[i] === slide) slideFound = true;
10128 }
10129 }
10130
10131 if (slide && slideFound) {
10132 s.clickedSlide = slide;
10133 s.clickedIndex = $(slide).index();
10134 }
10135 else {
10136 s.clickedSlide = undefined;
10137 s.clickedIndex = undefined;
10138 return;
10139 }
10140 if (s.params.slideToClickedSlide && s.clickedIndex !== undefined && s.clickedIndex !== s.activeIndex) {
10141 var slideToIndex = s.clickedIndex,
10142 realIndex,
10143 duplicatedSlides;
10144 if (s.params.loop) {
10145 if (s.animating) return;
10146 realIndex = $(s.clickedSlide).attr('data-swiper-slide-index');
10147 if (s.params.centeredSlides) {
10148 if ((slideToIndex < s.loopedSlides - s.params.slidesPerView/2) || (slideToIndex > s.slides.length - s.loopedSlides + s.params.slidesPerView/2)) {
10149 s.fixLoop();
10150 slideToIndex = s.wrapper.children('.' + s.params.slideClass + '[data-swiper-slide-index="' + realIndex + '"]:not(.swiper-slide-duplicate)').eq(0).index();
10151 setTimeout(function () {
10152 s.slideTo(slideToIndex);
10153 }, 0);
10154 }
10155 else {
10156 s.slideTo(slideToIndex);
10157 }
10158 }
10159 else {
10160 if (slideToIndex > s.slides.length - s.params.slidesPerView) {
10161 s.fixLoop();
10162 slideToIndex = s.wrapper.children('.' + s.params.slideClass + '[data-swiper-slide-index="' + realIndex + '"]:not(.swiper-slide-duplicate)').eq(0).index();
10163 setTimeout(function () {
10164 s.slideTo(slideToIndex);
10165 }, 0);
10166 }
10167 else {
10168 s.slideTo(slideToIndex);
10169 }
10170 }
10171 }
10172 else {
10173 s.slideTo(slideToIndex);
10174 }
10175 }
10176 };
10177
10178 var isTouched,
10179 isMoved,
10180 allowTouchCallbacks,
10181 touchStartTime,
10182 isScrolling,
10183 currentTranslate,
10184 startTranslate,
10185 allowThresholdMove,
10186 // Form elements to match
10187 formElements = 'input, select, textarea, button',
10188 // Last click time
10189 lastClickTime = Date.now(), clickTimeout,
10190 //Velocities
10191 velocities = [],
10192 allowMomentumBounce;
10193
10194 // Animating Flag
10195 s.animating = false;
10196
10197 // Touches information
10198 s.touches = {
10199 startX: 0,
10200 startY: 0,
10201 currentX: 0,
10202 currentY: 0,
10203 diff: 0
10204 };
10205
10206 // Touch handlers
10207 var isTouchEvent, startMoving;
10208 s.onTouchStart = function (e) {
10209 if (e.originalEvent) e = e.originalEvent;
10210 isTouchEvent = e.type === 'touchstart';
10211 if (!isTouchEvent && 'which' in e && e.which === 3) return;
10212 if (s.params.noSwiping && findElementInEvent(e, '.' + s.params.noSwipingClass)) {
10213 s.allowClick = true;
10214 return;
10215 }
10216 if (s.params.swipeHandler) {
10217 if (!findElementInEvent(e, s.params.swipeHandler)) return;
10218 }
10219
10220 var startX = s.touches.currentX = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.pageX;
10221 var startY = s.touches.currentY = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.pageY;
10222
10223 // Do NOT start if iOS edge swipe is detected. Otherwise iOS app (UIWebView) cannot swipe-to-go-back anymore
10224 if(s.device.ios && s.params.iOSEdgeSwipeDetection && startX <= s.params.iOSEdgeSwipeThreshold) {
10225 return;
10226 }
10227
10228 isTouched = true;
10229 isMoved = false;
10230 allowTouchCallbacks = true;
10231 isScrolling = undefined;
10232 startMoving = undefined;
10233 s.touches.startX = startX;
10234 s.touches.startY = startY;
10235 touchStartTime = Date.now();
10236 s.allowClick = true;
10237 s.updateContainerSize();
10238 s.swipeDirection = undefined;
10239 if (s.params.threshold > 0) allowThresholdMove = false;
10240 if (e.type !== 'touchstart') {
10241 var preventDefault = true;
10242 if ($(e.target).is(formElements)) preventDefault = false;
10243 if (document.activeElement && $(document.activeElement).is(formElements)) {
10244 document.activeElement.blur();
10245 }
10246 if (preventDefault) {
10247 e.preventDefault();
10248 }
10249 }
10250 s.emit('onTouchStart', s, e);
10251 };
10252
10253 s.onTouchMove = function (e) {
10254 if (e.originalEvent) e = e.originalEvent;
10255 if (isTouchEvent && e.type === 'mousemove') return;
10256 if (e.preventedByNestedSwiper) return;
10257 if (s.params.onlyExternal) {
10258 // isMoved = true;
10259 s.allowClick = false;
10260 if (isTouched) {
10261 s.touches.startX = s.touches.currentX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX;
10262 s.touches.startY = s.touches.currentY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
10263 touchStartTime = Date.now();
10264 }
10265 return;
10266 }
10267 if (isTouchEvent && document.activeElement) {
10268 if (e.target === document.activeElement && $(e.target).is(formElements)) {
10269 isMoved = true;
10270 s.allowClick = false;
10271 return;
10272 }
10273 }
10274 if (allowTouchCallbacks) {
10275 s.emit('onTouchMove', s, e);
10276 }
10277 if (e.targetTouches && e.targetTouches.length > 1) return;
10278
10279 s.touches.currentX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX;
10280 s.touches.currentY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
10281
10282 if (typeof isScrolling === 'undefined') {
10283 var touchAngle = Math.atan2(Math.abs(s.touches.currentY - s.touches.startY), Math.abs(s.touches.currentX - s.touches.startX)) * 180 / Math.PI;
10284 isScrolling = isH() ? touchAngle > s.params.touchAngle : (90 - touchAngle > s.params.touchAngle);
10285 }
10286 if (isScrolling) {
10287 s.emit('onTouchMoveOpposite', s, e);
10288 }
10289 if (typeof startMoving === 'undefined' && s.browser.ieTouch) {
10290 if (s.touches.currentX !== s.touches.startX || s.touches.currentY !== s.touches.startY) {
10291 startMoving = true;
10292 }
10293 }
10294 if (!isTouched) return;
10295 if (isScrolling) {
10296 isTouched = false;
10297 return;
10298 }
10299 if (!startMoving && s.browser.ieTouch) {
10300 return;
10301 }
10302 s.allowClick = false;
10303 s.emit('onSliderMove', s, e);
10304 e.preventDefault();
10305 if (s.params.touchMoveStopPropagation && !s.params.nested) {
10306 e.stopPropagation();
10307 }
10308
10309 if (!isMoved) {
10310 if (params.loop) {
10311 s.fixLoop();
10312 }
10313 startTranslate = s.getWrapperTranslate();
10314 s.setWrapperTransition(0);
10315 if (s.animating) {
10316 s.wrapper.trigger('webkitTransitionEnd transitionend oTransitionEnd MSTransitionEnd msTransitionEnd');
10317 }
10318 if (s.params.autoplay && s.autoplaying) {
10319 if (s.params.autoplayDisableOnInteraction) {
10320 s.stopAutoplay();
10321 }
10322 else {
10323 s.pauseAutoplay();
10324 }
10325 }
10326 allowMomentumBounce = false;
10327 //Grab Cursor
10328 if (s.params.grabCursor) {
10329 s.container[0].style.cursor = 'move';
10330 s.container[0].style.cursor = '-webkit-grabbing';
10331 s.container[0].style.cursor = '-moz-grabbin';
10332 s.container[0].style.cursor = 'grabbing';
10333 }
10334 }
10335 isMoved = true;
10336
10337 var diff = s.touches.diff = isH() ? s.touches.currentX - s.touches.startX : s.touches.currentY - s.touches.startY;
10338
10339 diff = diff * s.params.touchRatio;
10340 if (s.rtl) diff = -diff;
10341
10342 s.swipeDirection = diff > 0 ? 'prev' : 'next';
10343 currentTranslate = diff + startTranslate;
10344
10345 var disableParentSwiper = true;
10346 if ((diff > 0 && currentTranslate > s.minTranslate())) {
10347 disableParentSwiper = false;
10348 if (s.params.resistance) currentTranslate = s.minTranslate() - 1 + Math.pow(-s.minTranslate() + startTranslate + diff, s.params.resistanceRatio);
10349 }
10350 else if (diff < 0 && currentTranslate < s.maxTranslate()) {
10351 disableParentSwiper = false;
10352 if (s.params.resistance) currentTranslate = s.maxTranslate() + 1 - Math.pow(s.maxTranslate() - startTranslate - diff, s.params.resistanceRatio);
10353 }
10354
10355 if (disableParentSwiper) {
10356 e.preventedByNestedSwiper = true;
10357 }
10358
10359 // Directions locks
10360 if (!s.params.allowSwipeToNext && s.swipeDirection === 'next' && currentTranslate < startTranslate) {
10361 currentTranslate = startTranslate;
10362 }
10363 if (!s.params.allowSwipeToPrev && s.swipeDirection === 'prev' && currentTranslate > startTranslate) {
10364 currentTranslate = startTranslate;
10365 }
10366
10367 if (!s.params.followFinger) return;
10368
10369 // Threshold
10370 if (s.params.threshold > 0) {
10371 if (Math.abs(diff) > s.params.threshold || allowThresholdMove) {
10372 if (!allowThresholdMove) {
10373 allowThresholdMove = true;
10374 s.touches.startX = s.touches.currentX;
10375 s.touches.startY = s.touches.currentY;
10376 currentTranslate = startTranslate;
10377 s.touches.diff = isH() ? s.touches.currentX - s.touches.startX : s.touches.currentY - s.touches.startY;
10378 return;
10379 }
10380 }
10381 else {
10382 currentTranslate = startTranslate;
10383 return;
10384 }
10385 }
10386 // Update active index in free mode
10387 if (s.params.freeMode || s.params.watchSlidesProgress) {
10388 s.updateActiveIndex();
10389 }
10390 if (s.params.freeMode) {
10391 //Velocity
10392 if (velocities.length === 0) {
10393 velocities.push({
10394 position: s.touches[isH() ? 'startX' : 'startY'],
10395 time: touchStartTime
10396 });
10397 }
10398 velocities.push({
10399 position: s.touches[isH() ? 'currentX' : 'currentY'],
10400 time: (new window.Date()).getTime()
10401 });
10402 }
10403 // Update progress
10404 s.updateProgress(currentTranslate);
10405 // Update translate
10406 s.setWrapperTranslate(currentTranslate);
10407 };
10408 s.onTouchEnd = function (e) {
10409 if (e.originalEvent) e = e.originalEvent;
10410 if (allowTouchCallbacks) {
10411 s.emit('onTouchEnd', s, e);
10412 }
10413 allowTouchCallbacks = false;
10414 if (!isTouched) return;
10415 //Return Grab Cursor
10416 if (s.params.grabCursor && isMoved && isTouched) {
10417 s.container[0].style.cursor = 'move';
10418 s.container[0].style.cursor = '-webkit-grab';
10419 s.container[0].style.cursor = '-moz-grab';
10420 s.container[0].style.cursor = 'grab';
10421 }
10422
10423 // Time diff
10424 var touchEndTime = Date.now();
10425 var timeDiff = touchEndTime - touchStartTime;
10426
10427 // Tap, doubleTap, Click
10428 if (s.allowClick) {
10429 s.updateClickedSlide(e);
10430 s.emit('onTap', s, e);
10431 if (timeDiff < 300 && (touchEndTime - lastClickTime) > 300) {
10432 if (clickTimeout) clearTimeout(clickTimeout);
10433 clickTimeout = setTimeout(function () {
10434 if (!s) return;
10435 if (s.params.paginationHide && s.paginationContainer.length > 0 && !$(e.target).hasClass(s.params.bulletClass)) {
10436 s.paginationContainer.toggleClass(s.params.paginationHiddenClass);
10437 }
10438 s.emit('onClick', s, e);
10439 }, 300);
10440
10441 }
10442 if (timeDiff < 300 && (touchEndTime - lastClickTime) < 300) {
10443 if (clickTimeout) clearTimeout(clickTimeout);
10444 s.emit('onDoubleTap', s, e);
10445 }
10446 }
10447
10448 lastClickTime = Date.now();
10449 setTimeout(function () {
10450 if (s) s.allowClick = true;
10451 }, 0);
10452
10453 if (!isTouched || !isMoved || !s.swipeDirection || s.touches.diff === 0 || currentTranslate === startTranslate) {
10454 isTouched = isMoved = false;
10455 return;
10456 }
10457 isTouched = isMoved = false;
10458
10459 var currentPos;
10460 if (s.params.followFinger) {
10461 currentPos = s.rtl ? s.translate : -s.translate;
10462 }
10463 else {
10464 currentPos = -currentTranslate;
10465 }
10466 if (s.params.freeMode) {
10467 if (currentPos < -s.minTranslate()) {
10468 s.slideTo(s.activeIndex);
10469 return;
10470 }
10471 else if (currentPos > -s.maxTranslate()) {
10472 if (s.slides.length < s.snapGrid.length) {
10473 s.slideTo(s.snapGrid.length - 1);
10474 }
10475 else {
10476 s.slideTo(s.slides.length - 1);
10477 }
10478 return;
10479 }
10480
10481 if (s.params.freeModeMomentum) {
10482 if (velocities.length > 1) {
10483 var lastMoveEvent = velocities.pop(), velocityEvent = velocities.pop();
10484
10485 var distance = lastMoveEvent.position - velocityEvent.position;
10486 var time = lastMoveEvent.time - velocityEvent.time;
10487 s.velocity = distance / time;
10488 s.velocity = s.velocity / 2;
10489 if (Math.abs(s.velocity) < s.params.freeModeMinimumVelocity) {
10490 s.velocity = 0;
10491 }
10492 // this implies that the user stopped moving a finger then released.
10493 // There would be no events with distance zero, so the last event is stale.
10494 if (time > 150 || (new window.Date().getTime() - lastMoveEvent.time) > 300) {
10495 s.velocity = 0;
10496 }
10497 } else {
10498 s.velocity = 0;
10499 }
10500
10501 velocities.length = 0;
10502 var momentumDuration = 1000 * s.params.freeModeMomentumRatio;
10503 var momentumDistance = s.velocity * momentumDuration;
10504
10505 var newPosition = s.translate + momentumDistance;
10506 if (s.rtl) newPosition = - newPosition;
10507 var doBounce = false;
10508 var afterBouncePosition;
10509 var bounceAmount = Math.abs(s.velocity) * 20 * s.params.freeModeMomentumBounceRatio;
10510 if (newPosition < s.maxTranslate()) {
10511 if (s.params.freeModeMomentumBounce) {
10512 if (newPosition + s.maxTranslate() < -bounceAmount) {
10513 newPosition = s.maxTranslate() - bounceAmount;
10514 }
10515 afterBouncePosition = s.maxTranslate();
10516 doBounce = true;
10517 allowMomentumBounce = true;
10518 }
10519 else {
10520 newPosition = s.maxTranslate();
10521 }
10522 }
10523 else if (newPosition > s.minTranslate()) {
10524 if (s.params.freeModeMomentumBounce) {
10525 if (newPosition - s.minTranslate() > bounceAmount) {
10526 newPosition = s.minTranslate() + bounceAmount;
10527 }
10528 afterBouncePosition = s.minTranslate();
10529 doBounce = true;
10530 allowMomentumBounce = true;
10531 }
10532 else {
10533 newPosition = s.minTranslate();
10534 }
10535 }
10536 else if (s.params.freeModeSticky) {
10537 var j = 0,
10538 nextSlide;
10539 for (j = 0; j < s.snapGrid.length; j += 1) {
10540 if (s.snapGrid[j] > -newPosition) {
10541 nextSlide = j;
10542 break;
10543 }
10544
10545 }
10546 if (Math.abs(s.snapGrid[nextSlide] - newPosition) < Math.abs(s.snapGrid[nextSlide - 1] - newPosition) || s.swipeDirection === 'next') {
10547 newPosition = s.snapGrid[nextSlide];
10548 } else {
10549 newPosition = s.snapGrid[nextSlide - 1];
10550 }
10551 if (!s.rtl) newPosition = - newPosition;
10552 }
10553 //Fix duration
10554 if (s.velocity !== 0) {
10555 if (s.rtl) {
10556 momentumDuration = Math.abs((-newPosition - s.translate) / s.velocity);
10557 }
10558 else {
10559 momentumDuration = Math.abs((newPosition - s.translate) / s.velocity);
10560 }
10561 }
10562 else if (s.params.freeModeSticky) {
10563 s.slideReset();
10564 return;
10565 }
10566
10567 if (s.params.freeModeMomentumBounce && doBounce) {
10568 s.updateProgress(afterBouncePosition);
10569 s.setWrapperTransition(momentumDuration);
10570 s.setWrapperTranslate(newPosition);
10571 s.onTransitionStart();
10572 s.animating = true;
10573 s.wrapper.transitionEnd(function () {
10574 if (!s || !allowMomentumBounce) return;
10575 s.emit('onMomentumBounce', s);
10576
10577 s.setWrapperTransition(s.params.speed);
10578 s.setWrapperTranslate(afterBouncePosition);
10579 s.wrapper.transitionEnd(function () {
10580 if (!s) return;
10581 s.onTransitionEnd();
10582 });
10583 });
10584 } else if (s.velocity) {
10585 s.updateProgress(newPosition);
10586 s.setWrapperTransition(momentumDuration);
10587 s.setWrapperTranslate(newPosition);
10588 s.onTransitionStart();
10589 if (!s.animating) {
10590 s.animating = true;
10591 s.wrapper.transitionEnd(function () {
10592 if (!s) return;
10593 s.onTransitionEnd();
10594 });
10595 }
10596
10597 } else {
10598 s.updateProgress(newPosition);
10599 }
10600
10601 s.updateActiveIndex();
10602 }
10603 if (!s.params.freeModeMomentum || timeDiff >= s.params.longSwipesMs) {
10604 s.updateProgress();
10605 s.updateActiveIndex();
10606 }
10607 return;
10608 }
10609
10610 // Find current slide
10611 var i, stopIndex = 0, groupSize = s.slidesSizesGrid[0];
10612 for (i = 0; i < s.slidesGrid.length; i += s.params.slidesPerGroup) {
10613 if (typeof s.slidesGrid[i + s.params.slidesPerGroup] !== 'undefined') {
10614 if (currentPos >= s.slidesGrid[i] && currentPos < s.slidesGrid[i + s.params.slidesPerGroup]) {
10615 stopIndex = i;
10616 groupSize = s.slidesGrid[i + s.params.slidesPerGroup] - s.slidesGrid[i];
10617 }
10618 }
10619 else {
10620 if (currentPos >= s.slidesGrid[i]) {
10621 stopIndex = i;
10622 groupSize = s.slidesGrid[s.slidesGrid.length - 1] - s.slidesGrid[s.slidesGrid.length - 2];
10623 }
10624 }
10625 }
10626
10627 // Find current slide size
10628 var ratio = (currentPos - s.slidesGrid[stopIndex]) / groupSize;
10629
10630 if (timeDiff > s.params.longSwipesMs) {
10631 // Long touches
10632 if (!s.params.longSwipes) {
10633 s.slideTo(s.activeIndex);
10634 return;
10635 }
10636 if (s.swipeDirection === 'next') {
10637 if (ratio >= s.params.longSwipesRatio) s.slideTo(stopIndex + s.params.slidesPerGroup);
10638 else s.slideTo(stopIndex);
10639
10640 }
10641 if (s.swipeDirection === 'prev') {
10642 if (ratio > (1 - s.params.longSwipesRatio)) s.slideTo(stopIndex + s.params.slidesPerGroup);
10643 else s.slideTo(stopIndex);
10644 }
10645 }
10646 else {
10647 // Short swipes
10648 if (!s.params.shortSwipes) {
10649 s.slideTo(s.activeIndex);
10650 return;
10651 }
10652 if (s.swipeDirection === 'next') {
10653 s.slideTo(stopIndex + s.params.slidesPerGroup);
10654
10655 }
10656 if (s.swipeDirection === 'prev') {
10657 s.slideTo(stopIndex);
10658 }
10659 }
10660 };
10661 /*=========================
10662 Transitions
10663 ===========================*/
10664 s._slideTo = function (slideIndex, speed) {
10665 return s.slideTo(slideIndex, speed, true, true);
10666 };
10667 s.slideTo = function (slideIndex, speed, runCallbacks, internal) {
10668 if (typeof runCallbacks === 'undefined') runCallbacks = true;
10669 if (typeof slideIndex === 'undefined') slideIndex = 0;
10670 if (slideIndex < 0) slideIndex = 0;
10671 s.snapIndex = Math.floor(slideIndex / s.params.slidesPerGroup);
10672 if (s.snapIndex >= s.snapGrid.length) s.snapIndex = s.snapGrid.length - 1;
10673
10674 var translate = - s.snapGrid[s.snapIndex];
10675 // Stop autoplay
10676 if (s.params.autoplay && s.autoplaying) {
10677 if (internal || !s.params.autoplayDisableOnInteraction) {
10678 s.pauseAutoplay(speed);
10679 }
10680 else {
10681 s.stopAutoplay();
10682 }
10683 }
10684 // Update progress
10685 s.updateProgress(translate);
10686
10687 // Normalize slideIndex
10688 for (var i = 0; i < s.slidesGrid.length; i++) {
10689 if (- Math.floor(translate * 100) >= Math.floor(s.slidesGrid[i] * 100)) {
10690 slideIndex = i;
10691 }
10692 }
10693
10694 // Directions locks
10695 if (!s.params.allowSwipeToNext && translate < s.translate && translate < s.minTranslate()) {
10696 return false;
10697 }
10698 if (!s.params.allowSwipeToPrev && translate > s.translate && translate > s.maxTranslate()) {
10699 if ((s.activeIndex || 0) !== slideIndex ) return false;
10700 }
10701
10702 // Update Index
10703 if (typeof speed === 'undefined') speed = s.params.speed;
10704 s.previousIndex = s.activeIndex || 0;
10705 s.activeIndex = slideIndex;
10706
10707 if ((s.rtl && -translate === s.translate) || (!s.rtl && translate === s.translate)) {
10708 // Update Height
10709 if (s.params.autoHeight) {
10710 s.updateAutoHeight();
10711 }
10712 s.updateClasses();
10713 if (s.params.effect !== 'slide') {
10714 s.setWrapperTranslate(translate);
10715 }
10716 return false;
10717 }
10718 s.updateClasses();
10719 s.onTransitionStart(runCallbacks);
10720
10721 if (speed === 0) {
10722 s.setWrapperTranslate(translate);
10723 s.setWrapperTransition(0);
10724 s.onTransitionEnd(runCallbacks);
10725 }
10726 else {
10727 s.setWrapperTranslate(translate);
10728 s.setWrapperTransition(speed);
10729 if (!s.animating) {
10730 s.animating = true;
10731 s.wrapper.transitionEnd(function () {
10732 if (!s) return;
10733 s.onTransitionEnd(runCallbacks);
10734 });
10735 }
10736
10737 }
10738
10739 return true;
10740 };
10741
10742 s.onTransitionStart = function (runCallbacks) {
10743 if (typeof runCallbacks === 'undefined') runCallbacks = true;
10744 if (s.params.autoHeight) {
10745 s.updateAutoHeight();
10746 }
10747 if (s.lazy) s.lazy.onTransitionStart();
10748 if (runCallbacks) {
10749 s.emit('onTransitionStart', s);
10750 if (s.activeIndex !== s.previousIndex) {
10751 s.emit('onSlideChangeStart', s);
10752 _scope.$emit("$ionicSlides.slideChangeStart", {
10753 slider: s,
10754 activeIndex: s.getSlideDataIndex(s.activeIndex),
10755 previousIndex: s.getSlideDataIndex(s.previousIndex)
10756 });
10757 if (s.activeIndex > s.previousIndex) {
10758 s.emit('onSlideNextStart', s);
10759 }
10760 else {
10761 s.emit('onSlidePrevStart', s);
10762 }
10763 }
10764
10765 }
10766 };
10767 s.onTransitionEnd = function (runCallbacks) {
10768 s.animating = false;
10769 s.setWrapperTransition(0);
10770 if (typeof runCallbacks === 'undefined') runCallbacks = true;
10771 if (s.lazy) s.lazy.onTransitionEnd();
10772 if (runCallbacks) {
10773 s.emit('onTransitionEnd', s);
10774 if (s.activeIndex !== s.previousIndex) {
10775 s.emit('onSlideChangeEnd', s);
10776 _scope.$emit("$ionicSlides.slideChangeEnd", {
10777 slider: s,
10778 activeIndex: s.getSlideDataIndex(s.activeIndex),
10779 previousIndex: s.getSlideDataIndex(s.previousIndex)
10780 });
10781 if (s.activeIndex > s.previousIndex) {
10782 s.emit('onSlideNextEnd', s);
10783 }
10784 else {
10785 s.emit('onSlidePrevEnd', s);
10786 }
10787 }
10788 }
10789 if (s.params.hashnav && s.hashnav) {
10790 s.hashnav.setHash();
10791 }
10792
10793 };
10794 s.slideNext = function (runCallbacks, speed, internal) {
10795 if (s.params.loop) {
10796 if (s.animating) return false;
10797 s.fixLoop();
10798 var clientLeft = s.container[0].clientLeft;
10799 return s.slideTo(s.activeIndex + s.params.slidesPerGroup, speed, runCallbacks, internal);
10800 }
10801 else return s.slideTo(s.activeIndex + s.params.slidesPerGroup, speed, runCallbacks, internal);
10802 };
10803 s._slideNext = function (speed) {
10804 return s.slideNext(true, speed, true);
10805 };
10806 s.slidePrev = function (runCallbacks, speed, internal) {
10807 if (s.params.loop) {
10808 if (s.animating) return false;
10809 s.fixLoop();
10810 var clientLeft = s.container[0].clientLeft;
10811 return s.slideTo(s.activeIndex - 1, speed, runCallbacks, internal);
10812 }
10813 else return s.slideTo(s.activeIndex - 1, speed, runCallbacks, internal);
10814 };
10815 s._slidePrev = function (speed) {
10816 return s.slidePrev(true, speed, true);
10817 };
10818 s.slideReset = function (runCallbacks, speed, internal) {
10819 return s.slideTo(s.activeIndex, speed, runCallbacks);
10820 };
10821
10822 /*=========================
10823 Translate/transition helpers
10824 ===========================*/
10825 s.setWrapperTransition = function (duration, byController) {
10826 s.wrapper.transition(duration);
10827 if (s.params.effect !== 'slide' && s.effects[s.params.effect]) {
10828 s.effects[s.params.effect].setTransition(duration);
10829 }
10830 if (s.params.parallax && s.parallax) {
10831 s.parallax.setTransition(duration);
10832 }
10833 if (s.params.scrollbar && s.scrollbar) {
10834 s.scrollbar.setTransition(duration);
10835 }
10836 if (s.params.control && s.controller) {
10837 s.controller.setTransition(duration, byController);
10838 }
10839 s.emit('onSetTransition', s, duration);
10840 };
10841 s.setWrapperTranslate = function (translate, updateActiveIndex, byController) {
10842 var x = 0, y = 0, z = 0;
10843 if (isH()) {
10844 x = s.rtl ? -translate : translate;
10845 }
10846 else {
10847 y = translate;
10848 }
10849
10850 if (s.params.roundLengths) {
10851 x = round(x);
10852 y = round(y);
10853 }
10854
10855 if (!s.params.virtualTranslate) {
10856 if (s.support.transforms3d) s.wrapper.transform('translate3d(' + x + 'px, ' + y + 'px, ' + z + 'px)');
10857 else s.wrapper.transform('translate(' + x + 'px, ' + y + 'px)');
10858 }
10859
10860 s.translate = isH() ? x : y;
10861
10862 // Check if we need to update progress
10863 var progress;
10864 var translatesDiff = s.maxTranslate() - s.minTranslate();
10865 if (translatesDiff === 0) {
10866 progress = 0;
10867 }
10868 else {
10869 progress = (translate - s.minTranslate()) / (translatesDiff);
10870 }
10871 if (progress !== s.progress) {
10872 s.updateProgress(translate);
10873 }
10874
10875 if (updateActiveIndex) s.updateActiveIndex();
10876 if (s.params.effect !== 'slide' && s.effects[s.params.effect]) {
10877 s.effects[s.params.effect].setTranslate(s.translate);
10878 }
10879 if (s.params.parallax && s.parallax) {
10880 s.parallax.setTranslate(s.translate);
10881 }
10882 if (s.params.scrollbar && s.scrollbar) {
10883 s.scrollbar.setTranslate(s.translate);
10884 }
10885 if (s.params.control && s.controller) {
10886 s.controller.setTranslate(s.translate, byController);
10887 }
10888 s.emit('onSetTranslate', s, s.translate);
10889 };
10890
10891 s.getTranslate = function (el, axis) {
10892 var matrix, curTransform, curStyle, transformMatrix;
10893
10894 // automatic axis detection
10895 if (typeof axis === 'undefined') {
10896 axis = 'x';
10897 }
10898
10899 if (s.params.virtualTranslate) {
10900 return s.rtl ? -s.translate : s.translate;
10901 }
10902
10903 curStyle = window.getComputedStyle(el, null);
10904 if (window.WebKitCSSMatrix) {
10905 curTransform = curStyle.transform || curStyle.webkitTransform;
10906 if (curTransform.split(',').length > 6) {
10907 curTransform = curTransform.split(', ').map(function(a){
10908 return a.replace(',','.');
10909 }).join(', ');
10910 }
10911 // Some old versions of Webkit choke when 'none' is passed; pass
10912 // empty string instead in this case
10913 transformMatrix = new window.WebKitCSSMatrix(curTransform === 'none' ? '' : curTransform);
10914 }
10915 else {
10916 transformMatrix = curStyle.MozTransform || curStyle.OTransform || curStyle.MsTransform || curStyle.msTransform || curStyle.transform || curStyle.getPropertyValue('transform').replace('translate(', 'matrix(1, 0, 0, 1,');
10917 matrix = transformMatrix.toString().split(',');
10918 }
10919
10920 if (axis === 'x') {
10921 //Latest Chrome and webkits Fix
10922 if (window.WebKitCSSMatrix)
10923 curTransform = transformMatrix.m41;
10924 //Crazy IE10 Matrix
10925 else if (matrix.length === 16)
10926 curTransform = parseFloat(matrix[12]);
10927 //Normal Browsers
10928 else
10929 curTransform = parseFloat(matrix[4]);
10930 }
10931 if (axis === 'y') {
10932 //Latest Chrome and webkits Fix
10933 if (window.WebKitCSSMatrix)
10934 curTransform = transformMatrix.m42;
10935 //Crazy IE10 Matrix
10936 else if (matrix.length === 16)
10937 curTransform = parseFloat(matrix[13]);
10938 //Normal Browsers
10939 else
10940 curTransform = parseFloat(matrix[5]);
10941 }
10942 if (s.rtl && curTransform) curTransform = -curTransform;
10943 return curTransform || 0;
10944 };
10945 s.getWrapperTranslate = function (axis) {
10946 if (typeof axis === 'undefined') {
10947 axis = isH() ? 'x' : 'y';
10948 }
10949 return s.getTranslate(s.wrapper[0], axis);
10950 };
10951
10952 /*=========================
10953 Observer
10954 ===========================*/
10955 s.observers = [];
10956 function initObserver(target, options) {
10957 options = options || {};
10958 // create an observer instance
10959 var ObserverFunc = window.MutationObserver || window.WebkitMutationObserver;
10960 var observer = new ObserverFunc(function (mutations) {
10961 mutations.forEach(function (mutation) {
10962 s.onResize(true);
10963 s.emit('onObserverUpdate', s, mutation);
10964 });
10965 });
10966
10967 observer.observe(target, {
10968 attributes: typeof options.attributes === 'undefined' ? true : options.attributes,
10969 childList: typeof options.childList === 'undefined' ? true : options.childList,
10970 characterData: typeof options.characterData === 'undefined' ? true : options.characterData
10971 });
10972
10973 s.observers.push(observer);
10974 }
10975 s.initObservers = function () {
10976 if (s.params.observeParents) {
10977 var containerParents = s.container.parents();
10978 for (var i = 0; i < containerParents.length; i++) {
10979 initObserver(containerParents[i]);
10980 }
10981 }
10982
10983 // Observe container
10984 initObserver(s.container[0], {childList: false});
10985
10986 // Observe wrapper
10987 initObserver(s.wrapper[0], {attributes: false});
10988 };
10989 s.disconnectObservers = function () {
10990 for (var i = 0; i < s.observers.length; i++) {
10991 s.observers[i].disconnect();
10992 }
10993 s.observers = [];
10994 };
10995
10996 s.updateLoop = function(){
10997 var currentSlide = s.slides.eq(s.activeIndex);
10998 if ( angular.element(currentSlide).hasClass(s.params.slideDuplicateClass) ){
10999 // we're on a duplicate, so slide to the non-duplicate
11000 var swiperSlideIndex = angular.element(currentSlide).attr("data-swiper-slide-index");
11001 var slides = s.wrapper.children('.' + s.params.slideClass);
11002 for ( var i = 0; i < slides.length; i++ ){
11003 if ( !angular.element(slides[i]).hasClass(s.params.slideDuplicateClass) && angular.element(slides[i]).attr("data-swiper-slide-index") === swiperSlideIndex ){
11004 s.slideTo(i, 0, false, true);
11005 break;
11006 }
11007 }
11008 // if we needed to switch slides, we did that. So, now call the createLoop function internally
11009 setTimeout(function(){
11010 s.createLoop();
11011 }, 50);
11012 }
11013 }
11014
11015 s.getSlideDataIndex = function(slideIndex){
11016 // this is an Ionic custom function
11017 // Swiper loops utilize duplicate DOM elements for slides when in a loop
11018 // which means that we cannot rely on the actual slide index for our events
11019 // because index 0 does not necessarily point to index 0
11020 // and index n+1 does not necessarily point to the expected piece of data
11021 // therefore, rather than using the actual slide index we should
11022 // use the data index that swiper includes as an attribute on the dom elements
11023 // because this is what will be meaningful to the consumer of our events
11024 var slide = s.slides.eq(slideIndex);
11025 var attributeIndex = angular.element(slide).attr("data-swiper-slide-index");
11026 return parseInt(attributeIndex);
11027 }
11028
11029 /*=========================
11030 Loop
11031 ===========================*/
11032 // Create looped slides
11033 s.createLoop = function () {
11034 //console.log("Slider create loop method");
11035 //var toRemove = s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass);
11036 //angular.element(toRemove).remove();
11037 s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass).remove();
11038
11039 var slides = s.wrapper.children('.' + s.params.slideClass);
11040
11041 if(s.params.slidesPerView === 'auto' && !s.params.loopedSlides) s.params.loopedSlides = slides.length;
11042
11043 s.loopedSlides = parseInt(s.params.loopedSlides || s.params.slidesPerView, 10);
11044 s.loopedSlides = s.loopedSlides + s.params.loopAdditionalSlides;
11045 if (s.loopedSlides > slides.length) {
11046 s.loopedSlides = slides.length;
11047 }
11048
11049 var prependSlides = [], appendSlides = [], i, scope, newNode;
11050 slides.each(function (index, el) {
11051 var slide = $(this);
11052 if (index < s.loopedSlides) appendSlides.push(el);
11053 if (index < slides.length && index >= slides.length - s.loopedSlides) prependSlides.push(el);
11054 slide.attr('data-swiper-slide-index', index);
11055 });
11056 for (i = 0; i < appendSlides.length; i++) {
11057
11058 newNode = angular.element(appendSlides[i]).clone().addClass(s.params.slideDuplicateClass);
11059 newNode.removeAttr('ng-transclude');
11060 newNode.removeAttr('ng-repeat');
11061 scope = angular.element(appendSlides[i]).scope();
11062 newNode = $compile(newNode)(scope);
11063 angular.element(s.wrapper).append(newNode);
11064 //s.wrapper.append($(appendSlides[i].cloneNode(true)).addClass(s.params.slideDuplicateClass));
11065 }
11066 for (i = prependSlides.length - 1; i >= 0; i--) {
11067 //s.wrapper.prepend($(prependSlides[i].cloneNode(true)).addClass(s.params.slideDuplicateClass));
11068
11069 newNode = angular.element(prependSlides[i]).clone().addClass(s.params.slideDuplicateClass);
11070 newNode.removeAttr('ng-transclude');
11071 newNode.removeAttr('ng-repeat');
11072
11073 scope = angular.element(prependSlides[i]).scope();
11074 newNode = $compile(newNode)(scope);
11075 angular.element(s.wrapper).prepend(newNode);
11076 }
11077 };
11078 s.destroyLoop = function () {
11079 s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass).remove();
11080 s.slides.removeAttr('data-swiper-slide-index');
11081 };
11082 s.fixLoop = function () {
11083 var newIndex;
11084 //Fix For Negative Oversliding
11085 if (s.activeIndex < s.loopedSlides) {
11086 newIndex = s.slides.length - s.loopedSlides * 3 + s.activeIndex;
11087 newIndex = newIndex + s.loopedSlides;
11088 s.slideTo(newIndex, 0, false, true);
11089 }
11090 //Fix For Positive Oversliding
11091 else if ((s.params.slidesPerView === 'auto' && s.activeIndex >= s.loopedSlides * 2) || (s.activeIndex > s.slides.length - s.params.slidesPerView * 2)) {
11092 newIndex = -s.slides.length + s.activeIndex + s.loopedSlides;
11093 newIndex = newIndex + s.loopedSlides;
11094 s.slideTo(newIndex, 0, false, true);
11095 }
11096 };
11097 /*=========================
11098 Append/Prepend/Remove Slides
11099 ===========================*/
11100 s.appendSlide = function (slides) {
11101 if (s.params.loop) {
11102 s.destroyLoop();
11103 }
11104 if (typeof slides === 'object' && slides.length) {
11105 for (var i = 0; i < slides.length; i++) {
11106 if (slides[i]) s.wrapper.append(slides[i]);
11107 }
11108 }
11109 else {
11110 s.wrapper.append(slides);
11111 }
11112 if (s.params.loop) {
11113 s.createLoop();
11114 }
11115 if (!(s.params.observer && s.support.observer)) {
11116 s.update(true);
11117 }
11118 };
11119 s.prependSlide = function (slides) {
11120 if (s.params.loop) {
11121 s.destroyLoop();
11122 }
11123 var newActiveIndex = s.activeIndex + 1;
11124 if (typeof slides === 'object' && slides.length) {
11125 for (var i = 0; i < slides.length; i++) {
11126 if (slides[i]) s.wrapper.prepend(slides[i]);
11127 }
11128 newActiveIndex = s.activeIndex + slides.length;
11129 }
11130 else {
11131 s.wrapper.prepend(slides);
11132 }
11133 if (s.params.loop) {
11134 s.createLoop();
11135 }
11136 if (!(s.params.observer && s.support.observer)) {
11137 s.update(true);
11138 }
11139 s.slideTo(newActiveIndex, 0, false);
11140 };
11141 s.removeSlide = function (slidesIndexes) {
11142 if (s.params.loop) {
11143 s.destroyLoop();
11144 s.slides = s.wrapper.children('.' + s.params.slideClass);
11145 }
11146 var newActiveIndex = s.activeIndex,
11147 indexToRemove;
11148 if (typeof slidesIndexes === 'object' && slidesIndexes.length) {
11149 for (var i = 0; i < slidesIndexes.length; i++) {
11150 indexToRemove = slidesIndexes[i];
11151 if (s.slides[indexToRemove]) s.slides.eq(indexToRemove).remove();
11152 if (indexToRemove < newActiveIndex) newActiveIndex--;
11153 }
11154 newActiveIndex = Math.max(newActiveIndex, 0);
11155 }
11156 else {
11157 indexToRemove = slidesIndexes;
11158 if (s.slides[indexToRemove]) s.slides.eq(indexToRemove).remove();
11159 if (indexToRemove < newActiveIndex) newActiveIndex--;
11160 newActiveIndex = Math.max(newActiveIndex, 0);
11161 }
11162
11163 if (s.params.loop) {
11164 s.createLoop();
11165 }
11166
11167 if (!(s.params.observer && s.support.observer)) {
11168 s.update(true);
11169 }
11170 if (s.params.loop) {
11171 s.slideTo(newActiveIndex + s.loopedSlides, 0, false);
11172 }
11173 else {
11174 s.slideTo(newActiveIndex, 0, false);
11175 }
11176
11177 };
11178 s.removeAllSlides = function () {
11179 var slidesIndexes = [];
11180 for (var i = 0; i < s.slides.length; i++) {
11181 slidesIndexes.push(i);
11182 }
11183 s.removeSlide(slidesIndexes);
11184 };
11185
11186
11187 /*=========================
11188 Effects
11189 ===========================*/
11190 s.effects = {
11191 fade: {
11192 setTranslate: function () {
11193 for (var i = 0; i < s.slides.length; i++) {
11194 var slide = s.slides.eq(i);
11195 var offset = slide[0].swiperSlideOffset;
11196 var tx = -offset;
11197 if (!s.params.virtualTranslate) tx = tx - s.translate;
11198 var ty = 0;
11199 if (!isH()) {
11200 ty = tx;
11201 tx = 0;
11202 }
11203 var slideOpacity = s.params.fade.crossFade ?
11204 Math.max(1 - Math.abs(slide[0].progress), 0) :
11205 1 + Math.min(Math.max(slide[0].progress, -1), 0);
11206 slide
11207 .css({
11208 opacity: slideOpacity
11209 })
11210 .transform('translate3d(' + tx + 'px, ' + ty + 'px, 0px)');
11211
11212 }
11213
11214 },
11215 setTransition: function (duration) {
11216 s.slides.transition(duration);
11217 if (s.params.virtualTranslate && duration !== 0) {
11218 var eventTriggered = false;
11219 s.slides.transitionEnd(function () {
11220 if (eventTriggered) return;
11221 if (!s) return;
11222 eventTriggered = true;
11223 s.animating = false;
11224 var triggerEvents = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'];
11225 for (var i = 0; i < triggerEvents.length; i++) {
11226 s.wrapper.trigger(triggerEvents[i]);
11227 }
11228 });
11229 }
11230 }
11231 },
11232 cube: {
11233 setTranslate: function () {
11234 var wrapperRotate = 0, cubeShadow;
11235 if (s.params.cube.shadow) {
11236 if (isH()) {
11237 cubeShadow = s.wrapper.find('.swiper-cube-shadow');
11238 if (cubeShadow.length === 0) {
11239 cubeShadow = $('<div class="swiper-cube-shadow"></div>');
11240 s.wrapper.append(cubeShadow);
11241 }
11242 cubeShadow.css({height: s.width + 'px'});
11243 }
11244 else {
11245 cubeShadow = s.container.find('.swiper-cube-shadow');
11246 if (cubeShadow.length === 0) {
11247 cubeShadow = $('<div class="swiper-cube-shadow"></div>');
11248 s.container.append(cubeShadow);
11249 }
11250 }
11251 }
11252 for (var i = 0; i < s.slides.length; i++) {
11253 var slide = s.slides.eq(i);
11254 var slideAngle = i * 90;
11255 var round = Math.floor(slideAngle / 360);
11256 if (s.rtl) {
11257 slideAngle = -slideAngle;
11258 round = Math.floor(-slideAngle / 360);
11259 }
11260 var progress = Math.max(Math.min(slide[0].progress, 1), -1);
11261 var tx = 0, ty = 0, tz = 0;
11262 if (i % 4 === 0) {
11263 tx = - round * 4 * s.size;
11264 tz = 0;
11265 }
11266 else if ((i - 1) % 4 === 0) {
11267 tx = 0;
11268 tz = - round * 4 * s.size;
11269 }
11270 else if ((i - 2) % 4 === 0) {
11271 tx = s.size + round * 4 * s.size;
11272 tz = s.size;
11273 }
11274 else if ((i - 3) % 4 === 0) {
11275 tx = - s.size;
11276 tz = 3 * s.size + s.size * 4 * round;
11277 }
11278 if (s.rtl) {
11279 tx = -tx;
11280 }
11281
11282 if (!isH()) {
11283 ty = tx;
11284 tx = 0;
11285 }
11286
11287 var transform = 'rotateX(' + (isH() ? 0 : -slideAngle) + 'deg) rotateY(' + (isH() ? slideAngle : 0) + 'deg) translate3d(' + tx + 'px, ' + ty + 'px, ' + tz + 'px)';
11288 if (progress <= 1 && progress > -1) {
11289 wrapperRotate = i * 90 + progress * 90;
11290 if (s.rtl) wrapperRotate = -i * 90 - progress * 90;
11291 }
11292 slide.transform(transform);
11293 if (s.params.cube.slideShadows) {
11294 //Set shadows
11295 var shadowBefore = isH() ? slide.find('.swiper-slide-shadow-left') : slide.find('.swiper-slide-shadow-top');
11296 var shadowAfter = isH() ? slide.find('.swiper-slide-shadow-right') : slide.find('.swiper-slide-shadow-bottom');
11297 if (shadowBefore.length === 0) {
11298 shadowBefore = $('<div class="swiper-slide-shadow-' + (isH() ? 'left' : 'top') + '"></div>');
11299 slide.append(shadowBefore);
11300 }
11301 if (shadowAfter.length === 0) {
11302 shadowAfter = $('<div class="swiper-slide-shadow-' + (isH() ? 'right' : 'bottom') + '"></div>');
11303 slide.append(shadowAfter);
11304 }
11305 var shadowOpacity = slide[0].progress;
11306 if (shadowBefore.length) shadowBefore[0].style.opacity = -slide[0].progress;
11307 if (shadowAfter.length) shadowAfter[0].style.opacity = slide[0].progress;
11308 }
11309 }
11310 s.wrapper.css({
11311 '-webkit-transform-origin': '50% 50% -' + (s.size / 2) + 'px',
11312 '-moz-transform-origin': '50% 50% -' + (s.size / 2) + 'px',
11313 '-ms-transform-origin': '50% 50% -' + (s.size / 2) + 'px',
11314 'transform-origin': '50% 50% -' + (s.size / 2) + 'px'
11315 });
11316
11317 if (s.params.cube.shadow) {
11318 if (isH()) {
11319 cubeShadow.transform('translate3d(0px, ' + (s.width / 2 + s.params.cube.shadowOffset) + 'px, ' + (-s.width / 2) + 'px) rotateX(90deg) rotateZ(0deg) scale(' + (s.params.cube.shadowScale) + ')');
11320 }
11321 else {
11322 var shadowAngle = Math.abs(wrapperRotate) - Math.floor(Math.abs(wrapperRotate) / 90) * 90;
11323 var multiplier = 1.5 - (Math.sin(shadowAngle * 2 * Math.PI / 360) / 2 + Math.cos(shadowAngle * 2 * Math.PI / 360) / 2);
11324 var scale1 = s.params.cube.shadowScale,
11325 scale2 = s.params.cube.shadowScale / multiplier,
11326 offset = s.params.cube.shadowOffset;
11327 cubeShadow.transform('scale3d(' + scale1 + ', 1, ' + scale2 + ') translate3d(0px, ' + (s.height / 2 + offset) + 'px, ' + (-s.height / 2 / scale2) + 'px) rotateX(-90deg)');
11328 }
11329 }
11330 var zFactor = (s.isSafari || s.isUiWebView) ? (-s.size / 2) : 0;
11331 s.wrapper.transform('translate3d(0px,0,' + zFactor + 'px) rotateX(' + (isH() ? 0 : wrapperRotate) + 'deg) rotateY(' + (isH() ? -wrapperRotate : 0) + 'deg)');
11332 },
11333 setTransition: function (duration) {
11334 s.slides.transition(duration).find('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').transition(duration);
11335 if (s.params.cube.shadow && !isH()) {
11336 s.container.find('.swiper-cube-shadow').transition(duration);
11337 }
11338 }
11339 },
11340 coverflow: {
11341 setTranslate: function () {
11342 var transform = s.translate;
11343 var center = isH() ? -transform + s.width / 2 : -transform + s.height / 2;
11344 var rotate = isH() ? s.params.coverflow.rotate: -s.params.coverflow.rotate;
11345 var translate = s.params.coverflow.depth;
11346 //Each slide offset from center
11347 for (var i = 0, length = s.slides.length; i < length; i++) {
11348 var slide = s.slides.eq(i);
11349 var slideSize = s.slidesSizesGrid[i];
11350 var slideOffset = slide[0].swiperSlideOffset;
11351 var offsetMultiplier = (center - slideOffset - slideSize / 2) / slideSize * s.params.coverflow.modifier;
11352
11353 var rotateY = isH() ? rotate * offsetMultiplier : 0;
11354 var rotateX = isH() ? 0 : rotate * offsetMultiplier;
11355 // var rotateZ = 0
11356 var translateZ = -translate * Math.abs(offsetMultiplier);
11357
11358 var translateY = isH() ? 0 : s.params.coverflow.stretch * (offsetMultiplier);
11359 var translateX = isH() ? s.params.coverflow.stretch * (offsetMultiplier) : 0;
11360
11361 //Fix for ultra small values
11362 if (Math.abs(translateX) < 0.001) translateX = 0;
11363 if (Math.abs(translateY) < 0.001) translateY = 0;
11364 if (Math.abs(translateZ) < 0.001) translateZ = 0;
11365 if (Math.abs(rotateY) < 0.001) rotateY = 0;
11366 if (Math.abs(rotateX) < 0.001) rotateX = 0;
11367
11368 var slideTransform = 'translate3d(' + translateX + 'px,' + translateY + 'px,' + translateZ + 'px) rotateX(' + rotateX + 'deg) rotateY(' + rotateY + 'deg)';
11369
11370 slide.transform(slideTransform);
11371 slide[0].style.zIndex = -Math.abs(Math.round(offsetMultiplier)) + 1;
11372 if (s.params.coverflow.slideShadows) {
11373 //Set shadows
11374 var shadowBefore = isH() ? slide.find('.swiper-slide-shadow-left') : slide.find('.swiper-slide-shadow-top');
11375 var shadowAfter = isH() ? slide.find('.swiper-slide-shadow-right') : slide.find('.swiper-slide-shadow-bottom');
11376 if (shadowBefore.length === 0) {
11377 shadowBefore = $('<div class="swiper-slide-shadow-' + (isH() ? 'left' : 'top') + '"></div>');
11378 slide.append(shadowBefore);
11379 }
11380 if (shadowAfter.length === 0) {
11381 shadowAfter = $('<div class="swiper-slide-shadow-' + (isH() ? 'right' : 'bottom') + '"></div>');
11382 slide.append(shadowAfter);
11383 }
11384 if (shadowBefore.length) shadowBefore[0].style.opacity = offsetMultiplier > 0 ? offsetMultiplier : 0;
11385 if (shadowAfter.length) shadowAfter[0].style.opacity = (-offsetMultiplier) > 0 ? -offsetMultiplier : 0;
11386 }
11387 }
11388
11389 //Set correct perspective for IE10
11390 if (s.browser.ie) {
11391 var ws = s.wrapper[0].style;
11392 ws.perspectiveOrigin = center + 'px 50%';
11393 }
11394 },
11395 setTransition: function (duration) {
11396 s.slides.transition(duration).find('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').transition(duration);
11397 }
11398 }
11399 };
11400
11401 /*=========================
11402 Images Lazy Loading
11403 ===========================*/
11404 s.lazy = {
11405 initialImageLoaded: false,
11406 loadImageInSlide: function (index, loadInDuplicate) {
11407 if (typeof index === 'undefined') return;
11408 if (typeof loadInDuplicate === 'undefined') loadInDuplicate = true;
11409 if (s.slides.length === 0) return;
11410
11411 var slide = s.slides.eq(index);
11412 var img = slide.find('.swiper-lazy:not(.swiper-lazy-loaded):not(.swiper-lazy-loading)');
11413 if (slide.hasClass('swiper-lazy') && !slide.hasClass('swiper-lazy-loaded') && !slide.hasClass('swiper-lazy-loading')) {
11414 img = img.add(slide[0]);
11415 }
11416 if (img.length === 0) return;
11417
11418 img.each(function () {
11419 var _img = $(this);
11420 _img.addClass('swiper-lazy-loading');
11421 var background = _img.attr('data-background');
11422 var src = _img.attr('data-src'),
11423 srcset = _img.attr('data-srcset');
11424 s.loadImage(_img[0], (src || background), srcset, false, function () {
11425 if (background) {
11426 _img.css('background-image', 'url(' + background + ')');
11427 _img.removeAttr('data-background');
11428 }
11429 else {
11430 if (srcset) {
11431 _img.attr('srcset', srcset);
11432 _img.removeAttr('data-srcset');
11433 }
11434 if (src) {
11435 _img.attr('src', src);
11436 _img.removeAttr('data-src');
11437 }
11438
11439 }
11440
11441 _img.addClass('swiper-lazy-loaded').removeClass('swiper-lazy-loading');
11442 slide.find('.swiper-lazy-preloader, .preloader').remove();
11443 if (s.params.loop && loadInDuplicate) {
11444 var slideOriginalIndex = slide.attr('data-swiper-slide-index');
11445 if (slide.hasClass(s.params.slideDuplicateClass)) {
11446 var originalSlide = s.wrapper.children('[data-swiper-slide-index="' + slideOriginalIndex + '"]:not(.' + s.params.slideDuplicateClass + ')');
11447 s.lazy.loadImageInSlide(originalSlide.index(), false);
11448 }
11449 else {
11450 var duplicatedSlide = s.wrapper.children('.' + s.params.slideDuplicateClass + '[data-swiper-slide-index="' + slideOriginalIndex + '"]');
11451 s.lazy.loadImageInSlide(duplicatedSlide.index(), false);
11452 }
11453 }
11454 s.emit('onLazyImageReady', s, slide[0], _img[0]);
11455 });
11456
11457 s.emit('onLazyImageLoad', s, slide[0], _img[0]);
11458 });
11459
11460 },
11461 load: function () {
11462 var i;
11463 if (s.params.watchSlidesVisibility) {
11464 s.wrapper.children('.' + s.params.slideVisibleClass).each(function () {
11465 s.lazy.loadImageInSlide($(this).index());
11466 });
11467 }
11468 else {
11469 if (s.params.slidesPerView > 1) {
11470 for (i = s.activeIndex; i < s.activeIndex + s.params.slidesPerView ; i++) {
11471 if (s.slides[i]) s.lazy.loadImageInSlide(i);
11472 }
11473 }
11474 else {
11475 s.lazy.loadImageInSlide(s.activeIndex);
11476 }
11477 }
11478 if (s.params.lazyLoadingInPrevNext) {
11479 if (s.params.slidesPerView > 1) {
11480 // Next Slides
11481 for (i = s.activeIndex + s.params.slidesPerView; i < s.activeIndex + s.params.slidesPerView + s.params.slidesPerView; i++) {
11482 if (s.slides[i]) s.lazy.loadImageInSlide(i);
11483 }
11484 // Prev Slides
11485 for (i = s.activeIndex - s.params.slidesPerView; i < s.activeIndex ; i++) {
11486 if (s.slides[i]) s.lazy.loadImageInSlide(i);
11487 }
11488 }
11489 else {
11490 var nextSlide = s.wrapper.children('.' + s.params.slideNextClass);
11491 if (nextSlide.length > 0) s.lazy.loadImageInSlide(nextSlide.index());
11492
11493 var prevSlide = s.wrapper.children('.' + s.params.slidePrevClass);
11494 if (prevSlide.length > 0) s.lazy.loadImageInSlide(prevSlide.index());
11495 }
11496 }
11497 },
11498 onTransitionStart: function () {
11499 if (s.params.lazyLoading) {
11500 if (s.params.lazyLoadingOnTransitionStart || (!s.params.lazyLoadingOnTransitionStart && !s.lazy.initialImageLoaded)) {
11501 s.lazy.load();
11502 }
11503 }
11504 },
11505 onTransitionEnd: function () {
11506 if (s.params.lazyLoading && !s.params.lazyLoadingOnTransitionStart) {
11507 s.lazy.load();
11508 }
11509 }
11510 };
11511
11512
11513 /*=========================
11514 Scrollbar
11515 ===========================*/
11516 s.scrollbar = {
11517 isTouched: false,
11518 setDragPosition: function (e) {
11519 var sb = s.scrollbar;
11520 var x = 0, y = 0;
11521 var translate;
11522 var pointerPosition = isH() ?
11523 ((e.type === 'touchstart' || e.type === 'touchmove') ? e.targetTouches[0].pageX : e.pageX || e.clientX) :
11524 ((e.type === 'touchstart' || e.type === 'touchmove') ? e.targetTouches[0].pageY : e.pageY || e.clientY) ;
11525 var position = (pointerPosition) - sb.track.offset()[isH() ? 'left' : 'top'] - sb.dragSize / 2;
11526 var positionMin = -s.minTranslate() * sb.moveDivider;
11527 var positionMax = -s.maxTranslate() * sb.moveDivider;
11528 if (position < positionMin) {
11529 position = positionMin;
11530 }
11531 else if (position > positionMax) {
11532 position = positionMax;
11533 }
11534 position = -position / sb.moveDivider;
11535 s.updateProgress(position);
11536 s.setWrapperTranslate(position, true);
11537 },
11538 dragStart: function (e) {
11539 var sb = s.scrollbar;
11540 sb.isTouched = true;
11541 e.preventDefault();
11542 e.stopPropagation();
11543
11544 sb.setDragPosition(e);
11545 clearTimeout(sb.dragTimeout);
11546
11547 sb.track.transition(0);
11548 if (s.params.scrollbarHide) {
11549 sb.track.css('opacity', 1);
11550 }
11551 s.wrapper.transition(100);
11552 sb.drag.transition(100);
11553 s.emit('onScrollbarDragStart', s);
11554 },
11555 dragMove: function (e) {
11556 var sb = s.scrollbar;
11557 if (!sb.isTouched) return;
11558 if (e.preventDefault) e.preventDefault();
11559 else e.returnValue = false;
11560 sb.setDragPosition(e);
11561 s.wrapper.transition(0);
11562 sb.track.transition(0);
11563 sb.drag.transition(0);
11564 s.emit('onScrollbarDragMove', s);
11565 },
11566 dragEnd: function (e) {
11567 var sb = s.scrollbar;
11568 if (!sb.isTouched) return;
11569 sb.isTouched = false;
11570 if (s.params.scrollbarHide) {
11571 clearTimeout(sb.dragTimeout);
11572 sb.dragTimeout = setTimeout(function () {
11573 sb.track.css('opacity', 0);
11574 sb.track.transition(400);
11575 }, 1000);
11576
11577 }
11578 s.emit('onScrollbarDragEnd', s);
11579 if (s.params.scrollbarSnapOnRelease) {
11580 s.slideReset();
11581 }
11582 },
11583 enableDraggable: function () {
11584 var sb = s.scrollbar;
11585 var target = s.support.touch ? sb.track : document;
11586 $(sb.track).on(s.touchEvents.start, sb.dragStart);
11587 $(target).on(s.touchEvents.move, sb.dragMove);
11588 $(target).on(s.touchEvents.end, sb.dragEnd);
11589 },
11590 disableDraggable: function () {
11591 var sb = s.scrollbar;
11592 var target = s.support.touch ? sb.track : document;
11593 $(sb.track).off(s.touchEvents.start, sb.dragStart);
11594 $(target).off(s.touchEvents.move, sb.dragMove);
11595 $(target).off(s.touchEvents.end, sb.dragEnd);
11596 },
11597 set: function () {
11598 if (!s.params.scrollbar) return;
11599 var sb = s.scrollbar;
11600 sb.track = $(s.params.scrollbar);
11601 sb.drag = sb.track.find('.swiper-scrollbar-drag');
11602 if (sb.drag.length === 0) {
11603 sb.drag = $('<div class="swiper-scrollbar-drag"></div>');
11604 sb.track.append(sb.drag);
11605 }
11606 sb.drag[0].style.width = '';
11607 sb.drag[0].style.height = '';
11608 sb.trackSize = isH() ? sb.track[0].offsetWidth : sb.track[0].offsetHeight;
11609
11610 sb.divider = s.size / s.virtualSize;
11611 sb.moveDivider = sb.divider * (sb.trackSize / s.size);
11612 sb.dragSize = sb.trackSize * sb.divider;
11613
11614 if (isH()) {
11615 sb.drag[0].style.width = sb.dragSize + 'px';
11616 }
11617 else {
11618 sb.drag[0].style.height = sb.dragSize + 'px';
11619 }
11620
11621 if (sb.divider >= 1) {
11622 sb.track[0].style.display = 'none';
11623 }
11624 else {
11625 sb.track[0].style.display = '';
11626 }
11627 if (s.params.scrollbarHide) {
11628 sb.track[0].style.opacity = 0;
11629 }
11630 },
11631 setTranslate: function () {
11632 if (!s.params.scrollbar) return;
11633 var diff;
11634 var sb = s.scrollbar;
11635 var translate = s.translate || 0;
11636 var newPos;
11637
11638 var newSize = sb.dragSize;
11639 newPos = (sb.trackSize - sb.dragSize) * s.progress;
11640 if (s.rtl && isH()) {
11641 newPos = -newPos;
11642 if (newPos > 0) {
11643 newSize = sb.dragSize - newPos;
11644 newPos = 0;
11645 }
11646 else if (-newPos + sb.dragSize > sb.trackSize) {
11647 newSize = sb.trackSize + newPos;
11648 }
11649 }
11650 else {
11651 if (newPos < 0) {
11652 newSize = sb.dragSize + newPos;
11653 newPos = 0;
11654 }
11655 else if (newPos + sb.dragSize > sb.trackSize) {
11656 newSize = sb.trackSize - newPos;
11657 }
11658 }
11659 if (isH()) {
11660 if (s.support.transforms3d) {
11661 sb.drag.transform('translate3d(' + (newPos) + 'px, 0, 0)');
11662 }
11663 else {
11664 sb.drag.transform('translateX(' + (newPos) + 'px)');
11665 }
11666 sb.drag[0].style.width = newSize + 'px';
11667 }
11668 else {
11669 if (s.support.transforms3d) {
11670 sb.drag.transform('translate3d(0px, ' + (newPos) + 'px, 0)');
11671 }
11672 else {
11673 sb.drag.transform('translateY(' + (newPos) + 'px)');
11674 }
11675 sb.drag[0].style.height = newSize + 'px';
11676 }
11677 if (s.params.scrollbarHide) {
11678 clearTimeout(sb.timeout);
11679 sb.track[0].style.opacity = 1;
11680 sb.timeout = setTimeout(function () {
11681 sb.track[0].style.opacity = 0;
11682 sb.track.transition(400);
11683 }, 1000);
11684 }
11685 },
11686 setTransition: function (duration) {
11687 if (!s.params.scrollbar) return;
11688 s.scrollbar.drag.transition(duration);
11689 }
11690 };
11691
11692 /*=========================
11693 Controller
11694 ===========================*/
11695 s.controller = {
11696 LinearSpline: function (x, y) {
11697 this.x = x;
11698 this.y = y;
11699 this.lastIndex = x.length - 1;
11700 // Given an x value (x2), return the expected y2 value:
11701 // (x1,y1) is the known point before given value,
11702 // (x3,y3) is the known point after given value.
11703 var i1, i3;
11704 var l = this.x.length;
11705
11706 this.interpolate = function (x2) {
11707 if (!x2) return 0;
11708
11709 // Get the indexes of x1 and x3 (the array indexes before and after given x2):
11710 i3 = binarySearch(this.x, x2);
11711 i1 = i3 - 1;
11712
11713 // We have our indexes i1 & i3, so we can calculate already:
11714 // y2 := ((x2−x1) × (y3−y1)) ÷ (x3−x1) + y1
11715 return ((x2 - this.x[i1]) * (this.y[i3] - this.y[i1])) / (this.x[i3] - this.x[i1]) + this.y[i1];
11716 };
11717
11718 var binarySearch = (function() {
11719 var maxIndex, minIndex, guess;
11720 return function(array, val) {
11721 minIndex = -1;
11722 maxIndex = array.length;
11723 while (maxIndex - minIndex > 1)
11724 if (array[guess = maxIndex + minIndex >> 1] <= val) {
11725 minIndex = guess;
11726 } else {
11727 maxIndex = guess;
11728 }
11729 return maxIndex;
11730 };
11731 })();
11732 },
11733 //xxx: for now i will just save one spline function to to
11734 getInterpolateFunction: function(c){
11735 if(!s.controller.spline) s.controller.spline = s.params.loop ?
11736 new s.controller.LinearSpline(s.slidesGrid, c.slidesGrid) :
11737 new s.controller.LinearSpline(s.snapGrid, c.snapGrid);
11738 },
11739 setTranslate: function (translate, byController) {
11740 var controlled = s.params.control;
11741 var multiplier, controlledTranslate;
11742 function setControlledTranslate(c) {
11743 // this will create an Interpolate function based on the snapGrids
11744 // x is the Grid of the scrolled scroller and y will be the controlled scroller
11745 // it makes sense to create this only once and recall it for the interpolation
11746 // the function does a lot of value caching for performance
11747 translate = c.rtl && c.params.direction === 'horizontal' ? -s.translate : s.translate;
11748 if (s.params.controlBy === 'slide') {
11749 s.controller.getInterpolateFunction(c);
11750 // i am not sure why the values have to be multiplicated this way, tried to invert the snapGrid
11751 // but it did not work out
11752 controlledTranslate = -s.controller.spline.interpolate(-translate);
11753 }
11754
11755 if(!controlledTranslate || s.params.controlBy === 'container'){
11756 multiplier = (c.maxTranslate() - c.minTranslate()) / (s.maxTranslate() - s.minTranslate());
11757 controlledTranslate = (translate - s.minTranslate()) * multiplier + c.minTranslate();
11758 }
11759
11760 if (s.params.controlInverse) {
11761 controlledTranslate = c.maxTranslate() - controlledTranslate;
11762 }
11763 c.updateProgress(controlledTranslate);
11764 c.setWrapperTranslate(controlledTranslate, false, s);
11765 c.updateActiveIndex();
11766 }
11767 if (s.isArray(controlled)) {
11768 for (var i = 0; i < controlled.length; i++) {
11769 if (controlled[i] !== byController && controlled[i] instanceof Swiper) {
11770 setControlledTranslate(controlled[i]);
11771 }
11772 }
11773 }
11774 else if (controlled instanceof Swiper && byController !== controlled) {
11775
11776 setControlledTranslate(controlled);
11777 }
11778 },
11779 setTransition: function (duration, byController) {
11780 var controlled = s.params.control;
11781 var i;
11782 function setControlledTransition(c) {
11783 c.setWrapperTransition(duration, s);
11784 if (duration !== 0) {
11785 c.onTransitionStart();
11786 c.wrapper.transitionEnd(function(){
11787 if (!controlled) return;
11788 if (c.params.loop && s.params.controlBy === 'slide') {
11789 c.fixLoop();
11790 }
11791 c.onTransitionEnd();
11792
11793 });
11794 }
11795 }
11796 if (s.isArray(controlled)) {
11797 for (i = 0; i < controlled.length; i++) {
11798 if (controlled[i] !== byController && controlled[i] instanceof Swiper) {
11799 setControlledTransition(controlled[i]);
11800 }
11801 }
11802 }
11803 else if (controlled instanceof Swiper && byController !== controlled) {
11804 setControlledTransition(controlled);
11805 }
11806 }
11807 };
11808
11809 /*=========================
11810 Hash Navigation
11811 ===========================*/
11812 s.hashnav = {
11813 init: function () {
11814 if (!s.params.hashnav) return;
11815 s.hashnav.initialized = true;
11816 var hash = document.location.hash.replace('#', '');
11817 if (!hash) return;
11818 var speed = 0;
11819 for (var i = 0, length = s.slides.length; i < length; i++) {
11820 var slide = s.slides.eq(i);
11821 var slideHash = slide.attr('data-hash');
11822 if (slideHash === hash && !slide.hasClass(s.params.slideDuplicateClass)) {
11823 var index = slide.index();
11824 s.slideTo(index, speed, s.params.runCallbacksOnInit, true);
11825 }
11826 }
11827 },
11828 setHash: function () {
11829 if (!s.hashnav.initialized || !s.params.hashnav) return;
11830 document.location.hash = s.slides.eq(s.activeIndex).attr('data-hash') || '';
11831 }
11832 };
11833
11834 /*=========================
11835 Keyboard Control
11836 ===========================*/
11837 function handleKeyboard(e) {
11838 if (e.originalEvent) e = e.originalEvent; //jquery fix
11839 var kc = e.keyCode || e.charCode;
11840 // Directions locks
11841 if (!s.params.allowSwipeToNext && (isH() && kc === 39 || !isH() && kc === 40)) {
11842 return false;
11843 }
11844 if (!s.params.allowSwipeToPrev && (isH() && kc === 37 || !isH() && kc === 38)) {
11845 return false;
11846 }
11847 if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) {
11848 return;
11849 }
11850 if (document.activeElement && document.activeElement.nodeName && (document.activeElement.nodeName.toLowerCase() === 'input' || document.activeElement.nodeName.toLowerCase() === 'textarea')) {
11851 return;
11852 }
11853 if (kc === 37 || kc === 39 || kc === 38 || kc === 40) {
11854 var inView = false;
11855 //Check that swiper should be inside of visible area of window
11856 if (s.container.parents('.swiper-slide').length > 0 && s.container.parents('.swiper-slide-active').length === 0) {
11857 return;
11858 }
11859 var windowScroll = {
11860 left: window.pageXOffset,
11861 top: window.pageYOffset
11862 };
11863 var windowWidth = window.innerWidth;
11864 var windowHeight = window.innerHeight;
11865 var swiperOffset = s.container.offset();
11866 if (s.rtl) swiperOffset.left = swiperOffset.left - s.container[0].scrollLeft;
11867 var swiperCoord = [
11868 [swiperOffset.left, swiperOffset.top],
11869 [swiperOffset.left + s.width, swiperOffset.top],
11870 [swiperOffset.left, swiperOffset.top + s.height],
11871 [swiperOffset.left + s.width, swiperOffset.top + s.height]
11872 ];
11873 for (var i = 0; i < swiperCoord.length; i++) {
11874 var point = swiperCoord[i];
11875 if (
11876 point[0] >= windowScroll.left && point[0] <= windowScroll.left + windowWidth &&
11877 point[1] >= windowScroll.top && point[1] <= windowScroll.top + windowHeight
11878 ) {
11879 inView = true;
11880 }
11881
11882 }
11883 if (!inView) return;
11884 }
11885 if (isH()) {
11886 if (kc === 37 || kc === 39) {
11887 if (e.preventDefault) e.preventDefault();
11888 else e.returnValue = false;
11889 }
11890 if ((kc === 39 && !s.rtl) || (kc === 37 && s.rtl)) s.slideNext();
11891 if ((kc === 37 && !s.rtl) || (kc === 39 && s.rtl)) s.slidePrev();
11892 }
11893 else {
11894 if (kc === 38 || kc === 40) {
11895 if (e.preventDefault) e.preventDefault();
11896 else e.returnValue = false;
11897 }
11898 if (kc === 40) s.slideNext();
11899 if (kc === 38) s.slidePrev();
11900 }
11901 }
11902 s.disableKeyboardControl = function () {
11903 s.params.keyboardControl = false;
11904 $(document).off('keydown', handleKeyboard);
11905 };
11906 s.enableKeyboardControl = function () {
11907 s.params.keyboardControl = true;
11908 $(document).on('keydown', handleKeyboard);
11909 };
11910
11911
11912 /*=========================
11913 Mousewheel Control
11914 ===========================*/
11915 s.mousewheel = {
11916 event: false,
11917 lastScrollTime: (new window.Date()).getTime()
11918 };
11919 if (s.params.mousewheelControl) {
11920 try {
11921 new window.WheelEvent('wheel');
11922 s.mousewheel.event = 'wheel';
11923 } catch (e) {}
11924
11925 if (!s.mousewheel.event && document.onmousewheel !== undefined) {
11926 s.mousewheel.event = 'mousewheel';
11927 }
11928 if (!s.mousewheel.event) {
11929 s.mousewheel.event = 'DOMMouseScroll';
11930 }
11931 }
11932 function handleMousewheel(e) {
11933 if (e.originalEvent) e = e.originalEvent; //jquery fix
11934 var we = s.mousewheel.event;
11935 var delta = 0;
11936 var rtlFactor = s.rtl ? -1 : 1;
11937 //Opera & IE
11938 if (e.detail) delta = -e.detail;
11939 //WebKits
11940 else if (we === 'mousewheel') {
11941 if (s.params.mousewheelForceToAxis) {
11942 if (isH()) {
11943 if (Math.abs(e.wheelDeltaX) > Math.abs(e.wheelDeltaY)) delta = e.wheelDeltaX * rtlFactor;
11944 else return;
11945 }
11946 else {
11947 if (Math.abs(e.wheelDeltaY) > Math.abs(e.wheelDeltaX)) delta = e.wheelDeltaY;
11948 else return;
11949 }
11950 }
11951 else {
11952 delta = Math.abs(e.wheelDeltaX) > Math.abs(e.wheelDeltaY) ? - e.wheelDeltaX * rtlFactor : - e.wheelDeltaY;
11953 }
11954 }
11955 //Old FireFox
11956 else if (we === 'DOMMouseScroll') delta = -e.detail;
11957 //New FireFox
11958 else if (we === 'wheel') {
11959 if (s.params.mousewheelForceToAxis) {
11960 if (isH()) {
11961 if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) delta = -e.deltaX * rtlFactor;
11962 else return;
11963 }
11964 else {
11965 if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) delta = -e.deltaY;
11966 else return;
11967 }
11968 }
11969 else {
11970 delta = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? - e.deltaX * rtlFactor : - e.deltaY;
11971 }
11972 }
11973 if (delta === 0) return;
11974
11975 if (s.params.mousewheelInvert) delta = -delta;
11976
11977 if (!s.params.freeMode) {
11978 if ((new window.Date()).getTime() - s.mousewheel.lastScrollTime > 60) {
11979 if (delta < 0) {
11980 if ((!s.isEnd || s.params.loop) && !s.animating) s.slideNext();
11981 else if (s.params.mousewheelReleaseOnEdges) return true;
11982 }
11983 else {
11984 if ((!s.isBeginning || s.params.loop) && !s.animating) s.slidePrev();
11985 else if (s.params.mousewheelReleaseOnEdges) return true;
11986 }
11987 }
11988 s.mousewheel.lastScrollTime = (new window.Date()).getTime();
11989
11990 }
11991 else {
11992 //Freemode or scrollContainer:
11993 var position = s.getWrapperTranslate() + delta * s.params.mousewheelSensitivity;
11994 var wasBeginning = s.isBeginning,
11995 wasEnd = s.isEnd;
11996
11997 if (position >= s.minTranslate()) position = s.minTranslate();
11998 if (position <= s.maxTranslate()) position = s.maxTranslate();
11999
12000 s.setWrapperTransition(0);
12001 s.setWrapperTranslate(position);
12002 s.updateProgress();
12003 s.updateActiveIndex();
12004
12005 if (!wasBeginning && s.isBeginning || !wasEnd && s.isEnd) {
12006 s.updateClasses();
12007 }
12008
12009 if (s.params.freeModeSticky) {
12010 clearTimeout(s.mousewheel.timeout);
12011 s.mousewheel.timeout = setTimeout(function () {
12012 s.slideReset();
12013 }, 300);
12014 }
12015
12016 // Return page scroll on edge positions
12017 if (position === 0 || position === s.maxTranslate()) return;
12018 }
12019 if (s.params.autoplay) s.stopAutoplay();
12020
12021 if (e.preventDefault) e.preventDefault();
12022 else e.returnValue = false;
12023 return false;
12024 }
12025 s.disableMousewheelControl = function () {
12026 if (!s.mousewheel.event) return false;
12027 s.container.off(s.mousewheel.event, handleMousewheel);
12028 return true;
12029 };
12030
12031 s.enableMousewheelControl = function () {
12032 if (!s.mousewheel.event) return false;
12033 s.container.on(s.mousewheel.event, handleMousewheel);
12034 return true;
12035 };
12036
12037
12038 /*=========================
12039 Parallax
12040 ===========================*/
12041 function setParallaxTransform(el, progress) {
12042 el = $(el);
12043 var p, pX, pY;
12044 var rtlFactor = s.rtl ? -1 : 1;
12045
12046 p = el.attr('data-swiper-parallax') || '0';
12047 pX = el.attr('data-swiper-parallax-x');
12048 pY = el.attr('data-swiper-parallax-y');
12049 if (pX || pY) {
12050 pX = pX || '0';
12051 pY = pY || '0';
12052 }
12053 else {
12054 if (isH()) {
12055 pX = p;
12056 pY = '0';
12057 }
12058 else {
12059 pY = p;
12060 pX = '0';
12061 }
12062 }
12063
12064 if ((pX).indexOf('%') >= 0) {
12065 pX = parseInt(pX, 10) * progress * rtlFactor + '%';
12066 }
12067 else {
12068 pX = pX * progress * rtlFactor + 'px' ;
12069 }
12070 if ((pY).indexOf('%') >= 0) {
12071 pY = parseInt(pY, 10) * progress + '%';
12072 }
12073 else {
12074 pY = pY * progress + 'px' ;
12075 }
12076
12077 el.transform('translate3d(' + pX + ', ' + pY + ',0px)');
12078 }
12079 s.parallax = {
12080 setTranslate: function () {
12081 s.container.children('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function(){
12082 setParallaxTransform(this, s.progress);
12083
12084 });
12085 s.slides.each(function () {
12086 var slide = $(this);
12087 slide.find('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function () {
12088 var progress = Math.min(Math.max(slide[0].progress, -1), 1);
12089 setParallaxTransform(this, progress);
12090 });
12091 });
12092 },
12093 setTransition: function (duration) {
12094 if (typeof duration === 'undefined') duration = s.params.speed;
12095 s.container.find('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function(){
12096 var el = $(this);
12097 var parallaxDuration = parseInt(el.attr('data-swiper-parallax-duration'), 10) || duration;
12098 if (duration === 0) parallaxDuration = 0;
12099 el.transition(parallaxDuration);
12100 });
12101 }
12102 };
12103
12104
12105 /*=========================
12106 Plugins API. Collect all and init all plugins
12107 ===========================*/
12108 s._plugins = [];
12109 for (var plugin in s.plugins) {
12110 var p = s.plugins[plugin](s, s.params[plugin]);
12111 if (p) s._plugins.push(p);
12112 }
12113 // Method to call all plugins event/method
12114 s.callPlugins = function (eventName) {
12115 for (var i = 0; i < s._plugins.length; i++) {
12116 if (eventName in s._plugins[i]) {
12117 s._plugins[i][eventName](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
12118 }
12119 }
12120 };
12121
12122 /*=========================
12123 Events/Callbacks/Plugins Emitter
12124 ===========================*/
12125 function normalizeEventName (eventName) {
12126 if (eventName.indexOf('on') !== 0) {
12127 if (eventName[0] !== eventName[0].toUpperCase()) {
12128 eventName = 'on' + eventName[0].toUpperCase() + eventName.substring(1);
12129 }
12130 else {
12131 eventName = 'on' + eventName;
12132 }
12133 }
12134 return eventName;
12135 }
12136 s.emitterEventListeners = {
12137
12138 };
12139 s.emit = function (eventName) {
12140 // Trigger callbacks
12141 if (s.params[eventName]) {
12142 s.params[eventName](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
12143 }
12144 var i;
12145 // Trigger events
12146 if (s.emitterEventListeners[eventName]) {
12147 for (i = 0; i < s.emitterEventListeners[eventName].length; i++) {
12148 s.emitterEventListeners[eventName][i](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
12149 }
12150 }
12151 // Trigger plugins
12152 if (s.callPlugins) s.callPlugins(eventName, arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
12153 };
12154 s.on = function (eventName, handler) {
12155 eventName = normalizeEventName(eventName);
12156 if (!s.emitterEventListeners[eventName]) s.emitterEventListeners[eventName] = [];
12157 s.emitterEventListeners[eventName].push(handler);
12158 return s;
12159 };
12160 s.off = function (eventName, handler) {
12161 var i;
12162 eventName = normalizeEventName(eventName);
12163 if (typeof handler === 'undefined') {
12164 // Remove all handlers for such event
12165 s.emitterEventListeners[eventName] = [];
12166 return s;
12167 }
12168 if (!s.emitterEventListeners[eventName] || s.emitterEventListeners[eventName].length === 0) return;
12169 for (i = 0; i < s.emitterEventListeners[eventName].length; i++) {
12170 if(s.emitterEventListeners[eventName][i] === handler) s.emitterEventListeners[eventName].splice(i, 1);
12171 }
12172 return s;
12173 };
12174 s.once = function (eventName, handler) {
12175 eventName = normalizeEventName(eventName);
12176 var _handler = function () {
12177 handler(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
12178 s.off(eventName, _handler);
12179 };
12180 s.on(eventName, _handler);
12181 return s;
12182 };
12183
12184 // Accessibility tools
12185 s.a11y = {
12186 makeFocusable: function ($el) {
12187 $el.attr('tabIndex', '0');
12188 return $el;
12189 },
12190 addRole: function ($el, role) {
12191 $el.attr('role', role);
12192 return $el;
12193 },
12194
12195 addLabel: function ($el, label) {
12196 $el.attr('aria-label', label);
12197 return $el;
12198 },
12199
12200 disable: function ($el) {
12201 $el.attr('aria-disabled', true);
12202 return $el;
12203 },
12204
12205 enable: function ($el) {
12206 $el.attr('aria-disabled', false);
12207 return $el;
12208 },
12209
12210 onEnterKey: function (event) {
12211 if (event.keyCode !== 13) return;
12212 if ($(event.target).is(s.params.nextButton)) {
12213 s.onClickNext(event);
12214 if (s.isEnd) {
12215 s.a11y.notify(s.params.lastSlideMessage);
12216 }
12217 else {
12218 s.a11y.notify(s.params.nextSlideMessage);
12219 }
12220 }
12221 else if ($(event.target).is(s.params.prevButton)) {
12222 s.onClickPrev(event);
12223 if (s.isBeginning) {
12224 s.a11y.notify(s.params.firstSlideMessage);
12225 }
12226 else {
12227 s.a11y.notify(s.params.prevSlideMessage);
12228 }
12229 }
12230 if ($(event.target).is('.' + s.params.bulletClass)) {
12231 $(event.target)[0].click();
12232 }
12233 },
12234
12235 liveRegion: $('<span class="swiper-notification" aria-live="assertive" aria-atomic="true"></span>'),
12236
12237 notify: function (message) {
12238 var notification = s.a11y.liveRegion;
12239 if (notification.length === 0) return;
12240 notification.html('');
12241 notification.html(message);
12242 },
12243 init: function () {
12244 // Setup accessibility
12245 if (s.params.nextButton) {
12246 var nextButton = $(s.params.nextButton);
12247 s.a11y.makeFocusable(nextButton);
12248 s.a11y.addRole(nextButton, 'button');
12249 s.a11y.addLabel(nextButton, s.params.nextSlideMessage);
12250 }
12251 if (s.params.prevButton) {
12252 var prevButton = $(s.params.prevButton);
12253 s.a11y.makeFocusable(prevButton);
12254 s.a11y.addRole(prevButton, 'button');
12255 s.a11y.addLabel(prevButton, s.params.prevSlideMessage);
12256 }
12257
12258 $(s.container).append(s.a11y.liveRegion);
12259 },
12260 initPagination: function () {
12261 if (s.params.pagination && s.params.paginationClickable && s.bullets && s.bullets.length) {
12262 s.bullets.each(function () {
12263 var bullet = $(this);
12264 s.a11y.makeFocusable(bullet);
12265 s.a11y.addRole(bullet, 'button');
12266 s.a11y.addLabel(bullet, s.params.paginationBulletMessage.replace(/{{index}}/, bullet.index() + 1));
12267 });
12268 }
12269 },
12270 destroy: function () {
12271 if (s.a11y.liveRegion && s.a11y.liveRegion.length > 0) s.a11y.liveRegion.remove();
12272 }
12273 };
12274
12275
12276 /*=========================
12277 Init/Destroy
12278 ===========================*/
12279 s.init = function () {
12280 if (s.params.loop) s.createLoop();
12281 s.updateContainerSize();
12282 s.updateSlidesSize();
12283 s.updatePagination();
12284 if (s.params.scrollbar && s.scrollbar) {
12285 s.scrollbar.set();
12286 if (s.params.scrollbarDraggable) {
12287 s.scrollbar.enableDraggable();
12288 }
12289 }
12290 if (s.params.effect !== 'slide' && s.effects[s.params.effect]) {
12291 if (!s.params.loop) s.updateProgress();
12292 s.effects[s.params.effect].setTranslate();
12293 }
12294 if (s.params.loop) {
12295 s.slideTo(s.params.initialSlide + s.loopedSlides, 0, s.params.runCallbacksOnInit);
12296 }
12297 else {
12298 s.slideTo(s.params.initialSlide, 0, s.params.runCallbacksOnInit);
12299 if (s.params.initialSlide === 0) {
12300 if (s.parallax && s.params.parallax) s.parallax.setTranslate();
12301 if (s.lazy && s.params.lazyLoading) {
12302 s.lazy.load();
12303 s.lazy.initialImageLoaded = true;
12304 }
12305 }
12306 }
12307 s.attachEvents();
12308 if (s.params.observer && s.support.observer) {
12309 s.initObservers();
12310 }
12311 if (s.params.preloadImages && !s.params.lazyLoading) {
12312 s.preloadImages();
12313 }
12314 if (s.params.autoplay) {
12315 s.startAutoplay();
12316 }
12317 if (s.params.keyboardControl) {
12318 if (s.enableKeyboardControl) s.enableKeyboardControl();
12319 }
12320 if (s.params.mousewheelControl) {
12321 if (s.enableMousewheelControl) s.enableMousewheelControl();
12322 }
12323 if (s.params.hashnav) {
12324 if (s.hashnav) s.hashnav.init();
12325 }
12326 if (s.params.a11y && s.a11y) s.a11y.init();
12327 s.emit('onInit', s);
12328 };
12329
12330 // Cleanup dynamic styles
12331 s.cleanupStyles = function () {
12332 // Container
12333 s.container.removeClass(s.classNames.join(' ')).removeAttr('style');
12334
12335 // Wrapper
12336 s.wrapper.removeAttr('style');
12337
12338 // Slides
12339 if (s.slides && s.slides.length) {
12340 s.slides
12341 .removeClass([
12342 s.params.slideVisibleClass,
12343 s.params.slideActiveClass,
12344 s.params.slideNextClass,
12345 s.params.slidePrevClass
12346 ].join(' '))
12347 .removeAttr('style')
12348 .removeAttr('data-swiper-column')
12349 .removeAttr('data-swiper-row');
12350 }
12351
12352 // Pagination/Bullets
12353 if (s.paginationContainer && s.paginationContainer.length) {
12354 s.paginationContainer.removeClass(s.params.paginationHiddenClass);
12355 }
12356 if (s.bullets && s.bullets.length) {
12357 s.bullets.removeClass(s.params.bulletActiveClass);
12358 }
12359
12360 // Buttons
12361 if (s.params.prevButton) $(s.params.prevButton).removeClass(s.params.buttonDisabledClass);
12362 if (s.params.nextButton) $(s.params.nextButton).removeClass(s.params.buttonDisabledClass);
12363
12364 // Scrollbar
12365 if (s.params.scrollbar && s.scrollbar) {
12366 if (s.scrollbar.track && s.scrollbar.track.length) s.scrollbar.track.removeAttr('style');
12367 if (s.scrollbar.drag && s.scrollbar.drag.length) s.scrollbar.drag.removeAttr('style');
12368 }
12369 };
12370
12371 // Destroy
12372 s.destroy = function (deleteInstance, cleanupStyles) {
12373 // Detach evebts
12374 s.detachEvents();
12375 // Stop autoplay
12376 s.stopAutoplay();
12377 // Disable draggable
12378 if (s.params.scrollbar && s.scrollbar) {
12379 if (s.params.scrollbarDraggable) {
12380 s.scrollbar.disableDraggable();
12381 }
12382 }
12383 // Destroy loop
12384 if (s.params.loop) {
12385 s.destroyLoop();
12386 }
12387 // Cleanup styles
12388 if (cleanupStyles) {
12389 s.cleanupStyles();
12390 }
12391 // Disconnect observer
12392 s.disconnectObservers();
12393 // Disable keyboard/mousewheel
12394 if (s.params.keyboardControl) {
12395 if (s.disableKeyboardControl) s.disableKeyboardControl();
12396 }
12397 if (s.params.mousewheelControl) {
12398 if (s.disableMousewheelControl) s.disableMousewheelControl();
12399 }
12400 // Disable a11y
12401 if (s.params.a11y && s.a11y) s.a11y.destroy();
12402 // Destroy callback
12403 s.emit('onDestroy');
12404 // Delete instance
12405 if (deleteInstance !== false) s = null;
12406 };
12407
12408 s.init();
12409
12410
12411
12412 // Return swiper instance
12413 return s;
12414 };
12415
12416
12417 /*==================================================
12418 Prototype
12419 ====================================================*/
12420 Swiper.prototype = {
12421 isSafari: (function () {
12422 var ua = navigator.userAgent.toLowerCase();
12423 return (ua.indexOf('safari') >= 0 && ua.indexOf('chrome') < 0 && ua.indexOf('android') < 0);
12424 })(),
12425 isUiWebView: /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent),
12426 isArray: function (arr) {
12427 return Object.prototype.toString.apply(arr) === '[object Array]';
12428 },
12429 /*==================================================
12430 Browser
12431 ====================================================*/
12432 browser: {
12433 ie: window.navigator.pointerEnabled || window.navigator.msPointerEnabled,
12434 ieTouch: (window.navigator.msPointerEnabled && window.navigator.msMaxTouchPoints > 1) || (window.navigator.pointerEnabled && window.navigator.maxTouchPoints > 1)
12435 },
12436 /*==================================================
12437 Devices
12438 ====================================================*/
12439 device: (function () {
12440 var ua = navigator.userAgent;
12441 var android = ua.match(/(Android);?[\s\/]+([\d.]+)?/);
12442 var ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
12443 var ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/);
12444 var iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/);
12445 return {
12446 ios: ipad || iphone || ipod,
12447 android: android
12448 };
12449 })(),
12450 /*==================================================
12451 Feature Detection
12452 ====================================================*/
12453 support: {
12454 touch : (window.Modernizr && Modernizr.touch === true) || (function () {
12455 return !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch);
12456 })(),
12457
12458 transforms3d : (window.Modernizr && Modernizr.csstransforms3d === true) || (function () {
12459 var div = document.createElement('div').style;
12460 return ('webkitPerspective' in div || 'MozPerspective' in div || 'OPerspective' in div || 'MsPerspective' in div || 'perspective' in div);
12461 })(),
12462
12463 flexbox: (function () {
12464 var div = document.createElement('div').style;
12465 var styles = ('alignItems webkitAlignItems webkitBoxAlign msFlexAlign mozBoxAlign webkitFlexDirection msFlexDirection mozBoxDirection mozBoxOrient webkitBoxDirection webkitBoxOrient').split(' ');
12466 for (var i = 0; i < styles.length; i++) {
12467 if (styles[i] in div) return true;
12468 }
12469 })(),
12470
12471 observer: (function () {
12472 return ('MutationObserver' in window || 'WebkitMutationObserver' in window);
12473 })()
12474 },
12475 /*==================================================
12476 Plugins
12477 ====================================================*/
12478 plugins: {}
12479 };
12480
12481
12482 /*===========================
12483 Dom7 Library
12484 ===========================*/
12485 var Dom7 = (function () {
12486 var Dom7 = function (arr) {
12487 var _this = this, i = 0;
12488 // Create array-like object
12489 for (i = 0; i < arr.length; i++) {
12490 _this[i] = arr[i];
12491 }
12492 _this.length = arr.length;
12493 // Return collection with methods
12494 return this;
12495 };
12496 var $ = function (selector, context) {
12497 var arr = [], i = 0;
12498 if (selector && !context) {
12499 if (selector instanceof Dom7) {
12500 return selector;
12501 }
12502 }
12503 if (selector) {
12504 // String
12505 if (typeof selector === 'string') {
12506 var els, tempParent, html = selector.trim();
12507 if (html.indexOf('<') >= 0 && html.indexOf('>') >= 0) {
12508 var toCreate = 'div';
12509 if (html.indexOf('<li') === 0) toCreate = 'ul';
12510 if (html.indexOf('<tr') === 0) toCreate = 'tbody';
12511 if (html.indexOf('<td') === 0 || html.indexOf('<th') === 0) toCreate = 'tr';
12512 if (html.indexOf('<tbody') === 0) toCreate = 'table';
12513 if (html.indexOf('<option') === 0) toCreate = 'select';
12514 tempParent = document.createElement(toCreate);
12515 tempParent.innerHTML = selector;
12516 for (i = 0; i < tempParent.childNodes.length; i++) {
12517 arr.push(tempParent.childNodes[i]);
12518 }
12519 }
12520 else {
12521 if (!context && selector[0] === '#' && !selector.match(/[ .<>:~]/)) {
12522 // Pure ID selector
12523 els = [document.getElementById(selector.split('#')[1])];
12524 }
12525 else {
12526 // Other selectors
12527 els = (context || document).querySelectorAll(selector);
12528 }
12529 for (i = 0; i < els.length; i++) {
12530 if (els[i]) arr.push(els[i]);
12531 }
12532 }
12533 }
12534 // Node/element
12535 else if (selector.nodeType || selector === window || selector === document) {
12536 arr.push(selector);
12537 }
12538 //Array of elements or instance of Dom
12539 else if (selector.length > 0 && selector[0].nodeType) {
12540 for (i = 0; i < selector.length; i++) {
12541 arr.push(selector[i]);
12542 }
12543 }
12544 }
12545 return new Dom7(arr);
12546 };
12547 Dom7.prototype = {
12548 // Classes and attriutes
12549 addClass: function (className) {
12550 if (typeof className === 'undefined') {
12551 return this;
12552 }
12553 var classes = className.split(' ');
12554 for (var i = 0; i < classes.length; i++) {
12555 for (var j = 0; j < this.length; j++) {
12556 this[j].classList.add(classes[i]);
12557 }
12558 }
12559 return this;
12560 },
12561 removeClass: function (className) {
12562 var classes = className.split(' ');
12563 for (var i = 0; i < classes.length; i++) {
12564 for (var j = 0; j < this.length; j++) {
12565 this[j].classList.remove(classes[i]);
12566 }
12567 }
12568 return this;
12569 },
12570 hasClass: function (className) {
12571 if (!this[0]) return false;
12572 else return this[0].classList.contains(className);
12573 },
12574 toggleClass: function (className) {
12575 var classes = className.split(' ');
12576 for (var i = 0; i < classes.length; i++) {
12577 for (var j = 0; j < this.length; j++) {
12578 this[j].classList.toggle(classes[i]);
12579 }
12580 }
12581 return this;
12582 },
12583 attr: function (attrs, value) {
12584 if (arguments.length === 1 && typeof attrs === 'string') {
12585 // Get attr
12586 if (this[0]) return this[0].getAttribute(attrs);
12587 else return undefined;
12588 }
12589 else {
12590 // Set attrs
12591 for (var i = 0; i < this.length; i++) {
12592 if (arguments.length === 2) {
12593 // String
12594 this[i].setAttribute(attrs, value);
12595 }
12596 else {
12597 // Object
12598 for (var attrName in attrs) {
12599 this[i][attrName] = attrs[attrName];
12600 this[i].setAttribute(attrName, attrs[attrName]);
12601 }
12602 }
12603 }
12604 return this;
12605 }
12606 },
12607 removeAttr: function (attr) {
12608 for (var i = 0; i < this.length; i++) {
12609 this[i].removeAttribute(attr);
12610 }
12611 return this;
12612 },
12613 data: function (key, value) {
12614 if (typeof value === 'undefined') {
12615 // Get value
12616 if (this[0]) {
12617 var dataKey = this[0].getAttribute('data-' + key);
12618 if (dataKey) return dataKey;
12619 else if (this[0].dom7ElementDataStorage && (key in this[0].dom7ElementDataStorage)) return this[0].dom7ElementDataStorage[key];
12620 else return undefined;
12621 }
12622 else return undefined;
12623 }
12624 else {
12625 // Set value
12626 for (var i = 0; i < this.length; i++) {
12627 var el = this[i];
12628 if (!el.dom7ElementDataStorage) el.dom7ElementDataStorage = {};
12629 el.dom7ElementDataStorage[key] = value;
12630 }
12631 return this;
12632 }
12633 },
12634 // Transforms
12635 transform : function (transform) {
12636 for (var i = 0; i < this.length; i++) {
12637 var elStyle = this[i].style;
12638 elStyle.webkitTransform = elStyle.MsTransform = elStyle.msTransform = elStyle.MozTransform = elStyle.OTransform = elStyle.transform = transform;
12639 }
12640 return this;
12641 },
12642 transition: function (duration) {
12643 if (typeof duration !== 'string') {
12644 duration = duration + 'ms';
12645 }
12646 for (var i = 0; i < this.length; i++) {
12647 var elStyle = this[i].style;
12648 elStyle.webkitTransitionDuration = elStyle.MsTransitionDuration = elStyle.msTransitionDuration = elStyle.MozTransitionDuration = elStyle.OTransitionDuration = elStyle.transitionDuration = duration;
12649 }
12650 return this;
12651 },
12652 //Events
12653 on: function (eventName, targetSelector, listener, capture) {
12654 function handleLiveEvent(e) {
12655 var target = e.target;
12656 if ($(target).is(targetSelector)) listener.call(target, e);
12657 else {
12658 var parents = $(target).parents();
12659 for (var k = 0; k < parents.length; k++) {
12660 if ($(parents[k]).is(targetSelector)) listener.call(parents[k], e);
12661 }
12662 }
12663 }
12664 var events = eventName.split(' ');
12665 var i, j;
12666 for (i = 0; i < this.length; i++) {
12667 if (typeof targetSelector === 'function' || targetSelector === false) {
12668 // Usual events
12669 if (typeof targetSelector === 'function') {
12670 listener = arguments[1];
12671 capture = arguments[2] || false;
12672 }
12673 for (j = 0; j < events.length; j++) {
12674 this[i].addEventListener(events[j], listener, capture);
12675 }
12676 }
12677 else {
12678 //Live events
12679 for (j = 0; j < events.length; j++) {
12680 if (!this[i].dom7LiveListeners) this[i].dom7LiveListeners = [];
12681 this[i].dom7LiveListeners.push({listener: listener, liveListener: handleLiveEvent});
12682 this[i].addEventListener(events[j], handleLiveEvent, capture);
12683 }
12684 }
12685 }
12686
12687 return this;
12688 },
12689 off: function (eventName, targetSelector, listener, capture) {
12690 var events = eventName.split(' ');
12691 for (var i = 0; i < events.length; i++) {
12692 for (var j = 0; j < this.length; j++) {
12693 if (typeof targetSelector === 'function' || targetSelector === false) {
12694 // Usual events
12695 if (typeof targetSelector === 'function') {
12696 listener = arguments[1];
12697 capture = arguments[2] || false;
12698 }
12699 this[j].removeEventListener(events[i], listener, capture);
12700 }
12701 else {
12702 // Live event
12703 if (this[j].dom7LiveListeners) {
12704 for (var k = 0; k < this[j].dom7LiveListeners.length; k++) {
12705 if (this[j].dom7LiveListeners[k].listener === listener) {
12706 this[j].removeEventListener(events[i], this[j].dom7LiveListeners[k].liveListener, capture);
12707 }
12708 }
12709 }
12710 }
12711 }
12712 }
12713 return this;
12714 },
12715 once: function (eventName, targetSelector, listener, capture) {
12716 var dom = this;
12717 if (typeof targetSelector === 'function') {
12718 targetSelector = false;
12719 listener = arguments[1];
12720 capture = arguments[2];
12721 }
12722 function proxy(e) {
12723 listener(e);
12724 dom.off(eventName, targetSelector, proxy, capture);
12725 }
12726 dom.on(eventName, targetSelector, proxy, capture);
12727 },
12728 trigger: function (eventName, eventData) {
12729 for (var i = 0; i < this.length; i++) {
12730 var evt;
12731 try {
12732 evt = new window.CustomEvent(eventName, {detail: eventData, bubbles: true, cancelable: true});
12733 }
12734 catch (e) {
12735 evt = document.createEvent('Event');
12736 evt.initEvent(eventName, true, true);
12737 evt.detail = eventData;
12738 }
12739 this[i].dispatchEvent(evt);
12740 }
12741 return this;
12742 },
12743 transitionEnd: function (callback) {
12744 var events = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'],
12745 i, j, dom = this;
12746 function fireCallBack(e) {
12747 /*jshint validthis:true */
12748 if (e.target !== this) return;
12749 callback.call(this, e);
12750 for (i = 0; i < events.length; i++) {
12751 dom.off(events[i], fireCallBack);
12752 }
12753 }
12754 if (callback) {
12755 for (i = 0; i < events.length; i++) {
12756 dom.on(events[i], fireCallBack);
12757 }
12758 }
12759 return this;
12760 },
12761 // Sizing/Styles
12762 width: function () {
12763 if (this[0] === window) {
12764 return window.innerWidth;
12765 }
12766 else {
12767 if (this.length > 0) {
12768 return parseFloat(this.css('width'));
12769 }
12770 else {
12771 return null;
12772 }
12773 }
12774 },
12775 outerWidth: function (includeMargins) {
12776 if (this.length > 0) {
12777 if (includeMargins)
12778 return this[0].offsetWidth + parseFloat(this.css('margin-right')) + parseFloat(this.css('margin-left'));
12779 else
12780 return this[0].offsetWidth;
12781 }
12782 else return null;
12783 },
12784 height: function () {
12785 if (this[0] === window) {
12786 return window.innerHeight;
12787 }
12788 else {
12789 if (this.length > 0) {
12790 return parseFloat(this.css('height'));
12791 }
12792 else {
12793 return null;
12794 }
12795 }
12796 },
12797 outerHeight: function (includeMargins) {
12798 if (this.length > 0) {
12799 if (includeMargins)
12800 return this[0].offsetHeight + parseFloat(this.css('margin-top')) + parseFloat(this.css('margin-bottom'));
12801 else
12802 return this[0].offsetHeight;
12803 }
12804 else return null;
12805 },
12806 offset: function () {
12807 if (this.length > 0) {
12808 var el = this[0];
12809 var box = el.getBoundingClientRect();
12810 var body = document.body;
12811 var clientTop = el.clientTop || body.clientTop || 0;
12812 var clientLeft = el.clientLeft || body.clientLeft || 0;
12813 var scrollTop = window.pageYOffset || el.scrollTop;
12814 var scrollLeft = window.pageXOffset || el.scrollLeft;
12815 return {
12816 top: box.top + scrollTop - clientTop,
12817 left: box.left + scrollLeft - clientLeft
12818 };
12819 }
12820 else {
12821 return null;
12822 }
12823 },
12824 css: function (props, value) {
12825 var i;
12826 if (arguments.length === 1) {
12827 if (typeof props === 'string') {
12828 if (this[0]) return window.getComputedStyle(this[0], null).getPropertyValue(props);
12829 }
12830 else {
12831 for (i = 0; i < this.length; i++) {
12832 for (var prop in props) {
12833 this[i].style[prop] = props[prop];
12834 }
12835 }
12836 return this;
12837 }
12838 }
12839 if (arguments.length === 2 && typeof props === 'string') {
12840 for (i = 0; i < this.length; i++) {
12841 this[i].style[props] = value;
12842 }
12843 return this;
12844 }
12845 return this;
12846 },
12847
12848 //Dom manipulation
12849 each: function (callback) {
12850 for (var i = 0; i < this.length; i++) {
12851 callback.call(this[i], i, this[i]);
12852 }
12853 return this;
12854 },
12855 html: function (html) {
12856 if (typeof html === 'undefined') {
12857 return this[0] ? this[0].innerHTML : undefined;
12858 }
12859 else {
12860 for (var i = 0; i < this.length; i++) {
12861 this[i].innerHTML = html;
12862 }
12863 return this;
12864 }
12865 },
12866 is: function (selector) {
12867 if (!this[0]) return false;
12868 var compareWith, i;
12869 if (typeof selector === 'string') {
12870 var el = this[0];
12871 if (el === document) return selector === document;
12872 if (el === window) return selector === window;
12873
12874 if (el.matches) return el.matches(selector);
12875 else if (el.webkitMatchesSelector) return el.webkitMatchesSelector(selector);
12876 else if (el.mozMatchesSelector) return el.mozMatchesSelector(selector);
12877 else if (el.msMatchesSelector) return el.msMatchesSelector(selector);
12878 else {
12879 compareWith = $(selector);
12880 for (i = 0; i < compareWith.length; i++) {
12881 if (compareWith[i] === this[0]) return true;
12882 }
12883 return false;
12884 }
12885 }
12886 else if (selector === document) return this[0] === document;
12887 else if (selector === window) return this[0] === window;
12888 else {
12889 if (selector.nodeType || selector instanceof Dom7) {
12890 compareWith = selector.nodeType ? [selector] : selector;
12891 for (i = 0; i < compareWith.length; i++) {
12892 if (compareWith[i] === this[0]) return true;
12893 }
12894 return false;
12895 }
12896 return false;
12897 }
12898
12899 },
12900 index: function () {
12901 if (this[0]) {
12902 var child = this[0];
12903 var i = 0;
12904 while ((child = child.previousSibling) !== null) {
12905 if (child.nodeType === 1) i++;
12906 }
12907 return i;
12908 }
12909 else return undefined;
12910 },
12911 eq: function (index) {
12912 if (typeof index === 'undefined') return this;
12913 var length = this.length;
12914 var returnIndex;
12915 if (index > length - 1) {
12916 return new Dom7([]);
12917 }
12918 if (index < 0) {
12919 returnIndex = length + index;
12920 if (returnIndex < 0) return new Dom7([]);
12921 else return new Dom7([this[returnIndex]]);
12922 }
12923 return new Dom7([this[index]]);
12924 },
12925 append: function (newChild) {
12926 var i, j;
12927 for (i = 0; i < this.length; i++) {
12928 if (typeof newChild === 'string') {
12929 var tempDiv = document.createElement('div');
12930 tempDiv.innerHTML = newChild;
12931 while (tempDiv.firstChild) {
12932 this[i].appendChild(tempDiv.firstChild);
12933 }
12934 }
12935 else if (newChild instanceof Dom7) {
12936 for (j = 0; j < newChild.length; j++) {
12937 this[i].appendChild(newChild[j]);
12938 }
12939 }
12940 else {
12941 this[i].appendChild(newChild);
12942 }
12943 }
12944 return this;
12945 },
12946 prepend: function (newChild) {
12947 var i, j;
12948 for (i = 0; i < this.length; i++) {
12949 if (typeof newChild === 'string') {
12950 var tempDiv = document.createElement('div');
12951 tempDiv.innerHTML = newChild;
12952 for (j = tempDiv.childNodes.length - 1; j >= 0; j--) {
12953 this[i].insertBefore(tempDiv.childNodes[j], this[i].childNodes[0]);
12954 }
12955 // this[i].insertAdjacentHTML('afterbegin', newChild);
12956 }
12957 else if (newChild instanceof Dom7) {
12958 for (j = 0; j < newChild.length; j++) {
12959 this[i].insertBefore(newChild[j], this[i].childNodes[0]);
12960 }
12961 }
12962 else {
12963 this[i].insertBefore(newChild, this[i].childNodes[0]);
12964 }
12965 }
12966 return this;
12967 },
12968 insertBefore: function (selector) {
12969 var before = $(selector);
12970 for (var i = 0; i < this.length; i++) {
12971 if (before.length === 1) {
12972 before[0].parentNode.insertBefore(this[i], before[0]);
12973 }
12974 else if (before.length > 1) {
12975 for (var j = 0; j < before.length; j++) {
12976 before[j].parentNode.insertBefore(this[i].cloneNode(true), before[j]);
12977 }
12978 }
12979 }
12980 },
12981 insertAfter: function (selector) {
12982 var after = $(selector);
12983 for (var i = 0; i < this.length; i++) {
12984 if (after.length === 1) {
12985 after[0].parentNode.insertBefore(this[i], after[0].nextSibling);
12986 }
12987 else if (after.length > 1) {
12988 for (var j = 0; j < after.length; j++) {
12989 after[j].parentNode.insertBefore(this[i].cloneNode(true), after[j].nextSibling);
12990 }
12991 }
12992 }
12993 },
12994 next: function (selector) {
12995 if (this.length > 0) {
12996 if (selector) {
12997 if (this[0].nextElementSibling && $(this[0].nextElementSibling).is(selector)) return new Dom7([this[0].nextElementSibling]);
12998 else return new Dom7([]);
12999 }
13000 else {
13001 if (this[0].nextElementSibling) return new Dom7([this[0].nextElementSibling]);
13002 else return new Dom7([]);
13003 }
13004 }
13005 else return new Dom7([]);
13006 },
13007 nextAll: function (selector) {
13008 var nextEls = [];
13009 var el = this[0];
13010 if (!el) return new Dom7([]);
13011 while (el.nextElementSibling) {
13012 var next = el.nextElementSibling;
13013 if (selector) {
13014 if($(next).is(selector)) nextEls.push(next);
13015 }
13016 else nextEls.push(next);
13017 el = next;
13018 }
13019 return new Dom7(nextEls);
13020 },
13021 prev: function (selector) {
13022 if (this.length > 0) {
13023 if (selector) {
13024 if (this[0].previousElementSibling && $(this[0].previousElementSibling).is(selector)) return new Dom7([this[0].previousElementSibling]);
13025 else return new Dom7([]);
13026 }
13027 else {
13028 if (this[0].previousElementSibling) return new Dom7([this[0].previousElementSibling]);
13029 else return new Dom7([]);
13030 }
13031 }
13032 else return new Dom7([]);
13033 },
13034 prevAll: function (selector) {
13035 var prevEls = [];
13036 var el = this[0];
13037 if (!el) return new Dom7([]);
13038 while (el.previousElementSibling) {
13039 var prev = el.previousElementSibling;
13040 if (selector) {
13041 if($(prev).is(selector)) prevEls.push(prev);
13042 }
13043 else prevEls.push(prev);
13044 el = prev;
13045 }
13046 return new Dom7(prevEls);
13047 },
13048 parent: function (selector) {
13049 var parents = [];
13050 for (var i = 0; i < this.length; i++) {
13051 if (selector) {
13052 if ($(this[i].parentNode).is(selector)) parents.push(this[i].parentNode);
13053 }
13054 else {
13055 parents.push(this[i].parentNode);
13056 }
13057 }
13058 return $($.unique(parents));
13059 },
13060 parents: function (selector) {
13061 var parents = [];
13062 for (var i = 0; i < this.length; i++) {
13063 var parent = this[i].parentNode;
13064 while (parent) {
13065 if (selector) {
13066 if ($(parent).is(selector)) parents.push(parent);
13067 }
13068 else {
13069 parents.push(parent);
13070 }
13071 parent = parent.parentNode;
13072 }
13073 }
13074 return $($.unique(parents));
13075 },
13076 find : function (selector) {
13077 var foundElements = [];
13078 for (var i = 0; i < this.length; i++) {
13079 var found = this[i].querySelectorAll(selector);
13080 for (var j = 0; j < found.length; j++) {
13081 foundElements.push(found[j]);
13082 }
13083 }
13084 return new Dom7(foundElements);
13085 },
13086 children: function (selector) {
13087 var children = [];
13088 for (var i = 0; i < this.length; i++) {
13089 var childNodes = this[i].childNodes;
13090
13091 for (var j = 0; j < childNodes.length; j++) {
13092 if (!selector) {
13093 if (childNodes[j].nodeType === 1) children.push(childNodes[j]);
13094 }
13095 else {
13096 if (childNodes[j].nodeType === 1 && $(childNodes[j]).is(selector)) children.push(childNodes[j]);
13097 }
13098 }
13099 }
13100 return new Dom7($.unique(children));
13101 },
13102 remove: function () {
13103 for (var i = 0; i < this.length; i++) {
13104 if (this[i].parentNode) this[i].parentNode.removeChild(this[i]);
13105 }
13106 return this;
13107 },
13108 add: function () {
13109 var dom = this;
13110 var i, j;
13111 for (i = 0; i < arguments.length; i++) {
13112 var toAdd = $(arguments[i]);
13113 for (j = 0; j < toAdd.length; j++) {
13114 dom[dom.length] = toAdd[j];
13115 dom.length++;
13116 }
13117 }
13118 return dom;
13119 }
13120 };
13121 $.fn = Dom7.prototype;
13122 $.unique = function (arr) {
13123 var unique = [];
13124 for (var i = 0; i < arr.length; i++) {
13125 if (unique.indexOf(arr[i]) === -1) unique.push(arr[i]);
13126 }
13127 return unique;
13128 };
13129
13130 return $;
13131 })();
13132
13133
13134 /*===========================
13135 Get Dom libraries
13136 ===========================*/
13137 var swiperDomPlugins = ['jQuery', 'Zepto', 'Dom7'];
13138 for (var i = 0; i < swiperDomPlugins.length; i++) {
13139 if (window[swiperDomPlugins[i]]) {
13140 addLibraryPlugin(window[swiperDomPlugins[i]]);
13141 }
13142 }
13143 // Required DOM Plugins
13144 var domLib;
13145 if (typeof Dom7 === 'undefined') {
13146 domLib = window.Dom7 || window.Zepto || window.jQuery;
13147 }
13148 else {
13149 domLib = Dom7;
13150 }
13151
13152 /*===========================
13153 Add .swiper plugin from Dom libraries
13154 ===========================*/
13155 function addLibraryPlugin(lib) {
13156 lib.fn.swiper = function (params) {
13157 var firstInstance;
13158 lib(this).each(function () {
13159 var s = new Swiper(this, params);
13160 if (!firstInstance) firstInstance = s;
13161 });
13162 return firstInstance;
13163 };
13164 }
13165
13166 if (domLib) {
13167 if (!('transitionEnd' in domLib.fn)) {
13168 domLib.fn.transitionEnd = function (callback) {
13169 var events = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'],
13170 i, j, dom = this;
13171 function fireCallBack(e) {
13172 /*jshint validthis:true */
13173 if (e.target !== this) return;
13174 callback.call(this, e);
13175 for (i = 0; i < events.length; i++) {
13176 dom.off(events[i], fireCallBack);
13177 }
13178 }
13179 if (callback) {
13180 for (i = 0; i < events.length; i++) {
13181 dom.on(events[i], fireCallBack);
13182 }
13183 }
13184 return this;
13185 };
13186 }
13187 if (!('transform' in domLib.fn)) {
13188 domLib.fn.transform = function (transform) {
13189 for (var i = 0; i < this.length; i++) {
13190 var elStyle = this[i].style;
13191 elStyle.webkitTransform = elStyle.MsTransform = elStyle.msTransform = elStyle.MozTransform = elStyle.OTransform = elStyle.transform = transform;
13192 }
13193 return this;
13194 };
13195 }
13196 if (!('transition' in domLib.fn)) {
13197 domLib.fn.transition = function (duration) {
13198 if (typeof duration !== 'string') {
13199 duration = duration + 'ms';
13200 }
13201 for (var i = 0; i < this.length; i++) {
13202 var elStyle = this[i].style;
13203 elStyle.webkitTransitionDuration = elStyle.MsTransitionDuration = elStyle.msTransitionDuration = elStyle.MozTransitionDuration = elStyle.OTransitionDuration = elStyle.transitionDuration = duration;
13204 }
13205 return this;
13206 };
13207 }
13208 }
13209
13210 ionic.views.Swiper = Swiper;
13211})();
13212
13213(function(ionic) {
13214'use strict';
13215
13216 ionic.views.Toggle = ionic.views.View.inherit({
13217 initialize: function(opts) {
13218 var self = this;
13219
13220 this.el = opts.el;
13221 this.checkbox = opts.checkbox;
13222 this.track = opts.track;
13223 this.handle = opts.handle;
13224 this.openPercent = -1;
13225 this.onChange = opts.onChange || function() {};
13226
13227 this.triggerThreshold = opts.triggerThreshold || 20;
13228
13229 this.dragStartHandler = function(e) {
13230 self.dragStart(e);
13231 };
13232 this.dragHandler = function(e) {
13233 self.drag(e);
13234 };
13235 this.holdHandler = function(e) {
13236 self.hold(e);
13237 };
13238 this.releaseHandler = function(e) {
13239 self.release(e);
13240 };
13241
13242 this.dragStartGesture = ionic.onGesture('dragstart', this.dragStartHandler, this.el);
13243 this.dragGesture = ionic.onGesture('drag', this.dragHandler, this.el);
13244 this.dragHoldGesture = ionic.onGesture('hold', this.holdHandler, this.el);
13245 this.dragReleaseGesture = ionic.onGesture('release', this.releaseHandler, this.el);
13246 },
13247
13248 destroy: function() {
13249 ionic.offGesture(this.dragStartGesture, 'dragstart', this.dragStartGesture);
13250 ionic.offGesture(this.dragGesture, 'drag', this.dragGesture);
13251 ionic.offGesture(this.dragHoldGesture, 'hold', this.holdHandler);
13252 ionic.offGesture(this.dragReleaseGesture, 'release', this.releaseHandler);
13253 },
13254
13255 tap: function() {
13256 if(this.el.getAttribute('disabled') !== 'disabled') {
13257 this.val( !this.checkbox.checked );
13258 }
13259 },
13260
13261 dragStart: function(e) {
13262 if(this.checkbox.disabled) return;
13263
13264 this._dragInfo = {
13265 width: this.el.offsetWidth,
13266 left: this.el.offsetLeft,
13267 right: this.el.offsetLeft + this.el.offsetWidth,
13268 triggerX: this.el.offsetWidth / 2,
13269 initialState: this.checkbox.checked
13270 };
13271
13272 // Stop any parent dragging
13273 e.gesture.srcEvent.preventDefault();
13274
13275 // Trigger hold styles
13276 this.hold(e);
13277 },
13278
13279 drag: function(e) {
13280 var self = this;
13281 if(!this._dragInfo) { return; }
13282
13283 // Stop any parent dragging
13284 e.gesture.srcEvent.preventDefault();
13285
13286 ionic.requestAnimationFrame(function () {
13287 if (!self._dragInfo) { return; }
13288
13289 var px = e.gesture.touches[0].pageX - self._dragInfo.left;
13290 var mx = self._dragInfo.width - self.triggerThreshold;
13291
13292 // The initial state was on, so "tend towards" on
13293 if(self._dragInfo.initialState) {
13294 if(px < self.triggerThreshold) {
13295 self.setOpenPercent(0);
13296 } else if(px > self._dragInfo.triggerX) {
13297 self.setOpenPercent(100);
13298 }
13299 } else {
13300 // The initial state was off, so "tend towards" off
13301 if(px < self._dragInfo.triggerX) {
13302 self.setOpenPercent(0);
13303 } else if(px > mx) {
13304 self.setOpenPercent(100);
13305 }
13306 }
13307 });
13308 },
13309
13310 endDrag: function() {
13311 this._dragInfo = null;
13312 },
13313
13314 hold: function() {
13315 this.el.classList.add('dragging');
13316 },
13317 release: function(e) {
13318 this.el.classList.remove('dragging');
13319 this.endDrag(e);
13320 },
13321
13322
13323 setOpenPercent: function(openPercent) {
13324 // only make a change if the new open percent has changed
13325 if(this.openPercent < 0 || (openPercent < (this.openPercent - 3) || openPercent > (this.openPercent + 3) ) ) {
13326 this.openPercent = openPercent;
13327
13328 if(openPercent === 0) {
13329 this.val(false);
13330 } else if(openPercent === 100) {
13331 this.val(true);
13332 } else {
13333 var openPixel = Math.round( (openPercent / 100) * this.track.offsetWidth - (this.handle.offsetWidth) );
13334 openPixel = (openPixel < 1 ? 0 : openPixel);
13335 this.handle.style[ionic.CSS.TRANSFORM] = 'translate3d(' + openPixel + 'px,0,0)';
13336 }
13337 }
13338 },
13339
13340 val: function(value) {
13341 if(value === true || value === false) {
13342 if(this.handle.style[ionic.CSS.TRANSFORM] !== "") {
13343 this.handle.style[ionic.CSS.TRANSFORM] = "";
13344 }
13345 this.checkbox.checked = value;
13346 this.openPercent = (value ? 100 : 0);
13347 this.onChange && this.onChange();
13348 }
13349 return this.checkbox.checked;
13350 }
13351
13352 });
13353
13354})(ionic);
13355
13356})();