From 210e8feae2fb4842bfb2de38666e6c41671fef3c Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Wed, 27 Sep 2017 12:42:48 -0400 Subject: removed lib --- www/lib/ionic/js/ionic.js | 13298 -------------------------------------------- 1 file changed, 13298 deletions(-) delete mode 100644 www/lib/ionic/js/ionic.js (limited to 'www/lib/ionic/js/ionic.js') diff --git a/www/lib/ionic/js/ionic.js b/www/lib/ionic/js/ionic.js deleted file mode 100644 index 22da4135..00000000 --- a/www/lib/ionic/js/ionic.js +++ /dev/null @@ -1,13298 +0,0 @@ -/*! - * Copyright 2015 Drifty Co. - * http://drifty.com/ - * - * Ionic, v1.2.4-nightly-1917 - * A powerful HTML5 mobile app framework. - * http://ionicframework.com/ - * - * By @maxlynch, @benjsperry, @adamdbradley <3 - * - * Licensed under the MIT license. Please see LICENSE for more information. - * - */ - -(function() { - -// Create global ionic obj and its namespaces -// build processes may have already created an ionic obj -window.ionic = window.ionic || {}; -window.ionic.views = {}; -window.ionic.version = '1.2.4-nightly-1917'; - -(function (ionic) { - - ionic.DelegateService = function(methodNames) { - - if (methodNames.indexOf('$getByHandle') > -1) { - throw new Error("Method '$getByHandle' is implicitly added to each delegate service. Do not list it as a method."); - } - - function trueFn() { return true; } - - return ['$log', function($log) { - - /* - * Creates a new object that will have all the methodNames given, - * and call them on the given the controller instance matching given - * handle. - * The reason we don't just let $getByHandle return the controller instance - * itself is that the controller instance might not exist yet. - * - * We want people to be able to do - * `var instance = $ionicScrollDelegate.$getByHandle('foo')` on controller - * instantiation, but on controller instantiation a child directive - * may not have been compiled yet! - * - * So this is our way of solving this problem: we create an object - * that will only try to fetch the controller with given handle - * once the methods are actually called. - */ - function DelegateInstance(instances, handle) { - this._instances = instances; - this.handle = handle; - } - methodNames.forEach(function(methodName) { - DelegateInstance.prototype[methodName] = instanceMethodCaller(methodName); - }); - - - /** - * The delegate service (eg $ionicNavBarDelegate) is just an instance - * with a non-defined handle, a couple extra methods for registering - * and narrowing down to a specific handle. - */ - function DelegateService() { - this._instances = []; - } - DelegateService.prototype = DelegateInstance.prototype; - DelegateService.prototype._registerInstance = function(instance, handle, filterFn) { - var instances = this._instances; - instance.$$delegateHandle = handle; - instance.$$filterFn = filterFn || trueFn; - instances.push(instance); - - return function deregister() { - var index = instances.indexOf(instance); - if (index !== -1) { - instances.splice(index, 1); - } - }; - }; - DelegateService.prototype.$getByHandle = function(handle) { - return new DelegateInstance(this._instances, handle); - }; - - return new DelegateService(); - - function instanceMethodCaller(methodName) { - return function caller() { - var handle = this.handle; - var args = arguments; - var foundInstancesCount = 0; - var returnValue; - - this._instances.forEach(function(instance) { - if ((!handle || handle == instance.$$delegateHandle) && instance.$$filterFn(instance)) { - foundInstancesCount++; - var ret = instance[methodName].apply(instance, args); - //Only return the value from the first call - if (foundInstancesCount === 1) { - returnValue = ret; - } - } - }); - - if (!foundInstancesCount && handle) { - return $log.warn( - 'Delegate for handle "' + handle + '" could not find a ' + - 'corresponding element with delegate-handle="' + handle + '"! ' + - methodName + '() was not called!\n' + - 'Possible cause: If you are calling ' + methodName + '() immediately, and ' + - 'your element with delegate-handle="' + handle + '" is a child of your ' + - 'controller, then your element may not be compiled yet. Put a $timeout ' + - 'around your call to ' + methodName + '() and try again.' - ); - } - return returnValue; - }; - } - - }]; - }; - -})(window.ionic); - -(function(window, document, ionic) { - - var readyCallbacks = []; - var isDomReady = document.readyState === 'complete' || document.readyState === 'interactive'; - - function domReady() { - isDomReady = true; - for (var x = 0; x < readyCallbacks.length; x++) { - ionic.requestAnimationFrame(readyCallbacks[x]); - } - readyCallbacks = []; - document.removeEventListener('DOMContentLoaded', domReady); - } - if (!isDomReady) { - document.addEventListener('DOMContentLoaded', domReady); - } - - - // From the man himself, Mr. Paul Irish. - // The requestAnimationFrame polyfill - // Put it on window just to preserve its context - // without having to use .call - window._rAF = (function() { - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - function(callback) { - window.setTimeout(callback, 16); - }; - })(); - - var cancelAnimationFrame = window.cancelAnimationFrame || - window.webkitCancelAnimationFrame || - window.mozCancelAnimationFrame || - window.webkitCancelRequestAnimationFrame; - - /** - * @ngdoc utility - * @name ionic.DomUtil - * @module ionic - */ - ionic.DomUtil = { - //Call with proper context - /** - * @ngdoc method - * @name ionic.DomUtil#requestAnimationFrame - * @alias ionic.requestAnimationFrame - * @description Calls [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame), or a polyfill if not available. - * @param {function} callback The function to call when the next frame - * happens. - */ - requestAnimationFrame: function(cb) { - return window._rAF(cb); - }, - - cancelAnimationFrame: function(requestId) { - cancelAnimationFrame(requestId); - }, - - /** - * @ngdoc method - * @name ionic.DomUtil#animationFrameThrottle - * @alias ionic.animationFrameThrottle - * @description - * When given a callback, if that callback is called 100 times between - * animation frames, adding Throttle will make it only run the last of - * the 100 calls. - * - * @param {function} callback a function which will be throttled to - * requestAnimationFrame - * @returns {function} A function which will then call the passed in callback. - * The passed in callback will receive the context the returned function is - * called with. - */ - animationFrameThrottle: function(cb) { - var args, isQueued, context; - return function() { - args = arguments; - context = this; - if (!isQueued) { - isQueued = true; - ionic.requestAnimationFrame(function() { - cb.apply(context, args); - isQueued = false; - }); - } - }; - }, - - contains: function(parentNode, otherNode) { - var current = otherNode; - while (current) { - if (current === parentNode) return true; - current = current.parentNode; - } - }, - - /** - * @ngdoc method - * @name ionic.DomUtil#getPositionInParent - * @description - * Find an element's scroll offset within its container. - * @param {DOMElement} element The element to find the offset of. - * @returns {object} A position object with the following properties: - * - `{number}` `left` The left offset of the element. - * - `{number}` `top` The top offset of the element. - */ - getPositionInParent: function(el) { - return { - left: el.offsetLeft, - top: el.offsetTop - }; - }, - - getOffsetTop: function(el) { - var curtop = 0; - if (el.offsetParent) { - do { - curtop += el.offsetTop; - el = el.offsetParent; - } while (el) - return curtop; - } - }, - - /** - * @ngdoc method - * @name ionic.DomUtil#ready - * @description - * Call a function when the DOM is ready, or if it is already ready - * call the function immediately. - * @param {function} callback The function to be called. - */ - ready: function(cb) { - if (isDomReady) { - ionic.requestAnimationFrame(cb); - } else { - readyCallbacks.push(cb); - } - }, - - /** - * @ngdoc method - * @name ionic.DomUtil#getTextBounds - * @description - * Get a rect representing the bounds of the given textNode. - * @param {DOMElement} textNode The textNode to find the bounds of. - * @returns {object} An object representing the bounds of the node. Properties: - * - `{number}` `left` The left position of the textNode. - * - `{number}` `right` The right position of the textNode. - * - `{number}` `top` The top position of the textNode. - * - `{number}` `bottom` The bottom position of the textNode. - * - `{number}` `width` The width of the textNode. - * - `{number}` `height` The height of the textNode. - */ - getTextBounds: function(textNode) { - if (document.createRange) { - var range = document.createRange(); - range.selectNodeContents(textNode); - if (range.getBoundingClientRect) { - var rect = range.getBoundingClientRect(); - if (rect) { - var sx = window.scrollX; - var sy = window.scrollY; - - return { - top: rect.top + sy, - left: rect.left + sx, - right: rect.left + sx + rect.width, - bottom: rect.top + sy + rect.height, - width: rect.width, - height: rect.height - }; - } - } - } - return null; - }, - - /** - * @ngdoc method - * @name ionic.DomUtil#getChildIndex - * @description - * Get the first index of a child node within the given element of the - * specified type. - * @param {DOMElement} element The element to find the index of. - * @param {string} type The nodeName to match children of element against. - * @returns {number} The index, or -1, of a child with nodeName matching type. - */ - getChildIndex: function(element, type) { - if (type) { - var ch = element.parentNode.children; - var c; - for (var i = 0, k = 0, j = ch.length; i < j; i++) { - c = ch[i]; - if (c.nodeName && c.nodeName.toLowerCase() == type) { - if (c == element) { - return k; - } - k++; - } - } - } - return Array.prototype.slice.call(element.parentNode.children).indexOf(element); - }, - - /** - * @private - */ - swapNodes: function(src, dest) { - dest.parentNode.insertBefore(src, dest); - }, - - elementIsDescendant: function(el, parent, stopAt) { - var current = el; - do { - if (current === parent) return true; - current = current.parentNode; - } while (current && current !== stopAt); - return false; - }, - - /** - * @ngdoc method - * @name ionic.DomUtil#getParentWithClass - * @param {DOMElement} element - * @param {string} className - * @returns {DOMElement} The closest parent of element matching the - * className, or null. - */ - getParentWithClass: function(e, className, depth) { - depth = depth || 10; - while (e.parentNode && depth--) { - if (e.parentNode.classList && e.parentNode.classList.contains(className)) { - return e.parentNode; - } - e = e.parentNode; - } - return null; - }, - /** - * @ngdoc method - * @name ionic.DomUtil#getParentOrSelfWithClass - * @param {DOMElement} element - * @param {string} className - * @returns {DOMElement} The closest parent or self matching the - * className, or null. - */ - getParentOrSelfWithClass: function(e, className, depth) { - depth = depth || 10; - while (e && depth--) { - if (e.classList && e.classList.contains(className)) { - return e; - } - e = e.parentNode; - } - return null; - }, - - /** - * @ngdoc method - * @name ionic.DomUtil#rectContains - * @param {number} x - * @param {number} y - * @param {number} x1 - * @param {number} y1 - * @param {number} x2 - * @param {number} y2 - * @returns {boolean} Whether {x,y} fits within the rectangle defined by - * {x1,y1,x2,y2}. - */ - rectContains: function(x, y, x1, y1, x2, y2) { - if (x < x1 || x > x2) return false; - if (y < y1 || y > y2) return false; - return true; - }, - - /** - * @ngdoc method - * @name ionic.DomUtil#blurAll - * @description - * Blurs any currently focused input element - * @returns {DOMElement} The element blurred or null - */ - blurAll: function() { - if (document.activeElement && document.activeElement != document.body) { - document.activeElement.blur(); - return document.activeElement; - } - return null; - }, - - cachedAttr: function(ele, key, value) { - ele = ele && ele.length && ele[0] || ele; - if (ele && ele.setAttribute) { - var dataKey = '$attr-' + key; - if (arguments.length > 2) { - if (ele[dataKey] !== value) { - ele.setAttribute(key, value); - ele[dataKey] = value; - } - } else if (typeof ele[dataKey] == 'undefined') { - ele[dataKey] = ele.getAttribute(key); - } - return ele[dataKey]; - } - }, - - cachedStyles: function(ele, styles) { - ele = ele && ele.length && ele[0] || ele; - if (ele && ele.style) { - for (var prop in styles) { - if (ele['$style-' + prop] !== styles[prop]) { - ele.style[prop] = ele['$style-' + prop] = styles[prop]; - } - } - } - } - - }; - - //Shortcuts - ionic.requestAnimationFrame = ionic.DomUtil.requestAnimationFrame; - ionic.cancelAnimationFrame = ionic.DomUtil.cancelAnimationFrame; - ionic.animationFrameThrottle = ionic.DomUtil.animationFrameThrottle; - -})(window, document, ionic); - -/** - * ion-events.js - * - * Author: Max Lynch - * - * Framework events handles various mobile browser events, and - * detects special events like tap/swipe/etc. and emits them - * as custom events that can be used in an app. - * - * Portions lovingly adapted from github.com/maker/ratchet and github.com/alexgibson/tap.js - thanks guys! - */ - -(function(ionic) { - - // Custom event polyfill - ionic.CustomEvent = (function() { - if( typeof window.CustomEvent === 'function' ) return CustomEvent; - - var customEvent = function(event, params) { - var evt; - params = params || { - bubbles: false, - cancelable: false, - detail: undefined - }; - try { - evt = document.createEvent("CustomEvent"); - evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); - } catch (error) { - // fallback for browsers that don't support createEvent('CustomEvent') - evt = document.createEvent("Event"); - for (var param in params) { - evt[param] = params[param]; - } - evt.initEvent(event, params.bubbles, params.cancelable); - } - return evt; - }; - customEvent.prototype = window.Event.prototype; - return customEvent; - })(); - - - /** - * @ngdoc utility - * @name ionic.EventController - * @module ionic - */ - ionic.EventController = { - VIRTUALIZED_EVENTS: ['tap', 'swipe', 'swiperight', 'swipeleft', 'drag', 'hold', 'release'], - - /** - * @ngdoc method - * @name ionic.EventController#trigger - * @alias ionic.trigger - * @param {string} eventType The event to trigger. - * @param {object} data The data for the event. Hint: pass in - * `{target: targetElement}` - * @param {boolean=} bubbles Whether the event should bubble up the DOM. - * @param {boolean=} cancelable Whether the event should be cancelable. - */ - // Trigger a new event - trigger: function(eventType, data, bubbles, cancelable) { - var event = new ionic.CustomEvent(eventType, { - detail: data, - bubbles: !!bubbles, - cancelable: !!cancelable - }); - - // Make sure to trigger the event on the given target, or dispatch it from - // the window if we don't have an event target - data && data.target && data.target.dispatchEvent && data.target.dispatchEvent(event) || window.dispatchEvent(event); - }, - - /** - * @ngdoc method - * @name ionic.EventController#on - * @alias ionic.on - * @description Listen to an event on an element. - * @param {string} type The event to listen for. - * @param {function} callback The listener to be called. - * @param {DOMElement} element The element to listen for the event on. - */ - on: function(type, callback, element) { - var e = element || window; - - // Bind a gesture if it's a virtual event - for(var i = 0, j = this.VIRTUALIZED_EVENTS.length; i < j; i++) { - if(type == this.VIRTUALIZED_EVENTS[i]) { - var gesture = new ionic.Gesture(element); - gesture.on(type, callback); - return gesture; - } - } - - // Otherwise bind a normal event - e.addEventListener(type, callback); - }, - - /** - * @ngdoc method - * @name ionic.EventController#off - * @alias ionic.off - * @description Remove an event listener. - * @param {string} type - * @param {function} callback - * @param {DOMElement} element - */ - off: function(type, callback, element) { - element.removeEventListener(type, callback); - }, - - /** - * @ngdoc method - * @name ionic.EventController#onGesture - * @alias ionic.onGesture - * @description Add an event listener for a gesture on an element. - * - * Available eventTypes (from [hammer.js](http://eightmedia.github.io/hammer.js/)): - * - * `hold`, `tap`, `doubletap`, `drag`, `dragstart`, `dragend`, `dragup`, `dragdown`,
- * `dragleft`, `dragright`, `swipe`, `swipeup`, `swipedown`, `swipeleft`, `swiperight`,
- * `transform`, `transformstart`, `transformend`, `rotate`, `pinch`, `pinchin`, `pinchout`,
- * `touch`, `release` - * - * @param {string} eventType The gesture event to listen for. - * @param {function(e)} callback The function to call when the gesture - * happens. - * @param {DOMElement} element The angular element to listen for the event on. - * @param {object} options object. - * @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on). - */ - onGesture: function(type, callback, element, options) { - var gesture = new ionic.Gesture(element, options); - gesture.on(type, callback); - return gesture; - }, - - /** - * @ngdoc method - * @name ionic.EventController#offGesture - * @alias ionic.offGesture - * @description Remove an event listener for a gesture created on an element. - * @param {ionic.Gesture} gesture The gesture that should be removed. - * @param {string} eventType The gesture event to remove the listener for. - * @param {function(e)} callback The listener to remove. - - */ - offGesture: function(gesture, type, callback) { - gesture && gesture.off(type, callback); - }, - - handlePopState: function() {} - }; - - - // Map some convenient top-level functions for event handling - ionic.on = function() { ionic.EventController.on.apply(ionic.EventController, arguments); }; - ionic.off = function() { ionic.EventController.off.apply(ionic.EventController, arguments); }; - ionic.trigger = ionic.EventController.trigger;//function() { ionic.EventController.trigger.apply(ionic.EventController.trigger, arguments); }; - ionic.onGesture = function() { return ionic.EventController.onGesture.apply(ionic.EventController.onGesture, arguments); }; - ionic.offGesture = function() { return ionic.EventController.offGesture.apply(ionic.EventController.offGesture, arguments); }; - -})(window.ionic); - -/* eslint camelcase:0 */ -/** - * Simple gesture controllers with some common gestures that emit - * gesture events. - * - * Ported from github.com/EightMedia/hammer.js Gestures - thanks! - */ -(function(ionic) { - - /** - * ionic.Gestures - * use this to create instances - * @param {HTMLElement} element - * @param {Object} options - * @returns {ionic.Gestures.Instance} - * @constructor - */ - ionic.Gesture = function(element, options) { - return new ionic.Gestures.Instance(element, options || {}); - }; - - ionic.Gestures = {}; - - // default settings - ionic.Gestures.defaults = { - // add css to the element to prevent the browser from doing - // its native behavior. this doesnt prevent the scrolling, - // but cancels the contextmenu, tap highlighting etc - // set to false to disable this - stop_browser_behavior: 'disable-user-behavior' - }; - - // detect touchevents - ionic.Gestures.HAS_POINTEREVENTS = window.navigator.pointerEnabled || window.navigator.msPointerEnabled; - ionic.Gestures.HAS_TOUCHEVENTS = ('ontouchstart' in window); - - // dont use mouseevents on mobile devices - ionic.Gestures.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android|silk/i; - ionic.Gestures.NO_MOUSEEVENTS = ionic.Gestures.HAS_TOUCHEVENTS && window.navigator.userAgent.match(ionic.Gestures.MOBILE_REGEX); - - // eventtypes per touchevent (start, move, end) - // are filled by ionic.Gestures.event.determineEventTypes on setup - ionic.Gestures.EVENT_TYPES = {}; - - // direction defines - ionic.Gestures.DIRECTION_DOWN = 'down'; - ionic.Gestures.DIRECTION_LEFT = 'left'; - ionic.Gestures.DIRECTION_UP = 'up'; - ionic.Gestures.DIRECTION_RIGHT = 'right'; - - // pointer type - ionic.Gestures.POINTER_MOUSE = 'mouse'; - ionic.Gestures.POINTER_TOUCH = 'touch'; - ionic.Gestures.POINTER_PEN = 'pen'; - - // touch event defines - ionic.Gestures.EVENT_START = 'start'; - ionic.Gestures.EVENT_MOVE = 'move'; - ionic.Gestures.EVENT_END = 'end'; - - // hammer document where the base events are added at - ionic.Gestures.DOCUMENT = window.document; - - // plugins namespace - ionic.Gestures.plugins = {}; - - // if the window events are set... - ionic.Gestures.READY = false; - - /** - * setup events to detect gestures on the document - */ - function setup() { - if(ionic.Gestures.READY) { - return; - } - - // find what eventtypes we add listeners to - ionic.Gestures.event.determineEventTypes(); - - // Register all gestures inside ionic.Gestures.gestures - for(var name in ionic.Gestures.gestures) { - if(ionic.Gestures.gestures.hasOwnProperty(name)) { - ionic.Gestures.detection.register(ionic.Gestures.gestures[name]); - } - } - - // Add touch events on the document - ionic.Gestures.event.onTouch(ionic.Gestures.DOCUMENT, ionic.Gestures.EVENT_MOVE, ionic.Gestures.detection.detect); - ionic.Gestures.event.onTouch(ionic.Gestures.DOCUMENT, ionic.Gestures.EVENT_END, ionic.Gestures.detection.detect); - - // ionic.Gestures is ready...! - ionic.Gestures.READY = true; - } - - /** - * create new hammer instance - * all methods should return the instance itself, so it is chainable. - * @param {HTMLElement} element - * @param {Object} [options={}] - * @returns {ionic.Gestures.Instance} - * @name Gesture.Instance - * @constructor - */ - ionic.Gestures.Instance = function(element, options) { - var self = this; - - // A null element was passed into the instance, which means - // whatever lookup was done to find this element failed to find it - // so we can't listen for events on it. - if(element === null) { - void 0; - return this; - } - - // setup ionic.GesturesJS window events and register all gestures - // this also sets up the default options - setup(); - - this.element = element; - - // start/stop detection option - this.enabled = true; - - // merge options - this.options = ionic.Gestures.utils.extend( - ionic.Gestures.utils.extend({}, ionic.Gestures.defaults), - options || {}); - - // add some css to the element to prevent the browser from doing its native behavoir - if(this.options.stop_browser_behavior) { - ionic.Gestures.utils.stopDefaultBrowserBehavior(this.element, this.options.stop_browser_behavior); - } - - // start detection on touchstart - ionic.Gestures.event.onTouch(element, ionic.Gestures.EVENT_START, function(ev) { - if(self.enabled) { - ionic.Gestures.detection.startDetect(self, ev); - } - }); - - // return instance - return this; - }; - - - ionic.Gestures.Instance.prototype = { - /** - * bind events to the instance - * @param {String} gesture - * @param {Function} handler - * @returns {ionic.Gestures.Instance} - */ - on: function onEvent(gesture, handler){ - var gestures = gesture.split(' '); - for(var t = 0; t < gestures.length; t++) { - this.element.addEventListener(gestures[t], handler, false); - } - return this; - }, - - - /** - * unbind events to the instance - * @param {String} gesture - * @param {Function} handler - * @returns {ionic.Gestures.Instance} - */ - off: function offEvent(gesture, handler){ - var gestures = gesture.split(' '); - for(var t = 0; t < gestures.length; t++) { - this.element.removeEventListener(gestures[t], handler, false); - } - return this; - }, - - - /** - * trigger gesture event - * @param {String} gesture - * @param {Object} eventData - * @returns {ionic.Gestures.Instance} - */ - trigger: function triggerEvent(gesture, eventData){ - // create DOM event - var event = ionic.Gestures.DOCUMENT.createEvent('Event'); - event.initEvent(gesture, true, true); - event.gesture = eventData; - - // trigger on the target if it is in the instance element, - // this is for event delegation tricks - var element = this.element; - if(ionic.Gestures.utils.hasParent(eventData.target, element)) { - element = eventData.target; - } - - element.dispatchEvent(event); - return this; - }, - - - /** - * enable of disable hammer.js detection - * @param {Boolean} state - * @returns {ionic.Gestures.Instance} - */ - enable: function enable(state) { - this.enabled = state; - return this; - } - }; - - /** - * this holds the last move event, - * used to fix empty touchend issue - * see the onTouch event for an explanation - * type {Object} - */ - var last_move_event = null; - - - /** - * when the mouse is hold down, this is true - * type {Boolean} - */ - var enable_detect = false; - - - /** - * when touch events have been fired, this is true - * type {Boolean} - */ - var touch_triggered = false; - - - ionic.Gestures.event = { - /** - * simple addEventListener - * @param {HTMLElement} element - * @param {String} type - * @param {Function} handler - */ - bindDom: function(element, type, handler) { - var types = type.split(' '); - for(var t = 0; t < types.length; t++) { - element.addEventListener(types[t], handler, false); - } - }, - - - /** - * touch events with mouse fallback - * @param {HTMLElement} element - * @param {String} eventType like ionic.Gestures.EVENT_MOVE - * @param {Function} handler - */ - onTouch: function onTouch(element, eventType, handler) { - var self = this; - - this.bindDom(element, ionic.Gestures.EVENT_TYPES[eventType], function bindDomOnTouch(ev) { - var sourceEventType = ev.type.toLowerCase(); - - // onmouseup, but when touchend has been fired we do nothing. - // this is for touchdevices which also fire a mouseup on touchend - if(sourceEventType.match(/mouse/) && touch_triggered) { - return; - } - - // mousebutton must be down or a touch event - else if( sourceEventType.match(/touch/) || // touch events are always on screen - sourceEventType.match(/pointerdown/) || // pointerevents touch - (sourceEventType.match(/mouse/) && ev.which === 1) // mouse is pressed - ){ - enable_detect = true; - } - - // mouse isn't pressed - else if(sourceEventType.match(/mouse/) && ev.which !== 1) { - enable_detect = false; - } - - - // we are in a touch event, set the touch triggered bool to true, - // this for the conflicts that may occur on ios and android - if(sourceEventType.match(/touch|pointer/)) { - touch_triggered = true; - } - - // count the total touches on the screen - var count_touches = 0; - - // when touch has been triggered in this detection session - // and we are now handling a mouse event, we stop that to prevent conflicts - if(enable_detect) { - // update pointerevent - if(ionic.Gestures.HAS_POINTEREVENTS && eventType != ionic.Gestures.EVENT_END) { - count_touches = ionic.Gestures.PointerEvent.updatePointer(eventType, ev); - } - // touch - else if(sourceEventType.match(/touch/)) { - count_touches = ev.touches.length; - } - // mouse - else if(!touch_triggered) { - count_touches = sourceEventType.match(/up/) ? 0 : 1; - } - - // if we are in a end event, but when we remove one touch and - // we still have enough, set eventType to move - if(count_touches > 0 && eventType == ionic.Gestures.EVENT_END) { - eventType = ionic.Gestures.EVENT_MOVE; - } - // no touches, force the end event - else if(!count_touches) { - eventType = ionic.Gestures.EVENT_END; - } - - // store the last move event - if(count_touches || last_move_event === null) { - last_move_event = ev; - } - - // trigger the handler - handler.call(ionic.Gestures.detection, self.collectEventData(element, eventType, self.getTouchList(last_move_event, eventType), ev)); - - // remove pointerevent from list - if(ionic.Gestures.HAS_POINTEREVENTS && eventType == ionic.Gestures.EVENT_END) { - count_touches = ionic.Gestures.PointerEvent.updatePointer(eventType, ev); - } - } - - //debug(sourceEventType +" "+ eventType); - - // on the end we reset everything - if(!count_touches) { - last_move_event = null; - enable_detect = false; - touch_triggered = false; - ionic.Gestures.PointerEvent.reset(); - } - }); - }, - - - /** - * we have different events for each device/browser - * determine what we need and set them in the ionic.Gestures.EVENT_TYPES constant - */ - determineEventTypes: function determineEventTypes() { - // determine the eventtype we want to set - var types; - - // pointerEvents magic - if(ionic.Gestures.HAS_POINTEREVENTS) { - types = ionic.Gestures.PointerEvent.getEvents(); - } - // on Android, iOS, blackberry, windows mobile we dont want any mouseevents - else if(ionic.Gestures.NO_MOUSEEVENTS) { - types = [ - 'touchstart', - 'touchmove', - 'touchend touchcancel']; - } - // for non pointer events browsers and mixed browsers, - // like chrome on windows8 touch laptop - else { - types = [ - 'touchstart mousedown', - 'touchmove mousemove', - 'touchend touchcancel mouseup']; - } - - ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_START] = types[0]; - ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_MOVE] = types[1]; - ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_END] = types[2]; - }, - - - /** - * create touchlist depending on the event - * @param {Object} ev - * @param {String} eventType used by the fakemultitouch plugin - */ - getTouchList: function getTouchList(ev/*, eventType*/) { - // get the fake pointerEvent touchlist - if(ionic.Gestures.HAS_POINTEREVENTS) { - return ionic.Gestures.PointerEvent.getTouchList(); - } - // get the touchlist - else if(ev.touches) { - return ev.touches; - } - // make fake touchlist from mouse position - else { - ev.identifier = 1; - return [ev]; - } - }, - - - /** - * collect event data for ionic.Gestures js - * @param {HTMLElement} element - * @param {String} eventType like ionic.Gestures.EVENT_MOVE - * @param {Object} eventData - */ - collectEventData: function collectEventData(element, eventType, touches, ev) { - - // find out pointerType - var pointerType = ionic.Gestures.POINTER_TOUCH; - if(ev.type.match(/mouse/) || ionic.Gestures.PointerEvent.matchType(ionic.Gestures.POINTER_MOUSE, ev)) { - pointerType = ionic.Gestures.POINTER_MOUSE; - } - - return { - center: ionic.Gestures.utils.getCenter(touches), - timeStamp: new Date().getTime(), - target: ev.target, - touches: touches, - eventType: eventType, - pointerType: pointerType, - srcEvent: ev, - - /** - * prevent the browser default actions - * mostly used to disable scrolling of the browser - */ - preventDefault: function() { - if(this.srcEvent.preventManipulation) { - this.srcEvent.preventManipulation(); - } - - if(this.srcEvent.preventDefault) { - // this.srcEvent.preventDefault(); - } - }, - - /** - * stop bubbling the event up to its parents - */ - stopPropagation: function() { - this.srcEvent.stopPropagation(); - }, - - /** - * immediately stop gesture detection - * might be useful after a swipe was detected - * @return {*} - */ - stopDetect: function() { - return ionic.Gestures.detection.stopDetect(); - } - }; - } - }; - - ionic.Gestures.PointerEvent = { - /** - * holds all pointers - * type {Object} - */ - pointers: {}, - - /** - * get a list of pointers - * @returns {Array} touchlist - */ - getTouchList: function() { - var self = this; - var touchlist = []; - - // we can use forEach since pointerEvents only is in IE10 - Object.keys(self.pointers).sort().forEach(function(id) { - touchlist.push(self.pointers[id]); - }); - return touchlist; - }, - - /** - * update the position of a pointer - * @param {String} type ionic.Gestures.EVENT_END - * @param {Object} pointerEvent - */ - updatePointer: function(type, pointerEvent) { - if(type == ionic.Gestures.EVENT_END) { - this.pointers = {}; - } - else { - pointerEvent.identifier = pointerEvent.pointerId; - this.pointers[pointerEvent.pointerId] = pointerEvent; - } - - return Object.keys(this.pointers).length; - }, - - /** - * check if ev matches pointertype - * @param {String} pointerType ionic.Gestures.POINTER_MOUSE - * @param {PointerEvent} ev - */ - matchType: function(pointerType, ev) { - if(!ev.pointerType) { - return false; - } - - var types = {}; - types[ionic.Gestures.POINTER_MOUSE] = (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE || ev.pointerType == ionic.Gestures.POINTER_MOUSE); - types[ionic.Gestures.POINTER_TOUCH] = (ev.pointerType == ev.MSPOINTER_TYPE_TOUCH || ev.pointerType == ionic.Gestures.POINTER_TOUCH); - types[ionic.Gestures.POINTER_PEN] = (ev.pointerType == ev.MSPOINTER_TYPE_PEN || ev.pointerType == ionic.Gestures.POINTER_PEN); - return types[pointerType]; - }, - - - /** - * get events - */ - getEvents: function() { - return [ - 'pointerdown MSPointerDown', - 'pointermove MSPointerMove', - 'pointerup pointercancel MSPointerUp MSPointerCancel' - ]; - }, - - /** - * reset the list - */ - reset: function() { - this.pointers = {}; - } - }; - - - ionic.Gestures.utils = { - /** - * extend method, - * also used for cloning when dest is an empty object - * @param {Object} dest - * @param {Object} src - * @param {Boolean} merge do a merge - * @returns {Object} dest - */ - extend: function extend(dest, src, merge) { - for (var key in src) { - if(dest[key] !== undefined && merge) { - continue; - } - dest[key] = src[key]; - } - return dest; - }, - - - /** - * find if a node is in the given parent - * used for event delegation tricks - * @param {HTMLElement} node - * @param {HTMLElement} parent - * @returns {boolean} has_parent - */ - hasParent: function(node, parent) { - while(node){ - if(node == parent) { - return true; - } - node = node.parentNode; - } - return false; - }, - - - /** - * get the center of all the touches - * @param {Array} touches - * @returns {Object} center - */ - getCenter: function getCenter(touches) { - var valuesX = [], valuesY = []; - - for(var t = 0, len = touches.length; t < len; t++) { - valuesX.push(touches[t].pageX); - valuesY.push(touches[t].pageY); - } - - return { - pageX: ((Math.min.apply(Math, valuesX) + Math.max.apply(Math, valuesX)) / 2), - pageY: ((Math.min.apply(Math, valuesY) + Math.max.apply(Math, valuesY)) / 2) - }; - }, - - - /** - * calculate the velocity between two points - * @param {Number} delta_time - * @param {Number} delta_x - * @param {Number} delta_y - * @returns {Object} velocity - */ - getVelocity: function getVelocity(delta_time, delta_x, delta_y) { - return { - x: Math.abs(delta_x / delta_time) || 0, - y: Math.abs(delta_y / delta_time) || 0 - }; - }, - - - /** - * calculate the angle between two coordinates - * @param {Touch} touch1 - * @param {Touch} touch2 - * @returns {Number} angle - */ - getAngle: function getAngle(touch1, touch2) { - var y = touch2.pageY - touch1.pageY, - x = touch2.pageX - touch1.pageX; - return Math.atan2(y, x) * 180 / Math.PI; - }, - - - /** - * angle to direction define - * @param {Touch} touch1 - * @param {Touch} touch2 - * @returns {String} direction constant, like ionic.Gestures.DIRECTION_LEFT - */ - getDirection: function getDirection(touch1, touch2) { - var x = Math.abs(touch1.pageX - touch2.pageX), - y = Math.abs(touch1.pageY - touch2.pageY); - - if(x >= y) { - return touch1.pageX - touch2.pageX > 0 ? ionic.Gestures.DIRECTION_LEFT : ionic.Gestures.DIRECTION_RIGHT; - } - else { - return touch1.pageY - touch2.pageY > 0 ? ionic.Gestures.DIRECTION_UP : ionic.Gestures.DIRECTION_DOWN; - } - }, - - - /** - * calculate the distance between two touches - * @param {Touch} touch1 - * @param {Touch} touch2 - * @returns {Number} distance - */ - getDistance: function getDistance(touch1, touch2) { - var x = touch2.pageX - touch1.pageX, - y = touch2.pageY - touch1.pageY; - return Math.sqrt((x * x) + (y * y)); - }, - - - /** - * calculate the scale factor between two touchLists (fingers) - * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out - * @param {Array} start - * @param {Array} end - * @returns {Number} scale - */ - getScale: function getScale(start, end) { - // need two fingers... - if(start.length >= 2 && end.length >= 2) { - return this.getDistance(end[0], end[1]) / - this.getDistance(start[0], start[1]); - } - return 1; - }, - - - /** - * calculate the rotation degrees between two touchLists (fingers) - * @param {Array} start - * @param {Array} end - * @returns {Number} rotation - */ - getRotation: function getRotation(start, end) { - // need two fingers - if(start.length >= 2 && end.length >= 2) { - return this.getAngle(end[1], end[0]) - - this.getAngle(start[1], start[0]); - } - return 0; - }, - - - /** - * boolean if the direction is vertical - * @param {String} direction - * @returns {Boolean} is_vertical - */ - isVertical: function isVertical(direction) { - return (direction == ionic.Gestures.DIRECTION_UP || direction == ionic.Gestures.DIRECTION_DOWN); - }, - - - /** - * stop browser default behavior with css class - * @param {HtmlElement} element - * @param {Object} css_class - */ - stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_class) { - // changed from making many style changes to just adding a preset classname - // less DOM manipulations, less code, and easier to control in the CSS side of things - // hammer.js doesn't come with CSS, but ionic does, which is why we prefer this method - if(element && element.classList) { - element.classList.add(css_class); - element.onselectstart = function() { - return false; - }; - } - } - }; - - - ionic.Gestures.detection = { - // contains all registred ionic.Gestures.gestures in the correct order - gestures: [], - - // data of the current ionic.Gestures.gesture detection session - current: null, - - // the previous ionic.Gestures.gesture session data - // is a full clone of the previous gesture.current object - previous: null, - - // when this becomes true, no gestures are fired - stopped: false, - - - /** - * start ionic.Gestures.gesture detection - * @param {ionic.Gestures.Instance} inst - * @param {Object} eventData - */ - startDetect: function startDetect(inst, eventData) { - // already busy with a ionic.Gestures.gesture detection on an element - if(this.current) { - return; - } - - this.stopped = false; - - this.current = { - inst: inst, // reference to ionic.GesturesInstance we're working for - startEvent: ionic.Gestures.utils.extend({}, eventData), // start eventData for distances, timing etc - lastEvent: false, // last eventData - name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc - }; - - this.detect(eventData); - }, - - - /** - * ionic.Gestures.gesture detection - * @param {Object} eventData - */ - detect: function detect(eventData) { - if(!this.current || this.stopped) { - return null; - } - - // extend event data with calculations about scale, distance etc - eventData = this.extendEventData(eventData); - - // instance options - var inst_options = this.current.inst.options; - - // call ionic.Gestures.gesture handlers - for(var g = 0, len = this.gestures.length; g < len; g++) { - var gesture = this.gestures[g]; - - // only when the instance options have enabled this gesture - if(!this.stopped && inst_options[gesture.name] !== false) { - // if a handler returns false, we stop with the detection - if(gesture.handler.call(gesture, eventData, this.current.inst) === false) { - this.stopDetect(); - break; - } - } - } - - // store as previous event event - if(this.current) { - this.current.lastEvent = eventData; - } - - // endevent, but not the last touch, so dont stop - if(eventData.eventType == ionic.Gestures.EVENT_END && !eventData.touches.length - 1) { - this.stopDetect(); - } - - return eventData; - }, - - - /** - * clear the ionic.Gestures.gesture vars - * this is called on endDetect, but can also be used when a final ionic.Gestures.gesture has been detected - * to stop other ionic.Gestures.gestures from being fired - */ - stopDetect: function stopDetect() { - // clone current data to the store as the previous gesture - // used for the double tap gesture, since this is an other gesture detect session - this.previous = ionic.Gestures.utils.extend({}, this.current); - - // reset the current - this.current = null; - - // stopped! - this.stopped = true; - }, - - - /** - * extend eventData for ionic.Gestures.gestures - * @param {Object} ev - * @returns {Object} ev - */ - extendEventData: function extendEventData(ev) { - var startEv = this.current.startEvent; - - // if the touches change, set the new touches over the startEvent touches - // this because touchevents don't have all the touches on touchstart, or the - // user must place his fingers at the EXACT same time on the screen, which is not realistic - // but, sometimes it happens that both fingers are touching at the EXACT same time - if(startEv && (ev.touches.length != startEv.touches.length || ev.touches === startEv.touches)) { - // extend 1 level deep to get the touchlist with the touch objects - startEv.touches = []; - for(var i = 0, len = ev.touches.length; i < len; i++) { - startEv.touches.push(ionic.Gestures.utils.extend({}, ev.touches[i])); - } - } - - var delta_time = ev.timeStamp - startEv.timeStamp, - delta_x = ev.center.pageX - startEv.center.pageX, - delta_y = ev.center.pageY - startEv.center.pageY, - velocity = ionic.Gestures.utils.getVelocity(delta_time, delta_x, delta_y); - - ionic.Gestures.utils.extend(ev, { - deltaTime: delta_time, - deltaX: delta_x, - deltaY: delta_y, - - velocityX: velocity.x, - velocityY: velocity.y, - - distance: ionic.Gestures.utils.getDistance(startEv.center, ev.center), - angle: ionic.Gestures.utils.getAngle(startEv.center, ev.center), - direction: ionic.Gestures.utils.getDirection(startEv.center, ev.center), - - scale: ionic.Gestures.utils.getScale(startEv.touches, ev.touches), - rotation: ionic.Gestures.utils.getRotation(startEv.touches, ev.touches), - - startEvent: startEv - }); - - return ev; - }, - - - /** - * register new gesture - * @param {Object} gesture object, see gestures.js for documentation - * @returns {Array} gestures - */ - register: function register(gesture) { - // add an enable gesture options if there is no given - var options = gesture.defaults || {}; - if(options[gesture.name] === undefined) { - options[gesture.name] = true; - } - - // extend ionic.Gestures default options with the ionic.Gestures.gesture options - ionic.Gestures.utils.extend(ionic.Gestures.defaults, options, true); - - // set its index - gesture.index = gesture.index || 1000; - - // add ionic.Gestures.gesture to the list - this.gestures.push(gesture); - - // sort the list by index - this.gestures.sort(function(a, b) { - if (a.index < b.index) { - return -1; - } - if (a.index > b.index) { - return 1; - } - return 0; - }); - - return this.gestures; - } - }; - - - ionic.Gestures.gestures = ionic.Gestures.gestures || {}; - - /** - * Custom gestures - * ============================== - * - * Gesture object - * -------------------- - * The object structure of a gesture: - * - * { name: 'mygesture', - * index: 1337, - * defaults: { - * mygesture_option: true - * } - * handler: function(type, ev, inst) { - * // trigger gesture event - * inst.trigger(this.name, ev); - * } - * } - - * @param {String} name - * this should be the name of the gesture, lowercase - * it is also being used to disable/enable the gesture per instance config. - * - * @param {Number} [index=1000] - * the index of the gesture, where it is going to be in the stack of gestures detection - * like when you build an gesture that depends on the drag gesture, it is a good - * idea to place it after the index of the drag gesture. - * - * @param {Object} [defaults={}] - * the default settings of the gesture. these are added to the instance settings, - * and can be overruled per instance. you can also add the name of the gesture, - * but this is also added by default (and set to true). - * - * @param {Function} handler - * this handles the gesture detection of your custom gesture and receives the - * following arguments: - * - * @param {Object} eventData - * event data containing the following properties: - * timeStamp {Number} time the event occurred - * target {HTMLElement} target element - * touches {Array} touches (fingers, pointers, mouse) on the screen - * pointerType {String} kind of pointer that was used. matches ionic.Gestures.POINTER_MOUSE|TOUCH - * center {Object} center position of the touches. contains pageX and pageY - * deltaTime {Number} the total time of the touches in the screen - * deltaX {Number} the delta on x axis we haved moved - * deltaY {Number} the delta on y axis we haved moved - * velocityX {Number} the velocity on the x - * velocityY {Number} the velocity on y - * angle {Number} the angle we are moving - * direction {String} the direction we are moving. matches ionic.Gestures.DIRECTION_UP|DOWN|LEFT|RIGHT - * distance {Number} the distance we haved moved - * scale {Number} scaling of the touches, needs 2 touches - * rotation {Number} rotation of the touches, needs 2 touches * - * eventType {String} matches ionic.Gestures.EVENT_START|MOVE|END - * srcEvent {Object} the source event, like TouchStart or MouseDown * - * startEvent {Object} contains the same properties as above, - * but from the first touch. this is used to calculate - * distances, deltaTime, scaling etc - * - * @param {ionic.Gestures.Instance} inst - * the instance we are doing the detection for. you can get the options from - * the inst.options object and trigger the gesture event by calling inst.trigger - * - * - * Handle gestures - * -------------------- - * inside the handler you can get/set ionic.Gestures.detectionic.current. This is the current - * detection sessionic. It has the following properties - * @param {String} name - * contains the name of the gesture we have detected. it has not a real function, - * only to check in other gestures if something is detected. - * like in the drag gesture we set it to 'drag' and in the swipe gesture we can - * check if the current gesture is 'drag' by accessing ionic.Gestures.detectionic.current.name - * - * readonly - * @param {ionic.Gestures.Instance} inst - * the instance we do the detection for - * - * readonly - * @param {Object} startEvent - * contains the properties of the first gesture detection in this sessionic. - * Used for calculations about timing, distance, etc. - * - * readonly - * @param {Object} lastEvent - * contains all the properties of the last gesture detect in this sessionic. - * - * after the gesture detection session has been completed (user has released the screen) - * the ionic.Gestures.detectionic.current object is copied into ionic.Gestures.detectionic.previous, - * this is usefull for gestures like doubletap, where you need to know if the - * previous gesture was a tap - * - * options that have been set by the instance can be received by calling inst.options - * - * You can trigger a gesture event by calling inst.trigger("mygesture", event). - * The first param is the name of your gesture, the second the event argument - * - * - * Register gestures - * -------------------- - * When an gesture is added to the ionic.Gestures.gestures object, it is auto registered - * at the setup of the first ionic.Gestures instance. You can also call ionic.Gestures.detectionic.register - * manually and pass your gesture object as a param - * - */ - - /** - * Hold - * Touch stays at the same place for x time - * events hold - */ - ionic.Gestures.gestures.Hold = { - name: 'hold', - index: 10, - defaults: { - hold_timeout: 500, - hold_threshold: 9 - }, - timer: null, - handler: function holdGesture(ev, inst) { - switch(ev.eventType) { - case ionic.Gestures.EVENT_START: - // clear any running timers - clearTimeout(this.timer); - - // set the gesture so we can check in the timeout if it still is - ionic.Gestures.detection.current.name = this.name; - - // set timer and if after the timeout it still is hold, - // we trigger the hold event - this.timer = setTimeout(function() { - if(ionic.Gestures.detection.current.name == 'hold') { - ionic.tap.cancelClick(); - inst.trigger('hold', ev); - } - }, inst.options.hold_timeout); - break; - - // when you move or end we clear the timer - case ionic.Gestures.EVENT_MOVE: - if(ev.distance > inst.options.hold_threshold) { - clearTimeout(this.timer); - } - break; - - case ionic.Gestures.EVENT_END: - clearTimeout(this.timer); - break; - } - } - }; - - - /** - * Tap/DoubleTap - * Quick touch at a place or double at the same place - * events tap, doubletap - */ - ionic.Gestures.gestures.Tap = { - name: 'tap', - index: 100, - defaults: { - tap_max_touchtime: 250, - tap_max_distance: 10, - tap_always: true, - doubletap_distance: 20, - doubletap_interval: 300 - }, - handler: function tapGesture(ev, inst) { - if(ev.eventType == ionic.Gestures.EVENT_END && ev.srcEvent.type != 'touchcancel') { - // previous gesture, for the double tap since these are two different gesture detections - var prev = ionic.Gestures.detection.previous, - did_doubletap = false; - - // when the touchtime is higher then the max touch time - // or when the moving distance is too much - if(ev.deltaTime > inst.options.tap_max_touchtime || - ev.distance > inst.options.tap_max_distance) { - return; - } - - // check if double tap - if(prev && prev.name == 'tap' && - (ev.timeStamp - prev.lastEvent.timeStamp) < inst.options.doubletap_interval && - ev.distance < inst.options.doubletap_distance) { - inst.trigger('doubletap', ev); - did_doubletap = true; - } - - // do a single tap - if(!did_doubletap || inst.options.tap_always) { - ionic.Gestures.detection.current.name = 'tap'; - inst.trigger('tap', ev); - } - } - } - }; - - - /** - * Swipe - * triggers swipe events when the end velocity is above the threshold - * events swipe, swipeleft, swiperight, swipeup, swipedown - */ - ionic.Gestures.gestures.Swipe = { - name: 'swipe', - index: 40, - defaults: { - // set 0 for unlimited, but this can conflict with transform - swipe_max_touches: 1, - swipe_velocity: 0.4 - }, - handler: function swipeGesture(ev, inst) { - if(ev.eventType == ionic.Gestures.EVENT_END) { - // max touches - if(inst.options.swipe_max_touches > 0 && - ev.touches.length > inst.options.swipe_max_touches) { - return; - } - - // when the distance we moved is too small we skip this gesture - // or we can be already in dragging - if(ev.velocityX > inst.options.swipe_velocity || - ev.velocityY > inst.options.swipe_velocity) { - // trigger swipe events - inst.trigger(this.name, ev); - inst.trigger(this.name + ev.direction, ev); - } - } - } - }; - - - /** - * Drag - * Move with x fingers (default 1) around on the page. Blocking the scrolling when - * moving left and right is a good practice. When all the drag events are blocking - * you disable scrolling on that area. - * events drag, drapleft, dragright, dragup, dragdown - */ - ionic.Gestures.gestures.Drag = { - name: 'drag', - index: 50, - defaults: { - drag_min_distance: 10, - // Set correct_for_drag_min_distance to true to make the starting point of the drag - // be calculated from where the drag was triggered, not from where the touch started. - // Useful to avoid a jerk-starting drag, which can make fine-adjustments - // through dragging difficult, and be visually unappealing. - correct_for_drag_min_distance: true, - // set 0 for unlimited, but this can conflict with transform - drag_max_touches: 1, - // prevent default browser behavior when dragging occurs - // be careful with it, it makes the element a blocking element - // when you are using the drag gesture, it is a good practice to set this true - drag_block_horizontal: true, - drag_block_vertical: true, - // drag_lock_to_axis keeps the drag gesture on the axis that it started on, - // It disallows vertical directions if the initial direction was horizontal, and vice versa. - drag_lock_to_axis: false, - // drag lock only kicks in when distance > drag_lock_min_distance - // This way, locking occurs only when the distance has become large enough to reliably determine the direction - drag_lock_min_distance: 25, - // prevent default if the gesture is going the given direction - prevent_default_directions: [] - }, - triggered: false, - handler: function dragGesture(ev, inst) { - if (ev.srcEvent.type == 'touchstart' || ev.srcEvent.type == 'touchend') { - this.preventedFirstMove = false; - - } else if (!this.preventedFirstMove && ev.srcEvent.type == 'touchmove') { - // Prevent gestures that are not intended for this event handler from firing subsequent times - if (inst.options.prevent_default_directions.length > 0 - && inst.options.prevent_default_directions.indexOf(ev.direction) != -1) { - ev.srcEvent.preventDefault(); - } - this.preventedFirstMove = true; - } - - // current gesture isnt drag, but dragged is true - // this means an other gesture is busy. now call dragend - if(ionic.Gestures.detection.current.name != this.name && this.triggered) { - inst.trigger(this.name + 'end', ev); - this.triggered = false; - return; - } - - // max touches - if(inst.options.drag_max_touches > 0 && - ev.touches.length > inst.options.drag_max_touches) { - return; - } - - switch(ev.eventType) { - case ionic.Gestures.EVENT_START: - this.triggered = false; - break; - - case ionic.Gestures.EVENT_MOVE: - // when the distance we moved is too small we skip this gesture - // or we can be already in dragging - if(ev.distance < inst.options.drag_min_distance && - ionic.Gestures.detection.current.name != this.name) { - return; - } - - // we are dragging! - if(ionic.Gestures.detection.current.name != this.name) { - ionic.Gestures.detection.current.name = this.name; - if (inst.options.correct_for_drag_min_distance) { - // When a drag is triggered, set the event center to drag_min_distance pixels from the original event center. - // Without this correction, the dragged distance would jumpstart at drag_min_distance pixels instead of at 0. - // It might be useful to save the original start point somewhere - var factor = Math.abs(inst.options.drag_min_distance / ev.distance); - ionic.Gestures.detection.current.startEvent.center.pageX += ev.deltaX * factor; - ionic.Gestures.detection.current.startEvent.center.pageY += ev.deltaY * factor; - - // recalculate event data using new start point - ev = ionic.Gestures.detection.extendEventData(ev); - } - } - - // lock drag to axis? - if(ionic.Gestures.detection.current.lastEvent.drag_locked_to_axis || (inst.options.drag_lock_to_axis && inst.options.drag_lock_min_distance <= ev.distance)) { - ev.drag_locked_to_axis = true; - } - var last_direction = ionic.Gestures.detection.current.lastEvent.direction; - if(ev.drag_locked_to_axis && last_direction !== ev.direction) { - // keep direction on the axis that the drag gesture started on - if(ionic.Gestures.utils.isVertical(last_direction)) { - ev.direction = (ev.deltaY < 0) ? ionic.Gestures.DIRECTION_UP : ionic.Gestures.DIRECTION_DOWN; - } - else { - ev.direction = (ev.deltaX < 0) ? ionic.Gestures.DIRECTION_LEFT : ionic.Gestures.DIRECTION_RIGHT; - } - } - - // first time, trigger dragstart event - if(!this.triggered) { - inst.trigger(this.name + 'start', ev); - this.triggered = true; - } - - // trigger normal event - inst.trigger(this.name, ev); - - // direction event, like dragdown - inst.trigger(this.name + ev.direction, ev); - - // block the browser events - if( (inst.options.drag_block_vertical && ionic.Gestures.utils.isVertical(ev.direction)) || - (inst.options.drag_block_horizontal && !ionic.Gestures.utils.isVertical(ev.direction))) { - ev.preventDefault(); - } - break; - - case ionic.Gestures.EVENT_END: - // trigger dragend - if(this.triggered) { - inst.trigger(this.name + 'end', ev); - } - - this.triggered = false; - break; - } - } - }; - - - /** - * Transform - * User want to scale or rotate with 2 fingers - * events transform, pinch, pinchin, pinchout, rotate - */ - ionic.Gestures.gestures.Transform = { - name: 'transform', - index: 45, - defaults: { - // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1 - transform_min_scale: 0.01, - // rotation in degrees - transform_min_rotation: 1, - // prevent default browser behavior when two touches are on the screen - // but it makes the element a blocking element - // when you are using the transform gesture, it is a good practice to set this true - transform_always_block: false - }, - triggered: false, - handler: function transformGesture(ev, inst) { - // current gesture isnt drag, but dragged is true - // this means an other gesture is busy. now call dragend - if(ionic.Gestures.detection.current.name != this.name && this.triggered) { - inst.trigger(this.name + 'end', ev); - this.triggered = false; - return; - } - - // atleast multitouch - if(ev.touches.length < 2) { - return; - } - - // prevent default when two fingers are on the screen - if(inst.options.transform_always_block) { - ev.preventDefault(); - } - - switch(ev.eventType) { - case ionic.Gestures.EVENT_START: - this.triggered = false; - break; - - case ionic.Gestures.EVENT_MOVE: - var scale_threshold = Math.abs(1 - ev.scale); - var rotation_threshold = Math.abs(ev.rotation); - - // when the distance we moved is too small we skip this gesture - // or we can be already in dragging - if(scale_threshold < inst.options.transform_min_scale && - rotation_threshold < inst.options.transform_min_rotation) { - return; - } - - // we are transforming! - ionic.Gestures.detection.current.name = this.name; - - // first time, trigger dragstart event - if(!this.triggered) { - inst.trigger(this.name + 'start', ev); - this.triggered = true; - } - - inst.trigger(this.name, ev); // basic transform event - - // trigger rotate event - if(rotation_threshold > inst.options.transform_min_rotation) { - inst.trigger('rotate', ev); - } - - // trigger pinch event - if(scale_threshold > inst.options.transform_min_scale) { - inst.trigger('pinch', ev); - inst.trigger('pinch' + ((ev.scale < 1) ? 'in' : 'out'), ev); - } - break; - - case ionic.Gestures.EVENT_END: - // trigger dragend - if(this.triggered) { - inst.trigger(this.name + 'end', ev); - } - - this.triggered = false; - break; - } - } - }; - - - /** - * Touch - * Called as first, tells the user has touched the screen - * events touch - */ - ionic.Gestures.gestures.Touch = { - name: 'touch', - index: -Infinity, - defaults: { - // call preventDefault at touchstart, and makes the element blocking by - // disabling the scrolling of the page, but it improves gestures like - // transforming and dragging. - // be careful with using this, it can be very annoying for users to be stuck - // on the page - prevent_default: false, - - // disable mouse events, so only touch (or pen!) input triggers events - prevent_mouseevents: false - }, - handler: function touchGesture(ev, inst) { - if(inst.options.prevent_mouseevents && ev.pointerType == ionic.Gestures.POINTER_MOUSE) { - ev.stopDetect(); - return; - } - - if(inst.options.prevent_default) { - ev.preventDefault(); - } - - if(ev.eventType == ionic.Gestures.EVENT_START) { - inst.trigger(this.name, ev); - } - } - }; - - - /** - * Release - * Called as last, tells the user has released the screen - * events release - */ - ionic.Gestures.gestures.Release = { - name: 'release', - index: Infinity, - handler: function releaseGesture(ev, inst) { - if(ev.eventType == ionic.Gestures.EVENT_END) { - inst.trigger(this.name, ev); - } - } - }; -})(window.ionic); - -(function(window, document, ionic) { - - function getParameterByName(name) { - name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); - var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), - results = regex.exec(location.search); - return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); - } - - var IOS = 'ios'; - var ANDROID = 'android'; - var WINDOWS_PHONE = 'windowsphone'; - var EDGE = 'edge'; - var CROSSWALK = 'crosswalk'; - var requestAnimationFrame = ionic.requestAnimationFrame; - - /** - * @ngdoc utility - * @name ionic.Platform - * @module ionic - * @description - * A set of utility methods that can be used to retrieve the device ready state and - * various other information such as what kind of platform the app is currently installed on. - * - * @usage - * ```js - * angular.module('PlatformApp', ['ionic']) - * .controller('PlatformCtrl', function($scope) { - * - * ionic.Platform.ready(function(){ - * // will execute when device is ready, or immediately if the device is already ready. - * }); - * - * var deviceInformation = ionic.Platform.device(); - * - * var isWebView = ionic.Platform.isWebView(); - * var isIPad = ionic.Platform.isIPad(); - * var isIOS = ionic.Platform.isIOS(); - * var isAndroid = ionic.Platform.isAndroid(); - * var isWindowsPhone = ionic.Platform.isWindowsPhone(); - * - * var currentPlatform = ionic.Platform.platform(); - * var currentPlatformVersion = ionic.Platform.version(); - * - * ionic.Platform.exitApp(); // stops the app - * }); - * ``` - */ - var self = ionic.Platform = { - - // Put navigator on platform so it can be mocked and set - // the browser does not allow window.navigator to be set - navigator: window.navigator, - - /** - * @ngdoc property - * @name ionic.Platform#isReady - * @returns {boolean} Whether the device is ready. - */ - isReady: false, - /** - * @ngdoc property - * @name ionic.Platform#isFullScreen - * @returns {boolean} Whether the device is fullscreen. - */ - isFullScreen: false, - /** - * @ngdoc property - * @name ionic.Platform#platforms - * @returns {Array(string)} An array of all platforms found. - */ - platforms: null, - /** - * @ngdoc property - * @name ionic.Platform#grade - * @returns {string} What grade the current platform is. - */ - grade: null, - ua: navigator.userAgent, - - /** - * @ngdoc method - * @name ionic.Platform#ready - * @description - * Trigger a callback once the device is ready, or immediately - * if the device is already ready. This method can be run from - * anywhere and does not need to be wrapped by any additonal methods. - * When the app is within a WebView (Cordova), it'll fire - * the callback once the device is ready. If the app is within - * a web browser, it'll fire the callback after `window.load`. - * Please remember that Cordova features (Camera, FileSystem, etc) still - * will not work in a web browser. - * @param {function} callback The function to call. - */ - ready: function(cb) { - // run through tasks to complete now that the device is ready - if (self.isReady) { - cb(); - } else { - // the platform isn't ready yet, add it to this array - // which will be called once the platform is ready - readyCallbacks.push(cb); - } - }, - - /** - * @private - */ - detect: function() { - self._checkPlatforms(); - - requestAnimationFrame(function() { - // only add to the body class if we got platform info - for (var i = 0; i < self.platforms.length; i++) { - document.body.classList.add('platform-' + self.platforms[i]); - } - }); - }, - - /** - * @ngdoc method - * @name ionic.Platform#setGrade - * @description Set the grade of the device: 'a', 'b', or 'c'. 'a' is the best - * (most css features enabled), 'c' is the worst. By default, sets the grade - * depending on the current device. - * @param {string} grade The new grade to set. - */ - setGrade: function(grade) { - var oldGrade = self.grade; - self.grade = grade; - requestAnimationFrame(function() { - if (oldGrade) { - document.body.classList.remove('grade-' + oldGrade); - } - document.body.classList.add('grade-' + grade); - }); - }, - - /** - * @ngdoc method - * @name ionic.Platform#device - * @description Return the current device (given by cordova). - * @returns {object} The device object. - */ - device: function() { - return window.device || {}; - }, - - _checkPlatforms: function() { - self.platforms = []; - var grade = 'a'; - - if (self.isWebView()) { - self.platforms.push('webview'); - if (!(!window.cordova && !window.PhoneGap && !window.phonegap)) { - self.platforms.push('cordova'); - } else if (window.forge) { - self.platforms.push('trigger'); - } - } else { - self.platforms.push('browser'); - } - if (self.isIPad()) self.platforms.push('ipad'); - - var platform = self.platform(); - if (platform) { - self.platforms.push(platform); - - var version = self.version(); - if (version) { - var v = version.toString(); - if (v.indexOf('.') > 0) { - v = v.replace('.', '_'); - } else { - v += '_0'; - } - self.platforms.push(platform + v.split('_')[0]); - self.platforms.push(platform + v); - - if (self.isAndroid() && version < 4.4) { - grade = (version < 4 ? 'c' : 'b'); - } else if (self.isWindowsPhone()) { - grade = 'b'; - } - } - } - - self.setGrade(grade); - }, - - /** - * @ngdoc method - * @name ionic.Platform#isWebView - * @returns {boolean} Check if we are running within a WebView (such as Cordova). - */ - isWebView: function() { - return !(!window.cordova && !window.PhoneGap && !window.phonegap && !window.forge); - }, - /** - * @ngdoc method - * @name ionic.Platform#isIPad - * @returns {boolean} Whether we are running on iPad. - */ - isIPad: function() { - if (/iPad/i.test(self.navigator.platform)) { - return true; - } - return /iPad/i.test(self.ua); - }, - /** - * @ngdoc method - * @name ionic.Platform#isIOS - * @returns {boolean} Whether we are running on iOS. - */ - isIOS: function() { - return self.is(IOS); - }, - /** - * @ngdoc method - * @name ionic.Platform#isAndroid - * @returns {boolean} Whether we are running on Android. - */ - isAndroid: function() { - return self.is(ANDROID); - }, - /** - * @ngdoc method - * @name ionic.Platform#isWindowsPhone - * @returns {boolean} Whether we are running on Windows Phone. - */ - isWindowsPhone: function() { - return self.is(WINDOWS_PHONE); - }, - /** - * @ngdoc method - * @name ionic.Platform#isEdge - * @returns {boolean} Whether we are running on MS Edge/Windows 10 (inc. Phone) - */ - isEdge: function() { - return self.is(EDGE); - }, - - isCrosswalk: function() { - return self.is(CROSSWALK); - }, - - /** - * @ngdoc method - * @name ionic.Platform#platform - * @returns {string} The name of the current platform. - */ - platform: function() { - // singleton to get the platform name - if (platformName === null) self.setPlatform(self.device().platform); - return platformName; - }, - - /** - * @private - */ - setPlatform: function(n) { - if (typeof n != 'undefined' && n !== null && n.length) { - platformName = n.toLowerCase(); - } else if (getParameterByName('ionicplatform')) { - platformName = getParameterByName('ionicplatform'); - } else if (self.ua.indexOf('Edge') > -1) { - platformName = EDGE; - } else if (self.ua.indexOf('Windows Phone') > -1) { - platformName = WINDOWS_PHONE; - } else if (self.ua.indexOf('Android') > 0) { - platformName = ANDROID; - } else if (/iPhone|iPad|iPod/.test(self.ua)) { - platformName = IOS; - } else { - platformName = self.navigator.platform && navigator.platform.toLowerCase().split(' ')[0] || ''; - } - }, - - /** - * @ngdoc method - * @name ionic.Platform#version - * @returns {number} The version of the current device platform. - */ - version: function() { - // singleton to get the platform version - if (platformVersion === null) self.setVersion(self.device().version); - return platformVersion; - }, - - /** - * @private - */ - setVersion: function(v) { - if (typeof v != 'undefined' && v !== null) { - v = v.split('.'); - v = parseFloat(v[0] + '.' + (v.length > 1 ? v[1] : 0)); - if (!isNaN(v)) { - platformVersion = v; - return; - } - } - - platformVersion = 0; - - // fallback to user-agent checking - var pName = self.platform(); - var versionMatch = { - 'android': /Android (\d+).(\d+)?/, - 'ios': /OS (\d+)_(\d+)?/, - 'windowsphone': /Windows Phone (\d+).(\d+)?/ - }; - if (versionMatch[pName]) { - v = self.ua.match(versionMatch[pName]); - if (v && v.length > 2) { - platformVersion = parseFloat(v[1] + '.' + v[2]); - } - } - }, - - /** - * @ngdoc method - * @name ionic.Platform#is - * @param {string} Platform name. - * @returns {boolean} Whether the platform name provided is detected. - */ - is: function(type) { - type = type.toLowerCase(); - // check if it has an array of platforms - if (self.platforms) { - for (var x = 0; x < self.platforms.length; x++) { - if (self.platforms[x] === type) return true; - } - } - // exact match - var pName = self.platform(); - if (pName) { - return pName === type.toLowerCase(); - } - - // A quick hack for to check userAgent - return self.ua.toLowerCase().indexOf(type) >= 0; - }, - - /** - * @ngdoc method - * @name ionic.Platform#exitApp - * @description Exit the app. - */ - exitApp: function() { - self.ready(function() { - navigator.app && navigator.app.exitApp && navigator.app.exitApp(); - }); - }, - - /** - * @ngdoc method - * @name ionic.Platform#showStatusBar - * @description Shows or hides the device status bar (in Cordova). Requires `cordova plugin add org.apache.cordova.statusbar` - * @param {boolean} shouldShow Whether or not to show the status bar. - */ - showStatusBar: function(val) { - // Only useful when run within cordova - self._showStatusBar = val; - self.ready(function() { - // run this only when or if the platform (cordova) is ready - requestAnimationFrame(function() { - if (self._showStatusBar) { - // they do not want it to be full screen - window.StatusBar && window.StatusBar.show(); - document.body.classList.remove('status-bar-hide'); - } else { - // it should be full screen - window.StatusBar && window.StatusBar.hide(); - document.body.classList.add('status-bar-hide'); - } - }); - }); - }, - - /** - * @ngdoc method - * @name ionic.Platform#fullScreen - * @description - * Sets whether the app is fullscreen or not (in Cordova). - * @param {boolean=} showFullScreen Whether or not to set the app to fullscreen. Defaults to true. Requires `cordova plugin add org.apache.cordova.statusbar` - * @param {boolean=} showStatusBar Whether or not to show the device's status bar. Defaults to false. - */ - fullScreen: function(showFullScreen, showStatusBar) { - // showFullScreen: default is true if no param provided - self.isFullScreen = (showFullScreen !== false); - - // add/remove the fullscreen classname to the body - ionic.DomUtil.ready(function() { - // run this only when or if the DOM is ready - requestAnimationFrame(function() { - if (self.isFullScreen) { - document.body.classList.add('fullscreen'); - } else { - document.body.classList.remove('fullscreen'); - } - }); - // showStatusBar: default is false if no param provided - self.showStatusBar((showStatusBar === true)); - }); - } - - }; - - var platformName = null, // just the name, like iOS or Android - platformVersion = null, // a float of the major and minor, like 7.1 - readyCallbacks = [], - windowLoadListenderAttached, - platformReadyTimer = 2000; // How long to wait for platform ready before emitting a warning - - verifyPlatformReady(); - - // Warn the user if deviceready did not fire in a reasonable amount of time, and how to fix it. - function verifyPlatformReady() { - setTimeout(function() { - if(!self.isReady && self.isWebView()) { - void 0; - } - }, platformReadyTimer); - } - - // setup listeners to know when the device is ready to go - function onWindowLoad() { - if (self.isWebView()) { - // the window and scripts are fully loaded, and a cordova/phonegap - // object exists then let's listen for the deviceready - document.addEventListener("deviceready", onPlatformReady, false); - } else { - // the window and scripts are fully loaded, but the window object doesn't have the - // cordova/phonegap object, so its just a browser, not a webview wrapped w/ cordova - onPlatformReady(); - } - if (windowLoadListenderAttached) { - window.removeEventListener("load", onWindowLoad, false); - } - } - if (document.readyState === 'complete') { - onWindowLoad(); - } else { - windowLoadListenderAttached = true; - window.addEventListener("load", onWindowLoad, false); - } - - function onPlatformReady() { - // the device is all set to go, init our own stuff then fire off our event - self.isReady = true; - self.detect(); - for (var x = 0; x < readyCallbacks.length; x++) { - // fire off all the callbacks that were added before the platform was ready - readyCallbacks[x](); - } - readyCallbacks = []; - ionic.trigger('platformready', { target: document }); - - requestAnimationFrame(function() { - document.body.classList.add('platform-ready'); - }); - } - -})(window, document, ionic); - -(function(document, ionic) { - 'use strict'; - - // Ionic CSS polyfills - ionic.CSS = {}; - ionic.CSS.TRANSITION = []; - ionic.CSS.TRANSFORM = []; - - ionic.EVENTS = {}; - - (function() { - - // transform - var i, keys = ['webkitTransform', 'transform', '-webkit-transform', 'webkit-transform', - '-moz-transform', 'moz-transform', 'MozTransform', 'mozTransform', 'msTransform']; - - for (i = 0; i < keys.length; i++) { - if (document.documentElement.style[keys[i]] !== undefined) { - ionic.CSS.TRANSFORM = keys[i]; - break; - } - } - - // transition - keys = ['webkitTransition', 'mozTransition', 'msTransition', 'transition']; - for (i = 0; i < keys.length; i++) { - if (document.documentElement.style[keys[i]] !== undefined) { - ionic.CSS.TRANSITION = keys[i]; - break; - } - } - - // Fallback in case the keys don't exist at all - ionic.CSS.TRANSITION = ionic.CSS.TRANSITION || 'transition'; - - // The only prefix we care about is webkit for transitions. - var isWebkit = ionic.CSS.TRANSITION.indexOf('webkit') > -1; - - // transition duration - ionic.CSS.TRANSITION_DURATION = (isWebkit ? '-webkit-' : '') + 'transition-duration'; - - // To be sure transitionend works everywhere, include *both* the webkit and non-webkit events - ionic.CSS.TRANSITIONEND = (isWebkit ? 'webkitTransitionEnd ' : '') + 'transitionend'; - })(); - - (function() { - var touchStartEvent = 'touchstart'; - var touchMoveEvent = 'touchmove'; - var touchEndEvent = 'touchend'; - var touchCancelEvent = 'touchcancel'; - - if (window.navigator.pointerEnabled) { - touchStartEvent = 'pointerdown'; - touchMoveEvent = 'pointermove'; - touchEndEvent = 'pointerup'; - touchCancelEvent = 'pointercancel'; - } else if (window.navigator.msPointerEnabled) { - touchStartEvent = 'MSPointerDown'; - touchMoveEvent = 'MSPointerMove'; - touchEndEvent = 'MSPointerUp'; - touchCancelEvent = 'MSPointerCancel'; - } - - ionic.EVENTS.touchstart = touchStartEvent; - ionic.EVENTS.touchmove = touchMoveEvent; - ionic.EVENTS.touchend = touchEndEvent; - ionic.EVENTS.touchcancel = touchCancelEvent; - })(); - - // classList polyfill for them older Androids - // https://gist.github.com/devongovett/1381839 - if (!("classList" in document.documentElement) && Object.defineProperty && typeof HTMLElement !== 'undefined') { - Object.defineProperty(HTMLElement.prototype, 'classList', { - get: function() { - var self = this; - function update(fn) { - return function() { - var x, classes = self.className.split(/\s+/); - - for (x = 0; x < arguments.length; x++) { - fn(classes, classes.indexOf(arguments[x]), arguments[x]); - } - - self.className = classes.join(" "); - }; - } - - return { - add: update(function(classes, index, value) { - ~index || classes.push(value); - }), - - remove: update(function(classes, index) { - ~index && classes.splice(index, 1); - }), - - toggle: update(function(classes, index, value) { - ~index ? classes.splice(index, 1) : classes.push(value); - }), - - contains: function(value) { - return !!~self.className.split(/\s+/).indexOf(value); - }, - - item: function(i) { - return self.className.split(/\s+/)[i] || null; - } - }; - - } - }); - } - -})(document, ionic); - - -/** - * @ngdoc page - * @name tap - * @module ionic - * @description - * On touch devices such as a phone or tablet, some browsers implement a 300ms delay between - * the time the user stops touching the display and the moment the browser executes the - * click. This delay was initially introduced so the browser can know whether the user wants to - * double-tap to zoom in on the webpage. Basically, the browser waits roughly 300ms to see if - * the user is double-tapping, or just tapping on the display once. - * - * Out of the box, Ionic automatically removes the 300ms delay in order to make Ionic apps - * feel more "native" like. Resultingly, other solutions such as - * [fastclick](https://github.com/ftlabs/fastclick) and Angular's - * [ngTouch](https://docs.angularjs.org/api/ngTouch) should not be included, to avoid conflicts. - * - * Some browsers already remove the delay with certain settings, such as the CSS property - * `touch-events: none` or with specific meta tag viewport values. However, each of these - * browsers still handle clicks differently, such as when to fire off or cancel the event - * (like scrolling when the target is a button, or holding a button down). - * For browsers that already remove the 300ms delay, consider Ionic's tap system as a way to - * normalize how clicks are handled across the various devices so there's an expected response - * no matter what the device, platform or version. Additionally, Ionic will prevent - * ghostclicks which even browsers that remove the delay still experience. - * - * In some cases, third-party libraries may also be working with touch events which can interfere - * with the tap system. For example, mapping libraries like Google or Leaflet Maps often implement - * a touch detection system which conflicts with Ionic's tap system. - * - * ### Disabling the tap system - * - * To disable the tap for an element and all of its children elements, - * add the attribute `data-tap-disabled="true"`. - * - * ```html - *
- *
- *
- * ``` - * - * ### Additional Notes: - * - * - Ionic tap works with Ionic's JavaScript scrolling - * - Elements can come and go from the DOM and Ionic tap doesn't keep adding and removing - * listeners - * - No "tap delay" after the first "tap" (you can tap as fast as you want, they all click) - * - Minimal events listeners, only being added to document - * - Correct focus in/out on each input type (select, textearea, range) on each platform/device - * - Shows and hides virtual keyboard correctly for each platform/device - * - Works with labels surrounding inputs - * - Does not fire off a click if the user moves the pointer too far - * - Adds and removes an 'activated' css class - * - Multiple [unit tests](https://github.com/driftyco/ionic/blob/master/test/unit/utils/tap.unit.js) for each scenario - * - */ -/* - - IONIC TAP - --------------- - - Both touch and mouse events are added to the document.body on DOM ready - - If a touch event happens, it does not use mouse event listeners - - On touchend, if the distance between start and end was small, trigger a click - - In the triggered click event, add a 'isIonicTap' property - - The triggered click receives the same x,y coordinates as as the end event - - On document.body click listener (with useCapture=true), only allow clicks with 'isIonicTap' - - Triggering clicks with mouse events work the same as touch, except with mousedown/mouseup - - Tapping inputs is disabled during scrolling -*/ - -var tapDoc; // the element which the listeners are on (document.body) -var tapActiveEle; // the element which is active (probably has focus) -var tapEnabledTouchEvents; -var tapMouseResetTimer; -var tapPointerMoved; -var tapPointerStart; -var tapTouchFocusedInput; -var tapLastTouchTarget; -var tapTouchMoveListener = 'touchmove'; - -// how much the coordinates can be off between start/end, but still a click -var TAP_RELEASE_TOLERANCE = 12; // default tolerance -var TAP_RELEASE_BUTTON_TOLERANCE = 50; // button elements should have a larger tolerance - -var tapEventListeners = { - 'click': tapClickGateKeeper, - - 'mousedown': tapMouseDown, - 'mouseup': tapMouseUp, - 'mousemove': tapMouseMove, - - 'touchstart': tapTouchStart, - 'touchend': tapTouchEnd, - 'touchcancel': tapTouchCancel, - 'touchmove': tapTouchMove, - - 'pointerdown': tapTouchStart, - 'pointerup': tapTouchEnd, - 'pointercancel': tapTouchCancel, - 'pointermove': tapTouchMove, - - 'MSPointerDown': tapTouchStart, - 'MSPointerUp': tapTouchEnd, - 'MSPointerCancel': tapTouchCancel, - 'MSPointerMove': tapTouchMove, - - 'focusin': tapFocusIn, - 'focusout': tapFocusOut -}; - -ionic.tap = { - - register: function(ele) { - tapDoc = ele; - - tapEventListener('click', true, true); - tapEventListener('mouseup'); - tapEventListener('mousedown'); - - if (window.navigator.pointerEnabled) { - tapEventListener('pointerdown'); - tapEventListener('pointerup'); - tapEventListener('pointercancel'); - tapTouchMoveListener = 'pointermove'; - - } else if (window.navigator.msPointerEnabled) { - tapEventListener('MSPointerDown'); - tapEventListener('MSPointerUp'); - tapEventListener('MSPointerCancel'); - tapTouchMoveListener = 'MSPointerMove'; - - } else { - tapEventListener('touchstart'); - tapEventListener('touchend'); - tapEventListener('touchcancel'); - } - - tapEventListener('focusin'); - tapEventListener('focusout'); - - return function() { - for (var type in tapEventListeners) { - tapEventListener(type, false); - } - tapDoc = null; - tapActiveEle = null; - tapEnabledTouchEvents = false; - tapPointerMoved = false; - tapPointerStart = null; - }; - }, - - ignoreScrollStart: function(e) { - return (e.defaultPrevented) || // defaultPrevented has been assigned by another component handling the event - (/^(file|range)$/i).test(e.target.type) || - (e.target.dataset ? e.target.dataset.preventScroll : e.target.getAttribute('data-prevent-scroll')) == 'true' || // manually set within an elements attributes - (!!(/^(object|embed)$/i).test(e.target.tagName)) || // flash/movie/object touches should not try to scroll - ionic.tap.isElementTapDisabled(e.target); // check if this element, or an ancestor, has `data-tap-disabled` attribute - }, - - isTextInput: function(ele) { - return !!ele && - (ele.tagName == 'TEXTAREA' || - ele.contentEditable === 'true' || - (ele.tagName == 'INPUT' && !(/^(radio|checkbox|range|file|submit|reset|color|image|button)$/i).test(ele.type))); - }, - - isDateInput: function(ele) { - return !!ele && - (ele.tagName == 'INPUT' && (/^(date|time|datetime-local|month|week)$/i).test(ele.type)); - }, - - isVideo: function(ele) { - return !!ele && - (ele.tagName == 'VIDEO'); - }, - - isKeyboardElement: function(ele) { - if ( !ionic.Platform.isIOS() || ionic.Platform.isIPad() ) { - return ionic.tap.isTextInput(ele) && !ionic.tap.isDateInput(ele); - } else { - return ionic.tap.isTextInput(ele) || ( !!ele && ele.tagName == "SELECT"); - } - }, - - isLabelWithTextInput: function(ele) { - var container = tapContainingElement(ele, false); - - return !!container && - ionic.tap.isTextInput(tapTargetElement(container)); - }, - - containsOrIsTextInput: function(ele) { - return ionic.tap.isTextInput(ele) || ionic.tap.isLabelWithTextInput(ele); - }, - - cloneFocusedInput: function(container) { - if (ionic.tap.hasCheckedClone) return; - ionic.tap.hasCheckedClone = true; - - ionic.requestAnimationFrame(function() { - var focusInput = container.querySelector(':focus'); - if (ionic.tap.isTextInput(focusInput) && !ionic.tap.isDateInput(focusInput)) { - var clonedInput = focusInput.cloneNode(true); - - clonedInput.value = focusInput.value; - clonedInput.classList.add('cloned-text-input'); - clonedInput.readOnly = true; - if (focusInput.isContentEditable) { - clonedInput.contentEditable = focusInput.contentEditable; - clonedInput.innerHTML = focusInput.innerHTML; - } - focusInput.parentElement.insertBefore(clonedInput, focusInput); - focusInput.classList.add('previous-input-focus'); - - clonedInput.scrollTop = focusInput.scrollTop; - } - }); - }, - - hasCheckedClone: false, - - removeClonedInputs: function(container) { - ionic.tap.hasCheckedClone = false; - - ionic.requestAnimationFrame(function() { - var clonedInputs = container.querySelectorAll('.cloned-text-input'); - var previousInputFocus = container.querySelectorAll('.previous-input-focus'); - var x; - - for (x = 0; x < clonedInputs.length; x++) { - clonedInputs[x].parentElement.removeChild(clonedInputs[x]); - } - - for (x = 0; x < previousInputFocus.length; x++) { - previousInputFocus[x].classList.remove('previous-input-focus'); - previousInputFocus[x].style.top = ''; - if ( ionic.keyboard.isOpen && !ionic.keyboard.isClosing ) previousInputFocus[x].focus(); - } - }); - }, - - requiresNativeClick: function(ele) { - if (ionic.Platform.isWindowsPhone() && (ele.tagName == 'A' || ele.tagName == 'BUTTON' || ele.hasAttribute('ng-click') || (ele.tagName == 'INPUT' && (ele.type == 'button' || ele.type == 'submit')))) { - return true; //Windows Phone edge case, prevent ng-click (and similar) events from firing twice on this platform - } - if (!ele || ele.disabled || (/^(file|range)$/i).test(ele.type) || (/^(object|video)$/i).test(ele.tagName) || ionic.tap.isLabelContainingFileInput(ele)) { - return true; - } - return ionic.tap.isElementTapDisabled(ele); - }, - - isLabelContainingFileInput: function(ele) { - var lbl = tapContainingElement(ele); - if (lbl.tagName !== 'LABEL') return false; - var fileInput = lbl.querySelector('input[type=file]'); - if (fileInput && fileInput.disabled === false) return true; - return false; - }, - - isElementTapDisabled: function(ele) { - if (ele && ele.nodeType === 1) { - var element = ele; - while (element) { - if ((element.dataset ? element.dataset.tapDisabled : element.getAttribute && element.getAttribute('data-tap-disabled')) == 'true') { - return true; - } - element = element.parentElement; - } - } - return false; - }, - - setTolerance: function(releaseTolerance, releaseButtonTolerance) { - TAP_RELEASE_TOLERANCE = releaseTolerance; - TAP_RELEASE_BUTTON_TOLERANCE = releaseButtonTolerance; - }, - - cancelClick: function() { - // used to cancel any simulated clicks which may happen on a touchend/mouseup - // gestures uses this method within its tap and hold events - tapPointerMoved = true; - }, - - pointerCoord: function(event) { - // This method can get coordinates for both a mouse click - // or a touch depending on the given event - var c = { x: 0, y: 0 }; - if (event) { - var touches = event.touches && event.touches.length ? event.touches : [event]; - var e = (event.changedTouches && event.changedTouches[0]) || touches[0]; - if (e) { - c.x = e.clientX || e.pageX || 0; - c.y = e.clientY || e.pageY || 0; - } - } - return c; - } - -}; - -function tapEventListener(type, enable, useCapture) { - if (enable !== false) { - tapDoc.addEventListener(type, tapEventListeners[type], useCapture); - } else { - tapDoc.removeEventListener(type, tapEventListeners[type]); - } -} - -function tapClick(e) { - // simulate a normal click by running the element's click method then focus on it - var container = tapContainingElement(e.target); - var ele = tapTargetElement(container); - - if (ionic.tap.requiresNativeClick(ele) || tapPointerMoved) return false; - - var c = ionic.tap.pointerCoord(e); - - //console.log('tapClick', e.type, ele.tagName, '('+c.x+','+c.y+')'); - triggerMouseEvent('click', ele, c.x, c.y); - - // if it's an input, focus in on the target, otherwise blur - tapHandleFocus(ele); -} - -function triggerMouseEvent(type, ele, x, y) { - // using initMouseEvent instead of MouseEvent for our Android friends - var clickEvent = document.createEvent("MouseEvents"); - clickEvent.initMouseEvent(type, true, true, window, 1, 0, 0, x, y, false, false, false, false, 0, null); - clickEvent.isIonicTap = true; - ele.dispatchEvent(clickEvent); -} - -function tapClickGateKeeper(e) { - //console.log('click ' + Date.now() + ' isIonicTap: ' + (e.isIonicTap ? true : false)); - if (e.target.type == 'submit' && e.detail === 0) { - // do not prevent click if it came from an "Enter" or "Go" keypress submit - return null; - } - - // do not allow through any click events that were not created by ionic.tap - if ((ionic.scroll.isScrolling && ionic.tap.containsOrIsTextInput(e.target)) || - (!e.isIonicTap && !ionic.tap.requiresNativeClick(e.target))) { - //console.log('clickPrevent', e.target.tagName); - e.stopPropagation(); - - if (!ionic.tap.isLabelWithTextInput(e.target)) { - // labels clicks from native should not preventDefault othersize keyboard will not show on input focus - e.preventDefault(); - } - return false; - } -} - -// MOUSE -function tapMouseDown(e) { - //console.log('mousedown ' + Date.now()); - if (e.isIonicTap || tapIgnoreEvent(e)) return null; - - if (tapEnabledTouchEvents) { - //console.log('mousedown', 'stop event'); - e.stopPropagation(); - - if (!ionic.Platform.isEdge() && (!ionic.tap.isTextInput(e.target) || tapLastTouchTarget !== e.target) && - !isSelectOrOption(e.target.tagName) && !ionic.tap.isVideo(e.target)) { - // If you preventDefault on a text input then you cannot move its text caret/cursor. - // Allow through only the text input default. However, without preventDefault on an - // input the 300ms delay can change focus on inputs after the keyboard shows up. - // The focusin event handles the chance of focus changing after the keyboard shows. - // Windows Phone - if you preventDefault on a video element then you cannot operate - // its native controls. - e.preventDefault(); - } - - return false; - } - - tapPointerMoved = false; - tapPointerStart = ionic.tap.pointerCoord(e); - - tapEventListener('mousemove'); - ionic.activator.start(e); -} - -function tapMouseUp(e) { - //console.log("mouseup " + Date.now()); - if (tapEnabledTouchEvents) { - e.stopPropagation(); - e.preventDefault(); - return false; - } - - if (tapIgnoreEvent(e) || isSelectOrOption(e.target.tagName)) return false; - - if (!tapHasPointerMoved(e)) { - tapClick(e); - } - tapEventListener('mousemove', false); - ionic.activator.end(); - tapPointerMoved = false; -} - -function tapMouseMove(e) { - if (tapHasPointerMoved(e)) { - tapEventListener('mousemove', false); - ionic.activator.end(); - tapPointerMoved = true; - return false; - } -} - - -// TOUCH -function tapTouchStart(e) { - //console.log("touchstart " + Date.now()); - if (tapIgnoreEvent(e)) return; - - tapPointerMoved = false; - - tapEnableTouchEvents(); - tapPointerStart = ionic.tap.pointerCoord(e); - - tapEventListener(tapTouchMoveListener); - ionic.activator.start(e); - - if (ionic.Platform.isIOS() && ionic.tap.isLabelWithTextInput(e.target)) { - // if the tapped element is a label, which has a child input - // then preventDefault so iOS doesn't ugly auto scroll to the input - // but do not prevent default on Android or else you cannot move the text caret - // and do not prevent default on Android or else no virtual keyboard shows up - - var textInput = tapTargetElement(tapContainingElement(e.target)); - if (textInput !== tapActiveEle) { - // don't preventDefault on an already focused input or else iOS's text caret isn't usable - //console.log('Would prevent default here'); - e.preventDefault(); - } - } -} - -function tapTouchEnd(e) { - //console.log('touchend ' + Date.now()); - if (tapIgnoreEvent(e)) return; - - tapEnableTouchEvents(); - if (!tapHasPointerMoved(e)) { - tapClick(e); - - if (isSelectOrOption(e.target.tagName)) { - e.preventDefault(); - } - } - - tapLastTouchTarget = e.target; - tapTouchCancel(); -} - -function tapTouchMove(e) { - if (tapHasPointerMoved(e)) { - tapPointerMoved = true; - tapEventListener(tapTouchMoveListener, false); - ionic.activator.end(); - return false; - } -} - -function tapTouchCancel() { - tapEventListener(tapTouchMoveListener, false); - ionic.activator.end(); - tapPointerMoved = false; -} - -function tapEnableTouchEvents() { - tapEnabledTouchEvents = true; - clearTimeout(tapMouseResetTimer); - tapMouseResetTimer = setTimeout(function() { - tapEnabledTouchEvents = false; - }, 600); -} - -function tapIgnoreEvent(e) { - if (e.isTapHandled) return true; - e.isTapHandled = true; - - if(ionic.tap.isElementTapDisabled(e.target)) { - return true; - } - - if (ionic.scroll.isScrolling && ionic.tap.containsOrIsTextInput(e.target)) { - e.preventDefault(); - return true; - } -} - -function tapHandleFocus(ele) { - tapTouchFocusedInput = null; - - var triggerFocusIn = false; - - if (ele.tagName == 'SELECT') { - // trick to force Android options to show up - triggerMouseEvent('mousedown', ele, 0, 0); - ele.focus && ele.focus(); - triggerFocusIn = true; - - } else if (tapActiveElement() === ele) { - // already is the active element and has focus - triggerFocusIn = true; - - } else if ((/^(input|textarea|ion-label)$/i).test(ele.tagName) || ele.isContentEditable) { - triggerFocusIn = true; - ele.focus && ele.focus(); - ele.value = ele.value; - if (tapEnabledTouchEvents) { - tapTouchFocusedInput = ele; - } - - } else { - tapFocusOutActive(); - } - - if (triggerFocusIn) { - tapActiveElement(ele); - ionic.trigger('ionic.focusin', { - target: ele - }, true); - } -} - -function tapFocusOutActive() { - var ele = tapActiveElement(); - if (ele && ((/^(input|textarea|select)$/i).test(ele.tagName) || ele.isContentEditable)) { - //console.log('tapFocusOutActive', ele.tagName); - ele.blur(); - } - tapActiveElement(null); -} - -function tapFocusIn(e) { - //console.log('focusin ' + Date.now()); - // Because a text input doesn't preventDefault (so the caret still works) there's a chance - // that its mousedown event 300ms later will change the focus to another element after - // the keyboard shows up. - - if (tapEnabledTouchEvents && - ionic.tap.isTextInput(tapActiveElement()) && - ionic.tap.isTextInput(tapTouchFocusedInput) && - tapTouchFocusedInput !== e.target) { - - // 1) The pointer is from touch events - // 2) There is an active element which is a text input - // 3) A text input was just set to be focused on by a touch event - // 4) A new focus has been set, however the target isn't the one the touch event wanted - //console.log('focusin', 'tapTouchFocusedInput'); - tapTouchFocusedInput.focus(); - tapTouchFocusedInput = null; - } - ionic.scroll.isScrolling = false; -} - -function tapFocusOut() { - //console.log("focusout"); - tapActiveElement(null); -} - -function tapActiveElement(ele) { - if (arguments.length) { - tapActiveEle = ele; - } - return tapActiveEle || document.activeElement; -} - -function tapHasPointerMoved(endEvent) { - if (!endEvent || endEvent.target.nodeType !== 1 || !tapPointerStart || (tapPointerStart.x === 0 && tapPointerStart.y === 0)) { - return false; - } - var endCoordinates = ionic.tap.pointerCoord(endEvent); - - var hasClassList = !!(endEvent.target.classList && endEvent.target.classList.contains && - typeof endEvent.target.classList.contains === 'function'); - var releaseTolerance = hasClassList && endEvent.target.classList.contains('button') ? - TAP_RELEASE_BUTTON_TOLERANCE : - TAP_RELEASE_TOLERANCE; - - return Math.abs(tapPointerStart.x - endCoordinates.x) > releaseTolerance || - Math.abs(tapPointerStart.y - endCoordinates.y) > releaseTolerance; -} - -function tapContainingElement(ele, allowSelf) { - var climbEle = ele; - for (var x = 0; x < 6; x++) { - if (!climbEle) break; - if (climbEle.tagName === 'LABEL') return climbEle; - climbEle = climbEle.parentElement; - } - if (allowSelf !== false) return ele; -} - -function tapTargetElement(ele) { - if (ele && ele.tagName === 'LABEL') { - if (ele.control) return ele.control; - - // older devices do not support the "control" property - if (ele.querySelector) { - var control = ele.querySelector('input,textarea,select'); - if (control) return control; - } - } - return ele; -} - -function isSelectOrOption(tagName){ - return (/^(select|option)$/i).test(tagName); -} - -ionic.DomUtil.ready(function() { - var ng = typeof angular !== 'undefined' ? angular : null; - //do nothing for e2e tests - if (!ng || (ng && !ng.scenario)) { - ionic.tap.register(document); - } -}); - -(function(document, ionic) { - 'use strict'; - - var queueElements = {}; // elements that should get an active state in XX milliseconds - var activeElements = {}; // elements that are currently active - var keyId = 0; // a counter for unique keys for the above ojects - var ACTIVATED_CLASS = 'activated'; - - ionic.activator = { - - start: function(e) { - var hitX = ionic.tap.pointerCoord(e).x; - if (hitX > 0 && hitX < 30) { - return; - } - - // when an element is touched/clicked, it climbs up a few - // parents to see if it is an .item or .button element - ionic.requestAnimationFrame(function() { - if ((ionic.scroll && ionic.scroll.isScrolling) || ionic.tap.requiresNativeClick(e.target)) return; - var ele = e.target; - var eleToActivate; - - for (var x = 0; x < 6; x++) { - if (!ele || ele.nodeType !== 1) break; - if (eleToActivate && ele.classList && ele.classList.contains('item')) { - eleToActivate = ele; - break; - } - if (ele.tagName == 'A' || ele.tagName == 'BUTTON' || ele.hasAttribute('ng-click')) { - eleToActivate = ele; - break; - } - if (ele.classList.contains('button')) { - eleToActivate = ele; - break; - } - // no sense climbing past these - if (ele.tagName == 'ION-CONTENT' || (ele.classList && ele.classList.contains('pane')) || ele.tagName == 'BODY') { - break; - } - ele = ele.parentElement; - } - - if (eleToActivate) { - // queue that this element should be set to active - queueElements[keyId] = eleToActivate; - - // on the next frame, set the queued elements to active - ionic.requestAnimationFrame(activateElements); - - keyId = (keyId > 29 ? 0 : keyId + 1); - } - - }); - }, - - end: function() { - // clear out any active/queued elements after XX milliseconds - setTimeout(clear, 200); - } - - }; - - function clear() { - // clear out any elements that are queued to be set to active - queueElements = {}; - - // in the next frame, remove the active class from all active elements - ionic.requestAnimationFrame(deactivateElements); - } - - function activateElements() { - // activate all elements in the queue - for (var key in queueElements) { - if (queueElements[key]) { - queueElements[key].classList.add(ACTIVATED_CLASS); - activeElements[key] = queueElements[key]; - } - } - queueElements = {}; - } - - function deactivateElements() { - if (ionic.transition && ionic.transition.isActive) { - setTimeout(deactivateElements, 400); - return; - } - - for (var key in activeElements) { - if (activeElements[key]) { - activeElements[key].classList.remove(ACTIVATED_CLASS); - delete activeElements[key]; - } - } - } - -})(document, ionic); - -(function(ionic) { - /* for nextUid function below */ - var nextId = 0; - - /** - * Various utilities used throughout Ionic - * - * Some of these are adopted from underscore.js and backbone.js, both also MIT licensed. - */ - ionic.Utils = { - - arrayMove: function(arr, oldIndex, newIndex) { - if (newIndex >= arr.length) { - var k = newIndex - arr.length; - while ((k--) + 1) { - arr.push(undefined); - } - } - arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0]); - return arr; - }, - - /** - * Return a function that will be called with the given context - */ - proxy: function(func, context) { - var args = Array.prototype.slice.call(arguments, 2); - return function() { - return func.apply(context, args.concat(Array.prototype.slice.call(arguments))); - }; - }, - - /** - * Only call a function once in the given interval. - * - * @param func {Function} the function to call - * @param wait {int} how long to wait before/after to allow function calls - * @param immediate {boolean} whether to call immediately or after the wait interval - */ - debounce: function(func, wait, immediate) { - var timeout, args, context, timestamp, result; - return function() { - context = this; - args = arguments; - timestamp = new Date(); - var later = function() { - var last = (new Date()) - timestamp; - if (last < wait) { - timeout = setTimeout(later, wait - last); - } else { - timeout = null; - if (!immediate) result = func.apply(context, args); - } - }; - var callNow = immediate && !timeout; - if (!timeout) { - timeout = setTimeout(later, wait); - } - if (callNow) result = func.apply(context, args); - return result; - }; - }, - - /** - * Throttle the given fun, only allowing it to be - * called at most every `wait` ms. - */ - throttle: function(func, wait, options) { - var context, args, result; - var timeout = null; - var previous = 0; - options || (options = {}); - var later = function() { - previous = options.leading === false ? 0 : Date.now(); - timeout = null; - result = func.apply(context, args); - }; - return function() { - var now = Date.now(); - if (!previous && options.leading === false) previous = now; - var remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - } else if (!timeout && options.trailing !== false) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }, - // Borrowed from Backbone.js's extend - // Helper function to correctly set up the prototype chain, for subclasses. - // Similar to `goog.inherits`, but uses a hash of prototype properties and - // class properties to be extended. - inherit: function(protoProps, staticProps) { - var parent = this; - var child; - - // The constructor function for the new subclass is either defined by you - // (the "constructor" property in your `extend` definition), or defaulted - // by us to simply call the parent's constructor. - if (protoProps && protoProps.hasOwnProperty('constructor')) { - child = protoProps.constructor; - } else { - child = function() { return parent.apply(this, arguments); }; - } - - // Add static properties to the constructor function, if supplied. - ionic.extend(child, parent, staticProps); - - // Set the prototype chain to inherit from `parent`, without calling - // `parent`'s constructor function. - var Surrogate = function() { this.constructor = child; }; - Surrogate.prototype = parent.prototype; - child.prototype = new Surrogate(); - - // Add prototype properties (instance properties) to the subclass, - // if supplied. - if (protoProps) ionic.extend(child.prototype, protoProps); - - // Set a convenience property in case the parent's prototype is needed - // later. - child.__super__ = parent.prototype; - - return child; - }, - - // Extend adapted from Underscore.js - extend: function(obj) { - var args = Array.prototype.slice.call(arguments, 1); - for (var i = 0; i < args.length; i++) { - var source = args[i]; - if (source) { - for (var prop in source) { - obj[prop] = source[prop]; - } - } - } - return obj; - }, - - nextUid: function() { - return 'ion' + (nextId++); - }, - - disconnectScope: function disconnectScope(scope) { - if (!scope) return; - - if (scope.$root === scope) { - return; // we can't disconnect the root node; - } - var parent = scope.$parent; - scope.$$disconnected = true; - scope.$broadcast('$ionic.disconnectScope', scope); - - // See Scope.$destroy - if (parent.$$childHead === scope) { - parent.$$childHead = scope.$$nextSibling; - } - if (parent.$$childTail === scope) { - parent.$$childTail = scope.$$prevSibling; - } - if (scope.$$prevSibling) { - scope.$$prevSibling.$$nextSibling = scope.$$nextSibling; - } - if (scope.$$nextSibling) { - scope.$$nextSibling.$$prevSibling = scope.$$prevSibling; - } - scope.$$nextSibling = scope.$$prevSibling = null; - }, - - reconnectScope: function reconnectScope(scope) { - if (!scope) return; - - if (scope.$root === scope) { - return; // we can't disconnect the root node; - } - if (!scope.$$disconnected) { - return; - } - var parent = scope.$parent; - scope.$$disconnected = false; - scope.$broadcast('$ionic.reconnectScope', scope); - // See Scope.$new for this logic... - scope.$$prevSibling = parent.$$childTail; - if (parent.$$childHead) { - parent.$$childTail.$$nextSibling = scope; - parent.$$childTail = scope; - } else { - parent.$$childHead = parent.$$childTail = scope; - } - }, - - isScopeDisconnected: function(scope) { - var climbScope = scope; - while (climbScope) { - if (climbScope.$$disconnected) return true; - climbScope = climbScope.$parent; - } - return false; - } - }; - - // Bind a few of the most useful functions to the ionic scope - ionic.inherit = ionic.Utils.inherit; - ionic.extend = ionic.Utils.extend; - ionic.throttle = ionic.Utils.throttle; - ionic.proxy = ionic.Utils.proxy; - ionic.debounce = ionic.Utils.debounce; - -})(window.ionic); - -/** - * @ngdoc page - * @name keyboard - * @module ionic - * @description - * On both Android and iOS, Ionic will attempt to prevent the keyboard from - * obscuring inputs and focusable elements when it appears by scrolling them - * into view. In order for this to work, any focusable elements must be within - * a [Scroll View](http://ionicframework.com/docs/api/directive/ionScroll/) - * or a directive such as [Content](http://ionicframework.com/docs/api/directive/ionContent/) - * that has a Scroll View. - * - * It will also attempt to prevent the native overflow scrolling on focus, - * which can cause layout issues such as pushing headers up and out of view. - * - * The keyboard fixes work best in conjunction with the - * [Ionic Keyboard Plugin](https://github.com/driftyco/ionic-plugins-keyboard), - * although it will perform reasonably well without. However, if you are using - * Cordova there is no reason not to use the plugin. - * - * ### Hide when keyboard shows - * - * To hide an element when the keyboard is open, add the class `hide-on-keyboard-open`. - * - * ```html - *
- *
- *
- * ``` - * - * Note: For performance reasons, elements will not be hidden for 400ms after the start of the `native.keyboardshow` event - * from the Ionic Keyboard plugin. If you would like them to disappear immediately, you could do something - * like: - * - * ```js - * window.addEventListener('native.keyboardshow', function(){ - * document.body.classList.add('keyboard-open'); - * }); - * ``` - * This adds the same `keyboard-open` class that is normally added by Ionic 400ms after the keyboard - * opens. However, bear in mind that adding this class to the body immediately may cause jank in any - * animations on Android that occur when the keyboard opens (for example, scrolling any obscured inputs into view). - * - * ---------- - * - * ### Plugin Usage - * Information on using the plugin can be found at - * [https://github.com/driftyco/ionic-plugins-keyboard](https://github.com/driftyco/ionic-plugins-keyboard). - * - * ---------- - * - * ### Android Notes - * - If your app is running in fullscreen, i.e. you have - * `` in your `config.xml` file - * you will need to set `ionic.Platform.isFullScreen = true` manually. - * - * - You can configure the behavior of the web view when the keyboard shows by setting - * [android:windowSoftInputMode](http://developer.android.com/reference/android/R.attr.html#windowSoftInputMode) - * to either `adjustPan`, `adjustResize` or `adjustNothing` in your app's - * activity in `AndroidManifest.xml`. `adjustResize` is the recommended setting - * for Ionic, but if for some reason you do use `adjustPan` you will need to - * set `ionic.Platform.isFullScreen = true`. - * - * ```xml - * - * - * ``` - * - * ### iOS Notes - * - If the content of your app (including the header) is being pushed up and - * out of view on input focus, try setting `cordova.plugins.Keyboard.disableScroll(true)`. - * This does **not** disable scrolling in the Ionic scroll view, rather it - * disables the native overflow scrolling that happens automatically as a - * result of focusing on inputs below the keyboard. - * - */ - -/** - * The current viewport height. - */ -var keyboardCurrentViewportHeight = 0; - -/** - * The viewport height when in portrait orientation. - */ -var keyboardPortraitViewportHeight = 0; - -/** - * The viewport height when in landscape orientation. - */ -var keyboardLandscapeViewportHeight = 0; - -/** - * The currently focused input. - */ -var keyboardActiveElement; - -/** - * The previously focused input used to reset keyboard after focusing on a - * new non-keyboard element - */ -var lastKeyboardActiveElement; - -/** - * The scroll view containing the currently focused input. - */ -var scrollView; - -/** - * Timer for the setInterval that polls window.innerHeight to determine whether - * the layout has updated for the keyboard showing/hiding. - */ -var waitForResizeTimer; - -/** - * Sometimes when switching inputs or orientations, focusout will fire before - * focusin, so this timer is for the small setTimeout to determine if we should - * really focusout/hide the keyboard. - */ -var keyboardFocusOutTimer; - -/** - * on Android, orientationchange will fire before the keyboard plugin notifies - * the browser that the keyboard will show/is showing, so this flag indicates - * to nativeShow that there was an orientationChange and we should update - * the viewport height with an accurate keyboard height value - */ -var wasOrientationChange = false; - -/** - * CSS class added to the body indicating the keyboard is open. - */ -var KEYBOARD_OPEN_CSS = 'keyboard-open'; - -/** - * CSS class that indicates a scroll container. - */ -var SCROLL_CONTAINER_CSS = 'scroll-content'; - -/** - * Debounced keyboardFocusIn function - */ -var debouncedKeyboardFocusIn = ionic.debounce(keyboardFocusIn, 200, true); - -/** - * Debounced keyboardNativeShow function - */ -var debouncedKeyboardNativeShow = ionic.debounce(keyboardNativeShow, 100, true); - -/** - * Ionic keyboard namespace. - * @namespace keyboard - */ -ionic.keyboard = { - - /** - * Whether the keyboard is open or not. - */ - isOpen: false, - - /** - * Whether the keyboard is closing or not. - */ - isClosing: false, - - /** - * Whether the keyboard is opening or not. - */ - isOpening: false, - - /** - * The height of the keyboard in pixels, as reported by the keyboard plugin. - * If the plugin is not available, calculated as the difference in - * window.innerHeight after the keyboard has shown. - */ - height: 0, - - /** - * Whether the device is in landscape orientation or not. - */ - isLandscape: false, - - /** - * Whether the keyboard event listeners have been added or not - */ - isInitialized: false, - - /** - * Hide the keyboard, if it is open. - */ - hide: function() { - if (keyboardHasPlugin()) { - cordova.plugins.Keyboard.close(); - } - keyboardActiveElement && keyboardActiveElement.blur(); - }, - - /** - * An alias for cordova.plugins.Keyboard.show(). If the keyboard plugin - * is installed, show the keyboard. - */ - show: function() { - if (keyboardHasPlugin()) { - cordova.plugins.Keyboard.show(); - } - }, - - /** - * Remove all keyboard related event listeners, effectively disabling Ionic's - * keyboard adjustments. - */ - disable: function() { - if (keyboardHasPlugin()) { - window.removeEventListener('native.keyboardshow', debouncedKeyboardNativeShow ); - window.removeEventListener('native.keyboardhide', keyboardFocusOut); - } else { - document.body.removeEventListener('focusout', keyboardFocusOut); - } - - document.body.removeEventListener('ionic.focusin', debouncedKeyboardFocusIn); - document.body.removeEventListener('focusin', debouncedKeyboardFocusIn); - - window.removeEventListener('orientationchange', keyboardOrientationChange); - - if ( window.navigator.msPointerEnabled ) { - document.removeEventListener("MSPointerDown", keyboardInit); - } else { - document.removeEventListener('touchstart', keyboardInit); - } - ionic.keyboard.isInitialized = false; - }, - - /** - * Alias for keyboardInit, initialize all keyboard related event listeners. - */ - enable: function() { - keyboardInit(); - } -}; - -// Initialize the viewport height (after ionic.keyboard.height has been -// defined). -keyboardCurrentViewportHeight = getViewportHeight(); - - - /* Event handlers */ -/* ------------------------------------------------------------------------- */ - -/** - * Event handler for first touch event, initializes all event listeners - * for keyboard related events. Also aliased by ionic.keyboard.enable. - */ -function keyboardInit() { - - if (ionic.keyboard.isInitialized) return; - - if (keyboardHasPlugin()) { - window.addEventListener('native.keyboardshow', debouncedKeyboardNativeShow); - window.addEventListener('native.keyboardhide', keyboardFocusOut); - } else { - document.body.addEventListener('focusout', keyboardFocusOut); - } - - document.body.addEventListener('ionic.focusin', debouncedKeyboardFocusIn); - document.body.addEventListener('focusin', debouncedKeyboardFocusIn); - - if (window.navigator.msPointerEnabled) { - document.removeEventListener("MSPointerDown", keyboardInit); - } else { - document.removeEventListener('touchstart', keyboardInit); - } - - ionic.keyboard.isInitialized = true; -} - -/** - * Event handler for 'native.keyboardshow' event, sets keyboard.height to the - * reported height and keyboard.isOpening to true. Then calls - * keyboardWaitForResize with keyboardShow or keyboardUpdateViewportHeight as - * the callback depending on whether the event was triggered by a focusin or - * an orientationchange. - */ -function keyboardNativeShow(e) { - clearTimeout(keyboardFocusOutTimer); - //console.log("keyboardNativeShow fired at: " + Date.now()); - //console.log("keyboardNativeshow window.innerHeight: " + window.innerHeight); - - if (!ionic.keyboard.isOpen || ionic.keyboard.isClosing) { - ionic.keyboard.isOpening = true; - ionic.keyboard.isClosing = false; - } - - ionic.keyboard.height = e.keyboardHeight; - //console.log('nativeshow keyboard height:' + e.keyboardHeight); - - if (wasOrientationChange) { - keyboardWaitForResize(keyboardUpdateViewportHeight, true); - } else { - keyboardWaitForResize(keyboardShow, true); - } -} - -/** - * Event handler for 'focusin' and 'ionic.focusin' events. Initializes - * keyboard state (keyboardActiveElement and keyboard.isOpening) for the - * appropriate adjustments once the window has resized. If not using the - * keyboard plugin, calls keyboardWaitForResize with keyboardShow as the - * callback or keyboardShow right away if the keyboard is already open. If - * using the keyboard plugin does nothing and lets keyboardNativeShow handle - * adjustments with a more accurate keyboard height. - */ -function keyboardFocusIn(e) { - clearTimeout(keyboardFocusOutTimer); - //console.log("keyboardFocusIn from: " + e.type + " at: " + Date.now()); - - if (!e.target || - e.target.readOnly || - !ionic.tap.isKeyboardElement(e.target) || - !(scrollView = ionic.DomUtil.getParentWithClass(e.target, SCROLL_CONTAINER_CSS))) { - if (keyboardActiveElement) { - lastKeyboardActiveElement = keyboardActiveElement; - } - keyboardActiveElement = null; - return; - } - - keyboardActiveElement = e.target; - - // if using JS scrolling, undo the effects of native overflow scroll so the - // scroll view is positioned correctly - if (!scrollView.classList.contains("overflow-scroll")) { - document.body.scrollTop = 0; - scrollView.scrollTop = 0; - ionic.requestAnimationFrame(function(){ - document.body.scrollTop = 0; - scrollView.scrollTop = 0; - }); - - // any showing part of the document that isn't within the scroll the user - // could touchmove and cause some ugly changes to the app, so disable - // any touchmove events while the keyboard is open using e.preventDefault() - if (window.navigator.msPointerEnabled) { - document.addEventListener("MSPointerMove", keyboardPreventDefault, false); - } else { - document.addEventListener('touchmove', keyboardPreventDefault, false); - } - } - - if (!ionic.keyboard.isOpen || ionic.keyboard.isClosing) { - ionic.keyboard.isOpening = true; - ionic.keyboard.isClosing = false; - } - - // attempt to prevent browser from natively scrolling input into view while - // we are trying to do the same (while we are scrolling) if the user taps the - // keyboard - document.addEventListener('keydown', keyboardOnKeyDown, false); - - - - // if we aren't using the plugin and the keyboard isn't open yet, wait for the - // window to resize so we can get an accurate estimate of the keyboard size, - // otherwise we do nothing and let nativeShow call keyboardShow once we have - // an exact keyboard height - // if the keyboard is already open, go ahead and scroll the input into view - // if necessary - if (!ionic.keyboard.isOpen && !keyboardHasPlugin()) { - keyboardWaitForResize(keyboardShow, true); - - } else if (ionic.keyboard.isOpen) { - keyboardShow(); - } -} - -/** - * Event handler for 'focusout' events. Sets keyboard.isClosing to true and - * calls keyboardWaitForResize with keyboardHide as the callback after a small - * timeout. - */ -function keyboardFocusOut() { - clearTimeout(keyboardFocusOutTimer); - //console.log("keyboardFocusOut fired at: " + Date.now()); - //console.log("keyboardFocusOut event type: " + e.type); - - if (ionic.keyboard.isOpen || ionic.keyboard.isOpening) { - ionic.keyboard.isClosing = true; - ionic.keyboard.isOpening = false; - } - - // Call keyboardHide with a slight delay because sometimes on focus or - // orientation change focusin is called immediately after, so we give it time - // to cancel keyboardHide - keyboardFocusOutTimer = setTimeout(function() { - ionic.requestAnimationFrame(function() { - // focusOut during or right after an orientationchange, so we didn't get - // a chance to update the viewport height yet, do it and keyboardHide - //console.log("focusOut, wasOrientationChange: " + wasOrientationChange); - if (wasOrientationChange) { - keyboardWaitForResize(function(){ - keyboardUpdateViewportHeight(); - keyboardHide(); - }, false); - } else { - keyboardWaitForResize(keyboardHide, false); - } - }); - }, 50); -} - -/** - * Event handler for 'orientationchange' events. If using the keyboard plugin - * and the keyboard is open on Android, sets wasOrientationChange to true so - * nativeShow can update the viewport height with an accurate keyboard height. - * If the keyboard isn't open or keyboard plugin isn't being used, - * waits for the window to resize before updating the viewport height. - * - * On iOS, where orientationchange fires after the keyboard has already shown, - * updates the viewport immediately, regardless of if the keyboard is already - * open. - */ -function keyboardOrientationChange() { - //console.log("orientationchange fired at: " + Date.now()); - //console.log("orientation was: " + (ionic.keyboard.isLandscape ? "landscape" : "portrait")); - - // toggle orientation - ionic.keyboard.isLandscape = !ionic.keyboard.isLandscape; - // //console.log("now orientation is: " + (ionic.keyboard.isLandscape ? "landscape" : "portrait")); - - // no need to wait for resizing on iOS, and orientationchange always fires - // after the keyboard has opened, so it doesn't matter if it's open or not - if (ionic.Platform.isIOS()) { - keyboardUpdateViewportHeight(); - } - - // On Android, if the keyboard isn't open or we aren't using the keyboard - // plugin, update the viewport height once everything has resized. If the - // keyboard is open and we are using the keyboard plugin do nothing and let - // nativeShow handle it using an accurate keyboard height. - if ( ionic.Platform.isAndroid()) { - if (!ionic.keyboard.isOpen || !keyboardHasPlugin()) { - keyboardWaitForResize(keyboardUpdateViewportHeight, false); - } else { - wasOrientationChange = true; - } - } -} - -/** - * Event handler for 'keydown' event. Tries to prevent browser from natively - * scrolling an input into view when a user taps the keyboard while we are - * scrolling the input into view ourselves with JS. - */ -function keyboardOnKeyDown(e) { - if (ionic.scroll.isScrolling) { - keyboardPreventDefault(e); - } -} - -/** - * Event for 'touchmove' or 'MSPointerMove'. Prevents native scrolling on - * elements outside the scroll view while the keyboard is open. - */ -function keyboardPreventDefault(e) { - if (e.target.tagName !== 'TEXTAREA') { - e.preventDefault(); - } -} - - /* Private API */ -/* -------------------------------------------------------------------------- */ - -/** - * Polls window.innerHeight until it has updated to an expected value (or - * sufficient time has passed) before calling the specified callback function. - * Only necessary for non-fullscreen Android which sometimes reports multiple - * window.innerHeight values during interim layouts while it is resizing. - * - * On iOS, the window.innerHeight will already be updated, but we use the 50ms - * delay as essentially a timeout so that scroll view adjustments happen after - * the keyboard has shown so there isn't a white flash from us resizing too - * quickly. - * - * @param {Function} callback the function to call once the window has resized - * @param {boolean} isOpening whether the resize is from the keyboard opening - * or not - */ -function keyboardWaitForResize(callback, isOpening) { - clearInterval(waitForResizeTimer); - var count = 0; - var maxCount; - var initialHeight = getViewportHeight(); - var viewportHeight = initialHeight; - - //console.log("waitForResize initial viewport height: " + viewportHeight); - //var start = Date.now(); - //console.log("start: " + start); - - // want to fail relatively quickly on modern android devices, since it's much - // more likely we just have a bad keyboard height - if (ionic.Platform.isAndroid() && ionic.Platform.version() < 4.4) { - maxCount = 30; - } else if (ionic.Platform.isAndroid()) { - maxCount = 10; - } else { - maxCount = 1; - } - - // poll timer - waitForResizeTimer = setInterval(function(){ - viewportHeight = getViewportHeight(); - - // height hasn't updated yet, try again in 50ms - // if not using plugin, wait for maxCount to ensure we have waited long enough - // to get an accurate keyboard height - if (++count < maxCount && - ((!isPortraitViewportHeight(viewportHeight) && - !isLandscapeViewportHeight(viewportHeight)) || - !ionic.keyboard.height)) { - return; - } - - // infer the keyboard height from the resize if not using the keyboard plugin - if (!keyboardHasPlugin()) { - ionic.keyboard.height = Math.abs(initialHeight - window.innerHeight); - } - - // set to true if we were waiting for the keyboard to open - ionic.keyboard.isOpen = isOpening; - - clearInterval(waitForResizeTimer); - //var end = Date.now(); - //console.log("waitForResize count: " + count); - //console.log("end: " + end); - //console.log("difference: " + ( end - start ) + "ms"); - - //console.log("callback: " + callback.name); - callback(); - - }, 50); - - return maxCount; //for tests -} - -/** - * On keyboard close sets keyboard state to closed, resets the scroll view, - * removes CSS from body indicating keyboard was open, removes any event - * listeners for when the keyboard is open and on Android blurs the active - * element (which in some cases will still have focus even if the keyboard - * is closed and can cause it to reappear on subsequent taps). - */ -function keyboardHide() { - clearTimeout(keyboardFocusOutTimer); - //console.log("keyboardHide"); - - ionic.keyboard.isOpen = false; - ionic.keyboard.isClosing = false; - - if (keyboardActiveElement || lastKeyboardActiveElement) { - ionic.trigger('resetScrollView', { - target: keyboardActiveElement || lastKeyboardActiveElement - }, true); - } - - ionic.requestAnimationFrame(function(){ - document.body.classList.remove(KEYBOARD_OPEN_CSS); - }); - - // the keyboard is gone now, remove the touchmove that disables native scroll - if (window.navigator.msPointerEnabled) { - document.removeEventListener("MSPointerMove", keyboardPreventDefault); - } else { - document.removeEventListener('touchmove', keyboardPreventDefault); - } - document.removeEventListener('keydown', keyboardOnKeyDown); - - if (ionic.Platform.isAndroid()) { - // on android closing the keyboard with the back/dismiss button won't remove - // focus and keyboard can re-appear on subsequent taps (like scrolling) - if (keyboardHasPlugin()) cordova.plugins.Keyboard.close(); - keyboardActiveElement && keyboardActiveElement.blur(); - } - - keyboardActiveElement = null; - lastKeyboardActiveElement = null; -} - -/** - * On keyboard open sets keyboard state to open, adds CSS to the body - * indicating the keyboard is open and tells the scroll view to resize and - * the currently focused input into view if necessary. - */ -function keyboardShow() { - - ionic.keyboard.isOpen = true; - ionic.keyboard.isOpening = false; - - var details = { - keyboardHeight: keyboardGetHeight(), - viewportHeight: keyboardCurrentViewportHeight - }; - - if (keyboardActiveElement) { - details.target = keyboardActiveElement; - - var elementBounds = keyboardActiveElement.getBoundingClientRect(); - - details.elementTop = Math.round(elementBounds.top); - details.elementBottom = Math.round(elementBounds.bottom); - - details.windowHeight = details.viewportHeight - details.keyboardHeight; - //console.log("keyboardShow viewportHeight: " + details.viewportHeight + - //", windowHeight: " + details.windowHeight + - //", keyboardHeight: " + details.keyboardHeight); - - // figure out if the element is under the keyboard - details.isElementUnderKeyboard = (details.elementBottom > details.windowHeight); - //console.log("isUnderKeyboard: " + details.isElementUnderKeyboard); - //console.log("elementBottom: " + details.elementBottom); - - // send event so the scroll view adjusts - ionic.trigger('scrollChildIntoView', details, true); - } - - setTimeout(function(){ - document.body.classList.add(KEYBOARD_OPEN_CSS); - }, 400); - - return details; //for testing -} - -/* eslint no-unused-vars:0 */ -function keyboardGetHeight() { - // check if we already have a keyboard height from the plugin or resize calculations - if (ionic.keyboard.height) { - return ionic.keyboard.height; - } - - if (ionic.Platform.isAndroid()) { - // should be using the plugin, no way to know how big the keyboard is, so guess - if ( ionic.Platform.isFullScreen ) { - return 275; - } - // otherwise just calculate it - var contentHeight = window.innerHeight; - if (contentHeight < keyboardCurrentViewportHeight) { - return keyboardCurrentViewportHeight - contentHeight; - } else { - return 0; - } - } - - // fallback for when it's the webview without the plugin - // or for just the standard web browser - // TODO: have these be based on device - if (ionic.Platform.isIOS()) { - if (ionic.keyboard.isLandscape) { - return 206; - } - - if (!ionic.Platform.isWebView()) { - return 216; - } - - return 260; - } - - // safe guess - return 275; -} - -function isPortraitViewportHeight(viewportHeight) { - return !!(!ionic.keyboard.isLandscape && - keyboardPortraitViewportHeight && - (Math.abs(keyboardPortraitViewportHeight - viewportHeight) < 2)); -} - -function isLandscapeViewportHeight(viewportHeight) { - return !!(ionic.keyboard.isLandscape && - keyboardLandscapeViewportHeight && - (Math.abs(keyboardLandscapeViewportHeight - viewportHeight) < 2)); -} - -function keyboardUpdateViewportHeight() { - wasOrientationChange = false; - keyboardCurrentViewportHeight = getViewportHeight(); - - if (ionic.keyboard.isLandscape && !keyboardLandscapeViewportHeight) { - //console.log("saved landscape: " + keyboardCurrentViewportHeight); - keyboardLandscapeViewportHeight = keyboardCurrentViewportHeight; - - } else if (!ionic.keyboard.isLandscape && !keyboardPortraitViewportHeight) { - //console.log("saved portrait: " + keyboardCurrentViewportHeight); - keyboardPortraitViewportHeight = keyboardCurrentViewportHeight; - } - - if (keyboardActiveElement) { - ionic.trigger('resetScrollView', { - target: keyboardActiveElement - }, true); - } - - if (ionic.keyboard.isOpen && ionic.tap.isTextInput(keyboardActiveElement)) { - keyboardShow(); - } -} - -function keyboardInitViewportHeight() { - var viewportHeight = getViewportHeight(); - //console.log("Keyboard init VP: " + viewportHeight + " " + window.innerWidth); - // can't just use window.innerHeight in case the keyboard is opened immediately - if ((viewportHeight / window.innerWidth) < 1) { - ionic.keyboard.isLandscape = true; - } - //console.log("ionic.keyboard.isLandscape is: " + ionic.keyboard.isLandscape); - - // initialize or update the current viewport height values - keyboardCurrentViewportHeight = viewportHeight; - if (ionic.keyboard.isLandscape && !keyboardLandscapeViewportHeight) { - keyboardLandscapeViewportHeight = keyboardCurrentViewportHeight; - } else if (!ionic.keyboard.isLandscape && !keyboardPortraitViewportHeight) { - keyboardPortraitViewportHeight = keyboardCurrentViewportHeight; - } -} - -function getViewportHeight() { - var windowHeight = window.innerHeight; - //console.log('window.innerHeight is: ' + windowHeight); - //console.log('kb height is: ' + ionic.keyboard.height); - //console.log('kb isOpen: ' + ionic.keyboard.isOpen); - - //TODO: add iPad undocked/split kb once kb plugin supports it - // the keyboard overlays the window on Android fullscreen - if (!(ionic.Platform.isAndroid() && ionic.Platform.isFullScreen) && - (ionic.keyboard.isOpen || ionic.keyboard.isOpening) && - !ionic.keyboard.isClosing) { - - return windowHeight + keyboardGetHeight(); - } - return windowHeight; -} - -function keyboardHasPlugin() { - return !!(window.cordova && cordova.plugins && cordova.plugins.Keyboard); -} - -ionic.Platform.ready(function() { - keyboardInitViewportHeight(); - - window.addEventListener('orientationchange', keyboardOrientationChange); - - // if orientation changes while app is in background, update on resuming - /* - if ( ionic.Platform.isWebView() ) { - document.addEventListener('resume', keyboardInitViewportHeight); - - if (ionic.Platform.isAndroid()) { - //TODO: onbackpressed to detect keyboard close without focusout or plugin - } - } - */ - - // if orientation changes while app is in background, update on resuming -/* if ( ionic.Platform.isWebView() ) { - document.addEventListener('pause', function() { - window.removeEventListener('orientationchange', keyboardOrientationChange); - }) - document.addEventListener('resume', function() { - keyboardInitViewportHeight(); - window.addEventListener('orientationchange', keyboardOrientationChange) - }); - }*/ - - // Android sometimes reports bad innerHeight on window.load - // try it again in a lil bit to play it safe - setTimeout(keyboardInitViewportHeight, 999); - - // only initialize the adjustments for the virtual keyboard - // if a touchstart event happens - if (window.navigator.msPointerEnabled) { - document.addEventListener("MSPointerDown", keyboardInit, false); - } else { - document.addEventListener('touchstart', keyboardInit, false); - } -}); - - - -var viewportTag; -var viewportProperties = {}; - -ionic.viewport = { - orientation: function() { - // 0 = Portrait - // 90 = Landscape - // not using window.orientation because each device has a different implementation - return (window.innerWidth > window.innerHeight ? 90 : 0); - } -}; - -function viewportLoadTag() { - var x; - - for (x = 0; x < document.head.children.length; x++) { - if (document.head.children[x].name == 'viewport') { - viewportTag = document.head.children[x]; - break; - } - } - - if (viewportTag) { - var props = viewportTag.content.toLowerCase().replace(/\s+/g, '').split(','); - var keyValue; - for (x = 0; x < props.length; x++) { - if (props[x]) { - keyValue = props[x].split('='); - viewportProperties[ keyValue[0] ] = (keyValue.length > 1 ? keyValue[1] : '_'); - } - } - viewportUpdate(); - } -} - -function viewportUpdate() { - // unit tests in viewport.unit.js - - var initWidth = viewportProperties.width; - var initHeight = viewportProperties.height; - var p = ionic.Platform; - var version = p.version(); - var DEVICE_WIDTH = 'device-width'; - var DEVICE_HEIGHT = 'device-height'; - var orientation = ionic.viewport.orientation(); - - // Most times we're removing the height and adding the width - // So this is the default to start with, then modify per platform/version/oreintation - delete viewportProperties.height; - viewportProperties.width = DEVICE_WIDTH; - - if (p.isIPad()) { - // iPad - - if (version > 7) { - // iPad >= 7.1 - // https://issues.apache.org/jira/browse/CB-4323 - delete viewportProperties.width; - - } else { - // iPad <= 7.0 - - if (p.isWebView()) { - // iPad <= 7.0 WebView - - if (orientation == 90) { - // iPad <= 7.0 WebView Landscape - viewportProperties.height = '0'; - - } else if (version == 7) { - // iPad <= 7.0 WebView Portait - viewportProperties.height = DEVICE_HEIGHT; - } - } else { - // iPad <= 6.1 Browser - if (version < 7) { - viewportProperties.height = '0'; - } - } - } - - } else if (p.isIOS()) { - // iPhone - - if (p.isWebView()) { - // iPhone WebView - - if (version > 7) { - // iPhone >= 7.1 WebView - delete viewportProperties.width; - - } else if (version < 7) { - // iPhone <= 6.1 WebView - // if height was set it needs to get removed with this hack for <= 6.1 - if (initHeight) viewportProperties.height = '0'; - - } else if (version == 7) { - //iPhone == 7.0 WebView - viewportProperties.height = DEVICE_HEIGHT; - } - - } else { - // iPhone Browser - - if (version < 7) { - // iPhone <= 6.1 Browser - // if height was set it needs to get removed with this hack for <= 6.1 - if (initHeight) viewportProperties.height = '0'; - } - } - - } - - // only update the viewport tag if there was a change - if (initWidth !== viewportProperties.width || initHeight !== viewportProperties.height) { - viewportTagUpdate(); - } -} - -function viewportTagUpdate() { - var key, props = []; - for (key in viewportProperties) { - if (viewportProperties[key]) { - props.push(key + (viewportProperties[key] == '_' ? '' : '=' + viewportProperties[key])); - } - } - - viewportTag.content = props.join(', '); -} - -ionic.Platform.ready(function() { - viewportLoadTag(); - - window.addEventListener("orientationchange", function() { - setTimeout(viewportUpdate, 1000); - }, false); -}); - -(function(ionic) { -'use strict'; - ionic.views.View = function() { - this.initialize.apply(this, arguments); - }; - - ionic.views.View.inherit = ionic.inherit; - - ionic.extend(ionic.views.View.prototype, { - initialize: function() {} - }); - -})(window.ionic); - -/* - * Scroller - * http://github.com/zynga/scroller - * - * Copyright 2011, Zynga Inc. - * Licensed under the MIT License. - * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt - * - * Based on the work of: Unify Project (unify-project.org) - * http://unify-project.org - * Copyright 2011, Deutsche Telekom AG - * License: MIT + Apache (V2) - */ - -/* jshint eqnull: true */ - -/** - * Generic animation class with support for dropped frames both optional easing and duration. - * - * Optional duration is useful when the lifetime is defined by another condition than time - * e.g. speed of an animating object, etc. - * - * Dropped frame logic allows to keep using the same updater logic independent from the actual - * rendering. This eases a lot of cases where it might be pretty complex to break down a state - * based on the pure time difference. - */ -var zyngaCore = { effect: {} }; -(function(global) { - var time = Date.now || function() { - return +new Date(); - }; - var desiredFrames = 60; - var millisecondsPerSecond = 1000; - var running = {}; - var counter = 1; - - zyngaCore.effect.Animate = { - - /** - * A requestAnimationFrame wrapper / polyfill. - * - * @param callback {Function} The callback to be invoked before the next repaint. - * @param root {HTMLElement} The root element for the repaint - */ - requestAnimationFrame: (function() { - - // Check for request animation Frame support - var requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame; - var isNative = !!requestFrame; - - if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) { - isNative = false; - } - - if (isNative) { - return function(callback, root) { - requestFrame(callback, root); - }; - } - - var TARGET_FPS = 60; - var requests = {}; - var requestCount = 0; - var rafHandle = 1; - var intervalHandle = null; - var lastActive = +new Date(); - - return function(callback) { - var callbackHandle = rafHandle++; - - // Store callback - requests[callbackHandle] = callback; - requestCount++; - - // Create timeout at first request - if (intervalHandle === null) { - - intervalHandle = setInterval(function() { - - var time = +new Date(); - var currentRequests = requests; - - // Reset data structure before executing callbacks - requests = {}; - requestCount = 0; - - for(var key in currentRequests) { - if (currentRequests.hasOwnProperty(key)) { - currentRequests[key](time); - lastActive = time; - } - } - - // Disable the timeout when nothing happens for a certain - // period of time - if (time - lastActive > 2500) { - clearInterval(intervalHandle); - intervalHandle = null; - } - - }, 1000 / TARGET_FPS); - } - - return callbackHandle; - }; - - })(), - - - /** - * Stops the given animation. - * - * @param id {Integer} Unique animation ID - * @return {Boolean} Whether the animation was stopped (aka, was running before) - */ - stop: function(id) { - var cleared = running[id] != null; - if (cleared) { - running[id] = null; - } - - return cleared; - }, - - - /** - * Whether the given animation is still running. - * - * @param id {Integer} Unique animation ID - * @return {Boolean} Whether the animation is still running - */ - isRunning: function(id) { - return running[id] != null; - }, - - - /** - * Start the animation. - * - * @param stepCallback {Function} Pointer to function which is executed on every step. - * Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }` - * @param verifyCallback {Function} Executed before every animation step. - * Signature of the method should be `function() { return continueWithAnimation; }` - * @param completedCallback {Function} - * Signature of the method should be `function(droppedFrames, finishedAnimation) {}` - * @param duration {Integer} Milliseconds to run the animation - * @param easingMethod {Function} Pointer to easing function - * Signature of the method should be `function(percent) { return modifiedValue; }` - * @param root {Element} Render root, when available. Used for internal - * usage of requestAnimationFrame. - * @return {Integer} Identifier of animation. Can be used to stop it any time. - */ - start: function(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) { - - var start = time(); - var lastFrame = start; - var percent = 0; - var dropCounter = 0; - var id = counter++; - - if (!root) { - root = document.body; - } - - // Compacting running db automatically every few new animations - if (id % 20 === 0) { - var newRunning = {}; - for (var usedId in running) { - newRunning[usedId] = true; - } - running = newRunning; - } - - // This is the internal step method which is called every few milliseconds - var step = function(virtual) { - - // Normalize virtual value - var render = virtual !== true; - - // Get current time - var now = time(); - - // Verification is executed before next animation step - if (!running[id] || (verifyCallback && !verifyCallback(id))) { - - running[id] = null; - completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false); - return; - - } - - // For the current rendering to apply let's update omitted steps in memory. - // This is important to bring internal state variables up-to-date with progress in time. - if (render) { - - var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1; - for (var j = 0; j < Math.min(droppedFrames, 4); j++) { - step(true); - dropCounter++; - } - - } - - // Compute percent value - if (duration) { - percent = (now - start) / duration; - if (percent > 1) { - percent = 1; - } - } - - // Execute step callback, then... - var value = easingMethod ? easingMethod(percent) : percent; - if ((stepCallback(value, now, render) === false || percent === 1) && render) { - running[id] = null; - completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration == null); - } else if (render) { - lastFrame = now; - zyngaCore.effect.Animate.requestAnimationFrame(step, root); - } - }; - - // Mark as running - running[id] = true; - - // Init first step - zyngaCore.effect.Animate.requestAnimationFrame(step, root); - - // Return unique animation ID - return id; - } - }; -})(window); - -/* - * Scroller - * http://github.com/zynga/scroller - * - * Copyright 2011, Zynga Inc. - * Licensed under the MIT License. - * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt - * - * Based on the work of: Unify Project (unify-project.org) - * http://unify-project.org - * Copyright 2011, Deutsche Telekom AG - * License: MIT + Apache (V2) - */ - -(function(ionic) { - var NOOP = function(){}; - - // Easing Equations (c) 2003 Robert Penner, all rights reserved. - // Open source under the BSD License. - - /** - * @param pos {Number} position between 0 (start of effect) and 1 (end of effect) - **/ - var easeOutCubic = function(pos) { - return (Math.pow((pos - 1), 3) + 1); - }; - - /** - * @param pos {Number} position between 0 (start of effect) and 1 (end of effect) - **/ - var easeInOutCubic = function(pos) { - if ((pos /= 0.5) < 1) { - return 0.5 * Math.pow(pos, 3); - } - - return 0.5 * (Math.pow((pos - 2), 3) + 2); - }; - - -/** - * ionic.views.Scroll - * A powerful scroll view with support for bouncing, pull to refresh, and paging. - * @param {Object} options options for the scroll view - * @class A scroll view system - * @memberof ionic.views - */ -ionic.views.Scroll = ionic.views.View.inherit({ - initialize: function(options) { - var self = this; - - self.__container = options.el; - self.__content = options.el.firstElementChild; - - //Remove any scrollTop attached to these elements; they are virtual scroll now - //This also stops on-load-scroll-to-window.location.hash that the browser does - setTimeout(function() { - if (self.__container && self.__content) { - self.__container.scrollTop = 0; - self.__content.scrollTop = 0; - } - }); - - self.options = { - - /** Disable scrolling on x-axis by default */ - scrollingX: false, - scrollbarX: true, - - /** Enable scrolling on y-axis */ - scrollingY: true, - scrollbarY: true, - - startX: 0, - startY: 0, - - /** The amount to dampen mousewheel events */ - wheelDampen: 6, - - /** The minimum size the scrollbars scale to while scrolling */ - minScrollbarSizeX: 5, - minScrollbarSizeY: 5, - - /** Scrollbar fading after scrolling */ - scrollbarsFade: true, - scrollbarFadeDelay: 300, - /** The initial fade delay when the pane is resized or initialized */ - scrollbarResizeFadeDelay: 1000, - - /** Enable animations for deceleration, snap back, zooming and scrolling */ - animating: true, - - /** duration for animations triggered by scrollTo/zoomTo */ - animationDuration: 250, - - /** The velocity required to make the scroll view "slide" after touchend */ - decelVelocityThreshold: 4, - - /** The velocity required to make the scroll view "slide" after touchend when using paging */ - decelVelocityThresholdPaging: 4, - - /** Enable bouncing (content can be slowly moved outside and jumps back after releasing) */ - bouncing: true, - - /** Enable locking to the main axis if user moves only slightly on one of them at start */ - locking: true, - - /** Enable pagination mode (switching between full page content panes) */ - paging: false, - - /** Enable snapping of content to a configured pixel grid */ - snapping: false, - - /** Enable zooming of content via API, fingers and mouse wheel */ - zooming: false, - - /** Minimum zoom level */ - minZoom: 0.5, - - /** Maximum zoom level */ - maxZoom: 3, - - /** Multiply or decrease scrolling speed **/ - speedMultiplier: 1, - - deceleration: 0.97, - - /** Whether to prevent default on a scroll operation to capture drag events **/ - preventDefault: false, - - /** Callback that is fired on the later of touch end or deceleration end, - provided that another scrolling action has not begun. Used to know - when to fade out a scrollbar. */ - scrollingComplete: NOOP, - - /** This configures the amount of change applied to deceleration when reaching boundaries **/ - penetrationDeceleration: 0.03, - - /** This configures the amount of change applied to acceleration when reaching boundaries **/ - penetrationAcceleration: 0.08, - - // The ms interval for triggering scroll events - scrollEventInterval: 10, - - freeze: false, - - getContentWidth: function() { - return Math.max(self.__content.scrollWidth, self.__content.offsetWidth); - }, - getContentHeight: function() { - return Math.max(self.__content.scrollHeight, self.__content.offsetHeight + (self.__content.offsetTop * 2)); - } - }; - - for (var key in options) { - self.options[key] = options[key]; - } - - self.hintResize = ionic.debounce(function() { - self.resize(); - }, 1000, true); - - self.onScroll = function() { - - if (!ionic.scroll.isScrolling) { - setTimeout(self.setScrollStart, 50); - } else { - clearTimeout(self.scrollTimer); - self.scrollTimer = setTimeout(self.setScrollStop, 80); - } - - }; - - self.freeze = function(shouldFreeze) { - if (arguments.length) { - self.options.freeze = shouldFreeze; - } - return self.options.freeze; - }; - - // We can just use the standard freeze pop in our mouth - self.freezeShut = self.freeze; - - self.setScrollStart = function() { - ionic.scroll.isScrolling = Math.abs(ionic.scroll.lastTop - self.__scrollTop) > 1; - clearTimeout(self.scrollTimer); - self.scrollTimer = setTimeout(self.setScrollStop, 80); - }; - - self.setScrollStop = function() { - ionic.scroll.isScrolling = false; - ionic.scroll.lastTop = self.__scrollTop; - }; - - self.triggerScrollEvent = ionic.throttle(function() { - self.onScroll(); - ionic.trigger('scroll', { - scrollTop: self.__scrollTop, - scrollLeft: self.__scrollLeft, - target: self.__container - }); - }, self.options.scrollEventInterval); - - self.triggerScrollEndEvent = function() { - ionic.trigger('scrollend', { - scrollTop: self.__scrollTop, - scrollLeft: self.__scrollLeft, - target: self.__container - }); - }; - - self.__scrollLeft = self.options.startX; - self.__scrollTop = self.options.startY; - - // Get the render update function, initialize event handlers, - // and calculate the size of the scroll container - self.__callback = self.getRenderFn(); - self.__initEventHandlers(); - self.__createScrollbars(); - - }, - - run: function() { - this.resize(); - - // Fade them out - this.__fadeScrollbars('out', this.options.scrollbarResizeFadeDelay); - }, - - - - /* - --------------------------------------------------------------------------- - INTERNAL FIELDS :: STATUS - --------------------------------------------------------------------------- - */ - - /** Whether only a single finger is used in touch handling */ - __isSingleTouch: false, - - /** Whether a touch event sequence is in progress */ - __isTracking: false, - - /** Whether a deceleration animation went to completion. */ - __didDecelerationComplete: false, - - /** - * Whether a gesture zoom/rotate event is in progress. Activates when - * a gesturestart event happens. This has higher priority than dragging. - */ - __isGesturing: false, - - /** - * Whether the user has moved by such a distance that we have enabled - * dragging mode. Hint: It's only enabled after some pixels of movement to - * not interrupt with clicks etc. - */ - __isDragging: false, - - /** - * Not touching and dragging anymore, and smoothly animating the - * touch sequence using deceleration. - */ - __isDecelerating: false, - - /** - * Smoothly animating the currently configured change - */ - __isAnimating: false, - - - - /* - --------------------------------------------------------------------------- - INTERNAL FIELDS :: DIMENSIONS - --------------------------------------------------------------------------- - */ - - /** Available outer left position (from document perspective) */ - __clientLeft: 0, - - /** Available outer top position (from document perspective) */ - __clientTop: 0, - - /** Available outer width */ - __clientWidth: 0, - - /** Available outer height */ - __clientHeight: 0, - - /** Outer width of content */ - __contentWidth: 0, - - /** Outer height of content */ - __contentHeight: 0, - - /** Snapping width for content */ - __snapWidth: 100, - - /** Snapping height for content */ - __snapHeight: 100, - - /** Height to assign to refresh area */ - __refreshHeight: null, - - /** Whether the refresh process is enabled when the event is released now */ - __refreshActive: false, - - /** Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release */ - __refreshActivate: null, - - /** Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled */ - __refreshDeactivate: null, - - /** Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */ - __refreshStart: null, - - /** Zoom level */ - __zoomLevel: 1, - - /** Scroll position on x-axis */ - __scrollLeft: 0, - - /** Scroll position on y-axis */ - __scrollTop: 0, - - /** Maximum allowed scroll position on x-axis */ - __maxScrollLeft: 0, - - /** Maximum allowed scroll position on y-axis */ - __maxScrollTop: 0, - - /* Scheduled left position (final position when animating) */ - __scheduledLeft: 0, - - /* Scheduled top position (final position when animating) */ - __scheduledTop: 0, - - /* Scheduled zoom level (final scale when animating) */ - __scheduledZoom: 0, - - - - /* - --------------------------------------------------------------------------- - INTERNAL FIELDS :: LAST POSITIONS - --------------------------------------------------------------------------- - */ - - /** Left position of finger at start */ - __lastTouchLeft: null, - - /** Top position of finger at start */ - __lastTouchTop: null, - - /** Timestamp of last move of finger. Used to limit tracking range for deceleration speed. */ - __lastTouchMove: null, - - /** List of positions, uses three indexes for each state: left, top, timestamp */ - __positions: null, - - - - /* - --------------------------------------------------------------------------- - INTERNAL FIELDS :: DECELERATION SUPPORT - --------------------------------------------------------------------------- - */ - - /** Minimum left scroll position during deceleration */ - __minDecelerationScrollLeft: null, - - /** Minimum top scroll position during deceleration */ - __minDecelerationScrollTop: null, - - /** Maximum left scroll position during deceleration */ - __maxDecelerationScrollLeft: null, - - /** Maximum top scroll position during deceleration */ - __maxDecelerationScrollTop: null, - - /** Current factor to modify horizontal scroll position with on every step */ - __decelerationVelocityX: null, - - /** Current factor to modify vertical scroll position with on every step */ - __decelerationVelocityY: null, - - - /** the browser-specific property to use for transforms */ - __transformProperty: null, - __perspectiveProperty: null, - - /** scrollbar indicators */ - __indicatorX: null, - __indicatorY: null, - - /** Timeout for scrollbar fading */ - __scrollbarFadeTimeout: null, - - /** whether we've tried to wait for size already */ - __didWaitForSize: null, - __sizerTimeout: null, - - __initEventHandlers: function() { - var self = this; - - // Event Handler - var container = self.__container; - - // save height when scroll view is shrunk so we don't need to reflow - var scrollViewOffsetHeight; - - /** - * Shrink the scroll view when the keyboard is up if necessary and if the - * focused input is below the bottom of the shrunk scroll view, scroll it - * into view. - */ - self.scrollChildIntoView = function(e) { - //console.log("scrollChildIntoView at: " + Date.now()); - - // D - var scrollBottomOffsetToTop = container.getBoundingClientRect().bottom; - // D - A - scrollViewOffsetHeight = container.offsetHeight; - var alreadyShrunk = self.isShrunkForKeyboard; - - var isModal = container.parentNode.classList.contains('modal'); - // 680px is when the media query for 60% modal width kicks in - var isInsetModal = isModal && window.innerWidth >= 680; - - /* - * _______ - * |---A---| <- top of scroll view - * | | - * |---B---| <- keyboard - * | C | <- input - * |---D---| <- initial bottom of scroll view - * |___E___| <- bottom of viewport - * - * All commented calculations relative to the top of the viewport (ie E - * is the viewport height, not 0) - */ - if (!alreadyShrunk) { - // shrink scrollview so we can actually scroll if the input is hidden - // if it isn't shrink so we can scroll to inputs under the keyboard - // inset modals won't shrink on Android on their own when the keyboard appears - if ( ionic.Platform.isIOS() || ionic.Platform.isFullScreen || isInsetModal ) { - // if there are things below the scroll view account for them and - // subtract them from the keyboard height when resizing - // E - D E D - var scrollBottomOffsetToBottom = e.detail.viewportHeight - scrollBottomOffsetToTop; - - // 0 or D - B if D > B E - B E - D - var keyboardOffset = Math.max(0, e.detail.keyboardHeight - scrollBottomOffsetToBottom); - - ionic.requestAnimationFrame(function(){ - // D - A or B - A if D > B D - A max(0, D - B) - scrollViewOffsetHeight = scrollViewOffsetHeight - keyboardOffset; - container.style.height = scrollViewOffsetHeight + "px"; - container.style.overflow = "visible"; - - //update scroll view - self.resize(); - }); - } - - self.isShrunkForKeyboard = true; - } - - /* - * _______ - * |---A---| <- top of scroll view - * | * | <- where we want to scroll to - * |--B-D--| <- keyboard, bottom of scroll view - * | C | <- input - * | | - * |___E___| <- bottom of viewport - * - * All commented calculations relative to the top of the viewport (ie E - * is the viewport height, not 0) - */ - // if the element is positioned under the keyboard scroll it into view - if (e.detail.isElementUnderKeyboard) { - - ionic.requestAnimationFrame(function(){ - container.scrollTop = 0; - // update D if we shrunk - if (self.isShrunkForKeyboard && !alreadyShrunk) { - scrollBottomOffsetToTop = container.getBoundingClientRect().bottom; - } - - // middle of the scrollview, this is where we want to scroll to - // (D - A) / 2 - var scrollMidpointOffset = scrollViewOffsetHeight * 0.5; - //console.log("container.offsetHeight: " + scrollViewOffsetHeight); - - // middle of the input we want to scroll into view - // C - var inputMidpoint = ((e.detail.elementBottom + e.detail.elementTop) / 2); - - // distance from middle of input to the bottom of the scroll view - // C - D C D - var inputMidpointOffsetToScrollBottom = inputMidpoint - scrollBottomOffsetToTop; - - //C - D + (D - A)/2 C - D (D - A)/ 2 - var scrollTop = inputMidpointOffsetToScrollBottom + scrollMidpointOffset; - - if ( scrollTop > 0) { - if (ionic.Platform.isIOS()) ionic.tap.cloneFocusedInput(container, self); - self.scrollBy(0, scrollTop, true); - self.onScroll(); - } - }); - } - - // Only the first scrollView parent of the element that broadcasted this event - // (the active element that needs to be shown) should receive this event - e.stopPropagation(); - }; - - self.resetScrollView = function() { - //return scrollview to original height once keyboard has hidden - if ( self.isShrunkForKeyboard ) { - self.isShrunkForKeyboard = false; - container.style.height = ""; - container.style.overflow = ""; - } - self.resize(); - }; - - //Broadcasted when keyboard is shown on some platforms. - //See js/utils/keyboard.js - container.addEventListener('scrollChildIntoView', self.scrollChildIntoView); - - // Listen on document because container may not have had the last - // keyboardActiveElement, for example after closing a modal with a focused - // input and returning to a previously resized scroll view in an ion-content. - // Since we can only resize scroll views that are currently visible, just resize - // the current scroll view when the keyboard is closed. - document.addEventListener('resetScrollView', self.resetScrollView); - - function getEventTouches(e) { - return e.touches && e.touches.length ? e.touches : [{ - pageX: e.pageX, - pageY: e.pageY - }]; - } - - self.touchStart = function(e) { - self.startCoordinates = ionic.tap.pointerCoord(e); - - if ( ionic.tap.ignoreScrollStart(e) ) { - return; - } - - self.__isDown = true; - - if ( ionic.tap.containsOrIsTextInput(e.target) || e.target.tagName === 'SELECT' ) { - // do not start if the target is a text input - // if there is a touchmove on this input, then we can start the scroll - self.__hasStarted = false; - return; - } - - self.__isSelectable = true; - self.__enableScrollY = true; - self.__hasStarted = true; - self.doTouchStart(getEventTouches(e), e.timeStamp); - e.preventDefault(); - }; - - self.touchMove = function(e) { - if (self.options.freeze || !self.__isDown || - (!self.__isDown && e.defaultPrevented) || - (e.target.tagName === 'TEXTAREA' && e.target.parentElement.querySelector(':focus')) ) { - return; - } - - if ( !self.__hasStarted && ( ionic.tap.containsOrIsTextInput(e.target) || e.target.tagName === 'SELECT' ) ) { - // the target is a text input and scroll has started - // since the text input doesn't start on touchStart, do it here - self.__hasStarted = true; - self.doTouchStart(getEventTouches(e), e.timeStamp); - e.preventDefault(); - return; - } - - if (self.startCoordinates) { - // we have start coordinates, so get this touch move's current coordinates - var currentCoordinates = ionic.tap.pointerCoord(e); - - if ( self.__isSelectable && - ionic.tap.isTextInput(e.target) && - Math.abs(self.startCoordinates.x - currentCoordinates.x) > 20 ) { - // user slid the text input's caret on its x axis, disable any future y scrolling - self.__enableScrollY = false; - self.__isSelectable = true; - } - - if ( self.__enableScrollY && Math.abs(self.startCoordinates.y - currentCoordinates.y) > 10 ) { - // user scrolled the entire view on the y axis - // disabled being able to select text on an input - // hide the input which has focus, and show a cloned one that doesn't have focus - self.__isSelectable = false; - ionic.tap.cloneFocusedInput(container, self); - } - } - - self.doTouchMove(getEventTouches(e), e.timeStamp, e.scale); - self.__isDown = true; - }; - - self.touchMoveBubble = function(e) { - if(self.__isDown && self.options.preventDefault) { - e.preventDefault(); - } - }; - - self.touchEnd = function(e) { - if (!self.__isDown) return; - - self.doTouchEnd(e, e.timeStamp); - self.__isDown = false; - self.__hasStarted = false; - self.__isSelectable = true; - self.__enableScrollY = true; - - if ( !self.__isDragging && !self.__isDecelerating && !self.__isAnimating ) { - ionic.tap.removeClonedInputs(container, self); - } - }; - - self.mouseWheel = ionic.animationFrameThrottle(function(e) { - var scrollParent = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'ionic-scroll'); - if (!self.options.freeze && scrollParent === self.__container) { - - self.hintResize(); - self.scrollBy( - (e.wheelDeltaX || e.deltaX || 0) / self.options.wheelDampen, - (-e.wheelDeltaY || e.deltaY || 0) / self.options.wheelDampen - ); - - self.__fadeScrollbars('in'); - clearTimeout(self.__wheelHideBarTimeout); - self.__wheelHideBarTimeout = setTimeout(function() { - self.__fadeScrollbars('out'); - }, 100); - } - }); - - if ('ontouchstart' in window) { - // Touch Events - container.addEventListener("touchstart", self.touchStart, false); - if(self.options.preventDefault) container.addEventListener("touchmove", self.touchMoveBubble, false); - document.addEventListener("touchmove", self.touchMove, false); - document.addEventListener("touchend", self.touchEnd, false); - document.addEventListener("touchcancel", self.touchEnd, false); - document.addEventListener("wheel", self.mouseWheel, false); - - } else if (window.navigator.pointerEnabled) { - // Pointer Events - container.addEventListener("pointerdown", self.touchStart, false); - if(self.options.preventDefault) container.addEventListener("pointermove", self.touchMoveBubble, false); - document.addEventListener("pointermove", self.touchMove, false); - document.addEventListener("pointerup", self.touchEnd, false); - document.addEventListener("pointercancel", self.touchEnd, false); - document.addEventListener("wheel", self.mouseWheel, false); - - } else if (window.navigator.msPointerEnabled) { - // IE10, WP8 (Pointer Events) - container.addEventListener("MSPointerDown", self.touchStart, false); - if(self.options.preventDefault) container.addEventListener("MSPointerMove", self.touchMoveBubble, false); - document.addEventListener("MSPointerMove", self.touchMove, false); - document.addEventListener("MSPointerUp", self.touchEnd, false); - document.addEventListener("MSPointerCancel", self.touchEnd, false); - document.addEventListener("wheel", self.mouseWheel, false); - - } else { - // Mouse Events - var mousedown = false; - - self.mouseDown = function(e) { - if ( ionic.tap.ignoreScrollStart(e) || e.target.tagName === 'SELECT' ) { - return; - } - self.doTouchStart(getEventTouches(e), e.timeStamp); - - if ( !ionic.tap.isTextInput(e.target) ) { - e.preventDefault(); - } - mousedown = true; - }; - - self.mouseMove = function(e) { - if (self.options.freeze || !mousedown || (!mousedown && e.defaultPrevented)) { - return; - } - - self.doTouchMove(getEventTouches(e), e.timeStamp); - - mousedown = true; - }; - - self.mouseMoveBubble = function(e) { - if (mousedown && self.options.preventDefault) { - e.preventDefault(); - } - }; - - self.mouseUp = function(e) { - if (!mousedown) { - return; - } - - self.doTouchEnd(e, e.timeStamp); - - mousedown = false; - }; - - container.addEventListener("mousedown", self.mouseDown, false); - if(self.options.preventDefault) container.addEventListener("mousemove", self.mouseMoveBubble, false); - document.addEventListener("mousemove", self.mouseMove, false); - document.addEventListener("mouseup", self.mouseUp, false); - document.addEventListener('mousewheel', self.mouseWheel, false); - document.addEventListener('wheel', self.mouseWheel, false); - } - }, - - __cleanup: function() { - var self = this; - var container = self.__container; - - container.removeEventListener('touchstart', self.touchStart); - container.removeEventListener('touchmove', self.touchMoveBubble); - document.removeEventListener('touchmove', self.touchMove); - document.removeEventListener('touchend', self.touchEnd); - document.removeEventListener('touchcancel', self.touchEnd); - - container.removeEventListener("pointerdown", self.touchStart); - container.removeEventListener("pointermove", self.touchMoveBubble); - document.removeEventListener("pointermove", self.touchMove); - document.removeEventListener("pointerup", self.touchEnd); - document.removeEventListener("pointercancel", self.touchEnd); - - container.removeEventListener("MSPointerDown", self.touchStart); - container.removeEventListener("MSPointerMove", self.touchMoveBubble); - document.removeEventListener("MSPointerMove", self.touchMove); - document.removeEventListener("MSPointerUp", self.touchEnd); - document.removeEventListener("MSPointerCancel", self.touchEnd); - - container.removeEventListener("mousedown", self.mouseDown); - container.removeEventListener("mousemove", self.mouseMoveBubble); - document.removeEventListener("mousemove", self.mouseMove); - document.removeEventListener("mouseup", self.mouseUp); - document.removeEventListener('mousewheel', self.mouseWheel); - document.removeEventListener('wheel', self.mouseWheel); - - container.removeEventListener('scrollChildIntoView', self.scrollChildIntoView); - document.removeEventListener('resetScrollView', self.resetScrollView); - - ionic.tap.removeClonedInputs(container, self); - - delete self.__container; - delete self.__content; - delete self.__indicatorX; - delete self.__indicatorY; - delete self.options.el; - - self.__callback = self.scrollChildIntoView = self.resetScrollView = NOOP; - - self.mouseMove = self.mouseDown = self.mouseUp = self.mouseWheel = - self.touchStart = self.touchMove = self.touchEnd = self.touchCancel = NOOP; - - self.resize = self.scrollTo = self.zoomTo = - self.__scrollingComplete = NOOP; - container = null; - }, - - /** Create a scroll bar div with the given direction **/ - __createScrollbar: function(direction) { - var bar = document.createElement('div'), - indicator = document.createElement('div'); - - indicator.className = 'scroll-bar-indicator scroll-bar-fade-out'; - - if (direction == 'h') { - bar.className = 'scroll-bar scroll-bar-h'; - } else { - bar.className = 'scroll-bar scroll-bar-v'; - } - - bar.appendChild(indicator); - return bar; - }, - - __createScrollbars: function() { - var self = this; - var indicatorX, indicatorY; - - if (self.options.scrollingX) { - indicatorX = { - el: self.__createScrollbar('h'), - sizeRatio: 1 - }; - indicatorX.indicator = indicatorX.el.children[0]; - - if (self.options.scrollbarX) { - self.__container.appendChild(indicatorX.el); - } - self.__indicatorX = indicatorX; - } - - if (self.options.scrollingY) { - indicatorY = { - el: self.__createScrollbar('v'), - sizeRatio: 1 - }; - indicatorY.indicator = indicatorY.el.children[0]; - - if (self.options.scrollbarY) { - self.__container.appendChild(indicatorY.el); - } - self.__indicatorY = indicatorY; - } - }, - - __resizeScrollbars: function() { - var self = this; - - // Update horiz bar - if (self.__indicatorX) { - var width = Math.max(Math.round(self.__clientWidth * self.__clientWidth / (self.__contentWidth)), 20); - if (width > self.__contentWidth) { - width = 0; - } - if (width !== self.__indicatorX.size) { - ionic.requestAnimationFrame(function(){ - self.__indicatorX.indicator.style.width = width + 'px'; - }); - } - self.__indicatorX.size = width; - self.__indicatorX.minScale = self.options.minScrollbarSizeX / width; - self.__indicatorX.maxPos = self.__clientWidth - width; - self.__indicatorX.sizeRatio = self.__maxScrollLeft ? self.__indicatorX.maxPos / self.__maxScrollLeft : 1; - } - - // Update vert bar - if (self.__indicatorY) { - var height = Math.max(Math.round(self.__clientHeight * self.__clientHeight / (self.__contentHeight)), 20); - if (height > self.__contentHeight) { - height = 0; - } - if (height !== self.__indicatorY.size) { - ionic.requestAnimationFrame(function(){ - self.__indicatorY && (self.__indicatorY.indicator.style.height = height + 'px'); - }); - } - self.__indicatorY.size = height; - self.__indicatorY.minScale = self.options.minScrollbarSizeY / height; - self.__indicatorY.maxPos = self.__clientHeight - height; - self.__indicatorY.sizeRatio = self.__maxScrollTop ? self.__indicatorY.maxPos / self.__maxScrollTop : 1; - } - }, - - /** - * Move and scale the scrollbars as the page scrolls. - */ - __repositionScrollbars: function() { - var self = this, - heightScale, widthScale, - widthDiff, heightDiff, - x, y, - xstop = 0, ystop = 0; - - if (self.__indicatorX) { - // Handle the X scrollbar - - // Don't go all the way to the right if we have a vertical scrollbar as well - if (self.__indicatorY) xstop = 10; - - x = Math.round(self.__indicatorX.sizeRatio * self.__scrollLeft) || 0; - - // The the difference between the last content X position, and our overscrolled one - widthDiff = self.__scrollLeft - (self.__maxScrollLeft - xstop); - - if (self.__scrollLeft < 0) { - - widthScale = Math.max(self.__indicatorX.minScale, - (self.__indicatorX.size - Math.abs(self.__scrollLeft)) / self.__indicatorX.size); - - // Stay at left - x = 0; - - // Make sure scale is transformed from the left/center origin point - self.__indicatorX.indicator.style[self.__transformOriginProperty] = 'left center'; - } else if (widthDiff > 0) { - - widthScale = Math.max(self.__indicatorX.minScale, - (self.__indicatorX.size - widthDiff) / self.__indicatorX.size); - - // Stay at the furthest x for the scrollable viewport - x = self.__indicatorX.maxPos - xstop; - - // Make sure scale is transformed from the right/center origin point - self.__indicatorX.indicator.style[self.__transformOriginProperty] = 'right center'; - - } else { - - // Normal motion - x = Math.min(self.__maxScrollLeft, Math.max(0, x)); - widthScale = 1; - - } - - var translate3dX = 'translate3d(' + x + 'px, 0, 0) scaleX(' + widthScale + ')'; - if (self.__indicatorX.transformProp !== translate3dX) { - self.__indicatorX.indicator.style[self.__transformProperty] = translate3dX; - self.__indicatorX.transformProp = translate3dX; - } - } - - if (self.__indicatorY) { - - y = Math.round(self.__indicatorY.sizeRatio * self.__scrollTop) || 0; - - // Don't go all the way to the right if we have a vertical scrollbar as well - if (self.__indicatorX) ystop = 10; - - heightDiff = self.__scrollTop - (self.__maxScrollTop - ystop); - - if (self.__scrollTop < 0) { - - heightScale = Math.max(self.__indicatorY.minScale, (self.__indicatorY.size - Math.abs(self.__scrollTop)) / self.__indicatorY.size); - - // Stay at top - y = 0; - - // Make sure scale is transformed from the center/top origin point - if (self.__indicatorY.originProp !== 'center top') { - self.__indicatorY.indicator.style[self.__transformOriginProperty] = 'center top'; - self.__indicatorY.originProp = 'center top'; - } - - } else if (heightDiff > 0) { - - heightScale = Math.max(self.__indicatorY.minScale, (self.__indicatorY.size - heightDiff) / self.__indicatorY.size); - - // Stay at bottom of scrollable viewport - y = self.__indicatorY.maxPos - ystop; - - // Make sure scale is transformed from the center/bottom origin point - if (self.__indicatorY.originProp !== 'center bottom') { - self.__indicatorY.indicator.style[self.__transformOriginProperty] = 'center bottom'; - self.__indicatorY.originProp = 'center bottom'; - } - - } else { - - // Normal motion - y = Math.min(self.__maxScrollTop, Math.max(0, y)); - heightScale = 1; - - } - - var translate3dY = 'translate3d(0,' + y + 'px, 0) scaleY(' + heightScale + ')'; - if (self.__indicatorY.transformProp !== translate3dY) { - self.__indicatorY.indicator.style[self.__transformProperty] = translate3dY; - self.__indicatorY.transformProp = translate3dY; - } - } - }, - - __fadeScrollbars: function(direction, delay) { - var self = this; - - if (!self.options.scrollbarsFade) { - return; - } - - var className = 'scroll-bar-fade-out'; - - if (self.options.scrollbarsFade === true) { - clearTimeout(self.__scrollbarFadeTimeout); - - if (direction == 'in') { - if (self.__indicatorX) { self.__indicatorX.indicator.classList.remove(className); } - if (self.__indicatorY) { self.__indicatorY.indicator.classList.remove(className); } - } else { - self.__scrollbarFadeTimeout = setTimeout(function() { - if (self.__indicatorX) { self.__indicatorX.indicator.classList.add(className); } - if (self.__indicatorY) { self.__indicatorY.indicator.classList.add(className); } - }, delay || self.options.scrollbarFadeDelay); - } - } - }, - - __scrollingComplete: function() { - this.options.scrollingComplete(); - ionic.tap.removeClonedInputs(this.__container, this); - this.__fadeScrollbars('out'); - }, - - resize: function(continueScrolling) { - var self = this; - if (!self.__container || !self.options) return; - - // Update Scroller dimensions for changed content - // Add padding to bottom of content - self.setDimensions( - self.__container.clientWidth, - self.__container.clientHeight, - self.options.getContentWidth(), - self.options.getContentHeight(), - continueScrolling - ); - }, - /* - --------------------------------------------------------------------------- - PUBLIC API - --------------------------------------------------------------------------- - */ - - getRenderFn: function() { - var self = this; - - var content = self.__content; - - var docStyle = document.documentElement.style; - - var engine; - if ('MozAppearance' in docStyle) { - engine = 'gecko'; - } else if ('WebkitAppearance' in docStyle) { - engine = 'webkit'; - } else if (typeof navigator.cpuClass === 'string') { - engine = 'trident'; - } - - var vendorPrefix = { - trident: 'ms', - gecko: 'Moz', - webkit: 'Webkit', - presto: 'O' - }[engine]; - - var helperElem = document.createElement("div"); - var undef; - - var perspectiveProperty = vendorPrefix + "Perspective"; - var transformProperty = vendorPrefix + "Transform"; - var transformOriginProperty = vendorPrefix + 'TransformOrigin'; - - self.__perspectiveProperty = transformProperty; - self.__transformProperty = transformProperty; - self.__transformOriginProperty = transformOriginProperty; - - if (helperElem.style[perspectiveProperty] !== undef) { - - return function(left, top, zoom, wasResize) { - var translate3d = 'translate3d(' + (-left) + 'px,' + (-top) + 'px,0) scale(' + zoom + ')'; - if (translate3d !== self.contentTransform) { - content.style[transformProperty] = translate3d; - self.contentTransform = translate3d; - } - self.__repositionScrollbars(); - if (!wasResize) { - self.triggerScrollEvent(); - } - }; - - } else if (helperElem.style[transformProperty] !== undef) { - - return function(left, top, zoom, wasResize) { - content.style[transformProperty] = 'translate(' + (-left) + 'px,' + (-top) + 'px) scale(' + zoom + ')'; - self.__repositionScrollbars(); - if (!wasResize) { - self.triggerScrollEvent(); - } - }; - - } else { - - return function(left, top, zoom, wasResize) { - content.style.marginLeft = left ? (-left / zoom) + 'px' : ''; - content.style.marginTop = top ? (-top / zoom) + 'px' : ''; - content.style.zoom = zoom || ''; - self.__repositionScrollbars(); - if (!wasResize) { - self.triggerScrollEvent(); - } - }; - - } - }, - - - /** - * Configures the dimensions of the client (outer) and content (inner) elements. - * Requires the available space for the outer element and the outer size of the inner element. - * All values which are falsy (null or zero etc.) are ignored and the old value is kept. - * - * @param clientWidth {Integer} Inner width of outer element - * @param clientHeight {Integer} Inner height of outer element - * @param contentWidth {Integer} Outer width of inner element - * @param contentHeight {Integer} Outer height of inner element - */ - setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight, continueScrolling) { - var self = this; - - if (!clientWidth && !clientHeight && !contentWidth && !contentHeight) { - // this scrollview isn't rendered, don't bother - return; - } - - // Only update values which are defined - if (clientWidth === +clientWidth) { - self.__clientWidth = clientWidth; - } - - if (clientHeight === +clientHeight) { - self.__clientHeight = clientHeight; - } - - if (contentWidth === +contentWidth) { - self.__contentWidth = contentWidth; - } - - if (contentHeight === +contentHeight) { - self.__contentHeight = contentHeight; - } - - // Refresh maximums - self.__computeScrollMax(); - self.__resizeScrollbars(); - - // Refresh scroll position - if (!continueScrolling) { - self.scrollTo(self.__scrollLeft, self.__scrollTop, true, null, true); - } - - }, - - - /** - * Sets the client coordinates in relation to the document. - * - * @param left {Integer} Left position of outer element - * @param top {Integer} Top position of outer element - */ - setPosition: function(left, top) { - this.__clientLeft = left || 0; - this.__clientTop = top || 0; - }, - - - /** - * Configures the snapping (when snapping is active) - * - * @param width {Integer} Snapping width - * @param height {Integer} Snapping height - */ - setSnapSize: function(width, height) { - this.__snapWidth = width; - this.__snapHeight = height; - }, - - - /** - * Activates pull-to-refresh. A special zone on the top of the list to start a list refresh whenever - * the user event is released during visibility of this zone. This was introduced by some apps on iOS like - * the official Twitter client. - * - * @param height {Integer} Height of pull-to-refresh zone on top of rendered list - * @param activateCallback {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release. - * @param deactivateCallback {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled. - * @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh. - * @param showCallback {Function} Callback to execute when the refresher should be shown. This is for showing the refresher during a negative scrollTop. - * @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. - * @param tailCallback {Function} Callback to execute just before the refresher returns to it's original state. This is for zooming out the refresher. - * @param pullProgressCallback Callback to state the progress while pulling to refresh - */ - activatePullToRefresh: function(height, refresherMethods) { - var self = this; - - self.__refreshHeight = height; - self.__refreshActivate = function() { ionic.requestAnimationFrame(refresherMethods.activate); }; - self.__refreshDeactivate = function() { ionic.requestAnimationFrame(refresherMethods.deactivate); }; - self.__refreshStart = function() { ionic.requestAnimationFrame(refresherMethods.start); }; - self.__refreshShow = function() { ionic.requestAnimationFrame(refresherMethods.show); }; - self.__refreshHide = function() { ionic.requestAnimationFrame(refresherMethods.hide); }; - self.__refreshTail = function() { ionic.requestAnimationFrame(refresherMethods.tail); }; - self.__refreshTailTime = 100; - self.__minSpinTime = 600; - }, - - - /** - * Starts pull-to-refresh manually. - */ - triggerPullToRefresh: function() { - // Use publish instead of scrollTo to allow scrolling to out of boundary position - // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled - this.__publish(this.__scrollLeft, -this.__refreshHeight, this.__zoomLevel, true); - - var d = new Date(); - this.refreshStartTime = d.getTime(); - - if (this.__refreshStart) { - this.__refreshStart(); - } - }, - - - /** - * Signalizes that pull-to-refresh is finished. - */ - finishPullToRefresh: function() { - var self = this; - // delay to make sure the spinner has a chance to spin for a split second before it's dismissed - var d = new Date(); - var delay = 0; - if (self.refreshStartTime + self.__minSpinTime > d.getTime()) { - delay = self.refreshStartTime + self.__minSpinTime - d.getTime(); - } - setTimeout(function() { - if (self.__refreshTail) { - self.__refreshTail(); - } - setTimeout(function() { - self.__refreshActive = false; - if (self.__refreshDeactivate) { - self.__refreshDeactivate(); - } - if (self.__refreshHide) { - self.__refreshHide(); - } - - self.scrollTo(self.__scrollLeft, self.__scrollTop, true); - }, self.__refreshTailTime); - }, delay); - }, - - - /** - * Returns the scroll position and zooming values - * - * @return {Map} `left` and `top` scroll position and `zoom` level - */ - getValues: function() { - return { - left: this.__scrollLeft, - top: this.__scrollTop, - zoom: this.__zoomLevel - }; - }, - - - /** - * Returns the maximum scroll values - * - * @return {Map} `left` and `top` maximum scroll values - */ - getScrollMax: function() { - return { - left: this.__maxScrollLeft, - top: this.__maxScrollTop - }; - }, - - - /** - * Zooms to the given level. Supports optional animation. Zooms - * the center when no coordinates are given. - * - * @param level {Number} Level to zoom to - * @param animate {Boolean} Whether to use animation - * @param originLeft {Number} Zoom in at given left coordinate - * @param originTop {Number} Zoom in at given top coordinate - */ - zoomTo: function(level, animate, originLeft, originTop) { - var self = this; - - if (!self.options.zooming) { - throw new Error("Zooming is not enabled!"); - } - - // Stop deceleration - if (self.__isDecelerating) { - zyngaCore.effect.Animate.stop(self.__isDecelerating); - self.__isDecelerating = false; - } - - var oldLevel = self.__zoomLevel; - - // Normalize input origin to center of viewport if not defined - if (originLeft == null) { - originLeft = self.__clientWidth / 2; - } - - if (originTop == null) { - originTop = self.__clientHeight / 2; - } - - // Limit level according to configuration - level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom); - - // Recompute maximum values while temporary tweaking maximum scroll ranges - self.__computeScrollMax(level); - - // Recompute left and top coordinates based on new zoom level - var left = ((originLeft + self.__scrollLeft) * level / oldLevel) - originLeft; - var top = ((originTop + self.__scrollTop) * level / oldLevel) - originTop; - - // Limit x-axis - if (left > self.__maxScrollLeft) { - left = self.__maxScrollLeft; - } else if (left < 0) { - left = 0; - } - - // Limit y-axis - if (top > self.__maxScrollTop) { - top = self.__maxScrollTop; - } else if (top < 0) { - top = 0; - } - - // Push values out - self.__publish(left, top, level, animate); - - }, - - - /** - * Zooms the content by the given factor. - * - * @param factor {Number} Zoom by given factor - * @param animate {Boolean} Whether to use animation - * @param originLeft {Number} Zoom in at given left coordinate - * @param originTop {Number} Zoom in at given top coordinate - */ - zoomBy: function(factor, animate, originLeft, originTop) { - this.zoomTo(this.__zoomLevel * factor, animate, originLeft, originTop); - }, - - - /** - * Scrolls to the given position. Respect limitations and snapping automatically. - * - * @param left {Number} Horizontal scroll position, keeps current if value is null - * @param top {Number} Vertical scroll position, keeps current if value is null - * @param animate {Boolean} Whether the scrolling should happen using an animation - * @param zoom {Number} Zoom level to go to - */ - scrollTo: function(left, top, animate, zoom, wasResize) { - var self = this; - - // Stop deceleration - if (self.__isDecelerating) { - zyngaCore.effect.Animate.stop(self.__isDecelerating); - self.__isDecelerating = false; - } - - // Correct coordinates based on new zoom level - if (zoom != null && zoom !== self.__zoomLevel) { - - if (!self.options.zooming) { - throw new Error("Zooming is not enabled!"); - } - - left *= zoom; - top *= zoom; - - // Recompute maximum values while temporary tweaking maximum scroll ranges - self.__computeScrollMax(zoom); - - } else { - - // Keep zoom when not defined - zoom = self.__zoomLevel; - - } - - if (!self.options.scrollingX) { - - left = self.__scrollLeft; - - } else { - - if (self.options.paging) { - left = Math.round(left / self.__clientWidth) * self.__clientWidth; - } else if (self.options.snapping) { - left = Math.round(left / self.__snapWidth) * self.__snapWidth; - } - - } - - if (!self.options.scrollingY) { - - top = self.__scrollTop; - - } else { - - if (self.options.paging) { - top = Math.round(top / self.__clientHeight) * self.__clientHeight; - } else if (self.options.snapping) { - top = Math.round(top / self.__snapHeight) * self.__snapHeight; - } - - } - - // Limit for allowed ranges - left = Math.max(Math.min(self.__maxScrollLeft, left), 0); - top = Math.max(Math.min(self.__maxScrollTop, top), 0); - - // Don't animate when no change detected, still call publish to make sure - // that rendered position is really in-sync with internal data - if (left === self.__scrollLeft && top === self.__scrollTop) { - animate = false; - } - - // Publish new values - self.__publish(left, top, zoom, animate, wasResize); - - }, - - - /** - * Scroll by the given offset - * - * @param left {Number} Scroll x-axis by given offset - * @param top {Number} Scroll y-axis by given offset - * @param animate {Boolean} Whether to animate the given change - */ - scrollBy: function(left, top, animate) { - var self = this; - - var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft; - var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop; - - self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate); - }, - - - - /* - --------------------------------------------------------------------------- - EVENT CALLBACKS - --------------------------------------------------------------------------- - */ - - /** - * Mouse wheel handler for zooming support - */ - doMouseZoom: function(wheelDelta, timeStamp, pageX, pageY) { - var change = wheelDelta > 0 ? 0.97 : 1.03; - return this.zoomTo(this.__zoomLevel * change, false, pageX - this.__clientLeft, pageY - this.__clientTop); - }, - - /** - * Touch start handler for scrolling support - */ - doTouchStart: function(touches, timeStamp) { - var self = this; - - // remember if the deceleration was just stopped - self.__decStopped = !!(self.__isDecelerating || self.__isAnimating); - - self.hintResize(); - - if (timeStamp instanceof Date) { - timeStamp = timeStamp.valueOf(); - } - if (typeof timeStamp !== "number") { - timeStamp = Date.now(); - } - - // Reset interruptedAnimation flag - self.__interruptedAnimation = true; - - // Stop deceleration - if (self.__isDecelerating) { - zyngaCore.effect.Animate.stop(self.__isDecelerating); - self.__isDecelerating = false; - self.__interruptedAnimation = true; - } - - // Stop animation - if (self.__isAnimating) { - zyngaCore.effect.Animate.stop(self.__isAnimating); - self.__isAnimating = false; - self.__interruptedAnimation = true; - } - - // Use center point when dealing with two fingers - var currentTouchLeft, currentTouchTop; - var isSingleTouch = touches.length === 1; - if (isSingleTouch) { - currentTouchLeft = touches[0].pageX; - currentTouchTop = touches[0].pageY; - } else { - currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2; - currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2; - } - - // Store initial positions - self.__initialTouchLeft = currentTouchLeft; - self.__initialTouchTop = currentTouchTop; - - // Store initial touchList for scale calculation - self.__initialTouches = touches; - - // Store current zoom level - self.__zoomLevelStart = self.__zoomLevel; - - // Store initial touch positions - self.__lastTouchLeft = currentTouchLeft; - self.__lastTouchTop = currentTouchTop; - - // Store initial move time stamp - self.__lastTouchMove = timeStamp; - - // Reset initial scale - self.__lastScale = 1; - - // Reset locking flags - self.__enableScrollX = !isSingleTouch && self.options.scrollingX; - self.__enableScrollY = !isSingleTouch && self.options.scrollingY; - - // Reset tracking flag - self.__isTracking = true; - - // Reset deceleration complete flag - self.__didDecelerationComplete = false; - - // Dragging starts directly with two fingers, otherwise lazy with an offset - self.__isDragging = !isSingleTouch; - - // Some features are disabled in multi touch scenarios - self.__isSingleTouch = isSingleTouch; - - // Clearing data structure - self.__positions = []; - - }, - - - /** - * Touch move handler for scrolling support - */ - doTouchMove: function(touches, timeStamp, scale) { - if (timeStamp instanceof Date) { - timeStamp = timeStamp.valueOf(); - } - if (typeof timeStamp !== "number") { - timeStamp = Date.now(); - } - - var self = this; - - // Ignore event when tracking is not enabled (event might be outside of element) - if (!self.__isTracking) { - return; - } - - var currentTouchLeft, currentTouchTop; - - // Compute move based around of center of fingers - if (touches.length === 2) { - currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2; - currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2; - - // Calculate scale when not present and only when touches are used - if (!scale && self.options.zooming) { - scale = self.__getScale(self.__initialTouches, touches); - } - } else { - currentTouchLeft = touches[0].pageX; - currentTouchTop = touches[0].pageY; - } - - var positions = self.__positions; - - // Are we already is dragging mode? - if (self.__isDragging) { - self.__decStopped = false; - - // Compute move distance - var moveX = currentTouchLeft - self.__lastTouchLeft; - var moveY = currentTouchTop - self.__lastTouchTop; - - // Read previous scroll position and zooming - var scrollLeft = self.__scrollLeft; - var scrollTop = self.__scrollTop; - var level = self.__zoomLevel; - - // Work with scaling - if (scale != null && self.options.zooming) { - - var oldLevel = level; - - // Recompute level based on previous scale and new scale - level = level / self.__lastScale * scale; - - // Limit level according to configuration - level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom); - - // Only do further compution when change happened - if (oldLevel !== level) { - - // Compute relative event position to container - var currentTouchLeftRel = currentTouchLeft - self.__clientLeft; - var currentTouchTopRel = currentTouchTop - self.__clientTop; - - // Recompute left and top coordinates based on new zoom level - scrollLeft = ((currentTouchLeftRel + scrollLeft) * level / oldLevel) - currentTouchLeftRel; - scrollTop = ((currentTouchTopRel + scrollTop) * level / oldLevel) - currentTouchTopRel; - - // Recompute max scroll values - self.__computeScrollMax(level); - - } - } - - if (self.__enableScrollX) { - - scrollLeft -= moveX * self.options.speedMultiplier; - var maxScrollLeft = self.__maxScrollLeft; - - if (scrollLeft > maxScrollLeft || scrollLeft < 0) { - - // Slow down on the edges - if (self.options.bouncing) { - - scrollLeft += (moveX / 2 * self.options.speedMultiplier); - - } else if (scrollLeft > maxScrollLeft) { - - scrollLeft = maxScrollLeft; - - } else { - - scrollLeft = 0; - - } - } - } - - // Compute new vertical scroll position - if (self.__enableScrollY) { - - scrollTop -= moveY * self.options.speedMultiplier; - var maxScrollTop = self.__maxScrollTop; - - if (scrollTop > maxScrollTop || scrollTop < 0) { - - // Slow down on the edges - if (self.options.bouncing || (self.__refreshHeight && scrollTop < 0)) { - - scrollTop += (moveY / 2 * self.options.speedMultiplier); - - // Support pull-to-refresh (only when only y is scrollable) - if (!self.__enableScrollX && self.__refreshHeight != null) { - - // hide the refresher when it's behind the header bar in case of header transparency - if (scrollTop < 0) { - self.__refreshHidden = false; - self.__refreshShow(); - } else { - self.__refreshHide(); - self.__refreshHidden = true; - } - - if (!self.__refreshActive && scrollTop <= -self.__refreshHeight) { - - self.__refreshActive = true; - if (self.__refreshActivate) { - self.__refreshActivate(); - } - - } else if (self.__refreshActive && scrollTop > -self.__refreshHeight) { - - self.__refreshActive = false; - if (self.__refreshDeactivate) { - self.__refreshDeactivate(); - } - - } - } - - } else if (scrollTop > maxScrollTop) { - - scrollTop = maxScrollTop; - - } else { - - scrollTop = 0; - - } - } else if (self.__refreshHeight && !self.__refreshHidden) { - // if a positive scroll value and the refresher is still not hidden, hide it - self.__refreshHide(); - self.__refreshHidden = true; - } - } - - // Keep list from growing infinitely (holding min 10, max 20 measure points) - if (positions.length > 60) { - positions.splice(0, 30); - } - - // Track scroll movement for decleration - positions.push(scrollLeft, scrollTop, timeStamp); - - // Sync scroll position - self.__publish(scrollLeft, scrollTop, level); - - // Otherwise figure out whether we are switching into dragging mode now. - } else { - - var minimumTrackingForScroll = self.options.locking ? 3 : 0; - var minimumTrackingForDrag = 5; - - var distanceX = Math.abs(currentTouchLeft - self.__initialTouchLeft); - var distanceY = Math.abs(currentTouchTop - self.__initialTouchTop); - - self.__enableScrollX = self.options.scrollingX && distanceX >= minimumTrackingForScroll; - self.__enableScrollY = self.options.scrollingY && distanceY >= minimumTrackingForScroll; - - positions.push(self.__scrollLeft, self.__scrollTop, timeStamp); - - self.__isDragging = (self.__enableScrollX || self.__enableScrollY) && (distanceX >= minimumTrackingForDrag || distanceY >= minimumTrackingForDrag); - if (self.__isDragging) { - self.__interruptedAnimation = false; - self.__fadeScrollbars('in'); - } - - } - - // Update last touch positions and time stamp for next event - self.__lastTouchLeft = currentTouchLeft; - self.__lastTouchTop = currentTouchTop; - self.__lastTouchMove = timeStamp; - self.__lastScale = scale; - - }, - - - /** - * Touch end handler for scrolling support - */ - doTouchEnd: function(e, timeStamp) { - if (timeStamp instanceof Date) { - timeStamp = timeStamp.valueOf(); - } - if (typeof timeStamp !== "number") { - timeStamp = Date.now(); - } - - var self = this; - - // Ignore event when tracking is not enabled (no touchstart event on element) - // This is required as this listener ('touchmove') sits on the document and not on the element itself. - if (!self.__isTracking) { - return; - } - - // Not touching anymore (when two finger hit the screen there are two touch end events) - self.__isTracking = false; - - // Be sure to reset the dragging flag now. Here we also detect whether - // the finger has moved fast enough to switch into a deceleration animation. - if (self.__isDragging) { - - // Reset dragging flag - self.__isDragging = false; - - // Start deceleration - // Verify that the last move detected was in some relevant time frame - if (self.__isSingleTouch && self.options.animating && (timeStamp - self.__lastTouchMove) <= 100) { - - // Then figure out what the scroll position was about 100ms ago - var positions = self.__positions; - var endPos = positions.length - 1; - var startPos = endPos; - - // Move pointer to position measured 100ms ago - for (var i = endPos; i > 0 && positions[i] > (self.__lastTouchMove - 100); i -= 3) { - startPos = i; - } - - // If start and stop position is identical in a 100ms timeframe, - // we cannot compute any useful deceleration. - if (startPos !== endPos) { - - // Compute relative movement between these two points - var timeOffset = positions[endPos] - positions[startPos]; - var movedLeft = self.__scrollLeft - positions[startPos - 2]; - var movedTop = self.__scrollTop - positions[startPos - 1]; - - // Based on 50ms compute the movement to apply for each render step - self.__decelerationVelocityX = movedLeft / timeOffset * (1000 / 60); - self.__decelerationVelocityY = movedTop / timeOffset * (1000 / 60); - - // How much velocity is required to start the deceleration - var minVelocityToStartDeceleration = self.options.paging || self.options.snapping ? self.options.decelVelocityThresholdPaging : self.options.decelVelocityThreshold; - - // Verify that we have enough velocity to start deceleration - if (Math.abs(self.__decelerationVelocityX) > minVelocityToStartDeceleration || Math.abs(self.__decelerationVelocityY) > minVelocityToStartDeceleration) { - - // Deactivate pull-to-refresh when decelerating - if (!self.__refreshActive) { - self.__startDeceleration(timeStamp); - } - } - } else { - self.__scrollingComplete(); - } - } else if ((timeStamp - self.__lastTouchMove) > 100) { - self.__scrollingComplete(); - } - - } else if (self.__decStopped) { - // the deceleration was stopped - // user flicked the scroll fast, and stop dragging, then did a touchstart to stop the srolling - // tell the touchend event code to do nothing, we don't want to actually send a click - e.isTapHandled = true; - self.__decStopped = false; - } - - // If this was a slower move it is per default non decelerated, but this - // still means that we want snap back to the bounds which is done here. - // This is placed outside the condition above to improve edge case stability - // e.g. touchend fired without enabled dragging. This should normally do not - // have modified the scroll positions or even showed the scrollbars though. - if (!self.__isDecelerating) { - - if (self.__refreshActive && self.__refreshStart) { - - // Use publish instead of scrollTo to allow scrolling to out of boundary position - // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled - self.__publish(self.__scrollLeft, -self.__refreshHeight, self.__zoomLevel, true); - - var d = new Date(); - self.refreshStartTime = d.getTime(); - - if (self.__refreshStart) { - self.__refreshStart(); - } - // for iOS-ey style scrolling - if (!ionic.Platform.isAndroid())self.__startDeceleration(); - } else { - - if (self.__interruptedAnimation || self.__isDragging) { - self.__scrollingComplete(); - } - self.scrollTo(self.__scrollLeft, self.__scrollTop, true, self.__zoomLevel); - - // Directly signalize deactivation (nothing todo on refresh?) - if (self.__refreshActive) { - - self.__refreshActive = false; - if (self.__refreshDeactivate) { - self.__refreshDeactivate(); - } - - } - } - } - - // Fully cleanup list - self.__positions.length = 0; - - }, - - - - /* - --------------------------------------------------------------------------- - PRIVATE API - --------------------------------------------------------------------------- - */ - - /** - * Applies the scroll position to the content element - * - * @param left {Number} Left scroll position - * @param top {Number} Top scroll position - * @param animate {Boolean} Whether animation should be used to move to the new coordinates - */ - __publish: function(left, top, zoom, animate, wasResize) { - - var self = this; - - // Remember whether we had an animation, then we try to continue based on the current "drive" of the animation - var wasAnimating = self.__isAnimating; - if (wasAnimating) { - zyngaCore.effect.Animate.stop(wasAnimating); - self.__isAnimating = false; - } - - if (animate && self.options.animating) { - - // Keep scheduled positions for scrollBy/zoomBy functionality - self.__scheduledLeft = left; - self.__scheduledTop = top; - self.__scheduledZoom = zoom; - - var oldLeft = self.__scrollLeft; - var oldTop = self.__scrollTop; - var oldZoom = self.__zoomLevel; - - var diffLeft = left - oldLeft; - var diffTop = top - oldTop; - var diffZoom = zoom - oldZoom; - - var step = function(percent, now, render) { - - if (render) { - - self.__scrollLeft = oldLeft + (diffLeft * percent); - self.__scrollTop = oldTop + (diffTop * percent); - self.__zoomLevel = oldZoom + (diffZoom * percent); - - // Push values out - if (self.__callback) { - self.__callback(self.__scrollLeft, self.__scrollTop, self.__zoomLevel, wasResize); - } - - } - }; - - var verify = function(id) { - return self.__isAnimating === id; - }; - - var completed = function(renderedFramesPerSecond, animationId, wasFinished) { - if (animationId === self.__isAnimating) { - self.__isAnimating = false; - } - if (self.__didDecelerationComplete || wasFinished) { - self.__scrollingComplete(); - } - - if (self.options.zooming) { - self.__computeScrollMax(); - } - }; - - // When continuing based on previous animation we choose an ease-out animation instead of ease-in-out - self.__isAnimating = zyngaCore.effect.Animate.start(step, verify, completed, self.options.animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic); - - } else { - - self.__scheduledLeft = self.__scrollLeft = left; - self.__scheduledTop = self.__scrollTop = top; - self.__scheduledZoom = self.__zoomLevel = zoom; - - // Push values out - if (self.__callback) { - self.__callback(left, top, zoom, wasResize); - } - - // Fix max scroll ranges - if (self.options.zooming) { - self.__computeScrollMax(); - } - } - }, - - - /** - * Recomputes scroll minimum values based on client dimensions and content dimensions. - */ - __computeScrollMax: function(zoomLevel) { - var self = this; - - if (zoomLevel == null) { - zoomLevel = self.__zoomLevel; - } - - self.__maxScrollLeft = Math.max((self.__contentWidth * zoomLevel) - self.__clientWidth, 0); - self.__maxScrollTop = Math.max((self.__contentHeight * zoomLevel) - self.__clientHeight, 0); - - if (!self.__didWaitForSize && !self.__maxScrollLeft && !self.__maxScrollTop) { - self.__didWaitForSize = true; - self.__waitForSize(); - } - }, - - - /** - * If the scroll view isn't sized correctly on start, wait until we have at least some size - */ - __waitForSize: function() { - var self = this; - - clearTimeout(self.__sizerTimeout); - - var sizer = function() { - self.resize(true); - }; - - sizer(); - self.__sizerTimeout = setTimeout(sizer, 500); - }, - - /* - --------------------------------------------------------------------------- - ANIMATION (DECELERATION) SUPPORT - --------------------------------------------------------------------------- - */ - - /** - * Called when a touch sequence end and the speed of the finger was high enough - * to switch into deceleration mode. - */ - __startDeceleration: function() { - var self = this; - - if (self.options.paging) { - - var scrollLeft = Math.max(Math.min(self.__scrollLeft, self.__maxScrollLeft), 0); - var scrollTop = Math.max(Math.min(self.__scrollTop, self.__maxScrollTop), 0); - var clientWidth = self.__clientWidth; - var clientHeight = self.__clientHeight; - - // We limit deceleration not to the min/max values of the allowed range, but to the size of the visible client area. - // Each page should have exactly the size of the client area. - self.__minDecelerationScrollLeft = Math.floor(scrollLeft / clientWidth) * clientWidth; - self.__minDecelerationScrollTop = Math.floor(scrollTop / clientHeight) * clientHeight; - self.__maxDecelerationScrollLeft = Math.ceil(scrollLeft / clientWidth) * clientWidth; - self.__maxDecelerationScrollTop = Math.ceil(scrollTop / clientHeight) * clientHeight; - - } else { - - self.__minDecelerationScrollLeft = 0; - self.__minDecelerationScrollTop = 0; - self.__maxDecelerationScrollLeft = self.__maxScrollLeft; - self.__maxDecelerationScrollTop = self.__maxScrollTop; - if (self.__refreshActive) self.__minDecelerationScrollTop = self.__refreshHeight * -1; - } - - // Wrap class method - var step = function(percent, now, render) { - self.__stepThroughDeceleration(render); - }; - - // How much velocity is required to keep the deceleration running - self.__minVelocityToKeepDecelerating = self.options.snapping ? 4 : 0.1; - - // Detect whether it's still worth to continue animating steps - // If we are already slow enough to not being user perceivable anymore, we stop the whole process here. - var verify = function() { - var shouldContinue = Math.abs(self.__decelerationVelocityX) >= self.__minVelocityToKeepDecelerating || - Math.abs(self.__decelerationVelocityY) >= self.__minVelocityToKeepDecelerating; - if (!shouldContinue) { - self.__didDecelerationComplete = true; - - //Make sure the scroll values are within the boundaries after a bounce, - //not below 0 or above maximum - if (self.options.bouncing && !self.__refreshActive) { - self.scrollTo( - Math.min( Math.max(self.__scrollLeft, 0), self.__maxScrollLeft ), - Math.min( Math.max(self.__scrollTop, 0), self.__maxScrollTop ), - self.__refreshActive - ); - } - } - return shouldContinue; - }; - - var completed = function() { - self.__isDecelerating = false; - if (self.__didDecelerationComplete) { - self.__scrollingComplete(); - } - - // Animate to grid when snapping is active, otherwise just fix out-of-boundary positions - if (self.options.paging) { - self.scrollTo(self.__scrollLeft, self.__scrollTop, self.options.snapping); - } - }; - - // Start animation and switch on flag - self.__isDecelerating = zyngaCore.effect.Animate.start(step, verify, completed); - - }, - - - /** - * Called on every step of the animation - * - * @param inMemory {Boolean} Whether to not render the current step, but keep it in memory only. Used internally only! - */ - __stepThroughDeceleration: function(render) { - var self = this; - - - // - // COMPUTE NEXT SCROLL POSITION - // - - // Add deceleration to scroll position - var scrollLeft = self.__scrollLeft + self.__decelerationVelocityX;// * self.options.deceleration); - var scrollTop = self.__scrollTop + self.__decelerationVelocityY;// * self.options.deceleration); - - - // - // HARD LIMIT SCROLL POSITION FOR NON BOUNCING MODE - // - - if (!self.options.bouncing) { - - var scrollLeftFixed = Math.max(Math.min(self.__maxDecelerationScrollLeft, scrollLeft), self.__minDecelerationScrollLeft); - if (scrollLeftFixed !== scrollLeft) { - scrollLeft = scrollLeftFixed; - self.__decelerationVelocityX = 0; - } - - var scrollTopFixed = Math.max(Math.min(self.__maxDecelerationScrollTop, scrollTop), self.__minDecelerationScrollTop); - if (scrollTopFixed !== scrollTop) { - scrollTop = scrollTopFixed; - self.__decelerationVelocityY = 0; - } - - } - - - // - // UPDATE SCROLL POSITION - // - - if (render) { - - self.__publish(scrollLeft, scrollTop, self.__zoomLevel); - - } else { - - self.__scrollLeft = scrollLeft; - self.__scrollTop = scrollTop; - - } - - - // - // SLOW DOWN - // - - // Slow down velocity on every iteration - if (!self.options.paging) { - - // This is the factor applied to every iteration of the animation - // to slow down the process. This should emulate natural behavior where - // objects slow down when the initiator of the movement is removed - var frictionFactor = self.options.deceleration; - - self.__decelerationVelocityX *= frictionFactor; - self.__decelerationVelocityY *= frictionFactor; - - } - - - // - // BOUNCING SUPPORT - // - - if (self.options.bouncing) { - - var scrollOutsideX = 0; - var scrollOutsideY = 0; - - // This configures the amount of change applied to deceleration/acceleration when reaching boundaries - var penetrationDeceleration = self.options.penetrationDeceleration; - var penetrationAcceleration = self.options.penetrationAcceleration; - - // Check limits - if (scrollLeft < self.__minDecelerationScrollLeft) { - scrollOutsideX = self.__minDecelerationScrollLeft - scrollLeft; - } else if (scrollLeft > self.__maxDecelerationScrollLeft) { - scrollOutsideX = self.__maxDecelerationScrollLeft - scrollLeft; - } - - if (scrollTop < self.__minDecelerationScrollTop) { - scrollOutsideY = self.__minDecelerationScrollTop - scrollTop; - } else if (scrollTop > self.__maxDecelerationScrollTop) { - scrollOutsideY = self.__maxDecelerationScrollTop - scrollTop; - } - - // Slow down until slow enough, then flip back to snap position - if (scrollOutsideX !== 0) { - var isHeadingOutwardsX = scrollOutsideX * self.__decelerationVelocityX <= self.__minDecelerationScrollLeft; - if (isHeadingOutwardsX) { - self.__decelerationVelocityX += scrollOutsideX * penetrationDeceleration; - } - var isStoppedX = Math.abs(self.__decelerationVelocityX) <= self.__minVelocityToKeepDecelerating; - //If we're not heading outwards, or if the above statement got us below minDeceleration, go back towards bounds - if (!isHeadingOutwardsX || isStoppedX) { - self.__decelerationVelocityX = scrollOutsideX * penetrationAcceleration; - } - } - - if (scrollOutsideY !== 0) { - var isHeadingOutwardsY = scrollOutsideY * self.__decelerationVelocityY <= self.__minDecelerationScrollTop; - if (isHeadingOutwardsY) { - self.__decelerationVelocityY += scrollOutsideY * penetrationDeceleration; - } - var isStoppedY = Math.abs(self.__decelerationVelocityY) <= self.__minVelocityToKeepDecelerating; - //If we're not heading outwards, or if the above statement got us below minDeceleration, go back towards bounds - if (!isHeadingOutwardsY || isStoppedY) { - self.__decelerationVelocityY = scrollOutsideY * penetrationAcceleration; - } - } - } - }, - - - /** - * calculate the distance between two touches - * @param {Touch} touch1 - * @param {Touch} touch2 - * @returns {Number} distance - */ - __getDistance: function getDistance(touch1, touch2) { - var x = touch2.pageX - touch1.pageX, - y = touch2.pageY - touch1.pageY; - return Math.sqrt((x * x) + (y * y)); - }, - - - /** - * calculate the scale factor between two touchLists (fingers) - * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out - * @param {Array} start - * @param {Array} end - * @returns {Number} scale - */ - __getScale: function getScale(start, end) { - // need two fingers... - if (start.length >= 2 && end.length >= 2) { - return this.__getDistance(end[0], end[1]) / - this.__getDistance(start[0], start[1]); - } - return 1; - } -}); - -ionic.scroll = { - isScrolling: false, - lastTop: 0 -}; - -})(ionic); - -(function(ionic) { - var NOOP = function() {}; - var deprecated = function(name) { - void 0; - }; - ionic.views.ScrollNative = ionic.views.View.inherit({ - - initialize: function(options) { - var self = this; - self.__container = self.el = options.el; - self.__content = options.el.firstElementChild; - // Whether scrolling is frozen or not - self.__frozen = false; - self.isNative = true; - - self.__scrollTop = self.el.scrollTop; - self.__scrollLeft = self.el.scrollLeft; - self.__clientHeight = self.__content.clientHeight; - self.__clientWidth = self.__content.clientWidth; - self.__maxScrollTop = Math.max((self.__contentHeight) - self.__clientHeight, 0); - self.__maxScrollLeft = Math.max((self.__contentWidth) - self.__clientWidth, 0); - - if(options.startY >= 0 || options.startX >= 0) { - ionic.requestAnimationFrame(function() { - self.el.scrollTop = options.startY || 0; - self.el.scrollLeft = options.startX || 0; - - self.__scrollTop = self.el.scrollTop; - self.__scrollLeft = self.el.scrollLeft; - }); - } - - self.options = { - - freeze: false, - - getContentWidth: function() { - return Math.max(self.__content.scrollWidth, self.__content.offsetWidth); - }, - - getContentHeight: function() { - return Math.max(self.__content.scrollHeight, self.__content.offsetHeight + (self.__content.offsetTop * 2)); - } - - }; - - for (var key in options) { - self.options[key] = options[key]; - } - - /** - * Sets isScrolling to true, and automatically deactivates if not called again in 80ms. - */ - self.onScroll = function() { - if (!ionic.scroll.isScrolling) { - ionic.scroll.isScrolling = true; - } - - clearTimeout(self.scrollTimer); - self.scrollTimer = setTimeout(function() { - ionic.scroll.isScrolling = false; - }, 80); - }; - - self.freeze = function(shouldFreeze) { - self.__frozen = shouldFreeze; - }; - // A more powerful freeze pop that dominates all other freeze pops - self.freezeShut = function(shouldFreezeShut) { - self.__frozenShut = shouldFreezeShut; - }; - - self.__initEventHandlers(); - }, - - /** Methods not used in native scrolling */ - __callback: function() { deprecated('__callback'); }, - zoomTo: function() { deprecated('zoomTo'); }, - zoomBy: function() { deprecated('zoomBy'); }, - activatePullToRefresh: function() { deprecated('activatePullToRefresh'); }, - - /** - * Returns the scroll position and zooming values - * - * @return {Map} `left` and `top` scroll position and `zoom` level - */ - resize: function(continueScrolling) { - var self = this; - if (!self.__container || !self.options) return; - - // Update Scroller dimensions for changed content - // Add padding to bottom of content - self.setDimensions( - self.__container.clientWidth, - self.__container.clientHeight, - self.options.getContentWidth(), - self.options.getContentHeight(), - continueScrolling - ); - }, - - /** - * Initialize the scrollview - * In native scrolling, this only means we need to gather size information - */ - run: function() { - this.resize(); - }, - - /** - * Returns the scroll position and zooming values - * - * @return {Map} `left` and `top` scroll position and `zoom` level - */ - getValues: function() { - var self = this; - self.update(); - return { - left: self.__scrollLeft, - top: self.__scrollTop, - zoom: 1 - }; - }, - - /** - * Updates the __scrollLeft and __scrollTop values to el's current value - */ - update: function() { - var self = this; - self.__scrollLeft = self.el.scrollLeft; - self.__scrollTop = self.el.scrollTop; - }, - - /** - * Configures the dimensions of the client (outer) and content (inner) elements. - * Requires the available space for the outer element and the outer size of the inner element. - * All values which are falsy (null or zero etc.) are ignored and the old value is kept. - * - * @param clientWidth {Integer} Inner width of outer element - * @param clientHeight {Integer} Inner height of outer element - * @param contentWidth {Integer} Outer width of inner element - * @param contentHeight {Integer} Outer height of inner element - */ - setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight) { - var self = this; - - if (!clientWidth && !clientHeight && !contentWidth && !contentHeight) { - // this scrollview isn't rendered, don't bother - return; - } - - // Only update values which are defined - if (clientWidth === +clientWidth) { - self.__clientWidth = clientWidth; - } - - if (clientHeight === +clientHeight) { - self.__clientHeight = clientHeight; - } - - if (contentWidth === +contentWidth) { - self.__contentWidth = contentWidth; - } - - if (contentHeight === +contentHeight) { - self.__contentHeight = contentHeight; - } - - // Refresh maximums - self.__computeScrollMax(); - }, - - /** - * Returns the maximum scroll values - * - * @return {Map} `left` and `top` maximum scroll values - */ - getScrollMax: function() { - return { - left: this.__maxScrollLeft, - top: this.__maxScrollTop - }; - }, - - /** - * Scrolls by the given amount in px. - * - * @param left {Number} Horizontal scroll position, keeps current if value is null - * @param top {Number} Vertical scroll position, keeps current if value is null - * @param animate {Boolean} Whether the scrolling should happen using an animation - */ - - scrollBy: function(left, top, animate) { - var self = this; - - // update scroll vars before refferencing them - self.update(); - - var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft; - var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop; - - self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate); - }, - - /** - * Scrolls to the given position in px. - * - * @param left {Number} Horizontal scroll position, keeps current if value is null - * @param top {Number} Vertical scroll position, keeps current if value is null - * @param animate {Boolean} Whether the scrolling should happen using an animation - */ - scrollTo: function(left, top, animate) { - var self = this; - if (!animate) { - self.el.scrollTop = top; - self.el.scrollLeft = left; - self.resize(); - return; - } - - var oldOverflowX = self.el.style.overflowX; - var oldOverflowY = self.el.style.overflowY; - - clearTimeout(self.__scrollToCleanupTimeout); - self.__scrollToCleanupTimeout = setTimeout(function() { - self.el.style.overflowX = oldOverflowX; - self.el.style.overflowY = oldOverflowY; - }, 500); - - self.el.style.overflowY = 'hidden'; - self.el.style.overflowX = 'hidden'; - - animateScroll(top, left); - - function animateScroll(Y, X) { - // scroll animation loop w/ easing - // credit https://gist.github.com/dezinezync/5487119 - var start = Date.now(), - duration = 250, //milliseconds - fromY = self.el.scrollTop, - fromX = self.el.scrollLeft; - - if (fromY === Y && fromX === X) { - self.el.style.overflowX = oldOverflowX; - self.el.style.overflowY = oldOverflowY; - self.resize(); - return; /* Prevent scrolling to the Y point if already there */ - } - - // decelerating to zero velocity - function easeOutCubic(t) { - return (--t) * t * t + 1; - } - - // scroll loop - function animateScrollStep() { - var currentTime = Date.now(), - time = Math.min(1, ((currentTime - start) / duration)), - // where .5 would be 50% of time on a linear scale easedT gives a - // fraction based on the easing method - easedT = easeOutCubic(time); - - if (fromY != Y) { - self.el.scrollTop = parseInt((easedT * (Y - fromY)) + fromY, 10); - } - if (fromX != X) { - self.el.scrollLeft = parseInt((easedT * (X - fromX)) + fromX, 10); - } - - if (time < 1) { - ionic.requestAnimationFrame(animateScrollStep); - - } else { - // done - ionic.tap.removeClonedInputs(self.__container, self); - self.el.style.overflowX = oldOverflowX; - self.el.style.overflowY = oldOverflowY; - self.resize(); - } - } - - // start scroll loop - ionic.requestAnimationFrame(animateScrollStep); - } - }, - - - - /* - --------------------------------------------------------------------------- - PRIVATE API - --------------------------------------------------------------------------- - */ - - /** - * If the scroll view isn't sized correctly on start, wait until we have at least some size - */ - __waitForSize: function() { - var self = this; - - clearTimeout(self.__sizerTimeout); - - var sizer = function() { - self.resize(true); - }; - - sizer(); - self.__sizerTimeout = setTimeout(sizer, 500); - }, - - - /** - * Recomputes scroll minimum values based on client dimensions and content dimensions. - */ - __computeScrollMax: function() { - var self = this; - - self.__maxScrollLeft = Math.max((self.__contentWidth) - self.__clientWidth, 0); - self.__maxScrollTop = Math.max((self.__contentHeight) - self.__clientHeight, 0); - - if (!self.__didWaitForSize && !self.__maxScrollLeft && !self.__maxScrollTop) { - self.__didWaitForSize = true; - self.__waitForSize(); - } - }, - - __initEventHandlers: function() { - var self = this; - - // Event Handler - var container = self.__container; - // save height when scroll view is shrunk so we don't need to reflow - var scrollViewOffsetHeight; - - var lastKeyboardHeight; - - /** - * Shrink the scroll view when the keyboard is up if necessary and if the - * focused input is below the bottom of the shrunk scroll view, scroll it - * into view. - */ - self.scrollChildIntoView = function(e) { - var rect = container.getBoundingClientRect(); - if(!self.__originalContainerHeight) { - self.__originalContainerHeight = rect.height; - } - - // D - //var scrollBottomOffsetToTop = rect.bottom; - // D - A - scrollViewOffsetHeight = self.__originalContainerHeight; - //console.log('Scroll view offset height', scrollViewOffsetHeight); - //console.dir(container); - var alreadyShrunk = self.isShrunkForKeyboard; - - var isModal = container.parentNode.classList.contains('modal'); - var isPopover = container.parentNode.classList.contains('popover'); - // 680px is when the media query for 60% modal width kicks in - var isInsetModal = isModal && window.innerWidth >= 680; - - /* - * _______ - * |---A---| <- top of scroll view - * | | - * |---B---| <- keyboard - * | C | <- input - * |---D---| <- initial bottom of scroll view - * |___E___| <- bottom of viewport - * - * All commented calculations relative to the top of the viewport (ie E - * is the viewport height, not 0) - */ - - - var changedKeyboardHeight = lastKeyboardHeight && (lastKeyboardHeight !== e.detail.keyboardHeight); - - if (!alreadyShrunk || changedKeyboardHeight) { - // shrink scrollview so we can actually scroll if the input is hidden - // if it isn't shrink so we can scroll to inputs under the keyboard - // inset modals won't shrink on Android on their own when the keyboard appears - if ( !isPopover && (ionic.Platform.isIOS() || ionic.Platform.isFullScreen || isInsetModal) ) { - // if there are things below the scroll view account for them and - // subtract them from the keyboard height when resizing - // E - D E D - //var scrollBottomOffsetToBottom = e.detail.viewportHeight - scrollBottomOffsetToTop; - - // 0 or D - B if D > B E - B E - D - //var keyboardOffset = e.detail.keyboardHeight - scrollBottomOffsetToBottom; - - ionic.requestAnimationFrame(function(){ - // D - A or B - A if D > B D - A max(0, D - B) - scrollViewOffsetHeight = Math.max(0, Math.min(self.__originalContainerHeight, self.__originalContainerHeight - (e.detail.keyboardHeight - 43)));//keyboardOffset >= 0 ? scrollViewOffsetHeight - keyboardOffset : scrollViewOffsetHeight + keyboardOffset; - - //console.log('Old container height', self.__originalContainerHeight, 'New container height', scrollViewOffsetHeight, 'Keyboard height', e.detail.keyboardHeight); - - container.style.height = scrollViewOffsetHeight + "px"; - - /* - if (ionic.Platform.isIOS()) { - // Force redraw to avoid disappearing content - var disp = container.style.display; - container.style.display = 'none'; - var trick = container.offsetHeight; - container.style.display = disp; - } - */ - container.classList.add('keyboard-up'); - //update scroll view - self.resize(); - }); - } - - self.isShrunkForKeyboard = true; - } - - lastKeyboardHeight = e.detail.keyboardHeight; - - /* - * _______ - * |---A---| <- top of scroll view - * | * | <- where we want to scroll to - * |--B-D--| <- keyboard, bottom of scroll view - * | C | <- input - * | | - * |___E___| <- bottom of viewport - * - * All commented calculations relative to the top of the viewport (ie E - * is the viewport height, not 0) - */ - // if the element is positioned under the keyboard scroll it into view - if (e.detail.isElementUnderKeyboard) { - - ionic.requestAnimationFrame(function(){ - var pos = ionic.DomUtil.getOffsetTop(e.detail.target); - setTimeout(function() { - if (ionic.Platform.isIOS()) { - ionic.tap.cloneFocusedInput(container, self); - } - // Scroll the input into view, with a 100px buffer - self.scrollTo(0, pos - (rect.top + 100), true); - self.onScroll(); - }, 32); - - /* - // update D if we shrunk - if (self.isShrunkForKeyboard && !alreadyShrunk) { - scrollBottomOffsetToTop = container.getBoundingClientRect().bottom; - console.log('Scroll bottom', scrollBottomOffsetToTop); - } - - // middle of the scrollview, this is where we want to scroll to - // (D - A) / 2 - var scrollMidpointOffset = scrollViewOffsetHeight * 0.5; - console.log('Midpoint', scrollMidpointOffset); - //console.log("container.offsetHeight: " + scrollViewOffsetHeight); - - // middle of the input we want to scroll into view - // C - var inputMidpoint = ((e.detail.elementBottom + e.detail.elementTop) / 2); - console.log('Input midpoint'); - - // distance from middle of input to the bottom of the scroll view - // C - D C D - var inputMidpointOffsetToScrollBottom = inputMidpoint - scrollBottomOffsetToTop; - console.log('Input midpoint offset', inputMidpointOffsetToScrollBottom); - - //C - D + (D - A)/2 C - D (D - A)/ 2 - var scrollTop = inputMidpointOffsetToScrollBottom + scrollMidpointOffset; - console.log('Scroll top', scrollTop); - - if ( scrollTop > 0) { - if (ionic.Platform.isIOS()) { - //just shrank scroll view, give it some breathing room before scrolling - setTimeout(function(){ - ionic.tap.cloneFocusedInput(container, self); - self.scrollBy(0, scrollTop, true); - self.onScroll(); - }, 32); - } else { - self.scrollBy(0, scrollTop, true); - self.onScroll(); - } - } - */ - }); - } - - // Only the first scrollView parent of the element that broadcasted this event - // (the active element that needs to be shown) should receive this event - e.stopPropagation(); - }; - - self.resetScrollView = function() { - //return scrollview to original height once keyboard has hidden - if (self.isShrunkForKeyboard) { - self.isShrunkForKeyboard = false; - container.style.height = ""; - - /* - if (ionic.Platform.isIOS()) { - // Force redraw to avoid disappearing content - var disp = container.style.display; - container.style.display = 'none'; - var trick = container.offsetHeight; - container.style.display = disp; - } - */ - - self.__originalContainerHeight = container.getBoundingClientRect().height; - - if (ionic.Platform.isIOS()) { - ionic.requestAnimationFrame(function() { - container.classList.remove('keyboard-up'); - }); - } - - } - self.resize(); - }; - - self.handleTouchMove = function(e) { - if(self.__frozenShut) { - e.preventDefault(); - e.stopPropagation(); - return false; - } - }; - - container.addEventListener('scroll', self.onScroll); - - //Broadcasted when keyboard is shown on some platforms. - //See js/utils/keyboard.js - container.addEventListener('scrollChildIntoView', self.scrollChildIntoView); - - container.addEventListener(ionic.EVENTS.touchstart, self.handleTouchMove); - container.addEventListener(ionic.EVENTS.touchmove, self.handleTouchMove); - - // Listen on document because container may not have had the last - // keyboardActiveElement, for example after closing a modal with a focused - // input and returning to a previously resized scroll view in an ion-content. - // Since we can only resize scroll views that are currently visible, just resize - // the current scroll view when the keyboard is closed. - document.addEventListener('resetScrollView', self.resetScrollView); - }, - - __cleanup: function() { - var self = this; - var container = self.__container; - - container.removeEventListener('resetScrollView', self.resetScrollView); - container.removeEventListener('scroll', self.onScroll); - - container.removeEventListener('scrollChildIntoView', self.scrollChildIntoView); - container.removeEventListener('resetScrollView', self.resetScrollView); - - container.removeEventListener(ionic.EVENTS.touchstart, self.handleTouchMove); - container.removeEventListener(ionic.EVENTS.touchmove, self.handleTouchMove); - - ionic.tap.removeClonedInputs(container, self); - - delete self.__container; - delete self.__content; - delete self.__indicatorX; - delete self.__indicatorY; - delete self.options.el; - - self.resize = self.scrollTo = self.onScroll = self.resetScrollView = NOOP; - self.scrollChildIntoView = NOOP; - container = null; - } - }); - -})(ionic); - -(function(ionic) { -'use strict'; - - var ITEM_CLASS = 'item'; - var ITEM_CONTENT_CLASS = 'item-content'; - var ITEM_SLIDING_CLASS = 'item-sliding'; - var ITEM_OPTIONS_CLASS = 'item-options'; - var ITEM_PLACEHOLDER_CLASS = 'item-placeholder'; - var ITEM_REORDERING_CLASS = 'item-reordering'; - var ITEM_REORDER_BTN_CLASS = 'item-reorder'; - - var DragOp = function() {}; - DragOp.prototype = { - start: function(){}, - drag: function(){}, - end: function(){}, - isSameItem: function() { - return false; - } - }; - - var SlideDrag = function(opts) { - this.dragThresholdX = opts.dragThresholdX || 10; - this.el = opts.el; - this.item = opts.item; - this.canSwipe = opts.canSwipe; - }; - - SlideDrag.prototype = new DragOp(); - - SlideDrag.prototype.start = function(e) { - var content, buttons, offsetX, buttonsWidth; - - if (!this.canSwipe()) { - return; - } - - if (e.target.classList.contains(ITEM_CONTENT_CLASS)) { - content = e.target; - } else if (e.target.classList.contains(ITEM_CLASS)) { - content = e.target.querySelector('.' + ITEM_CONTENT_CLASS); - } else { - content = ionic.DomUtil.getParentWithClass(e.target, ITEM_CONTENT_CLASS); - } - - // If we don't have a content area as one of our children (or ourselves), skip - if (!content) { - return; - } - - // Make sure we aren't animating as we slide - content.classList.remove(ITEM_SLIDING_CLASS); - - // Grab the starting X point for the item (for example, so we can tell whether it is open or closed to start) - offsetX = parseFloat(content.style[ionic.CSS.TRANSFORM].replace('translate3d(', '').split(',')[0]) || 0; - - // Grab the buttons - buttons = content.parentNode.querySelector('.' + ITEM_OPTIONS_CLASS); - if (!buttons) { - return; - } - buttons.classList.remove('invisible'); - - buttonsWidth = buttons.offsetWidth; - - this._currentDrag = { - buttons: buttons, - buttonsWidth: buttonsWidth, - content: content, - startOffsetX: offsetX - }; - }; - - /** - * Check if this is the same item that was previously dragged. - */ - SlideDrag.prototype.isSameItem = function(op) { - if (op._lastDrag && this._currentDrag) { - return this._currentDrag.content == op._lastDrag.content; - } - return false; - }; - - SlideDrag.prototype.clean = function(isInstant) { - var lastDrag = this._lastDrag; - - if (!lastDrag || !lastDrag.content) return; - - lastDrag.content.style[ionic.CSS.TRANSITION] = ''; - lastDrag.content.style[ionic.CSS.TRANSFORM] = ''; - if (isInstant) { - lastDrag.content.style[ionic.CSS.TRANSITION] = 'none'; - makeInvisible(); - ionic.requestAnimationFrame(function() { - lastDrag.content.style[ionic.CSS.TRANSITION] = ''; - }); - } else { - ionic.requestAnimationFrame(function() { - setTimeout(makeInvisible, 250); - }); - } - function makeInvisible() { - lastDrag.buttons && lastDrag.buttons.classList.add('invisible'); - } - }; - - SlideDrag.prototype.drag = ionic.animationFrameThrottle(function(e) { - var buttonsWidth; - - // We really aren't dragging - if (!this._currentDrag) { - return; - } - - // Check if we should start dragging. Check if we've dragged past the threshold, - // or we are starting from the open state. - if (!this._isDragging && - ((Math.abs(e.gesture.deltaX) > this.dragThresholdX) || - (Math.abs(this._currentDrag.startOffsetX) > 0))) { - this._isDragging = true; - } - - if (this._isDragging) { - buttonsWidth = this._currentDrag.buttonsWidth; - - // Grab the new X point, capping it at zero - var newX = Math.min(0, this._currentDrag.startOffsetX + e.gesture.deltaX); - - // If the new X position is past the buttons, we need to slow down the drag (rubber band style) - if (newX < -buttonsWidth) { - // Calculate the new X position, capped at the top of the buttons - newX = Math.min(-buttonsWidth, -buttonsWidth + (((e.gesture.deltaX + buttonsWidth) * 0.4))); - } - - this._currentDrag.content.$$ionicOptionsOpen = newX !== 0; - - this._currentDrag.content.style[ionic.CSS.TRANSFORM] = 'translate3d(' + newX + 'px, 0, 0)'; - this._currentDrag.content.style[ionic.CSS.TRANSITION] = 'none'; - } - }); - - SlideDrag.prototype.end = function(e, doneCallback) { - var self = this; - - // There is no drag, just end immediately - if (!self._currentDrag) { - doneCallback && doneCallback(); - return; - } - - // If we are currently dragging, we want to snap back into place - // The final resting point X will be the width of the exposed buttons - var restingPoint = -self._currentDrag.buttonsWidth; - - // Check if the drag didn't clear the buttons mid-point - // and we aren't moving fast enough to swipe open - if (e.gesture.deltaX > -(self._currentDrag.buttonsWidth / 2)) { - - // If we are going left but too slow, or going right, go back to resting - if (e.gesture.direction == "left" && Math.abs(e.gesture.velocityX) < 0.3) { - restingPoint = 0; - - } else if (e.gesture.direction == "right") { - restingPoint = 0; - } - - } - - ionic.requestAnimationFrame(function() { - if (restingPoint === 0) { - self._currentDrag.content.style[ionic.CSS.TRANSFORM] = ''; - var buttons = self._currentDrag.buttons; - setTimeout(function() { - buttons && buttons.classList.add('invisible'); - }, 250); - } else { - self._currentDrag.content.style[ionic.CSS.TRANSFORM] = 'translate3d(' + restingPoint + 'px,0,0)'; - } - self._currentDrag.content.style[ionic.CSS.TRANSITION] = ''; - - - // Kill the current drag - if (!self._lastDrag) { - self._lastDrag = {}; - } - ionic.extend(self._lastDrag, self._currentDrag); - if (self._currentDrag) { - self._currentDrag.buttons = null; - self._currentDrag.content = null; - } - self._currentDrag = null; - - // We are done, notify caller - doneCallback && doneCallback(); - }); - }; - - var ReorderDrag = function(opts) { - var self = this; - - self.dragThresholdY = opts.dragThresholdY || 0; - self.onReorder = opts.onReorder; - self.listEl = opts.listEl; - self.el = self.item = opts.el; - self.scrollEl = opts.scrollEl; - self.scrollView = opts.scrollView; - // Get the True Top of the list el http://www.quirksmode.org/js/findpos.html - self.listElTrueTop = 0; - if (self.listEl.offsetParent) { - var obj = self.listEl; - do { - self.listElTrueTop += obj.offsetTop; - obj = obj.offsetParent; - } while (obj); - } - }; - - ReorderDrag.prototype = new DragOp(); - - ReorderDrag.prototype._moveElement = function(e) { - var y = e.gesture.center.pageY + - this.scrollView.getValues().top - - (this._currentDrag.elementHeight / 2) - - this.listElTrueTop; - this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(0, ' + y + 'px, 0)'; - }; - - ReorderDrag.prototype.deregister = function() { - this.listEl = this.el = this.scrollEl = this.scrollView = null; - }; - - ReorderDrag.prototype.start = function(e) { - - var startIndex = ionic.DomUtil.getChildIndex(this.el, this.el.nodeName.toLowerCase()); - var elementHeight = this.el.scrollHeight; - var placeholder = this.el.cloneNode(true); - - placeholder.classList.add(ITEM_PLACEHOLDER_CLASS); - - this.el.parentNode.insertBefore(placeholder, this.el); - this.el.classList.add(ITEM_REORDERING_CLASS); - - this._currentDrag = { - elementHeight: elementHeight, - startIndex: startIndex, - placeholder: placeholder, - scrollHeight: scroll, - list: placeholder.parentNode - }; - - this._moveElement(e); - }; - - ReorderDrag.prototype.drag = ionic.animationFrameThrottle(function(e) { - // We really aren't dragging - var self = this; - if (!this._currentDrag) { - return; - } - - var scrollY = 0; - var pageY = e.gesture.center.pageY; - var offset = this.listElTrueTop; - - //If we have a scrollView, check scroll boundaries for dragged element and scroll if necessary - if (this.scrollView) { - - var container = this.scrollView.__container; - scrollY = this.scrollView.getValues().top; - - var containerTop = container.offsetTop; - var pixelsPastTop = containerTop - pageY + this._currentDrag.elementHeight / 2; - var pixelsPastBottom = pageY + this._currentDrag.elementHeight / 2 - containerTop - container.offsetHeight; - - if (e.gesture.deltaY < 0 && pixelsPastTop > 0 && scrollY > 0) { - this.scrollView.scrollBy(null, -pixelsPastTop); - //Trigger another drag so the scrolling keeps going - ionic.requestAnimationFrame(function() { - self.drag(e); - }); - } - if (e.gesture.deltaY > 0 && pixelsPastBottom > 0) { - if (scrollY < this.scrollView.getScrollMax().top) { - this.scrollView.scrollBy(null, pixelsPastBottom); - //Trigger another drag so the scrolling keeps going - ionic.requestAnimationFrame(function() { - self.drag(e); - }); - } - } - } - - // Check if we should start dragging. Check if we've dragged past the threshold, - // or we are starting from the open state. - if (!this._isDragging && Math.abs(e.gesture.deltaY) > this.dragThresholdY) { - this._isDragging = true; - } - - if (this._isDragging) { - this._moveElement(e); - - this._currentDrag.currentY = scrollY + pageY - offset; - - // this._reorderItems(); - } - }); - - // When an item is dragged, we need to reorder any items for sorting purposes - ReorderDrag.prototype._getReorderIndex = function() { - var self = this; - - var siblings = Array.prototype.slice.call(self._currentDrag.placeholder.parentNode.children) - .filter(function(el) { - return el.nodeName === self.el.nodeName && el !== self.el; - }); - - var dragOffsetTop = self._currentDrag.currentY; - var el; - for (var i = 0, len = siblings.length; i < len; i++) { - el = siblings[i]; - if (i === len - 1) { - if (dragOffsetTop > el.offsetTop) { - return i; - } - } else if (i === 0) { - if (dragOffsetTop < el.offsetTop + el.offsetHeight) { - return i; - } - } else if (dragOffsetTop > el.offsetTop - el.offsetHeight / 2 && - dragOffsetTop < el.offsetTop + el.offsetHeight) { - return i; - } - } - return self._currentDrag.startIndex; - }; - - ReorderDrag.prototype.end = function(e, doneCallback) { - if (!this._currentDrag) { - doneCallback && doneCallback(); - return; - } - - var placeholder = this._currentDrag.placeholder; - var finalIndex = this._getReorderIndex(); - - // Reposition the element - this.el.classList.remove(ITEM_REORDERING_CLASS); - this.el.style[ionic.CSS.TRANSFORM] = ''; - - placeholder.parentNode.insertBefore(this.el, placeholder); - placeholder.parentNode.removeChild(placeholder); - - this.onReorder && this.onReorder(this.el, this._currentDrag.startIndex, finalIndex); - - this._currentDrag = { - placeholder: null, - content: null - }; - this._currentDrag = null; - doneCallback && doneCallback(); - }; - - - - /** - * The ListView handles a list of items. It will process drag animations, edit mode, - * and other operations that are common on mobile lists or table views. - */ - ionic.views.ListView = ionic.views.View.inherit({ - initialize: function(opts) { - var self = this; - - opts = ionic.extend({ - onReorder: function() {}, - virtualRemoveThreshold: -200, - virtualAddThreshold: 200, - canSwipe: function() { - return true; - } - }, opts); - - ionic.extend(self, opts); - - if (!self.itemHeight && self.listEl) { - self.itemHeight = self.listEl.children[0] && parseInt(self.listEl.children[0].style.height, 10); - } - - self.onRefresh = opts.onRefresh || function() {}; - self.onRefreshOpening = opts.onRefreshOpening || function() {}; - self.onRefreshHolding = opts.onRefreshHolding || function() {}; - - var gestureOpts = {}; - // don't prevent native scrolling - if (ionic.DomUtil.getParentOrSelfWithClass(self.el, 'overflow-scroll')) { - gestureOpts.prevent_default_directions = ['left', 'right']; - } - - window.ionic.onGesture('release', function(e) { - self._handleEndDrag(e); - }, self.el, gestureOpts); - - window.ionic.onGesture('drag', function(e) { - self._handleDrag(e); - }, self.el, gestureOpts); - // Start the drag states - self._initDrag(); - }, - - /** - * Be sure to cleanup references. - */ - deregister: function() { - this.el = this.listEl = this.scrollEl = this.scrollView = null; - - // ensure no scrolls have been left frozen - if (this.isScrollFreeze) { - self.scrollView.freeze(false); - } - }, - - /** - * Called to tell the list to stop refreshing. This is useful - * if you are refreshing the list and are done with refreshing. - */ - stopRefreshing: function() { - var refresher = this.el.querySelector('.list-refresher'); - refresher.style.height = '0'; - }, - - /** - * If we scrolled and have virtual mode enabled, compute the window - * of active elements in order to figure out the viewport to render. - */ - didScroll: function(e) { - var self = this; - - if (self.isVirtual) { - var itemHeight = self.itemHeight; - - // Grab the total height of the list - var scrollHeight = e.target.scrollHeight; - - // Get the viewport height - var viewportHeight = self.el.parentNode.offsetHeight; - - // High water is the pixel position of the first element to include (everything before - // that will be removed) - var highWater = Math.max(0, e.scrollTop + self.virtualRemoveThreshold); - - // Low water is the pixel position of the last element to include (everything after - // that will be removed) - var lowWater = Math.min(scrollHeight, Math.abs(e.scrollTop) + viewportHeight + self.virtualAddThreshold); - - // Get the first and last elements in the list based on how many can fit - // between the pixel range of lowWater and highWater - var first = parseInt(Math.abs(highWater / itemHeight), 10); - var last = parseInt(Math.abs(lowWater / itemHeight), 10); - - // Get the items we need to remove - self._virtualItemsToRemove = Array.prototype.slice.call(self.listEl.children, 0, first); - - self.renderViewport && self.renderViewport(highWater, lowWater, first, last); - } - }, - - didStopScrolling: function() { - if (this.isVirtual) { - for (var i = 0; i < this._virtualItemsToRemove.length; i++) { - //el.parentNode.removeChild(el); - this.didHideItem && this.didHideItem(i); - } - // Once scrolling stops, check if we need to remove old items - - } - }, - - /** - * Clear any active drag effects on the list. - */ - clearDragEffects: function(isInstant) { - if (this._lastDragOp) { - this._lastDragOp.clean && this._lastDragOp.clean(isInstant); - this._lastDragOp.deregister && this._lastDragOp.deregister(); - this._lastDragOp = null; - } - }, - - _initDrag: function() { - // Store the last one - if (this._lastDragOp) { - this._lastDragOp.deregister && this._lastDragOp.deregister(); - } - this._lastDragOp = this._dragOp; - - this._dragOp = null; - }, - - // Return the list item from the given target - _getItem: function(target) { - while (target) { - if (target.classList && target.classList.contains(ITEM_CLASS)) { - return target; - } - target = target.parentNode; - } - return null; - }, - - - _startDrag: function(e) { - var self = this; - - self._isDragging = false; - - var lastDragOp = self._lastDragOp; - var item; - - // If we have an open SlideDrag and we're scrolling the list. Clear it. - if (self._didDragUpOrDown && lastDragOp instanceof SlideDrag) { - lastDragOp.clean && lastDragOp.clean(); - } - - // Check if this is a reorder drag - if (ionic.DomUtil.getParentOrSelfWithClass(e.target, ITEM_REORDER_BTN_CLASS) && (e.gesture.direction == 'up' || e.gesture.direction == 'down')) { - item = self._getItem(e.target); - - if (item) { - self._dragOp = new ReorderDrag({ - listEl: self.el, - el: item, - scrollEl: self.scrollEl, - scrollView: self.scrollView, - onReorder: function(el, start, end) { - self.onReorder && self.onReorder(el, start, end); - } - }); - self._dragOp.start(e); - e.preventDefault(); - } - } - - // Or check if this is a swipe to the side drag - else if (!self._didDragUpOrDown && (e.gesture.direction == 'left' || e.gesture.direction == 'right') && Math.abs(e.gesture.deltaX) > 5) { - - // Make sure this is an item with buttons - item = self._getItem(e.target); - if (item && item.querySelector('.item-options')) { - self._dragOp = new SlideDrag({ - el: self.el, - item: item, - canSwipe: self.canSwipe - }); - self._dragOp.start(e); - e.preventDefault(); - self.isScrollFreeze = self.scrollView.freeze(true); - } - } - - // If we had a last drag operation and this is a new one on a different item, clean that last one - if (lastDragOp && self._dragOp && !self._dragOp.isSameItem(lastDragOp) && e.defaultPrevented) { - lastDragOp.clean && lastDragOp.clean(); - } - }, - - - _handleEndDrag: function(e) { - var self = this; - - if (self.scrollView) { - self.isScrollFreeze = self.scrollView.freeze(false); - } - - self._didDragUpOrDown = false; - - if (!self._dragOp) { - return; - } - - self._dragOp.end(e, function() { - self._initDrag(); - }); - }, - - /** - * Process the drag event to move the item to the left or right. - */ - _handleDrag: function(e) { - var self = this; - - if (Math.abs(e.gesture.deltaY) > 5) { - self._didDragUpOrDown = true; - } - - // If we get a drag event, make sure we aren't in another drag, then check if we should - // start one - if (!self.isDragging && !self._dragOp) { - self._startDrag(e); - } - - // No drag still, pass it up - if (!self._dragOp) { - return; - } - - e.gesture.srcEvent.preventDefault(); - self._dragOp.drag(e); - } - - }); - -})(ionic); - -(function(ionic) { -'use strict'; - - ionic.views.Modal = ionic.views.View.inherit({ - initialize: function(opts) { - opts = ionic.extend({ - focusFirstInput: false, - unfocusOnHide: true, - focusFirstDelay: 600, - backdropClickToClose: true, - hardwareBackButtonClose: true, - }, opts); - - ionic.extend(this, opts); - - this.el = opts.el; - }, - show: function() { - var self = this; - - if(self.focusFirstInput) { - // Let any animations run first - window.setTimeout(function() { - var input = self.el.querySelector('input, textarea'); - input && input.focus && input.focus(); - }, self.focusFirstDelay); - } - }, - hide: function() { - // Unfocus all elements - if(this.unfocusOnHide) { - var inputs = this.el.querySelectorAll('input, textarea'); - // Let any animations run first - window.setTimeout(function() { - for(var i = 0; i < inputs.length; i++) { - inputs[i].blur && inputs[i].blur(); - } - }); - } - } - }); - -})(ionic); - -(function(ionic) { -'use strict'; - - /** - * The side menu view handles one of the side menu's in a Side Menu Controller - * configuration. - * It takes a DOM reference to that side menu element. - */ - ionic.views.SideMenu = ionic.views.View.inherit({ - initialize: function(opts) { - this.el = opts.el; - this.isEnabled = (typeof opts.isEnabled === 'undefined') ? true : opts.isEnabled; - this.setWidth(opts.width); - }, - getFullWidth: function() { - return this.width; - }, - setWidth: function(width) { - this.width = width; - this.el.style.width = width + 'px'; - }, - setIsEnabled: function(isEnabled) { - this.isEnabled = isEnabled; - }, - bringUp: function() { - if(this.el.style.zIndex !== '0') { - this.el.style.zIndex = '0'; - } - }, - pushDown: function() { - if(this.el.style.zIndex !== '-1') { - this.el.style.zIndex = '-1'; - } - } - }); - - ionic.views.SideMenuContent = ionic.views.View.inherit({ - initialize: function(opts) { - ionic.extend(this, { - animationClass: 'menu-animated', - onDrag: function() {}, - onEndDrag: function() {} - }, opts); - - ionic.onGesture('drag', ionic.proxy(this._onDrag, this), this.el); - ionic.onGesture('release', ionic.proxy(this._onEndDrag, this), this.el); - }, - _onDrag: function(e) { - this.onDrag && this.onDrag(e); - }, - _onEndDrag: function(e) { - this.onEndDrag && this.onEndDrag(e); - }, - disableAnimation: function() { - this.el.classList.remove(this.animationClass); - }, - enableAnimation: function() { - this.el.classList.add(this.animationClass); - }, - getTranslateX: function() { - return parseFloat(this.el.style[ionic.CSS.TRANSFORM].replace('translate3d(', '').split(',')[0]); - }, - setTranslateX: ionic.animationFrameThrottle(function(x) { - this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + x + 'px, 0, 0)'; - }) - }); - -})(ionic); - -/* - * Adapted from Swipe.js 2.0 - * - * Brad Birdsall - * Copyright 2013, MIT License - * -*/ - -(function(ionic) { -'use strict'; - -ionic.views.Slider = ionic.views.View.inherit({ - initialize: function (options) { - var slider = this; - - var touchStartEvent, touchMoveEvent, touchEndEvent; - if (window.navigator.pointerEnabled) { - touchStartEvent = 'pointerdown'; - touchMoveEvent = 'pointermove'; - touchEndEvent = 'pointerup'; - } else if (window.navigator.msPointerEnabled) { - touchStartEvent = 'MSPointerDown'; - touchMoveEvent = 'MSPointerMove'; - touchEndEvent = 'MSPointerUp'; - } else { - touchStartEvent = 'touchstart'; - touchMoveEvent = 'touchmove'; - touchEndEvent = 'touchend'; - } - - var mouseStartEvent = 'mousedown'; - var mouseMoveEvent = 'mousemove'; - var mouseEndEvent = 'mouseup'; - - // utilities - var noop = function() {}; // simple no operation function - var offloadFn = function(fn) { setTimeout(fn || noop, 0); }; // offload a functions execution - - // check browser capabilities - var browser = { - addEventListener: !!window.addEventListener, - transitions: (function(temp) { - var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition']; - for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true; - return false; - })(document.createElement('swipe')) - }; - - - var container = options.el; - - // quit if no root element - if (!container) return; - var element = container.children[0]; - var slides, slidePos, width, length; - options = options || {}; - var index = parseInt(options.startSlide, 10) || 0; - var speed = options.speed || 300; - options.continuous = options.continuous !== undefined ? options.continuous : true; - - function setup() { - - // do not setup if the container has no width - if (!container.offsetWidth) { - return; - } - - // cache slides - slides = element.children; - length = slides.length; - - // set continuous to false if only one slide - if (slides.length < 2) options.continuous = false; - - //special case if two slides - if (browser.transitions && options.continuous && slides.length < 3) { - element.appendChild(slides[0].cloneNode(true)); - element.appendChild(element.children[1].cloneNode(true)); - slides = element.children; - } - - // create an array to store current positions of each slide - slidePos = new Array(slides.length); - - // determine width of each slide - width = container.offsetWidth || container.getBoundingClientRect().width; - - element.style.width = (slides.length * width) + 'px'; - - // stack elements - var pos = slides.length; - while(pos--) { - - var slide = slides[pos]; - - slide.style.width = width + 'px'; - slide.setAttribute('data-index', pos); - - if (browser.transitions) { - slide.style.left = (pos * -width) + 'px'; - move(pos, index > pos ? -width : (index < pos ? width : 0), 0); - } - - } - - // reposition elements before and after index - if (options.continuous && browser.transitions) { - move(circle(index - 1), -width, 0); - move(circle(index + 1), width, 0); - } - - if (!browser.transitions) element.style.left = (index * -width) + 'px'; - - container.style.visibility = 'visible'; - - options.slidesChanged && options.slidesChanged(); - } - - function prev(slideSpeed) { - - if (options.continuous) slide(index - 1, slideSpeed); - else if (index) slide(index - 1, slideSpeed); - - } - - function next(slideSpeed) { - - if (options.continuous) slide(index + 1, slideSpeed); - else if (index < slides.length - 1) slide(index + 1, slideSpeed); - - } - - function circle(index) { - - // a simple positive modulo using slides.length - return (slides.length + (index % slides.length)) % slides.length; - - } - - function slide(to, slideSpeed) { - - // do nothing if already on requested slide - if (index == to) return; - - if (!slides) { - index = to; - return; - } - - if (browser.transitions) { - - var direction = Math.abs(index - to) / (index - to); // 1: backward, -1: forward - - // get the actual position of the slide - if (options.continuous) { - var naturalDirection = direction; - direction = -slidePos[circle(to)] / width; - - // if going forward but to < index, use to = slides.length + to - // if going backward but to > index, use to = -slides.length + to - if (direction !== naturalDirection) to = -direction * slides.length + to; - - } - - var diff = Math.abs(index - to) - 1; - - // move all the slides between index and to in the right direction - while (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0); - - to = circle(to); - - move(index, width * direction, slideSpeed || speed); - move(to, 0, slideSpeed || speed); - - if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place - - } else { - - to = circle(to); - animate(index * -width, to * -width, slideSpeed || speed); - //no fallback for a circular continuous if the browser does not accept transitions - } - - index = to; - offloadFn(options.callback && options.callback(index, slides[index])); - } - - function move(index, dist, speed) { - - translate(index, dist, speed); - slidePos[index] = dist; - - } - - function translate(index, dist, speed) { - - var slide = slides[index]; - var style = slide && slide.style; - - if (!style) return; - - style.webkitTransitionDuration = - style.MozTransitionDuration = - style.msTransitionDuration = - style.OTransitionDuration = - style.transitionDuration = speed + 'ms'; - - style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)'; - style.msTransform = - style.MozTransform = - style.OTransform = 'translateX(' + dist + 'px)'; - - } - - function animate(from, to, speed) { - - // if not an animation, just reposition - if (!speed) { - - element.style.left = to + 'px'; - return; - - } - - var start = +new Date(); - - var timer = setInterval(function() { - - var timeElap = +new Date() - start; - - if (timeElap > speed) { - - element.style.left = to + 'px'; - - if (delay) begin(); - - options.transitionEnd && options.transitionEnd.call(event, index, slides[index]); - - clearInterval(timer); - return; - - } - - element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px'; - - }, 4); - - } - - // setup auto slideshow - var delay = options.auto || 0; - var interval; - - function begin() { - - interval = setTimeout(next, delay); - - } - - function stop() { - - delay = options.auto || 0; - clearTimeout(interval); - - } - - - // setup initial vars - var start = {}; - var delta = {}; - var isScrolling; - - // setup event capturing - var events = { - - handleEvent: function(event) { - if(!event.touches && event.pageX && event.pageY) { - event.touches = [{ - pageX: event.pageX, - pageY: event.pageY - }]; - } - - switch (event.type) { - case touchStartEvent: this.start(event); break; - case mouseStartEvent: this.start(event); break; - case touchMoveEvent: this.touchmove(event); break; - case mouseMoveEvent: this.touchmove(event); break; - case touchEndEvent: offloadFn(this.end(event)); break; - case mouseEndEvent: offloadFn(this.end(event)); break; - case 'webkitTransitionEnd': - case 'msTransitionEnd': - case 'oTransitionEnd': - case 'otransitionend': - case 'transitionend': offloadFn(this.transitionEnd(event)); break; - case 'resize': offloadFn(setup); break; - } - - if (options.stopPropagation) event.stopPropagation(); - - }, - start: function(event) { - - // prevent to start if there is no valid event - if (!event.touches) { - return; - } - - var touches = event.touches[0]; - - // measure start values - start = { - - // get initial touch coords - x: touches.pageX, - y: touches.pageY, - - // store time to determine touch duration - time: +new Date() - - }; - - // used for testing first move event - isScrolling = undefined; - - // reset delta and end measurements - delta = {}; - - // attach touchmove and touchend listeners - element.addEventListener(touchMoveEvent, this, false); - element.addEventListener(mouseMoveEvent, this, false); - - element.addEventListener(touchEndEvent, this, false); - element.addEventListener(mouseEndEvent, this, false); - - document.addEventListener(touchEndEvent, this, false); - document.addEventListener(mouseEndEvent, this, false); - }, - touchmove: function(event) { - - // ensure there is a valid event - // ensure swiping with one touch and not pinching - // ensure sliding is enabled - if (!event.touches || - event.touches.length > 1 || - event.scale && event.scale !== 1 || - slider.slideIsDisabled) { - return; - } - - if (options.disableScroll) event.preventDefault(); - - var touches = event.touches[0]; - - // measure change in x and y - delta = { - x: touches.pageX - start.x, - y: touches.pageY - start.y - }; - - // determine if scrolling test has run - one time test - if ( typeof isScrolling == 'undefined') { - isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) ); - } - - // if user is not trying to scroll vertically - if (!isScrolling) { - - // prevent native scrolling - event.preventDefault(); - - // stop slideshow - stop(); - - // increase resistance if first or last slide - if (options.continuous) { // we don't add resistance at the end - - translate(circle(index - 1), delta.x + slidePos[circle(index - 1)], 0); - translate(index, delta.x + slidePos[index], 0); - translate(circle(index + 1), delta.x + slidePos[circle(index + 1)], 0); - - } else { - // If the slider bounces, do the bounce! - if(options.bouncing) { - delta.x = - delta.x / - ( (!index && delta.x > 0 || // if first slide and sliding left - index == slides.length - 1 && // or if last slide and sliding right - delta.x < 0 // and if sliding at all - ) ? - ( Math.abs(delta.x) / width + 1 ) // determine resistance level - : 1 ); // no resistance if false - } else { - if(width * index - delta.x < 0) { //We are trying scroll past left boundary - delta.x = Math.min(delta.x, width * index); //Set delta.x so we don't go past left screen - } - if(Math.abs(delta.x) > width * (slides.length - index - 1)){ //We are trying to scroll past right bondary - delta.x = Math.max( -width * (slides.length - index - 1), delta.x); //Set delta.x so we don't go past right screen - } - } - - // translate 1:1 - translate(index - 1, delta.x + slidePos[index - 1], 0); - translate(index, delta.x + slidePos[index], 0); - translate(index + 1, delta.x + slidePos[index + 1], 0); - } - - options.onDrag && options.onDrag(); - } - - }, - end: function() { - - // measure duration - var duration = +new Date() - start.time; - - // determine if slide attempt triggers next/prev slide - var isValidSlide = - Number(duration) < 250 && // if slide duration is less than 250ms - Math.abs(delta.x) > 20 || // and if slide amt is greater than 20px - Math.abs(delta.x) > width / 2; // or if slide amt is greater than half the width - - // determine if slide attempt is past start and end - var isPastBounds = (!index && delta.x > 0) || // if first slide and slide amt is greater than 0 - (index == slides.length - 1 && delta.x < 0); // or if last slide and slide amt is less than 0 - - if (options.continuous) isPastBounds = false; - - // determine direction of swipe (true:right, false:left) - var direction = delta.x < 0; - - // if not scrolling vertically - if (!isScrolling) { - - if (isValidSlide && !isPastBounds) { - - if (direction) { - - if (options.continuous) { // we need to get the next in this direction in place - - move(circle(index - 1), -width, 0); - move(circle(index + 2), width, 0); - - } else { - move(index - 1, -width, 0); - } - - move(index, slidePos[index] - width, speed); - move(circle(index + 1), slidePos[circle(index + 1)] - width, speed); - index = circle(index + 1); - - } else { - if (options.continuous) { // we need to get the next in this direction in place - - move(circle(index + 1), width, 0); - move(circle(index - 2), -width, 0); - - } else { - move(index + 1, width, 0); - } - - move(index, slidePos[index] + width, speed); - move(circle(index - 1), slidePos[circle(index - 1)] + width, speed); - index = circle(index - 1); - - } - - options.callback && options.callback(index, slides[index]); - - } else { - - if (options.continuous) { - - move(circle(index - 1), -width, speed); - move(index, 0, speed); - move(circle(index + 1), width, speed); - - } else { - - move(index - 1, -width, speed); - move(index, 0, speed); - move(index + 1, width, speed); - } - - } - - } - - // kill touchmove and touchend event listeners until touchstart called again - element.removeEventListener(touchMoveEvent, events, false); - element.removeEventListener(mouseMoveEvent, events, false); - - element.removeEventListener(touchEndEvent, events, false); - element.removeEventListener(mouseEndEvent, events, false); - - document.removeEventListener(touchEndEvent, events, false); - document.removeEventListener(mouseEndEvent, events, false); - - options.onDragEnd && options.onDragEnd(); - }, - transitionEnd: function(event) { - - if (parseInt(event.target.getAttribute('data-index'), 10) == index) { - - if (delay) begin(); - - options.transitionEnd && options.transitionEnd.call(event, index, slides[index]); - - } - - } - - }; - - // Public API - this.update = function() { - setTimeout(setup); - }; - this.setup = function() { - setup(); - }; - - this.loop = function(value) { - if (arguments.length) options.continuous = !!value; - return options.continuous; - }; - - this.enableSlide = function(shouldEnable) { - if (arguments.length) { - this.slideIsDisabled = !shouldEnable; - } - return !this.slideIsDisabled; - }; - - this.slide = this.select = function(to, speed) { - // cancel slideshow - stop(); - - slide(to, speed); - }; - - this.prev = this.previous = function() { - // cancel slideshow - stop(); - - prev(); - }; - - this.next = function() { - // cancel slideshow - stop(); - - next(); - }; - - this.stop = function() { - // cancel slideshow - stop(); - }; - - this.start = function() { - begin(); - }; - - this.autoPlay = function(newDelay) { - if (!delay || delay < 0) { - stop(); - } else { - delay = newDelay; - begin(); - } - }; - - this.currentIndex = this.selected = function() { - // return current index position - return index; - }; - - this.slidesCount = this.count = function() { - // return total number of slides - return length; - }; - - this.kill = function() { - // cancel slideshow - stop(); - - // reset element - element.style.width = ''; - element.style.left = ''; - - // reset slides so no refs are held on to - slides && (slides = []); - - // removed event listeners - if (browser.addEventListener) { - - // remove current event listeners - element.removeEventListener(touchStartEvent, events, false); - element.removeEventListener(mouseStartEvent, events, false); - element.removeEventListener('webkitTransitionEnd', events, false); - element.removeEventListener('msTransitionEnd', events, false); - element.removeEventListener('oTransitionEnd', events, false); - element.removeEventListener('otransitionend', events, false); - element.removeEventListener('transitionend', events, false); - window.removeEventListener('resize', events, false); - - } - else { - - window.onresize = null; - - } - }; - - this.load = function() { - // trigger setup - setup(); - - // start auto slideshow if applicable - if (delay) begin(); - - - // add event listeners - if (browser.addEventListener) { - - // set touchstart event on element - element.addEventListener(touchStartEvent, events, false); - element.addEventListener(mouseStartEvent, events, false); - - if (browser.transitions) { - element.addEventListener('webkitTransitionEnd', events, false); - element.addEventListener('msTransitionEnd', events, false); - element.addEventListener('oTransitionEnd', events, false); - element.addEventListener('otransitionend', events, false); - element.addEventListener('transitionend', events, false); - } - - // set resize event on window - window.addEventListener('resize', events, false); - - } else { - - window.onresize = function () { setup(); }; // to play nice with old IE - - } - }; - - } -}); - -})(ionic); - -/*eslint space-after-keywords: 0*/ - -/** - * Swiper 3.2.7 - * Most modern mobile touch slider and framework with hardware accelerated transitions - * - * http://www.idangero.us/swiper/ - * - * Copyright 2015, Vladimir Kharlampidi - * The iDangero.us - * http://www.idangero.us/ - * - * Licensed under MIT - * - * Released on: December 7, 2015 - */ -(function () { - 'use strict'; - var $; - /*=========================== - Swiper - ===========================*/ - var Swiper = function (container, params, _scope, $compile) { - - if (!(this instanceof Swiper)) return new Swiper(container, params); - - var defaults = { - direction: 'horizontal', - touchEventsTarget: 'container', - initialSlide: 0, - speed: 300, - // autoplay - autoplay: false, - autoplayDisableOnInteraction: true, - // To support iOS's swipe-to-go-back gesture (when being used in-app, with UIWebView). - iOSEdgeSwipeDetection: false, - iOSEdgeSwipeThreshold: 20, - // Free mode - freeMode: false, - freeModeMomentum: true, - freeModeMomentumRatio: 1, - freeModeMomentumBounce: true, - freeModeMomentumBounceRatio: 1, - freeModeSticky: false, - freeModeMinimumVelocity: 0.02, - // Autoheight - autoHeight: false, - // Set wrapper width - setWrapperSize: false, - // Virtual Translate - virtualTranslate: false, - // Effects - effect: 'slide', // 'slide' or 'fade' or 'cube' or 'coverflow' - coverflow: { - rotate: 50, - stretch: 0, - depth: 100, - modifier: 1, - slideShadows : true - }, - cube: { - slideShadows: true, - shadow: true, - shadowOffset: 20, - shadowScale: 0.94 - }, - fade: { - crossFade: false - }, - // Parallax - parallax: false, - // Scrollbar - scrollbar: null, - scrollbarHide: true, - scrollbarDraggable: false, - scrollbarSnapOnRelease: false, - // Keyboard Mousewheel - keyboardControl: false, - mousewheelControl: false, - mousewheelReleaseOnEdges: false, - mousewheelInvert: false, - mousewheelForceToAxis: false, - mousewheelSensitivity: 1, - // Hash Navigation - hashnav: false, - // Breakpoints - breakpoints: undefined, - // Slides grid - spaceBetween: 0, - slidesPerView: 1, - slidesPerColumn: 1, - slidesPerColumnFill: 'column', - slidesPerGroup: 1, - centeredSlides: false, - slidesOffsetBefore: 0, // in px - slidesOffsetAfter: 0, // in px - // Round length - roundLengths: false, - // Touches - touchRatio: 1, - touchAngle: 45, - simulateTouch: true, - shortSwipes: true, - longSwipes: true, - longSwipesRatio: 0.5, - longSwipesMs: 300, - followFinger: true, - onlyExternal: false, - threshold: 0, - touchMoveStopPropagation: true, - // Pagination - pagination: null, - paginationElement: 'span', - paginationClickable: false, - paginationHide: false, - paginationBulletRender: null, - // Resistance - resistance: true, - resistanceRatio: 0.85, - // Next/prev buttons - nextButton: null, - prevButton: null, - // Progress - watchSlidesProgress: false, - watchSlidesVisibility: false, - // Cursor - grabCursor: false, - // Clicks - preventClicks: true, - preventClicksPropagation: true, - slideToClickedSlide: false, - // Lazy Loading - lazyLoading: false, - lazyLoadingInPrevNext: false, - lazyLoadingOnTransitionStart: false, - // Images - preloadImages: true, - updateOnImagesReady: true, - // loop - loop: false, - loopAdditionalSlides: 0, - loopedSlides: null, - // Control - control: undefined, - controlInverse: false, - controlBy: 'slide', //or 'container' - // Swiping/no swiping - allowSwipeToPrev: true, - allowSwipeToNext: true, - swipeHandler: null, //'.swipe-handler', - noSwiping: true, - noSwipingClass: 'swiper-no-swiping', - // NS - slideClass: 'swiper-slide', - slideActiveClass: 'swiper-slide-active', - slideVisibleClass: 'swiper-slide-visible', - slideDuplicateClass: 'swiper-slide-duplicate', - slideNextClass: 'swiper-slide-next', - slidePrevClass: 'swiper-slide-prev', - wrapperClass: 'swiper-wrapper', - bulletClass: 'swiper-pagination-bullet', - bulletActiveClass: 'swiper-pagination-bullet-active', - buttonDisabledClass: 'swiper-button-disabled', - paginationHiddenClass: 'swiper-pagination-hidden', - // Observer - observer: false, - observeParents: false, - // Accessibility - a11y: false, - prevSlideMessage: 'Previous slide', - nextSlideMessage: 'Next slide', - firstSlideMessage: 'This is the first slide', - lastSlideMessage: 'This is the last slide', - paginationBulletMessage: 'Go to slide {{index}}', - // Callbacks - runCallbacksOnInit: true - /* - Callbacks: - onInit: function (swiper) - onDestroy: function (swiper) - onClick: function (swiper, e) - onTap: function (swiper, e) - onDoubleTap: function (swiper, e) - onSliderMove: function (swiper, e) - onSlideChangeStart: function (swiper) - onSlideChangeEnd: function (swiper) - onTransitionStart: function (swiper) - onTransitionEnd: function (swiper) - onImagesReady: function (swiper) - onProgress: function (swiper, progress) - onTouchStart: function (swiper, e) - onTouchMove: function (swiper, e) - onTouchMoveOpposite: function (swiper, e) - onTouchEnd: function (swiper, e) - onReachBeginning: function (swiper) - onReachEnd: function (swiper) - onSetTransition: function (swiper, duration) - onSetTranslate: function (swiper, translate) - onAutoplayStart: function (swiper) - onAutoplayStop: function (swiper), - onLazyImageLoad: function (swiper, slide, image) - onLazyImageReady: function (swiper, slide, image) - */ - - }; - var initialVirtualTranslate = params && params.virtualTranslate; - - params = params || {}; - var originalParams = {}; - for (var param in params) { - 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))) { - originalParams[param] = {}; - for (var deepParam in params[param]) { - originalParams[param][deepParam] = params[param][deepParam]; - } - } - else { - originalParams[param] = params[param]; - } - } - for (var def in defaults) { - if (typeof params[def] === 'undefined') { - params[def] = defaults[def]; - } - else if (typeof params[def] === 'object') { - for (var deepDef in defaults[def]) { - if (typeof params[def][deepDef] === 'undefined') { - params[def][deepDef] = defaults[def][deepDef]; - } - } - } - } - - // Swiper - var s = this; - - // Params - s.params = params; - s.originalParams = originalParams; - - // Classname - s.classNames = []; - /*========================= - Dom Library and plugins - ===========================*/ - if (typeof $ !== 'undefined' && typeof Dom7 !== 'undefined'){ - $ = Dom7; - } - if (typeof $ === 'undefined') { - if (typeof Dom7 === 'undefined') { - $ = window.Dom7 || window.Zepto || window.jQuery; - } - else { - $ = Dom7; - } - if (!$) return; - } - // Export it to Swiper instance - s.$ = $; - - /*========================= - Breakpoints - ===========================*/ - s.currentBreakpoint = undefined; - s.getActiveBreakpoint = function () { - //Get breakpoint for window width - if (!s.params.breakpoints) return false; - var breakpoint = false; - var points = [], point; - for ( point in s.params.breakpoints ) { - if (s.params.breakpoints.hasOwnProperty(point)) { - points.push(point); - } - } - points.sort(function (a, b) { - return parseInt(a, 10) > parseInt(b, 10); - }); - for (var i = 0; i < points.length; i++) { - point = points[i]; - if (point >= window.innerWidth && !breakpoint) { - breakpoint = point; - } - } - return breakpoint || 'max'; - }; - s.setBreakpoint = function () { - //Set breakpoint for window width and update parameters - var breakpoint = s.getActiveBreakpoint(); - if (breakpoint && s.currentBreakpoint !== breakpoint) { - var breakPointsParams = breakpoint in s.params.breakpoints ? s.params.breakpoints[breakpoint] : s.originalParams; - for ( var param in breakPointsParams ) { - s.params[param] = breakPointsParams[param]; - } - s.currentBreakpoint = breakpoint; - } - }; - // Set breakpoint on load - if (s.params.breakpoints) { - s.setBreakpoint(); - } - - /*========================= - Preparation - Define Container, Wrapper and Pagination - ===========================*/ - s.container = $(container); - if (s.container.length === 0) return; - if (s.container.length > 1) { - s.container.each(function () { - new Swiper(this, params); - }); - return; - } - - // Save instance in container HTML Element and in data - s.container[0].swiper = s; - s.container.data('swiper', s); - - s.classNames.push('swiper-container-' + s.params.direction); - - if (s.params.freeMode) { - s.classNames.push('swiper-container-free-mode'); - } - if (!s.support.flexbox) { - s.classNames.push('swiper-container-no-flexbox'); - s.params.slidesPerColumn = 1; - } - if (s.params.autoHeight) { - s.classNames.push('swiper-container-autoheight'); - } - // Enable slides progress when required - if (s.params.parallax || s.params.watchSlidesVisibility) { - s.params.watchSlidesProgress = true; - } - // Coverflow / 3D - if (['cube', 'coverflow'].indexOf(s.params.effect) >= 0) { - if (s.support.transforms3d) { - s.params.watchSlidesProgress = true; - s.classNames.push('swiper-container-3d'); - } - else { - s.params.effect = 'slide'; - } - } - if (s.params.effect !== 'slide') { - s.classNames.push('swiper-container-' + s.params.effect); - } - if (s.params.effect === 'cube') { - s.params.resistanceRatio = 0; - s.params.slidesPerView = 1; - s.params.slidesPerColumn = 1; - s.params.slidesPerGroup = 1; - s.params.centeredSlides = false; - s.params.spaceBetween = 0; - s.params.virtualTranslate = true; - s.params.setWrapperSize = false; - } - if (s.params.effect === 'fade') { - s.params.slidesPerView = 1; - s.params.slidesPerColumn = 1; - s.params.slidesPerGroup = 1; - s.params.watchSlidesProgress = true; - s.params.spaceBetween = 0; - if (typeof initialVirtualTranslate === 'undefined') { - s.params.virtualTranslate = true; - } - } - - // Grab Cursor - if (s.params.grabCursor && s.support.touch) { - s.params.grabCursor = false; - } - - // Wrapper - s.wrapper = s.container.children('.' + s.params.wrapperClass); - - // Pagination - if (s.params.pagination) { - s.paginationContainer = $(s.params.pagination); - if (s.params.paginationClickable) { - s.paginationContainer.addClass('swiper-pagination-clickable'); - } - } - - // Is Horizontal - function isH() { - return s.params.direction === 'horizontal'; - } - - // RTL - s.rtl = isH() && (s.container[0].dir.toLowerCase() === 'rtl' || s.container.css('direction') === 'rtl'); - if (s.rtl) { - s.classNames.push('swiper-container-rtl'); - } - - // Wrong RTL support - if (s.rtl) { - s.wrongRTL = s.wrapper.css('display') === '-webkit-box'; - } - - // Columns - if (s.params.slidesPerColumn > 1) { - s.classNames.push('swiper-container-multirow'); - } - - // Check for Android - if (s.device.android) { - s.classNames.push('swiper-container-android'); - } - - // Add classes - s.container.addClass(s.classNames.join(' ')); - - // Translate - s.translate = 0; - - // Progress - s.progress = 0; - - // Velocity - s.velocity = 0; - - /*========================= - Locks, unlocks - ===========================*/ - s.lockSwipeToNext = function () { - s.params.allowSwipeToNext = false; - }; - s.lockSwipeToPrev = function () { - s.params.allowSwipeToPrev = false; - }; - s.lockSwipes = function () { - s.params.allowSwipeToNext = s.params.allowSwipeToPrev = false; - }; - s.unlockSwipeToNext = function () { - s.params.allowSwipeToNext = true; - }; - s.unlockSwipeToPrev = function () { - s.params.allowSwipeToPrev = true; - }; - s.unlockSwipes = function () { - s.params.allowSwipeToNext = s.params.allowSwipeToPrev = true; - }; - - /*========================= - Round helper - ===========================*/ - function round(a) { - return Math.floor(a); - } - /*========================= - Set grab cursor - ===========================*/ - if (s.params.grabCursor) { - s.container[0].style.cursor = 'move'; - s.container[0].style.cursor = '-webkit-grab'; - s.container[0].style.cursor = '-moz-grab'; - s.container[0].style.cursor = 'grab'; - } - /*========================= - Update on Images Ready - ===========================*/ - s.imagesToLoad = []; - s.imagesLoaded = 0; - - s.loadImage = function (imgElement, src, srcset, checkForComplete, callback) { - var image; - function onReady () { - if (callback) callback(); - } - if (!imgElement.complete || !checkForComplete) { - if (src) { - image = new window.Image(); - image.onload = onReady; - image.onerror = onReady; - if (srcset) { - image.srcset = srcset; - } - if (src) { - image.src = src; - } - } else { - onReady(); - } - - } else {//image already loaded... - onReady(); - } - }; - s.preloadImages = function () { - s.imagesToLoad = s.container.find('img'); - function _onReady() { - if (typeof s === 'undefined' || s === null) return; - if (s.imagesLoaded !== undefined) s.imagesLoaded++; - if (s.imagesLoaded === s.imagesToLoad.length) { - if (s.params.updateOnImagesReady) s.update(); - s.emit('onImagesReady', s); - } - } - for (var i = 0; i < s.imagesToLoad.length; i++) { - 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); - } - }; - - /*========================= - Autoplay - ===========================*/ - s.autoplayTimeoutId = undefined; - s.autoplaying = false; - s.autoplayPaused = false; - function autoplay() { - s.autoplayTimeoutId = setTimeout(function () { - if (s.params.loop) { - s.fixLoop(); - s._slideNext(); - } - else { - if (!s.isEnd) { - s._slideNext(); - } - else { - if (!params.autoplayStopOnLast) { - s._slideTo(0); - } - else { - s.stopAutoplay(); - } - } - } - }, s.params.autoplay); - } - s.startAutoplay = function () { - if (typeof s.autoplayTimeoutId !== 'undefined') return false; - if (!s.params.autoplay) return false; - if (s.autoplaying) return false; - s.autoplaying = true; - s.emit('onAutoplayStart', s); - autoplay(); - }; - s.stopAutoplay = function (internal) { - if (!s.autoplayTimeoutId) return; - if (s.autoplayTimeoutId) clearTimeout(s.autoplayTimeoutId); - s.autoplaying = false; - s.autoplayTimeoutId = undefined; - s.emit('onAutoplayStop', s); - }; - s.pauseAutoplay = function (speed) { - if (s.autoplayPaused) return; - if (s.autoplayTimeoutId) clearTimeout(s.autoplayTimeoutId); - s.autoplayPaused = true; - if (speed === 0) { - s.autoplayPaused = false; - autoplay(); - } - else { - s.wrapper.transitionEnd(function () { - if (!s) return; - s.autoplayPaused = false; - if (!s.autoplaying) { - s.stopAutoplay(); - } - else { - autoplay(); - } - }); - } - }; - /*========================= - Min/Max Translate - ===========================*/ - s.minTranslate = function () { - return (-s.snapGrid[0]); - }; - s.maxTranslate = function () { - return (-s.snapGrid[s.snapGrid.length - 1]); - }; - /*========================= - Slider/slides sizes - ===========================*/ - s.updateAutoHeight = function () { - // Update Height - var newHeight = s.slides.eq(s.activeIndex)[0].offsetHeight; - if (newHeight) s.wrapper.css('height', s.slides.eq(s.activeIndex)[0].offsetHeight + 'px'); - }; - s.updateContainerSize = function () { - var width, height; - if (typeof s.params.width !== 'undefined') { - width = s.params.width; - } - else { - width = s.container[0].clientWidth; - } - if (typeof s.params.height !== 'undefined') { - height = s.params.height; - } - else { - height = s.container[0].clientHeight; - } - if (width === 0 && isH() || height === 0 && !isH()) { - return; - } - - //Subtract paddings - width = width - parseInt(s.container.css('padding-left'), 10) - parseInt(s.container.css('padding-right'), 10); - height = height - parseInt(s.container.css('padding-top'), 10) - parseInt(s.container.css('padding-bottom'), 10); - - // Store values - s.width = width; - s.height = height; - s.size = isH() ? s.width : s.height; - }; - - s.updateSlidesSize = function () { - s.slides = s.wrapper.children('.' + s.params.slideClass); - s.snapGrid = []; - s.slidesGrid = []; - s.slidesSizesGrid = []; - - var spaceBetween = s.params.spaceBetween, - slidePosition = -s.params.slidesOffsetBefore, - i, - prevSlideSize = 0, - index = 0; - if (typeof spaceBetween === 'string' && spaceBetween.indexOf('%') >= 0) { - spaceBetween = parseFloat(spaceBetween.replace('%', '')) / 100 * s.size; - } - - s.virtualSize = -spaceBetween; - // reset margins - if (s.rtl) s.slides.css({marginLeft: '', marginTop: ''}); - else s.slides.css({marginRight: '', marginBottom: ''}); - - var slidesNumberEvenToRows; - if (s.params.slidesPerColumn > 1) { - if (Math.floor(s.slides.length / s.params.slidesPerColumn) === s.slides.length / s.params.slidesPerColumn) { - slidesNumberEvenToRows = s.slides.length; - } - else { - slidesNumberEvenToRows = Math.ceil(s.slides.length / s.params.slidesPerColumn) * s.params.slidesPerColumn; - } - if (s.params.slidesPerView !== 'auto' && s.params.slidesPerColumnFill === 'row') { - slidesNumberEvenToRows = Math.max(slidesNumberEvenToRows, s.params.slidesPerView * s.params.slidesPerColumn); - } - } - - // Calc slides - var slideSize; - var slidesPerColumn = s.params.slidesPerColumn; - var slidesPerRow = slidesNumberEvenToRows / slidesPerColumn; - var numFullColumns = slidesPerRow - (s.params.slidesPerColumn * slidesPerRow - s.slides.length); - for (i = 0; i < s.slides.length; i++) { - slideSize = 0; - var slide = s.slides.eq(i); - if (s.params.slidesPerColumn > 1) { - // Set slides order - var newSlideOrderIndex; - var column, row; - if (s.params.slidesPerColumnFill === 'column') { - column = Math.floor(i / slidesPerColumn); - row = i - column * slidesPerColumn; - if (column > numFullColumns || (column === numFullColumns && row === slidesPerColumn-1)) { - if (++row >= slidesPerColumn) { - row = 0; - column++; - } - } - newSlideOrderIndex = column + row * slidesNumberEvenToRows / slidesPerColumn; - slide - .css({ - '-webkit-box-ordinal-group': newSlideOrderIndex, - '-moz-box-ordinal-group': newSlideOrderIndex, - '-ms-flex-order': newSlideOrderIndex, - '-webkit-order': newSlideOrderIndex, - 'order': newSlideOrderIndex - }); - } - else { - row = Math.floor(i / slidesPerRow); - column = i - row * slidesPerRow; - } - slide - .css({ - 'margin-top': (row !== 0 && s.params.spaceBetween) && (s.params.spaceBetween + 'px') - }) - .attr('data-swiper-column', column) - .attr('data-swiper-row', row); - - } - if (slide.css('display') === 'none') continue; - if (s.params.slidesPerView === 'auto') { - slideSize = isH() ? slide.outerWidth(true) : slide.outerHeight(true); - if (s.params.roundLengths) slideSize = round(slideSize); - } - else { - slideSize = (s.size - (s.params.slidesPerView - 1) * spaceBetween) / s.params.slidesPerView; - if (s.params.roundLengths) slideSize = round(slideSize); - - if (isH()) { - s.slides[i].style.width = slideSize + 'px'; - } - else { - s.slides[i].style.height = slideSize + 'px'; - } - } - s.slides[i].swiperSlideSize = slideSize; - s.slidesSizesGrid.push(slideSize); - - - if (s.params.centeredSlides) { - slidePosition = slidePosition + slideSize / 2 + prevSlideSize / 2 + spaceBetween; - if (i === 0) slidePosition = slidePosition - s.size / 2 - spaceBetween; - if (Math.abs(slidePosition) < 1 / 1000) slidePosition = 0; - if ((index) % s.params.slidesPerGroup === 0) s.snapGrid.push(slidePosition); - s.slidesGrid.push(slidePosition); - } - else { - if ((index) % s.params.slidesPerGroup === 0) s.snapGrid.push(slidePosition); - s.slidesGrid.push(slidePosition); - slidePosition = slidePosition + slideSize + spaceBetween; - } - - s.virtualSize += slideSize + spaceBetween; - - prevSlideSize = slideSize; - - index ++; - } - s.virtualSize = Math.max(s.virtualSize, s.size) + s.params.slidesOffsetAfter; - var newSlidesGrid; - - if ( - s.rtl && s.wrongRTL && (s.params.effect === 'slide' || s.params.effect === 'coverflow')) { - s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'}); - } - if (!s.support.flexbox || s.params.setWrapperSize) { - if (isH()) s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'}); - else s.wrapper.css({height: s.virtualSize + s.params.spaceBetween + 'px'}); - } - - if (s.params.slidesPerColumn > 1) { - s.virtualSize = (slideSize + s.params.spaceBetween) * slidesNumberEvenToRows; - s.virtualSize = Math.ceil(s.virtualSize / s.params.slidesPerColumn) - s.params.spaceBetween; - s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'}); - if (s.params.centeredSlides) { - newSlidesGrid = []; - for (i = 0; i < s.snapGrid.length; i++) { - if (s.snapGrid[i] < s.virtualSize + s.snapGrid[0]) newSlidesGrid.push(s.snapGrid[i]); - } - s.snapGrid = newSlidesGrid; - } - } - - // Remove last grid elements depending on width - if (!s.params.centeredSlides) { - newSlidesGrid = []; - for (i = 0; i < s.snapGrid.length; i++) { - if (s.snapGrid[i] <= s.virtualSize - s.size) { - newSlidesGrid.push(s.snapGrid[i]); - } - } - s.snapGrid = newSlidesGrid; - if (Math.floor(s.virtualSize - s.size) > Math.floor(s.snapGrid[s.snapGrid.length - 1])) { - s.snapGrid.push(s.virtualSize - s.size); - } - } - if (s.snapGrid.length === 0) s.snapGrid = [0]; - - if (s.params.spaceBetween !== 0) { - if (isH()) { - if (s.rtl) s.slides.css({marginLeft: spaceBetween + 'px'}); - else s.slides.css({marginRight: spaceBetween + 'px'}); - } - else s.slides.css({marginBottom: spaceBetween + 'px'}); - } - if (s.params.watchSlidesProgress) { - s.updateSlidesOffset(); - } - }; - s.updateSlidesOffset = function () { - for (var i = 0; i < s.slides.length; i++) { - s.slides[i].swiperSlideOffset = isH() ? s.slides[i].offsetLeft : s.slides[i].offsetTop; - } - }; - - /*========================= - Slider/slides progress - ===========================*/ - s.updateSlidesProgress = function (translate) { - if (typeof translate === 'undefined') { - translate = s.translate || 0; - } - if (s.slides.length === 0) return; - if (typeof s.slides[0].swiperSlideOffset === 'undefined') s.updateSlidesOffset(); - - var offsetCenter = -translate; - if (s.rtl) offsetCenter = translate; - - // Visible Slides - s.slides.removeClass(s.params.slideVisibleClass); - for (var i = 0; i < s.slides.length; i++) { - var slide = s.slides[i]; - var slideProgress = (offsetCenter - slide.swiperSlideOffset) / (slide.swiperSlideSize + s.params.spaceBetween); - if (s.params.watchSlidesVisibility) { - var slideBefore = -(offsetCenter - slide.swiperSlideOffset); - var slideAfter = slideBefore + s.slidesSizesGrid[i]; - var isVisible = - (slideBefore >= 0 && slideBefore < s.size) || - (slideAfter > 0 && slideAfter <= s.size) || - (slideBefore <= 0 && slideAfter >= s.size); - if (isVisible) { - s.slides.eq(i).addClass(s.params.slideVisibleClass); - } - } - slide.progress = s.rtl ? -slideProgress : slideProgress; - } - }; - s.updateProgress = function (translate) { - if (typeof translate === 'undefined') { - translate = s.translate || 0; - } - var translatesDiff = s.maxTranslate() - s.minTranslate(); - var wasBeginning = s.isBeginning; - var wasEnd = s.isEnd; - if (translatesDiff === 0) { - s.progress = 0; - s.isBeginning = s.isEnd = true; - } - else { - s.progress = (translate - s.minTranslate()) / (translatesDiff); - s.isBeginning = s.progress <= 0; - s.isEnd = s.progress >= 1; - } - if (s.isBeginning && !wasBeginning) s.emit('onReachBeginning', s); - if (s.isEnd && !wasEnd) s.emit('onReachEnd', s); - - if (s.params.watchSlidesProgress) s.updateSlidesProgress(translate); - s.emit('onProgress', s, s.progress); - }; - s.updateActiveIndex = function () { - var translate = s.rtl ? s.translate : -s.translate; - var newActiveIndex, i, snapIndex; - for (i = 0; i < s.slidesGrid.length; i ++) { - if (typeof s.slidesGrid[i + 1] !== 'undefined') { - if (translate >= s.slidesGrid[i] && translate < s.slidesGrid[i + 1] - (s.slidesGrid[i + 1] - s.slidesGrid[i]) / 2) { - newActiveIndex = i; - } - else if (translate >= s.slidesGrid[i] && translate < s.slidesGrid[i + 1]) { - newActiveIndex = i + 1; - } - } - else { - if (translate >= s.slidesGrid[i]) { - newActiveIndex = i; - } - } - } - // Normalize slideIndex - if (newActiveIndex < 0 || typeof newActiveIndex === 'undefined') newActiveIndex = 0; - // for (i = 0; i < s.slidesGrid.length; i++) { - // if (- translate >= s.slidesGrid[i]) { - // newActiveIndex = i; - // } - // } - snapIndex = Math.floor(newActiveIndex / s.params.slidesPerGroup); - if (snapIndex >= s.snapGrid.length) snapIndex = s.snapGrid.length - 1; - - if (newActiveIndex === s.activeIndex) { - return; - } - s.snapIndex = snapIndex; - s.previousIndex = s.activeIndex; - s.activeIndex = newActiveIndex; - s.updateClasses(); - }; - - /*========================= - Classes - ===========================*/ - s.updateClasses = function () { - s.slides.removeClass(s.params.slideActiveClass + ' ' + s.params.slideNextClass + ' ' + s.params.slidePrevClass); - var activeSlide = s.slides.eq(s.activeIndex); - // Active classes - activeSlide.addClass(s.params.slideActiveClass); - activeSlide.next('.' + s.params.slideClass).addClass(s.params.slideNextClass); - activeSlide.prev('.' + s.params.slideClass).addClass(s.params.slidePrevClass); - - // Pagination - if (s.bullets && s.bullets.length > 0) { - s.bullets.removeClass(s.params.bulletActiveClass); - var bulletIndex; - if (s.params.loop) { - bulletIndex = Math.ceil(s.activeIndex - s.loopedSlides)/s.params.slidesPerGroup; - if (bulletIndex > s.slides.length - 1 - s.loopedSlides * 2) { - bulletIndex = bulletIndex - (s.slides.length - s.loopedSlides * 2); - } - if (bulletIndex > s.bullets.length - 1) bulletIndex = bulletIndex - s.bullets.length; - } - else { - if (typeof s.snapIndex !== 'undefined') { - bulletIndex = s.snapIndex; - } - else { - bulletIndex = s.activeIndex || 0; - } - } - if (s.paginationContainer.length > 1) { - s.bullets.each(function () { - if ($(this).index() === bulletIndex) $(this).addClass(s.params.bulletActiveClass); - }); - } - else { - s.bullets.eq(bulletIndex).addClass(s.params.bulletActiveClass); - } - } - - // Next/active buttons - if (!s.params.loop) { - if (s.params.prevButton) { - if (s.isBeginning) { - $(s.params.prevButton).addClass(s.params.buttonDisabledClass); - if (s.params.a11y && s.a11y) s.a11y.disable($(s.params.prevButton)); - } - else { - $(s.params.prevButton).removeClass(s.params.buttonDisabledClass); - if (s.params.a11y && s.a11y) s.a11y.enable($(s.params.prevButton)); - } - } - if (s.params.nextButton) { - if (s.isEnd) { - $(s.params.nextButton).addClass(s.params.buttonDisabledClass); - if (s.params.a11y && s.a11y) s.a11y.disable($(s.params.nextButton)); - } - else { - $(s.params.nextButton).removeClass(s.params.buttonDisabledClass); - if (s.params.a11y && s.a11y) s.a11y.enable($(s.params.nextButton)); - } - } - } - }; - - /*========================= - Pagination - ===========================*/ - s.updatePagination = function () { - if (!s.params.pagination) return; - if (s.paginationContainer && s.paginationContainer.length > 0) { - var bulletsHTML = ''; - var numberOfBullets = s.params.loop ? Math.ceil((s.slides.length - s.loopedSlides * 2) / s.params.slidesPerGroup) : s.snapGrid.length; - for (var i = 0; i < numberOfBullets; i++) { - if (s.params.paginationBulletRender) { - bulletsHTML += s.params.paginationBulletRender(i, s.params.bulletClass); - } - else { - bulletsHTML += '<' + s.params.paginationElement+' class="' + s.params.bulletClass + '">'; - } - } - s.paginationContainer.html(bulletsHTML); - s.bullets = s.paginationContainer.find('.' + s.params.bulletClass); - if (s.params.paginationClickable && s.params.a11y && s.a11y) { - s.a11y.initPagination(); - } - } - }; - /*========================= - Common update method - ===========================*/ - s.update = function (updateTranslate) { - s.updateContainerSize(); - s.updateSlidesSize(); - s.updateProgress(); - s.updatePagination(); - s.updateClasses(); - if (s.params.scrollbar && s.scrollbar) { - s.scrollbar.set(); - } - function forceSetTranslate() { - newTranslate = Math.min(Math.max(s.translate, s.maxTranslate()), s.minTranslate()); - s.setWrapperTranslate(newTranslate); - s.updateActiveIndex(); - s.updateClasses(); - } - if (updateTranslate) { - var translated, newTranslate; - if (s.controller && s.controller.spline) { - s.controller.spline = undefined; - } - if (s.params.freeMode) { - forceSetTranslate(); - if (s.params.autoHeight) { - s.updateAutoHeight(); - } - } - else { - if ((s.params.slidesPerView === 'auto' || s.params.slidesPerView > 1) && s.isEnd && !s.params.centeredSlides) { - translated = s.slideTo(s.slides.length - 1, 0, false, true); - } - else { - translated = s.slideTo(s.activeIndex, 0, false, true); - } - if (!translated) { - forceSetTranslate(); - } - } - } - else if (s.params.autoHeight) { - s.updateAutoHeight(); - } - }; - - /*========================= - Resize Handler - ===========================*/ - s.onResize = function (forceUpdatePagination) { - //Breakpoints - if (s.params.breakpoints) { - s.setBreakpoint(); - } - - // Disable locks on resize - var allowSwipeToPrev = s.params.allowSwipeToPrev; - var allowSwipeToNext = s.params.allowSwipeToNext; - s.params.allowSwipeToPrev = s.params.allowSwipeToNext = true; - - s.updateContainerSize(); - s.updateSlidesSize(); - if (s.params.slidesPerView === 'auto' || s.params.freeMode || forceUpdatePagination) s.updatePagination(); - if (s.params.scrollbar && s.scrollbar) { - s.scrollbar.set(); - } - if (s.controller && s.controller.spline) { - s.controller.spline = undefined; - } - if (s.params.freeMode) { - var newTranslate = Math.min(Math.max(s.translate, s.maxTranslate()), s.minTranslate()); - s.setWrapperTranslate(newTranslate); - s.updateActiveIndex(); - s.updateClasses(); - - if (s.params.autoHeight) { - s.updateAutoHeight(); - } - } - else { - s.updateClasses(); - if ((s.params.slidesPerView === 'auto' || s.params.slidesPerView > 1) && s.isEnd && !s.params.centeredSlides) { - s.slideTo(s.slides.length - 1, 0, false, true); - } - else { - s.slideTo(s.activeIndex, 0, false, true); - } - } - // Return locks after resize - s.params.allowSwipeToPrev = allowSwipeToPrev; - s.params.allowSwipeToNext = allowSwipeToNext; - }; - - /*========================= - Events - ===========================*/ - - //Define Touch Events - var desktopEvents = ['mousedown', 'mousemove', 'mouseup']; - if (window.navigator.pointerEnabled) desktopEvents = ['pointerdown', 'pointermove', 'pointerup']; - else if (window.navigator.msPointerEnabled) desktopEvents = ['MSPointerDown', 'MSPointerMove', 'MSPointerUp']; - s.touchEvents = { - start : s.support.touch || !s.params.simulateTouch ? 'touchstart' : desktopEvents[0], - move : s.support.touch || !s.params.simulateTouch ? 'touchmove' : desktopEvents[1], - end : s.support.touch || !s.params.simulateTouch ? 'touchend' : desktopEvents[2] - }; - - - // WP8 Touch Events Fix - if (window.navigator.pointerEnabled || window.navigator.msPointerEnabled) { - (s.params.touchEventsTarget === 'container' ? s.container : s.wrapper).addClass('swiper-wp8-' + s.params.direction); - } - - // Attach/detach events - s.initEvents = function (detach) { - var actionDom = detach ? 'off' : 'on'; - var action = detach ? 'removeEventListener' : 'addEventListener'; - var touchEventsTarget = s.params.touchEventsTarget === 'container' ? s.container[0] : s.wrapper[0]; - var target = s.support.touch ? touchEventsTarget : document; - - var moveCapture = s.params.nested ? true : false; - - //Touch Events - if (s.browser.ie) { - touchEventsTarget[action](s.touchEvents.start, s.onTouchStart, false); - target[action](s.touchEvents.move, s.onTouchMove, moveCapture); - target[action](s.touchEvents.end, s.onTouchEnd, false); - } - else { - if (s.support.touch) { - touchEventsTarget[action](s.touchEvents.start, s.onTouchStart, false); - touchEventsTarget[action](s.touchEvents.move, s.onTouchMove, moveCapture); - touchEventsTarget[action](s.touchEvents.end, s.onTouchEnd, false); - } - if (params.simulateTouch && !s.device.ios && !s.device.android) { - touchEventsTarget[action]('mousedown', s.onTouchStart, false); - document[action]('mousemove', s.onTouchMove, moveCapture); - document[action]('mouseup', s.onTouchEnd, false); - } - } - window[action]('resize', s.onResize); - - // Next, Prev, Index - if (s.params.nextButton) { - $(s.params.nextButton)[actionDom]('click', s.onClickNext); - if (s.params.a11y && s.a11y) $(s.params.nextButton)[actionDom]('keydown', s.a11y.onEnterKey); - } - if (s.params.prevButton) { - $(s.params.prevButton)[actionDom]('click', s.onClickPrev); - if (s.params.a11y && s.a11y) $(s.params.prevButton)[actionDom]('keydown', s.a11y.onEnterKey); - } - if (s.params.pagination && s.params.paginationClickable) { - $(s.paginationContainer)[actionDom]('click', '.' + s.params.bulletClass, s.onClickIndex); - if (s.params.a11y && s.a11y) $(s.paginationContainer)[actionDom]('keydown', '.' + s.params.bulletClass, s.a11y.onEnterKey); - } - - // Prevent Links Clicks - if (s.params.preventClicks || s.params.preventClicksPropagation) touchEventsTarget[action]('click', s.preventClicks, true); - }; - s.attachEvents = function (detach) { - s.initEvents(); - }; - s.detachEvents = function () { - s.initEvents(true); - }; - - /*========================= - Handle Clicks - ===========================*/ - // Prevent Clicks - s.allowClick = true; - s.preventClicks = function (e) { - if (!s.allowClick) { - if (s.params.preventClicks) e.preventDefault(); - if (s.params.preventClicksPropagation && s.animating) { - e.stopPropagation(); - e.stopImmediatePropagation(); - } - } - }; - // Clicks - s.onClickNext = function (e) { - e.preventDefault(); - if (s.isEnd && !s.params.loop) return; - s.slideNext(); - }; - s.onClickPrev = function (e) { - e.preventDefault(); - if (s.isBeginning && !s.params.loop) return; - s.slidePrev(); - }; - s.onClickIndex = function (e) { - e.preventDefault(); - var index = $(this).index() * s.params.slidesPerGroup; - if (s.params.loop) index = index + s.loopedSlides; - s.slideTo(index); - }; - - /*========================= - Handle Touches - ===========================*/ - function findElementInEvent(e, selector) { - var el = $(e.target); - if (!el.is(selector)) { - if (typeof selector === 'string') { - el = el.parents(selector); - } - else if (selector.nodeType) { - var found; - el.parents().each(function (index, _el) { - if (_el === selector) found = selector; - }); - if (!found) return undefined; - else return selector; - } - } - if (el.length === 0) { - return undefined; - } - return el[0]; - } - s.updateClickedSlide = function (e) { - var slide = findElementInEvent(e, '.' + s.params.slideClass); - var slideFound = false; - if (slide) { - for (var i = 0; i < s.slides.length; i++) { - if (s.slides[i] === slide) slideFound = true; - } - } - - if (slide && slideFound) { - s.clickedSlide = slide; - s.clickedIndex = $(slide).index(); - } - else { - s.clickedSlide = undefined; - s.clickedIndex = undefined; - return; - } - if (s.params.slideToClickedSlide && s.clickedIndex !== undefined && s.clickedIndex !== s.activeIndex) { - var slideToIndex = s.clickedIndex, - realIndex, - duplicatedSlides; - if (s.params.loop) { - if (s.animating) return; - realIndex = $(s.clickedSlide).attr('data-swiper-slide-index'); - if (s.params.centeredSlides) { - if ((slideToIndex < s.loopedSlides - s.params.slidesPerView/2) || (slideToIndex > s.slides.length - s.loopedSlides + s.params.slidesPerView/2)) { - s.fixLoop(); - slideToIndex = s.wrapper.children('.' + s.params.slideClass + '[data-swiper-slide-index="' + realIndex + '"]:not(.swiper-slide-duplicate)').eq(0).index(); - setTimeout(function () { - s.slideTo(slideToIndex); - }, 0); - } - else { - s.slideTo(slideToIndex); - } - } - else { - if (slideToIndex > s.slides.length - s.params.slidesPerView) { - s.fixLoop(); - slideToIndex = s.wrapper.children('.' + s.params.slideClass + '[data-swiper-slide-index="' + realIndex + '"]:not(.swiper-slide-duplicate)').eq(0).index(); - setTimeout(function () { - s.slideTo(slideToIndex); - }, 0); - } - else { - s.slideTo(slideToIndex); - } - } - } - else { - s.slideTo(slideToIndex); - } - } - }; - - var isTouched, - isMoved, - allowTouchCallbacks, - touchStartTime, - isScrolling, - currentTranslate, - startTranslate, - allowThresholdMove, - // Form elements to match - formElements = 'input, select, textarea, button', - // Last click time - lastClickTime = Date.now(), clickTimeout, - //Velocities - velocities = [], - allowMomentumBounce; - - // Animating Flag - s.animating = false; - - // Touches information - s.touches = { - startX: 0, - startY: 0, - currentX: 0, - currentY: 0, - diff: 0 - }; - - // Touch handlers - var isTouchEvent, startMoving; - s.onTouchStart = function (e) { - if (e.originalEvent) e = e.originalEvent; - isTouchEvent = e.type === 'touchstart'; - if (!isTouchEvent && 'which' in e && e.which === 3) return; - if (s.params.noSwiping && findElementInEvent(e, '.' + s.params.noSwipingClass)) { - s.allowClick = true; - return; - } - if (s.params.swipeHandler) { - if (!findElementInEvent(e, s.params.swipeHandler)) return; - } - - var startX = s.touches.currentX = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.pageX; - var startY = s.touches.currentY = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.pageY; - - // Do NOT start if iOS edge swipe is detected. Otherwise iOS app (UIWebView) cannot swipe-to-go-back anymore - if(s.device.ios && s.params.iOSEdgeSwipeDetection && startX <= s.params.iOSEdgeSwipeThreshold) { - return; - } - - isTouched = true; - isMoved = false; - allowTouchCallbacks = true; - isScrolling = undefined; - startMoving = undefined; - s.touches.startX = startX; - s.touches.startY = startY; - touchStartTime = Date.now(); - s.allowClick = true; - s.updateContainerSize(); - s.swipeDirection = undefined; - if (s.params.threshold > 0) allowThresholdMove = false; - if (e.type !== 'touchstart') { - var preventDefault = true; - if ($(e.target).is(formElements)) preventDefault = false; - if (document.activeElement && $(document.activeElement).is(formElements)) { - document.activeElement.blur(); - } - if (preventDefault) { - e.preventDefault(); - } - } - s.emit('onTouchStart', s, e); - }; - - s.onTouchMove = function (e) { - if (e.originalEvent) e = e.originalEvent; - if (isTouchEvent && e.type === 'mousemove') return; - if (e.preventedByNestedSwiper) return; - if (s.params.onlyExternal) { - // isMoved = true; - s.allowClick = false; - if (isTouched) { - s.touches.startX = s.touches.currentX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX; - s.touches.startY = s.touches.currentY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY; - touchStartTime = Date.now(); - } - return; - } - if (isTouchEvent && document.activeElement) { - if (e.target === document.activeElement && $(e.target).is(formElements)) { - isMoved = true; - s.allowClick = false; - return; - } - } - if (allowTouchCallbacks) { - s.emit('onTouchMove', s, e); - } - if (e.targetTouches && e.targetTouches.length > 1) return; - - s.touches.currentX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX; - s.touches.currentY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY; - - if (typeof isScrolling === 'undefined') { - var touchAngle = Math.atan2(Math.abs(s.touches.currentY - s.touches.startY), Math.abs(s.touches.currentX - s.touches.startX)) * 180 / Math.PI; - isScrolling = isH() ? touchAngle > s.params.touchAngle : (90 - touchAngle > s.params.touchAngle); - } - if (isScrolling) { - s.emit('onTouchMoveOpposite', s, e); - } - if (typeof startMoving === 'undefined' && s.browser.ieTouch) { - if (s.touches.currentX !== s.touches.startX || s.touches.currentY !== s.touches.startY) { - startMoving = true; - } - } - if (!isTouched) return; - if (isScrolling) { - isTouched = false; - return; - } - if (!startMoving && s.browser.ieTouch) { - return; - } - s.allowClick = false; - s.emit('onSliderMove', s, e); - e.preventDefault(); - if (s.params.touchMoveStopPropagation && !s.params.nested) { - e.stopPropagation(); - } - - if (!isMoved) { - if (params.loop) { - s.fixLoop(); - } - startTranslate = s.getWrapperTranslate(); - s.setWrapperTransition(0); - if (s.animating) { - s.wrapper.trigger('webkitTransitionEnd transitionend oTransitionEnd MSTransitionEnd msTransitionEnd'); - } - if (s.params.autoplay && s.autoplaying) { - if (s.params.autoplayDisableOnInteraction) { - s.stopAutoplay(); - } - else { - s.pauseAutoplay(); - } - } - allowMomentumBounce = false; - //Grab Cursor - if (s.params.grabCursor) { - s.container[0].style.cursor = 'move'; - s.container[0].style.cursor = '-webkit-grabbing'; - s.container[0].style.cursor = '-moz-grabbin'; - s.container[0].style.cursor = 'grabbing'; - } - } - isMoved = true; - - var diff = s.touches.diff = isH() ? s.touches.currentX - s.touches.startX : s.touches.currentY - s.touches.startY; - - diff = diff * s.params.touchRatio; - if (s.rtl) diff = -diff; - - s.swipeDirection = diff > 0 ? 'prev' : 'next'; - currentTranslate = diff + startTranslate; - - var disableParentSwiper = true; - if ((diff > 0 && currentTranslate > s.minTranslate())) { - disableParentSwiper = false; - if (s.params.resistance) currentTranslate = s.minTranslate() - 1 + Math.pow(-s.minTranslate() + startTranslate + diff, s.params.resistanceRatio); - } - else if (diff < 0 && currentTranslate < s.maxTranslate()) { - disableParentSwiper = false; - if (s.params.resistance) currentTranslate = s.maxTranslate() + 1 - Math.pow(s.maxTranslate() - startTranslate - diff, s.params.resistanceRatio); - } - - if (disableParentSwiper) { - e.preventedByNestedSwiper = true; - } - - // Directions locks - if (!s.params.allowSwipeToNext && s.swipeDirection === 'next' && currentTranslate < startTranslate) { - currentTranslate = startTranslate; - } - if (!s.params.allowSwipeToPrev && s.swipeDirection === 'prev' && currentTranslate > startTranslate) { - currentTranslate = startTranslate; - } - - if (!s.params.followFinger) return; - - // Threshold - if (s.params.threshold > 0) { - if (Math.abs(diff) > s.params.threshold || allowThresholdMove) { - if (!allowThresholdMove) { - allowThresholdMove = true; - s.touches.startX = s.touches.currentX; - s.touches.startY = s.touches.currentY; - currentTranslate = startTranslate; - s.touches.diff = isH() ? s.touches.currentX - s.touches.startX : s.touches.currentY - s.touches.startY; - return; - } - } - else { - currentTranslate = startTranslate; - return; - } - } - // Update active index in free mode - if (s.params.freeMode || s.params.watchSlidesProgress) { - s.updateActiveIndex(); - } - if (s.params.freeMode) { - //Velocity - if (velocities.length === 0) { - velocities.push({ - position: s.touches[isH() ? 'startX' : 'startY'], - time: touchStartTime - }); - } - velocities.push({ - position: s.touches[isH() ? 'currentX' : 'currentY'], - time: (new window.Date()).getTime() - }); - } - // Update progress - s.updateProgress(currentTranslate); - // Update translate - s.setWrapperTranslate(currentTranslate); - }; - s.onTouchEnd = function (e) { - if (e.originalEvent) e = e.originalEvent; - if (allowTouchCallbacks) { - s.emit('onTouchEnd', s, e); - } - allowTouchCallbacks = false; - if (!isTouched) return; - //Return Grab Cursor - if (s.params.grabCursor && isMoved && isTouched) { - s.container[0].style.cursor = 'move'; - s.container[0].style.cursor = '-webkit-grab'; - s.container[0].style.cursor = '-moz-grab'; - s.container[0].style.cursor = 'grab'; - } - - // Time diff - var touchEndTime = Date.now(); - var timeDiff = touchEndTime - touchStartTime; - - // Tap, doubleTap, Click - if (s.allowClick) { - s.updateClickedSlide(e); - s.emit('onTap', s, e); - if (timeDiff < 300 && (touchEndTime - lastClickTime) > 300) { - if (clickTimeout) clearTimeout(clickTimeout); - clickTimeout = setTimeout(function () { - if (!s) return; - if (s.params.paginationHide && s.paginationContainer.length > 0 && !$(e.target).hasClass(s.params.bulletClass)) { - s.paginationContainer.toggleClass(s.params.paginationHiddenClass); - } - s.emit('onClick', s, e); - }, 300); - - } - if (timeDiff < 300 && (touchEndTime - lastClickTime) < 300) { - if (clickTimeout) clearTimeout(clickTimeout); - s.emit('onDoubleTap', s, e); - } - } - - lastClickTime = Date.now(); - setTimeout(function () { - if (s) s.allowClick = true; - }, 0); - - if (!isTouched || !isMoved || !s.swipeDirection || s.touches.diff === 0 || currentTranslate === startTranslate) { - isTouched = isMoved = false; - return; - } - isTouched = isMoved = false; - - var currentPos; - if (s.params.followFinger) { - currentPos = s.rtl ? s.translate : -s.translate; - } - else { - currentPos = -currentTranslate; - } - if (s.params.freeMode) { - if (currentPos < -s.minTranslate()) { - s.slideTo(s.activeIndex); - return; - } - else if (currentPos > -s.maxTranslate()) { - if (s.slides.length < s.snapGrid.length) { - s.slideTo(s.snapGrid.length - 1); - } - else { - s.slideTo(s.slides.length - 1); - } - return; - } - - if (s.params.freeModeMomentum) { - if (velocities.length > 1) { - var lastMoveEvent = velocities.pop(), velocityEvent = velocities.pop(); - - var distance = lastMoveEvent.position - velocityEvent.position; - var time = lastMoveEvent.time - velocityEvent.time; - s.velocity = distance / time; - s.velocity = s.velocity / 2; - if (Math.abs(s.velocity) < s.params.freeModeMinimumVelocity) { - s.velocity = 0; - } - // this implies that the user stopped moving a finger then released. - // There would be no events with distance zero, so the last event is stale. - if (time > 150 || (new window.Date().getTime() - lastMoveEvent.time) > 300) { - s.velocity = 0; - } - } else { - s.velocity = 0; - } - - velocities.length = 0; - var momentumDuration = 1000 * s.params.freeModeMomentumRatio; - var momentumDistance = s.velocity * momentumDuration; - - var newPosition = s.translate + momentumDistance; - if (s.rtl) newPosition = - newPosition; - var doBounce = false; - var afterBouncePosition; - var bounceAmount = Math.abs(s.velocity) * 20 * s.params.freeModeMomentumBounceRatio; - if (newPosition < s.maxTranslate()) { - if (s.params.freeModeMomentumBounce) { - if (newPosition + s.maxTranslate() < -bounceAmount) { - newPosition = s.maxTranslate() - bounceAmount; - } - afterBouncePosition = s.maxTranslate(); - doBounce = true; - allowMomentumBounce = true; - } - else { - newPosition = s.maxTranslate(); - } - } - else if (newPosition > s.minTranslate()) { - if (s.params.freeModeMomentumBounce) { - if (newPosition - s.minTranslate() > bounceAmount) { - newPosition = s.minTranslate() + bounceAmount; - } - afterBouncePosition = s.minTranslate(); - doBounce = true; - allowMomentumBounce = true; - } - else { - newPosition = s.minTranslate(); - } - } - else if (s.params.freeModeSticky) { - var j = 0, - nextSlide; - for (j = 0; j < s.snapGrid.length; j += 1) { - if (s.snapGrid[j] > -newPosition) { - nextSlide = j; - break; - } - - } - if (Math.abs(s.snapGrid[nextSlide] - newPosition) < Math.abs(s.snapGrid[nextSlide - 1] - newPosition) || s.swipeDirection === 'next') { - newPosition = s.snapGrid[nextSlide]; - } else { - newPosition = s.snapGrid[nextSlide - 1]; - } - if (!s.rtl) newPosition = - newPosition; - } - //Fix duration - if (s.velocity !== 0) { - if (s.rtl) { - momentumDuration = Math.abs((-newPosition - s.translate) / s.velocity); - } - else { - momentumDuration = Math.abs((newPosition - s.translate) / s.velocity); - } - } - else if (s.params.freeModeSticky) { - s.slideReset(); - return; - } - - if (s.params.freeModeMomentumBounce && doBounce) { - s.updateProgress(afterBouncePosition); - s.setWrapperTransition(momentumDuration); - s.setWrapperTranslate(newPosition); - s.onTransitionStart(); - s.animating = true; - s.wrapper.transitionEnd(function () { - if (!s || !allowMomentumBounce) return; - s.emit('onMomentumBounce', s); - - s.setWrapperTransition(s.params.speed); - s.setWrapperTranslate(afterBouncePosition); - s.wrapper.transitionEnd(function () { - if (!s) return; - s.onTransitionEnd(); - }); - }); - } else if (s.velocity) { - s.updateProgress(newPosition); - s.setWrapperTransition(momentumDuration); - s.setWrapperTranslate(newPosition); - s.onTransitionStart(); - if (!s.animating) { - s.animating = true; - s.wrapper.transitionEnd(function () { - if (!s) return; - s.onTransitionEnd(); - }); - } - - } else { - s.updateProgress(newPosition); - } - - s.updateActiveIndex(); - } - if (!s.params.freeModeMomentum || timeDiff >= s.params.longSwipesMs) { - s.updateProgress(); - s.updateActiveIndex(); - } - return; - } - - // Find current slide - var i, stopIndex = 0, groupSize = s.slidesSizesGrid[0]; - for (i = 0; i < s.slidesGrid.length; i += s.params.slidesPerGroup) { - if (typeof s.slidesGrid[i + s.params.slidesPerGroup] !== 'undefined') { - if (currentPos >= s.slidesGrid[i] && currentPos < s.slidesGrid[i + s.params.slidesPerGroup]) { - stopIndex = i; - groupSize = s.slidesGrid[i + s.params.slidesPerGroup] - s.slidesGrid[i]; - } - } - else { - if (currentPos >= s.slidesGrid[i]) { - stopIndex = i; - groupSize = s.slidesGrid[s.slidesGrid.length - 1] - s.slidesGrid[s.slidesGrid.length - 2]; - } - } - } - - // Find current slide size - var ratio = (currentPos - s.slidesGrid[stopIndex]) / groupSize; - - if (timeDiff > s.params.longSwipesMs) { - // Long touches - if (!s.params.longSwipes) { - s.slideTo(s.activeIndex); - return; - } - if (s.swipeDirection === 'next') { - if (ratio >= s.params.longSwipesRatio) s.slideTo(stopIndex + s.params.slidesPerGroup); - else s.slideTo(stopIndex); - - } - if (s.swipeDirection === 'prev') { - if (ratio > (1 - s.params.longSwipesRatio)) s.slideTo(stopIndex + s.params.slidesPerGroup); - else s.slideTo(stopIndex); - } - } - else { - // Short swipes - if (!s.params.shortSwipes) { - s.slideTo(s.activeIndex); - return; - } - if (s.swipeDirection === 'next') { - s.slideTo(stopIndex + s.params.slidesPerGroup); - - } - if (s.swipeDirection === 'prev') { - s.slideTo(stopIndex); - } - } - }; - /*========================= - Transitions - ===========================*/ - s._slideTo = function (slideIndex, speed) { - return s.slideTo(slideIndex, speed, true, true); - }; - s.slideTo = function (slideIndex, speed, runCallbacks, internal) { - if (typeof runCallbacks === 'undefined') runCallbacks = true; - if (typeof slideIndex === 'undefined') slideIndex = 0; - if (slideIndex < 0) slideIndex = 0; - s.snapIndex = Math.floor(slideIndex / s.params.slidesPerGroup); - if (s.snapIndex >= s.snapGrid.length) s.snapIndex = s.snapGrid.length - 1; - - var translate = - s.snapGrid[s.snapIndex]; - // Stop autoplay - if (s.params.autoplay && s.autoplaying) { - if (internal || !s.params.autoplayDisableOnInteraction) { - s.pauseAutoplay(speed); - } - else { - s.stopAutoplay(); - } - } - // Update progress - s.updateProgress(translate); - - // Normalize slideIndex - for (var i = 0; i < s.slidesGrid.length; i++) { - if (- Math.floor(translate * 100) >= Math.floor(s.slidesGrid[i] * 100)) { - slideIndex = i; - } - } - - // Directions locks - if (!s.params.allowSwipeToNext && translate < s.translate && translate < s.minTranslate()) { - return false; - } - if (!s.params.allowSwipeToPrev && translate > s.translate && translate > s.maxTranslate()) { - if ((s.activeIndex || 0) !== slideIndex ) return false; - } - - // Update Index - if (typeof speed === 'undefined') speed = s.params.speed; - s.previousIndex = s.activeIndex || 0; - s.activeIndex = slideIndex; - - if ((s.rtl && -translate === s.translate) || (!s.rtl && translate === s.translate)) { - // Update Height - if (s.params.autoHeight) { - s.updateAutoHeight(); - } - s.updateClasses(); - if (s.params.effect !== 'slide') { - s.setWrapperTranslate(translate); - } - return false; - } - s.updateClasses(); - s.onTransitionStart(runCallbacks); - - if (speed === 0) { - s.setWrapperTranslate(translate); - s.setWrapperTransition(0); - s.onTransitionEnd(runCallbacks); - } - else { - s.setWrapperTranslate(translate); - s.setWrapperTransition(speed); - if (!s.animating) { - s.animating = true; - s.wrapper.transitionEnd(function () { - if (!s) return; - s.onTransitionEnd(runCallbacks); - }); - } - - } - - return true; - }; - - s.onTransitionStart = function (runCallbacks) { - if (typeof runCallbacks === 'undefined') runCallbacks = true; - if (s.params.autoHeight) { - s.updateAutoHeight(); - } - if (s.lazy) s.lazy.onTransitionStart(); - if (runCallbacks) { - s.emit('onTransitionStart', s); - if (s.activeIndex !== s.previousIndex) { - s.emit('onSlideChangeStart', s); - if (s.activeIndex > s.previousIndex) { - s.emit('onSlideNextStart', s); - } - else { - s.emit('onSlidePrevStart', s); - } - } - - } - }; - s.onTransitionEnd = function (runCallbacks) { - s.animating = false; - s.setWrapperTransition(0); - if (typeof runCallbacks === 'undefined') runCallbacks = true; - if (s.lazy) s.lazy.onTransitionEnd(); - if (runCallbacks) { - s.emit('onTransitionEnd', s); - if (s.activeIndex !== s.previousIndex) { - s.emit('onSlideChangeEnd', s); - if (s.activeIndex > s.previousIndex) { - s.emit('onSlideNextEnd', s); - } - else { - s.emit('onSlidePrevEnd', s); - } - } - } - if (s.params.hashnav && s.hashnav) { - s.hashnav.setHash(); - } - - }; - s.slideNext = function (runCallbacks, speed, internal) { - if (s.params.loop) { - if (s.animating) return false; - s.fixLoop(); - var clientLeft = s.container[0].clientLeft; - return s.slideTo(s.activeIndex + s.params.slidesPerGroup, speed, runCallbacks, internal); - } - else return s.slideTo(s.activeIndex + s.params.slidesPerGroup, speed, runCallbacks, internal); - }; - s._slideNext = function (speed) { - return s.slideNext(true, speed, true); - }; - s.slidePrev = function (runCallbacks, speed, internal) { - if (s.params.loop) { - if (s.animating) return false; - s.fixLoop(); - var clientLeft = s.container[0].clientLeft; - return s.slideTo(s.activeIndex - 1, speed, runCallbacks, internal); - } - else return s.slideTo(s.activeIndex - 1, speed, runCallbacks, internal); - }; - s._slidePrev = function (speed) { - return s.slidePrev(true, speed, true); - }; - s.slideReset = function (runCallbacks, speed, internal) { - return s.slideTo(s.activeIndex, speed, runCallbacks); - }; - - /*========================= - Translate/transition helpers - ===========================*/ - s.setWrapperTransition = function (duration, byController) { - s.wrapper.transition(duration); - if (s.params.effect !== 'slide' && s.effects[s.params.effect]) { - s.effects[s.params.effect].setTransition(duration); - } - if (s.params.parallax && s.parallax) { - s.parallax.setTransition(duration); - } - if (s.params.scrollbar && s.scrollbar) { - s.scrollbar.setTransition(duration); - } - if (s.params.control && s.controller) { - s.controller.setTransition(duration, byController); - } - s.emit('onSetTransition', s, duration); - }; - s.setWrapperTranslate = function (translate, updateActiveIndex, byController) { - var x = 0, y = 0, z = 0; - if (isH()) { - x = s.rtl ? -translate : translate; - } - else { - y = translate; - } - - if (s.params.roundLengths) { - x = round(x); - y = round(y); - } - - if (!s.params.virtualTranslate) { - if (s.support.transforms3d) s.wrapper.transform('translate3d(' + x + 'px, ' + y + 'px, ' + z + 'px)'); - else s.wrapper.transform('translate(' + x + 'px, ' + y + 'px)'); - } - - s.translate = isH() ? x : y; - - // Check if we need to update progress - var progress; - var translatesDiff = s.maxTranslate() - s.minTranslate(); - if (translatesDiff === 0) { - progress = 0; - } - else { - progress = (translate - s.minTranslate()) / (translatesDiff); - } - if (progress !== s.progress) { - s.updateProgress(translate); - } - - if (updateActiveIndex) s.updateActiveIndex(); - if (s.params.effect !== 'slide' && s.effects[s.params.effect]) { - s.effects[s.params.effect].setTranslate(s.translate); - } - if (s.params.parallax && s.parallax) { - s.parallax.setTranslate(s.translate); - } - if (s.params.scrollbar && s.scrollbar) { - s.scrollbar.setTranslate(s.translate); - } - if (s.params.control && s.controller) { - s.controller.setTranslate(s.translate, byController); - } - s.emit('onSetTranslate', s, s.translate); - }; - - s.getTranslate = function (el, axis) { - var matrix, curTransform, curStyle, transformMatrix; - - // automatic axis detection - if (typeof axis === 'undefined') { - axis = 'x'; - } - - if (s.params.virtualTranslate) { - return s.rtl ? -s.translate : s.translate; - } - - curStyle = window.getComputedStyle(el, null); - if (window.WebKitCSSMatrix) { - curTransform = curStyle.transform || curStyle.webkitTransform; - if (curTransform.split(',').length > 6) { - curTransform = curTransform.split(', ').map(function(a){ - return a.replace(',','.'); - }).join(', '); - } - // Some old versions of Webkit choke when 'none' is passed; pass - // empty string instead in this case - transformMatrix = new window.WebKitCSSMatrix(curTransform === 'none' ? '' : curTransform); - } - else { - transformMatrix = curStyle.MozTransform || curStyle.OTransform || curStyle.MsTransform || curStyle.msTransform || curStyle.transform || curStyle.getPropertyValue('transform').replace('translate(', 'matrix(1, 0, 0, 1,'); - matrix = transformMatrix.toString().split(','); - } - - if (axis === 'x') { - //Latest Chrome and webkits Fix - if (window.WebKitCSSMatrix) - curTransform = transformMatrix.m41; - //Crazy IE10 Matrix - else if (matrix.length === 16) - curTransform = parseFloat(matrix[12]); - //Normal Browsers - else - curTransform = parseFloat(matrix[4]); - } - if (axis === 'y') { - //Latest Chrome and webkits Fix - if (window.WebKitCSSMatrix) - curTransform = transformMatrix.m42; - //Crazy IE10 Matrix - else if (matrix.length === 16) - curTransform = parseFloat(matrix[13]); - //Normal Browsers - else - curTransform = parseFloat(matrix[5]); - } - if (s.rtl && curTransform) curTransform = -curTransform; - return curTransform || 0; - }; - s.getWrapperTranslate = function (axis) { - if (typeof axis === 'undefined') { - axis = isH() ? 'x' : 'y'; - } - return s.getTranslate(s.wrapper[0], axis); - }; - - /*========================= - Observer - ===========================*/ - s.observers = []; - function initObserver(target, options) { - options = options || {}; - // create an observer instance - var ObserverFunc = window.MutationObserver || window.WebkitMutationObserver; - var observer = new ObserverFunc(function (mutations) { - mutations.forEach(function (mutation) { - s.onResize(true); - s.emit('onObserverUpdate', s, mutation); - }); - }); - - observer.observe(target, { - attributes: typeof options.attributes === 'undefined' ? true : options.attributes, - childList: typeof options.childList === 'undefined' ? true : options.childList, - characterData: typeof options.characterData === 'undefined' ? true : options.characterData - }); - - s.observers.push(observer); - } - s.initObservers = function () { - if (s.params.observeParents) { - var containerParents = s.container.parents(); - for (var i = 0; i < containerParents.length; i++) { - initObserver(containerParents[i]); - } - } - - // Observe container - initObserver(s.container[0], {childList: false}); - - // Observe wrapper - initObserver(s.wrapper[0], {attributes: false}); - }; - s.disconnectObservers = function () { - for (var i = 0; i < s.observers.length; i++) { - s.observers[i].disconnect(); - } - s.observers = []; - }; - /*========================= - Loop - ===========================*/ - // Create looped slides - s.createLoop = function () { - - var toRemove = s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass); - angular.element(toRemove).remove(); - - var slides = s.wrapper.children('.' + s.params.slideClass); - - if(s.params.slidesPerView === 'auto' && !s.params.loopedSlides) s.params.loopedSlides = slides.length; - - s.loopedSlides = parseInt(s.params.loopedSlides || s.params.slidesPerView, 10); - s.loopedSlides = s.loopedSlides + s.params.loopAdditionalSlides; - if (s.loopedSlides > slides.length) { - s.loopedSlides = slides.length; - } - - var prependSlides = [], appendSlides = [], i, scope, newNode; - slides.each(function (index, el) { - var slide = $(this); - if (index < s.loopedSlides) appendSlides.push(el); - if (index < slides.length && index >= slides.length - s.loopedSlides) prependSlides.push(el); - slide.attr('data-swiper-slide-index', index); - }); - for (i = 0; i < appendSlides.length; i++) { - newNode = angular.element(appendSlides[i]).clone().addClass(s.params.slideDuplicateClass); - newNode.removeAttr('ng-transclude'); - newNode.removeAttr('ng-repeat'); - scope = angular.element(appendSlides[i]).scope(); - newNode = $compile(newNode)(scope); - angular.element(s.wrapper).append(newNode); - //s.wrapper.append($(appendSlides[i].cloneNode(true)).addClass(s.params.slideDuplicateClass)); - } - for (i = prependSlides.length - 1; i >= 0; i--) { - //s.wrapper.prepend($(prependSlides[i].cloneNode(true)).addClass(s.params.slideDuplicateClass)); - - newNode = angular.element(prependSlides[i]).clone().addClass(s.params.slideDuplicateClass); - newNode.removeAttr('ng-transclude'); - newNode.removeAttr('ng-repeat'); - - scope = angular.element(prependSlides[i]).scope(); - newNode = $compile(newNode)(scope); - angular.element(s.wrapper).prepend(newNode); - } - }; - s.destroyLoop = function () { - s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass).remove(); - s.slides.removeAttr('data-swiper-slide-index'); - }; - s.fixLoop = function () { - var newIndex; - //Fix For Negative Oversliding - if (s.activeIndex < s.loopedSlides) { - newIndex = s.slides.length - s.loopedSlides * 3 + s.activeIndex; - newIndex = newIndex + s.loopedSlides; - s.slideTo(newIndex, 0, false, true); - } - //Fix For Positive Oversliding - else if ((s.params.slidesPerView === 'auto' && s.activeIndex >= s.loopedSlides * 2) || (s.activeIndex > s.slides.length - s.params.slidesPerView * 2)) { - newIndex = -s.slides.length + s.activeIndex + s.loopedSlides; - newIndex = newIndex + s.loopedSlides; - s.slideTo(newIndex, 0, false, true); - } - }; - /*========================= - Append/Prepend/Remove Slides - ===========================*/ - s.appendSlide = function (slides) { - if (s.params.loop) { - s.destroyLoop(); - } - if (typeof slides === 'object' && slides.length) { - for (var i = 0; i < slides.length; i++) { - if (slides[i]) s.wrapper.append(slides[i]); - } - } - else { - s.wrapper.append(slides); - } - if (s.params.loop) { - s.createLoop(); - } - if (!(s.params.observer && s.support.observer)) { - s.update(true); - } - }; - s.prependSlide = function (slides) { - if (s.params.loop) { - s.destroyLoop(); - } - var newActiveIndex = s.activeIndex + 1; - if (typeof slides === 'object' && slides.length) { - for (var i = 0; i < slides.length; i++) { - if (slides[i]) s.wrapper.prepend(slides[i]); - } - newActiveIndex = s.activeIndex + slides.length; - } - else { - s.wrapper.prepend(slides); - } - if (s.params.loop) { - s.createLoop(); - } - if (!(s.params.observer && s.support.observer)) { - s.update(true); - } - s.slideTo(newActiveIndex, 0, false); - }; - s.removeSlide = function (slidesIndexes) { - if (s.params.loop) { - s.destroyLoop(); - s.slides = s.wrapper.children('.' + s.params.slideClass); - } - var newActiveIndex = s.activeIndex, - indexToRemove; - if (typeof slidesIndexes === 'object' && slidesIndexes.length) { - for (var i = 0; i < slidesIndexes.length; i++) { - indexToRemove = slidesIndexes[i]; - if (s.slides[indexToRemove]) s.slides.eq(indexToRemove).remove(); - if (indexToRemove < newActiveIndex) newActiveIndex--; - } - newActiveIndex = Math.max(newActiveIndex, 0); - } - else { - indexToRemove = slidesIndexes; - if (s.slides[indexToRemove]) s.slides.eq(indexToRemove).remove(); - if (indexToRemove < newActiveIndex) newActiveIndex--; - newActiveIndex = Math.max(newActiveIndex, 0); - } - - if (s.params.loop) { - s.createLoop(); - } - - if (!(s.params.observer && s.support.observer)) { - s.update(true); - } - if (s.params.loop) { - s.slideTo(newActiveIndex + s.loopedSlides, 0, false); - } - else { - s.slideTo(newActiveIndex, 0, false); - } - - }; - s.removeAllSlides = function () { - var slidesIndexes = []; - for (var i = 0; i < s.slides.length; i++) { - slidesIndexes.push(i); - } - s.removeSlide(slidesIndexes); - }; - - - /*========================= - Effects - ===========================*/ - s.effects = { - fade: { - setTranslate: function () { - for (var i = 0; i < s.slides.length; i++) { - var slide = s.slides.eq(i); - var offset = slide[0].swiperSlideOffset; - var tx = -offset; - if (!s.params.virtualTranslate) tx = tx - s.translate; - var ty = 0; - if (!isH()) { - ty = tx; - tx = 0; - } - var slideOpacity = s.params.fade.crossFade ? - Math.max(1 - Math.abs(slide[0].progress), 0) : - 1 + Math.min(Math.max(slide[0].progress, -1), 0); - slide - .css({ - opacity: slideOpacity - }) - .transform('translate3d(' + tx + 'px, ' + ty + 'px, 0px)'); - - } - - }, - setTransition: function (duration) { - s.slides.transition(duration); - if (s.params.virtualTranslate && duration !== 0) { - var eventTriggered = false; - s.slides.transitionEnd(function () { - if (eventTriggered) return; - if (!s) return; - eventTriggered = true; - s.animating = false; - var triggerEvents = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd']; - for (var i = 0; i < triggerEvents.length; i++) { - s.wrapper.trigger(triggerEvents[i]); - } - }); - } - } - }, - cube: { - setTranslate: function () { - var wrapperRotate = 0, cubeShadow; - if (s.params.cube.shadow) { - if (isH()) { - cubeShadow = s.wrapper.find('.swiper-cube-shadow'); - if (cubeShadow.length === 0) { - cubeShadow = $('
'); - s.wrapper.append(cubeShadow); - } - cubeShadow.css({height: s.width + 'px'}); - } - else { - cubeShadow = s.container.find('.swiper-cube-shadow'); - if (cubeShadow.length === 0) { - cubeShadow = $('
'); - s.container.append(cubeShadow); - } - } - } - for (var i = 0; i < s.slides.length; i++) { - var slide = s.slides.eq(i); - var slideAngle = i * 90; - var round = Math.floor(slideAngle / 360); - if (s.rtl) { - slideAngle = -slideAngle; - round = Math.floor(-slideAngle / 360); - } - var progress = Math.max(Math.min(slide[0].progress, 1), -1); - var tx = 0, ty = 0, tz = 0; - if (i % 4 === 0) { - tx = - round * 4 * s.size; - tz = 0; - } - else if ((i - 1) % 4 === 0) { - tx = 0; - tz = - round * 4 * s.size; - } - else if ((i - 2) % 4 === 0) { - tx = s.size + round * 4 * s.size; - tz = s.size; - } - else if ((i - 3) % 4 === 0) { - tx = - s.size; - tz = 3 * s.size + s.size * 4 * round; - } - if (s.rtl) { - tx = -tx; - } - - if (!isH()) { - ty = tx; - tx = 0; - } - - var transform = 'rotateX(' + (isH() ? 0 : -slideAngle) + 'deg) rotateY(' + (isH() ? slideAngle : 0) + 'deg) translate3d(' + tx + 'px, ' + ty + 'px, ' + tz + 'px)'; - if (progress <= 1 && progress > -1) { - wrapperRotate = i * 90 + progress * 90; - if (s.rtl) wrapperRotate = -i * 90 - progress * 90; - } - slide.transform(transform); - if (s.params.cube.slideShadows) { - //Set shadows - var shadowBefore = isH() ? slide.find('.swiper-slide-shadow-left') : slide.find('.swiper-slide-shadow-top'); - var shadowAfter = isH() ? slide.find('.swiper-slide-shadow-right') : slide.find('.swiper-slide-shadow-bottom'); - if (shadowBefore.length === 0) { - shadowBefore = $('
'); - slide.append(shadowBefore); - } - if (shadowAfter.length === 0) { - shadowAfter = $('
'); - slide.append(shadowAfter); - } - var shadowOpacity = slide[0].progress; - if (shadowBefore.length) shadowBefore[0].style.opacity = -slide[0].progress; - if (shadowAfter.length) shadowAfter[0].style.opacity = slide[0].progress; - } - } - s.wrapper.css({ - '-webkit-transform-origin': '50% 50% -' + (s.size / 2) + 'px', - '-moz-transform-origin': '50% 50% -' + (s.size / 2) + 'px', - '-ms-transform-origin': '50% 50% -' + (s.size / 2) + 'px', - 'transform-origin': '50% 50% -' + (s.size / 2) + 'px' - }); - - if (s.params.cube.shadow) { - if (isH()) { - 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) + ')'); - } - else { - var shadowAngle = Math.abs(wrapperRotate) - Math.floor(Math.abs(wrapperRotate) / 90) * 90; - var multiplier = 1.5 - (Math.sin(shadowAngle * 2 * Math.PI / 360) / 2 + Math.cos(shadowAngle * 2 * Math.PI / 360) / 2); - var scale1 = s.params.cube.shadowScale, - scale2 = s.params.cube.shadowScale / multiplier, - offset = s.params.cube.shadowOffset; - cubeShadow.transform('scale3d(' + scale1 + ', 1, ' + scale2 + ') translate3d(0px, ' + (s.height / 2 + offset) + 'px, ' + (-s.height / 2 / scale2) + 'px) rotateX(-90deg)'); - } - } - var zFactor = (s.isSafari || s.isUiWebView) ? (-s.size / 2) : 0; - s.wrapper.transform('translate3d(0px,0,' + zFactor + 'px) rotateX(' + (isH() ? 0 : wrapperRotate) + 'deg) rotateY(' + (isH() ? -wrapperRotate : 0) + 'deg)'); - }, - setTransition: function (duration) { - s.slides.transition(duration).find('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').transition(duration); - if (s.params.cube.shadow && !isH()) { - s.container.find('.swiper-cube-shadow').transition(duration); - } - } - }, - coverflow: { - setTranslate: function () { - var transform = s.translate; - var center = isH() ? -transform + s.width / 2 : -transform + s.height / 2; - var rotate = isH() ? s.params.coverflow.rotate: -s.params.coverflow.rotate; - var translate = s.params.coverflow.depth; - //Each slide offset from center - for (var i = 0, length = s.slides.length; i < length; i++) { - var slide = s.slides.eq(i); - var slideSize = s.slidesSizesGrid[i]; - var slideOffset = slide[0].swiperSlideOffset; - var offsetMultiplier = (center - slideOffset - slideSize / 2) / slideSize * s.params.coverflow.modifier; - - var rotateY = isH() ? rotate * offsetMultiplier : 0; - var rotateX = isH() ? 0 : rotate * offsetMultiplier; - // var rotateZ = 0 - var translateZ = -translate * Math.abs(offsetMultiplier); - - var translateY = isH() ? 0 : s.params.coverflow.stretch * (offsetMultiplier); - var translateX = isH() ? s.params.coverflow.stretch * (offsetMultiplier) : 0; - - //Fix for ultra small values - if (Math.abs(translateX) < 0.001) translateX = 0; - if (Math.abs(translateY) < 0.001) translateY = 0; - if (Math.abs(translateZ) < 0.001) translateZ = 0; - if (Math.abs(rotateY) < 0.001) rotateY = 0; - if (Math.abs(rotateX) < 0.001) rotateX = 0; - - var slideTransform = 'translate3d(' + translateX + 'px,' + translateY + 'px,' + translateZ + 'px) rotateX(' + rotateX + 'deg) rotateY(' + rotateY + 'deg)'; - - slide.transform(slideTransform); - slide[0].style.zIndex = -Math.abs(Math.round(offsetMultiplier)) + 1; - if (s.params.coverflow.slideShadows) { - //Set shadows - var shadowBefore = isH() ? slide.find('.swiper-slide-shadow-left') : slide.find('.swiper-slide-shadow-top'); - var shadowAfter = isH() ? slide.find('.swiper-slide-shadow-right') : slide.find('.swiper-slide-shadow-bottom'); - if (shadowBefore.length === 0) { - shadowBefore = $('
'); - slide.append(shadowBefore); - } - if (shadowAfter.length === 0) { - shadowAfter = $('
'); - slide.append(shadowAfter); - } - if (shadowBefore.length) shadowBefore[0].style.opacity = offsetMultiplier > 0 ? offsetMultiplier : 0; - if (shadowAfter.length) shadowAfter[0].style.opacity = (-offsetMultiplier) > 0 ? -offsetMultiplier : 0; - } - } - - //Set correct perspective for IE10 - if (s.browser.ie) { - var ws = s.wrapper[0].style; - ws.perspectiveOrigin = center + 'px 50%'; - } - }, - setTransition: function (duration) { - s.slides.transition(duration).find('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').transition(duration); - } - } - }; - - /*========================= - Images Lazy Loading - ===========================*/ - s.lazy = { - initialImageLoaded: false, - loadImageInSlide: function (index, loadInDuplicate) { - if (typeof index === 'undefined') return; - if (typeof loadInDuplicate === 'undefined') loadInDuplicate = true; - if (s.slides.length === 0) return; - - var slide = s.slides.eq(index); - var img = slide.find('.swiper-lazy:not(.swiper-lazy-loaded):not(.swiper-lazy-loading)'); - if (slide.hasClass('swiper-lazy') && !slide.hasClass('swiper-lazy-loaded') && !slide.hasClass('swiper-lazy-loading')) { - img = img.add(slide[0]); - } - if (img.length === 0) return; - - img.each(function () { - var _img = $(this); - _img.addClass('swiper-lazy-loading'); - var background = _img.attr('data-background'); - var src = _img.attr('data-src'), - srcset = _img.attr('data-srcset'); - s.loadImage(_img[0], (src || background), srcset, false, function () { - if (background) { - _img.css('background-image', 'url(' + background + ')'); - _img.removeAttr('data-background'); - } - else { - if (srcset) { - _img.attr('srcset', srcset); - _img.removeAttr('data-srcset'); - } - if (src) { - _img.attr('src', src); - _img.removeAttr('data-src'); - } - - } - - _img.addClass('swiper-lazy-loaded').removeClass('swiper-lazy-loading'); - slide.find('.swiper-lazy-preloader, .preloader').remove(); - if (s.params.loop && loadInDuplicate) { - var slideOriginalIndex = slide.attr('data-swiper-slide-index'); - if (slide.hasClass(s.params.slideDuplicateClass)) { - var originalSlide = s.wrapper.children('[data-swiper-slide-index="' + slideOriginalIndex + '"]:not(.' + s.params.slideDuplicateClass + ')'); - s.lazy.loadImageInSlide(originalSlide.index(), false); - } - else { - var duplicatedSlide = s.wrapper.children('.' + s.params.slideDuplicateClass + '[data-swiper-slide-index="' + slideOriginalIndex + '"]'); - s.lazy.loadImageInSlide(duplicatedSlide.index(), false); - } - } - s.emit('onLazyImageReady', s, slide[0], _img[0]); - }); - - s.emit('onLazyImageLoad', s, slide[0], _img[0]); - }); - - }, - load: function () { - var i; - if (s.params.watchSlidesVisibility) { - s.wrapper.children('.' + s.params.slideVisibleClass).each(function () { - s.lazy.loadImageInSlide($(this).index()); - }); - } - else { - if (s.params.slidesPerView > 1) { - for (i = s.activeIndex; i < s.activeIndex + s.params.slidesPerView ; i++) { - if (s.slides[i]) s.lazy.loadImageInSlide(i); - } - } - else { - s.lazy.loadImageInSlide(s.activeIndex); - } - } - if (s.params.lazyLoadingInPrevNext) { - if (s.params.slidesPerView > 1) { - // Next Slides - for (i = s.activeIndex + s.params.slidesPerView; i < s.activeIndex + s.params.slidesPerView + s.params.slidesPerView; i++) { - if (s.slides[i]) s.lazy.loadImageInSlide(i); - } - // Prev Slides - for (i = s.activeIndex - s.params.slidesPerView; i < s.activeIndex ; i++) { - if (s.slides[i]) s.lazy.loadImageInSlide(i); - } - } - else { - var nextSlide = s.wrapper.children('.' + s.params.slideNextClass); - if (nextSlide.length > 0) s.lazy.loadImageInSlide(nextSlide.index()); - - var prevSlide = s.wrapper.children('.' + s.params.slidePrevClass); - if (prevSlide.length > 0) s.lazy.loadImageInSlide(prevSlide.index()); - } - } - }, - onTransitionStart: function () { - if (s.params.lazyLoading) { - if (s.params.lazyLoadingOnTransitionStart || (!s.params.lazyLoadingOnTransitionStart && !s.lazy.initialImageLoaded)) { - s.lazy.load(); - } - } - }, - onTransitionEnd: function () { - if (s.params.lazyLoading && !s.params.lazyLoadingOnTransitionStart) { - s.lazy.load(); - } - } - }; - - - /*========================= - Scrollbar - ===========================*/ - s.scrollbar = { - isTouched: false, - setDragPosition: function (e) { - var sb = s.scrollbar; - var x = 0, y = 0; - var translate; - var pointerPosition = isH() ? - ((e.type === 'touchstart' || e.type === 'touchmove') ? e.targetTouches[0].pageX : e.pageX || e.clientX) : - ((e.type === 'touchstart' || e.type === 'touchmove') ? e.targetTouches[0].pageY : e.pageY || e.clientY) ; - var position = (pointerPosition) - sb.track.offset()[isH() ? 'left' : 'top'] - sb.dragSize / 2; - var positionMin = -s.minTranslate() * sb.moveDivider; - var positionMax = -s.maxTranslate() * sb.moveDivider; - if (position < positionMin) { - position = positionMin; - } - else if (position > positionMax) { - position = positionMax; - } - position = -position / sb.moveDivider; - s.updateProgress(position); - s.setWrapperTranslate(position, true); - }, - dragStart: function (e) { - var sb = s.scrollbar; - sb.isTouched = true; - e.preventDefault(); - e.stopPropagation(); - - sb.setDragPosition(e); - clearTimeout(sb.dragTimeout); - - sb.track.transition(0); - if (s.params.scrollbarHide) { - sb.track.css('opacity', 1); - } - s.wrapper.transition(100); - sb.drag.transition(100); - s.emit('onScrollbarDragStart', s); - }, - dragMove: function (e) { - var sb = s.scrollbar; - if (!sb.isTouched) return; - if (e.preventDefault) e.preventDefault(); - else e.returnValue = false; - sb.setDragPosition(e); - s.wrapper.transition(0); - sb.track.transition(0); - sb.drag.transition(0); - s.emit('onScrollbarDragMove', s); - }, - dragEnd: function (e) { - var sb = s.scrollbar; - if (!sb.isTouched) return; - sb.isTouched = false; - if (s.params.scrollbarHide) { - clearTimeout(sb.dragTimeout); - sb.dragTimeout = setTimeout(function () { - sb.track.css('opacity', 0); - sb.track.transition(400); - }, 1000); - - } - s.emit('onScrollbarDragEnd', s); - if (s.params.scrollbarSnapOnRelease) { - s.slideReset(); - } - }, - enableDraggable: function () { - var sb = s.scrollbar; - var target = s.support.touch ? sb.track : document; - $(sb.track).on(s.touchEvents.start, sb.dragStart); - $(target).on(s.touchEvents.move, sb.dragMove); - $(target).on(s.touchEvents.end, sb.dragEnd); - }, - disableDraggable: function () { - var sb = s.scrollbar; - var target = s.support.touch ? sb.track : document; - $(sb.track).off(s.touchEvents.start, sb.dragStart); - $(target).off(s.touchEvents.move, sb.dragMove); - $(target).off(s.touchEvents.end, sb.dragEnd); - }, - set: function () { - if (!s.params.scrollbar) return; - var sb = s.scrollbar; - sb.track = $(s.params.scrollbar); - sb.drag = sb.track.find('.swiper-scrollbar-drag'); - if (sb.drag.length === 0) { - sb.drag = $('
'); - sb.track.append(sb.drag); - } - sb.drag[0].style.width = ''; - sb.drag[0].style.height = ''; - sb.trackSize = isH() ? sb.track[0].offsetWidth : sb.track[0].offsetHeight; - - sb.divider = s.size / s.virtualSize; - sb.moveDivider = sb.divider * (sb.trackSize / s.size); - sb.dragSize = sb.trackSize * sb.divider; - - if (isH()) { - sb.drag[0].style.width = sb.dragSize + 'px'; - } - else { - sb.drag[0].style.height = sb.dragSize + 'px'; - } - - if (sb.divider >= 1) { - sb.track[0].style.display = 'none'; - } - else { - sb.track[0].style.display = ''; - } - if (s.params.scrollbarHide) { - sb.track[0].style.opacity = 0; - } - }, - setTranslate: function () { - if (!s.params.scrollbar) return; - var diff; - var sb = s.scrollbar; - var translate = s.translate || 0; - var newPos; - - var newSize = sb.dragSize; - newPos = (sb.trackSize - sb.dragSize) * s.progress; - if (s.rtl && isH()) { - newPos = -newPos; - if (newPos > 0) { - newSize = sb.dragSize - newPos; - newPos = 0; - } - else if (-newPos + sb.dragSize > sb.trackSize) { - newSize = sb.trackSize + newPos; - } - } - else { - if (newPos < 0) { - newSize = sb.dragSize + newPos; - newPos = 0; - } - else if (newPos + sb.dragSize > sb.trackSize) { - newSize = sb.trackSize - newPos; - } - } - if (isH()) { - if (s.support.transforms3d) { - sb.drag.transform('translate3d(' + (newPos) + 'px, 0, 0)'); - } - else { - sb.drag.transform('translateX(' + (newPos) + 'px)'); - } - sb.drag[0].style.width = newSize + 'px'; - } - else { - if (s.support.transforms3d) { - sb.drag.transform('translate3d(0px, ' + (newPos) + 'px, 0)'); - } - else { - sb.drag.transform('translateY(' + (newPos) + 'px)'); - } - sb.drag[0].style.height = newSize + 'px'; - } - if (s.params.scrollbarHide) { - clearTimeout(sb.timeout); - sb.track[0].style.opacity = 1; - sb.timeout = setTimeout(function () { - sb.track[0].style.opacity = 0; - sb.track.transition(400); - }, 1000); - } - }, - setTransition: function (duration) { - if (!s.params.scrollbar) return; - s.scrollbar.drag.transition(duration); - } - }; - - /*========================= - Controller - ===========================*/ - s.controller = { - LinearSpline: function (x, y) { - this.x = x; - this.y = y; - this.lastIndex = x.length - 1; - // Given an x value (x2), return the expected y2 value: - // (x1,y1) is the known point before given value, - // (x3,y3) is the known point after given value. - var i1, i3; - var l = this.x.length; - - this.interpolate = function (x2) { - if (!x2) return 0; - - // Get the indexes of x1 and x3 (the array indexes before and after given x2): - i3 = binarySearch(this.x, x2); - i1 = i3 - 1; - - // We have our indexes i1 & i3, so we can calculate already: - // y2 := ((x2−x1) × (y3−y1)) ÷ (x3−x1) + y1 - return ((x2 - this.x[i1]) * (this.y[i3] - this.y[i1])) / (this.x[i3] - this.x[i1]) + this.y[i1]; - }; - - var binarySearch = (function() { - var maxIndex, minIndex, guess; - return function(array, val) { - minIndex = -1; - maxIndex = array.length; - while (maxIndex - minIndex > 1) - if (array[guess = maxIndex + minIndex >> 1] <= val) { - minIndex = guess; - } else { - maxIndex = guess; - } - return maxIndex; - }; - })(); - }, - //xxx: for now i will just save one spline function to to - getInterpolateFunction: function(c){ - if(!s.controller.spline) s.controller.spline = s.params.loop ? - new s.controller.LinearSpline(s.slidesGrid, c.slidesGrid) : - new s.controller.LinearSpline(s.snapGrid, c.snapGrid); - }, - setTranslate: function (translate, byController) { - var controlled = s.params.control; - var multiplier, controlledTranslate; - function setControlledTranslate(c) { - // this will create an Interpolate function based on the snapGrids - // x is the Grid of the scrolled scroller and y will be the controlled scroller - // it makes sense to create this only once and recall it for the interpolation - // the function does a lot of value caching for performance - translate = c.rtl && c.params.direction === 'horizontal' ? -s.translate : s.translate; - if (s.params.controlBy === 'slide') { - s.controller.getInterpolateFunction(c); - // i am not sure why the values have to be multiplicated this way, tried to invert the snapGrid - // but it did not work out - controlledTranslate = -s.controller.spline.interpolate(-translate); - } - - if(!controlledTranslate || s.params.controlBy === 'container'){ - multiplier = (c.maxTranslate() - c.minTranslate()) / (s.maxTranslate() - s.minTranslate()); - controlledTranslate = (translate - s.minTranslate()) * multiplier + c.minTranslate(); - } - - if (s.params.controlInverse) { - controlledTranslate = c.maxTranslate() - controlledTranslate; - } - c.updateProgress(controlledTranslate); - c.setWrapperTranslate(controlledTranslate, false, s); - c.updateActiveIndex(); - } - if (s.isArray(controlled)) { - for (var i = 0; i < controlled.length; i++) { - if (controlled[i] !== byController && controlled[i] instanceof Swiper) { - setControlledTranslate(controlled[i]); - } - } - } - else if (controlled instanceof Swiper && byController !== controlled) { - - setControlledTranslate(controlled); - } - }, - setTransition: function (duration, byController) { - var controlled = s.params.control; - var i; - function setControlledTransition(c) { - c.setWrapperTransition(duration, s); - if (duration !== 0) { - c.onTransitionStart(); - c.wrapper.transitionEnd(function(){ - if (!controlled) return; - if (c.params.loop && s.params.controlBy === 'slide') { - c.fixLoop(); - } - c.onTransitionEnd(); - - }); - } - } - if (s.isArray(controlled)) { - for (i = 0; i < controlled.length; i++) { - if (controlled[i] !== byController && controlled[i] instanceof Swiper) { - setControlledTransition(controlled[i]); - } - } - } - else if (controlled instanceof Swiper && byController !== controlled) { - setControlledTransition(controlled); - } - } - }; - - /*========================= - Hash Navigation - ===========================*/ - s.hashnav = { - init: function () { - if (!s.params.hashnav) return; - s.hashnav.initialized = true; - var hash = document.location.hash.replace('#', ''); - if (!hash) return; - var speed = 0; - for (var i = 0, length = s.slides.length; i < length; i++) { - var slide = s.slides.eq(i); - var slideHash = slide.attr('data-hash'); - if (slideHash === hash && !slide.hasClass(s.params.slideDuplicateClass)) { - var index = slide.index(); - s.slideTo(index, speed, s.params.runCallbacksOnInit, true); - } - } - }, - setHash: function () { - if (!s.hashnav.initialized || !s.params.hashnav) return; - document.location.hash = s.slides.eq(s.activeIndex).attr('data-hash') || ''; - } - }; - - /*========================= - Keyboard Control - ===========================*/ - function handleKeyboard(e) { - if (e.originalEvent) e = e.originalEvent; //jquery fix - var kc = e.keyCode || e.charCode; - // Directions locks - if (!s.params.allowSwipeToNext && (isH() && kc === 39 || !isH() && kc === 40)) { - return false; - } - if (!s.params.allowSwipeToPrev && (isH() && kc === 37 || !isH() && kc === 38)) { - return false; - } - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) { - return; - } - if (document.activeElement && document.activeElement.nodeName && (document.activeElement.nodeName.toLowerCase() === 'input' || document.activeElement.nodeName.toLowerCase() === 'textarea')) { - return; - } - if (kc === 37 || kc === 39 || kc === 38 || kc === 40) { - var inView = false; - //Check that swiper should be inside of visible area of window - if (s.container.parents('.swiper-slide').length > 0 && s.container.parents('.swiper-slide-active').length === 0) { - return; - } - var windowScroll = { - left: window.pageXOffset, - top: window.pageYOffset - }; - var windowWidth = window.innerWidth; - var windowHeight = window.innerHeight; - var swiperOffset = s.container.offset(); - if (s.rtl) swiperOffset.left = swiperOffset.left - s.container[0].scrollLeft; - var swiperCoord = [ - [swiperOffset.left, swiperOffset.top], - [swiperOffset.left + s.width, swiperOffset.top], - [swiperOffset.left, swiperOffset.top + s.height], - [swiperOffset.left + s.width, swiperOffset.top + s.height] - ]; - for (var i = 0; i < swiperCoord.length; i++) { - var point = swiperCoord[i]; - if ( - point[0] >= windowScroll.left && point[0] <= windowScroll.left + windowWidth && - point[1] >= windowScroll.top && point[1] <= windowScroll.top + windowHeight - ) { - inView = true; - } - - } - if (!inView) return; - } - if (isH()) { - if (kc === 37 || kc === 39) { - if (e.preventDefault) e.preventDefault(); - else e.returnValue = false; - } - if ((kc === 39 && !s.rtl) || (kc === 37 && s.rtl)) s.slideNext(); - if ((kc === 37 && !s.rtl) || (kc === 39 && s.rtl)) s.slidePrev(); - } - else { - if (kc === 38 || kc === 40) { - if (e.preventDefault) e.preventDefault(); - else e.returnValue = false; - } - if (kc === 40) s.slideNext(); - if (kc === 38) s.slidePrev(); - } - } - s.disableKeyboardControl = function () { - s.params.keyboardControl = false; - $(document).off('keydown', handleKeyboard); - }; - s.enableKeyboardControl = function () { - s.params.keyboardControl = true; - $(document).on('keydown', handleKeyboard); - }; - - - /*========================= - Mousewheel Control - ===========================*/ - s.mousewheel = { - event: false, - lastScrollTime: (new window.Date()).getTime() - }; - if (s.params.mousewheelControl) { - try { - new window.WheelEvent('wheel'); - s.mousewheel.event = 'wheel'; - } catch (e) {} - - if (!s.mousewheel.event && document.onmousewheel !== undefined) { - s.mousewheel.event = 'mousewheel'; - } - if (!s.mousewheel.event) { - s.mousewheel.event = 'DOMMouseScroll'; - } - } - function handleMousewheel(e) { - if (e.originalEvent) e = e.originalEvent; //jquery fix - var we = s.mousewheel.event; - var delta = 0; - var rtlFactor = s.rtl ? -1 : 1; - //Opera & IE - if (e.detail) delta = -e.detail; - //WebKits - else if (we === 'mousewheel') { - if (s.params.mousewheelForceToAxis) { - if (isH()) { - if (Math.abs(e.wheelDeltaX) > Math.abs(e.wheelDeltaY)) delta = e.wheelDeltaX * rtlFactor; - else return; - } - else { - if (Math.abs(e.wheelDeltaY) > Math.abs(e.wheelDeltaX)) delta = e.wheelDeltaY; - else return; - } - } - else { - delta = Math.abs(e.wheelDeltaX) > Math.abs(e.wheelDeltaY) ? - e.wheelDeltaX * rtlFactor : - e.wheelDeltaY; - } - } - //Old FireFox - else if (we === 'DOMMouseScroll') delta = -e.detail; - //New FireFox - else if (we === 'wheel') { - if (s.params.mousewheelForceToAxis) { - if (isH()) { - if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) delta = -e.deltaX * rtlFactor; - else return; - } - else { - if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) delta = -e.deltaY; - else return; - } - } - else { - delta = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? - e.deltaX * rtlFactor : - e.deltaY; - } - } - if (delta === 0) return; - - if (s.params.mousewheelInvert) delta = -delta; - - if (!s.params.freeMode) { - if ((new window.Date()).getTime() - s.mousewheel.lastScrollTime > 60) { - if (delta < 0) { - if ((!s.isEnd || s.params.loop) && !s.animating) s.slideNext(); - else if (s.params.mousewheelReleaseOnEdges) return true; - } - else { - if ((!s.isBeginning || s.params.loop) && !s.animating) s.slidePrev(); - else if (s.params.mousewheelReleaseOnEdges) return true; - } - } - s.mousewheel.lastScrollTime = (new window.Date()).getTime(); - - } - else { - //Freemode or scrollContainer: - var position = s.getWrapperTranslate() + delta * s.params.mousewheelSensitivity; - var wasBeginning = s.isBeginning, - wasEnd = s.isEnd; - - if (position >= s.minTranslate()) position = s.minTranslate(); - if (position <= s.maxTranslate()) position = s.maxTranslate(); - - s.setWrapperTransition(0); - s.setWrapperTranslate(position); - s.updateProgress(); - s.updateActiveIndex(); - - if (!wasBeginning && s.isBeginning || !wasEnd && s.isEnd) { - s.updateClasses(); - } - - if (s.params.freeModeSticky) { - clearTimeout(s.mousewheel.timeout); - s.mousewheel.timeout = setTimeout(function () { - s.slideReset(); - }, 300); - } - - // Return page scroll on edge positions - if (position === 0 || position === s.maxTranslate()) return; - } - if (s.params.autoplay) s.stopAutoplay(); - - if (e.preventDefault) e.preventDefault(); - else e.returnValue = false; - return false; - } - s.disableMousewheelControl = function () { - if (!s.mousewheel.event) return false; - s.container.off(s.mousewheel.event, handleMousewheel); - return true; - }; - - s.enableMousewheelControl = function () { - if (!s.mousewheel.event) return false; - s.container.on(s.mousewheel.event, handleMousewheel); - return true; - }; - - - /*========================= - Parallax - ===========================*/ - function setParallaxTransform(el, progress) { - el = $(el); - var p, pX, pY; - var rtlFactor = s.rtl ? -1 : 1; - - p = el.attr('data-swiper-parallax') || '0'; - pX = el.attr('data-swiper-parallax-x'); - pY = el.attr('data-swiper-parallax-y'); - if (pX || pY) { - pX = pX || '0'; - pY = pY || '0'; - } - else { - if (isH()) { - pX = p; - pY = '0'; - } - else { - pY = p; - pX = '0'; - } - } - - if ((pX).indexOf('%') >= 0) { - pX = parseInt(pX, 10) * progress * rtlFactor + '%'; - } - else { - pX = pX * progress * rtlFactor + 'px' ; - } - if ((pY).indexOf('%') >= 0) { - pY = parseInt(pY, 10) * progress + '%'; - } - else { - pY = pY * progress + 'px' ; - } - - el.transform('translate3d(' + pX + ', ' + pY + ',0px)'); - } - s.parallax = { - setTranslate: function () { - s.container.children('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function(){ - setParallaxTransform(this, s.progress); - - }); - s.slides.each(function () { - var slide = $(this); - slide.find('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function () { - var progress = Math.min(Math.max(slide[0].progress, -1), 1); - setParallaxTransform(this, progress); - }); - }); - }, - setTransition: function (duration) { - if (typeof duration === 'undefined') duration = s.params.speed; - s.container.find('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function(){ - var el = $(this); - var parallaxDuration = parseInt(el.attr('data-swiper-parallax-duration'), 10) || duration; - if (duration === 0) parallaxDuration = 0; - el.transition(parallaxDuration); - }); - } - }; - - - /*========================= - Plugins API. Collect all and init all plugins - ===========================*/ - s._plugins = []; - for (var plugin in s.plugins) { - var p = s.plugins[plugin](s, s.params[plugin]); - if (p) s._plugins.push(p); - } - // Method to call all plugins event/method - s.callPlugins = function (eventName) { - for (var i = 0; i < s._plugins.length; i++) { - if (eventName in s._plugins[i]) { - s._plugins[i][eventName](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); - } - } - }; - - /*========================= - Events/Callbacks/Plugins Emitter - ===========================*/ - function normalizeEventName (eventName) { - if (eventName.indexOf('on') !== 0) { - if (eventName[0] !== eventName[0].toUpperCase()) { - eventName = 'on' + eventName[0].toUpperCase() + eventName.substring(1); - } - else { - eventName = 'on' + eventName; - } - } - return eventName; - } - s.emitterEventListeners = { - - }; - s.emit = function (eventName) { - // Trigger callbacks - if (s.params[eventName]) { - s.params[eventName](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); - } - var i; - // Trigger events - if (s.emitterEventListeners[eventName]) { - for (i = 0; i < s.emitterEventListeners[eventName].length; i++) { - s.emitterEventListeners[eventName][i](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); - } - } - // Trigger plugins - if (s.callPlugins) s.callPlugins(eventName, arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); - }; - s.on = function (eventName, handler) { - eventName = normalizeEventName(eventName); - if (!s.emitterEventListeners[eventName]) s.emitterEventListeners[eventName] = []; - s.emitterEventListeners[eventName].push(handler); - return s; - }; - s.off = function (eventName, handler) { - var i; - eventName = normalizeEventName(eventName); - if (typeof handler === 'undefined') { - // Remove all handlers for such event - s.emitterEventListeners[eventName] = []; - return s; - } - if (!s.emitterEventListeners[eventName] || s.emitterEventListeners[eventName].length === 0) return; - for (i = 0; i < s.emitterEventListeners[eventName].length; i++) { - if(s.emitterEventListeners[eventName][i] === handler) s.emitterEventListeners[eventName].splice(i, 1); - } - return s; - }; - s.once = function (eventName, handler) { - eventName = normalizeEventName(eventName); - var _handler = function () { - handler(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]); - s.off(eventName, _handler); - }; - s.on(eventName, _handler); - return s; - }; - - // Accessibility tools - s.a11y = { - makeFocusable: function ($el) { - $el.attr('tabIndex', '0'); - return $el; - }, - addRole: function ($el, role) { - $el.attr('role', role); - return $el; - }, - - addLabel: function ($el, label) { - $el.attr('aria-label', label); - return $el; - }, - - disable: function ($el) { - $el.attr('aria-disabled', true); - return $el; - }, - - enable: function ($el) { - $el.attr('aria-disabled', false); - return $el; - }, - - onEnterKey: function (event) { - if (event.keyCode !== 13) return; - if ($(event.target).is(s.params.nextButton)) { - s.onClickNext(event); - if (s.isEnd) { - s.a11y.notify(s.params.lastSlideMessage); - } - else { - s.a11y.notify(s.params.nextSlideMessage); - } - } - else if ($(event.target).is(s.params.prevButton)) { - s.onClickPrev(event); - if (s.isBeginning) { - s.a11y.notify(s.params.firstSlideMessage); - } - else { - s.a11y.notify(s.params.prevSlideMessage); - } - } - if ($(event.target).is('.' + s.params.bulletClass)) { - $(event.target)[0].click(); - } - }, - - liveRegion: $(''), - - notify: function (message) { - var notification = s.a11y.liveRegion; - if (notification.length === 0) return; - notification.html(''); - notification.html(message); - }, - init: function () { - // Setup accessibility - if (s.params.nextButton) { - var nextButton = $(s.params.nextButton); - s.a11y.makeFocusable(nextButton); - s.a11y.addRole(nextButton, 'button'); - s.a11y.addLabel(nextButton, s.params.nextSlideMessage); - } - if (s.params.prevButton) { - var prevButton = $(s.params.prevButton); - s.a11y.makeFocusable(prevButton); - s.a11y.addRole(prevButton, 'button'); - s.a11y.addLabel(prevButton, s.params.prevSlideMessage); - } - - $(s.container).append(s.a11y.liveRegion); - }, - initPagination: function () { - if (s.params.pagination && s.params.paginationClickable && s.bullets && s.bullets.length) { - s.bullets.each(function () { - var bullet = $(this); - s.a11y.makeFocusable(bullet); - s.a11y.addRole(bullet, 'button'); - s.a11y.addLabel(bullet, s.params.paginationBulletMessage.replace(/{{index}}/, bullet.index() + 1)); - }); - } - }, - destroy: function () { - if (s.a11y.liveRegion && s.a11y.liveRegion.length > 0) s.a11y.liveRegion.remove(); - } - }; - - - /*========================= - Init/Destroy - ===========================*/ - s.init = function () { - if (s.params.loop) s.createLoop(); - s.updateContainerSize(); - s.updateSlidesSize(); - s.updatePagination(); - if (s.params.scrollbar && s.scrollbar) { - s.scrollbar.set(); - if (s.params.scrollbarDraggable) { - s.scrollbar.enableDraggable(); - } - } - if (s.params.effect !== 'slide' && s.effects[s.params.effect]) { - if (!s.params.loop) s.updateProgress(); - s.effects[s.params.effect].setTranslate(); - } - if (s.params.loop) { - s.slideTo(s.params.initialSlide + s.loopedSlides, 0, s.params.runCallbacksOnInit); - } - else { - s.slideTo(s.params.initialSlide, 0, s.params.runCallbacksOnInit); - if (s.params.initialSlide === 0) { - if (s.parallax && s.params.parallax) s.parallax.setTranslate(); - if (s.lazy && s.params.lazyLoading) { - s.lazy.load(); - s.lazy.initialImageLoaded = true; - } - } - } - s.attachEvents(); - if (s.params.observer && s.support.observer) { - s.initObservers(); - } - if (s.params.preloadImages && !s.params.lazyLoading) { - s.preloadImages(); - } - if (s.params.autoplay) { - s.startAutoplay(); - } - if (s.params.keyboardControl) { - if (s.enableKeyboardControl) s.enableKeyboardControl(); - } - if (s.params.mousewheelControl) { - if (s.enableMousewheelControl) s.enableMousewheelControl(); - } - if (s.params.hashnav) { - if (s.hashnav) s.hashnav.init(); - } - if (s.params.a11y && s.a11y) s.a11y.init(); - s.emit('onInit', s); - }; - - // Cleanup dynamic styles - s.cleanupStyles = function () { - // Container - s.container.removeClass(s.classNames.join(' ')).removeAttr('style'); - - // Wrapper - s.wrapper.removeAttr('style'); - - // Slides - if (s.slides && s.slides.length) { - s.slides - .removeClass([ - s.params.slideVisibleClass, - s.params.slideActiveClass, - s.params.slideNextClass, - s.params.slidePrevClass - ].join(' ')) - .removeAttr('style') - .removeAttr('data-swiper-column') - .removeAttr('data-swiper-row'); - } - - // Pagination/Bullets - if (s.paginationContainer && s.paginationContainer.length) { - s.paginationContainer.removeClass(s.params.paginationHiddenClass); - } - if (s.bullets && s.bullets.length) { - s.bullets.removeClass(s.params.bulletActiveClass); - } - - // Buttons - if (s.params.prevButton) $(s.params.prevButton).removeClass(s.params.buttonDisabledClass); - if (s.params.nextButton) $(s.params.nextButton).removeClass(s.params.buttonDisabledClass); - - // Scrollbar - if (s.params.scrollbar && s.scrollbar) { - if (s.scrollbar.track && s.scrollbar.track.length) s.scrollbar.track.removeAttr('style'); - if (s.scrollbar.drag && s.scrollbar.drag.length) s.scrollbar.drag.removeAttr('style'); - } - }; - - // Destroy - s.destroy = function (deleteInstance, cleanupStyles) { - // Detach evebts - s.detachEvents(); - // Stop autoplay - s.stopAutoplay(); - // Disable draggable - if (s.params.scrollbar && s.scrollbar) { - if (s.params.scrollbarDraggable) { - s.scrollbar.disableDraggable(); - } - } - // Destroy loop - if (s.params.loop) { - s.destroyLoop(); - } - // Cleanup styles - if (cleanupStyles) { - s.cleanupStyles(); - } - // Disconnect observer - s.disconnectObservers(); - // Disable keyboard/mousewheel - if (s.params.keyboardControl) { - if (s.disableKeyboardControl) s.disableKeyboardControl(); - } - if (s.params.mousewheelControl) { - if (s.disableMousewheelControl) s.disableMousewheelControl(); - } - // Disable a11y - if (s.params.a11y && s.a11y) s.a11y.destroy(); - // Destroy callback - s.emit('onDestroy'); - // Delete instance - if (deleteInstance !== false) s = null; - }; - - s.init(); - - - - // Return swiper instance - return s; - }; - - - /*================================================== - Prototype - ====================================================*/ - Swiper.prototype = { - isSafari: (function () { - var ua = navigator.userAgent.toLowerCase(); - return (ua.indexOf('safari') >= 0 && ua.indexOf('chrome') < 0 && ua.indexOf('android') < 0); - })(), - isUiWebView: /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent), - isArray: function (arr) { - return Object.prototype.toString.apply(arr) === '[object Array]'; - }, - /*================================================== - Browser - ====================================================*/ - browser: { - ie: window.navigator.pointerEnabled || window.navigator.msPointerEnabled, - ieTouch: (window.navigator.msPointerEnabled && window.navigator.msMaxTouchPoints > 1) || (window.navigator.pointerEnabled && window.navigator.maxTouchPoints > 1) - }, - /*================================================== - Devices - ====================================================*/ - device: (function () { - var ua = navigator.userAgent; - var android = ua.match(/(Android);?[\s\/]+([\d.]+)?/); - var ipad = ua.match(/(iPad).*OS\s([\d_]+)/); - var ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/); - var iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/); - return { - ios: ipad || iphone || ipod, - android: android - }; - })(), - /*================================================== - Feature Detection - ====================================================*/ - support: { - touch : (window.Modernizr && Modernizr.touch === true) || (function () { - return !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch); - })(), - - transforms3d : (window.Modernizr && Modernizr.csstransforms3d === true) || (function () { - var div = document.createElement('div').style; - return ('webkitPerspective' in div || 'MozPerspective' in div || 'OPerspective' in div || 'MsPerspective' in div || 'perspective' in div); - })(), - - flexbox: (function () { - var div = document.createElement('div').style; - var styles = ('alignItems webkitAlignItems webkitBoxAlign msFlexAlign mozBoxAlign webkitFlexDirection msFlexDirection mozBoxDirection mozBoxOrient webkitBoxDirection webkitBoxOrient').split(' '); - for (var i = 0; i < styles.length; i++) { - if (styles[i] in div) return true; - } - })(), - - observer: (function () { - return ('MutationObserver' in window || 'WebkitMutationObserver' in window); - })() - }, - /*================================================== - Plugins - ====================================================*/ - plugins: {} - }; - - - /*=========================== - Dom7 Library - ===========================*/ - var Dom7 = (function () { - var Dom7 = function (arr) { - var _this = this, i = 0; - // Create array-like object - for (i = 0; i < arr.length; i++) { - _this[i] = arr[i]; - } - _this.length = arr.length; - // Return collection with methods - return this; - }; - var $ = function (selector, context) { - var arr = [], i = 0; - if (selector && !context) { - if (selector instanceof Dom7) { - return selector; - } - } - if (selector) { - // String - if (typeof selector === 'string') { - var els, tempParent, html = selector.trim(); - if (html.indexOf('<') >= 0 && html.indexOf('>') >= 0) { - var toCreate = 'div'; - if (html.indexOf(':~]/)) { - // Pure ID selector - els = [document.getElementById(selector.split('#')[1])]; - } - else { - // Other selectors - els = (context || document).querySelectorAll(selector); - } - for (i = 0; i < els.length; i++) { - if (els[i]) arr.push(els[i]); - } - } - } - // Node/element - else if (selector.nodeType || selector === window || selector === document) { - arr.push(selector); - } - //Array of elements or instance of Dom - else if (selector.length > 0 && selector[0].nodeType) { - for (i = 0; i < selector.length; i++) { - arr.push(selector[i]); - } - } - } - return new Dom7(arr); - }; - Dom7.prototype = { - // Classes and attriutes - addClass: function (className) { - if (typeof className === 'undefined') { - return this; - } - var classes = className.split(' '); - for (var i = 0; i < classes.length; i++) { - for (var j = 0; j < this.length; j++) { - this[j].classList.add(classes[i]); - } - } - return this; - }, - removeClass: function (className) { - var classes = className.split(' '); - for (var i = 0; i < classes.length; i++) { - for (var j = 0; j < this.length; j++) { - this[j].classList.remove(classes[i]); - } - } - return this; - }, - hasClass: function (className) { - if (!this[0]) return false; - else return this[0].classList.contains(className); - }, - toggleClass: function (className) { - var classes = className.split(' '); - for (var i = 0; i < classes.length; i++) { - for (var j = 0; j < this.length; j++) { - this[j].classList.toggle(classes[i]); - } - } - return this; - }, - attr: function (attrs, value) { - if (arguments.length === 1 && typeof attrs === 'string') { - // Get attr - if (this[0]) return this[0].getAttribute(attrs); - else return undefined; - } - else { - // Set attrs - for (var i = 0; i < this.length; i++) { - if (arguments.length === 2) { - // String - this[i].setAttribute(attrs, value); - } - else { - // Object - for (var attrName in attrs) { - this[i][attrName] = attrs[attrName]; - this[i].setAttribute(attrName, attrs[attrName]); - } - } - } - return this; - } - }, - removeAttr: function (attr) { - for (var i = 0; i < this.length; i++) { - this[i].removeAttribute(attr); - } - return this; - }, - data: function (key, value) { - if (typeof value === 'undefined') { - // Get value - if (this[0]) { - var dataKey = this[0].getAttribute('data-' + key); - if (dataKey) return dataKey; - else if (this[0].dom7ElementDataStorage && (key in this[0].dom7ElementDataStorage)) return this[0].dom7ElementDataStorage[key]; - else return undefined; - } - else return undefined; - } - else { - // Set value - for (var i = 0; i < this.length; i++) { - var el = this[i]; - if (!el.dom7ElementDataStorage) el.dom7ElementDataStorage = {}; - el.dom7ElementDataStorage[key] = value; - } - return this; - } - }, - // Transforms - transform : function (transform) { - for (var i = 0; i < this.length; i++) { - var elStyle = this[i].style; - elStyle.webkitTransform = elStyle.MsTransform = elStyle.msTransform = elStyle.MozTransform = elStyle.OTransform = elStyle.transform = transform; - } - return this; - }, - transition: function (duration) { - if (typeof duration !== 'string') { - duration = duration + 'ms'; - } - for (var i = 0; i < this.length; i++) { - var elStyle = this[i].style; - elStyle.webkitTransitionDuration = elStyle.MsTransitionDuration = elStyle.msTransitionDuration = elStyle.MozTransitionDuration = elStyle.OTransitionDuration = elStyle.transitionDuration = duration; - } - return this; - }, - //Events - on: function (eventName, targetSelector, listener, capture) { - function handleLiveEvent(e) { - var target = e.target; - if ($(target).is(targetSelector)) listener.call(target, e); - else { - var parents = $(target).parents(); - for (var k = 0; k < parents.length; k++) { - if ($(parents[k]).is(targetSelector)) listener.call(parents[k], e); - } - } - } - var events = eventName.split(' '); - var i, j; - for (i = 0; i < this.length; i++) { - if (typeof targetSelector === 'function' || targetSelector === false) { - // Usual events - if (typeof targetSelector === 'function') { - listener = arguments[1]; - capture = arguments[2] || false; - } - for (j = 0; j < events.length; j++) { - this[i].addEventListener(events[j], listener, capture); - } - } - else { - //Live events - for (j = 0; j < events.length; j++) { - if (!this[i].dom7LiveListeners) this[i].dom7LiveListeners = []; - this[i].dom7LiveListeners.push({listener: listener, liveListener: handleLiveEvent}); - this[i].addEventListener(events[j], handleLiveEvent, capture); - } - } - } - - return this; - }, - off: function (eventName, targetSelector, listener, capture) { - var events = eventName.split(' '); - for (var i = 0; i < events.length; i++) { - for (var j = 0; j < this.length; j++) { - if (typeof targetSelector === 'function' || targetSelector === false) { - // Usual events - if (typeof targetSelector === 'function') { - listener = arguments[1]; - capture = arguments[2] || false; - } - this[j].removeEventListener(events[i], listener, capture); - } - else { - // Live event - if (this[j].dom7LiveListeners) { - for (var k = 0; k < this[j].dom7LiveListeners.length; k++) { - if (this[j].dom7LiveListeners[k].listener === listener) { - this[j].removeEventListener(events[i], this[j].dom7LiveListeners[k].liveListener, capture); - } - } - } - } - } - } - return this; - }, - once: function (eventName, targetSelector, listener, capture) { - var dom = this; - if (typeof targetSelector === 'function') { - targetSelector = false; - listener = arguments[1]; - capture = arguments[2]; - } - function proxy(e) { - listener(e); - dom.off(eventName, targetSelector, proxy, capture); - } - dom.on(eventName, targetSelector, proxy, capture); - }, - trigger: function (eventName, eventData) { - for (var i = 0; i < this.length; i++) { - var evt; - try { - evt = new window.CustomEvent(eventName, {detail: eventData, bubbles: true, cancelable: true}); - } - catch (e) { - evt = document.createEvent('Event'); - evt.initEvent(eventName, true, true); - evt.detail = eventData; - } - this[i].dispatchEvent(evt); - } - return this; - }, - transitionEnd: function (callback) { - var events = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'], - i, j, dom = this; - function fireCallBack(e) { - /*jshint validthis:true */ - if (e.target !== this) return; - callback.call(this, e); - for (i = 0; i < events.length; i++) { - dom.off(events[i], fireCallBack); - } - } - if (callback) { - for (i = 0; i < events.length; i++) { - dom.on(events[i], fireCallBack); - } - } - return this; - }, - // Sizing/Styles - width: function () { - if (this[0] === window) { - return window.innerWidth; - } - else { - if (this.length > 0) { - return parseFloat(this.css('width')); - } - else { - return null; - } - } - }, - outerWidth: function (includeMargins) { - if (this.length > 0) { - if (includeMargins) - return this[0].offsetWidth + parseFloat(this.css('margin-right')) + parseFloat(this.css('margin-left')); - else - return this[0].offsetWidth; - } - else return null; - }, - height: function () { - if (this[0] === window) { - return window.innerHeight; - } - else { - if (this.length > 0) { - return parseFloat(this.css('height')); - } - else { - return null; - } - } - }, - outerHeight: function (includeMargins) { - if (this.length > 0) { - if (includeMargins) - return this[0].offsetHeight + parseFloat(this.css('margin-top')) + parseFloat(this.css('margin-bottom')); - else - return this[0].offsetHeight; - } - else return null; - }, - offset: function () { - if (this.length > 0) { - var el = this[0]; - var box = el.getBoundingClientRect(); - var body = document.body; - var clientTop = el.clientTop || body.clientTop || 0; - var clientLeft = el.clientLeft || body.clientLeft || 0; - var scrollTop = window.pageYOffset || el.scrollTop; - var scrollLeft = window.pageXOffset || el.scrollLeft; - return { - top: box.top + scrollTop - clientTop, - left: box.left + scrollLeft - clientLeft - }; - } - else { - return null; - } - }, - css: function (props, value) { - var i; - if (arguments.length === 1) { - if (typeof props === 'string') { - if (this[0]) return window.getComputedStyle(this[0], null).getPropertyValue(props); - } - else { - for (i = 0; i < this.length; i++) { - for (var prop in props) { - this[i].style[prop] = props[prop]; - } - } - return this; - } - } - if (arguments.length === 2 && typeof props === 'string') { - for (i = 0; i < this.length; i++) { - this[i].style[props] = value; - } - return this; - } - return this; - }, - - //Dom manipulation - each: function (callback) { - for (var i = 0; i < this.length; i++) { - callback.call(this[i], i, this[i]); - } - return this; - }, - html: function (html) { - if (typeof html === 'undefined') { - return this[0] ? this[0].innerHTML : undefined; - } - else { - for (var i = 0; i < this.length; i++) { - this[i].innerHTML = html; - } - return this; - } - }, - is: function (selector) { - if (!this[0]) return false; - var compareWith, i; - if (typeof selector === 'string') { - var el = this[0]; - if (el === document) return selector === document; - if (el === window) return selector === window; - - if (el.matches) return el.matches(selector); - else if (el.webkitMatchesSelector) return el.webkitMatchesSelector(selector); - else if (el.mozMatchesSelector) return el.mozMatchesSelector(selector); - else if (el.msMatchesSelector) return el.msMatchesSelector(selector); - else { - compareWith = $(selector); - for (i = 0; i < compareWith.length; i++) { - if (compareWith[i] === this[0]) return true; - } - return false; - } - } - else if (selector === document) return this[0] === document; - else if (selector === window) return this[0] === window; - else { - if (selector.nodeType || selector instanceof Dom7) { - compareWith = selector.nodeType ? [selector] : selector; - for (i = 0; i < compareWith.length; i++) { - if (compareWith[i] === this[0]) return true; - } - return false; - } - return false; - } - - }, - index: function () { - if (this[0]) { - var child = this[0]; - var i = 0; - while ((child = child.previousSibling) !== null) { - if (child.nodeType === 1) i++; - } - return i; - } - else return undefined; - }, - eq: function (index) { - if (typeof index === 'undefined') return this; - var length = this.length; - var returnIndex; - if (index > length - 1) { - return new Dom7([]); - } - if (index < 0) { - returnIndex = length + index; - if (returnIndex < 0) return new Dom7([]); - else return new Dom7([this[returnIndex]]); - } - return new Dom7([this[index]]); - }, - append: function (newChild) { - var i, j; - for (i = 0; i < this.length; i++) { - if (typeof newChild === 'string') { - var tempDiv = document.createElement('div'); - tempDiv.innerHTML = newChild; - while (tempDiv.firstChild) { - this[i].appendChild(tempDiv.firstChild); - } - } - else if (newChild instanceof Dom7) { - for (j = 0; j < newChild.length; j++) { - this[i].appendChild(newChild[j]); - } - } - else { - this[i].appendChild(newChild); - } - } - return this; - }, - prepend: function (newChild) { - var i, j; - for (i = 0; i < this.length; i++) { - if (typeof newChild === 'string') { - var tempDiv = document.createElement('div'); - tempDiv.innerHTML = newChild; - for (j = tempDiv.childNodes.length - 1; j >= 0; j--) { - this[i].insertBefore(tempDiv.childNodes[j], this[i].childNodes[0]); - } - // this[i].insertAdjacentHTML('afterbegin', newChild); - } - else if (newChild instanceof Dom7) { - for (j = 0; j < newChild.length; j++) { - this[i].insertBefore(newChild[j], this[i].childNodes[0]); - } - } - else { - this[i].insertBefore(newChild, this[i].childNodes[0]); - } - } - return this; - }, - insertBefore: function (selector) { - var before = $(selector); - for (var i = 0; i < this.length; i++) { - if (before.length === 1) { - before[0].parentNode.insertBefore(this[i], before[0]); - } - else if (before.length > 1) { - for (var j = 0; j < before.length; j++) { - before[j].parentNode.insertBefore(this[i].cloneNode(true), before[j]); - } - } - } - }, - insertAfter: function (selector) { - var after = $(selector); - for (var i = 0; i < this.length; i++) { - if (after.length === 1) { - after[0].parentNode.insertBefore(this[i], after[0].nextSibling); - } - else if (after.length > 1) { - for (var j = 0; j < after.length; j++) { - after[j].parentNode.insertBefore(this[i].cloneNode(true), after[j].nextSibling); - } - } - } - }, - next: function (selector) { - if (this.length > 0) { - if (selector) { - if (this[0].nextElementSibling && $(this[0].nextElementSibling).is(selector)) return new Dom7([this[0].nextElementSibling]); - else return new Dom7([]); - } - else { - if (this[0].nextElementSibling) return new Dom7([this[0].nextElementSibling]); - else return new Dom7([]); - } - } - else return new Dom7([]); - }, - nextAll: function (selector) { - var nextEls = []; - var el = this[0]; - if (!el) return new Dom7([]); - while (el.nextElementSibling) { - var next = el.nextElementSibling; - if (selector) { - if($(next).is(selector)) nextEls.push(next); - } - else nextEls.push(next); - el = next; - } - return new Dom7(nextEls); - }, - prev: function (selector) { - if (this.length > 0) { - if (selector) { - if (this[0].previousElementSibling && $(this[0].previousElementSibling).is(selector)) return new Dom7([this[0].previousElementSibling]); - else return new Dom7([]); - } - else { - if (this[0].previousElementSibling) return new Dom7([this[0].previousElementSibling]); - else return new Dom7([]); - } - } - else return new Dom7([]); - }, - prevAll: function (selector) { - var prevEls = []; - var el = this[0]; - if (!el) return new Dom7([]); - while (el.previousElementSibling) { - var prev = el.previousElementSibling; - if (selector) { - if($(prev).is(selector)) prevEls.push(prev); - } - else prevEls.push(prev); - el = prev; - } - return new Dom7(prevEls); - }, - parent: function (selector) { - var parents = []; - for (var i = 0; i < this.length; i++) { - if (selector) { - if ($(this[i].parentNode).is(selector)) parents.push(this[i].parentNode); - } - else { - parents.push(this[i].parentNode); - } - } - return $($.unique(parents)); - }, - parents: function (selector) { - var parents = []; - for (var i = 0; i < this.length; i++) { - var parent = this[i].parentNode; - while (parent) { - if (selector) { - if ($(parent).is(selector)) parents.push(parent); - } - else { - parents.push(parent); - } - parent = parent.parentNode; - } - } - return $($.unique(parents)); - }, - find : function (selector) { - var foundElements = []; - for (var i = 0; i < this.length; i++) { - var found = this[i].querySelectorAll(selector); - for (var j = 0; j < found.length; j++) { - foundElements.push(found[j]); - } - } - return new Dom7(foundElements); - }, - children: function (selector) { - var children = []; - for (var i = 0; i < this.length; i++) { - var childNodes = this[i].childNodes; - - for (var j = 0; j < childNodes.length; j++) { - if (!selector) { - if (childNodes[j].nodeType === 1) children.push(childNodes[j]); - } - else { - if (childNodes[j].nodeType === 1 && $(childNodes[j]).is(selector)) children.push(childNodes[j]); - } - } - } - return new Dom7($.unique(children)); - }, - remove: function () { - for (var i = 0; i < this.length; i++) { - if (this[i].parentNode) this[i].parentNode.removeChild(this[i]); - } - return this; - }, - add: function () { - var dom = this; - var i, j; - for (i = 0; i < arguments.length; i++) { - var toAdd = $(arguments[i]); - for (j = 0; j < toAdd.length; j++) { - dom[dom.length] = toAdd[j]; - dom.length++; - } - } - return dom; - } - }; - $.fn = Dom7.prototype; - $.unique = function (arr) { - var unique = []; - for (var i = 0; i < arr.length; i++) { - if (unique.indexOf(arr[i]) === -1) unique.push(arr[i]); - } - return unique; - }; - - return $; - })(); - - - /*=========================== - Get Dom libraries - ===========================*/ - var swiperDomPlugins = ['jQuery', 'Zepto', 'Dom7']; - for (var i = 0; i < swiperDomPlugins.length; i++) { - if (window[swiperDomPlugins[i]]) { - addLibraryPlugin(window[swiperDomPlugins[i]]); - } - } - // Required DOM Plugins - var domLib; - if (typeof Dom7 === 'undefined') { - domLib = window.Dom7 || window.Zepto || window.jQuery; - } - else { - domLib = Dom7; - } - - /*=========================== - Add .swiper plugin from Dom libraries - ===========================*/ - function addLibraryPlugin(lib) { - lib.fn.swiper = function (params) { - var firstInstance; - lib(this).each(function () { - var s = new Swiper(this, params); - if (!firstInstance) firstInstance = s; - }); - return firstInstance; - }; - } - - if (domLib) { - if (!('transitionEnd' in domLib.fn)) { - domLib.fn.transitionEnd = function (callback) { - var events = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'], - i, j, dom = this; - function fireCallBack(e) { - /*jshint validthis:true */ - if (e.target !== this) return; - callback.call(this, e); - for (i = 0; i < events.length; i++) { - dom.off(events[i], fireCallBack); - } - } - if (callback) { - for (i = 0; i < events.length; i++) { - dom.on(events[i], fireCallBack); - } - } - return this; - }; - } - if (!('transform' in domLib.fn)) { - domLib.fn.transform = function (transform) { - for (var i = 0; i < this.length; i++) { - var elStyle = this[i].style; - elStyle.webkitTransform = elStyle.MsTransform = elStyle.msTransform = elStyle.MozTransform = elStyle.OTransform = elStyle.transform = transform; - } - return this; - }; - } - if (!('transition' in domLib.fn)) { - domLib.fn.transition = function (duration) { - if (typeof duration !== 'string') { - duration = duration + 'ms'; - } - for (var i = 0; i < this.length; i++) { - var elStyle = this[i].style; - elStyle.webkitTransitionDuration = elStyle.MsTransitionDuration = elStyle.msTransitionDuration = elStyle.MozTransitionDuration = elStyle.OTransitionDuration = elStyle.transitionDuration = duration; - } - return this; - }; - } - } - - ionic.views.Swiper = Swiper; -})(); - -(function(ionic) { -'use strict'; - - ionic.views.Toggle = ionic.views.View.inherit({ - initialize: function(opts) { - var self = this; - - this.el = opts.el; - this.checkbox = opts.checkbox; - this.track = opts.track; - this.handle = opts.handle; - this.openPercent = -1; - this.onChange = opts.onChange || function() {}; - - this.triggerThreshold = opts.triggerThreshold || 20; - - this.dragStartHandler = function(e) { - self.dragStart(e); - }; - this.dragHandler = function(e) { - self.drag(e); - }; - this.holdHandler = function(e) { - self.hold(e); - }; - this.releaseHandler = function(e) { - self.release(e); - }; - - this.dragStartGesture = ionic.onGesture('dragstart', this.dragStartHandler, this.el); - this.dragGesture = ionic.onGesture('drag', this.dragHandler, this.el); - this.dragHoldGesture = ionic.onGesture('hold', this.holdHandler, this.el); - this.dragReleaseGesture = ionic.onGesture('release', this.releaseHandler, this.el); - }, - - destroy: function() { - ionic.offGesture(this.dragStartGesture, 'dragstart', this.dragStartGesture); - ionic.offGesture(this.dragGesture, 'drag', this.dragGesture); - ionic.offGesture(this.dragHoldGesture, 'hold', this.holdHandler); - ionic.offGesture(this.dragReleaseGesture, 'release', this.releaseHandler); - }, - - tap: function() { - if(this.el.getAttribute('disabled') !== 'disabled') { - this.val( !this.checkbox.checked ); - } - }, - - dragStart: function(e) { - if(this.checkbox.disabled) return; - - this._dragInfo = { - width: this.el.offsetWidth, - left: this.el.offsetLeft, - right: this.el.offsetLeft + this.el.offsetWidth, - triggerX: this.el.offsetWidth / 2, - initialState: this.checkbox.checked - }; - - // Stop any parent dragging - e.gesture.srcEvent.preventDefault(); - - // Trigger hold styles - this.hold(e); - }, - - drag: function(e) { - var self = this; - if(!this._dragInfo) { return; } - - // Stop any parent dragging - e.gesture.srcEvent.preventDefault(); - - ionic.requestAnimationFrame(function () { - if (!self._dragInfo) { return; } - - var px = e.gesture.touches[0].pageX - self._dragInfo.left; - var mx = self._dragInfo.width - self.triggerThreshold; - - // The initial state was on, so "tend towards" on - if(self._dragInfo.initialState) { - if(px < self.triggerThreshold) { - self.setOpenPercent(0); - } else if(px > self._dragInfo.triggerX) { - self.setOpenPercent(100); - } - } else { - // The initial state was off, so "tend towards" off - if(px < self._dragInfo.triggerX) { - self.setOpenPercent(0); - } else if(px > mx) { - self.setOpenPercent(100); - } - } - }); - }, - - endDrag: function() { - this._dragInfo = null; - }, - - hold: function() { - this.el.classList.add('dragging'); - }, - release: function(e) { - this.el.classList.remove('dragging'); - this.endDrag(e); - }, - - - setOpenPercent: function(openPercent) { - // only make a change if the new open percent has changed - if(this.openPercent < 0 || (openPercent < (this.openPercent - 3) || openPercent > (this.openPercent + 3) ) ) { - this.openPercent = openPercent; - - if(openPercent === 0) { - this.val(false); - } else if(openPercent === 100) { - this.val(true); - } else { - var openPixel = Math.round( (openPercent / 100) * this.track.offsetWidth - (this.handle.offsetWidth) ); - openPixel = (openPixel < 1 ? 0 : openPixel); - this.handle.style[ionic.CSS.TRANSFORM] = 'translate3d(' + openPixel + 'px,0,0)'; - } - } - }, - - val: function(value) { - if(value === true || value === false) { - if(this.handle.style[ionic.CSS.TRANSFORM] !== "") { - this.handle.style[ionic.CSS.TRANSFORM] = ""; - } - this.checkbox.checked = value; - this.openPercent = (value ? 100 : 0); - this.onChange && this.onChange(); - } - return this.checkbox.checked; - } - - }); - -})(ionic); - -})(); \ No newline at end of file -- cgit v1.2.3