Advertisement
Guest User

edited ionic bundle

a guest
Oct 5th, 2015
586
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*!
  2.  * ionic.bundle.js is a concatenation of:
  3.  * ionic.js, angular.js, angular-animate.js,
  4.  * angular-sanitize.js, angular-ui-router.js,
  5.  * and ionic-angular.js
  6.  */
  7.  
  8. /*!
  9.  * Copyright 2014 Drifty Co.
  10.  * http://drifty.com/
  11.  *
  12.  * Ionic, v1.0.1
  13.  * A powerful HTML5 mobile app framework.
  14.  * http://ionicframework.com/
  15.  *
  16.  * By @maxlynch, @benjsperry, @adamdbradley <3
  17.  *
  18.  * Licensed under the MIT license. Please see LICENSE for more information.
  19.  *
  20.  */
  21.  
  22. /*  IMPORTANT
  23.     This file has been modified in the following places, search in comments for the *CHANGE* tag
  24.  */
  25.  
  26. (function() {
  27.  
  28. // Create global ionic obj and its namespaces
  29. // build processes may have already created an ionic obj
  30. window.ionic = window.ionic || {};
  31. window.ionic.views = {};
  32. window.ionic.version = '1.0.1';
  33.  
  34. (function (ionic) {
  35.  
  36.   ionic.DelegateService = function(methodNames) {
  37.  
  38.     if (methodNames.indexOf('$getByHandle') > -1) {
  39.       throw new Error("Method '$getByHandle' is implicitly added to each delegate service. Do not list it as a method.");
  40.     }
  41.  
  42.     function trueFn() { return true; }
  43.  
  44.     return ['$log', function($log) {
  45.  
  46.       /*
  47.        * Creates a new object that will have all the methodNames given,
  48.        * and call them on the given the controller instance matching given
  49.        * handle.
  50.        * The reason we don't just let $getByHandle return the controller instance
  51.        * itself is that the controller instance might not exist yet.
  52.        *
  53.        * We want people to be able to do
  54.        * `var instance = $ionicScrollDelegate.$getByHandle('foo')` on controller
  55.        * instantiation, but on controller instantiation a child directive
  56.        * may not have been compiled yet!
  57.        *
  58.        * So this is our way of solving this problem: we create an object
  59.        * that will only try to fetch the controller with given handle
  60.        * once the methods are actually called.
  61.        */
  62.       function DelegateInstance(instances, handle) {
  63.         this._instances = instances;
  64.         this.handle = handle;
  65.       }
  66.       methodNames.forEach(function(methodName) {
  67.         DelegateInstance.prototype[methodName] = instanceMethodCaller(methodName);
  68.       });
  69.  
  70.  
  71.       /**
  72.        * The delegate service (eg $ionicNavBarDelegate) is just an instance
  73.        * with a non-defined handle, a couple extra methods for registering
  74.        * and narrowing down to a specific handle.
  75.        */
  76.       function DelegateService() {
  77.         this._instances = [];
  78.       }
  79.       DelegateService.prototype = DelegateInstance.prototype;
  80.       DelegateService.prototype._registerInstance = function(instance, handle, filterFn) {
  81.         var instances = this._instances;
  82.         instance.$$delegateHandle = handle;
  83.         instance.$$filterFn = filterFn || trueFn;
  84.         instances.push(instance);
  85.  
  86.         return function deregister() {
  87.           var index = instances.indexOf(instance);
  88.           if (index !== -1) {
  89.             instances.splice(index, 1);
  90.           }
  91.         };
  92.       };
  93.       DelegateService.prototype.$getByHandle = function(handle) {
  94.         return new DelegateInstance(this._instances, handle);
  95.       };
  96.  
  97.       return new DelegateService();
  98.  
  99.       function instanceMethodCaller(methodName) {
  100.         return function caller() {
  101.           var handle = this.handle;
  102.           var args = arguments;
  103.           var foundInstancesCount = 0;
  104.           var returnValue;
  105.  
  106.           this._instances.forEach(function(instance) {
  107.             if ((!handle || handle == instance.$$delegateHandle) && instance.$$filterFn(instance)) {
  108.               foundInstancesCount++;
  109.               var ret = instance[methodName].apply(instance, args);
  110.               //Only return the value from the first call
  111.               if (foundInstancesCount === 1) {
  112.                 returnValue = ret;
  113.               }
  114.             }
  115.           });
  116.  
  117.           if (!foundInstancesCount && handle) {
  118.             return $log.warn(
  119.               'Delegate for handle "' + handle + '" could not find a ' +
  120.               'corresponding element with delegate-handle="' + handle + '"! ' +
  121.               methodName + '() was not called!\n' +
  122.               'Possible cause: If you are calling ' + methodName + '() immediately, and ' +
  123.               'your element with delegate-handle="' + handle + '" is a child of your ' +
  124.               'controller, then your element may not be compiled yet. Put a $timeout ' +
  125.               'around your call to ' + methodName + '() and try again.'
  126.             );
  127.           }
  128.           return returnValue;
  129.         };
  130.       }
  131.  
  132.     }];
  133.   };
  134.  
  135. })(window.ionic);
  136.  
  137. (function(window, document, ionic) {
  138.  
  139.   var readyCallbacks = [];
  140.   var isDomReady = document.readyState === 'complete' || document.readyState === 'interactive';
  141.  
  142.   function domReady() {
  143.     isDomReady = true;
  144.     for (var x = 0; x < readyCallbacks.length; x++) {
  145.       ionic.requestAnimationFrame(readyCallbacks[x]);
  146.     }
  147.     readyCallbacks = [];
  148.     document.removeEventListener('DOMContentLoaded', domReady);
  149.   }
  150.   if (!isDomReady) {
  151.     document.addEventListener('DOMContentLoaded', domReady);
  152.   }
  153.  
  154.  
  155.   // From the man himself, Mr. Paul Irish.
  156.   // The requestAnimationFrame polyfill
  157.   // Put it on window just to preserve its context
  158.   // without having to use .call
  159.   window._rAF = (function() {
  160.     return window.requestAnimationFrame ||
  161.            window.webkitRequestAnimationFrame ||
  162.            window.mozRequestAnimationFrame ||
  163.            function(callback) {
  164.              window.setTimeout(callback, 16);
  165.            };
  166.   })();
  167.  
  168.   var cancelAnimationFrame = window.cancelAnimationFrame ||
  169.     window.webkitCancelAnimationFrame ||
  170.     window.mozCancelAnimationFrame ||
  171.     window.webkitCancelRequestAnimationFrame;
  172.  
  173.   /**
  174.   * @ngdoc utility
  175.   * @name ionic.DomUtil
  176.   * @module ionic
  177.   */
  178.   ionic.DomUtil = {
  179.     //Call with proper context
  180.     /**
  181.      * @ngdoc method
  182.      * @name ionic.DomUtil#requestAnimationFrame
  183.      * @alias ionic.requestAnimationFrame
  184.      * @description Calls [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame), or a polyfill if not available.
  185.      * @param {function} callback The function to call when the next frame
  186.      * happens.
  187.      */
  188.     requestAnimationFrame: function(cb) {
  189.       return window._rAF(cb);
  190.     },
  191.  
  192.     cancelAnimationFrame: function(requestId) {
  193.       cancelAnimationFrame(requestId);
  194.     },
  195.  
  196.     /**
  197.      * @ngdoc method
  198.      * @name ionic.DomUtil#animationFrameThrottle
  199.      * @alias ionic.animationFrameThrottle
  200.      * @description
  201.      * When given a callback, if that callback is called 100 times between
  202.      * animation frames, adding Throttle will make it only run the last of
  203.      * the 100 calls.
  204.      *
  205.      * @param {function} callback a function which will be throttled to
  206.      * requestAnimationFrame
  207.      * @returns {function} A function which will then call the passed in callback.
  208.      * The passed in callback will receive the context the returned function is
  209.      * called with.
  210.      */
  211.     animationFrameThrottle: function(cb) {
  212.       var args, isQueued, context;
  213.       return function() {
  214.         args = arguments;
  215.         context = this;
  216.         if (!isQueued) {
  217.           isQueued = true;
  218.           ionic.requestAnimationFrame(function() {
  219.             cb.apply(context, args);
  220.             isQueued = false;
  221.           });
  222.         }
  223.       };
  224.     },
  225.  
  226.     contains: function(parentNode, otherNode) {
  227.       var current = otherNode;
  228.       while (current) {
  229.         if (current === parentNode) return true;
  230.         current = current.parentNode;
  231.       }
  232.     },
  233.  
  234.     /**
  235.      * @ngdoc method
  236.      * @name ionic.DomUtil#getPositionInParent
  237.      * @description
  238.      * Find an element's scroll offset within its container.
  239.      * @param {DOMElement} element The element to find the offset of.
  240.      * @returns {object} A position object with the following properties:
  241.      *   - `{number}` `left` The left offset of the element.
  242.      *   - `{number}` `top` The top offset of the element.
  243.      */
  244.     getPositionInParent: function(el) {
  245.       return {
  246.         left: el.offsetLeft,
  247.         top: el.offsetTop
  248.       };
  249.     },
  250.  
  251.     /**
  252.      * @ngdoc method
  253.      * @name ionic.DomUtil#ready
  254.      * @description
  255.      * Call a function when the DOM is ready, or if it is already ready
  256.      * call the function immediately.
  257.      * @param {function} callback The function to be called.
  258.      */
  259.     ready: function(cb) {
  260.       if (isDomReady) {
  261.         ionic.requestAnimationFrame(cb);
  262.       } else {
  263.         readyCallbacks.push(cb);
  264.       }
  265.     },
  266.  
  267.     /**
  268.      * @ngdoc method
  269.      * @name ionic.DomUtil#getTextBounds
  270.      * @description
  271.      * Get a rect representing the bounds of the given textNode.
  272.      * @param {DOMElement} textNode The textNode to find the bounds of.
  273.      * @returns {object} An object representing the bounds of the node. Properties:
  274.      *   - `{number}` `left` The left position of the textNode.
  275.      *   - `{number}` `right` The right position of the textNode.
  276.      *   - `{number}` `top` The top position of the textNode.
  277.      *   - `{number}` `bottom` The bottom position of the textNode.
  278.      *   - `{number}` `width` The width of the textNode.
  279.      *   - `{number}` `height` The height of the textNode.
  280.      */
  281.     getTextBounds: function(textNode) {
  282.       if (document.createRange) {
  283.         var range = document.createRange();
  284.         range.selectNodeContents(textNode);
  285.         if (range.getBoundingClientRect) {
  286.           var rect = range.getBoundingClientRect();
  287.           if (rect) {
  288.             var sx = window.scrollX;
  289.             var sy = window.scrollY;
  290.  
  291.             return {
  292.               top: rect.top + sy,
  293.               left: rect.left + sx,
  294.               right: rect.left + sx + rect.width,
  295.               bottom: rect.top + sy + rect.height,
  296.               width: rect.width,
  297.               height: rect.height
  298.             };
  299.           }
  300.         }
  301.       }
  302.       return null;
  303.     },
  304.  
  305.     /**
  306.      * @ngdoc method
  307.      * @name ionic.DomUtil#getChildIndex
  308.      * @description
  309.      * Get the first index of a child node within the given element of the
  310.      * specified type.
  311.      * @param {DOMElement} element The element to find the index of.
  312.      * @param {string} type The nodeName to match children of element against.
  313.      * @returns {number} The index, or -1, of a child with nodeName matching type.
  314.      */
  315.     getChildIndex: function(element, type) {
  316.       if (type) {
  317.         var ch = element.parentNode.children;
  318.         var c;
  319.         for (var i = 0, k = 0, j = ch.length; i < j; i++) {
  320.           c = ch[i];
  321.           if (c.nodeName && c.nodeName.toLowerCase() == type) {
  322.             if (c == element) {
  323.               return k;
  324.             }
  325.             k++;
  326.           }
  327.         }
  328.       }
  329.       return Array.prototype.slice.call(element.parentNode.children).indexOf(element);
  330.     },
  331.  
  332.     /**
  333.      * @private
  334.      */
  335.     swapNodes: function(src, dest) {
  336.       dest.parentNode.insertBefore(src, dest);
  337.     },
  338.  
  339.     elementIsDescendant: function(el, parent, stopAt) {
  340.       var current = el;
  341.       do {
  342.         if (current === parent) return true;
  343.         current = current.parentNode;
  344.       } while (current && current !== stopAt);
  345.       return false;
  346.     },
  347.  
  348.     /**
  349.      * @ngdoc method
  350.      * @name ionic.DomUtil#getParentWithClass
  351.      * @param {DOMElement} element
  352.      * @param {string} className
  353.      * @returns {DOMElement} The closest parent of element matching the
  354.      * className, or null.
  355.      */
  356.     getParentWithClass: function(e, className, depth) {
  357.       depth = depth || 10;
  358.       while (e.parentNode && depth--) {
  359.         if (e.parentNode.classList && e.parentNode.classList.contains(className)) {
  360.           return e.parentNode;
  361.         }
  362.         e = e.parentNode;
  363.       }
  364.       return null;
  365.     },
  366.     /**
  367.      * @ngdoc method
  368.      * @name ionic.DomUtil#getParentOrSelfWithClass
  369.      * @param {DOMElement} element
  370.      * @param {string} className
  371.      * @returns {DOMElement} The closest parent or self matching the
  372.      * className, or null.
  373.      */
  374.     getParentOrSelfWithClass: function(e, className, depth) {
  375.       depth = depth || 10;
  376.       while (e && depth--) {
  377.         if (e.classList && e.classList.contains(className)) {
  378.           return e;
  379.         }
  380.         e = e.parentNode;
  381.       }
  382.       return null;
  383.     },
  384.  
  385.     /**
  386.      * @ngdoc method
  387.      * @name ionic.DomUtil#rectContains
  388.      * @param {number} x
  389.      * @param {number} y
  390.      * @param {number} x1
  391.      * @param {number} y1
  392.      * @param {number} x2
  393.      * @param {number} y2
  394.      * @returns {boolean} Whether {x,y} fits within the rectangle defined by
  395.      * {x1,y1,x2,y2}.
  396.      */
  397.     rectContains: function(x, y, x1, y1, x2, y2) {
  398.       if (x < x1 || x > x2) return false;
  399.       if (y < y1 || y > y2) return false;
  400.       return true;
  401.     },
  402.  
  403.     /**
  404.      * @ngdoc method
  405.      * @name ionic.DomUtil#blurAll
  406.      * @description
  407.      * Blurs any currently focused input element
  408.      * @returns {DOMElement} The element blurred or null
  409.      */
  410.     blurAll: function() {
  411.       if (document.activeElement && document.activeElement != document.body) {
  412.         document.activeElement.blur();
  413.         return document.activeElement;
  414.       }
  415.       return null;
  416.     },
  417.  
  418.     cachedAttr: function(ele, key, value) {
  419.       ele = ele && ele.length && ele[0] || ele;
  420.       if (ele && ele.setAttribute) {
  421.         var dataKey = '$attr-' + key;
  422.         if (arguments.length > 2) {
  423.           if (ele[dataKey] !== value) {
  424.             ele.setAttribute(key, value);
  425.             ele[dataKey] = value;
  426.           }
  427.         } else if (typeof ele[dataKey] == 'undefined') {
  428.           ele[dataKey] = ele.getAttribute(key);
  429.         }
  430.         return ele[dataKey];
  431.       }
  432.     },
  433.  
  434.     cachedStyles: function(ele, styles) {
  435.       ele = ele && ele.length && ele[0] || ele;
  436.       if (ele && ele.style) {
  437.         for (var prop in styles) {
  438.           if (ele['$style-' + prop] !== styles[prop]) {
  439.             ele.style[prop] = ele['$style-' + prop] = styles[prop];
  440.           }
  441.         }
  442.       }
  443.     }
  444.  
  445.   };
  446.  
  447.   //Shortcuts
  448.   ionic.requestAnimationFrame = ionic.DomUtil.requestAnimationFrame;
  449.   ionic.cancelAnimationFrame = ionic.DomUtil.cancelAnimationFrame;
  450.   ionic.animationFrameThrottle = ionic.DomUtil.animationFrameThrottle;
  451.  
  452. })(window, document, ionic);
  453.  
  454. /**
  455.  * ion-events.js
  456.  *
  457.  * Author: Max Lynch <max@drifty.com>
  458.  *
  459.  * Framework events handles various mobile browser events, and
  460.  * detects special events like tap/swipe/etc. and emits them
  461.  * as custom events that can be used in an app.
  462.  *
  463.  * Portions lovingly adapted from github.com/maker/ratchet and github.com/alexgibson/tap.js - thanks guys!
  464.  */
  465.  
  466. (function(ionic) {
  467.  
  468.   // Custom event polyfill
  469.   ionic.CustomEvent = (function() {
  470.     if( typeof window.CustomEvent === 'function' ) return CustomEvent;
  471.  
  472.     var customEvent = function(event, params) {
  473.       var evt;
  474.       params = params || {
  475.         bubbles: false,
  476.         cancelable: false,
  477.         detail: undefined
  478.       };
  479.       try {
  480.         evt = document.createEvent("CustomEvent");
  481.         evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
  482.       } catch (error) {
  483.         // fallback for browsers that don't support createEvent('CustomEvent')
  484.         evt = document.createEvent("Event");
  485.         for (var param in params) {
  486.           evt[param] = params[param];
  487.         }
  488.         evt.initEvent(event, params.bubbles, params.cancelable);
  489.       }
  490.       return evt;
  491.     };
  492.     customEvent.prototype = window.Event.prototype;
  493.     return customEvent;
  494.   })();
  495.  
  496.  
  497.   /**
  498.    * @ngdoc utility
  499.    * @name ionic.EventController
  500.    * @module ionic
  501.    */
  502.   ionic.EventController = {
  503.     VIRTUALIZED_EVENTS: ['tap', 'swipe', 'swiperight', 'swipeleft', 'drag', 'hold', 'release'],
  504.  
  505.     /**
  506.      * @ngdoc method
  507.      * @name ionic.EventController#trigger
  508.      * @alias ionic.trigger
  509.      * @param {string} eventType The event to trigger.
  510.      * @param {object} data The data for the event. Hint: pass in
  511.      * `{target: targetElement}`
  512.      * @param {boolean=} bubbles Whether the event should bubble up the DOM.
  513.      * @param {boolean=} cancelable Whether the event should be cancelable.
  514.      */
  515.     // Trigger a new event
  516.     trigger: function(eventType, data, bubbles, cancelable) {
  517.       var event = new ionic.CustomEvent(eventType, {
  518.         detail: data,
  519.         bubbles: !!bubbles,
  520.         cancelable: !!cancelable
  521.       });
  522.  
  523.       // Make sure to trigger the event on the given target, or dispatch it from
  524.       // the window if we don't have an event target
  525.       data && data.target && data.target.dispatchEvent && data.target.dispatchEvent(event) || window.dispatchEvent(event);
  526.     },
  527.  
  528.     /**
  529.      * @ngdoc method
  530.      * @name ionic.EventController#on
  531.      * @alias ionic.on
  532.      * @description Listen to an event on an element.
  533.      * @param {string} type The event to listen for.
  534.      * @param {function} callback The listener to be called.
  535.      * @param {DOMElement} element The element to listen for the event on.
  536.      */
  537.     on: function(type, callback, element) {
  538.       var e = element || window;
  539.  
  540.       // Bind a gesture if it's a virtual event
  541.       for(var i = 0, j = this.VIRTUALIZED_EVENTS.length; i < j; i++) {
  542.         if(type == this.VIRTUALIZED_EVENTS[i]) {
  543.           var gesture = new ionic.Gesture(element);
  544.           gesture.on(type, callback);
  545.           return gesture;
  546.         }
  547.       }
  548.  
  549.       // Otherwise bind a normal event
  550.       e.addEventListener(type, callback);
  551.     },
  552.  
  553.     /**
  554.      * @ngdoc method
  555.      * @name ionic.EventController#off
  556.      * @alias ionic.off
  557.      * @description Remove an event listener.
  558.      * @param {string} type
  559.      * @param {function} callback
  560.      * @param {DOMElement} element
  561.      */
  562.     off: function(type, callback, element) {
  563.       element.removeEventListener(type, callback);
  564.     },
  565.  
  566.     /**
  567.      * @ngdoc method
  568.      * @name ionic.EventController#onGesture
  569.      * @alias ionic.onGesture
  570.      * @description Add an event listener for a gesture on an element.
  571.      *
  572.      * Available eventTypes (from [hammer.js](http://eightmedia.github.io/hammer.js/)):
  573.      *
  574.      * `hold`, `tap`, `doubletap`, `drag`, `dragstart`, `dragend`, `dragup`, `dragdown`, <br/>
  575.      * `dragleft`, `dragright`, `swipe`, `swipeup`, `swipedown`, `swipeleft`, `swiperight`, <br/>
  576.      * `transform`, `transformstart`, `transformend`, `rotate`, `pinch`, `pinchin`, `pinchout`, </br>
  577.      * `touch`, `release`
  578.      *
  579.      * @param {string} eventType The gesture event to listen for.
  580.      * @param {function(e)} callback The function to call when the gesture
  581.      * happens.
  582.      * @param {DOMElement} element The angular element to listen for the event on.
  583.      * @param {object} options object.
  584.      * @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on).
  585.      */
  586.     onGesture: function(type, callback, element, options) {
  587.       var gesture = new ionic.Gesture(element, options);
  588.       gesture.on(type, callback);
  589.       return gesture;
  590.     },
  591.  
  592.     /**
  593.      * @ngdoc method
  594.      * @name ionic.EventController#offGesture
  595.      * @alias ionic.offGesture
  596.      * @description Remove an event listener for a gesture created on an element.
  597.      * @param {ionic.Gesture} gesture The gesture that should be removed.
  598.      * @param {string} eventType The gesture event to remove the listener for.
  599.      * @param {function(e)} callback The listener to remove.
  600.  
  601.      */
  602.     offGesture: function(gesture, type, callback) {
  603.       gesture && gesture.off(type, callback);
  604.     },
  605.  
  606.     handlePopState: function() {}
  607.   };
  608.  
  609.  
  610.   // Map some convenient top-level functions for event handling
  611.   ionic.on = function() { ionic.EventController.on.apply(ionic.EventController, arguments); };
  612.   ionic.off = function() { ionic.EventController.off.apply(ionic.EventController, arguments); };
  613.   ionic.trigger = ionic.EventController.trigger;//function() { ionic.EventController.trigger.apply(ionic.EventController.trigger, arguments); };
  614.   ionic.onGesture = function() { return ionic.EventController.onGesture.apply(ionic.EventController.onGesture, arguments); };
  615.   ionic.offGesture = function() { return ionic.EventController.offGesture.apply(ionic.EventController.offGesture, arguments); };
  616.  
  617. })(window.ionic);
  618.  
  619. /* eslint camelcase:0 */
  620. /**
  621.   * Simple gesture controllers with some common gestures that emit
  622.   * gesture events.
  623.   *
  624.   * Ported from github.com/EightMedia/hammer.js Gestures - thanks!
  625.   */
  626. (function(ionic) {
  627.  
  628.   /**
  629.    * ionic.Gestures
  630.    * use this to create instances
  631.    * @param   {HTMLElement}   element
  632.    * @param   {Object}        options
  633.    * @returns {ionic.Gestures.Instance}
  634.    * @constructor
  635.    */
  636.   ionic.Gesture = function(element, options) {
  637.     return new ionic.Gestures.Instance(element, options || {});
  638.   };
  639.  
  640.   ionic.Gestures = {};
  641.  
  642.   // default settings
  643.   ionic.Gestures.defaults = {
  644.     // add css to the element to prevent the browser from doing
  645.     // its native behavior. this doesnt prevent the scrolling,
  646.     // but cancels the contextmenu, tap highlighting etc
  647.     // set to false to disable this
  648.     stop_browser_behavior: 'disable-user-behavior'
  649.   };
  650.  
  651.   // detect touchevents
  652.   ionic.Gestures.HAS_POINTEREVENTS = window.navigator.pointerEnabled || window.navigator.msPointerEnabled;
  653.   ionic.Gestures.HAS_TOUCHEVENTS = ('ontouchstart' in window);
  654.  
  655.   // dont use mouseevents on mobile devices
  656.   ionic.Gestures.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android|silk/i;
  657.   ionic.Gestures.NO_MOUSEEVENTS = ionic.Gestures.HAS_TOUCHEVENTS && window.navigator.userAgent.match(ionic.Gestures.MOBILE_REGEX);
  658.  
  659.   // eventtypes per touchevent (start, move, end)
  660.   // are filled by ionic.Gestures.event.determineEventTypes on setup
  661.   ionic.Gestures.EVENT_TYPES = {};
  662.  
  663.   // direction defines
  664.   ionic.Gestures.DIRECTION_DOWN = 'down';
  665.   ionic.Gestures.DIRECTION_LEFT = 'left';
  666.   ionic.Gestures.DIRECTION_UP = 'up';
  667.   ionic.Gestures.DIRECTION_RIGHT = 'right';
  668.  
  669.   // pointer type
  670.   ionic.Gestures.POINTER_MOUSE = 'mouse';
  671.   ionic.Gestures.POINTER_TOUCH = 'touch';
  672.   ionic.Gestures.POINTER_PEN = 'pen';
  673.  
  674.   // touch event defines
  675.   ionic.Gestures.EVENT_START = 'start';
  676.   ionic.Gestures.EVENT_MOVE = 'move';
  677.   ionic.Gestures.EVENT_END = 'end';
  678.  
  679.   // hammer document where the base events are added at
  680.   ionic.Gestures.DOCUMENT = window.document;
  681.  
  682.   // plugins namespace
  683.   ionic.Gestures.plugins = {};
  684.  
  685.   // if the window events are set...
  686.   ionic.Gestures.READY = false;
  687.  
  688.   /**
  689.    * setup events to detect gestures on the document
  690.    */
  691.   function setup() {
  692.     if(ionic.Gestures.READY) {
  693.       return;
  694.     }
  695.  
  696.     // find what eventtypes we add listeners to
  697.     ionic.Gestures.event.determineEventTypes();
  698.  
  699.     // Register all gestures inside ionic.Gestures.gestures
  700.     for(var name in ionic.Gestures.gestures) {
  701.       if(ionic.Gestures.gestures.hasOwnProperty(name)) {
  702.         ionic.Gestures.detection.register(ionic.Gestures.gestures[name]);
  703.       }
  704.     }
  705.  
  706.     // Add touch events on the document
  707.     ionic.Gestures.event.onTouch(ionic.Gestures.DOCUMENT, ionic.Gestures.EVENT_MOVE, ionic.Gestures.detection.detect);
  708.     ionic.Gestures.event.onTouch(ionic.Gestures.DOCUMENT, ionic.Gestures.EVENT_END, ionic.Gestures.detection.detect);
  709.  
  710.     // ionic.Gestures is ready...!
  711.     ionic.Gestures.READY = true;
  712.   }
  713.  
  714.   /**
  715.    * create new hammer instance
  716.    * all methods should return the instance itself, so it is chainable.
  717.    * @param   {HTMLElement}       element
  718.    * @param   {Object}            [options={}]
  719.    * @returns {ionic.Gestures.Instance}
  720.    * @name Gesture.Instance
  721.    * @constructor
  722.    */
  723.   ionic.Gestures.Instance = function(element, options) {
  724.     var self = this;
  725.  
  726.     // A null element was passed into the instance, which means
  727.     // whatever lookup was done to find this element failed to find it
  728.     // so we can't listen for events on it.
  729.     if(element === null) {
  730.       void 0;
  731.       return this;
  732.     }
  733.  
  734.     // setup ionic.GesturesJS window events and register all gestures
  735.     // this also sets up the default options
  736.     setup();
  737.  
  738.     this.element = element;
  739.  
  740.     // start/stop detection option
  741.     this.enabled = true;
  742.  
  743.     // merge options
  744.     this.options = ionic.Gestures.utils.extend(
  745.         ionic.Gestures.utils.extend({}, ionic.Gestures.defaults),
  746.         options || {});
  747.  
  748.     // add some css to the element to prevent the browser from doing its native behavoir
  749.     if(this.options.stop_browser_behavior) {
  750.       ionic.Gestures.utils.stopDefaultBrowserBehavior(this.element, this.options.stop_browser_behavior);
  751.     }
  752.  
  753.     // start detection on touchstart
  754.     ionic.Gestures.event.onTouch(element, ionic.Gestures.EVENT_START, function(ev) {
  755.       if(self.enabled) {
  756.         ionic.Gestures.detection.startDetect(self, ev);
  757.       }
  758.     });
  759.  
  760.     // return instance
  761.     return this;
  762.   };
  763.  
  764.  
  765.   ionic.Gestures.Instance.prototype = {
  766.     /**
  767.      * bind events to the instance
  768.      * @param   {String}      gesture
  769.      * @param   {Function}    handler
  770.      * @returns {ionic.Gestures.Instance}
  771.      */
  772.     on: function onEvent(gesture, handler){
  773.       var gestures = gesture.split(' ');
  774.       for(var t = 0; t < gestures.length; t++) {
  775.         this.element.addEventListener(gestures[t], handler, false);
  776.       }
  777.       return this;
  778.     },
  779.  
  780.  
  781.     /**
  782.      * unbind events to the instance
  783.      * @param   {String}      gesture
  784.      * @param   {Function}    handler
  785.      * @returns {ionic.Gestures.Instance}
  786.      */
  787.     off: function offEvent(gesture, handler){
  788.       var gestures = gesture.split(' ');
  789.       for(var t = 0; t < gestures.length; t++) {
  790.         this.element.removeEventListener(gestures[t], handler, false);
  791.       }
  792.       return this;
  793.     },
  794.  
  795.  
  796.     /**
  797.      * trigger gesture event
  798.      * @param   {String}      gesture
  799.      * @param   {Object}      eventData
  800.      * @returns {ionic.Gestures.Instance}
  801.      */
  802.     trigger: function triggerEvent(gesture, eventData){
  803.       // create DOM event
  804.       var event = ionic.Gestures.DOCUMENT.createEvent('Event');
  805.       event.initEvent(gesture, true, true);
  806.       event.gesture = eventData;
  807.  
  808.       // trigger on the target if it is in the instance element,
  809.       // this is for event delegation tricks
  810.       var element = this.element;
  811.       if(ionic.Gestures.utils.hasParent(eventData.target, element)) {
  812.         element = eventData.target;
  813.       }
  814.  
  815.       element.dispatchEvent(event);
  816.       return this;
  817.     },
  818.  
  819.  
  820.     /**
  821.      * enable of disable hammer.js detection
  822.      * @param   {Boolean}   state
  823.      * @returns {ionic.Gestures.Instance}
  824.      */
  825.     enable: function enable(state) {
  826.       this.enabled = state;
  827.       return this;
  828.     }
  829.   };
  830.  
  831.   /**
  832.    * this holds the last move event,
  833.    * used to fix empty touchend issue
  834.    * see the onTouch event for an explanation
  835.    * type {Object}
  836.    */
  837.   var last_move_event = null;
  838.  
  839.  
  840.   /**
  841.    * when the mouse is hold down, this is true
  842.    * type {Boolean}
  843.    */
  844.   var enable_detect = false;
  845.  
  846.  
  847.   /**
  848.    * when touch events have been fired, this is true
  849.    * type {Boolean}
  850.    */
  851.   var touch_triggered = false;
  852.  
  853.  
  854.   ionic.Gestures.event = {
  855.     /**
  856.      * simple addEventListener
  857.      * @param   {HTMLElement}   element
  858.      * @param   {String}        type
  859.      * @param   {Function}      handler
  860.      */
  861.     bindDom: function(element, type, handler) {
  862.       var types = type.split(' ');
  863.       for(var t = 0; t < types.length; t++) {
  864.         element.addEventListener(types[t], handler, false);
  865.       }
  866.     },
  867.  
  868.  
  869.     /**
  870.      * touch events with mouse fallback
  871.      * @param   {HTMLElement}   element
  872.      * @param   {String}        eventType        like ionic.Gestures.EVENT_MOVE
  873.      * @param   {Function}      handler
  874.      */
  875.     onTouch: function onTouch(element, eventType, handler) {
  876.       var self = this;
  877.  
  878.       this.bindDom(element, ionic.Gestures.EVENT_TYPES[eventType], function bindDomOnTouch(ev) {
  879.         var sourceEventType = ev.type.toLowerCase();
  880.  
  881.         // onmouseup, but when touchend has been fired we do nothing.
  882.         // this is for touchdevices which also fire a mouseup on touchend
  883.         if(sourceEventType.match(/mouse/) && touch_triggered) {
  884.           return;
  885.         }
  886.  
  887.         // mousebutton must be down or a touch event
  888.         else if( sourceEventType.match(/touch/) ||   // touch events are always on screen
  889.           sourceEventType.match(/pointerdown/) || // pointerevents touch
  890.           (sourceEventType.match(/mouse/) && ev.which === 1)   // mouse is pressed
  891.           ){
  892.             enable_detect = true;
  893.           }
  894.  
  895.         // mouse isn't pressed
  896.         else if(sourceEventType.match(/mouse/) && ev.which !== 1) {
  897.           enable_detect = false;
  898.         }
  899.  
  900.  
  901.         // we are in a touch event, set the touch triggered bool to true,
  902.         // this for the conflicts that may occur on ios and android
  903.         if(sourceEventType.match(/touch|pointer/)) {
  904.           touch_triggered = true;
  905.         }
  906.  
  907.         // count the total touches on the screen
  908.         var count_touches = 0;
  909.  
  910.         // when touch has been triggered in this detection session
  911.         // and we are now handling a mouse event, we stop that to prevent conflicts
  912.         if(enable_detect) {
  913.           // update pointerevent
  914.           if(ionic.Gestures.HAS_POINTEREVENTS && eventType != ionic.Gestures.EVENT_END) {
  915.             count_touches = ionic.Gestures.PointerEvent.updatePointer(eventType, ev);
  916.           }
  917.           // touch
  918.           else if(sourceEventType.match(/touch/)) {
  919.             count_touches = ev.touches.length;
  920.           }
  921.           // mouse
  922.           else if(!touch_triggered) {
  923.             count_touches = sourceEventType.match(/up/) ? 0 : 1;
  924.           }
  925.  
  926.           // if we are in a end event, but when we remove one touch and
  927.           // we still have enough, set eventType to move
  928.           if(count_touches > 0 && eventType == ionic.Gestures.EVENT_END) {
  929.             eventType = ionic.Gestures.EVENT_MOVE;
  930.           }
  931.           // no touches, force the end event
  932.           else if(!count_touches) {
  933.             eventType = ionic.Gestures.EVENT_END;
  934.           }
  935.  
  936.           // store the last move event
  937.           if(count_touches || last_move_event === null) {
  938.             last_move_event = ev;
  939.           }
  940.  
  941.           // trigger the handler
  942.           handler.call(ionic.Gestures.detection, self.collectEventData(element, eventType, self.getTouchList(last_move_event, eventType), ev));
  943.  
  944.           // remove pointerevent from list
  945.           if(ionic.Gestures.HAS_POINTEREVENTS && eventType == ionic.Gestures.EVENT_END) {
  946.             count_touches = ionic.Gestures.PointerEvent.updatePointer(eventType, ev);
  947.           }
  948.         }
  949.  
  950.         //debug(sourceEventType +" "+ eventType);
  951.  
  952.         // on the end we reset everything
  953.         if(!count_touches) {
  954.           last_move_event = null;
  955.           enable_detect = false;
  956.           touch_triggered = false;
  957.           ionic.Gestures.PointerEvent.reset();
  958.         }
  959.       });
  960.     },
  961.  
  962.  
  963.     /**
  964.      * we have different events for each device/browser
  965.      * determine what we need and set them in the ionic.Gestures.EVENT_TYPES constant
  966.      */
  967.     determineEventTypes: function determineEventTypes() {
  968.       // determine the eventtype we want to set
  969.       var types;
  970.  
  971.       // pointerEvents magic
  972.       if(ionic.Gestures.HAS_POINTEREVENTS) {
  973.         types = ionic.Gestures.PointerEvent.getEvents();
  974.       }
  975.       // on Android, iOS, blackberry, windows mobile we dont want any mouseevents
  976.       else if(ionic.Gestures.NO_MOUSEEVENTS) {
  977.         types = [
  978.           'touchstart',
  979.           'touchmove',
  980.           'touchend touchcancel'];
  981.       }
  982.       // for non pointer events browsers and mixed browsers,
  983.       // like chrome on windows8 touch laptop
  984.       else {
  985.         types = [
  986.           'touchstart mousedown',
  987.           'touchmove mousemove',
  988.           'touchend touchcancel mouseup'];
  989.       }
  990.  
  991.       ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_START] = types[0];
  992.       ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_MOVE] = types[1];
  993.       ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_END] = types[2];
  994.     },
  995.  
  996.  
  997.     /**
  998.      * create touchlist depending on the event
  999.      * @param   {Object}    ev
  1000.      * @param   {String}    eventType   used by the fakemultitouch plugin
  1001.      */
  1002.     getTouchList: function getTouchList(ev/*, eventType*/) {
  1003.       // get the fake pointerEvent touchlist
  1004.       if(ionic.Gestures.HAS_POINTEREVENTS) {
  1005.         return ionic.Gestures.PointerEvent.getTouchList();
  1006.       }
  1007.       // get the touchlist
  1008.       else if(ev.touches) {
  1009.         return ev.touches;
  1010.       }
  1011.       // make fake touchlist from mouse position
  1012.       else {
  1013.         ev.identifier = 1;
  1014.         return [ev];
  1015.       }
  1016.     },
  1017.  
  1018.  
  1019.     /**
  1020.      * collect event data for ionic.Gestures js
  1021.      * @param   {HTMLElement}   element
  1022.      * @param   {String}        eventType        like ionic.Gestures.EVENT_MOVE
  1023.      * @param   {Object}        eventData
  1024.      */
  1025.     collectEventData: function collectEventData(element, eventType, touches, ev) {
  1026.  
  1027.       // find out pointerType
  1028.       var pointerType = ionic.Gestures.POINTER_TOUCH;
  1029.       if(ev.type.match(/mouse/) || ionic.Gestures.PointerEvent.matchType(ionic.Gestures.POINTER_MOUSE, ev)) {
  1030.         pointerType = ionic.Gestures.POINTER_MOUSE;
  1031.       }
  1032.  
  1033.       return {
  1034.         center: ionic.Gestures.utils.getCenter(touches),
  1035.         timeStamp: new Date().getTime(),
  1036.         target: ev.target,
  1037.         touches: touches,
  1038.         eventType: eventType,
  1039.         pointerType: pointerType,
  1040.         srcEvent: ev,
  1041.  
  1042.         /**
  1043.          * prevent the browser default actions
  1044.          * mostly used to disable scrolling of the browser
  1045.          */
  1046.         preventDefault: function() {
  1047.           if(this.srcEvent.preventManipulation) {
  1048.             this.srcEvent.preventManipulation();
  1049.           }
  1050.  
  1051.           if(this.srcEvent.preventDefault) {
  1052.             // this.srcEvent.preventDefault();
  1053.           }
  1054.         },
  1055.  
  1056.         /**
  1057.          * stop bubbling the event up to its parents
  1058.          */
  1059.         stopPropagation: function() {
  1060.           this.srcEvent.stopPropagation();
  1061.         },
  1062.  
  1063.         /**
  1064.          * immediately stop gesture detection
  1065.          * might be useful after a swipe was detected
  1066.          * @return {*}
  1067.          */
  1068.         stopDetect: function() {
  1069.           return ionic.Gestures.detection.stopDetect();
  1070.         }
  1071.       };
  1072.     }
  1073.   };
  1074.  
  1075.   ionic.Gestures.PointerEvent = {
  1076.     /**
  1077.      * holds all pointers
  1078.      * type {Object}
  1079.      */
  1080.     pointers: {},
  1081.  
  1082.     /**
  1083.      * get a list of pointers
  1084.      * @returns {Array}     touchlist
  1085.      */
  1086.     getTouchList: function() {
  1087.       var self = this;
  1088.       var touchlist = [];
  1089.  
  1090.       // we can use forEach since pointerEvents only is in IE10
  1091.       Object.keys(self.pointers).sort().forEach(function(id) {
  1092.         touchlist.push(self.pointers[id]);
  1093.       });
  1094.       return touchlist;
  1095.     },
  1096.  
  1097.     /**
  1098.      * update the position of a pointer
  1099.      * @param   {String}   type             ionic.Gestures.EVENT_END
  1100.      * @param   {Object}   pointerEvent
  1101.      */
  1102.     updatePointer: function(type, pointerEvent) {
  1103.       if(type == ionic.Gestures.EVENT_END) {
  1104.         this.pointers = {};
  1105.       }
  1106.       else {
  1107.         pointerEvent.identifier = pointerEvent.pointerId;
  1108.         this.pointers[pointerEvent.pointerId] = pointerEvent;
  1109.       }
  1110.  
  1111.       return Object.keys(this.pointers).length;
  1112.     },
  1113.  
  1114.     /**
  1115.      * check if ev matches pointertype
  1116.      * @param   {String}        pointerType     ionic.Gestures.POINTER_MOUSE
  1117.      * @param   {PointerEvent}  ev
  1118.      */
  1119.     matchType: function(pointerType, ev) {
  1120.       if(!ev.pointerType) {
  1121.         return false;
  1122.       }
  1123.  
  1124.       var types = {};
  1125.       types[ionic.Gestures.POINTER_MOUSE] = (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE || ev.pointerType == ionic.Gestures.POINTER_MOUSE);
  1126.       types[ionic.Gestures.POINTER_TOUCH] = (ev.pointerType == ev.MSPOINTER_TYPE_TOUCH || ev.pointerType == ionic.Gestures.POINTER_TOUCH);
  1127.       types[ionic.Gestures.POINTER_PEN] = (ev.pointerType == ev.MSPOINTER_TYPE_PEN || ev.pointerType == ionic.Gestures.POINTER_PEN);
  1128.       return types[pointerType];
  1129.     },
  1130.  
  1131.  
  1132.     /**
  1133.      * get events
  1134.      */
  1135.     getEvents: function() {
  1136.       return [
  1137.         'pointerdown MSPointerDown',
  1138.       'pointermove MSPointerMove',
  1139.       'pointerup pointercancel MSPointerUp MSPointerCancel'
  1140.         ];
  1141.     },
  1142.  
  1143.     /**
  1144.      * reset the list
  1145.      */
  1146.     reset: function() {
  1147.       this.pointers = {};
  1148.     }
  1149.   };
  1150.  
  1151.  
  1152.   ionic.Gestures.utils = {
  1153.     /**
  1154.      * extend method,
  1155.      * also used for cloning when dest is an empty object
  1156.      * @param   {Object}    dest
  1157.      * @param   {Object}    src
  1158.      * @param   {Boolean}   merge       do a merge
  1159.      * @returns {Object}    dest
  1160.      */
  1161.     extend: function extend(dest, src, merge) {
  1162.       for (var key in src) {
  1163.         if(dest[key] !== undefined && merge) {
  1164.           continue;
  1165.         }
  1166.         dest[key] = src[key];
  1167.       }
  1168.       return dest;
  1169.     },
  1170.  
  1171.  
  1172.     /**
  1173.      * find if a node is in the given parent
  1174.      * used for event delegation tricks
  1175.      * @param   {HTMLElement}   node
  1176.      * @param   {HTMLElement}   parent
  1177.      * @returns {boolean}       has_parent
  1178.      */
  1179.     hasParent: function(node, parent) {
  1180.       while(node){
  1181.         if(node == parent) {
  1182.           return true;
  1183.         }
  1184.         node = node.parentNode;
  1185.       }
  1186.       return false;
  1187.     },
  1188.  
  1189.  
  1190.     /**
  1191.      * get the center of all the touches
  1192.      * @param   {Array}     touches
  1193.      * @returns {Object}    center
  1194.      */
  1195.     getCenter: function getCenter(touches) {
  1196.       var valuesX = [], valuesY = [];
  1197.  
  1198.       for(var t = 0, len = touches.length; t < len; t++) {
  1199.         valuesX.push(touches[t].pageX);
  1200.         valuesY.push(touches[t].pageY);
  1201.       }
  1202.  
  1203.       return {
  1204.         pageX: ((Math.min.apply(Math, valuesX) + Math.max.apply(Math, valuesX)) / 2),
  1205.           pageY: ((Math.min.apply(Math, valuesY) + Math.max.apply(Math, valuesY)) / 2)
  1206.       };
  1207.     },
  1208.  
  1209.  
  1210.     /**
  1211.      * calculate the velocity between two points
  1212.      * @param   {Number}    delta_time
  1213.      * @param   {Number}    delta_x
  1214.      * @param   {Number}    delta_y
  1215.      * @returns {Object}    velocity
  1216.      */
  1217.     getVelocity: function getVelocity(delta_time, delta_x, delta_y) {
  1218.       return {
  1219.         x: Math.abs(delta_x / delta_time) || 0,
  1220.         y: Math.abs(delta_y / delta_time) || 0
  1221.       };
  1222.     },
  1223.  
  1224.  
  1225.     /**
  1226.      * calculate the angle between two coordinates
  1227.      * @param   {Touch}     touch1
  1228.      * @param   {Touch}     touch2
  1229.      * @returns {Number}    angle
  1230.      */
  1231.     getAngle: function getAngle(touch1, touch2) {
  1232.       var y = touch2.pageY - touch1.pageY,
  1233.       x = touch2.pageX - touch1.pageX;
  1234.       return Math.atan2(y, x) * 180 / Math.PI;
  1235.     },
  1236.  
  1237.  
  1238.     /**
  1239.      * angle to direction define
  1240.      * @param   {Touch}     touch1
  1241.      * @param   {Touch}     touch2
  1242.      * @returns {String}    direction constant, like ionic.Gestures.DIRECTION_LEFT
  1243.      */
  1244.     getDirection: function getDirection(touch1, touch2) {
  1245.       var x = Math.abs(touch1.pageX - touch2.pageX),
  1246.       y = Math.abs(touch1.pageY - touch2.pageY);
  1247.  
  1248.       if(x >= y) {
  1249.         return touch1.pageX - touch2.pageX > 0 ? ionic.Gestures.DIRECTION_LEFT : ionic.Gestures.DIRECTION_RIGHT;
  1250.       }
  1251.       else {
  1252.         return touch1.pageY - touch2.pageY > 0 ? ionic.Gestures.DIRECTION_UP : ionic.Gestures.DIRECTION_DOWN;
  1253.       }
  1254.     },
  1255.  
  1256.  
  1257.     /**
  1258.      * calculate the distance between two touches
  1259.      * @param   {Touch}     touch1
  1260.      * @param   {Touch}     touch2
  1261.      * @returns {Number}    distance
  1262.      */
  1263.     getDistance: function getDistance(touch1, touch2) {
  1264.       var x = touch2.pageX - touch1.pageX,
  1265.       y = touch2.pageY - touch1.pageY;
  1266.       return Math.sqrt((x * x) + (y * y));
  1267.     },
  1268.  
  1269.  
  1270.     /**
  1271.      * calculate the scale factor between two touchLists (fingers)
  1272.      * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
  1273.      * @param   {Array}     start
  1274.      * @param   {Array}     end
  1275.      * @returns {Number}    scale
  1276.      */
  1277.     getScale: function getScale(start, end) {
  1278.       // need two fingers...
  1279.       if(start.length >= 2 && end.length >= 2) {
  1280.         return this.getDistance(end[0], end[1]) /
  1281.           this.getDistance(start[0], start[1]);
  1282.       }
  1283.       return 1;
  1284.     },
  1285.  
  1286.  
  1287.     /**
  1288.      * calculate the rotation degrees between two touchLists (fingers)
  1289.      * @param   {Array}     start
  1290.      * @param   {Array}     end
  1291.      * @returns {Number}    rotation
  1292.      */
  1293.     getRotation: function getRotation(start, end) {
  1294.       // need two fingers
  1295.       if(start.length >= 2 && end.length >= 2) {
  1296.         return this.getAngle(end[1], end[0]) -
  1297.           this.getAngle(start[1], start[0]);
  1298.       }
  1299.       return 0;
  1300.     },
  1301.  
  1302.  
  1303.     /**
  1304.      * boolean if the direction is vertical
  1305.      * @param    {String}    direction
  1306.      * @returns  {Boolean}   is_vertical
  1307.      */
  1308.     isVertical: function isVertical(direction) {
  1309.       return (direction == ionic.Gestures.DIRECTION_UP || direction == ionic.Gestures.DIRECTION_DOWN);
  1310.     },
  1311.  
  1312.  
  1313.     /**
  1314.      * stop browser default behavior with css class
  1315.      * @param   {HtmlElement}   element
  1316.      * @param   {Object}        css_class
  1317.      */
  1318.     stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_class) {
  1319.       // changed from making many style changes to just adding a preset classname
  1320.       // less DOM manipulations, less code, and easier to control in the CSS side of things
  1321.       // hammer.js doesn't come with CSS, but ionic does, which is why we prefer this method
  1322.       if(element && element.classList) {
  1323.         element.classList.add(css_class);
  1324.         element.onselectstart = function() {
  1325.           return false;
  1326.         };
  1327.       }
  1328.     }
  1329.   };
  1330.  
  1331.  
  1332.   ionic.Gestures.detection = {
  1333.     // contains all registred ionic.Gestures.gestures in the correct order
  1334.     gestures: [],
  1335.  
  1336.     // data of the current ionic.Gestures.gesture detection session
  1337.     current: null,
  1338.  
  1339.     // the previous ionic.Gestures.gesture session data
  1340.     // is a full clone of the previous gesture.current object
  1341.     previous: null,
  1342.  
  1343.     // when this becomes true, no gestures are fired
  1344.     stopped: false,
  1345.  
  1346.  
  1347.     /**
  1348.      * start ionic.Gestures.gesture detection
  1349.      * @param   {ionic.Gestures.Instance}   inst
  1350.      * @param   {Object}            eventData
  1351.      */
  1352.     startDetect: function startDetect(inst, eventData) {
  1353.       // already busy with a ionic.Gestures.gesture detection on an element
  1354.       if(this.current) {
  1355.         return;
  1356.       }
  1357.  
  1358.       this.stopped = false;
  1359.  
  1360.       this.current = {
  1361.         inst: inst, // reference to ionic.GesturesInstance we're working for
  1362.         startEvent: ionic.Gestures.utils.extend({}, eventData), // start eventData for distances, timing etc
  1363.         lastEvent: false, // last eventData
  1364.         name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
  1365.       };
  1366.  
  1367.       this.detect(eventData);
  1368.     },
  1369.  
  1370.  
  1371.     /**
  1372.      * ionic.Gestures.gesture detection
  1373.      * @param   {Object}    eventData
  1374.      */
  1375.     detect: function detect(eventData) {
  1376.       if(!this.current || this.stopped) {
  1377.         return null;
  1378.       }
  1379.  
  1380.       // extend event data with calculations about scale, distance etc
  1381.       eventData = this.extendEventData(eventData);
  1382.  
  1383.       // instance options
  1384.       var inst_options = this.current.inst.options;
  1385.  
  1386.       // call ionic.Gestures.gesture handlers
  1387.       for(var g = 0, len = this.gestures.length; g < len; g++) {
  1388.         var gesture = this.gestures[g];
  1389.  
  1390.         // only when the instance options have enabled this gesture
  1391.         if(!this.stopped && inst_options[gesture.name] !== false) {
  1392.           // if a handler returns false, we stop with the detection
  1393.           if(gesture.handler.call(gesture, eventData, this.current.inst) === false) {
  1394.             this.stopDetect();
  1395.             break;
  1396.           }
  1397.         }
  1398.       }
  1399.  
  1400.       // store as previous event event
  1401.       if(this.current) {
  1402.         this.current.lastEvent = eventData;
  1403.       }
  1404.  
  1405.       // endevent, but not the last touch, so dont stop
  1406.       if(eventData.eventType == ionic.Gestures.EVENT_END && !eventData.touches.length - 1) {
  1407.         this.stopDetect();
  1408.       }
  1409.  
  1410.       return eventData;
  1411.     },
  1412.  
  1413.  
  1414.     /**
  1415.      * clear the ionic.Gestures.gesture vars
  1416.      * this is called on endDetect, but can also be used when a final ionic.Gestures.gesture has been detected
  1417.      * to stop other ionic.Gestures.gestures from being fired
  1418.      */
  1419.     stopDetect: function stopDetect() {
  1420.       // clone current data to the store as the previous gesture
  1421.       // used for the double tap gesture, since this is an other gesture detect session
  1422.       this.previous = ionic.Gestures.utils.extend({}, this.current);
  1423.  
  1424.       // reset the current
  1425.       this.current = null;
  1426.  
  1427.       // stopped!
  1428.       this.stopped = true;
  1429.     },
  1430.  
  1431.  
  1432.     /**
  1433.      * extend eventData for ionic.Gestures.gestures
  1434.      * @param   {Object}   ev
  1435.      * @returns {Object}   ev
  1436.      */
  1437.     extendEventData: function extendEventData(ev) {
  1438.       var startEv = this.current.startEvent;
  1439.  
  1440.       // if the touches change, set the new touches over the startEvent touches
  1441.       // this because touchevents don't have all the touches on touchstart, or the
  1442.       // user must place his fingers at the EXACT same time on the screen, which is not realistic
  1443.       // but, sometimes it happens that both fingers are touching at the EXACT same time
  1444.       if(startEv && (ev.touches.length != startEv.touches.length || ev.touches === startEv.touches)) {
  1445.         // extend 1 level deep to get the touchlist with the touch objects
  1446.         startEv.touches = [];
  1447.         for(var i = 0, len = ev.touches.length; i < len; i++) {
  1448.           startEv.touches.push(ionic.Gestures.utils.extend({}, ev.touches[i]));
  1449.         }
  1450.       }
  1451.  
  1452.       var delta_time = ev.timeStamp - startEv.timeStamp,
  1453.           delta_x = ev.center.pageX - startEv.center.pageX,
  1454.           delta_y = ev.center.pageY - startEv.center.pageY,
  1455.           velocity = ionic.Gestures.utils.getVelocity(delta_time, delta_x, delta_y);
  1456.  
  1457.       ionic.Gestures.utils.extend(ev, {
  1458.         deltaTime: delta_time,
  1459.         deltaX: delta_x,
  1460.         deltaY: delta_y,
  1461.  
  1462.         velocityX: velocity.x,
  1463.         velocityY: velocity.y,
  1464.  
  1465.         distance: ionic.Gestures.utils.getDistance(startEv.center, ev.center),
  1466.         angle: ionic.Gestures.utils.getAngle(startEv.center, ev.center),
  1467.         direction: ionic.Gestures.utils.getDirection(startEv.center, ev.center),
  1468.  
  1469.         scale: ionic.Gestures.utils.getScale(startEv.touches, ev.touches),
  1470.         rotation: ionic.Gestures.utils.getRotation(startEv.touches, ev.touches),
  1471.  
  1472.         startEvent: startEv
  1473.       });
  1474.  
  1475.       return ev;
  1476.     },
  1477.  
  1478.  
  1479.     /**
  1480.      * register new gesture
  1481.      * @param   {Object}    gesture object, see gestures.js for documentation
  1482.      * @returns {Array}     gestures
  1483.      */
  1484.     register: function register(gesture) {
  1485.       // add an enable gesture options if there is no given
  1486.       var options = gesture.defaults || {};
  1487.       if(options[gesture.name] === undefined) {
  1488.         options[gesture.name] = true;
  1489.       }
  1490.  
  1491.       // extend ionic.Gestures default options with the ionic.Gestures.gesture options
  1492.       ionic.Gestures.utils.extend(ionic.Gestures.defaults, options, true);
  1493.  
  1494.       // set its index
  1495.       gesture.index = gesture.index || 1000;
  1496.  
  1497.       // add ionic.Gestures.gesture to the list
  1498.       this.gestures.push(gesture);
  1499.  
  1500.       // sort the list by index
  1501.       this.gestures.sort(function(a, b) {
  1502.         if (a.index < b.index) {
  1503.           return -1;
  1504.         }
  1505.         if (a.index > b.index) {
  1506.           return 1;
  1507.         }
  1508.         return 0;
  1509.       });
  1510.  
  1511.       return this.gestures;
  1512.     }
  1513.   };
  1514.  
  1515.  
  1516.   ionic.Gestures.gestures = ionic.Gestures.gestures || {};
  1517.  
  1518.   /**
  1519.    * Custom gestures
  1520.    * ==============================
  1521.    *
  1522.    * Gesture object
  1523.    * --------------------
  1524.    * The object structure of a gesture:
  1525.    *
  1526.    * { name: 'mygesture',
  1527.    *   index: 1337,
  1528.    *   defaults: {
  1529.    *     mygesture_option: true
  1530.    *   }
  1531.    *   handler: function(type, ev, inst) {
  1532.    *     // trigger gesture event
  1533.    *     inst.trigger(this.name, ev);
  1534.    *   }
  1535.    * }
  1536.  
  1537.    * @param   {String}    name
  1538.    * this should be the name of the gesture, lowercase
  1539.    * it is also being used to disable/enable the gesture per instance config.
  1540.    *
  1541.    * @param   {Number}    [index=1000]
  1542.    * the index of the gesture, where it is going to be in the stack of gestures detection
  1543.    * like when you build an gesture that depends on the drag gesture, it is a good
  1544.    * idea to place it after the index of the drag gesture.
  1545.    *
  1546.    * @param   {Object}    [defaults={}]
  1547.    * the default settings of the gesture. these are added to the instance settings,
  1548.    * and can be overruled per instance. you can also add the name of the gesture,
  1549.    * but this is also added by default (and set to true).
  1550.    *
  1551.    * @param   {Function}  handler
  1552.    * this handles the gesture detection of your custom gesture and receives the
  1553.    * following arguments:
  1554.    *
  1555.    *      @param  {Object}    eventData
  1556.    *      event data containing the following properties:
  1557.    *          timeStamp   {Number}        time the event occurred
  1558.    *          target      {HTMLElement}   target element
  1559.    *          touches     {Array}         touches (fingers, pointers, mouse) on the screen
  1560.    *          pointerType {String}        kind of pointer that was used. matches ionic.Gestures.POINTER_MOUSE|TOUCH
  1561.    *          center      {Object}        center position of the touches. contains pageX and pageY
  1562.    *          deltaTime   {Number}        the total time of the touches in the screen
  1563.    *          deltaX      {Number}        the delta on x axis we haved moved
  1564.    *          deltaY      {Number}        the delta on y axis we haved moved
  1565.    *          velocityX   {Number}        the velocity on the x
  1566.    *          velocityY   {Number}        the velocity on y
  1567.    *          angle       {Number}        the angle we are moving
  1568.    *          direction   {String}        the direction we are moving. matches ionic.Gestures.DIRECTION_UP|DOWN|LEFT|RIGHT
  1569.    *          distance    {Number}        the distance we haved moved
  1570.    *          scale       {Number}        scaling of the touches, needs 2 touches
  1571.    *          rotation    {Number}        rotation of the touches, needs 2 touches *
  1572.    *          eventType   {String}        matches ionic.Gestures.EVENT_START|MOVE|END
  1573.    *          srcEvent    {Object}        the source event, like TouchStart or MouseDown *
  1574.    *          startEvent  {Object}        contains the same properties as above,
  1575.    *                                      but from the first touch. this is used to calculate
  1576.    *                                      distances, deltaTime, scaling etc
  1577.    *
  1578.    *      @param  {ionic.Gestures.Instance}    inst
  1579.    *      the instance we are doing the detection for. you can get the options from
  1580.    *      the inst.options object and trigger the gesture event by calling inst.trigger
  1581.    *
  1582.    *
  1583.    * Handle gestures
  1584.    * --------------------
  1585.    * inside the handler you can get/set ionic.Gestures.detectionic.current. This is the current
  1586.    * detection sessionic. It has the following properties
  1587.    *      @param  {String}    name
  1588.    *      contains the name of the gesture we have detected. it has not a real function,
  1589.    *      only to check in other gestures if something is detected.
  1590.    *      like in the drag gesture we set it to 'drag' and in the swipe gesture we can
  1591.    *      check if the current gesture is 'drag' by accessing ionic.Gestures.detectionic.current.name
  1592.    *
  1593.    *      readonly
  1594.    *      @param  {ionic.Gestures.Instance}    inst
  1595.    *      the instance we do the detection for
  1596.    *
  1597.    *      readonly
  1598.    *      @param  {Object}    startEvent
  1599.    *      contains the properties of the first gesture detection in this sessionic.
  1600.    *      Used for calculations about timing, distance, etc.
  1601.    *
  1602.    *      readonly
  1603.    *      @param  {Object}    lastEvent
  1604.    *      contains all the properties of the last gesture detect in this sessionic.
  1605.    *
  1606.    * after the gesture detection session has been completed (user has released the screen)
  1607.    * the ionic.Gestures.detectionic.current object is copied into ionic.Gestures.detectionic.previous,
  1608.    * this is usefull for gestures like doubletap, where you need to know if the
  1609.    * previous gesture was a tap
  1610.    *
  1611.    * options that have been set by the instance can be received by calling inst.options
  1612.    *
  1613.    * You can trigger a gesture event by calling inst.trigger("mygesture", event).
  1614.    * The first param is the name of your gesture, the second the event argument
  1615.    *
  1616.    *
  1617.    * Register gestures
  1618.    * --------------------
  1619.    * When an gesture is added to the ionic.Gestures.gestures object, it is auto registered
  1620.    * at the setup of the first ionic.Gestures instance. You can also call ionic.Gestures.detectionic.register
  1621.    * manually and pass your gesture object as a param
  1622.    *
  1623.    */
  1624.  
  1625.   /**
  1626.    * Hold
  1627.    * Touch stays at the same place for x time
  1628.    * events  hold
  1629.    */
  1630.   ionic.Gestures.gestures.Hold = {
  1631.     name: 'hold',
  1632.     index: 10,
  1633.     defaults: {
  1634.       hold_timeout: 500,
  1635.       hold_threshold: 1
  1636.     },
  1637.     timer: null,
  1638.     handler: function holdGesture(ev, inst) {
  1639.       switch(ev.eventType) {
  1640.         case ionic.Gestures.EVENT_START:
  1641.           // clear any running timers
  1642.           clearTimeout(this.timer);
  1643.  
  1644.           // set the gesture so we can check in the timeout if it still is
  1645.           ionic.Gestures.detection.current.name = this.name;
  1646.  
  1647.           // set timer and if after the timeout it still is hold,
  1648.           // we trigger the hold event
  1649.           this.timer = setTimeout(function() {
  1650.             if(ionic.Gestures.detection.current.name == 'hold') {
  1651.               ionic.tap.cancelClick();
  1652.               inst.trigger('hold', ev);
  1653.             }
  1654.           }, inst.options.hold_timeout);
  1655.           break;
  1656.  
  1657.           // when you move or end we clear the timer
  1658.         case ionic.Gestures.EVENT_MOVE:
  1659.           if(ev.distance > inst.options.hold_threshold) {
  1660.             clearTimeout(this.timer);
  1661.           }
  1662.           break;
  1663.  
  1664.         case ionic.Gestures.EVENT_END:
  1665.           clearTimeout(this.timer);
  1666.           break;
  1667.       }
  1668.     }
  1669.   };
  1670.  
  1671.  
  1672.   /**
  1673.    * Tap/DoubleTap
  1674.    * Quick touch at a place or double at the same place
  1675.    * events  tap, doubletap
  1676.    */
  1677.   ionic.Gestures.gestures.Tap = {
  1678.     name: 'tap',
  1679.     index: 100,
  1680.     defaults: {
  1681.       tap_max_touchtime: 250,
  1682.       tap_max_distance: 10,
  1683.       tap_always: true,
  1684.       doubletap_distance: 20,
  1685.       doubletap_interval: 300
  1686.     },
  1687.     handler: function tapGesture(ev, inst) {
  1688.       if(ev.eventType == ionic.Gestures.EVENT_END && ev.srcEvent.type != 'touchcancel') {
  1689.         // previous gesture, for the double tap since these are two different gesture detections
  1690.         var prev = ionic.Gestures.detection.previous,
  1691.         did_doubletap = false;
  1692.  
  1693.         // when the touchtime is higher then the max touch time
  1694.         // or when the moving distance is too much
  1695.         if(ev.deltaTime > inst.options.tap_max_touchtime ||
  1696.             ev.distance > inst.options.tap_max_distance) {
  1697.               return;
  1698.             }
  1699.  
  1700.         // check if double tap
  1701.         if(prev && prev.name == 'tap' &&
  1702.             (ev.timeStamp - prev.lastEvent.timeStamp) < inst.options.doubletap_interval &&
  1703.             ev.distance < inst.options.doubletap_distance) {
  1704.               inst.trigger('doubletap', ev);
  1705.               did_doubletap = true;
  1706.             }
  1707.  
  1708.         // do a single tap
  1709.         if(!did_doubletap || inst.options.tap_always) {
  1710.           ionic.Gestures.detection.current.name = 'tap';
  1711.           inst.trigger('tap', ev);
  1712.         }
  1713.       }
  1714.     }
  1715.   };
  1716.  
  1717.  
  1718.   /**
  1719.    * Swipe
  1720.    * triggers swipe events when the end velocity is above the threshold
  1721.    * events  swipe, swipeleft, swiperight, swipeup, swipedown
  1722.    */
  1723.   ionic.Gestures.gestures.Swipe = {
  1724.     name: 'swipe',
  1725.     index: 40,
  1726.     defaults: {
  1727.       // set 0 for unlimited, but this can conflict with transform
  1728.       swipe_max_touches: 1,
  1729.       swipe_velocity: 0.4
  1730.     },
  1731.     handler: function swipeGesture(ev, inst) {
  1732.       if(ev.eventType == ionic.Gestures.EVENT_END) {
  1733.         // max touches
  1734.         if(inst.options.swipe_max_touches > 0 &&
  1735.             ev.touches.length > inst.options.swipe_max_touches) {
  1736.               return;
  1737.             }
  1738.  
  1739.         // when the distance we moved is too small we skip this gesture
  1740.         // or we can be already in dragging
  1741.         if(ev.velocityX > inst.options.swipe_velocity ||
  1742.             ev.velocityY > inst.options.swipe_velocity) {
  1743.               // trigger swipe events
  1744.               inst.trigger(this.name, ev);
  1745.               inst.trigger(this.name + ev.direction, ev);
  1746.             }
  1747.       }
  1748.     }
  1749.   };
  1750.  
  1751.  
  1752.   /**
  1753.    * Drag
  1754.    * Move with x fingers (default 1) around on the page. Blocking the scrolling when
  1755.    * moving left and right is a good practice. When all the drag events are blocking
  1756.    * you disable scrolling on that area.
  1757.    * events  drag, drapleft, dragright, dragup, dragdown
  1758.    */
  1759.   ionic.Gestures.gestures.Drag = {
  1760.     name: 'drag',
  1761.     index: 50,
  1762.     defaults: {
  1763.       drag_min_distance: 10,
  1764.       // Set correct_for_drag_min_distance to true to make the starting point of the drag
  1765.       // be calculated from where the drag was triggered, not from where the touch started.
  1766.       // Useful to avoid a jerk-starting drag, which can make fine-adjustments
  1767.       // through dragging difficult, and be visually unappealing.
  1768.       correct_for_drag_min_distance: true,
  1769.       // set 0 for unlimited, but this can conflict with transform
  1770.       drag_max_touches: 1,
  1771.       // prevent default browser behavior when dragging occurs
  1772.       // be careful with it, it makes the element a blocking element
  1773.       // when you are using the drag gesture, it is a good practice to set this true
  1774.       drag_block_horizontal: true,
  1775.       drag_block_vertical: true,
  1776.       // drag_lock_to_axis keeps the drag gesture on the axis that it started on,
  1777.       // It disallows vertical directions if the initial direction was horizontal, and vice versa.
  1778.       drag_lock_to_axis: false,
  1779.       // drag lock only kicks in when distance > drag_lock_min_distance
  1780.       // This way, locking occurs only when the distance has become large enough to reliably determine the direction
  1781.       drag_lock_min_distance: 25,
  1782.       // prevent default if the gesture is going the given direction
  1783.       prevent_default_directions: []
  1784.     },
  1785.     triggered: false,
  1786.     handler: function dragGesture(ev, inst) {
  1787.       if (ev.srcEvent.type == 'touchstart' || ev.srcEvent.type == 'touchend') {
  1788.         this.preventedFirstMove = false;
  1789.  
  1790.       } else if (!this.preventedFirstMove && ev.srcEvent.type == 'touchmove') {
  1791.         // Prevent gestures that are not intended for this event handler from firing subsequent times
  1792.         if (inst.options.prevent_default_directions.length === 0
  1793.             || inst.options.prevent_default_directions.indexOf(ev.direction) != -1) {
  1794.           ev.srcEvent.preventDefault();
  1795.         }
  1796.         this.preventedFirstMove = true;
  1797.       }
  1798.  
  1799.       // current gesture isnt drag, but dragged is true
  1800.       // this means an other gesture is busy. now call dragend
  1801.       if(ionic.Gestures.detection.current.name != this.name && this.triggered) {
  1802.         inst.trigger(this.name + 'end', ev);
  1803.         this.triggered = false;
  1804.         return;
  1805.       }
  1806.  
  1807.       // max touches
  1808.       if(inst.options.drag_max_touches > 0 &&
  1809.           ev.touches.length > inst.options.drag_max_touches) {
  1810.             return;
  1811.           }
  1812.  
  1813.       switch(ev.eventType) {
  1814.         case ionic.Gestures.EVENT_START:
  1815.           this.triggered = false;
  1816.           break;
  1817.  
  1818.         case ionic.Gestures.EVENT_MOVE:
  1819.           // when the distance we moved is too small we skip this gesture
  1820.           // or we can be already in dragging
  1821.           if(ev.distance < inst.options.drag_min_distance &&
  1822.               ionic.Gestures.detection.current.name != this.name) {
  1823.                 return;
  1824.               }
  1825.  
  1826.           // we are dragging!
  1827.           if(ionic.Gestures.detection.current.name != this.name) {
  1828.             ionic.Gestures.detection.current.name = this.name;
  1829.             if (inst.options.correct_for_drag_min_distance) {
  1830.               // When a drag is triggered, set the event center to drag_min_distance pixels from the original event center.
  1831.               // Without this correction, the dragged distance would jumpstart at drag_min_distance pixels instead of at 0.
  1832.               // It might be useful to save the original start point somewhere
  1833.               var factor = Math.abs(inst.options.drag_min_distance / ev.distance);
  1834.               ionic.Gestures.detection.current.startEvent.center.pageX += ev.deltaX * factor;
  1835.               ionic.Gestures.detection.current.startEvent.center.pageY += ev.deltaY * factor;
  1836.  
  1837.               // recalculate event data using new start point
  1838.               ev = ionic.Gestures.detection.extendEventData(ev);
  1839.             }
  1840.           }
  1841.  
  1842.           // lock drag to axis?
  1843.           if(ionic.Gestures.detection.current.lastEvent.drag_locked_to_axis || (inst.options.drag_lock_to_axis && inst.options.drag_lock_min_distance <= ev.distance)) {
  1844.             ev.drag_locked_to_axis = true;
  1845.           }
  1846.           var last_direction = ionic.Gestures.detection.current.lastEvent.direction;
  1847.           if(ev.drag_locked_to_axis && last_direction !== ev.direction) {
  1848.             // keep direction on the axis that the drag gesture started on
  1849.             if(ionic.Gestures.utils.isVertical(last_direction)) {
  1850.               ev.direction = (ev.deltaY < 0) ? ionic.Gestures.DIRECTION_UP : ionic.Gestures.DIRECTION_DOWN;
  1851.             }
  1852.             else {
  1853.               ev.direction = (ev.deltaX < 0) ? ionic.Gestures.DIRECTION_LEFT : ionic.Gestures.DIRECTION_RIGHT;
  1854.             }
  1855.           }
  1856.  
  1857.           // first time, trigger dragstart event
  1858.           if(!this.triggered) {
  1859.             inst.trigger(this.name + 'start', ev);
  1860.             this.triggered = true;
  1861.           }
  1862.  
  1863.           // trigger normal event
  1864.           inst.trigger(this.name, ev);
  1865.  
  1866.           // direction event, like dragdown
  1867.           inst.trigger(this.name + ev.direction, ev);
  1868.  
  1869.           // block the browser events
  1870.           if( (inst.options.drag_block_vertical && ionic.Gestures.utils.isVertical(ev.direction)) ||
  1871.               (inst.options.drag_block_horizontal && !ionic.Gestures.utils.isVertical(ev.direction))) {
  1872.                 ev.preventDefault();
  1873.               }
  1874.           break;
  1875.  
  1876.         case ionic.Gestures.EVENT_END:
  1877.           // trigger dragend
  1878.           if(this.triggered) {
  1879.             inst.trigger(this.name + 'end', ev);
  1880.           }
  1881.  
  1882.           this.triggered = false;
  1883.           break;
  1884.       }
  1885.     }
  1886.   };
  1887.  
  1888.  
  1889.   /**
  1890.    * Transform
  1891.    * User want to scale or rotate with 2 fingers
  1892.    * events  transform, pinch, pinchin, pinchout, rotate
  1893.    */
  1894.   ionic.Gestures.gestures.Transform = {
  1895.     name: 'transform',
  1896.     index: 45,
  1897.     defaults: {
  1898.       // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
  1899.       transform_min_scale: 0.01,
  1900.       // rotation in degrees
  1901.       transform_min_rotation: 1,
  1902.       // prevent default browser behavior when two touches are on the screen
  1903.       // but it makes the element a blocking element
  1904.       // when you are using the transform gesture, it is a good practice to set this true
  1905.       transform_always_block: false
  1906.     },
  1907.     triggered: false,
  1908.     handler: function transformGesture(ev, inst) {
  1909.       // current gesture isnt drag, but dragged is true
  1910.       // this means an other gesture is busy. now call dragend
  1911.       if(ionic.Gestures.detection.current.name != this.name && this.triggered) {
  1912.         inst.trigger(this.name + 'end', ev);
  1913.         this.triggered = false;
  1914.         return;
  1915.       }
  1916.  
  1917.       // atleast multitouch
  1918.       if(ev.touches.length < 2) {
  1919.         return;
  1920.       }
  1921.  
  1922.       // prevent default when two fingers are on the screen
  1923.       if(inst.options.transform_always_block) {
  1924.         ev.preventDefault();
  1925.       }
  1926.  
  1927.       switch(ev.eventType) {
  1928.         case ionic.Gestures.EVENT_START:
  1929.           this.triggered = false;
  1930.           break;
  1931.  
  1932.         case ionic.Gestures.EVENT_MOVE:
  1933.           var scale_threshold = Math.abs(1 - ev.scale);
  1934.           var rotation_threshold = Math.abs(ev.rotation);
  1935.  
  1936.           // when the distance we moved is too small we skip this gesture
  1937.           // or we can be already in dragging
  1938.           if(scale_threshold < inst.options.transform_min_scale &&
  1939.               rotation_threshold < inst.options.transform_min_rotation) {
  1940.                 return;
  1941.               }
  1942.  
  1943.           // we are transforming!
  1944.           ionic.Gestures.detection.current.name = this.name;
  1945.  
  1946.           // first time, trigger dragstart event
  1947.           if(!this.triggered) {
  1948.             inst.trigger(this.name + 'start', ev);
  1949.             this.triggered = true;
  1950.           }
  1951.  
  1952.           inst.trigger(this.name, ev); // basic transform event
  1953.  
  1954.           // trigger rotate event
  1955.           if(rotation_threshold > inst.options.transform_min_rotation) {
  1956.             inst.trigger('rotate', ev);
  1957.           }
  1958.  
  1959.           // trigger pinch event
  1960.           if(scale_threshold > inst.options.transform_min_scale) {
  1961.             inst.trigger('pinch', ev);
  1962.             inst.trigger('pinch' + ((ev.scale < 1) ? 'in' : 'out'), ev);
  1963.           }
  1964.           break;
  1965.  
  1966.         case ionic.Gestures.EVENT_END:
  1967.           // trigger dragend
  1968.           if(this.triggered) {
  1969.             inst.trigger(this.name + 'end', ev);
  1970.           }
  1971.  
  1972.           this.triggered = false;
  1973.           break;
  1974.       }
  1975.     }
  1976.   };
  1977.  
  1978.  
  1979.   /**
  1980.    * Touch
  1981.    * Called as first, tells the user has touched the screen
  1982.    * events  touch
  1983.    */
  1984.   ionic.Gestures.gestures.Touch = {
  1985.     name: 'touch',
  1986.     index: -Infinity,
  1987.     defaults: {
  1988.       // call preventDefault at touchstart, and makes the element blocking by
  1989.       // disabling the scrolling of the page, but it improves gestures like
  1990.       // transforming and dragging.
  1991.       // be careful with using this, it can be very annoying for users to be stuck
  1992.       // on the page
  1993.       prevent_default: false,
  1994.  
  1995.       // disable mouse events, so only touch (or pen!) input triggers events
  1996.       prevent_mouseevents: false
  1997.     },
  1998.     handler: function touchGesture(ev, inst) {
  1999.       if(inst.options.prevent_mouseevents && ev.pointerType == ionic.Gestures.POINTER_MOUSE) {
  2000.         ev.stopDetect();
  2001.         return;
  2002.       }
  2003.  
  2004.       if(inst.options.prevent_default) {
  2005.         ev.preventDefault();
  2006.       }
  2007.  
  2008.       if(ev.eventType == ionic.Gestures.EVENT_START) {
  2009.         inst.trigger(this.name, ev);
  2010.       }
  2011.     }
  2012.   };
  2013.  
  2014.  
  2015.   /**
  2016.    * Release
  2017.    * Called as last, tells the user has released the screen
  2018.    * events  release
  2019.    */
  2020.   ionic.Gestures.gestures.Release = {
  2021.     name: 'release',
  2022.     index: Infinity,
  2023.     handler: function releaseGesture(ev, inst) {
  2024.       if(ev.eventType == ionic.Gestures.EVENT_END) {
  2025.         inst.trigger(this.name, ev);
  2026.       }
  2027.     }
  2028.   };
  2029. })(window.ionic);
  2030.  
  2031. (function(window, document, ionic) {
  2032.  
  2033.   function getParameterByName(name) {
  2034.     name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
  2035.     var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
  2036.     results = regex.exec(location.search);
  2037.     return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
  2038.   }
  2039.  
  2040.   var IOS = 'ios';
  2041.   var ANDROID = 'android';
  2042.   var WINDOWS_PHONE = 'windowsphone';
  2043.   var requestAnimationFrame = ionic.requestAnimationFrame;
  2044.  
  2045.   /**
  2046.    * @ngdoc utility
  2047.    * @name ionic.Platform
  2048.    * @module ionic
  2049.    * @description
  2050.    * A set of utility methods that can be used to retrieve the device ready state and
  2051.    * various other information such as what kind of platform the app is currently installed on.
  2052.    *
  2053.    * @usage
  2054.    * ```js
  2055.    * angular.module('PlatformApp', ['ionic'])
  2056.    * .controller('PlatformCtrl', function($scope) {
  2057.    *
  2058.    *   ionic.Platform.ready(function(){
  2059.    *     // will execute when device is ready, or immediately if the device is already ready.
  2060.    *   });
  2061.    *
  2062.    *   var deviceInformation = ionic.Platform.device();
  2063.    *
  2064.    *   var isWebView = ionic.Platform.isWebView();
  2065.    *   var isIPad = ionic.Platform.isIPad();
  2066.    *   var isIOS = ionic.Platform.isIOS();
  2067.    *   var isAndroid = ionic.Platform.isAndroid();
  2068.    *   var isWindowsPhone = ionic.Platform.isWindowsPhone();
  2069.    *
  2070.    *   var currentPlatform = ionic.Platform.platform();
  2071.    *   var currentPlatformVersion = ionic.Platform.version();
  2072.    *
  2073.    *   ionic.Platform.exitApp(); // stops the app
  2074.    * });
  2075.    * ```
  2076.    */
  2077.   var self = ionic.Platform = {
  2078.  
  2079.     // Put navigator on platform so it can be mocked and set
  2080.     // the browser does not allow window.navigator to be set
  2081.     navigator: window.navigator,
  2082.  
  2083.     /**
  2084.      * @ngdoc property
  2085.      * @name ionic.Platform#isReady
  2086.      * @returns {boolean} Whether the device is ready.
  2087.      */
  2088.     isReady: false,
  2089.     /**
  2090.      * @ngdoc property
  2091.      * @name ionic.Platform#isFullScreen
  2092.      * @returns {boolean} Whether the device is fullscreen.
  2093.      */
  2094.     isFullScreen: false,
  2095.     /**
  2096.      * @ngdoc property
  2097.      * @name ionic.Platform#platforms
  2098.      * @returns {Array(string)} An array of all platforms found.
  2099.      */
  2100.     platforms: null,
  2101.     /**
  2102.      * @ngdoc property
  2103.      * @name ionic.Platform#grade
  2104.      * @returns {string} What grade the current platform is.
  2105.      */
  2106.     grade: null,
  2107.     ua: navigator.userAgent,
  2108.  
  2109.     /**
  2110.      * @ngdoc method
  2111.      * @name ionic.Platform#ready
  2112.      * @description
  2113.      * Trigger a callback once the device is ready, or immediately
  2114.      * if the device is already ready. This method can be run from
  2115.      * anywhere and does not need to be wrapped by any additonal methods.
  2116.      * When the app is within a WebView (Cordova), it'll fire
  2117.      * the callback once the device is ready. If the app is within
  2118.      * a web browser, it'll fire the callback after `window.load`.
  2119.      * Please remember that Cordova features (Camera, FileSystem, etc) still
  2120.      * will not work in a web browser.
  2121.      * @param {function} callback The function to call.
  2122.      */
  2123.     ready: function(cb) {
  2124.       // run through tasks to complete now that the device is ready
  2125.       if (self.isReady) {
  2126.         cb();
  2127.       } else {
  2128.         // the platform isn't ready yet, add it to this array
  2129.         // which will be called once the platform is ready
  2130.         readyCallbacks.push(cb);
  2131.       }
  2132.     },
  2133.  
  2134.     /**
  2135.      * @private
  2136.      */
  2137.     detect: function() {
  2138.       self._checkPlatforms();
  2139.  
  2140.       requestAnimationFrame(function() {
  2141.         // only add to the body class if we got platform info
  2142.         for (var i = 0; i < self.platforms.length; i++) {
  2143.           document.body.classList.add('platform-' + self.platforms[i]);
  2144.         }
  2145.       });
  2146.     },
  2147.  
  2148.     /**
  2149.      * @ngdoc method
  2150.      * @name ionic.Platform#setGrade
  2151.      * @description Set the grade of the device: 'a', 'b', or 'c'. 'a' is the best
  2152.      * (most css features enabled), 'c' is the worst.  By default, sets the grade
  2153.      * depending on the current device.
  2154.      * @param {string} grade The new grade to set.
  2155.      */
  2156.     setGrade: function(grade) {
  2157.       var oldGrade = self.grade;
  2158.       self.grade = grade;
  2159.       requestAnimationFrame(function() {
  2160.         if (oldGrade) {
  2161.           document.body.classList.remove('grade-' + oldGrade);
  2162.         }
  2163.         document.body.classList.add('grade-' + grade);
  2164.       });
  2165.     },
  2166.  
  2167.     /**
  2168.      * @ngdoc method
  2169.      * @name ionic.Platform#device
  2170.      * @description Return the current device (given by cordova).
  2171.      * @returns {object} The device object.
  2172.      */
  2173.     device: function() {
  2174.       return window.device || {};
  2175.     },
  2176.  
  2177.     _checkPlatforms: function() {
  2178.       self.platforms = [];
  2179.       var grade = 'a';
  2180.  
  2181.       if (self.isWebView()) {
  2182.         self.platforms.push('webview');
  2183.         if (!(!window.cordova && !window.PhoneGap && !window.phonegap)) {
  2184.           self.platforms.push('cordova');
  2185.         } else if (window.forge) {
  2186.           self.platforms.push('trigger');
  2187.         }
  2188.       } else {
  2189.         self.platforms.push('browser');
  2190.       }
  2191.       if (self.isIPad()) self.platforms.push('ipad');
  2192.  
  2193.       var platform = self.platform();
  2194.       if (platform) {
  2195.         self.platforms.push(platform);
  2196.  
  2197.         var version = self.version();
  2198.         if (version) {
  2199.           var v = version.toString();
  2200.           if (v.indexOf('.') > 0) {
  2201.             v = v.replace('.', '_');
  2202.           } else {
  2203.             v += '_0';
  2204.           }
  2205.           self.platforms.push(platform + v.split('_')[0]);
  2206.           self.platforms.push(platform + v);
  2207.  
  2208.           if (self.isAndroid() && version < 4.4) {
  2209.             grade = (version < 4 ? 'c' : 'b');
  2210.           } else if (self.isWindowsPhone()) {
  2211.             grade = 'b';
  2212.           }
  2213.         }
  2214.       }
  2215.  
  2216.       self.setGrade(grade);
  2217.     },
  2218.  
  2219.     /**
  2220.      * @ngdoc method
  2221.      * @name ionic.Platform#isWebView
  2222.      * @returns {boolean} Check if we are running within a WebView (such as Cordova).
  2223.      */
  2224.     isWebView: function() {
  2225.       return !(!window.cordova && !window.PhoneGap && !window.phonegap && !window.forge);
  2226.     },
  2227.     /**
  2228.      * @ngdoc method
  2229.      * @name ionic.Platform#isIPad
  2230.      * @returns {boolean} Whether we are running on iPad.
  2231.      */
  2232.     isIPad: function() {
  2233.       if (/iPad/i.test(self.navigator.platform)) {
  2234.         return true;
  2235.       }
  2236.       return /iPad/i.test(self.ua);
  2237.     },
  2238.     /**
  2239.      * @ngdoc method
  2240.      * @name ionic.Platform#isIOS
  2241.      * @returns {boolean} Whether we are running on iOS.
  2242.      */
  2243.     isIOS: function() {
  2244.       return self.is(IOS);
  2245.     },
  2246.     /**
  2247.      * @ngdoc method
  2248.      * @name ionic.Platform#isAndroid
  2249.      * @returns {boolean} Whether we are running on Android.
  2250.      */
  2251.     isAndroid: function() {
  2252.       return self.is(ANDROID);
  2253.     },
  2254.     /**
  2255.      * @ngdoc method
  2256.      * @name ionic.Platform#isWindowsPhone
  2257.      * @returns {boolean} Whether we are running on Windows Phone.
  2258.      */
  2259.     isWindowsPhone: function() {
  2260.       return self.is(WINDOWS_PHONE);
  2261.     },
  2262.  
  2263.     /**
  2264.      * @ngdoc method
  2265.      * @name ionic.Platform#platform
  2266.      * @returns {string} The name of the current platform.
  2267.      */
  2268.     platform: function() {
  2269.       // singleton to get the platform name
  2270.       if (platformName === null) self.setPlatform(self.device().platform);
  2271.       return platformName;
  2272.     },
  2273.  
  2274.     /**
  2275.      * @private
  2276.      */
  2277.     setPlatform: function(n) {
  2278.       if (typeof n != 'undefined' && n !== null && n.length) {
  2279.         platformName = n.toLowerCase();
  2280.       } else if (getParameterByName('ionicplatform')) {
  2281.         platformName = getParameterByName('ionicplatform');
  2282.       } else if (self.ua.indexOf('Android') > 0) {
  2283.         platformName = ANDROID;
  2284.       } else if (/iPhone|iPad|iPod/.test(self.ua)) {
  2285.         platformName = IOS;
  2286.       } else if (self.ua.indexOf('Windows Phone') > -1) {
  2287.         platformName = WINDOWS_PHONE;
  2288.       } else {
  2289.         platformName = self.navigator.platform && navigator.platform.toLowerCase().split(' ')[0] || '';
  2290.       }
  2291.     },
  2292.  
  2293.     /**
  2294.      * @ngdoc method
  2295.      * @name ionic.Platform#version
  2296.      * @returns {number} The version of the current device platform.
  2297.      */
  2298.     version: function() {
  2299.       // singleton to get the platform version
  2300.       if (platformVersion === null) self.setVersion(self.device().version);
  2301.       return platformVersion;
  2302.     },
  2303.  
  2304.     /**
  2305.      * @private
  2306.      */
  2307.     setVersion: function(v) {
  2308.       if (typeof v != 'undefined' && v !== null) {
  2309.         v = v.split('.');
  2310.         v = parseFloat(v[0] + '.' + (v.length > 1 ? v[1] : 0));
  2311.         if (!isNaN(v)) {
  2312.           platformVersion = v;
  2313.           return;
  2314.         }
  2315.       }
  2316.  
  2317.       platformVersion = 0;
  2318.  
  2319.       // fallback to user-agent checking
  2320.       var pName = self.platform();
  2321.       var versionMatch = {
  2322.         'android': /Android (\d+).(\d+)?/,
  2323.         'ios': /OS (\d+)_(\d+)?/,
  2324.         'windowsphone': /Windows Phone (\d+).(\d+)?/
  2325.       };
  2326.       if (versionMatch[pName]) {
  2327.         v = self.ua.match(versionMatch[pName]);
  2328.         if (v && v.length > 2) {
  2329.           platformVersion = parseFloat(v[1] + '.' + v[2]);
  2330.         }
  2331.       }
  2332.     },
  2333.  
  2334.     // Check if the platform is the one detected by cordova
  2335.     is: function(type) {
  2336.       type = type.toLowerCase();
  2337.       // check if it has an array of platforms
  2338.       if (self.platforms) {
  2339.         for (var x = 0; x < self.platforms.length; x++) {
  2340.           if (self.platforms[x] === type) return true;
  2341.         }
  2342.       }
  2343.       // exact match
  2344.       var pName = self.platform();
  2345.       if (pName) {
  2346.         return pName === type.toLowerCase();
  2347.       }
  2348.  
  2349.       // A quick hack for to check userAgent
  2350.       return self.ua.toLowerCase().indexOf(type) >= 0;
  2351.     },
  2352.  
  2353.     /**
  2354.      * @ngdoc method
  2355.      * @name ionic.Platform#exitApp
  2356.      * @description Exit the app.
  2357.      */
  2358.     exitApp: function() {
  2359.       self.ready(function() {
  2360.         navigator.app && navigator.app.exitApp && navigator.app.exitApp();
  2361.       });
  2362.     },
  2363.  
  2364.     /**
  2365.      * @ngdoc method
  2366.      * @name ionic.Platform#showStatusBar
  2367.      * @description Shows or hides the device status bar (in Cordova). Requires `cordova plugin add org.apache.cordova.statusbar`
  2368.      * @param {boolean} shouldShow Whether or not to show the status bar.
  2369.      */
  2370.     showStatusBar: function(val) {
  2371.       // Only useful when run within cordova
  2372.       self._showStatusBar = val;
  2373.       self.ready(function() {
  2374.         // run this only when or if the platform (cordova) is ready
  2375.         requestAnimationFrame(function() {
  2376.           if (self._showStatusBar) {
  2377.             // they do not want it to be full screen
  2378.             window.StatusBar && window.StatusBar.show();
  2379.             document.body.classList.remove('status-bar-hide');
  2380.           } else {
  2381.             // it should be full screen
  2382.             window.StatusBar && window.StatusBar.hide();
  2383.             document.body.classList.add('status-bar-hide');
  2384.           }
  2385.         });
  2386.       });
  2387.     },
  2388.  
  2389.     /**
  2390.      * @ngdoc method
  2391.      * @name ionic.Platform#fullScreen
  2392.      * @description
  2393.      * Sets whether the app is fullscreen or not (in Cordova).
  2394.      * @param {boolean=} showFullScreen Whether or not to set the app to fullscreen. Defaults to true. Requires `cordova plugin add org.apache.cordova.statusbar`
  2395.      * @param {boolean=} showStatusBar Whether or not to show the device's status bar. Defaults to false.
  2396.      */
  2397.     fullScreen: function(showFullScreen, showStatusBar) {
  2398.       // showFullScreen: default is true if no param provided
  2399.       self.isFullScreen = (showFullScreen !== false);
  2400.  
  2401.       // add/remove the fullscreen classname to the body
  2402.       ionic.DomUtil.ready(function() {
  2403.         // run this only when or if the DOM is ready
  2404.         requestAnimationFrame(function() {
  2405.           if (self.isFullScreen) {
  2406.             document.body.classList.add('fullscreen');
  2407.           } else {
  2408.             document.body.classList.remove('fullscreen');
  2409.           }
  2410.         });
  2411.         // showStatusBar: default is false if no param provided
  2412.         self.showStatusBar((showStatusBar === true));
  2413.       });
  2414.     }
  2415.  
  2416.   };
  2417.  
  2418.   var platformName = null, // just the name, like iOS or Android
  2419.   platformVersion = null, // a float of the major and minor, like 7.1
  2420.   readyCallbacks = [],
  2421.   windowLoadListenderAttached;
  2422.  
  2423.   // setup listeners to know when the device is ready to go
  2424.   function onWindowLoad() {
  2425.     if (self.isWebView()) {
  2426.       // the window and scripts are fully loaded, and a cordova/phonegap
  2427.       // object exists then let's listen for the deviceready
  2428.       document.addEventListener("deviceready", onPlatformReady, false);
  2429.     } else {
  2430.       // the window and scripts are fully loaded, but the window object doesn't have the
  2431.       // cordova/phonegap object, so its just a browser, not a webview wrapped w/ cordova
  2432.       onPlatformReady();
  2433.     }
  2434.     if (windowLoadListenderAttached) {
  2435.       window.removeEventListener("load", onWindowLoad, false);
  2436.     }
  2437.   }
  2438.   if (document.readyState === 'complete') {
  2439.     onWindowLoad();
  2440.   } else {
  2441.     windowLoadListenderAttached = true;
  2442.     window.addEventListener("load", onWindowLoad, false);
  2443.   }
  2444.  
  2445.   function onPlatformReady() {
  2446.     // the device is all set to go, init our own stuff then fire off our event
  2447.     self.isReady = true;
  2448.     self.detect();
  2449.     for (var x = 0; x < readyCallbacks.length; x++) {
  2450.       // fire off all the callbacks that were added before the platform was ready
  2451.       readyCallbacks[x]();
  2452.     }
  2453.     readyCallbacks = [];
  2454.     ionic.trigger('platformready', { target: document });
  2455.  
  2456.     requestAnimationFrame(function() {
  2457.       document.body.classList.add('platform-ready');
  2458.     });
  2459.   }
  2460.  
  2461. })(this, document, ionic);
  2462.  
  2463. (function(document, ionic) {
  2464.   'use strict';
  2465.  
  2466.   // Ionic CSS polyfills
  2467.   ionic.CSS = {};
  2468.  
  2469.   (function() {
  2470.  
  2471.     // transform
  2472.     var i, keys = ['webkitTransform', 'transform', '-webkit-transform', 'webkit-transform',
  2473.                    '-moz-transform', 'moz-transform', 'MozTransform', 'mozTransform', 'msTransform'];
  2474.  
  2475.     for (i = 0; i < keys.length; i++) {
  2476.       if (document.documentElement.style[keys[i]] !== undefined) {
  2477.         ionic.CSS.TRANSFORM = keys[i];
  2478.         break;
  2479.       }
  2480.     }
  2481.  
  2482.     // transition
  2483.     keys = ['webkitTransition', 'mozTransition', 'msTransition', 'transition'];
  2484.     for (i = 0; i < keys.length; i++) {
  2485.       if (document.documentElement.style[keys[i]] !== undefined) {
  2486.         ionic.CSS.TRANSITION = keys[i];
  2487.         break;
  2488.       }
  2489.     }
  2490.  
  2491.     // The only prefix we care about is webkit for transitions.
  2492.     var isWebkit = ionic.CSS.TRANSITION.indexOf('webkit') > -1;
  2493.  
  2494.     // transition duration
  2495.     ionic.CSS.TRANSITION_DURATION = (isWebkit ? '-webkit-' : '') + 'transition-duration';
  2496.  
  2497.     // To be sure transitionend works everywhere, include *both* the webkit and non-webkit events
  2498.     ionic.CSS.TRANSITIONEND = (isWebkit ? 'webkitTransitionEnd ' : '') + 'transitionend';
  2499.   })();
  2500.  
  2501.   // classList polyfill for them older Androids
  2502.   // https://gist.github.com/devongovett/1381839
  2503.   if (!("classList" in document.documentElement) && Object.defineProperty && typeof HTMLElement !== 'undefined') {
  2504.     Object.defineProperty(HTMLElement.prototype, 'classList', {
  2505.       get: function() {
  2506.         var self = this;
  2507.         function update(fn) {
  2508.           return function() {
  2509.             var x, classes = self.className.split(/\s+/);
  2510.  
  2511.             for (x = 0; x < arguments.length; x++) {
  2512.               fn(classes, classes.indexOf(arguments[x]), arguments[x]);
  2513.             }
  2514.  
  2515.             self.className = classes.join(" ");
  2516.           };
  2517.         }
  2518.  
  2519.         return {
  2520.           add: update(function(classes, index, value) {
  2521.             ~index || classes.push(value);
  2522.           }),
  2523.  
  2524.           remove: update(function(classes, index) {
  2525.             ~index && classes.splice(index, 1);
  2526.           }),
  2527.  
  2528.           toggle: update(function(classes, index, value) {
  2529.             ~index ? classes.splice(index, 1) : classes.push(value);
  2530.           }),
  2531.  
  2532.           contains: function(value) {
  2533.             return !!~self.className.split(/\s+/).indexOf(value);
  2534.           },
  2535.  
  2536.           item: function(i) {
  2537.             return self.className.split(/\s+/)[i] || null;
  2538.           }
  2539.         };
  2540.  
  2541.       }
  2542.     });
  2543.   }
  2544.  
  2545. })(document, ionic);
  2546.  
  2547.  
  2548. /**
  2549.  * @ngdoc page
  2550.  * @name tap
  2551.  * @module ionic
  2552.  * @description
  2553.  * On touch devices such as a phone or tablet, some browsers implement a 300ms delay between
  2554.  * the time the user stops touching the display and the moment the browser executes the
  2555.  * click. This delay was initially introduced so the browser can know whether the user wants to
  2556.  * double-tap to zoom in on the webpage.  Basically, the browser waits roughly 300ms to see if
  2557.  * the user is double-tapping, or just tapping on the display once.
  2558.  *
  2559.  * Out of the box, Ionic automatically removes the 300ms delay in order to make Ionic apps
  2560.  * feel more "native" like. Resultingly, other solutions such as
  2561.  * [fastclick](https://github.com/ftlabs/fastclick) and Angular's
  2562.  * [ngTouch](https://docs.angularjs.org/api/ngTouch) should not be included, to avoid conflicts.
  2563.  *
  2564.  * Some browsers already remove the delay with certain settings, such as the CSS property
  2565.  * `touch-events: none` or with specific meta tag viewport values. However, each of these
  2566.  * browsers still handle clicks differently, such as when to fire off or cancel the event
  2567.  * (like scrolling when the target is a button, or holding a button down).
  2568.  * For browsers that already remove the 300ms delay, consider Ionic's tap system as a way to
  2569.  * normalize how clicks are handled across the various devices so there's an expected response
  2570.  * no matter what the device, platform or version. Additionally, Ionic will prevent
  2571.  * ghostclicks which even browsers that remove the delay still experience.
  2572.  *
  2573.  * In some cases, third-party libraries may also be working with touch events which can interfere
  2574.  * with the tap system. For example, mapping libraries like Google or Leaflet Maps often implement
  2575.  * a touch detection system which conflicts with Ionic's tap system.
  2576.  *
  2577.  * ### Disabling the tap system
  2578.  *
  2579.  * To disable the tap for an element and all of its children elements,
  2580.  * add the attribute `data-tap-disabled="true"`.
  2581.  *
  2582.  * ```html
  2583.  * <div data-tap-disabled="true">
  2584.  *     <div id="google-map"></div>
  2585.  * </div>
  2586.  * ```
  2587.  *
  2588.  * ### Additional Notes:
  2589.  *
  2590.  * - Ionic tap  works with Ionic's JavaScript scrolling
  2591.  * - Elements can come and go from the DOM and Ionic tap doesn't keep adding and removing
  2592.  *   listeners
  2593.  * - No "tap delay" after the first "tap" (you can tap as fast as you want, they all click)
  2594.  * - Minimal events listeners, only being added to document
  2595.  * - Correct focus in/out on each input type (select, textearea, range) on each platform/device
  2596.  * - Shows and hides virtual keyboard correctly for each platform/device
  2597.  * - Works with labels surrounding inputs
  2598.  * - Does not fire off a click if the user moves the pointer too far
  2599.  * - Adds and removes an 'activated' css class
  2600.  * - Multiple [unit tests](https://github.com/driftyco/ionic/blob/master/test/unit/utils/tap.unit.js) for each scenario
  2601.  *
  2602.  */
  2603. /*
  2604.  
  2605.  IONIC TAP
  2606.  ---------------
  2607.  - Both touch and mouse events are added to the document.body on DOM ready
  2608.  - If a touch event happens, it does not use mouse event listeners
  2609.  - On touchend, if the distance between start and end was small, trigger a click
  2610.  - In the triggered click event, add a 'isIonicTap' property
  2611.  - The triggered click receives the same x,y coordinates as as the end event
  2612.  - On document.body click listener (with useCapture=true), only allow clicks with 'isIonicTap'
  2613.  - Triggering clicks with mouse events work the same as touch, except with mousedown/mouseup
  2614.  - Tapping inputs is disabled during scrolling
  2615. */
  2616.  
  2617. var tapDoc; // the element which the listeners are on (document.body)
  2618. var tapActiveEle; // the element which is active (probably has focus)
  2619. var tapEnabledTouchEvents;
  2620. var tapMouseResetTimer;
  2621. var tapPointerMoved;
  2622. var tapPointerStart;
  2623. var tapTouchFocusedInput;
  2624. var tapLastTouchTarget;
  2625. var tapTouchMoveListener = 'touchmove';
  2626.  
  2627. // how much the coordinates can be off between start/end, but still a click
  2628. var TAP_RELEASE_TOLERANCE = 12; // default tolerance
  2629. var TAP_RELEASE_BUTTON_TOLERANCE = 50; // button elements should have a larger tolerance
  2630.  
  2631. var tapEventListeners = {
  2632.   'click': tapClickGateKeeper,
  2633.  
  2634.   'mousedown': tapMouseDown,
  2635.   'mouseup': tapMouseUp,
  2636.   'mousemove': tapMouseMove,
  2637.  
  2638.   'touchstart': tapTouchStart,
  2639.   'touchend': tapTouchEnd,
  2640.   'touchcancel': tapTouchCancel,
  2641.   'touchmove': tapTouchMove,
  2642.  
  2643.   'pointerdown': tapTouchStart,
  2644.   'pointerup': tapTouchEnd,
  2645.   'pointercancel': tapTouchCancel,
  2646.   'pointermove': tapTouchMove,
  2647.  
  2648.   'MSPointerDown': tapTouchStart,
  2649.   'MSPointerUp': tapTouchEnd,
  2650.   'MSPointerCancel': tapTouchCancel,
  2651.   'MSPointerMove': tapTouchMove,
  2652.  
  2653.   'focusin': tapFocusIn,
  2654.   'focusout': tapFocusOut
  2655. };
  2656.  
  2657. ionic.tap = {
  2658.  
  2659.   register: function(ele) {
  2660.     tapDoc = ele;
  2661.  
  2662.     tapEventListener('click', true, true);
  2663.     tapEventListener('mouseup');
  2664.     tapEventListener('mousedown');
  2665.  
  2666.     if (window.navigator.pointerEnabled) {
  2667.       tapEventListener('pointerdown');
  2668.       tapEventListener('pointerup');
  2669.       tapEventListener('pointcancel');
  2670.       tapTouchMoveListener = 'pointermove';
  2671.  
  2672.     } else if (window.navigator.msPointerEnabled) {
  2673.       tapEventListener('MSPointerDown');
  2674.       tapEventListener('MSPointerUp');
  2675.       tapEventListener('MSPointerCancel');
  2676.       tapTouchMoveListener = 'MSPointerMove';
  2677.  
  2678.     } else {
  2679.       tapEventListener('touchstart');
  2680.       tapEventListener('touchend');
  2681.       tapEventListener('touchcancel');
  2682.     }
  2683.  
  2684.     tapEventListener('focusin');
  2685.     tapEventListener('focusout');
  2686.  
  2687.     return function() {
  2688.       for (var type in tapEventListeners) {
  2689.         tapEventListener(type, false);
  2690.       }
  2691.       tapDoc = null;
  2692.       tapActiveEle = null;
  2693.       tapEnabledTouchEvents = false;
  2694.       tapPointerMoved = false;
  2695.       tapPointerStart = null;
  2696.     };
  2697.   },
  2698.  
  2699.   ignoreScrollStart: function(e) {
  2700.     return (e.defaultPrevented) ||  // defaultPrevented has been assigned by another component handling the event
  2701.            (/^(file|range)$/i).test(e.target.type) ||
  2702.            (e.target.dataset ? e.target.dataset.preventScroll : e.target.getAttribute('data-prevent-scroll')) == 'true' || // manually set within an elements attributes
  2703.            (!!(/^(object|embed)$/i).test(e.target.tagName)) ||  // flash/movie/object touches should not try to scroll
  2704.            ionic.tap.isElementTapDisabled(e.target); // check if this element, or an ancestor, has `data-tap-disabled` attribute
  2705.   },
  2706.  
  2707.   isTextInput: function(ele) {
  2708.     return !!ele &&
  2709.            (ele.tagName == 'TEXTAREA' ||
  2710.             ele.contentEditable === 'true' ||
  2711.             (ele.tagName == 'INPUT' && !(/^(radio|checkbox|range|file|submit|reset|color|image|button)$/i).test(ele.type)));
  2712.   },
  2713.  
  2714.   isDateInput: function(ele) {
  2715.     return !!ele &&
  2716.             (ele.tagName == 'INPUT' && (/^(date|time|datetime-local|month|week)$/i).test(ele.type));
  2717.   },
  2718.  
  2719.   isKeyboardElement: function(ele) {
  2720.     if ( !ionic.Platform.isIOS() || ionic.Platform.isIPad() ) {
  2721.       return ionic.tap.isTextInput(ele) && !ionic.tap.isDateInput(ele);
  2722.     } else {
  2723.       return ionic.tap.isTextInput(ele) || ( !!ele && ele.tagName == "SELECT");
  2724.     }
  2725.   },
  2726.  
  2727.   isLabelWithTextInput: function(ele) {
  2728.     var container = tapContainingElement(ele, false);
  2729.  
  2730.     return !!container &&
  2731.            ionic.tap.isTextInput(tapTargetElement(container));
  2732.   },
  2733.  
  2734.   containsOrIsTextInput: function(ele) {
  2735.     return ionic.tap.isTextInput(ele) || ionic.tap.isLabelWithTextInput(ele);
  2736.   },
  2737.  
  2738.   cloneFocusedInput: function(container) {
  2739.     if (ionic.tap.hasCheckedClone) return;
  2740.     ionic.tap.hasCheckedClone = true;
  2741.  
  2742.     ionic.requestAnimationFrame(function() {
  2743.       var focusInput = container.querySelector(':focus');
  2744.       if (ionic.tap.isTextInput(focusInput) && !ionic.tap.isDateInput(focusInput)) {
  2745.         var clonedInput = focusInput.cloneNode(true);
  2746.  
  2747.         clonedInput.value = focusInput.value;
  2748.         clonedInput.classList.add('cloned-text-input');
  2749.         clonedInput.readOnly = true;
  2750.         if (focusInput.isContentEditable) {
  2751.           clonedInput.contentEditable = focusInput.contentEditable;
  2752.           clonedInput.innerHTML = focusInput.innerHTML;
  2753.         }
  2754.         focusInput.parentElement.insertBefore(clonedInput, focusInput);
  2755.         focusInput.classList.add('previous-input-focus');
  2756.  
  2757.         clonedInput.scrollTop = focusInput.scrollTop;
  2758.       }
  2759.     });
  2760.   },
  2761.  
  2762.   hasCheckedClone: false,
  2763.  
  2764.   removeClonedInputs: function(container) {
  2765.     ionic.tap.hasCheckedClone = false;
  2766.  
  2767.     ionic.requestAnimationFrame(function() {
  2768.       var clonedInputs = container.querySelectorAll('.cloned-text-input');
  2769.       var previousInputFocus = container.querySelectorAll('.previous-input-focus');
  2770.       var x;
  2771.  
  2772.       for (x = 0; x < clonedInputs.length; x++) {
  2773.         clonedInputs[x].parentElement.removeChild(clonedInputs[x]);
  2774.       }
  2775.  
  2776.       for (x = 0; x < previousInputFocus.length; x++) {
  2777.         previousInputFocus[x].classList.remove('previous-input-focus');
  2778.         previousInputFocus[x].style.top = '';
  2779.         if ( ionic.keyboard.isOpen && !ionic.keyboard.isClosing ) previousInputFocus[x].focus();
  2780.       }
  2781.     });
  2782.   },
  2783.  
  2784.   requiresNativeClick: function(ele) {
  2785.     if (!ele || ele.disabled || (/^(file|range)$/i).test(ele.type) || (/^(object|video)$/i).test(ele.tagName) || ionic.tap.isLabelContainingFileInput(ele)) {
  2786.       return true;
  2787.     }
  2788.     return ionic.tap.isElementTapDisabled(ele);
  2789.   },
  2790.  
  2791.   isLabelContainingFileInput: function(ele) {
  2792.     var lbl = tapContainingElement(ele);
  2793.     if (lbl.tagName !== 'LABEL') return false;
  2794.     var fileInput = lbl.querySelector('input[type=file]');
  2795.     if (fileInput && fileInput.disabled === false) return true;
  2796.     return false;
  2797.   },
  2798.  
  2799.   isElementTapDisabled: function(ele) {
  2800.     if (ele && ele.nodeType === 1) {
  2801.       var element = ele;
  2802.       while (element) {
  2803.         if ((element.dataset ? element.dataset.tapDisabled : element.getAttribute('data-tap-disabled')) == 'true') {
  2804.           return true;
  2805.         }
  2806.         element = element.parentElement;
  2807.       }
  2808.     }
  2809.     return false;
  2810.   },
  2811.  
  2812.   setTolerance: function(releaseTolerance, releaseButtonTolerance) {
  2813.     TAP_RELEASE_TOLERANCE = releaseTolerance;
  2814.     TAP_RELEASE_BUTTON_TOLERANCE = releaseButtonTolerance;
  2815.   },
  2816.  
  2817.   cancelClick: function() {
  2818.     // used to cancel any simulated clicks which may happen on a touchend/mouseup
  2819.     // gestures uses this method within its tap and hold events
  2820.     tapPointerMoved = true;
  2821.   },
  2822.  
  2823.   pointerCoord: function(event) {
  2824.     // This method can get coordinates for both a mouse click
  2825.     // or a touch depending on the given event
  2826.     var c = { x: 0, y: 0 };
  2827.     if (event) {
  2828.       var touches = event.touches && event.touches.length ? event.touches : [event];
  2829.       var e = (event.changedTouches && event.changedTouches[0]) || touches[0];
  2830.       if (e) {
  2831.         c.x = e.clientX || e.pageX || 0;
  2832.         c.y = e.clientY || e.pageY || 0;
  2833.       }
  2834.     }
  2835.     return c;
  2836.   }
  2837.  
  2838. };
  2839.  
  2840. function tapEventListener(type, enable, useCapture) {
  2841.   if (enable !== false) {
  2842.     tapDoc.addEventListener(type, tapEventListeners[type], useCapture);
  2843.   } else {
  2844.     tapDoc.removeEventListener(type, tapEventListeners[type]);
  2845.   }
  2846. }
  2847.  
  2848. function tapClick(e) {
  2849.   // simulate a normal click by running the element's click method then focus on it
  2850.   var container = tapContainingElement(e.target);
  2851.   var ele = tapTargetElement(container);
  2852.  
  2853.   if (ionic.tap.requiresNativeClick(ele) || tapPointerMoved) return false;
  2854.  
  2855.   var c = ionic.tap.pointerCoord(e);
  2856.  
  2857.   //console.log('tapClick', e.type, ele.tagName, '('+c.x+','+c.y+')');
  2858.   triggerMouseEvent('click', ele, c.x, c.y);
  2859.  
  2860.   // if it's an input, focus in on the target, otherwise blur
  2861.   tapHandleFocus(ele);
  2862. }
  2863.  
  2864. function triggerMouseEvent(type, ele, x, y) {
  2865.   // using initMouseEvent instead of MouseEvent for our Android friends
  2866.   var clickEvent = document.createEvent("MouseEvents");
  2867.   clickEvent.initMouseEvent(type, true, true, window, 1, 0, 0, x, y, false, false, false, false, 0, null);
  2868.   clickEvent.isIonicTap = true;
  2869.   ele.dispatchEvent(clickEvent);
  2870. }
  2871.  
  2872. function tapClickGateKeeper(e) {
  2873.   //console.log('click ' + Date.now() + ' isIonicTap: ' + (e.isIonicTap ? true : false));
  2874.   if (e.target.type == 'submit' && e.detail === 0) {
  2875.     // do not prevent click if it came from an "Enter" or "Go" keypress submit
  2876.     return null;
  2877.   }
  2878.  
  2879.   // do not allow through any click events that were not created by ionic.tap
  2880.   if ((ionic.scroll.isScrolling && ionic.tap.containsOrIsTextInput(e.target)) ||
  2881.       (!e.isIonicTap && !ionic.tap.requiresNativeClick(e.target))) {
  2882.     //console.log('clickPrevent', e.target.tagName);
  2883.     e.stopPropagation();
  2884.  
  2885.     if (!ionic.tap.isLabelWithTextInput(e.target)) {
  2886.       // labels clicks from native should not preventDefault othersize keyboard will not show on input focus
  2887.       e.preventDefault();
  2888.     }
  2889.     return false;
  2890.   }
  2891. }
  2892.  
  2893. // MOUSE
  2894. function tapMouseDown(e) {
  2895.   //console.log('mousedown ' + Date.now());
  2896.   if (e.isIonicTap || tapIgnoreEvent(e)) return null;
  2897.  
  2898.   if (tapEnabledTouchEvents) {
  2899.     void 0;
  2900.     e.stopPropagation();
  2901.  
  2902.     if ((!ionic.tap.isTextInput(e.target) || tapLastTouchTarget !== e.target) && !(/^(select|option)$/i).test(e.target.tagName)) {
  2903.       // If you preventDefault on a text input then you cannot move its text caret/cursor.
  2904.       // Allow through only the text input default. However, without preventDefault on an
  2905.       // input the 300ms delay can change focus on inputs after the keyboard shows up.
  2906.       // The focusin event handles the chance of focus changing after the keyboard shows.
  2907.       e.preventDefault();
  2908.     }
  2909.  
  2910.     return false;
  2911.   }
  2912.  
  2913.   tapPointerMoved = false;
  2914.   tapPointerStart = ionic.tap.pointerCoord(e);
  2915.  
  2916.   tapEventListener('mousemove');
  2917.   ionic.activator.start(e);
  2918. }
  2919.  
  2920. function tapMouseUp(e) {
  2921.   //console.log("mouseup " + Date.now());
  2922.   if (tapEnabledTouchEvents) {
  2923.     e.stopPropagation();
  2924.     e.preventDefault();
  2925.     return false;
  2926.   }
  2927.  
  2928.   if (tapIgnoreEvent(e) || (/^(select|option)$/i).test(e.target.tagName)) return false;
  2929.  
  2930.   if (!tapHasPointerMoved(e)) {
  2931.     tapClick(e);
  2932.   }
  2933.   tapEventListener('mousemove', false);
  2934.   ionic.activator.end();
  2935.   tapPointerMoved = false;
  2936. }
  2937.  
  2938. function tapMouseMove(e) {
  2939.   if (tapHasPointerMoved(e)) {
  2940.     tapEventListener('mousemove', false);
  2941.     ionic.activator.end();
  2942.     tapPointerMoved = true;
  2943.     return false;
  2944.   }
  2945. }
  2946.  
  2947.  
  2948. // TOUCH
  2949. function tapTouchStart(e) {
  2950.   //console.log("touchstart " + Date.now());
  2951.   if (tapIgnoreEvent(e)) return;
  2952.  
  2953.   tapPointerMoved = false;
  2954.  
  2955.   tapEnableTouchEvents();
  2956.   tapPointerStart = ionic.tap.pointerCoord(e);
  2957.  
  2958.   tapEventListener(tapTouchMoveListener);
  2959.   ionic.activator.start(e);
  2960.  
  2961.   if (ionic.Platform.isIOS() && ionic.tap.isLabelWithTextInput(e.target)) {
  2962.     // if the tapped element is a label, which has a child input
  2963.     // then preventDefault so iOS doesn't ugly auto scroll to the input
  2964.     // but do not prevent default on Android or else you cannot move the text caret
  2965.     // and do not prevent default on Android or else no virtual keyboard shows up
  2966.  
  2967.     var textInput = tapTargetElement(tapContainingElement(e.target));
  2968.     if (textInput !== tapActiveEle) {
  2969.       // don't preventDefault on an already focused input or else iOS's text caret isn't usable
  2970.       e.preventDefault();
  2971.     }
  2972.   }
  2973. }
  2974.  
  2975. function tapTouchEnd(e) {
  2976.   //console.log('touchend ' + Date.now());
  2977.   if (tapIgnoreEvent(e)) return;
  2978.  
  2979.   tapEnableTouchEvents();
  2980.   if (!tapHasPointerMoved(e)) {
  2981.     tapClick(e);
  2982.  
  2983.     if ((/^(select|option)$/i).test(e.target.tagName)) {
  2984.       e.preventDefault();
  2985.     }
  2986.   }
  2987.  
  2988.   tapLastTouchTarget = e.target;
  2989.   tapTouchCancel();
  2990. }
  2991.  
  2992. function tapTouchMove(e) {
  2993.   if (tapHasPointerMoved(e)) {
  2994.     tapPointerMoved = true;
  2995.     tapEventListener(tapTouchMoveListener, false);
  2996.     ionic.activator.end();
  2997.     return false;
  2998.   }
  2999. }
  3000.  
  3001. function tapTouchCancel() {
  3002.   tapEventListener(tapTouchMoveListener, false);
  3003.   ionic.activator.end();
  3004.   tapPointerMoved = false;
  3005. }
  3006.  
  3007. function tapEnableTouchEvents() {
  3008.   tapEnabledTouchEvents = true;
  3009.   clearTimeout(tapMouseResetTimer);
  3010.   tapMouseResetTimer = setTimeout(function() {
  3011.     tapEnabledTouchEvents = false;
  3012.   }, 600);
  3013. }
  3014.  
  3015. function tapIgnoreEvent(e) {
  3016.   if (e.isTapHandled) return true;
  3017.   e.isTapHandled = true;
  3018.  
  3019.   if (ionic.scroll.isScrolling && ionic.tap.containsOrIsTextInput(e.target)) {
  3020.     e.preventDefault();
  3021.     return true;
  3022.   }
  3023. }
  3024.  
  3025. function tapHandleFocus(ele) {
  3026.   tapTouchFocusedInput = null;
  3027.  
  3028.   var triggerFocusIn = false;
  3029.  
  3030.   if (ele.tagName == 'SELECT') {
  3031.     // trick to force Android options to show up
  3032.     triggerMouseEvent('mousedown', ele, 0, 0);
  3033.     ele.focus && ele.focus();
  3034.     triggerFocusIn = true;
  3035.  
  3036.   } else if (tapActiveElement() === ele) {
  3037.     // already is the active element and has focus
  3038.     triggerFocusIn = true;
  3039.  
  3040.   } else if ((/^(input|textarea)$/i).test(ele.tagName) || ele.isContentEditable) {
  3041.     triggerFocusIn = true;
  3042.     ele.focus && ele.focus();
  3043.     ele.value = ele.value;
  3044.     if (tapEnabledTouchEvents) {
  3045.       tapTouchFocusedInput = ele;
  3046.     }
  3047.  
  3048.   } else {
  3049.     tapFocusOutActive();
  3050.   }
  3051.  
  3052.   if (triggerFocusIn) {
  3053.     tapActiveElement(ele);
  3054.     ionic.trigger('ionic.focusin', {
  3055.       target: ele
  3056.     }, true);
  3057.   }
  3058. }
  3059.  
  3060. function tapFocusOutActive() {
  3061.   var ele = tapActiveElement();
  3062.   if (ele && ((/^(input|textarea|select)$/i).test(ele.tagName) || ele.isContentEditable)) {
  3063.     void 0;
  3064.     ele.blur();
  3065.   }
  3066.   tapActiveElement(null);
  3067. }
  3068.  
  3069. function tapFocusIn(e) {
  3070.   //console.log('focusin ' + Date.now());
  3071.   // Because a text input doesn't preventDefault (so the caret still works) there's a chance
  3072.   // that its mousedown event 300ms later will change the focus to another element after
  3073.   // the keyboard shows up.
  3074.  
  3075.   if (tapEnabledTouchEvents &&
  3076.       ionic.tap.isTextInput(tapActiveElement()) &&
  3077.       ionic.tap.isTextInput(tapTouchFocusedInput) &&
  3078.       tapTouchFocusedInput !== e.target) {
  3079.  
  3080.     // 1) The pointer is from touch events
  3081.     // 2) There is an active element which is a text input
  3082.     // 3) A text input was just set to be focused on by a touch event
  3083.     // 4) A new focus has been set, however the target isn't the one the touch event wanted
  3084.     void 0;
  3085.     tapTouchFocusedInput.focus();
  3086.     tapTouchFocusedInput = null;
  3087.   }
  3088.   ionic.scroll.isScrolling = false;
  3089. }
  3090.  
  3091. function tapFocusOut() {
  3092.   //console.log("focusout");
  3093.   tapActiveElement(null);
  3094. }
  3095.  
  3096. function tapActiveElement(ele) {
  3097.   if (arguments.length) {
  3098.     tapActiveEle = ele;
  3099.   }
  3100.   return tapActiveEle || document.activeElement;
  3101. }
  3102.  
  3103. function tapHasPointerMoved(endEvent) {
  3104.   if (!endEvent || endEvent.target.nodeType !== 1 || !tapPointerStart || (tapPointerStart.x === 0 && tapPointerStart.y === 0)) {
  3105.     return false;
  3106.   }
  3107.   var endCoordinates = ionic.tap.pointerCoord(endEvent);
  3108.  
  3109.   var hasClassList = !!(endEvent.target.classList && endEvent.target.classList.contains &&
  3110.     typeof endEvent.target.classList.contains === 'function');
  3111.   var releaseTolerance = hasClassList && endEvent.target.classList.contains('button') ?
  3112.     TAP_RELEASE_BUTTON_TOLERANCE :
  3113.     TAP_RELEASE_TOLERANCE;
  3114.  
  3115.   return Math.abs(tapPointerStart.x - endCoordinates.x) > releaseTolerance ||
  3116.          Math.abs(tapPointerStart.y - endCoordinates.y) > releaseTolerance;
  3117. }
  3118.  
  3119. function tapContainingElement(ele, allowSelf) {
  3120.   var climbEle = ele;
  3121.   for (var x = 0; x < 6; x++) {
  3122.     if (!climbEle) break;
  3123.     if (climbEle.tagName === 'LABEL') return climbEle;
  3124.     climbEle = climbEle.parentElement;
  3125.   }
  3126.   if (allowSelf !== false) return ele;
  3127. }
  3128.  
  3129. function tapTargetElement(ele) {
  3130.   if (ele && ele.tagName === 'LABEL') {
  3131.     if (ele.control) return ele.control;
  3132.  
  3133.     // older devices do not support the "control" property
  3134.     if (ele.querySelector) {
  3135.       var control = ele.querySelector('input,textarea,select');
  3136.       if (control) return control;
  3137.     }
  3138.   }
  3139.   return ele;
  3140. }
  3141.  
  3142. ionic.DomUtil.ready(function() {
  3143.   var ng = typeof angular !== 'undefined' ? angular : null;
  3144.   //do nothing for e2e tests
  3145.   if (!ng || (ng && !ng.scenario)) {
  3146.     ionic.tap.register(document);
  3147.   }
  3148. });
  3149.  
  3150. (function(document, ionic) {
  3151.   'use strict';
  3152.  
  3153.   var queueElements = {};   // elements that should get an active state in XX milliseconds
  3154.   var activeElements = {};  // elements that are currently active
  3155.   var keyId = 0;            // a counter for unique keys for the above ojects
  3156.   var ACTIVATED_CLASS = 'activated';
  3157.  
  3158.   ionic.activator = {
  3159.  
  3160.     start: function(e) {
  3161.       var hitX = ionic.tap.pointerCoord(e).x;
  3162.       if (hitX > 0 && hitX < 30) {
  3163.         return;
  3164.       }
  3165.  
  3166.       // when an element is touched/clicked, it climbs up a few
  3167.       // parents to see if it is an .item or .button element
  3168.       ionic.requestAnimationFrame(function() {
  3169.         if ((ionic.scroll && ionic.scroll.isScrolling) || ionic.tap.requiresNativeClick(e.target)) return;
  3170.         var ele = e.target;
  3171.         var eleToActivate;
  3172.  
  3173.         for (var x = 0; x < 6; x++) {
  3174.           if (!ele || ele.nodeType !== 1) break;
  3175.           if (eleToActivate && ele.classList && ele.classList.contains('item')) {
  3176.             eleToActivate = ele;
  3177.             break;
  3178.           }
  3179.           if (ele.tagName == 'A' || ele.tagName == 'BUTTON' || ele.hasAttribute('ng-click')) {
  3180.             eleToActivate = ele;
  3181.             break;
  3182.           }
  3183.           if (ele.classList.contains('button')) {
  3184.             eleToActivate = ele;
  3185.             break;
  3186.           }
  3187.           // no sense climbing past these
  3188.           if (ele.tagName == 'ION-CONTENT' || (ele.classList && ele.classList.contains('pane')) || ele.tagName == 'BODY') {
  3189.             break;
  3190.           }
  3191.           ele = ele.parentElement;
  3192.         }
  3193.  
  3194.         if (eleToActivate) {
  3195.           // queue that this element should be set to active
  3196.           queueElements[keyId] = eleToActivate;
  3197.  
  3198.           // on the next frame, set the queued elements to active
  3199.           ionic.requestAnimationFrame(activateElements);
  3200.  
  3201.           keyId = (keyId > 29 ? 0 : keyId + 1);
  3202.         }
  3203.  
  3204.       });
  3205.     },
  3206.  
  3207.     end: function() {
  3208.       // clear out any active/queued elements after XX milliseconds
  3209.       setTimeout(clear, 200);
  3210.     }
  3211.  
  3212.   };
  3213.  
  3214.   function clear() {
  3215.     // clear out any elements that are queued to be set to active
  3216.     queueElements = {};
  3217.  
  3218.     // in the next frame, remove the active class from all active elements
  3219.     ionic.requestAnimationFrame(deactivateElements);
  3220.   }
  3221.  
  3222.   function activateElements() {
  3223.     // activate all elements in the queue
  3224.     for (var key in queueElements) {
  3225.       if (queueElements[key]) {
  3226.         queueElements[key].classList.add(ACTIVATED_CLASS);
  3227.         activeElements[key] = queueElements[key];
  3228.       }
  3229.     }
  3230.     queueElements = {};
  3231.   }
  3232.  
  3233.   function deactivateElements() {
  3234.     if (ionic.transition && ionic.transition.isActive) {
  3235.       setTimeout(deactivateElements, 400);
  3236.       return;
  3237.     }
  3238.  
  3239.     for (var key in activeElements) {
  3240.       if (activeElements[key]) {
  3241.         activeElements[key].classList.remove(ACTIVATED_CLASS);
  3242.         delete activeElements[key];
  3243.       }
  3244.     }
  3245.   }
  3246.  
  3247. })(document, ionic);
  3248.  
  3249. (function(ionic) {
  3250.   /* for nextUid function below */
  3251.   var nextId = 0;
  3252.  
  3253.   /**
  3254.    * Various utilities used throughout Ionic
  3255.    *
  3256.    * Some of these are adopted from underscore.js and backbone.js, both also MIT licensed.
  3257.    */
  3258.   ionic.Utils = {
  3259.  
  3260.     arrayMove: function(arr, oldIndex, newIndex) {
  3261.       if (newIndex >= arr.length) {
  3262.         var k = newIndex - arr.length;
  3263.         while ((k--) + 1) {
  3264.           arr.push(undefined);
  3265.         }
  3266.       }
  3267.       arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0]);
  3268.       return arr;
  3269.     },
  3270.  
  3271.     /**
  3272.      * Return a function that will be called with the given context
  3273.      */
  3274.     proxy: function(func, context) {
  3275.       var args = Array.prototype.slice.call(arguments, 2);
  3276.       return function() {
  3277.         return func.apply(context, args.concat(Array.prototype.slice.call(arguments)));
  3278.       };
  3279.     },
  3280.  
  3281.     /**
  3282.      * Only call a function once in the given interval.
  3283.      *
  3284.      * @param func {Function} the function to call
  3285.      * @param wait {int} how long to wait before/after to allow function calls
  3286.      * @param immediate {boolean} whether to call immediately or after the wait interval
  3287.      */
  3288.      debounce: function(func, wait, immediate) {
  3289.       var timeout, args, context, timestamp, result;
  3290.       return function() {
  3291.         context = this;
  3292.         args = arguments;
  3293.         timestamp = new Date();
  3294.         var later = function() {
  3295.           var last = (new Date()) - timestamp;
  3296.           if (last < wait) {
  3297.             timeout = setTimeout(later, wait - last);
  3298.           } else {
  3299.             timeout = null;
  3300.             if (!immediate) result = func.apply(context, args);
  3301.           }
  3302.         };
  3303.         var callNow = immediate && !timeout;
  3304.         if (!timeout) {
  3305.           timeout = setTimeout(later, wait);
  3306.         }
  3307.         if (callNow) result = func.apply(context, args);
  3308.         return result;
  3309.       };
  3310.     },
  3311.  
  3312.     /**
  3313.      * Throttle the given fun, only allowing it to be
  3314.      * called at most every `wait` ms.
  3315.      */
  3316.     throttle: function(func, wait, options) {
  3317.       var context, args, result;
  3318.       var timeout = null;
  3319.       var previous = 0;
  3320.       options || (options = {});
  3321.       var later = function() {
  3322.         previous = options.leading === false ? 0 : Date.now();
  3323.         timeout = null;
  3324.         result = func.apply(context, args);
  3325.       };
  3326.       return function() {
  3327.         var now = Date.now();
  3328.         if (!previous && options.leading === false) previous = now;
  3329.         var remaining = wait - (now - previous);
  3330.         context = this;
  3331.         args = arguments;
  3332.         if (remaining <= 0) {
  3333.           clearTimeout(timeout);
  3334.           timeout = null;
  3335.           previous = now;
  3336.           result = func.apply(context, args);
  3337.         } else if (!timeout && options.trailing !== false) {
  3338.           timeout = setTimeout(later, remaining);
  3339.         }
  3340.         return result;
  3341.       };
  3342.     },
  3343.      // Borrowed from Backbone.js's extend
  3344.      // Helper function to correctly set up the prototype chain, for subclasses.
  3345.      // Similar to `goog.inherits`, but uses a hash of prototype properties and
  3346.      // class properties to be extended.
  3347.     inherit: function(protoProps, staticProps) {
  3348.       var parent = this;
  3349.       var child;
  3350.  
  3351.       // The constructor function for the new subclass is either defined by you
  3352.       // (the "constructor" property in your `extend` definition), or defaulted
  3353.       // by us to simply call the parent's constructor.
  3354.       if (protoProps && protoProps.hasOwnProperty('constructor')) {
  3355.         child = protoProps.constructor;
  3356.       } else {
  3357.         child = function() { return parent.apply(this, arguments); };
  3358.       }
  3359.  
  3360.       // Add static properties to the constructor function, if supplied.
  3361.       ionic.extend(child, parent, staticProps);
  3362.  
  3363.       // Set the prototype chain to inherit from `parent`, without calling
  3364.       // `parent`'s constructor function.
  3365.       var Surrogate = function() { this.constructor = child; };
  3366.       Surrogate.prototype = parent.prototype;
  3367.       child.prototype = new Surrogate();
  3368.  
  3369.       // Add prototype properties (instance properties) to the subclass,
  3370.       // if supplied.
  3371.       if (protoProps) ionic.extend(child.prototype, protoProps);
  3372.  
  3373.       // Set a convenience property in case the parent's prototype is needed
  3374.       // later.
  3375.       child.__super__ = parent.prototype;
  3376.  
  3377.       return child;
  3378.     },
  3379.  
  3380.     // Extend adapted from Underscore.js
  3381.     extend: function(obj) {
  3382.        var args = Array.prototype.slice.call(arguments, 1);
  3383.        for (var i = 0; i < args.length; i++) {
  3384.          var source = args[i];
  3385.          if (source) {
  3386.            for (var prop in source) {
  3387.              obj[prop] = source[prop];
  3388.            }
  3389.          }
  3390.        }
  3391.        return obj;
  3392.     },
  3393.  
  3394.     nextUid: function() {
  3395.       return 'ion' + (nextId++);
  3396.     },
  3397.  
  3398.     disconnectScope: function disconnectScope(scope) {
  3399.       if (!scope) return;
  3400.  
  3401.       if (scope.$root === scope) {
  3402.         return; // we can't disconnect the root node;
  3403.       }
  3404.       var parent = scope.$parent;
  3405.       scope.$$disconnected = true;
  3406.       scope.$broadcast('$ionic.disconnectScope', scope);
  3407.  
  3408.       // See Scope.$destroy
  3409.       if (parent.$$childHead === scope) {
  3410.         parent.$$childHead = scope.$$nextSibling;
  3411.       }
  3412.       if (parent.$$childTail === scope) {
  3413.         parent.$$childTail = scope.$$prevSibling;
  3414.       }
  3415.       if (scope.$$prevSibling) {
  3416.         scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;
  3417.       }
  3418.       if (scope.$$nextSibling) {
  3419.         scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;
  3420.       }
  3421.       scope.$$nextSibling = scope.$$prevSibling = null;
  3422.     },
  3423.  
  3424.     reconnectScope: function reconnectScope(scope) {
  3425.       if (!scope) return;
  3426.  
  3427.       if (scope.$root === scope) {
  3428.         return; // we can't disconnect the root node;
  3429.       }
  3430.       if (!scope.$$disconnected) {
  3431.         return;
  3432.       }
  3433.       var parent = scope.$parent;
  3434.       scope.$$disconnected = false;
  3435.       scope.$broadcast('$ionic.reconnectScope', scope);
  3436.       // See Scope.$new for this logic...
  3437.       scope.$$prevSibling = parent.$$childTail;
  3438.       if (parent.$$childHead) {
  3439.         parent.$$childTail.$$nextSibling = scope;
  3440.         parent.$$childTail = scope;
  3441.       } else {
  3442.         parent.$$childHead = parent.$$childTail = scope;
  3443.       }
  3444.     },
  3445.  
  3446.     isScopeDisconnected: function(scope) {
  3447.       var climbScope = scope;
  3448.       while (climbScope) {
  3449.         if (climbScope.$$disconnected) return true;
  3450.         climbScope = climbScope.$parent;
  3451.       }
  3452.       return false;
  3453.     }
  3454.   };
  3455.  
  3456.   // Bind a few of the most useful functions to the ionic scope
  3457.   ionic.inherit = ionic.Utils.inherit;
  3458.   ionic.extend = ionic.Utils.extend;
  3459.   ionic.throttle = ionic.Utils.throttle;
  3460.   ionic.proxy = ionic.Utils.proxy;
  3461.   ionic.debounce = ionic.Utils.debounce;
  3462.  
  3463. })(window.ionic);
  3464.  
  3465. /**
  3466.  * @ngdoc page
  3467.  * @name keyboard
  3468.  * @module ionic
  3469.  * @description
  3470.  * On both Android and iOS, Ionic will attempt to prevent the keyboard from
  3471.  * obscuring inputs and focusable elements when it appears by scrolling them
  3472.  * into view.  In order for this to work, any focusable elements must be within
  3473.  * a [Scroll View](http://ionicframework.com/docs/api/directive/ionScroll/)
  3474.  * or a directive such as [Content](http://ionicframework.com/docs/api/directive/ionContent/)
  3475.  * that has a Scroll View.
  3476.  *
  3477.  * It will also attempt to prevent the native overflow scrolling on focus,
  3478.  * which can cause layout issues such as pushing headers up and out of view.
  3479.  *
  3480.  * The keyboard fixes work best in conjunction with the
  3481.  * [Ionic Keyboard Plugin](https://github.com/driftyco/ionic-plugins-keyboard),
  3482.  * although it will perform reasonably well without.  However, if you are using
  3483.  * Cordova there is no reason not to use the plugin.
  3484.  *
  3485.  * ### Hide when keyboard shows
  3486.  *
  3487.  * To hide an element when the keyboard is open, add the class `hide-on-keyboard-open`.
  3488.  *
  3489.  * ```html
  3490.  * <div class="hide-on-keyboard-open">
  3491.  *   <div id="google-map"></div>
  3492.  * </div>
  3493.  * ```
  3494.  *
  3495.  * Note: For performance reasons, elements will not be hidden for 400ms after the start of the `native.keyboardshow` event
  3496.  * from the Ionic Keyboard plugin. If you would like them to disappear immediately, you could do something
  3497.  * like:
  3498.  *
  3499.  * ```js
  3500.  *   window.addEventListener('native.keyboardshow', function(){
  3501.  *     document.body.classList.add('keyboard-open');
  3502.  *   });
  3503.  * ```
  3504.  * This adds the same `keyboard-open` class that is normally added by Ionic 400ms after the keyboard
  3505.  * opens. However, bear in mind that adding this class to the body immediately may cause jank in any
  3506.  * animations on Android that occur when the keyboard opens (for example, scrolling any obscured inputs into view).
  3507.  *
  3508.  * ----------
  3509.  *
  3510.  * ### Plugin Usage
  3511.  * Information on using the plugin can be found at
  3512.  * [https://github.com/driftyco/ionic-plugins-keyboard](https://github.com/driftyco/ionic-plugins-keyboard).
  3513.  *
  3514.  * ----------
  3515.  *
  3516.  * ### Android Notes
  3517.  * - If your app is running in fullscreen, i.e. you have
  3518.  *   `<preference name="Fullscreen" value="true" />` in your `config.xml` file
  3519.  *   you will need to set `ionic.Platform.isFullScreen = true` manually.
  3520.  *
  3521.  * - You can configure the behavior of the web view when the keyboard shows by setting
  3522.  *   [android:windowSoftInputMode](http://developer.android.com/reference/android/R.attr.html#windowSoftInputMode)
  3523.  *   to either `adjustPan`, `adjustResize` or `adjustNothing` in your app's
  3524.  *   activity in `AndroidManifest.xml`. `adjustResize` is the recommended setting
  3525.  *   for Ionic, but if for some reason you do use `adjustPan` you will need to
  3526.  *   set `ionic.Platform.isFullScreen = true`.
  3527.  *
  3528.  *   ```xml
  3529.  *   <activity android:windowSoftInputMode="adjustResize">
  3530.  *
  3531.  *   ```
  3532.  *
  3533.  * ### iOS Notes
  3534.  * - If the content of your app (including the header) is being pushed up and
  3535.  *   out of view on input focus, try setting `cordova.plugins.Keyboard.disableScroll(true)`.
  3536.  *   This does **not** disable scrolling in the Ionic scroll view, rather it
  3537.  *   disables the native overflow scrolling that happens automatically as a
  3538.  *   result of focusing on inputs below the keyboard.
  3539.  *
  3540.  */
  3541.  
  3542. /**
  3543.  * The current viewport height.
  3544.  */
  3545. var keyboardCurrentViewportHeight = 0;
  3546.  
  3547. /**
  3548.  * The viewport height when in portrait orientation.
  3549.  */
  3550. var keyboardPortraitViewportHeight = 0;
  3551.  
  3552. /**
  3553.  * The viewport height when in landscape orientation.
  3554.  */
  3555. var keyboardLandscapeViewportHeight = 0;
  3556.  
  3557. /**
  3558.  * The currently focused input.
  3559.  */
  3560. var keyboardActiveElement;
  3561.  
  3562. /**
  3563.  * The scroll view containing the currently focused input.
  3564.  */
  3565. var scrollView;
  3566.  
  3567. /**
  3568.  * Timer for the setInterval that polls window.innerHeight to determine whether
  3569.  * the layout has updated for the keyboard showing/hiding.
  3570.  */
  3571. var waitForResizeTimer;
  3572.  
  3573. /**
  3574.  * Sometimes when switching inputs or orientations, focusout will fire before
  3575.  * focusin, so this timer is for the small setTimeout to determine if we should
  3576.  * really focusout/hide the keyboard.
  3577.  */
  3578. var keyboardFocusOutTimer;
  3579.  
  3580. /**
  3581.  * on Android, orientationchange will fire before the keyboard plugin notifies
  3582.  * the browser that the keyboard will show/is showing, so this flag indicates
  3583.  * to nativeShow that there was an orientationChange and we should update
  3584.  * the viewport height with an accurate keyboard height value
  3585.  */
  3586. var wasOrientationChange = false;
  3587.  
  3588. /**
  3589.  * CSS class added to the body indicating the keyboard is open.
  3590.  */
  3591. var KEYBOARD_OPEN_CSS = 'keyboard-open';
  3592.  
  3593. /**
  3594.  * CSS class that indicates a scroll container.
  3595.  */
  3596. var SCROLL_CONTAINER_CSS = 'scroll-content';
  3597.  
  3598. /**
  3599.  * Debounced keyboardFocusIn function
  3600.  */
  3601. var debouncedKeyboardFocusIn = ionic.debounce(keyboardFocusIn, 200, true);
  3602.  
  3603. /**
  3604.  * Debounced keyboardNativeShow function
  3605.  */
  3606. var debouncedKeyboardNativeShow = ionic.debounce(keyboardNativeShow, 100, true);
  3607.  
  3608. /**
  3609.  * Ionic keyboard namespace.
  3610.  * @namespace keyboard
  3611.  */
  3612. ionic.keyboard = {
  3613.  
  3614.   /**
  3615.    * Whether the keyboard is open or not.
  3616.    */
  3617.   isOpen: false,
  3618.  
  3619.   /**
  3620.    * Whether the keyboard is closing or not.
  3621.    */
  3622.   isClosing: false,
  3623.  
  3624.   /**
  3625.    * Whether the keyboard is opening or not.
  3626.    */
  3627.   isOpening: false,
  3628.  
  3629.   /**
  3630.    * The height of the keyboard in pixels, as reported by the keyboard plugin.
  3631.    * If the plugin is not available, calculated as the difference in
  3632.    * window.innerHeight after the keyboard has shown.
  3633.    */
  3634.   height: 0,
  3635.  
  3636.   /**
  3637.    * Whether the device is in landscape orientation or not.
  3638.    */
  3639.   isLandscape: false,
  3640.  
  3641.   /**
  3642.    * Whether the keyboard event listeners have been added or not
  3643.    */
  3644.   isInitialized: false,
  3645.  
  3646.   /**
  3647.    * Hide the keyboard, if it is open.
  3648.    */
  3649.   hide: function() {
  3650.     if (keyboardHasPlugin()) {
  3651.       cordova.plugins.Keyboard.close();
  3652.     }
  3653.     keyboardActiveElement && keyboardActiveElement.blur();
  3654.   },
  3655.  
  3656.   /**
  3657.    * An alias for cordova.plugins.Keyboard.show(). If the keyboard plugin
  3658.    * is installed, show the keyboard.
  3659.    */
  3660.   show: function() {
  3661.     if (keyboardHasPlugin()) {
  3662.       cordova.plugins.Keyboard.show();
  3663.     }
  3664.   },
  3665.  
  3666.   /**
  3667.    * Remove all keyboard related event listeners, effectively disabling Ionic's
  3668.    * keyboard adjustments.
  3669.    */
  3670.   disable: function() {
  3671.     if (keyboardHasPlugin()) {
  3672.       window.removeEventListener('native.keyboardshow', debouncedKeyboardNativeShow );
  3673.       window.removeEventListener('native.keyboardhide', keyboardFocusOut);
  3674.     } else {
  3675.       document.body.removeEventListener('focusout', keyboardFocusOut);
  3676.     }
  3677.  
  3678.     document.body.removeEventListener('ionic.focusin', debouncedKeyboardFocusIn);
  3679.     document.body.removeEventListener('focusin', debouncedKeyboardFocusIn);
  3680.  
  3681.     window.removeEventListener('orientationchange', keyboardOrientationChange);
  3682.  
  3683.     if ( window.navigator.msPointerEnabled ) {
  3684.       document.removeEventListener("MSPointerDown", keyboardInit);
  3685.     } else {
  3686.       document.removeEventListener('touchstart', keyboardInit);
  3687.     }
  3688.     ionic.keyboard.isInitialized = false;
  3689.   },
  3690.  
  3691.   /**
  3692.    * Alias for keyboardInit, initialize all keyboard related event listeners.
  3693.    */
  3694.   enable: function() {
  3695.     keyboardInit();
  3696.   }
  3697. };
  3698.  
  3699. // Initialize the viewport height (after ionic.keyboard.height has been
  3700. // defined).
  3701. keyboardCurrentViewportHeight = getViewportHeight();
  3702.  
  3703.  
  3704.                              /* Event handlers */
  3705. /* ------------------------------------------------------------------------- */
  3706.  
  3707. /**
  3708.  * Event handler for first touch event, initializes all event listeners
  3709.  * for keyboard related events. Also aliased by ionic.keyboard.enable.
  3710.  */
  3711. function keyboardInit() {
  3712.  
  3713.   if (ionic.keyboard.isInitialized) return;
  3714.  
  3715.   if (keyboardHasPlugin()) {
  3716.     window.addEventListener('native.keyboardshow', debouncedKeyboardNativeShow);
  3717.     window.addEventListener('native.keyboardhide', keyboardFocusOut);
  3718.   } else {
  3719.     document.body.addEventListener('focusout', keyboardFocusOut);
  3720.   }
  3721.  
  3722.   document.body.addEventListener('ionic.focusin', debouncedKeyboardFocusIn);
  3723.   document.body.addEventListener('focusin', debouncedKeyboardFocusIn);
  3724.  
  3725.   if (window.navigator.msPointerEnabled) {
  3726.     document.removeEventListener("MSPointerDown", keyboardInit);
  3727.   } else {
  3728.     document.removeEventListener('touchstart', keyboardInit);
  3729.   }
  3730.  
  3731.   ionic.keyboard.isInitialized = true;
  3732. }
  3733.  
  3734. /**
  3735.  * Event handler for 'native.keyboardshow' event, sets keyboard.height to the
  3736.  * reported height and keyboard.isOpening to true. Then calls
  3737.  * keyboardWaitForResize with keyboardShow or keyboardUpdateViewportHeight as
  3738.  * the callback depending on whether the event was triggered by a focusin or
  3739.  * an orientationchange.
  3740.  */
  3741. function keyboardNativeShow(e) {
  3742.   clearTimeout(keyboardFocusOutTimer);
  3743.   //console.log("keyboardNativeShow fired at: " + Date.now());
  3744.   //console.log("keyboardNativeshow window.innerHeight: " + window.innerHeight);
  3745.  
  3746.   if (!ionic.keyboard.isOpen || ionic.keyboard.isClosing) {
  3747.     ionic.keyboard.isOpening = true;
  3748.     ionic.keyboard.isClosing = false;
  3749.   }
  3750.  
  3751.   ionic.keyboard.height = e.keyboardHeight;
  3752.   //console.log('nativeshow keyboard height:' + e.keyboardHeight);
  3753.  
  3754.   if (wasOrientationChange) {
  3755.     keyboardWaitForResize(keyboardUpdateViewportHeight, true);
  3756.   } else {
  3757.     keyboardWaitForResize(keyboardShow, true);
  3758.   }
  3759. }
  3760.  
  3761. /**
  3762.  * Event handler for 'focusin' and 'ionic.focusin' events. Initializes
  3763.  * keyboard state (keyboardActiveElement and keyboard.isOpening) for the
  3764.  * appropriate adjustments once the window has resized.  If not using the
  3765.  * keyboard plugin, calls keyboardWaitForResize with keyboardShow as the
  3766.  * callback or keyboardShow right away if the keyboard is already open.  If
  3767.  * using the keyboard plugin does nothing and lets keyboardNativeShow handle
  3768.  * adjustments with a more accurate keyboard height.
  3769.  */
  3770. function keyboardFocusIn(e) {
  3771.   clearTimeout(keyboardFocusOutTimer);
  3772.   //console.log("keyboardFocusIn from: " + e.type + " at: " + Date.now());
  3773.  
  3774.   if (!e.target ||
  3775.       e.target.readOnly ||
  3776.       !ionic.tap.isKeyboardElement(e.target) ||
  3777.       !(scrollView = ionic.DomUtil.getParentWithClass(e.target, SCROLL_CONTAINER_CSS))) {
  3778.     keyboardActiveElement = null;
  3779.     return;
  3780.   }
  3781.  
  3782.   keyboardActiveElement = e.target;
  3783.  
  3784.   // if using JS scrolling, undo the effects of native overflow scroll so the
  3785.   // scroll view is positioned correctly
  3786.   if (!scrollView.classList.contains("overflow-scroll")) {
  3787.     document.body.scrollTop = 0;
  3788.     scrollView.scrollTop = 0;
  3789.     ionic.requestAnimationFrame(function(){
  3790.       document.body.scrollTop = 0;
  3791.       scrollView.scrollTop = 0;
  3792.     });
  3793.  
  3794.     // any showing part of the document that isn't within the scroll the user
  3795.     // could touchmove and cause some ugly changes to the app, so disable
  3796.     // any touchmove events while the keyboard is open using e.preventDefault()
  3797.     if (window.navigator.msPointerEnabled) {
  3798.       document.addEventListener("MSPointerMove", keyboardPreventDefault, false);
  3799.     } else {
  3800.       document.addEventListener('touchmove', keyboardPreventDefault, false);
  3801.     }
  3802.   }
  3803.  
  3804.   if (!ionic.keyboard.isOpen || ionic.keyboard.isClosing) {
  3805.     ionic.keyboard.isOpening = true;
  3806.     ionic.keyboard.isClosing = false;
  3807.   }
  3808.  
  3809.   // attempt to prevent browser from natively scrolling input into view while
  3810.   // we are trying to do the same (while we are scrolling) if the user taps the
  3811.   // keyboard
  3812.   document.addEventListener('keydown', keyboardOnKeyDown, false);
  3813.  
  3814.  
  3815.  
  3816.   // if we aren't using the plugin and the keyboard isn't open yet, wait for the
  3817.   // window to resize so we can get an accurate estimate of the keyboard size,
  3818.   // otherwise we do nothing and let nativeShow call keyboardShow once we have
  3819.   // an exact keyboard height
  3820.   // if the keyboard is already open, go ahead and scroll the input into view
  3821.   // if necessary
  3822.   if (!ionic.keyboard.isOpen && !keyboardHasPlugin()) {
  3823.     keyboardWaitForResize(keyboardShow, true);
  3824.  
  3825.   } else if (ionic.keyboard.isOpen) {
  3826.     keyboardShow();
  3827.   }
  3828. }
  3829.  
  3830. /**
  3831.  * Event handler for 'focusout' events. Sets keyboard.isClosing to true and
  3832.  * calls keyboardWaitForResize with keyboardHide as the callback after a small
  3833.  * timeout.
  3834.  */
  3835. function keyboardFocusOut() {
  3836.   clearTimeout(keyboardFocusOutTimer);
  3837.   //console.log("keyboardFocusOut fired at: " + Date.now());
  3838.   //console.log("keyboardFocusOut event type: " + e.type);
  3839.  
  3840.   if (ionic.keyboard.isOpen || ionic.keyboard.isOpening) {
  3841.     ionic.keyboard.isClosing = true;
  3842.     ionic.keyboard.isOpening = false;
  3843.   }
  3844.  
  3845.   // Call keyboardHide with a slight delay because sometimes on focus or
  3846.   // orientation change focusin is called immediately after, so we give it time
  3847.   // to cancel keyboardHide
  3848.   keyboardFocusOutTimer = setTimeout(function() {
  3849.     ionic.requestAnimationFrame(function() {
  3850.       // focusOut during or right after an orientationchange, so we didn't get
  3851.       // a chance to update the viewport height yet, do it and keyboardHide
  3852.       //console.log("focusOut, wasOrientationChange: " + wasOrientationChange);
  3853.       if (wasOrientationChange) {
  3854.         keyboardWaitForResize(function(){
  3855.           keyboardUpdateViewportHeight();
  3856.           keyboardHide();
  3857.         }, false);
  3858.       } else {
  3859.         keyboardWaitForResize(keyboardHide, false);
  3860.       }
  3861.     });
  3862.   }, 50);
  3863. }
  3864.  
  3865. /**
  3866.  * Event handler for 'orientationchange' events. If using the keyboard plugin
  3867.  * and the keyboard is open on Android, sets wasOrientationChange to true so
  3868.  * nativeShow can update the viewport height with an accurate keyboard height.
  3869.  * If the keyboard isn't open or keyboard plugin isn't being used,
  3870.  * waits for the window to resize before updating the viewport height.
  3871.  *
  3872.  * On iOS, where orientationchange fires after the keyboard has already shown,
  3873.  * updates the viewport immediately, regardless of if the keyboard is already
  3874.  * open.
  3875.  */
  3876. function keyboardOrientationChange() {
  3877.   //console.log("orientationchange fired at: " + Date.now());
  3878.   //console.log("orientation was: " + (ionic.keyboard.isLandscape ? "landscape" : "portrait"));
  3879.  
  3880.   // toggle orientation
  3881.   ionic.keyboard.isLandscape = !ionic.keyboard.isLandscape;
  3882.   // //console.log("now orientation is: " + (ionic.keyboard.isLandscape ? "landscape" : "portrait"));
  3883.  
  3884.   // no need to wait for resizing on iOS, and orientationchange always fires
  3885.   // after the keyboard has opened, so it doesn't matter if it's open or not
  3886.   if (ionic.Platform.isIOS()) {
  3887.     keyboardUpdateViewportHeight();
  3888.   }
  3889.  
  3890.   // On Android, if the keyboard isn't open or we aren't using the keyboard
  3891.   // plugin, update the viewport height once everything has resized. If the
  3892.   // keyboard is open and we are using the keyboard plugin do nothing and let
  3893.   // nativeShow handle it using an accurate keyboard height.
  3894.   if ( ionic.Platform.isAndroid()) {
  3895.     if (!ionic.keyboard.isOpen || !keyboardHasPlugin()) {
  3896.       keyboardWaitForResize(keyboardUpdateViewportHeight, false);
  3897.     } else {
  3898.       wasOrientationChange = true;
  3899.     }
  3900.   }
  3901. }
  3902.  
  3903. /**
  3904.  * Event handler for 'keydown' event. Tries to prevent browser from natively
  3905.  * scrolling an input into view when a user taps the keyboard while we are
  3906.  * scrolling the input into view ourselves with JS.
  3907.  */
  3908. function keyboardOnKeyDown(e) {
  3909.   if (ionic.scroll.isScrolling) {
  3910.     keyboardPreventDefault(e);
  3911.   }
  3912. }
  3913.  
  3914. /**
  3915.  * Event for 'touchmove' or 'MSPointerMove'. Prevents native scrolling on
  3916.  * elements outside the scroll view while the keyboard is open.
  3917.  */
  3918. function keyboardPreventDefault(e) {
  3919.   if (e.target.tagName !== 'TEXTAREA') {
  3920.     e.preventDefault();
  3921.   }
  3922. }
  3923.  
  3924.                               /* Private API */
  3925. /* -------------------------------------------------------------------------- */
  3926.  
  3927. /**
  3928.  * Polls window.innerHeight until it has updated to an expected value (or
  3929.  * sufficient time has passed) before calling the specified callback function.
  3930.  * Only necessary for non-fullscreen Android which sometimes reports multiple
  3931.  * window.innerHeight values during interim layouts while it is resizing.
  3932.  *
  3933.  * On iOS, the window.innerHeight will already be updated, but we use the 50ms
  3934.  * delay as essentially a timeout so that scroll view adjustments happen after
  3935.  * the keyboard has shown so there isn't a white flash from us resizing too
  3936.  * quickly.
  3937.  *
  3938.  * @param {Function} callback the function to call once the window has resized
  3939.  * @param {boolean} isOpening whether the resize is from the keyboard opening
  3940.  * or not
  3941.  */
  3942. function keyboardWaitForResize(callback, isOpening) {
  3943.   clearInterval(waitForResizeTimer);
  3944.   var count = 0;
  3945.   var maxCount;
  3946.   var initialHeight = getViewportHeight();
  3947.   var viewportHeight = initialHeight;
  3948.  
  3949.   //console.log("waitForResize initial viewport height: " + viewportHeight);
  3950.   //var start = Date.now();
  3951.   //console.log("start: " + start);
  3952.  
  3953.   // want to fail relatively quickly on modern android devices, since it's much
  3954.   // more likely we just have a bad keyboard height
  3955.   if (ionic.Platform.isAndroid() && ionic.Platform.version() < 4.4) {
  3956.     maxCount = 30;
  3957.   } else if (ionic.Platform.isAndroid()) {
  3958.     maxCount = 10;
  3959.   } else {
  3960.     maxCount = 1;
  3961.   }
  3962.  
  3963.   // poll timer
  3964.   waitForResizeTimer = setInterval(function(){
  3965.     viewportHeight = getViewportHeight();
  3966.  
  3967.     // height hasn't updated yet, try again in 50ms
  3968.     // if not using plugin, wait for maxCount to ensure we have waited long enough
  3969.     // to get an accurate keyboard height
  3970.     if (++count < maxCount &&
  3971.         ((!isPortraitViewportHeight(viewportHeight) &&
  3972.          !isLandscapeViewportHeight(viewportHeight)) ||
  3973.          !ionic.keyboard.height)) {
  3974.       return;
  3975.     }
  3976.  
  3977.     // infer the keyboard height from the resize if not using the keyboard plugin
  3978.     if (!keyboardHasPlugin()) {
  3979.       ionic.keyboard.height = Math.abs(initialHeight - window.innerHeight);
  3980.     }
  3981.  
  3982.     // set to true if we were waiting for the keyboard to open
  3983.     ionic.keyboard.isOpen = isOpening;
  3984.  
  3985.     clearInterval(waitForResizeTimer);
  3986.     //var end = Date.now();
  3987.     //console.log("waitForResize count: " + count);
  3988.     //console.log("end: " + end);
  3989.     //console.log("difference: " + ( end - start ) + "ms");
  3990.  
  3991.     //console.log("callback: " + callback.name);
  3992.     callback();
  3993.  
  3994.   }, 50);
  3995.  
  3996.   return maxCount; //for tests
  3997. }
  3998.  
  3999. /**
  4000.  * On keyboard close sets keyboard state to closed, resets the scroll view,
  4001.  * removes CSS from body indicating keyboard was open, removes any event
  4002.  * listeners for when the keyboard is open and on Android blurs the active
  4003.  * element (which in some cases will still have focus even if the keyboard
  4004.  * is closed and can cause it to reappear on subsequent taps).
  4005.  */
  4006. function keyboardHide() {
  4007.   clearTimeout(keyboardFocusOutTimer);
  4008.   //console.log("keyboardHide");
  4009.  
  4010.   ionic.keyboard.isOpen = false;
  4011.   ionic.keyboard.isClosing = false;
  4012.  
  4013.   if (keyboardActiveElement) {
  4014.     ionic.trigger('resetScrollView', {
  4015.       target: keyboardActiveElement
  4016.     }, true);
  4017.   }
  4018.  
  4019.   ionic.requestAnimationFrame(function(){
  4020.     document.body.classList.remove(KEYBOARD_OPEN_CSS);
  4021.   });
  4022.  
  4023.   // the keyboard is gone now, remove the touchmove that disables native scroll
  4024.   if (window.navigator.msPointerEnabled) {
  4025.     document.removeEventListener("MSPointerMove", keyboardPreventDefault);
  4026.   } else {
  4027.     document.removeEventListener('touchmove', keyboardPreventDefault);
  4028.   }
  4029.   document.removeEventListener('keydown', keyboardOnKeyDown);
  4030.  
  4031.   if (ionic.Platform.isAndroid()) {
  4032.     // on android closing the keyboard with the back/dismiss button won't remove
  4033.     // focus and keyboard can re-appear on subsequent taps (like scrolling)
  4034.     if (keyboardHasPlugin()) cordova.plugins.Keyboard.close();
  4035.     keyboardActiveElement && keyboardActiveElement.blur();
  4036.   }
  4037.  
  4038.   keyboardActiveElement = null;
  4039. }
  4040.  
  4041. /**
  4042.  * On keyboard open sets keyboard state to open, adds CSS to the body
  4043.  * indicating the keyboard is open and tells the scroll view to resize and
  4044.  * the currently focused input into view if necessary.
  4045.  */
  4046. function keyboardShow() {
  4047.  
  4048.   ionic.keyboard.isOpen = true;
  4049.   ionic.keyboard.isOpening = false;
  4050.  
  4051.   var details = {
  4052.     keyboardHeight: keyboardGetHeight(),
  4053.     viewportHeight: keyboardCurrentViewportHeight
  4054.   };
  4055.  
  4056.   if (keyboardActiveElement) {
  4057.     details.target = keyboardActiveElement;
  4058.  
  4059.     var elementBounds = keyboardActiveElement.getBoundingClientRect();
  4060.  
  4061.     details.elementTop = Math.round(elementBounds.top);
  4062.     details.elementBottom = Math.round(elementBounds.bottom);
  4063.  
  4064.     details.windowHeight = details.viewportHeight - details.keyboardHeight;
  4065.     //console.log("keyboardShow viewportHeight: " + details.viewportHeight +
  4066.     //", windowHeight: " + details.windowHeight +
  4067.     //", keyboardHeight: " + details.keyboardHeight);
  4068.  
  4069.     // figure out if the element is under the keyboard
  4070.     details.isElementUnderKeyboard = (details.elementBottom > details.windowHeight);
  4071.     //console.log("isUnderKeyboard: " + details.isElementUnderKeyboard);
  4072.     //console.log("elementBottom: " + details.elementBottom);
  4073.  
  4074.     // send event so the scroll view adjusts
  4075.     ionic.trigger('scrollChildIntoView', details, true);
  4076.   }
  4077.  
  4078.   setTimeout(function(){
  4079.     document.body.classList.add(KEYBOARD_OPEN_CSS);
  4080.   }, 400);
  4081.  
  4082.   return details; //for testing
  4083. }
  4084.  
  4085. /* eslint no-unused-vars:0 */
  4086. function keyboardGetHeight() {
  4087.   // check if we already have a keyboard height from the plugin or resize calculations
  4088.   if (ionic.keyboard.height) {
  4089.     return ionic.keyboard.height;
  4090.   }
  4091.  
  4092.   if (ionic.Platform.isAndroid()) {
  4093.     // should be using the plugin, no way to know how big the keyboard is, so guess
  4094.     if ( ionic.Platform.isFullScreen ) {
  4095.       return 275;
  4096.     }
  4097.     // otherwise just calculate it
  4098.     var contentHeight = window.innerHeight;
  4099.     if (contentHeight < keyboardCurrentViewportHeight) {
  4100.       return keyboardCurrentViewportHeight - contentHeight;
  4101.     } else {
  4102.       return 0;
  4103.     }
  4104.   }
  4105.  
  4106.   // fallback for when it's the webview without the plugin
  4107.   // or for just the standard web browser
  4108.   // TODO: have these be based on device
  4109.   if (ionic.Platform.isIOS()) {
  4110.     if (ionic.keyboard.isLandscape) {
  4111.       return 206;
  4112.     }
  4113.  
  4114.     if (!ionic.Platform.isWebView()) {
  4115.       return 216;
  4116.     }
  4117.  
  4118.     return 260;
  4119.   }
  4120.  
  4121.   // safe guess
  4122.   return 275;
  4123. }
  4124.  
  4125. function isPortraitViewportHeight(viewportHeight) {
  4126.   return !!(!ionic.keyboard.isLandscape &&
  4127.          keyboardPortraitViewportHeight &&
  4128.          (Math.abs(keyboardPortraitViewportHeight - viewportHeight) < 2));
  4129. }
  4130.  
  4131. function isLandscapeViewportHeight(viewportHeight) {
  4132.   return !!(ionic.keyboard.isLandscape &&
  4133.          keyboardLandscapeViewportHeight &&
  4134.          (Math.abs(keyboardLandscapeViewportHeight - viewportHeight) < 2));
  4135. }
  4136.  
  4137. function keyboardUpdateViewportHeight() {
  4138.   wasOrientationChange = false;
  4139.   keyboardCurrentViewportHeight = getViewportHeight();
  4140.  
  4141.   if (ionic.keyboard.isLandscape && !keyboardLandscapeViewportHeight) {
  4142.     //console.log("saved landscape: " + keyboardCurrentViewportHeight);
  4143.     keyboardLandscapeViewportHeight = keyboardCurrentViewportHeight;
  4144.  
  4145.   } else if (!ionic.keyboard.isLandscape && !keyboardPortraitViewportHeight) {
  4146.     //console.log("saved portrait: " + keyboardCurrentViewportHeight);
  4147.     keyboardPortraitViewportHeight = keyboardCurrentViewportHeight;
  4148.   }
  4149.  
  4150.   if (keyboardActiveElement) {
  4151.     ionic.trigger('resetScrollView', {
  4152.       target: keyboardActiveElement
  4153.     }, true);
  4154.   }
  4155.  
  4156.   if (ionic.keyboard.isOpen && ionic.tap.isTextInput(keyboardActiveElement)) {
  4157.     keyboardShow();
  4158.   }
  4159. }
  4160.  
  4161. function keyboardInitViewportHeight() {
  4162.   var viewportHeight = getViewportHeight();
  4163.   //console.log("Keyboard init VP: " + viewportHeight + " " + window.innerWidth);
  4164.   // can't just use window.innerHeight in case the keyboard is opened immediately
  4165.   if ((viewportHeight / window.innerWidth) < 1) {
  4166.     ionic.keyboard.isLandscape = true;
  4167.   }
  4168.   //console.log("ionic.keyboard.isLandscape is: " + ionic.keyboard.isLandscape);
  4169.  
  4170.   // initialize or update the current viewport height values
  4171.   keyboardCurrentViewportHeight = viewportHeight;
  4172.   if (ionic.keyboard.isLandscape && !keyboardLandscapeViewportHeight) {
  4173.     keyboardLandscapeViewportHeight = keyboardCurrentViewportHeight;
  4174.   } else if (!ionic.keyboard.isLandscape && !keyboardPortraitViewportHeight) {
  4175.     keyboardPortraitViewportHeight = keyboardCurrentViewportHeight;
  4176.   }
  4177. }
  4178.  
  4179. function getViewportHeight() {
  4180.   var windowHeight = window.innerHeight;
  4181.   //console.log('window.innerHeight is: ' + windowHeight);
  4182.   //console.log('kb height is: ' + ionic.keyboard.height);
  4183.   //console.log('kb isOpen: ' + ionic.keyboard.isOpen);
  4184.  
  4185.   //TODO: add iPad undocked/split kb once kb plugin supports it
  4186.   // the keyboard overlays the window on Android fullscreen
  4187.   if (!(ionic.Platform.isAndroid() && ionic.Platform.isFullScreen) &&
  4188.       (ionic.keyboard.isOpen || ionic.keyboard.isOpening) &&
  4189.       !ionic.keyboard.isClosing) {
  4190.  
  4191.      return windowHeight + keyboardGetHeight();
  4192.   }
  4193.   return windowHeight;
  4194. }
  4195.  
  4196. function keyboardHasPlugin() {
  4197.   return !!(window.cordova && cordova.plugins && cordova.plugins.Keyboard);
  4198. }
  4199.  
  4200. ionic.Platform.ready(function() {
  4201.   keyboardInitViewportHeight();
  4202.  
  4203.   window.addEventListener('orientationchange', keyboardOrientationChange);
  4204.  
  4205.   // if orientation changes while app is in background, update on resuming
  4206.   /*
  4207.   if ( ionic.Platform.isWebView() ) {
  4208.     document.addEventListener('resume', keyboardInitViewportHeight);
  4209.  
  4210.     if (ionic.Platform.isAndroid()) {
  4211.       //TODO: onbackpressed to detect keyboard close without focusout or plugin
  4212.     }
  4213.   }
  4214.   */
  4215.  
  4216.   // if orientation changes while app is in background, update on resuming
  4217. /*  if ( ionic.Platform.isWebView() ) {
  4218.     document.addEventListener('pause', function() {
  4219.       window.removeEventListener('orientationchange', keyboardOrientationChange);
  4220.     })
  4221.     document.addEventListener('resume', function() {
  4222.       keyboardInitViewportHeight();
  4223.       window.addEventListener('orientationchange', keyboardOrientationChange)
  4224.     });
  4225.   }*/
  4226.  
  4227.   // Android sometimes reports bad innerHeight on window.load
  4228.   // try it again in a lil bit to play it safe
  4229.   setTimeout(keyboardInitViewportHeight, 999);
  4230.  
  4231.   // only initialize the adjustments for the virtual keyboard
  4232.   // if a touchstart event happens
  4233.   if (window.navigator.msPointerEnabled) {
  4234.     document.addEventListener("MSPointerDown", keyboardInit, false);
  4235.   } else {
  4236.     document.addEventListener('touchstart', keyboardInit, false);
  4237.   }
  4238. });
  4239.  
  4240.  
  4241.  
  4242. var viewportTag;
  4243. var viewportProperties = {};
  4244.  
  4245. ionic.viewport = {
  4246.   orientation: function() {
  4247.     // 0 = Portrait
  4248.     // 90 = Landscape
  4249.     // not using window.orientation because each device has a different implementation
  4250.     return (window.innerWidth > window.innerHeight ? 90 : 0);
  4251.   }
  4252. };
  4253.  
  4254. function viewportLoadTag() {
  4255.   var x;
  4256.  
  4257.   for (x = 0; x < document.head.children.length; x++) {
  4258.     if (document.head.children[x].name == 'viewport') {
  4259.       viewportTag = document.head.children[x];
  4260.       break;
  4261.     }
  4262.   }
  4263.  
  4264.   if (viewportTag) {
  4265.     var props = viewportTag.content.toLowerCase().replace(/\s+/g, '').split(',');
  4266.     var keyValue;
  4267.     for (x = 0; x < props.length; x++) {
  4268.       if (props[x]) {
  4269.         keyValue = props[x].split('=');
  4270.         viewportProperties[ keyValue[0] ] = (keyValue.length > 1 ? keyValue[1] : '_');
  4271.       }
  4272.     }
  4273.     viewportUpdate();
  4274.   }
  4275. }
  4276.  
  4277. function viewportUpdate() {
  4278.   // unit tests in viewport.unit.js
  4279.  
  4280.   var initWidth = viewportProperties.width;
  4281.   var initHeight = viewportProperties.height;
  4282.   var p = ionic.Platform;
  4283.   var version = p.version();
  4284.   var DEVICE_WIDTH = 'device-width';
  4285.   var DEVICE_HEIGHT = 'device-height';
  4286.   var orientation = ionic.viewport.orientation();
  4287.  
  4288.   // Most times we're removing the height and adding the width
  4289.   // So this is the default to start with, then modify per platform/version/oreintation
  4290.   delete viewportProperties.height;
  4291.   viewportProperties.width = DEVICE_WIDTH;
  4292.  
  4293.   if (p.isIPad()) {
  4294.     // iPad
  4295.  
  4296.     if (version > 7) {
  4297.       // iPad >= 7.1
  4298.       // https://issues.apache.org/jira/browse/CB-4323
  4299.       delete viewportProperties.width;
  4300.  
  4301.     } else {
  4302.       // iPad <= 7.0
  4303.  
  4304.       if (p.isWebView()) {
  4305.         // iPad <= 7.0 WebView
  4306.  
  4307.         if (orientation == 90) {
  4308.           // iPad <= 7.0 WebView Landscape
  4309.           viewportProperties.height = '0';
  4310.  
  4311.         } else if (version == 7) {
  4312.           // iPad <= 7.0 WebView Portait
  4313.           viewportProperties.height = DEVICE_HEIGHT;
  4314.         }
  4315.       } else {
  4316.         // iPad <= 6.1 Browser
  4317.         if (version < 7) {
  4318.           viewportProperties.height = '0';
  4319.         }
  4320.       }
  4321.     }
  4322.  
  4323.   } else if (p.isIOS()) {
  4324.     // iPhone
  4325.  
  4326.     if (p.isWebView()) {
  4327.       // iPhone WebView
  4328.  
  4329.       if (version > 7) {
  4330.         // iPhone >= 7.1 WebView
  4331.         delete viewportProperties.width;
  4332.  
  4333.       } else if (version < 7) {
  4334.         // iPhone <= 6.1 WebView
  4335.         // if height was set it needs to get removed with this hack for <= 6.1
  4336.         if (initHeight) viewportProperties.height = '0';
  4337.  
  4338.       } else if (version == 7) {
  4339.         //iPhone == 7.0 WebView
  4340.         viewportProperties.height = DEVICE_HEIGHT;
  4341.       }
  4342.  
  4343.     } else {
  4344.       // iPhone Browser
  4345.  
  4346.       if (version < 7) {
  4347.         // iPhone <= 6.1 Browser
  4348.         // if height was set it needs to get removed with this hack for <= 6.1
  4349.         if (initHeight) viewportProperties.height = '0';
  4350.       }
  4351.     }
  4352.  
  4353.   }
  4354.  
  4355.   // only update the viewport tag if there was a change
  4356.   if (initWidth !== viewportProperties.width || initHeight !== viewportProperties.height) {
  4357.     viewportTagUpdate();
  4358.   }
  4359. }
  4360.  
  4361. function viewportTagUpdate() {
  4362.   var key, props = [];
  4363.   for (key in viewportProperties) {
  4364.     if (viewportProperties[key]) {
  4365.       props.push(key + (viewportProperties[key] == '_' ? '' : '=' + viewportProperties[key]));
  4366.     }
  4367.   }
  4368.  
  4369.   viewportTag.content = props.join(', ');
  4370. }
  4371.  
  4372. ionic.Platform.ready(function() {
  4373.   viewportLoadTag();
  4374.  
  4375.   window.addEventListener("orientationchange", function() {
  4376.     setTimeout(viewportUpdate, 1000);
  4377.   }, false);
  4378. });
  4379.  
  4380. (function(ionic) {
  4381. 'use strict';
  4382.   ionic.views.View = function() {
  4383.     this.initialize.apply(this, arguments);
  4384.   };
  4385.  
  4386.   ionic.views.View.inherit = ionic.inherit;
  4387.  
  4388.   ionic.extend(ionic.views.View.prototype, {
  4389.     initialize: function() {}
  4390.   });
  4391.  
  4392. })(window.ionic);
  4393.  
  4394. /*
  4395.  * Scroller
  4396.  * http://github.com/zynga/scroller
  4397.  *
  4398.  * Copyright 2011, Zynga Inc.
  4399.  * Licensed under the MIT License.
  4400.  * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt
  4401.  *
  4402.  * Based on the work of: Unify Project (unify-project.org)
  4403.  * http://unify-project.org
  4404.  * Copyright 2011, Deutsche Telekom AG
  4405.  * License: MIT + Apache (V2)
  4406.  */
  4407.  
  4408. /* jshint eqnull: true */
  4409.  
  4410. /**
  4411.  * Generic animation class with support for dropped frames both optional easing and duration.
  4412.  *
  4413.  * Optional duration is useful when the lifetime is defined by another condition than time
  4414.  * e.g. speed of an animating object, etc.
  4415.  *
  4416.  * Dropped frame logic allows to keep using the same updater logic independent from the actual
  4417.  * rendering. This eases a lot of cases where it might be pretty complex to break down a state
  4418.  * based on the pure time difference.
  4419.  */
  4420. var zyngaCore = { effect: {} };
  4421. (function(global) {
  4422.   var time = Date.now || function() {
  4423.     return +new Date();
  4424.   };
  4425.   var desiredFrames = 60;
  4426.   var millisecondsPerSecond = 1000;
  4427.   var running = {};
  4428.   var counter = 1;
  4429.  
  4430.   zyngaCore.effect.Animate = {
  4431.  
  4432.     /**
  4433.      * A requestAnimationFrame wrapper / polyfill.
  4434.      *
  4435.      * @param callback {Function} The callback to be invoked before the next repaint.
  4436.      * @param root {HTMLElement} The root element for the repaint
  4437.      */
  4438.     requestAnimationFrame: (function() {
  4439.  
  4440.       // Check for request animation Frame support
  4441.       var requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame;
  4442.       var isNative = !!requestFrame;
  4443.  
  4444.       if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) {
  4445.         isNative = false;
  4446.       }
  4447.  
  4448.       if (isNative) {
  4449.         return function(callback, root) {
  4450.           requestFrame(callback, root);
  4451.         };
  4452.       }
  4453.  
  4454.       var TARGET_FPS = 60;
  4455.       var requests = {};
  4456.       var requestCount = 0;
  4457.       var rafHandle = 1;
  4458.       var intervalHandle = null;
  4459.       var lastActive = +new Date();
  4460.  
  4461.       return function(callback) {
  4462.         var callbackHandle = rafHandle++;
  4463.  
  4464.         // Store callback
  4465.         requests[callbackHandle] = callback;
  4466.         requestCount++;
  4467.  
  4468.         // Create timeout at first request
  4469.         if (intervalHandle === null) {
  4470.  
  4471.           intervalHandle = setInterval(function() {
  4472.  
  4473.             var time = +new Date();
  4474.             var currentRequests = requests;
  4475.  
  4476.             // Reset data structure before executing callbacks
  4477.             requests = {};
  4478.             requestCount = 0;
  4479.  
  4480.             for(var key in currentRequests) {
  4481.               if (currentRequests.hasOwnProperty(key)) {
  4482.                 currentRequests[key](time);
  4483.                 lastActive = time;
  4484.               }
  4485.             }
  4486.  
  4487.             // Disable the timeout when nothing happens for a certain
  4488.             // period of time
  4489.             if (time - lastActive > 2500) {
  4490.               clearInterval(intervalHandle);
  4491.               intervalHandle = null;
  4492.             }
  4493.  
  4494.           }, 1000 / TARGET_FPS);
  4495.         }
  4496.  
  4497.         return callbackHandle;
  4498.       };
  4499.  
  4500.     })(),
  4501.  
  4502.  
  4503.     /**
  4504.      * Stops the given animation.
  4505.      *
  4506.      * @param id {Integer} Unique animation ID
  4507.      * @return {Boolean} Whether the animation was stopped (aka, was running before)
  4508.      */
  4509.     stop: function(id) {
  4510.       var cleared = running[id] != null;
  4511.       if (cleared) {
  4512.         running[id] = null;
  4513.       }
  4514.  
  4515.       return cleared;
  4516.     },
  4517.  
  4518.  
  4519.     /**
  4520.      * Whether the given animation is still running.
  4521.      *
  4522.      * @param id {Integer} Unique animation ID
  4523.      * @return {Boolean} Whether the animation is still running
  4524.      */
  4525.     isRunning: function(id) {
  4526.       return running[id] != null;
  4527.     },
  4528.  
  4529.  
  4530.     /**
  4531.      * Start the animation.
  4532.      *
  4533.      * @param stepCallback {Function} Pointer to function which is executed on every step.
  4534.      *   Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }`
  4535.      * @param verifyCallback {Function} Executed before every animation step.
  4536.      *   Signature of the method should be `function() { return continueWithAnimation; }`
  4537.      * @param completedCallback {Function}
  4538.      *   Signature of the method should be `function(droppedFrames, finishedAnimation) {}`
  4539.      * @param duration {Integer} Milliseconds to run the animation
  4540.      * @param easingMethod {Function} Pointer to easing function
  4541.      *   Signature of the method should be `function(percent) { return modifiedValue; }`
  4542.      * @param root {Element} Render root, when available. Used for internal
  4543.      *   usage of requestAnimationFrame.
  4544.      * @return {Integer} Identifier of animation. Can be used to stop it any time.
  4545.      */
  4546.     start: function(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {
  4547.  
  4548.       var start = time();
  4549.       var lastFrame = start;
  4550.       var percent = 0;
  4551.       var dropCounter = 0;
  4552.       var id = counter++;
  4553.  
  4554.       if (!root) {
  4555.         root = document.body;
  4556.       }
  4557.  
  4558.       // Compacting running db automatically every few new animations
  4559.       if (id % 20 === 0) {
  4560.         var newRunning = {};
  4561.         for (var usedId in running) {
  4562.           newRunning[usedId] = true;
  4563.         }
  4564.         running = newRunning;
  4565.       }
  4566.  
  4567.       // This is the internal step method which is called every few milliseconds
  4568.       var step = function(virtual) {
  4569.  
  4570.         // Normalize virtual value
  4571.         var render = virtual !== true;
  4572.  
  4573.         // Get current time
  4574.         var now = time();
  4575.  
  4576.         // Verification is executed before next animation step
  4577.         if (!running[id] || (verifyCallback && !verifyCallback(id))) {
  4578.  
  4579.           running[id] = null;
  4580.           completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false);
  4581.           return;
  4582.  
  4583.         }
  4584.  
  4585.         // For the current rendering to apply let's update omitted steps in memory.
  4586.         // This is important to bring internal state variables up-to-date with progress in time.
  4587.         if (render) {
  4588.  
  4589.           var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1;
  4590.           for (var j = 0; j < Math.min(droppedFrames, 4); j++) {
  4591.             step(true);
  4592.             dropCounter++;
  4593.           }
  4594.  
  4595.         }
  4596.  
  4597.         // Compute percent value
  4598.         if (duration) {
  4599.           percent = (now - start) / duration;
  4600.           if (percent > 1) {
  4601.             percent = 1;
  4602.           }
  4603.         }
  4604.  
  4605.         // Execute step callback, then...
  4606.         var value = easingMethod ? easingMethod(percent) : percent;
  4607.         if ((stepCallback(value, now, render) === false || percent === 1) && render) {
  4608.           running[id] = null;
  4609.           completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration == null);
  4610.         } else if (render) {
  4611.           lastFrame = now;
  4612.           zyngaCore.effect.Animate.requestAnimationFrame(step, root);
  4613.         }
  4614.       };
  4615.  
  4616.       // Mark as running
  4617.       running[id] = true;
  4618.  
  4619.       // Init first step
  4620.       zyngaCore.effect.Animate.requestAnimationFrame(step, root);
  4621.  
  4622.       // Return unique animation ID
  4623.       return id;
  4624.     }
  4625.   };
  4626. })(this);
  4627.  
  4628. /*
  4629.  * Scroller
  4630.  * http://github.com/zynga/scroller
  4631.  *
  4632.  * Copyright 2011, Zynga Inc.
  4633.  * Licensed under the MIT License.
  4634.  * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt
  4635.  *
  4636.  * Based on the work of: Unify Project (unify-project.org)
  4637.  * http://unify-project.org
  4638.  * Copyright 2011, Deutsche Telekom AG
  4639.  * License: MIT + Apache (V2)
  4640.  */
  4641.  
  4642. (function(ionic) {
  4643.   var NOOP = function(){};
  4644.  
  4645.   // Easing Equations (c) 2003 Robert Penner, all rights reserved.
  4646.   // Open source under the BSD License.
  4647.  
  4648.   /**
  4649.    * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)
  4650.   **/
  4651.   var easeOutCubic = function(pos) {
  4652.     return (Math.pow((pos - 1), 3) + 1);
  4653.   };
  4654.  
  4655.   /**
  4656.    * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)
  4657.   **/
  4658.   var easeInOutCubic = function(pos) {
  4659.     if ((pos /= 0.5) < 1) {
  4660.       return 0.5 * Math.pow(pos, 3);
  4661.     }
  4662.  
  4663.     return 0.5 * (Math.pow((pos - 2), 3) + 2);
  4664.   };
  4665.  
  4666.  
  4667. /**
  4668.  * ionic.views.Scroll
  4669.  * A powerful scroll view with support for bouncing, pull to refresh, and paging.
  4670.  * @param   {Object}        options options for the scroll view
  4671.  * @class A scroll view system
  4672.  * @memberof ionic.views
  4673.  */
  4674. ionic.views.Scroll = ionic.views.View.inherit({
  4675.   initialize: function(options) {
  4676.     var self = this;
  4677.  
  4678.     self.__container = options.el;
  4679.     self.__content = options.el.firstElementChild;
  4680.  
  4681.     //Remove any scrollTop attached to these elements; they are virtual scroll now
  4682.     //This also stops on-load-scroll-to-window.location.hash that the browser does
  4683.     setTimeout(function() {
  4684.       if (self.__container && self.__content) {
  4685.         self.__container.scrollTop = 0;
  4686.         self.__content.scrollTop = 0;
  4687.       }
  4688.     });
  4689.  
  4690.     self.options = {
  4691.  
  4692.       /** Disable scrolling on x-axis by default */
  4693.       scrollingX: false,
  4694.       scrollbarX: true,
  4695.  
  4696.       /** Enable scrolling on y-axis */
  4697.       scrollingY: true,
  4698.       scrollbarY: true,
  4699.  
  4700.       startX: 0,
  4701.       startY: 0,
  4702.  
  4703.       /** The amount to dampen mousewheel events */
  4704.       wheelDampen: 6,
  4705.  
  4706.       /** The minimum size the scrollbars scale to while scrolling */
  4707.       minScrollbarSizeX: 5,
  4708.       minScrollbarSizeY: 5,
  4709.  
  4710.       /** Scrollbar fading after scrolling */
  4711.       scrollbarsFade: true,
  4712.       scrollbarFadeDelay: 300,
  4713.       /** The initial fade delay when the pane is resized or initialized */
  4714.       scrollbarResizeFadeDelay: 1000,
  4715.  
  4716.       /** Enable animations for deceleration, snap back, zooming and scrolling */
  4717.       animating: true,
  4718.  
  4719.       /** duration for animations triggered by scrollTo/zoomTo */
  4720.       animationDuration: 250,
  4721.  
  4722.       /** The velocity required to make the scroll view "slide" after touchend */
  4723.       decelVelocityThreshold: 4,
  4724.  
  4725.       /** The velocity required to make the scroll view "slide" after touchend when using paging */
  4726.       decelVelocityThresholdPaging: 4,
  4727.  
  4728.       /** Enable bouncing (content can be slowly moved outside and jumps back after releasing) */
  4729.       bouncing: true,
  4730.  
  4731.       /** Enable locking to the main axis if user moves only slightly on one of them at start */
  4732.       locking: true,
  4733.  
  4734.       /** Enable pagination mode (switching between full page content panes) */
  4735.       paging: false,
  4736.  
  4737.       /** Enable snapping of content to a configured pixel grid */
  4738.       snapping: false,
  4739.  
  4740.       /** Enable zooming of content via API, fingers and mouse wheel */
  4741.       zooming: false,
  4742.  
  4743.       /** Minimum zoom level */
  4744.       minZoom: 0.5,
  4745.  
  4746.       /** Maximum zoom level */
  4747.       maxZoom: 3,
  4748.  
  4749.       /** Multiply or decrease scrolling speed **/
  4750.       speedMultiplier: 1,
  4751.  
  4752.       deceleration: 0.97,
  4753.  
  4754.       /** Whether to prevent default on a scroll operation to capture drag events **/
  4755.       preventDefault: false,
  4756.  
  4757.       /** Callback that is fired on the later of touch end or deceleration end,
  4758.         provided that another scrolling action has not begun. Used to know
  4759.         when to fade out a scrollbar. */
  4760.       scrollingComplete: NOOP,
  4761.  
  4762.       /** This configures the amount of change applied to deceleration when reaching boundaries  **/
  4763.       penetrationDeceleration: 0.03,
  4764.  
  4765.       /** This configures the amount of change applied to acceleration when reaching boundaries  **/
  4766.       penetrationAcceleration: 0.08,
  4767.  
  4768.       // The ms interval for triggering scroll events
  4769.       scrollEventInterval: 10,
  4770.  
  4771.       freeze: false,
  4772.  
  4773.       getContentWidth: function() {
  4774.         return Math.max(self.__content.scrollWidth, self.__content.offsetWidth);
  4775.       },
  4776.       getContentHeight: function() {
  4777.         return Math.max(self.__content.scrollHeight, self.__content.offsetHeight + (self.__content.offsetTop * 2));
  4778.       }
  4779.     };
  4780.  
  4781.     for (var key in options) {
  4782.       self.options[key] = options[key];
  4783.     }
  4784.  
  4785.     self.hintResize = ionic.debounce(function() {
  4786.       self.resize();
  4787.     }, 1000, true);
  4788.  
  4789.     self.onScroll = function() {
  4790.  
  4791.       if (!ionic.scroll.isScrolling) {
  4792.         setTimeout(self.setScrollStart, 50);
  4793.       } else {
  4794.         clearTimeout(self.scrollTimer);
  4795.         self.scrollTimer = setTimeout(self.setScrollStop, 80);
  4796.       }
  4797.  
  4798.     };
  4799.  
  4800.     self.freeze = function(shouldFreeze) {
  4801.       if (arguments.length) {
  4802.         self.options.freeze = shouldFreeze;
  4803.       }
  4804.       return self.options.freeze;
  4805.     };
  4806.  
  4807.     self.setScrollStart = function() {
  4808.       ionic.scroll.isScrolling = Math.abs(ionic.scroll.lastTop - self.__scrollTop) > 1;
  4809.       clearTimeout(self.scrollTimer);
  4810.       self.scrollTimer = setTimeout(self.setScrollStop, 80);
  4811.     };
  4812.  
  4813.     self.setScrollStop = function() {
  4814.       ionic.scroll.isScrolling = false;
  4815.       ionic.scroll.lastTop = self.__scrollTop;
  4816.     };
  4817.  
  4818.     self.triggerScrollEvent = ionic.throttle(function() {
  4819.       self.onScroll();
  4820.       ionic.trigger('scroll', {
  4821.         scrollTop: self.__scrollTop,
  4822.         scrollLeft: self.__scrollLeft,
  4823.         target: self.__container
  4824.       });
  4825.     }, self.options.scrollEventInterval);
  4826.  
  4827.     self.triggerScrollEndEvent = function() {
  4828.       ionic.trigger('scrollend', {
  4829.         scrollTop: self.__scrollTop,
  4830.         scrollLeft: self.__scrollLeft,
  4831.         target: self.__container
  4832.       });
  4833.     };
  4834.  
  4835.     self.__scrollLeft = self.options.startX;
  4836.     self.__scrollTop = self.options.startY;
  4837.  
  4838.     // Get the render update function, initialize event handlers,
  4839.     // and calculate the size of the scroll container
  4840.     self.__callback = self.getRenderFn();
  4841.     self.__initEventHandlers();
  4842.     self.__createScrollbars();
  4843.  
  4844.   },
  4845.  
  4846.   run: function() {
  4847.     this.resize();
  4848.  
  4849.     // Fade them out
  4850.     this.__fadeScrollbars('out', this.options.scrollbarResizeFadeDelay);
  4851.   },
  4852.  
  4853.  
  4854.  
  4855.   /*
  4856.   ---------------------------------------------------------------------------
  4857.     INTERNAL FIELDS :: STATUS
  4858.   ---------------------------------------------------------------------------
  4859.   */
  4860.  
  4861.   /** Whether only a single finger is used in touch handling */
  4862.   __isSingleTouch: false,
  4863.  
  4864.   /** Whether a touch event sequence is in progress */
  4865.   __isTracking: false,
  4866.  
  4867.   /** Whether a deceleration animation went to completion. */
  4868.   __didDecelerationComplete: false,
  4869.  
  4870.   /**
  4871.    * Whether a gesture zoom/rotate event is in progress. Activates when
  4872.    * a gesturestart event happens. This has higher priority than dragging.
  4873.    */
  4874.   __isGesturing: false,
  4875.  
  4876.   /**
  4877.    * Whether the user has moved by such a distance that we have enabled
  4878.    * dragging mode. Hint: It's only enabled after some pixels of movement to
  4879.    * not interrupt with clicks etc.
  4880.    */
  4881.   __isDragging: false,
  4882.  
  4883.   /**
  4884.    * Not touching and dragging anymore, and smoothly animating the
  4885.    * touch sequence using deceleration.
  4886.    */
  4887.   __isDecelerating: false,
  4888.  
  4889.   /**
  4890.    * Smoothly animating the currently configured change
  4891.    */
  4892.   __isAnimating: false,
  4893.  
  4894.  
  4895.  
  4896.   /*
  4897.   ---------------------------------------------------------------------------
  4898.     INTERNAL FIELDS :: DIMENSIONS
  4899.   ---------------------------------------------------------------------------
  4900.   */
  4901.  
  4902.   /** Available outer left position (from document perspective) */
  4903.   __clientLeft: 0,
  4904.  
  4905.   /** Available outer top position (from document perspective) */
  4906.   __clientTop: 0,
  4907.  
  4908.   /** Available outer width */
  4909.   __clientWidth: 0,
  4910.  
  4911.   /** Available outer height */
  4912.   __clientHeight: 0,
  4913.  
  4914.   /** Outer width of content */
  4915.   __contentWidth: 0,
  4916.  
  4917.   /** Outer height of content */
  4918.   __contentHeight: 0,
  4919.  
  4920.   /** Snapping width for content */
  4921.   __snapWidth: 100,
  4922.  
  4923.   /** Snapping height for content */
  4924.   __snapHeight: 100,
  4925.  
  4926.   /** Height to assign to refresh area */
  4927.   __refreshHeight: null,
  4928.  
  4929.   /** Whether the refresh process is enabled when the event is released now */
  4930.   __refreshActive: false,
  4931.  
  4932.   /** Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release */
  4933.   __refreshActivate: null,
  4934.  
  4935.   /** Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled */
  4936.   __refreshDeactivate: null,
  4937.  
  4938.   /** Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */
  4939.   __refreshStart: null,
  4940.  
  4941.   /** Zoom level */
  4942.   __zoomLevel: 1,
  4943.  
  4944.   /** Scroll position on x-axis */
  4945.   __scrollLeft: 0,
  4946.  
  4947.   /** Scroll position on y-axis */
  4948.   __scrollTop: 0,
  4949.  
  4950.   /** Maximum allowed scroll position on x-axis */
  4951.   __maxScrollLeft: 0,
  4952.  
  4953.   /** Maximum allowed scroll position on y-axis */
  4954.   __maxScrollTop: 0,
  4955.  
  4956.   /* Scheduled left position (final position when animating) */
  4957.   __scheduledLeft: 0,
  4958.  
  4959.   /* Scheduled top position (final position when animating) */
  4960.   __scheduledTop: 0,
  4961.  
  4962.   /* Scheduled zoom level (final scale when animating) */
  4963.   __scheduledZoom: 0,
  4964.  
  4965.  
  4966.  
  4967.   /*
  4968.   ---------------------------------------------------------------------------
  4969.     INTERNAL FIELDS :: LAST POSITIONS
  4970.   ---------------------------------------------------------------------------
  4971.   */
  4972.  
  4973.   /** Left position of finger at start */
  4974.   __lastTouchLeft: null,
  4975.  
  4976.   /** Top position of finger at start */
  4977.   __lastTouchTop: null,
  4978.  
  4979.   /** Timestamp of last move of finger. Used to limit tracking range for deceleration speed. */
  4980.   __lastTouchMove: null,
  4981.  
  4982.   /** List of positions, uses three indexes for each state: left, top, timestamp */
  4983.   __positions: null,
  4984.  
  4985.  
  4986.  
  4987.   /*
  4988.   ---------------------------------------------------------------------------
  4989.     INTERNAL FIELDS :: DECELERATION SUPPORT
  4990.   ---------------------------------------------------------------------------
  4991.   */
  4992.  
  4993.   /** Minimum left scroll position during deceleration */
  4994.   __minDecelerationScrollLeft: null,
  4995.  
  4996.   /** Minimum top scroll position during deceleration */
  4997.   __minDecelerationScrollTop: null,
  4998.  
  4999.   /** Maximum left scroll position during deceleration */
  5000.   __maxDecelerationScrollLeft: null,
  5001.  
  5002.   /** Maximum top scroll position during deceleration */
  5003.   __maxDecelerationScrollTop: null,
  5004.  
  5005.   /** Current factor to modify horizontal scroll position with on every step */
  5006.   __decelerationVelocityX: null,
  5007.  
  5008.   /** Current factor to modify vertical scroll position with on every step */
  5009.   __decelerationVelocityY: null,
  5010.  
  5011.  
  5012.   /** the browser-specific property to use for transforms */
  5013.   __transformProperty: null,
  5014.   __perspectiveProperty: null,
  5015.  
  5016.   /** scrollbar indicators */
  5017.   __indicatorX: null,
  5018.   __indicatorY: null,
  5019.  
  5020.   /** Timeout for scrollbar fading */
  5021.   __scrollbarFadeTimeout: null,
  5022.  
  5023.   /** whether we've tried to wait for size already */
  5024.   __didWaitForSize: null,
  5025.   __sizerTimeout: null,
  5026.  
  5027.   __initEventHandlers: function() {
  5028.     var self = this;
  5029.  
  5030.     // Event Handler
  5031.     var container = self.__container;
  5032.  
  5033.     // save height when scroll view is shrunk so we don't need to reflow
  5034.     var scrollViewOffsetHeight;
  5035.  
  5036.     /**
  5037.      * Shrink the scroll view when the keyboard is up if necessary and if the
  5038.      * focused input is below the bottom of the shrunk scroll view, scroll it
  5039.      * into view.
  5040.      */
  5041.     self.scrollChildIntoView = function(e) {
  5042.       //console.log("scrollChildIntoView at: " + Date.now());
  5043.  
  5044.       // D
  5045.       var scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
  5046.       // D - A
  5047.       scrollViewOffsetHeight = container.offsetHeight;
  5048.       var alreadyShrunk = self.isShrunkForKeyboard;
  5049.  
  5050.       var isModal = container.parentNode.classList.contains('modal');
  5051.       // 680px is when the media query for 60% modal width kicks in
  5052.       var isInsetModal = isModal && window.innerWidth >= 680;
  5053.  
  5054.      /*
  5055.       *  _______
  5056.       * |---A---| <- top of scroll view
  5057.       * |       |
  5058.       * |---B---| <- keyboard
  5059.       * |   C   | <- input
  5060.       * |---D---| <- initial bottom of scroll view
  5061.       * |___E___| <- bottom of viewport
  5062.       *
  5063.       *  All commented calculations relative to the top of the viewport (ie E
  5064.       *  is the viewport height, not 0)
  5065.       */
  5066.       if (!alreadyShrunk) {
  5067.         // shrink scrollview so we can actually scroll if the input is hidden
  5068.         // if it isn't shrink so we can scroll to inputs under the keyboard
  5069.         // inset modals won't shrink on Android on their own when the keyboard appears
  5070.         if ( ionic.Platform.isIOS() || ionic.Platform.isFullScreen || isInsetModal ) {
  5071.           // if there are things below the scroll view account for them and
  5072.           // subtract them from the keyboard height when resizing
  5073.           // E - D                         E                         D
  5074.           var scrollBottomOffsetToBottom = e.detail.viewportHeight - scrollBottomOffsetToTop;
  5075.  
  5076.           // 0 or D - B if D > B           E - B                     E - D
  5077.           var keyboardOffset = Math.max(0, e.detail.keyboardHeight - scrollBottomOffsetToBottom);
  5078.  
  5079.           ionic.requestAnimationFrame(function(){
  5080.             // D - A or B - A if D > B       D - A             max(0, D - B)
  5081.             scrollViewOffsetHeight = scrollViewOffsetHeight - keyboardOffset;
  5082.             container.style.height = scrollViewOffsetHeight + "px";
  5083.             container.style.overflow = "visible";
  5084.  
  5085.             //update scroll view
  5086.             self.resize();
  5087.           });
  5088.         }
  5089.  
  5090.         self.isShrunkForKeyboard = true;
  5091.       }
  5092.  
  5093.       /*
  5094.        *  _______
  5095.        * |---A---| <- top of scroll view
  5096.        * |   *   | <- where we want to scroll to
  5097.        * |--B-D--| <- keyboard, bottom of scroll view
  5098.        * |   C   | <- input
  5099.        * |       |
  5100.        * |___E___| <- bottom of viewport
  5101.        *
  5102.        *  All commented calculations relative to the top of the viewport (ie E
  5103.        *  is the viewport height, not 0)
  5104.        */
  5105.       // if the element is positioned under the keyboard scroll it into view
  5106.       if (e.detail.isElementUnderKeyboard) {
  5107.  
  5108.         ionic.requestAnimationFrame(function(){
  5109.           container.scrollTop = 0;
  5110.           // update D if we shrunk
  5111.           if (self.isShrunkForKeyboard && !alreadyShrunk) {
  5112.             scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
  5113.           }
  5114.  
  5115.           // middle of the scrollview, this is where we want to scroll to
  5116.           // (D - A) / 2
  5117.           var scrollMidpointOffset = scrollViewOffsetHeight * 0.5;
  5118.           //console.log("container.offsetHeight: " + scrollViewOffsetHeight);
  5119.  
  5120.           // middle of the input we want to scroll into view
  5121.           // C
  5122.           var inputMidpoint = ((e.detail.elementBottom + e.detail.elementTop) / 2);
  5123.  
  5124.           // distance from middle of input to the bottom of the scroll view
  5125.           // C - D                                C               D
  5126.           var inputMidpointOffsetToScrollBottom = inputMidpoint - scrollBottomOffsetToTop;
  5127.  
  5128.           //C - D + (D - A)/2          C - D                     (D - A)/ 2
  5129.           var scrollTop = inputMidpointOffsetToScrollBottom + scrollMidpointOffset;
  5130.  
  5131.           if ( scrollTop > 0) {
  5132.             if (ionic.Platform.isIOS()) ionic.tap.cloneFocusedInput(container, self);
  5133.             self.scrollBy(0, scrollTop, true);
  5134.             self.onScroll();
  5135.           }
  5136.         });
  5137.       }
  5138.  
  5139.       // Only the first scrollView parent of the element that broadcasted this event
  5140.       // (the active element that needs to be shown) should receive this event
  5141.       e.stopPropagation();
  5142.     };
  5143.  
  5144.     self.resetScrollView = function() {
  5145.       //return scrollview to original height once keyboard has hidden
  5146.       if ( self.isShrunkForKeyboard ) {
  5147.         self.isShrunkForKeyboard = false;
  5148.         container.style.height = "";
  5149.         container.style.overflow = "";
  5150.       }
  5151.       self.resize();
  5152.     };
  5153.  
  5154.     //Broadcasted when keyboard is shown on some platforms.
  5155.     //See js/utils/keyboard.js
  5156.     container.addEventListener('scrollChildIntoView', self.scrollChildIntoView);
  5157.  
  5158.     // Listen on document because container may not have had the last
  5159.     // keyboardActiveElement, for example after closing a modal with a focused
  5160.     // input and returning to a previously resized scroll view in an ion-content.
  5161.     // Since we can only resize scroll views that are currently visible, just resize
  5162.     // the current scroll view when the keyboard is closed.
  5163.     document.addEventListener('resetScrollView', self.resetScrollView);
  5164.  
  5165.     function getEventTouches(e) {
  5166.       return e.touches && e.touches.length ? e.touches : [{
  5167.         pageX: e.pageX,
  5168.         pageY: e.pageY
  5169.       }];
  5170.     }
  5171.  
  5172.     self.touchStart = function(e) {
  5173.       self.startCoordinates = ionic.tap.pointerCoord(e);
  5174.  
  5175.       if ( ionic.tap.ignoreScrollStart(e) ) {
  5176.         return;
  5177.       }
  5178.  
  5179.       self.__isDown = true;
  5180.  
  5181.       if ( ionic.tap.containsOrIsTextInput(e.target) || e.target.tagName === 'SELECT' ) {
  5182.         // do not start if the target is a text input
  5183.         // if there is a touchmove on this input, then we can start the scroll
  5184.         self.__hasStarted = false;
  5185.         return;
  5186.       }
  5187.  
  5188.       self.__isSelectable = true;
  5189.       self.__enableScrollY = true;
  5190.       self.__hasStarted = true;
  5191.       self.doTouchStart(getEventTouches(e), e.timeStamp);
  5192.       e.preventDefault();
  5193.     };
  5194.  
  5195.     self.touchMove = function(e) {
  5196.       if (self.options.freeze || !self.__isDown ||
  5197.         (!self.__isDown && e.defaultPrevented) ||
  5198.         (e.target.tagName === 'TEXTAREA' && e.target.parentElement.querySelector(':focus')) ) {
  5199.         return;
  5200.       }
  5201.  
  5202.       if ( !self.__hasStarted && ( ionic.tap.containsOrIsTextInput(e.target) || e.target.tagName === 'SELECT' ) ) {
  5203.         // the target is a text input and scroll has started
  5204.         // since the text input doesn't start on touchStart, do it here
  5205.         self.__hasStarted = true;
  5206.         self.doTouchStart(getEventTouches(e), e.timeStamp);
  5207.         e.preventDefault();
  5208.         return;
  5209.       }
  5210.  
  5211.       if (self.startCoordinates) {
  5212.         // we have start coordinates, so get this touch move's current coordinates
  5213.         var currentCoordinates = ionic.tap.pointerCoord(e);
  5214.  
  5215.         if ( self.__isSelectable &&
  5216.             ionic.tap.isTextInput(e.target) &&
  5217.             Math.abs(self.startCoordinates.x - currentCoordinates.x) > 20 ) {
  5218.           // user slid the text input's caret on its x axis, disable any future y scrolling
  5219.           self.__enableScrollY = false;
  5220.           self.__isSelectable = true;
  5221.         }
  5222.  
  5223.         if ( self.__enableScrollY && Math.abs(self.startCoordinates.y - currentCoordinates.y) > 10 ) {
  5224.           // user scrolled the entire view on the y axis
  5225.           // disabled being able to select text on an input
  5226.           // hide the input which has focus, and show a cloned one that doesn't have focus
  5227.           self.__isSelectable = false;
  5228.           ionic.tap.cloneFocusedInput(container, self);
  5229.         }
  5230.       }
  5231.  
  5232.       self.doTouchMove(getEventTouches(e), e.timeStamp, e.scale);
  5233.       self.__isDown = true;
  5234.     };
  5235.  
  5236.     self.touchMoveBubble = function(e) {
  5237.       if(self.__isDown && self.options.preventDefault) {
  5238.         e.preventDefault();
  5239.       }
  5240.     };
  5241.  
  5242.     self.touchEnd = function(e) {
  5243.       if (!self.__isDown) return;
  5244.  
  5245.       self.doTouchEnd(e, e.timeStamp);
  5246.       self.__isDown = false;
  5247.       self.__hasStarted = false;
  5248.       self.__isSelectable = true;
  5249.       self.__enableScrollY = true;
  5250.  
  5251.       if ( !self.__isDragging && !self.__isDecelerating && !self.__isAnimating ) {
  5252.         ionic.tap.removeClonedInputs(container, self);
  5253.       }
  5254.     };
  5255.  
  5256.     self.mouseWheel = ionic.animationFrameThrottle(function(e) {
  5257.       var scrollParent = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'ionic-scroll');
  5258.       //if (!self.options.freeze && scrollParent === self.__container) {  Alan Kaiser 5/13/15.... scroll no matter what
  5259.       if (!self.options.freeze)  {
  5260.         self.hintResize();
  5261.         self.scrollBy(
  5262.           (e.wheelDeltaX || e.deltaX || 0) / self.options.wheelDampen,
  5263.           (-e.wheelDeltaY || e.deltaY || 0) / self.options.wheelDampen
  5264.         );
  5265.  
  5266.         self.__fadeScrollbars('in');
  5267.         clearTimeout(self.__wheelHideBarTimeout);
  5268.         self.__wheelHideBarTimeout = setTimeout(function() {
  5269.           self.__fadeScrollbars('out');
  5270.         }, 100);
  5271.       }
  5272.     });
  5273.  
  5274.     if ('ontouchstart' in window) {
  5275.       // Touch Events
  5276.       container.addEventListener("touchstart", self.touchStart, false);
  5277.       if(self.options.preventDefault) container.addEventListener("touchmove", self.touchMoveBubble, false);
  5278.       document.addEventListener("touchmove", self.touchMove, false);
  5279.       document.addEventListener("touchend", self.touchEnd, false);
  5280.       document.addEventListener("touchcancel", self.touchEnd, false);
  5281.  
  5282.     } else if (window.navigator.pointerEnabled) {
  5283.       // Pointer Events
  5284.       container.addEventListener("pointerdown", self.touchStart, false);
  5285.       if(self.options.preventDefault) container.addEventListener("pointermove", self.touchMoveBubble, false);
  5286.       document.addEventListener("pointermove", self.touchMove, false);
  5287.       document.addEventListener("pointerup", self.touchEnd, false);
  5288.       document.addEventListener("pointercancel", self.touchEnd, false);
  5289.       document.addEventListener("wheel", self.mouseWheel, false);
  5290.  
  5291.     } else if (window.navigator.msPointerEnabled) {
  5292.       // IE10, WP8 (Pointer Events)
  5293.       container.addEventListener("MSPointerDown", self.touchStart, false);
  5294.       if(self.options.preventDefault) container.addEventListener("MSPointerMove", self.touchMoveBubble, false);
  5295.       document.addEventListener("MSPointerMove", self.touchMove, false);
  5296.       document.addEventListener("MSPointerUp", self.touchEnd, false);
  5297.       document.addEventListener("MSPointerCancel", self.touchEnd, false);
  5298.       document.addEventListener("wheel", self.mouseWheel, false);
  5299.  
  5300.     }
  5301.     // *CHANGE* chrome scrolling fix. removed 'else' statement
  5302.     // Mouse Events
  5303.     var mousedown = false;
  5304.  
  5305.     self.mouseDown = function(e) {
  5306.       if ( ionic.tap.ignoreScrollStart(e) || e.target.tagName === 'SELECT' ) {
  5307.         return;
  5308.       }
  5309.       self.doTouchStart(getEventTouches(e), e.timeStamp);
  5310.  
  5311.       if ( !ionic.tap.isTextInput(e.target) ) {
  5312.         e.preventDefault();
  5313.       }
  5314.       mousedown = true;
  5315.     };
  5316.  
  5317.     self.mouseMove = function(e) {
  5318.       if (self.options.freeze || !mousedown || (!mousedown && e.defaultPrevented)) {
  5319.         return;
  5320.       }
  5321.  
  5322.       self.doTouchMove(getEventTouches(e), e.timeStamp);
  5323.  
  5324.       mousedown = true;
  5325.     };
  5326.  
  5327.     self.mouseMoveBubble = function(e) {
  5328.       if (mousedown && self.options.preventDefault) {
  5329.         e.preventDefault();
  5330.       }
  5331.     };
  5332.  
  5333.     self.mouseUp = function(e) {
  5334.       if (!mousedown) {
  5335.         return;
  5336.       }
  5337.  
  5338.       self.doTouchEnd(e, e.timeStamp);
  5339.  
  5340.       mousedown = false;
  5341.     };
  5342.  
  5343.     container.addEventListener("mousedown", self.mouseDown, false);
  5344.     if(self.options.preventDefault) container.addEventListener("mousemove", self.mouseMoveBubble, false);
  5345.     document.addEventListener("mousemove", self.mouseMove, false);
  5346.     document.addEventListener("mouseup", self.mouseUp, false);
  5347.     document.addEventListener('mousewheel', self.mouseWheel, false);
  5348.     document.addEventListener('wheel', self.mouseWheel, false);
  5349.  
  5350.   },
  5351.  
  5352.   __cleanup: function() {
  5353.     var self = this;
  5354.     var container = self.__container;
  5355.  
  5356.     container.removeEventListener('touchstart', self.touchStart);
  5357.     container.removeEventListener('touchmove', self.touchMoveBubble);
  5358.     document.removeEventListener('touchmove', self.touchMove);
  5359.     document.removeEventListener('touchend', self.touchEnd);
  5360.     document.removeEventListener('touchcancel', self.touchEnd);
  5361.  
  5362.     container.removeEventListener("pointerdown", self.touchStart);
  5363.     container.removeEventListener("pointermove", self.touchMoveBubble);
  5364.     document.removeEventListener("pointermove", self.touchMove);
  5365.     document.removeEventListener("pointerup", self.touchEnd);
  5366.     document.removeEventListener("pointercancel", self.touchEnd);
  5367.  
  5368.     container.removeEventListener("MSPointerDown", self.touchStart);
  5369.     container.removeEventListener("MSPointerMove", self.touchMoveBubble);
  5370.     document.removeEventListener("MSPointerMove", self.touchMove);
  5371.     document.removeEventListener("MSPointerUp", self.touchEnd);
  5372.     document.removeEventListener("MSPointerCancel", self.touchEnd);
  5373.  
  5374.     container.removeEventListener("mousedown", self.mouseDown);
  5375.     container.removeEventListener("mousemove", self.mouseMoveBubble);
  5376.     document.removeEventListener("mousemove", self.mouseMove);
  5377.     document.removeEventListener("mouseup", self.mouseUp);
  5378.     document.removeEventListener('mousewheel', self.mouseWheel);
  5379.     document.removeEventListener('wheel', self.mouseWheel);
  5380.  
  5381.     container.removeEventListener('scrollChildIntoView', self.scrollChildIntoView);
  5382.     document.removeEventListener('resetScrollView', self.resetScrollView);
  5383.  
  5384.     ionic.tap.removeClonedInputs(container, self);
  5385.  
  5386.     delete self.__container;
  5387.     delete self.__content;
  5388.     delete self.__indicatorX;
  5389.     delete self.__indicatorY;
  5390.     delete self.options.el;
  5391.  
  5392.     self.__callback = self.scrollChildIntoView = self.resetScrollView = NOOP;
  5393.  
  5394.     self.mouseMove = self.mouseDown = self.mouseUp = self.mouseWheel =
  5395.       self.touchStart = self.touchMove = self.touchEnd = self.touchCancel = NOOP;
  5396.  
  5397.     self.resize = self.scrollTo = self.zoomTo =
  5398.       self.__scrollingComplete = NOOP;
  5399.     container = null;
  5400.   },
  5401.  
  5402.   /** Create a scroll bar div with the given direction **/
  5403.   __createScrollbar: function(direction) {
  5404.     var bar = document.createElement('div'),
  5405.       indicator = document.createElement('div');
  5406.  
  5407.     indicator.className = 'scroll-bar-indicator scroll-bar-fade-out';
  5408.  
  5409.     if (direction == 'h') {
  5410.       bar.className = 'scroll-bar scroll-bar-h';
  5411.     } else {
  5412.       bar.className = 'scroll-bar scroll-bar-v';
  5413.     }
  5414.  
  5415.     bar.appendChild(indicator);
  5416.     return bar;
  5417.   },
  5418.  
  5419.   __createScrollbars: function() {
  5420.     var self = this;
  5421.     var indicatorX, indicatorY;
  5422.  
  5423.     if (self.options.scrollingX) {
  5424.       indicatorX = {
  5425.         el: self.__createScrollbar('h'),
  5426.         sizeRatio: 1
  5427.       };
  5428.       indicatorX.indicator = indicatorX.el.children[0];
  5429.  
  5430.       if (self.options.scrollbarX) {
  5431.         self.__container.appendChild(indicatorX.el);
  5432.       }
  5433.       self.__indicatorX = indicatorX;
  5434.     }
  5435.  
  5436.     if (self.options.scrollingY) {
  5437.       indicatorY = {
  5438.         el: self.__createScrollbar('v'),
  5439.         sizeRatio: 1
  5440.       };
  5441.       indicatorY.indicator = indicatorY.el.children[0];
  5442.  
  5443.       if (self.options.scrollbarY) {
  5444.         self.__container.appendChild(indicatorY.el);
  5445.       }
  5446.       self.__indicatorY = indicatorY;
  5447.     }
  5448.   },
  5449.  
  5450.   __resizeScrollbars: function() {
  5451.     var self = this;
  5452.  
  5453.     // Update horiz bar
  5454.     if (self.__indicatorX) {
  5455.       var width = Math.max(Math.round(self.__clientWidth * self.__clientWidth / (self.__contentWidth)), 20);
  5456.       if (width > self.__contentWidth) {
  5457.         width = 0;
  5458.       }
  5459.       if (width !== self.__indicatorX.size) {
  5460.         ionic.requestAnimationFrame(function(){
  5461.           self.__indicatorX.indicator.style.width = width + 'px';
  5462.         });
  5463.       }
  5464.       self.__indicatorX.size = width;
  5465.       self.__indicatorX.minScale = self.options.minScrollbarSizeX / width;
  5466.       self.__indicatorX.maxPos = self.__clientWidth - width;
  5467.       self.__indicatorX.sizeRatio = self.__maxScrollLeft ? self.__indicatorX.maxPos / self.__maxScrollLeft : 1;
  5468.     }
  5469.  
  5470.     // Update vert bar
  5471.     if (self.__indicatorY) {
  5472.       var height = Math.max(Math.round(self.__clientHeight * self.__clientHeight / (self.__contentHeight)), 20);
  5473.       if (height > self.__contentHeight) {
  5474.         height = 0;
  5475.       }
  5476.       if (height !== self.__indicatorY.size) {
  5477.         ionic.requestAnimationFrame(function(){
  5478.           self.__indicatorY && (self.__indicatorY.indicator.style.height = height + 'px');
  5479.         });
  5480.       }
  5481.       self.__indicatorY.size = height;
  5482.       self.__indicatorY.minScale = self.options.minScrollbarSizeY / height;
  5483.       self.__indicatorY.maxPos = self.__clientHeight - height;
  5484.       self.__indicatorY.sizeRatio = self.__maxScrollTop ? self.__indicatorY.maxPos / self.__maxScrollTop : 1;
  5485.     }
  5486.   },
  5487.  
  5488.   /**
  5489.    * Move and scale the scrollbars as the page scrolls.
  5490.    */
  5491.   __repositionScrollbars: function() {
  5492.     var self = this,
  5493.         heightScale, widthScale,
  5494.         widthDiff, heightDiff,
  5495.         x, y,
  5496.         xstop = 0, ystop = 0;
  5497.  
  5498.     if (self.__indicatorX) {
  5499.       // Handle the X scrollbar
  5500.  
  5501.       // Don't go all the way to the right if we have a vertical scrollbar as well
  5502.       if (self.__indicatorY) xstop = 10;
  5503.  
  5504.       x = Math.round(self.__indicatorX.sizeRatio * self.__scrollLeft) || 0;
  5505.  
  5506.       // The the difference between the last content X position, and our overscrolled one
  5507.       widthDiff = self.__scrollLeft - (self.__maxScrollLeft - xstop);
  5508.  
  5509.       if (self.__scrollLeft < 0) {
  5510.  
  5511.         widthScale = Math.max(self.__indicatorX.minScale,
  5512.             (self.__indicatorX.size - Math.abs(self.__scrollLeft)) / self.__indicatorX.size);
  5513.  
  5514.         // Stay at left
  5515.         x = 0;
  5516.  
  5517.         // Make sure scale is transformed from the left/center origin point
  5518.         self.__indicatorX.indicator.style[self.__transformOriginProperty] = 'left center';
  5519.       } else if (widthDiff > 0) {
  5520.  
  5521.         widthScale = Math.max(self.__indicatorX.minScale,
  5522.             (self.__indicatorX.size - widthDiff) / self.__indicatorX.size);
  5523.  
  5524.         // Stay at the furthest x for the scrollable viewport
  5525.         x = self.__indicatorX.maxPos - xstop;
  5526.  
  5527.         // Make sure scale is transformed from the right/center origin point
  5528.         self.__indicatorX.indicator.style[self.__transformOriginProperty] = 'right center';
  5529.  
  5530.       } else {
  5531.  
  5532.         // Normal motion
  5533.         x = Math.min(self.__maxScrollLeft, Math.max(0, x));
  5534.         widthScale = 1;
  5535.  
  5536.       }
  5537.  
  5538.       var translate3dX = 'translate3d(' + x + 'px, 0, 0) scaleX(' + widthScale + ')';
  5539.       if (self.__indicatorX.transformProp !== translate3dX) {
  5540.         self.__indicatorX.indicator.style[self.__transformProperty] = translate3dX;
  5541.         self.__indicatorX.transformProp = translate3dX;
  5542.       }
  5543.     }
  5544.  
  5545.     if (self.__indicatorY) {
  5546.  
  5547.       y = Math.round(self.__indicatorY.sizeRatio * self.__scrollTop) || 0;
  5548.  
  5549.       // Don't go all the way to the right if we have a vertical scrollbar as well
  5550.       if (self.__indicatorX) ystop = 10;
  5551.  
  5552.       heightDiff = self.__scrollTop - (self.__maxScrollTop - ystop);
  5553.  
  5554.       if (self.__scrollTop < 0) {
  5555.  
  5556.         heightScale = Math.max(self.__indicatorY.minScale, (self.__indicatorY.size - Math.abs(self.__scrollTop)) / self.__indicatorY.size);
  5557.  
  5558.         // Stay at top
  5559.         y = 0;
  5560.  
  5561.         // Make sure scale is transformed from the center/top origin point
  5562.         if (self.__indicatorY.originProp !== 'center top') {
  5563.           self.__indicatorY.indicator.style[self.__transformOriginProperty] = 'center top';
  5564.           self.__indicatorY.originProp = 'center top';
  5565.         }
  5566.  
  5567.       } else if (heightDiff > 0) {
  5568.  
  5569.         heightScale = Math.max(self.__indicatorY.minScale, (self.__indicatorY.size - heightDiff) / self.__indicatorY.size);
  5570.  
  5571.         // Stay at bottom of scrollable viewport
  5572.         y = self.__indicatorY.maxPos - ystop;
  5573.  
  5574.         // Make sure scale is transformed from the center/bottom origin point
  5575.         if (self.__indicatorY.originProp !== 'center bottom') {
  5576.           self.__indicatorY.indicator.style[self.__transformOriginProperty] = 'center bottom';
  5577.           self.__indicatorY.originProp = 'center bottom';
  5578.         }
  5579.  
  5580.       } else {
  5581.  
  5582.         // Normal motion
  5583.         y = Math.min(self.__maxScrollTop, Math.max(0, y));
  5584.         heightScale = 1;
  5585.  
  5586.       }
  5587.  
  5588.       var translate3dY = 'translate3d(0,' + y + 'px, 0) scaleY(' + heightScale + ')';
  5589.       if (self.__indicatorY.transformProp !== translate3dY) {
  5590.         self.__indicatorY.indicator.style[self.__transformProperty] = translate3dY;
  5591.         self.__indicatorY.transformProp = translate3dY;
  5592.       }
  5593.     }
  5594.   },
  5595.  
  5596.   __fadeScrollbars: function(direction, delay) {
  5597.     var self = this;
  5598.  
  5599.     if (!self.options.scrollbarsFade) {
  5600.       return;
  5601.     }
  5602.  
  5603.     var className = 'scroll-bar-fade-out';
  5604.  
  5605.     if (self.options.scrollbarsFade === true) {
  5606.       clearTimeout(self.__scrollbarFadeTimeout);
  5607.  
  5608.       if (direction == 'in') {
  5609.         if (self.__indicatorX) { self.__indicatorX.indicator.classList.remove(className); }
  5610.         if (self.__indicatorY) { self.__indicatorY.indicator.classList.remove(className); }
  5611.       } else {
  5612.         self.__scrollbarFadeTimeout = setTimeout(function() {
  5613.           if (self.__indicatorX) { self.__indicatorX.indicator.classList.add(className); }
  5614.           if (self.__indicatorY) { self.__indicatorY.indicator.classList.add(className); }
  5615.         }, delay || self.options.scrollbarFadeDelay);
  5616.       }
  5617.     }
  5618.   },
  5619.  
  5620.   __scrollingComplete: function() {
  5621.     this.options.scrollingComplete();
  5622.     ionic.tap.removeClonedInputs(this.__container, this);
  5623.     this.__fadeScrollbars('out');
  5624.   },
  5625.  
  5626.   resize: function(continueScrolling) {
  5627.     var self = this;
  5628.     if (!self.__container || !self.options) return;
  5629.  
  5630.     // Update Scroller dimensions for changed content
  5631.     // Add padding to bottom of content
  5632.     self.setDimensions(
  5633.       self.__container.clientWidth,
  5634.       self.__container.clientHeight,
  5635.       self.options.getContentWidth(),
  5636.       self.options.getContentHeight(),
  5637.       continueScrolling
  5638.     );
  5639.   },
  5640.   /*
  5641.   ---------------------------------------------------------------------------
  5642.     PUBLIC API
  5643.   ---------------------------------------------------------------------------
  5644.   */
  5645.  
  5646.   getRenderFn: function() {
  5647.     var self = this;
  5648.  
  5649.     var content = self.__content;
  5650.  
  5651.     var docStyle = document.documentElement.style;
  5652.  
  5653.     var engine;
  5654.     if ('MozAppearance' in docStyle) {
  5655.       engine = 'gecko';
  5656.     } else if ('WebkitAppearance' in docStyle) {
  5657.       engine = 'webkit';
  5658.     } else if (typeof navigator.cpuClass === 'string') {
  5659.       engine = 'trident';
  5660.     }
  5661.  
  5662.     var vendorPrefix = {
  5663.       trident: 'ms',
  5664.       gecko: 'Moz',
  5665.       webkit: 'Webkit',
  5666.       presto: 'O'
  5667.     }[engine];
  5668.  
  5669.     var helperElem = document.createElement("div");
  5670.     var undef;
  5671.  
  5672.     var perspectiveProperty = vendorPrefix + "Perspective";
  5673.     var transformProperty = vendorPrefix + "Transform";
  5674.     var transformOriginProperty = vendorPrefix + 'TransformOrigin';
  5675.  
  5676.     self.__perspectiveProperty = transformProperty;
  5677.     self.__transformProperty = transformProperty;
  5678.     self.__transformOriginProperty = transformOriginProperty;
  5679.  
  5680.     if (helperElem.style[perspectiveProperty] !== undef) {
  5681.  
  5682.       return function(left, top, zoom, wasResize) {
  5683.         var translate3d = 'translate3d(' + (-left) + 'px,' + (-top) + 'px,0) scale(' + zoom + ')';
  5684.         if (translate3d !== self.contentTransform) {
  5685.           content.style[transformProperty] = translate3d;
  5686.           self.contentTransform = translate3d;
  5687.         }
  5688.         self.__repositionScrollbars();
  5689.         if (!wasResize) {
  5690.           self.triggerScrollEvent();
  5691.         }
  5692.       };
  5693.  
  5694.     } else if (helperElem.style[transformProperty] !== undef) {
  5695.  
  5696.       return function(left, top, zoom, wasResize) {
  5697.         content.style[transformProperty] = 'translate(' + (-left) + 'px,' + (-top) + 'px) scale(' + zoom + ')';
  5698.         self.__repositionScrollbars();
  5699.         if (!wasResize) {
  5700.           self.triggerScrollEvent();
  5701.         }
  5702.       };
  5703.  
  5704.     } else {
  5705.  
  5706.       return function(left, top, zoom, wasResize) {
  5707.         content.style.marginLeft = left ? (-left / zoom) + 'px' : '';
  5708.         content.style.marginTop = top ? (-top / zoom) + 'px' : '';
  5709.         content.style.zoom = zoom || '';
  5710.         self.__repositionScrollbars();
  5711.         if (!wasResize) {
  5712.           self.triggerScrollEvent();
  5713.         }
  5714.       };
  5715.  
  5716.     }
  5717.   },
  5718.  
  5719.  
  5720.   /**
  5721.    * Configures the dimensions of the client (outer) and content (inner) elements.
  5722.    * Requires the available space for the outer element and the outer size of the inner element.
  5723.    * All values which are falsy (null or zero etc.) are ignored and the old value is kept.
  5724.    *
  5725.    * @param clientWidth {Integer} Inner width of outer element
  5726.    * @param clientHeight {Integer} Inner height of outer element
  5727.    * @param contentWidth {Integer} Outer width of inner element
  5728.    * @param contentHeight {Integer} Outer height of inner element
  5729.    */
  5730.   setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight, continueScrolling) {
  5731.     var self = this;
  5732.  
  5733.     if (!clientWidth && !clientHeight && !contentWidth && !contentHeight) {
  5734.       // this scrollview isn't rendered, don't bother
  5735.       return;
  5736.     }
  5737.  
  5738.     // Only update values which are defined
  5739.     if (clientWidth === +clientWidth) {
  5740.       self.__clientWidth = clientWidth;
  5741.     }
  5742.  
  5743.     if (clientHeight === +clientHeight) {
  5744.       self.__clientHeight = clientHeight;
  5745.     }
  5746.  
  5747.     if (contentWidth === +contentWidth) {
  5748.       self.__contentWidth = contentWidth;
  5749.     }
  5750.  
  5751.     if (contentHeight === +contentHeight) {
  5752.       self.__contentHeight = contentHeight;
  5753.     }
  5754.  
  5755.     // Refresh maximums
  5756.     self.__computeScrollMax();
  5757.     self.__resizeScrollbars();
  5758.  
  5759.     // Refresh scroll position
  5760.     if (!continueScrolling) {
  5761.       self.scrollTo(self.__scrollLeft, self.__scrollTop, true, null, true);
  5762.     }
  5763.  
  5764.   },
  5765.  
  5766.  
  5767.   /**
  5768.    * Sets the client coordinates in relation to the document.
  5769.    *
  5770.    * @param left {Integer} Left position of outer element
  5771.    * @param top {Integer} Top position of outer element
  5772.    */
  5773.   setPosition: function(left, top) {
  5774.     this.__clientLeft = left || 0;
  5775.     this.__clientTop = top || 0;
  5776.   },
  5777.  
  5778.  
  5779.   /**
  5780.    * Configures the snapping (when snapping is active)
  5781.    *
  5782.    * @param width {Integer} Snapping width
  5783.    * @param height {Integer} Snapping height
  5784.    */
  5785.   setSnapSize: function(width, height) {
  5786.     this.__snapWidth = width;
  5787.     this.__snapHeight = height;
  5788.   },
  5789.  
  5790.  
  5791.   /**
  5792.    * Activates pull-to-refresh. A special zone on the top of the list to start a list refresh whenever
  5793.    * the user event is released during visibility of this zone. This was introduced by some apps on iOS like
  5794.    * the official Twitter client.
  5795.    *
  5796.    * @param height {Integer} Height of pull-to-refresh zone on top of rendered list
  5797.    * @param activateCallback {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release.
  5798.    * @param deactivateCallback {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled.
  5799.    * @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh.
  5800.    * @param showCallback {Function} Callback to execute when the refresher should be shown. This is for showing the refresher during a negative scrollTop.
  5801.    * @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.
  5802.    * @param tailCallback {Function} Callback to execute just before the refresher returns to it's original state. This is for zooming out the refresher.
  5803.    * @param pullProgressCallback Callback to state the progress while pulling to refresh
  5804.    */
  5805.   activatePullToRefresh: function(height, refresherMethods) {
  5806.     var self = this;
  5807.  
  5808.     self.__refreshHeight = height;
  5809.     self.__refreshActivate = function() { ionic.requestAnimationFrame(refresherMethods.activate); };
  5810.     self.__refreshDeactivate = function() { ionic.requestAnimationFrame(refresherMethods.deactivate); };
  5811.     self.__refreshStart = function() { ionic.requestAnimationFrame(refresherMethods.start); };
  5812.     self.__refreshShow = function() { ionic.requestAnimationFrame(refresherMethods.show); };
  5813.     self.__refreshHide = function() { ionic.requestAnimationFrame(refresherMethods.hide); };
  5814.     self.__refreshTail = function() { ionic.requestAnimationFrame(refresherMethods.tail); };
  5815.     self.__refreshTailTime = 100;
  5816.     self.__minSpinTime = 600;
  5817.   },
  5818.  
  5819.  
  5820.   /**
  5821.    * Starts pull-to-refresh manually.
  5822.    */
  5823.   triggerPullToRefresh: function() {
  5824.     // Use publish instead of scrollTo to allow scrolling to out of boundary position
  5825.     // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled
  5826.     this.__publish(this.__scrollLeft, -this.__refreshHeight, this.__zoomLevel, true);
  5827.  
  5828.     var d = new Date();
  5829.     this.refreshStartTime = d.getTime();
  5830.  
  5831.     if (this.__refreshStart) {
  5832.       this.__refreshStart();
  5833.     }
  5834.   },
  5835.  
  5836.  
  5837.   /**
  5838.    * Signalizes that pull-to-refresh is finished.
  5839.    */
  5840.   finishPullToRefresh: function() {
  5841.     var self = this;
  5842.     // delay to make sure the spinner has a chance to spin for a split second before it's dismissed
  5843.     var d = new Date();
  5844.     var delay = 0;
  5845.     if (self.refreshStartTime + self.__minSpinTime > d.getTime()) {
  5846.       delay = self.refreshStartTime + self.__minSpinTime - d.getTime();
  5847.     }
  5848.     setTimeout(function() {
  5849.       if (self.__refreshTail) {
  5850.         self.__refreshTail();
  5851.       }
  5852.       setTimeout(function() {
  5853.         self.__refreshActive = false;
  5854.         if (self.__refreshDeactivate) {
  5855.           self.__refreshDeactivate();
  5856.         }
  5857.         if (self.__refreshHide) {
  5858.           self.__refreshHide();
  5859.         }
  5860.  
  5861.         self.scrollTo(self.__scrollLeft, self.__scrollTop, true);
  5862.       }, self.__refreshTailTime);
  5863.     }, delay);
  5864.   },
  5865.  
  5866.  
  5867.   /**
  5868.    * Returns the scroll position and zooming values
  5869.    *
  5870.    * @return {Map} `left` and `top` scroll position and `zoom` level
  5871.    */
  5872.   getValues: function() {
  5873.     return {
  5874.       left: this.__scrollLeft,
  5875.       top: this.__scrollTop,
  5876.       zoom: this.__zoomLevel
  5877.     };
  5878.   },
  5879.  
  5880.  
  5881.   /**
  5882.    * Returns the maximum scroll values
  5883.    *
  5884.    * @return {Map} `left` and `top` maximum scroll values
  5885.    */
  5886.   getScrollMax: function() {
  5887.     return {
  5888.       left: this.__maxScrollLeft,
  5889.       top: this.__maxScrollTop
  5890.     };
  5891.   },
  5892.  
  5893.  
  5894.   /**
  5895.    * Zooms to the given level. Supports optional animation. Zooms
  5896.    * the center when no coordinates are given.
  5897.    *
  5898.    * @param level {Number} Level to zoom to
  5899.    * @param animate {Boolean} Whether to use animation
  5900.    * @param originLeft {Number} Zoom in at given left coordinate
  5901.    * @param originTop {Number} Zoom in at given top coordinate
  5902.    */
  5903.   zoomTo: function(level, animate, originLeft, originTop) {
  5904.     var self = this;
  5905.  
  5906.     if (!self.options.zooming) {
  5907.       throw new Error("Zooming is not enabled!");
  5908.     }
  5909.  
  5910.     // Stop deceleration
  5911.     if (self.__isDecelerating) {
  5912.       zyngaCore.effect.Animate.stop(self.__isDecelerating);
  5913.       self.__isDecelerating = false;
  5914.     }
  5915.  
  5916.     var oldLevel = self.__zoomLevel;
  5917.  
  5918.     // Normalize input origin to center of viewport if not defined
  5919.     if (originLeft == null) {
  5920.       originLeft = self.__clientWidth / 2;
  5921.     }
  5922.  
  5923.     if (originTop == null) {
  5924.       originTop = self.__clientHeight / 2;
  5925.     }
  5926.  
  5927.     // Limit level according to configuration
  5928.     level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);
  5929.  
  5930.     // Recompute maximum values while temporary tweaking maximum scroll ranges
  5931.     self.__computeScrollMax(level);
  5932.  
  5933.     // Recompute left and top coordinates based on new zoom level
  5934.     var left = ((originLeft + self.__scrollLeft) * level / oldLevel) - originLeft;
  5935.     var top = ((originTop + self.__scrollTop) * level / oldLevel) - originTop;
  5936.  
  5937.     // Limit x-axis
  5938.     if (left > self.__maxScrollLeft) {
  5939.       left = self.__maxScrollLeft;
  5940.     } else if (left < 0) {
  5941.       left = 0;
  5942.     }
  5943.  
  5944.     // Limit y-axis
  5945.     if (top > self.__maxScrollTop) {
  5946.       top = self.__maxScrollTop;
  5947.     } else if (top < 0) {
  5948.       top = 0;
  5949.     }
  5950.  
  5951.     // Push values out
  5952.     self.__publish(left, top, level, animate);
  5953.  
  5954.   },
  5955.  
  5956.  
  5957.   /**
  5958.    * Zooms the content by the given factor.
  5959.    *
  5960.    * @param factor {Number} Zoom by given factor
  5961.    * @param animate {Boolean} Whether to use animation
  5962.    * @param originLeft {Number} Zoom in at given left coordinate
  5963.    * @param originTop {Number} Zoom in at given top coordinate
  5964.    */
  5965.   zoomBy: function(factor, animate, originLeft, originTop) {
  5966.     this.zoomTo(this.__zoomLevel * factor, animate, originLeft, originTop);
  5967.   },
  5968.  
  5969.  
  5970.   /**
  5971.    * Scrolls to the given position. Respect limitations and snapping automatically.
  5972.    *
  5973.    * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
  5974.    * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
  5975.    * @param animate {Boolean} Whether the scrolling should happen using an animation
  5976.    * @param zoom {Number} Zoom level to go to
  5977.    */
  5978.   scrollTo: function(left, top, animate, zoom, wasResize) {
  5979.     var self = this;
  5980.  
  5981.     // Stop deceleration
  5982.     if (self.__isDecelerating) {
  5983.       zyngaCore.effect.Animate.stop(self.__isDecelerating);
  5984.       self.__isDecelerating = false;
  5985.     }
  5986.  
  5987.     // Correct coordinates based on new zoom level
  5988.     if (zoom != null && zoom !== self.__zoomLevel) {
  5989.  
  5990.       if (!self.options.zooming) {
  5991.         throw new Error("Zooming is not enabled!");
  5992.       }
  5993.  
  5994.       left *= zoom;
  5995.       top *= zoom;
  5996.  
  5997.       // Recompute maximum values while temporary tweaking maximum scroll ranges
  5998.       self.__computeScrollMax(zoom);
  5999.  
  6000.     } else {
  6001.  
  6002.       // Keep zoom when not defined
  6003.       zoom = self.__zoomLevel;
  6004.  
  6005.     }
  6006.  
  6007.     if (!self.options.scrollingX) {
  6008.  
  6009.       left = self.__scrollLeft;
  6010.  
  6011.     } else {
  6012.  
  6013.       if (self.options.paging) {
  6014.         left = Math.round(left / self.__clientWidth) * self.__clientWidth;
  6015.       } else if (self.options.snapping) {
  6016.         left = Math.round(left / self.__snapWidth) * self.__snapWidth;
  6017.       }
  6018.  
  6019.     }
  6020.  
  6021.     if (!self.options.scrollingY) {
  6022.  
  6023.       top = self.__scrollTop;
  6024.  
  6025.     } else {
  6026.  
  6027.       if (self.options.paging) {
  6028.         top = Math.round(top / self.__clientHeight) * self.__clientHeight;
  6029.       } else if (self.options.snapping) {
  6030.         top = Math.round(top / self.__snapHeight) * self.__snapHeight;
  6031.       }
  6032.  
  6033.     }
  6034.  
  6035.     // Limit for allowed ranges
  6036.     left = Math.max(Math.min(self.__maxScrollLeft, left), 0);
  6037.     top = Math.max(Math.min(self.__maxScrollTop, top), 0);
  6038.  
  6039.     // Don't animate when no change detected, still call publish to make sure
  6040.     // that rendered position is really in-sync with internal data
  6041.     if (left === self.__scrollLeft && top === self.__scrollTop) {
  6042.       animate = false;
  6043.     }
  6044.  
  6045.     // Publish new values
  6046.     self.__publish(left, top, zoom, animate, wasResize);
  6047.  
  6048.   },
  6049.  
  6050.  
  6051.   /**
  6052.    * Scroll by the given offset
  6053.    *
  6054.    * @param left {Number} Scroll x-axis by given offset
  6055.    * @param top {Number} Scroll y-axis by given offset
  6056.    * @param animate {Boolean} Whether to animate the given change
  6057.    */
  6058.   scrollBy: function(left, top, animate) {
  6059.     var self = this;
  6060.  
  6061.     var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft;
  6062.     var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop;
  6063.  
  6064.     self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate);
  6065.   },
  6066.  
  6067.  
  6068.  
  6069.   /*
  6070.   ---------------------------------------------------------------------------
  6071.     EVENT CALLBACKS
  6072.   ---------------------------------------------------------------------------
  6073.   */
  6074.  
  6075.   /**
  6076.    * Mouse wheel handler for zooming support
  6077.    */
  6078.   doMouseZoom: function(wheelDelta, timeStamp, pageX, pageY) {
  6079.     var change = wheelDelta > 0 ? 0.97 : 1.03;
  6080.     return this.zoomTo(this.__zoomLevel * change, false, pageX - this.__clientLeft, pageY - this.__clientTop);
  6081.   },
  6082.  
  6083.   /**
  6084.    * Touch start handler for scrolling support
  6085.    */
  6086.   doTouchStart: function(touches, timeStamp) {
  6087.     var self = this;
  6088.  
  6089.     // remember if the deceleration was just stopped
  6090.     self.__decStopped = !!(self.__isDecelerating || self.__isAnimating);
  6091.  
  6092.     self.hintResize();
  6093.  
  6094.     if (timeStamp instanceof Date) {
  6095.       timeStamp = timeStamp.valueOf();
  6096.     }
  6097.     if (typeof timeStamp !== "number") {
  6098.       timeStamp = Date.now();
  6099.     }
  6100.  
  6101.     // Reset interruptedAnimation flag
  6102.     self.__interruptedAnimation = true;
  6103.  
  6104.     // Stop deceleration
  6105.     if (self.__isDecelerating) {
  6106.       zyngaCore.effect.Animate.stop(self.__isDecelerating);
  6107.       self.__isDecelerating = false;
  6108.       self.__interruptedAnimation = true;
  6109.     }
  6110.  
  6111.     // Stop animation
  6112.     if (self.__isAnimating) {
  6113.       zyngaCore.effect.Animate.stop(self.__isAnimating);
  6114.       self.__isAnimating = false;
  6115.       self.__interruptedAnimation = true;
  6116.     }
  6117.  
  6118.     // Use center point when dealing with two fingers
  6119.     var currentTouchLeft, currentTouchTop;
  6120.     var isSingleTouch = touches.length === 1;
  6121.     if (isSingleTouch) {
  6122.       currentTouchLeft = touches[0].pageX;
  6123.       currentTouchTop = touches[0].pageY;
  6124.     } else {
  6125.       currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;
  6126.       currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;
  6127.     }
  6128.  
  6129.     // Store initial positions
  6130.     self.__initialTouchLeft = currentTouchLeft;
  6131.     self.__initialTouchTop = currentTouchTop;
  6132.  
  6133.     // Store initial touchList for scale calculation
  6134.     self.__initialTouches = touches;
  6135.  
  6136.     // Store current zoom level
  6137.     self.__zoomLevelStart = self.__zoomLevel;
  6138.  
  6139.     // Store initial touch positions
  6140.     self.__lastTouchLeft = currentTouchLeft;
  6141.     self.__lastTouchTop = currentTouchTop;
  6142.  
  6143.     // Store initial move time stamp
  6144.     self.__lastTouchMove = timeStamp;
  6145.  
  6146.     // Reset initial scale
  6147.     self.__lastScale = 1;
  6148.  
  6149.     // Reset locking flags
  6150.     self.__enableScrollX = !isSingleTouch && self.options.scrollingX;
  6151.     self.__enableScrollY = !isSingleTouch && self.options.scrollingY;
  6152.  
  6153.     // Reset tracking flag
  6154.     self.__isTracking = true;
  6155.  
  6156.     // Reset deceleration complete flag
  6157.     self.__didDecelerationComplete = false;
  6158.  
  6159.     // Dragging starts directly with two fingers, otherwise lazy with an offset
  6160.     self.__isDragging = !isSingleTouch;
  6161.  
  6162.     // Some features are disabled in multi touch scenarios
  6163.     self.__isSingleTouch = isSingleTouch;
  6164.  
  6165.     // Clearing data structure
  6166.     self.__positions = [];
  6167.  
  6168.   },
  6169.  
  6170.  
  6171.   /**
  6172.    * Touch move handler for scrolling support
  6173.    */
  6174.   doTouchMove: function(touches, timeStamp, scale) {
  6175.     if (timeStamp instanceof Date) {
  6176.       timeStamp = timeStamp.valueOf();
  6177.     }
  6178.     if (typeof timeStamp !== "number") {
  6179.       timeStamp = Date.now();
  6180.     }
  6181.  
  6182.     var self = this;
  6183.  
  6184.     // Ignore event when tracking is not enabled (event might be outside of element)
  6185.     if (!self.__isTracking) {
  6186.       return;
  6187.     }
  6188.  
  6189.     var currentTouchLeft, currentTouchTop;
  6190.  
  6191.     // Compute move based around of center of fingers
  6192.     if (touches.length === 2) {
  6193.       currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;
  6194.       currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;
  6195.  
  6196.       // Calculate scale when not present and only when touches are used
  6197.       if (!scale && self.options.zooming) {
  6198.         scale = self.__getScale(self.__initialTouches, touches);
  6199.       }
  6200.     } else {
  6201.       currentTouchLeft = touches[0].pageX;
  6202.       currentTouchTop = touches[0].pageY;
  6203.     }
  6204.  
  6205.     var positions = self.__positions;
  6206.  
  6207.     // Are we already is dragging mode?
  6208.     if (self.__isDragging) {
  6209.         self.__decStopped = false;
  6210.  
  6211.       // Compute move distance
  6212.       var moveX = currentTouchLeft - self.__lastTouchLeft;
  6213.       var moveY = currentTouchTop - self.__lastTouchTop;
  6214.  
  6215.       // Read previous scroll position and zooming
  6216.       var scrollLeft = self.__scrollLeft;
  6217.       var scrollTop = self.__scrollTop;
  6218.       var level = self.__zoomLevel;
  6219.  
  6220.       // Work with scaling
  6221.       if (scale != null && self.options.zooming) {
  6222.  
  6223.         var oldLevel = level;
  6224.  
  6225.         // Recompute level based on previous scale and new scale
  6226.         level = level / self.__lastScale * scale;
  6227.  
  6228.         // Limit level according to configuration
  6229.         level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);
  6230.  
  6231.         // Only do further compution when change happened
  6232.         if (oldLevel !== level) {
  6233.  
  6234.           // Compute relative event position to container
  6235.           var currentTouchLeftRel = currentTouchLeft - self.__clientLeft;
  6236.           var currentTouchTopRel = currentTouchTop - self.__clientTop;
  6237.  
  6238.           // Recompute left and top coordinates based on new zoom level
  6239.           scrollLeft = ((currentTouchLeftRel + scrollLeft) * level / oldLevel) - currentTouchLeftRel;
  6240.           scrollTop = ((currentTouchTopRel + scrollTop) * level / oldLevel) - currentTouchTopRel;
  6241.  
  6242.           // Recompute max scroll values
  6243.           self.__computeScrollMax(level);
  6244.  
  6245.         }
  6246.       }
  6247.  
  6248.       if (self.__enableScrollX) {
  6249.  
  6250.         scrollLeft -= moveX * self.options.speedMultiplier;
  6251.         var maxScrollLeft = self.__maxScrollLeft;
  6252.  
  6253.         if (scrollLeft > maxScrollLeft || scrollLeft < 0) {
  6254.  
  6255.           // Slow down on the edges
  6256.           if (self.options.bouncing) {
  6257.  
  6258.             scrollLeft += (moveX / 2 * self.options.speedMultiplier);
  6259.  
  6260.           } else if (scrollLeft > maxScrollLeft) {
  6261.  
  6262.             scrollLeft = maxScrollLeft;
  6263.  
  6264.           } else {
  6265.  
  6266.             scrollLeft = 0;
  6267.  
  6268.           }
  6269.         }
  6270.       }
  6271.  
  6272.       // Compute new vertical scroll position
  6273.       if (self.__enableScrollY) {
  6274.  
  6275.         scrollTop -= moveY * self.options.speedMultiplier;
  6276.         var maxScrollTop = self.__maxScrollTop;
  6277.  
  6278.         if (scrollTop > maxScrollTop || scrollTop < 0) {
  6279.  
  6280.           // Slow down on the edges
  6281.           if (self.options.bouncing || (self.__refreshHeight && scrollTop < 0)) {
  6282.  
  6283.             scrollTop += (moveY / 2 * self.options.speedMultiplier);
  6284.  
  6285.             // Support pull-to-refresh (only when only y is scrollable)
  6286.             if (!self.__enableScrollX && self.__refreshHeight != null) {
  6287.  
  6288.               // hide the refresher when it's behind the header bar in case of header transparency
  6289.               if (scrollTop < 0) {
  6290.                 self.__refreshHidden = false;
  6291.                 self.__refreshShow();
  6292.               } else {
  6293.                 self.__refreshHide();
  6294.                 self.__refreshHidden = true;
  6295.               }
  6296.  
  6297.               if (!self.__refreshActive && scrollTop <= -self.__refreshHeight) {
  6298.  
  6299.                 self.__refreshActive = true;
  6300.                 if (self.__refreshActivate) {
  6301.                   self.__refreshActivate();
  6302.                 }
  6303.  
  6304.               } else if (self.__refreshActive && scrollTop > -self.__refreshHeight) {
  6305.  
  6306.                 self.__refreshActive = false;
  6307.                 if (self.__refreshDeactivate) {
  6308.                   self.__refreshDeactivate();
  6309.                 }
  6310.  
  6311.               }
  6312.             }
  6313.  
  6314.           } else if (scrollTop > maxScrollTop) {
  6315.  
  6316.             scrollTop = maxScrollTop;
  6317.  
  6318.           } else {
  6319.  
  6320.             scrollTop = 0;
  6321.  
  6322.           }
  6323.         } else if (self.__refreshHeight && !self.__refreshHidden) {
  6324.           // if a positive scroll value and the refresher is still not hidden, hide it
  6325.           self.__refreshHide();
  6326.           self.__refreshHidden = true;
  6327.         }
  6328.       }
  6329.  
  6330.       // Keep list from growing infinitely (holding min 10, max 20 measure points)
  6331.       if (positions.length > 60) {
  6332.         positions.splice(0, 30);
  6333.       }
  6334.  
  6335.       // Track scroll movement for decleration
  6336.       positions.push(scrollLeft, scrollTop, timeStamp);
  6337.  
  6338.       // Sync scroll position
  6339.       self.__publish(scrollLeft, scrollTop, level);
  6340.  
  6341.     // Otherwise figure out whether we are switching into dragging mode now.
  6342.     } else {
  6343.  
  6344.       var minimumTrackingForScroll = self.options.locking ? 3 : 0;
  6345.       var minimumTrackingForDrag = 5;
  6346.  
  6347.       var distanceX = Math.abs(currentTouchLeft - self.__initialTouchLeft);
  6348.       var distanceY = Math.abs(currentTouchTop - self.__initialTouchTop);
  6349.  
  6350.       self.__enableScrollX = self.options.scrollingX && distanceX >= minimumTrackingForScroll;
  6351.       self.__enableScrollY = self.options.scrollingY && distanceY >= minimumTrackingForScroll;
  6352.  
  6353.       positions.push(self.__scrollLeft, self.__scrollTop, timeStamp);
  6354.  
  6355.       self.__isDragging = (self.__enableScrollX || self.__enableScrollY) && (distanceX >= minimumTrackingForDrag || distanceY >= minimumTrackingForDrag);
  6356.       if (self.__isDragging) {
  6357.         self.__interruptedAnimation = false;
  6358.         self.__fadeScrollbars('in');
  6359.       }
  6360.  
  6361.     }
  6362.  
  6363.     // Update last touch positions and time stamp for next event
  6364.     self.__lastTouchLeft = currentTouchLeft;
  6365.     self.__lastTouchTop = currentTouchTop;
  6366.     self.__lastTouchMove = timeStamp;
  6367.     self.__lastScale = scale;
  6368.  
  6369.   },
  6370.  
  6371.  
  6372.   /**
  6373.    * Touch end handler for scrolling support
  6374.    */
  6375.   doTouchEnd: function(e, timeStamp) {
  6376.     if (timeStamp instanceof Date) {
  6377.       timeStamp = timeStamp.valueOf();
  6378.     }
  6379.     if (typeof timeStamp !== "number") {
  6380.       timeStamp = Date.now();
  6381.     }
  6382.  
  6383.     var self = this;
  6384.  
  6385.     // Ignore event when tracking is not enabled (no touchstart event on element)
  6386.     // This is required as this listener ('touchmove') sits on the document and not on the element itself.
  6387.     if (!self.__isTracking) {
  6388.       return;
  6389.     }
  6390.  
  6391.     // Not touching anymore (when two finger hit the screen there are two touch end events)
  6392.     self.__isTracking = false;
  6393.  
  6394.     // Be sure to reset the dragging flag now. Here we also detect whether
  6395.     // the finger has moved fast enough to switch into a deceleration animation.
  6396.     if (self.__isDragging) {
  6397.  
  6398.       // Reset dragging flag
  6399.       self.__isDragging = false;
  6400.  
  6401.       // Start deceleration
  6402.       // Verify that the last move detected was in some relevant time frame
  6403.       if (self.__isSingleTouch && self.options.animating && (timeStamp - self.__lastTouchMove) <= 100) {
  6404.  
  6405.         // Then figure out what the scroll position was about 100ms ago
  6406.         var positions = self.__positions;
  6407.         var endPos = positions.length - 1;
  6408.         var startPos = endPos;
  6409.  
  6410.         // Move pointer to position measured 100ms ago
  6411.         for (var i = endPos; i > 0 && positions[i] > (self.__lastTouchMove - 100); i -= 3) {
  6412.           startPos = i;
  6413.         }
  6414.  
  6415.         // If start and stop position is identical in a 100ms timeframe,
  6416.         // we cannot compute any useful deceleration.
  6417.         if (startPos !== endPos) {
  6418.  
  6419.           // Compute relative movement between these two points
  6420.           var timeOffset = positions[endPos] - positions[startPos];
  6421.           var movedLeft = self.__scrollLeft - positions[startPos - 2];
  6422.           var movedTop = self.__scrollTop - positions[startPos - 1];
  6423.  
  6424.           // Based on 50ms compute the movement to apply for each render step
  6425.           self.__decelerationVelocityX = movedLeft / timeOffset * (1000 / 60);
  6426.           self.__decelerationVelocityY = movedTop / timeOffset * (1000 / 60);
  6427.  
  6428.           // How much velocity is required to start the deceleration
  6429.           var minVelocityToStartDeceleration = self.options.paging || self.options.snapping ? self.options.decelVelocityThresholdPaging : self.options.decelVelocityThreshold;
  6430.  
  6431.           // Verify that we have enough velocity to start deceleration
  6432.           if (Math.abs(self.__decelerationVelocityX) > minVelocityToStartDeceleration || Math.abs(self.__decelerationVelocityY) > minVelocityToStartDeceleration) {
  6433.  
  6434.             // Deactivate pull-to-refresh when decelerating
  6435.             if (!self.__refreshActive) {
  6436.               self.__startDeceleration(timeStamp);
  6437.             }
  6438.           }
  6439.         } else {
  6440.           self.__scrollingComplete();
  6441.         }
  6442.       } else if ((timeStamp - self.__lastTouchMove) > 100) {
  6443.         self.__scrollingComplete();
  6444.       }
  6445.  
  6446.     } else if (self.__decStopped) {
  6447.       // the deceleration was stopped
  6448.       // user flicked the scroll fast, and stop dragging, then did a touchstart to stop the srolling
  6449.       // tell the touchend event code to do nothing, we don't want to actually send a click
  6450.       e.isTapHandled = true;
  6451.       self.__decStopped = false;
  6452.     }
  6453.  
  6454.     // If this was a slower move it is per default non decelerated, but this
  6455.     // still means that we want snap back to the bounds which is done here.
  6456.     // This is placed outside the condition above to improve edge case stability
  6457.     // e.g. touchend fired without enabled dragging. This should normally do not
  6458.     // have modified the scroll positions or even showed the scrollbars though.
  6459.     if (!self.__isDecelerating) {
  6460.  
  6461.       if (self.__refreshActive && self.__refreshStart) {
  6462.  
  6463.         // Use publish instead of scrollTo to allow scrolling to out of boundary position
  6464.         // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled
  6465.         self.__publish(self.__scrollLeft, -self.__refreshHeight, self.__zoomLevel, true);
  6466.  
  6467.         var d = new Date();
  6468.         self.refreshStartTime = d.getTime();
  6469.  
  6470.         if (self.__refreshStart) {
  6471.           self.__refreshStart();
  6472.         }
  6473.         // for iOS-ey style scrolling
  6474.         if (!ionic.Platform.isAndroid())self.__startDeceleration();
  6475.       } else {
  6476.  
  6477.         if (self.__interruptedAnimation || self.__isDragging) {
  6478.           self.__scrollingComplete();
  6479.         }
  6480.         self.scrollTo(self.__scrollLeft, self.__scrollTop, true, self.__zoomLevel);
  6481.  
  6482.         // Directly signalize deactivation (nothing todo on refresh?)
  6483.         if (self.__refreshActive) {
  6484.  
  6485.           self.__refreshActive = false;
  6486.           if (self.__refreshDeactivate) {
  6487.             self.__refreshDeactivate();
  6488.           }
  6489.  
  6490.         }
  6491.       }
  6492.     }
  6493.  
  6494.     // Fully cleanup list
  6495.     self.__positions.length = 0;
  6496.  
  6497.   },
  6498.  
  6499.  
  6500.  
  6501.   /*
  6502.   ---------------------------------------------------------------------------
  6503.     PRIVATE API
  6504.   ---------------------------------------------------------------------------
  6505.   */
  6506.  
  6507.   /**
  6508.    * Applies the scroll position to the content element
  6509.    *
  6510.    * @param left {Number} Left scroll position
  6511.    * @param top {Number} Top scroll position
  6512.    * @param animate {Boolean} Whether animation should be used to move to the new coordinates
  6513.    */
  6514.   __publish: function(left, top, zoom, animate, wasResize) {
  6515.  
  6516.     var self = this;
  6517.  
  6518.     // Remember whether we had an animation, then we try to continue based on the current "drive" of the animation
  6519.     var wasAnimating = self.__isAnimating;
  6520.     if (wasAnimating) {
  6521.       zyngaCore.effect.Animate.stop(wasAnimating);
  6522.       self.__isAnimating = false;
  6523.     }
  6524.  
  6525.     if (animate && self.options.animating) {
  6526.  
  6527.       // Keep scheduled positions for scrollBy/zoomBy functionality
  6528.       self.__scheduledLeft = left;
  6529.       self.__scheduledTop = top;
  6530.       self.__scheduledZoom = zoom;
  6531.  
  6532.       var oldLeft = self.__scrollLeft;
  6533.       var oldTop = self.__scrollTop;
  6534.       var oldZoom = self.__zoomLevel;
  6535.  
  6536.       var diffLeft = left - oldLeft;
  6537.       var diffTop = top - oldTop;
  6538.       var diffZoom = zoom - oldZoom;
  6539.  
  6540.       var step = function(percent, now, render) {
  6541.  
  6542.         if (render) {
  6543.  
  6544.           self.__scrollLeft = oldLeft + (diffLeft * percent);
  6545.           self.__scrollTop = oldTop + (diffTop * percent);
  6546.           self.__zoomLevel = oldZoom + (diffZoom * percent);
  6547.  
  6548.           // Push values out
  6549.           if (self.__callback) {
  6550.             self.__callback(self.__scrollLeft, self.__scrollTop, self.__zoomLevel, wasResize);
  6551.           }
  6552.  
  6553.         }
  6554.       };
  6555.  
  6556.       var verify = function(id) {
  6557.         return self.__isAnimating === id;
  6558.       };
  6559.  
  6560.       var completed = function(renderedFramesPerSecond, animationId, wasFinished) {
  6561.         if (animationId === self.__isAnimating) {
  6562.           self.__isAnimating = false;
  6563.         }
  6564.         if (self.__didDecelerationComplete || wasFinished) {
  6565.           self.__scrollingComplete();
  6566.         }
  6567.  
  6568.         if (self.options.zooming) {
  6569.           self.__computeScrollMax();
  6570.         }
  6571.       };
  6572.  
  6573.       // When continuing based on previous animation we choose an ease-out animation instead of ease-in-out
  6574.       self.__isAnimating = zyngaCore.effect.Animate.start(step, verify, completed, self.options.animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic);
  6575.  
  6576.     } else {
  6577.  
  6578.       self.__scheduledLeft = self.__scrollLeft = left;
  6579.       self.__scheduledTop = self.__scrollTop = top;
  6580.       self.__scheduledZoom = self.__zoomLevel = zoom;
  6581.  
  6582.       // Push values out
  6583.       if (self.__callback) {
  6584.         self.__callback(left, top, zoom, wasResize);
  6585.       }
  6586.  
  6587.       // Fix max scroll ranges
  6588.       if (self.options.zooming) {
  6589.         self.__computeScrollMax();
  6590.       }
  6591.     }
  6592.   },
  6593.  
  6594.  
  6595.   /**
  6596.    * Recomputes scroll minimum values based on client dimensions and content dimensions.
  6597.    */
  6598.   __computeScrollMax: function(zoomLevel) {
  6599.     var self = this;
  6600.  
  6601.     if (zoomLevel == null) {
  6602.       zoomLevel = self.__zoomLevel;
  6603.     }
  6604.  
  6605.     self.__maxScrollLeft = Math.max((self.__contentWidth * zoomLevel) - self.__clientWidth, 0);
  6606.     self.__maxScrollTop = Math.max((self.__contentHeight * zoomLevel) - self.__clientHeight, 0);
  6607.  
  6608.     if (!self.__didWaitForSize && !self.__maxScrollLeft && !self.__maxScrollTop) {
  6609.       self.__didWaitForSize = true;
  6610.       self.__waitForSize();
  6611.     }
  6612.   },
  6613.  
  6614.  
  6615.   /**
  6616.    * If the scroll view isn't sized correctly on start, wait until we have at least some size
  6617.    */
  6618.   __waitForSize: function() {
  6619.     var self = this;
  6620.  
  6621.     clearTimeout(self.__sizerTimeout);
  6622.  
  6623.     var sizer = function() {
  6624.       self.resize(true);
  6625.     };
  6626.  
  6627.     sizer();
  6628.     self.__sizerTimeout = setTimeout(sizer, 500);
  6629.   },
  6630.  
  6631.   /*
  6632.   ---------------------------------------------------------------------------
  6633.     ANIMATION (DECELERATION) SUPPORT
  6634.   ---------------------------------------------------------------------------
  6635.   */
  6636.  
  6637.   /**
  6638.    * Called when a touch sequence end and the speed of the finger was high enough
  6639.    * to switch into deceleration mode.
  6640.    */
  6641.   __startDeceleration: function() {
  6642.     var self = this;
  6643.  
  6644.     if (self.options.paging) {
  6645.  
  6646.       var scrollLeft = Math.max(Math.min(self.__scrollLeft, self.__maxScrollLeft), 0);
  6647.       var scrollTop = Math.max(Math.min(self.__scrollTop, self.__maxScrollTop), 0);
  6648.       var clientWidth = self.__clientWidth;
  6649.       var clientHeight = self.__clientHeight;
  6650.  
  6651.       // We limit deceleration not to the min/max values of the allowed range, but to the size of the visible client area.
  6652.       // Each page should have exactly the size of the client area.
  6653.       self.__minDecelerationScrollLeft = Math.floor(scrollLeft / clientWidth) * clientWidth;
  6654.       self.__minDecelerationScrollTop = Math.floor(scrollTop / clientHeight) * clientHeight;
  6655.       self.__maxDecelerationScrollLeft = Math.ceil(scrollLeft / clientWidth) * clientWidth;
  6656.       self.__maxDecelerationScrollTop = Math.ceil(scrollTop / clientHeight) * clientHeight;
  6657.  
  6658.     } else {
  6659.  
  6660.       self.__minDecelerationScrollLeft = 0;
  6661.       self.__minDecelerationScrollTop = 0;
  6662.       self.__maxDecelerationScrollLeft = self.__maxScrollLeft;
  6663.       self.__maxDecelerationScrollTop = self.__maxScrollTop;
  6664.       if (self.__refreshActive) self.__minDecelerationScrollTop = self.__refreshHeight * -1;
  6665.     }
  6666.  
  6667.     // Wrap class method
  6668.     var step = function(percent, now, render) {
  6669.       self.__stepThroughDeceleration(render);
  6670.     };
  6671.  
  6672.     // How much velocity is required to keep the deceleration running
  6673.     self.__minVelocityToKeepDecelerating = self.options.snapping ? 4 : 0.1;
  6674.  
  6675.     // Detect whether it's still worth to continue animating steps
  6676.     // If we are already slow enough to not being user perceivable anymore, we stop the whole process here.
  6677.     var verify = function() {
  6678.       var shouldContinue = Math.abs(self.__decelerationVelocityX) >= self.__minVelocityToKeepDecelerating ||
  6679.         Math.abs(self.__decelerationVelocityY) >= self.__minVelocityToKeepDecelerating;
  6680.       if (!shouldContinue) {
  6681.         self.__didDecelerationComplete = true;
  6682.  
  6683.         //Make sure the scroll values are within the boundaries after a bounce,
  6684.         //not below 0 or above maximum
  6685.         if (self.options.bouncing && !self.__refreshActive) {
  6686.           self.scrollTo(
  6687.             Math.min( Math.max(self.__scrollLeft, 0), self.__maxScrollLeft ),
  6688.             Math.min( Math.max(self.__scrollTop, 0), self.__maxScrollTop ),
  6689.             self.__refreshActive
  6690.           );
  6691.         }
  6692.       }
  6693.       return shouldContinue;
  6694.     };
  6695.  
  6696.     var completed = function() {
  6697.       self.__isDecelerating = false;
  6698.       if (self.__didDecelerationComplete) {
  6699.         self.__scrollingComplete();
  6700.       }
  6701.  
  6702.       // Animate to grid when snapping is active, otherwise just fix out-of-boundary positions
  6703.       if (self.options.paging) {
  6704.         self.scrollTo(self.__scrollLeft, self.__scrollTop, self.options.snapping);
  6705.       }
  6706.     };
  6707.  
  6708.     // Start animation and switch on flag
  6709.     self.__isDecelerating = zyngaCore.effect.Animate.start(step, verify, completed);
  6710.  
  6711.   },
  6712.  
  6713.  
  6714.   /**
  6715.    * Called on every step of the animation
  6716.    *
  6717.    * @param inMemory {Boolean} Whether to not render the current step, but keep it in memory only. Used internally only!
  6718.    */
  6719.   __stepThroughDeceleration: function(render) {
  6720.     var self = this;
  6721.  
  6722.  
  6723.     //
  6724.     // COMPUTE NEXT SCROLL POSITION
  6725.     //
  6726.  
  6727.     // Add deceleration to scroll position
  6728.     var scrollLeft = self.__scrollLeft + self.__decelerationVelocityX;// * self.options.deceleration);
  6729.     var scrollTop = self.__scrollTop + self.__decelerationVelocityY;// * self.options.deceleration);
  6730.  
  6731.  
  6732.     //
  6733.     // HARD LIMIT SCROLL POSITION FOR NON BOUNCING MODE
  6734.     //
  6735.  
  6736.     if (!self.options.bouncing) {
  6737.  
  6738.       var scrollLeftFixed = Math.max(Math.min(self.__maxDecelerationScrollLeft, scrollLeft), self.__minDecelerationScrollLeft);
  6739.       if (scrollLeftFixed !== scrollLeft) {
  6740.         scrollLeft = scrollLeftFixed;
  6741.         self.__decelerationVelocityX = 0;
  6742.       }
  6743.  
  6744.       var scrollTopFixed = Math.max(Math.min(self.__maxDecelerationScrollTop, scrollTop), self.__minDecelerationScrollTop);
  6745.       if (scrollTopFixed !== scrollTop) {
  6746.         scrollTop = scrollTopFixed;
  6747.         self.__decelerationVelocityY = 0;
  6748.       }
  6749.  
  6750.     }
  6751.  
  6752.  
  6753.     //
  6754.     // UPDATE SCROLL POSITION
  6755.     //
  6756.  
  6757.     if (render) {
  6758.  
  6759.       self.__publish(scrollLeft, scrollTop, self.__zoomLevel);
  6760.  
  6761.     } else {
  6762.  
  6763.       self.__scrollLeft = scrollLeft;
  6764.       self.__scrollTop = scrollTop;
  6765.  
  6766.     }
  6767.  
  6768.  
  6769.     //
  6770.     // SLOW DOWN
  6771.     //
  6772.  
  6773.     // Slow down velocity on every iteration
  6774.     if (!self.options.paging) {
  6775.  
  6776.       // This is the factor applied to every iteration of the animation
  6777.       // to slow down the process. This should emulate natural behavior where
  6778.       // objects slow down when the initiator of the movement is removed
  6779.       var frictionFactor = self.options.deceleration;
  6780.  
  6781.       self.__decelerationVelocityX *= frictionFactor;
  6782.       self.__decelerationVelocityY *= frictionFactor;
  6783.  
  6784.     }
  6785.  
  6786.  
  6787.     //
  6788.     // BOUNCING SUPPORT
  6789.     //
  6790.  
  6791.     if (self.options.bouncing) {
  6792.  
  6793.       var scrollOutsideX = 0;
  6794.       var scrollOutsideY = 0;
  6795.  
  6796.       // This configures the amount of change applied to deceleration/acceleration when reaching boundaries
  6797.       var penetrationDeceleration = self.options.penetrationDeceleration;
  6798.       var penetrationAcceleration = self.options.penetrationAcceleration;
  6799.  
  6800.       // Check limits
  6801.       if (scrollLeft < self.__minDecelerationScrollLeft) {
  6802.         scrollOutsideX = self.__minDecelerationScrollLeft - scrollLeft;
  6803.       } else if (scrollLeft > self.__maxDecelerationScrollLeft) {
  6804.         scrollOutsideX = self.__maxDecelerationScrollLeft - scrollLeft;
  6805.       }
  6806.  
  6807.       if (scrollTop < self.__minDecelerationScrollTop) {
  6808.         scrollOutsideY = self.__minDecelerationScrollTop - scrollTop;
  6809.       } else if (scrollTop > self.__maxDecelerationScrollTop) {
  6810.         scrollOutsideY = self.__maxDecelerationScrollTop - scrollTop;
  6811.       }
  6812.  
  6813.       // Slow down until slow enough, then flip back to snap position
  6814.       if (scrollOutsideX !== 0) {
  6815.         var isHeadingOutwardsX = scrollOutsideX * self.__decelerationVelocityX <= self.__minDecelerationScrollLeft;
  6816.         if (isHeadingOutwardsX) {
  6817.           self.__decelerationVelocityX += scrollOutsideX * penetrationDeceleration;
  6818.         }
  6819.         var isStoppedX = Math.abs(self.__decelerationVelocityX) <= self.__minVelocityToKeepDecelerating;
  6820.         //If we're not heading outwards, or if the above statement got us below minDeceleration, go back towards bounds
  6821.         if (!isHeadingOutwardsX || isStoppedX) {
  6822.           self.__decelerationVelocityX = scrollOutsideX * penetrationAcceleration;
  6823.         }
  6824.       }
  6825.  
  6826.       if (scrollOutsideY !== 0) {
  6827.         var isHeadingOutwardsY = scrollOutsideY * self.__decelerationVelocityY <= self.__minDecelerationScrollTop;
  6828.         if (isHeadingOutwardsY) {
  6829.           self.__decelerationVelocityY += scrollOutsideY * penetrationDeceleration;
  6830.         }
  6831.         var isStoppedY = Math.abs(self.__decelerationVelocityY) <= self.__minVelocityToKeepDecelerating;
  6832.         //If we're not heading outwards, or if the above statement got us below minDeceleration, go back towards bounds
  6833.         if (!isHeadingOutwardsY || isStoppedY) {
  6834.           self.__decelerationVelocityY = scrollOutsideY * penetrationAcceleration;
  6835.         }
  6836.       }
  6837.     }
  6838.   },
  6839.  
  6840.  
  6841.   /**
  6842.    * calculate the distance between two touches
  6843.    * @param   {Touch}     touch1
  6844.    * @param   {Touch}     touch2
  6845.    * @returns {Number}    distance
  6846.    */
  6847.   __getDistance: function getDistance(touch1, touch2) {
  6848.     var x = touch2.pageX - touch1.pageX,
  6849.     y = touch2.pageY - touch1.pageY;
  6850.     return Math.sqrt((x * x) + (y * y));
  6851.   },
  6852.  
  6853.  
  6854.   /**
  6855.    * calculate the scale factor between two touchLists (fingers)
  6856.    * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
  6857.    * @param   {Array}     start
  6858.    * @param   {Array}     end
  6859.    * @returns {Number}    scale
  6860.    */
  6861.   __getScale: function getScale(start, end) {
  6862.     // need two fingers...
  6863.     if (start.length >= 2 && end.length >= 2) {
  6864.       return this.__getDistance(end[0], end[1]) /
  6865.         this.__getDistance(start[0], start[1]);
  6866.     }
  6867.     return 1;
  6868.   }
  6869. });
  6870.  
  6871. ionic.scroll = {
  6872.   isScrolling: false,
  6873.   lastTop: 0
  6874. };
  6875.  
  6876. })(ionic);
  6877.  
  6878. (function(ionic) {
  6879.   var NOOP = function() {};
  6880.   var depreciated = function(name) {
  6881.     void 0;
  6882.   };
  6883.   ionic.views.ScrollNative = ionic.views.View.inherit({
  6884.  
  6885.     initialize: function(options) {
  6886.       var self = this;
  6887.       self.__container = self.el = options.el;
  6888.       self.__content = options.el.firstElementChild;
  6889.       self.isNative = true;
  6890.  
  6891.       self.__scrollTop = self.el.scrollTop;
  6892.       self.__scrollLeft = self.el.scrollLeft;
  6893.       self.__clientHeight = self.__content.clientHeight;
  6894.       self.__clientWidth = self.__content.clientWidth;
  6895.       self.__maxScrollTop = Math.max((self.__contentHeight) - self.__clientHeight, 0);
  6896.       self.__maxScrollLeft = Math.max((self.__contentWidth) - self.__clientWidth, 0);
  6897.  
  6898.       self.options = {
  6899.  
  6900.         freeze: false,
  6901.  
  6902.         getContentWidth: function() {
  6903.           return Math.max(self.__content.scrollWidth, self.__content.offsetWidth);
  6904.         },
  6905.  
  6906.         getContentHeight: function() {
  6907.           return Math.max(self.__content.scrollHeight, self.__content.offsetHeight + (self.__content.offsetTop * 2));
  6908.         }
  6909.  
  6910.       };
  6911.  
  6912.       for (var key in options) {
  6913.         self.options[key] = options[key];
  6914.       }
  6915.  
  6916.       /**
  6917.        * Sets isScrolling to true, and automatically deactivates if not called again in 80ms.
  6918.        */
  6919.       self.onScroll = function() {
  6920.         if (!ionic.scroll.isScrolling) {
  6921.           ionic.scroll.isScrolling = true;
  6922.         }
  6923.  
  6924.         clearTimeout(self.scrollTimer);
  6925.         self.scrollTimer = setTimeout(function() {
  6926.           ionic.scroll.isScrolling = false;
  6927.         }, 80);
  6928.       };
  6929.  
  6930.       self.freeze = NOOP;
  6931.  
  6932.       self.__initEventHandlers();
  6933.     },
  6934.  
  6935.     /**  Methods not used in native scrolling */
  6936.     __callback: function() { depreciated('__callback'); },
  6937.     zoomTo: function() { depreciated('zoomTo'); },
  6938.     zoomBy: function() { depreciated('zoomBy'); },
  6939.     activatePullToRefresh: function() { depreciated('activatePullToRefresh'); },
  6940.  
  6941.     /**
  6942.      * Returns the scroll position and zooming values
  6943.      *
  6944.      * @return {Map} `left` and `top` scroll position and `zoom` level
  6945.      */
  6946.     resize: function(continueScrolling) {
  6947.       var self = this;
  6948.       if (!self.__container || !self.options) return;
  6949.  
  6950.       // Update Scroller dimensions for changed content
  6951.       // Add padding to bottom of content
  6952.       self.setDimensions(
  6953.         self.__container.clientWidth,
  6954.         self.__container.clientHeight,
  6955.         self.options.getContentWidth(),
  6956.         self.options.getContentHeight(),
  6957.         continueScrolling
  6958.       );
  6959.     },
  6960.  
  6961.     /**
  6962.      * Initialize the scrollview
  6963.      * In native scrolling, this only means we need to gather size information
  6964.      */
  6965.     run: function() {
  6966.       this.resize();
  6967.     },
  6968.  
  6969.     /**
  6970.      * Returns the scroll position and zooming values
  6971.      *
  6972.      * @return {Map} `left` and `top` scroll position and `zoom` level
  6973.      */
  6974.     getValues: function() {
  6975.       var self = this;
  6976.       self.update();
  6977.       return {
  6978.         left: self.__scrollLeft,
  6979.         top: self.__scrollTop,
  6980.         zoom: 1
  6981.       };
  6982.     },
  6983.  
  6984.     /**
  6985.      * Updates the __scrollLeft and __scrollTop values to el's current value
  6986.      */
  6987.     update: function() {
  6988.       var self = this;
  6989.       self.__scrollLeft = self.el.scrollLeft;
  6990.       self.__scrollTop = self.el.scrollTop;
  6991.     },
  6992.  
  6993.     /**
  6994.      * Configures the dimensions of the client (outer) and content (inner) elements.
  6995.      * Requires the available space for the outer element and the outer size of the inner element.
  6996.      * All values which are falsy (null or zero etc.) are ignored and the old value is kept.
  6997.      *
  6998.      * @param clientWidth {Integer} Inner width of outer element
  6999.      * @param clientHeight {Integer} Inner height of outer element
  7000.      * @param contentWidth {Integer} Outer width of inner element
  7001.      * @param contentHeight {Integer} Outer height of inner element
  7002.      */
  7003.     setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight) {
  7004.       var self = this;
  7005.  
  7006.       if (!clientWidth && !clientHeight && !contentWidth && !contentHeight) {
  7007.         // this scrollview isn't rendered, don't bother
  7008.         return;
  7009.       }
  7010.  
  7011.       // Only update values which are defined
  7012.       if (clientWidth === +clientWidth) {
  7013.         self.__clientWidth = clientWidth;
  7014.       }
  7015.  
  7016.       if (clientHeight === +clientHeight) {
  7017.         self.__clientHeight = clientHeight;
  7018.       }
  7019.  
  7020.       if (contentWidth === +contentWidth) {
  7021.         self.__contentWidth = contentWidth;
  7022.       }
  7023.  
  7024.       if (contentHeight === +contentHeight) {
  7025.         self.__contentHeight = contentHeight;
  7026.       }
  7027.  
  7028.       // Refresh maximums
  7029.       self.__computeScrollMax();
  7030.     },
  7031.  
  7032.     /**
  7033.      * Returns the maximum scroll values
  7034.      *
  7035.      * @return {Map} `left` and `top` maximum scroll values
  7036.      */
  7037.     getScrollMax: function() {
  7038.       return {
  7039.         left: this.__maxScrollLeft,
  7040.         top: this.__maxScrollTop
  7041.       };
  7042.     },
  7043.  
  7044.     /**
  7045.      * Scrolls by the given amount in px.
  7046.      *
  7047.      * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
  7048.      * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
  7049.      * @param animate {Boolean} Whether the scrolling should happen using an animation
  7050.      */
  7051.  
  7052.     scrollBy: function(left, top, animate) {
  7053.       var self = this;
  7054.  
  7055.       // update scroll vars before refferencing them
  7056.       self.update();
  7057.  
  7058.       var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft;
  7059.       var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop;
  7060.  
  7061.       self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate);
  7062.     },
  7063.  
  7064.     /**
  7065.      * Scrolls to the given position in px.
  7066.      *
  7067.      * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
  7068.      * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
  7069.      * @param animate {Boolean} Whether the scrolling should happen using an animation
  7070.      */
  7071.     scrollTo: function(left, top, animate) {
  7072.       var self = this;
  7073.       if (!animate) {
  7074.         self.el.scrollTop = top;
  7075.         self.el.scrollLeft = left;
  7076.         self.resize();
  7077.         return;
  7078.       }
  7079.       animateScroll(top, left);
  7080.  
  7081.       function animateScroll(Y, X) {
  7082.         // scroll animation loop w/ easing
  7083.         // credit https://gist.github.com/dezinezync/5487119
  7084.         var start = Date.now(),
  7085.           duration = 250, //milliseconds
  7086.           fromY = self.el.scrollTop,
  7087.           fromX = self.el.scrollLeft;
  7088.  
  7089.         if (fromY === Y && fromX === X) {
  7090.           self.resize();
  7091.           return; /* Prevent scrolling to the Y point if already there */
  7092.         }
  7093.  
  7094.         // decelerating to zero velocity
  7095.         function easeOutCubic(t) {
  7096.           return (--t) * t * t + 1;
  7097.         }
  7098.  
  7099.         // scroll loop
  7100.         function animateScrollStep() {
  7101.           var currentTime = Date.now(),
  7102.             time = Math.min(1, ((currentTime - start) / duration)),
  7103.           // where .5 would be 50% of time on a linear scale easedT gives a
  7104.           // fraction based on the easing method
  7105.             easedT = easeOutCubic(time);
  7106.  
  7107.           if (fromY != Y) {
  7108.             self.el.scrollTop = parseInt((easedT * (Y - fromY)) + fromY, 10);
  7109.           }
  7110.           if (fromX != X) {
  7111.             self.el.scrollLeft = parseInt((easedT * (X - fromX)) + fromX, 10);
  7112.           }
  7113.  
  7114.           if (time < 1) {
  7115.             ionic.requestAnimationFrame(animateScrollStep);
  7116.  
  7117.           } else {
  7118.             // done
  7119.             ionic.tap.removeClonedInputs(self.__container, self);
  7120.             self.resize();
  7121.           }
  7122.         }
  7123.  
  7124.         // start scroll loop
  7125.         ionic.requestAnimationFrame(animateScrollStep);
  7126.       }
  7127.     },
  7128.  
  7129.  
  7130.  
  7131.     /*
  7132.      ---------------------------------------------------------------------------
  7133.      PRIVATE API
  7134.      ---------------------------------------------------------------------------
  7135.      */
  7136.  
  7137.     /**
  7138.      * If the scroll view isn't sized correctly on start, wait until we have at least some size
  7139.      */
  7140.     __waitForSize: function() {
  7141.       var self = this;
  7142.  
  7143.       clearTimeout(self.__sizerTimeout);
  7144.  
  7145.       var sizer = function() {
  7146.         self.resize(true);
  7147.       };
  7148.  
  7149.       sizer();
  7150.       self.__sizerTimeout = setTimeout(sizer, 500);
  7151.     },
  7152.  
  7153.  
  7154.     /**
  7155.      * Recomputes scroll minimum values based on client dimensions and content dimensions.
  7156.      */
  7157.     __computeScrollMax: function() {
  7158.       var self = this;
  7159.  
  7160.       self.__maxScrollLeft = Math.max((self.__contentWidth) - self.__clientWidth, 0);
  7161.       self.__maxScrollTop = Math.max((self.__contentHeight) - self.__clientHeight, 0);
  7162.  
  7163.       if (!self.__didWaitForSize && !self.__maxScrollLeft && !self.__maxScrollTop) {
  7164.         self.__didWaitForSize = true;
  7165.         self.__waitForSize();
  7166.       }
  7167.     },
  7168.  
  7169.     __initEventHandlers: function() {
  7170.       var self = this;
  7171.  
  7172.       // Event Handler
  7173.       var container = self.__container;
  7174.       // save height when scroll view is shrunk so we don't need to reflow
  7175.       var scrollViewOffsetHeight;
  7176.  
  7177.       /**
  7178.        * Shrink the scroll view when the keyboard is up if necessary and if the
  7179.        * focused input is below the bottom of the shrunk scroll view, scroll it
  7180.        * into view.
  7181.        */
  7182.       self.scrollChildIntoView = function(e) {
  7183.         //console.log("scrollChildIntoView at: " + Date.now());
  7184.  
  7185.         // D
  7186.         var scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
  7187.         // D - A
  7188.         scrollViewOffsetHeight = container.offsetHeight;
  7189.         var alreadyShrunk = self.isShrunkForKeyboard;
  7190.  
  7191.         var isModal = container.parentNode.classList.contains('modal');
  7192.         // 680px is when the media query for 60% modal width kicks in
  7193.         var isInsetModal = isModal && window.innerWidth >= 680;
  7194.  
  7195.        /*
  7196.         *  _______
  7197.         * |---A---| <- top of scroll view
  7198.         * |       |
  7199.         * |---B---| <- keyboard
  7200.         * |   C   | <- input
  7201.         * |---D---| <- initial bottom of scroll view
  7202.         * |___E___| <- bottom of viewport
  7203.         *
  7204.         *  All commented calculations relative to the top of the viewport (ie E
  7205.         *  is the viewport height, not 0)
  7206.         */
  7207.         if (!alreadyShrunk) {
  7208.           // shrink scrollview so we can actually scroll if the input is hidden
  7209.           // if it isn't shrink so we can scroll to inputs under the keyboard
  7210.           // inset modals won't shrink on Android on their own when the keyboard appears
  7211.           if ( ionic.Platform.isIOS() || ionic.Platform.isFullScreen || isInsetModal ) {
  7212.             // if there are things below the scroll view account for them and
  7213.             // subtract them from the keyboard height when resizing
  7214.             // E - D                         E                         D
  7215.             var scrollBottomOffsetToBottom = e.detail.viewportHeight - scrollBottomOffsetToTop;
  7216.  
  7217.             // 0 or D - B if D > B           E - B                     E - D
  7218.             var keyboardOffset = Math.max(0, e.detail.keyboardHeight - scrollBottomOffsetToBottom);
  7219.  
  7220.             ionic.requestAnimationFrame(function(){
  7221.               // D - A or B - A if D > B       D - A             max(0, D - B)
  7222.               scrollViewOffsetHeight = scrollViewOffsetHeight - keyboardOffset;
  7223.               container.style.height = scrollViewOffsetHeight + "px";
  7224.  
  7225.               //update scroll view
  7226.               self.resize();
  7227.             });
  7228.           }
  7229.  
  7230.           self.isShrunkForKeyboard = true;
  7231.         }
  7232.  
  7233.         /*
  7234.          *  _______
  7235.          * |---A---| <- top of scroll view
  7236.          * |   *   | <- where we want to scroll to
  7237.          * |--B-D--| <- keyboard, bottom of scroll view
  7238.          * |   C   | <- input
  7239.          * |       |
  7240.          * |___E___| <- bottom of viewport
  7241.          *
  7242.          *  All commented calculations relative to the top of the viewport (ie E
  7243.          *  is the viewport height, not 0)
  7244.          */
  7245.         // if the element is positioned under the keyboard scroll it into view
  7246.         if (e.detail.isElementUnderKeyboard) {
  7247.  
  7248.           ionic.requestAnimationFrame(function(){
  7249.             // update D if we shrunk
  7250.             if (self.isShrunkForKeyboard && !alreadyShrunk) {
  7251.               scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
  7252.             }
  7253.  
  7254.             // middle of the scrollview, this is where we want to scroll to
  7255.             // (D - A) / 2
  7256.             var scrollMidpointOffset = scrollViewOffsetHeight * 0.5;
  7257.             //console.log("container.offsetHeight: " + scrollViewOffsetHeight);
  7258.  
  7259.             // middle of the input we want to scroll into view
  7260.             // C
  7261.             var inputMidpoint = ((e.detail.elementBottom + e.detail.elementTop) / 2);
  7262.  
  7263.             // distance from middle of input to the bottom of the scroll view
  7264.             // C - D                                C               D
  7265.             var inputMidpointOffsetToScrollBottom = inputMidpoint - scrollBottomOffsetToTop;
  7266.  
  7267.             //C - D + (D - A)/2          C - D                     (D - A)/ 2
  7268.             var scrollTop = inputMidpointOffsetToScrollBottom + scrollMidpointOffset;
  7269.  
  7270.             if ( scrollTop > 0) {
  7271.               if (ionic.Platform.isIOS()) {
  7272.                 //just shrank scroll view, give it some breathing room before scrolling
  7273.                 setTimeout(function(){
  7274.                   ionic.tap.cloneFocusedInput(container, self);
  7275.                   self.scrollBy(0, scrollTop, true);
  7276.                   self.onScroll();
  7277.                 }, 32);
  7278.               } else {
  7279.                 self.scrollBy(0, scrollTop, true);
  7280.                 self.onScroll();
  7281.               }
  7282.             }
  7283.           });
  7284.         }
  7285.  
  7286.         // Only the first scrollView parent of the element that broadcasted this event
  7287.         // (the active element that needs to be shown) should receive this event
  7288.         e.stopPropagation();
  7289.       };
  7290.  
  7291.       self.resetScrollView = function() {
  7292.         //return scrollview to original height once keyboard has hidden
  7293.         if (self.isShrunkForKeyboard) {
  7294.           self.isShrunkForKeyboard = false;
  7295.           container.style.height = "";
  7296.         }
  7297.         self.resize();
  7298.       };
  7299.  
  7300.       container.addEventListener('scroll', self.onScroll);
  7301.  
  7302.       //Broadcasted when keyboard is shown on some platforms.
  7303.       //See js/utils/keyboard.js
  7304.       container.addEventListener('scrollChildIntoView', self.scrollChildIntoView);
  7305.  
  7306.       // Listen on document because container may not have had the last
  7307.       // keyboardActiveElement, for example after closing a modal with a focused
  7308.       // input and returning to a previously resized scroll view in an ion-content.
  7309.       // Since we can only resize scroll views that are currently visible, just resize
  7310.       // the current scroll view when the keyboard is closed.
  7311.       document.addEventListener('resetScrollView', self.resetScrollView);
  7312.     },
  7313.  
  7314.     __cleanup: function() {
  7315.       var self = this;
  7316.       var container = self.__container;
  7317.  
  7318.       container.removeEventListener('resetScrollView', self.resetScrollView);
  7319.       container.removeEventListener('scroll', self.onScroll);
  7320.  
  7321.       container.removeEventListener('scrollChildIntoView', self.scrollChildIntoView);
  7322.       container.removeEventListener('resetScrollView', self.resetScrollView);
  7323.  
  7324.       ionic.tap.removeClonedInputs(container, self);
  7325.  
  7326.       delete self.__container;
  7327.       delete self.__content;
  7328.       delete self.__indicatorX;
  7329.       delete self.__indicatorY;
  7330.       delete self.options.el;
  7331.  
  7332.       self.resize = self.scrollTo = self.onScroll = self.resetScrollView = NOOP;
  7333.       self.scrollChildIntoView = NOOP;
  7334.       container = null;
  7335.     }
  7336.   });
  7337.  
  7338. })(ionic);
  7339.  
  7340.  
  7341. (function(ionic) {
  7342. 'use strict';
  7343.  
  7344.   var ITEM_CLASS = 'item';
  7345.   var ITEM_CONTENT_CLASS = 'item-content';
  7346.   var ITEM_SLIDING_CLASS = 'item-sliding';
  7347.   var ITEM_OPTIONS_CLASS = 'item-options';
  7348.   var ITEM_PLACEHOLDER_CLASS = 'item-placeholder';
  7349.   var ITEM_REORDERING_CLASS = 'item-reordering';
  7350.   var ITEM_REORDER_BTN_CLASS = 'item-reorder';
  7351.  
  7352.   var DragOp = function() {};
  7353.   DragOp.prototype = {
  7354.     start: function(){},
  7355.     drag: function(){},
  7356.     end: function(){},
  7357.     isSameItem: function() {
  7358.       return false;
  7359.     }
  7360.   };
  7361.  
  7362.   var SlideDrag = function(opts) {
  7363.     this.dragThresholdX = opts.dragThresholdX || 10;
  7364.     this.el = opts.el;
  7365.     this.item = opts.item;
  7366.     this.canSwipe = opts.canSwipe;
  7367.   };
  7368.  
  7369.   SlideDrag.prototype = new DragOp();
  7370.  
  7371.   SlideDrag.prototype.start = function(e) {
  7372.     var content, buttons, offsetX, buttonsWidth;
  7373.  
  7374.     if (!this.canSwipe()) {
  7375.       return;
  7376.     }
  7377.  
  7378.     if (e.target.classList.contains(ITEM_CONTENT_CLASS)) {
  7379.       content = e.target;
  7380.     } else if (e.target.classList.contains(ITEM_CLASS)) {
  7381.       content = e.target.querySelector('.' + ITEM_CONTENT_CLASS);
  7382.     } else {
  7383.       content = ionic.DomUtil.getParentWithClass(e.target, ITEM_CONTENT_CLASS);
  7384.     }
  7385.  
  7386.     // If we don't have a content area as one of our children (or ourselves), skip
  7387.     if (!content) {
  7388.       return;
  7389.     }
  7390.  
  7391.     // Make sure we aren't animating as we slide
  7392.     content.classList.remove(ITEM_SLIDING_CLASS);
  7393.  
  7394.     // Grab the starting X point for the item (for example, so we can tell whether it is open or closed to start)
  7395.     offsetX = parseFloat(content.style[ionic.CSS.TRANSFORM].replace('translate3d(', '').split(',')[0]) || 0;
  7396.  
  7397.     // Grab the buttons
  7398.     buttons = content.parentNode.querySelector('.' + ITEM_OPTIONS_CLASS);
  7399.     if (!buttons) {
  7400.       return;
  7401.     }
  7402.     buttons.classList.remove('invisible');
  7403.  
  7404.     buttonsWidth = buttons.offsetWidth;
  7405.  
  7406.     this._currentDrag = {
  7407.       buttons: buttons,
  7408.       buttonsWidth: buttonsWidth,
  7409.       content: content,
  7410.       startOffsetX: offsetX
  7411.     };
  7412.   };
  7413.  
  7414.   /**
  7415.    * Check if this is the same item that was previously dragged.
  7416.    */
  7417.   SlideDrag.prototype.isSameItem = function(op) {
  7418.     if (op._lastDrag && this._currentDrag) {
  7419.       return this._currentDrag.content == op._lastDrag.content;
  7420.     }
  7421.     return false;
  7422.   };
  7423.  
  7424.   SlideDrag.prototype.clean = function(isInstant) {
  7425.     var lastDrag = this._lastDrag;
  7426.  
  7427.     if (!lastDrag || !lastDrag.content) return;
  7428.  
  7429.     lastDrag.content.style[ionic.CSS.TRANSITION] = '';
  7430.     lastDrag.content.style[ionic.CSS.TRANSFORM] = '';
  7431.     if (isInstant) {
  7432.       lastDrag.content.style[ionic.CSS.TRANSITION] = 'none';
  7433.       makeInvisible();
  7434.       ionic.requestAnimationFrame(function() {
  7435.         lastDrag.content.style[ionic.CSS.TRANSITION] = '';
  7436.       });
  7437.     } else {
  7438.       ionic.requestAnimationFrame(function() {
  7439.         setTimeout(makeInvisible, 250);
  7440.       });
  7441.     }
  7442.     function makeInvisible() {
  7443.       lastDrag.buttons && lastDrag.buttons.classList.add('invisible');
  7444.     }
  7445.   };
  7446.  
  7447.   SlideDrag.prototype.drag = ionic.animationFrameThrottle(function(e) {
  7448.     var buttonsWidth;
  7449.  
  7450.     // We really aren't dragging
  7451.     if (!this._currentDrag) {
  7452.       return;
  7453.     }
  7454.  
  7455.     // Check if we should start dragging. Check if we've dragged past the threshold,
  7456.     // or we are starting from the open state.
  7457.     if (!this._isDragging &&
  7458.         ((Math.abs(e.gesture.deltaX) > this.dragThresholdX) ||
  7459.         (Math.abs(this._currentDrag.startOffsetX) > 0))) {
  7460.       this._isDragging = true;
  7461.     }
  7462.  
  7463.     if (this._isDragging) {
  7464.       buttonsWidth = this._currentDrag.buttonsWidth;
  7465.  
  7466.       // Grab the new X point, capping it at zero
  7467.       var newX = Math.min(0, this._currentDrag.startOffsetX + e.gesture.deltaX);
  7468.  
  7469.       // If the new X position is past the buttons, we need to slow down the drag (rubber band style)
  7470.       if (newX < -buttonsWidth) {
  7471.         // Calculate the new X position, capped at the top of the buttons
  7472.         newX = Math.min(-buttonsWidth, -buttonsWidth + (((e.gesture.deltaX + buttonsWidth) * 0.4)));
  7473.       }
  7474.  
  7475.       this._currentDrag.content.$$ionicOptionsOpen = newX !== 0;
  7476.  
  7477.       this._currentDrag.content.style[ionic.CSS.TRANSFORM] = 'translate3d(' + newX + 'px, 0, 0)';
  7478.       this._currentDrag.content.style[ionic.CSS.TRANSITION] = 'none';
  7479.     }
  7480.   });
  7481.  
  7482.   SlideDrag.prototype.end = function(e, doneCallback) {
  7483.     var self = this;
  7484.  
  7485.     // There is no drag, just end immediately
  7486.     if (!self._currentDrag) {
  7487.       doneCallback && doneCallback();
  7488.       return;
  7489.     }
  7490.  
  7491.     // If we are currently dragging, we want to snap back into place
  7492.     // The final resting point X will be the width of the exposed buttons
  7493.     var restingPoint = -self._currentDrag.buttonsWidth;
  7494.  
  7495.     // Check if the drag didn't clear the buttons mid-point
  7496.     // and we aren't moving fast enough to swipe open
  7497.     if (e.gesture.deltaX > -(self._currentDrag.buttonsWidth / 2)) {
  7498.  
  7499.       // If we are going left but too slow, or going right, go back to resting
  7500.       if (e.gesture.direction == "left" && Math.abs(e.gesture.velocityX) < 0.3) {
  7501.         restingPoint = 0;
  7502.  
  7503.       } else if (e.gesture.direction == "right") {
  7504.         restingPoint = 0;
  7505.       }
  7506.  
  7507.     }
  7508.  
  7509.     ionic.requestAnimationFrame(function() {
  7510.       if (restingPoint === 0) {
  7511.         self._currentDrag.content.style[ionic.CSS.TRANSFORM] = '';
  7512.         var buttons = self._currentDrag.buttons;
  7513.         setTimeout(function() {
  7514.           buttons && buttons.classList.add('invisible');
  7515.         }, 250);
  7516.       } else {
  7517.         self._currentDrag.content.style[ionic.CSS.TRANSFORM] = 'translate3d(' + restingPoint + 'px,0,0)';
  7518.       }
  7519.       self._currentDrag.content.style[ionic.CSS.TRANSITION] = '';
  7520.  
  7521.  
  7522.       // Kill the current drag
  7523.       if (!self._lastDrag) {
  7524.         self._lastDrag = {};
  7525.       }
  7526.       ionic.extend(self._lastDrag, self._currentDrag);
  7527.       if (self._currentDrag) {
  7528.         self._currentDrag.buttons = null;
  7529.         self._currentDrag.content = null;
  7530.       }
  7531.       self._currentDrag = null;
  7532.  
  7533.       // We are done, notify caller
  7534.       doneCallback && doneCallback();
  7535.     });
  7536.   };
  7537.  
  7538.   var ReorderDrag = function(opts) {
  7539.     var self = this;
  7540.  
  7541.     self.dragThresholdY = opts.dragThresholdY || 0;
  7542.     self.onReorder = opts.onReorder;
  7543.     self.listEl = opts.listEl;
  7544.     self.el = self.item = opts.el;
  7545.     self.scrollEl = opts.scrollEl;
  7546.     self.scrollView = opts.scrollView;
  7547.     // Get the True Top of the list el http://www.quirksmode.org/js/findpos.html
  7548.     self.listElTrueTop = 0;
  7549.     if (self.listEl.offsetParent) {
  7550.       var obj = self.listEl;
  7551.       do {
  7552.         self.listElTrueTop += obj.offsetTop;
  7553.         obj = obj.offsetParent;
  7554.       } while (obj);
  7555.     }
  7556.   };
  7557.  
  7558.   ReorderDrag.prototype = new DragOp();
  7559.  
  7560.   ReorderDrag.prototype._moveElement = function(e) {
  7561.     var y = e.gesture.center.pageY +
  7562.       this.scrollView.getValues().top -
  7563.       (this._currentDrag.elementHeight / 2) -
  7564.       this.listElTrueTop;
  7565.     this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(0, ' + y + 'px, 0)';
  7566.   };
  7567.  
  7568.   ReorderDrag.prototype.deregister = function() {
  7569.     this.listEl = this.el = this.scrollEl = this.scrollView = null;
  7570.   };
  7571.  
  7572.   ReorderDrag.prototype.start = function(e) {
  7573.  
  7574.     var startIndex = ionic.DomUtil.getChildIndex(this.el, this.el.nodeName.toLowerCase());
  7575.     var elementHeight = this.el.scrollHeight;
  7576.     var placeholder = this.el.cloneNode(true);
  7577.  
  7578.     placeholder.classList.add(ITEM_PLACEHOLDER_CLASS);
  7579.  
  7580.     this.el.parentNode.insertBefore(placeholder, this.el);
  7581.     this.el.classList.add(ITEM_REORDERING_CLASS);
  7582.  
  7583.     this._currentDrag = {
  7584.       elementHeight: elementHeight,
  7585.       startIndex: startIndex,
  7586.       placeholder: placeholder,
  7587.       scrollHeight: scroll,
  7588.       list: placeholder.parentNode
  7589.     };
  7590.  
  7591.     this._moveElement(e);
  7592.   };
  7593.  
  7594.   ReorderDrag.prototype.drag = ionic.animationFrameThrottle(function(e) {
  7595.     // We really aren't dragging
  7596.     var self = this;
  7597.     if (!this._currentDrag) {
  7598.       return;
  7599.     }
  7600.  
  7601.     var scrollY = 0;
  7602.     var pageY = e.gesture.center.pageY;
  7603.     var offset = this.listElTrueTop;
  7604.  
  7605.     //If we have a scrollView, check scroll boundaries for dragged element and scroll if necessary
  7606.     if (this.scrollView) {
  7607.  
  7608.       var container = this.scrollView.__container;
  7609.       scrollY = this.scrollView.getValues().top;
  7610.  
  7611.       var containerTop = container.offsetTop;
  7612.       var pixelsPastTop = containerTop - pageY + this._currentDrag.elementHeight / 2;
  7613.       var pixelsPastBottom = pageY + this._currentDrag.elementHeight / 2 - containerTop - container.offsetHeight;
  7614.  
  7615.       if (e.gesture.deltaY < 0 && pixelsPastTop > 0 && scrollY > 0) {
  7616.         this.scrollView.scrollBy(null, -pixelsPastTop);
  7617.         //Trigger another drag so the scrolling keeps going
  7618.         ionic.requestAnimationFrame(function() {
  7619.           self.drag(e);
  7620.         });
  7621.       }
  7622.       if (e.gesture.deltaY > 0 && pixelsPastBottom > 0) {
  7623.         if (scrollY < this.scrollView.getScrollMax().top) {
  7624.           this.scrollView.scrollBy(null, pixelsPastBottom);
  7625.           //Trigger another drag so the scrolling keeps going
  7626.           ionic.requestAnimationFrame(function() {
  7627.             self.drag(e);
  7628.           });
  7629.         }
  7630.       }
  7631.     }
  7632.  
  7633.     // Check if we should start dragging. Check if we've dragged past the threshold,
  7634.     // or we are starting from the open state.
  7635.     if (!this._isDragging && Math.abs(e.gesture.deltaY) > this.dragThresholdY) {
  7636.       this._isDragging = true;
  7637.     }
  7638.  
  7639.     if (this._isDragging) {
  7640.       this._moveElement(e);
  7641.  
  7642.       this._currentDrag.currentY = scrollY + pageY - offset;
  7643.  
  7644.       // this._reorderItems();
  7645.     }
  7646.   });
  7647.  
  7648.   // When an item is dragged, we need to reorder any items for sorting purposes
  7649.   ReorderDrag.prototype._getReorderIndex = function() {
  7650.     var self = this;
  7651.  
  7652.     var siblings = Array.prototype.slice.call(self._currentDrag.placeholder.parentNode.children)
  7653.       .filter(function(el) {
  7654.         return el.nodeName === self.el.nodeName && el !== self.el;
  7655.       });
  7656.  
  7657.     var dragOffsetTop = self._currentDrag.currentY;
  7658.     var el;
  7659.     for (var i = 0, len = siblings.length; i < len; i++) {
  7660.       el = siblings[i];
  7661.       if (i === len - 1) {
  7662.         if (dragOffsetTop > el.offsetTop) {
  7663.           return i;
  7664.         }
  7665.       } else if (i === 0) {
  7666.         if (dragOffsetTop < el.offsetTop + el.offsetHeight) {
  7667.           return i;
  7668.         }
  7669.       } else if (dragOffsetTop > el.offsetTop - el.offsetHeight / 2 &&
  7670.                  dragOffsetTop < el.offsetTop + el.offsetHeight) {
  7671.         return i;
  7672.       }
  7673.     }
  7674.     return self._currentDrag.startIndex;
  7675.   };
  7676.  
  7677.   ReorderDrag.prototype.end = function(e, doneCallback) {
  7678.     if (!this._currentDrag) {
  7679.       doneCallback && doneCallback();
  7680.       return;
  7681.     }
  7682.  
  7683.     var placeholder = this._currentDrag.placeholder;
  7684.     var finalIndex = this._getReorderIndex();
  7685.  
  7686.     // Reposition the element
  7687.     this.el.classList.remove(ITEM_REORDERING_CLASS);
  7688.     this.el.style[ionic.CSS.TRANSFORM] = '';
  7689.  
  7690.     placeholder.parentNode.insertBefore(this.el, placeholder);
  7691.     placeholder.parentNode.removeChild(placeholder);
  7692.  
  7693.     this.onReorder && this.onReorder(this.el, this._currentDrag.startIndex, finalIndex);
  7694.  
  7695.     this._currentDrag = {
  7696.       placeholder: null,
  7697.       content: null
  7698.     };
  7699.     this._currentDrag = null;
  7700.     doneCallback && doneCallback();
  7701.   };
  7702.  
  7703.  
  7704.  
  7705.   /**
  7706.    * The ListView handles a list of items. It will process drag animations, edit mode,
  7707.    * and other operations that are common on mobile lists or table views.
  7708.    */
  7709.   ionic.views.ListView = ionic.views.View.inherit({
  7710.     initialize: function(opts) {
  7711.       var self = this;
  7712.  
  7713.       opts = ionic.extend({
  7714.         onReorder: function() {},
  7715.         virtualRemoveThreshold: -200,
  7716.         virtualAddThreshold: 200,
  7717.         canSwipe: function() {
  7718.           return true;
  7719.         }
  7720.       }, opts);
  7721.  
  7722.       ionic.extend(self, opts);
  7723.  
  7724.       if (!self.itemHeight && self.listEl) {
  7725.         self.itemHeight = self.listEl.children[0] && parseInt(self.listEl.children[0].style.height, 10);
  7726.       }
  7727.  
  7728.       self.onRefresh = opts.onRefresh || function() {};
  7729.       self.onRefreshOpening = opts.onRefreshOpening || function() {};
  7730.       self.onRefreshHolding = opts.onRefreshHolding || function() {};
  7731.  
  7732.       var gestureOpts = {};
  7733.       // don't prevent native scrolling
  7734.       if (ionic.DomUtil.getParentOrSelfWithClass(self.el, 'overflow-scroll')) {
  7735.         gestureOpts.prevent_default_directions = ['left', 'right'];
  7736.       }
  7737.  
  7738.       window.ionic.onGesture('release', function(e) {
  7739.         self._handleEndDrag(e);
  7740.       }, self.el, gestureOpts);
  7741.  
  7742.       window.ionic.onGesture('drag', function(e) {
  7743.         self._handleDrag(e);
  7744.       }, self.el, gestureOpts);
  7745.       // Start the drag states
  7746.       self._initDrag();
  7747.     },
  7748.  
  7749.     /**
  7750.      * Be sure to cleanup references.
  7751.      */
  7752.     deregister: function() {
  7753.       this.el = this.listEl = this.scrollEl = this.scrollView = null;
  7754.  
  7755.       // ensure no scrolls have been left frozen
  7756.       if (this.isScrollFreeze) {
  7757.         self.scrollView.freeze(false);
  7758.       }
  7759.     },
  7760.  
  7761.     /**
  7762.      * Called to tell the list to stop refreshing. This is useful
  7763.      * if you are refreshing the list and are done with refreshing.
  7764.      */
  7765.     stopRefreshing: function() {
  7766.       var refresher = this.el.querySelector('.list-refresher');
  7767.       refresher.style.height = '0';
  7768.     },
  7769.  
  7770.     /**
  7771.      * If we scrolled and have virtual mode enabled, compute the window
  7772.      * of active elements in order to figure out the viewport to render.
  7773.      */
  7774.     didScroll: function(e) {
  7775.       var self = this;
  7776.  
  7777.       if (self.isVirtual) {
  7778.         var itemHeight = self.itemHeight;
  7779.  
  7780.         // Grab the total height of the list
  7781.         var scrollHeight = e.target.scrollHeight;
  7782.  
  7783.         // Get the viewport height
  7784.         var viewportHeight = self.el.parentNode.offsetHeight;
  7785.  
  7786.         // High water is the pixel position of the first element to include (everything before
  7787.         // that will be removed)
  7788.         var highWater = Math.max(0, e.scrollTop + self.virtualRemoveThreshold);
  7789.  
  7790.         // Low water is the pixel position of the last element to include (everything after
  7791.         // that will be removed)
  7792.         var lowWater = Math.min(scrollHeight, Math.abs(e.scrollTop) + viewportHeight + self.virtualAddThreshold);
  7793.  
  7794.         // Get the first and last elements in the list based on how many can fit
  7795.         // between the pixel range of lowWater and highWater
  7796.         var first = parseInt(Math.abs(highWater / itemHeight), 10);
  7797.         var last = parseInt(Math.abs(lowWater / itemHeight), 10);
  7798.  
  7799.         // Get the items we need to remove
  7800.         self._virtualItemsToRemove = Array.prototype.slice.call(self.listEl.children, 0, first);
  7801.  
  7802.         self.renderViewport && self.renderViewport(highWater, lowWater, first, last);
  7803.       }
  7804.     },
  7805.  
  7806.     didStopScrolling: function() {
  7807.       if (this.isVirtual) {
  7808.         for (var i = 0; i < this._virtualItemsToRemove.length; i++) {
  7809.           //el.parentNode.removeChild(el);
  7810.           this.didHideItem && this.didHideItem(i);
  7811.         }
  7812.         // Once scrolling stops, check if we need to remove old items
  7813.  
  7814.       }
  7815.     },
  7816.  
  7817.     /**
  7818.      * Clear any active drag effects on the list.
  7819.      */
  7820.     clearDragEffects: function(isInstant) {
  7821.       if (this._lastDragOp) {
  7822.         this._lastDragOp.clean && this._lastDragOp.clean(isInstant);
  7823.         this._lastDragOp.deregister && this._lastDragOp.deregister();
  7824.         this._lastDragOp = null;
  7825.       }
  7826.     },
  7827.  
  7828.     _initDrag: function() {
  7829.       // Store the last one
  7830.       if (this._lastDragOp) {
  7831.         this._lastDragOp.deregister && this._lastDragOp.deregister();
  7832.       }
  7833.       this._lastDragOp = this._dragOp;
  7834.  
  7835.       this._dragOp = null;
  7836.     },
  7837.  
  7838.     // Return the list item from the given target
  7839.     _getItem: function(target) {
  7840.       while (target) {
  7841.         if (target.classList && target.classList.contains(ITEM_CLASS)) {
  7842.           return target;
  7843.         }
  7844.         target = target.parentNode;
  7845.       }
  7846.       return null;
  7847.     },
  7848.  
  7849.  
  7850.     _startDrag: function(e) {
  7851.       var self = this;
  7852.  
  7853.       self._isDragging = false;
  7854.  
  7855.       var lastDragOp = self._lastDragOp;
  7856.       var item;
  7857.  
  7858.       // If we have an open SlideDrag and we're scrolling the list. Clear it.
  7859.       if (self._didDragUpOrDown && lastDragOp instanceof SlideDrag) {
  7860.           lastDragOp.clean && lastDragOp.clean();
  7861.       }
  7862.  
  7863.       // Check if this is a reorder drag
  7864.       if (ionic.DomUtil.getParentOrSelfWithClass(e.target, ITEM_REORDER_BTN_CLASS) && (e.gesture.direction == 'up' || e.gesture.direction == 'down')) {
  7865.         item = self._getItem(e.target);
  7866.  
  7867.         if (item) {
  7868.           self._dragOp = new ReorderDrag({
  7869.             listEl: self.el,
  7870.             el: item,
  7871.             scrollEl: self.scrollEl,
  7872.             scrollView: self.scrollView,
  7873.             onReorder: function(el, start, end) {
  7874.               self.onReorder && self.onReorder(el, start, end);
  7875.             }
  7876.           });
  7877.           self._dragOp.start(e);
  7878.           e.preventDefault();
  7879.         }
  7880.       }
  7881.  
  7882.       // Or check if this is a swipe to the side drag
  7883.       else if (!self._didDragUpOrDown && (e.gesture.direction == 'left' || e.gesture.direction == 'right') && Math.abs(e.gesture.deltaX) > 5) {
  7884.  
  7885.         // Make sure this is an item with buttons
  7886.         item = self._getItem(e.target);
  7887.         if (item && item.querySelector('.item-options')) {
  7888.           self._dragOp = new SlideDrag({
  7889.             el: self.el,
  7890.             item: item,
  7891.             canSwipe: self.canSwipe
  7892.           });
  7893.           self._dragOp.start(e);
  7894.           e.preventDefault();
  7895.           self.isScrollFreeze = self.scrollView.freeze(true);
  7896.         }
  7897.       }
  7898.  
  7899.       // If we had a last drag operation and this is a new one on a different item, clean that last one
  7900.       if (lastDragOp && self._dragOp && !self._dragOp.isSameItem(lastDragOp) && e.defaultPrevented) {
  7901.         lastDragOp.clean && lastDragOp.clean();
  7902.       }
  7903.     },
  7904.  
  7905.  
  7906.     _handleEndDrag: function(e) {
  7907.       var self = this;
  7908.  
  7909.       if (self.scrollView) {
  7910.         self.isScrollFreeze = self.scrollView.freeze(false);
  7911.       }
  7912.  
  7913.       self._didDragUpOrDown = false;
  7914.  
  7915.       if (!self._dragOp) {
  7916.         return;
  7917.       }
  7918.  
  7919.       self._dragOp.end(e, function() {
  7920.         self._initDrag();
  7921.       });
  7922.     },
  7923.  
  7924.     /**
  7925.      * Process the drag event to move the item to the left or right.
  7926.      */
  7927.     _handleDrag: function(e) {
  7928.       var self = this;
  7929.  
  7930.       if (Math.abs(e.gesture.deltaY) > 5) {
  7931.         self._didDragUpOrDown = true;
  7932.       }
  7933.  
  7934.       // If we get a drag event, make sure we aren't in another drag, then check if we should
  7935.       // start one
  7936.       if (!self.isDragging && !self._dragOp) {
  7937.         self._startDrag(e);
  7938.       }
  7939.  
  7940.       // No drag still, pass it up
  7941.       if (!self._dragOp) {
  7942.         return;
  7943.       }
  7944.  
  7945.       e.gesture.srcEvent.preventDefault();
  7946.       self._dragOp.drag(e);
  7947.     }
  7948.  
  7949.   });
  7950.  
  7951. })(ionic);
  7952.  
  7953. (function(ionic) {
  7954. 'use strict';
  7955.  
  7956.   ionic.views.Modal = ionic.views.View.inherit({
  7957.     initialize: function(opts) {
  7958.       opts = ionic.extend({
  7959.         focusFirstInput: false,
  7960.         unfocusOnHide: true,
  7961.         focusFirstDelay: 600,
  7962.         backdropClickToClose: true,
  7963.         hardwareBackButtonClose: true,
  7964.       }, opts);
  7965.  
  7966.       ionic.extend(this, opts);
  7967.  
  7968.       this.el = opts.el;
  7969.     },
  7970.     show: function() {
  7971.       var self = this;
  7972.  
  7973.       if(self.focusFirstInput) {
  7974.         // Let any animations run first
  7975.         window.setTimeout(function() {
  7976.           var input = self.el.querySelector('input, textarea');
  7977.           input && input.focus && input.focus();
  7978.         }, self.focusFirstDelay);
  7979.       }
  7980.     },
  7981.     hide: function() {
  7982.       // Unfocus all elements
  7983.       if(this.unfocusOnHide) {
  7984.         var inputs = this.el.querySelectorAll('input, textarea');
  7985.         // Let any animations run first
  7986.         window.setTimeout(function() {
  7987.           for(var i = 0; i < inputs.length; i++) {
  7988.             inputs[i].blur && inputs[i].blur();
  7989.           }
  7990.         });
  7991.       }
  7992.     }
  7993.   });
  7994.  
  7995. })(ionic);
  7996.  
  7997. (function(ionic) {
  7998. 'use strict';
  7999.  
  8000.   /**
  8001.    * The side menu view handles one of the side menu's in a Side Menu Controller
  8002.    * configuration.
  8003.    * It takes a DOM reference to that side menu element.
  8004.    */
  8005.   ionic.views.SideMenu = ionic.views.View.inherit({
  8006.     initialize: function(opts) {
  8007.       this.el = opts.el;
  8008.       this.isEnabled = (typeof opts.isEnabled === 'undefined') ? true : opts.isEnabled;
  8009.       this.setWidth(opts.width);
  8010.     },
  8011.     getFullWidth: function() {
  8012.       return this.width;
  8013.     },
  8014.     setWidth: function(width) {
  8015.       this.width = width;
  8016.       this.el.style.width = width + 'px';
  8017.     },
  8018.     setIsEnabled: function(isEnabled) {
  8019.       this.isEnabled = isEnabled;
  8020.     },
  8021.     bringUp: function() {
  8022.       if(this.el.style.zIndex !== '0') {
  8023.         this.el.style.zIndex = '0';
  8024.       }
  8025.     },
  8026.     pushDown: function() {
  8027.       if(this.el.style.zIndex !== '-1') {
  8028.         this.el.style.zIndex = '-1';
  8029.       }
  8030.     }
  8031.   });
  8032.  
  8033.   ionic.views.SideMenuContent = ionic.views.View.inherit({
  8034.     initialize: function(opts) {
  8035.       ionic.extend(this, {
  8036.         animationClass: 'menu-animated',
  8037.         onDrag: function() {},
  8038.         onEndDrag: function() {}
  8039.       }, opts);
  8040.  
  8041.       ionic.onGesture('drag', ionic.proxy(this._onDrag, this), this.el);
  8042.       ionic.onGesture('release', ionic.proxy(this._onEndDrag, this), this.el);
  8043.     },
  8044.     _onDrag: function(e) {
  8045.       this.onDrag && this.onDrag(e);
  8046.     },
  8047.     _onEndDrag: function(e) {
  8048.       this.onEndDrag && this.onEndDrag(e);
  8049.     },
  8050.     disableAnimation: function() {
  8051.       this.el.classList.remove(this.animationClass);
  8052.     },
  8053.     enableAnimation: function() {
  8054.       this.el.classList.add(this.animationClass);
  8055.     },
  8056.     getTranslateX: function() {
  8057.       return parseFloat(this.el.style[ionic.CSS.TRANSFORM].replace('translate3d(', '').split(',')[0]);
  8058.     },
  8059.     setTranslateX: ionic.animationFrameThrottle(function(x) {
  8060.       this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + x + 'px, 0, 0)';
  8061.     })
  8062.   });
  8063.  
  8064. })(ionic);
  8065.  
  8066. /*
  8067.  * Adapted from Swipe.js 2.0
  8068.  *
  8069.  * Brad Birdsall
  8070.  * Copyright 2013, MIT License
  8071.  *
  8072. */
  8073.  
  8074. (function(ionic) {
  8075. 'use strict';
  8076.  
  8077. ionic.views.Slider = ionic.views.View.inherit({
  8078.   initialize: function (options) {
  8079.     var slider = this;
  8080.  
  8081.     // utilities
  8082.     var noop = function() {}; // simple no operation function
  8083.     var offloadFn = function(fn) { setTimeout(fn || noop, 0); }; // offload a functions execution
  8084.  
  8085.     // check browser capabilities
  8086.     var browser = {
  8087.       addEventListener: !!window.addEventListener,
  8088.       touch: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch,
  8089.       transitions: (function(temp) {
  8090.         var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];
  8091.         for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true;
  8092.         return false;
  8093.       })(document.createElement('swipe'))
  8094.     };
  8095.  
  8096.  
  8097.     var container = options.el;
  8098.  
  8099.     // quit if no root element
  8100.     if (!container) return;
  8101.     var element = container.children[0];
  8102.     var slides, slidePos, width, length;
  8103.     options = options || {};
  8104.     var index = parseInt(options.startSlide, 10) || 0;
  8105.     var speed = options.speed || 300;
  8106.     options.continuous = options.continuous !== undefined ? options.continuous : true;
  8107.  
  8108.     function setup() {
  8109.  
  8110.       // do not setup if the container has no width
  8111.       if (!container.offsetWidth) {
  8112.         return;
  8113.       }
  8114.  
  8115.       // cache slides
  8116.       slides = element.children;
  8117.       length = slides.length;
  8118.  
  8119.       // set continuous to false if only one slide
  8120.       if (slides.length < 2) options.continuous = false;
  8121.  
  8122.       //special case if two slides
  8123.       if (browser.transitions && options.continuous && slides.length < 3) {
  8124.         element.appendChild(slides[0].cloneNode(true));
  8125.         element.appendChild(element.children[1].cloneNode(true));
  8126.         slides = element.children;
  8127.       }
  8128.  
  8129.       // create an array to store current positions of each slide
  8130.       slidePos = new Array(slides.length);
  8131.  
  8132.       // determine width of each slide
  8133.       width = container.offsetWidth || container.getBoundingClientRect().width;
  8134.  
  8135.       element.style.width = (slides.length * width) + 'px';
  8136.  
  8137.       // stack elements
  8138.       var pos = slides.length;
  8139.       while(pos--) {
  8140.  
  8141.         var slide = slides[pos];
  8142.  
  8143.         slide.style.width = width + 'px';
  8144.         slide.setAttribute('data-index', pos);
  8145.  
  8146.         if (browser.transitions) {
  8147.           slide.style.left = (pos * -width) + 'px';
  8148.           move(pos, index > pos ? -width : (index < pos ? width : 0), 0);
  8149.         }
  8150.  
  8151.       }
  8152.  
  8153.       // reposition elements before and after index
  8154.       if (options.continuous && browser.transitions) {
  8155.         move(circle(index - 1), -width, 0);
  8156.         move(circle(index + 1), width, 0);
  8157.       }
  8158.  
  8159.       if (!browser.transitions) element.style.left = (index * -width) + 'px';
  8160.  
  8161.       container.style.visibility = 'visible';
  8162.  
  8163.       options.slidesChanged && options.slidesChanged();
  8164.     }
  8165.  
  8166.     function prev(slideSpeed) {
  8167.  
  8168.       if (options.continuous) slide(index - 1, slideSpeed);
  8169.       else if (index) slide(index - 1, slideSpeed);
  8170.  
  8171.     }
  8172.  
  8173.     function next(slideSpeed) {
  8174.  
  8175.       if (options.continuous) slide(index + 1, slideSpeed);
  8176.       else if (index < slides.length - 1) slide(index + 1, slideSpeed);
  8177.  
  8178.     }
  8179.  
  8180.     function circle(index) {
  8181.  
  8182.       // a simple positive modulo using slides.length
  8183.       return (slides.length + (index % slides.length)) % slides.length;
  8184.  
  8185.     }
  8186.  
  8187.     function slide(to, slideSpeed) {
  8188.  
  8189.       // do nothing if already on requested slide
  8190.       if (index == to) return;
  8191.  
  8192.       if (browser.transitions) {
  8193.  
  8194.         var direction = Math.abs(index - to) / (index - to); // 1: backward, -1: forward
  8195.  
  8196.         // get the actual position of the slide
  8197.         if (options.continuous) {
  8198.           var naturalDirection = direction;
  8199.           direction = -slidePos[circle(to)] / width;
  8200.  
  8201.           // if going forward but to < index, use to = slides.length + to
  8202.           // if going backward but to > index, use to = -slides.length + to
  8203.           if (direction !== naturalDirection) to = -direction * slides.length + to;
  8204.  
  8205.         }
  8206.  
  8207.         var diff = Math.abs(index - to) - 1;
  8208.  
  8209.         // move all the slides between index and to in the right direction
  8210.         while (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0);
  8211.  
  8212.         to = circle(to);
  8213.  
  8214.         move(index, width * direction, slideSpeed || speed);
  8215.         move(to, 0, slideSpeed || speed);
  8216.  
  8217.         if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place
  8218.  
  8219.       } else {
  8220.  
  8221.         to = circle(to);
  8222.         animate(index * -width, to * -width, slideSpeed || speed);
  8223.         //no fallback for a circular continuous if the browser does not accept transitions
  8224.       }
  8225.  
  8226.       index = to;
  8227.       offloadFn(options.callback && options.callback(index, slides[index]));
  8228.     }
  8229.  
  8230.     function move(index, dist, speed) {
  8231.  
  8232.       translate(index, dist, speed);
  8233.       slidePos[index] = dist;
  8234.  
  8235.     }
  8236.  
  8237.     function translate(index, dist, speed) {
  8238.  
  8239.       var slide = slides[index];
  8240.       var style = slide && slide.style;
  8241.  
  8242.       if (!style) return;
  8243.  
  8244.       style.webkitTransitionDuration =
  8245.       style.MozTransitionDuration =
  8246.       style.msTransitionDuration =
  8247.       style.OTransitionDuration =
  8248.       style.transitionDuration = speed + 'ms';
  8249.  
  8250.       style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)';
  8251.       style.msTransform =
  8252.       style.MozTransform =
  8253.       style.OTransform = 'translateX(' + dist + 'px)';
  8254.  
  8255.     }
  8256.  
  8257.     function animate(from, to, speed) {
  8258.  
  8259.       // if not an animation, just reposition
  8260.       if (!speed) {
  8261.  
  8262.         element.style.left = to + 'px';
  8263.         return;
  8264.  
  8265.       }
  8266.  
  8267.       var start = +new Date();
  8268.  
  8269.       var timer = setInterval(function() {
  8270.  
  8271.         var timeElap = +new Date() - start;
  8272.  
  8273.         if (timeElap > speed) {
  8274.  
  8275.           element.style.left = to + 'px';
  8276.  
  8277.           if (delay) begin();
  8278.  
  8279.           options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
  8280.  
  8281.           clearInterval(timer);
  8282.           return;
  8283.  
  8284.         }
  8285.  
  8286.         element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px';
  8287.  
  8288.       }, 4);
  8289.  
  8290.     }
  8291.  
  8292.     // setup auto slideshow
  8293.     var delay = options.auto || 0;
  8294.     var interval;
  8295.  
  8296.     function begin() {
  8297.  
  8298.       interval = setTimeout(next, delay);
  8299.  
  8300.     }
  8301.  
  8302.     function stop() {
  8303.  
  8304.       delay = options.auto || 0;
  8305.       clearTimeout(interval);
  8306.  
  8307.     }
  8308.  
  8309.  
  8310.     // setup initial vars
  8311.     var start = {};
  8312.     var delta = {};
  8313.     var isScrolling;
  8314.  
  8315.     // setup event capturing
  8316.     var events = {
  8317.  
  8318.       handleEvent: function(event) {
  8319.         if(event.type == 'mousedown' || event.type == 'mouseup' || event.type == 'mousemove') {
  8320.           event.touches = [{
  8321.             pageX: event.pageX,
  8322.             pageY: event.pageY
  8323.           }];
  8324.         }
  8325.  
  8326.         switch (event.type) {
  8327.           case 'mousedown': this.start(event); break;
  8328.           case 'touchstart': this.start(event); break;
  8329.           case 'touchmove': this.touchmove(event); break;
  8330.           case 'mousemove': this.touchmove(event); break;
  8331.           case 'touchend': offloadFn(this.end(event)); break;
  8332.           case 'mouseup': offloadFn(this.end(event)); break;
  8333.           case 'webkitTransitionEnd':
  8334.           case 'msTransitionEnd':
  8335.           case 'oTransitionEnd':
  8336.           case 'otransitionend':
  8337.           case 'transitionend': offloadFn(this.transitionEnd(event)); break;
  8338.           case 'resize': offloadFn(setup); break;
  8339.         }
  8340.  
  8341.         if (options.stopPropagation) event.stopPropagation();
  8342.  
  8343.       },
  8344.       start: function(event) {
  8345.  
  8346.         var touches = event.touches[0];
  8347.  
  8348.         // measure start values
  8349.         start = {
  8350.  
  8351.           // get initial touch coords
  8352.           x: touches.pageX,
  8353.           y: touches.pageY,
  8354.  
  8355.           // store time to determine touch duration
  8356.           time: +new Date()
  8357.  
  8358.         };
  8359.  
  8360.         // used for testing first move event
  8361.         isScrolling = undefined;
  8362.  
  8363.         // reset delta and end measurements
  8364.         delta = {};
  8365.  
  8366.         // attach touchmove and touchend listeners
  8367.         if(browser.touch) {
  8368.           element.addEventListener('touchmove', this, false);
  8369.           element.addEventListener('touchend', this, false);
  8370.         }
  8371.         // *CHANGE* chrome scrolling fix. removed 'else' statement
  8372.         element.addEventListener('mousemove', this, false);
  8373.         element.addEventListener('mouseup', this, false);
  8374.         document.addEventListener('mouseup', this, false);
  8375.  
  8376.       },
  8377.       touchmove: function(event) {
  8378.  
  8379.         // ensure swiping with one touch and not pinching
  8380.         // ensure sliding is enabled
  8381.         if (event.touches.length > 1 ||
  8382.             event.scale && event.scale !== 1 ||
  8383.             slider.slideIsDisabled) {
  8384.           return;
  8385.         }
  8386.  
  8387.         if (options.disableScroll) event.preventDefault();
  8388.  
  8389.         var touches = event.touches[0];
  8390.  
  8391.         // measure change in x and y
  8392.         delta = {
  8393.           x: touches.pageX - start.x,
  8394.           y: touches.pageY - start.y
  8395.         };
  8396.  
  8397.         // determine if scrolling test has run - one time test
  8398.         if ( typeof isScrolling == 'undefined') {
  8399.           isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) );
  8400.         }
  8401.  
  8402.         // if user is not trying to scroll vertically
  8403.         if (!isScrolling) {
  8404.  
  8405.           // prevent native scrolling
  8406.           event.preventDefault();
  8407.  
  8408.           // stop slideshow
  8409.           stop();
  8410.  
  8411.           // increase resistance if first or last slide
  8412.           if (options.continuous) { // we don't add resistance at the end
  8413.  
  8414.             translate(circle(index - 1), delta.x + slidePos[circle(index - 1)], 0);
  8415.             translate(index, delta.x + slidePos[index], 0);
  8416.             translate(circle(index + 1), delta.x + slidePos[circle(index + 1)], 0);
  8417.  
  8418.           } else {
  8419.  
  8420.             delta.x =
  8421.               delta.x /
  8422.                 ( (!index && delta.x > 0 ||         // if first slide and sliding left
  8423.                   index == slides.length - 1 &&     // or if last slide and sliding right
  8424.                   delta.x < 0                       // and if sliding at all
  8425.                 ) ?
  8426.                 ( Math.abs(delta.x) / width + 1 )      // determine resistance level
  8427.                 : 1 );                                 // no resistance if false
  8428.  
  8429.             // translate 1:1
  8430.             translate(index - 1, delta.x + slidePos[index - 1], 0);
  8431.             translate(index, delta.x + slidePos[index], 0);
  8432.             translate(index + 1, delta.x + slidePos[index + 1], 0);
  8433.           }
  8434.  
  8435.           options.onDrag && options.onDrag();
  8436.         }
  8437.  
  8438.       },
  8439.       end: function() {
  8440.  
  8441.         // measure duration
  8442.         var duration = +new Date() - start.time;
  8443.  
  8444.         // determine if slide attempt triggers next/prev slide
  8445.         var isValidSlide =
  8446.               Number(duration) < 250 &&         // if slide duration is less than 250ms
  8447.               Math.abs(delta.x) > 20 ||         // and if slide amt is greater than 20px
  8448.               Math.abs(delta.x) > width / 2;      // or if slide amt is greater than half the width
  8449.  
  8450.         // determine if slide attempt is past start and end
  8451.         var isPastBounds = (!index && delta.x > 0) ||      // if first slide and slide amt is greater than 0
  8452.               (index == slides.length - 1 && delta.x < 0); // or if last slide and slide amt is less than 0
  8453.  
  8454.         if (options.continuous) isPastBounds = false;
  8455.  
  8456.         // determine direction of swipe (true:right, false:left)
  8457.         var direction = delta.x < 0;
  8458.  
  8459.         // if not scrolling vertically
  8460.         if (!isScrolling) {
  8461.  
  8462.           if (isValidSlide && !isPastBounds) {
  8463.  
  8464.             if (direction) {
  8465.  
  8466.               if (options.continuous) { // we need to get the next in this direction in place
  8467.  
  8468.                 move(circle(index - 1), -width, 0);
  8469.                 move(circle(index + 2), width, 0);
  8470.  
  8471.               } else {
  8472.                 move(index - 1, -width, 0);
  8473.               }
  8474.  
  8475.               move(index, slidePos[index] - width, speed);
  8476.               move(circle(index + 1), slidePos[circle(index + 1)] - width, speed);
  8477.               index = circle(index + 1);
  8478.  
  8479.             } else {
  8480.               if (options.continuous) { // we need to get the next in this direction in place
  8481.  
  8482.                 move(circle(index + 1), width, 0);
  8483.                 move(circle(index - 2), -width, 0);
  8484.  
  8485.               } else {
  8486.                 move(index + 1, width, 0);
  8487.               }
  8488.  
  8489.               move(index, slidePos[index] + width, speed);
  8490.               move(circle(index - 1), slidePos[circle(index - 1)] + width, speed);
  8491.               index = circle(index - 1);
  8492.  
  8493.             }
  8494.  
  8495.             options.callback && options.callback(index, slides[index]);
  8496.  
  8497.           } else {
  8498.  
  8499.             if (options.continuous) {
  8500.  
  8501.               move(circle(index - 1), -width, speed);
  8502.               move(index, 0, speed);
  8503.               move(circle(index + 1), width, speed);
  8504.  
  8505.             } else {
  8506.  
  8507.               move(index - 1, -width, speed);
  8508.               move(index, 0, speed);
  8509.               move(index + 1, width, speed);
  8510.             }
  8511.  
  8512.           }
  8513.  
  8514.         }
  8515.  
  8516.         // kill touchmove and touchend event listeners until touchstart called again
  8517.         if(browser.touch) {
  8518.           element.removeEventListener('touchmove', events, false);
  8519.           element.removeEventListener('touchend', events, false);
  8520.         }
  8521.         // *CHANGE* chrome scrolling fix. removed 'else' statement
  8522.         element.removeEventListener('mousemove', events, false);
  8523.         element.removeEventListener('mouseup', events, false);
  8524.         document.removeEventListener('mouseup', events, false);
  8525.  
  8526.         options.onDragEnd && options.onDragEnd();
  8527.       },
  8528.       transitionEnd: function(event) {
  8529.  
  8530.         if (parseInt(event.target.getAttribute('data-index'), 10) == index) {
  8531.  
  8532.           if (delay) begin();
  8533.  
  8534.           options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
  8535.  
  8536.         }
  8537.  
  8538.       }
  8539.  
  8540.     };
  8541.  
  8542.     // Public API
  8543.     this.update = function() {
  8544.       setTimeout(setup);
  8545.     };
  8546.     this.setup = function() {
  8547.       setup();
  8548.     };
  8549.  
  8550.     this.loop = function(value) {
  8551.       if (arguments.length) options.continuous = !!value;
  8552.       return options.continuous;
  8553.     };
  8554.  
  8555.     this.enableSlide = function(shouldEnable) {
  8556.       if (arguments.length) {
  8557.         this.slideIsDisabled = !shouldEnable;
  8558.       }
  8559.       return !this.slideIsDisabled;
  8560.     };
  8561.  
  8562.     this.slide = this.select = function(to, speed) {
  8563.       // cancel slideshow
  8564.       stop();
  8565.  
  8566.       slide(to, speed);
  8567.     };
  8568.  
  8569.     this.prev = this.previous = function() {
  8570.       // cancel slideshow
  8571.       stop();
  8572.  
  8573.       prev();
  8574.     };
  8575.  
  8576.     this.next = function() {
  8577.       // cancel slideshow
  8578.       stop();
  8579.  
  8580.       next();
  8581.     };
  8582.  
  8583.     this.stop = function() {
  8584.       // cancel slideshow
  8585.       stop();
  8586.     };
  8587.  
  8588.     this.start = function() {
  8589.       begin();
  8590.     };
  8591.  
  8592.     this.autoPlay = function(newDelay) {
  8593.       if (!delay || delay < 0) {
  8594.         stop();
  8595.       } else {
  8596.         delay = newDelay;
  8597.         begin();
  8598.       }
  8599.     };
  8600.  
  8601.     this.currentIndex = this.selected = function() {
  8602.       // return current index position
  8603.       return index;
  8604.     };
  8605.  
  8606.     this.slidesCount = this.count = function() {
  8607.       // return total number of slides
  8608.       return length;
  8609.     };
  8610.  
  8611.     this.kill = function() {
  8612.       // cancel slideshow
  8613.       stop();
  8614.  
  8615.       // reset element
  8616.       element.style.width = '';
  8617.       element.style.left = '';
  8618.  
  8619.       // reset slides so no refs are held on to
  8620.       slides && (slides = []);
  8621.  
  8622.       // removed event listeners
  8623.       if (browser.addEventListener) {
  8624.  
  8625.         // remove current event listeners
  8626.         element.removeEventListener('touchstart', events, false);
  8627.         element.removeEventListener('webkitTransitionEnd', events, false);
  8628.         element.removeEventListener('msTransitionEnd', events, false);
  8629.         element.removeEventListener('oTransitionEnd', events, false);
  8630.         element.removeEventListener('otransitionend', events, false);
  8631.         element.removeEventListener('transitionend', events, false);
  8632.         window.removeEventListener('resize', events, false);
  8633.  
  8634.       }
  8635.       else {
  8636.  
  8637.         window.onresize = null;
  8638.  
  8639.       }
  8640.     };
  8641.  
  8642.     this.load = function() {
  8643.       // trigger setup
  8644.       setup();
  8645.  
  8646.       // start auto slideshow if applicable
  8647.       if (delay) begin();
  8648.  
  8649.  
  8650.       // add event listeners
  8651.       if (browser.addEventListener) {
  8652.  
  8653.         // set touchstart event on element
  8654.         if (browser.touch) {
  8655.           element.addEventListener('touchstart', events, false);
  8656.         }
  8657.         // *CHANGE* chrome scrolling fix. removed 'else' statement
  8658.         element.addEventListener('mousedown', events, false);
  8659.  
  8660.  
  8661.         if (browser.transitions) {
  8662.           element.addEventListener('webkitTransitionEnd', events, false);
  8663.           element.addEventListener('msTransitionEnd', events, false);
  8664.           element.addEventListener('oTransitionEnd', events, false);
  8665.           element.addEventListener('otransitionend', events, false);
  8666.           element.addEventListener('transitionend', events, false);
  8667.         }
  8668.  
  8669.         // set resize event on window
  8670.         window.addEventListener('resize', events, false);
  8671.  
  8672.       } else {
  8673.  
  8674.         window.onresize = function () { setup(); }; // to play nice with old IE
  8675.  
  8676.       }
  8677.     };
  8678.  
  8679.   }
  8680. });
  8681.  
  8682. })(ionic);
  8683.  
  8684. (function(ionic) {
  8685. 'use strict';
  8686.  
  8687.   ionic.views.Toggle = ionic.views.View.inherit({
  8688.     initialize: function(opts) {
  8689.       var self = this;
  8690.  
  8691.       this.el = opts.el;
  8692.       this.checkbox = opts.checkbox;
  8693.       this.track = opts.track;
  8694.       this.handle = opts.handle;
  8695.       this.openPercent = -1;
  8696.       this.onChange = opts.onChange || function() {};
  8697.  
  8698.       this.triggerThreshold = opts.triggerThreshold || 20;
  8699.  
  8700.       this.dragStartHandler = function(e) {
  8701.         self.dragStart(e);
  8702.       };
  8703.       this.dragHandler = function(e) {
  8704.         self.drag(e);
  8705.       };
  8706.       this.holdHandler = function(e) {
  8707.         self.hold(e);
  8708.       };
  8709.       this.releaseHandler = function(e) {
  8710.         self.release(e);
  8711.       };
  8712.  
  8713.       this.dragStartGesture = ionic.onGesture('dragstart', this.dragStartHandler, this.el);
  8714.       this.dragGesture = ionic.onGesture('drag', this.dragHandler, this.el);
  8715.       this.dragHoldGesture = ionic.onGesture('hold', this.holdHandler, this.el);
  8716.       this.dragReleaseGesture = ionic.onGesture('release', this.releaseHandler, this.el);
  8717.     },
  8718.  
  8719.     destroy: function() {
  8720.       ionic.offGesture(this.dragStartGesture, 'dragstart', this.dragStartGesture);
  8721.       ionic.offGesture(this.dragGesture, 'drag', this.dragGesture);
  8722.       ionic.offGesture(this.dragHoldGesture, 'hold', this.holdHandler);
  8723.       ionic.offGesture(this.dragReleaseGesture, 'release', this.releaseHandler);
  8724.     },
  8725.  
  8726.     tap: function() {
  8727.       if(this.el.getAttribute('disabled') !== 'disabled') {
  8728.         this.val( !this.checkbox.checked );
  8729.       }
  8730.     },
  8731.  
  8732.     dragStart: function(e) {
  8733.       if(this.checkbox.disabled) return;
  8734.  
  8735.       this._dragInfo = {
  8736.         width: this.el.offsetWidth,
  8737.         left: this.el.offsetLeft,
  8738.         right: this.el.offsetLeft + this.el.offsetWidth,
  8739.         triggerX: this.el.offsetWidth / 2,
  8740.         initialState: this.checkbox.checked
  8741.       };
  8742.  
  8743.       // Stop any parent dragging
  8744.       e.gesture.srcEvent.preventDefault();
  8745.  
  8746.       // Trigger hold styles
  8747.       this.hold(e);
  8748.     },
  8749.  
  8750.     drag: function(e) {
  8751.       var self = this;
  8752.       if(!this._dragInfo) { return; }
  8753.  
  8754.       // Stop any parent dragging
  8755.       e.gesture.srcEvent.preventDefault();
  8756.  
  8757.       ionic.requestAnimationFrame(function () {
  8758.         if (!self._dragInfo) { return; }
  8759.  
  8760.         var px = e.gesture.touches[0].pageX - self._dragInfo.left;
  8761.         var mx = self._dragInfo.width - self.triggerThreshold;
  8762.  
  8763.         // The initial state was on, so "tend towards" on
  8764.         if(self._dragInfo.initialState) {
  8765.           if(px < self.triggerThreshold) {
  8766.             self.setOpenPercent(0);
  8767.           } else if(px > self._dragInfo.triggerX) {
  8768.             self.setOpenPercent(100);
  8769.           }
  8770.         } else {
  8771.           // The initial state was off, so "tend towards" off
  8772.           if(px < self._dragInfo.triggerX) {
  8773.             self.setOpenPercent(0);
  8774.           } else if(px > mx) {
  8775.             self.setOpenPercent(100);
  8776.           }
  8777.         }
  8778.       });
  8779.     },
  8780.  
  8781.     endDrag: function() {
  8782.       this._dragInfo = null;
  8783.     },
  8784.  
  8785.     hold: function() {
  8786.       this.el.classList.add('dragging');
  8787.     },
  8788.     release: function(e) {
  8789.       this.el.classList.remove('dragging');
  8790.       this.endDrag(e);
  8791.     },
  8792.  
  8793.  
  8794.     setOpenPercent: function(openPercent) {
  8795.       // only make a change if the new open percent has changed
  8796.       if(this.openPercent < 0 || (openPercent < (this.openPercent - 3) || openPercent > (this.openPercent + 3) ) ) {
  8797.         this.openPercent = openPercent;
  8798.  
  8799.         if(openPercent === 0) {
  8800.           this.val(false);
  8801.         } else if(openPercent === 100) {
  8802.           this.val(true);
  8803.         } else {
  8804.           var openPixel = Math.round( (openPercent / 100) * this.track.offsetWidth - (this.handle.offsetWidth) );
  8805.           openPixel = (openPixel < 1 ? 0 : openPixel);
  8806.           this.handle.style[ionic.CSS.TRANSFORM] = 'translate3d(' + openPixel + 'px,0,0)';
  8807.         }
  8808.       }
  8809.     },
  8810.  
  8811.     val: function(value) {
  8812.       if(value === true || value === false) {
  8813.         if(this.handle.style[ionic.CSS.TRANSFORM] !== "") {
  8814.           this.handle.style[ionic.CSS.TRANSFORM] = "";
  8815.         }
  8816.         this.checkbox.checked = value;
  8817.         this.openPercent = (value ? 100 : 0);
  8818.         this.onChange && this.onChange();
  8819.       }
  8820.       return this.checkbox.checked;
  8821.     }
  8822.  
  8823.   });
  8824.  
  8825. })(ionic);
  8826.  
  8827. })();
  8828. /*!
  8829.  * ionic.bundle.js is a concatenation of:
  8830.  * ionic.js, angular.js, angular-animate.js,
  8831.  * angular-sanitize.js, angular-ui-router.js,
  8832.  * and ionic-angular.js
  8833.  */
  8834.  
  8835. /**
  8836.  * @license AngularJS v1.3.13
  8837.  * (c) 2010-2014 Google, Inc. http://angularjs.org
  8838.  * License: MIT
  8839.  */
  8840. (function(window, document, undefined) {'use strict';
  8841.  
  8842. /**
  8843.  * @description
  8844.  *
  8845.  * This object provides a utility for producing rich Error messages within
  8846.  * Angular. It can be called as follows:
  8847.  *
  8848.  * var exampleMinErr = minErr('example');
  8849.  * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
  8850.  *
  8851.  * The above creates an instance of minErr in the example namespace. The
  8852.  * resulting error will have a namespaced error code of example.one.  The
  8853.  * resulting error will replace {0} with the value of foo, and {1} with the
  8854.  * value of bar. The object is not restricted in the number of arguments it can
  8855.  * take.
  8856.  *
  8857.  * If fewer arguments are specified than necessary for interpolation, the extra
  8858.  * interpolation markers will be preserved in the final string.
  8859.  *
  8860.  * Since data will be parsed statically during a build step, some restrictions
  8861.  * are applied with respect to how minErr instances are created and called.
  8862.  * Instances should have names of the form namespaceMinErr for a minErr created
  8863.  * using minErr('namespace') . Error codes, namespaces and template strings
  8864.  * should all be static strings, not variables or general expressions.
  8865.  *
  8866.  * @param {string} module The namespace to use for the new minErr instance.
  8867.  * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
  8868.  *   error from returned function, for cases when a particular type of error is useful.
  8869.  * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
  8870.  */
  8871.  
  8872. function minErr(module, ErrorConstructor) {
  8873.   ErrorConstructor = ErrorConstructor || Error;
  8874.   return function() {
  8875.     var code = arguments[0],
  8876.       prefix = '[' + (module ? module + ':' : '') + code + '] ',
  8877.       template = arguments[1],
  8878.       templateArgs = arguments,
  8879.  
  8880.       message, i;
  8881.  
  8882.     message = prefix + template.replace(/\{\d+\}/g, function(match) {
  8883.       var index = +match.slice(1, -1), arg;
  8884.  
  8885.       if (index + 2 < templateArgs.length) {
  8886.         return toDebugString(templateArgs[index + 2]);
  8887.       }
  8888.       return match;
  8889.     });
  8890.  
  8891.     message = message + '\nhttp://errors.angularjs.org/1.3.13/' +
  8892.       (module ? module + '/' : '') + code;
  8893.     for (i = 2; i < arguments.length; i++) {
  8894.       message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
  8895.         encodeURIComponent(toDebugString(arguments[i]));
  8896.     }
  8897.     return new ErrorConstructor(message);
  8898.   };
  8899. }
  8900.  
  8901. /* We need to tell jshint what variables are being exported */
  8902. /* global angular: true,
  8903.   msie: true,
  8904.   jqLite: true,
  8905.   jQuery: true,
  8906.   slice: true,
  8907.   splice: true,
  8908.   push: true,
  8909.   toString: true,
  8910.   ngMinErr: true,
  8911.   angularModule: true,
  8912.   uid: true,
  8913.   REGEX_STRING_REGEXP: true,
  8914.   VALIDITY_STATE_PROPERTY: true,
  8915.  
  8916.   lowercase: true,
  8917.   uppercase: true,
  8918.   manualLowercase: true,
  8919.   manualUppercase: true,
  8920.   nodeName_: true,
  8921.   isArrayLike: true,
  8922.   forEach: true,
  8923.   sortedKeys: true,
  8924.   forEachSorted: true,
  8925.   reverseParams: true,
  8926.   nextUid: true,
  8927.   setHashKey: true,
  8928.   extend: true,
  8929.   int: true,
  8930.   inherit: true,
  8931.   noop: true,
  8932.   identity: true,
  8933.   valueFn: true,
  8934.   isUndefined: true,
  8935.   isDefined: true,
  8936.   isObject: true,
  8937.   isString: true,
  8938.   isNumber: true,
  8939.   isDate: true,
  8940.   isArray: true,
  8941.   isFunction: true,
  8942.   isRegExp: true,
  8943.   isWindow: true,
  8944.   isScope: true,
  8945.   isFile: true,
  8946.   isFormData: true,
  8947.   isBlob: true,
  8948.   isBoolean: true,
  8949.   isPromiseLike: true,
  8950.   trim: true,
  8951.   escapeForRegexp: true,
  8952.   isElement: true,
  8953.   makeMap: true,
  8954.   includes: true,
  8955.   arrayRemove: true,
  8956.   copy: true,
  8957.   shallowCopy: true,
  8958.   equals: true,
  8959.   csp: true,
  8960.   concat: true,
  8961.   sliceArgs: true,
  8962.   bind: true,
  8963.   toJsonReplacer: true,
  8964.   toJson: true,
  8965.   fromJson: true,
  8966.   startingTag: true,
  8967.   tryDecodeURIComponent: true,
  8968.   parseKeyValue: true,
  8969.   toKeyValue: true,
  8970.   encodeUriSegment: true,
  8971.   encodeUriQuery: true,
  8972.   angularInit: true,
  8973.   bootstrap: true,
  8974.   getTestability: true,
  8975.   snake_case: true,
  8976.   bindJQuery: true,
  8977.   assertArg: true,
  8978.   assertArgFn: true,
  8979.   assertNotHasOwnProperty: true,
  8980.   getter: true,
  8981.   getBlockNodes: true,
  8982.   hasOwnProperty: true,
  8983.   createMap: true,
  8984.  
  8985.   NODE_TYPE_ELEMENT: true,
  8986.   NODE_TYPE_TEXT: true,
  8987.   NODE_TYPE_COMMENT: true,
  8988.   NODE_TYPE_DOCUMENT: true,
  8989.   NODE_TYPE_DOCUMENT_FRAGMENT: true,
  8990. */
  8991.  
  8992. ////////////////////////////////////
  8993.  
  8994. /**
  8995.  * @ngdoc module
  8996.  * @name ng
  8997.  * @module ng
  8998.  * @description
  8999.  *
  9000.  * # ng (core module)
  9001.  * The ng module is loaded by default when an AngularJS application is started. The module itself
  9002.  * contains the essential components for an AngularJS application to function. The table below
  9003.  * lists a high level breakdown of each of the services/factories, filters, directives and testing
  9004.  * components available within this core module.
  9005.  *
  9006.  * <div doc-module-components="ng"></div>
  9007.  */
  9008.  
  9009. var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/;
  9010.  
  9011. // The name of a form control's ValidityState property.
  9012. // This is used so that it's possible for internal tests to create mock ValidityStates.
  9013. var VALIDITY_STATE_PROPERTY = 'validity';
  9014.  
  9015. /**
  9016.  * @ngdoc function
  9017.  * @name angular.lowercase
  9018.  * @module ng
  9019.  * @kind function
  9020.  *
  9021.  * @description Converts the specified string to lowercase.
  9022.  * @param {string} string String to be converted to lowercase.
  9023.  * @returns {string} Lowercased string.
  9024.  */
  9025. var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;};
  9026. var hasOwnProperty = Object.prototype.hasOwnProperty;
  9027.  
  9028. /**
  9029.  * @ngdoc function
  9030.  * @name angular.uppercase
  9031.  * @module ng
  9032.  * @kind function
  9033.  *
  9034.  * @description Converts the specified string to uppercase.
  9035.  * @param {string} string String to be converted to uppercase.
  9036.  * @returns {string} Uppercased string.
  9037.  */
  9038. var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;};
  9039.  
  9040.  
  9041. var manualLowercase = function(s) {
  9042.   /* jshint bitwise: false */
  9043.   return isString(s)
  9044.       ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
  9045.       : s;
  9046. };
  9047. var manualUppercase = function(s) {
  9048.   /* jshint bitwise: false */
  9049.   return isString(s)
  9050.       ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
  9051.       : s;
  9052. };
  9053.  
  9054.  
  9055. // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
  9056. // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
  9057. // with correct but slower alternatives.
  9058. if ('i' !== 'I'.toLowerCase()) {
  9059.   lowercase = manualLowercase;
  9060.   uppercase = manualUppercase;
  9061. }
  9062.  
  9063.  
  9064. var
  9065.     msie,             // holds major version number for IE, or NaN if UA is not IE.
  9066.     jqLite,           // delay binding since jQuery could be loaded after us.
  9067.     jQuery,           // delay binding
  9068.     slice             = [].slice,
  9069.     splice            = [].splice,
  9070.     push              = [].push,
  9071.     toString          = Object.prototype.toString,
  9072.     ngMinErr          = minErr('ng'),
  9073.  
  9074.     /** @name angular */
  9075.     angular           = window.angular || (window.angular = {}),
  9076.     angularModule,
  9077.     uid               = 0;
  9078.  
  9079. /**
  9080.  * documentMode is an IE-only property
  9081.  * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
  9082.  */
  9083. msie = document.documentMode;
  9084.  
  9085.  
  9086. /**
  9087.  * @private
  9088.  * @param {*} obj
  9089.  * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
  9090.  *                   String ...)
  9091.  */
  9092. function isArrayLike(obj) {
  9093.   if (obj == null || isWindow(obj)) {
  9094.     return false;
  9095.   }
  9096.  
  9097.   var length = obj.length;
  9098.  
  9099.   if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
  9100.     return true;
  9101.   }
  9102.  
  9103.   return isString(obj) || isArray(obj) || length === 0 ||
  9104.          typeof length === 'number' && length > 0 && (length - 1) in obj;
  9105. }
  9106.  
  9107. /**
  9108.  * @ngdoc function
  9109.  * @name angular.forEach
  9110.  * @module ng
  9111.  * @kind function
  9112.  *
  9113.  * @description
  9114.  * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
  9115.  * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value`
  9116.  * is the value of an object property or an array element, `key` is the object property key or
  9117.  * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional.
  9118.  *
  9119.  * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
  9120.  * using the `hasOwnProperty` method.
  9121.  *
  9122.  * Unlike ES262's
  9123.  * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18),
  9124.  * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just
  9125.  * return the value provided.
  9126.  *
  9127.    ```js
  9128.      var values = {name: 'misko', gender: 'male'};
  9129.      var log = [];
  9130.      angular.forEach(values, function(value, key) {
  9131.        this.push(key + ': ' + value);
  9132.      }, log);
  9133.      expect(log).toEqual(['name: misko', 'gender: male']);
  9134.    ```
  9135.  *
  9136.  * @param {Object|Array} obj Object to iterate over.
  9137.  * @param {Function} iterator Iterator function.
  9138.  * @param {Object=} context Object to become context (`this`) for the iterator function.
  9139.  * @returns {Object|Array} Reference to `obj`.
  9140.  */
  9141.  
  9142. function forEach(obj, iterator, context) {
  9143.   var key, length;
  9144.   if (obj) {
  9145.     if (isFunction(obj)) {
  9146.       for (key in obj) {
  9147.         // Need to check if hasOwnProperty exists,
  9148.         // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
  9149.         if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
  9150.           iterator.call(context, obj[key], key, obj);
  9151.         }
  9152.       }
  9153.     } else if (isArray(obj) || isArrayLike(obj)) {
  9154.       var isPrimitive = typeof obj !== 'object';
  9155.       for (key = 0, length = obj.length; key < length; key++) {
  9156.         if (isPrimitive || key in obj) {
  9157.           iterator.call(context, obj[key], key, obj);
  9158.         }
  9159.       }
  9160.     } else if (obj.forEach && obj.forEach !== forEach) {
  9161.         obj.forEach(iterator, context, obj);
  9162.     } else {
  9163.       for (key in obj) {
  9164.         if (obj.hasOwnProperty(key)) {
  9165.           iterator.call(context, obj[key], key, obj);
  9166.         }
  9167.       }
  9168.     }
  9169.   }
  9170.   return obj;
  9171. }
  9172.  
  9173. function sortedKeys(obj) {
  9174.   return Object.keys(obj).sort();
  9175. }
  9176.  
  9177. function forEachSorted(obj, iterator, context) {
  9178.   var keys = sortedKeys(obj);
  9179.   for (var i = 0; i < keys.length; i++) {
  9180.     iterator.call(context, obj[keys[i]], keys[i]);
  9181.   }
  9182.   return keys;
  9183. }
  9184.  
  9185.  
  9186. /**
  9187.  * when using forEach the params are value, key, but it is often useful to have key, value.
  9188.  * @param {function(string, *)} iteratorFn
  9189.  * @returns {function(*, string)}
  9190.  */
  9191. function reverseParams(iteratorFn) {
  9192.   return function(value, key) { iteratorFn(key, value); };
  9193. }
  9194.  
  9195. /**
  9196.  * A consistent way of creating unique IDs in angular.
  9197.  *
  9198.  * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
  9199.  * we hit number precision issues in JavaScript.
  9200.  *
  9201.  * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
  9202.  *
  9203.  * @returns {number} an unique alpha-numeric string
  9204.  */
  9205. function nextUid() {
  9206.   return ++uid;
  9207. }
  9208.  
  9209.  
  9210. /**
  9211.  * Set or clear the hashkey for an object.
  9212.  * @param obj object
  9213.  * @param h the hashkey (!truthy to delete the hashkey)
  9214.  */
  9215. function setHashKey(obj, h) {
  9216.   if (h) {
  9217.     obj.$$hashKey = h;
  9218.   } else {
  9219.     delete obj.$$hashKey;
  9220.   }
  9221. }
  9222.  
  9223. /**
  9224.  * @ngdoc function
  9225.  * @name angular.extend
  9226.  * @module ng
  9227.  * @kind function
  9228.  *
  9229.  * @description
  9230.  * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
  9231.  * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
  9232.  * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
  9233.  * Note: Keep in mind that `angular.extend` does not support recursive merge (deep copy).
  9234.  *
  9235.  * @param {Object} dst Destination object.
  9236.  * @param {...Object} src Source object(s).
  9237.  * @returns {Object} Reference to `dst`.
  9238.  */
  9239. function extend(dst) {
  9240.   var h = dst.$$hashKey;
  9241.  
  9242.   for (var i = 1, ii = arguments.length; i < ii; i++) {
  9243.     var obj = arguments[i];
  9244.     if (obj) {
  9245.       var keys = Object.keys(obj);
  9246.       for (var j = 0, jj = keys.length; j < jj; j++) {
  9247.         var key = keys[j];
  9248.         dst[key] = obj[key];
  9249.       }
  9250.     }
  9251.   }
  9252.  
  9253.   setHashKey(dst, h);
  9254.   return dst;
  9255. }
  9256.  
  9257. function int(str) {
  9258.   return parseInt(str, 10);
  9259. }
  9260.  
  9261.  
  9262. function inherit(parent, extra) {
  9263.   return extend(Object.create(parent), extra);
  9264. }
  9265.  
  9266. /**
  9267.  * @ngdoc function
  9268.  * @name angular.noop
  9269.  * @module ng
  9270.  * @kind function
  9271.  *
  9272.  * @description
  9273.  * A function that performs no operations. This function can be useful when writing code in the
  9274.  * functional style.
  9275.    ```js
  9276.      function foo(callback) {
  9277.        var result = calculateResult();
  9278.        (callback || angular.noop)(result);
  9279.      }
  9280.    ```
  9281.  */
  9282. function noop() {}
  9283. noop.$inject = [];
  9284.  
  9285.  
  9286. /**
  9287.  * @ngdoc function
  9288.  * @name angular.identity
  9289.  * @module ng
  9290.  * @kind function
  9291.  *
  9292.  * @description
  9293.  * A function that returns its first argument. This function is useful when writing code in the
  9294.  * functional style.
  9295.  *
  9296.    ```js
  9297.      function transformer(transformationFn, value) {
  9298.        return (transformationFn || angular.identity)(value);
  9299.      };
  9300.    ```
  9301.   * @param {*} value to be returned.
  9302.   * @returns {*} the value passed in.
  9303.  */
  9304. function identity($) {return $;}
  9305. identity.$inject = [];
  9306.  
  9307.  
  9308. function valueFn(value) {return function() {return value;};}
  9309.  
  9310. /**
  9311.  * @ngdoc function
  9312.  * @name angular.isUndefined
  9313.  * @module ng
  9314.  * @kind function
  9315.  *
  9316.  * @description
  9317.  * Determines if a reference is undefined.
  9318.  *
  9319.  * @param {*} value Reference to check.
  9320.  * @returns {boolean} True if `value` is undefined.
  9321.  */
  9322. function isUndefined(value) {return typeof value === 'undefined';}
  9323.  
  9324.  
  9325. /**
  9326.  * @ngdoc function
  9327.  * @name angular.isDefined
  9328.  * @module ng
  9329.  * @kind function
  9330.  *
  9331.  * @description
  9332.  * Determines if a reference is defined.
  9333.  *
  9334.  * @param {*} value Reference to check.
  9335.  * @returns {boolean} True if `value` is defined.
  9336.  */
  9337. function isDefined(value) {return typeof value !== 'undefined';}
  9338.  
  9339.  
  9340. /**
  9341.  * @ngdoc function
  9342.  * @name angular.isObject
  9343.  * @module ng
  9344.  * @kind function
  9345.  *
  9346.  * @description
  9347.  * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
  9348.  * considered to be objects. Note that JavaScript arrays are objects.
  9349.  *
  9350.  * @param {*} value Reference to check.
  9351.  * @returns {boolean} True if `value` is an `Object` but not `null`.
  9352.  */
  9353. function isObject(value) {
  9354.   // http://jsperf.com/isobject4
  9355.   return value !== null && typeof value === 'object';
  9356. }
  9357.  
  9358.  
  9359. /**
  9360.  * @ngdoc function
  9361.  * @name angular.isString
  9362.  * @module ng
  9363.  * @kind function
  9364.  *
  9365.  * @description
  9366.  * Determines if a reference is a `String`.
  9367.  *
  9368.  * @param {*} value Reference to check.
  9369.  * @returns {boolean} True if `value` is a `String`.
  9370.  */
  9371. function isString(value) {return typeof value === 'string';}
  9372.  
  9373.  
  9374. /**
  9375.  * @ngdoc function
  9376.  * @name angular.isNumber
  9377.  * @module ng
  9378.  * @kind function
  9379.  *
  9380.  * @description
  9381.  * Determines if a reference is a `Number`.
  9382.  *
  9383.  * @param {*} value Reference to check.
  9384.  * @returns {boolean} True if `value` is a `Number`.
  9385.  */
  9386. function isNumber(value) {return typeof value === 'number';}
  9387.  
  9388.  
  9389. /**
  9390.  * @ngdoc function
  9391.  * @name angular.isDate
  9392.  * @module ng
  9393.  * @kind function
  9394.  *
  9395.  * @description
  9396.  * Determines if a value is a date.
  9397.  *
  9398.  * @param {*} value Reference to check.
  9399.  * @returns {boolean} True if `value` is a `Date`.
  9400.  */
  9401. function isDate(value) {
  9402.   return toString.call(value) === '[object Date]';
  9403. }
  9404.  
  9405.  
  9406. /**
  9407.  * @ngdoc function
  9408.  * @name angular.isArray
  9409.  * @module ng
  9410.  * @kind function
  9411.  *
  9412.  * @description
  9413.  * Determines if a reference is an `Array`.
  9414.  *
  9415.  * @param {*} value Reference to check.
  9416.  * @returns {boolean} True if `value` is an `Array`.
  9417.  */
  9418. var isArray = Array.isArray;
  9419.  
  9420. /**
  9421.  * @ngdoc function
  9422.  * @name angular.isFunction
  9423.  * @module ng
  9424.  * @kind function
  9425.  *
  9426.  * @description
  9427.  * Determines if a reference is a `Function`.
  9428.  *
  9429.  * @param {*} value Reference to check.
  9430.  * @returns {boolean} True if `value` is a `Function`.
  9431.  */
  9432. function isFunction(value) {return typeof value === 'function';}
  9433.  
  9434.  
  9435. /**
  9436.  * Determines if a value is a regular expression object.
  9437.  *
  9438.  * @private
  9439.  * @param {*} value Reference to check.
  9440.  * @returns {boolean} True if `value` is a `RegExp`.
  9441.  */
  9442. function isRegExp(value) {
  9443.   return toString.call(value) === '[object RegExp]';
  9444. }
  9445.  
  9446.  
  9447. /**
  9448.  * Checks if `obj` is a window object.
  9449.  *
  9450.  * @private
  9451.  * @param {*} obj Object to check
  9452.  * @returns {boolean} True if `obj` is a window obj.
  9453.  */
  9454. function isWindow(obj) {
  9455.   return obj && obj.window === obj;
  9456. }
  9457.  
  9458.  
  9459. function isScope(obj) {
  9460.   return obj && obj.$evalAsync && obj.$watch;
  9461. }
  9462.  
  9463.  
  9464. function isFile(obj) {
  9465.   return toString.call(obj) === '[object File]';
  9466. }
  9467.  
  9468.  
  9469. function isFormData(obj) {
  9470.   return toString.call(obj) === '[object FormData]';
  9471. }
  9472.  
  9473.  
  9474. function isBlob(obj) {
  9475.   return toString.call(obj) === '[object Blob]';
  9476. }
  9477.  
  9478.  
  9479. function isBoolean(value) {
  9480.   return typeof value === 'boolean';
  9481. }
  9482.  
  9483.  
  9484. function isPromiseLike(obj) {
  9485.   return obj && isFunction(obj.then);
  9486. }
  9487.  
  9488.  
  9489. var trim = function(value) {
  9490.   return isString(value) ? value.trim() : value;
  9491. };
  9492.  
  9493. // Copied from:
  9494. // http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021
  9495. // Prereq: s is a string.
  9496. var escapeForRegexp = function(s) {
  9497.   return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
  9498.            replace(/\x08/g, '\\x08');
  9499. };
  9500.  
  9501.  
  9502. /**
  9503.  * @ngdoc function
  9504.  * @name angular.isElement
  9505.  * @module ng
  9506.  * @kind function
  9507.  *
  9508.  * @description
  9509.  * Determines if a reference is a DOM element (or wrapped jQuery element).
  9510.  *
  9511.  * @param {*} value Reference to check.
  9512.  * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
  9513.  */
  9514. function isElement(node) {
  9515.   return !!(node &&
  9516.     (node.nodeName  // we are a direct element
  9517.     || (node.prop && node.attr && node.find)));  // we have an on and find method part of jQuery API
  9518. }
  9519.  
  9520. /**
  9521.  * @param str 'key1,key2,...'
  9522.  * @returns {object} in the form of {key1:true, key2:true, ...}
  9523.  */
  9524. function makeMap(str) {
  9525.   var obj = {}, items = str.split(","), i;
  9526.   for (i = 0; i < items.length; i++)
  9527.     obj[items[i]] = true;
  9528.   return obj;
  9529. }
  9530.  
  9531.  
  9532. function nodeName_(element) {
  9533.   return lowercase(element.nodeName || (element[0] && element[0].nodeName));
  9534. }
  9535.  
  9536. function includes(array, obj) {
  9537.   return Array.prototype.indexOf.call(array, obj) != -1;
  9538. }
  9539.  
  9540. function arrayRemove(array, value) {
  9541.   var index = array.indexOf(value);
  9542.   if (index >= 0)
  9543.     array.splice(index, 1);
  9544.   return value;
  9545. }
  9546.  
  9547. /**
  9548.  * @ngdoc function
  9549.  * @name angular.copy
  9550.  * @module ng
  9551.  * @kind function
  9552.  *
  9553.  * @description
  9554.  * Creates a deep copy of `source`, which should be an object or an array.
  9555.  *
  9556.  * * If no destination is supplied, a copy of the object or array is created.
  9557.  * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
  9558.  *   are deleted and then all elements/properties from the source are copied to it.
  9559.  * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
  9560.  * * If `source` is identical to 'destination' an exception will be thrown.
  9561.  *
  9562.  * @param {*} source The source that will be used to make a copy.
  9563.  *                   Can be any type, including primitives, `null`, and `undefined`.
  9564.  * @param {(Object|Array)=} destination Destination into which the source is copied. If
  9565.  *     provided, must be of the same type as `source`.
  9566.  * @returns {*} The copy or updated `destination`, if `destination` was specified.
  9567.  *
  9568.  * @example
  9569.  <example module="copyExample">
  9570.  <file name="index.html">
  9571.  <div ng-controller="ExampleController">
  9572.  <form novalidate class="simple-form">
  9573.  Name: <input type="text" ng-model="user.name" /><br />
  9574.  E-mail: <input type="email" ng-model="user.email" /><br />
  9575.  Gender: <input type="radio" ng-model="user.gender" value="male" />male
  9576.  <input type="radio" ng-model="user.gender" value="female" />female<br />
  9577.  <button ng-click="reset()">RESET</button>
  9578.  <button ng-click="update(user)">SAVE</button>
  9579.  </form>
  9580.  <pre>form = {{user | json}}</pre>
  9581.  <pre>master = {{master | json}}</pre>
  9582.  </div>
  9583.  
  9584.  <script>
  9585.   angular.module('copyExample', [])
  9586.     .controller('ExampleController', ['$scope', function($scope) {
  9587.       $scope.master= {};
  9588.  
  9589.       $scope.update = function(user) {
  9590.         // Example with 1 argument
  9591.         $scope.master= angular.copy(user);
  9592.       };
  9593.  
  9594.       $scope.reset = function() {
  9595.         // Example with 2 arguments
  9596.         angular.copy($scope.master, $scope.user);
  9597.       };
  9598.  
  9599.       $scope.reset();
  9600.     }]);
  9601.  </script>
  9602.  </file>
  9603.  </example>
  9604.  */
  9605. function copy(source, destination, stackSource, stackDest) {
  9606.   if (isWindow(source) || isScope(source)) {
  9607.     throw ngMinErr('cpws',
  9608.       "Can't copy! Making copies of Window or Scope instances is not supported.");
  9609.   }
  9610.  
  9611.   if (!destination) {
  9612.     destination = source;
  9613.     if (source) {
  9614.       if (isArray(source)) {
  9615.         destination = copy(source, [], stackSource, stackDest);
  9616.       } else if (isDate(source)) {
  9617.         destination = new Date(source.getTime());
  9618.       } else if (isRegExp(source)) {
  9619.         destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
  9620.         destination.lastIndex = source.lastIndex;
  9621.       } else if (isObject(source)) {
  9622.         var emptyObject = Object.create(Object.getPrototypeOf(source));
  9623.         destination = copy(source, emptyObject, stackSource, stackDest);
  9624.       }
  9625.     }
  9626.   } else {
  9627.     if (source === destination) throw ngMinErr('cpi',
  9628.       "Can't copy! Source and destination are identical.");
  9629.  
  9630.     stackSource = stackSource || [];
  9631.     stackDest = stackDest || [];
  9632.  
  9633.     if (isObject(source)) {
  9634.       var index = stackSource.indexOf(source);
  9635.       if (index !== -1) return stackDest[index];
  9636.  
  9637.       stackSource.push(source);
  9638.       stackDest.push(destination);
  9639.     }
  9640.  
  9641.     var result;
  9642.     if (isArray(source)) {
  9643.       destination.length = 0;
  9644.       for (var i = 0; i < source.length; i++) {
  9645.         result = copy(source[i], null, stackSource, stackDest);
  9646.         if (isObject(source[i])) {
  9647.           stackSource.push(source[i]);
  9648.           stackDest.push(result);
  9649.         }
  9650.         destination.push(result);
  9651.       }
  9652.     } else {
  9653.       var h = destination.$$hashKey;
  9654.       if (isArray(destination)) {
  9655.         destination.length = 0;
  9656.       } else {
  9657.         forEach(destination, function(value, key) {
  9658.           delete destination[key];
  9659.         });
  9660.       }
  9661.       for (var key in source) {
  9662.         if (source.hasOwnProperty(key)) {
  9663.           result = copy(source[key], null, stackSource, stackDest);
  9664.           if (isObject(source[key])) {
  9665.             stackSource.push(source[key]);
  9666.             stackDest.push(result);
  9667.           }
  9668.           destination[key] = result;
  9669.         }
  9670.       }
  9671.       setHashKey(destination,h);
  9672.     }
  9673.  
  9674.   }
  9675.   return destination;
  9676. }
  9677.  
  9678. /**
  9679.  * Creates a shallow copy of an object, an array or a primitive.
  9680.  *
  9681.  * Assumes that there are no proto properties for objects.
  9682.  */
  9683. function shallowCopy(src, dst) {
  9684.   if (isArray(src)) {
  9685.     dst = dst || [];
  9686.  
  9687.     for (var i = 0, ii = src.length; i < ii; i++) {
  9688.       dst[i] = src[i];
  9689.     }
  9690.   } else if (isObject(src)) {
  9691.     dst = dst || {};
  9692.  
  9693.     for (var key in src) {
  9694.       if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
  9695.         dst[key] = src[key];
  9696.       }
  9697.     }
  9698.   }
  9699.  
  9700.   return dst || src;
  9701. }
  9702.  
  9703.  
  9704. /**
  9705.  * @ngdoc function
  9706.  * @name angular.equals
  9707.  * @module ng
  9708.  * @kind function
  9709.  *
  9710.  * @description
  9711.  * Determines if two objects or two values are equivalent. Supports value types, regular
  9712.  * expressions, arrays and objects.
  9713.  *
  9714.  * Two objects or values are considered equivalent if at least one of the following is true:
  9715.  *
  9716.  * * Both objects or values pass `===` comparison.
  9717.  * * Both objects or values are of the same type and all of their properties are equal by
  9718.  *   comparing them with `angular.equals`.
  9719.  * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
  9720.  * * Both values represent the same regular expression (In JavaScript,
  9721.  *   /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
  9722.  *   representation matches).
  9723.  *
  9724.  * During a property comparison, properties of `function` type and properties with names
  9725.  * that begin with `$` are ignored.
  9726.  *
  9727.  * Scope and DOMWindow objects are being compared only by identify (`===`).
  9728.  *
  9729.  * @param {*} o1 Object or value to compare.
  9730.  * @param {*} o2 Object or value to compare.
  9731.  * @returns {boolean} True if arguments are equal.
  9732.  */
  9733. function equals(o1, o2) {
  9734.   if (o1 === o2) return true;
  9735.   if (o1 === null || o2 === null) return false;
  9736.   if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
  9737.   var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
  9738.   if (t1 == t2) {
  9739.     if (t1 == 'object') {
  9740.       if (isArray(o1)) {
  9741.         if (!isArray(o2)) return false;
  9742.         if ((length = o1.length) == o2.length) {
  9743.           for (key = 0; key < length; key++) {
  9744.             if (!equals(o1[key], o2[key])) return false;
  9745.           }
  9746.           return true;
  9747.         }
  9748.       } else if (isDate(o1)) {
  9749.         if (!isDate(o2)) return false;
  9750.         return equals(o1.getTime(), o2.getTime());
  9751.       } else if (isRegExp(o1) && isRegExp(o2)) {
  9752.         return o1.toString() == o2.toString();
  9753.       } else {
  9754.         if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false;
  9755.         keySet = {};
  9756.         for (key in o1) {
  9757.           if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
  9758.           if (!equals(o1[key], o2[key])) return false;
  9759.           keySet[key] = true;
  9760.         }
  9761.         for (key in o2) {
  9762.           if (!keySet.hasOwnProperty(key) &&
  9763.               key.charAt(0) !== '$' &&
  9764.               o2[key] !== undefined &&
  9765.               !isFunction(o2[key])) return false;
  9766.         }
  9767.         return true;
  9768.       }
  9769.     }
  9770.   }
  9771.   return false;
  9772. }
  9773.  
  9774. var csp = function() {
  9775.   if (isDefined(csp.isActive_)) return csp.isActive_;
  9776.  
  9777.   var active = !!(document.querySelector('[ng-csp]') ||
  9778.                   document.querySelector('[data-ng-csp]'));
  9779.  
  9780.   if (!active) {
  9781.     try {
  9782.       /* jshint -W031, -W054 */
  9783.       new Function('');
  9784.       /* jshint +W031, +W054 */
  9785.     } catch (e) {
  9786.       active = true;
  9787.     }
  9788.   }
  9789.  
  9790.   return (csp.isActive_ = active);
  9791. };
  9792.  
  9793.  
  9794.  
  9795. function concat(array1, array2, index) {
  9796.   return array1.concat(slice.call(array2, index));
  9797. }
  9798.  
  9799. function sliceArgs(args, startIndex) {
  9800.   return slice.call(args, startIndex || 0);
  9801. }
  9802.  
  9803.  
  9804. /* jshint -W101 */
  9805. /**
  9806.  * @ngdoc function
  9807.  * @name angular.bind
  9808.  * @module ng
  9809.  * @kind function
  9810.  *
  9811.  * @description
  9812.  * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
  9813.  * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
  9814.  * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
  9815.  * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
  9816.  *
  9817.  * @param {Object} self Context which `fn` should be evaluated in.
  9818.  * @param {function()} fn Function to be bound.
  9819.  * @param {...*} args Optional arguments to be prebound to the `fn` function call.
  9820.  * @returns {function()} Function that wraps the `fn` with all the specified bindings.
  9821.  */
  9822. /* jshint +W101 */
  9823. function bind(self, fn) {
  9824.   var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
  9825.   if (isFunction(fn) && !(fn instanceof RegExp)) {
  9826.     return curryArgs.length
  9827.       ? function() {
  9828.           return arguments.length
  9829.             ? fn.apply(self, concat(curryArgs, arguments, 0))
  9830.             : fn.apply(self, curryArgs);
  9831.         }
  9832.       : function() {
  9833.           return arguments.length
  9834.             ? fn.apply(self, arguments)
  9835.             : fn.call(self);
  9836.         };
  9837.   } else {
  9838.     // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
  9839.     return fn;
  9840.   }
  9841. }
  9842.  
  9843.  
  9844. function toJsonReplacer(key, value) {
  9845.   var val = value;
  9846.  
  9847.   if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
  9848.     val = undefined;
  9849.   } else if (isWindow(value)) {
  9850.     val = '$WINDOW';
  9851.   } else if (value &&  document === value) {
  9852.     val = '$DOCUMENT';
  9853.   } else if (isScope(value)) {
  9854.     val = '$SCOPE';
  9855.   }
  9856.  
  9857.   return val;
  9858. }
  9859.  
  9860.  
  9861. /**
  9862.  * @ngdoc function
  9863.  * @name angular.toJson
  9864.  * @module ng
  9865.  * @kind function
  9866.  *
  9867.  * @description
  9868.  * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
  9869.  * stripped since angular uses this notation internally.
  9870.  *
  9871.  * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
  9872.  * @param {boolean|number=} pretty If set to true, the JSON output will contain newlines and whitespace.
  9873.  *    If set to an integer, the JSON output will contain that many spaces per indentation (the default is 2).
  9874.  * @returns {string|undefined} JSON-ified string representing `obj`.
  9875.  */
  9876. function toJson(obj, pretty) {
  9877.   if (typeof obj === 'undefined') return undefined;
  9878.   if (!isNumber(pretty)) {
  9879.     pretty = pretty ? 2 : null;
  9880.   }
  9881.   return JSON.stringify(obj, toJsonReplacer, pretty);
  9882. }
  9883.  
  9884.  
  9885. /**
  9886.  * @ngdoc function
  9887.  * @name angular.fromJson
  9888.  * @module ng
  9889.  * @kind function
  9890.  *
  9891.  * @description
  9892.  * Deserializes a JSON string.
  9893.  *
  9894.  * @param {string} json JSON string to deserialize.
  9895.  * @returns {Object|Array|string|number} Deserialized JSON string.
  9896.  */
  9897. function fromJson(json) {
  9898.   return isString(json)
  9899.       ? JSON.parse(json)
  9900.       : json;
  9901. }
  9902.  
  9903.  
  9904. /**
  9905.  * @returns {string} Returns the string representation of the element.
  9906.  */
  9907. function startingTag(element) {
  9908.   element = jqLite(element).clone();
  9909.   try {
  9910.     // turns out IE does not let you set .html() on elements which
  9911.     // are not allowed to have children. So we just ignore it.
  9912.     element.empty();
  9913.   } catch (e) {}
  9914.   var elemHtml = jqLite('<div>').append(element).html();
  9915.   try {
  9916.     return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
  9917.         elemHtml.
  9918.           match(/^(<[^>]+>)/)[1].
  9919.           replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
  9920.   } catch (e) {
  9921.     return lowercase(elemHtml);
  9922.   }
  9923.  
  9924. }
  9925.  
  9926.  
  9927. /////////////////////////////////////////////////
  9928.  
  9929. /**
  9930.  * Tries to decode the URI component without throwing an exception.
  9931.  *
  9932.  * @private
  9933.  * @param str value potential URI component to check.
  9934.  * @returns {boolean} True if `value` can be decoded
  9935.  * with the decodeURIComponent function.
  9936.  */
  9937. function tryDecodeURIComponent(value) {
  9938.   try {
  9939.     return decodeURIComponent(value);
  9940.   } catch (e) {
  9941.     // Ignore any invalid uri component
  9942.   }
  9943. }
  9944.  
  9945.  
  9946. /**
  9947.  * Parses an escaped url query string into key-value pairs.
  9948.  * @returns {Object.<string,boolean|Array>}
  9949.  */
  9950. function parseKeyValue(/**string*/keyValue) {
  9951.   var obj = {}, key_value, key;
  9952.   forEach((keyValue || "").split('&'), function(keyValue) {
  9953.     if (keyValue) {
  9954.       key_value = keyValue.replace(/\+/g,'%20').split('=');
  9955.       key = tryDecodeURIComponent(key_value[0]);
  9956.       if (isDefined(key)) {
  9957.         var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
  9958.         if (!hasOwnProperty.call(obj, key)) {
  9959.           obj[key] = val;
  9960.         } else if (isArray(obj[key])) {
  9961.           obj[key].push(val);
  9962.         } else {
  9963.           obj[key] = [obj[key],val];
  9964.         }
  9965.       }
  9966.     }
  9967.   });
  9968.   return obj;
  9969. }
  9970.  
  9971. function toKeyValue(obj) {
  9972.   var parts = [];
  9973.   forEach(obj, function(value, key) {
  9974.     if (isArray(value)) {
  9975.       forEach(value, function(arrayValue) {
  9976.         parts.push(encodeUriQuery(key, true) +
  9977.                    (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
  9978.       });
  9979.     } else {
  9980.     parts.push(encodeUriQuery(key, true) +
  9981.                (value === true ? '' : '=' + encodeUriQuery(value, true)));
  9982.     }
  9983.   });
  9984.   return parts.length ? parts.join('&') : '';
  9985. }
  9986.  
  9987.  
  9988. /**
  9989.  * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
  9990.  * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
  9991.  * segments:
  9992.  *    segment       = *pchar
  9993.  *    pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
  9994.  *    pct-encoded   = "%" HEXDIG HEXDIG
  9995.  *    unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
  9996.  *    sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
  9997.  *                     / "*" / "+" / "," / ";" / "="
  9998.  */
  9999. function encodeUriSegment(val) {
  10000.   return encodeUriQuery(val, true).
  10001.              replace(/%26/gi, '&').
  10002.              replace(/%3D/gi, '=').
  10003.              replace(/%2B/gi, '+');
  10004. }
  10005.  
  10006.  
  10007. /**
  10008.  * This method is intended for encoding *key* or *value* parts of query component. We need a custom
  10009.  * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
  10010.  * encoded per http://tools.ietf.org/html/rfc3986:
  10011.  *    query       = *( pchar / "/" / "?" )
  10012.  *    pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
  10013.  *    unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
  10014.  *    pct-encoded   = "%" HEXDIG HEXDIG
  10015.  *    sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
  10016.  *                     / "*" / "+" / "," / ";" / "="
  10017.  */
  10018. function encodeUriQuery(val, pctEncodeSpaces) {
  10019.   return encodeURIComponent(val).
  10020.              replace(/%40/gi, '@').
  10021.              replace(/%3A/gi, ':').
  10022.              replace(/%24/g, '$').
  10023.              replace(/%2C/gi, ',').
  10024.              replace(/%3B/gi, ';').
  10025.              replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
  10026. }
  10027.  
  10028. var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
  10029.  
  10030. function getNgAttribute(element, ngAttr) {
  10031.   var attr, i, ii = ngAttrPrefixes.length;
  10032.   element = jqLite(element);
  10033.   for (i = 0; i < ii; ++i) {
  10034.     attr = ngAttrPrefixes[i] + ngAttr;
  10035.     if (isString(attr = element.attr(attr))) {
  10036.       return attr;
  10037.     }
  10038.   }
  10039.   return null;
  10040. }
  10041.  
  10042. /**
  10043.  * @ngdoc directive
  10044.  * @name ngApp
  10045.  * @module ng
  10046.  *
  10047.  * @element ANY
  10048.  * @param {angular.Module} ngApp an optional application
  10049.  *   {@link angular.module module} name to load.
  10050.  * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
  10051.  *   created in "strict-di" mode. This means that the application will fail to invoke functions which
  10052.  *   do not use explicit function annotation (and are thus unsuitable for minification), as described
  10053.  *   in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
  10054.  *   tracking down the root of these bugs.
  10055.  *
  10056.  * @description
  10057.  *
  10058.  * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
  10059.  * designates the **root element** of the application and is typically placed near the root element
  10060.  * of the page - e.g. on the `<body>` or `<html>` tags.
  10061.  *
  10062.  * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp`
  10063.  * found in the document will be used to define the root element to auto-bootstrap as an
  10064.  * application. To run multiple applications in an HTML document you must manually bootstrap them using
  10065.  * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
  10066.  *
  10067.  * You can specify an **AngularJS module** to be used as the root module for the application.  This
  10068.  * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
  10069.  * should contain the application code needed or have dependencies on other modules that will
  10070.  * contain the code. See {@link angular.module} for more information.
  10071.  *
  10072.  * In the example below if the `ngApp` directive were not placed on the `html` element then the
  10073.  * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
  10074.  * would not be resolved to `3`.
  10075.  *
  10076.  * `ngApp` is the easiest, and most common way to bootstrap an application.
  10077.  *
  10078.  <example module="ngAppDemo">
  10079.    <file name="index.html">
  10080.    <div ng-controller="ngAppDemoController">
  10081.      I can add: {{a}} + {{b}} =  {{ a+b }}
  10082.    </div>
  10083.    </file>
  10084.    <file name="script.js">
  10085.    angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
  10086.      $scope.a = 1;
  10087.      $scope.b = 2;
  10088.    });
  10089.    </file>
  10090.  </example>
  10091.  *
  10092.  * Using `ngStrictDi`, you would see something like this:
  10093.  *
  10094.  <example ng-app-included="true">
  10095.    <file name="index.html">
  10096.    <div ng-app="ngAppStrictDemo" ng-strict-di>
  10097.        <div ng-controller="GoodController1">
  10098.            I can add: {{a}} + {{b}} =  {{ a+b }}
  10099.  
  10100.            <p>This renders because the controller does not fail to
  10101.               instantiate, by using explicit annotation style (see
  10102.               script.js for details)
  10103.            </p>
  10104.        </div>
  10105.  
  10106.        <div ng-controller="GoodController2">
  10107.            Name: <input ng-model="name"><br />
  10108.            Hello, {{name}}!
  10109.  
  10110.            <p>This renders because the controller does not fail to
  10111.               instantiate, by using explicit annotation style
  10112.               (see script.js for details)
  10113.            </p>
  10114.        </div>
  10115.  
  10116.        <div ng-controller="BadController">
  10117.            I can add: {{a}} + {{b}} =  {{ a+b }}
  10118.  
  10119.            <p>The controller could not be instantiated, due to relying
  10120.               on automatic function annotations (which are disabled in
  10121.               strict mode). As such, the content of this section is not
  10122.               interpolated, and there should be an error in your web console.
  10123.            </p>
  10124.        </div>
  10125.    </div>
  10126.    </file>
  10127.    <file name="script.js">
  10128.    angular.module('ngAppStrictDemo', [])
  10129.      // BadController will fail to instantiate, due to relying on automatic function annotation,
  10130.      // rather than an explicit annotation
  10131.      .controller('BadController', function($scope) {
  10132.        $scope.a = 1;
  10133.        $scope.b = 2;
  10134.      })
  10135.      // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
  10136.      // due to using explicit annotations using the array style and $inject property, respectively.
  10137.      .controller('GoodController1', ['$scope', function($scope) {
  10138.        $scope.a = 1;
  10139.        $scope.b = 2;
  10140.      }])
  10141.      .controller('GoodController2', GoodController2);
  10142.      function GoodController2($scope) {
  10143.        $scope.name = "World";
  10144.      }
  10145.      GoodController2.$inject = ['$scope'];
  10146.    </file>
  10147.    <file name="style.css">
  10148.    div[ng-controller] {
  10149.        margin-bottom: 1em;
  10150.        -webkit-border-radius: 4px;
  10151.        border-radius: 4px;
  10152.        border: 1px solid;
  10153.        padding: .5em;
  10154.    }
  10155.    div[ng-controller^=Good] {
  10156.        border-color: #d6e9c6;
  10157.        background-color: #dff0d8;
  10158.        color: #3c763d;
  10159.    }
  10160.    div[ng-controller^=Bad] {
  10161.        border-color: #ebccd1;
  10162.        background-color: #f2dede;
  10163.        color: #a94442;
  10164.        margin-bottom: 0;
  10165.    }
  10166.    </file>
  10167.  </example>
  10168.  */
  10169. function angularInit(element, bootstrap) {
  10170.   var appElement,
  10171.       module,
  10172.       config = {};
  10173.  
  10174.   // The element `element` has priority over any other element
  10175.   forEach(ngAttrPrefixes, function(prefix) {
  10176.     var name = prefix + 'app';
  10177.  
  10178.     if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
  10179.       appElement = element;
  10180.       module = element.getAttribute(name);
  10181.     }
  10182.   });
  10183.   forEach(ngAttrPrefixes, function(prefix) {
  10184.     var name = prefix + 'app';
  10185.     var candidate;
  10186.  
  10187.     if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
  10188.       appElement = candidate;
  10189.       module = candidate.getAttribute(name);
  10190.     }
  10191.   });
  10192.   if (appElement) {
  10193.     config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
  10194.     bootstrap(appElement, module ? [module] : [], config);
  10195.   }
  10196. }
  10197.  
  10198. /**
  10199.  * @ngdoc function
  10200.  * @name angular.bootstrap
  10201.  * @module ng
  10202.  * @description
  10203.  * Use this function to manually start up angular application.
  10204.  *
  10205.  * See: {@link guide/bootstrap Bootstrap}
  10206.  *
  10207.  * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually.
  10208.  * They must use {@link ng.directive:ngApp ngApp}.
  10209.  *
  10210.  * Angular will detect if it has been loaded into the browser more than once and only allow the
  10211.  * first loaded script to be bootstrapped and will report a warning to the browser console for
  10212.  * each of the subsequent scripts. This prevents strange results in applications, where otherwise
  10213.  * multiple instances of Angular try to work on the DOM.
  10214.  *
  10215.  * ```html
  10216.  * <!doctype html>
  10217.  * <html>
  10218.  * <body>
  10219.  * <div ng-controller="WelcomeController">
  10220.  *   {{greeting}}
  10221.  * </div>
  10222.  *
  10223.  * <script src="angular.js"></script>
  10224.  * <script>
  10225.  *   var app = angular.module('demo', [])
  10226.  *   .controller('WelcomeController', function($scope) {
  10227.  *       $scope.greeting = 'Welcome!';
  10228.  *   });
  10229.  *   angular.bootstrap(document, ['demo']);
  10230.  * </script>
  10231.  * </body>
  10232.  * </html>
  10233.  * ```
  10234.  *
  10235.  * @param {DOMElement} element DOM element which is the root of angular application.
  10236.  * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
  10237.  *     Each item in the array should be the name of a predefined module or a (DI annotated)
  10238.  *     function that will be invoked by the injector as a `config` block.
  10239.  *     See: {@link angular.module modules}
  10240.  * @param {Object=} config an object for defining configuration options for the application. The
  10241.  *     following keys are supported:
  10242.  *
  10243.  * * `strictDi` - disable automatic function annotation for the application. This is meant to
  10244.  *   assist in finding bugs which break minified code. Defaults to `false`.
  10245.  *
  10246.  * @returns {auto.$injector} Returns the newly created injector for this app.
  10247.  */
  10248. function bootstrap(element, modules, config) {
  10249.   if (!isObject(config)) config = {};
  10250.   var defaultConfig = {
  10251.     strictDi: false
  10252.   };
  10253.   config = extend(defaultConfig, config);
  10254.   var doBootstrap = function() {
  10255.     element = jqLite(element);
  10256.  
  10257.     if (element.injector()) {
  10258.       var tag = (element[0] === document) ? 'document' : startingTag(element);
  10259.       //Encode angle brackets to prevent input from being sanitized to empty string #8683
  10260.       throw ngMinErr(
  10261.           'btstrpd',
  10262.           "App Already Bootstrapped with this Element '{0}'",
  10263.           tag.replace(/</,'&lt;').replace(/>/,'&gt;'));
  10264.     }
  10265.  
  10266.     modules = modules || [];
  10267.     modules.unshift(['$provide', function($provide) {
  10268.       $provide.value('$rootElement', element);
  10269.     }]);
  10270.  
  10271.     if (config.debugInfoEnabled) {
  10272.       // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
  10273.       modules.push(['$compileProvider', function($compileProvider) {
  10274.         $compileProvider.debugInfoEnabled(true);
  10275.       }]);
  10276.     }
  10277.  
  10278.     modules.unshift('ng');
  10279.     var injector = createInjector(modules, config.strictDi);
  10280.     injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
  10281.        function bootstrapApply(scope, element, compile, injector) {
  10282.         scope.$apply(function() {
  10283.           element.data('$injector', injector);
  10284.           compile(element)(scope);
  10285.         });
  10286.       }]
  10287.     );
  10288.     return injector;
  10289.   };
  10290.  
  10291.   var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
  10292.   var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
  10293.  
  10294.   if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
  10295.     config.debugInfoEnabled = true;
  10296.     window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
  10297.   }
  10298.  
  10299.   if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
  10300.     return doBootstrap();
  10301.   }
  10302.  
  10303.   window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
  10304.   angular.resumeBootstrap = function(extraModules) {
  10305.     forEach(extraModules, function(module) {
  10306.       modules.push(module);
  10307.     });
  10308.     return doBootstrap();
  10309.   };
  10310.  
  10311.   if (isFunction(angular.resumeDeferredBootstrap)) {
  10312.     angular.resumeDeferredBootstrap();
  10313.   }
  10314. }
  10315.  
  10316. /**
  10317.  * @ngdoc function
  10318.  * @name angular.reloadWithDebugInfo
  10319.  * @module ng
  10320.  * @description
  10321.  * Use this function to reload the current application with debug information turned on.
  10322.  * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
  10323.  *
  10324.  * See {@link ng.$compileProvider#debugInfoEnabled} for more.
  10325.  */
  10326. function reloadWithDebugInfo() {
  10327.   window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
  10328.   window.location.reload();
  10329. }
  10330.  
  10331. /**
  10332.  * @name angular.getTestability
  10333.  * @module ng
  10334.  * @description
  10335.  * Get the testability service for the instance of Angular on the given
  10336.  * element.
  10337.  * @param {DOMElement} element DOM element which is the root of angular application.
  10338.  */
  10339. function getTestability(rootElement) {
  10340.   var injector = angular.element(rootElement).injector();
  10341.   if (!injector) {
  10342.     throw ngMinErr('test',
  10343.       'no injector found for element argument to getTestability');
  10344.   }
  10345.   return injector.get('$$testability');
  10346. }
  10347.  
  10348. var SNAKE_CASE_REGEXP = /[A-Z]/g;
  10349. function snake_case(name, separator) {
  10350.   separator = separator || '_';
  10351.   return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
  10352.     return (pos ? separator : '') + letter.toLowerCase();
  10353.   });
  10354. }
  10355.  
  10356. var bindJQueryFired = false;
  10357. var skipDestroyOnNextJQueryCleanData;
  10358. function bindJQuery() {
  10359.   var originalCleanData;
  10360.  
  10361.   if (bindJQueryFired) {
  10362.     return;
  10363.   }
  10364.  
  10365.   // bind to jQuery if present;
  10366.   jQuery = window.jQuery;
  10367.   // Use jQuery if it exists with proper functionality, otherwise default to us.
  10368.   // Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
  10369.   // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
  10370.   // versions. It will not work for sure with jQuery <1.7, though.
  10371.   if (jQuery && jQuery.fn.on) {
  10372.     jqLite = jQuery;
  10373.     extend(jQuery.fn, {
  10374.       scope: JQLitePrototype.scope,
  10375.       isolateScope: JQLitePrototype.isolateScope,
  10376.       controller: JQLitePrototype.controller,
  10377.       injector: JQLitePrototype.injector,
  10378.       inheritedData: JQLitePrototype.inheritedData
  10379.     });
  10380.  
  10381.     // All nodes removed from the DOM via various jQuery APIs like .remove()
  10382.     // are passed through jQuery.cleanData. Monkey-patch this method to fire
  10383.     // the $destroy event on all removed nodes.
  10384.     originalCleanData = jQuery.cleanData;
  10385.     jQuery.cleanData = function(elems) {
  10386.       var events;
  10387.       if (!skipDestroyOnNextJQueryCleanData) {
  10388.         for (var i = 0, elem; (elem = elems[i]) != null; i++) {
  10389.           events = jQuery._data(elem, "events");
  10390.           if (events && events.$destroy) {
  10391.             jQuery(elem).triggerHandler('$destroy');
  10392.           }
  10393.         }
  10394.       } else {
  10395.         skipDestroyOnNextJQueryCleanData = false;
  10396.       }
  10397.       originalCleanData(elems);
  10398.     };
  10399.   } else {
  10400.     jqLite = JQLite;
  10401.   }
  10402.  
  10403.   angular.element = jqLite;
  10404.  
  10405.   // Prevent double-proxying.
  10406.   bindJQueryFired = true;
  10407. }
  10408.  
  10409. /**
  10410.  * throw error if the argument is falsy.
  10411.  */
  10412. function assertArg(arg, name, reason) {
  10413.   if (!arg) {
  10414.     throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
  10415.   }
  10416.   return arg;
  10417. }
  10418.  
  10419. function assertArgFn(arg, name, acceptArrayAnnotation) {
  10420.   if (acceptArrayAnnotation && isArray(arg)) {
  10421.       arg = arg[arg.length - 1];
  10422.   }
  10423.  
  10424.   assertArg(isFunction(arg), name, 'not a function, got ' +
  10425.       (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
  10426.   return arg;
  10427. }
  10428.  
  10429. /**
  10430.  * throw error if the name given is hasOwnProperty
  10431.  * @param  {String} name    the name to test
  10432.  * @param  {String} context the context in which the name is used, such as module or directive
  10433.  */
  10434. function assertNotHasOwnProperty(name, context) {
  10435.   if (name === 'hasOwnProperty') {
  10436.     throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context);
  10437.   }
  10438. }
  10439.  
  10440. /**
  10441.  * Return the value accessible from the object by path. Any undefined traversals are ignored
  10442.  * @param {Object} obj starting object
  10443.  * @param {String} path path to traverse
  10444.  * @param {boolean} [bindFnToScope=true]
  10445.  * @returns {Object} value as accessible by path
  10446.  */
  10447. //TODO(misko): this function needs to be removed
  10448. function getter(obj, path, bindFnToScope) {
  10449.   if (!path) return obj;
  10450.   var keys = path.split('.');
  10451.   var key;
  10452.   var lastInstance = obj;
  10453.   var len = keys.length;
  10454.  
  10455.   for (var i = 0; i < len; i++) {
  10456.     key = keys[i];
  10457.     if (obj) {
  10458.       obj = (lastInstance = obj)[key];
  10459.     }
  10460.   }
  10461.   if (!bindFnToScope && isFunction(obj)) {
  10462.     return bind(lastInstance, obj);
  10463.   }
  10464.   return obj;
  10465. }
  10466.  
  10467. /**
  10468.  * Return the DOM siblings between the first and last node in the given array.
  10469.  * @param {Array} array like object
  10470.  * @returns {jqLite} jqLite collection containing the nodes
  10471.  */
  10472. function getBlockNodes(nodes) {
  10473.   // TODO(perf): just check if all items in `nodes` are siblings and if they are return the original
  10474.   //             collection, otherwise update the original collection.
  10475.   var node = nodes[0];
  10476.   var endNode = nodes[nodes.length - 1];
  10477.   var blockNodes = [node];
  10478.  
  10479.   do {
  10480.     node = node.nextSibling;
  10481.     if (!node) break;
  10482.     blockNodes.push(node);
  10483.   } while (node !== endNode);
  10484.  
  10485.   return jqLite(blockNodes);
  10486. }
  10487.  
  10488.  
  10489. /**
  10490.  * Creates a new object without a prototype. This object is useful for lookup without having to
  10491.  * guard against prototypically inherited properties via hasOwnProperty.
  10492.  *
  10493.  * Related micro-benchmarks:
  10494.  * - http://jsperf.com/object-create2
  10495.  * - http://jsperf.com/proto-map-lookup/2
  10496.  * - http://jsperf.com/for-in-vs-object-keys2
  10497.  *
  10498.  * @returns {Object}
  10499.  */
  10500. function createMap() {
  10501.   return Object.create(null);
  10502. }
  10503.  
  10504. var NODE_TYPE_ELEMENT = 1;
  10505. var NODE_TYPE_TEXT = 3;
  10506. var NODE_TYPE_COMMENT = 8;
  10507. var NODE_TYPE_DOCUMENT = 9;
  10508. var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
  10509.  
  10510. /**
  10511.  * @ngdoc type
  10512.  * @name angular.Module
  10513.  * @module ng
  10514.  * @description
  10515.  *
  10516.  * Interface for configuring angular {@link angular.module modules}.
  10517.  */
  10518.  
  10519. function setupModuleLoader(window) {
  10520.  
  10521.   var $injectorMinErr = minErr('$injector');
  10522.   var ngMinErr = minErr('ng');
  10523.  
  10524.   function ensure(obj, name, factory) {
  10525.     return obj[name] || (obj[name] = factory());
  10526.   }
  10527.  
  10528.   var angular = ensure(window, 'angular', Object);
  10529.  
  10530.   // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
  10531.   angular.$$minErr = angular.$$minErr || minErr;
  10532.  
  10533.   return ensure(angular, 'module', function() {
  10534.     /** @type {Object.<string, angular.Module>} */
  10535.     var modules = {};
  10536.  
  10537.     /**
  10538.      * @ngdoc function
  10539.      * @name angular.module
  10540.      * @module ng
  10541.      * @description
  10542.      *
  10543.      * The `angular.module` is a global place for creating, registering and retrieving Angular
  10544.      * modules.
  10545.      * All modules (angular core or 3rd party) that should be available to an application must be
  10546.      * registered using this mechanism.
  10547.      *
  10548.      * When passed two or more arguments, a new module is created.  If passed only one argument, an
  10549.      * existing module (the name passed as the first argument to `module`) is retrieved.
  10550.      *
  10551.      *
  10552.      * # Module
  10553.      *
  10554.      * A module is a collection of services, directives, controllers, filters, and configuration information.
  10555.      * `angular.module` is used to configure the {@link auto.$injector $injector}.
  10556.      *
  10557.      * ```js
  10558.      * // Create a new module
  10559.      * var myModule = angular.module('myModule', []);
  10560.      *
  10561.      * // register a new service
  10562.      * myModule.value('appName', 'MyCoolApp');
  10563.      *
  10564.      * // configure existing services inside initialization blocks.
  10565.      * myModule.config(['$locationProvider', function($locationProvider) {
  10566.      *   // Configure existing providers
  10567.      *   $locationProvider.hashPrefix('!');
  10568.      * }]);
  10569.      * ```
  10570.      *
  10571.      * Then you can create an injector and load your modules like this:
  10572.      *
  10573.      * ```js
  10574.      * var injector = angular.injector(['ng', 'myModule'])
  10575.      * ```
  10576.      *
  10577.      * However it's more likely that you'll just use
  10578.      * {@link ng.directive:ngApp ngApp} or
  10579.      * {@link angular.bootstrap} to simplify this process for you.
  10580.      *
  10581.      * @param {!string} name The name of the module to create or retrieve.
  10582.      * @param {!Array.<string>=} requires If specified then new module is being created. If
  10583.      *        unspecified then the module is being retrieved for further configuration.
  10584.      * @param {Function=} configFn Optional configuration function for the module. Same as
  10585.      *        {@link angular.Module#config Module#config()}.
  10586.      * @returns {module} new module with the {@link angular.Module} api.
  10587.      */
  10588.     return function module(name, requires, configFn) {
  10589.       var assertNotHasOwnProperty = function(name, context) {
  10590.         if (name === 'hasOwnProperty') {
  10591.           throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
  10592.         }
  10593.       };
  10594.  
  10595.       assertNotHasOwnProperty(name, 'module');
  10596.       if (requires && modules.hasOwnProperty(name)) {
  10597.         modules[name] = null;
  10598.       }
  10599.       return ensure(modules, name, function() {
  10600.         if (!requires) {
  10601.           throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
  10602.              "the module name or forgot to load it. If registering a module ensure that you " +
  10603.              "specify the dependencies as the second argument.", name);
  10604.         }
  10605.  
  10606.         /** @type {!Array.<Array.<*>>} */
  10607.         var invokeQueue = [];
  10608.  
  10609.         /** @type {!Array.<Function>} */
  10610.         var configBlocks = [];
  10611.  
  10612.         /** @type {!Array.<Function>} */
  10613.         var runBlocks = [];
  10614.  
  10615.         var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
  10616.  
  10617.         /** @type {angular.Module} */
  10618.         var moduleInstance = {
  10619.           // Private state
  10620.           _invokeQueue: invokeQueue,
  10621.           _configBlocks: configBlocks,
  10622.           _runBlocks: runBlocks,
  10623.  
  10624.           /**
  10625.            * @ngdoc property
  10626.            * @name angular.Module#requires
  10627.            * @module ng
  10628.            *
  10629.            * @description
  10630.            * Holds the list of modules which the injector will load before the current module is
  10631.            * loaded.
  10632.            */
  10633.           requires: requires,
  10634.  
  10635.           /**
  10636.            * @ngdoc property
  10637.            * @name angular.Module#name
  10638.            * @module ng
  10639.            *
  10640.            * @description
  10641.            * Name of the module.
  10642.            */
  10643.           name: name,
  10644.  
  10645.  
  10646.           /**
  10647.            * @ngdoc method
  10648.            * @name angular.Module#provider
  10649.            * @module ng
  10650.            * @param {string} name service name
  10651.            * @param {Function} providerType Construction function for creating new instance of the
  10652.            *                                service.
  10653.            * @description
  10654.            * See {@link auto.$provide#provider $provide.provider()}.
  10655.            */
  10656.           provider: invokeLater('$provide', 'provider'),
  10657.  
  10658.           /**
  10659.            * @ngdoc method
  10660.            * @name angular.Module#factory
  10661.            * @module ng
  10662.            * @param {string} name service name
  10663.            * @param {Function} providerFunction Function for creating new instance of the service.
  10664.            * @description
  10665.            * See {@link auto.$provide#factory $provide.factory()}.
  10666.            */
  10667.           factory: invokeLater('$provide', 'factory'),
  10668.  
  10669.           /**
  10670.            * @ngdoc method
  10671.            * @name angular.Module#service
  10672.            * @module ng
  10673.            * @param {string} name service name
  10674.            * @param {Function} constructor A constructor function that will be instantiated.
  10675.            * @description
  10676.            * See {@link auto.$provide#service $provide.service()}.
  10677.            */
  10678.           service: invokeLater('$provide', 'service'),
  10679.  
  10680.           /**
  10681.            * @ngdoc method
  10682.            * @name angular.Module#value
  10683.            * @module ng
  10684.            * @param {string} name service name
  10685.            * @param {*} object Service instance object.
  10686.            * @description
  10687.            * See {@link auto.$provide#value $provide.value()}.
  10688.            */
  10689.           value: invokeLater('$provide', 'value'),
  10690.  
  10691.           /**
  10692.            * @ngdoc method
  10693.            * @name angular.Module#constant
  10694.            * @module ng
  10695.            * @param {string} name constant name
  10696.            * @param {*} object Constant value.
  10697.            * @description
  10698.            * Because the constant are fixed, they get applied before other provide methods.
  10699.            * See {@link auto.$provide#constant $provide.constant()}.
  10700.            */
  10701.           constant: invokeLater('$provide', 'constant', 'unshift'),
  10702.  
  10703.           /**
  10704.            * @ngdoc method
  10705.            * @name angular.Module#animation
  10706.            * @module ng
  10707.            * @param {string} name animation name
  10708.            * @param {Function} animationFactory Factory function for creating new instance of an
  10709.            *                                    animation.
  10710.            * @description
  10711.            *
  10712.            * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
  10713.            *
  10714.            *
  10715.            * Defines an animation hook that can be later used with
  10716.            * {@link ngAnimate.$animate $animate} service and directives that use this service.
  10717.            *
  10718.            * ```js
  10719.            * module.animation('.animation-name', function($inject1, $inject2) {
  10720.            *   return {
  10721.            *     eventName : function(element, done) {
  10722.            *       //code to run the animation
  10723.            *       //once complete, then run done()
  10724.            *       return function cancellationFunction(element) {
  10725.            *         //code to cancel the animation
  10726.            *       }
  10727.            *     }
  10728.            *   }
  10729.            * })
  10730.            * ```
  10731.            *
  10732.            * See {@link ng.$animateProvider#register $animateProvider.register()} and
  10733.            * {@link ngAnimate ngAnimate module} for more information.
  10734.            */
  10735.           animation: invokeLater('$animateProvider', 'register'),
  10736.  
  10737.           /**
  10738.            * @ngdoc method
  10739.            * @name angular.Module#filter
  10740.            * @module ng
  10741.            * @param {string} name Filter name.
  10742.            * @param {Function} filterFactory Factory function for creating new instance of filter.
  10743.            * @description
  10744.            * See {@link ng.$filterProvider#register $filterProvider.register()}.
  10745.            */
  10746.           filter: invokeLater('$filterProvider', 'register'),
  10747.  
  10748.           /**
  10749.            * @ngdoc method
  10750.            * @name angular.Module#controller
  10751.            * @module ng
  10752.            * @param {string|Object} name Controller name, or an object map of controllers where the
  10753.            *    keys are the names and the values are the constructors.
  10754.            * @param {Function} constructor Controller constructor function.
  10755.            * @description
  10756.            * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
  10757.            */
  10758.           controller: invokeLater('$controllerProvider', 'register'),
  10759.  
  10760.           /**
  10761.            * @ngdoc method
  10762.            * @name angular.Module#directive
  10763.            * @module ng
  10764.            * @param {string|Object} name Directive name, or an object map of directives where the
  10765.            *    keys are the names and the values are the factories.
  10766.            * @param {Function} directiveFactory Factory function for creating new instance of
  10767.            * directives.
  10768.            * @description
  10769.            * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
  10770.            */
  10771.           directive: invokeLater('$compileProvider', 'directive'),
  10772.  
  10773.           /**
  10774.            * @ngdoc method
  10775.            * @name angular.Module#config
  10776.            * @module ng
  10777.            * @param {Function} configFn Execute this function on module load. Useful for service
  10778.            *    configuration.
  10779.            * @description
  10780.            * Use this method to register work which needs to be performed on module loading.
  10781.            * For more about how to configure services, see
  10782.            * {@link providers#provider-recipe Provider Recipe}.
  10783.            */
  10784.           config: config,
  10785.  
  10786.           /**
  10787.            * @ngdoc method
  10788.            * @name angular.Module#run
  10789.            * @module ng
  10790.            * @param {Function} initializationFn Execute this function after injector creation.
  10791.            *    Useful for application initialization.
  10792.            * @description
  10793.            * Use this method to register work which should be performed when the injector is done
  10794.            * loading all modules.
  10795.            */
  10796.           run: function(block) {
  10797.             runBlocks.push(block);
  10798.             return this;
  10799.           }
  10800.         };
  10801.  
  10802.         if (configFn) {
  10803.           config(configFn);
  10804.         }
  10805.  
  10806.         return moduleInstance;
  10807.  
  10808.         /**
  10809.          * @param {string} provider
  10810.          * @param {string} method
  10811.          * @param {String=} insertMethod
  10812.          * @returns {angular.Module}
  10813.          */
  10814.         function invokeLater(provider, method, insertMethod, queue) {
  10815.           if (!queue) queue = invokeQueue;
  10816.           return function() {
  10817.             queue[insertMethod || 'push']([provider, method, arguments]);
  10818.             return moduleInstance;
  10819.           };
  10820.         }
  10821.       });
  10822.     };
  10823.   });
  10824.  
  10825. }
  10826.  
  10827. /* global: toDebugString: true */
  10828.  
  10829. function serializeObject(obj) {
  10830.   var seen = [];
  10831.  
  10832.   return JSON.stringify(obj, function(key, val) {
  10833.     val = toJsonReplacer(key, val);
  10834.     if (isObject(val)) {
  10835.  
  10836.       if (seen.indexOf(val) >= 0) return '<<already seen>>';
  10837.  
  10838.       seen.push(val);
  10839.     }
  10840.     return val;
  10841.   });
  10842. }
  10843.  
  10844. function toDebugString(obj) {
  10845.   if (typeof obj === 'function') {
  10846.     return obj.toString().replace(/ \{[\s\S]*$/, '');
  10847.   } else if (typeof obj === 'undefined') {
  10848.     return 'undefined';
  10849.   } else if (typeof obj !== 'string') {
  10850.     return serializeObject(obj);
  10851.   }
  10852.   return obj;
  10853. }
  10854.  
  10855. /* global angularModule: true,
  10856.   version: true,
  10857.  
  10858.   $LocaleProvider,
  10859.   $CompileProvider,
  10860.  
  10861.   htmlAnchorDirective,
  10862.   inputDirective,
  10863.   inputDirective,
  10864.   formDirective,
  10865.   scriptDirective,
  10866.   selectDirective,
  10867.   styleDirective,
  10868.   optionDirective,
  10869.   ngBindDirective,
  10870.   ngBindHtmlDirective,
  10871.   ngBindTemplateDirective,
  10872.   ngClassDirective,
  10873.   ngClassEvenDirective,
  10874.   ngClassOddDirective,
  10875.   ngCspDirective,
  10876.   ngCloakDirective,
  10877.   ngControllerDirective,
  10878.   ngFormDirective,
  10879.   ngHideDirective,
  10880.   ngIfDirective,
  10881.   ngIncludeDirective,
  10882.   ngIncludeFillContentDirective,
  10883.   ngInitDirective,
  10884.   ngNonBindableDirective,
  10885.   ngPluralizeDirective,
  10886.   ngRepeatDirective,
  10887.   ngShowDirective,
  10888.   ngStyleDirective,
  10889.   ngSwitchDirective,
  10890.   ngSwitchWhenDirective,
  10891.   ngSwitchDefaultDirective,
  10892.   ngOptionsDirective,
  10893.   ngTranscludeDirective,
  10894.   ngModelDirective,
  10895.   ngListDirective,
  10896.   ngChangeDirective,
  10897.   patternDirective,
  10898.   patternDirective,
  10899.   requiredDirective,
  10900.   requiredDirective,
  10901.   minlengthDirective,
  10902.   minlengthDirective,
  10903.   maxlengthDirective,
  10904.   maxlengthDirective,
  10905.   ngValueDirective,
  10906.   ngModelOptionsDirective,
  10907.   ngAttributeAliasDirectives,
  10908.   ngEventDirectives,
  10909.  
  10910.   $AnchorScrollProvider,
  10911.   $AnimateProvider,
  10912.   $BrowserProvider,
  10913.   $CacheFactoryProvider,
  10914.   $ControllerProvider,
  10915.   $DocumentProvider,
  10916.   $ExceptionHandlerProvider,
  10917.   $FilterProvider,
  10918.   $InterpolateProvider,
  10919.   $IntervalProvider,
  10920.   $HttpProvider,
  10921.   $HttpBackendProvider,
  10922.   $LocationProvider,
  10923.   $LogProvider,
  10924.   $ParseProvider,
  10925.   $RootScopeProvider,
  10926.   $QProvider,
  10927.   $$QProvider,
  10928.   $$SanitizeUriProvider,
  10929.   $SceProvider,
  10930.   $SceDelegateProvider,
  10931.   $SnifferProvider,
  10932.   $TemplateCacheProvider,
  10933.   $TemplateRequestProvider,
  10934.   $$TestabilityProvider,
  10935.   $TimeoutProvider,
  10936.   $$RAFProvider,
  10937.   $$AsyncCallbackProvider,
  10938.   $WindowProvider,
  10939.   $$jqLiteProvider
  10940. */
  10941.  
  10942.  
  10943. /**
  10944.  * @ngdoc object
  10945.  * @name angular.version
  10946.  * @module ng
  10947.  * @description
  10948.  * An object that contains information about the current AngularJS version. This object has the
  10949.  * following properties:
  10950.  *
  10951.  * - `full` – `{string}` – Full version string, such as "0.9.18".
  10952.  * - `major` – `{number}` – Major version number, such as "0".
  10953.  * - `minor` – `{number}` – Minor version number, such as "9".
  10954.  * - `dot` – `{number}` – Dot version number, such as "18".
  10955.  * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
  10956.  */
  10957. var version = {
  10958.   full: '1.3.13',    // all of these placeholder strings will be replaced by grunt's
  10959.   major: 1,    // package task
  10960.   minor: 3,
  10961.   dot: 13,
  10962.   codeName: 'meticulous-riffleshuffle'
  10963. };
  10964.  
  10965.  
  10966. function publishExternalAPI(angular) {
  10967.   extend(angular, {
  10968.     'bootstrap': bootstrap,
  10969.     'copy': copy,
  10970.     'extend': extend,
  10971.     'equals': equals,
  10972.     'element': jqLite,
  10973.     'forEach': forEach,
  10974.     'injector': createInjector,
  10975.     'noop': noop,
  10976.     'bind': bind,
  10977.     'toJson': toJson,
  10978.     'fromJson': fromJson,
  10979.     'identity': identity,
  10980.     'isUndefined': isUndefined,
  10981.     'isDefined': isDefined,
  10982.     'isString': isString,
  10983.     'isFunction': isFunction,
  10984.     'isObject': isObject,
  10985.     'isNumber': isNumber,
  10986.     'isElement': isElement,
  10987.     'isArray': isArray,
  10988.     'version': version,
  10989.     'isDate': isDate,
  10990.     'lowercase': lowercase,
  10991.     'uppercase': uppercase,
  10992.     'callbacks': {counter: 0},
  10993.     'getTestability': getTestability,
  10994.     '$$minErr': minErr,
  10995.     '$$csp': csp,
  10996.     'reloadWithDebugInfo': reloadWithDebugInfo
  10997.   });
  10998.  
  10999.   angularModule = setupModuleLoader(window);
  11000.   try {
  11001.     angularModule('ngLocale');
  11002.   } catch (e) {
  11003.     angularModule('ngLocale', []).provider('$locale', $LocaleProvider);
  11004.   }
  11005.  
  11006.   angularModule('ng', ['ngLocale'], ['$provide',
  11007.     function ngModule($provide) {
  11008.       // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
  11009.       $provide.provider({
  11010.         $$sanitizeUri: $$SanitizeUriProvider
  11011.       });
  11012.       $provide.provider('$compile', $CompileProvider).
  11013.         directive({
  11014.             a: htmlAnchorDirective,
  11015.             input: inputDirective,
  11016.             textarea: inputDirective,
  11017.             form: formDirective,
  11018.             script: scriptDirective,
  11019.             select: selectDirective,
  11020.             style: styleDirective,
  11021.             option: optionDirective,
  11022.             ngBind: ngBindDirective,
  11023.             ngBindHtml: ngBindHtmlDirective,
  11024.             ngBindTemplate: ngBindTemplateDirective,
  11025.             ngClass: ngClassDirective,
  11026.             ngClassEven: ngClassEvenDirective,
  11027.             ngClassOdd: ngClassOddDirective,
  11028.             ngCloak: ngCloakDirective,
  11029.             ngController: ngControllerDirective,
  11030.             ngForm: ngFormDirective,
  11031.             ngHide: ngHideDirective,
  11032.             ngIf: ngIfDirective,
  11033.             ngInclude: ngIncludeDirective,
  11034.             ngInit: ngInitDirective,
  11035.             ngNonBindable: ngNonBindableDirective,
  11036.             ngPluralize: ngPluralizeDirective,
  11037.             ngRepeat: ngRepeatDirective,
  11038.             ngShow: ngShowDirective,
  11039.             ngStyle: ngStyleDirective,
  11040.             ngSwitch: ngSwitchDirective,
  11041.             ngSwitchWhen: ngSwitchWhenDirective,
  11042.             ngSwitchDefault: ngSwitchDefaultDirective,
  11043.             ngOptions: ngOptionsDirective,
  11044.             ngTransclude: ngTranscludeDirective,
  11045.             ngModel: ngModelDirective,
  11046.             ngList: ngListDirective,
  11047.             ngChange: ngChangeDirective,
  11048.             pattern: patternDirective,
  11049.             ngPattern: patternDirective,
  11050.             required: requiredDirective,
  11051.             ngRequired: requiredDirective,
  11052.             minlength: minlengthDirective,
  11053.             ngMinlength: minlengthDirective,
  11054.             maxlength: maxlengthDirective,
  11055.             ngMaxlength: maxlengthDirective,
  11056.             ngValue: ngValueDirective,
  11057.             ngModelOptions: ngModelOptionsDirective
  11058.         }).
  11059.         directive({
  11060.           ngInclude: ngIncludeFillContentDirective
  11061.         }).
  11062.         directive(ngAttributeAliasDirectives).
  11063.         directive(ngEventDirectives);
  11064.       $provide.provider({
  11065.         $anchorScroll: $AnchorScrollProvider,
  11066.         $animate: $AnimateProvider,
  11067.         $browser: $BrowserProvider,
  11068.         $cacheFactory: $CacheFactoryProvider,
  11069.         $controller: $ControllerProvider,
  11070.         $document: $DocumentProvider,
  11071.         $exceptionHandler: $ExceptionHandlerProvider,
  11072.         $filter: $FilterProvider,
  11073.         $interpolate: $InterpolateProvider,
  11074.         $interval: $IntervalProvider,
  11075.         $http: $HttpProvider,
  11076.         $httpBackend: $HttpBackendProvider,
  11077.         $location: $LocationProvider,
  11078.         $log: $LogProvider,
  11079.         $parse: $ParseProvider,
  11080.         $rootScope: $RootScopeProvider,
  11081.         $q: $QProvider,
  11082.         $$q: $$QProvider,
  11083.         $sce: $SceProvider,
  11084.         $sceDelegate: $SceDelegateProvider,
  11085.         $sniffer: $SnifferProvider,
  11086.         $templateCache: $TemplateCacheProvider,
  11087.         $templateRequest: $TemplateRequestProvider,
  11088.         $$testability: $$TestabilityProvider,
  11089.         $timeout: $TimeoutProvider,
  11090.         $window: $WindowProvider,
  11091.         $$rAF: $$RAFProvider,
  11092.         $$asyncCallback: $$AsyncCallbackProvider,
  11093.         $$jqLite: $$jqLiteProvider
  11094.       });
  11095.     }
  11096.   ]);
  11097. }
  11098.  
  11099. /* global JQLitePrototype: true,
  11100.   addEventListenerFn: true,
  11101.   removeEventListenerFn: true,
  11102.   BOOLEAN_ATTR: true,
  11103.   ALIASED_ATTR: true,
  11104. */
  11105.  
  11106. //////////////////////////////////
  11107. //JQLite
  11108. //////////////////////////////////
  11109.  
  11110. /**
  11111.  * @ngdoc function
  11112.  * @name angular.element
  11113.  * @module ng
  11114.  * @kind function
  11115.  *
  11116.  * @description
  11117.  * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
  11118.  *
  11119.  * If jQuery is available, `angular.element` is an alias for the
  11120.  * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
  11121.  * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
  11122.  *
  11123.  * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
  11124.  * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
  11125.  * commonly needed functionality with the goal of having a very small footprint.</div>
  11126.  *
  11127.  * To use jQuery, simply load it before `DOMContentLoaded` event fired.
  11128.  *
  11129.  * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
  11130.  * jqLite; they are never raw DOM references.</div>
  11131.  *
  11132.  * ## Angular's jqLite
  11133.  * jqLite provides only the following jQuery methods:
  11134.  *
  11135.  * - [`addClass()`](http://api.jquery.com/addClass/)
  11136.  * - [`after()`](http://api.jquery.com/after/)
  11137.  * - [`append()`](http://api.jquery.com/append/)
  11138.  * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
  11139.  * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
  11140.  * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
  11141.  * - [`clone()`](http://api.jquery.com/clone/)
  11142.  * - [`contents()`](http://api.jquery.com/contents/)
  11143.  * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`
  11144.  * - [`data()`](http://api.jquery.com/data/)
  11145.  * - [`detach()`](http://api.jquery.com/detach/)
  11146.  * - [`empty()`](http://api.jquery.com/empty/)
  11147.  * - [`eq()`](http://api.jquery.com/eq/)
  11148.  * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
  11149.  * - [`hasClass()`](http://api.jquery.com/hasClass/)
  11150.  * - [`html()`](http://api.jquery.com/html/)
  11151.  * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
  11152.  * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
  11153.  * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors
  11154.  * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
  11155.  * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
  11156.  * - [`prepend()`](http://api.jquery.com/prepend/)
  11157.  * - [`prop()`](http://api.jquery.com/prop/)
  11158.  * - [`ready()`](http://api.jquery.com/ready/)
  11159.  * - [`remove()`](http://api.jquery.com/remove/)
  11160.  * - [`removeAttr()`](http://api.jquery.com/removeAttr/)
  11161.  * - [`removeClass()`](http://api.jquery.com/removeClass/)
  11162.  * - [`removeData()`](http://api.jquery.com/removeData/)
  11163.  * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
  11164.  * - [`text()`](http://api.jquery.com/text/)
  11165.  * - [`toggleClass()`](http://api.jquery.com/toggleClass/)
  11166.  * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
  11167.  * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces
  11168.  * - [`val()`](http://api.jquery.com/val/)
  11169.  * - [`wrap()`](http://api.jquery.com/wrap/)
  11170.  *
  11171.  * ## jQuery/jqLite Extras
  11172.  * Angular also provides the following additional methods and events to both jQuery and jqLite:
  11173.  *
  11174.  * ### Events
  11175.  * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
  11176.  *    on all DOM nodes being removed.  This can be used to clean up any 3rd party bindings to the DOM
  11177.  *    element before it is removed.
  11178.  *
  11179.  * ### Methods
  11180.  * - `controller(name)` - retrieves the controller of the current element or its parent. By default
  11181.  *   retrieves controller associated with the `ngController` directive. If `name` is provided as
  11182.  *   camelCase directive name, then the controller for this directive will be retrieved (e.g.
  11183.  *   `'ngModel'`).
  11184.  * - `injector()` - retrieves the injector of the current element or its parent.
  11185.  * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
  11186.  *   element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
  11187.  *   be enabled.
  11188.  * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
  11189.  *   current element. This getter should be used only on elements that contain a directive which starts a new isolate
  11190.  *   scope. Calling `scope()` on this element always returns the original non-isolate scope.
  11191.  *   Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
  11192.  * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
  11193.  *   parent element is reached.
  11194.  *
  11195.  * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
  11196.  * @returns {Object} jQuery object.
  11197.  */
  11198.  
  11199. JQLite.expando = 'ng339';
  11200.  
  11201. var jqCache = JQLite.cache = {},
  11202.     jqId = 1,
  11203.     addEventListenerFn = function(element, type, fn) {
  11204.       element.addEventListener(type, fn, false);
  11205.     },
  11206.     removeEventListenerFn = function(element, type, fn) {
  11207.       element.removeEventListener(type, fn, false);
  11208.     };
  11209.  
  11210. /*
  11211.  * !!! This is an undocumented "private" function !!!
  11212.  */
  11213. JQLite._data = function(node) {
  11214.   //jQuery always returns an object on cache miss
  11215.   return this.cache[node[this.expando]] || {};
  11216. };
  11217.  
  11218. function jqNextId() { return ++jqId; }
  11219.  
  11220.  
  11221. var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
  11222. var MOZ_HACK_REGEXP = /^moz([A-Z])/;
  11223. var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"};
  11224. var jqLiteMinErr = minErr('jqLite');
  11225.  
  11226. /**
  11227.  * Converts snake_case to camelCase.
  11228.  * Also there is special case for Moz prefix starting with upper case letter.
  11229.  * @param name Name to normalize
  11230.  */
  11231. function camelCase(name) {
  11232.   return name.
  11233.     replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
  11234.       return offset ? letter.toUpperCase() : letter;
  11235.     }).
  11236.     replace(MOZ_HACK_REGEXP, 'Moz$1');
  11237. }
  11238.  
  11239. var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/;
  11240. var HTML_REGEXP = /<|&#?\w+;/;
  11241. var TAG_NAME_REGEXP = /<([\w:]+)/;
  11242. var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi;
  11243.  
  11244. var wrapMap = {
  11245.   'option': [1, '<select multiple="multiple">', '</select>'],
  11246.  
  11247.   'thead': [1, '<table>', '</table>'],
  11248.   'col': [2, '<table><colgroup>', '</colgroup></table>'],
  11249.   'tr': [2, '<table><tbody>', '</tbody></table>'],
  11250.   'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
  11251.   '_default': [0, "", ""]
  11252. };
  11253.  
  11254. wrapMap.optgroup = wrapMap.option;
  11255. wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
  11256. wrapMap.th = wrapMap.td;
  11257.  
  11258.  
  11259. function jqLiteIsTextNode(html) {
  11260.   return !HTML_REGEXP.test(html);
  11261. }
  11262.  
  11263. function jqLiteAcceptsData(node) {
  11264.   // The window object can accept data but has no nodeType
  11265.   // Otherwise we are only interested in elements (1) and documents (9)
  11266.   var nodeType = node.nodeType;
  11267.   return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
  11268. }
  11269.  
  11270. function jqLiteBuildFragment(html, context) {
  11271.   var tmp, tag, wrap,
  11272.       fragment = context.createDocumentFragment(),
  11273.       nodes = [], i;
  11274.  
  11275.   if (jqLiteIsTextNode(html)) {
  11276.     // Convert non-html into a text node
  11277.     nodes.push(context.createTextNode(html));
  11278.   } else {
  11279.     // Convert html into DOM nodes
  11280.     tmp = tmp || fragment.appendChild(context.createElement("div"));
  11281.     tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
  11282.     wrap = wrapMap[tag] || wrapMap._default;
  11283.     tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];
  11284.  
  11285.     // Descend through wrappers to the right content
  11286.     i = wrap[0];
  11287.     while (i--) {
  11288.       tmp = tmp.lastChild;
  11289.     }
  11290.  
  11291.     nodes = concat(nodes, tmp.childNodes);
  11292.  
  11293.     tmp = fragment.firstChild;
  11294.     tmp.textContent = "";
  11295.   }
  11296.  
  11297.   // Remove wrapper from fragment
  11298.   fragment.textContent = "";
  11299.   fragment.innerHTML = ""; // Clear inner HTML
  11300.   forEach(nodes, function(node) {
  11301.     fragment.appendChild(node);
  11302.   });
  11303.  
  11304.   return fragment;
  11305. }
  11306.  
  11307. function jqLiteParseHTML(html, context) {
  11308.   context = context || document;
  11309.   var parsed;
  11310.  
  11311.   if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
  11312.     return [context.createElement(parsed[1])];
  11313.   }
  11314.  
  11315.   if ((parsed = jqLiteBuildFragment(html, context))) {
  11316.     return parsed.childNodes;
  11317.   }
  11318.  
  11319.   return [];
  11320. }
  11321.  
  11322. /////////////////////////////////////////////
  11323. function JQLite(element) {
  11324.   if (element instanceof JQLite) {
  11325.     return element;
  11326.   }
  11327.  
  11328.   var argIsString;
  11329.  
  11330.   if (isString(element)) {
  11331.     element = trim(element);
  11332.     argIsString = true;
  11333.   }
  11334.   if (!(this instanceof JQLite)) {
  11335.     if (argIsString && element.charAt(0) != '<') {
  11336.       throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
  11337.     }
  11338.     return new JQLite(element);
  11339.   }
  11340.  
  11341.   if (argIsString) {
  11342.     jqLiteAddNodes(this, jqLiteParseHTML(element));
  11343.   } else {
  11344.     jqLiteAddNodes(this, element);
  11345.   }
  11346. }
  11347.  
  11348. function jqLiteClone(element) {
  11349.   return element.cloneNode(true);
  11350. }
  11351.  
  11352. function jqLiteDealoc(element, onlyDescendants) {
  11353.   if (!onlyDescendants) jqLiteRemoveData(element);
  11354.  
  11355.   if (element.querySelectorAll) {
  11356.     var descendants = element.querySelectorAll('*');
  11357.     for (var i = 0, l = descendants.length; i < l; i++) {
  11358.       jqLiteRemoveData(descendants[i]);
  11359.     }
  11360.   }
  11361. }
  11362.  
  11363. function jqLiteOff(element, type, fn, unsupported) {
  11364.   if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
  11365.  
  11366.   var expandoStore = jqLiteExpandoStore(element);
  11367.   var events = expandoStore && expandoStore.events;
  11368.   var handle = expandoStore && expandoStore.handle;
  11369.  
  11370.   if (!handle) return; //no listeners registered
  11371.  
  11372.   if (!type) {
  11373.     for (type in events) {
  11374.       if (type !== '$destroy') {
  11375.         removeEventListenerFn(element, type, handle);
  11376.       }
  11377.       delete events[type];
  11378.     }
  11379.   } else {
  11380.     forEach(type.split(' '), function(type) {
  11381.       if (isDefined(fn)) {
  11382.         var listenerFns = events[type];
  11383.         arrayRemove(listenerFns || [], fn);
  11384.         if (listenerFns && listenerFns.length > 0) {
  11385.           return;
  11386.         }
  11387.       }
  11388.  
  11389.       removeEventListenerFn(element, type, handle);
  11390.       delete events[type];
  11391.     });
  11392.   }
  11393. }
  11394.  
  11395. function jqLiteRemoveData(element, name) {
  11396.   var expandoId = element.ng339;
  11397.   var expandoStore = expandoId && jqCache[expandoId];
  11398.  
  11399.   if (expandoStore) {
  11400.     if (name) {
  11401.       delete expandoStore.data[name];
  11402.       return;
  11403.     }
  11404.  
  11405.     if (expandoStore.handle) {
  11406.       if (expandoStore.events.$destroy) {
  11407.         expandoStore.handle({}, '$destroy');
  11408.       }
  11409.       jqLiteOff(element);
  11410.     }
  11411.     delete jqCache[expandoId];
  11412.     element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
  11413.   }
  11414. }
  11415.  
  11416.  
  11417. function jqLiteExpandoStore(element, createIfNecessary) {
  11418.   var expandoId = element.ng339,
  11419.       expandoStore = expandoId && jqCache[expandoId];
  11420.  
  11421.   if (createIfNecessary && !expandoStore) {
  11422.     elem
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement