Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*!
- * ionic.bundle.js is a concatenation of:
- * ionic.js, angular.js, angular-animate.js,
- * angular-sanitize.js, angular-ui-router.js,
- * and ionic-angular.js
- */
- /*!
- * Copyright 2014 Drifty Co.
- * http://drifty.com/
- *
- * Ionic, v1.0.1
- * 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.
- *
- */
- /* IMPORTANT
- This file has been modified in the following places, search in comments for the *CHANGE* tag
- */
- (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.0.1';
- (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
- };
- },
- /**
- * @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 <max@drifty.com>
- *
- * 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`, <br/>
- * `dragleft`, `dragright`, `swipe`, `swipeup`, `swipedown`, `swipeleft`, `swiperight`, <br/>
- * `transform`, `transformstart`, `transformend`, `rotate`, `pinch`, `pinchin`, `pinchout`, </br>
- * `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: 1
- },
- 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 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#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('Android') > 0) {
- platformName = ANDROID;
- } else if (/iPhone|iPad|iPod/.test(self.ua)) {
- platformName = IOS;
- } else if (self.ua.indexOf('Windows Phone') > -1) {
- platformName = WINDOWS_PHONE;
- } 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]);
- }
- }
- },
- // Check if the platform is the one detected by cordova
- 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;
- // 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');
- });
- }
- })(this, document, ionic);
- (function(document, ionic) {
- 'use strict';
- // Ionic CSS polyfills
- ionic.CSS = {};
- (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;
- }
- }
- // 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';
- })();
- // 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
- * <div data-tap-disabled="true">
- * <div id="google-map"></div>
- * </div>
- * ```
- *
- * ### 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('pointcancel');
- 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));
- },
- 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 (!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('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) {
- void 0;
- e.stopPropagation();
- if ((!ionic.tap.isTextInput(e.target) || tapLastTouchTarget !== e.target) && !(/^(select|option)$/i).test(e.target.tagName)) {
- // 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.
- 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) || (/^(select|option)$/i).test(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
- e.preventDefault();
- }
- }
- }
- function tapTouchEnd(e) {
- //console.log('touchend ' + Date.now());
- if (tapIgnoreEvent(e)) return;
- tapEnableTouchEvents();
- if (!tapHasPointerMoved(e)) {
- tapClick(e);
- if ((/^(select|option)$/i).test(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.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)$/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)) {
- void 0;
- 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
- void 0;
- 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;
- }
- 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
- * <div class="hide-on-keyboard-open">
- * <div id="google-map"></div>
- * </div>
- * ```
- *
- * 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
- * `<preference name="Fullscreen" value="true" />` 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
- * <activity android:windowSoftInputMode="adjustResize">
- *
- * ```
- *
- * ### 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 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))) {
- 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) {
- ionic.trigger('resetScrollView', {
- target: keyboardActiveElement
- }, 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;
- }
- /**
- * 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;
- }
- };
- })(this);
- /*
- * 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;
- };
- 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) { Alan Kaiser 5/13/15.... scroll no matter what
- if (!self.options.freeze) {
- 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);
- } 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);
- }
- // *CHANGE* chrome scrolling fix. removed 'else' statement
- // 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 <code>null</code>
- * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
- * @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 depreciated = 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;
- 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);
- 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 = NOOP;
- self.__initEventHandlers();
- },
- /** Methods not used in native scrolling */
- __callback: function() { depreciated('__callback'); },
- zoomTo: function() { depreciated('zoomTo'); },
- zoomBy: function() { depreciated('zoomBy'); },
- activatePullToRefresh: function() { depreciated('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 <code>null</code>
- * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
- * @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 <code>null</code>
- * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
- * @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;
- }
- 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.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.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;
- /**
- * 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";
- //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(){
- // 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()) {
- //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 = "";
- }
- self.resize();
- };
- container.addEventListener('scroll', self.onScroll);
- //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);
- },
- __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);
- 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;
- // 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,
- touch: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch,
- 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 (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.type == 'mousedown' || event.type == 'mouseup' || event.type == 'mousemove') {
- event.touches = [{
- pageX: event.pageX,
- pageY: event.pageY
- }];
- }
- switch (event.type) {
- case 'mousedown': this.start(event); break;
- case 'touchstart': this.start(event); break;
- case 'touchmove': this.touchmove(event); break;
- case 'mousemove': this.touchmove(event); break;
- case 'touchend': offloadFn(this.end(event)); break;
- case 'mouseup': 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) {
- 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
- if(browser.touch) {
- element.addEventListener('touchmove', this, false);
- element.addEventListener('touchend', this, false);
- }
- // *CHANGE* chrome scrolling fix. removed 'else' statement
- element.addEventListener('mousemove', this, false);
- element.addEventListener('mouseup', this, false);
- document.addEventListener('mouseup', this, false);
- },
- touchmove: function(event) {
- // ensure swiping with one touch and not pinching
- // ensure sliding is enabled
- if (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 {
- 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
- // 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
- if(browser.touch) {
- element.removeEventListener('touchmove', events, false);
- element.removeEventListener('touchend', events, false);
- }
- // *CHANGE* chrome scrolling fix. removed 'else' statement
- element.removeEventListener('mousemove', events, false);
- element.removeEventListener('mouseup', events, false);
- document.removeEventListener('mouseup', 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('touchstart', 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
- if (browser.touch) {
- element.addEventListener('touchstart', events, false);
- }
- // *CHANGE* chrome scrolling fix. removed 'else' statement
- element.addEventListener('mousedown', 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);
- (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);
- })();
- /*!
- * ionic.bundle.js is a concatenation of:
- * ionic.js, angular.js, angular-animate.js,
- * angular-sanitize.js, angular-ui-router.js,
- * and ionic-angular.js
- */
- /**
- * @license AngularJS v1.3.13
- * (c) 2010-2014 Google, Inc. http://angularjs.org
- * License: MIT
- */
- (function(window, document, undefined) {'use strict';
- /**
- * @description
- *
- * This object provides a utility for producing rich Error messages within
- * Angular. It can be called as follows:
- *
- * var exampleMinErr = minErr('example');
- * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
- *
- * The above creates an instance of minErr in the example namespace. The
- * resulting error will have a namespaced error code of example.one. The
- * resulting error will replace {0} with the value of foo, and {1} with the
- * value of bar. The object is not restricted in the number of arguments it can
- * take.
- *
- * If fewer arguments are specified than necessary for interpolation, the extra
- * interpolation markers will be preserved in the final string.
- *
- * Since data will be parsed statically during a build step, some restrictions
- * are applied with respect to how minErr instances are created and called.
- * Instances should have names of the form namespaceMinErr for a minErr created
- * using minErr('namespace') . Error codes, namespaces and template strings
- * should all be static strings, not variables or general expressions.
- *
- * @param {string} module The namespace to use for the new minErr instance.
- * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
- * error from returned function, for cases when a particular type of error is useful.
- * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
- */
- function minErr(module, ErrorConstructor) {
- ErrorConstructor = ErrorConstructor || Error;
- return function() {
- var code = arguments[0],
- prefix = '[' + (module ? module + ':' : '') + code + '] ',
- template = arguments[1],
- templateArgs = arguments,
- message, i;
- message = prefix + template.replace(/\{\d+\}/g, function(match) {
- var index = +match.slice(1, -1), arg;
- if (index + 2 < templateArgs.length) {
- return toDebugString(templateArgs[index + 2]);
- }
- return match;
- });
- message = message + '\nhttp://errors.angularjs.org/1.3.13/' +
- (module ? module + '/' : '') + code;
- for (i = 2; i < arguments.length; i++) {
- message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
- encodeURIComponent(toDebugString(arguments[i]));
- }
- return new ErrorConstructor(message);
- };
- }
- /* We need to tell jshint what variables are being exported */
- /* global angular: true,
- msie: true,
- jqLite: true,
- jQuery: true,
- slice: true,
- splice: true,
- push: true,
- toString: true,
- ngMinErr: true,
- angularModule: true,
- uid: true,
- REGEX_STRING_REGEXP: true,
- VALIDITY_STATE_PROPERTY: true,
- lowercase: true,
- uppercase: true,
- manualLowercase: true,
- manualUppercase: true,
- nodeName_: true,
- isArrayLike: true,
- forEach: true,
- sortedKeys: true,
- forEachSorted: true,
- reverseParams: true,
- nextUid: true,
- setHashKey: true,
- extend: true,
- int: true,
- inherit: true,
- noop: true,
- identity: true,
- valueFn: true,
- isUndefined: true,
- isDefined: true,
- isObject: true,
- isString: true,
- isNumber: true,
- isDate: true,
- isArray: true,
- isFunction: true,
- isRegExp: true,
- isWindow: true,
- isScope: true,
- isFile: true,
- isFormData: true,
- isBlob: true,
- isBoolean: true,
- isPromiseLike: true,
- trim: true,
- escapeForRegexp: true,
- isElement: true,
- makeMap: true,
- includes: true,
- arrayRemove: true,
- copy: true,
- shallowCopy: true,
- equals: true,
- csp: true,
- concat: true,
- sliceArgs: true,
- bind: true,
- toJsonReplacer: true,
- toJson: true,
- fromJson: true,
- startingTag: true,
- tryDecodeURIComponent: true,
- parseKeyValue: true,
- toKeyValue: true,
- encodeUriSegment: true,
- encodeUriQuery: true,
- angularInit: true,
- bootstrap: true,
- getTestability: true,
- snake_case: true,
- bindJQuery: true,
- assertArg: true,
- assertArgFn: true,
- assertNotHasOwnProperty: true,
- getter: true,
- getBlockNodes: true,
- hasOwnProperty: true,
- createMap: true,
- NODE_TYPE_ELEMENT: true,
- NODE_TYPE_TEXT: true,
- NODE_TYPE_COMMENT: true,
- NODE_TYPE_DOCUMENT: true,
- NODE_TYPE_DOCUMENT_FRAGMENT: true,
- */
- ////////////////////////////////////
- /**
- * @ngdoc module
- * @name ng
- * @module ng
- * @description
- *
- * # ng (core module)
- * The ng module is loaded by default when an AngularJS application is started. The module itself
- * contains the essential components for an AngularJS application to function. The table below
- * lists a high level breakdown of each of the services/factories, filters, directives and testing
- * components available within this core module.
- *
- * <div doc-module-components="ng"></div>
- */
- var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
- // The name of a form control's ValidityState property.
- // This is used so that it's possible for internal tests to create mock ValidityStates.
- var VALIDITY_STATE_PROPERTY = 'validity';
- /**
- * @ngdoc function
- * @name angular.lowercase
- * @module ng
- * @kind function
- *
- * @description Converts the specified string to lowercase.
- * @param {string} string String to be converted to lowercase.
- * @returns {string} Lowercased string.
- */
- var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
- var hasOwnProperty = Object.prototype.hasOwnProperty;
- /**
- * @ngdoc function
- * @name angular.uppercase
- * @module ng
- * @kind function
- *
- * @description Converts the specified string to uppercase.
- * @param {string} string String to be converted to uppercase.
- * @returns {string} Uppercased string.
- */
- var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
- var manualLowercase = function(s) {
- /* jshint bitwise: false */
- return isString(s)
- ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
- : s;
- };
- var manualUppercase = function(s) {
- /* jshint bitwise: false */
- return isString(s)
- ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
- : s;
- };
- // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
- // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
- // with correct but slower alternatives.
- if ('i' !== 'I'.toLowerCase()) {
- lowercase = manualLowercase;
- uppercase = manualUppercase;
- }
- var
- msie, // holds major version number for IE, or NaN if UA is not IE.
- jqLite, // delay binding since jQuery could be loaded after us.
- jQuery, // delay binding
- slice = [].slice,
- splice = [].splice,
- push = [].push,
- toString = Object.prototype.toString,
- ngMinErr = minErr('ng'),
- /** @name angular */
- angular = window.angular || (window.angular = {}),
- angularModule,
- uid = 0;
- /**
- * documentMode is an IE-only property
- * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
- */
- msie = document.documentMode;
- /**
- * @private
- * @param {*} obj
- * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
- * String ...)
- */
- function isArrayLike(obj) {
- if (obj == null || isWindow(obj)) {
- return false;
- }
- var length = obj.length;
- if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
- return true;
- }
- return isString(obj) || isArray(obj) || length === 0 ||
- typeof length === 'number' && length > 0 && (length - 1) in obj;
- }
- /**
- * @ngdoc function
- * @name angular.forEach
- * @module ng
- * @kind function
- *
- * @description
- * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
- * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
- * is the value of an object property or an array element, `key` is the object property key or
- * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
- *
- * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
- * using the `hasOwnProperty` method.
- *
- * Unlike ES262's
- * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
- * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
- * return the value provided.
- *
- ```js
- var values = {name: 'misko', gender: 'male'};
- var log = [];
- angular.forEach(values, function(value, key) {
- this.push(key + ': ' + value);
- }, log);
- expect(log).toEqual(['name: misko', 'gender: male']);
- ```
- *
- * @param {Object|Array} obj Object to iterate over.
- * @param {Function} iterator Iterator function.
- * @param {Object=} context Object to become context (`this`) for the iterator function.
- * @returns {Object|Array} Reference to `obj`.
- */
- function forEach(obj, iterator, context) {
- var key, length;
- if (obj) {
- if (isFunction(obj)) {
- for (key in obj) {
- // Need to check if hasOwnProperty exists,
- // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
- if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
- iterator.call(context, obj[key], key, obj);
- }
- }
- } else if (isArray(obj) || isArrayLike(obj)) {
- var isPrimitive = typeof obj !== 'object';
- for (key = 0, length = obj.length; key < length; key++) {
- if (isPrimitive || key in obj) {
- iterator.call(context, obj[key], key, obj);
- }
- }
- } else if (obj.forEach && obj.forEach !== forEach) {
- obj.forEach(iterator, context, obj);
- } else {
- for (key in obj) {
- if (obj.hasOwnProperty(key)) {
- iterator.call(context, obj[key], key, obj);
- }
- }
- }
- }
- return obj;
- }
- function sortedKeys(obj) {
- return Object.keys(obj).sort();
- }
- function forEachSorted(obj, iterator, context) {
- var keys = sortedKeys(obj);
- for (var i = 0; i < keys.length; i++) {
- iterator.call(context, obj[keys[i]], keys[i]);
- }
- return keys;
- }
- /**
- * when using forEach the params are value, key, but it is often useful to have key, value.
- * @param {function(string, *)} iteratorFn
- * @returns {function(*, string)}
- */
- function reverseParams(iteratorFn) {
- return function(value, key) { iteratorFn(key, value); };
- }
- /**
- * A consistent way of creating unique IDs in angular.
- *
- * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
- * we hit number precision issues in JavaScript.
- *
- * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
- *
- * @returns {number} an unique alpha-numeric string
- */
- function nextUid() {
- return ++uid;
- }
- /**
- * Set or clear the hashkey for an object.
- * @param obj object
- * @param h the hashkey (!truthy to delete the hashkey)
- */
- function setHashKey(obj, h) {
- if (h) {
- obj.$$hashKey = h;
- } else {
- delete obj.$$hashKey;
- }
- }
- /**
- * @ngdoc function
- * @name angular.extend
- * @module ng
- * @kind function
- *
- * @description
- * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
- * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
- * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
- * Note: Keep in mind that `angular.extend` does not support recursive merge (deep copy).
- *
- * @param {Object} dst Destination object.
- * @param {...Object} src Source object(s).
- * @returns {Object} Reference to `dst`.
- */
- function extend(dst) {
- var h = dst.$$hashKey;
- for (var i = 1, ii = arguments.length; i < ii; i++) {
- var obj = arguments[i];
- if (obj) {
- var keys = Object.keys(obj);
- for (var j = 0, jj = keys.length; j < jj; j++) {
- var key = keys[j];
- dst[key] = obj[key];
- }
- }
- }
- setHashKey(dst, h);
- return dst;
- }
- function int(str) {
- return parseInt(str, 10);
- }
- function inherit(parent, extra) {
- return extend(Object.create(parent), extra);
- }
- /**
- * @ngdoc function
- * @name angular.noop
- * @module ng
- * @kind function
- *
- * @description
- * A function that performs no operations. This function can be useful when writing code in the
- * functional style.
- ```js
- function foo(callback) {
- var result = calculateResult();
- (callback || angular.noop)(result);
- }
- ```
- */
- function noop() {}
- noop.$inject = [];
- /**
- * @ngdoc function
- * @name angular.identity
- * @module ng
- * @kind function
- *
- * @description
- * A function that returns its first argument. This function is useful when writing code in the
- * functional style.
- *
- ```js
- function transformer(transformationFn, value) {
- return (transformationFn || angular.identity)(value);
- };
- ```
- * @param {*} value to be returned.
- * @returns {*} the value passed in.
- */
- function identity($) {return $;}
- identity.$inject = [];
- function valueFn(value) {return function() {return value;};}
- /**
- * @ngdoc function
- * @name angular.isUndefined
- * @module ng
- * @kind function
- *
- * @description
- * Determines if a reference is undefined.
- *
- * @param {*} value Reference to check.
- * @returns {boolean} True if `value` is undefined.
- */
- function isUndefined(value) {return typeof value === 'undefined';}
- /**
- * @ngdoc function
- * @name angular.isDefined
- * @module ng
- * @kind function
- *
- * @description
- * Determines if a reference is defined.
- *
- * @param {*} value Reference to check.
- * @returns {boolean} True if `value` is defined.
- */
- function isDefined(value) {return typeof value !== 'undefined';}
- /**
- * @ngdoc function
- * @name angular.isObject
- * @module ng
- * @kind function
- *
- * @description
- * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
- * considered to be objects. Note that JavaScript arrays are objects.
- *
- * @param {*} value Reference to check.
- * @returns {boolean} True if `value` is an `Object` but not `null`.
- */
- function isObject(value) {
- // http://jsperf.com/isobject4
- return value !== null && typeof value === 'object';
- }
- /**
- * @ngdoc function
- * @name angular.isString
- * @module ng
- * @kind function
- *
- * @description
- * Determines if a reference is a `String`.
- *
- * @param {*} value Reference to check.
- * @returns {boolean} True if `value` is a `String`.
- */
- function isString(value) {return typeof value === 'string';}
- /**
- * @ngdoc function
- * @name angular.isNumber
- * @module ng
- * @kind function
- *
- * @description
- * Determines if a reference is a `Number`.
- *
- * @param {*} value Reference to check.
- * @returns {boolean} True if `value` is a `Number`.
- */
- function isNumber(value) {return typeof value === 'number';}
- /**
- * @ngdoc function
- * @name angular.isDate
- * @module ng
- * @kind function
- *
- * @description
- * Determines if a value is a date.
- *
- * @param {*} value Reference to check.
- * @returns {boolean} True if `value` is a `Date`.
- */
- function isDate(value) {
- return toString.call(value) === '[object Date]';
- }
- /**
- * @ngdoc function
- * @name angular.isArray
- * @module ng
- * @kind function
- *
- * @description
- * Determines if a reference is an `Array`.
- *
- * @param {*} value Reference to check.
- * @returns {boolean} True if `value` is an `Array`.
- */
- var isArray = Array.isArray;
- /**
- * @ngdoc function
- * @name angular.isFunction
- * @module ng
- * @kind function
- *
- * @description
- * Determines if a reference is a `Function`.
- *
- * @param {*} value Reference to check.
- * @returns {boolean} True if `value` is a `Function`.
- */
- function isFunction(value) {return typeof value === 'function';}
- /**
- * Determines if a value is a regular expression object.
- *
- * @private
- * @param {*} value Reference to check.
- * @returns {boolean} True if `value` is a `RegExp`.
- */
- function isRegExp(value) {
- return toString.call(value) === '[object RegExp]';
- }
- /**
- * Checks if `obj` is a window object.
- *
- * @private
- * @param {*} obj Object to check
- * @returns {boolean} True if `obj` is a window obj.
- */
- function isWindow(obj) {
- return obj && obj.window === obj;
- }
- function isScope(obj) {
- return obj && obj.$evalAsync && obj.$watch;
- }
- function isFile(obj) {
- return toString.call(obj) === '[object File]';
- }
- function isFormData(obj) {
- return toString.call(obj) === '[object FormData]';
- }
- function isBlob(obj) {
- return toString.call(obj) === '[object Blob]';
- }
- function isBoolean(value) {
- return typeof value === 'boolean';
- }
- function isPromiseLike(obj) {
- return obj && isFunction(obj.then);
- }
- var trim = function(value) {
- return isString(value) ? value.trim() : value;
- };
- // Copied from:
- // http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
- // Prereq: s is a string.
- var escapeForRegexp = function(s) {
- return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
- replace(/\x08/g, '\\x08');
- };
- /**
- * @ngdoc function
- * @name angular.isElement
- * @module ng
- * @kind function
- *
- * @description
- * Determines if a reference is a DOM element (or wrapped jQuery element).
- *
- * @param {*} value Reference to check.
- * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
- */
- function isElement(node) {
- return !!(node &&
- (node.nodeName // we are a direct element
- || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API
- }
- /**
- * @param str 'key1,key2,...'
- * @returns {object} in the form of {key1:true, key2:true, ...}
- */
- function makeMap(str) {
- var obj = {}, items = str.split(","), i;
- for (i = 0; i < items.length; i++)
- obj[items[i]] = true;
- return obj;
- }
- function nodeName_(element) {
- return lowercase(element.nodeName || (element[0] && element[0].nodeName));
- }
- function includes(array, obj) {
- return Array.prototype.indexOf.call(array, obj) != -1;
- }
- function arrayRemove(array, value) {
- var index = array.indexOf(value);
- if (index >= 0)
- array.splice(index, 1);
- return value;
- }
- /**
- * @ngdoc function
- * @name angular.copy
- * @module ng
- * @kind function
- *
- * @description
- * Creates a deep copy of `source`, which should be an object or an array.
- *
- * * If no destination is supplied, a copy of the object or array is created.
- * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
- * are deleted and then all elements/properties from the source are copied to it.
- * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
- * * If `source` is identical to 'destination' an exception will be thrown.
- *
- * @param {*} source The source that will be used to make a copy.
- * Can be any type, including primitives, `null`, and `undefined`.
- * @param {(Object|Array)=} destination Destination into which the source is copied. If
- * provided, must be of the same type as `source`.
- * @returns {*} The copy or updated `destination`, if `destination` was specified.
- *
- * @example
- <example module="copyExample">
- <file name="index.html">
- <div ng-controller="ExampleController">
- <form novalidate class="simple-form">
- Name: <input type="text" ng-model="user.name" /><br />
- E-mail: <input type="email" ng-model="user.email" /><br />
- Gender: <input type="radio" ng-model="user.gender" value="male" />male
- <input type="radio" ng-model="user.gender" value="female" />female<br />
- <button ng-click="reset()">RESET</button>
- <button ng-click="update(user)">SAVE</button>
- </form>
- <pre>form = {{user | json}}</pre>
- <pre>master = {{master | json}}</pre>
- </div>
- <script>
- angular.module('copyExample', [])
- .controller('ExampleController', ['$scope', function($scope) {
- $scope.master= {};
- $scope.update = function(user) {
- // Example with 1 argument
- $scope.master= angular.copy(user);
- };
- $scope.reset = function() {
- // Example with 2 arguments
- angular.copy($scope.master, $scope.user);
- };
- $scope.reset();
- }]);
- </script>
- </file>
- </example>
- */
- function copy(source, destination, stackSource, stackDest) {
- if (isWindow(source) || isScope(source)) {
- throw ngMinErr('cpws',
- "Can't copy! Making copies of Window or Scope instances is not supported.");
- }
- if (!destination) {
- destination = source;
- if (source) {
- if (isArray(source)) {
- destination = copy(source, [], stackSource, stackDest);
- } else if (isDate(source)) {
- destination = new Date(source.getTime());
- } else if (isRegExp(source)) {
- destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
- destination.lastIndex = source.lastIndex;
- } else if (isObject(source)) {
- var emptyObject = Object.create(Object.getPrototypeOf(source));
- destination = copy(source, emptyObject, stackSource, stackDest);
- }
- }
- } else {
- if (source === destination) throw ngMinErr('cpi',
- "Can't copy! Source and destination are identical.");
- stackSource = stackSource || [];
- stackDest = stackDest || [];
- if (isObject(source)) {
- var index = stackSource.indexOf(source);
- if (index !== -1) return stackDest[index];
- stackSource.push(source);
- stackDest.push(destination);
- }
- var result;
- if (isArray(source)) {
- destination.length = 0;
- for (var i = 0; i < source.length; i++) {
- result = copy(source[i], null, stackSource, stackDest);
- if (isObject(source[i])) {
- stackSource.push(source[i]);
- stackDest.push(result);
- }
- destination.push(result);
- }
- } else {
- var h = destination.$$hashKey;
- if (isArray(destination)) {
- destination.length = 0;
- } else {
- forEach(destination, function(value, key) {
- delete destination[key];
- });
- }
- for (var key in source) {
- if (source.hasOwnProperty(key)) {
- result = copy(source[key], null, stackSource, stackDest);
- if (isObject(source[key])) {
- stackSource.push(source[key]);
- stackDest.push(result);
- }
- destination[key] = result;
- }
- }
- setHashKey(destination,h);
- }
- }
- return destination;
- }
- /**
- * Creates a shallow copy of an object, an array or a primitive.
- *
- * Assumes that there are no proto properties for objects.
- */
- function shallowCopy(src, dst) {
- if (isArray(src)) {
- dst = dst || [];
- for (var i = 0, ii = src.length; i < ii; i++) {
- dst[i] = src[i];
- }
- } else if (isObject(src)) {
- dst = dst || {};
- for (var key in src) {
- if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
- dst[key] = src[key];
- }
- }
- }
- return dst || src;
- }
- /**
- * @ngdoc function
- * @name angular.equals
- * @module ng
- * @kind function
- *
- * @description
- * Determines if two objects or two values are equivalent. Supports value types, regular
- * expressions, arrays and objects.
- *
- * Two objects or values are considered equivalent if at least one of the following is true:
- *
- * * Both objects or values pass `===` comparison.
- * * Both objects or values are of the same type and all of their properties are equal by
- * comparing them with `angular.equals`.
- * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
- * * Both values represent the same regular expression (In JavaScript,
- * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
- * representation matches).
- *
- * During a property comparison, properties of `function` type and properties with names
- * that begin with `$` are ignored.
- *
- * Scope and DOMWindow objects are being compared only by identify (`===`).
- *
- * @param {*} o1 Object or value to compare.
- * @param {*} o2 Object or value to compare.
- * @returns {boolean} True if arguments are equal.
- */
- function equals(o1, o2) {
- if (o1 === o2) return true;
- if (o1 === null || o2 === null) return false;
- if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
- var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
- if (t1 == t2) {
- if (t1 == 'object') {
- if (isArray(o1)) {
- if (!isArray(o2)) return false;
- if ((length = o1.length) == o2.length) {
- for (key = 0; key < length; key++) {
- if (!equals(o1[key], o2[key])) return false;
- }
- return true;
- }
- } else if (isDate(o1)) {
- if (!isDate(o2)) return false;
- return equals(o1.getTime(), o2.getTime());
- } else if (isRegExp(o1) && isRegExp(o2)) {
- return o1.toString() == o2.toString();
- } else {
- if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false;
- keySet = {};
- for (key in o1) {
- if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
- if (!equals(o1[key], o2[key])) return false;
- keySet[key] = true;
- }
- for (key in o2) {
- if (!keySet.hasOwnProperty(key) &&
- key.charAt(0) !== '$' &&
- o2[key] !== undefined &&
- !isFunction(o2[key])) return false;
- }
- return true;
- }
- }
- }
- return false;
- }
- var csp = function() {
- if (isDefined(csp.isActive_)) return csp.isActive_;
- var active = !!(document.querySelector('[ng-csp]') ||
- document.querySelector('[data-ng-csp]'));
- if (!active) {
- try {
- /* jshint -W031, -W054 */
- new Function('');
- /* jshint +W031, +W054 */
- } catch (e) {
- active = true;
- }
- }
- return (csp.isActive_ = active);
- };
- function concat(array1, array2, index) {
- return array1.concat(slice.call(array2, index));
- }
- function sliceArgs(args, startIndex) {
- return slice.call(args, startIndex || 0);
- }
- /* jshint -W101 */
- /**
- * @ngdoc function
- * @name angular.bind
- * @module ng
- * @kind function
- *
- * @description
- * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
- * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
- * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
- * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
- *
- * @param {Object} self Context which `fn` should be evaluated in.
- * @param {function()} fn Function to be bound.
- * @param {...*} args Optional arguments to be prebound to the `fn` function call.
- * @returns {function()} Function that wraps the `fn` with all the specified bindings.
- */
- /* jshint +W101 */
- function bind(self, fn) {
- var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
- if (isFunction(fn) && !(fn instanceof RegExp)) {
- return curryArgs.length
- ? function() {
- return arguments.length
- ? fn.apply(self, concat(curryArgs, arguments, 0))
- : fn.apply(self, curryArgs);
- }
- : function() {
- return arguments.length
- ? fn.apply(self, arguments)
- : fn.call(self);
- };
- } else {
- // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
- return fn;
- }
- }
- function toJsonReplacer(key, value) {
- var val = value;
- if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
- val = undefined;
- } else if (isWindow(value)) {
- val = '$WINDOW';
- } else if (value && document === value) {
- val = '$DOCUMENT';
- } else if (isScope(value)) {
- val = '$SCOPE';
- }
- return val;
- }
- /**
- * @ngdoc function
- * @name angular.toJson
- * @module ng
- * @kind function
- *
- * @description
- * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
- * stripped since angular uses this notation internally.
- *
- * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
- * @param {boolean|number=} pretty If set to true, the JSON output will contain newlines and whitespace.
- * If set to an integer, the JSON output will contain that many spaces per indentation (the default is 2).
- * @returns {string|undefined} JSON-ified string representing `obj`.
- */
- function toJson(obj, pretty) {
- if (typeof obj === 'undefined') return undefined;
- if (!isNumber(pretty)) {
- pretty = pretty ? 2 : null;
- }
- return JSON.stringify(obj, toJsonReplacer, pretty);
- }
- /**
- * @ngdoc function
- * @name angular.fromJson
- * @module ng
- * @kind function
- *
- * @description
- * Deserializes a JSON string.
- *
- * @param {string} json JSON string to deserialize.
- * @returns {Object|Array|string|number} Deserialized JSON string.
- */
- function fromJson(json) {
- return isString(json)
- ? JSON.parse(json)
- : json;
- }
- /**
- * @returns {string} Returns the string representation of the element.
- */
- function startingTag(element) {
- element = jqLite(element).clone();
- try {
- // turns out IE does not let you set .html() on elements which
- // are not allowed to have children. So we just ignore it.
- element.empty();
- } catch (e) {}
- var elemHtml = jqLite('<div>').append(element).html();
- try {
- return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
- elemHtml.
- match(/^(<[^>]+>)/)[1].
- replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
- } catch (e) {
- return lowercase(elemHtml);
- }
- }
- /////////////////////////////////////////////////
- /**
- * Tries to decode the URI component without throwing an exception.
- *
- * @private
- * @param str value potential URI component to check.
- * @returns {boolean} True if `value` can be decoded
- * with the decodeURIComponent function.
- */
- function tryDecodeURIComponent(value) {
- try {
- return decodeURIComponent(value);
- } catch (e) {
- // Ignore any invalid uri component
- }
- }
- /**
- * Parses an escaped url query string into key-value pairs.
- * @returns {Object.<string,boolean|Array>}
- */
- function parseKeyValue(/**string*/keyValue) {
- var obj = {}, key_value, key;
- forEach((keyValue || "").split('&'), function(keyValue) {
- if (keyValue) {
- key_value = keyValue.replace(/\+/g,'%20').split('=');
- key = tryDecodeURIComponent(key_value[0]);
- if (isDefined(key)) {
- var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
- if (!hasOwnProperty.call(obj, key)) {
- obj[key] = val;
- } else if (isArray(obj[key])) {
- obj[key].push(val);
- } else {
- obj[key] = [obj[key],val];
- }
- }
- }
- });
- return obj;
- }
- function toKeyValue(obj) {
- var parts = [];
- forEach(obj, function(value, key) {
- if (isArray(value)) {
- forEach(value, function(arrayValue) {
- parts.push(encodeUriQuery(key, true) +
- (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
- });
- } else {
- parts.push(encodeUriQuery(key, true) +
- (value === true ? '' : '=' + encodeUriQuery(value, true)));
- }
- });
- return parts.length ? parts.join('&') : '';
- }
- /**
- * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
- * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
- * segments:
- * segment = *pchar
- * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
- * pct-encoded = "%" HEXDIG HEXDIG
- * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
- * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
- * / "*" / "+" / "," / ";" / "="
- */
- function encodeUriSegment(val) {
- return encodeUriQuery(val, true).
- replace(/%26/gi, '&').
- replace(/%3D/gi, '=').
- replace(/%2B/gi, '+');
- }
- /**
- * This method is intended for encoding *key* or *value* parts of query component. We need a custom
- * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
- * encoded per http://tools.ietf.org/html/rfc3986:
- * query = *( pchar / "/" / "?" )
- * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
- * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
- * pct-encoded = "%" HEXDIG HEXDIG
- * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
- * / "*" / "+" / "," / ";" / "="
- */
- function encodeUriQuery(val, pctEncodeSpaces) {
- return encodeURIComponent(val).
- replace(/%40/gi, '@').
- replace(/%3A/gi, ':').
- replace(/%24/g, '$').
- replace(/%2C/gi, ',').
- replace(/%3B/gi, ';').
- replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
- }
- var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
- function getNgAttribute(element, ngAttr) {
- var attr, i, ii = ngAttrPrefixes.length;
- element = jqLite(element);
- for (i = 0; i < ii; ++i) {
- attr = ngAttrPrefixes[i] + ngAttr;
- if (isString(attr = element.attr(attr))) {
- return attr;
- }
- }
- return null;
- }
- /**
- * @ngdoc directive
- * @name ngApp
- * @module ng
- *
- * @element ANY
- * @param {angular.Module} ngApp an optional application
- * {@link angular.module module} name to load.
- * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
- * created in "strict-di" mode. This means that the application will fail to invoke functions which
- * do not use explicit function annotation (and are thus unsuitable for minification), as described
- * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
- * tracking down the root of these bugs.
- *
- * @description
- *
- * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
- * designates the **root element** of the application and is typically placed near the root element
- * of the page - e.g. on the `<body>` or `<html>` tags.
- *
- * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
- * found in the document will be used to define the root element to auto-bootstrap as an
- * application. To run multiple applications in an HTML document you must manually bootstrap them using
- * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
- *
- * You can specify an **AngularJS module** to be used as the root module for the application. This
- * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
- * should contain the application code needed or have dependencies on other modules that will
- * contain the code. See {@link angular.module} for more information.
- *
- * In the example below if the `ngApp` directive were not placed on the `html` element then the
- * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
- * would not be resolved to `3`.
- *
- * `ngApp` is the easiest, and most common way to bootstrap an application.
- *
- <example module="ngAppDemo">
- <file name="index.html">
- <div ng-controller="ngAppDemoController">
- I can add: {{a}} + {{b}} = {{ a+b }}
- </div>
- </file>
- <file name="script.js">
- angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
- $scope.a = 1;
- $scope.b = 2;
- });
- </file>
- </example>
- *
- * Using `ngStrictDi`, you would see something like this:
- *
- <example ng-app-included="true">
- <file name="index.html">
- <div ng-app="ngAppStrictDemo" ng-strict-di>
- <div ng-controller="GoodController1">
- I can add: {{a}} + {{b}} = {{ a+b }}
- <p>This renders because the controller does not fail to
- instantiate, by using explicit annotation style (see
- script.js for details)
- </p>
- </div>
- <div ng-controller="GoodController2">
- Name: <input ng-model="name"><br />
- Hello, {{name}}!
- <p>This renders because the controller does not fail to
- instantiate, by using explicit annotation style
- (see script.js for details)
- </p>
- </div>
- <div ng-controller="BadController">
- I can add: {{a}} + {{b}} = {{ a+b }}
- <p>The controller could not be instantiated, due to relying
- on automatic function annotations (which are disabled in
- strict mode). As such, the content of this section is not
- interpolated, and there should be an error in your web console.
- </p>
- </div>
- </div>
- </file>
- <file name="script.js">
- angular.module('ngAppStrictDemo', [])
- // BadController will fail to instantiate, due to relying on automatic function annotation,
- // rather than an explicit annotation
- .controller('BadController', function($scope) {
- $scope.a = 1;
- $scope.b = 2;
- })
- // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
- // due to using explicit annotations using the array style and $inject property, respectively.
- .controller('GoodController1', ['$scope', function($scope) {
- $scope.a = 1;
- $scope.b = 2;
- }])
- .controller('GoodController2', GoodController2);
- function GoodController2($scope) {
- $scope.name = "World";
- }
- GoodController2.$inject = ['$scope'];
- </file>
- <file name="style.css">
- div[ng-controller] {
- margin-bottom: 1em;
- -webkit-border-radius: 4px;
- border-radius: 4px;
- border: 1px solid;
- padding: .5em;
- }
- div[ng-controller^=Good] {
- border-color: #d6e9c6;
- background-color: #dff0d8;
- color: #3c763d;
- }
- div[ng-controller^=Bad] {
- border-color: #ebccd1;
- background-color: #f2dede;
- color: #a94442;
- margin-bottom: 0;
- }
- </file>
- </example>
- */
- function angularInit(element, bootstrap) {
- var appElement,
- module,
- config = {};
- // The element `element` has priority over any other element
- forEach(ngAttrPrefixes, function(prefix) {
- var name = prefix + 'app';
- if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
- appElement = element;
- module = element.getAttribute(name);
- }
- });
- forEach(ngAttrPrefixes, function(prefix) {
- var name = prefix + 'app';
- var candidate;
- if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
- appElement = candidate;
- module = candidate.getAttribute(name);
- }
- });
- if (appElement) {
- config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
- bootstrap(appElement, module ? [module] : [], config);
- }
- }
- /**
- * @ngdoc function
- * @name angular.bootstrap
- * @module ng
- * @description
- * Use this function to manually start up angular application.
- *
- * See: {@link guide/bootstrap Bootstrap}
- *
- * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually.
- * They must use {@link ng.directive:ngApp ngApp}.
- *
- * Angular will detect if it has been loaded into the browser more than once and only allow the
- * first loaded script to be bootstrapped and will report a warning to the browser console for
- * each of the subsequent scripts. This prevents strange results in applications, where otherwise
- * multiple instances of Angular try to work on the DOM.
- *
- * ```html
- * <!doctype html>
- * <html>
- * <body>
- * <div ng-controller="WelcomeController">
- * {{greeting}}
- * </div>
- *
- * <script src="angular.js"></script>
- * <script>
- * var app = angular.module('demo', [])
- * .controller('WelcomeController', function($scope) {
- * $scope.greeting = 'Welcome!';
- * });
- * angular.bootstrap(document, ['demo']);
- * </script>
- * </body>
- * </html>
- * ```
- *
- * @param {DOMElement} element DOM element which is the root of angular application.
- * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
- * Each item in the array should be the name of a predefined module or a (DI annotated)
- * function that will be invoked by the injector as a `config` block.
- * See: {@link angular.module modules}
- * @param {Object=} config an object for defining configuration options for the application. The
- * following keys are supported:
- *
- * * `strictDi` - disable automatic function annotation for the application. This is meant to
- * assist in finding bugs which break minified code. Defaults to `false`.
- *
- * @returns {auto.$injector} Returns the newly created injector for this app.
- */
- function bootstrap(element, modules, config) {
- if (!isObject(config)) config = {};
- var defaultConfig = {
- strictDi: false
- };
- config = extend(defaultConfig, config);
- var doBootstrap = function() {
- element = jqLite(element);
- if (element.injector()) {
- var tag = (element[0] === document) ? 'document' : startingTag(element);
- //Encode angle brackets to prevent input from being sanitized to empty string #8683
- throw ngMinErr(
- 'btstrpd',
- "App Already Bootstrapped with this Element '{0}'",
- tag.replace(/</,'<').replace(/>/,'>'));
- }
- modules = modules || [];
- modules.unshift(['$provide', function($provide) {
- $provide.value('$rootElement', element);
- }]);
- if (config.debugInfoEnabled) {
- // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
- modules.push(['$compileProvider', function($compileProvider) {
- $compileProvider.debugInfoEnabled(true);
- }]);
- }
- modules.unshift('ng');
- var injector = createInjector(modules, config.strictDi);
- injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
- function bootstrapApply(scope, element, compile, injector) {
- scope.$apply(function() {
- element.data('$injector', injector);
- compile(element)(scope);
- });
- }]
- );
- return injector;
- };
- var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
- var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
- if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
- config.debugInfoEnabled = true;
- window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
- }
- if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
- return doBootstrap();
- }
- window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
- angular.resumeBootstrap = function(extraModules) {
- forEach(extraModules, function(module) {
- modules.push(module);
- });
- return doBootstrap();
- };
- if (isFunction(angular.resumeDeferredBootstrap)) {
- angular.resumeDeferredBootstrap();
- }
- }
- /**
- * @ngdoc function
- * @name angular.reloadWithDebugInfo
- * @module ng
- * @description
- * Use this function to reload the current application with debug information turned on.
- * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
- *
- * See {@link ng.$compileProvider#debugInfoEnabled} for more.
- */
- function reloadWithDebugInfo() {
- window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
- window.location.reload();
- }
- /**
- * @name angular.getTestability
- * @module ng
- * @description
- * Get the testability service for the instance of Angular on the given
- * element.
- * @param {DOMElement} element DOM element which is the root of angular application.
- */
- function getTestability(rootElement) {
- var injector = angular.element(rootElement).injector();
- if (!injector) {
- throw ngMinErr('test',
- 'no injector found for element argument to getTestability');
- }
- return injector.get('$$testability');
- }
- var SNAKE_CASE_REGEXP = /[A-Z]/g;
- function snake_case(name, separator) {
- separator = separator || '_';
- return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
- return (pos ? separator : '') + letter.toLowerCase();
- });
- }
- var bindJQueryFired = false;
- var skipDestroyOnNextJQueryCleanData;
- function bindJQuery() {
- var originalCleanData;
- if (bindJQueryFired) {
- return;
- }
- // bind to jQuery if present;
- jQuery = window.jQuery;
- // Use jQuery if it exists with proper functionality, otherwise default to us.
- // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
- // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
- // versions. It will not work for sure with jQuery <1.7, though.
- if (jQuery && jQuery.fn.on) {
- jqLite = jQuery;
- extend(jQuery.fn, {
- scope: JQLitePrototype.scope,
- isolateScope: JQLitePrototype.isolateScope,
- controller: JQLitePrototype.controller,
- injector: JQLitePrototype.injector,
- inheritedData: JQLitePrototype.inheritedData
- });
- // All nodes removed from the DOM via various jQuery APIs like .remove()
- // are passed through jQuery.cleanData. Monkey-patch this method to fire
- // the $destroy event on all removed nodes.
- originalCleanData = jQuery.cleanData;
- jQuery.cleanData = function(elems) {
- var events;
- if (!skipDestroyOnNextJQueryCleanData) {
- for (var i = 0, elem; (elem = elems[i]) != null; i++) {
- events = jQuery._data(elem, "events");
- if (events && events.$destroy) {
- jQuery(elem).triggerHandler('$destroy');
- }
- }
- } else {
- skipDestroyOnNextJQueryCleanData = false;
- }
- originalCleanData(elems);
- };
- } else {
- jqLite = JQLite;
- }
- angular.element = jqLite;
- // Prevent double-proxying.
- bindJQueryFired = true;
- }
- /**
- * throw error if the argument is falsy.
- */
- function assertArg(arg, name, reason) {
- if (!arg) {
- throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
- }
- return arg;
- }
- function assertArgFn(arg, name, acceptArrayAnnotation) {
- if (acceptArrayAnnotation && isArray(arg)) {
- arg = arg[arg.length - 1];
- }
- assertArg(isFunction(arg), name, 'not a function, got ' +
- (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
- return arg;
- }
- /**
- * throw error if the name given is hasOwnProperty
- * @param {String} name the name to test
- * @param {String} context the context in which the name is used, such as module or directive
- */
- function assertNotHasOwnProperty(name, context) {
- if (name === 'hasOwnProperty') {
- throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context);
- }
- }
- /**
- * Return the value accessible from the object by path. Any undefined traversals are ignored
- * @param {Object} obj starting object
- * @param {String} path path to traverse
- * @param {boolean} [bindFnToScope=true]
- * @returns {Object} value as accessible by path
- */
- //TODO(misko): this function needs to be removed
- function getter(obj, path, bindFnToScope) {
- if (!path) return obj;
- var keys = path.split('.');
- var key;
- var lastInstance = obj;
- var len = keys.length;
- for (var i = 0; i < len; i++) {
- key = keys[i];
- if (obj) {
- obj = (lastInstance = obj)[key];
- }
- }
- if (!bindFnToScope && isFunction(obj)) {
- return bind(lastInstance, obj);
- }
- return obj;
- }
- /**
- * Return the DOM siblings between the first and last node in the given array.
- * @param {Array} array like object
- * @returns {jqLite} jqLite collection containing the nodes
- */
- function getBlockNodes(nodes) {
- // TODO(perf): just check if all items in `nodes` are siblings and if they are return the original
- // collection, otherwise update the original collection.
- var node = nodes[0];
- var endNode = nodes[nodes.length - 1];
- var blockNodes = [node];
- do {
- node = node.nextSibling;
- if (!node) break;
- blockNodes.push(node);
- } while (node !== endNode);
- return jqLite(blockNodes);
- }
- /**
- * Creates a new object without a prototype. This object is useful for lookup without having to
- * guard against prototypically inherited properties via hasOwnProperty.
- *
- * Related micro-benchmarks:
- * - http://jsperf.com/object-create2
- * - http://jsperf.com/proto-map-lookup/2
- * - http://jsperf.com/for-in-vs-object-keys2
- *
- * @returns {Object}
- */
- function createMap() {
- return Object.create(null);
- }
- var NODE_TYPE_ELEMENT = 1;
- var NODE_TYPE_TEXT = 3;
- var NODE_TYPE_COMMENT = 8;
- var NODE_TYPE_DOCUMENT = 9;
- var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
- /**
- * @ngdoc type
- * @name angular.Module
- * @module ng
- * @description
- *
- * Interface for configuring angular {@link angular.module modules}.
- */
- function setupModuleLoader(window) {
- var $injectorMinErr = minErr('$injector');
- var ngMinErr = minErr('ng');
- function ensure(obj, name, factory) {
- return obj[name] || (obj[name] = factory());
- }
- var angular = ensure(window, 'angular', Object);
- // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
- angular.$$minErr = angular.$$minErr || minErr;
- return ensure(angular, 'module', function() {
- /** @type {Object.<string, angular.Module>} */
- var modules = {};
- /**
- * @ngdoc function
- * @name angular.module
- * @module ng
- * @description
- *
- * The `angular.module` is a global place for creating, registering and retrieving Angular
- * modules.
- * All modules (angular core or 3rd party) that should be available to an application must be
- * registered using this mechanism.
- *
- * When passed two or more arguments, a new module is created. If passed only one argument, an
- * existing module (the name passed as the first argument to `module`) is retrieved.
- *
- *
- * # Module
- *
- * A module is a collection of services, directives, controllers, filters, and configuration information.
- * `angular.module` is used to configure the {@link auto.$injector $injector}.
- *
- * ```js
- * // Create a new module
- * var myModule = angular.module('myModule', []);
- *
- * // register a new service
- * myModule.value('appName', 'MyCoolApp');
- *
- * // configure existing services inside initialization blocks.
- * myModule.config(['$locationProvider', function($locationProvider) {
- * // Configure existing providers
- * $locationProvider.hashPrefix('!');
- * }]);
- * ```
- *
- * Then you can create an injector and load your modules like this:
- *
- * ```js
- * var injector = angular.injector(['ng', 'myModule'])
- * ```
- *
- * However it's more likely that you'll just use
- * {@link ng.directive:ngApp ngApp} or
- * {@link angular.bootstrap} to simplify this process for you.
- *
- * @param {!string} name The name of the module to create or retrieve.
- * @param {!Array.<string>=} requires If specified then new module is being created. If
- * unspecified then the module is being retrieved for further configuration.
- * @param {Function=} configFn Optional configuration function for the module. Same as
- * {@link angular.Module#config Module#config()}.
- * @returns {module} new module with the {@link angular.Module} api.
- */
- return function module(name, requires, configFn) {
- var assertNotHasOwnProperty = function(name, context) {
- if (name === 'hasOwnProperty') {
- throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
- }
- };
- assertNotHasOwnProperty(name, 'module');
- if (requires && modules.hasOwnProperty(name)) {
- modules[name] = null;
- }
- return ensure(modules, name, function() {
- if (!requires) {
- throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
- "the module name or forgot to load it. If registering a module ensure that you " +
- "specify the dependencies as the second argument.", name);
- }
- /** @type {!Array.<Array.<*>>} */
- var invokeQueue = [];
- /** @type {!Array.<Function>} */
- var configBlocks = [];
- /** @type {!Array.<Function>} */
- var runBlocks = [];
- var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
- /** @type {angular.Module} */
- var moduleInstance = {
- // Private state
- _invokeQueue: invokeQueue,
- _configBlocks: configBlocks,
- _runBlocks: runBlocks,
- /**
- * @ngdoc property
- * @name angular.Module#requires
- * @module ng
- *
- * @description
- * Holds the list of modules which the injector will load before the current module is
- * loaded.
- */
- requires: requires,
- /**
- * @ngdoc property
- * @name angular.Module#name
- * @module ng
- *
- * @description
- * Name of the module.
- */
- name: name,
- /**
- * @ngdoc method
- * @name angular.Module#provider
- * @module ng
- * @param {string} name service name
- * @param {Function} providerType Construction function for creating new instance of the
- * service.
- * @description
- * See {@link auto.$provide#provider $provide.provider()}.
- */
- provider: invokeLater('$provide', 'provider'),
- /**
- * @ngdoc method
- * @name angular.Module#factory
- * @module ng
- * @param {string} name service name
- * @param {Function} providerFunction Function for creating new instance of the service.
- * @description
- * See {@link auto.$provide#factory $provide.factory()}.
- */
- factory: invokeLater('$provide', 'factory'),
- /**
- * @ngdoc method
- * @name angular.Module#service
- * @module ng
- * @param {string} name service name
- * @param {Function} constructor A constructor function that will be instantiated.
- * @description
- * See {@link auto.$provide#service $provide.service()}.
- */
- service: invokeLater('$provide', 'service'),
- /**
- * @ngdoc method
- * @name angular.Module#value
- * @module ng
- * @param {string} name service name
- * @param {*} object Service instance object.
- * @description
- * See {@link auto.$provide#value $provide.value()}.
- */
- value: invokeLater('$provide', 'value'),
- /**
- * @ngdoc method
- * @name angular.Module#constant
- * @module ng
- * @param {string} name constant name
- * @param {*} object Constant value.
- * @description
- * Because the constant are fixed, they get applied before other provide methods.
- * See {@link auto.$provide#constant $provide.constant()}.
- */
- constant: invokeLater('$provide', 'constant', 'unshift'),
- /**
- * @ngdoc method
- * @name angular.Module#animation
- * @module ng
- * @param {string} name animation name
- * @param {Function} animationFactory Factory function for creating new instance of an
- * animation.
- * @description
- *
- * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
- *
- *
- * Defines an animation hook that can be later used with
- * {@link ngAnimate.$animate $animate} service and directives that use this service.
- *
- * ```js
- * module.animation('.animation-name', function($inject1, $inject2) {
- * return {
- * eventName : function(element, done) {
- * //code to run the animation
- * //once complete, then run done()
- * return function cancellationFunction(element) {
- * //code to cancel the animation
- * }
- * }
- * }
- * })
- * ```
- *
- * See {@link ng.$animateProvider#register $animateProvider.register()} and
- * {@link ngAnimate ngAnimate module} for more information.
- */
- animation: invokeLater('$animateProvider', 'register'),
- /**
- * @ngdoc method
- * @name angular.Module#filter
- * @module ng
- * @param {string} name Filter name.
- * @param {Function} filterFactory Factory function for creating new instance of filter.
- * @description
- * See {@link ng.$filterProvider#register $filterProvider.register()}.
- */
- filter: invokeLater('$filterProvider', 'register'),
- /**
- * @ngdoc method
- * @name angular.Module#controller
- * @module ng
- * @param {string|Object} name Controller name, or an object map of controllers where the
- * keys are the names and the values are the constructors.
- * @param {Function} constructor Controller constructor function.
- * @description
- * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
- */
- controller: invokeLater('$controllerProvider', 'register'),
- /**
- * @ngdoc method
- * @name angular.Module#directive
- * @module ng
- * @param {string|Object} name Directive name, or an object map of directives where the
- * keys are the names and the values are the factories.
- * @param {Function} directiveFactory Factory function for creating new instance of
- * directives.
- * @description
- * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
- */
- directive: invokeLater('$compileProvider', 'directive'),
- /**
- * @ngdoc method
- * @name angular.Module#config
- * @module ng
- * @param {Function} configFn Execute this function on module load. Useful for service
- * configuration.
- * @description
- * Use this method to register work which needs to be performed on module loading.
- * For more about how to configure services, see
- * {@link providers#provider-recipe Provider Recipe}.
- */
- config: config,
- /**
- * @ngdoc method
- * @name angular.Module#run
- * @module ng
- * @param {Function} initializationFn Execute this function after injector creation.
- * Useful for application initialization.
- * @description
- * Use this method to register work which should be performed when the injector is done
- * loading all modules.
- */
- run: function(block) {
- runBlocks.push(block);
- return this;
- }
- };
- if (configFn) {
- config(configFn);
- }
- return moduleInstance;
- /**
- * @param {string} provider
- * @param {string} method
- * @param {String=} insertMethod
- * @returns {angular.Module}
- */
- function invokeLater(provider, method, insertMethod, queue) {
- if (!queue) queue = invokeQueue;
- return function() {
- queue[insertMethod || 'push']([provider, method, arguments]);
- return moduleInstance;
- };
- }
- });
- };
- });
- }
- /* global: toDebugString: true */
- function serializeObject(obj) {
- var seen = [];
- return JSON.stringify(obj, function(key, val) {
- val = toJsonReplacer(key, val);
- if (isObject(val)) {
- if (seen.indexOf(val) >= 0) return '<<already seen>>';
- seen.push(val);
- }
- return val;
- });
- }
- function toDebugString(obj) {
- if (typeof obj === 'function') {
- return obj.toString().replace(/ \{[\s\S]*$/, '');
- } else if (typeof obj === 'undefined') {
- return 'undefined';
- } else if (typeof obj !== 'string') {
- return serializeObject(obj);
- }
- return obj;
- }
- /* global angularModule: true,
- version: true,
- $LocaleProvider,
- $CompileProvider,
- htmlAnchorDirective,
- inputDirective,
- inputDirective,
- formDirective,
- scriptDirective,
- selectDirective,
- styleDirective,
- optionDirective,
- ngBindDirective,
- ngBindHtmlDirective,
- ngBindTemplateDirective,
- ngClassDirective,
- ngClassEvenDirective,
- ngClassOddDirective,
- ngCspDirective,
- ngCloakDirective,
- ngControllerDirective,
- ngFormDirective,
- ngHideDirective,
- ngIfDirective,
- ngIncludeDirective,
- ngIncludeFillContentDirective,
- ngInitDirective,
- ngNonBindableDirective,
- ngPluralizeDirective,
- ngRepeatDirective,
- ngShowDirective,
- ngStyleDirective,
- ngSwitchDirective,
- ngSwitchWhenDirective,
- ngSwitchDefaultDirective,
- ngOptionsDirective,
- ngTranscludeDirective,
- ngModelDirective,
- ngListDirective,
- ngChangeDirective,
- patternDirective,
- patternDirective,
- requiredDirective,
- requiredDirective,
- minlengthDirective,
- minlengthDirective,
- maxlengthDirective,
- maxlengthDirective,
- ngValueDirective,
- ngModelOptionsDirective,
- ngAttributeAliasDirectives,
- ngEventDirectives,
- $AnchorScrollProvider,
- $AnimateProvider,
- $BrowserProvider,
- $CacheFactoryProvider,
- $ControllerProvider,
- $DocumentProvider,
- $ExceptionHandlerProvider,
- $FilterProvider,
- $InterpolateProvider,
- $IntervalProvider,
- $HttpProvider,
- $HttpBackendProvider,
- $LocationProvider,
- $LogProvider,
- $ParseProvider,
- $RootScopeProvider,
- $QProvider,
- $$QProvider,
- $$SanitizeUriProvider,
- $SceProvider,
- $SceDelegateProvider,
- $SnifferProvider,
- $TemplateCacheProvider,
- $TemplateRequestProvider,
- $$TestabilityProvider,
- $TimeoutProvider,
- $$RAFProvider,
- $$AsyncCallbackProvider,
- $WindowProvider,
- $$jqLiteProvider
- */
- /**
- * @ngdoc object
- * @name angular.version
- * @module ng
- * @description
- * An object that contains information about the current AngularJS version. This object has the
- * following properties:
- *
- * - `full` – `{string}` – Full version string, such as "0.9.18".
- * - `major` – `{number}` – Major version number, such as "0".
- * - `minor` – `{number}` – Minor version number, such as "9".
- * - `dot` – `{number}` – Dot version number, such as "18".
- * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
- */
- var version = {
- full: '1.3.13', // all of these placeholder strings will be replaced by grunt's
- major: 1, // package task
- minor: 3,
- dot: 13,
- codeName: 'meticulous-riffleshuffle'
- };
- function publishExternalAPI(angular) {
- extend(angular, {
- 'bootstrap': bootstrap,
- 'copy': copy,
- 'extend': extend,
- 'equals': equals,
- 'element': jqLite,
- 'forEach': forEach,
- 'injector': createInjector,
- 'noop': noop,
- 'bind': bind,
- 'toJson': toJson,
- 'fromJson': fromJson,
- 'identity': identity,
- 'isUndefined': isUndefined,
- 'isDefined': isDefined,
- 'isString': isString,
- 'isFunction': isFunction,
- 'isObject': isObject,
- 'isNumber': isNumber,
- 'isElement': isElement,
- 'isArray': isArray,
- 'version': version,
- 'isDate': isDate,
- 'lowercase': lowercase,
- 'uppercase': uppercase,
- 'callbacks': {counter: 0},
- 'getTestability': getTestability,
- '$$minErr': minErr,
- '$$csp': csp,
- 'reloadWithDebugInfo': reloadWithDebugInfo
- });
- angularModule = setupModuleLoader(window);
- try {
- angularModule('ngLocale');
- } catch (e) {
- angularModule('ngLocale', []).provider('$locale', $LocaleProvider);
- }
- angularModule('ng', ['ngLocale'], ['$provide',
- function ngModule($provide) {
- // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
- $provide.provider({
- $$sanitizeUri: $$SanitizeUriProvider
- });
- $provide.provider('$compile', $CompileProvider).
- directive({
- a: htmlAnchorDirective,
- input: inputDirective,
- textarea: inputDirective,
- form: formDirective,
- script: scriptDirective,
- select: selectDirective,
- style: styleDirective,
- option: optionDirective,
- ngBind: ngBindDirective,
- ngBindHtml: ngBindHtmlDirective,
- ngBindTemplate: ngBindTemplateDirective,
- ngClass: ngClassDirective,
- ngClassEven: ngClassEvenDirective,
- ngClassOdd: ngClassOddDirective,
- ngCloak: ngCloakDirective,
- ngController: ngControllerDirective,
- ngForm: ngFormDirective,
- ngHide: ngHideDirective,
- ngIf: ngIfDirective,
- ngInclude: ngIncludeDirective,
- ngInit: ngInitDirective,
- ngNonBindable: ngNonBindableDirective,
- ngPluralize: ngPluralizeDirective,
- ngRepeat: ngRepeatDirective,
- ngShow: ngShowDirective,
- ngStyle: ngStyleDirective,
- ngSwitch: ngSwitchDirective,
- ngSwitchWhen: ngSwitchWhenDirective,
- ngSwitchDefault: ngSwitchDefaultDirective,
- ngOptions: ngOptionsDirective,
- ngTransclude: ngTranscludeDirective,
- ngModel: ngModelDirective,
- ngList: ngListDirective,
- ngChange: ngChangeDirective,
- pattern: patternDirective,
- ngPattern: patternDirective,
- required: requiredDirective,
- ngRequired: requiredDirective,
- minlength: minlengthDirective,
- ngMinlength: minlengthDirective,
- maxlength: maxlengthDirective,
- ngMaxlength: maxlengthDirective,
- ngValue: ngValueDirective,
- ngModelOptions: ngModelOptionsDirective
- }).
- directive({
- ngInclude: ngIncludeFillContentDirective
- }).
- directive(ngAttributeAliasDirectives).
- directive(ngEventDirectives);
- $provide.provider({
- $anchorScroll: $AnchorScrollProvider,
- $animate: $AnimateProvider,
- $browser: $BrowserProvider,
- $cacheFactory: $CacheFactoryProvider,
- $controller: $ControllerProvider,
- $document: $DocumentProvider,
- $exceptionHandler: $ExceptionHandlerProvider,
- $filter: $FilterProvider,
- $interpolate: $InterpolateProvider,
- $interval: $IntervalProvider,
- $http: $HttpProvider,
- $httpBackend: $HttpBackendProvider,
- $location: $LocationProvider,
- $log: $LogProvider,
- $parse: $ParseProvider,
- $rootScope: $RootScopeProvider,
- $q: $QProvider,
- $$q: $$QProvider,
- $sce: $SceProvider,
- $sceDelegate: $SceDelegateProvider,
- $sniffer: $SnifferProvider,
- $templateCache: $TemplateCacheProvider,
- $templateRequest: $TemplateRequestProvider,
- $$testability: $$TestabilityProvider,
- $timeout: $TimeoutProvider,
- $window: $WindowProvider,
- $$rAF: $$RAFProvider,
- $$asyncCallback: $$AsyncCallbackProvider,
- $$jqLite: $$jqLiteProvider
- });
- }
- ]);
- }
- /* global JQLitePrototype: true,
- addEventListenerFn: true,
- removeEventListenerFn: true,
- BOOLEAN_ATTR: true,
- ALIASED_ATTR: true,
- */
- //////////////////////////////////
- //JQLite
- //////////////////////////////////
- /**
- * @ngdoc function
- * @name angular.element
- * @module ng
- * @kind function
- *
- * @description
- * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
- *
- * If jQuery is available, `angular.element` is an alias for the
- * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
- * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
- *
- * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
- * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
- * commonly needed functionality with the goal of having a very small footprint.</div>
- *
- * To use jQuery, simply load it before `DOMContentLoaded` event fired.
- *
- * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
- * jqLite; they are never raw DOM references.</div>
- *
- * ## Angular's jqLite
- * jqLite provides only the following jQuery methods:
- *
- * - [`addClass()`](http://api.jquery.com/addClass/)
- * - [`after()`](http://api.jquery.com/after/)
- * - [`append()`](http://api.jquery.com/append/)
- * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
- * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
- * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
- * - [`clone()`](http://api.jquery.com/clone/)
- * - [`contents()`](http://api.jquery.com/contents/)
- * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`
- * - [`data()`](http://api.jquery.com/data/)
- * - [`detach()`](http://api.jquery.com/detach/)
- * - [`empty()`](http://api.jquery.com/empty/)
- * - [`eq()`](http://api.jquery.com/eq/)
- * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
- * - [`hasClass()`](http://api.jquery.com/hasClass/)
- * - [`html()`](http://api.jquery.com/html/)
- * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
- * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
- * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors
- * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
- * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
- * - [`prepend()`](http://api.jquery.com/prepend/)
- * - [`prop()`](http://api.jquery.com/prop/)
- * - [`ready()`](http://api.jquery.com/ready/)
- * - [`remove()`](http://api.jquery.com/remove/)
- * - [`removeAttr()`](http://api.jquery.com/removeAttr/)
- * - [`removeClass()`](http://api.jquery.com/removeClass/)
- * - [`removeData()`](http://api.jquery.com/removeData/)
- * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
- * - [`text()`](http://api.jquery.com/text/)
- * - [`toggleClass()`](http://api.jquery.com/toggleClass/)
- * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
- * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces
- * - [`val()`](http://api.jquery.com/val/)
- * - [`wrap()`](http://api.jquery.com/wrap/)
- *
- * ## jQuery/jqLite Extras
- * Angular also provides the following additional methods and events to both jQuery and jqLite:
- *
- * ### Events
- * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
- * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM
- * element before it is removed.
- *
- * ### Methods
- * - `controller(name)` - retrieves the controller of the current element or its parent. By default
- * retrieves controller associated with the `ngController` directive. If `name` is provided as
- * camelCase directive name, then the controller for this directive will be retrieved (e.g.
- * `'ngModel'`).
- * - `injector()` - retrieves the injector of the current element or its parent.
- * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
- * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
- * be enabled.
- * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
- * current element. This getter should be used only on elements that contain a directive which starts a new isolate
- * scope. Calling `scope()` on this element always returns the original non-isolate scope.
- * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
- * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
- * parent element is reached.
- *
- * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
- * @returns {Object} jQuery object.
- */
- JQLite.expando = 'ng339';
- var jqCache = JQLite.cache = {},
- jqId = 1,
- addEventListenerFn = function(element, type, fn) {
- element.addEventListener(type, fn, false);
- },
- removeEventListenerFn = function(element, type, fn) {
- element.removeEventListener(type, fn, false);
- };
- /*
- * !!! This is an undocumented "private" function !!!
- */
- JQLite._data = function(node) {
- //jQuery always returns an object on cache miss
- return this.cache[node[this.expando]] || {};
- };
- function jqNextId() { return ++jqId; }
- var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
- var MOZ_HACK_REGEXP = /^moz([A-Z])/;
- var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"};
- var jqLiteMinErr = minErr('jqLite');
- /**
- * Converts snake_case to camelCase.
- * Also there is special case for Moz prefix starting with upper case letter.
- * @param name Name to normalize
- */
- function camelCase(name) {
- return name.
- replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
- return offset ? letter.toUpperCase() : letter;
- }).
- replace(MOZ_HACK_REGEXP, 'Moz$1');
- }
- var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/;
- var HTML_REGEXP = /<|&#?\w+;/;
- var TAG_NAME_REGEXP = /<([\w:]+)/;
- var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi;
- var wrapMap = {
- 'option': [1, '<select multiple="multiple">', '</select>'],
- 'thead': [1, '<table>', '</table>'],
- 'col': [2, '<table><colgroup>', '</colgroup></table>'],
- 'tr': [2, '<table><tbody>', '</tbody></table>'],
- 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
- '_default': [0, "", ""]
- };
- wrapMap.optgroup = wrapMap.option;
- wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
- wrapMap.th = wrapMap.td;
- function jqLiteIsTextNode(html) {
- return !HTML_REGEXP.test(html);
- }
- function jqLiteAcceptsData(node) {
- // The window object can accept data but has no nodeType
- // Otherwise we are only interested in elements (1) and documents (9)
- var nodeType = node.nodeType;
- return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
- }
- function jqLiteBuildFragment(html, context) {
- var tmp, tag, wrap,
- fragment = context.createDocumentFragment(),
- nodes = [], i;
- if (jqLiteIsTextNode(html)) {
- // Convert non-html into a text node
- nodes.push(context.createTextNode(html));
- } else {
- // Convert html into DOM nodes
- tmp = tmp || fragment.appendChild(context.createElement("div"));
- tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
- wrap = wrapMap[tag] || wrapMap._default;
- tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];
- // Descend through wrappers to the right content
- i = wrap[0];
- while (i--) {
- tmp = tmp.lastChild;
- }
- nodes = concat(nodes, tmp.childNodes);
- tmp = fragment.firstChild;
- tmp.textContent = "";
- }
- // Remove wrapper from fragment
- fragment.textContent = "";
- fragment.innerHTML = ""; // Clear inner HTML
- forEach(nodes, function(node) {
- fragment.appendChild(node);
- });
- return fragment;
- }
- function jqLiteParseHTML(html, context) {
- context = context || document;
- var parsed;
- if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
- return [context.createElement(parsed[1])];
- }
- if ((parsed = jqLiteBuildFragment(html, context))) {
- return parsed.childNodes;
- }
- return [];
- }
- /////////////////////////////////////////////
- function JQLite(element) {
- if (element instanceof JQLite) {
- return element;
- }
- var argIsString;
- if (isString(element)) {
- element = trim(element);
- argIsString = true;
- }
- if (!(this instanceof JQLite)) {
- if (argIsString && element.charAt(0) != '<') {
- throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
- }
- return new JQLite(element);
- }
- if (argIsString) {
- jqLiteAddNodes(this, jqLiteParseHTML(element));
- } else {
- jqLiteAddNodes(this, element);
- }
- }
- function jqLiteClone(element) {
- return element.cloneNode(true);
- }
- function jqLiteDealoc(element, onlyDescendants) {
- if (!onlyDescendants) jqLiteRemoveData(element);
- if (element.querySelectorAll) {
- var descendants = element.querySelectorAll('*');
- for (var i = 0, l = descendants.length; i < l; i++) {
- jqLiteRemoveData(descendants[i]);
- }
- }
- }
- function jqLiteOff(element, type, fn, unsupported) {
- if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
- var expandoStore = jqLiteExpandoStore(element);
- var events = expandoStore && expandoStore.events;
- var handle = expandoStore && expandoStore.handle;
- if (!handle) return; //no listeners registered
- if (!type) {
- for (type in events) {
- if (type !== '$destroy') {
- removeEventListenerFn(element, type, handle);
- }
- delete events[type];
- }
- } else {
- forEach(type.split(' '), function(type) {
- if (isDefined(fn)) {
- var listenerFns = events[type];
- arrayRemove(listenerFns || [], fn);
- if (listenerFns && listenerFns.length > 0) {
- return;
- }
- }
- removeEventListenerFn(element, type, handle);
- delete events[type];
- });
- }
- }
- function jqLiteRemoveData(element, name) {
- var expandoId = element.ng339;
- var expandoStore = expandoId && jqCache[expandoId];
- if (expandoStore) {
- if (name) {
- delete expandoStore.data[name];
- return;
- }
- if (expandoStore.handle) {
- if (expandoStore.events.$destroy) {
- expandoStore.handle({}, '$destroy');
- }
- jqLiteOff(element);
- }
- delete jqCache[expandoId];
- element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
- }
- }
- function jqLiteExpandoStore(element, createIfNecessary) {
- var expandoId = element.ng339,
- expandoStore = expandoId && jqCache[expandoId];
- if (createIfNecessary && !expandoStore) {
- elem
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement