arnaucube
arnaucube/commonroutesAppPublic Self-hosted repository
commonroutesApp/www/lib/ionic/js/ionic.js
www/lib/ionic/js/ionic.jsRaw
    1/*!
    2 * Copyright 2015 Drifty Co.
    3 * http://drifty.com/
    4 *
    5 * Ionic, v1.3.1
    6 * A powerful HTML5 mobile app framework.
    7 * http://ionicframework.com/
    8 *
    9 * By @maxlynch, @benjsperry, @adamdbradley <3
   10 *
   11 * Licensed under the MIT license. Please see LICENSE for more information.
   12 *
   13 */
   14
   15(function() {
   16
   17// Create global ionic obj and its namespaces
   18// build processes may have already created an ionic obj
   19window.ionic = window.ionic || {};
   20window.ionic.views = {};
   21window.ionic.version = '1.3.1';
   22
   23(function (ionic) {
   24
   25  ionic.DelegateService = function(methodNames) {
   26
   27    if (methodNames.indexOf('$getByHandle') > -1) {
   28      throw new Error("Method '$getByHandle' is implicitly added to each delegate service. Do not list it as a method.");
   29    }
   30
   31    function trueFn() { return true; }
   32
   33    return ['$log', function($log) {
   34
   35      /*
   36       * Creates a new object that will have all the methodNames given,
   37       * and call them on the given the controller instance matching given
   38       * handle.
   39       * The reason we don't just let $getByHandle return the controller instance
   40       * itself is that the controller instance might not exist yet.
   41       *
   42       * We want people to be able to do
   43       * `var instance = $ionicScrollDelegate.$getByHandle('foo')` on controller
   44       * instantiation, but on controller instantiation a child directive
   45       * may not have been compiled yet!
   46       *
   47       * So this is our way of solving this problem: we create an object
   48       * that will only try to fetch the controller with given handle
   49       * once the methods are actually called.
   50       */
   51      function DelegateInstance(instances, handle) {
   52        this._instances = instances;
   53        this.handle = handle;
   54      }
   55      methodNames.forEach(function(methodName) {
   56        DelegateInstance.prototype[methodName] = instanceMethodCaller(methodName);
   57      });
   58
   59
   60      /**
   61       * The delegate service (eg $ionicNavBarDelegate) is just an instance
   62       * with a non-defined handle, a couple extra methods for registering
   63       * and narrowing down to a specific handle.
   64       */
   65      function DelegateService() {
   66        this._instances = [];
   67      }
   68      DelegateService.prototype = DelegateInstance.prototype;
   69      DelegateService.prototype._registerInstance = function(instance, handle, filterFn) {
   70        var instances = this._instances;
   71        instance.$$delegateHandle = handle;
   72        instance.$$filterFn = filterFn || trueFn;
   73        instances.push(instance);
   74
   75        return function deregister() {
   76          var index = instances.indexOf(instance);
   77          if (index !== -1) {
   78            instances.splice(index, 1);
   79          }
   80        };
   81      };
   82      DelegateService.prototype.$getByHandle = function(handle) {
   83        return new DelegateInstance(this._instances, handle);
   84      };
   85
   86      return new DelegateService();
   87
   88      function instanceMethodCaller(methodName) {
   89        return function caller() {
   90          var handle = this.handle;
   91          var args = arguments;
   92          var foundInstancesCount = 0;
   93          var returnValue;
   94
   95          this._instances.forEach(function(instance) {
   96            if ((!handle || handle == instance.$$delegateHandle) && instance.$$filterFn(instance)) {
   97              foundInstancesCount++;
   98              var ret = instance[methodName].apply(instance, args);
   99              //Only return the value from the first call
  100              if (foundInstancesCount === 1) {
  101                returnValue = ret;
  102              }
  103            }
  104          });
  105
  106          if (!foundInstancesCount && handle) {
  107            return $log.warn(
  108              'Delegate for handle "' + handle + '" could not find a ' +
  109              'corresponding element with delegate-handle="' + handle + '"! ' +
  110              methodName + '() was not called!\n' +
  111              'Possible cause: If you are calling ' + methodName + '() immediately, and ' +
  112              'your element with delegate-handle="' + handle + '" is a child of your ' +
  113              'controller, then your element may not be compiled yet. Put a $timeout ' +
  114              'around your call to ' + methodName + '() and try again.'
  115            );
  116          }
  117          return returnValue;
  118        };
  119      }
  120
  121    }];
  122  };
  123
  124})(window.ionic);
  125
  126(function(window, document, ionic) {
  127
  128  var readyCallbacks = [];
  129  var isDomReady = document.readyState === 'complete' || document.readyState === 'interactive';
  130
  131  function domReady() {
  132    isDomReady = true;
  133    for (var x = 0; x < readyCallbacks.length; x++) {
  134      ionic.requestAnimationFrame(readyCallbacks[x]);
  135    }
  136    readyCallbacks = [];
  137    document.removeEventListener('DOMContentLoaded', domReady);
  138  }
  139  if (!isDomReady) {
  140    document.addEventListener('DOMContentLoaded', domReady);
  141  }
  142
  143
  144  // From the man himself, Mr. Paul Irish.
  145  // The requestAnimationFrame polyfill
  146  // Put it on window just to preserve its context
  147  // without having to use .call
  148  window._rAF = (function() {
  149    return window.requestAnimationFrame ||
  150           window.webkitRequestAnimationFrame ||
  151           window.mozRequestAnimationFrame ||
  152           function(callback) {
  153             window.setTimeout(callback, 16);
  154           };
  155  })();
  156
  157  var cancelAnimationFrame = window.cancelAnimationFrame ||
  158    window.webkitCancelAnimationFrame ||
  159    window.mozCancelAnimationFrame ||
  160    window.webkitCancelRequestAnimationFrame;
  161
  162  /**
  163  * @ngdoc utility
  164  * @name ionic.DomUtil
  165  * @module ionic
  166  */
  167  ionic.DomUtil = {
  168    //Call with proper context
  169    /**
  170     * @ngdoc method
  171     * @name ionic.DomUtil#requestAnimationFrame
  172     * @alias ionic.requestAnimationFrame
  173     * @description Calls [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame), or a polyfill if not available.
  174     * @param {function} callback The function to call when the next frame
  175     * happens.
  176     */
  177    requestAnimationFrame: function(cb) {
  178      return window._rAF(cb);
  179    },
  180
  181    cancelAnimationFrame: function(requestId) {
  182      cancelAnimationFrame(requestId);
  183    },
  184
  185    /**
  186     * @ngdoc method
  187     * @name ionic.DomUtil#animationFrameThrottle
  188     * @alias ionic.animationFrameThrottle
  189     * @description
  190     * When given a callback, if that callback is called 100 times between
  191     * animation frames, adding Throttle will make it only run the last of
  192     * the 100 calls.
  193     *
  194     * @param {function} callback a function which will be throttled to
  195     * requestAnimationFrame
  196     * @returns {function} A function which will then call the passed in callback.
  197     * The passed in callback will receive the context the returned function is
  198     * called with.
  199     */
  200    animationFrameThrottle: function(cb) {
  201      var args, isQueued, context;
  202      return function() {
  203        args = arguments;
  204        context = this;
  205        if (!isQueued) {
  206          isQueued = true;
  207          ionic.requestAnimationFrame(function() {
  208            cb.apply(context, args);
  209            isQueued = false;
  210          });
  211        }
  212      };
  213    },
  214
  215    contains: function(parentNode, otherNode) {
  216      var current = otherNode;
  217      while (current) {
  218        if (current === parentNode) return true;
  219        current = current.parentNode;
  220      }
  221    },
  222
  223    /**
  224     * @ngdoc method
  225     * @name ionic.DomUtil#getPositionInParent
  226     * @description
  227     * Find an element's scroll offset within its container.
  228     * @param {DOMElement} element The element to find the offset of.
  229     * @returns {object} A position object with the following properties:
  230     *   - `{number}` `left` The left offset of the element.
  231     *   - `{number}` `top` The top offset of the element.
  232     */
  233    getPositionInParent: function(el) {
  234      return {
  235        left: el.offsetLeft,
  236        top: el.offsetTop
  237      };
  238    },
  239
  240    getOffsetTop: function(el) {
  241      var curtop = 0;
  242      if (el.offsetParent) {
  243        do {
  244          curtop += el.offsetTop;
  245          el = el.offsetParent;
  246        } while (el)
  247        return curtop;
  248      }
  249    },
  250
  251    /**
  252     * @ngdoc method
  253     * @name ionic.DomUtil#ready
  254     * @description
  255     * Call a function when the DOM is ready, or if it is already ready
  256     * call the function immediately.
  257     * @param {function} callback The function to be called.
  258     */
  259    ready: function(cb) {
  260      if (isDomReady) {
  261        ionic.requestAnimationFrame(cb);
  262      } else {
  263        readyCallbacks.push(cb);
  264      }
  265    },
  266
  267    /**
  268     * @ngdoc method
  269     * @name ionic.DomUtil#getTextBounds
  270     * @description
  271     * Get a rect representing the bounds of the given textNode.
  272     * @param {DOMElement} textNode The textNode to find the bounds of.
  273     * @returns {object} An object representing the bounds of the node. Properties:
  274     *   - `{number}` `left` The left position of the textNode.
  275     *   - `{number}` `right` The right position of the textNode.
  276     *   - `{number}` `top` The top position of the textNode.
  277     *   - `{number}` `bottom` The bottom position of the textNode.
  278     *   - `{number}` `width` The width of the textNode.
  279     *   - `{number}` `height` The height of the textNode.
  280     */
  281    getTextBounds: function(textNode) {
  282      if (document.createRange) {
  283        var range = document.createRange();
  284        range.selectNodeContents(textNode);
  285        if (range.getBoundingClientRect) {
  286          var rect = range.getBoundingClientRect();
  287          if (rect) {
  288            var sx = window.scrollX;
  289            var sy = window.scrollY;
  290
  291            return {
  292              top: rect.top + sy,
  293              left: rect.left + sx,
  294              right: rect.left + sx + rect.width,
  295              bottom: rect.top + sy + rect.height,
  296              width: rect.width,
  297              height: rect.height
  298            };
  299          }
  300        }
  301      }
  302      return null;
  303    },
  304
  305    /**
  306     * @ngdoc method
  307     * @name ionic.DomUtil#getChildIndex
  308     * @description
  309     * Get the first index of a child node within the given element of the
  310     * specified type.
  311     * @param {DOMElement} element The element to find the index of.
  312     * @param {string} type The nodeName to match children of element against.
  313     * @returns {number} The index, or -1, of a child with nodeName matching type.
  314     */
  315    getChildIndex: function(element, type) {
  316      if (type) {
  317        var ch = element.parentNode.children;
  318        var c;
  319        for (var i = 0, k = 0, j = ch.length; i < j; i++) {
  320          c = ch[i];
  321          if (c.nodeName && c.nodeName.toLowerCase() == type) {
  322            if (c == element) {
  323              return k;
  324            }
  325            k++;
  326          }
  327        }
  328      }
  329      return Array.prototype.slice.call(element.parentNode.children).indexOf(element);
  330    },
  331
  332    /**
  333     * @private
  334     */
  335    swapNodes: function(src, dest) {
  336      dest.parentNode.insertBefore(src, dest);
  337    },
  338
  339    elementIsDescendant: function(el, parent, stopAt) {
  340      var current = el;
  341      do {
  342        if (current === parent) return true;
  343        current = current.parentNode;
  344      } while (current && current !== stopAt);
  345      return false;
  346    },
  347
  348    /**
  349     * @ngdoc method
  350     * @name ionic.DomUtil#getParentWithClass
  351     * @param {DOMElement} element
  352     * @param {string} className
  353     * @returns {DOMElement} The closest parent of element matching the
  354     * className, or null.
  355     */
  356    getParentWithClass: function(e, className, depth) {
  357      depth = depth || 10;
  358      while (e.parentNode && depth--) {
  359        if (e.parentNode.classList && e.parentNode.classList.contains(className)) {
  360          return e.parentNode;
  361        }
  362        e = e.parentNode;
  363      }
  364      return null;
  365    },
  366    /**
  367     * @ngdoc method
  368     * @name ionic.DomUtil#getParentOrSelfWithClass
  369     * @param {DOMElement} element
  370     * @param {string} className
  371     * @returns {DOMElement} The closest parent or self matching the
  372     * className, or null.
  373     */
  374    getParentOrSelfWithClass: function(e, className, depth) {
  375      depth = depth || 10;
  376      while (e && depth--) {
  377        if (e.classList && e.classList.contains(className)) {
  378          return e;
  379        }
  380        e = e.parentNode;
  381      }
  382      return null;
  383    },
  384
  385    /**
  386     * @ngdoc method
  387     * @name ionic.DomUtil#rectContains
  388     * @param {number} x
  389     * @param {number} y
  390     * @param {number} x1
  391     * @param {number} y1
  392     * @param {number} x2
  393     * @param {number} y2
  394     * @returns {boolean} Whether {x,y} fits within the rectangle defined by
  395     * {x1,y1,x2,y2}.
  396     */
  397    rectContains: function(x, y, x1, y1, x2, y2) {
  398      if (x < x1 || x > x2) return false;
  399      if (y < y1 || y > y2) return false;
  400      return true;
  401    },
  402
  403    /**
  404     * @ngdoc method
  405     * @name ionic.DomUtil#blurAll
  406     * @description
  407     * Blurs any currently focused input element
  408     * @returns {DOMElement} The element blurred or null
  409     */
  410    blurAll: function() {
  411      if (document.activeElement && document.activeElement != document.body) {
  412        document.activeElement.blur();
  413        return document.activeElement;
  414      }
  415      return null;
  416    },
  417
  418    cachedAttr: function(ele, key, value) {
  419      ele = ele && ele.length && ele[0] || ele;
  420      if (ele && ele.setAttribute) {
  421        var dataKey = '$attr-' + key;
  422        if (arguments.length > 2) {
  423          if (ele[dataKey] !== value) {
  424            ele.setAttribute(key, value);
  425            ele[dataKey] = value;
  426          }
  427        } else if (typeof ele[dataKey] == 'undefined') {
  428          ele[dataKey] = ele.getAttribute(key);
  429        }
  430        return ele[dataKey];
  431      }
  432    },
  433
  434    cachedStyles: function(ele, styles) {
  435      ele = ele && ele.length && ele[0] || ele;
  436      if (ele && ele.style) {
  437        for (var prop in styles) {
  438          if (ele['$style-' + prop] !== styles[prop]) {
  439            ele.style[prop] = ele['$style-' + prop] = styles[prop];
  440          }
  441        }
  442      }
  443    }
  444
  445  };
  446
  447  //Shortcuts
  448  ionic.requestAnimationFrame = ionic.DomUtil.requestAnimationFrame;
  449  ionic.cancelAnimationFrame = ionic.DomUtil.cancelAnimationFrame;
  450  ionic.animationFrameThrottle = ionic.DomUtil.animationFrameThrottle;
  451
  452})(window, document, ionic);
  453
  454/**
  455 * ion-events.js
  456 *
  457 * Author: Max Lynch <max@drifty.com>
  458 *
  459 * Framework events handles various mobile browser events, and
  460 * detects special events like tap/swipe/etc. and emits them
  461 * as custom events that can be used in an app.
  462 *
  463 * Portions lovingly adapted from github.com/maker/ratchet and github.com/alexgibson/tap.js - thanks guys!
  464 */
  465
  466(function(ionic) {
  467
  468  // Custom event polyfill
  469  ionic.CustomEvent = (function() {
  470    if( typeof window.CustomEvent === 'function' ) return CustomEvent;
  471
  472    var customEvent = function(event, params) {
  473      var evt;
  474      params = params || {
  475        bubbles: false,
  476        cancelable: false,
  477        detail: undefined
  478      };
  479      try {
  480        evt = document.createEvent("CustomEvent");
  481        evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
  482      } catch (error) {
  483        // fallback for browsers that don't support createEvent('CustomEvent')
  484        evt = document.createEvent("Event");
  485        for (var param in params) {
  486          evt[param] = params[param];
  487        }
  488        evt.initEvent(event, params.bubbles, params.cancelable);
  489      }
  490      return evt;
  491    };
  492    customEvent.prototype = window.Event.prototype;
  493    return customEvent;
  494  })();
  495
  496
  497  /**
  498   * @ngdoc utility
  499   * @name ionic.EventController
  500   * @module ionic
  501   */
  502  ionic.EventController = {
  503    VIRTUALIZED_EVENTS: ['tap', 'swipe', 'swiperight', 'swipeleft', 'drag', 'hold', 'release'],
  504
  505    /**
  506     * @ngdoc method
  507     * @name ionic.EventController#trigger
  508     * @alias ionic.trigger
  509     * @param {string} eventType The event to trigger.
  510     * @param {object} data The data for the event. Hint: pass in
  511     * `{target: targetElement}`
  512     * @param {boolean=} bubbles Whether the event should bubble up the DOM.
  513     * @param {boolean=} cancelable Whether the event should be cancelable.
  514     */
  515    // Trigger a new event
  516    trigger: function(eventType, data, bubbles, cancelable) {
  517      var event = new ionic.CustomEvent(eventType, {
  518        detail: data,
  519        bubbles: !!bubbles,
  520        cancelable: !!cancelable
  521      });
  522
  523      // Make sure to trigger the event on the given target, or dispatch it from
  524      // the window if we don't have an event target
  525      data && data.target && data.target.dispatchEvent && data.target.dispatchEvent(event) || window.dispatchEvent(event);
  526    },
  527
  528    /**
  529     * @ngdoc method
  530     * @name ionic.EventController#on
  531     * @alias ionic.on
  532     * @description Listen to an event on an element.
  533     * @param {string} type The event to listen for.
  534     * @param {function} callback The listener to be called.
  535     * @param {DOMElement} element The element to listen for the event on.
  536     */
  537    on: function(type, callback, element) {
  538      var e = element || window;
  539
  540      // Bind a gesture if it's a virtual event
  541      for(var i = 0, j = this.VIRTUALIZED_EVENTS.length; i < j; i++) {
  542        if(type == this.VIRTUALIZED_EVENTS[i]) {
  543          var gesture = new ionic.Gesture(element);
  544          gesture.on(type, callback);
  545          return gesture;
  546        }
  547      }
  548
  549      // Otherwise bind a normal event
  550      e.addEventListener(type, callback);
  551    },
  552
  553    /**
  554     * @ngdoc method
  555     * @name ionic.EventController#off
  556     * @alias ionic.off
  557     * @description Remove an event listener.
  558     * @param {string} type
  559     * @param {function} callback
  560     * @param {DOMElement} element
  561     */
  562    off: function(type, callback, element) {
  563      element.removeEventListener(type, callback);
  564    },
  565
  566    /**
  567     * @ngdoc method
  568     * @name ionic.EventController#onGesture
  569     * @alias ionic.onGesture
  570     * @description Add an event listener for a gesture on an element.
  571     *
  572     * Available eventTypes (from [hammer.js](http://eightmedia.github.io/hammer.js/)):
  573     *
  574     * `hold`, `tap`, `doubletap`, `drag`, `dragstart`, `dragend`, `dragup`, `dragdown`, <br/>
  575     * `dragleft`, `dragright`, `swipe`, `swipeup`, `swipedown`, `swipeleft`, `swiperight`, <br/>
  576     * `transform`, `transformstart`, `transformend`, `rotate`, `pinch`, `pinchin`, `pinchout`, <br/>
  577     * `touch`, `release`
  578     *
  579     * @param {string} eventType The gesture event to listen for.
  580     * @param {function(e)} callback The function to call when the gesture
  581     * happens.
  582     * @param {DOMElement} element The angular element to listen for the event on.
  583     * @param {object} options object.
  584     * @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on).
  585     */
  586    onGesture: function(type, callback, element, options) {
  587      var gesture = new ionic.Gesture(element, options);
  588      gesture.on(type, callback);
  589      return gesture;
  590    },
  591
  592    /**
  593     * @ngdoc method
  594     * @name ionic.EventController#offGesture
  595     * @alias ionic.offGesture
  596     * @description Remove an event listener for a gesture created on an element.
  597     * @param {ionic.Gesture} gesture The gesture that should be removed.
  598     * @param {string} eventType The gesture event to remove the listener for.
  599     * @param {function(e)} callback The listener to remove.
  600
  601     */
  602    offGesture: function(gesture, type, callback) {
  603      gesture && gesture.off(type, callback);
  604    },
  605
  606    handlePopState: function() {}
  607  };
  608
  609
  610  // Map some convenient top-level functions for event handling
  611  ionic.on = function() { ionic.EventController.on.apply(ionic.EventController, arguments); };
  612  ionic.off = function() { ionic.EventController.off.apply(ionic.EventController, arguments); };
  613  ionic.trigger = ionic.EventController.trigger;//function() { ionic.EventController.trigger.apply(ionic.EventController.trigger, arguments); };
  614  ionic.onGesture = function() { return ionic.EventController.onGesture.apply(ionic.EventController.onGesture, arguments); };
  615  ionic.offGesture = function() { return ionic.EventController.offGesture.apply(ionic.EventController.offGesture, arguments); };
  616
  617})(window.ionic);
  618
  619/* eslint camelcase:0 */
  620/**
  621  * Simple gesture controllers with some common gestures that emit
  622  * gesture events.
  623  *
  624  * Ported from github.com/EightMedia/hammer.js Gestures - thanks!
  625  */
  626(function(ionic) {
  627
  628  /**
  629   * ionic.Gestures
  630   * use this to create instances
  631   * @param   {HTMLElement}   element
  632   * @param   {Object}        options
  633   * @returns {ionic.Gestures.Instance}
  634   * @constructor
  635   */
  636  ionic.Gesture = function(element, options) {
  637    return new ionic.Gestures.Instance(element, options || {});
  638  };
  639
  640  ionic.Gestures = {};
  641
  642  // default settings
  643  ionic.Gestures.defaults = {
  644    // add css to the element to prevent the browser from doing
  645    // its native behavior. this doesnt prevent the scrolling,
  646    // but cancels the contextmenu, tap highlighting etc
  647    // set to false to disable this
  648    stop_browser_behavior: 'disable-user-behavior'
  649  };
  650
  651  // detect touchevents
  652  ionic.Gestures.HAS_POINTEREVENTS = window.navigator.pointerEnabled || window.navigator.msPointerEnabled;
  653  ionic.Gestures.HAS_TOUCHEVENTS = ('ontouchstart' in window);
  654
  655  // dont use mouseevents on mobile devices
  656  ionic.Gestures.MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android|silk/i;
  657  ionic.Gestures.NO_MOUSEEVENTS = ionic.Gestures.HAS_TOUCHEVENTS && window.navigator.userAgent.match(ionic.Gestures.MOBILE_REGEX);
  658
  659  // eventtypes per touchevent (start, move, end)
  660  // are filled by ionic.Gestures.event.determineEventTypes on setup
  661  ionic.Gestures.EVENT_TYPES = {};
  662
  663  // direction defines
  664  ionic.Gestures.DIRECTION_DOWN = 'down';
  665  ionic.Gestures.DIRECTION_LEFT = 'left';
  666  ionic.Gestures.DIRECTION_UP = 'up';
  667  ionic.Gestures.DIRECTION_RIGHT = 'right';
  668
  669  // pointer type
  670  ionic.Gestures.POINTER_MOUSE = 'mouse';
  671  ionic.Gestures.POINTER_TOUCH = 'touch';
  672  ionic.Gestures.POINTER_PEN = 'pen';
  673
  674  // touch event defines
  675  ionic.Gestures.EVENT_START = 'start';
  676  ionic.Gestures.EVENT_MOVE = 'move';
  677  ionic.Gestures.EVENT_END = 'end';
  678
  679  // hammer document where the base events are added at
  680  ionic.Gestures.DOCUMENT = window.document;
  681
  682  // plugins namespace
  683  ionic.Gestures.plugins = {};
  684
  685  // if the window events are set...
  686  ionic.Gestures.READY = false;
  687
  688  /**
  689   * setup events to detect gestures on the document
  690   */
  691  function setup() {
  692    if(ionic.Gestures.READY) {
  693      return;
  694    }
  695
  696    // find what eventtypes we add listeners to
  697    ionic.Gestures.event.determineEventTypes();
  698
  699    // Register all gestures inside ionic.Gestures.gestures
  700    for(var name in ionic.Gestures.gestures) {
  701      if(ionic.Gestures.gestures.hasOwnProperty(name)) {
  702        ionic.Gestures.detection.register(ionic.Gestures.gestures[name]);
  703      }
  704    }
  705
  706    // Add touch events on the document
  707    ionic.Gestures.event.onTouch(ionic.Gestures.DOCUMENT, ionic.Gestures.EVENT_MOVE, ionic.Gestures.detection.detect);
  708    ionic.Gestures.event.onTouch(ionic.Gestures.DOCUMENT, ionic.Gestures.EVENT_END, ionic.Gestures.detection.detect);
  709
  710    // ionic.Gestures is ready...!
  711    ionic.Gestures.READY = true;
  712  }
  713
  714  /**
  715   * create new hammer instance
  716   * all methods should return the instance itself, so it is chainable.
  717   * @param   {HTMLElement}       element
  718   * @param   {Object}            [options={}]
  719   * @returns {ionic.Gestures.Instance}
  720   * @name Gesture.Instance
  721   * @constructor
  722   */
  723  ionic.Gestures.Instance = function(element, options) {
  724    var self = this;
  725
  726    // A null element was passed into the instance, which means
  727    // whatever lookup was done to find this element failed to find it
  728    // so we can't listen for events on it.
  729    if(element === null) {
  730      void 0;
  731      return this;
  732    }
  733
  734    // setup ionic.GesturesJS window events and register all gestures
  735    // this also sets up the default options
  736    setup();
  737
  738    this.element = element;
  739
  740    // start/stop detection option
  741    this.enabled = true;
  742
  743    // merge options
  744    this.options = ionic.Gestures.utils.extend(
  745        ionic.Gestures.utils.extend({}, ionic.Gestures.defaults),
  746        options || {});
  747
  748    // add some css to the element to prevent the browser from doing its native behavoir
  749    if(this.options.stop_browser_behavior) {
  750      ionic.Gestures.utils.stopDefaultBrowserBehavior(this.element, this.options.stop_browser_behavior);
  751    }
  752
  753    // start detection on touchstart
  754    ionic.Gestures.event.onTouch(element, ionic.Gestures.EVENT_START, function(ev) {
  755      if(self.enabled) {
  756        ionic.Gestures.detection.startDetect(self, ev);
  757      }
  758    });
  759
  760    // return instance
  761    return this;
  762  };
  763
  764
  765  ionic.Gestures.Instance.prototype = {
  766    /**
  767     * bind events to the instance
  768     * @param   {String}      gesture
  769     * @param   {Function}    handler
  770     * @returns {ionic.Gestures.Instance}
  771     */
  772    on: function onEvent(gesture, handler){
  773      var gestures = gesture.split(' ');
  774      for(var t = 0; t < gestures.length; t++) {
  775        this.element.addEventListener(gestures[t], handler, false);
  776      }
  777      return this;
  778    },
  779
  780
  781    /**
  782     * unbind events to the instance
  783     * @param   {String}      gesture
  784     * @param   {Function}    handler
  785     * @returns {ionic.Gestures.Instance}
  786     */
  787    off: function offEvent(gesture, handler){
  788      var gestures = gesture.split(' ');
  789      for(var t = 0; t < gestures.length; t++) {
  790        this.element.removeEventListener(gestures[t], handler, false);
  791      }
  792      return this;
  793    },
  794
  795
  796    /**
  797     * trigger gesture event
  798     * @param   {String}      gesture
  799     * @param   {Object}      eventData
  800     * @returns {ionic.Gestures.Instance}
  801     */
  802    trigger: function triggerEvent(gesture, eventData){
  803      // create DOM event
  804      var event = ionic.Gestures.DOCUMENT.createEvent('Event');
  805      event.initEvent(gesture, true, true);
  806      event.gesture = eventData;
  807
  808      // trigger on the target if it is in the instance element,
  809      // this is for event delegation tricks
  810      var element = this.element;
  811      if(ionic.Gestures.utils.hasParent(eventData.target, element)) {
  812        element = eventData.target;
  813      }
  814
  815      element.dispatchEvent(event);
  816      return this;
  817    },
  818
  819
  820    /**
  821     * enable of disable hammer.js detection
  822     * @param   {Boolean}   state
  823     * @returns {ionic.Gestures.Instance}
  824     */
  825    enable: function enable(state) {
  826      this.enabled = state;
  827      return this;
  828    }
  829  };
  830
  831  /**
  832   * this holds the last move event,
  833   * used to fix empty touchend issue
  834   * see the onTouch event for an explanation
  835   * type {Object}
  836   */
  837  var last_move_event = null;
  838
  839
  840  /**
  841   * when the mouse is hold down, this is true
  842   * type {Boolean}
  843   */
  844  var enable_detect = false;
  845
  846
  847  /**
  848   * when touch events have been fired, this is true
  849   * type {Boolean}
  850   */
  851  var touch_triggered = false;
  852
  853
  854  ionic.Gestures.event = {
  855    /**
  856     * simple addEventListener
  857     * @param   {HTMLElement}   element
  858     * @param   {String}        type
  859     * @param   {Function}      handler
  860     */
  861    bindDom: function(element, type, handler) {
  862      var types = type.split(' ');
  863      for(var t = 0; t < types.length; t++) {
  864        element.addEventListener(types[t], handler, false);
  865      }
  866    },
  867
  868
  869    /**
  870     * touch events with mouse fallback
  871     * @param   {HTMLElement}   element
  872     * @param   {String}        eventType        like ionic.Gestures.EVENT_MOVE
  873     * @param   {Function}      handler
  874     */
  875    onTouch: function onTouch(element, eventType, handler) {
  876      var self = this;
  877
  878      this.bindDom(element, ionic.Gestures.EVENT_TYPES[eventType], function bindDomOnTouch(ev) {
  879        var sourceEventType = ev.type.toLowerCase();
  880
  881        // onmouseup, but when touchend has been fired we do nothing.
  882        // this is for touchdevices which also fire a mouseup on touchend
  883        if(sourceEventType.match(/mouse/) && touch_triggered) {
  884          return;
  885        }
  886
  887        // mousebutton must be down or a touch event
  888        else if( sourceEventType.match(/touch/) ||   // touch events are always on screen
  889          sourceEventType.match(/pointerdown/) || // pointerevents touch
  890          (sourceEventType.match(/mouse/) && ev.which === 1)   // mouse is pressed
  891          ){
  892            enable_detect = true;
  893          }
  894
  895        // mouse isn't pressed
  896        else if(sourceEventType.match(/mouse/) && ev.which !== 1) {
  897          enable_detect = false;
  898        }
  899
  900
  901        // we are in a touch event, set the touch triggered bool to true,
  902        // this for the conflicts that may occur on ios and android
  903        if(sourceEventType.match(/touch|pointer/)) {
  904          touch_triggered = true;
  905        }
  906
  907        // count the total touches on the screen
  908        var count_touches = 0;
  909
  910        // when touch has been triggered in this detection session
  911        // and we are now handling a mouse event, we stop that to prevent conflicts
  912        if(enable_detect) {
  913          // update pointerevent
  914          if(ionic.Gestures.HAS_POINTEREVENTS && eventType != ionic.Gestures.EVENT_END) {
  915            count_touches = ionic.Gestures.PointerEvent.updatePointer(eventType, ev);
  916          }
  917          // touch
  918          else if(sourceEventType.match(/touch/)) {
  919            count_touches = ev.touches.length;
  920          }
  921          // mouse
  922          else if(!touch_triggered) {
  923            count_touches = sourceEventType.match(/up/) ? 0 : 1;
  924          }
  925
  926          // if we are in a end event, but when we remove one touch and
  927          // we still have enough, set eventType to move
  928          if(count_touches > 0 && eventType == ionic.Gestures.EVENT_END) {
  929            eventType = ionic.Gestures.EVENT_MOVE;
  930          }
  931          // no touches, force the end event
  932          else if(!count_touches) {
  933            eventType = ionic.Gestures.EVENT_END;
  934          }
  935
  936          // store the last move event
  937          if(count_touches || last_move_event === null) {
  938            last_move_event = ev;
  939          }
  940
  941          // trigger the handler
  942          handler.call(ionic.Gestures.detection, self.collectEventData(element, eventType, self.getTouchList(last_move_event, eventType), ev));
  943
  944          // remove pointerevent from list
  945          if(ionic.Gestures.HAS_POINTEREVENTS && eventType == ionic.Gestures.EVENT_END) {
  946            count_touches = ionic.Gestures.PointerEvent.updatePointer(eventType, ev);
  947          }
  948        }
  949
  950        //debug(sourceEventType +" "+ eventType);
  951
  952        // on the end we reset everything
  953        if(!count_touches) {
  954          last_move_event = null;
  955          enable_detect = false;
  956          touch_triggered = false;
  957          ionic.Gestures.PointerEvent.reset();
  958        }
  959      });
  960    },
  961
  962
  963    /**
  964     * we have different events for each device/browser
  965     * determine what we need and set them in the ionic.Gestures.EVENT_TYPES constant
  966     */
  967    determineEventTypes: function determineEventTypes() {
  968      // determine the eventtype we want to set
  969      var types;
  970
  971      // pointerEvents magic
  972      if(ionic.Gestures.HAS_POINTEREVENTS) {
  973        types = ionic.Gestures.PointerEvent.getEvents();
  974      }
  975      // on Android, iOS, blackberry, windows mobile we dont want any mouseevents
  976      else if(ionic.Gestures.NO_MOUSEEVENTS) {
  977        types = [
  978          'touchstart',
  979          'touchmove',
  980          'touchend touchcancel'];
  981      }
  982      // for non pointer events browsers and mixed browsers,
  983      // like chrome on windows8 touch laptop
  984      else {
  985        types = [
  986          'touchstart mousedown',
  987          'touchmove mousemove',
  988          'touchend touchcancel mouseup'];
  989      }
  990
  991      ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_START] = types[0];
  992      ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_MOVE] = types[1];
  993      ionic.Gestures.EVENT_TYPES[ionic.Gestures.EVENT_END] = types[2];
  994    },
  995
  996
  997    /**
  998     * create touchlist depending on the event
  999     * @param   {Object}    ev
 1000     * @param   {String}    eventType   used by the fakemultitouch plugin
 1001     */
 1002    getTouchList: function getTouchList(ev/*, eventType*/) {
 1003      // get the fake pointerEvent touchlist
 1004      if(ionic.Gestures.HAS_POINTEREVENTS) {
 1005        return ionic.Gestures.PointerEvent.getTouchList();
 1006      }
 1007      // get the touchlist
 1008      else if(ev.touches) {
 1009        return ev.touches;
 1010      }
 1011      // make fake touchlist from mouse position
 1012      else {
 1013        ev.identifier = 1;
 1014        return [ev];
 1015      }
 1016    },
 1017
 1018
 1019    /**
 1020     * collect event data for ionic.Gestures js
 1021     * @param   {HTMLElement}   element
 1022     * @param   {String}        eventType        like ionic.Gestures.EVENT_MOVE
 1023     * @param   {Object}        eventData
 1024     */
 1025    collectEventData: function collectEventData(element, eventType, touches, ev) {
 1026
 1027      // find out pointerType
 1028      var pointerType = ionic.Gestures.POINTER_TOUCH;
 1029      if(ev.type.match(/mouse/) || ionic.Gestures.PointerEvent.matchType(ionic.Gestures.POINTER_MOUSE, ev)) {
 1030        pointerType = ionic.Gestures.POINTER_MOUSE;
 1031      }
 1032
 1033      return {
 1034        center: ionic.Gestures.utils.getCenter(touches),
 1035        timeStamp: new Date().getTime(),
 1036        target: ev.target,
 1037        touches: touches,
 1038        eventType: eventType,
 1039        pointerType: pointerType,
 1040        srcEvent: ev,
 1041
 1042        /**
 1043         * prevent the browser default actions
 1044         * mostly used to disable scrolling of the browser
 1045         */
 1046        preventDefault: function() {
 1047          if(this.srcEvent.preventManipulation) {
 1048            this.srcEvent.preventManipulation();
 1049          }
 1050
 1051          if(this.srcEvent.preventDefault) {
 1052            // this.srcEvent.preventDefault();
 1053          }
 1054        },
 1055
 1056        /**
 1057         * stop bubbling the event up to its parents
 1058         */
 1059        stopPropagation: function() {
 1060          this.srcEvent.stopPropagation();
 1061        },
 1062
 1063        /**
 1064         * immediately stop gesture detection
 1065         * might be useful after a swipe was detected
 1066         * @return {*}
 1067         */
 1068        stopDetect: function() {
 1069          return ionic.Gestures.detection.stopDetect();
 1070        }
 1071      };
 1072    }
 1073  };
 1074
 1075  ionic.Gestures.PointerEvent = {
 1076    /**
 1077     * holds all pointers
 1078     * type {Object}
 1079     */
 1080    pointers: {},
 1081
 1082    /**
 1083     * get a list of pointers
 1084     * @returns {Array}     touchlist
 1085     */
 1086    getTouchList: function() {
 1087      var self = this;
 1088      var touchlist = [];
 1089
 1090      // we can use forEach since pointerEvents only is in IE10
 1091      Object.keys(self.pointers).sort().forEach(function(id) {
 1092        touchlist.push(self.pointers[id]);
 1093      });
 1094      return touchlist;
 1095    },
 1096
 1097    /**
 1098     * update the position of a pointer
 1099     * @param   {String}   type             ionic.Gestures.EVENT_END
 1100     * @param   {Object}   pointerEvent
 1101     */
 1102    updatePointer: function(type, pointerEvent) {
 1103      if(type == ionic.Gestures.EVENT_END) {
 1104        this.pointers = {};
 1105      }
 1106      else {
 1107        pointerEvent.identifier = pointerEvent.pointerId;
 1108        this.pointers[pointerEvent.pointerId] = pointerEvent;
 1109      }
 1110
 1111      return Object.keys(this.pointers).length;
 1112    },
 1113
 1114    /**
 1115     * check if ev matches pointertype
 1116     * @param   {String}        pointerType     ionic.Gestures.POINTER_MOUSE
 1117     * @param   {PointerEvent}  ev
 1118     */
 1119    matchType: function(pointerType, ev) {
 1120      if(!ev.pointerType) {
 1121        return false;
 1122      }
 1123
 1124      var types = {};
 1125      types[ionic.Gestures.POINTER_MOUSE] = (ev.pointerType == ev.MSPOINTER_TYPE_MOUSE || ev.pointerType == ionic.Gestures.POINTER_MOUSE);
 1126      types[ionic.Gestures.POINTER_TOUCH] = (ev.pointerType == ev.MSPOINTER_TYPE_TOUCH || ev.pointerType == ionic.Gestures.POINTER_TOUCH);
 1127      types[ionic.Gestures.POINTER_PEN] = (ev.pointerType == ev.MSPOINTER_TYPE_PEN || ev.pointerType == ionic.Gestures.POINTER_PEN);
 1128      return types[pointerType];
 1129    },
 1130
 1131
 1132    /**
 1133     * get events
 1134     */
 1135    getEvents: function() {
 1136      return [
 1137        'pointerdown MSPointerDown',
 1138      'pointermove MSPointerMove',
 1139      'pointerup pointercancel MSPointerUp MSPointerCancel'
 1140        ];
 1141    },
 1142
 1143    /**
 1144     * reset the list
 1145     */
 1146    reset: function() {
 1147      this.pointers = {};
 1148    }
 1149  };
 1150
 1151
 1152  ionic.Gestures.utils = {
 1153    /**
 1154     * extend method,
 1155     * also used for cloning when dest is an empty object
 1156     * @param   {Object}    dest
 1157     * @param   {Object}    src
 1158     * @param	{Boolean}	merge		do a merge
 1159     * @returns {Object}    dest
 1160     */
 1161    extend: function extend(dest, src, merge) {
 1162      for (var key in src) {
 1163        if(dest[key] !== undefined && merge) {
 1164          continue;
 1165        }
 1166        dest[key] = src[key];
 1167      }
 1168      return dest;
 1169    },
 1170
 1171
 1172    /**
 1173     * find if a node is in the given parent
 1174     * used for event delegation tricks
 1175     * @param   {HTMLElement}   node
 1176     * @param   {HTMLElement}   parent
 1177     * @returns {boolean}       has_parent
 1178     */
 1179    hasParent: function(node, parent) {
 1180      while(node){
 1181        if(node == parent) {
 1182          return true;
 1183        }
 1184        node = node.parentNode;
 1185      }
 1186      return false;
 1187    },
 1188
 1189
 1190    /**
 1191     * get the center of all the touches
 1192     * @param   {Array}     touches
 1193     * @returns {Object}    center
 1194     */
 1195    getCenter: function getCenter(touches) {
 1196      var valuesX = [], valuesY = [];
 1197
 1198      for(var t = 0, len = touches.length; t < len; t++) {
 1199        valuesX.push(touches[t].pageX);
 1200        valuesY.push(touches[t].pageY);
 1201      }
 1202
 1203      return {
 1204        pageX: ((Math.min.apply(Math, valuesX) + Math.max.apply(Math, valuesX)) / 2),
 1205          pageY: ((Math.min.apply(Math, valuesY) + Math.max.apply(Math, valuesY)) / 2)
 1206      };
 1207    },
 1208
 1209
 1210    /**
 1211     * calculate the velocity between two points
 1212     * @param   {Number}    delta_time
 1213     * @param   {Number}    delta_x
 1214     * @param   {Number}    delta_y
 1215     * @returns {Object}    velocity
 1216     */
 1217    getVelocity: function getVelocity(delta_time, delta_x, delta_y) {
 1218      return {
 1219        x: Math.abs(delta_x / delta_time) || 0,
 1220        y: Math.abs(delta_y / delta_time) || 0
 1221      };
 1222    },
 1223
 1224
 1225    /**
 1226     * calculate the angle between two coordinates
 1227     * @param   {Touch}     touch1
 1228     * @param   {Touch}     touch2
 1229     * @returns {Number}    angle
 1230     */
 1231    getAngle: function getAngle(touch1, touch2) {
 1232      var y = touch2.pageY - touch1.pageY,
 1233      x = touch2.pageX - touch1.pageX;
 1234      return Math.atan2(y, x) * 180 / Math.PI;
 1235    },
 1236
 1237
 1238    /**
 1239     * angle to direction define
 1240     * @param   {Touch}     touch1
 1241     * @param   {Touch}     touch2
 1242     * @returns {String}    direction constant, like ionic.Gestures.DIRECTION_LEFT
 1243     */
 1244    getDirection: function getDirection(touch1, touch2) {
 1245      var x = Math.abs(touch1.pageX - touch2.pageX),
 1246      y = Math.abs(touch1.pageY - touch2.pageY);
 1247
 1248      if(x >= y) {
 1249        return touch1.pageX - touch2.pageX > 0 ? ionic.Gestures.DIRECTION_LEFT : ionic.Gestures.DIRECTION_RIGHT;
 1250      }
 1251      else {
 1252        return touch1.pageY - touch2.pageY > 0 ? ionic.Gestures.DIRECTION_UP : ionic.Gestures.DIRECTION_DOWN;
 1253      }
 1254    },
 1255
 1256
 1257    /**
 1258     * calculate the distance between two touches
 1259     * @param   {Touch}     touch1
 1260     * @param   {Touch}     touch2
 1261     * @returns {Number}    distance
 1262     */
 1263    getDistance: function getDistance(touch1, touch2) {
 1264      var x = touch2.pageX - touch1.pageX,
 1265      y = touch2.pageY - touch1.pageY;
 1266      return Math.sqrt((x * x) + (y * y));
 1267    },
 1268
 1269
 1270    /**
 1271     * calculate the scale factor between two touchLists (fingers)
 1272     * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
 1273     * @param   {Array}     start
 1274     * @param   {Array}     end
 1275     * @returns {Number}    scale
 1276     */
 1277    getScale: function getScale(start, end) {
 1278      // need two fingers...
 1279      if(start.length >= 2 && end.length >= 2) {
 1280        return this.getDistance(end[0], end[1]) /
 1281          this.getDistance(start[0], start[1]);
 1282      }
 1283      return 1;
 1284    },
 1285
 1286
 1287    /**
 1288     * calculate the rotation degrees between two touchLists (fingers)
 1289     * @param   {Array}     start
 1290     * @param   {Array}     end
 1291     * @returns {Number}    rotation
 1292     */
 1293    getRotation: function getRotation(start, end) {
 1294      // need two fingers
 1295      if(start.length >= 2 && end.length >= 2) {
 1296        return this.getAngle(end[1], end[0]) -
 1297          this.getAngle(start[1], start[0]);
 1298      }
 1299      return 0;
 1300    },
 1301
 1302
 1303    /**
 1304     * boolean if the direction is vertical
 1305     * @param    {String}    direction
 1306     * @returns  {Boolean}   is_vertical
 1307     */
 1308    isVertical: function isVertical(direction) {
 1309      return (direction == ionic.Gestures.DIRECTION_UP || direction == ionic.Gestures.DIRECTION_DOWN);
 1310    },
 1311
 1312
 1313    /**
 1314     * stop browser default behavior with css class
 1315     * @param   {HtmlElement}   element
 1316     * @param   {Object}        css_class
 1317     */
 1318    stopDefaultBrowserBehavior: function stopDefaultBrowserBehavior(element, css_class) {
 1319      // changed from making many style changes to just adding a preset classname
 1320      // less DOM manipulations, less code, and easier to control in the CSS side of things
 1321      // hammer.js doesn't come with CSS, but ionic does, which is why we prefer this method
 1322      if(element && element.classList) {
 1323        element.classList.add(css_class);
 1324        element.onselectstart = function() {
 1325          return false;
 1326        };
 1327      }
 1328    }
 1329  };
 1330
 1331
 1332  ionic.Gestures.detection = {
 1333    // contains all registred ionic.Gestures.gestures in the correct order
 1334    gestures: [],
 1335
 1336    // data of the current ionic.Gestures.gesture detection session
 1337    current: null,
 1338
 1339    // the previous ionic.Gestures.gesture session data
 1340    // is a full clone of the previous gesture.current object
 1341    previous: null,
 1342
 1343    // when this becomes true, no gestures are fired
 1344    stopped: false,
 1345
 1346
 1347    /**
 1348     * start ionic.Gestures.gesture detection
 1349     * @param   {ionic.Gestures.Instance}   inst
 1350     * @param   {Object}            eventData
 1351     */
 1352    startDetect: function startDetect(inst, eventData) {
 1353      // already busy with a ionic.Gestures.gesture detection on an element
 1354      if(this.current) {
 1355        return;
 1356      }
 1357
 1358      this.stopped = false;
 1359
 1360      this.current = {
 1361        inst: inst, // reference to ionic.GesturesInstance we're working for
 1362        startEvent: ionic.Gestures.utils.extend({}, eventData), // start eventData for distances, timing etc
 1363        lastEvent: false, // last eventData
 1364        name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
 1365      };
 1366
 1367      this.detect(eventData);
 1368    },
 1369
 1370
 1371    /**
 1372     * ionic.Gestures.gesture detection
 1373     * @param   {Object}    eventData
 1374     */
 1375    detect: function detect(eventData) {
 1376      if(!this.current || this.stopped) {
 1377        return null;
 1378      }
 1379
 1380      // extend event data with calculations about scale, distance etc
 1381      eventData = this.extendEventData(eventData);
 1382
 1383      // instance options
 1384      var inst_options = this.current.inst.options;
 1385
 1386      // call ionic.Gestures.gesture handlers
 1387      for(var g = 0, len = this.gestures.length; g < len; g++) {
 1388        var gesture = this.gestures[g];
 1389
 1390        // only when the instance options have enabled this gesture
 1391        if(!this.stopped && inst_options[gesture.name] !== false) {
 1392          // if a handler returns false, we stop with the detection
 1393          if(gesture.handler.call(gesture, eventData, this.current.inst) === false) {
 1394            this.stopDetect();
 1395            break;
 1396          }
 1397        }
 1398      }
 1399
 1400      // store as previous event event
 1401      if(this.current) {
 1402        this.current.lastEvent = eventData;
 1403      }
 1404
 1405      // endevent, but not the last touch, so dont stop
 1406      if(eventData.eventType == ionic.Gestures.EVENT_END && !eventData.touches.length - 1) {
 1407        this.stopDetect();
 1408      }
 1409
 1410      return eventData;
 1411    },
 1412
 1413
 1414    /**
 1415     * clear the ionic.Gestures.gesture vars
 1416     * this is called on endDetect, but can also be used when a final ionic.Gestures.gesture has been detected
 1417     * to stop other ionic.Gestures.gestures from being fired
 1418     */
 1419    stopDetect: function stopDetect() {
 1420      // clone current data to the store as the previous gesture
 1421      // used for the double tap gesture, since this is an other gesture detect session
 1422      this.previous = ionic.Gestures.utils.extend({}, this.current);
 1423
 1424      // reset the current
 1425      this.current = null;
 1426
 1427      // stopped!
 1428      this.stopped = true;
 1429    },
 1430
 1431
 1432    /**
 1433     * extend eventData for ionic.Gestures.gestures
 1434     * @param   {Object}   ev
 1435     * @returns {Object}   ev
 1436     */
 1437    extendEventData: function extendEventData(ev) {
 1438      var startEv = this.current.startEvent;
 1439
 1440      // if the touches change, set the new touches over the startEvent touches
 1441      // this because touchevents don't have all the touches on touchstart, or the
 1442      // user must place his fingers at the EXACT same time on the screen, which is not realistic
 1443      // but, sometimes it happens that both fingers are touching at the EXACT same time
 1444      if(startEv && (ev.touches.length != startEv.touches.length || ev.touches === startEv.touches)) {
 1445        // extend 1 level deep to get the touchlist with the touch objects
 1446        startEv.touches = [];
 1447        for(var i = 0, len = ev.touches.length; i < len; i++) {
 1448          startEv.touches.push(ionic.Gestures.utils.extend({}, ev.touches[i]));
 1449        }
 1450      }
 1451
 1452      var delta_time = ev.timeStamp - startEv.timeStamp,
 1453          delta_x = ev.center.pageX - startEv.center.pageX,
 1454          delta_y = ev.center.pageY - startEv.center.pageY,
 1455          velocity = ionic.Gestures.utils.getVelocity(delta_time, delta_x, delta_y);
 1456
 1457      ionic.Gestures.utils.extend(ev, {
 1458        deltaTime: delta_time,
 1459        deltaX: delta_x,
 1460        deltaY: delta_y,
 1461
 1462        velocityX: velocity.x,
 1463        velocityY: velocity.y,
 1464
 1465        distance: ionic.Gestures.utils.getDistance(startEv.center, ev.center),
 1466        angle: ionic.Gestures.utils.getAngle(startEv.center, ev.center),
 1467        direction: ionic.Gestures.utils.getDirection(startEv.center, ev.center),
 1468
 1469        scale: ionic.Gestures.utils.getScale(startEv.touches, ev.touches),
 1470        rotation: ionic.Gestures.utils.getRotation(startEv.touches, ev.touches),
 1471
 1472        startEvent: startEv
 1473      });
 1474
 1475      return ev;
 1476    },
 1477
 1478
 1479    /**
 1480     * register new gesture
 1481     * @param   {Object}    gesture object, see gestures.js for documentation
 1482     * @returns {Array}     gestures
 1483     */
 1484    register: function register(gesture) {
 1485      // add an enable gesture options if there is no given
 1486      var options = gesture.defaults || {};
 1487      if(options[gesture.name] === undefined) {
 1488        options[gesture.name] = true;
 1489      }
 1490
 1491      // extend ionic.Gestures default options with the ionic.Gestures.gesture options
 1492      ionic.Gestures.utils.extend(ionic.Gestures.defaults, options, true);
 1493
 1494      // set its index
 1495      gesture.index = gesture.index || 1000;
 1496
 1497      // add ionic.Gestures.gesture to the list
 1498      this.gestures.push(gesture);
 1499
 1500      // sort the list by index
 1501      this.gestures.sort(function(a, b) {
 1502        if (a.index < b.index) {
 1503          return -1;
 1504        }
 1505        if (a.index > b.index) {
 1506          return 1;
 1507        }
 1508        return 0;
 1509      });
 1510
 1511      return this.gestures;
 1512    }
 1513  };
 1514
 1515
 1516  ionic.Gestures.gestures = ionic.Gestures.gestures || {};
 1517
 1518  /**
 1519   * Custom gestures
 1520   * ==============================
 1521   *
 1522   * Gesture object
 1523   * --------------------
 1524   * The object structure of a gesture:
 1525   *
 1526   * { name: 'mygesture',
 1527   *   index: 1337,
 1528   *   defaults: {
 1529   *     mygesture_option: true
 1530   *   }
 1531   *   handler: function(type, ev, inst) {
 1532   *     // trigger gesture event
 1533   *     inst.trigger(this.name, ev);
 1534   *   }
 1535   * }
 1536
 1537   * @param   {String}    name
 1538   * this should be the name of the gesture, lowercase
 1539   * it is also being used to disable/enable the gesture per instance config.
 1540   *
 1541   * @param   {Number}    [index=1000]
 1542   * the index of the gesture, where it is going to be in the stack of gestures detection
 1543   * like when you build an gesture that depends on the drag gesture, it is a good
 1544   * idea to place it after the index of the drag gesture.
 1545   *
 1546   * @param   {Object}    [defaults={}]
 1547   * the default settings of the gesture. these are added to the instance settings,
 1548   * and can be overruled per instance. you can also add the name of the gesture,
 1549   * but this is also added by default (and set to true).
 1550   *
 1551   * @param   {Function}  handler
 1552   * this handles the gesture detection of your custom gesture and receives the
 1553   * following arguments:
 1554   *
 1555   *      @param  {Object}    eventData
 1556   *      event data containing the following properties:
 1557   *          timeStamp   {Number}        time the event occurred
 1558   *          target      {HTMLElement}   target element
 1559   *          touches     {Array}         touches (fingers, pointers, mouse) on the screen
 1560   *          pointerType {String}        kind of pointer that was used. matches ionic.Gestures.POINTER_MOUSE|TOUCH
 1561   *          center      {Object}        center position of the touches. contains pageX and pageY
 1562   *          deltaTime   {Number}        the total time of the touches in the screen
 1563   *          deltaX      {Number}        the delta on x axis we haved moved
 1564   *          deltaY      {Number}        the delta on y axis we haved moved
 1565   *          velocityX   {Number}        the velocity on the x
 1566   *          velocityY   {Number}        the velocity on y
 1567   *          angle       {Number}        the angle we are moving
 1568   *          direction   {String}        the direction we are moving. matches ionic.Gestures.DIRECTION_UP|DOWN|LEFT|RIGHT
 1569   *          distance    {Number}        the distance we haved moved
 1570   *          scale       {Number}        scaling of the touches, needs 2 touches
 1571   *          rotation    {Number}        rotation of the touches, needs 2 touches *
 1572   *          eventType   {String}        matches ionic.Gestures.EVENT_START|MOVE|END
 1573   *          srcEvent    {Object}        the source event, like TouchStart or MouseDown *
 1574   *          startEvent  {Object}        contains the same properties as above,
 1575   *                                      but from the first touch. this is used to calculate
 1576   *                                      distances, deltaTime, scaling etc
 1577   *
 1578   *      @param  {ionic.Gestures.Instance}    inst
 1579   *      the instance we are doing the detection for. you can get the options from
 1580   *      the inst.options object and trigger the gesture event by calling inst.trigger
 1581   *
 1582   *
 1583   * Handle gestures
 1584   * --------------------
 1585   * inside the handler you can get/set ionic.Gestures.detectionic.current. This is the current
 1586   * detection sessionic. It has the following properties
 1587   *      @param  {String}    name
 1588   *      contains the name of the gesture we have detected. it has not a real function,
 1589   *      only to check in other gestures if something is detected.
 1590   *      like in the drag gesture we set it to 'drag' and in the swipe gesture we can
 1591   *      check if the current gesture is 'drag' by accessing ionic.Gestures.detectionic.current.name
 1592   *
 1593   *      readonly
 1594   *      @param  {ionic.Gestures.Instance}    inst
 1595   *      the instance we do the detection for
 1596   *
 1597   *      readonly
 1598   *      @param  {Object}    startEvent
 1599   *      contains the properties of the first gesture detection in this sessionic.
 1600   *      Used for calculations about timing, distance, etc.
 1601   *
 1602   *      readonly
 1603   *      @param  {Object}    lastEvent
 1604   *      contains all the properties of the last gesture detect in this sessionic.
 1605   *
 1606   * after the gesture detection session has been completed (user has released the screen)
 1607   * the ionic.Gestures.detectionic.current object is copied into ionic.Gestures.detectionic.previous,
 1608   * this is usefull for gestures like doubletap, where you need to know if the
 1609   * previous gesture was a tap
 1610   *
 1611   * options that have been set by the instance can be received by calling inst.options
 1612   *
 1613   * You can trigger a gesture event by calling inst.trigger("mygesture", event).
 1614   * The first param is the name of your gesture, the second the event argument
 1615   *
 1616   *
 1617   * Register gestures
 1618   * --------------------
 1619   * When an gesture is added to the ionic.Gestures.gestures object, it is auto registered
 1620   * at the setup of the first ionic.Gestures instance. You can also call ionic.Gestures.detectionic.register
 1621   * manually and pass your gesture object as a param
 1622   *
 1623   */
 1624
 1625  /**
 1626   * Hold
 1627   * Touch stays at the same place for x time
 1628   * events  hold
 1629   */
 1630  ionic.Gestures.gestures.Hold = {
 1631    name: 'hold',
 1632    index: 10,
 1633    defaults: {
 1634      hold_timeout: 500,
 1635      hold_threshold: 9
 1636    },
 1637    timer: null,
 1638    handler: function holdGesture(ev, inst) {
 1639      switch(ev.eventType) {
 1640        case ionic.Gestures.EVENT_START:
 1641          // clear any running timers
 1642          clearTimeout(this.timer);
 1643
 1644          // set the gesture so we can check in the timeout if it still is
 1645          ionic.Gestures.detection.current.name = this.name;
 1646
 1647          // set timer and if after the timeout it still is hold,
 1648          // we trigger the hold event
 1649          this.timer = setTimeout(function() {
 1650            if(ionic.Gestures.detection.current.name == 'hold') {
 1651              ionic.tap.cancelClick();
 1652              inst.trigger('hold', ev);
 1653            }
 1654          }, inst.options.hold_timeout);
 1655          break;
 1656
 1657          // when you move or end we clear the timer
 1658        case ionic.Gestures.EVENT_MOVE:
 1659          if(ev.distance > inst.options.hold_threshold) {
 1660            clearTimeout(this.timer);
 1661          }
 1662          break;
 1663
 1664        case ionic.Gestures.EVENT_END:
 1665          clearTimeout(this.timer);
 1666          break;
 1667      }
 1668    }
 1669  };
 1670
 1671
 1672  /**
 1673   * Tap/DoubleTap
 1674   * Quick touch at a place or double at the same place
 1675   * events  tap, doubletap
 1676   */
 1677  ionic.Gestures.gestures.Tap = {
 1678    name: 'tap',
 1679    index: 100,
 1680    defaults: {
 1681      tap_max_touchtime: 250,
 1682      tap_max_distance: 10,
 1683      tap_always: true,
 1684      doubletap_distance: 20,
 1685      doubletap_interval: 300
 1686    },
 1687    handler: function tapGesture(ev, inst) {
 1688      if(ev.eventType == ionic.Gestures.EVENT_END && ev.srcEvent.type != 'touchcancel') {
 1689        // previous gesture, for the double tap since these are two different gesture detections
 1690        var prev = ionic.Gestures.detection.previous,
 1691        did_doubletap = false;
 1692
 1693        // when the touchtime is higher then the max touch time
 1694        // or when the moving distance is too much
 1695        if(ev.deltaTime > inst.options.tap_max_touchtime ||
 1696            ev.distance > inst.options.tap_max_distance) {
 1697              return;
 1698            }
 1699
 1700        // check if double tap
 1701        if(prev && prev.name == 'tap' &&
 1702            (ev.timeStamp - prev.lastEvent.timeStamp) < inst.options.doubletap_interval &&
 1703            ev.distance < inst.options.doubletap_distance) {
 1704              inst.trigger('doubletap', ev);
 1705              did_doubletap = true;
 1706            }
 1707
 1708        // do a single tap
 1709        if(!did_doubletap || inst.options.tap_always) {
 1710          ionic.Gestures.detection.current.name = 'tap';
 1711          inst.trigger('tap', ev);
 1712        }
 1713      }
 1714    }
 1715  };
 1716
 1717
 1718  /**
 1719   * Swipe
 1720   * triggers swipe events when the end velocity is above the threshold
 1721   * events  swipe, swipeleft, swiperight, swipeup, swipedown
 1722   */
 1723  ionic.Gestures.gestures.Swipe = {
 1724    name: 'swipe',
 1725    index: 40,
 1726    defaults: {
 1727      // set 0 for unlimited, but this can conflict with transform
 1728      swipe_max_touches: 1,
 1729      swipe_velocity: 0.4
 1730    },
 1731    handler: function swipeGesture(ev, inst) {
 1732      if(ev.eventType == ionic.Gestures.EVENT_END) {
 1733        // max touches
 1734        if(inst.options.swipe_max_touches > 0 &&
 1735            ev.touches.length > inst.options.swipe_max_touches) {
 1736              return;
 1737            }
 1738
 1739        // when the distance we moved is too small we skip this gesture
 1740        // or we can be already in dragging
 1741        if(ev.velocityX > inst.options.swipe_velocity ||
 1742            ev.velocityY > inst.options.swipe_velocity) {
 1743              // trigger swipe events
 1744              inst.trigger(this.name, ev);
 1745              inst.trigger(this.name + ev.direction, ev);
 1746            }
 1747      }
 1748    }
 1749  };
 1750
 1751
 1752  /**
 1753   * Drag
 1754   * Move with x fingers (default 1) around on the page. Blocking the scrolling when
 1755   * moving left and right is a good practice. When all the drag events are blocking
 1756   * you disable scrolling on that area.
 1757   * events  drag, drapleft, dragright, dragup, dragdown
 1758   */
 1759  ionic.Gestures.gestures.Drag = {
 1760    name: 'drag',
 1761    index: 50,
 1762    defaults: {
 1763      drag_min_distance: 10,
 1764      // Set correct_for_drag_min_distance to true to make the starting point of the drag
 1765      // be calculated from where the drag was triggered, not from where the touch started.
 1766      // Useful to avoid a jerk-starting drag, which can make fine-adjustments
 1767      // through dragging difficult, and be visually unappealing.
 1768      correct_for_drag_min_distance: true,
 1769      // set 0 for unlimited, but this can conflict with transform
 1770      drag_max_touches: 1,
 1771      // prevent default browser behavior when dragging occurs
 1772      // be careful with it, it makes the element a blocking element
 1773      // when you are using the drag gesture, it is a good practice to set this true
 1774      drag_block_horizontal: true,
 1775      drag_block_vertical: true,
 1776      // drag_lock_to_axis keeps the drag gesture on the axis that it started on,
 1777      // It disallows vertical directions if the initial direction was horizontal, and vice versa.
 1778      drag_lock_to_axis: false,
 1779      // drag lock only kicks in when distance > drag_lock_min_distance
 1780      // This way, locking occurs only when the distance has become large enough to reliably determine the direction
 1781      drag_lock_min_distance: 25,
 1782      // prevent default if the gesture is going the given direction
 1783      prevent_default_directions: []
 1784    },
 1785    triggered: false,
 1786    handler: function dragGesture(ev, inst) {
 1787      if (ev.srcEvent.type == 'touchstart' || ev.srcEvent.type == 'touchend') {
 1788        this.preventedFirstMove = false;
 1789
 1790      } else if (!this.preventedFirstMove && ev.srcEvent.type == 'touchmove') {
 1791        // Prevent gestures that are not intended for this event handler from firing subsequent times
 1792        if (inst.options.prevent_default_directions.length > 0
 1793            && inst.options.prevent_default_directions.indexOf(ev.direction) != -1) {
 1794          ev.srcEvent.preventDefault();
 1795        }
 1796        this.preventedFirstMove = true;
 1797      }
 1798
 1799      // current gesture isnt drag, but dragged is true
 1800      // this means an other gesture is busy. now call dragend
 1801      if(ionic.Gestures.detection.current.name != this.name && this.triggered) {
 1802        inst.trigger(this.name + 'end', ev);
 1803        this.triggered = false;
 1804        return;
 1805      }
 1806
 1807      // max touches
 1808      if(inst.options.drag_max_touches > 0 &&
 1809          ev.touches.length > inst.options.drag_max_touches) {
 1810            return;
 1811          }
 1812
 1813      switch(ev.eventType) {
 1814        case ionic.Gestures.EVENT_START:
 1815          this.triggered = false;
 1816          break;
 1817
 1818        case ionic.Gestures.EVENT_MOVE:
 1819          // when the distance we moved is too small we skip this gesture
 1820          // or we can be already in dragging
 1821          if(ev.distance < inst.options.drag_min_distance &&
 1822              ionic.Gestures.detection.current.name != this.name) {
 1823                return;
 1824              }
 1825
 1826          // we are dragging!
 1827          if(ionic.Gestures.detection.current.name != this.name) {
 1828            ionic.Gestures.detection.current.name = this.name;
 1829            if (inst.options.correct_for_drag_min_distance) {
 1830              // When a drag is triggered, set the event center to drag_min_distance pixels from the original event center.
 1831              // Without this correction, the dragged distance would jumpstart at drag_min_distance pixels instead of at 0.
 1832              // It might be useful to save the original start point somewhere
 1833              var factor = Math.abs(inst.options.drag_min_distance / ev.distance);
 1834              ionic.Gestures.detection.current.startEvent.center.pageX += ev.deltaX * factor;
 1835              ionic.Gestures.detection.current.startEvent.center.pageY += ev.deltaY * factor;
 1836
 1837              // recalculate event data using new start point
 1838              ev = ionic.Gestures.detection.extendEventData(ev);
 1839            }
 1840          }
 1841
 1842          // lock drag to axis?
 1843          if(ionic.Gestures.detection.current.lastEvent.drag_locked_to_axis || (inst.options.drag_lock_to_axis && inst.options.drag_lock_min_distance <= ev.distance)) {
 1844            ev.drag_locked_to_axis = true;
 1845          }
 1846          var last_direction = ionic.Gestures.detection.current.lastEvent.direction;
 1847          if(ev.drag_locked_to_axis && last_direction !== ev.direction) {
 1848            // keep direction on the axis that the drag gesture started on
 1849            if(ionic.Gestures.utils.isVertical(last_direction)) {
 1850              ev.direction = (ev.deltaY < 0) ? ionic.Gestures.DIRECTION_UP : ionic.Gestures.DIRECTION_DOWN;
 1851            }
 1852            else {
 1853              ev.direction = (ev.deltaX < 0) ? ionic.Gestures.DIRECTION_LEFT : ionic.Gestures.DIRECTION_RIGHT;
 1854            }
 1855          }
 1856
 1857          // first time, trigger dragstart event
 1858          if(!this.triggered) {
 1859            inst.trigger(this.name + 'start', ev);
 1860            this.triggered = true;
 1861          }
 1862
 1863          // trigger normal event
 1864          inst.trigger(this.name, ev);
 1865
 1866          // direction event, like dragdown
 1867          inst.trigger(this.name + ev.direction, ev);
 1868
 1869          // block the browser events
 1870          if( (inst.options.drag_block_vertical && ionic.Gestures.utils.isVertical(ev.direction)) ||
 1871              (inst.options.drag_block_horizontal && !ionic.Gestures.utils.isVertical(ev.direction))) {
 1872                ev.preventDefault();
 1873              }
 1874          break;
 1875
 1876        case ionic.Gestures.EVENT_END:
 1877          // trigger dragend
 1878          if(this.triggered) {
 1879            inst.trigger(this.name + 'end', ev);
 1880          }
 1881
 1882          this.triggered = false;
 1883          break;
 1884      }
 1885    }
 1886  };
 1887
 1888
 1889  /**
 1890   * Transform
 1891   * User want to scale or rotate with 2 fingers
 1892   * events  transform, pinch, pinchin, pinchout, rotate
 1893   */
 1894  ionic.Gestures.gestures.Transform = {
 1895    name: 'transform',
 1896    index: 45,
 1897    defaults: {
 1898      // factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
 1899      transform_min_scale: 0.01,
 1900      // rotation in degrees
 1901      transform_min_rotation: 1,
 1902      // prevent default browser behavior when two touches are on the screen
 1903      // but it makes the element a blocking element
 1904      // when you are using the transform gesture, it is a good practice to set this true
 1905      transform_always_block: false
 1906    },
 1907    triggered: false,
 1908    handler: function transformGesture(ev, inst) {
 1909      // current gesture isnt drag, but dragged is true
 1910      // this means an other gesture is busy. now call dragend
 1911      if(ionic.Gestures.detection.current.name != this.name && this.triggered) {
 1912        inst.trigger(this.name + 'end', ev);
 1913        this.triggered = false;
 1914        return;
 1915      }
 1916
 1917      // atleast multitouch
 1918      if(ev.touches.length < 2) {
 1919        return;
 1920      }
 1921
 1922      // prevent default when two fingers are on the screen
 1923      if(inst.options.transform_always_block) {
 1924        ev.preventDefault();
 1925      }
 1926
 1927      switch(ev.eventType) {
 1928        case ionic.Gestures.EVENT_START:
 1929          this.triggered = false;
 1930          break;
 1931
 1932        case ionic.Gestures.EVENT_MOVE:
 1933          var scale_threshold = Math.abs(1 - ev.scale);
 1934          var rotation_threshold = Math.abs(ev.rotation);
 1935
 1936          // when the distance we moved is too small we skip this gesture
 1937          // or we can be already in dragging
 1938          if(scale_threshold < inst.options.transform_min_scale &&
 1939              rotation_threshold < inst.options.transform_min_rotation) {
 1940                return;
 1941              }
 1942
 1943          // we are transforming!
 1944          ionic.Gestures.detection.current.name = this.name;
 1945
 1946          // first time, trigger dragstart event
 1947          if(!this.triggered) {
 1948            inst.trigger(this.name + 'start', ev);
 1949            this.triggered = true;
 1950          }
 1951
 1952          inst.trigger(this.name, ev); // basic transform event
 1953
 1954          // trigger rotate event
 1955          if(rotation_threshold > inst.options.transform_min_rotation) {
 1956            inst.trigger('rotate', ev);
 1957          }
 1958
 1959          // trigger pinch event
 1960          if(scale_threshold > inst.options.transform_min_scale) {
 1961            inst.trigger('pinch', ev);
 1962            inst.trigger('pinch' + ((ev.scale < 1) ? 'in' : 'out'), ev);
 1963          }
 1964          break;
 1965
 1966        case ionic.Gestures.EVENT_END:
 1967          // trigger dragend
 1968          if(this.triggered) {
 1969            inst.trigger(this.name + 'end', ev);
 1970          }
 1971
 1972          this.triggered = false;
 1973          break;
 1974      }
 1975    }
 1976  };
 1977
 1978
 1979  /**
 1980   * Touch
 1981   * Called as first, tells the user has touched the screen
 1982   * events  touch
 1983   */
 1984  ionic.Gestures.gestures.Touch = {
 1985    name: 'touch',
 1986    index: -Infinity,
 1987    defaults: {
 1988      // call preventDefault at touchstart, and makes the element blocking by
 1989      // disabling the scrolling of the page, but it improves gestures like
 1990      // transforming and dragging.
 1991      // be careful with using this, it can be very annoying for users to be stuck
 1992      // on the page
 1993      prevent_default: false,
 1994
 1995      // disable mouse events, so only touch (or pen!) input triggers events
 1996      prevent_mouseevents: false
 1997    },
 1998    handler: function touchGesture(ev, inst) {
 1999      if(inst.options.prevent_mouseevents && ev.pointerType == ionic.Gestures.POINTER_MOUSE) {
 2000        ev.stopDetect();
 2001        return;
 2002      }
 2003
 2004      if(inst.options.prevent_default) {
 2005        ev.preventDefault();
 2006      }
 2007
 2008      if(ev.eventType == ionic.Gestures.EVENT_START) {
 2009        inst.trigger(this.name, ev);
 2010      }
 2011    }
 2012  };
 2013
 2014
 2015  /**
 2016   * Release
 2017   * Called as last, tells the user has released the screen
 2018   * events  release
 2019   */
 2020  ionic.Gestures.gestures.Release = {
 2021    name: 'release',
 2022    index: Infinity,
 2023    handler: function releaseGesture(ev, inst) {
 2024      if(ev.eventType == ionic.Gestures.EVENT_END) {
 2025        inst.trigger(this.name, ev);
 2026      }
 2027    }
 2028  };
 2029})(window.ionic);
 2030
 2031(function(window, document, ionic) {
 2032
 2033  function getParameterByName(name) {
 2034    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
 2035    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
 2036    results = regex.exec(location.search);
 2037    return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
 2038  }
 2039
 2040  var IOS = 'ios';
 2041  var ANDROID = 'android';
 2042  var WINDOWS_PHONE = 'windowsphone';
 2043  var EDGE = 'edge';
 2044  var CROSSWALK = 'crosswalk';
 2045  var requestAnimationFrame = ionic.requestAnimationFrame;
 2046
 2047  /**
 2048   * @ngdoc utility
 2049   * @name ionic.Platform
 2050   * @module ionic
 2051   * @description
 2052   * A set of utility methods that can be used to retrieve the device ready state and
 2053   * various other information such as what kind of platform the app is currently installed on.
 2054   *
 2055   * @usage
 2056   * ```js
 2057   * angular.module('PlatformApp', ['ionic'])
 2058   * .controller('PlatformCtrl', function($scope) {
 2059   *
 2060   *   ionic.Platform.ready(function(){
 2061   *     // will execute when device is ready, or immediately if the device is already ready.
 2062   *   });
 2063   *
 2064   *   var deviceInformation = ionic.Platform.device();
 2065   *
 2066   *   var isWebView = ionic.Platform.isWebView();
 2067   *   var isIPad = ionic.Platform.isIPad();
 2068   *   var isIOS = ionic.Platform.isIOS();
 2069   *   var isAndroid = ionic.Platform.isAndroid();
 2070   *   var isWindowsPhone = ionic.Platform.isWindowsPhone();
 2071   *
 2072   *   var currentPlatform = ionic.Platform.platform();
 2073   *   var currentPlatformVersion = ionic.Platform.version();
 2074   *
 2075   *   ionic.Platform.exitApp(); // stops the app
 2076   * });
 2077   * ```
 2078   */
 2079  var self = ionic.Platform = {
 2080
 2081    // Put navigator on platform so it can be mocked and set
 2082    // the browser does not allow window.navigator to be set
 2083    navigator: window.navigator,
 2084
 2085    /**
 2086     * @ngdoc property
 2087     * @name ionic.Platform#isReady
 2088     * @returns {boolean} Whether the device is ready.
 2089     */
 2090    isReady: false,
 2091    /**
 2092     * @ngdoc property
 2093     * @name ionic.Platform#isFullScreen
 2094     * @returns {boolean} Whether the device is fullscreen.
 2095     */
 2096    isFullScreen: false,
 2097    /**
 2098     * @ngdoc property
 2099     * @name ionic.Platform#platforms
 2100     * @returns {Array(string)} An array of all platforms found.
 2101     */
 2102    platforms: null,
 2103    /**
 2104     * @ngdoc property
 2105     * @name ionic.Platform#grade
 2106     * @returns {string} What grade the current platform is.
 2107     */
 2108    grade: null,
 2109    /**
 2110     * @ngdoc property
 2111     * @name ionic.Platform#ua
 2112     * @returns {string} What User Agent is.
 2113     */
 2114    ua: navigator.userAgent,
 2115
 2116    /**
 2117     * @ngdoc method
 2118     * @name ionic.Platform#ready
 2119     * @description
 2120     * Trigger a callback once the device is ready, or immediately
 2121     * if the device is already ready. This method can be run from
 2122     * anywhere and does not need to be wrapped by any additonal methods.
 2123     * When the app is within a WebView (Cordova), it'll fire
 2124     * the callback once the device is ready. If the app is within
 2125     * a web browser, it'll fire the callback after `window.load`.
 2126     * Please remember that Cordova features (Camera, FileSystem, etc) still
 2127     * will not work in a web browser.
 2128     * @param {function} callback The function to call.
 2129     */
 2130    ready: function(cb) {
 2131      // run through tasks to complete now that the device is ready
 2132      if (self.isReady) {
 2133        cb();
 2134      } else {
 2135        // the platform isn't ready yet, add it to this array
 2136        // which will be called once the platform is ready
 2137        readyCallbacks.push(cb);
 2138      }
 2139    },
 2140
 2141    /**
 2142     * @private
 2143     */
 2144    detect: function() {
 2145      self._checkPlatforms();
 2146
 2147      requestAnimationFrame(function() {
 2148        // only add to the body class if we got platform info
 2149        for (var i = 0; i < self.platforms.length; i++) {
 2150          document.body.classList.add('platform-' + self.platforms[i]);
 2151        }
 2152      });
 2153    },
 2154
 2155    /**
 2156     * @ngdoc method
 2157     * @name ionic.Platform#setGrade
 2158     * @description Set the grade of the device: 'a', 'b', or 'c'. 'a' is the best
 2159     * (most css features enabled), 'c' is the worst.  By default, sets the grade
 2160     * depending on the current device.
 2161     * @param {string} grade The new grade to set.
 2162     */
 2163    setGrade: function(grade) {
 2164      var oldGrade = self.grade;
 2165      self.grade = grade;
 2166      requestAnimationFrame(function() {
 2167        if (oldGrade) {
 2168          document.body.classList.remove('grade-' + oldGrade);
 2169        }
 2170        document.body.classList.add('grade-' + grade);
 2171      });
 2172    },
 2173
 2174    /**
 2175     * @ngdoc method
 2176     * @name ionic.Platform#device
 2177     * @description Return the current device (given by cordova).
 2178     * @returns {object} The device object.
 2179     */
 2180    device: function() {
 2181      return window.device || {};
 2182    },
 2183
 2184    _checkPlatforms: function() {
 2185      self.platforms = [];
 2186      var grade = 'a';
 2187
 2188      if (self.isWebView()) {
 2189        self.platforms.push('webview');
 2190        if (!(!window.cordova && !window.PhoneGap && !window.phonegap)) {
 2191          self.platforms.push('cordova');
 2192        } else if (typeof window.forge === 'object') {
 2193          self.platforms.push('trigger');
 2194        }
 2195      } else {
 2196        self.platforms.push('browser');
 2197      }
 2198      if (self.isIPad()) self.platforms.push('ipad');
 2199
 2200      var platform = self.platform();
 2201      if (platform) {
 2202        self.platforms.push(platform);
 2203
 2204        var version = self.version();
 2205        if (version) {
 2206          var v = version.toString();
 2207          if (v.indexOf('.') > 0) {
 2208            v = v.replace('.', '_');
 2209          } else {
 2210            v += '_0';
 2211          }
 2212          self.platforms.push(platform + v.split('_')[0]);
 2213          self.platforms.push(platform + v);
 2214
 2215          if (self.isAndroid() && version < 4.4) {
 2216            grade = (version < 4 ? 'c' : 'b');
 2217          } else if (self.isWindowsPhone()) {
 2218            grade = 'b';
 2219          }
 2220        }
 2221      }
 2222
 2223      self.setGrade(grade);
 2224    },
 2225
 2226    /**
 2227     * @ngdoc method
 2228     * @name ionic.Platform#isWebView
 2229     * @returns {boolean} Check if we are running within a WebView (such as Cordova).
 2230     */
 2231    isWebView: function() {
 2232      return !(!window.cordova && !window.PhoneGap && !window.phonegap && window.forge !== 'object');
 2233    },
 2234    /**
 2235     * @ngdoc method
 2236     * @name ionic.Platform#isIPad
 2237     * @returns {boolean} Whether we are running on iPad.
 2238     */
 2239    isIPad: function() {
 2240      if (/iPad/i.test(self.navigator.platform)) {
 2241        return true;
 2242      }
 2243      return /iPad/i.test(self.ua);
 2244    },
 2245    /**
 2246     * @ngdoc method
 2247     * @name ionic.Platform#isIOS
 2248     * @returns {boolean} Whether we are running on iOS.
 2249     */
 2250    isIOS: function() {
 2251      return self.is(IOS);
 2252    },
 2253    /**
 2254     * @ngdoc method
 2255     * @name ionic.Platform#isAndroid
 2256     * @returns {boolean} Whether we are running on Android.
 2257     */
 2258    isAndroid: function() {
 2259      return self.is(ANDROID);
 2260    },
 2261    /**
 2262     * @ngdoc method
 2263     * @name ionic.Platform#isWindowsPhone
 2264     * @returns {boolean} Whether we are running on Windows Phone.
 2265     */
 2266    isWindowsPhone: function() {
 2267      return self.is(WINDOWS_PHONE);
 2268    },
 2269    /**
 2270     * @ngdoc method
 2271     * @name ionic.Platform#isEdge
 2272     * @returns {boolean} Whether we are running on MS Edge/Windows 10 (inc. Phone)
 2273     */
 2274    isEdge: function() {
 2275      return self.is(EDGE);
 2276    },
 2277
 2278    isCrosswalk: function() {
 2279      return self.is(CROSSWALK);
 2280    },
 2281
 2282    /**
 2283     * @ngdoc method
 2284     * @name ionic.Platform#platform
 2285     * @returns {string} The name of the current platform.
 2286     */
 2287    platform: function() {
 2288      // singleton to get the platform name
 2289      if (platformName === null) self.setPlatform(self.device().platform);
 2290      return platformName;
 2291    },
 2292
 2293    /**
 2294     * @private
 2295     */
 2296    setPlatform: function(n) {
 2297      if (typeof n != 'undefined' && n !== null && n.length) {
 2298        platformName = n.toLowerCase();
 2299      } else if (getParameterByName('ionicplatform')) {
 2300        platformName = getParameterByName('ionicplatform');
 2301      } else if (self.ua.indexOf('Edge') > -1) {
 2302        platformName = EDGE;
 2303      } else if (self.ua.indexOf('Windows Phone') > -1) {
 2304        platformName = WINDOWS_PHONE;
 2305      } else if (self.ua.indexOf('Android') > 0) {
 2306        platformName = ANDROID;
 2307      } else if (/iPhone|iPad|iPod/.test(self.ua)) {
 2308        platformName = IOS;
 2309      } else {
 2310        platformName = self.navigator.platform && navigator.platform.toLowerCase().split(' ')[0] || '';
 2311      }
 2312    },
 2313
 2314    /**
 2315     * @ngdoc method
 2316     * @name ionic.Platform#version
 2317     * @returns {number} The version of the current device platform.
 2318     */
 2319    version: function() {
 2320      // singleton to get the platform version
 2321      if (platformVersion === null) self.setVersion(self.device().version);
 2322      return platformVersion;
 2323    },
 2324
 2325    /**
 2326     * @private
 2327     */
 2328    setVersion: function(v) {
 2329      if (typeof v != 'undefined' && v !== null) {
 2330        v = v.split('.');
 2331        v = parseFloat(v[0] + '.' + (v.length > 1 ? v[1] : 0));
 2332        if (!isNaN(v)) {
 2333          platformVersion = v;
 2334          return;
 2335        }
 2336      }
 2337
 2338      platformVersion = 0;
 2339
 2340      // fallback to user-agent checking
 2341      var pName = self.platform();
 2342      var versionMatch = {
 2343        'android': /Android (\d+).(\d+)?/,
 2344        'ios': /OS (\d+)_(\d+)?/,
 2345        'windowsphone': /Windows Phone (\d+).(\d+)?/
 2346      };
 2347      if (versionMatch[pName]) {
 2348        v = self.ua.match(versionMatch[pName]);
 2349        if (v && v.length > 2) {
 2350          platformVersion = parseFloat(v[1] + '.' + v[2]);
 2351        }
 2352      }
 2353    },
 2354
 2355    /**
 2356     * @ngdoc method
 2357     * @name ionic.Platform#is
 2358     * @param {string} Platform name.
 2359     * @returns {boolean} Whether the platform name provided is detected.
 2360     */
 2361    is: function(type) {
 2362      type = type.toLowerCase();
 2363      // check if it has an array of platforms
 2364      if (self.platforms) {
 2365        for (var x = 0; x < self.platforms.length; x++) {
 2366          if (self.platforms[x] === type) return true;
 2367        }
 2368      }
 2369      // exact match
 2370      var pName = self.platform();
 2371      if (pName) {
 2372        return pName === type.toLowerCase();
 2373      }
 2374
 2375      // A quick hack for to check userAgent
 2376      return self.ua.toLowerCase().indexOf(type) >= 0;
 2377    },
 2378
 2379    /**
 2380     * @ngdoc method
 2381     * @name ionic.Platform#exitApp
 2382     * @description Exit the app.
 2383     */
 2384    exitApp: function() {
 2385      self.ready(function() {
 2386        navigator.app && navigator.app.exitApp && navigator.app.exitApp();
 2387      });
 2388    },
 2389
 2390    /**
 2391     * @ngdoc method
 2392     * @name ionic.Platform#showStatusBar
 2393     * @description Shows or hides the device status bar (in Cordova). Requires `cordova plugin add org.apache.cordova.statusbar`
 2394     * @param {boolean} shouldShow Whether or not to show the status bar.
 2395     */
 2396    showStatusBar: function(val) {
 2397      // Only useful when run within cordova
 2398      self._showStatusBar = val;
 2399      self.ready(function() {
 2400        // run this only when or if the platform (cordova) is ready
 2401        requestAnimationFrame(function() {
 2402          if (self._showStatusBar) {
 2403            // they do not want it to be full screen
 2404            window.StatusBar && window.StatusBar.show();
 2405            document.body.classList.remove('status-bar-hide');
 2406          } else {
 2407            // it should be full screen
 2408            window.StatusBar && window.StatusBar.hide();
 2409            document.body.classList.add('status-bar-hide');
 2410          }
 2411        });
 2412      });
 2413    },
 2414
 2415    /**
 2416     * @ngdoc method
 2417     * @name ionic.Platform#fullScreen
 2418     * @description
 2419     * Sets whether the app is fullscreen or not (in Cordova).
 2420     * @param {boolean=} showFullScreen Whether or not to set the app to fullscreen. Defaults to true. Requires `cordova plugin add org.apache.cordova.statusbar`
 2421     * @param {boolean=} showStatusBar Whether or not to show the device's status bar. Defaults to false.
 2422     */
 2423    fullScreen: function(showFullScreen, showStatusBar) {
 2424      // showFullScreen: default is true if no param provided
 2425      self.isFullScreen = (showFullScreen !== false);
 2426
 2427      // add/remove the fullscreen classname to the body
 2428      ionic.DomUtil.ready(function() {
 2429        // run this only when or if the DOM is ready
 2430        requestAnimationFrame(function() {
 2431          if (self.isFullScreen) {
 2432            document.body.classList.add('fullscreen');
 2433          } else {
 2434            document.body.classList.remove('fullscreen');
 2435          }
 2436        });
 2437        // showStatusBar: default is false if no param provided
 2438        self.showStatusBar((showStatusBar === true));
 2439      });
 2440    }
 2441
 2442  };
 2443
 2444  var platformName = null, // just the name, like iOS or Android
 2445  platformVersion = null, // a float of the major and minor, like 7.1
 2446  readyCallbacks = [],
 2447  windowLoadListenderAttached,
 2448  platformReadyTimer = 2000; // How long to wait for platform ready before emitting a warning
 2449
 2450  verifyPlatformReady();
 2451
 2452  // Warn the user if deviceready did not fire in a reasonable amount of time, and how to fix it.
 2453  function verifyPlatformReady() {
 2454    setTimeout(function() {
 2455      if(!self.isReady && self.isWebView()) {
 2456        void 0;
 2457      }
 2458    }, platformReadyTimer);
 2459  }
 2460
 2461  // setup listeners to know when the device is ready to go
 2462  function onWindowLoad() {
 2463    if (self.isWebView()) {
 2464      // the window and scripts are fully loaded, and a cordova/phonegap
 2465      // object exists then let's listen for the deviceready
 2466      document.addEventListener("deviceready", onPlatformReady, false);
 2467    } else {
 2468      // the window and scripts are fully loaded, but the window object doesn't have the
 2469      // cordova/phonegap object, so its just a browser, not a webview wrapped w/ cordova
 2470      onPlatformReady();
 2471    }
 2472    if (windowLoadListenderAttached) {
 2473      window.removeEventListener("load", onWindowLoad, false);
 2474    }
 2475  }
 2476  if (document.readyState === 'complete') {
 2477    onWindowLoad();
 2478  } else {
 2479    windowLoadListenderAttached = true;
 2480    window.addEventListener("load", onWindowLoad, false);
 2481  }
 2482
 2483  function onPlatformReady() {
 2484    // the device is all set to go, init our own stuff then fire off our event
 2485    self.isReady = true;
 2486    self.detect();
 2487    for (var x = 0; x < readyCallbacks.length; x++) {
 2488      // fire off all the callbacks that were added before the platform was ready
 2489      readyCallbacks[x]();
 2490    }
 2491    readyCallbacks = [];
 2492    ionic.trigger('platformready', { target: document });
 2493
 2494    requestAnimationFrame(function() {
 2495      document.body.classList.add('platform-ready');
 2496    });
 2497  }
 2498
 2499})(window, document, ionic);
 2500
 2501(function(document, ionic) {
 2502  'use strict';
 2503
 2504  // Ionic CSS polyfills
 2505  ionic.CSS = {};
 2506  ionic.CSS.TRANSITION = [];
 2507  ionic.CSS.TRANSFORM = [];
 2508
 2509  ionic.EVENTS = {};
 2510
 2511  (function() {
 2512
 2513    // transform
 2514    var i, keys = ['webkitTransform', 'transform', '-webkit-transform', 'webkit-transform',
 2515                   '-moz-transform', 'moz-transform', 'MozTransform', 'mozTransform', 'msTransform'];
 2516
 2517    for (i = 0; i < keys.length; i++) {
 2518      if (document.documentElement.style[keys[i]] !== undefined) {
 2519        ionic.CSS.TRANSFORM = keys[i];
 2520        break;
 2521      }
 2522    }
 2523
 2524    // transition
 2525    keys = ['webkitTransition', 'mozTransition', 'msTransition', 'transition'];
 2526    for (i = 0; i < keys.length; i++) {
 2527      if (document.documentElement.style[keys[i]] !== undefined) {
 2528        ionic.CSS.TRANSITION = keys[i];
 2529        break;
 2530      }
 2531    }
 2532
 2533    // Fallback in case the keys don't exist at all
 2534    ionic.CSS.TRANSITION = ionic.CSS.TRANSITION || 'transition';
 2535
 2536    // The only prefix we care about is webkit for transitions.
 2537    var isWebkit = ionic.CSS.TRANSITION.indexOf('webkit') > -1;
 2538
 2539    // transition duration
 2540    ionic.CSS.TRANSITION_DURATION = (isWebkit ? '-webkit-' : '') + 'transition-duration';
 2541
 2542    // To be sure transitionend works everywhere, include *both* the webkit and non-webkit events
 2543    ionic.CSS.TRANSITIONEND = (isWebkit ? 'webkitTransitionEnd ' : '') + 'transitionend';
 2544  })();
 2545
 2546  (function() {
 2547      var touchStartEvent = 'touchstart';
 2548      var touchMoveEvent = 'touchmove';
 2549      var touchEndEvent = 'touchend';
 2550      var touchCancelEvent = 'touchcancel';
 2551
 2552      if (window.navigator.pointerEnabled) {
 2553        touchStartEvent = 'pointerdown';
 2554        touchMoveEvent = 'pointermove';
 2555        touchEndEvent = 'pointerup';
 2556        touchCancelEvent = 'pointercancel';
 2557      } else if (window.navigator.msPointerEnabled) {
 2558        touchStartEvent = 'MSPointerDown';
 2559        touchMoveEvent = 'MSPointerMove';
 2560        touchEndEvent = 'MSPointerUp';
 2561        touchCancelEvent = 'MSPointerCancel';
 2562      }
 2563
 2564      ionic.EVENTS.touchstart = touchStartEvent;
 2565      ionic.EVENTS.touchmove = touchMoveEvent;
 2566      ionic.EVENTS.touchend = touchEndEvent;
 2567      ionic.EVENTS.touchcancel = touchCancelEvent;
 2568  })();
 2569
 2570  // classList polyfill for them older Androids
 2571  // https://gist.github.com/devongovett/1381839
 2572  if (!("classList" in document.documentElement) && Object.defineProperty && typeof HTMLElement !== 'undefined') {
 2573    Object.defineProperty(HTMLElement.prototype, 'classList', {
 2574      get: function() {
 2575        var self = this;
 2576        function update(fn) {
 2577          return function() {
 2578            var x, classes = self.className.split(/\s+/);
 2579
 2580            for (x = 0; x < arguments.length; x++) {
 2581              fn(classes, classes.indexOf(arguments[x]), arguments[x]);
 2582            }
 2583
 2584            self.className = classes.join(" ");
 2585          };
 2586        }
 2587
 2588        return {
 2589          add: update(function(classes, index, value) {
 2590            ~index || classes.push(value);
 2591          }),
 2592
 2593          remove: update(function(classes, index) {
 2594            ~index && classes.splice(index, 1);
 2595          }),
 2596
 2597          toggle: update(function(classes, index, value) {
 2598            ~index ? classes.splice(index, 1) : classes.push(value);
 2599          }),
 2600
 2601          contains: function(value) {
 2602            return !!~self.className.split(/\s+/).indexOf(value);
 2603          },
 2604
 2605          item: function(i) {
 2606            return self.className.split(/\s+/)[i] || null;
 2607          }
 2608        };
 2609
 2610      }
 2611    });
 2612  }
 2613
 2614})(document, ionic);
 2615
 2616
 2617/**
 2618 * @ngdoc page
 2619 * @name tap
 2620 * @module ionic
 2621 * @description
 2622 * On touch devices such as a phone or tablet, some browsers implement a 300ms delay between
 2623 * the time the user stops touching the display and the moment the browser executes the
 2624 * click. This delay was initially introduced so the browser can know whether the user wants to
 2625 * double-tap to zoom in on the webpage.  Basically, the browser waits roughly 300ms to see if
 2626 * the user is double-tapping, or just tapping on the display once.
 2627 *
 2628 * Out of the box, Ionic automatically removes the 300ms delay in order to make Ionic apps
 2629 * feel more "native" like. Resultingly, other solutions such as
 2630 * [fastclick](https://github.com/ftlabs/fastclick) and Angular's
 2631 * [ngTouch](https://docs.angularjs.org/api/ngTouch) should not be included, to avoid conflicts.
 2632 *
 2633 * Some browsers already remove the delay with certain settings, such as the CSS property
 2634 * `touch-events: none` or with specific meta tag viewport values. However, each of these
 2635 * browsers still handle clicks differently, such as when to fire off or cancel the event
 2636 * (like scrolling when the target is a button, or holding a button down).
 2637 * For browsers that already remove the 300ms delay, consider Ionic's tap system as a way to
 2638 * normalize how clicks are handled across the various devices so there's an expected response
 2639 * no matter what the device, platform or version. Additionally, Ionic will prevent
 2640 * ghostclicks which even browsers that remove the delay still experience.
 2641 *
 2642 * In some cases, third-party libraries may also be working with touch events which can interfere
 2643 * with the tap system. For example, mapping libraries like Google or Leaflet Maps often implement
 2644 * a touch detection system which conflicts with Ionic's tap system.
 2645 *
 2646 * ### Disabling the tap system
 2647 *
 2648 * To disable the tap for an element and all of its children elements,
 2649 * add the attribute `data-tap-disabled="true"`.
 2650 *
 2651 * ```html
 2652 * <div data-tap-disabled="true">
 2653 *     <div id="google-map"></div>
 2654 * </div>
 2655 * ```
 2656 *
 2657 * ### Additional Notes:
 2658 *
 2659 * - Ionic tap  works with Ionic's JavaScript scrolling
 2660 * - Elements can come and go from the DOM and Ionic tap doesn't keep adding and removing
 2661 *   listeners
 2662 * - No "tap delay" after the first "tap" (you can tap as fast as you want, they all click)
 2663 * - Minimal events listeners, only being added to document
 2664 * - Correct focus in/out on each input type (select, textearea, range) on each platform/device
 2665 * - Shows and hides virtual keyboard correctly for each platform/device
 2666 * - Works with labels surrounding inputs
 2667 * - Does not fire off a click if the user moves the pointer too far
 2668 * - Adds and removes an 'activated' css class
 2669 * - Multiple [unit tests](https://github.com/driftyco/ionic/blob/master/test/unit/utils/tap.unit.js) for each scenario
 2670 *
 2671 */
 2672/*
 2673
 2674 IONIC TAP
 2675 ---------------
 2676 - Both touch and mouse events are added to the document.body on DOM ready
 2677 - If a touch event happens, it does not use mouse event listeners
 2678 - On touchend, if the distance between start and end was small, trigger a click
 2679 - In the triggered click event, add a 'isIonicTap' property
 2680 - The triggered click receives the same x,y coordinates as as the end event
 2681 - On document.body click listener (with useCapture=true), only allow clicks with 'isIonicTap'
 2682 - Triggering clicks with mouse events work the same as touch, except with mousedown/mouseup
 2683 - Tapping inputs is disabled during scrolling
 2684*/
 2685
 2686var tapDoc; // the element which the listeners are on (document.body)
 2687var tapActiveEle; // the element which is active (probably has focus)
 2688var tapEnabledTouchEvents;
 2689var tapMouseResetTimer;
 2690var tapPointerMoved;
 2691var tapPointerStart;
 2692var tapTouchFocusedInput;
 2693var tapLastTouchTarget;
 2694var tapTouchMoveListener = 'touchmove';
 2695
 2696// how much the coordinates can be off between start/end, but still a click
 2697var TAP_RELEASE_TOLERANCE = 12; // default tolerance
 2698var TAP_RELEASE_BUTTON_TOLERANCE = 50; // button elements should have a larger tolerance
 2699
 2700var tapEventListeners = {
 2701  'click': tapClickGateKeeper,
 2702
 2703  'mousedown': tapMouseDown,
 2704  'mouseup': tapMouseUp,
 2705  'mousemove': tapMouseMove,
 2706
 2707  'touchstart': tapTouchStart,
 2708  'touchend': tapTouchEnd,
 2709  'touchcancel': tapTouchCancel,
 2710  'touchmove': tapTouchMove,
 2711
 2712  'pointerdown': tapTouchStart,
 2713  'pointerup': tapTouchEnd,
 2714  'pointercancel': tapTouchCancel,
 2715  'pointermove': tapTouchMove,
 2716
 2717  'MSPointerDown': tapTouchStart,
 2718  'MSPointerUp': tapTouchEnd,
 2719  'MSPointerCancel': tapTouchCancel,
 2720  'MSPointerMove': tapTouchMove,
 2721
 2722  'focusin': tapFocusIn,
 2723  'focusout': tapFocusOut
 2724};
 2725
 2726ionic.tap = {
 2727
 2728  register: function(ele) {
 2729    tapDoc = ele;
 2730
 2731    tapEventListener('click', true, true);
 2732    tapEventListener('mouseup');
 2733    tapEventListener('mousedown');
 2734
 2735    if (window.navigator.pointerEnabled) {
 2736      tapEventListener('pointerdown');
 2737      tapEventListener('pointerup');
 2738      tapEventListener('pointercancel');
 2739      tapTouchMoveListener = 'pointermove';
 2740
 2741    } else if (window.navigator.msPointerEnabled) {
 2742      tapEventListener('MSPointerDown');
 2743      tapEventListener('MSPointerUp');
 2744      tapEventListener('MSPointerCancel');
 2745      tapTouchMoveListener = 'MSPointerMove';
 2746
 2747    } else {
 2748      tapEventListener('touchstart');
 2749      tapEventListener('touchend');
 2750      tapEventListener('touchcancel');
 2751    }
 2752
 2753    tapEventListener('focusin');
 2754    tapEventListener('focusout');
 2755
 2756    return function() {
 2757      for (var type in tapEventListeners) {
 2758        tapEventListener(type, false);
 2759      }
 2760      tapDoc = null;
 2761      tapActiveEle = null;
 2762      tapEnabledTouchEvents = false;
 2763      tapPointerMoved = false;
 2764      tapPointerStart = null;
 2765    };
 2766  },
 2767
 2768  ignoreScrollStart: function(e) {
 2769    return (e.defaultPrevented) ||  // defaultPrevented has been assigned by another component handling the event
 2770           (/^(file|range)$/i).test(e.target.type) ||
 2771           (e.target.dataset ? e.target.dataset.preventScroll : e.target.getAttribute('data-prevent-scroll')) == 'true' || // manually set within an elements attributes
 2772           (!!(/^(object|embed)$/i).test(e.target.tagName)) ||  // flash/movie/object touches should not try to scroll
 2773           ionic.tap.isElementTapDisabled(e.target); // check if this element, or an ancestor, has `data-tap-disabled` attribute
 2774  },
 2775
 2776  isTextInput: function(ele) {
 2777    return !!ele &&
 2778           (ele.tagName == 'TEXTAREA' ||
 2779            ele.contentEditable === 'true' ||
 2780            (ele.tagName == 'INPUT' && !(/^(radio|checkbox|range|file|submit|reset|color|image|button)$/i).test(ele.type)));
 2781  },
 2782
 2783  isDateInput: function(ele) {
 2784    return !!ele &&
 2785            (ele.tagName == 'INPUT' && (/^(date|time|datetime-local|month|week)$/i).test(ele.type));
 2786  },
 2787
 2788  isVideo: function(ele) {
 2789    return !!ele &&
 2790            (ele.tagName == 'VIDEO');
 2791  },
 2792
 2793  isKeyboardElement: function(ele) {
 2794    if ( !ionic.Platform.isIOS() || ionic.Platform.isIPad() ) {
 2795      return ionic.tap.isTextInput(ele) && !ionic.tap.isDateInput(ele);
 2796    } else {
 2797      return ionic.tap.isTextInput(ele) || ( !!ele && ele.tagName == "SELECT");
 2798    }
 2799  },
 2800
 2801  isLabelWithTextInput: function(ele) {
 2802    var container = tapContainingElement(ele, false);
 2803
 2804    return !!container &&
 2805           ionic.tap.isTextInput(tapTargetElement(container));
 2806  },
 2807
 2808  containsOrIsTextInput: function(ele) {
 2809    return ionic.tap.isTextInput(ele) || ionic.tap.isLabelWithTextInput(ele);
 2810  },
 2811
 2812  cloneFocusedInput: function(container) {
 2813    if (ionic.tap.hasCheckedClone) return;
 2814    ionic.tap.hasCheckedClone = true;
 2815
 2816    ionic.requestAnimationFrame(function() {
 2817      var focusInput = container.querySelector(':focus');
 2818      if (ionic.tap.isTextInput(focusInput) && !ionic.tap.isDateInput(focusInput)) {
 2819        var clonedInput = focusInput.cloneNode(true);
 2820
 2821        clonedInput.value = focusInput.value;
 2822        clonedInput.classList.add('cloned-text-input');
 2823        clonedInput.readOnly = true;
 2824        if (focusInput.isContentEditable) {
 2825          clonedInput.contentEditable = focusInput.contentEditable;
 2826          clonedInput.innerHTML = focusInput.innerHTML;
 2827        }
 2828        focusInput.parentElement.insertBefore(clonedInput, focusInput);
 2829        focusInput.classList.add('previous-input-focus');
 2830
 2831        clonedInput.scrollTop = focusInput.scrollTop;
 2832      }
 2833    });
 2834  },
 2835
 2836  hasCheckedClone: false,
 2837
 2838  removeClonedInputs: function(container) {
 2839    ionic.tap.hasCheckedClone = false;
 2840
 2841    ionic.requestAnimationFrame(function() {
 2842      var clonedInputs = container.querySelectorAll('.cloned-text-input');
 2843      var previousInputFocus = container.querySelectorAll('.previous-input-focus');
 2844      var x;
 2845
 2846      for (x = 0; x < clonedInputs.length; x++) {
 2847        clonedInputs[x].parentElement.removeChild(clonedInputs[x]);
 2848      }
 2849
 2850      for (x = 0; x < previousInputFocus.length; x++) {
 2851        previousInputFocus[x].classList.remove('previous-input-focus');
 2852        previousInputFocus[x].style.top = '';
 2853        if ( ionic.keyboard.isOpen && !ionic.keyboard.isClosing ) previousInputFocus[x].focus();
 2854      }
 2855    });
 2856  },
 2857
 2858  requiresNativeClick: function(ele) {
 2859    if (ionic.Platform.isWindowsPhone() && (ele.tagName == 'A' || ele.tagName == 'BUTTON' || ele.hasAttribute('ng-click') || (ele.tagName == 'INPUT' && (ele.type == 'button' || ele.type == 'submit')))) {
 2860      return true; //Windows Phone edge case, prevent ng-click (and similar) events from firing twice on this platform
 2861    }
 2862    if (!ele || ele.disabled || (/^(file|range)$/i).test(ele.type) || (/^(object|video)$/i).test(ele.tagName) || ionic.tap.isLabelContainingFileInput(ele)) {
 2863      return true;
 2864    }
 2865    return ionic.tap.isElementTapDisabled(ele);
 2866  },
 2867
 2868  isLabelContainingFileInput: function(ele) {
 2869    var lbl = tapContainingElement(ele);
 2870    if (lbl.tagName !== 'LABEL') return false;
 2871    var fileInput = lbl.querySelector('input[type=file]');
 2872    if (fileInput && fileInput.disabled === false) return true;
 2873    return false;
 2874  },
 2875
 2876  isElementTapDisabled: function(ele) {
 2877    if (ele && ele.nodeType === 1) {
 2878      var element = ele;
 2879      while (element) {
 2880        if (element.getAttribute && element.getAttribute('data-tap-disabled') == 'true') {
 2881          return true;
 2882        }
 2883        element = element.parentElement;
 2884      }
 2885    }
 2886    return false;
 2887  },
 2888
 2889  setTolerance: function(releaseTolerance, releaseButtonTolerance) {
 2890    TAP_RELEASE_TOLERANCE = releaseTolerance;
 2891    TAP_RELEASE_BUTTON_TOLERANCE = releaseButtonTolerance;
 2892  },
 2893
 2894  cancelClick: function() {
 2895    // used to cancel any simulated clicks which may happen on a touchend/mouseup
 2896    // gestures uses this method within its tap and hold events
 2897    tapPointerMoved = true;
 2898  },
 2899
 2900  pointerCoord: function(event) {
 2901    // This method can get coordinates for both a mouse click
 2902    // or a touch depending on the given event
 2903    var c = { x: 0, y: 0 };
 2904    if (event) {
 2905      var touches = event.touches && event.touches.length ? event.touches : [event];
 2906      var e = (event.changedTouches && event.changedTouches[0]) || touches[0];
 2907      if (e) {
 2908        c.x = e.clientX || e.pageX || 0;
 2909        c.y = e.clientY || e.pageY || 0;
 2910      }
 2911    }
 2912    return c;
 2913  }
 2914
 2915};
 2916
 2917function tapEventListener(type, enable, useCapture) {
 2918  if (enable !== false) {
 2919    tapDoc.addEventListener(type, tapEventListeners[type], useCapture);
 2920  } else {
 2921    tapDoc.removeEventListener(type, tapEventListeners[type]);
 2922  }
 2923}
 2924
 2925function tapClick(e) {
 2926  // simulate a normal click by running the element's click method then focus on it
 2927  var container = tapContainingElement(e.target);
 2928  var ele = tapTargetElement(container);
 2929
 2930  if (ionic.tap.requiresNativeClick(ele) || tapPointerMoved) return false;
 2931
 2932  var c = ionic.tap.pointerCoord(e);
 2933
 2934  //console.log('tapClick', e.type, ele.tagName, '('+c.x+','+c.y+')');
 2935  triggerMouseEvent('click', ele, c.x, c.y);
 2936
 2937  // if it's an input, focus in on the target, otherwise blur
 2938  tapHandleFocus(ele);
 2939}
 2940
 2941function triggerMouseEvent(type, ele, x, y) {
 2942  // using initMouseEvent instead of MouseEvent for our Android friends
 2943  var clickEvent = document.createEvent("MouseEvents");
 2944  clickEvent.initMouseEvent(type, true, true, window, 1, 0, 0, x, y, false, false, false, false, 0, null);
 2945  clickEvent.isIonicTap = true;
 2946  ele.dispatchEvent(clickEvent);
 2947}
 2948
 2949function tapClickGateKeeper(e) {
 2950  //console.log('click ' + Date.now() + ' isIonicTap: ' + (e.isIonicTap ? true : false));
 2951  if (e.target.type == 'submit' && e.detail === 0) {
 2952    // do not prevent click if it came from an "Enter" or "Go" keypress submit
 2953    return null;
 2954  }
 2955
 2956  // do not allow through any click events that were not created by ionic.tap
 2957  if ((ionic.scroll.isScrolling && ionic.tap.containsOrIsTextInput(e.target)) ||
 2958      (!e.isIonicTap && !ionic.tap.requiresNativeClick(e.target))) {
 2959    //console.log('clickPrevent', e.target.tagName);
 2960    e.stopPropagation();
 2961
 2962    if (!ionic.tap.isLabelWithTextInput(e.target)) {
 2963      // labels clicks from native should not preventDefault othersize keyboard will not show on input focus
 2964      e.preventDefault();
 2965    }
 2966    return false;
 2967  }
 2968}
 2969
 2970// MOUSE
 2971function tapMouseDown(e) {
 2972  //console.log('mousedown ' + Date.now());
 2973  if (e.isIonicTap || tapIgnoreEvent(e)) return null;
 2974
 2975  if (tapEnabledTouchEvents) {
 2976    //console.log('mousedown', 'stop event');
 2977    e.stopPropagation();
 2978
 2979    if (!ionic.Platform.isEdge() && (!ionic.tap.isTextInput(e.target) || tapLastTouchTarget !== e.target) &&
 2980      !isSelectOrOption(e.target.tagName) && !ionic.tap.isVideo(e.target)) {
 2981      // If you preventDefault on a text input then you cannot move its text caret/cursor.
 2982      // Allow through only the text input default. However, without preventDefault on an
 2983      // input the 300ms delay can change focus on inputs after the keyboard shows up.
 2984      // The focusin event handles the chance of focus changing after the keyboard shows.
 2985      // Windows Phone - if you preventDefault on a video element then you cannot operate
 2986      // its native controls.
 2987      e.preventDefault();
 2988    }
 2989
 2990    return false;
 2991  }
 2992
 2993  tapPointerMoved = false;
 2994  tapPointerStart = ionic.tap.pointerCoord(e);
 2995
 2996  tapEventListener('mousemove');
 2997  ionic.activator.start(e);
 2998}
 2999
 3000function tapMouseUp(e) {
 3001  //console.log("mouseup " + Date.now());
 3002  if (tapEnabledTouchEvents) {
 3003    e.stopPropagation();
 3004    e.preventDefault();
 3005    return false;
 3006  }
 3007
 3008  if (tapIgnoreEvent(e) || isSelectOrOption(e.target.tagName)) return false;
 3009
 3010  if (!tapHasPointerMoved(e)) {
 3011    tapClick(e);
 3012  }
 3013  tapEventListener('mousemove', false);
 3014  ionic.activator.end();
 3015  tapPointerMoved = false;
 3016}
 3017
 3018function tapMouseMove(e) {
 3019  if (tapHasPointerMoved(e)) {
 3020    tapEventListener('mousemove', false);
 3021    ionic.activator.end();
 3022    tapPointerMoved = true;
 3023    return false;
 3024  }
 3025}
 3026
 3027
 3028// TOUCH
 3029function tapTouchStart(e) {
 3030  //console.log("touchstart " + Date.now());
 3031  if (tapIgnoreEvent(e)) return;
 3032
 3033  tapPointerMoved = false;
 3034
 3035  tapEnableTouchEvents();
 3036  tapPointerStart = ionic.tap.pointerCoord(e);
 3037
 3038  tapEventListener(tapTouchMoveListener);
 3039  ionic.activator.start(e);
 3040
 3041  if (ionic.Platform.isIOS() && ionic.tap.isLabelWithTextInput(e.target)) {
 3042    // if the tapped element is a label, which has a child input
 3043    // then preventDefault so iOS doesn't ugly auto scroll to the input
 3044    // but do not prevent default on Android or else you cannot move the text caret
 3045    // and do not prevent default on Android or else no virtual keyboard shows up
 3046
 3047    var textInput = tapTargetElement(tapContainingElement(e.target));
 3048    if (textInput !== tapActiveEle) {
 3049      // don't preventDefault on an already focused input or else iOS's text caret isn't usable
 3050      //console.log('Would prevent default here');
 3051      e.preventDefault();
 3052    }
 3053  }
 3054}
 3055
 3056function tapTouchEnd(e) {
 3057  //console.log('touchend ' + Date.now());
 3058  if (tapIgnoreEvent(e)) return;
 3059
 3060  tapEnableTouchEvents();
 3061  if (!tapHasPointerMoved(e)) {
 3062    tapClick(e);
 3063
 3064    if (isSelectOrOption(e.target.tagName)) {
 3065      e.preventDefault();
 3066    }
 3067  }
 3068
 3069  tapLastTouchTarget = e.target;
 3070  tapTouchCancel();
 3071}
 3072
 3073function tapTouchMove(e) {
 3074  if (tapHasPointerMoved(e)) {
 3075    tapPointerMoved = true;
 3076    tapEventListener(tapTouchMoveListener, false);
 3077    ionic.activator.end();
 3078    return false;
 3079  }
 3080}
 3081
 3082function tapTouchCancel() {
 3083  tapEventListener(tapTouchMoveListener, false);
 3084  ionic.activator.end();
 3085  tapPointerMoved = false;
 3086}
 3087
 3088function tapEnableTouchEvents() {
 3089  tapEnabledTouchEvents = true;
 3090  clearTimeout(tapMouseResetTimer);
 3091  tapMouseResetTimer = setTimeout(function() {
 3092    tapEnabledTouchEvents = false;
 3093  }, 600);
 3094}
 3095
 3096function tapIgnoreEvent(e) {
 3097  if (e.isTapHandled) return true;
 3098  e.isTapHandled = true;
 3099
 3100  if(ionic.tap.isElementTapDisabled(e.target)) {
 3101    return true;
 3102  }
 3103
 3104  if (ionic.scroll.isScrolling && ionic.tap.containsOrIsTextInput(e.target)) {
 3105    e.preventDefault();
 3106    return true;
 3107  }
 3108}
 3109
 3110function tapHandleFocus(ele) {
 3111  tapTouchFocusedInput = null;
 3112
 3113  var triggerFocusIn = false;
 3114
 3115  if (ele.tagName == 'SELECT') {
 3116    // trick to force Android options to show up
 3117    triggerMouseEvent('mousedown', ele, 0, 0);
 3118    ele.focus && ele.focus();
 3119    triggerFocusIn = true;
 3120
 3121  } else if (tapActiveElement() === ele) {
 3122    // already is the active element and has focus
 3123    triggerFocusIn = true;
 3124
 3125  } else if ((/^(input|textarea|ion-label)$/i).test(ele.tagName) || ele.isContentEditable) {
 3126    triggerFocusIn = true;
 3127    ele.focus && ele.focus();
 3128    ele.value = ele.value;
 3129    if (tapEnabledTouchEvents) {
 3130      tapTouchFocusedInput = ele;
 3131    }
 3132
 3133  } else {
 3134    tapFocusOutActive();
 3135  }
 3136
 3137  if (triggerFocusIn) {
 3138    tapActiveElement(ele);
 3139    ionic.trigger('ionic.focusin', {
 3140      target: ele
 3141    }, true);
 3142  }
 3143}
 3144
 3145function tapFocusOutActive() {
 3146  var ele = tapActiveElement();
 3147  if (ele && ((/^(input|textarea|select)$/i).test(ele.tagName) || ele.isContentEditable)) {
 3148    //console.log('tapFocusOutActive', ele.tagName);
 3149    ele.blur();
 3150  }
 3151  tapActiveElement(null);
 3152}
 3153
 3154function tapFocusIn(e) {
 3155  //console.log('focusin ' + Date.now());
 3156  // Because a text input doesn't preventDefault (so the caret still works) there's a chance
 3157  // that its mousedown event 300ms later will change the focus to another element after
 3158  // the keyboard shows up.
 3159
 3160  if (tapEnabledTouchEvents &&
 3161      ionic.tap.isTextInput(tapActiveElement()) &&
 3162      ionic.tap.isTextInput(tapTouchFocusedInput) &&
 3163      tapTouchFocusedInput !== e.target) {
 3164
 3165    // 1) The pointer is from touch events
 3166    // 2) There is an active element which is a text input
 3167    // 3) A text input was just set to be focused on by a touch event
 3168    // 4) A new focus has been set, however the target isn't the one the touch event wanted
 3169    //console.log('focusin', 'tapTouchFocusedInput');
 3170    tapTouchFocusedInput.focus();
 3171    tapTouchFocusedInput = null;
 3172  }
 3173  ionic.scroll.isScrolling = false;
 3174}
 3175
 3176function tapFocusOut() {
 3177  //console.log("focusout");
 3178  tapActiveElement(null);
 3179}
 3180
 3181function tapActiveElement(ele) {
 3182  if (arguments.length) {
 3183    tapActiveEle = ele;
 3184  }
 3185  return tapActiveEle || document.activeElement;
 3186}
 3187
 3188function tapHasPointerMoved(endEvent) {
 3189  if (!endEvent || endEvent.target.nodeType !== 1 || !tapPointerStart || (tapPointerStart.x === 0 && tapPointerStart.y === 0)) {
 3190    return false;
 3191  }
 3192  var endCoordinates = ionic.tap.pointerCoord(endEvent);
 3193
 3194  var hasClassList = !!(endEvent.target.classList && endEvent.target.classList.contains &&
 3195    typeof endEvent.target.classList.contains === 'function');
 3196  var releaseTolerance = hasClassList && endEvent.target.classList.contains('button') ?
 3197    TAP_RELEASE_BUTTON_TOLERANCE :
 3198    TAP_RELEASE_TOLERANCE;
 3199
 3200  return Math.abs(tapPointerStart.x - endCoordinates.x) > releaseTolerance ||
 3201         Math.abs(tapPointerStart.y - endCoordinates.y) > releaseTolerance;
 3202}
 3203
 3204function tapContainingElement(ele, allowSelf) {
 3205  var climbEle = ele;
 3206  for (var x = 0; x < 6; x++) {
 3207    if (!climbEle) break;
 3208    if (climbEle.tagName === 'LABEL') return climbEle;
 3209    climbEle = climbEle.parentElement;
 3210  }
 3211  if (allowSelf !== false) return ele;
 3212}
 3213
 3214function tapTargetElement(ele) {
 3215  if (ele && ele.tagName === 'LABEL') {
 3216    if (ele.control) return ele.control;
 3217
 3218    // older devices do not support the "control" property
 3219    if (ele.querySelector) {
 3220      var control = ele.querySelector('input,textarea,select');
 3221      if (control) return control;
 3222    }
 3223  }
 3224  return ele;
 3225}
 3226
 3227function isSelectOrOption(tagName){
 3228  return (/^(select|option)$/i).test(tagName);
 3229}
 3230
 3231ionic.DomUtil.ready(function() {
 3232  var ng = typeof angular !== 'undefined' ? angular : null;
 3233  //do nothing for e2e tests
 3234  if (!ng || (ng && !ng.scenario)) {
 3235    ionic.tap.register(document);
 3236  }
 3237});
 3238
 3239(function(document, ionic) {
 3240  'use strict';
 3241
 3242  var queueElements = {};   // elements that should get an active state in XX milliseconds
 3243  var activeElements = {};  // elements that are currently active
 3244  var keyId = 0;            // a counter for unique keys for the above ojects
 3245  var ACTIVATED_CLASS = 'activated';
 3246
 3247  ionic.activator = {
 3248
 3249    start: function(e) {
 3250      var hitX = ionic.tap.pointerCoord(e).x;
 3251      if (hitX > 0 && hitX < 30) {
 3252        return;
 3253      }
 3254
 3255      // when an element is touched/clicked, it climbs up a few
 3256      // parents to see if it is an .item or .button element
 3257      ionic.requestAnimationFrame(function() {
 3258        if ((ionic.scroll && ionic.scroll.isScrolling) || ionic.tap.requiresNativeClick(e.target)) return;
 3259        var ele = e.target;
 3260        var eleToActivate;
 3261
 3262        for (var x = 0; x < 6; x++) {
 3263          if (!ele || ele.nodeType !== 1) break;
 3264          if (eleToActivate && ele.classList && ele.classList.contains('item')) {
 3265            eleToActivate = ele;
 3266            break;
 3267          }
 3268          if (ele.tagName == 'A' || ele.tagName == 'BUTTON' || ele.hasAttribute('ng-click')) {
 3269            eleToActivate = ele;
 3270            break;
 3271          }
 3272          if (ele.classList && ele.classList.contains('button')) {
 3273            eleToActivate = ele;
 3274            break;
 3275          }
 3276          // no sense climbing past these
 3277          if (ele.tagName == 'ION-CONTENT' || (ele.classList && ele.classList.contains('pane')) || ele.tagName == 'BODY') {
 3278            break;
 3279          }
 3280          ele = ele.parentElement;
 3281        }
 3282
 3283        if (eleToActivate) {
 3284          // queue that this element should be set to active
 3285          queueElements[keyId] = eleToActivate;
 3286
 3287          // on the next frame, set the queued elements to active
 3288          ionic.requestAnimationFrame(activateElements);
 3289
 3290          keyId = (keyId > 29 ? 0 : keyId + 1);
 3291        }
 3292
 3293      });
 3294    },
 3295
 3296    end: function() {
 3297      // clear out any active/queued elements after XX milliseconds
 3298      setTimeout(clear, 200);
 3299    }
 3300
 3301  };
 3302
 3303  function clear() {
 3304    // clear out any elements that are queued to be set to active
 3305    queueElements = {};
 3306
 3307    // in the next frame, remove the active class from all active elements
 3308    ionic.requestAnimationFrame(deactivateElements);
 3309  }
 3310
 3311  function activateElements() {
 3312    // activate all elements in the queue
 3313    for (var key in queueElements) {
 3314      if (queueElements[key]) {
 3315        queueElements[key].classList.add(ACTIVATED_CLASS);
 3316        activeElements[key] = queueElements[key];
 3317      }
 3318    }
 3319    queueElements = {};
 3320  }
 3321
 3322  function deactivateElements() {
 3323    if (ionic.transition && ionic.transition.isActive) {
 3324      setTimeout(deactivateElements, 400);
 3325      return;
 3326    }
 3327
 3328    for (var key in activeElements) {
 3329      if (activeElements[key]) {
 3330        activeElements[key].classList.remove(ACTIVATED_CLASS);
 3331        delete activeElements[key];
 3332      }
 3333    }
 3334  }
 3335
 3336})(document, ionic);
 3337
 3338(function(ionic) {
 3339  /* for nextUid function below */
 3340  var nextId = 0;
 3341
 3342  /**
 3343   * Various utilities used throughout Ionic
 3344   *
 3345   * Some of these are adopted from underscore.js and backbone.js, both also MIT licensed.
 3346   */
 3347  ionic.Utils = {
 3348
 3349    arrayMove: function(arr, oldIndex, newIndex) {
 3350      if (newIndex >= arr.length) {
 3351        var k = newIndex - arr.length;
 3352        while ((k--) + 1) {
 3353          arr.push(undefined);
 3354        }
 3355      }
 3356      arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0]);
 3357      return arr;
 3358    },
 3359
 3360    /**
 3361     * Return a function that will be called with the given context
 3362     */
 3363    proxy: function(func, context) {
 3364      var args = Array.prototype.slice.call(arguments, 2);
 3365      return function() {
 3366        return func.apply(context, args.concat(Array.prototype.slice.call(arguments)));
 3367      };
 3368    },
 3369
 3370    /**
 3371     * Only call a function once in the given interval.
 3372     *
 3373     * @param func {Function} the function to call
 3374     * @param wait {int} how long to wait before/after to allow function calls
 3375     * @param immediate {boolean} whether to call immediately or after the wait interval
 3376     */
 3377     debounce: function(func, wait, immediate) {
 3378      var timeout, args, context, timestamp, result;
 3379      return function() {
 3380        context = this;
 3381        args = arguments;
 3382        timestamp = new Date();
 3383        var later = function() {
 3384          var last = (new Date()) - timestamp;
 3385          if (last < wait) {
 3386            timeout = setTimeout(later, wait - last);
 3387          } else {
 3388            timeout = null;
 3389            if (!immediate) result = func.apply(context, args);
 3390          }
 3391        };
 3392        var callNow = immediate && !timeout;
 3393        if (!timeout) {
 3394          timeout = setTimeout(later, wait);
 3395        }
 3396        if (callNow) result = func.apply(context, args);
 3397        return result;
 3398      };
 3399    },
 3400
 3401    /**
 3402     * Throttle the given fun, only allowing it to be
 3403     * called at most every `wait` ms.
 3404     */
 3405    throttle: function(func, wait, options) {
 3406      var context, args, result;
 3407      var timeout = null;
 3408      var previous = 0;
 3409      options || (options = {});
 3410      var later = function() {
 3411        previous = options.leading === false ? 0 : Date.now();
 3412        timeout = null;
 3413        result = func.apply(context, args);
 3414      };
 3415      return function() {
 3416        var now = Date.now();
 3417        if (!previous && options.leading === false) previous = now;
 3418        var remaining = wait - (now - previous);
 3419        context = this;
 3420        args = arguments;
 3421        if (remaining <= 0) {
 3422          clearTimeout(timeout);
 3423          timeout = null;
 3424          previous = now;
 3425          result = func.apply(context, args);
 3426        } else if (!timeout && options.trailing !== false) {
 3427          timeout = setTimeout(later, remaining);
 3428        }
 3429        return result;
 3430      };
 3431    },
 3432     // Borrowed from Backbone.js's extend
 3433     // Helper function to correctly set up the prototype chain, for subclasses.
 3434     // Similar to `goog.inherits`, but uses a hash of prototype properties and
 3435     // class properties to be extended.
 3436    inherit: function(protoProps, staticProps) {
 3437      var parent = this;
 3438      var child;
 3439
 3440      // The constructor function for the new subclass is either defined by you
 3441      // (the "constructor" property in your `extend` definition), or defaulted
 3442      // by us to simply call the parent's constructor.
 3443      if (protoProps && protoProps.hasOwnProperty('constructor')) {
 3444        child = protoProps.constructor;
 3445      } else {
 3446        child = function() { return parent.apply(this, arguments); };
 3447      }
 3448
 3449      // Add static properties to the constructor function, if supplied.
 3450      ionic.extend(child, parent, staticProps);
 3451
 3452      // Set the prototype chain to inherit from `parent`, without calling
 3453      // `parent`'s constructor function.
 3454      var Surrogate = function() { this.constructor = child; };
 3455      Surrogate.prototype = parent.prototype;
 3456      child.prototype = new Surrogate();
 3457
 3458      // Add prototype properties (instance properties) to the subclass,
 3459      // if supplied.
 3460      if (protoProps) ionic.extend(child.prototype, protoProps);
 3461
 3462      // Set a convenience property in case the parent's prototype is needed
 3463      // later.
 3464      child.__super__ = parent.prototype;
 3465
 3466      return child;
 3467    },
 3468
 3469    // Extend adapted from Underscore.js
 3470    extend: function(obj) {
 3471       var args = Array.prototype.slice.call(arguments, 1);
 3472       for (var i = 0; i < args.length; i++) {
 3473         var source = args[i];
 3474         if (source) {
 3475           for (var prop in source) {
 3476             obj[prop] = source[prop];
 3477           }
 3478         }
 3479       }
 3480       return obj;
 3481    },
 3482
 3483    nextUid: function() {
 3484      return 'ion' + (nextId++);
 3485    },
 3486
 3487    disconnectScope: function disconnectScope(scope) {
 3488      if (!scope) return;
 3489
 3490      if (scope.$root === scope) {
 3491        return; // we can't disconnect the root node;
 3492      }
 3493      var parent = scope.$parent;
 3494      scope.$$disconnected = true;
 3495      scope.$broadcast('$ionic.disconnectScope', scope);
 3496
 3497      // See Scope.$destroy
 3498      if (parent.$$childHead === scope) {
 3499        parent.$$childHead = scope.$$nextSibling;
 3500      }
 3501      if (parent.$$childTail === scope) {
 3502        parent.$$childTail = scope.$$prevSibling;
 3503      }
 3504      if (scope.$$prevSibling) {
 3505        scope.$$prevSibling.$$nextSibling = scope.$$nextSibling;
 3506      }
 3507      if (scope.$$nextSibling) {
 3508        scope.$$nextSibling.$$prevSibling = scope.$$prevSibling;
 3509      }
 3510      scope.$$nextSibling = scope.$$prevSibling = null;
 3511    },
 3512
 3513    reconnectScope: function reconnectScope(scope) {
 3514      if (!scope) return;
 3515
 3516      if (scope.$root === scope) {
 3517        return; // we can't disconnect the root node;
 3518      }
 3519      if (!scope.$$disconnected) {
 3520        return;
 3521      }
 3522      var parent = scope.$parent;
 3523      scope.$$disconnected = false;
 3524      scope.$broadcast('$ionic.reconnectScope', scope);
 3525      // See Scope.$new for this logic...
 3526      scope.$$prevSibling = parent.$$childTail;
 3527      if (parent.$$childHead) {
 3528        parent.$$childTail.$$nextSibling = scope;
 3529        parent.$$childTail = scope;
 3530      } else {
 3531        parent.$$childHead = parent.$$childTail = scope;
 3532      }
 3533    },
 3534
 3535    isScopeDisconnected: function(scope) {
 3536      var climbScope = scope;
 3537      while (climbScope) {
 3538        if (climbScope.$$disconnected) return true;
 3539        climbScope = climbScope.$parent;
 3540      }
 3541      return false;
 3542    }
 3543  };
 3544
 3545  // Bind a few of the most useful functions to the ionic scope
 3546  ionic.inherit = ionic.Utils.inherit;
 3547  ionic.extend = ionic.Utils.extend;
 3548  ionic.throttle = ionic.Utils.throttle;
 3549  ionic.proxy = ionic.Utils.proxy;
 3550  ionic.debounce = ionic.Utils.debounce;
 3551
 3552})(window.ionic);
 3553
 3554/**
 3555 * @ngdoc page
 3556 * @name keyboard
 3557 * @module ionic
 3558 * @description
 3559 * On both Android and iOS, Ionic will attempt to prevent the keyboard from
 3560 * obscuring inputs and focusable elements when it appears by scrolling them
 3561 * into view.  In order for this to work, any focusable elements must be within
 3562 * a [Scroll View](http://ionicframework.com/docs/api/directive/ionScroll/)
 3563 * or a directive such as [Content](http://ionicframework.com/docs/api/directive/ionContent/)
 3564 * that has a Scroll View.
 3565 *
 3566 * It will also attempt to prevent the native overflow scrolling on focus,
 3567 * which can cause layout issues such as pushing headers up and out of view.
 3568 *
 3569 * The keyboard fixes work best in conjunction with the
 3570 * [Ionic Keyboard Plugin](https://github.com/driftyco/ionic-plugins-keyboard),
 3571 * although it will perform reasonably well without.  However, if you are using
 3572 * Cordova there is no reason not to use the plugin.
 3573 *
 3574 * ### Hide when keyboard shows
 3575 *
 3576 * To hide an element when the keyboard is open, add the class `hide-on-keyboard-open`.
 3577 *
 3578 * ```html
 3579 * <div class="hide-on-keyboard-open">
 3580 *   <div id="google-map"></div>
 3581 * </div>
 3582 * ```
 3583 *
 3584 * Note: For performance reasons, elements will not be hidden for 400ms after the start of the `native.keyboardshow` event
 3585 * from the Ionic Keyboard plugin. If you would like them to disappear immediately, you could do something
 3586 * like:
 3587 *
 3588 * ```js
 3589 *   window.addEventListener('native.keyboardshow', function(){
 3590 *     document.body.classList.add('keyboard-open');
 3591 *   });
 3592 * ```
 3593 * This adds the same `keyboard-open` class that is normally added by Ionic 400ms after the keyboard
 3594 * opens. However, bear in mind that adding this class to the body immediately may cause jank in any
 3595 * animations on Android that occur when the keyboard opens (for example, scrolling any obscured inputs into view).
 3596 *
 3597 * ----------
 3598 *
 3599 * ### Plugin Usage
 3600 * Information on using the plugin can be found at
 3601 * [https://github.com/driftyco/ionic-plugins-keyboard](https://github.com/driftyco/ionic-plugins-keyboard).
 3602 *
 3603 * ----------
 3604 *
 3605 * ### Android Notes
 3606 * - If your app is running in fullscreen, i.e. you have
 3607 *   `<preference name="Fullscreen" value="true" />` in your `config.xml` file
 3608 *   you will need to set `ionic.Platform.isFullScreen = true` manually.
 3609 *
 3610 * - You can configure the behavior of the web view when the keyboard shows by setting
 3611 *   [android:windowSoftInputMode](http://developer.android.com/reference/android/R.attr.html#windowSoftInputMode)
 3612 *   to either `adjustPan`, `adjustResize` or `adjustNothing` in your app's
 3613 *   activity in `AndroidManifest.xml`. `adjustResize` is the recommended setting
 3614 *   for Ionic, but if for some reason you do use `adjustPan` you will need to
 3615 *   set `ionic.Platform.isFullScreen = true`.
 3616 *
 3617 *   ```xml
 3618 *   <activity android:windowSoftInputMode="adjustResize">
 3619 *
 3620 *   ```
 3621 *
 3622 * ### iOS Notes
 3623 * - If the content of your app (including the header) is being pushed up and
 3624 *   out of view on input focus, try setting `cordova.plugins.Keyboard.disableScroll(true)`.
 3625 *   This does **not** disable scrolling in the Ionic scroll view, rather it
 3626 *   disables the native overflow scrolling that happens automatically as a
 3627 *   result of focusing on inputs below the keyboard.
 3628 *
 3629 */
 3630
 3631/**
 3632 * The current viewport height.
 3633 */
 3634var keyboardCurrentViewportHeight = 0;
 3635
 3636/**
 3637 * The viewport height when in portrait orientation.
 3638 */
 3639var keyboardPortraitViewportHeight = 0;
 3640
 3641/**
 3642 * The viewport height when in landscape orientation.
 3643 */
 3644var keyboardLandscapeViewportHeight = 0;
 3645
 3646/**
 3647 * The currently focused input.
 3648 */
 3649var keyboardActiveElement;
 3650
 3651/**
 3652 * The previously focused input used to reset keyboard after focusing on a
 3653 * new non-keyboard element
 3654 */
 3655var lastKeyboardActiveElement;
 3656
 3657/**
 3658 * The scroll view containing the currently focused input.
 3659 */
 3660var scrollView;
 3661
 3662/**
 3663 * Timer for the setInterval that polls window.innerHeight to determine whether
 3664 * the layout has updated for the keyboard showing/hiding.
 3665 */
 3666var waitForResizeTimer;
 3667
 3668/**
 3669 * Sometimes when switching inputs or orientations, focusout will fire before
 3670 * focusin, so this timer is for the small setTimeout to determine if we should
 3671 * really focusout/hide the keyboard.
 3672 */
 3673var keyboardFocusOutTimer;
 3674
 3675/**
 3676 * on Android, orientationchange will fire before the keyboard plugin notifies
 3677 * the browser that the keyboard will show/is showing, so this flag indicates
 3678 * to nativeShow that there was an orientationChange and we should update
 3679 * the viewport height with an accurate keyboard height value
 3680 */
 3681var wasOrientationChange = false;
 3682
 3683/**
 3684 * CSS class added to the body indicating the keyboard is open.
 3685 */
 3686var KEYBOARD_OPEN_CSS = 'keyboard-open';
 3687
 3688/**
 3689 * CSS class that indicates a scroll container.
 3690 */
 3691var SCROLL_CONTAINER_CSS = 'scroll-content';
 3692
 3693/**
 3694 * Debounced keyboardFocusIn function
 3695 */
 3696var debouncedKeyboardFocusIn = ionic.debounce(keyboardFocusIn, 200, true);
 3697
 3698/**
 3699 * Debounced keyboardNativeShow function
 3700 */
 3701var debouncedKeyboardNativeShow = ionic.debounce(keyboardNativeShow, 100, true);
 3702
 3703/**
 3704 * Ionic keyboard namespace.
 3705 * @namespace keyboard
 3706 */
 3707ionic.keyboard = {
 3708
 3709  /**
 3710   * Whether the keyboard is open or not.
 3711   */
 3712  isOpen: false,
 3713
 3714  /**
 3715   * Whether the keyboard is closing or not.
 3716   */
 3717  isClosing: false,
 3718
 3719  /**
 3720   * Whether the keyboard is opening or not.
 3721   */
 3722  isOpening: false,
 3723
 3724  /**
 3725   * The height of the keyboard in pixels, as reported by the keyboard plugin.
 3726   * If the plugin is not available, calculated as the difference in
 3727   * window.innerHeight after the keyboard has shown.
 3728   */
 3729  height: 0,
 3730
 3731  /**
 3732   * Whether the device is in landscape orientation or not.
 3733   */
 3734  isLandscape: false,
 3735
 3736  /**
 3737   * Whether the keyboard event listeners have been added or not
 3738   */
 3739  isInitialized: false,
 3740
 3741  /**
 3742   * Hide the keyboard, if it is open.
 3743   */
 3744  hide: function() {
 3745    if (keyboardHasPlugin()) {
 3746      cordova.plugins.Keyboard.close();
 3747    }
 3748    keyboardActiveElement && keyboardActiveElement.blur();
 3749  },
 3750
 3751  /**
 3752   * An alias for cordova.plugins.Keyboard.show(). If the keyboard plugin
 3753   * is installed, show the keyboard.
 3754   */
 3755  show: function() {
 3756    if (keyboardHasPlugin()) {
 3757      cordova.plugins.Keyboard.show();
 3758    }
 3759  },
 3760
 3761  /**
 3762   * Remove all keyboard related event listeners, effectively disabling Ionic's
 3763   * keyboard adjustments.
 3764   */
 3765  disable: function() {
 3766    if (keyboardHasPlugin()) {
 3767      window.removeEventListener('native.keyboardshow', debouncedKeyboardNativeShow );
 3768      window.removeEventListener('native.keyboardhide', keyboardFocusOut);
 3769    } else {
 3770      document.body.removeEventListener('focusout', keyboardFocusOut);
 3771    }
 3772
 3773    document.body.removeEventListener('ionic.focusin', debouncedKeyboardFocusIn);
 3774    document.body.removeEventListener('focusin', debouncedKeyboardFocusIn);
 3775
 3776    window.removeEventListener('orientationchange', keyboardOrientationChange);
 3777
 3778    if ( window.navigator.msPointerEnabled ) {
 3779      document.removeEventListener("MSPointerDown", keyboardInit);
 3780    } else {
 3781      document.removeEventListener('touchstart', keyboardInit);
 3782    }
 3783    ionic.keyboard.isInitialized = false;
 3784  },
 3785
 3786  /**
 3787   * Alias for keyboardInit, initialize all keyboard related event listeners.
 3788   */
 3789  enable: function() {
 3790    keyboardInit();
 3791  }
 3792};
 3793
 3794// Initialize the viewport height (after ionic.keyboard.height has been
 3795// defined).
 3796keyboardCurrentViewportHeight = getViewportHeight();
 3797
 3798
 3799                             /* Event handlers */
 3800/* ------------------------------------------------------------------------- */
 3801
 3802/**
 3803 * Event handler for first touch event, initializes all event listeners
 3804 * for keyboard related events. Also aliased by ionic.keyboard.enable.
 3805 */
 3806function keyboardInit() {
 3807
 3808  if (ionic.keyboard.isInitialized) return;
 3809
 3810  if (keyboardHasPlugin()) {
 3811    window.addEventListener('native.keyboardshow', debouncedKeyboardNativeShow);
 3812    window.addEventListener('native.keyboardhide', keyboardFocusOut);
 3813  } else {
 3814    document.body.addEventListener('focusout', keyboardFocusOut);
 3815  }
 3816
 3817  document.body.addEventListener('ionic.focusin', debouncedKeyboardFocusIn);
 3818  document.body.addEventListener('focusin', debouncedKeyboardFocusIn);
 3819
 3820  if (window.navigator.msPointerEnabled) {
 3821    document.removeEventListener("MSPointerDown", keyboardInit);
 3822  } else {
 3823    document.removeEventListener('touchstart', keyboardInit);
 3824  }
 3825
 3826  ionic.keyboard.isInitialized = true;
 3827}
 3828
 3829/**
 3830 * Event handler for 'native.keyboardshow' event, sets keyboard.height to the
 3831 * reported height and keyboard.isOpening to true. Then calls
 3832 * keyboardWaitForResize with keyboardShow or keyboardUpdateViewportHeight as
 3833 * the callback depending on whether the event was triggered by a focusin or
 3834 * an orientationchange.
 3835 */
 3836function keyboardNativeShow(e) {
 3837  clearTimeout(keyboardFocusOutTimer);
 3838  //console.log("keyboardNativeShow fired at: " + Date.now());
 3839  //console.log("keyboardNativeshow window.innerHeight: " + window.innerHeight);
 3840
 3841  if (!ionic.keyboard.isOpen || ionic.keyboard.isClosing) {
 3842    ionic.keyboard.isOpening = true;
 3843    ionic.keyboard.isClosing = false;
 3844  }
 3845
 3846  ionic.keyboard.height = e.keyboardHeight;
 3847  //console.log('nativeshow keyboard height:' + e.keyboardHeight);
 3848
 3849  if (wasOrientationChange) {
 3850    keyboardWaitForResize(keyboardUpdateViewportHeight, true);
 3851  } else {
 3852    keyboardWaitForResize(keyboardShow, true);
 3853  }
 3854}
 3855
 3856/**
 3857 * Event handler for 'focusin' and 'ionic.focusin' events. Initializes
 3858 * keyboard state (keyboardActiveElement and keyboard.isOpening) for the
 3859 * appropriate adjustments once the window has resized.  If not using the
 3860 * keyboard plugin, calls keyboardWaitForResize with keyboardShow as the
 3861 * callback or keyboardShow right away if the keyboard is already open.  If
 3862 * using the keyboard plugin does nothing and lets keyboardNativeShow handle
 3863 * adjustments with a more accurate keyboard height.
 3864 */
 3865function keyboardFocusIn(e) {
 3866  clearTimeout(keyboardFocusOutTimer);
 3867  //console.log("keyboardFocusIn from: " + e.type + " at: " + Date.now());
 3868
 3869  if (!e.target ||
 3870      e.target.readOnly ||
 3871      !ionic.tap.isKeyboardElement(e.target) ||
 3872      !(scrollView = ionic.DomUtil.getParentWithClass(e.target, SCROLL_CONTAINER_CSS))) {
 3873    if (keyboardActiveElement) {
 3874        lastKeyboardActiveElement = keyboardActiveElement;
 3875    }
 3876    keyboardActiveElement = null;
 3877    return;
 3878  }
 3879
 3880  keyboardActiveElement = e.target;
 3881
 3882  // if using JS scrolling, undo the effects of native overflow scroll so the
 3883  // scroll view is positioned correctly
 3884  if (!scrollView.classList.contains("overflow-scroll")) {
 3885    document.body.scrollTop = 0;
 3886    scrollView.scrollTop = 0;
 3887    ionic.requestAnimationFrame(function(){
 3888      document.body.scrollTop = 0;
 3889      scrollView.scrollTop = 0;
 3890    });
 3891
 3892    // any showing part of the document that isn't within the scroll the user
 3893    // could touchmove and cause some ugly changes to the app, so disable
 3894    // any touchmove events while the keyboard is open using e.preventDefault()
 3895    if (window.navigator.msPointerEnabled) {
 3896      document.addEventListener("MSPointerMove", keyboardPreventDefault, false);
 3897    } else {
 3898      document.addEventListener('touchmove', keyboardPreventDefault, false);
 3899    }
 3900  }
 3901
 3902  if (!ionic.keyboard.isOpen || ionic.keyboard.isClosing) {
 3903    ionic.keyboard.isOpening = true;
 3904    ionic.keyboard.isClosing = false;
 3905  }
 3906
 3907  // attempt to prevent browser from natively scrolling input into view while
 3908  // we are trying to do the same (while we are scrolling) if the user taps the
 3909  // keyboard
 3910  document.addEventListener('keydown', keyboardOnKeyDown, false);
 3911
 3912
 3913
 3914  // if we aren't using the plugin and the keyboard isn't open yet, wait for the
 3915  // window to resize so we can get an accurate estimate of the keyboard size,
 3916  // otherwise we do nothing and let nativeShow call keyboardShow once we have
 3917  // an exact keyboard height
 3918  // if the keyboard is already open, go ahead and scroll the input into view
 3919  // if necessary
 3920  if (!ionic.keyboard.isOpen && !keyboardHasPlugin()) {
 3921    keyboardWaitForResize(keyboardShow, true);
 3922
 3923  } else if (ionic.keyboard.isOpen) {
 3924    keyboardShow();
 3925  }
 3926}
 3927
 3928/**
 3929 * Event handler for 'focusout' events. Sets keyboard.isClosing to true and
 3930 * calls keyboardWaitForResize with keyboardHide as the callback after a small
 3931 * timeout.
 3932 */
 3933function keyboardFocusOut() {
 3934  clearTimeout(keyboardFocusOutTimer);
 3935  //console.log("keyboardFocusOut fired at: " + Date.now());
 3936  //console.log("keyboardFocusOut event type: " + e.type);
 3937
 3938  if (ionic.keyboard.isOpen || ionic.keyboard.isOpening) {
 3939    ionic.keyboard.isClosing = true;
 3940    ionic.keyboard.isOpening = false;
 3941  }
 3942
 3943  // Call keyboardHide with a slight delay because sometimes on focus or
 3944  // orientation change focusin is called immediately after, so we give it time
 3945  // to cancel keyboardHide
 3946  keyboardFocusOutTimer = setTimeout(function() {
 3947    ionic.requestAnimationFrame(function() {
 3948      // focusOut during or right after an orientationchange, so we didn't get
 3949      // a chance to update the viewport height yet, do it and keyboardHide
 3950      //console.log("focusOut, wasOrientationChange: " + wasOrientationChange);
 3951      if (wasOrientationChange) {
 3952        keyboardWaitForResize(function(){
 3953          keyboardUpdateViewportHeight();
 3954          keyboardHide();
 3955        }, false);
 3956      } else {
 3957        keyboardWaitForResize(keyboardHide, false);
 3958      }
 3959    });
 3960  }, 50);
 3961}
 3962
 3963/**
 3964 * Event handler for 'orientationchange' events. If using the keyboard plugin
 3965 * and the keyboard is open on Android, sets wasOrientationChange to true so
 3966 * nativeShow can update the viewport height with an accurate keyboard height.
 3967 * If the keyboard isn't open or keyboard plugin isn't being used,
 3968 * waits for the window to resize before updating the viewport height.
 3969 *
 3970 * On iOS, where orientationchange fires after the keyboard has already shown,
 3971 * updates the viewport immediately, regardless of if the keyboard is already
 3972 * open.
 3973 */
 3974function keyboardOrientationChange() {
 3975  //console.log("orientationchange fired at: " + Date.now());
 3976  //console.log("orientation was: " + (ionic.keyboard.isLandscape ? "landscape" : "portrait"));
 3977
 3978  // toggle orientation
 3979  ionic.keyboard.isLandscape = !ionic.keyboard.isLandscape;
 3980  // //console.log("now orientation is: " + (ionic.keyboard.isLandscape ? "landscape" : "portrait"));
 3981
 3982  // no need to wait for resizing on iOS, and orientationchange always fires
 3983  // after the keyboard has opened, so it doesn't matter if it's open or not
 3984  if (ionic.Platform.isIOS()) {
 3985    keyboardUpdateViewportHeight();
 3986  }
 3987
 3988  // On Android, if the keyboard isn't open or we aren't using the keyboard
 3989  // plugin, update the viewport height once everything has resized. If the
 3990  // keyboard is open and we are using the keyboard plugin do nothing and let
 3991  // nativeShow handle it using an accurate keyboard height.
 3992  if ( ionic.Platform.isAndroid()) {
 3993    if (!ionic.keyboard.isOpen || !keyboardHasPlugin()) {
 3994      keyboardWaitForResize(keyboardUpdateViewportHeight, false);
 3995    } else {
 3996      wasOrientationChange = true;
 3997    }
 3998  }
 3999}
 4000
 4001/**
 4002 * Event handler for 'keydown' event. Tries to prevent browser from natively
 4003 * scrolling an input into view when a user taps the keyboard while we are
 4004 * scrolling the input into view ourselves with JS.
 4005 */
 4006function keyboardOnKeyDown(e) {
 4007  if (ionic.scroll.isScrolling) {
 4008    keyboardPreventDefault(e);
 4009  }
 4010}
 4011
 4012/**
 4013 * Event for 'touchmove' or 'MSPointerMove'. Prevents native scrolling on
 4014 * elements outside the scroll view while the keyboard is open.
 4015 */
 4016function keyboardPreventDefault(e) {
 4017  if (e.target.tagName !== 'TEXTAREA') {
 4018    e.preventDefault();
 4019  }
 4020}
 4021
 4022                              /* Private API */
 4023/* -------------------------------------------------------------------------- */
 4024
 4025/**
 4026 * Polls window.innerHeight until it has updated to an expected value (or
 4027 * sufficient time has passed) before calling the specified callback function.
 4028 * Only necessary for non-fullscreen Android which sometimes reports multiple
 4029 * window.innerHeight values during interim layouts while it is resizing.
 4030 *
 4031 * On iOS, the window.innerHeight will already be updated, but we use the 50ms
 4032 * delay as essentially a timeout so that scroll view adjustments happen after
 4033 * the keyboard has shown so there isn't a white flash from us resizing too
 4034 * quickly.
 4035 *
 4036 * @param {Function} callback the function to call once the window has resized
 4037 * @param {boolean} isOpening whether the resize is from the keyboard opening
 4038 * or not
 4039 */
 4040function keyboardWaitForResize(callback, isOpening) {
 4041  clearInterval(waitForResizeTimer);
 4042  var count = 0;
 4043  var maxCount;
 4044  var initialHeight = getViewportHeight();
 4045  var viewportHeight = initialHeight;
 4046
 4047  //console.log("waitForResize initial viewport height: " + viewportHeight);
 4048  //var start = Date.now();
 4049  //console.log("start: " + start);
 4050
 4051  // want to fail relatively quickly on modern android devices, since it's much
 4052  // more likely we just have a bad keyboard height
 4053  if (ionic.Platform.isAndroid() && ionic.Platform.version() < 4.4) {
 4054    maxCount = 30;
 4055  } else if (ionic.Platform.isAndroid()) {
 4056    maxCount = 10;
 4057  } else {
 4058    maxCount = 1;
 4059  }
 4060
 4061  // poll timer
 4062  waitForResizeTimer = setInterval(function(){
 4063    viewportHeight = getViewportHeight();
 4064
 4065    // height hasn't updated yet, try again in 50ms
 4066    // if not using plugin, wait for maxCount to ensure we have waited long enough
 4067    // to get an accurate keyboard height
 4068    if (++count < maxCount &&
 4069        ((!isPortraitViewportHeight(viewportHeight) &&
 4070         !isLandscapeViewportHeight(viewportHeight)) ||
 4071         !ionic.keyboard.height)) {
 4072      return;
 4073    }
 4074
 4075    // infer the keyboard height from the resize if not using the keyboard plugin
 4076    if (!keyboardHasPlugin()) {
 4077      ionic.keyboard.height = Math.abs(initialHeight - window.innerHeight);
 4078    }
 4079
 4080    // set to true if we were waiting for the keyboard to open
 4081    ionic.keyboard.isOpen = isOpening;
 4082
 4083    clearInterval(waitForResizeTimer);
 4084    //var end = Date.now();
 4085    //console.log("waitForResize count: " + count);
 4086    //console.log("end: " + end);
 4087    //console.log("difference: " + ( end - start ) + "ms");
 4088
 4089    //console.log("callback: " + callback.name);
 4090    callback();
 4091
 4092  }, 50);
 4093
 4094  return maxCount; //for tests
 4095}
 4096
 4097/**
 4098 * On keyboard close sets keyboard state to closed, resets the scroll view,
 4099 * removes CSS from body indicating keyboard was open, removes any event
 4100 * listeners for when the keyboard is open and on Android blurs the active
 4101 * element (which in some cases will still have focus even if the keyboard
 4102 * is closed and can cause it to reappear on subsequent taps).
 4103 */
 4104function keyboardHide() {
 4105  clearTimeout(keyboardFocusOutTimer);
 4106  //console.log("keyboardHide");
 4107
 4108  ionic.keyboard.isOpen = false;
 4109  ionic.keyboard.isClosing = false;
 4110
 4111  if (keyboardActiveElement || lastKeyboardActiveElement) {
 4112    ionic.trigger('resetScrollView', {
 4113      target: keyboardActiveElement || lastKeyboardActiveElement
 4114    }, true);
 4115  }
 4116
 4117  ionic.requestAnimationFrame(function(){
 4118    document.body.classList.remove(KEYBOARD_OPEN_CSS);
 4119  });
 4120
 4121  // the keyboard is gone now, remove the touchmove that disables native scroll
 4122  if (window.navigator.msPointerEnabled) {
 4123    document.removeEventListener("MSPointerMove", keyboardPreventDefault);
 4124  } else {
 4125    document.removeEventListener('touchmove', keyboardPreventDefault);
 4126  }
 4127  document.removeEventListener('keydown', keyboardOnKeyDown);
 4128
 4129  if (ionic.Platform.isAndroid()) {
 4130    // on android closing the keyboard with the back/dismiss button won't remove
 4131    // focus and keyboard can re-appear on subsequent taps (like scrolling)
 4132    if (keyboardHasPlugin()) cordova.plugins.Keyboard.close();
 4133    keyboardActiveElement && keyboardActiveElement.blur();
 4134  }
 4135
 4136  keyboardActiveElement = null;
 4137  lastKeyboardActiveElement = null;
 4138}
 4139
 4140/**
 4141 * On keyboard open sets keyboard state to open, adds CSS to the body
 4142 * indicating the keyboard is open and tells the scroll view to resize and
 4143 * the currently focused input into view if necessary.
 4144 */
 4145function keyboardShow() {
 4146
 4147  ionic.keyboard.isOpen = true;
 4148  ionic.keyboard.isOpening = false;
 4149
 4150  var details = {
 4151    keyboardHeight: keyboardGetHeight(),
 4152    viewportHeight: keyboardCurrentViewportHeight
 4153  };
 4154
 4155  if (keyboardActiveElement) {
 4156    details.target = keyboardActiveElement;
 4157
 4158    var elementBounds = keyboardActiveElement.getBoundingClientRect();
 4159
 4160    details.elementTop = Math.round(elementBounds.top);
 4161    details.elementBottom = Math.round(elementBounds.bottom);
 4162
 4163    details.windowHeight = details.viewportHeight - details.keyboardHeight;
 4164    //console.log("keyboardShow viewportHeight: " + details.viewportHeight +
 4165    //", windowHeight: " + details.windowHeight +
 4166    //", keyboardHeight: " + details.keyboardHeight);
 4167
 4168    // figure out if the element is under the keyboard
 4169    details.isElementUnderKeyboard = (details.elementBottom > details.windowHeight);
 4170    //console.log("isUnderKeyboard: " + details.isElementUnderKeyboard);
 4171    //console.log("elementBottom: " + details.elementBottom);
 4172
 4173    // send event so the scroll view adjusts
 4174    ionic.trigger('scrollChildIntoView', details, true);
 4175  }
 4176
 4177  setTimeout(function(){
 4178    document.body.classList.add(KEYBOARD_OPEN_CSS);
 4179  }, 400);
 4180
 4181  return details; //for testing
 4182}
 4183
 4184/* eslint no-unused-vars:0 */
 4185function keyboardGetHeight() {
 4186  // check if we already have a keyboard height from the plugin or resize calculations
 4187  if (ionic.keyboard.height) {
 4188    return ionic.keyboard.height;
 4189  }
 4190
 4191  if (ionic.Platform.isAndroid()) {
 4192    // should be using the plugin, no way to know how big the keyboard is, so guess
 4193    if ( ionic.Platform.isFullScreen ) {
 4194      return 275;
 4195    }
 4196    // otherwise just calculate it
 4197    var contentHeight = window.innerHeight;
 4198    if (contentHeight < keyboardCurrentViewportHeight) {
 4199      return keyboardCurrentViewportHeight - contentHeight;
 4200    } else {
 4201      return 0;
 4202    }
 4203  }
 4204
 4205  // fallback for when it's the webview without the plugin
 4206  // or for just the standard web browser
 4207  // TODO: have these be based on device
 4208  if (ionic.Platform.isIOS()) {
 4209    if (ionic.keyboard.isLandscape) {
 4210      return 206;
 4211    }
 4212
 4213    if (!ionic.Platform.isWebView()) {
 4214      return 216;
 4215    }
 4216
 4217    return 260;
 4218  }
 4219
 4220  // safe guess
 4221  return 275;
 4222}
 4223
 4224function isPortraitViewportHeight(viewportHeight) {
 4225  return !!(!ionic.keyboard.isLandscape &&
 4226         keyboardPortraitViewportHeight &&
 4227         (Math.abs(keyboardPortraitViewportHeight - viewportHeight) < 2));
 4228}
 4229
 4230function isLandscapeViewportHeight(viewportHeight) {
 4231  return !!(ionic.keyboard.isLandscape &&
 4232         keyboardLandscapeViewportHeight &&
 4233         (Math.abs(keyboardLandscapeViewportHeight - viewportHeight) < 2));
 4234}
 4235
 4236function keyboardUpdateViewportHeight() {
 4237  wasOrientationChange = false;
 4238  keyboardCurrentViewportHeight = getViewportHeight();
 4239
 4240  if (ionic.keyboard.isLandscape && !keyboardLandscapeViewportHeight) {
 4241    //console.log("saved landscape: " + keyboardCurrentViewportHeight);
 4242    keyboardLandscapeViewportHeight = keyboardCurrentViewportHeight;
 4243
 4244  } else if (!ionic.keyboard.isLandscape && !keyboardPortraitViewportHeight) {
 4245    //console.log("saved portrait: " + keyboardCurrentViewportHeight);
 4246    keyboardPortraitViewportHeight = keyboardCurrentViewportHeight;
 4247  }
 4248
 4249  if (keyboardActiveElement) {
 4250    ionic.trigger('resetScrollView', {
 4251      target: keyboardActiveElement
 4252    }, true);
 4253  }
 4254
 4255  if (ionic.keyboard.isOpen && ionic.tap.isTextInput(keyboardActiveElement)) {
 4256    keyboardShow();
 4257  }
 4258}
 4259
 4260function keyboardInitViewportHeight() {
 4261  var viewportHeight = getViewportHeight();
 4262  //console.log("Keyboard init VP: " + viewportHeight + " " + window.innerWidth);
 4263  // can't just use window.innerHeight in case the keyboard is opened immediately
 4264  if ((viewportHeight / window.innerWidth) < 1) {
 4265    ionic.keyboard.isLandscape = true;
 4266  }
 4267  //console.log("ionic.keyboard.isLandscape is: " + ionic.keyboard.isLandscape);
 4268
 4269  // initialize or update the current viewport height values
 4270  keyboardCurrentViewportHeight = viewportHeight;
 4271  if (ionic.keyboard.isLandscape && !keyboardLandscapeViewportHeight) {
 4272    keyboardLandscapeViewportHeight = keyboardCurrentViewportHeight;
 4273  } else if (!ionic.keyboard.isLandscape && !keyboardPortraitViewportHeight) {
 4274    keyboardPortraitViewportHeight = keyboardCurrentViewportHeight;
 4275  }
 4276}
 4277
 4278function getViewportHeight() {
 4279  var windowHeight = window.innerHeight;
 4280  //console.log('window.innerHeight is: ' + windowHeight);
 4281  //console.log('kb height is: ' + ionic.keyboard.height);
 4282  //console.log('kb isOpen: ' + ionic.keyboard.isOpen);
 4283
 4284  //TODO: add iPad undocked/split kb once kb plugin supports it
 4285  // the keyboard overlays the window on Android fullscreen
 4286  if (!(ionic.Platform.isAndroid() && ionic.Platform.isFullScreen) &&
 4287      (ionic.keyboard.isOpen || ionic.keyboard.isOpening) &&
 4288      !ionic.keyboard.isClosing) {
 4289
 4290     return windowHeight + keyboardGetHeight();
 4291  }
 4292  return windowHeight;
 4293}
 4294
 4295function keyboardHasPlugin() {
 4296  return !!(window.cordova && cordova.plugins && cordova.plugins.Keyboard);
 4297}
 4298
 4299ionic.Platform.ready(function() {
 4300  keyboardInitViewportHeight();
 4301
 4302  window.addEventListener('orientationchange', keyboardOrientationChange);
 4303
 4304  // if orientation changes while app is in background, update on resuming
 4305  /*
 4306  if ( ionic.Platform.isWebView() ) {
 4307    document.addEventListener('resume', keyboardInitViewportHeight);
 4308
 4309    if (ionic.Platform.isAndroid()) {
 4310      //TODO: onbackpressed to detect keyboard close without focusout or plugin
 4311    }
 4312  }
 4313  */
 4314
 4315  // if orientation changes while app is in background, update on resuming
 4316/*  if ( ionic.Platform.isWebView() ) {
 4317    document.addEventListener('pause', function() {
 4318      window.removeEventListener('orientationchange', keyboardOrientationChange);
 4319    })
 4320    document.addEventListener('resume', function() {
 4321      keyboardInitViewportHeight();
 4322      window.addEventListener('orientationchange', keyboardOrientationChange)
 4323    });
 4324  }*/
 4325
 4326  // Android sometimes reports bad innerHeight on window.load
 4327  // try it again in a lil bit to play it safe
 4328  setTimeout(keyboardInitViewportHeight, 999);
 4329
 4330  // only initialize the adjustments for the virtual keyboard
 4331  // if a touchstart event happens
 4332  if (window.navigator.msPointerEnabled) {
 4333    document.addEventListener("MSPointerDown", keyboardInit, false);
 4334  } else {
 4335    document.addEventListener('touchstart', keyboardInit, false);
 4336  }
 4337});
 4338
 4339
 4340
 4341var viewportTag;
 4342var viewportProperties = {};
 4343
 4344ionic.viewport = {
 4345  orientation: function() {
 4346    // 0 = Portrait
 4347    // 90 = Landscape
 4348    // not using window.orientation because each device has a different implementation
 4349    return (window.innerWidth > window.innerHeight ? 90 : 0);
 4350  }
 4351};
 4352
 4353function viewportLoadTag() {
 4354  var x;
 4355
 4356  for (x = 0; x < document.head.children.length; x++) {
 4357    if (document.head.children[x].name == 'viewport') {
 4358      viewportTag = document.head.children[x];
 4359      break;
 4360    }
 4361  }
 4362
 4363  if (viewportTag) {
 4364    var props = viewportTag.content.toLowerCase().replace(/\s+/g, '').split(',');
 4365    var keyValue;
 4366    for (x = 0; x < props.length; x++) {
 4367      if (props[x]) {
 4368        keyValue = props[x].split('=');
 4369        viewportProperties[ keyValue[0] ] = (keyValue.length > 1 ? keyValue[1] : '_');
 4370      }
 4371    }
 4372    viewportUpdate();
 4373  }
 4374}
 4375
 4376function viewportUpdate() {
 4377  // unit tests in viewport.unit.js
 4378
 4379  var initWidth = viewportProperties.width;
 4380  var initHeight = viewportProperties.height;
 4381  var p = ionic.Platform;
 4382  var version = p.version();
 4383  var DEVICE_WIDTH = 'device-width';
 4384  var DEVICE_HEIGHT = 'device-height';
 4385  var orientation = ionic.viewport.orientation();
 4386
 4387  // Most times we're removing the height and adding the width
 4388  // So this is the default to start with, then modify per platform/version/oreintation
 4389  delete viewportProperties.height;
 4390  viewportProperties.width = DEVICE_WIDTH;
 4391
 4392  if (p.isIPad()) {
 4393    // iPad
 4394
 4395    if (version > 7) {
 4396      // iPad >= 7.1
 4397      // https://issues.apache.org/jira/browse/CB-4323
 4398      delete viewportProperties.width;
 4399
 4400    } else {
 4401      // iPad <= 7.0
 4402
 4403      if (p.isWebView()) {
 4404        // iPad <= 7.0 WebView
 4405
 4406        if (orientation == 90) {
 4407          // iPad <= 7.0 WebView Landscape
 4408          viewportProperties.height = '0';
 4409
 4410        } else if (version == 7) {
 4411          // iPad <= 7.0 WebView Portait
 4412          viewportProperties.height = DEVICE_HEIGHT;
 4413        }
 4414      } else {
 4415        // iPad <= 6.1 Browser
 4416        if (version < 7) {
 4417          viewportProperties.height = '0';
 4418        }
 4419      }
 4420    }
 4421
 4422  } else if (p.isIOS()) {
 4423    // iPhone
 4424
 4425    if (p.isWebView()) {
 4426      // iPhone WebView
 4427
 4428      if (version > 7) {
 4429        // iPhone >= 7.1 WebView
 4430        delete viewportProperties.width;
 4431
 4432      } else if (version < 7) {
 4433        // iPhone <= 6.1 WebView
 4434        // if height was set it needs to get removed with this hack for <= 6.1
 4435        if (initHeight) viewportProperties.height = '0';
 4436
 4437      } else if (version == 7) {
 4438        //iPhone == 7.0 WebView
 4439        viewportProperties.height = DEVICE_HEIGHT;
 4440      }
 4441
 4442    } else {
 4443      // iPhone Browser
 4444
 4445      if (version < 7) {
 4446        // iPhone <= 6.1 Browser
 4447        // if height was set it needs to get removed with this hack for <= 6.1
 4448        if (initHeight) viewportProperties.height = '0';
 4449      }
 4450    }
 4451
 4452  }
 4453
 4454  // only update the viewport tag if there was a change
 4455  if (initWidth !== viewportProperties.width || initHeight !== viewportProperties.height) {
 4456    viewportTagUpdate();
 4457  }
 4458}
 4459
 4460function viewportTagUpdate() {
 4461  var key, props = [];
 4462  for (key in viewportProperties) {
 4463    if (viewportProperties[key]) {
 4464      props.push(key + (viewportProperties[key] == '_' ? '' : '=' + viewportProperties[key]));
 4465    }
 4466  }
 4467
 4468  viewportTag.content = props.join(', ');
 4469}
 4470
 4471ionic.Platform.ready(function() {
 4472  viewportLoadTag();
 4473
 4474  window.addEventListener("orientationchange", function() {
 4475    setTimeout(viewportUpdate, 1000);
 4476  }, false);
 4477});
 4478
 4479(function(ionic) {
 4480'use strict';
 4481  ionic.views.View = function() {
 4482    this.initialize.apply(this, arguments);
 4483  };
 4484
 4485  ionic.views.View.inherit = ionic.inherit;
 4486
 4487  ionic.extend(ionic.views.View.prototype, {
 4488    initialize: function() {}
 4489  });
 4490
 4491})(window.ionic);
 4492
 4493/*
 4494 * Scroller
 4495 * http://github.com/zynga/scroller
 4496 *
 4497 * Copyright 2011, Zynga Inc.
 4498 * Licensed under the MIT License.
 4499 * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt
 4500 *
 4501 * Based on the work of: Unify Project (unify-project.org)
 4502 * http://unify-project.org
 4503 * Copyright 2011, Deutsche Telekom AG
 4504 * License: MIT + Apache (V2)
 4505 */
 4506
 4507/* jshint eqnull: true */
 4508
 4509/**
 4510 * Generic animation class with support for dropped frames both optional easing and duration.
 4511 *
 4512 * Optional duration is useful when the lifetime is defined by another condition than time
 4513 * e.g. speed of an animating object, etc.
 4514 *
 4515 * Dropped frame logic allows to keep using the same updater logic independent from the actual
 4516 * rendering. This eases a lot of cases where it might be pretty complex to break down a state
 4517 * based on the pure time difference.
 4518 */
 4519var zyngaCore = { effect: {} };
 4520(function(global) {
 4521  var time = Date.now || function() {
 4522    return +new Date();
 4523  };
 4524  var desiredFrames = 60;
 4525  var millisecondsPerSecond = 1000;
 4526  var running = {};
 4527  var counter = 1;
 4528
 4529  zyngaCore.effect.Animate = {
 4530
 4531    /**
 4532     * A requestAnimationFrame wrapper / polyfill.
 4533     *
 4534     * @param callback {Function} The callback to be invoked before the next repaint.
 4535     * @param root {HTMLElement} The root element for the repaint
 4536     */
 4537    requestAnimationFrame: (function() {
 4538
 4539      // Check for request animation Frame support
 4540      var requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame;
 4541      var isNative = !!requestFrame;
 4542
 4543      if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) {
 4544        isNative = false;
 4545      }
 4546
 4547      if (isNative) {
 4548        return function(callback, root) {
 4549          requestFrame(callback, root);
 4550        };
 4551      }
 4552
 4553      var TARGET_FPS = 60;
 4554      var requests = {};
 4555      var requestCount = 0;
 4556      var rafHandle = 1;
 4557      var intervalHandle = null;
 4558      var lastActive = +new Date();
 4559
 4560      return function(callback) {
 4561        var callbackHandle = rafHandle++;
 4562
 4563        // Store callback
 4564        requests[callbackHandle] = callback;
 4565        requestCount++;
 4566
 4567        // Create timeout at first request
 4568        if (intervalHandle === null) {
 4569
 4570          intervalHandle = setInterval(function() {
 4571
 4572            var time = +new Date();
 4573            var currentRequests = requests;
 4574
 4575            // Reset data structure before executing callbacks
 4576            requests = {};
 4577            requestCount = 0;
 4578
 4579            for(var key in currentRequests) {
 4580              if (currentRequests.hasOwnProperty(key)) {
 4581                currentRequests[key](time);
 4582                lastActive = time;
 4583              }
 4584            }
 4585
 4586            // Disable the timeout when nothing happens for a certain
 4587            // period of time
 4588            if (time - lastActive > 2500) {
 4589              clearInterval(intervalHandle);
 4590              intervalHandle = null;
 4591            }
 4592
 4593          }, 1000 / TARGET_FPS);
 4594        }
 4595
 4596        return callbackHandle;
 4597      };
 4598
 4599    })(),
 4600
 4601
 4602    /**
 4603     * Stops the given animation.
 4604     *
 4605     * @param id {Integer} Unique animation ID
 4606     * @return {Boolean} Whether the animation was stopped (aka, was running before)
 4607     */
 4608    stop: function(id) {
 4609      var cleared = running[id] != null;
 4610      if (cleared) {
 4611        running[id] = null;
 4612      }
 4613
 4614      return cleared;
 4615    },
 4616
 4617
 4618    /**
 4619     * Whether the given animation is still running.
 4620     *
 4621     * @param id {Integer} Unique animation ID
 4622     * @return {Boolean} Whether the animation is still running
 4623     */
 4624    isRunning: function(id) {
 4625      return running[id] != null;
 4626    },
 4627
 4628
 4629    /**
 4630     * Start the animation.
 4631     *
 4632     * @param stepCallback {Function} Pointer to function which is executed on every step.
 4633     *   Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }`
 4634     * @param verifyCallback {Function} Executed before every animation step.
 4635     *   Signature of the method should be `function() { return continueWithAnimation; }`
 4636     * @param completedCallback {Function}
 4637     *   Signature of the method should be `function(droppedFrames, finishedAnimation) {}`
 4638     * @param duration {Integer} Milliseconds to run the animation
 4639     * @param easingMethod {Function} Pointer to easing function
 4640     *   Signature of the method should be `function(percent) { return modifiedValue; }`
 4641     * @param root {Element} Render root, when available. Used for internal
 4642     *   usage of requestAnimationFrame.
 4643     * @return {Integer} Identifier of animation. Can be used to stop it any time.
 4644     */
 4645    start: function(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {
 4646
 4647      var start = time();
 4648      var lastFrame = start;
 4649      var percent = 0;
 4650      var dropCounter = 0;
 4651      var id = counter++;
 4652
 4653      if (!root) {
 4654        root = document.body;
 4655      }
 4656
 4657      // Compacting running db automatically every few new animations
 4658      if (id % 20 === 0) {
 4659        var newRunning = {};
 4660        for (var usedId in running) {
 4661          newRunning[usedId] = true;
 4662        }
 4663        running = newRunning;
 4664      }
 4665
 4666      // This is the internal step method which is called every few milliseconds
 4667      var step = function(virtual) {
 4668
 4669        // Normalize virtual value
 4670        var render = virtual !== true;
 4671
 4672        // Get current time
 4673        var now = time();
 4674
 4675        // Verification is executed before next animation step
 4676        if (!running[id] || (verifyCallback && !verifyCallback(id))) {
 4677
 4678          running[id] = null;
 4679          completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false);
 4680          return;
 4681
 4682        }
 4683
 4684        // For the current rendering to apply let's update omitted steps in memory.
 4685        // This is important to bring internal state variables up-to-date with progress in time.
 4686        if (render) {
 4687
 4688          var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1;
 4689          for (var j = 0; j < Math.min(droppedFrames, 4); j++) {
 4690            step(true);
 4691            dropCounter++;
 4692          }
 4693
 4694        }
 4695
 4696        // Compute percent value
 4697        if (duration) {
 4698          percent = (now - start) / duration;
 4699          if (percent > 1) {
 4700            percent = 1;
 4701          }
 4702        }
 4703
 4704        // Execute step callback, then...
 4705        var value = easingMethod ? easingMethod(percent) : percent;
 4706        if ((stepCallback(value, now, render) === false || percent === 1) && render) {
 4707          running[id] = null;
 4708          completedCallback && completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration == null);
 4709        } else if (render) {
 4710          lastFrame = now;
 4711          zyngaCore.effect.Animate.requestAnimationFrame(step, root);
 4712        }
 4713      };
 4714
 4715      // Mark as running
 4716      running[id] = true;
 4717
 4718      // Init first step
 4719      zyngaCore.effect.Animate.requestAnimationFrame(step, root);
 4720
 4721      // Return unique animation ID
 4722      return id;
 4723    }
 4724  };
 4725})(window);
 4726
 4727/*
 4728 * Scroller
 4729 * http://github.com/zynga/scroller
 4730 *
 4731 * Copyright 2011, Zynga Inc.
 4732 * Licensed under the MIT License.
 4733 * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt
 4734 *
 4735 * Based on the work of: Unify Project (unify-project.org)
 4736 * http://unify-project.org
 4737 * Copyright 2011, Deutsche Telekom AG
 4738 * License: MIT + Apache (V2)
 4739 */
 4740
 4741(function(ionic) {
 4742  var NOOP = function(){};
 4743
 4744  // Easing Equations (c) 2003 Robert Penner, all rights reserved.
 4745  // Open source under the BSD License.
 4746
 4747  /**
 4748   * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)
 4749  **/
 4750  var easeOutCubic = function(pos) {
 4751    return (Math.pow((pos - 1), 3) + 1);
 4752  };
 4753
 4754  /**
 4755   * @param pos {Number} position between 0 (start of effect) and 1 (end of effect)
 4756  **/
 4757  var easeInOutCubic = function(pos) {
 4758    if ((pos /= 0.5) < 1) {
 4759      return 0.5 * Math.pow(pos, 3);
 4760    }
 4761
 4762    return 0.5 * (Math.pow((pos - 2), 3) + 2);
 4763  };
 4764
 4765
 4766/**
 4767 * ionic.views.Scroll
 4768 * A powerful scroll view with support for bouncing, pull to refresh, and paging.
 4769 * @param   {Object}        options options for the scroll view
 4770 * @class A scroll view system
 4771 * @memberof ionic.views
 4772 */
 4773ionic.views.Scroll = ionic.views.View.inherit({
 4774  initialize: function(options) {
 4775    var self = this;
 4776
 4777    self.__container = options.el;
 4778    self.__content = options.el.firstElementChild;
 4779
 4780    //Remove any scrollTop attached to these elements; they are virtual scroll now
 4781    //This also stops on-load-scroll-to-window.location.hash that the browser does
 4782    setTimeout(function() {
 4783      if (self.__container && self.__content) {
 4784        self.__container.scrollTop = 0;
 4785        self.__content.scrollTop = 0;
 4786      }
 4787    });
 4788
 4789    self.options = {
 4790
 4791      /** Disable scrolling on x-axis by default */
 4792      scrollingX: false,
 4793      scrollbarX: true,
 4794
 4795      /** Enable scrolling on y-axis */
 4796      scrollingY: true,
 4797      scrollbarY: true,
 4798
 4799      startX: 0,
 4800      startY: 0,
 4801
 4802      /** The amount to dampen mousewheel events */
 4803      wheelDampen: 6,
 4804
 4805      /** The minimum size the scrollbars scale to while scrolling */
 4806      minScrollbarSizeX: 5,
 4807      minScrollbarSizeY: 5,
 4808
 4809      /** Scrollbar fading after scrolling */
 4810      scrollbarsFade: true,
 4811      scrollbarFadeDelay: 300,
 4812      /** The initial fade delay when the pane is resized or initialized */
 4813      scrollbarResizeFadeDelay: 1000,
 4814
 4815      /** Enable animations for deceleration, snap back, zooming and scrolling */
 4816      animating: true,
 4817
 4818      /** duration for animations triggered by scrollTo/zoomTo */
 4819      animationDuration: 250,
 4820
 4821      /** The velocity required to make the scroll view "slide" after touchend */
 4822      decelVelocityThreshold: 4,
 4823
 4824      /** The velocity required to make the scroll view "slide" after touchend when using paging */
 4825      decelVelocityThresholdPaging: 4,
 4826
 4827      /** Enable bouncing (content can be slowly moved outside and jumps back after releasing) */
 4828      bouncing: true,
 4829
 4830      /** Enable locking to the main axis if user moves only slightly on one of them at start */
 4831      locking: true,
 4832
 4833      /** Enable pagination mode (switching between full page content panes) */
 4834      paging: false,
 4835
 4836      /** Enable snapping of content to a configured pixel grid */
 4837      snapping: false,
 4838
 4839      /** Enable zooming of content via API, fingers and mouse wheel */
 4840      zooming: false,
 4841
 4842      /** Minimum zoom level */
 4843      minZoom: 0.5,
 4844
 4845      /** Maximum zoom level */
 4846      maxZoom: 3,
 4847
 4848      /** Multiply or decrease scrolling speed **/
 4849      speedMultiplier: 1,
 4850
 4851      deceleration: 0.97,
 4852
 4853      /** Whether to prevent default on a scroll operation to capture drag events **/
 4854      preventDefault: false,
 4855
 4856      /** Callback that is fired on the later of touch end or deceleration end,
 4857        provided that another scrolling action has not begun. Used to know
 4858        when to fade out a scrollbar. */
 4859      scrollingComplete: NOOP,
 4860
 4861      /** This configures the amount of change applied to deceleration when reaching boundaries  **/
 4862      penetrationDeceleration: 0.03,
 4863
 4864      /** This configures the amount of change applied to acceleration when reaching boundaries  **/
 4865      penetrationAcceleration: 0.08,
 4866
 4867      // The ms interval for triggering scroll events
 4868      scrollEventInterval: 10,
 4869
 4870      freeze: false,
 4871
 4872      getContentWidth: function() {
 4873        return Math.max(self.__content.scrollWidth, self.__content.offsetWidth);
 4874      },
 4875      getContentHeight: function() {
 4876        return Math.max(self.__content.scrollHeight, self.__content.offsetHeight + (self.__content.offsetTop * 2));
 4877      }
 4878    };
 4879
 4880    for (var key in options) {
 4881      self.options[key] = options[key];
 4882    }
 4883
 4884    self.hintResize = ionic.debounce(function() {
 4885      self.resize();
 4886    }, 1000, true);
 4887
 4888    self.onScroll = function() {
 4889
 4890      if (!ionic.scroll.isScrolling) {
 4891        setTimeout(self.setScrollStart, 50);
 4892      } else {
 4893        clearTimeout(self.scrollTimer);
 4894        self.scrollTimer = setTimeout(self.setScrollStop, 80);
 4895      }
 4896
 4897    };
 4898
 4899    self.freeze = function(shouldFreeze) {
 4900      if (arguments.length) {
 4901        self.options.freeze = shouldFreeze;
 4902      }
 4903      return self.options.freeze;
 4904    };
 4905
 4906    // We can just use the standard freeze pop in our mouth
 4907    self.freezeShut = self.freeze;
 4908
 4909    self.setScrollStart = function() {
 4910      ionic.scroll.isScrolling = Math.abs(ionic.scroll.lastTop - self.__scrollTop) > 1;
 4911      clearTimeout(self.scrollTimer);
 4912      self.scrollTimer = setTimeout(self.setScrollStop, 80);
 4913    };
 4914
 4915    self.setScrollStop = function() {
 4916      ionic.scroll.isScrolling = false;
 4917      ionic.scroll.lastTop = self.__scrollTop;
 4918    };
 4919
 4920    self.triggerScrollEvent = ionic.throttle(function() {
 4921      self.onScroll();
 4922      ionic.trigger('scroll', {
 4923        scrollTop: self.__scrollTop,
 4924        scrollLeft: self.__scrollLeft,
 4925        target: self.__container
 4926      });
 4927    }, self.options.scrollEventInterval);
 4928
 4929    self.triggerScrollEndEvent = function() {
 4930      ionic.trigger('scrollend', {
 4931        scrollTop: self.__scrollTop,
 4932        scrollLeft: self.__scrollLeft,
 4933        target: self.__container
 4934      });
 4935    };
 4936
 4937    self.__scrollLeft = self.options.startX;
 4938    self.__scrollTop = self.options.startY;
 4939
 4940    // Get the render update function, initialize event handlers,
 4941    // and calculate the size of the scroll container
 4942    self.__callback = self.getRenderFn();
 4943    self.__initEventHandlers();
 4944    self.__createScrollbars();
 4945
 4946  },
 4947
 4948  run: function() {
 4949    this.resize();
 4950
 4951    // Fade them out
 4952    this.__fadeScrollbars('out', this.options.scrollbarResizeFadeDelay);
 4953  },
 4954
 4955
 4956
 4957  /*
 4958  ---------------------------------------------------------------------------
 4959    INTERNAL FIELDS :: STATUS
 4960  ---------------------------------------------------------------------------
 4961  */
 4962
 4963  /** Whether only a single finger is used in touch handling */
 4964  __isSingleTouch: false,
 4965
 4966  /** Whether a touch event sequence is in progress */
 4967  __isTracking: false,
 4968
 4969  /** Whether a deceleration animation went to completion. */
 4970  __didDecelerationComplete: false,
 4971
 4972  /**
 4973   * Whether a gesture zoom/rotate event is in progress. Activates when
 4974   * a gesturestart event happens. This has higher priority than dragging.
 4975   */
 4976  __isGesturing: false,
 4977
 4978  /**
 4979   * Whether the user has moved by such a distance that we have enabled
 4980   * dragging mode. Hint: It's only enabled after some pixels of movement to
 4981   * not interrupt with clicks etc.
 4982   */
 4983  __isDragging: false,
 4984
 4985  /**
 4986   * Not touching and dragging anymore, and smoothly animating the
 4987   * touch sequence using deceleration.
 4988   */
 4989  __isDecelerating: false,
 4990
 4991  /**
 4992   * Smoothly animating the currently configured change
 4993   */
 4994  __isAnimating: false,
 4995
 4996
 4997
 4998  /*
 4999  ---------------------------------------------------------------------------
 5000    INTERNAL FIELDS :: DIMENSIONS
 5001  ---------------------------------------------------------------------------
 5002  */
 5003
 5004  /** Available outer left position (from document perspective) */
 5005  __clientLeft: 0,
 5006
 5007  /** Available outer top position (from document perspective) */
 5008  __clientTop: 0,
 5009
 5010  /** Available outer width */
 5011  __clientWidth: 0,
 5012
 5013  /** Available outer height */
 5014  __clientHeight: 0,
 5015
 5016  /** Outer width of content */
 5017  __contentWidth: 0,
 5018
 5019  /** Outer height of content */
 5020  __contentHeight: 0,
 5021
 5022  /** Snapping width for content */
 5023  __snapWidth: 100,
 5024
 5025  /** Snapping height for content */
 5026  __snapHeight: 100,
 5027
 5028  /** Height to assign to refresh area */
 5029  __refreshHeight: null,
 5030
 5031  /** Whether the refresh process is enabled when the event is released now */
 5032  __refreshActive: false,
 5033
 5034  /** Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release */
 5035  __refreshActivate: null,
 5036
 5037  /** Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled */
 5038  __refreshDeactivate: null,
 5039
 5040  /** Callback to execute to start the actual refresh. Call {@link #refreshFinish} when done */
 5041  __refreshStart: null,
 5042
 5043  /** Zoom level */
 5044  __zoomLevel: 1,
 5045
 5046  /** Scroll position on x-axis */
 5047  __scrollLeft: 0,
 5048
 5049  /** Scroll position on y-axis */
 5050  __scrollTop: 0,
 5051
 5052  /** Maximum allowed scroll position on x-axis */
 5053  __maxScrollLeft: 0,
 5054
 5055  /** Maximum allowed scroll position on y-axis */
 5056  __maxScrollTop: 0,
 5057
 5058  /* Scheduled left position (final position when animating) */
 5059  __scheduledLeft: 0,
 5060
 5061  /* Scheduled top position (final position when animating) */
 5062  __scheduledTop: 0,
 5063
 5064  /* Scheduled zoom level (final scale when animating) */
 5065  __scheduledZoom: 0,
 5066
 5067
 5068
 5069  /*
 5070  ---------------------------------------------------------------------------
 5071    INTERNAL FIELDS :: LAST POSITIONS
 5072  ---------------------------------------------------------------------------
 5073  */
 5074
 5075  /** Left position of finger at start */
 5076  __lastTouchLeft: null,
 5077
 5078  /** Top position of finger at start */
 5079  __lastTouchTop: null,
 5080
 5081  /** Timestamp of last move of finger. Used to limit tracking range for deceleration speed. */
 5082  __lastTouchMove: null,
 5083
 5084  /** List of positions, uses three indexes for each state: left, top, timestamp */
 5085  __positions: null,
 5086
 5087
 5088
 5089  /*
 5090  ---------------------------------------------------------------------------
 5091    INTERNAL FIELDS :: DECELERATION SUPPORT
 5092  ---------------------------------------------------------------------------
 5093  */
 5094
 5095  /** Minimum left scroll position during deceleration */
 5096  __minDecelerationScrollLeft: null,
 5097
 5098  /** Minimum top scroll position during deceleration */
 5099  __minDecelerationScrollTop: null,
 5100
 5101  /** Maximum left scroll position during deceleration */
 5102  __maxDecelerationScrollLeft: null,
 5103
 5104  /** Maximum top scroll position during deceleration */
 5105  __maxDecelerationScrollTop: null,
 5106
 5107  /** Current factor to modify horizontal scroll position with on every step */
 5108  __decelerationVelocityX: null,
 5109
 5110  /** Current factor to modify vertical scroll position with on every step */
 5111  __decelerationVelocityY: null,
 5112
 5113
 5114  /** the browser-specific property to use for transforms */
 5115  __transformProperty: null,
 5116  __perspectiveProperty: null,
 5117
 5118  /** scrollbar indicators */
 5119  __indicatorX: null,
 5120  __indicatorY: null,
 5121
 5122  /** Timeout for scrollbar fading */
 5123  __scrollbarFadeTimeout: null,
 5124
 5125  /** whether we've tried to wait for size already */
 5126  __didWaitForSize: null,
 5127  __sizerTimeout: null,
 5128
 5129  __initEventHandlers: function() {
 5130    var self = this;
 5131
 5132    // Event Handler
 5133    var container = self.__container;
 5134
 5135    // save height when scroll view is shrunk so we don't need to reflow
 5136    var scrollViewOffsetHeight;
 5137
 5138    /**
 5139     * Shrink the scroll view when the keyboard is up if necessary and if the
 5140     * focused input is below the bottom of the shrunk scroll view, scroll it
 5141     * into view.
 5142     */
 5143    self.scrollChildIntoView = function(e) {
 5144      //console.log("scrollChildIntoView at: " + Date.now());
 5145
 5146      // D
 5147      var scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
 5148      // D - A
 5149      scrollViewOffsetHeight = container.offsetHeight;
 5150      var alreadyShrunk = self.isShrunkForKeyboard;
 5151
 5152      var isModal = container.parentNode.classList.contains('modal');
 5153      // 680px is when the media query for 60% modal width kicks in
 5154      var isInsetModal = isModal && window.innerWidth >= 680;
 5155
 5156     /*
 5157      *  _______
 5158      * |---A---| <- top of scroll view
 5159      * |       |
 5160      * |---B---| <- keyboard
 5161      * |   C   | <- input
 5162      * |---D---| <- initial bottom of scroll view
 5163      * |___E___| <- bottom of viewport
 5164      *
 5165      *  All commented calculations relative to the top of the viewport (ie E
 5166      *  is the viewport height, not 0)
 5167      */
 5168      if (!alreadyShrunk) {
 5169        // shrink scrollview so we can actually scroll if the input is hidden
 5170        // if it isn't shrink so we can scroll to inputs under the keyboard
 5171        // inset modals won't shrink on Android on their own when the keyboard appears
 5172        if ( ionic.Platform.isIOS() || ionic.Platform.isFullScreen || isInsetModal ) {
 5173          // if there are things below the scroll view account for them and
 5174          // subtract them from the keyboard height when resizing
 5175          // E - D                         E                         D
 5176          var scrollBottomOffsetToBottom = e.detail.viewportHeight - scrollBottomOffsetToTop;
 5177
 5178          // 0 or D - B if D > B           E - B                     E - D
 5179          var keyboardOffset = Math.max(0, e.detail.keyboardHeight - scrollBottomOffsetToBottom);
 5180
 5181          ionic.requestAnimationFrame(function(){
 5182            // D - A or B - A if D > B       D - A             max(0, D - B)
 5183            scrollViewOffsetHeight = scrollViewOffsetHeight - keyboardOffset;
 5184            container.style.height = scrollViewOffsetHeight + "px";
 5185            container.style.overflow = "visible";
 5186
 5187            //update scroll view
 5188            self.resize();
 5189          });
 5190        }
 5191
 5192        self.isShrunkForKeyboard = true;
 5193      }
 5194
 5195      /*
 5196       *  _______
 5197       * |---A---| <- top of scroll view
 5198       * |   *   | <- where we want to scroll to
 5199       * |--B-D--| <- keyboard, bottom of scroll view
 5200       * |   C   | <- input
 5201       * |       |
 5202       * |___E___| <- bottom of viewport
 5203       *
 5204       *  All commented calculations relative to the top of the viewport (ie E
 5205       *  is the viewport height, not 0)
 5206       */
 5207      // if the element is positioned under the keyboard scroll it into view
 5208      if (e.detail.isElementUnderKeyboard) {
 5209
 5210        ionic.requestAnimationFrame(function(){
 5211          container.scrollTop = 0;
 5212          // update D if we shrunk
 5213          if (self.isShrunkForKeyboard && !alreadyShrunk) {
 5214            scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
 5215          }
 5216
 5217          // middle of the scrollview, this is where we want to scroll to
 5218          // (D - A) / 2
 5219          var scrollMidpointOffset = scrollViewOffsetHeight * 0.5;
 5220          //console.log("container.offsetHeight: " + scrollViewOffsetHeight);
 5221
 5222          // middle of the input we want to scroll into view
 5223          // C
 5224          var inputMidpoint = ((e.detail.elementBottom + e.detail.elementTop) / 2);
 5225
 5226          // distance from middle of input to the bottom of the scroll view
 5227          // C - D                                C               D
 5228          var inputMidpointOffsetToScrollBottom = inputMidpoint - scrollBottomOffsetToTop;
 5229
 5230          //C - D + (D - A)/2          C - D                     (D - A)/ 2
 5231          var scrollTop = inputMidpointOffsetToScrollBottom + scrollMidpointOffset;
 5232
 5233          if ( scrollTop > 0) {
 5234            if (ionic.Platform.isIOS()) ionic.tap.cloneFocusedInput(container, self);
 5235            self.scrollBy(0, scrollTop, true);
 5236            self.onScroll();
 5237          }
 5238        });
 5239      }
 5240
 5241      // Only the first scrollView parent of the element that broadcasted this event
 5242      // (the active element that needs to be shown) should receive this event
 5243      e.stopPropagation();
 5244    };
 5245
 5246    self.resetScrollView = function() {
 5247      //return scrollview to original height once keyboard has hidden
 5248      if ( self.isShrunkForKeyboard ) {
 5249        self.isShrunkForKeyboard = false;
 5250        container.style.height = "";
 5251        container.style.overflow = "";
 5252      }
 5253      self.resize();
 5254    };
 5255
 5256    //Broadcasted when keyboard is shown on some platforms.
 5257    //See js/utils/keyboard.js
 5258    container.addEventListener('scrollChildIntoView', self.scrollChildIntoView);
 5259
 5260    // Listen on document because container may not have had the last
 5261    // keyboardActiveElement, for example after closing a modal with a focused
 5262    // input and returning to a previously resized scroll view in an ion-content.
 5263    // Since we can only resize scroll views that are currently visible, just resize
 5264    // the current scroll view when the keyboard is closed.
 5265    document.addEventListener('resetScrollView', self.resetScrollView);
 5266
 5267    function getEventTouches(e) {
 5268      return e.touches && e.touches.length ? e.touches : [{
 5269        pageX: e.pageX,
 5270        pageY: e.pageY
 5271      }];
 5272    }
 5273
 5274    self.touchStart = function(e) {
 5275      self.startCoordinates = ionic.tap.pointerCoord(e);
 5276
 5277      if ( ionic.tap.ignoreScrollStart(e) ) {
 5278        return;
 5279      }
 5280
 5281      self.__isDown = true;
 5282
 5283      if ( ionic.tap.containsOrIsTextInput(e.target) || e.target.tagName === 'SELECT' ) {
 5284        // do not start if the target is a text input
 5285        // if there is a touchmove on this input, then we can start the scroll
 5286        self.__hasStarted = false;
 5287        return;
 5288      }
 5289
 5290      self.__isSelectable = true;
 5291      self.__enableScrollY = true;
 5292      self.__hasStarted = true;
 5293      self.doTouchStart(getEventTouches(e), e.timeStamp);
 5294      e.preventDefault();
 5295    };
 5296
 5297    self.touchMove = function(e) {
 5298      if (self.options.freeze || !self.__isDown ||
 5299        (!self.__isDown && e.defaultPrevented) ||
 5300        (e.target.tagName === 'TEXTAREA' && e.target.parentElement.querySelector(':focus')) ) {
 5301        return;
 5302      }
 5303
 5304      if ( !self.__hasStarted && ( ionic.tap.containsOrIsTextInput(e.target) || e.target.tagName === 'SELECT' ) ) {
 5305        // the target is a text input and scroll has started
 5306        // since the text input doesn't start on touchStart, do it here
 5307        self.__hasStarted = true;
 5308        self.doTouchStart(getEventTouches(e), e.timeStamp);
 5309        e.preventDefault();
 5310        return;
 5311      }
 5312
 5313      if (self.startCoordinates) {
 5314        // we have start coordinates, so get this touch move's current coordinates
 5315        var currentCoordinates = ionic.tap.pointerCoord(e);
 5316
 5317        if ( self.__isSelectable &&
 5318            ionic.tap.isTextInput(e.target) &&
 5319            Math.abs(self.startCoordinates.x - currentCoordinates.x) > 20 ) {
 5320          // user slid the text input's caret on its x axis, disable any future y scrolling
 5321          self.__enableScrollY = false;
 5322          self.__isSelectable = true;
 5323        }
 5324
 5325        if ( self.__enableScrollY && Math.abs(self.startCoordinates.y - currentCoordinates.y) > 10 ) {
 5326          // user scrolled the entire view on the y axis
 5327          // disabled being able to select text on an input
 5328          // hide the input which has focus, and show a cloned one that doesn't have focus
 5329          self.__isSelectable = false;
 5330          ionic.tap.cloneFocusedInput(container, self);
 5331        }
 5332      }
 5333
 5334      self.doTouchMove(getEventTouches(e), e.timeStamp, e.scale);
 5335      self.__isDown = true;
 5336    };
 5337
 5338    self.touchMoveBubble = function(e) {
 5339      if(self.__isDown && self.options.preventDefault) {
 5340        e.preventDefault();
 5341      }
 5342    };
 5343
 5344    self.touchEnd = function(e) {
 5345      if (!self.__isDown) return;
 5346
 5347      self.doTouchEnd(e, e.timeStamp);
 5348      self.__isDown = false;
 5349      self.__hasStarted = false;
 5350      self.__isSelectable = true;
 5351      self.__enableScrollY = true;
 5352
 5353      if ( !self.__isDragging && !self.__isDecelerating && !self.__isAnimating ) {
 5354        ionic.tap.removeClonedInputs(container, self);
 5355      }
 5356    };
 5357
 5358    self.mouseWheel = ionic.animationFrameThrottle(function(e) {
 5359      var scrollParent = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'ionic-scroll');
 5360      if (!self.options.freeze && scrollParent === self.__container) {
 5361
 5362        self.hintResize();
 5363        self.scrollBy(
 5364          (e.wheelDeltaX || e.deltaX || 0) / self.options.wheelDampen,
 5365          (-e.wheelDeltaY || e.deltaY || 0) / self.options.wheelDampen
 5366        );
 5367
 5368        self.__fadeScrollbars('in');
 5369        clearTimeout(self.__wheelHideBarTimeout);
 5370        self.__wheelHideBarTimeout = setTimeout(function() {
 5371          self.__fadeScrollbars('out');
 5372        }, 100);
 5373      }
 5374    });
 5375
 5376    if ('ontouchstart' in window) {
 5377      // Touch Events
 5378      container.addEventListener("touchstart", self.touchStart, false);
 5379      if(self.options.preventDefault) container.addEventListener("touchmove", self.touchMoveBubble, false);
 5380      document.addEventListener("touchmove", self.touchMove, false);
 5381      document.addEventListener("touchend", self.touchEnd, false);
 5382      document.addEventListener("touchcancel", self.touchEnd, false);
 5383      document.addEventListener("wheel", self.mouseWheel, false);
 5384
 5385    } else if (window.navigator.pointerEnabled) {
 5386      // Pointer Events
 5387      container.addEventListener("pointerdown", self.touchStart, false);
 5388      if(self.options.preventDefault) container.addEventListener("pointermove", self.touchMoveBubble, false);
 5389      document.addEventListener("pointermove", self.touchMove, false);
 5390      document.addEventListener("pointerup", self.touchEnd, false);
 5391      document.addEventListener("pointercancel", self.touchEnd, false);
 5392      document.addEventListener("wheel", self.mouseWheel, false);
 5393
 5394    } else if (window.navigator.msPointerEnabled) {
 5395      // IE10, WP8 (Pointer Events)
 5396      container.addEventListener("MSPointerDown", self.touchStart, false);
 5397      if(self.options.preventDefault) container.addEventListener("MSPointerMove", self.touchMoveBubble, false);
 5398      document.addEventListener("MSPointerMove", self.touchMove, false);
 5399      document.addEventListener("MSPointerUp", self.touchEnd, false);
 5400      document.addEventListener("MSPointerCancel", self.touchEnd, false);
 5401      document.addEventListener("wheel", self.mouseWheel, false);
 5402
 5403    } else {
 5404      // Mouse Events
 5405      var mousedown = false;
 5406
 5407      self.mouseDown = function(e) {
 5408        if ( ionic.tap.ignoreScrollStart(e) || e.target.tagName === 'SELECT' ) {
 5409          return;
 5410        }
 5411        self.doTouchStart(getEventTouches(e), e.timeStamp);
 5412
 5413        if ( !ionic.tap.isTextInput(e.target) ) {
 5414          e.preventDefault();
 5415        }
 5416        mousedown = true;
 5417      };
 5418
 5419      self.mouseMove = function(e) {
 5420        if (self.options.freeze || !mousedown || (!mousedown && e.defaultPrevented)) {
 5421          return;
 5422        }
 5423
 5424        self.doTouchMove(getEventTouches(e), e.timeStamp);
 5425
 5426        mousedown = true;
 5427      };
 5428
 5429      self.mouseMoveBubble = function(e) {
 5430        if (mousedown && self.options.preventDefault) {
 5431          e.preventDefault();
 5432        }
 5433      };
 5434
 5435      self.mouseUp = function(e) {
 5436        if (!mousedown) {
 5437          return;
 5438        }
 5439
 5440        self.doTouchEnd(e, e.timeStamp);
 5441
 5442        mousedown = false;
 5443      };
 5444
 5445      container.addEventListener("mousedown", self.mouseDown, false);
 5446      if(self.options.preventDefault) container.addEventListener("mousemove", self.mouseMoveBubble, false);
 5447      document.addEventListener("mousemove", self.mouseMove, false);
 5448      document.addEventListener("mouseup", self.mouseUp, false);
 5449      document.addEventListener('mousewheel', self.mouseWheel, false);
 5450      document.addEventListener('wheel', self.mouseWheel, false);
 5451    }
 5452  },
 5453
 5454  __cleanup: function() {
 5455    var self = this;
 5456    var container = self.__container;
 5457
 5458    container.removeEventListener('touchstart', self.touchStart);
 5459    container.removeEventListener('touchmove', self.touchMoveBubble);
 5460    document.removeEventListener('touchmove', self.touchMove);
 5461    document.removeEventListener('touchend', self.touchEnd);
 5462    document.removeEventListener('touchcancel', self.touchEnd);
 5463
 5464    container.removeEventListener("pointerdown", self.touchStart);
 5465    container.removeEventListener("pointermove", self.touchMoveBubble);
 5466    document.removeEventListener("pointermove", self.touchMove);
 5467    document.removeEventListener("pointerup", self.touchEnd);
 5468    document.removeEventListener("pointercancel", self.touchEnd);
 5469
 5470    container.removeEventListener("MSPointerDown", self.touchStart);
 5471    container.removeEventListener("MSPointerMove", self.touchMoveBubble);
 5472    document.removeEventListener("MSPointerMove", self.touchMove);
 5473    document.removeEventListener("MSPointerUp", self.touchEnd);
 5474    document.removeEventListener("MSPointerCancel", self.touchEnd);
 5475
 5476    container.removeEventListener("mousedown", self.mouseDown);
 5477    container.removeEventListener("mousemove", self.mouseMoveBubble);
 5478    document.removeEventListener("mousemove", self.mouseMove);
 5479    document.removeEventListener("mouseup", self.mouseUp);
 5480    document.removeEventListener('mousewheel', self.mouseWheel);
 5481    document.removeEventListener('wheel', self.mouseWheel);
 5482
 5483    container.removeEventListener('scrollChildIntoView', self.scrollChildIntoView);
 5484    document.removeEventListener('resetScrollView', self.resetScrollView);
 5485
 5486    ionic.tap.removeClonedInputs(container, self);
 5487
 5488    delete self.__container;
 5489    delete self.__content;
 5490    delete self.__indicatorX;
 5491    delete self.__indicatorY;
 5492    delete self.options.el;
 5493
 5494    self.__callback = self.scrollChildIntoView = self.resetScrollView = NOOP;
 5495
 5496    self.mouseMove = self.mouseDown = self.mouseUp = self.mouseWheel =
 5497      self.touchStart = self.touchMove = self.touchEnd = self.touchCancel = NOOP;
 5498
 5499    self.resize = self.scrollTo = self.zoomTo =
 5500      self.__scrollingComplete = NOOP;
 5501    container = null;
 5502  },
 5503
 5504  /** Create a scroll bar div with the given direction **/
 5505  __createScrollbar: function(direction) {
 5506    var bar = document.createElement('div'),
 5507      indicator = document.createElement('div');
 5508
 5509    indicator.className = 'scroll-bar-indicator scroll-bar-fade-out';
 5510
 5511    if (direction == 'h') {
 5512      bar.className = 'scroll-bar scroll-bar-h';
 5513    } else {
 5514      bar.className = 'scroll-bar scroll-bar-v';
 5515    }
 5516
 5517    bar.appendChild(indicator);
 5518    return bar;
 5519  },
 5520
 5521  __createScrollbars: function() {
 5522    var self = this;
 5523    var indicatorX, indicatorY;
 5524
 5525    if (self.options.scrollingX) {
 5526      indicatorX = {
 5527        el: self.__createScrollbar('h'),
 5528        sizeRatio: 1
 5529      };
 5530      indicatorX.indicator = indicatorX.el.children[0];
 5531
 5532      if (self.options.scrollbarX) {
 5533        self.__container.appendChild(indicatorX.el);
 5534      }
 5535      self.__indicatorX = indicatorX;
 5536    }
 5537
 5538    if (self.options.scrollingY) {
 5539      indicatorY = {
 5540        el: self.__createScrollbar('v'),
 5541        sizeRatio: 1
 5542      };
 5543      indicatorY.indicator = indicatorY.el.children[0];
 5544
 5545      if (self.options.scrollbarY) {
 5546        self.__container.appendChild(indicatorY.el);
 5547      }
 5548      self.__indicatorY = indicatorY;
 5549    }
 5550  },
 5551
 5552  __resizeScrollbars: function() {
 5553    var self = this;
 5554
 5555    // Update horiz bar
 5556    if (self.__indicatorX) {
 5557      var width = Math.max(Math.round(self.__clientWidth * self.__clientWidth / (self.__contentWidth)), 20);
 5558      if (width > self.__contentWidth) {
 5559        width = 0;
 5560      }
 5561      if (width !== self.__indicatorX.size) {
 5562        ionic.requestAnimationFrame(function(){
 5563          self.__indicatorX.indicator.style.width = width + 'px';
 5564        });
 5565      }
 5566      self.__indicatorX.size = width;
 5567      self.__indicatorX.minScale = self.options.minScrollbarSizeX / width;
 5568      self.__indicatorX.maxPos = self.__clientWidth - width;
 5569      self.__indicatorX.sizeRatio = self.__maxScrollLeft ? self.__indicatorX.maxPos / self.__maxScrollLeft : 1;
 5570    }
 5571
 5572    // Update vert bar
 5573    if (self.__indicatorY) {
 5574      var height = Math.max(Math.round(self.__clientHeight * self.__clientHeight / (self.__contentHeight)), 20);
 5575      if (height > self.__contentHeight) {
 5576        height = 0;
 5577      }
 5578      if (height !== self.__indicatorY.size) {
 5579        ionic.requestAnimationFrame(function(){
 5580          self.__indicatorY && (self.__indicatorY.indicator.style.height = height + 'px');
 5581        });
 5582      }
 5583      self.__indicatorY.size = height;
 5584      self.__indicatorY.minScale = self.options.minScrollbarSizeY / height;
 5585      self.__indicatorY.maxPos = self.__clientHeight - height;
 5586      self.__indicatorY.sizeRatio = self.__maxScrollTop ? self.__indicatorY.maxPos / self.__maxScrollTop : 1;
 5587    }
 5588  },
 5589
 5590  /**
 5591   * Move and scale the scrollbars as the page scrolls.
 5592   */
 5593  __repositionScrollbars: function() {
 5594    var self = this,
 5595        heightScale, widthScale,
 5596        widthDiff, heightDiff,
 5597        x, y,
 5598        xstop = 0, ystop = 0;
 5599
 5600    if (self.__indicatorX) {
 5601      // Handle the X scrollbar
 5602
 5603      // Don't go all the way to the right if we have a vertical scrollbar as well
 5604      if (self.__indicatorY) xstop = 10;
 5605
 5606      x = Math.round(self.__indicatorX.sizeRatio * self.__scrollLeft) || 0;
 5607
 5608      // The the difference between the last content X position, and our overscrolled one
 5609      widthDiff = self.__scrollLeft - (self.__maxScrollLeft - xstop);
 5610
 5611      if (self.__scrollLeft < 0) {
 5612
 5613        widthScale = Math.max(self.__indicatorX.minScale,
 5614            (self.__indicatorX.size - Math.abs(self.__scrollLeft)) / self.__indicatorX.size);
 5615
 5616        // Stay at left
 5617        x = 0;
 5618
 5619        // Make sure scale is transformed from the left/center origin point
 5620        self.__indicatorX.indicator.style[self.__transformOriginProperty] = 'left center';
 5621      } else if (widthDiff > 0) {
 5622
 5623        widthScale = Math.max(self.__indicatorX.minScale,
 5624            (self.__indicatorX.size - widthDiff) / self.__indicatorX.size);
 5625
 5626        // Stay at the furthest x for the scrollable viewport
 5627        x = self.__indicatorX.maxPos - xstop;
 5628
 5629        // Make sure scale is transformed from the right/center origin point
 5630        self.__indicatorX.indicator.style[self.__transformOriginProperty] = 'right center';
 5631
 5632      } else {
 5633
 5634        // Normal motion
 5635        x = Math.min(self.__maxScrollLeft, Math.max(0, x));
 5636        widthScale = 1;
 5637
 5638      }
 5639
 5640      var translate3dX = 'translate3d(' + x + 'px, 0, 0) scaleX(' + widthScale + ')';
 5641      if (self.__indicatorX.transformProp !== translate3dX) {
 5642        self.__indicatorX.indicator.style[self.__transformProperty] = translate3dX;
 5643        self.__indicatorX.transformProp = translate3dX;
 5644      }
 5645    }
 5646
 5647    if (self.__indicatorY) {
 5648
 5649      y = Math.round(self.__indicatorY.sizeRatio * self.__scrollTop) || 0;
 5650
 5651      // Don't go all the way to the right if we have a vertical scrollbar as well
 5652      if (self.__indicatorX) ystop = 10;
 5653
 5654      heightDiff = self.__scrollTop - (self.__maxScrollTop - ystop);
 5655
 5656      if (self.__scrollTop < 0) {
 5657
 5658        heightScale = Math.max(self.__indicatorY.minScale, (self.__indicatorY.size - Math.abs(self.__scrollTop)) / self.__indicatorY.size);
 5659
 5660        // Stay at top
 5661        y = 0;
 5662
 5663        // Make sure scale is transformed from the center/top origin point
 5664        if (self.__indicatorY.originProp !== 'center top') {
 5665          self.__indicatorY.indicator.style[self.__transformOriginProperty] = 'center top';
 5666          self.__indicatorY.originProp = 'center top';
 5667        }
 5668
 5669      } else if (heightDiff > 0) {
 5670
 5671        heightScale = Math.max(self.__indicatorY.minScale, (self.__indicatorY.size - heightDiff) / self.__indicatorY.size);
 5672
 5673        // Stay at bottom of scrollable viewport
 5674        y = self.__indicatorY.maxPos - ystop;
 5675
 5676        // Make sure scale is transformed from the center/bottom origin point
 5677        if (self.__indicatorY.originProp !== 'center bottom') {
 5678          self.__indicatorY.indicator.style[self.__transformOriginProperty] = 'center bottom';
 5679          self.__indicatorY.originProp = 'center bottom';
 5680        }
 5681
 5682      } else {
 5683
 5684        // Normal motion
 5685        y = Math.min(self.__maxScrollTop, Math.max(0, y));
 5686        heightScale = 1;
 5687
 5688      }
 5689
 5690      var translate3dY = 'translate3d(0,' + y + 'px, 0) scaleY(' + heightScale + ')';
 5691      if (self.__indicatorY.transformProp !== translate3dY) {
 5692        self.__indicatorY.indicator.style[self.__transformProperty] = translate3dY;
 5693        self.__indicatorY.transformProp = translate3dY;
 5694      }
 5695    }
 5696  },
 5697
 5698  __fadeScrollbars: function(direction, delay) {
 5699    var self = this;
 5700
 5701    if (!self.options.scrollbarsFade) {
 5702      return;
 5703    }
 5704
 5705    var className = 'scroll-bar-fade-out';
 5706
 5707    if (self.options.scrollbarsFade === true) {
 5708      clearTimeout(self.__scrollbarFadeTimeout);
 5709
 5710      if (direction == 'in') {
 5711        if (self.__indicatorX) { self.__indicatorX.indicator.classList.remove(className); }
 5712        if (self.__indicatorY) { self.__indicatorY.indicator.classList.remove(className); }
 5713      } else {
 5714        self.__scrollbarFadeTimeout = setTimeout(function() {
 5715          if (self.__indicatorX) { self.__indicatorX.indicator.classList.add(className); }
 5716          if (self.__indicatorY) { self.__indicatorY.indicator.classList.add(className); }
 5717        }, delay || self.options.scrollbarFadeDelay);
 5718      }
 5719    }
 5720  },
 5721
 5722  __scrollingComplete: function() {
 5723    this.options.scrollingComplete();
 5724    ionic.tap.removeClonedInputs(this.__container, this);
 5725    this.__fadeScrollbars('out');
 5726  },
 5727
 5728  resize: function(continueScrolling) {
 5729    var self = this;
 5730    if (!self.__container || !self.options) return;
 5731
 5732    // Update Scroller dimensions for changed content
 5733    // Add padding to bottom of content
 5734    self.setDimensions(
 5735      self.__container.clientWidth,
 5736      self.__container.clientHeight,
 5737      self.options.getContentWidth(),
 5738      self.options.getContentHeight(),
 5739      continueScrolling
 5740    );
 5741  },
 5742  /*
 5743  ---------------------------------------------------------------------------
 5744    PUBLIC API
 5745  ---------------------------------------------------------------------------
 5746  */
 5747
 5748  getRenderFn: function() {
 5749    var self = this;
 5750
 5751    var content = self.__content;
 5752
 5753    var docStyle = document.documentElement.style;
 5754
 5755    var engine;
 5756    if ('MozAppearance' in docStyle) {
 5757      engine = 'gecko';
 5758    } else if ('WebkitAppearance' in docStyle) {
 5759      engine = 'webkit';
 5760    } else if (typeof navigator.cpuClass === 'string') {
 5761      engine = 'trident';
 5762    }
 5763
 5764    var vendorPrefix = {
 5765      trident: 'ms',
 5766      gecko: 'Moz',
 5767      webkit: 'Webkit',
 5768      presto: 'O'
 5769    }[engine];
 5770
 5771    var helperElem = document.createElement("div");
 5772    var undef;
 5773
 5774    var perspectiveProperty = vendorPrefix + "Perspective";
 5775    var transformProperty = vendorPrefix + "Transform";
 5776    var transformOriginProperty = vendorPrefix + 'TransformOrigin';
 5777
 5778    self.__perspectiveProperty = transformProperty;
 5779    self.__transformProperty = transformProperty;
 5780    self.__transformOriginProperty = transformOriginProperty;
 5781
 5782    if (helperElem.style[perspectiveProperty] !== undef) {
 5783
 5784      return function(left, top, zoom, wasResize) {
 5785        var translate3d = 'translate3d(' + (-left) + 'px,' + (-top) + 'px,0) scale(' + zoom + ')';
 5786        if (translate3d !== self.contentTransform) {
 5787          content.style[transformProperty] = translate3d;
 5788          self.contentTransform = translate3d;
 5789        }
 5790        self.__repositionScrollbars();
 5791        if (!wasResize) {
 5792          self.triggerScrollEvent();
 5793        }
 5794      };
 5795
 5796    } else if (helperElem.style[transformProperty] !== undef) {
 5797
 5798      return function(left, top, zoom, wasResize) {
 5799        content.style[transformProperty] = 'translate(' + (-left) + 'px,' + (-top) + 'px) scale(' + zoom + ')';
 5800        self.__repositionScrollbars();
 5801        if (!wasResize) {
 5802          self.triggerScrollEvent();
 5803        }
 5804      };
 5805
 5806    } else {
 5807
 5808      return function(left, top, zoom, wasResize) {
 5809        content.style.marginLeft = left ? (-left / zoom) + 'px' : '';
 5810        content.style.marginTop = top ? (-top / zoom) + 'px' : '';
 5811        content.style.zoom = zoom || '';
 5812        self.__repositionScrollbars();
 5813        if (!wasResize) {
 5814          self.triggerScrollEvent();
 5815        }
 5816      };
 5817
 5818    }
 5819  },
 5820
 5821
 5822  /**
 5823   * Configures the dimensions of the client (outer) and content (inner) elements.
 5824   * Requires the available space for the outer element and the outer size of the inner element.
 5825   * All values which are falsy (null or zero etc.) are ignored and the old value is kept.
 5826   *
 5827   * @param clientWidth {Integer} Inner width of outer element
 5828   * @param clientHeight {Integer} Inner height of outer element
 5829   * @param contentWidth {Integer} Outer width of inner element
 5830   * @param contentHeight {Integer} Outer height of inner element
 5831   */
 5832  setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight, continueScrolling) {
 5833    var self = this;
 5834
 5835    if (!clientWidth && !clientHeight && !contentWidth && !contentHeight) {
 5836      // this scrollview isn't rendered, don't bother
 5837      return;
 5838    }
 5839
 5840    // Only update values which are defined
 5841    if (clientWidth === +clientWidth) {
 5842      self.__clientWidth = clientWidth;
 5843    }
 5844
 5845    if (clientHeight === +clientHeight) {
 5846      self.__clientHeight = clientHeight;
 5847    }
 5848
 5849    if (contentWidth === +contentWidth) {
 5850      self.__contentWidth = contentWidth;
 5851    }
 5852
 5853    if (contentHeight === +contentHeight) {
 5854      self.__contentHeight = contentHeight;
 5855    }
 5856
 5857    // Refresh maximums
 5858    self.__computeScrollMax();
 5859    self.__resizeScrollbars();
 5860
 5861    // Refresh scroll position
 5862    if (!continueScrolling) {
 5863      self.scrollTo(self.__scrollLeft, self.__scrollTop, true, null, true);
 5864    }
 5865
 5866  },
 5867
 5868
 5869  /**
 5870   * Sets the client coordinates in relation to the document.
 5871   *
 5872   * @param left {Integer} Left position of outer element
 5873   * @param top {Integer} Top position of outer element
 5874   */
 5875  setPosition: function(left, top) {
 5876    this.__clientLeft = left || 0;
 5877    this.__clientTop = top || 0;
 5878  },
 5879
 5880
 5881  /**
 5882   * Configures the snapping (when snapping is active)
 5883   *
 5884   * @param width {Integer} Snapping width
 5885   * @param height {Integer} Snapping height
 5886   */
 5887  setSnapSize: function(width, height) {
 5888    this.__snapWidth = width;
 5889    this.__snapHeight = height;
 5890  },
 5891
 5892
 5893  /**
 5894   * Activates pull-to-refresh. A special zone on the top of the list to start a list refresh whenever
 5895   * the user event is released during visibility of this zone. This was introduced by some apps on iOS like
 5896   * the official Twitter client.
 5897   *
 5898   * @param height {Integer} Height of pull-to-refresh zone on top of rendered list
 5899   * @param activateCallback {Function} Callback to execute on activation. This is for signalling the user about a refresh is about to happen when he release.
 5900   * @param deactivateCallback {Function} Callback to execute on deactivation. This is for signalling the user about the refresh being cancelled.
 5901   * @param startCallback {Function} Callback to execute to start the real async refresh action. Call {@link #finishPullToRefresh} after finish of refresh.
 5902   * @param showCallback {Function} Callback to execute when the refresher should be shown. This is for showing the refresher during a negative scrollTop.
 5903   * @param hideCallback {Function} Callback to execute when the refresher should be hidden. This is for hiding the refresher when it's behind the nav bar.
 5904   * @param tailCallback {Function} Callback to execute just before the refresher returns to it's original state. This is for zooming out the refresher.
 5905   * @param pullProgressCallback Callback to state the progress while pulling to refresh
 5906   */
 5907  activatePullToRefresh: function(height, refresherMethods) {
 5908    var self = this;
 5909
 5910    self.__refreshHeight = height;
 5911    self.__refreshActivate = function() { ionic.requestAnimationFrame(refresherMethods.activate); };
 5912    self.__refreshDeactivate = function() { ionic.requestAnimationFrame(refresherMethods.deactivate); };
 5913    self.__refreshStart = function() { ionic.requestAnimationFrame(refresherMethods.start); };
 5914    self.__refreshShow = function() { ionic.requestAnimationFrame(refresherMethods.show); };
 5915    self.__refreshHide = function() { ionic.requestAnimationFrame(refresherMethods.hide); };
 5916    self.__refreshTail = function() { ionic.requestAnimationFrame(refresherMethods.tail); };
 5917    self.__refreshTailTime = 100;
 5918    self.__minSpinTime = 600;
 5919  },
 5920
 5921
 5922  /**
 5923   * Starts pull-to-refresh manually.
 5924   */
 5925  triggerPullToRefresh: function() {
 5926    // Use publish instead of scrollTo to allow scrolling to out of boundary position
 5927    // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled
 5928    this.__publish(this.__scrollLeft, -this.__refreshHeight, this.__zoomLevel, true);
 5929
 5930    var d = new Date();
 5931    this.refreshStartTime = d.getTime();
 5932
 5933    if (this.__refreshStart) {
 5934      this.__refreshStart();
 5935    }
 5936  },
 5937
 5938
 5939  /**
 5940   * Signalizes that pull-to-refresh is finished.
 5941   */
 5942  finishPullToRefresh: function() {
 5943    var self = this;
 5944    // delay to make sure the spinner has a chance to spin for a split second before it's dismissed
 5945    var d = new Date();
 5946    var delay = 0;
 5947    if (self.refreshStartTime + self.__minSpinTime > d.getTime()) {
 5948      delay = self.refreshStartTime + self.__minSpinTime - d.getTime();
 5949    }
 5950    setTimeout(function() {
 5951      if (self.__refreshTail) {
 5952        self.__refreshTail();
 5953      }
 5954      setTimeout(function() {
 5955        self.__refreshActive = false;
 5956        if (self.__refreshDeactivate) {
 5957          self.__refreshDeactivate();
 5958        }
 5959        if (self.__refreshHide) {
 5960          self.__refreshHide();
 5961        }
 5962
 5963        self.scrollTo(self.__scrollLeft, self.__scrollTop, true);
 5964      }, self.__refreshTailTime);
 5965    }, delay);
 5966  },
 5967
 5968
 5969  /**
 5970   * Returns the scroll position and zooming values
 5971   *
 5972   * @return {Map} `left` and `top` scroll position and `zoom` level
 5973   */
 5974  getValues: function() {
 5975    return {
 5976      left: this.__scrollLeft,
 5977      top: this.__scrollTop,
 5978      zoom: this.__zoomLevel
 5979    };
 5980  },
 5981
 5982
 5983  /**
 5984   * Returns the maximum scroll values
 5985   *
 5986   * @return {Map} `left` and `top` maximum scroll values
 5987   */
 5988  getScrollMax: function() {
 5989    return {
 5990      left: this.__maxScrollLeft,
 5991      top: this.__maxScrollTop
 5992    };
 5993  },
 5994
 5995
 5996  /**
 5997   * Zooms to the given level. Supports optional animation. Zooms
 5998   * the center when no coordinates are given.
 5999   *
 6000   * @param level {Number} Level to zoom to
 6001   * @param animate {Boolean} Whether to use animation
 6002   * @param originLeft {Number} Zoom in at given left coordinate
 6003   * @param originTop {Number} Zoom in at given top coordinate
 6004   */
 6005  zoomTo: function(level, animate, originLeft, originTop) {
 6006    var self = this;
 6007
 6008    if (!self.options.zooming) {
 6009      throw new Error("Zooming is not enabled!");
 6010    }
 6011
 6012    // Stop deceleration
 6013    if (self.__isDecelerating) {
 6014      zyngaCore.effect.Animate.stop(self.__isDecelerating);
 6015      self.__isDecelerating = false;
 6016    }
 6017
 6018    var oldLevel = self.__zoomLevel;
 6019
 6020    // Normalize input origin to center of viewport if not defined
 6021    if (originLeft == null) {
 6022      originLeft = self.__clientWidth / 2;
 6023    }
 6024
 6025    if (originTop == null) {
 6026      originTop = self.__clientHeight / 2;
 6027    }
 6028
 6029    // Limit level according to configuration
 6030    level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);
 6031
 6032    // Recompute maximum values while temporary tweaking maximum scroll ranges
 6033    self.__computeScrollMax(level);
 6034
 6035    // Recompute left and top coordinates based on new zoom level
 6036    var left = ((originLeft + self.__scrollLeft) * level / oldLevel) - originLeft;
 6037    var top = ((originTop + self.__scrollTop) * level / oldLevel) - originTop;
 6038
 6039    // Limit x-axis
 6040    if (left > self.__maxScrollLeft) {
 6041      left = self.__maxScrollLeft;
 6042    } else if (left < 0) {
 6043      left = 0;
 6044    }
 6045
 6046    // Limit y-axis
 6047    if (top > self.__maxScrollTop) {
 6048      top = self.__maxScrollTop;
 6049    } else if (top < 0) {
 6050      top = 0;
 6051    }
 6052
 6053    // Push values out
 6054    self.__publish(left, top, level, animate);
 6055
 6056  },
 6057
 6058
 6059  /**
 6060   * Zooms the content by the given factor.
 6061   *
 6062   * @param factor {Number} Zoom by given factor
 6063   * @param animate {Boolean} Whether to use animation
 6064   * @param originLeft {Number} Zoom in at given left coordinate
 6065   * @param originTop {Number} Zoom in at given top coordinate
 6066   */
 6067  zoomBy: function(factor, animate, originLeft, originTop) {
 6068    this.zoomTo(this.__zoomLevel * factor, animate, originLeft, originTop);
 6069  },
 6070
 6071
 6072  /**
 6073   * Scrolls to the given position. Respect limitations and snapping automatically.
 6074   *
 6075   * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
 6076   * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
 6077   * @param animate {Boolean} Whether the scrolling should happen using an animation
 6078   * @param zoom {Number} Zoom level to go to
 6079   */
 6080  scrollTo: function(left, top, animate, zoom, wasResize) {
 6081    var self = this;
 6082
 6083    // Stop deceleration
 6084    if (self.__isDecelerating) {
 6085      zyngaCore.effect.Animate.stop(self.__isDecelerating);
 6086      self.__isDecelerating = false;
 6087    }
 6088
 6089    // Correct coordinates based on new zoom level
 6090    if (zoom != null && zoom !== self.__zoomLevel) {
 6091
 6092      if (!self.options.zooming) {
 6093        throw new Error("Zooming is not enabled!");
 6094      }
 6095
 6096      left *= zoom;
 6097      top *= zoom;
 6098
 6099      // Recompute maximum values while temporary tweaking maximum scroll ranges
 6100      self.__computeScrollMax(zoom);
 6101
 6102    } else {
 6103
 6104      // Keep zoom when not defined
 6105      zoom = self.__zoomLevel;
 6106
 6107    }
 6108
 6109    if (!self.options.scrollingX) {
 6110
 6111      left = self.__scrollLeft;
 6112
 6113    } else {
 6114
 6115      if (self.options.paging) {
 6116        left = Math.round(left / self.__clientWidth) * self.__clientWidth;
 6117      } else if (self.options.snapping) {
 6118        left = Math.round(left / self.__snapWidth) * self.__snapWidth;
 6119      }
 6120
 6121    }
 6122
 6123    if (!self.options.scrollingY) {
 6124
 6125      top = self.__scrollTop;
 6126
 6127    } else {
 6128
 6129      if (self.options.paging) {
 6130        top = Math.round(top / self.__clientHeight) * self.__clientHeight;
 6131      } else if (self.options.snapping) {
 6132        top = Math.round(top / self.__snapHeight) * self.__snapHeight;
 6133      }
 6134
 6135    }
 6136
 6137    // Limit for allowed ranges
 6138    left = Math.max(Math.min(self.__maxScrollLeft, left), 0);
 6139    top = Math.max(Math.min(self.__maxScrollTop, top), 0);
 6140
 6141    // Don't animate when no change detected, still call publish to make sure
 6142    // that rendered position is really in-sync with internal data
 6143    if (left === self.__scrollLeft && top === self.__scrollTop) {
 6144      animate = false;
 6145    }
 6146
 6147    // Publish new values
 6148    self.__publish(left, top, zoom, animate, wasResize);
 6149
 6150  },
 6151
 6152
 6153  /**
 6154   * Scroll by the given offset
 6155   *
 6156   * @param left {Number} Scroll x-axis by given offset
 6157   * @param top {Number} Scroll y-axis by given offset
 6158   * @param animate {Boolean} Whether to animate the given change
 6159   */
 6160  scrollBy: function(left, top, animate) {
 6161    var self = this;
 6162
 6163    var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft;
 6164    var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop;
 6165
 6166    self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate);
 6167  },
 6168
 6169
 6170
 6171  /*
 6172  ---------------------------------------------------------------------------
 6173    EVENT CALLBACKS
 6174  ---------------------------------------------------------------------------
 6175  */
 6176
 6177  /**
 6178   * Mouse wheel handler for zooming support
 6179   */
 6180  doMouseZoom: function(wheelDelta, timeStamp, pageX, pageY) {
 6181    var change = wheelDelta > 0 ? 0.97 : 1.03;
 6182    return this.zoomTo(this.__zoomLevel * change, false, pageX - this.__clientLeft, pageY - this.__clientTop);
 6183  },
 6184
 6185  /**
 6186   * Touch start handler for scrolling support
 6187   */
 6188  doTouchStart: function(touches, timeStamp) {
 6189    var self = this;
 6190
 6191    // remember if the deceleration was just stopped
 6192    self.__decStopped = !!(self.__isDecelerating || self.__isAnimating);
 6193
 6194    self.hintResize();
 6195
 6196    if (timeStamp instanceof Date) {
 6197      timeStamp = timeStamp.valueOf();
 6198    }
 6199    if (typeof timeStamp !== "number") {
 6200      timeStamp = Date.now();
 6201    }
 6202
 6203    // Reset interruptedAnimation flag
 6204    self.__interruptedAnimation = true;
 6205
 6206    // Stop deceleration
 6207    if (self.__isDecelerating) {
 6208      zyngaCore.effect.Animate.stop(self.__isDecelerating);
 6209      self.__isDecelerating = false;
 6210      self.__interruptedAnimation = true;
 6211    }
 6212
 6213    // Stop animation
 6214    if (self.__isAnimating) {
 6215      zyngaCore.effect.Animate.stop(self.__isAnimating);
 6216      self.__isAnimating = false;
 6217      self.__interruptedAnimation = true;
 6218    }
 6219
 6220    // Use center point when dealing with two fingers
 6221    var currentTouchLeft, currentTouchTop;
 6222    var isSingleTouch = touches.length === 1;
 6223    if (isSingleTouch) {
 6224      currentTouchLeft = touches[0].pageX;
 6225      currentTouchTop = touches[0].pageY;
 6226    } else {
 6227      currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;
 6228      currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;
 6229    }
 6230
 6231    // Store initial positions
 6232    self.__initialTouchLeft = currentTouchLeft;
 6233    self.__initialTouchTop = currentTouchTop;
 6234
 6235    // Store initial touchList for scale calculation
 6236    self.__initialTouches = touches;
 6237
 6238    // Store current zoom level
 6239    self.__zoomLevelStart = self.__zoomLevel;
 6240
 6241    // Store initial touch positions
 6242    self.__lastTouchLeft = currentTouchLeft;
 6243    self.__lastTouchTop = currentTouchTop;
 6244
 6245    // Store initial move time stamp
 6246    self.__lastTouchMove = timeStamp;
 6247
 6248    // Reset initial scale
 6249    self.__lastScale = 1;
 6250
 6251    // Reset locking flags
 6252    self.__enableScrollX = !isSingleTouch && self.options.scrollingX;
 6253    self.__enableScrollY = !isSingleTouch && self.options.scrollingY;
 6254
 6255    // Reset tracking flag
 6256    self.__isTracking = true;
 6257
 6258    // Reset deceleration complete flag
 6259    self.__didDecelerationComplete = false;
 6260
 6261    // Dragging starts directly with two fingers, otherwise lazy with an offset
 6262    self.__isDragging = !isSingleTouch;
 6263
 6264    // Some features are disabled in multi touch scenarios
 6265    self.__isSingleTouch = isSingleTouch;
 6266
 6267    // Clearing data structure
 6268    self.__positions = [];
 6269
 6270  },
 6271
 6272
 6273  /**
 6274   * Touch move handler for scrolling support
 6275   */
 6276  doTouchMove: function(touches, timeStamp, scale) {
 6277    if (timeStamp instanceof Date) {
 6278      timeStamp = timeStamp.valueOf();
 6279    }
 6280    if (typeof timeStamp !== "number") {
 6281      timeStamp = Date.now();
 6282    }
 6283
 6284    var self = this;
 6285
 6286    // Ignore event when tracking is not enabled (event might be outside of element)
 6287    if (!self.__isTracking) {
 6288      return;
 6289    }
 6290
 6291    var currentTouchLeft, currentTouchTop;
 6292
 6293    // Compute move based around of center of fingers
 6294    if (touches.length === 2) {
 6295      currentTouchLeft = Math.abs(touches[0].pageX + touches[1].pageX) / 2;
 6296      currentTouchTop = Math.abs(touches[0].pageY + touches[1].pageY) / 2;
 6297
 6298      // Calculate scale when not present and only when touches are used
 6299      if (!scale && self.options.zooming) {
 6300        scale = self.__getScale(self.__initialTouches, touches);
 6301      }
 6302    } else {
 6303      currentTouchLeft = touches[0].pageX;
 6304      currentTouchTop = touches[0].pageY;
 6305    }
 6306
 6307    var positions = self.__positions;
 6308
 6309    // Are we already is dragging mode?
 6310    if (self.__isDragging) {
 6311        self.__decStopped = false;
 6312
 6313      // Compute move distance
 6314      var moveX = currentTouchLeft - self.__lastTouchLeft;
 6315      var moveY = currentTouchTop - self.__lastTouchTop;
 6316
 6317      // Read previous scroll position and zooming
 6318      var scrollLeft = self.__scrollLeft;
 6319      var scrollTop = self.__scrollTop;
 6320      var level = self.__zoomLevel;
 6321
 6322      // Work with scaling
 6323      if (scale != null && self.options.zooming) {
 6324
 6325        var oldLevel = level;
 6326
 6327        // Recompute level based on previous scale and new scale
 6328        level = level / self.__lastScale * scale;
 6329
 6330        // Limit level according to configuration
 6331        level = Math.max(Math.min(level, self.options.maxZoom), self.options.minZoom);
 6332
 6333        // Only do further compution when change happened
 6334        if (oldLevel !== level) {
 6335
 6336          // Compute relative event position to container
 6337          var currentTouchLeftRel = currentTouchLeft - self.__clientLeft;
 6338          var currentTouchTopRel = currentTouchTop - self.__clientTop;
 6339
 6340          // Recompute left and top coordinates based on new zoom level
 6341          scrollLeft = ((currentTouchLeftRel + scrollLeft) * level / oldLevel) - currentTouchLeftRel;
 6342          scrollTop = ((currentTouchTopRel + scrollTop) * level / oldLevel) - currentTouchTopRel;
 6343
 6344          // Recompute max scroll values
 6345          self.__computeScrollMax(level);
 6346
 6347        }
 6348      }
 6349
 6350      if (self.__enableScrollX) {
 6351
 6352        scrollLeft -= moveX * self.options.speedMultiplier;
 6353        var maxScrollLeft = self.__maxScrollLeft;
 6354
 6355        if (scrollLeft > maxScrollLeft || scrollLeft < 0) {
 6356
 6357          // Slow down on the edges
 6358          if (self.options.bouncing) {
 6359
 6360            scrollLeft += (moveX / 2 * self.options.speedMultiplier);
 6361
 6362          } else if (scrollLeft > maxScrollLeft) {
 6363
 6364            scrollLeft = maxScrollLeft;
 6365
 6366          } else {
 6367
 6368            scrollLeft = 0;
 6369
 6370          }
 6371        }
 6372      }
 6373
 6374      // Compute new vertical scroll position
 6375      if (self.__enableScrollY) {
 6376
 6377        scrollTop -= moveY * self.options.speedMultiplier;
 6378        var maxScrollTop = self.__maxScrollTop;
 6379
 6380        if (scrollTop > maxScrollTop || scrollTop < 0) {
 6381
 6382          // Slow down on the edges
 6383          if (self.options.bouncing || (self.__refreshHeight && scrollTop < 0)) {
 6384
 6385            scrollTop += (moveY / 2 * self.options.speedMultiplier);
 6386
 6387            // Support pull-to-refresh (only when only y is scrollable)
 6388            if (!self.__enableScrollX && self.__refreshHeight != null) {
 6389
 6390              // hide the refresher when it's behind the header bar in case of header transparency
 6391              if (scrollTop < 0) {
 6392                self.__refreshHidden = false;
 6393                self.__refreshShow();
 6394              } else {
 6395                self.__refreshHide();
 6396                self.__refreshHidden = true;
 6397              }
 6398
 6399              if (!self.__refreshActive && scrollTop <= -self.__refreshHeight) {
 6400
 6401                self.__refreshActive = true;
 6402                if (self.__refreshActivate) {
 6403                  self.__refreshActivate();
 6404                }
 6405
 6406              } else if (self.__refreshActive && scrollTop > -self.__refreshHeight) {
 6407
 6408                self.__refreshActive = false;
 6409                if (self.__refreshDeactivate) {
 6410                  self.__refreshDeactivate();
 6411                }
 6412
 6413              }
 6414            }
 6415
 6416          } else if (scrollTop > maxScrollTop) {
 6417
 6418            scrollTop = maxScrollTop;
 6419
 6420          } else {
 6421
 6422            scrollTop = 0;
 6423
 6424          }
 6425        } else if (self.__refreshHeight && !self.__refreshHidden) {
 6426          // if a positive scroll value and the refresher is still not hidden, hide it
 6427          self.__refreshHide();
 6428          self.__refreshHidden = true;
 6429        }
 6430      }
 6431
 6432      // Keep list from growing infinitely (holding min 10, max 20 measure points)
 6433      if (positions.length > 60) {
 6434        positions.splice(0, 30);
 6435      }
 6436
 6437      // Track scroll movement for decleration
 6438      positions.push(scrollLeft, scrollTop, timeStamp);
 6439
 6440      // Sync scroll position
 6441      self.__publish(scrollLeft, scrollTop, level);
 6442
 6443    // Otherwise figure out whether we are switching into dragging mode now.
 6444    } else {
 6445
 6446      var minimumTrackingForScroll = self.options.locking ? 3 : 0;
 6447      var minimumTrackingForDrag = 5;
 6448
 6449      var distanceX = Math.abs(currentTouchLeft - self.__initialTouchLeft);
 6450      var distanceY = Math.abs(currentTouchTop - self.__initialTouchTop);
 6451
 6452      self.__enableScrollX = self.options.scrollingX && distanceX >= minimumTrackingForScroll;
 6453      self.__enableScrollY = self.options.scrollingY && distanceY >= minimumTrackingForScroll;
 6454
 6455      positions.push(self.__scrollLeft, self.__scrollTop, timeStamp);
 6456
 6457      self.__isDragging = (self.__enableScrollX || self.__enableScrollY) && (distanceX >= minimumTrackingForDrag || distanceY >= minimumTrackingForDrag);
 6458      if (self.__isDragging) {
 6459        self.__interruptedAnimation = false;
 6460        self.__fadeScrollbars('in');
 6461      }
 6462
 6463    }
 6464
 6465    // Update last touch positions and time stamp for next event
 6466    self.__lastTouchLeft = currentTouchLeft;
 6467    self.__lastTouchTop = currentTouchTop;
 6468    self.__lastTouchMove = timeStamp;
 6469    self.__lastScale = scale;
 6470
 6471  },
 6472
 6473
 6474  /**
 6475   * Touch end handler for scrolling support
 6476   */
 6477  doTouchEnd: function(e, timeStamp) {
 6478    if (timeStamp instanceof Date) {
 6479      timeStamp = timeStamp.valueOf();
 6480    }
 6481    if (typeof timeStamp !== "number") {
 6482      timeStamp = Date.now();
 6483    }
 6484
 6485    var self = this;
 6486
 6487    // Ignore event when tracking is not enabled (no touchstart event on element)
 6488    // This is required as this listener ('touchmove') sits on the document and not on the element itself.
 6489    if (!self.__isTracking) {
 6490      return;
 6491    }
 6492
 6493    // Not touching anymore (when two finger hit the screen there are two touch end events)
 6494    self.__isTracking = false;
 6495
 6496    // Be sure to reset the dragging flag now. Here we also detect whether
 6497    // the finger has moved fast enough to switch into a deceleration animation.
 6498    if (self.__isDragging) {
 6499
 6500      // Reset dragging flag
 6501      self.__isDragging = false;
 6502
 6503      // Start deceleration
 6504      // Verify that the last move detected was in some relevant time frame
 6505      if (self.__isSingleTouch && self.options.animating && (timeStamp - self.__lastTouchMove) <= 100) {
 6506
 6507        // Then figure out what the scroll position was about 100ms ago
 6508        var positions = self.__positions;
 6509        var endPos = positions.length - 1;
 6510        var startPos = endPos;
 6511
 6512        // Move pointer to position measured 100ms ago
 6513        for (var i = endPos; i > 0 && positions[i] > (self.__lastTouchMove - 100); i -= 3) {
 6514          startPos = i;
 6515        }
 6516
 6517        // If start and stop position is identical in a 100ms timeframe,
 6518        // we cannot compute any useful deceleration.
 6519        if (startPos !== endPos) {
 6520
 6521          // Compute relative movement between these two points
 6522          var timeOffset = positions[endPos] - positions[startPos];
 6523          var movedLeft = self.__scrollLeft - positions[startPos - 2];
 6524          var movedTop = self.__scrollTop - positions[startPos - 1];
 6525
 6526          // Based on 50ms compute the movement to apply for each render step
 6527          self.__decelerationVelocityX = movedLeft / timeOffset * (1000 / 60);
 6528          self.__decelerationVelocityY = movedTop / timeOffset * (1000 / 60);
 6529
 6530          // How much velocity is required to start the deceleration
 6531          var minVelocityToStartDeceleration = self.options.paging || self.options.snapping ? self.options.decelVelocityThresholdPaging : self.options.decelVelocityThreshold;
 6532
 6533          // Verify that we have enough velocity to start deceleration
 6534          if (Math.abs(self.__decelerationVelocityX) > minVelocityToStartDeceleration || Math.abs(self.__decelerationVelocityY) > minVelocityToStartDeceleration) {
 6535
 6536            // Deactivate pull-to-refresh when decelerating
 6537            if (!self.__refreshActive) {
 6538              self.__startDeceleration(timeStamp);
 6539            }
 6540          }
 6541        } else {
 6542          self.__scrollingComplete();
 6543        }
 6544      } else if ((timeStamp - self.__lastTouchMove) > 100) {
 6545        self.__scrollingComplete();
 6546      }
 6547
 6548    } else if (self.__decStopped) {
 6549      // the deceleration was stopped
 6550      // user flicked the scroll fast, and stop dragging, then did a touchstart to stop the srolling
 6551      // tell the touchend event code to do nothing, we don't want to actually send a click
 6552      e.isTapHandled = true;
 6553      self.__decStopped = false;
 6554    }
 6555
 6556    // If this was a slower move it is per default non decelerated, but this
 6557    // still means that we want snap back to the bounds which is done here.
 6558    // This is placed outside the condition above to improve edge case stability
 6559    // e.g. touchend fired without enabled dragging. This should normally do not
 6560    // have modified the scroll positions or even showed the scrollbars though.
 6561    if (!self.__isDecelerating) {
 6562
 6563      if (self.__refreshActive && self.__refreshStart) {
 6564
 6565        // Use publish instead of scrollTo to allow scrolling to out of boundary position
 6566        // We don't need to normalize scrollLeft, zoomLevel, etc. here because we only y-scrolling when pull-to-refresh is enabled
 6567        self.__publish(self.__scrollLeft, -self.__refreshHeight, self.__zoomLevel, true);
 6568
 6569        var d = new Date();
 6570        self.refreshStartTime = d.getTime();
 6571
 6572        if (self.__refreshStart) {
 6573          self.__refreshStart();
 6574        }
 6575        // for iOS-ey style scrolling
 6576        if (!ionic.Platform.isAndroid())self.__startDeceleration();
 6577      } else {
 6578
 6579        if (self.__interruptedAnimation || self.__isDragging) {
 6580          self.__scrollingComplete();
 6581        }
 6582        self.scrollTo(self.__scrollLeft, self.__scrollTop, true, self.__zoomLevel);
 6583
 6584        // Directly signalize deactivation (nothing todo on refresh?)
 6585        if (self.__refreshActive) {
 6586
 6587          self.__refreshActive = false;
 6588          if (self.__refreshDeactivate) {
 6589            self.__refreshDeactivate();
 6590          }
 6591
 6592        }
 6593      }
 6594    }
 6595
 6596    // Fully cleanup list
 6597    self.__positions.length = 0;
 6598
 6599  },
 6600
 6601
 6602
 6603  /*
 6604  ---------------------------------------------------------------------------
 6605    PRIVATE API
 6606  ---------------------------------------------------------------------------
 6607  */
 6608
 6609  /**
 6610   * Applies the scroll position to the content element
 6611   *
 6612   * @param left {Number} Left scroll position
 6613   * @param top {Number} Top scroll position
 6614   * @param animate {Boolean} Whether animation should be used to move to the new coordinates
 6615   */
 6616  __publish: function(left, top, zoom, animate, wasResize) {
 6617
 6618    var self = this;
 6619
 6620    // Remember whether we had an animation, then we try to continue based on the current "drive" of the animation
 6621    var wasAnimating = self.__isAnimating;
 6622    if (wasAnimating) {
 6623      zyngaCore.effect.Animate.stop(wasAnimating);
 6624      self.__isAnimating = false;
 6625    }
 6626
 6627    if (animate && self.options.animating) {
 6628
 6629      // Keep scheduled positions for scrollBy/zoomBy functionality
 6630      self.__scheduledLeft = left;
 6631      self.__scheduledTop = top;
 6632      self.__scheduledZoom = zoom;
 6633
 6634      var oldLeft = self.__scrollLeft;
 6635      var oldTop = self.__scrollTop;
 6636      var oldZoom = self.__zoomLevel;
 6637
 6638      var diffLeft = left - oldLeft;
 6639      var diffTop = top - oldTop;
 6640      var diffZoom = zoom - oldZoom;
 6641
 6642      var step = function(percent, now, render) {
 6643
 6644        if (render) {
 6645
 6646          self.__scrollLeft = oldLeft + (diffLeft * percent);
 6647          self.__scrollTop = oldTop + (diffTop * percent);
 6648          self.__zoomLevel = oldZoom + (diffZoom * percent);
 6649
 6650          // Push values out
 6651          if (self.__callback) {
 6652            self.__callback(self.__scrollLeft, self.__scrollTop, self.__zoomLevel, wasResize);
 6653          }
 6654
 6655        }
 6656      };
 6657
 6658      var verify = function(id) {
 6659        return self.__isAnimating === id;
 6660      };
 6661
 6662      var completed = function(renderedFramesPerSecond, animationId, wasFinished) {
 6663        if (animationId === self.__isAnimating) {
 6664          self.__isAnimating = false;
 6665        }
 6666        if (self.__didDecelerationComplete || wasFinished) {
 6667          self.__scrollingComplete();
 6668        }
 6669
 6670        if (self.options.zooming) {
 6671          self.__computeScrollMax();
 6672        }
 6673      };
 6674
 6675      // When continuing based on previous animation we choose an ease-out animation instead of ease-in-out
 6676      self.__isAnimating = zyngaCore.effect.Animate.start(step, verify, completed, self.options.animationDuration, wasAnimating ? easeOutCubic : easeInOutCubic);
 6677
 6678    } else {
 6679
 6680      self.__scheduledLeft = self.__scrollLeft = left;
 6681      self.__scheduledTop = self.__scrollTop = top;
 6682      self.__scheduledZoom = self.__zoomLevel = zoom;
 6683
 6684      // Push values out
 6685      if (self.__callback) {
 6686        self.__callback(left, top, zoom, wasResize);
 6687      }
 6688
 6689      // Fix max scroll ranges
 6690      if (self.options.zooming) {
 6691        self.__computeScrollMax();
 6692      }
 6693    }
 6694  },
 6695
 6696
 6697  /**
 6698   * Recomputes scroll minimum values based on client dimensions and content dimensions.
 6699   */
 6700  __computeScrollMax: function(zoomLevel) {
 6701    var self = this;
 6702
 6703    if (zoomLevel == null) {
 6704      zoomLevel = self.__zoomLevel;
 6705    }
 6706
 6707    self.__maxScrollLeft = Math.max((self.__contentWidth * zoomLevel) - self.__clientWidth, 0);
 6708    self.__maxScrollTop = Math.max((self.__contentHeight * zoomLevel) - self.__clientHeight, 0);
 6709
 6710    if (!self.__didWaitForSize && !self.__maxScrollLeft && !self.__maxScrollTop) {
 6711      self.__didWaitForSize = true;
 6712      self.__waitForSize();
 6713    }
 6714  },
 6715
 6716
 6717  /**
 6718   * If the scroll view isn't sized correctly on start, wait until we have at least some size
 6719   */
 6720  __waitForSize: function() {
 6721    var self = this;
 6722
 6723    clearTimeout(self.__sizerTimeout);
 6724
 6725    var sizer = function() {
 6726      self.resize(true);
 6727    };
 6728
 6729    sizer();
 6730    self.__sizerTimeout = setTimeout(sizer, 500);
 6731  },
 6732
 6733  /*
 6734  ---------------------------------------------------------------------------
 6735    ANIMATION (DECELERATION) SUPPORT
 6736  ---------------------------------------------------------------------------
 6737  */
 6738
 6739  /**
 6740   * Called when a touch sequence end and the speed of the finger was high enough
 6741   * to switch into deceleration mode.
 6742   */
 6743  __startDeceleration: function() {
 6744    var self = this;
 6745
 6746    if (self.options.paging) {
 6747
 6748      var scrollLeft = Math.max(Math.min(self.__scrollLeft, self.__maxScrollLeft), 0);
 6749      var scrollTop = Math.max(Math.min(self.__scrollTop, self.__maxScrollTop), 0);
 6750      var clientWidth = self.__clientWidth;
 6751      var clientHeight = self.__clientHeight;
 6752
 6753      // We limit deceleration not to the min/max values of the allowed range, but to the size of the visible client area.
 6754      // Each page should have exactly the size of the client area.
 6755      self.__minDecelerationScrollLeft = Math.floor(scrollLeft / clientWidth) * clientWidth;
 6756      self.__minDecelerationScrollTop = Math.floor(scrollTop / clientHeight) * clientHeight;
 6757      self.__maxDecelerationScrollLeft = Math.ceil(scrollLeft / clientWidth) * clientWidth;
 6758      self.__maxDecelerationScrollTop = Math.ceil(scrollTop / clientHeight) * clientHeight;
 6759
 6760    } else {
 6761
 6762      self.__minDecelerationScrollLeft = 0;
 6763      self.__minDecelerationScrollTop = 0;
 6764      self.__maxDecelerationScrollLeft = self.__maxScrollLeft;
 6765      self.__maxDecelerationScrollTop = self.__maxScrollTop;
 6766      if (self.__refreshActive) self.__minDecelerationScrollTop = self.__refreshHeight * -1;
 6767    }
 6768
 6769    // Wrap class method
 6770    var step = function(percent, now, render) {
 6771      self.__stepThroughDeceleration(render);
 6772    };
 6773
 6774    // How much velocity is required to keep the deceleration running
 6775    self.__minVelocityToKeepDecelerating = self.options.snapping ? 4 : 0.1;
 6776
 6777    // Detect whether it's still worth to continue animating steps
 6778    // If we are already slow enough to not being user perceivable anymore, we stop the whole process here.
 6779    var verify = function() {
 6780      var shouldContinue = Math.abs(self.__decelerationVelocityX) >= self.__minVelocityToKeepDecelerating ||
 6781        Math.abs(self.__decelerationVelocityY) >= self.__minVelocityToKeepDecelerating;
 6782      if (!shouldContinue) {
 6783        self.__didDecelerationComplete = true;
 6784
 6785        //Make sure the scroll values are within the boundaries after a bounce,
 6786        //not below 0 or above maximum
 6787        if (self.options.bouncing && !self.__refreshActive) {
 6788          self.scrollTo(
 6789            Math.min( Math.max(self.__scrollLeft, 0), self.__maxScrollLeft ),
 6790            Math.min( Math.max(self.__scrollTop, 0), self.__maxScrollTop ),
 6791            self.__refreshActive
 6792          );
 6793        }
 6794      }
 6795      return shouldContinue;
 6796    };
 6797
 6798    var completed = function() {
 6799      self.__isDecelerating = false;
 6800      if (self.__didDecelerationComplete) {
 6801        self.__scrollingComplete();
 6802      }
 6803
 6804      // Animate to grid when snapping is active, otherwise just fix out-of-boundary positions
 6805      if (self.options.paging) {
 6806        self.scrollTo(self.__scrollLeft, self.__scrollTop, self.options.snapping);
 6807      }
 6808    };
 6809
 6810    // Start animation and switch on flag
 6811    self.__isDecelerating = zyngaCore.effect.Animate.start(step, verify, completed);
 6812
 6813  },
 6814
 6815
 6816  /**
 6817   * Called on every step of the animation
 6818   *
 6819   * @param inMemory {Boolean} Whether to not render the current step, but keep it in memory only. Used internally only!
 6820   */
 6821  __stepThroughDeceleration: function(render) {
 6822    var self = this;
 6823
 6824
 6825    //
 6826    // COMPUTE NEXT SCROLL POSITION
 6827    //
 6828
 6829    // Add deceleration to scroll position
 6830    var scrollLeft = self.__scrollLeft + self.__decelerationVelocityX;// * self.options.deceleration);
 6831    var scrollTop = self.__scrollTop + self.__decelerationVelocityY;// * self.options.deceleration);
 6832
 6833
 6834    //
 6835    // HARD LIMIT SCROLL POSITION FOR NON BOUNCING MODE
 6836    //
 6837
 6838    if (!self.options.bouncing) {
 6839
 6840      var scrollLeftFixed = Math.max(Math.min(self.__maxDecelerationScrollLeft, scrollLeft), self.__minDecelerationScrollLeft);
 6841      if (scrollLeftFixed !== scrollLeft) {
 6842        scrollLeft = scrollLeftFixed;
 6843        self.__decelerationVelocityX = 0;
 6844      }
 6845
 6846      var scrollTopFixed = Math.max(Math.min(self.__maxDecelerationScrollTop, scrollTop), self.__minDecelerationScrollTop);
 6847      if (scrollTopFixed !== scrollTop) {
 6848        scrollTop = scrollTopFixed;
 6849        self.__decelerationVelocityY = 0;
 6850      }
 6851
 6852    }
 6853
 6854
 6855    //
 6856    // UPDATE SCROLL POSITION
 6857    //
 6858
 6859    if (render) {
 6860
 6861      self.__publish(scrollLeft, scrollTop, self.__zoomLevel);
 6862
 6863    } else {
 6864
 6865      self.__scrollLeft = scrollLeft;
 6866      self.__scrollTop = scrollTop;
 6867
 6868    }
 6869
 6870
 6871    //
 6872    // SLOW DOWN
 6873    //
 6874
 6875    // Slow down velocity on every iteration
 6876    if (!self.options.paging) {
 6877
 6878      // This is the factor applied to every iteration of the animation
 6879      // to slow down the process. This should emulate natural behavior where
 6880      // objects slow down when the initiator of the movement is removed
 6881      var frictionFactor = self.options.deceleration;
 6882
 6883      self.__decelerationVelocityX *= frictionFactor;
 6884      self.__decelerationVelocityY *= frictionFactor;
 6885
 6886    }
 6887
 6888
 6889    //
 6890    // BOUNCING SUPPORT
 6891    //
 6892
 6893    if (self.options.bouncing) {
 6894
 6895      var scrollOutsideX = 0;
 6896      var scrollOutsideY = 0;
 6897
 6898      // This configures the amount of change applied to deceleration/acceleration when reaching boundaries
 6899      var penetrationDeceleration = self.options.penetrationDeceleration;
 6900      var penetrationAcceleration = self.options.penetrationAcceleration;
 6901
 6902      // Check limits
 6903      if (scrollLeft < self.__minDecelerationScrollLeft) {
 6904        scrollOutsideX = self.__minDecelerationScrollLeft - scrollLeft;
 6905      } else if (scrollLeft > self.__maxDecelerationScrollLeft) {
 6906        scrollOutsideX = self.__maxDecelerationScrollLeft - scrollLeft;
 6907      }
 6908
 6909      if (scrollTop < self.__minDecelerationScrollTop) {
 6910        scrollOutsideY = self.__minDecelerationScrollTop - scrollTop;
 6911      } else if (scrollTop > self.__maxDecelerationScrollTop) {
 6912        scrollOutsideY = self.__maxDecelerationScrollTop - scrollTop;
 6913      }
 6914
 6915      // Slow down until slow enough, then flip back to snap position
 6916      if (scrollOutsideX !== 0) {
 6917        var isHeadingOutwardsX = scrollOutsideX * self.__decelerationVelocityX <= self.__minDecelerationScrollLeft;
 6918        if (isHeadingOutwardsX) {
 6919          self.__decelerationVelocityX += scrollOutsideX * penetrationDeceleration;
 6920        }
 6921        var isStoppedX = Math.abs(self.__decelerationVelocityX) <= self.__minVelocityToKeepDecelerating;
 6922        //If we're not heading outwards, or if the above statement got us below minDeceleration, go back towards bounds
 6923        if (!isHeadingOutwardsX || isStoppedX) {
 6924          self.__decelerationVelocityX = scrollOutsideX * penetrationAcceleration;
 6925        }
 6926      }
 6927
 6928      if (scrollOutsideY !== 0) {
 6929        var isHeadingOutwardsY = scrollOutsideY * self.__decelerationVelocityY <= self.__minDecelerationScrollTop;
 6930        if (isHeadingOutwardsY) {
 6931          self.__decelerationVelocityY += scrollOutsideY * penetrationDeceleration;
 6932        }
 6933        var isStoppedY = Math.abs(self.__decelerationVelocityY) <= self.__minVelocityToKeepDecelerating;
 6934        //If we're not heading outwards, or if the above statement got us below minDeceleration, go back towards bounds
 6935        if (!isHeadingOutwardsY || isStoppedY) {
 6936          self.__decelerationVelocityY = scrollOutsideY * penetrationAcceleration;
 6937        }
 6938      }
 6939    }
 6940  },
 6941
 6942
 6943  /**
 6944   * calculate the distance between two touches
 6945   * @param   {Touch}     touch1
 6946   * @param   {Touch}     touch2
 6947   * @returns {Number}    distance
 6948   */
 6949  __getDistance: function getDistance(touch1, touch2) {
 6950    var x = touch2.pageX - touch1.pageX,
 6951    y = touch2.pageY - touch1.pageY;
 6952    return Math.sqrt((x * x) + (y * y));
 6953  },
 6954
 6955
 6956  /**
 6957   * calculate the scale factor between two touchLists (fingers)
 6958   * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
 6959   * @param   {Array}     start
 6960   * @param   {Array}     end
 6961   * @returns {Number}    scale
 6962   */
 6963  __getScale: function getScale(start, end) {
 6964    // need two fingers...
 6965    if (start.length >= 2 && end.length >= 2) {
 6966      return this.__getDistance(end[0], end[1]) /
 6967        this.__getDistance(start[0], start[1]);
 6968    }
 6969    return 1;
 6970  }
 6971});
 6972
 6973ionic.scroll = {
 6974  isScrolling: false,
 6975  lastTop: 0
 6976};
 6977
 6978})(ionic);
 6979
 6980(function(ionic) {
 6981  var NOOP = function() {};
 6982  var deprecated = function(name) {
 6983    void 0;
 6984  };
 6985  ionic.views.ScrollNative = ionic.views.View.inherit({
 6986
 6987    initialize: function(options) {
 6988      var self = this;
 6989      self.__container = self.el = options.el;
 6990      self.__content = options.el.firstElementChild;
 6991      // Whether scrolling is frozen or not
 6992      self.__frozen = false;
 6993      self.isNative = true;
 6994
 6995      self.__scrollTop = self.el.scrollTop;
 6996      self.__scrollLeft = self.el.scrollLeft;
 6997      self.__clientHeight = self.__content.clientHeight;
 6998      self.__clientWidth = self.__content.clientWidth;
 6999      self.__maxScrollTop = Math.max((self.__contentHeight) - self.__clientHeight, 0);
 7000      self.__maxScrollLeft = Math.max((self.__contentWidth) - self.__clientWidth, 0);
 7001
 7002      if(options.startY >= 0 || options.startX >= 0) {
 7003        ionic.requestAnimationFrame(function() {
 7004          self.el.scrollTop = options.startY || 0;
 7005          self.el.scrollLeft = options.startX || 0;
 7006
 7007          self.__scrollTop = self.el.scrollTop;
 7008          self.__scrollLeft = self.el.scrollLeft;
 7009        });
 7010      }
 7011
 7012      self.options = {
 7013
 7014        freeze: false,
 7015
 7016        getContentWidth: function() {
 7017          return Math.max(self.__content.scrollWidth, self.__content.offsetWidth);
 7018        },
 7019
 7020        getContentHeight: function() {
 7021          return Math.max(self.__content.scrollHeight, self.__content.offsetHeight + (self.__content.offsetTop * 2));
 7022        }
 7023
 7024      };
 7025
 7026      for (var key in options) {
 7027        self.options[key] = options[key];
 7028      }
 7029
 7030      /**
 7031       * Sets isScrolling to true, and automatically deactivates if not called again in 80ms.
 7032       */
 7033      self.onScroll = function() {
 7034        if (!ionic.scroll.isScrolling) {
 7035          ionic.scroll.isScrolling = true;
 7036        }
 7037
 7038        clearTimeout(self.scrollTimer);
 7039        self.scrollTimer = setTimeout(function() {
 7040          ionic.scroll.isScrolling = false;
 7041        }, 80);
 7042      };
 7043
 7044      self.freeze = function(shouldFreeze) {
 7045        self.__frozen = shouldFreeze;
 7046      };
 7047      // A more powerful freeze pop that dominates all other freeze pops
 7048      self.freezeShut = function(shouldFreezeShut) {
 7049        self.__frozenShut = shouldFreezeShut;
 7050      };
 7051
 7052      self.__initEventHandlers();
 7053    },
 7054
 7055    /**  Methods not used in native scrolling */
 7056    __callback: function() { deprecated('__callback'); },
 7057    zoomTo: function() { deprecated('zoomTo'); },
 7058    zoomBy: function() { deprecated('zoomBy'); },
 7059    activatePullToRefresh: function() { deprecated('activatePullToRefresh'); },
 7060
 7061    /**
 7062     * Returns the scroll position and zooming values
 7063     *
 7064     * @return {Map} `left` and `top` scroll position and `zoom` level
 7065     */
 7066    resize: function(continueScrolling) {
 7067      var self = this;
 7068      if (!self.__container || !self.options) return;
 7069
 7070      // Update Scroller dimensions for changed content
 7071      // Add padding to bottom of content
 7072      self.setDimensions(
 7073        self.__container.clientWidth,
 7074        self.__container.clientHeight,
 7075        self.options.getContentWidth(),
 7076        self.options.getContentHeight(),
 7077        continueScrolling
 7078      );
 7079    },
 7080
 7081    /**
 7082     * Initialize the scrollview
 7083     * In native scrolling, this only means we need to gather size information
 7084     */
 7085    run: function() {
 7086      this.resize();
 7087    },
 7088
 7089    /**
 7090     * Returns the scroll position and zooming values
 7091     *
 7092     * @return {Map} `left` and `top` scroll position and `zoom` level
 7093     */
 7094    getValues: function() {
 7095      var self = this;
 7096      self.update();
 7097      return {
 7098        left: self.__scrollLeft,
 7099        top: self.__scrollTop,
 7100        zoom: 1
 7101      };
 7102    },
 7103
 7104    /**
 7105     * Updates the __scrollLeft and __scrollTop values to el's current value
 7106     */
 7107    update: function() {
 7108      var self = this;
 7109      self.__scrollLeft = self.el.scrollLeft;
 7110      self.__scrollTop = self.el.scrollTop;
 7111    },
 7112
 7113    /**
 7114     * Configures the dimensions of the client (outer) and content (inner) elements.
 7115     * Requires the available space for the outer element and the outer size of the inner element.
 7116     * All values which are falsy (null or zero etc.) are ignored and the old value is kept.
 7117     *
 7118     * @param clientWidth {Integer} Inner width of outer element
 7119     * @param clientHeight {Integer} Inner height of outer element
 7120     * @param contentWidth {Integer} Outer width of inner element
 7121     * @param contentHeight {Integer} Outer height of inner element
 7122     */
 7123    setDimensions: function(clientWidth, clientHeight, contentWidth, contentHeight) {
 7124      var self = this;
 7125
 7126      if (!clientWidth && !clientHeight && !contentWidth && !contentHeight) {
 7127        // this scrollview isn't rendered, don't bother
 7128        return;
 7129      }
 7130
 7131      // Only update values which are defined
 7132      if (clientWidth === +clientWidth) {
 7133        self.__clientWidth = clientWidth;
 7134      }
 7135
 7136      if (clientHeight === +clientHeight) {
 7137        self.__clientHeight = clientHeight;
 7138      }
 7139
 7140      if (contentWidth === +contentWidth) {
 7141        self.__contentWidth = contentWidth;
 7142      }
 7143
 7144      if (contentHeight === +contentHeight) {
 7145        self.__contentHeight = contentHeight;
 7146      }
 7147
 7148      // Refresh maximums
 7149      self.__computeScrollMax();
 7150    },
 7151
 7152    /**
 7153     * Returns the maximum scroll values
 7154     *
 7155     * @return {Map} `left` and `top` maximum scroll values
 7156     */
 7157    getScrollMax: function() {
 7158      return {
 7159        left: this.__maxScrollLeft,
 7160        top: this.__maxScrollTop
 7161      };
 7162    },
 7163
 7164    /**
 7165     * Scrolls by the given amount in px.
 7166     *
 7167     * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
 7168     * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
 7169     * @param animate {Boolean} Whether the scrolling should happen using an animation
 7170     */
 7171
 7172    scrollBy: function(left, top, animate) {
 7173      var self = this;
 7174
 7175      // update scroll vars before refferencing them
 7176      self.update();
 7177
 7178      var startLeft = self.__isAnimating ? self.__scheduledLeft : self.__scrollLeft;
 7179      var startTop = self.__isAnimating ? self.__scheduledTop : self.__scrollTop;
 7180
 7181      self.scrollTo(startLeft + (left || 0), startTop + (top || 0), animate);
 7182    },
 7183
 7184    /**
 7185     * Scrolls to the given position in px.
 7186     *
 7187     * @param left {Number} Horizontal scroll position, keeps current if value is <code>null</code>
 7188     * @param top {Number} Vertical scroll position, keeps current if value is <code>null</code>
 7189     * @param animate {Boolean} Whether the scrolling should happen using an animation
 7190     */
 7191    scrollTo: function(left, top, animate) {
 7192      var self = this;
 7193      if (!animate) {
 7194        self.el.scrollTop = top;
 7195        self.el.scrollLeft = left;
 7196        self.resize();
 7197        return;
 7198      }
 7199
 7200      var oldOverflowX = self.el.style.overflowX;
 7201      var oldOverflowY = self.el.style.overflowY;
 7202
 7203      clearTimeout(self.__scrollToCleanupTimeout);
 7204      self.__scrollToCleanupTimeout = setTimeout(function() {
 7205        self.el.style.overflowX = oldOverflowX;
 7206        self.el.style.overflowY = oldOverflowY;
 7207      }, 500);
 7208
 7209      self.el.style.overflowY = 'hidden';
 7210      self.el.style.overflowX = 'hidden';
 7211
 7212      animateScroll(top, left);
 7213
 7214      function animateScroll(Y, X) {
 7215        // scroll animation loop w/ easing
 7216        // credit https://gist.github.com/dezinezync/5487119
 7217        var start = Date.now(),
 7218          duration = 250, //milliseconds
 7219          fromY = self.el.scrollTop,
 7220          fromX = self.el.scrollLeft;
 7221
 7222        if (fromY === Y && fromX === X) {
 7223          self.el.style.overflowX = oldOverflowX;
 7224          self.el.style.overflowY = oldOverflowY;
 7225          self.resize();
 7226          return; /* Prevent scrolling to the Y point if already there */
 7227        }
 7228
 7229        // decelerating to zero velocity
 7230        function easeOutCubic(t) {
 7231          return (--t) * t * t + 1;
 7232        }
 7233
 7234        // scroll loop
 7235        function animateScrollStep() {
 7236          var currentTime = Date.now(),
 7237            time = Math.min(1, ((currentTime - start) / duration)),
 7238          // where .5 would be 50% of time on a linear scale easedT gives a
 7239          // fraction based on the easing method
 7240            easedT = easeOutCubic(time);
 7241
 7242          if (fromY != Y) {
 7243            self.el.scrollTop = parseInt((easedT * (Y - fromY)) + fromY, 10);
 7244          }
 7245          if (fromX != X) {
 7246            self.el.scrollLeft = parseInt((easedT * (X - fromX)) + fromX, 10);
 7247          }
 7248
 7249          if (time < 1) {
 7250            ionic.requestAnimationFrame(animateScrollStep);
 7251
 7252          } else {
 7253            // done
 7254            ionic.tap.removeClonedInputs(self.__container, self);
 7255            self.el.style.overflowX = oldOverflowX;
 7256            self.el.style.overflowY = oldOverflowY;
 7257            self.resize();
 7258          }
 7259        }
 7260
 7261        // start scroll loop
 7262        ionic.requestAnimationFrame(animateScrollStep);
 7263      }
 7264    },
 7265
 7266
 7267
 7268    /*
 7269     ---------------------------------------------------------------------------
 7270     PRIVATE API
 7271     ---------------------------------------------------------------------------
 7272     */
 7273
 7274    /**
 7275     * If the scroll view isn't sized correctly on start, wait until we have at least some size
 7276     */
 7277    __waitForSize: function() {
 7278      var self = this;
 7279
 7280      clearTimeout(self.__sizerTimeout);
 7281
 7282      var sizer = function() {
 7283        self.resize(true);
 7284      };
 7285
 7286      sizer();
 7287      self.__sizerTimeout = setTimeout(sizer, 500);
 7288    },
 7289
 7290
 7291    /**
 7292     * Recomputes scroll minimum values based on client dimensions and content dimensions.
 7293     */
 7294    __computeScrollMax: function() {
 7295      var self = this;
 7296
 7297      self.__maxScrollLeft = Math.max((self.__contentWidth) - self.__clientWidth, 0);
 7298      self.__maxScrollTop = Math.max((self.__contentHeight) - self.__clientHeight, 0);
 7299
 7300      if (!self.__didWaitForSize && !self.__maxScrollLeft && !self.__maxScrollTop) {
 7301        self.__didWaitForSize = true;
 7302        self.__waitForSize();
 7303      }
 7304    },
 7305
 7306    __initEventHandlers: function() {
 7307      var self = this;
 7308
 7309      // Event Handler
 7310      var container = self.__container;
 7311      // save height when scroll view is shrunk so we don't need to reflow
 7312      var scrollViewOffsetHeight;
 7313
 7314      var lastKeyboardHeight;
 7315
 7316      /**
 7317       * Shrink the scroll view when the keyboard is up if necessary and if the
 7318       * focused input is below the bottom of the shrunk scroll view, scroll it
 7319       * into view.
 7320       */
 7321      self.scrollChildIntoView = function(e) {
 7322        var rect = container.getBoundingClientRect();
 7323        if(!self.__originalContainerHeight) {
 7324          self.__originalContainerHeight = rect.height;
 7325        }
 7326
 7327        // D
 7328        //var scrollBottomOffsetToTop = rect.bottom;
 7329        // D - A
 7330        scrollViewOffsetHeight = self.__originalContainerHeight;
 7331        //console.log('Scroll view offset height', scrollViewOffsetHeight);
 7332        //console.dir(container);
 7333        var alreadyShrunk = self.isShrunkForKeyboard;
 7334
 7335        var isModal = container.parentNode.classList.contains('modal');
 7336        var isPopover = container.parentNode.classList.contains('popover');
 7337        // 680px is when the media query for 60% modal width kicks in
 7338        var isInsetModal = isModal && window.innerWidth >= 680;
 7339
 7340       /*
 7341        *  _______
 7342        * |---A---| <- top of scroll view
 7343        * |       |
 7344        * |---B---| <- keyboard
 7345        * |   C   | <- input
 7346        * |---D---| <- initial bottom of scroll view
 7347        * |___E___| <- bottom of viewport
 7348        *
 7349        *  All commented calculations relative to the top of the viewport (ie E
 7350        *  is the viewport height, not 0)
 7351        */
 7352
 7353
 7354        var changedKeyboardHeight = lastKeyboardHeight && (lastKeyboardHeight !== e.detail.keyboardHeight);
 7355
 7356        if (!alreadyShrunk || changedKeyboardHeight) {
 7357          // shrink scrollview so we can actually scroll if the input is hidden
 7358          // if it isn't shrink so we can scroll to inputs under the keyboard
 7359          // inset modals won't shrink on Android on their own when the keyboard appears
 7360          if ( !isPopover && (ionic.Platform.isIOS() || ionic.Platform.isFullScreen || isInsetModal) ) {
 7361            // if there are things below the scroll view account for them and
 7362            // subtract them from the keyboard height when resizing
 7363            // E - D                         E                         D
 7364            //var scrollBottomOffsetToBottom = e.detail.viewportHeight - scrollBottomOffsetToTop;
 7365
 7366            // 0 or D - B if D > B           E - B                     E - D
 7367            //var keyboardOffset = e.detail.keyboardHeight - scrollBottomOffsetToBottom;
 7368
 7369            ionic.requestAnimationFrame(function(){
 7370              // D - A or B - A if D > B       D - A             max(0, D - B)
 7371              scrollViewOffsetHeight = Math.max(0, Math.min(self.__originalContainerHeight, self.__originalContainerHeight - (e.detail.keyboardHeight - 43)));//keyboardOffset >= 0 ? scrollViewOffsetHeight - keyboardOffset : scrollViewOffsetHeight + keyboardOffset;
 7372
 7373              //console.log('Old container height', self.__originalContainerHeight, 'New container height', scrollViewOffsetHeight, 'Keyboard height', e.detail.keyboardHeight);
 7374
 7375              container.style.height = scrollViewOffsetHeight + "px";
 7376
 7377              /*
 7378              if (ionic.Platform.isIOS()) {
 7379                // Force redraw to avoid disappearing content
 7380                var disp = container.style.display;
 7381                container.style.display = 'none';
 7382                var trick = container.offsetHeight;
 7383                container.style.display = disp;
 7384              }
 7385              */
 7386              container.classList.add('keyboard-up');
 7387              //update scroll view
 7388              self.resize();
 7389            });
 7390          }
 7391
 7392          self.isShrunkForKeyboard = true;
 7393        }
 7394
 7395        lastKeyboardHeight = e.detail.keyboardHeight;
 7396
 7397        /*
 7398         *  _______
 7399         * |---A---| <- top of scroll view
 7400         * |   *   | <- where we want to scroll to
 7401         * |--B-D--| <- keyboard, bottom of scroll view
 7402         * |   C   | <- input
 7403         * |       |
 7404         * |___E___| <- bottom of viewport
 7405         *
 7406         *  All commented calculations relative to the top of the viewport (ie E
 7407         *  is the viewport height, not 0)
 7408         */
 7409        // if the element is positioned under the keyboard scroll it into view
 7410        if (e.detail.isElementUnderKeyboard) {
 7411
 7412          ionic.requestAnimationFrame(function(){
 7413            var pos = ionic.DomUtil.getOffsetTop(e.detail.target);
 7414            setTimeout(function() {
 7415              if (ionic.Platform.isIOS()) {
 7416                ionic.tap.cloneFocusedInput(container, self);
 7417              }
 7418              // Scroll the input into view, with a 100px buffer
 7419              self.scrollTo(0, pos - (rect.top + 100), true);
 7420              self.onScroll();
 7421            }, 32);
 7422
 7423            /*
 7424            // update D if we shrunk
 7425            if (self.isShrunkForKeyboard && !alreadyShrunk) {
 7426              scrollBottomOffsetToTop = container.getBoundingClientRect().bottom;
 7427              console.log('Scroll bottom', scrollBottomOffsetToTop);
 7428            }
 7429
 7430            // middle of the scrollview, this is where we want to scroll to
 7431            // (D - A) / 2
 7432            var scrollMidpointOffset = scrollViewOffsetHeight * 0.5;
 7433            console.log('Midpoint', scrollMidpointOffset);
 7434            //console.log("container.offsetHeight: " + scrollViewOffsetHeight);
 7435
 7436            // middle of the input we want to scroll into view
 7437            // C
 7438            var inputMidpoint = ((e.detail.elementBottom + e.detail.elementTop) / 2);
 7439            console.log('Input midpoint');
 7440
 7441            // distance from middle of input to the bottom of the scroll view
 7442            // C - D                                C               D
 7443            var inputMidpointOffsetToScrollBottom = inputMidpoint - scrollBottomOffsetToTop;
 7444            console.log('Input midpoint offset', inputMidpointOffsetToScrollBottom);
 7445
 7446            //C - D + (D - A)/2          C - D                     (D - A)/ 2
 7447            var scrollTop = inputMidpointOffsetToScrollBottom + scrollMidpointOffset;
 7448            console.log('Scroll top', scrollTop);
 7449
 7450            if ( scrollTop > 0) {
 7451              if (ionic.Platform.isIOS()) {
 7452                //just shrank scroll view, give it some breathing room before scrolling
 7453                setTimeout(function(){
 7454                  ionic.tap.cloneFocusedInput(container, self);
 7455                  self.scrollBy(0, scrollTop, true);
 7456                  self.onScroll();
 7457                }, 32);
 7458              } else {
 7459                self.scrollBy(0, scrollTop, true);
 7460                self.onScroll();
 7461              }
 7462            }
 7463            */
 7464          });
 7465        }
 7466
 7467        // Only the first scrollView parent of the element that broadcasted this event
 7468        // (the active element that needs to be shown) should receive this event
 7469        e.stopPropagation();
 7470      };
 7471
 7472      self.resetScrollView = function() {
 7473        //return scrollview to original height once keyboard has hidden
 7474        if (self.isShrunkForKeyboard) {
 7475          self.isShrunkForKeyboard = false;
 7476          container.style.height = "";
 7477
 7478          /*
 7479          if (ionic.Platform.isIOS()) {
 7480            // Force redraw to avoid disappearing content
 7481            var disp = container.style.display;
 7482            container.style.display = 'none';
 7483            var trick = container.offsetHeight;
 7484            container.style.display = disp;
 7485          }
 7486          */
 7487
 7488          self.__originalContainerHeight = container.getBoundingClientRect().height;
 7489
 7490          if (ionic.Platform.isIOS()) {
 7491            ionic.requestAnimationFrame(function() {
 7492              container.classList.remove('keyboard-up');
 7493            });
 7494          }
 7495
 7496        }
 7497        self.resize();
 7498      };
 7499
 7500      self.handleTouchMove = function(e) {
 7501        if (self.__frozenShut) {
 7502          e.preventDefault();
 7503          e.stopPropagation();
 7504          return false;
 7505
 7506        } else if ( self.__frozen ){
 7507          e.preventDefault();
 7508          // let it propagate so other events such as drag events can happen,
 7509          // but don't let it actually scroll
 7510          return false;
 7511        }
 7512        return true;
 7513      };
 7514
 7515      container.addEventListener('scroll', self.onScroll);
 7516
 7517      //Broadcasted when keyboard is shown on some platforms.
 7518      //See js/utils/keyboard.js
 7519      container.addEventListener('scrollChildIntoView', self.scrollChildIntoView);
 7520
 7521      container.addEventListener(ionic.EVENTS.touchstart, self.handleTouchMove);
 7522      container.addEventListener(ionic.EVENTS.touchmove, self.handleTouchMove);
 7523
 7524      // Listen on document because container may not have had the last
 7525      // keyboardActiveElement, for example after closing a modal with a focused
 7526      // input and returning to a previously resized scroll view in an ion-content.
 7527      // Since we can only resize scroll views that are currently visible, just resize
 7528      // the current scroll view when the keyboard is closed.
 7529      document.addEventListener('resetScrollView', self.resetScrollView);
 7530    },
 7531
 7532    __cleanup: function() {
 7533      var self = this;
 7534      var container = self.__container;
 7535
 7536      container.removeEventListener('resetScrollView', self.resetScrollView);
 7537      container.removeEventListener('scroll', self.onScroll);
 7538
 7539      container.removeEventListener('scrollChildIntoView', self.scrollChildIntoView);
 7540      container.removeEventListener('resetScrollView', self.resetScrollView);
 7541
 7542      container.removeEventListener(ionic.EVENTS.touchstart, self.handleTouchMove);
 7543      container.removeEventListener(ionic.EVENTS.touchmove, self.handleTouchMove);
 7544
 7545      ionic.tap.removeClonedInputs(container, self);
 7546
 7547      delete self.__container;
 7548      delete self.__content;
 7549      delete self.__indicatorX;
 7550      delete self.__indicatorY;
 7551      delete self.options.el;
 7552
 7553      self.resize = self.scrollTo = self.onScroll = self.resetScrollView = NOOP;
 7554      self.scrollChildIntoView = NOOP;
 7555      container = null;
 7556    }
 7557  });
 7558
 7559})(ionic);
 7560
 7561(function(ionic) {
 7562'use strict';
 7563
 7564  var ITEM_CLASS = 'item';
 7565  var ITEM_CONTENT_CLASS = 'item-content';
 7566  var ITEM_SLIDING_CLASS = 'item-sliding';
 7567  var ITEM_OPTIONS_CLASS = 'item-options';
 7568  var ITEM_PLACEHOLDER_CLASS = 'item-placeholder';
 7569  var ITEM_REORDERING_CLASS = 'item-reordering';
 7570  var ITEM_REORDER_BTN_CLASS = 'item-reorder';
 7571
 7572  var DragOp = function() {};
 7573  DragOp.prototype = {
 7574    start: function(){},
 7575    drag: function(){},
 7576    end: function(){},
 7577    isSameItem: function() {
 7578      return false;
 7579    }
 7580  };
 7581
 7582  var SlideDrag = function(opts) {
 7583    this.dragThresholdX = opts.dragThresholdX || 10;
 7584    this.el = opts.el;
 7585    this.item = opts.item;
 7586    this.canSwipe = opts.canSwipe;
 7587  };
 7588
 7589  SlideDrag.prototype = new DragOp();
 7590
 7591  SlideDrag.prototype.start = function(e) {
 7592    var content, buttons, offsetX, buttonsWidth;
 7593
 7594    if (!this.canSwipe()) {
 7595      return;
 7596    }
 7597
 7598    if (e.target.classList.contains(ITEM_CONTENT_CLASS)) {
 7599      content = e.target;
 7600    } else if (e.target.classList.contains(ITEM_CLASS)) {
 7601      content = e.target.querySelector('.' + ITEM_CONTENT_CLASS);
 7602    } else {
 7603      content = ionic.DomUtil.getParentWithClass(e.target, ITEM_CONTENT_CLASS);
 7604    }
 7605
 7606    // If we don't have a content area as one of our children (or ourselves), skip
 7607    if (!content) {
 7608      return;
 7609    }
 7610
 7611    // Make sure we aren't animating as we slide
 7612    content.classList.remove(ITEM_SLIDING_CLASS);
 7613
 7614    // Grab the starting X point for the item (for example, so we can tell whether it is open or closed to start)
 7615    offsetX = parseFloat(content.style[ionic.CSS.TRANSFORM].replace('translate3d(', '').split(',')[0]) || 0;
 7616
 7617    // Grab the buttons
 7618    buttons = content.parentNode.querySelector('.' + ITEM_OPTIONS_CLASS);
 7619    if (!buttons) {
 7620      return;
 7621    }
 7622    buttons.classList.remove('invisible');
 7623
 7624    buttonsWidth = buttons.offsetWidth;
 7625
 7626    this._currentDrag = {
 7627      buttons: buttons,
 7628      buttonsWidth: buttonsWidth,
 7629      content: content,
 7630      startOffsetX: offsetX
 7631    };
 7632  };
 7633
 7634  /**
 7635   * Check if this is the same item that was previously dragged.
 7636   */
 7637  SlideDrag.prototype.isSameItem = function(op) {
 7638    if (op._lastDrag && this._currentDrag) {
 7639      return this._currentDrag.content == op._lastDrag.content;
 7640    }
 7641    return false;
 7642  };
 7643
 7644  SlideDrag.prototype.clean = function(isInstant) {
 7645    var lastDrag = this._lastDrag;
 7646
 7647    if (!lastDrag || !lastDrag.content) return;
 7648
 7649    lastDrag.content.style[ionic.CSS.TRANSITION] = '';
 7650    lastDrag.content.style[ionic.CSS.TRANSFORM] = '';
 7651    if (isInstant) {
 7652      lastDrag.content.style[ionic.CSS.TRANSITION] = 'none';
 7653      makeInvisible();
 7654      ionic.requestAnimationFrame(function() {
 7655        lastDrag.content.style[ionic.CSS.TRANSITION] = '';
 7656      });
 7657    } else {
 7658      ionic.requestAnimationFrame(function() {
 7659        setTimeout(makeInvisible, 250);
 7660      });
 7661    }
 7662    function makeInvisible() {
 7663      lastDrag.buttons && lastDrag.buttons.classList.add('invisible');
 7664    }
 7665  };
 7666
 7667  SlideDrag.prototype.drag = ionic.animationFrameThrottle(function(e) {
 7668    var buttonsWidth;
 7669
 7670    // We really aren't dragging
 7671    if (!this._currentDrag) {
 7672      return;
 7673    }
 7674
 7675    // Check if we should start dragging. Check if we've dragged past the threshold,
 7676    // or we are starting from the open state.
 7677    if (!this._isDragging &&
 7678        ((Math.abs(e.gesture.deltaX) > this.dragThresholdX) ||
 7679        (Math.abs(this._currentDrag.startOffsetX) > 0))) {
 7680      this._isDragging = true;
 7681    }
 7682
 7683    if (this._isDragging) {
 7684      buttonsWidth = this._currentDrag.buttonsWidth;
 7685
 7686      // Grab the new X point, capping it at zero
 7687      var newX = Math.min(0, this._currentDrag.startOffsetX + e.gesture.deltaX);
 7688
 7689      // If the new X position is past the buttons, we need to slow down the drag (rubber band style)
 7690      if (newX < -buttonsWidth) {
 7691        // Calculate the new X position, capped at the top of the buttons
 7692        newX = Math.min(-buttonsWidth, -buttonsWidth + (((e.gesture.deltaX + buttonsWidth) * 0.4)));
 7693      }
 7694
 7695      this._currentDrag.content.$$ionicOptionsOpen = newX !== 0;
 7696
 7697      this._currentDrag.content.style[ionic.CSS.TRANSFORM] = 'translate3d(' + newX + 'px, 0, 0)';
 7698      this._currentDrag.content.style[ionic.CSS.TRANSITION] = 'none';
 7699    }
 7700  });
 7701
 7702  SlideDrag.prototype.end = function(e, doneCallback) {
 7703    var self = this;
 7704
 7705    // There is no drag, just end immediately
 7706    if (!self._currentDrag) {
 7707      doneCallback && doneCallback();
 7708      return;
 7709    }
 7710
 7711    // If we are currently dragging, we want to snap back into place
 7712    // The final resting point X will be the width of the exposed buttons
 7713    var restingPoint = -self._currentDrag.buttonsWidth;
 7714
 7715    // Check if the drag didn't clear the buttons mid-point
 7716    // and we aren't moving fast enough to swipe open
 7717    if (e.gesture.deltaX > -(self._currentDrag.buttonsWidth / 2)) {
 7718
 7719      // If we are going left but too slow, or going right, go back to resting
 7720      if (e.gesture.direction == "left" && Math.abs(e.gesture.velocityX) < 0.3) {
 7721        restingPoint = 0;
 7722
 7723      } else if (e.gesture.direction == "right") {
 7724        restingPoint = 0;
 7725      }
 7726
 7727    }
 7728
 7729    ionic.requestAnimationFrame(function() {
 7730      if (restingPoint === 0) {
 7731        self._currentDrag.content.style[ionic.CSS.TRANSFORM] = '';
 7732        var buttons = self._currentDrag.buttons;
 7733        setTimeout(function() {
 7734          buttons && buttons.classList.add('invisible');
 7735        }, 250);
 7736      } else {
 7737        self._currentDrag.content.style[ionic.CSS.TRANSFORM] = 'translate3d(' + restingPoint + 'px,0,0)';
 7738      }
 7739      self._currentDrag.content.style[ionic.CSS.TRANSITION] = '';
 7740
 7741
 7742      // Kill the current drag
 7743      if (!self._lastDrag) {
 7744        self._lastDrag = {};
 7745      }
 7746      ionic.extend(self._lastDrag, self._currentDrag);
 7747      if (self._currentDrag) {
 7748        self._currentDrag.buttons = null;
 7749        self._currentDrag.content = null;
 7750      }
 7751      self._currentDrag = null;
 7752
 7753      // We are done, notify caller
 7754      doneCallback && doneCallback();
 7755    });
 7756  };
 7757
 7758  var ReorderDrag = function(opts) {
 7759    var self = this;
 7760
 7761    self.dragThresholdY = opts.dragThresholdY || 0;
 7762    self.onReorder = opts.onReorder;
 7763    self.listEl = opts.listEl;
 7764    self.el = self.item = opts.el;
 7765    self.scrollEl = opts.scrollEl;
 7766    self.scrollView = opts.scrollView;
 7767    // Get the True Top of the list el http://www.quirksmode.org/js/findpos.html
 7768    self.listElTrueTop = 0;
 7769    if (self.listEl.offsetParent) {
 7770      var obj = self.listEl;
 7771      do {
 7772        self.listElTrueTop += obj.offsetTop;
 7773        obj = obj.offsetParent;
 7774      } while (obj);
 7775    }
 7776  };
 7777
 7778  ReorderDrag.prototype = new DragOp();
 7779
 7780  ReorderDrag.prototype._moveElement = function(e) {
 7781    var y = e.gesture.center.pageY +
 7782      this.scrollView.getValues().top -
 7783      (this._currentDrag.elementHeight / 2) -
 7784      this.listElTrueTop;
 7785    this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(0, ' + y + 'px, 0)';
 7786  };
 7787
 7788  ReorderDrag.prototype.deregister = function() {
 7789    this.listEl = this.el = this.scrollEl = this.scrollView = null;
 7790  };
 7791
 7792  ReorderDrag.prototype.start = function(e) {
 7793
 7794    var startIndex = ionic.DomUtil.getChildIndex(this.el, this.el.nodeName.toLowerCase());
 7795    var elementHeight = this.el.scrollHeight;
 7796    var placeholder = this.el.cloneNode(true);
 7797
 7798    placeholder.classList.add(ITEM_PLACEHOLDER_CLASS);
 7799
 7800    this.el.parentNode.insertBefore(placeholder, this.el);
 7801    this.el.classList.add(ITEM_REORDERING_CLASS);
 7802
 7803    this._currentDrag = {
 7804      elementHeight: elementHeight,
 7805      startIndex: startIndex,
 7806      placeholder: placeholder,
 7807      scrollHeight: scroll,
 7808      list: placeholder.parentNode
 7809    };
 7810
 7811    this._moveElement(e);
 7812  };
 7813
 7814  ReorderDrag.prototype.drag = ionic.animationFrameThrottle(function(e) {
 7815    // We really aren't dragging
 7816    var self = this;
 7817    if (!this._currentDrag) {
 7818      return;
 7819    }
 7820
 7821    var scrollY = 0;
 7822    var pageY = e.gesture.center.pageY;
 7823    var offset = this.listElTrueTop;
 7824
 7825    //If we have a scrollView, check scroll boundaries for dragged element and scroll if necessary
 7826    if (this.scrollView) {
 7827
 7828      var container = this.scrollView.__container;
 7829      scrollY = this.scrollView.getValues().top;
 7830
 7831      var containerTop = container.offsetTop;
 7832      var pixelsPastTop = containerTop - pageY + this._currentDrag.elementHeight / 2;
 7833      var pixelsPastBottom = pageY + this._currentDrag.elementHeight / 2 - containerTop - container.offsetHeight;
 7834
 7835      if (e.gesture.deltaY < 0 && pixelsPastTop > 0 && scrollY > 0) {
 7836        this.scrollView.scrollBy(null, -pixelsPastTop);
 7837        //Trigger another drag so the scrolling keeps going
 7838        ionic.requestAnimationFrame(function() {
 7839          self.drag(e);
 7840        });
 7841      }
 7842      if (e.gesture.deltaY > 0 && pixelsPastBottom > 0) {
 7843        if (scrollY < this.scrollView.getScrollMax().top) {
 7844          this.scrollView.scrollBy(null, pixelsPastBottom);
 7845          //Trigger another drag so the scrolling keeps going
 7846          ionic.requestAnimationFrame(function() {
 7847            self.drag(e);
 7848          });
 7849        }
 7850      }
 7851    }
 7852
 7853    // Check if we should start dragging. Check if we've dragged past the threshold,
 7854    // or we are starting from the open state.
 7855    if (!this._isDragging && Math.abs(e.gesture.deltaY) > this.dragThresholdY) {
 7856      this._isDragging = true;
 7857    }
 7858
 7859    if (this._isDragging) {
 7860      this._moveElement(e);
 7861
 7862      this._currentDrag.currentY = scrollY + pageY - offset;
 7863
 7864      // this._reorderItems();
 7865    }
 7866  });
 7867
 7868  // When an item is dragged, we need to reorder any items for sorting purposes
 7869  ReorderDrag.prototype._getReorderIndex = function() {
 7870    var self = this;
 7871
 7872    var siblings = Array.prototype.slice.call(self._currentDrag.placeholder.parentNode.children)
 7873      .filter(function(el) {
 7874        return el.nodeName === self.el.nodeName && el !== self.el;
 7875      });
 7876
 7877    var dragOffsetTop = self._currentDrag.currentY;
 7878    var el;
 7879    for (var i = 0, len = siblings.length; i < len; i++) {
 7880      el = siblings[i];
 7881      if (i === len - 1) {
 7882        if (dragOffsetTop > el.offsetTop) {
 7883          return i;
 7884        }
 7885      } else if (i === 0) {
 7886        if (dragOffsetTop < el.offsetTop + el.offsetHeight) {
 7887          return i;
 7888        }
 7889      } else if (dragOffsetTop > el.offsetTop - el.offsetHeight / 2 &&
 7890                 dragOffsetTop < el.offsetTop + el.offsetHeight) {
 7891        return i;
 7892      }
 7893    }
 7894    return self._currentDrag.startIndex;
 7895  };
 7896
 7897  ReorderDrag.prototype.end = function(e, doneCallback) {
 7898    if (!this._currentDrag) {
 7899      doneCallback && doneCallback();
 7900      return;
 7901    }
 7902
 7903    var placeholder = this._currentDrag.placeholder;
 7904    var finalIndex = this._getReorderIndex();
 7905
 7906    // Reposition the element
 7907    this.el.classList.remove(ITEM_REORDERING_CLASS);
 7908    this.el.style[ionic.CSS.TRANSFORM] = '';
 7909
 7910    placeholder.parentNode.insertBefore(this.el, placeholder);
 7911    placeholder.parentNode.removeChild(placeholder);
 7912
 7913    this.onReorder && this.onReorder(this.el, this._currentDrag.startIndex, finalIndex);
 7914
 7915    this._currentDrag = {
 7916      placeholder: null,
 7917      content: null
 7918    };
 7919    this._currentDrag = null;
 7920    doneCallback && doneCallback();
 7921  };
 7922
 7923
 7924
 7925  /**
 7926   * The ListView handles a list of items. It will process drag animations, edit mode,
 7927   * and other operations that are common on mobile lists or table views.
 7928   */
 7929  ionic.views.ListView = ionic.views.View.inherit({
 7930    initialize: function(opts) {
 7931      var self = this;
 7932
 7933      opts = ionic.extend({
 7934        onReorder: function() {},
 7935        virtualRemoveThreshold: -200,
 7936        virtualAddThreshold: 200,
 7937        canSwipe: function() {
 7938          return true;
 7939        }
 7940      }, opts);
 7941
 7942      ionic.extend(self, opts);
 7943
 7944      if (!self.itemHeight && self.listEl) {
 7945        self.itemHeight = self.listEl.children[0] && parseInt(self.listEl.children[0].style.height, 10);
 7946      }
 7947
 7948      self.onRefresh = opts.onRefresh || function() {};
 7949      self.onRefreshOpening = opts.onRefreshOpening || function() {};
 7950      self.onRefreshHolding = opts.onRefreshHolding || function() {};
 7951
 7952      var gestureOpts = {};
 7953      // don't prevent native scrolling
 7954      if (ionic.DomUtil.getParentOrSelfWithClass(self.el, 'overflow-scroll')) {
 7955        gestureOpts.prevent_default_directions = ['left', 'right'];
 7956      }
 7957
 7958      window.ionic.onGesture('release', function(e) {
 7959        self._handleEndDrag(e);
 7960      }, self.el, gestureOpts);
 7961
 7962      window.ionic.onGesture('drag', function(e) {
 7963        self._handleDrag(e);
 7964      }, self.el, gestureOpts);
 7965      // Start the drag states
 7966      self._initDrag();
 7967    },
 7968
 7969    /**
 7970     * Be sure to cleanup references.
 7971     */
 7972    deregister: function() {
 7973      this.el = this.listEl = this.scrollEl = this.scrollView = null;
 7974
 7975      // ensure no scrolls have been left frozen
 7976      if (this.isScrollFreeze) {
 7977        self.scrollView.freeze(false);
 7978      }
 7979    },
 7980
 7981    /**
 7982     * Called to tell the list to stop refreshing. This is useful
 7983     * if you are refreshing the list and are done with refreshing.
 7984     */
 7985    stopRefreshing: function() {
 7986      var refresher = this.el.querySelector('.list-refresher');
 7987      refresher.style.height = '0';
 7988    },
 7989
 7990    /**
 7991     * If we scrolled and have virtual mode enabled, compute the window
 7992     * of active elements in order to figure out the viewport to render.
 7993     */
 7994    didScroll: function(e) {
 7995      var self = this;
 7996
 7997      if (self.isVirtual) {
 7998        var itemHeight = self.itemHeight;
 7999
 8000        // Grab the total height of the list
 8001        var scrollHeight = e.target.scrollHeight;
 8002
 8003        // Get the viewport height
 8004        var viewportHeight = self.el.parentNode.offsetHeight;
 8005
 8006        // High water is the pixel position of the first element to include (everything before
 8007        // that will be removed)
 8008        var highWater = Math.max(0, e.scrollTop + self.virtualRemoveThreshold);
 8009
 8010        // Low water is the pixel position of the last element to include (everything after
 8011        // that will be removed)
 8012        var lowWater = Math.min(scrollHeight, Math.abs(e.scrollTop) + viewportHeight + self.virtualAddThreshold);
 8013
 8014        // Get the first and last elements in the list based on how many can fit
 8015        // between the pixel range of lowWater and highWater
 8016        var first = parseInt(Math.abs(highWater / itemHeight), 10);
 8017        var last = parseInt(Math.abs(lowWater / itemHeight), 10);
 8018
 8019        // Get the items we need to remove
 8020        self._virtualItemsToRemove = Array.prototype.slice.call(self.listEl.children, 0, first);
 8021
 8022        self.renderViewport && self.renderViewport(highWater, lowWater, first, last);
 8023      }
 8024    },
 8025
 8026    didStopScrolling: function() {
 8027      if (this.isVirtual) {
 8028        for (var i = 0; i < this._virtualItemsToRemove.length; i++) {
 8029          //el.parentNode.removeChild(el);
 8030          this.didHideItem && this.didHideItem(i);
 8031        }
 8032        // Once scrolling stops, check if we need to remove old items
 8033
 8034      }
 8035    },
 8036
 8037    /**
 8038     * Clear any active drag effects on the list.
 8039     */
 8040    clearDragEffects: function(isInstant) {
 8041      if (this._lastDragOp) {
 8042        this._lastDragOp.clean && this._lastDragOp.clean(isInstant);
 8043        this._lastDragOp.deregister && this._lastDragOp.deregister();
 8044        this._lastDragOp = null;
 8045      }
 8046    },
 8047
 8048    _initDrag: function() {
 8049      // Store the last one
 8050      if (this._lastDragOp) {
 8051        this._lastDragOp.deregister && this._lastDragOp.deregister();
 8052      }
 8053      this._lastDragOp = this._dragOp;
 8054
 8055      this._dragOp = null;
 8056    },
 8057
 8058    // Return the list item from the given target
 8059    _getItem: function(target) {
 8060      while (target) {
 8061        if (target.classList && target.classList.contains(ITEM_CLASS)) {
 8062          return target;
 8063        }
 8064        target = target.parentNode;
 8065      }
 8066      return null;
 8067    },
 8068
 8069
 8070    _startDrag: function(e) {
 8071      var self = this;
 8072
 8073      self._isDragging = false;
 8074
 8075      var lastDragOp = self._lastDragOp;
 8076      var item;
 8077
 8078      // If we have an open SlideDrag and we're scrolling the list. Clear it.
 8079      if (self._didDragUpOrDown && lastDragOp instanceof SlideDrag) {
 8080          lastDragOp.clean && lastDragOp.clean();
 8081      }
 8082
 8083      // Check if this is a reorder drag
 8084      if (ionic.DomUtil.getParentOrSelfWithClass(e.target, ITEM_REORDER_BTN_CLASS) && (e.gesture.direction == 'up' || e.gesture.direction == 'down')) {
 8085        item = self._getItem(e.target);
 8086
 8087        if (item) {
 8088          self._dragOp = new ReorderDrag({
 8089            listEl: self.el,
 8090            el: item,
 8091            scrollEl: self.scrollEl,
 8092            scrollView: self.scrollView,
 8093            onReorder: function(el, start, end) {
 8094              self.onReorder && self.onReorder(el, start, end);
 8095            }
 8096          });
 8097          self._dragOp.start(e);
 8098          e.preventDefault();
 8099        }
 8100      }
 8101
 8102      // Or check if this is a swipe to the side drag
 8103      else if (!self._didDragUpOrDown && (e.gesture.direction == 'left' || e.gesture.direction == 'right') && Math.abs(e.gesture.deltaX) > 5) {
 8104
 8105        // Make sure this is an item with buttons
 8106        item = self._getItem(e.target);
 8107        if (item && item.querySelector('.item-options')) {
 8108          self._dragOp = new SlideDrag({
 8109            el: self.el,
 8110            item: item,
 8111            canSwipe: self.canSwipe
 8112          });
 8113          self._dragOp.start(e);
 8114          e.preventDefault();
 8115          self.isScrollFreeze = self.scrollView.freeze(true);
 8116        }
 8117      }
 8118
 8119      // If we had a last drag operation and this is a new one on a different item, clean that last one
 8120      if (lastDragOp && self._dragOp && !self._dragOp.isSameItem(lastDragOp) && e.defaultPrevented) {
 8121        lastDragOp.clean && lastDragOp.clean();
 8122      }
 8123    },
 8124
 8125
 8126    _handleEndDrag: function(e) {
 8127      var self = this;
 8128
 8129      if (self.scrollView) {
 8130        self.isScrollFreeze = self.scrollView.freeze(false);
 8131      }
 8132
 8133      self._didDragUpOrDown = false;
 8134
 8135      if (!self._dragOp) {
 8136        return;
 8137      }
 8138
 8139      self._dragOp.end(e, function() {
 8140        self._initDrag();
 8141      });
 8142    },
 8143
 8144    /**
 8145     * Process the drag event to move the item to the left or right.
 8146     */
 8147    _handleDrag: function(e) {
 8148      var self = this;
 8149
 8150      if (Math.abs(e.gesture.deltaY) > 5) {
 8151        self._didDragUpOrDown = true;
 8152      }
 8153
 8154      // If we get a drag event, make sure we aren't in another drag, then check if we should
 8155      // start one
 8156      if (!self.isDragging && !self._dragOp) {
 8157        self._startDrag(e);
 8158      }
 8159
 8160      // No drag still, pass it up
 8161      if (!self._dragOp) {
 8162        return;
 8163      }
 8164
 8165      e.gesture.srcEvent.preventDefault();
 8166      self._dragOp.drag(e);
 8167    }
 8168
 8169  });
 8170
 8171})(ionic);
 8172
 8173(function(ionic) {
 8174'use strict';
 8175
 8176  ionic.views.Modal = ionic.views.View.inherit({
 8177    initialize: function(opts) {
 8178      opts = ionic.extend({
 8179        focusFirstInput: false,
 8180        unfocusOnHide: true,
 8181        focusFirstDelay: 600,
 8182        backdropClickToClose: true,
 8183        hardwareBackButtonClose: true,
 8184      }, opts);
 8185
 8186      ionic.extend(this, opts);
 8187
 8188      this.el = opts.el;
 8189    },
 8190    show: function() {
 8191      var self = this;
 8192
 8193      if(self.focusFirstInput) {
 8194        // Let any animations run first
 8195        window.setTimeout(function() {
 8196          var input = self.el.querySelector('input, textarea');
 8197          input && input.focus && input.focus();
 8198        }, self.focusFirstDelay);
 8199      }
 8200    },
 8201    hide: function() {
 8202      // Unfocus all elements
 8203      if(this.unfocusOnHide) {
 8204        var inputs = this.el.querySelectorAll('input, textarea');
 8205        // Let any animations run first
 8206        window.setTimeout(function() {
 8207          for(var i = 0; i < inputs.length; i++) {
 8208            inputs[i].blur && inputs[i].blur();
 8209          }
 8210        });
 8211      }
 8212    }
 8213  });
 8214
 8215})(ionic);
 8216
 8217(function(ionic) {
 8218'use strict';
 8219
 8220  /**
 8221   * The side menu view handles one of the side menu's in a Side Menu Controller
 8222   * configuration.
 8223   * It takes a DOM reference to that side menu element.
 8224   */
 8225  ionic.views.SideMenu = ionic.views.View.inherit({
 8226    initialize: function(opts) {
 8227      this.el = opts.el;
 8228      this.isEnabled = (typeof opts.isEnabled === 'undefined') ? true : opts.isEnabled;
 8229      this.setWidth(opts.width);
 8230    },
 8231    getFullWidth: function() {
 8232      return this.width;
 8233    },
 8234    setWidth: function(width) {
 8235      this.width = width;
 8236      this.el.style.width = width + 'px';
 8237    },
 8238    setIsEnabled: function(isEnabled) {
 8239      this.isEnabled = isEnabled;
 8240    },
 8241    bringUp: function() {
 8242      if(this.el.style.zIndex !== '0') {
 8243        this.el.style.zIndex = '0';
 8244      }
 8245    },
 8246    pushDown: function() {
 8247      if(this.el.style.zIndex !== '-1') {
 8248        this.el.style.zIndex = '-1';
 8249      }
 8250    }
 8251  });
 8252
 8253  ionic.views.SideMenuContent = ionic.views.View.inherit({
 8254    initialize: function(opts) {
 8255      ionic.extend(this, {
 8256        animationClass: 'menu-animated',
 8257        onDrag: function() {},
 8258        onEndDrag: function() {}
 8259      }, opts);
 8260
 8261      ionic.onGesture('drag', ionic.proxy(this._onDrag, this), this.el);
 8262      ionic.onGesture('release', ionic.proxy(this._onEndDrag, this), this.el);
 8263    },
 8264    _onDrag: function(e) {
 8265      this.onDrag && this.onDrag(e);
 8266    },
 8267    _onEndDrag: function(e) {
 8268      this.onEndDrag && this.onEndDrag(e);
 8269    },
 8270    disableAnimation: function() {
 8271      this.el.classList.remove(this.animationClass);
 8272    },
 8273    enableAnimation: function() {
 8274      this.el.classList.add(this.animationClass);
 8275    },
 8276    getTranslateX: function() {
 8277      return parseFloat(this.el.style[ionic.CSS.TRANSFORM].replace('translate3d(', '').split(',')[0]);
 8278    },
 8279    setTranslateX: ionic.animationFrameThrottle(function(x) {
 8280      this.el.style[ionic.CSS.TRANSFORM] = 'translate3d(' + x + 'px, 0, 0)';
 8281    })
 8282  });
 8283
 8284})(ionic);
 8285
 8286/*
 8287 * Adapted from Swipe.js 2.0
 8288 *
 8289 * Brad Birdsall
 8290 * Copyright 2013, MIT License
 8291 *
 8292*/
 8293
 8294(function(ionic) {
 8295'use strict';
 8296
 8297ionic.views.Slider = ionic.views.View.inherit({
 8298  initialize: function (options) {
 8299    var slider = this;
 8300
 8301    var touchStartEvent, touchMoveEvent, touchEndEvent;
 8302    if (window.navigator.pointerEnabled) {
 8303      touchStartEvent = 'pointerdown';
 8304      touchMoveEvent = 'pointermove';
 8305      touchEndEvent = 'pointerup';
 8306    } else if (window.navigator.msPointerEnabled) {
 8307      touchStartEvent = 'MSPointerDown';
 8308      touchMoveEvent = 'MSPointerMove';
 8309      touchEndEvent = 'MSPointerUp';
 8310    } else {
 8311      touchStartEvent = 'touchstart';
 8312      touchMoveEvent = 'touchmove';
 8313      touchEndEvent = 'touchend';
 8314    }
 8315
 8316    var mouseStartEvent = 'mousedown';
 8317    var mouseMoveEvent = 'mousemove';
 8318    var mouseEndEvent = 'mouseup';
 8319
 8320    // utilities
 8321    var noop = function() {}; // simple no operation function
 8322    var offloadFn = function(fn) { setTimeout(fn || noop, 0); }; // offload a functions execution
 8323
 8324    // check browser capabilities
 8325    var browser = {
 8326      addEventListener: !!window.addEventListener,
 8327      transitions: (function(temp) {
 8328        var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];
 8329        for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true;
 8330        return false;
 8331      })(document.createElement('swipe'))
 8332    };
 8333
 8334
 8335    var container = options.el;
 8336
 8337    // quit if no root element
 8338    if (!container) return;
 8339    var element = container.children[0];
 8340    var slides, slidePos, width, length;
 8341    options = options || {};
 8342    var index = parseInt(options.startSlide, 10) || 0;
 8343    var speed = options.speed || 300;
 8344    options.continuous = options.continuous !== undefined ? options.continuous : true;
 8345
 8346    function setup() {
 8347
 8348      // do not setup if the container has no width
 8349      if (!container.offsetWidth) {
 8350        return;
 8351      }
 8352
 8353      // cache slides
 8354      slides = element.children;
 8355      length = slides.length;
 8356
 8357      // set continuous to false if only one slide
 8358      if (slides.length < 2) options.continuous = false;
 8359
 8360      //special case if two slides
 8361      if (browser.transitions && options.continuous && slides.length < 3) {
 8362        element.appendChild(slides[0].cloneNode(true));
 8363        element.appendChild(element.children[1].cloneNode(true));
 8364        slides = element.children;
 8365      }
 8366
 8367      // create an array to store current positions of each slide
 8368      slidePos = new Array(slides.length);
 8369
 8370      // determine width of each slide
 8371      width = container.offsetWidth || container.getBoundingClientRect().width;
 8372
 8373      element.style.width = (slides.length * width) + 'px';
 8374
 8375      // stack elements
 8376      var pos = slides.length;
 8377      while(pos--) {
 8378
 8379        var slide = slides[pos];
 8380
 8381        slide.style.width = width + 'px';
 8382        slide.setAttribute('data-index', pos);
 8383
 8384        if (browser.transitions) {
 8385          slide.style.left = (pos * -width) + 'px';
 8386          move(pos, index > pos ? -width : (index < pos ? width : 0), 0);
 8387        }
 8388
 8389      }
 8390
 8391      // reposition elements before and after index
 8392      if (options.continuous && browser.transitions) {
 8393        move(circle(index - 1), -width, 0);
 8394        move(circle(index + 1), width, 0);
 8395      }
 8396
 8397      if (!browser.transitions) element.style.left = (index * -width) + 'px';
 8398
 8399      container.style.visibility = 'visible';
 8400
 8401      options.slidesChanged && options.slidesChanged();
 8402    }
 8403
 8404    function prev(slideSpeed) {
 8405
 8406      if (options.continuous) slide(index - 1, slideSpeed);
 8407      else if (index) slide(index - 1, slideSpeed);
 8408
 8409    }
 8410
 8411    function next(slideSpeed) {
 8412
 8413      if (options.continuous) slide(index + 1, slideSpeed);
 8414      else if (index < slides.length - 1) slide(index + 1, slideSpeed);
 8415
 8416    }
 8417
 8418    function circle(index) {
 8419
 8420      // a simple positive modulo using slides.length
 8421      return (slides.length + (index % slides.length)) % slides.length;
 8422
 8423    }
 8424
 8425    function slide(to, slideSpeed) {
 8426
 8427      // do nothing if already on requested slide
 8428      if (index == to) return;
 8429
 8430      if (!slides) {
 8431        index = to;
 8432        return;
 8433      }
 8434
 8435      if (browser.transitions) {
 8436
 8437        var direction = Math.abs(index - to) / (index - to); // 1: backward, -1: forward
 8438
 8439        // get the actual position of the slide
 8440        if (options.continuous) {
 8441          var naturalDirection = direction;
 8442          direction = -slidePos[circle(to)] / width;
 8443
 8444          // if going forward but to < index, use to = slides.length + to
 8445          // if going backward but to > index, use to = -slides.length + to
 8446          if (direction !== naturalDirection) to = -direction * slides.length + to;
 8447
 8448        }
 8449
 8450        var diff = Math.abs(index - to) - 1;
 8451
 8452        // move all the slides between index and to in the right direction
 8453        while (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0);
 8454
 8455        to = circle(to);
 8456
 8457        move(index, width * direction, slideSpeed || speed);
 8458        move(to, 0, slideSpeed || speed);
 8459
 8460        if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place
 8461
 8462      } else {
 8463
 8464        to = circle(to);
 8465        animate(index * -width, to * -width, slideSpeed || speed);
 8466        //no fallback for a circular continuous if the browser does not accept transitions
 8467      }
 8468
 8469      index = to;
 8470      offloadFn(options.callback && options.callback(index, slides[index]));
 8471    }
 8472
 8473    function move(index, dist, speed) {
 8474
 8475      translate(index, dist, speed);
 8476      slidePos[index] = dist;
 8477
 8478    }
 8479
 8480    function translate(index, dist, speed) {
 8481
 8482      var slide = slides[index];
 8483      var style = slide && slide.style;
 8484
 8485      if (!style) return;
 8486
 8487      style.webkitTransitionDuration =
 8488      style.MozTransitionDuration =
 8489      style.msTransitionDuration =
 8490      style.OTransitionDuration =
 8491      style.transitionDuration = speed + 'ms';
 8492
 8493      style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)';
 8494      style.msTransform =
 8495      style.MozTransform =
 8496      style.OTransform = 'translateX(' + dist + 'px)';
 8497
 8498    }
 8499
 8500    function animate(from, to, speed) {
 8501
 8502      // if not an animation, just reposition
 8503      if (!speed) {
 8504
 8505        element.style.left = to + 'px';
 8506        return;
 8507
 8508      }
 8509
 8510      var start = +new Date();
 8511
 8512      var timer = setInterval(function() {
 8513
 8514        var timeElap = +new Date() - start;
 8515
 8516        if (timeElap > speed) {
 8517
 8518          element.style.left = to + 'px';
 8519
 8520          if (delay) begin();
 8521
 8522          options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
 8523
 8524          clearInterval(timer);
 8525          return;
 8526
 8527        }
 8528
 8529        element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px';
 8530
 8531      }, 4);
 8532
 8533    }
 8534
 8535    // setup auto slideshow
 8536    var delay = options.auto || 0;
 8537    var interval;
 8538
 8539    function begin() {
 8540
 8541      interval = setTimeout(next, delay);
 8542
 8543    }
 8544
 8545    function stop() {
 8546
 8547      delay = options.auto || 0;
 8548      clearTimeout(interval);
 8549
 8550    }
 8551
 8552
 8553    // setup initial vars
 8554    var start = {};
 8555    var delta = {};
 8556    var isScrolling;
 8557
 8558    // setup event capturing
 8559    var events = {
 8560
 8561      handleEvent: function(event) {
 8562        if(!event.touches && event.pageX && event.pageY) {
 8563          event.touches = [{
 8564            pageX: event.pageX,
 8565            pageY: event.pageY
 8566          }];
 8567        }
 8568
 8569        switch (event.type) {
 8570          case touchStartEvent: this.start(event); break;
 8571          case mouseStartEvent: this.start(event); break;
 8572          case touchMoveEvent: this.touchmove(event); break;
 8573          case mouseMoveEvent: this.touchmove(event); break;
 8574          case touchEndEvent: offloadFn(this.end(event)); break;
 8575          case mouseEndEvent: offloadFn(this.end(event)); break;
 8576          case 'webkitTransitionEnd':
 8577          case 'msTransitionEnd':
 8578          case 'oTransitionEnd':
 8579          case 'otransitionend':
 8580          case 'transitionend': offloadFn(this.transitionEnd(event)); break;
 8581          case 'resize': offloadFn(setup); break;
 8582        }
 8583
 8584        if (options.stopPropagation) event.stopPropagation();
 8585
 8586      },
 8587      start: function(event) {
 8588
 8589        // prevent to start if there is no valid event
 8590        if (!event.touches) {
 8591          return;
 8592        }
 8593
 8594        var touches = event.touches[0];
 8595
 8596        // measure start values
 8597        start = {
 8598
 8599          // get initial touch coords
 8600          x: touches.pageX,
 8601          y: touches.pageY,
 8602
 8603          // store time to determine touch duration
 8604          time: +new Date()
 8605
 8606        };
 8607
 8608        // used for testing first move event
 8609        isScrolling = undefined;
 8610
 8611        // reset delta and end measurements
 8612        delta = {};
 8613
 8614        // attach touchmove and touchend listeners
 8615        element.addEventListener(touchMoveEvent, this, false);
 8616        element.addEventListener(mouseMoveEvent, this, false);
 8617
 8618        element.addEventListener(touchEndEvent, this, false);
 8619        element.addEventListener(mouseEndEvent, this, false);
 8620
 8621        document.addEventListener(touchEndEvent, this, false);
 8622        document.addEventListener(mouseEndEvent, this, false);
 8623      },
 8624      touchmove: function(event) {
 8625
 8626        // ensure there is a valid event
 8627        // ensure swiping with one touch and not pinching
 8628        // ensure sliding is enabled
 8629        if (!event.touches ||
 8630            event.touches.length > 1 ||
 8631            event.scale && event.scale !== 1 ||
 8632            slider.slideIsDisabled) {
 8633          return;
 8634        }
 8635
 8636        if (options.disableScroll) event.preventDefault();
 8637
 8638        var touches = event.touches[0];
 8639
 8640        // measure change in x and y
 8641        delta = {
 8642          x: touches.pageX - start.x,
 8643          y: touches.pageY - start.y
 8644        };
 8645
 8646        // determine if scrolling test has run - one time test
 8647        if ( typeof isScrolling == 'undefined') {
 8648          isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) );
 8649        }
 8650
 8651        // if user is not trying to scroll vertically
 8652        if (!isScrolling) {
 8653
 8654          // prevent native scrolling
 8655          event.preventDefault();
 8656
 8657          // stop slideshow
 8658          stop();
 8659
 8660          // increase resistance if first or last slide
 8661          if (options.continuous) { // we don't add resistance at the end
 8662
 8663            translate(circle(index - 1), delta.x + slidePos[circle(index - 1)], 0);
 8664            translate(index, delta.x + slidePos[index], 0);
 8665            translate(circle(index + 1), delta.x + slidePos[circle(index + 1)], 0);
 8666
 8667          } else {
 8668            // If the slider bounces, do the bounce!
 8669            if(options.bouncing) {
 8670              delta.x =
 8671               delta.x /
 8672                 ( (!index && delta.x > 0 ||         // if first slide and sliding left
 8673                   index == slides.length - 1 &&     // or if last slide and sliding right
 8674                   delta.x < 0                       // and if sliding at all
 8675                 ) ?
 8676                 ( Math.abs(delta.x) / width + 1 )      // determine resistance level
 8677                 : 1 );                                 // no resistance if false
 8678             } else {
 8679               if(width * index - delta.x < 0) {               //We are trying scroll past left boundary
 8680                 delta.x = Math.min(delta.x, width * index);  //Set delta.x so we don't go past left screen
 8681               }
 8682               if(Math.abs(delta.x) > width * (slides.length - index - 1)){         //We are trying to scroll past right bondary
 8683                 delta.x = Math.max( -width * (slides.length - index - 1), delta.x);  //Set delta.x so we don't go past right screen
 8684               }
 8685             }
 8686
 8687            // translate 1:1
 8688            translate(index - 1, delta.x + slidePos[index - 1], 0);
 8689            translate(index, delta.x + slidePos[index], 0);
 8690            translate(index + 1, delta.x + slidePos[index + 1], 0);
 8691          }
 8692
 8693          options.onDrag && options.onDrag();
 8694        }
 8695
 8696      },
 8697      end: function() {
 8698
 8699        // measure duration
 8700        var duration = +new Date() - start.time;
 8701
 8702        // determine if slide attempt triggers next/prev slide
 8703        var isValidSlide =
 8704              Number(duration) < 250 &&         // if slide duration is less than 250ms
 8705              Math.abs(delta.x) > 20 ||         // and if slide amt is greater than 20px
 8706              Math.abs(delta.x) > width / 2;      // or if slide amt is greater than half the width
 8707
 8708        // determine if slide attempt is past start and end
 8709        var isPastBounds = (!index && delta.x > 0) ||      // if first slide and slide amt is greater than 0
 8710              (index == slides.length - 1 && delta.x < 0); // or if last slide and slide amt is less than 0
 8711
 8712        if (options.continuous) isPastBounds = false;
 8713
 8714        // determine direction of swipe (true:right, false:left)
 8715        var direction = delta.x < 0;
 8716
 8717        // if not scrolling vertically
 8718        if (!isScrolling) {
 8719
 8720          if (isValidSlide && !isPastBounds) {
 8721
 8722            if (direction) {
 8723
 8724              if (options.continuous) { // we need to get the next in this direction in place
 8725
 8726                move(circle(index - 1), -width, 0);
 8727                move(circle(index + 2), width, 0);
 8728
 8729              } else {
 8730                move(index - 1, -width, 0);
 8731              }
 8732
 8733              move(index, slidePos[index] - width, speed);
 8734              move(circle(index + 1), slidePos[circle(index + 1)] - width, speed);
 8735              index = circle(index + 1);
 8736
 8737            } else {
 8738              if (options.continuous) { // we need to get the next in this direction in place
 8739
 8740                move(circle(index + 1), width, 0);
 8741                move(circle(index - 2), -width, 0);
 8742
 8743              } else {
 8744                move(index + 1, width, 0);
 8745              }
 8746
 8747              move(index, slidePos[index] + width, speed);
 8748              move(circle(index - 1), slidePos[circle(index - 1)] + width, speed);
 8749              index = circle(index - 1);
 8750
 8751            }
 8752
 8753            options.callback && options.callback(index, slides[index]);
 8754
 8755          } else {
 8756
 8757            if (options.continuous) {
 8758
 8759              move(circle(index - 1), -width, speed);
 8760              move(index, 0, speed);
 8761              move(circle(index + 1), width, speed);
 8762
 8763            } else {
 8764
 8765              move(index - 1, -width, speed);
 8766              move(index, 0, speed);
 8767              move(index + 1, width, speed);
 8768            }
 8769
 8770          }
 8771
 8772        }
 8773
 8774        // kill touchmove and touchend event listeners until touchstart called again
 8775        element.removeEventListener(touchMoveEvent, events, false);
 8776        element.removeEventListener(mouseMoveEvent, events, false);
 8777
 8778        element.removeEventListener(touchEndEvent, events, false);
 8779        element.removeEventListener(mouseEndEvent, events, false);
 8780
 8781        document.removeEventListener(touchEndEvent, events, false);
 8782        document.removeEventListener(mouseEndEvent, events, false);
 8783
 8784        options.onDragEnd && options.onDragEnd();
 8785      },
 8786      transitionEnd: function(event) {
 8787
 8788        if (parseInt(event.target.getAttribute('data-index'), 10) == index) {
 8789
 8790          if (delay) begin();
 8791
 8792          options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
 8793
 8794        }
 8795
 8796      }
 8797
 8798    };
 8799
 8800    // Public API
 8801    this.update = function() {
 8802      setTimeout(setup);
 8803    };
 8804    this.setup = function() {
 8805      setup();
 8806    };
 8807
 8808    this.loop = function(value) {
 8809      if (arguments.length) options.continuous = !!value;
 8810      return options.continuous;
 8811    };
 8812
 8813    this.enableSlide = function(shouldEnable) {
 8814      if (arguments.length) {
 8815        this.slideIsDisabled = !shouldEnable;
 8816      }
 8817      return !this.slideIsDisabled;
 8818    };
 8819
 8820    this.slide = this.select = function(to, speed) {
 8821      // cancel slideshow
 8822      stop();
 8823
 8824      slide(to, speed);
 8825    };
 8826
 8827    this.prev = this.previous = function() {
 8828      // cancel slideshow
 8829      stop();
 8830
 8831      prev();
 8832    };
 8833
 8834    this.next = function() {
 8835      // cancel slideshow
 8836      stop();
 8837
 8838      next();
 8839    };
 8840
 8841    this.stop = function() {
 8842      // cancel slideshow
 8843      stop();
 8844    };
 8845
 8846    this.start = function() {
 8847      begin();
 8848    };
 8849
 8850    this.autoPlay = function(newDelay) {
 8851      if (!delay || delay < 0) {
 8852        stop();
 8853      } else {
 8854        delay = newDelay;
 8855        begin();
 8856      }
 8857    };
 8858
 8859    this.currentIndex = this.selected = function() {
 8860      // return current index position
 8861      return index;
 8862    };
 8863
 8864    this.slidesCount = this.count = function() {
 8865      // return total number of slides
 8866      return length;
 8867    };
 8868
 8869    this.kill = function() {
 8870      // cancel slideshow
 8871      stop();
 8872
 8873      // reset element
 8874      element.style.width = '';
 8875      element.style.left = '';
 8876
 8877      // reset slides so no refs are held on to
 8878      slides && (slides = []);
 8879
 8880      // removed event listeners
 8881      if (browser.addEventListener) {
 8882
 8883        // remove current event listeners
 8884        element.removeEventListener(touchStartEvent, events, false);
 8885        element.removeEventListener(mouseStartEvent, events, false);
 8886        element.removeEventListener('webkitTransitionEnd', events, false);
 8887        element.removeEventListener('msTransitionEnd', events, false);
 8888        element.removeEventListener('oTransitionEnd', events, false);
 8889        element.removeEventListener('otransitionend', events, false);
 8890        element.removeEventListener('transitionend', events, false);
 8891        window.removeEventListener('resize', events, false);
 8892
 8893      }
 8894      else {
 8895
 8896        window.onresize = null;
 8897
 8898      }
 8899    };
 8900
 8901    this.load = function() {
 8902      // trigger setup
 8903      setup();
 8904
 8905      // start auto slideshow if applicable
 8906      if (delay) begin();
 8907
 8908
 8909      // add event listeners
 8910      if (browser.addEventListener) {
 8911
 8912        // set touchstart event on element
 8913        element.addEventListener(touchStartEvent, events, false);
 8914        element.addEventListener(mouseStartEvent, events, false);
 8915
 8916        if (browser.transitions) {
 8917          element.addEventListener('webkitTransitionEnd', events, false);
 8918          element.addEventListener('msTransitionEnd', events, false);
 8919          element.addEventListener('oTransitionEnd', events, false);
 8920          element.addEventListener('otransitionend', events, false);
 8921          element.addEventListener('transitionend', events, false);
 8922        }
 8923
 8924        // set resize event on window
 8925        window.addEventListener('resize', events, false);
 8926
 8927      } else {
 8928
 8929        window.onresize = function () { setup(); }; // to play nice with old IE
 8930
 8931      }
 8932    };
 8933
 8934  }
 8935});
 8936
 8937})(ionic);
 8938
 8939/*eslint space-after-keywords: 0*/
 8940
 8941/**
 8942 * Swiper 3.2.7
 8943 * Most modern mobile touch slider and framework with hardware accelerated transitions
 8944 *
 8945 * http://www.idangero.us/swiper/
 8946 *
 8947 * Copyright 2015, Vladimir Kharlampidi
 8948 * The iDangero.us
 8949 * http://www.idangero.us/
 8950 *
 8951 * Licensed under MIT
 8952 *
 8953 * Released on: December 7, 2015
 8954 */
 8955(function () {
 8956    'use strict';
 8957    var $;
 8958    /*===========================
 8959    Swiper
 8960    ===========================*/
 8961    var Swiper = function (container, params, _scope, $compile) {
 8962
 8963        if (!(this instanceof Swiper)) return new Swiper(container, params);
 8964
 8965        var defaults = {
 8966            direction: 'horizontal',
 8967            touchEventsTarget: 'container',
 8968            initialSlide: 0,
 8969            speed: 300,
 8970            // autoplay
 8971            autoplay: false,
 8972            autoplayDisableOnInteraction: true,
 8973            // To support iOS's swipe-to-go-back gesture (when being used in-app, with UIWebView).
 8974            iOSEdgeSwipeDetection: false,
 8975            iOSEdgeSwipeThreshold: 20,
 8976            // Free mode
 8977            freeMode: false,
 8978            freeModeMomentum: true,
 8979            freeModeMomentumRatio: 1,
 8980            freeModeMomentumBounce: true,
 8981            freeModeMomentumBounceRatio: 1,
 8982            freeModeSticky: false,
 8983            freeModeMinimumVelocity: 0.02,
 8984            // Autoheight
 8985            autoHeight: false,
 8986            // Set wrapper width
 8987            setWrapperSize: false,
 8988            // Virtual Translate
 8989            virtualTranslate: false,
 8990            // Effects
 8991            effect: 'slide', // 'slide' or 'fade' or 'cube' or 'coverflow'
 8992            coverflow: {
 8993                rotate: 50,
 8994                stretch: 0,
 8995                depth: 100,
 8996                modifier: 1,
 8997                slideShadows : true
 8998            },
 8999            cube: {
 9000                slideShadows: true,
 9001                shadow: true,
 9002                shadowOffset: 20,
 9003                shadowScale: 0.94
 9004            },
 9005            fade: {
 9006                crossFade: false
 9007            },
 9008            // Parallax
 9009            parallax: false,
 9010            // Scrollbar
 9011            scrollbar: null,
 9012            scrollbarHide: true,
 9013            scrollbarDraggable: false,
 9014            scrollbarSnapOnRelease: false,
 9015            // Keyboard Mousewheel
 9016            keyboardControl: false,
 9017            mousewheelControl: false,
 9018            mousewheelReleaseOnEdges: false,
 9019            mousewheelInvert: false,
 9020            mousewheelForceToAxis: false,
 9021            mousewheelSensitivity: 1,
 9022            // Hash Navigation
 9023            hashnav: false,
 9024            // Breakpoints
 9025            breakpoints: undefined,
 9026            // Slides grid
 9027            spaceBetween: 0,
 9028            slidesPerView: 1,
 9029            slidesPerColumn: 1,
 9030            slidesPerColumnFill: 'column',
 9031            slidesPerGroup: 1,
 9032            centeredSlides: false,
 9033            slidesOffsetBefore: 0, // in px
 9034            slidesOffsetAfter: 0, // in px
 9035            // Round length
 9036            roundLengths: false,
 9037            // Touches
 9038            touchRatio: 1,
 9039            touchAngle: 45,
 9040            simulateTouch: true,
 9041            shortSwipes: true,
 9042            longSwipes: true,
 9043            longSwipesRatio: 0.5,
 9044            longSwipesMs: 300,
 9045            followFinger: true,
 9046            onlyExternal: false,
 9047            threshold: 0,
 9048            touchMoveStopPropagation: true,
 9049            // Pagination
 9050            pagination: null,
 9051            paginationElement: 'span',
 9052            paginationClickable: false,
 9053            paginationHide: false,
 9054            paginationBulletRender: null,
 9055            // Resistance
 9056            resistance: true,
 9057            resistanceRatio: 0.85,
 9058            // Next/prev buttons
 9059            nextButton: null,
 9060            prevButton: null,
 9061            // Progress
 9062            watchSlidesProgress: false,
 9063            watchSlidesVisibility: false,
 9064            // Cursor
 9065            grabCursor: false,
 9066            // Clicks
 9067            preventClicks: true,
 9068            preventClicksPropagation: true,
 9069            slideToClickedSlide: false,
 9070            // Lazy Loading
 9071            lazyLoading: false,
 9072            lazyLoadingInPrevNext: false,
 9073            lazyLoadingOnTransitionStart: false,
 9074            // Images
 9075            preloadImages: true,
 9076            updateOnImagesReady: true,
 9077            // loop
 9078            loop: false,
 9079            loopAdditionalSlides: 0,
 9080            loopedSlides: null,
 9081            // Control
 9082            control: undefined,
 9083            controlInverse: false,
 9084            controlBy: 'slide', //or 'container'
 9085            // Swiping/no swiping
 9086            allowSwipeToPrev: true,
 9087            allowSwipeToNext: true,
 9088            swipeHandler: null, //'.swipe-handler',
 9089            noSwiping: true,
 9090            noSwipingClass: 'swiper-no-swiping',
 9091            // NS
 9092            slideClass: 'swiper-slide',
 9093            slideActiveClass: 'swiper-slide-active',
 9094            slideVisibleClass: 'swiper-slide-visible',
 9095            slideDuplicateClass: 'swiper-slide-duplicate',
 9096            slideNextClass: 'swiper-slide-next',
 9097            slidePrevClass: 'swiper-slide-prev',
 9098            wrapperClass: 'swiper-wrapper',
 9099            bulletClass: 'swiper-pagination-bullet',
 9100            bulletActiveClass: 'swiper-pagination-bullet-active',
 9101            buttonDisabledClass: 'swiper-button-disabled',
 9102            paginationHiddenClass: 'swiper-pagination-hidden',
 9103            // Observer
 9104            observer: false,
 9105            observeParents: false,
 9106            // Accessibility
 9107            a11y: false,
 9108            prevSlideMessage: 'Previous slide',
 9109            nextSlideMessage: 'Next slide',
 9110            firstSlideMessage: 'This is the first slide',
 9111            lastSlideMessage: 'This is the last slide',
 9112            paginationBulletMessage: 'Go to slide {{index}}',
 9113            // Callbacks
 9114            runCallbacksOnInit: true
 9115            /*
 9116            Callbacks:
 9117            onInit: function (swiper)
 9118            onDestroy: function (swiper)
 9119            onClick: function (swiper, e)
 9120            onTap: function (swiper, e)
 9121            onDoubleTap: function (swiper, e)
 9122            onSliderMove: function (swiper, e)
 9123            onSlideChangeStart: function (swiper)
 9124            onSlideChangeEnd: function (swiper)
 9125            onTransitionStart: function (swiper)
 9126            onTransitionEnd: function (swiper)
 9127            onImagesReady: function (swiper)
 9128            onProgress: function (swiper, progress)
 9129            onTouchStart: function (swiper, e)
 9130            onTouchMove: function (swiper, e)
 9131            onTouchMoveOpposite: function (swiper, e)
 9132            onTouchEnd: function (swiper, e)
 9133            onReachBeginning: function (swiper)
 9134            onReachEnd: function (swiper)
 9135            onSetTransition: function (swiper, duration)
 9136            onSetTranslate: function (swiper, translate)
 9137            onAutoplayStart: function (swiper)
 9138            onAutoplayStop: function (swiper),
 9139            onLazyImageLoad: function (swiper, slide, image)
 9140            onLazyImageReady: function (swiper, slide, image)
 9141            */
 9142
 9143        };
 9144        var initialVirtualTranslate = params && params.virtualTranslate;
 9145
 9146        params = params || {};
 9147        var originalParams = {};
 9148        for (var param in params) {
 9149            if (typeof params[param] === 'object' && !(params[param].nodeType || params[param] === window || params[param] === document || (typeof Dom7 !== 'undefined' && params[param] instanceof Dom7) || (typeof jQuery !== 'undefined' && params[param] instanceof jQuery))) {
 9150                originalParams[param] = {};
 9151                for (var deepParam in params[param]) {
 9152                    originalParams[param][deepParam] = params[param][deepParam];
 9153                }
 9154            }
 9155            else {
 9156                originalParams[param] = params[param];
 9157            }
 9158        }
 9159        for (var def in defaults) {
 9160            if (typeof params[def] === 'undefined') {
 9161                params[def] = defaults[def];
 9162            }
 9163            else if (typeof params[def] === 'object') {
 9164                for (var deepDef in defaults[def]) {
 9165                    if (typeof params[def][deepDef] === 'undefined') {
 9166                        params[def][deepDef] = defaults[def][deepDef];
 9167                    }
 9168                }
 9169            }
 9170        }
 9171
 9172        // Swiper
 9173        var s = this;
 9174
 9175        // Params
 9176        s.params = params;
 9177        s.originalParams = originalParams;
 9178
 9179        // Classname
 9180        s.classNames = [];
 9181        /*=========================
 9182          Dom Library and plugins
 9183          ===========================*/
 9184        if (typeof $ !== 'undefined' && typeof Dom7 !== 'undefined'){
 9185            $ = Dom7;
 9186        }
 9187        if (typeof $ === 'undefined') {
 9188            if (typeof Dom7 === 'undefined') {
 9189                $ = window.Dom7 || window.Zepto || window.jQuery;
 9190            }
 9191            else {
 9192                $ = Dom7;
 9193            }
 9194            if (!$) return;
 9195        }
 9196        // Export it to Swiper instance
 9197        s.$ = $;
 9198
 9199        /*=========================
 9200          Breakpoints
 9201          ===========================*/
 9202        s.currentBreakpoint = undefined;
 9203        s.getActiveBreakpoint = function () {
 9204            //Get breakpoint for window width
 9205            if (!s.params.breakpoints) return false;
 9206            var breakpoint = false;
 9207            var points = [], point;
 9208            for ( point in s.params.breakpoints ) {
 9209                if (s.params.breakpoints.hasOwnProperty(point)) {
 9210                    points.push(point);
 9211                }
 9212            }
 9213            points.sort(function (a, b) {
 9214                return parseInt(a, 10) > parseInt(b, 10);
 9215            });
 9216            for (var i = 0; i < points.length; i++) {
 9217                point = points[i];
 9218                if (point >= window.innerWidth && !breakpoint) {
 9219                    breakpoint = point;
 9220                }
 9221            }
 9222            return breakpoint || 'max';
 9223        };
 9224        s.setBreakpoint = function () {
 9225            //Set breakpoint for window width and update parameters
 9226            var breakpoint = s.getActiveBreakpoint();
 9227            if (breakpoint && s.currentBreakpoint !== breakpoint) {
 9228                var breakPointsParams = breakpoint in s.params.breakpoints ? s.params.breakpoints[breakpoint] : s.originalParams;
 9229                for ( var param in breakPointsParams ) {
 9230                    s.params[param] = breakPointsParams[param];
 9231                }
 9232                s.currentBreakpoint = breakpoint;
 9233            }
 9234        };
 9235        // Set breakpoint on load
 9236        if (s.params.breakpoints) {
 9237            s.setBreakpoint();
 9238        }
 9239
 9240        /*=========================
 9241          Preparation - Define Container, Wrapper and Pagination
 9242          ===========================*/
 9243        s.container = $(container);
 9244        if (s.container.length === 0) return;
 9245        if (s.container.length > 1) {
 9246            s.container.each(function () {
 9247                new Swiper(this, params);
 9248            });
 9249            return;
 9250        }
 9251
 9252        // Save instance in container HTML Element and in data
 9253        s.container[0].swiper = s;
 9254        s.container.data('swiper', s);
 9255
 9256        s.classNames.push('swiper-container-' + s.params.direction);
 9257
 9258        if (s.params.freeMode) {
 9259            s.classNames.push('swiper-container-free-mode');
 9260        }
 9261        if (!s.support.flexbox) {
 9262            s.classNames.push('swiper-container-no-flexbox');
 9263            s.params.slidesPerColumn = 1;
 9264        }
 9265        if (s.params.autoHeight) {
 9266            s.classNames.push('swiper-container-autoheight');
 9267        }
 9268        // Enable slides progress when required
 9269        if (s.params.parallax || s.params.watchSlidesVisibility) {
 9270            s.params.watchSlidesProgress = true;
 9271        }
 9272        // Coverflow / 3D
 9273        if (['cube', 'coverflow'].indexOf(s.params.effect) >= 0) {
 9274            if (s.support.transforms3d) {
 9275                s.params.watchSlidesProgress = true;
 9276                s.classNames.push('swiper-container-3d');
 9277            }
 9278            else {
 9279                s.params.effect = 'slide';
 9280            }
 9281        }
 9282        if (s.params.effect !== 'slide') {
 9283            s.classNames.push('swiper-container-' + s.params.effect);
 9284        }
 9285        if (s.params.effect === 'cube') {
 9286            s.params.resistanceRatio = 0;
 9287            s.params.slidesPerView = 1;
 9288            s.params.slidesPerColumn = 1;
 9289            s.params.slidesPerGroup = 1;
 9290            s.params.centeredSlides = false;
 9291            s.params.spaceBetween = 0;
 9292            s.params.virtualTranslate = true;
 9293            s.params.setWrapperSize = false;
 9294        }
 9295        if (s.params.effect === 'fade') {
 9296            s.params.slidesPerView = 1;
 9297            s.params.slidesPerColumn = 1;
 9298            s.params.slidesPerGroup = 1;
 9299            s.params.watchSlidesProgress = true;
 9300            s.params.spaceBetween = 0;
 9301            if (typeof initialVirtualTranslate === 'undefined') {
 9302                s.params.virtualTranslate = true;
 9303            }
 9304        }
 9305
 9306        // Grab Cursor
 9307        if (s.params.grabCursor && s.support.touch) {
 9308            s.params.grabCursor = false;
 9309        }
 9310
 9311        // Wrapper
 9312        s.wrapper = s.container.children('.' + s.params.wrapperClass);
 9313
 9314        // Pagination
 9315        if (s.params.pagination) {
 9316            s.paginationContainer = $(s.params.pagination);
 9317            if (s.params.paginationClickable) {
 9318                s.paginationContainer.addClass('swiper-pagination-clickable');
 9319            }
 9320        }
 9321
 9322        // Is Horizontal
 9323        function isH() {
 9324            return s.params.direction === 'horizontal';
 9325        }
 9326
 9327        // RTL
 9328        s.rtl = isH() && (s.container[0].dir.toLowerCase() === 'rtl' || s.container.css('direction') === 'rtl');
 9329        if (s.rtl) {
 9330            s.classNames.push('swiper-container-rtl');
 9331        }
 9332
 9333        // Wrong RTL support
 9334        if (s.rtl) {
 9335            s.wrongRTL = s.wrapper.css('display') === '-webkit-box';
 9336        }
 9337
 9338        // Columns
 9339        if (s.params.slidesPerColumn > 1) {
 9340            s.classNames.push('swiper-container-multirow');
 9341        }
 9342
 9343        // Check for Android
 9344        if (s.device.android) {
 9345            s.classNames.push('swiper-container-android');
 9346        }
 9347
 9348        // Add classes
 9349        s.container.addClass(s.classNames.join(' '));
 9350
 9351        // Translate
 9352        s.translate = 0;
 9353
 9354        // Progress
 9355        s.progress = 0;
 9356
 9357        // Velocity
 9358        s.velocity = 0;
 9359
 9360        /*=========================
 9361          Locks, unlocks
 9362          ===========================*/
 9363        s.lockSwipeToNext = function () {
 9364            s.params.allowSwipeToNext = false;
 9365        };
 9366        s.lockSwipeToPrev = function () {
 9367            s.params.allowSwipeToPrev = false;
 9368        };
 9369        s.lockSwipes = function () {
 9370            s.params.allowSwipeToNext = s.params.allowSwipeToPrev = false;
 9371        };
 9372        s.unlockSwipeToNext = function () {
 9373            s.params.allowSwipeToNext = true;
 9374        };
 9375        s.unlockSwipeToPrev = function () {
 9376            s.params.allowSwipeToPrev = true;
 9377        };
 9378        s.unlockSwipes = function () {
 9379            s.params.allowSwipeToNext = s.params.allowSwipeToPrev = true;
 9380        };
 9381
 9382        /*=========================
 9383          Round helper
 9384          ===========================*/
 9385        function round(a) {
 9386            return Math.floor(a);
 9387        }
 9388        /*=========================
 9389          Set grab cursor
 9390          ===========================*/
 9391        if (s.params.grabCursor) {
 9392            s.container[0].style.cursor = 'move';
 9393            s.container[0].style.cursor = '-webkit-grab';
 9394            s.container[0].style.cursor = '-moz-grab';
 9395            s.container[0].style.cursor = 'grab';
 9396        }
 9397        /*=========================
 9398          Update on Images Ready
 9399          ===========================*/
 9400        s.imagesToLoad = [];
 9401        s.imagesLoaded = 0;
 9402
 9403        s.loadImage = function (imgElement, src, srcset, checkForComplete, callback) {
 9404            var image;
 9405            function onReady () {
 9406                if (callback) callback();
 9407            }
 9408            if (!imgElement.complete || !checkForComplete) {
 9409                if (src) {
 9410                    image = new window.Image();
 9411                    image.onload = onReady;
 9412                    image.onerror = onReady;
 9413                    if (srcset) {
 9414                        image.srcset = srcset;
 9415                    }
 9416                    if (src) {
 9417                        image.src = src;
 9418                    }
 9419                } else {
 9420                    onReady();
 9421                }
 9422
 9423            } else {//image already loaded...
 9424                onReady();
 9425            }
 9426        };
 9427        s.preloadImages = function () {
 9428            s.imagesToLoad = s.container.find('img');
 9429            function _onReady() {
 9430                if (typeof s === 'undefined' || s === null) return;
 9431                if (s.imagesLoaded !== undefined) s.imagesLoaded++;
 9432                if (s.imagesLoaded === s.imagesToLoad.length) {
 9433                    if (s.params.updateOnImagesReady) s.update();
 9434                    s.emit('onImagesReady', s);
 9435                }
 9436            }
 9437            for (var i = 0; i < s.imagesToLoad.length; i++) {
 9438                s.loadImage(s.imagesToLoad[i], (s.imagesToLoad[i].currentSrc || s.imagesToLoad[i].getAttribute('src')), (s.imagesToLoad[i].srcset || s.imagesToLoad[i].getAttribute('srcset')), true, _onReady);
 9439            }
 9440        };
 9441
 9442        /*=========================
 9443          Autoplay
 9444          ===========================*/
 9445        s.autoplayTimeoutId = undefined;
 9446        s.autoplaying = false;
 9447        s.autoplayPaused = false;
 9448        function autoplay() {
 9449            s.autoplayTimeoutId = setTimeout(function () {
 9450                if (s.params.loop) {
 9451                    s.fixLoop();
 9452                    s._slideNext();
 9453                }
 9454                else {
 9455                    if (!s.isEnd) {
 9456                        s._slideNext();
 9457                    }
 9458                    else {
 9459                        if (!params.autoplayStopOnLast) {
 9460                            s._slideTo(0);
 9461                        }
 9462                        else {
 9463                            s.stopAutoplay();
 9464                        }
 9465                    }
 9466                }
 9467            }, s.params.autoplay);
 9468        }
 9469        s.startAutoplay = function () {
 9470            if (typeof s.autoplayTimeoutId !== 'undefined') return false;
 9471            if (!s.params.autoplay) return false;
 9472            if (s.autoplaying) return false;
 9473            s.autoplaying = true;
 9474            s.emit('onAutoplayStart', s);
 9475            autoplay();
 9476        };
 9477        s.stopAutoplay = function (internal) {
 9478            if (!s.autoplayTimeoutId) return;
 9479            if (s.autoplayTimeoutId) clearTimeout(s.autoplayTimeoutId);
 9480            s.autoplaying = false;
 9481            s.autoplayTimeoutId = undefined;
 9482            s.emit('onAutoplayStop', s);
 9483        };
 9484        s.pauseAutoplay = function (speed) {
 9485            if (s.autoplayPaused) return;
 9486            if (s.autoplayTimeoutId) clearTimeout(s.autoplayTimeoutId);
 9487            s.autoplayPaused = true;
 9488            if (speed === 0) {
 9489                s.autoplayPaused = false;
 9490                autoplay();
 9491            }
 9492            else {
 9493                s.wrapper.transitionEnd(function () {
 9494                    if (!s) return;
 9495                    s.autoplayPaused = false;
 9496                    if (!s.autoplaying) {
 9497                        s.stopAutoplay();
 9498                    }
 9499                    else {
 9500                        autoplay();
 9501                    }
 9502                });
 9503            }
 9504        };
 9505        /*=========================
 9506          Min/Max Translate
 9507          ===========================*/
 9508        s.minTranslate = function () {
 9509            return (-s.snapGrid[0]);
 9510        };
 9511        s.maxTranslate = function () {
 9512            return (-s.snapGrid[s.snapGrid.length - 1]);
 9513        };
 9514        /*=========================
 9515          Slider/slides sizes
 9516          ===========================*/
 9517        s.updateAutoHeight = function () {
 9518            // Update Height
 9519            var newHeight = s.slides.eq(s.activeIndex)[0].offsetHeight;
 9520            if (newHeight) s.wrapper.css('height', s.slides.eq(s.activeIndex)[0].offsetHeight + 'px');
 9521        };
 9522        s.updateContainerSize = function () {
 9523            var width, height;
 9524            if (typeof s.params.width !== 'undefined') {
 9525                width = s.params.width;
 9526            }
 9527            else {
 9528                width = s.container[0].clientWidth;
 9529            }
 9530            if (typeof s.params.height !== 'undefined') {
 9531                height = s.params.height;
 9532            }
 9533            else {
 9534                height = s.container[0].clientHeight;
 9535            }
 9536            if (width === 0 && isH() || height === 0 && !isH()) {
 9537                return;
 9538            }
 9539
 9540            //Subtract paddings
 9541            width = width - parseInt(s.container.css('padding-left'), 10) - parseInt(s.container.css('padding-right'), 10);
 9542            height = height - parseInt(s.container.css('padding-top'), 10) - parseInt(s.container.css('padding-bottom'), 10);
 9543
 9544            // Store values
 9545            s.width = width;
 9546            s.height = height;
 9547            s.size = isH() ? s.width : s.height;
 9548        };
 9549
 9550        s.updateSlidesSize = function () {
 9551            s.slides = s.wrapper.children('.' + s.params.slideClass);
 9552            s.snapGrid = [];
 9553            s.slidesGrid = [];
 9554            s.slidesSizesGrid = [];
 9555
 9556            var spaceBetween = s.params.spaceBetween,
 9557                slidePosition = -s.params.slidesOffsetBefore,
 9558                i,
 9559                prevSlideSize = 0,
 9560                index = 0;
 9561            if (typeof spaceBetween === 'string' && spaceBetween.indexOf('%') >= 0) {
 9562                spaceBetween = parseFloat(spaceBetween.replace('%', '')) / 100 * s.size;
 9563            }
 9564
 9565            s.virtualSize = -spaceBetween;
 9566            // reset margins
 9567            if (s.rtl) s.slides.css({marginLeft: '', marginTop: ''});
 9568            else s.slides.css({marginRight: '', marginBottom: ''});
 9569
 9570            var slidesNumberEvenToRows;
 9571            if (s.params.slidesPerColumn > 1) {
 9572                if (Math.floor(s.slides.length / s.params.slidesPerColumn) === s.slides.length / s.params.slidesPerColumn) {
 9573                    slidesNumberEvenToRows = s.slides.length;
 9574                }
 9575                else {
 9576                    slidesNumberEvenToRows = Math.ceil(s.slides.length / s.params.slidesPerColumn) * s.params.slidesPerColumn;
 9577                }
 9578                if (s.params.slidesPerView !== 'auto' && s.params.slidesPerColumnFill === 'row') {
 9579                    slidesNumberEvenToRows = Math.max(slidesNumberEvenToRows, s.params.slidesPerView * s.params.slidesPerColumn);
 9580                }
 9581            }
 9582
 9583            // Calc slides
 9584            var slideSize;
 9585            var slidesPerColumn = s.params.slidesPerColumn;
 9586            var slidesPerRow = slidesNumberEvenToRows / slidesPerColumn;
 9587            var numFullColumns = slidesPerRow - (s.params.slidesPerColumn * slidesPerRow - s.slides.length);
 9588            for (i = 0; i < s.slides.length; i++) {
 9589                slideSize = 0;
 9590                var slide = s.slides.eq(i);
 9591                if (s.params.slidesPerColumn > 1) {
 9592                    // Set slides order
 9593                    var newSlideOrderIndex;
 9594                    var column, row;
 9595                    if (s.params.slidesPerColumnFill === 'column') {
 9596                        column = Math.floor(i / slidesPerColumn);
 9597                        row = i - column * slidesPerColumn;
 9598                        if (column > numFullColumns || (column === numFullColumns && row === slidesPerColumn-1)) {
 9599                            if (++row >= slidesPerColumn) {
 9600                                row = 0;
 9601                                column++;
 9602                            }
 9603                        }
 9604                        newSlideOrderIndex = column + row * slidesNumberEvenToRows / slidesPerColumn;
 9605                        slide
 9606                            .css({
 9607                                '-webkit-box-ordinal-group': newSlideOrderIndex,
 9608                                '-moz-box-ordinal-group': newSlideOrderIndex,
 9609                                '-ms-flex-order': newSlideOrderIndex,
 9610                                '-webkit-order': newSlideOrderIndex,
 9611                                'order': newSlideOrderIndex
 9612                            });
 9613                    }
 9614                    else {
 9615                        row = Math.floor(i / slidesPerRow);
 9616                        column = i - row * slidesPerRow;
 9617                    }
 9618                    slide
 9619                        .css({
 9620                            'margin-top': (row !== 0 && s.params.spaceBetween) && (s.params.spaceBetween + 'px')
 9621                        })
 9622                        .attr('data-swiper-column', column)
 9623                        .attr('data-swiper-row', row);
 9624
 9625                }
 9626                if (slide.css('display') === 'none') continue;
 9627                if (s.params.slidesPerView === 'auto') {
 9628                    slideSize = isH() ? slide.outerWidth(true) : slide.outerHeight(true);
 9629                    if (s.params.roundLengths) slideSize = round(slideSize);
 9630                }
 9631                else {
 9632                    slideSize = (s.size - (s.params.slidesPerView - 1) * spaceBetween) / s.params.slidesPerView;
 9633                    if (s.params.roundLengths) slideSize = round(slideSize);
 9634
 9635                    if (isH()) {
 9636                        s.slides[i].style.width = slideSize + 'px';
 9637                    }
 9638                    else {
 9639                        s.slides[i].style.height = slideSize + 'px';
 9640                    }
 9641                }
 9642                s.slides[i].swiperSlideSize = slideSize;
 9643                s.slidesSizesGrid.push(slideSize);
 9644
 9645
 9646                if (s.params.centeredSlides) {
 9647                    slidePosition = slidePosition + slideSize / 2 + prevSlideSize / 2 + spaceBetween;
 9648                    if (i === 0) slidePosition = slidePosition - s.size / 2 - spaceBetween;
 9649                    if (Math.abs(slidePosition) < 1 / 1000) slidePosition = 0;
 9650                    if ((index) % s.params.slidesPerGroup === 0) s.snapGrid.push(slidePosition);
 9651                    s.slidesGrid.push(slidePosition);
 9652                }
 9653                else {
 9654                    if ((index) % s.params.slidesPerGroup === 0) s.snapGrid.push(slidePosition);
 9655                    s.slidesGrid.push(slidePosition);
 9656                    slidePosition = slidePosition + slideSize + spaceBetween;
 9657                }
 9658
 9659                s.virtualSize += slideSize + spaceBetween;
 9660
 9661                prevSlideSize = slideSize;
 9662
 9663                index ++;
 9664            }
 9665            s.virtualSize = Math.max(s.virtualSize, s.size) + s.params.slidesOffsetAfter;
 9666            var newSlidesGrid;
 9667
 9668            if (
 9669                s.rtl && s.wrongRTL && (s.params.effect === 'slide' || s.params.effect === 'coverflow')) {
 9670                s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'});
 9671            }
 9672            if (!s.support.flexbox || s.params.setWrapperSize) {
 9673                if (isH()) s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'});
 9674                else s.wrapper.css({height: s.virtualSize + s.params.spaceBetween + 'px'});
 9675            }
 9676
 9677            if (s.params.slidesPerColumn > 1) {
 9678                s.virtualSize = (slideSize + s.params.spaceBetween) * slidesNumberEvenToRows;
 9679                s.virtualSize = Math.ceil(s.virtualSize / s.params.slidesPerColumn) - s.params.spaceBetween;
 9680                s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'});
 9681                if (s.params.centeredSlides) {
 9682                    newSlidesGrid = [];
 9683                    for (i = 0; i < s.snapGrid.length; i++) {
 9684                        if (s.snapGrid[i] < s.virtualSize + s.snapGrid[0]) newSlidesGrid.push(s.snapGrid[i]);
 9685                    }
 9686                    s.snapGrid = newSlidesGrid;
 9687                }
 9688            }
 9689
 9690            // Remove last grid elements depending on width
 9691            if (!s.params.centeredSlides) {
 9692                newSlidesGrid = [];
 9693                for (i = 0; i < s.snapGrid.length; i++) {
 9694                    if (s.snapGrid[i] <= s.virtualSize - s.size) {
 9695                        newSlidesGrid.push(s.snapGrid[i]);
 9696                    }
 9697                }
 9698                s.snapGrid = newSlidesGrid;
 9699                if (Math.floor(s.virtualSize - s.size) > Math.floor(s.snapGrid[s.snapGrid.length - 1])) {
 9700                    s.snapGrid.push(s.virtualSize - s.size);
 9701                }
 9702            }
 9703            if (s.snapGrid.length === 0) s.snapGrid = [0];
 9704
 9705            if (s.params.spaceBetween !== 0) {
 9706                if (isH()) {
 9707                    if (s.rtl) s.slides.css({marginLeft: spaceBetween + 'px'});
 9708                    else s.slides.css({marginRight: spaceBetween + 'px'});
 9709                }
 9710                else s.slides.css({marginBottom: spaceBetween + 'px'});
 9711            }
 9712            if (s.params.watchSlidesProgress) {
 9713                s.updateSlidesOffset();
 9714            }
 9715        };
 9716        s.updateSlidesOffset = function () {
 9717            for (var i = 0; i < s.slides.length; i++) {
 9718                s.slides[i].swiperSlideOffset = isH() ? s.slides[i].offsetLeft : s.slides[i].offsetTop;
 9719            }
 9720        };
 9721
 9722        /*=========================
 9723          Slider/slides progress
 9724          ===========================*/
 9725        s.updateSlidesProgress = function (translate) {
 9726            if (typeof translate === 'undefined') {
 9727                translate = s.translate || 0;
 9728            }
 9729            if (s.slides.length === 0) return;
 9730            if (typeof s.slides[0].swiperSlideOffset === 'undefined') s.updateSlidesOffset();
 9731
 9732            var offsetCenter = -translate;
 9733            if (s.rtl) offsetCenter = translate;
 9734
 9735            // Visible Slides
 9736            s.slides.removeClass(s.params.slideVisibleClass);
 9737            for (var i = 0; i < s.slides.length; i++) {
 9738                var slide = s.slides[i];
 9739                var slideProgress = (offsetCenter - slide.swiperSlideOffset) / (slide.swiperSlideSize + s.params.spaceBetween);
 9740                if (s.params.watchSlidesVisibility) {
 9741                    var slideBefore = -(offsetCenter - slide.swiperSlideOffset);
 9742                    var slideAfter = slideBefore + s.slidesSizesGrid[i];
 9743                    var isVisible =
 9744                        (slideBefore >= 0 && slideBefore < s.size) ||
 9745                        (slideAfter > 0 && slideAfter <= s.size) ||
 9746                        (slideBefore <= 0 && slideAfter >= s.size);
 9747                    if (isVisible) {
 9748                        s.slides.eq(i).addClass(s.params.slideVisibleClass);
 9749                    }
 9750                }
 9751                slide.progress = s.rtl ? -slideProgress : slideProgress;
 9752            }
 9753        };
 9754        s.updateProgress = function (translate) {
 9755            if (typeof translate === 'undefined') {
 9756                translate = s.translate || 0;
 9757            }
 9758            var translatesDiff = s.maxTranslate() - s.minTranslate();
 9759            var wasBeginning = s.isBeginning;
 9760            var wasEnd = s.isEnd;
 9761            if (translatesDiff === 0) {
 9762                s.progress = 0;
 9763                s.isBeginning = s.isEnd = true;
 9764            }
 9765            else {
 9766                s.progress = (translate - s.minTranslate()) / (translatesDiff);
 9767                s.isBeginning = s.progress <= 0;
 9768                s.isEnd = s.progress >= 1;
 9769            }
 9770            if (s.isBeginning && !wasBeginning) s.emit('onReachBeginning', s);
 9771            if (s.isEnd && !wasEnd) s.emit('onReachEnd', s);
 9772
 9773            if (s.params.watchSlidesProgress) s.updateSlidesProgress(translate);
 9774            s.emit('onProgress', s, s.progress);
 9775        };
 9776        s.updateActiveIndex = function () {
 9777            var translate = s.rtl ? s.translate : -s.translate;
 9778            var newActiveIndex, i, snapIndex;
 9779            for (i = 0; i < s.slidesGrid.length; i ++) {
 9780                if (typeof s.slidesGrid[i + 1] !== 'undefined') {
 9781                    if (translate >= s.slidesGrid[i] && translate < s.slidesGrid[i + 1] - (s.slidesGrid[i + 1] - s.slidesGrid[i]) / 2) {
 9782                        newActiveIndex = i;
 9783                    }
 9784                    else if (translate >= s.slidesGrid[i] && translate < s.slidesGrid[i + 1]) {
 9785                        newActiveIndex = i + 1;
 9786                    }
 9787                }
 9788                else {
 9789                    if (translate >= s.slidesGrid[i]) {
 9790                        newActiveIndex = i;
 9791                    }
 9792                }
 9793            }
 9794            // Normalize slideIndex
 9795            if (newActiveIndex < 0 || typeof newActiveIndex === 'undefined') newActiveIndex = 0;
 9796            // for (i = 0; i < s.slidesGrid.length; i++) {
 9797                // if (- translate >= s.slidesGrid[i]) {
 9798                    // newActiveIndex = i;
 9799                // }
 9800            // }
 9801            snapIndex = Math.floor(newActiveIndex / s.params.slidesPerGroup);
 9802            if (snapIndex >= s.snapGrid.length) snapIndex = s.snapGrid.length - 1;
 9803
 9804            if (newActiveIndex === s.activeIndex) {
 9805                return;
 9806            }
 9807            s.snapIndex = snapIndex;
 9808            s.previousIndex = s.activeIndex;
 9809            s.activeIndex = newActiveIndex;
 9810            s.updateClasses();
 9811        };
 9812
 9813        /*=========================
 9814          Classes
 9815          ===========================*/
 9816        s.updateClasses = function () {
 9817            s.slides.removeClass(s.params.slideActiveClass + ' ' + s.params.slideNextClass + ' ' + s.params.slidePrevClass);
 9818            var activeSlide = s.slides.eq(s.activeIndex);
 9819            // Active classes
 9820            activeSlide.addClass(s.params.slideActiveClass);
 9821            activeSlide.next('.' + s.params.slideClass).addClass(s.params.slideNextClass);
 9822            activeSlide.prev('.' + s.params.slideClass).addClass(s.params.slidePrevClass);
 9823
 9824            // Pagination
 9825            if (s.bullets && s.bullets.length > 0) {
 9826                s.bullets.removeClass(s.params.bulletActiveClass);
 9827                var bulletIndex;
 9828                if (s.params.loop) {
 9829                    bulletIndex = Math.ceil(s.activeIndex - s.loopedSlides)/s.params.slidesPerGroup;
 9830                    if (bulletIndex > s.slides.length - 1 - s.loopedSlides * 2) {
 9831                        bulletIndex = bulletIndex - (s.slides.length - s.loopedSlides * 2);
 9832                    }
 9833                    if (bulletIndex > s.bullets.length - 1) bulletIndex = bulletIndex - s.bullets.length;
 9834                }
 9835                else {
 9836                    if (typeof s.snapIndex !== 'undefined') {
 9837                        bulletIndex = s.snapIndex;
 9838                    }
 9839                    else {
 9840                        bulletIndex = s.activeIndex || 0;
 9841                    }
 9842                }
 9843                if (s.paginationContainer.length > 1) {
 9844                    s.bullets.each(function () {
 9845                        if ($(this).index() === bulletIndex) $(this).addClass(s.params.bulletActiveClass);
 9846                    });
 9847                }
 9848                else {
 9849                    s.bullets.eq(bulletIndex).addClass(s.params.bulletActiveClass);
 9850                }
 9851            }
 9852
 9853            // Next/active buttons
 9854            if (!s.params.loop) {
 9855                if (s.params.prevButton) {
 9856                    if (s.isBeginning) {
 9857                        $(s.params.prevButton).addClass(s.params.buttonDisabledClass);
 9858                        if (s.params.a11y && s.a11y) s.a11y.disable($(s.params.prevButton));
 9859                    }
 9860                    else {
 9861                        $(s.params.prevButton).removeClass(s.params.buttonDisabledClass);
 9862                        if (s.params.a11y && s.a11y) s.a11y.enable($(s.params.prevButton));
 9863                    }
 9864                }
 9865                if (s.params.nextButton) {
 9866                    if (s.isEnd) {
 9867                        $(s.params.nextButton).addClass(s.params.buttonDisabledClass);
 9868                        if (s.params.a11y && s.a11y) s.a11y.disable($(s.params.nextButton));
 9869                    }
 9870                    else {
 9871                        $(s.params.nextButton).removeClass(s.params.buttonDisabledClass);
 9872                        if (s.params.a11y && s.a11y) s.a11y.enable($(s.params.nextButton));
 9873                    }
 9874                }
 9875            }
 9876        };
 9877
 9878        /*=========================
 9879          Pagination
 9880          ===========================*/
 9881        s.updatePagination = function () {
 9882            if (!s.params.pagination) return;
 9883            if (s.paginationContainer && s.paginationContainer.length > 0) {
 9884                var bulletsHTML = '';
 9885                var numberOfBullets = s.params.loop ? Math.ceil((s.slides.length - s.loopedSlides * 2) / s.params.slidesPerGroup) : s.snapGrid.length;
 9886                for (var i = 0; i < numberOfBullets; i++) {
 9887                    if (s.params.paginationBulletRender) {
 9888                        bulletsHTML += s.params.paginationBulletRender(i, s.params.bulletClass);
 9889                    }
 9890                    else {
 9891                        bulletsHTML += '<' + s.params.paginationElement+' class="' + s.params.bulletClass + '"></' + s.params.paginationElement + '>';
 9892                    }
 9893                }
 9894                s.paginationContainer.html(bulletsHTML);
 9895                s.bullets = s.paginationContainer.find('.' + s.params.bulletClass);
 9896                if (s.params.paginationClickable && s.params.a11y && s.a11y) {
 9897                    s.a11y.initPagination();
 9898                }
 9899            }
 9900        };
 9901        /*=========================
 9902          Common update method
 9903          ===========================*/
 9904        s.update = function (updateTranslate) {
 9905            s.updateContainerSize();
 9906            s.updateSlidesSize();
 9907            s.updateProgress();
 9908            s.updatePagination();
 9909            s.updateClasses();
 9910            if (s.params.scrollbar && s.scrollbar) {
 9911                s.scrollbar.set();
 9912            }
 9913            function forceSetTranslate() {
 9914                newTranslate = Math.min(Math.max(s.translate, s.maxTranslate()), s.minTranslate());
 9915                s.setWrapperTranslate(newTranslate);
 9916                s.updateActiveIndex();
 9917                s.updateClasses();
 9918            }
 9919            if (updateTranslate) {
 9920                var translated, newTranslate;
 9921                if (s.controller && s.controller.spline) {
 9922                    s.controller.spline = undefined;
 9923                }
 9924                if (s.params.freeMode) {
 9925                    forceSetTranslate();
 9926                    if (s.params.autoHeight) {
 9927                        s.updateAutoHeight();
 9928                    }
 9929                }
 9930                else {
 9931                    if ((s.params.slidesPerView === 'auto' || s.params.slidesPerView > 1) && s.isEnd && !s.params.centeredSlides) {
 9932                        translated = s.slideTo(s.slides.length - 1, 0, false, true);
 9933                    }
 9934                    else {
 9935                        translated = s.slideTo(s.activeIndex, 0, false, true);
 9936                    }
 9937                    if (!translated) {
 9938                        forceSetTranslate();
 9939                    }
 9940                }
 9941            }
 9942            else if (s.params.autoHeight) {
 9943                s.updateAutoHeight();
 9944            }
 9945        };
 9946
 9947        /*=========================
 9948          Resize Handler
 9949          ===========================*/
 9950        s.onResize = function (forceUpdatePagination) {
 9951            //Breakpoints
 9952            if (s.params.breakpoints) {
 9953                s.setBreakpoint();
 9954            }
 9955
 9956            // Disable locks on resize
 9957            var allowSwipeToPrev = s.params.allowSwipeToPrev;
 9958            var allowSwipeToNext = s.params.allowSwipeToNext;
 9959            s.params.allowSwipeToPrev = s.params.allowSwipeToNext = true;
 9960
 9961            s.updateContainerSize();
 9962            s.updateSlidesSize();
 9963            if (s.params.slidesPerView === 'auto' || s.params.freeMode || forceUpdatePagination) s.updatePagination();
 9964            if (s.params.scrollbar && s.scrollbar) {
 9965                s.scrollbar.set();
 9966            }
 9967            if (s.controller && s.controller.spline) {
 9968                s.controller.spline = undefined;
 9969            }
 9970            if (s.params.freeMode) {
 9971                var newTranslate = Math.min(Math.max(s.translate, s.maxTranslate()), s.minTranslate());
 9972                s.setWrapperTranslate(newTranslate);
 9973                s.updateActiveIndex();
 9974                s.updateClasses();
 9975
 9976                if (s.params.autoHeight) {
 9977                    s.updateAutoHeight();
 9978                }
 9979            }
 9980            else {
 9981                s.updateClasses();
 9982                if ((s.params.slidesPerView === 'auto' || s.params.slidesPerView > 1) && s.isEnd && !s.params.centeredSlides) {
 9983                    s.slideTo(s.slides.length - 1, 0, false, true);
 9984                }
 9985                else {
 9986                    s.slideTo(s.activeIndex, 0, false, true);
 9987                }
 9988            }
 9989            // Return locks after resize
 9990            s.params.allowSwipeToPrev = allowSwipeToPrev;
 9991            s.params.allowSwipeToNext = allowSwipeToNext;
 9992        };
 9993
 9994        /*=========================
 9995          Events
 9996          ===========================*/
 9997
 9998        //Define Touch Events
 9999        var desktopEvents = ['mousedown', 'mousemove', 'mouseup'];
10000        if (window.navigator.pointerEnabled) desktopEvents = ['pointerdown', 'pointermove', 'pointerup'];
10001        else if (window.navigator.msPointerEnabled) desktopEvents = ['MSPointerDown', 'MSPointerMove', 'MSPointerUp'];
10002        s.touchEvents = {
10003            start : s.support.touch || !s.params.simulateTouch  ? 'touchstart' : desktopEvents[0],
10004            move : s.support.touch || !s.params.simulateTouch ? 'touchmove' : desktopEvents[1],
10005            end : s.support.touch || !s.params.simulateTouch ? 'touchend' : desktopEvents[2]
10006        };
10007
10008
10009        // WP8 Touch Events Fix
10010        if (window.navigator.pointerEnabled || window.navigator.msPointerEnabled) {
10011            (s.params.touchEventsTarget === 'container' ? s.container : s.wrapper).addClass('swiper-wp8-' + s.params.direction);
10012        }
10013
10014        // Attach/detach events
10015        s.initEvents = function (detach) {
10016            var actionDom = detach ? 'off' : 'on';
10017            var action = detach ? 'removeEventListener' : 'addEventListener';
10018            var touchEventsTarget = s.params.touchEventsTarget === 'container' ? s.container[0] : s.wrapper[0];
10019            var target = s.support.touch ? touchEventsTarget : document;
10020
10021            var moveCapture = s.params.nested ? true : false;
10022
10023            //Touch Events
10024            if (s.browser.ie) {
10025                touchEventsTarget[action](s.touchEvents.start, s.onTouchStart, false);
10026                target[action](s.touchEvents.move, s.onTouchMove, moveCapture);
10027                target[action](s.touchEvents.end, s.onTouchEnd, false);
10028            }
10029            else {
10030                if (s.support.touch) {
10031                    touchEventsTarget[action](s.touchEvents.start, s.onTouchStart, false);
10032                    touchEventsTarget[action](s.touchEvents.move, s.onTouchMove, moveCapture);
10033                    touchEventsTarget[action](s.touchEvents.end, s.onTouchEnd, false);
10034                }
10035                if (params.simulateTouch && !s.device.ios && !s.device.android) {
10036                    touchEventsTarget[action]('mousedown', s.onTouchStart, false);
10037                    document[action]('mousemove', s.onTouchMove, moveCapture);
10038                    document[action]('mouseup', s.onTouchEnd, false);
10039                }
10040            }
10041            window[action]('resize', s.onResize);
10042
10043            // Next, Prev, Index
10044            if (s.params.nextButton) {
10045                $(s.params.nextButton)[actionDom]('click', s.onClickNext);
10046                if (s.params.a11y && s.a11y) $(s.params.nextButton)[actionDom]('keydown', s.a11y.onEnterKey);
10047            }
10048            if (s.params.prevButton) {
10049                $(s.params.prevButton)[actionDom]('click', s.onClickPrev);
10050                if (s.params.a11y && s.a11y) $(s.params.prevButton)[actionDom]('keydown', s.a11y.onEnterKey);
10051            }
10052            if (s.params.pagination && s.params.paginationClickable) {
10053                $(s.paginationContainer)[actionDom]('click', '.' + s.params.bulletClass, s.onClickIndex);
10054                if (s.params.a11y && s.a11y) $(s.paginationContainer)[actionDom]('keydown', '.' + s.params.bulletClass, s.a11y.onEnterKey);
10055            }
10056
10057            // Prevent Links Clicks
10058            if (s.params.preventClicks || s.params.preventClicksPropagation) touchEventsTarget[action]('click', s.preventClicks, true);
10059        };
10060        s.attachEvents = function (detach) {
10061            s.initEvents();
10062        };
10063        s.detachEvents = function () {
10064            s.initEvents(true);
10065        };
10066
10067        /*=========================
10068          Handle Clicks
10069          ===========================*/
10070        // Prevent Clicks
10071        s.allowClick = true;
10072        s.preventClicks = function (e) {
10073            if (!s.allowClick) {
10074                if (s.params.preventClicks) e.preventDefault();
10075                if (s.params.preventClicksPropagation && s.animating) {
10076                    e.stopPropagation();
10077                    e.stopImmediatePropagation();
10078                }
10079            }
10080        };
10081        // Clicks
10082        s.onClickNext = function (e) {
10083            e.preventDefault();
10084            if (s.isEnd && !s.params.loop) return;
10085            s.slideNext();
10086        };
10087        s.onClickPrev = function (e) {
10088            e.preventDefault();
10089            if (s.isBeginning && !s.params.loop) return;
10090            s.slidePrev();
10091        };
10092        s.onClickIndex = function (e) {
10093            e.preventDefault();
10094            var index = $(this).index() * s.params.slidesPerGroup;
10095            if (s.params.loop) index = index + s.loopedSlides;
10096            s.slideTo(index);
10097        };
10098
10099        /*=========================
10100          Handle Touches
10101          ===========================*/
10102        function findElementInEvent(e, selector) {
10103            var el = $(e.target);
10104            if (!el.is(selector)) {
10105                if (typeof selector === 'string') {
10106                    el = el.parents(selector);
10107                }
10108                else if (selector.nodeType) {
10109                    var found;
10110                    el.parents().each(function (index, _el) {
10111                        if (_el === selector) found = selector;
10112                    });
10113                    if (!found) return undefined;
10114                    else return selector;
10115                }
10116            }
10117            if (el.length === 0) {
10118                return undefined;
10119            }
10120            return el[0];
10121        }
10122        s.updateClickedSlide = function (e) {
10123            var slide = findElementInEvent(e, '.' + s.params.slideClass);
10124            var slideFound = false;
10125            if (slide) {
10126                for (var i = 0; i < s.slides.length; i++) {
10127                    if (s.slides[i] === slide) slideFound = true;
10128                }
10129            }
10130
10131            if (slide && slideFound) {
10132                s.clickedSlide = slide;
10133                s.clickedIndex = $(slide).index();
10134            }
10135            else {
10136                s.clickedSlide = undefined;
10137                s.clickedIndex = undefined;
10138                return;
10139            }
10140            if (s.params.slideToClickedSlide && s.clickedIndex !== undefined && s.clickedIndex !== s.activeIndex) {
10141                var slideToIndex = s.clickedIndex,
10142                    realIndex,
10143                    duplicatedSlides;
10144                if (s.params.loop) {
10145                    if (s.animating) return;
10146                    realIndex = $(s.clickedSlide).attr('data-swiper-slide-index');
10147                    if (s.params.centeredSlides) {
10148                        if ((slideToIndex < s.loopedSlides - s.params.slidesPerView/2) || (slideToIndex > s.slides.length - s.loopedSlides + s.params.slidesPerView/2)) {
10149                            s.fixLoop();
10150                            slideToIndex = s.wrapper.children('.' + s.params.slideClass + '[data-swiper-slide-index="' + realIndex + '"]:not(.swiper-slide-duplicate)').eq(0).index();
10151                            setTimeout(function () {
10152                                s.slideTo(slideToIndex);
10153                            }, 0);
10154                        }
10155                        else {
10156                            s.slideTo(slideToIndex);
10157                        }
10158                    }
10159                    else {
10160                        if (slideToIndex > s.slides.length - s.params.slidesPerView) {
10161                            s.fixLoop();
10162                            slideToIndex = s.wrapper.children('.' + s.params.slideClass + '[data-swiper-slide-index="' + realIndex + '"]:not(.swiper-slide-duplicate)').eq(0).index();
10163                            setTimeout(function () {
10164                                s.slideTo(slideToIndex);
10165                            }, 0);
10166                        }
10167                        else {
10168                            s.slideTo(slideToIndex);
10169                        }
10170                    }
10171                }
10172                else {
10173                    s.slideTo(slideToIndex);
10174                }
10175            }
10176        };
10177
10178        var isTouched,
10179            isMoved,
10180            allowTouchCallbacks,
10181            touchStartTime,
10182            isScrolling,
10183            currentTranslate,
10184            startTranslate,
10185            allowThresholdMove,
10186            // Form elements to match
10187            formElements = 'input, select, textarea, button',
10188            // Last click time
10189            lastClickTime = Date.now(), clickTimeout,
10190            //Velocities
10191            velocities = [],
10192            allowMomentumBounce;
10193
10194        // Animating Flag
10195        s.animating = false;
10196
10197        // Touches information
10198        s.touches = {
10199            startX: 0,
10200            startY: 0,
10201            currentX: 0,
10202            currentY: 0,
10203            diff: 0
10204        };
10205
10206        // Touch handlers
10207        var isTouchEvent, startMoving;
10208        s.onTouchStart = function (e) {
10209            if (e.originalEvent) e = e.originalEvent;
10210            isTouchEvent = e.type === 'touchstart';
10211            if (!isTouchEvent && 'which' in e && e.which === 3) return;
10212            if (s.params.noSwiping && findElementInEvent(e, '.' + s.params.noSwipingClass)) {
10213                s.allowClick = true;
10214                return;
10215            }
10216            if (s.params.swipeHandler) {
10217                if (!findElementInEvent(e, s.params.swipeHandler)) return;
10218            }
10219
10220            var startX = s.touches.currentX = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.pageX;
10221            var startY = s.touches.currentY = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.pageY;
10222
10223            // Do NOT start if iOS edge swipe is detected. Otherwise iOS app (UIWebView) cannot swipe-to-go-back anymore
10224            if(s.device.ios && s.params.iOSEdgeSwipeDetection && startX <= s.params.iOSEdgeSwipeThreshold) {
10225                return;
10226            }
10227
10228            isTouched = true;
10229            isMoved = false;
10230            allowTouchCallbacks = true;
10231            isScrolling = undefined;
10232            startMoving = undefined;
10233            s.touches.startX = startX;
10234            s.touches.startY = startY;
10235            touchStartTime = Date.now();
10236            s.allowClick = true;
10237            s.updateContainerSize();
10238            s.swipeDirection = undefined;
10239            if (s.params.threshold > 0) allowThresholdMove = false;
10240            if (e.type !== 'touchstart') {
10241                var preventDefault = true;
10242                if ($(e.target).is(formElements)) preventDefault = false;
10243                if (document.activeElement && $(document.activeElement).is(formElements)) {
10244                    document.activeElement.blur();
10245                }
10246                if (preventDefault) {
10247                    e.preventDefault();
10248                }
10249            }
10250            s.emit('onTouchStart', s, e);
10251        };
10252
10253        s.onTouchMove = function (e) {
10254            if (e.originalEvent) e = e.originalEvent;
10255            if (isTouchEvent && e.type === 'mousemove') return;
10256            if (e.preventedByNestedSwiper) return;
10257            if (s.params.onlyExternal) {
10258                // isMoved = true;
10259                s.allowClick = false;
10260                if (isTouched) {
10261                    s.touches.startX = s.touches.currentX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX;
10262                    s.touches.startY = s.touches.currentY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
10263                    touchStartTime = Date.now();
10264                }
10265                return;
10266            }
10267            if (isTouchEvent && document.activeElement) {
10268                if (e.target === document.activeElement && $(e.target).is(formElements)) {
10269                    isMoved = true;
10270                    s.allowClick = false;
10271                    return;
10272                }
10273            }
10274            if (allowTouchCallbacks) {
10275                s.emit('onTouchMove', s, e);
10276            }
10277            if (e.targetTouches && e.targetTouches.length > 1) return;
10278
10279            s.touches.currentX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX;
10280            s.touches.currentY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
10281
10282            if (typeof isScrolling === 'undefined') {
10283                var touchAngle = Math.atan2(Math.abs(s.touches.currentY - s.touches.startY), Math.abs(s.touches.currentX - s.touches.startX)) * 180 / Math.PI;
10284                isScrolling = isH() ? touchAngle > s.params.touchAngle : (90 - touchAngle > s.params.touchAngle);
10285            }
10286            if (isScrolling) {
10287                s.emit('onTouchMoveOpposite', s, e);
10288            }
10289            if (typeof startMoving === 'undefined' && s.browser.ieTouch) {
10290                if (s.touches.currentX !== s.touches.startX || s.touches.currentY !== s.touches.startY) {
10291                    startMoving = true;
10292                }
10293            }
10294            if (!isTouched) return;
10295            if (isScrolling)  {
10296                isTouched = false;
10297                return;
10298            }
10299            if (!startMoving && s.browser.ieTouch) {
10300                return;
10301            }
10302            s.allowClick = false;
10303            s.emit('onSliderMove', s, e);
10304            e.preventDefault();
10305            if (s.params.touchMoveStopPropagation && !s.params.nested) {
10306                e.stopPropagation();
10307            }
10308
10309            if (!isMoved) {
10310                if (params.loop) {
10311                    s.fixLoop();
10312                }
10313                startTranslate = s.getWrapperTranslate();
10314                s.setWrapperTransition(0);
10315                if (s.animating) {
10316                    s.wrapper.trigger('webkitTransitionEnd transitionend oTransitionEnd MSTransitionEnd msTransitionEnd');
10317                }
10318                if (s.params.autoplay && s.autoplaying) {
10319                    if (s.params.autoplayDisableOnInteraction) {
10320                        s.stopAutoplay();
10321                    }
10322                    else {
10323                        s.pauseAutoplay();
10324                    }
10325                }
10326                allowMomentumBounce = false;
10327                //Grab Cursor
10328                if (s.params.grabCursor) {
10329                    s.container[0].style.cursor = 'move';
10330                    s.container[0].style.cursor = '-webkit-grabbing';
10331                    s.container[0].style.cursor = '-moz-grabbin';
10332                    s.container[0].style.cursor = 'grabbing';
10333                }
10334            }
10335            isMoved = true;
10336
10337            var diff = s.touches.diff = isH() ? s.touches.currentX - s.touches.startX : s.touches.currentY - s.touches.startY;
10338
10339            diff = diff * s.params.touchRatio;
10340            if (s.rtl) diff = -diff;
10341
10342            s.swipeDirection = diff > 0 ? 'prev' : 'next';
10343            currentTranslate = diff + startTranslate;
10344
10345            var disableParentSwiper = true;
10346            if ((diff > 0 && currentTranslate > s.minTranslate())) {
10347                disableParentSwiper = false;
10348                if (s.params.resistance) currentTranslate = s.minTranslate() - 1 + Math.pow(-s.minTranslate() + startTranslate + diff, s.params.resistanceRatio);
10349            }
10350            else if (diff < 0 && currentTranslate < s.maxTranslate()) {
10351                disableParentSwiper = false;
10352                if (s.params.resistance) currentTranslate = s.maxTranslate() + 1 - Math.pow(s.maxTranslate() - startTranslate - diff, s.params.resistanceRatio);
10353            }
10354
10355            if (disableParentSwiper) {
10356                e.preventedByNestedSwiper = true;
10357            }
10358
10359            // Directions locks
10360            if (!s.params.allowSwipeToNext && s.swipeDirection === 'next' && currentTranslate < startTranslate) {
10361                currentTranslate = startTranslate;
10362            }
10363            if (!s.params.allowSwipeToPrev && s.swipeDirection === 'prev' && currentTranslate > startTranslate) {
10364                currentTranslate = startTranslate;
10365            }
10366
10367            if (!s.params.followFinger) return;
10368
10369            // Threshold
10370            if (s.params.threshold > 0) {
10371                if (Math.abs(diff) > s.params.threshold || allowThresholdMove) {
10372                    if (!allowThresholdMove) {
10373                        allowThresholdMove = true;
10374                        s.touches.startX = s.touches.currentX;
10375                        s.touches.startY = s.touches.currentY;
10376                        currentTranslate = startTranslate;
10377                        s.touches.diff = isH() ? s.touches.currentX - s.touches.startX : s.touches.currentY - s.touches.startY;
10378                        return;
10379                    }
10380                }
10381                else {
10382                    currentTranslate = startTranslate;
10383                    return;
10384                }
10385            }
10386            // Update active index in free mode
10387            if (s.params.freeMode || s.params.watchSlidesProgress) {
10388                s.updateActiveIndex();
10389            }
10390            if (s.params.freeMode) {
10391                //Velocity
10392                if (velocities.length === 0) {
10393                    velocities.push({
10394                        position: s.touches[isH() ? 'startX' : 'startY'],
10395                        time: touchStartTime
10396                    });
10397                }
10398                velocities.push({
10399                    position: s.touches[isH() ? 'currentX' : 'currentY'],
10400                    time: (new window.Date()).getTime()
10401                });
10402            }
10403            // Update progress
10404            s.updateProgress(currentTranslate);
10405            // Update translate
10406            s.setWrapperTranslate(currentTranslate);
10407        };
10408        s.onTouchEnd = function (e) {
10409            if (e.originalEvent) e = e.originalEvent;
10410            if (allowTouchCallbacks) {
10411                s.emit('onTouchEnd', s, e);
10412            }
10413            allowTouchCallbacks = false;
10414            if (!isTouched) return;
10415            //Return Grab Cursor
10416            if (s.params.grabCursor && isMoved && isTouched) {
10417                s.container[0].style.cursor = 'move';
10418                s.container[0].style.cursor = '-webkit-grab';
10419                s.container[0].style.cursor = '-moz-grab';
10420                s.container[0].style.cursor = 'grab';
10421            }
10422
10423            // Time diff
10424            var touchEndTime = Date.now();
10425            var timeDiff = touchEndTime - touchStartTime;
10426
10427            // Tap, doubleTap, Click
10428            if (s.allowClick) {
10429                s.updateClickedSlide(e);
10430                s.emit('onTap', s, e);
10431                if (timeDiff < 300 && (touchEndTime - lastClickTime) > 300) {
10432                    if (clickTimeout) clearTimeout(clickTimeout);
10433                    clickTimeout = setTimeout(function () {
10434                        if (!s) return;
10435                        if (s.params.paginationHide && s.paginationContainer.length > 0 && !$(e.target).hasClass(s.params.bulletClass)) {
10436                            s.paginationContainer.toggleClass(s.params.paginationHiddenClass);
10437                        }
10438                        s.emit('onClick', s, e);
10439                    }, 300);
10440
10441                }
10442                if (timeDiff < 300 && (touchEndTime - lastClickTime) < 300) {
10443                    if (clickTimeout) clearTimeout(clickTimeout);
10444                    s.emit('onDoubleTap', s, e);
10445                }
10446            }
10447
10448            lastClickTime = Date.now();
10449            setTimeout(function () {
10450                if (s) s.allowClick = true;
10451            }, 0);
10452
10453            if (!isTouched || !isMoved || !s.swipeDirection || s.touches.diff === 0 || currentTranslate === startTranslate) {
10454                isTouched = isMoved = false;
10455                return;
10456            }
10457            isTouched = isMoved = false;
10458
10459            var currentPos;
10460            if (s.params.followFinger) {
10461                currentPos = s.rtl ? s.translate : -s.translate;
10462            }
10463            else {
10464                currentPos = -currentTranslate;
10465            }
10466            if (s.params.freeMode) {
10467                if (currentPos < -s.minTranslate()) {
10468                    s.slideTo(s.activeIndex);
10469                    return;
10470                }
10471                else if (currentPos > -s.maxTranslate()) {
10472                    if (s.slides.length < s.snapGrid.length) {
10473                        s.slideTo(s.snapGrid.length - 1);
10474                    }
10475                    else {
10476                        s.slideTo(s.slides.length - 1);
10477                    }
10478                    return;
10479                }
10480
10481                if (s.params.freeModeMomentum) {
10482                    if (velocities.length > 1) {
10483                        var lastMoveEvent = velocities.pop(), velocityEvent = velocities.pop();
10484
10485                        var distance = lastMoveEvent.position - velocityEvent.position;
10486                        var time = lastMoveEvent.time - velocityEvent.time;
10487                        s.velocity = distance / time;
10488                        s.velocity = s.velocity / 2;
10489                        if (Math.abs(s.velocity) < s.params.freeModeMinimumVelocity) {
10490                            s.velocity = 0;
10491                        }
10492                        // this implies that the user stopped moving a finger then released.
10493                        // There would be no events with distance zero, so the last event is stale.
10494                        if (time > 150 || (new window.Date().getTime() - lastMoveEvent.time) > 300) {
10495                            s.velocity = 0;
10496                        }
10497                    } else {
10498                        s.velocity = 0;
10499                    }
10500
10501                    velocities.length = 0;
10502                    var momentumDuration = 1000 * s.params.freeModeMomentumRatio;
10503                    var momentumDistance = s.velocity * momentumDuration;
10504
10505                    var newPosition = s.translate + momentumDistance;
10506                    if (s.rtl) newPosition = - newPosition;
10507                    var doBounce = false;
10508                    var afterBouncePosition;
10509                    var bounceAmount = Math.abs(s.velocity) * 20 * s.params.freeModeMomentumBounceRatio;
10510                    if (newPosition < s.maxTranslate()) {
10511                        if (s.params.freeModeMomentumBounce) {
10512                            if (newPosition + s.maxTranslate() < -bounceAmount) {
10513                                newPosition = s.maxTranslate() - bounceAmount;
10514                            }
10515                            afterBouncePosition = s.maxTranslate();
10516                            doBounce = true;
10517                            allowMomentumBounce = true;
10518                        }
10519                        else {
10520                            newPosition = s.maxTranslate();
10521                        }
10522                    }
10523                    else if (newPosition > s.minTranslate()) {
10524                        if (s.params.freeModeMomentumBounce) {
10525                            if (newPosition - s.minTranslate() > bounceAmount) {
10526                                newPosition = s.minTranslate() + bounceAmount;
10527                            }
10528                            afterBouncePosition = s.minTranslate();
10529                            doBounce = true;
10530                            allowMomentumBounce = true;
10531                        }
10532                        else {
10533                            newPosition = s.minTranslate();
10534                        }
10535                    }
10536                    else if (s.params.freeModeSticky) {
10537                        var j = 0,
10538                            nextSlide;
10539                        for (j = 0; j < s.snapGrid.length; j += 1) {
10540                            if (s.snapGrid[j] > -newPosition) {
10541                                nextSlide = j;
10542                                break;
10543                            }
10544
10545                        }
10546                        if (Math.abs(s.snapGrid[nextSlide] - newPosition) < Math.abs(s.snapGrid[nextSlide - 1] - newPosition) || s.swipeDirection === 'next') {
10547                            newPosition = s.snapGrid[nextSlide];
10548                        } else {
10549                            newPosition = s.snapGrid[nextSlide - 1];
10550                        }
10551                        if (!s.rtl) newPosition = - newPosition;
10552                    }
10553                    //Fix duration
10554                    if (s.velocity !== 0) {
10555                        if (s.rtl) {
10556                            momentumDuration = Math.abs((-newPosition - s.translate) / s.velocity);
10557                        }
10558                        else {
10559                            momentumDuration = Math.abs((newPosition - s.translate) / s.velocity);
10560                        }
10561                    }
10562                    else if (s.params.freeModeSticky) {
10563                        s.slideReset();
10564                        return;
10565                    }
10566
10567                    if (s.params.freeModeMomentumBounce && doBounce) {
10568                        s.updateProgress(afterBouncePosition);
10569                        s.setWrapperTransition(momentumDuration);
10570                        s.setWrapperTranslate(newPosition);
10571                        s.onTransitionStart();
10572                        s.animating = true;
10573                        s.wrapper.transitionEnd(function () {
10574                            if (!s || !allowMomentumBounce) return;
10575                            s.emit('onMomentumBounce', s);
10576
10577                            s.setWrapperTransition(s.params.speed);
10578                            s.setWrapperTranslate(afterBouncePosition);
10579                            s.wrapper.transitionEnd(function () {
10580                                if (!s) return;
10581                                s.onTransitionEnd();
10582                            });
10583                        });
10584                    } else if (s.velocity) {
10585                        s.updateProgress(newPosition);
10586                        s.setWrapperTransition(momentumDuration);
10587                        s.setWrapperTranslate(newPosition);
10588                        s.onTransitionStart();
10589                        if (!s.animating) {
10590                            s.animating = true;
10591                            s.wrapper.transitionEnd(function () {
10592                                if (!s) return;
10593                                s.onTransitionEnd();
10594                            });
10595                        }
10596
10597                    } else {
10598                        s.updateProgress(newPosition);
10599                    }
10600
10601                    s.updateActiveIndex();
10602                }
10603                if (!s.params.freeModeMomentum || timeDiff >= s.params.longSwipesMs) {
10604                    s.updateProgress();
10605                    s.updateActiveIndex();
10606                }
10607                return;
10608            }
10609
10610            // Find current slide
10611            var i, stopIndex = 0, groupSize = s.slidesSizesGrid[0];
10612            for (i = 0; i < s.slidesGrid.length; i += s.params.slidesPerGroup) {
10613                if (typeof s.slidesGrid[i + s.params.slidesPerGroup] !== 'undefined') {
10614                    if (currentPos >= s.slidesGrid[i] && currentPos < s.slidesGrid[i + s.params.slidesPerGroup]) {
10615                        stopIndex = i;
10616                        groupSize = s.slidesGrid[i + s.params.slidesPerGroup] - s.slidesGrid[i];
10617                    }
10618                }
10619                else {
10620                    if (currentPos >= s.slidesGrid[i]) {
10621                        stopIndex = i;
10622                        groupSize = s.slidesGrid[s.slidesGrid.length - 1] - s.slidesGrid[s.slidesGrid.length - 2];
10623                    }
10624                }
10625            }
10626
10627            // Find current slide size
10628            var ratio = (currentPos - s.slidesGrid[stopIndex]) / groupSize;
10629
10630            if (timeDiff > s.params.longSwipesMs) {
10631                // Long touches
10632                if (!s.params.longSwipes) {
10633                    s.slideTo(s.activeIndex);
10634                    return;
10635                }
10636                if (s.swipeDirection === 'next') {
10637                    if (ratio >= s.params.longSwipesRatio) s.slideTo(stopIndex + s.params.slidesPerGroup);
10638                    else s.slideTo(stopIndex);
10639
10640                }
10641                if (s.swipeDirection === 'prev') {
10642                    if (ratio > (1 - s.params.longSwipesRatio)) s.slideTo(stopIndex + s.params.slidesPerGroup);
10643                    else s.slideTo(stopIndex);
10644                }
10645            }
10646            else {
10647                // Short swipes
10648                if (!s.params.shortSwipes) {
10649                    s.slideTo(s.activeIndex);
10650                    return;
10651                }
10652                if (s.swipeDirection === 'next') {
10653                    s.slideTo(stopIndex + s.params.slidesPerGroup);
10654
10655                }
10656                if (s.swipeDirection === 'prev') {
10657                    s.slideTo(stopIndex);
10658                }
10659            }
10660        };
10661        /*=========================
10662          Transitions
10663          ===========================*/
10664        s._slideTo = function (slideIndex, speed) {
10665            return s.slideTo(slideIndex, speed, true, true);
10666        };
10667        s.slideTo = function (slideIndex, speed, runCallbacks, internal) {
10668            if (typeof runCallbacks === 'undefined') runCallbacks = true;
10669            if (typeof slideIndex === 'undefined') slideIndex = 0;
10670            if (slideIndex < 0) slideIndex = 0;
10671            s.snapIndex = Math.floor(slideIndex / s.params.slidesPerGroup);
10672            if (s.snapIndex >= s.snapGrid.length) s.snapIndex = s.snapGrid.length - 1;
10673
10674            var translate = - s.snapGrid[s.snapIndex];
10675            // Stop autoplay
10676            if (s.params.autoplay && s.autoplaying) {
10677                if (internal || !s.params.autoplayDisableOnInteraction) {
10678                    s.pauseAutoplay(speed);
10679                }
10680                else {
10681                    s.stopAutoplay();
10682                }
10683            }
10684            // Update progress
10685            s.updateProgress(translate);
10686
10687            // Normalize slideIndex
10688            for (var i = 0; i < s.slidesGrid.length; i++) {
10689                if (- Math.floor(translate * 100) >= Math.floor(s.slidesGrid[i] * 100)) {
10690                    slideIndex = i;
10691                }
10692            }
10693
10694            // Directions locks
10695            if (!s.params.allowSwipeToNext && translate < s.translate && translate < s.minTranslate()) {
10696                return false;
10697            }
10698            if (!s.params.allowSwipeToPrev && translate > s.translate && translate > s.maxTranslate()) {
10699                if ((s.activeIndex || 0) !== slideIndex ) return false;
10700            }
10701
10702            // Update Index
10703            if (typeof speed === 'undefined') speed = s.params.speed;
10704            s.previousIndex = s.activeIndex || 0;
10705            s.activeIndex = slideIndex;
10706
10707            if ((s.rtl && -translate === s.translate) || (!s.rtl && translate === s.translate)) {
10708                // Update Height
10709                if (s.params.autoHeight) {
10710                    s.updateAutoHeight();
10711                }
10712                s.updateClasses();
10713                if (s.params.effect !== 'slide') {
10714                    s.setWrapperTranslate(translate);
10715                }
10716                return false;
10717            }
10718            s.updateClasses();
10719            s.onTransitionStart(runCallbacks);
10720
10721            if (speed === 0) {
10722                s.setWrapperTranslate(translate);
10723                s.setWrapperTransition(0);
10724                s.onTransitionEnd(runCallbacks);
10725            }
10726            else {
10727                s.setWrapperTranslate(translate);
10728                s.setWrapperTransition(speed);
10729                if (!s.animating) {
10730                    s.animating = true;
10731                    s.wrapper.transitionEnd(function () {
10732                        if (!s) return;
10733                        s.onTransitionEnd(runCallbacks);
10734                    });
10735                }
10736
10737            }
10738
10739            return true;
10740        };
10741
10742        s.onTransitionStart = function (runCallbacks) {
10743            if (typeof runCallbacks === 'undefined') runCallbacks = true;
10744            if (s.params.autoHeight) {
10745                s.updateAutoHeight();
10746            }
10747            if (s.lazy) s.lazy.onTransitionStart();
10748            if (runCallbacks) {
10749                s.emit('onTransitionStart', s);
10750                if (s.activeIndex !== s.previousIndex) {
10751                    s.emit('onSlideChangeStart', s);
10752                    _scope.$emit("$ionicSlides.slideChangeStart", {
10753                      slider: s,
10754                      activeIndex: s.getSlideDataIndex(s.activeIndex),
10755                      previousIndex: s.getSlideDataIndex(s.previousIndex)
10756                    });
10757                    if (s.activeIndex > s.previousIndex) {
10758                        s.emit('onSlideNextStart', s);
10759                    }
10760                    else {
10761                        s.emit('onSlidePrevStart', s);
10762                    }
10763                }
10764
10765            }
10766        };
10767        s.onTransitionEnd = function (runCallbacks) {
10768            s.animating = false;
10769            s.setWrapperTransition(0);
10770            if (typeof runCallbacks === 'undefined') runCallbacks = true;
10771            if (s.lazy) s.lazy.onTransitionEnd();
10772            if (runCallbacks) {
10773                s.emit('onTransitionEnd', s);
10774                if (s.activeIndex !== s.previousIndex) {
10775                    s.emit('onSlideChangeEnd', s);
10776                    _scope.$emit("$ionicSlides.slideChangeEnd", {
10777                      slider: s,
10778                      activeIndex: s.getSlideDataIndex(s.activeIndex),
10779                      previousIndex: s.getSlideDataIndex(s.previousIndex)
10780                    });
10781                    if (s.activeIndex > s.previousIndex) {
10782                        s.emit('onSlideNextEnd', s);
10783                    }
10784                    else {
10785                        s.emit('onSlidePrevEnd', s);
10786                    }
10787                }
10788            }
10789            if (s.params.hashnav && s.hashnav) {
10790                s.hashnav.setHash();
10791            }
10792
10793        };
10794        s.slideNext = function (runCallbacks, speed, internal) {
10795            if (s.params.loop) {
10796                if (s.animating) return false;
10797                s.fixLoop();
10798                var clientLeft = s.container[0].clientLeft;
10799                return s.slideTo(s.activeIndex + s.params.slidesPerGroup, speed, runCallbacks, internal);
10800            }
10801            else return s.slideTo(s.activeIndex + s.params.slidesPerGroup, speed, runCallbacks, internal);
10802        };
10803        s._slideNext = function (speed) {
10804            return s.slideNext(true, speed, true);
10805        };
10806        s.slidePrev = function (runCallbacks, speed, internal) {
10807            if (s.params.loop) {
10808                if (s.animating) return false;
10809                s.fixLoop();
10810                var clientLeft = s.container[0].clientLeft;
10811                return s.slideTo(s.activeIndex - 1, speed, runCallbacks, internal);
10812            }
10813            else return s.slideTo(s.activeIndex - 1, speed, runCallbacks, internal);
10814        };
10815        s._slidePrev = function (speed) {
10816            return s.slidePrev(true, speed, true);
10817        };
10818        s.slideReset = function (runCallbacks, speed, internal) {
10819            return s.slideTo(s.activeIndex, speed, runCallbacks);
10820        };
10821
10822        /*=========================
10823          Translate/transition helpers
10824          ===========================*/
10825        s.setWrapperTransition = function (duration, byController) {
10826            s.wrapper.transition(duration);
10827            if (s.params.effect !== 'slide' && s.effects[s.params.effect]) {
10828                s.effects[s.params.effect].setTransition(duration);
10829            }
10830            if (s.params.parallax && s.parallax) {
10831                s.parallax.setTransition(duration);
10832            }
10833            if (s.params.scrollbar && s.scrollbar) {
10834                s.scrollbar.setTransition(duration);
10835            }
10836            if (s.params.control && s.controller) {
10837                s.controller.setTransition(duration, byController);
10838            }
10839            s.emit('onSetTransition', s, duration);
10840        };
10841        s.setWrapperTranslate = function (translate, updateActiveIndex, byController) {
10842            var x = 0, y = 0, z = 0;
10843            if (isH()) {
10844                x = s.rtl ? -translate : translate;
10845            }
10846            else {
10847                y = translate;
10848            }
10849
10850            if (s.params.roundLengths) {
10851                x = round(x);
10852                y = round(y);
10853            }
10854
10855            if (!s.params.virtualTranslate) {
10856                if (s.support.transforms3d) s.wrapper.transform('translate3d(' + x + 'px, ' + y + 'px, ' + z + 'px)');
10857                else s.wrapper.transform('translate(' + x + 'px, ' + y + 'px)');
10858            }
10859
10860            s.translate = isH() ? x : y;
10861
10862            // Check if we need to update progress
10863            var progress;
10864            var translatesDiff = s.maxTranslate() - s.minTranslate();
10865            if (translatesDiff === 0) {
10866                progress = 0;
10867            }
10868            else {
10869                progress = (translate - s.minTranslate()) / (translatesDiff);
10870            }
10871            if (progress !== s.progress) {
10872                s.updateProgress(translate);
10873            }
10874
10875            if (updateActiveIndex) s.updateActiveIndex();
10876            if (s.params.effect !== 'slide' && s.effects[s.params.effect]) {
10877                s.effects[s.params.effect].setTranslate(s.translate);
10878            }
10879            if (s.params.parallax && s.parallax) {
10880                s.parallax.setTranslate(s.translate);
10881            }
10882            if (s.params.scrollbar && s.scrollbar) {
10883                s.scrollbar.setTranslate(s.translate);
10884            }
10885            if (s.params.control && s.controller) {
10886                s.controller.setTranslate(s.translate, byController);
10887            }
10888            s.emit('onSetTranslate', s, s.translate);
10889        };
10890
10891        s.getTranslate = function (el, axis) {
10892            var matrix, curTransform, curStyle, transformMatrix;
10893
10894            // automatic axis detection
10895            if (typeof axis === 'undefined') {
10896                axis = 'x';
10897            }
10898
10899            if (s.params.virtualTranslate) {
10900                return s.rtl ? -s.translate : s.translate;
10901            }
10902
10903            curStyle = window.getComputedStyle(el, null);
10904            if (window.WebKitCSSMatrix) {
10905                curTransform = curStyle.transform || curStyle.webkitTransform;
10906                if (curTransform.split(',').length > 6) {
10907                    curTransform = curTransform.split(', ').map(function(a){
10908                        return a.replace(',','.');
10909                    }).join(', ');
10910                }
10911                // Some old versions of Webkit choke when 'none' is passed; pass
10912                // empty string instead in this case
10913                transformMatrix = new window.WebKitCSSMatrix(curTransform === 'none' ? '' : curTransform);
10914            }
10915            else {
10916                transformMatrix = curStyle.MozTransform || curStyle.OTransform || curStyle.MsTransform || curStyle.msTransform  || curStyle.transform || curStyle.getPropertyValue('transform').replace('translate(', 'matrix(1, 0, 0, 1,');
10917                matrix = transformMatrix.toString().split(',');
10918            }
10919
10920            if (axis === 'x') {
10921                //Latest Chrome and webkits Fix
10922                if (window.WebKitCSSMatrix)
10923                    curTransform = transformMatrix.m41;
10924                //Crazy IE10 Matrix
10925                else if (matrix.length === 16)
10926                    curTransform = parseFloat(matrix[12]);
10927                //Normal Browsers
10928                else
10929                    curTransform = parseFloat(matrix[4]);
10930            }
10931            if (axis === 'y') {
10932                //Latest Chrome and webkits Fix
10933                if (window.WebKitCSSMatrix)
10934                    curTransform = transformMatrix.m42;
10935                //Crazy IE10 Matrix
10936                else if (matrix.length === 16)
10937                    curTransform = parseFloat(matrix[13]);
10938                //Normal Browsers
10939                else
10940                    curTransform = parseFloat(matrix[5]);
10941            }
10942            if (s.rtl && curTransform) curTransform = -curTransform;
10943            return curTransform || 0;
10944        };
10945        s.getWrapperTranslate = function (axis) {
10946            if (typeof axis === 'undefined') {
10947                axis = isH() ? 'x' : 'y';
10948            }
10949            return s.getTranslate(s.wrapper[0], axis);
10950        };
10951
10952        /*=========================
10953          Observer
10954          ===========================*/
10955        s.observers = [];
10956        function initObserver(target, options) {
10957            options = options || {};
10958            // create an observer instance
10959            var ObserverFunc = window.MutationObserver || window.WebkitMutationObserver;
10960            var observer = new ObserverFunc(function (mutations) {
10961                mutations.forEach(function (mutation) {
10962                    s.onResize(true);
10963                    s.emit('onObserverUpdate', s, mutation);
10964                });
10965            });
10966
10967            observer.observe(target, {
10968                attributes: typeof options.attributes === 'undefined' ? true : options.attributes,
10969                childList: typeof options.childList === 'undefined' ? true : options.childList,
10970                characterData: typeof options.characterData === 'undefined' ? true : options.characterData
10971            });
10972
10973            s.observers.push(observer);
10974        }
10975        s.initObservers = function () {
10976            if (s.params.observeParents) {
10977                var containerParents = s.container.parents();
10978                for (var i = 0; i < containerParents.length; i++) {
10979                    initObserver(containerParents[i]);
10980                }
10981            }
10982
10983            // Observe container
10984            initObserver(s.container[0], {childList: false});
10985
10986            // Observe wrapper
10987            initObserver(s.wrapper[0], {attributes: false});
10988        };
10989        s.disconnectObservers = function () {
10990            for (var i = 0; i < s.observers.length; i++) {
10991                s.observers[i].disconnect();
10992            }
10993            s.observers = [];
10994        };
10995
10996        s.updateLoop = function(){
10997          var currentSlide = s.slides.eq(s.activeIndex);
10998          if ( angular.element(currentSlide).hasClass(s.params.slideDuplicateClass) ){
10999            // we're on a duplicate, so slide to the non-duplicate
11000            var swiperSlideIndex = angular.element(currentSlide).attr("data-swiper-slide-index");
11001            var slides = s.wrapper.children('.' + s.params.slideClass);
11002            for ( var i = 0; i < slides.length; i++ ){
11003              if ( !angular.element(slides[i]).hasClass(s.params.slideDuplicateClass) && angular.element(slides[i]).attr("data-swiper-slide-index") === swiperSlideIndex ){
11004                s.slideTo(i, 0, false, true);
11005                break;
11006              }
11007            }
11008            // if we needed to switch slides, we did that.  So, now call the createLoop function internally
11009            setTimeout(function(){
11010              s.createLoop();
11011            }, 50);
11012          }
11013        }
11014
11015        s.getSlideDataIndex = function(slideIndex){
11016          // this is an Ionic custom function
11017          // Swiper loops utilize duplicate DOM elements for slides when in a loop
11018          // which means that we cannot rely on the actual slide index for our events
11019          // because index 0 does not necessarily point to index 0
11020          // and index n+1 does not necessarily point to the expected piece of data
11021          // therefore, rather than using the actual slide index we should
11022          // use the data index that swiper includes as an attribute on the dom elements
11023          // because this is what will be meaningful to the consumer of our events
11024          var slide = s.slides.eq(slideIndex);
11025          var attributeIndex = angular.element(slide).attr("data-swiper-slide-index");
11026          return parseInt(attributeIndex);
11027        }
11028
11029        /*=========================
11030          Loop
11031          ===========================*/
11032        // Create looped slides
11033        s.createLoop = function () {
11034          //console.log("Slider create loop method");
11035            //var toRemove = s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass);
11036            //angular.element(toRemove).remove();
11037            s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass).remove();
11038
11039            var slides = s.wrapper.children('.' + s.params.slideClass);
11040
11041            if(s.params.slidesPerView === 'auto' && !s.params.loopedSlides) s.params.loopedSlides = slides.length;
11042
11043            s.loopedSlides = parseInt(s.params.loopedSlides || s.params.slidesPerView, 10);
11044            s.loopedSlides = s.loopedSlides + s.params.loopAdditionalSlides;
11045            if (s.loopedSlides > slides.length) {
11046                s.loopedSlides = slides.length;
11047            }
11048
11049            var prependSlides = [], appendSlides = [], i, scope, newNode;
11050            slides.each(function (index, el) {
11051                var slide = $(this);
11052                if (index < s.loopedSlides) appendSlides.push(el);
11053                if (index < slides.length && index >= slides.length - s.loopedSlides) prependSlides.push(el);
11054                slide.attr('data-swiper-slide-index', index);
11055            });
11056            for (i = 0; i < appendSlides.length; i++) {
11057
11058              newNode = angular.element(appendSlides[i]).clone().addClass(s.params.slideDuplicateClass);
11059              newNode.removeAttr('ng-transclude');
11060              newNode.removeAttr('ng-repeat');
11061              scope = angular.element(appendSlides[i]).scope();
11062              newNode = $compile(newNode)(scope);
11063              angular.element(s.wrapper).append(newNode);
11064              //s.wrapper.append($(appendSlides[i].cloneNode(true)).addClass(s.params.slideDuplicateClass));
11065            }
11066            for (i = prependSlides.length - 1; i >= 0; i--) {
11067              //s.wrapper.prepend($(prependSlides[i].cloneNode(true)).addClass(s.params.slideDuplicateClass));
11068
11069              newNode = angular.element(prependSlides[i]).clone().addClass(s.params.slideDuplicateClass);
11070              newNode.removeAttr('ng-transclude');
11071              newNode.removeAttr('ng-repeat');
11072
11073              scope = angular.element(prependSlides[i]).scope();
11074              newNode = $compile(newNode)(scope);
11075              angular.element(s.wrapper).prepend(newNode);
11076            }
11077        };
11078        s.destroyLoop = function () {
11079            s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass).remove();
11080            s.slides.removeAttr('data-swiper-slide-index');
11081        };
11082        s.fixLoop = function () {
11083            var newIndex;
11084            //Fix For Negative Oversliding
11085            if (s.activeIndex < s.loopedSlides) {
11086                newIndex = s.slides.length - s.loopedSlides * 3 + s.activeIndex;
11087                newIndex = newIndex + s.loopedSlides;
11088                s.slideTo(newIndex, 0, false, true);
11089            }
11090            //Fix For Positive Oversliding
11091            else if ((s.params.slidesPerView === 'auto' && s.activeIndex >= s.loopedSlides * 2) || (s.activeIndex > s.slides.length - s.params.slidesPerView * 2)) {
11092                newIndex = -s.slides.length + s.activeIndex + s.loopedSlides;
11093                newIndex = newIndex + s.loopedSlides;
11094                s.slideTo(newIndex, 0, false, true);
11095            }
11096        };
11097        /*=========================
11098          Append/Prepend/Remove Slides
11099          ===========================*/
11100        s.appendSlide = function (slides) {
11101            if (s.params.loop) {
11102                s.destroyLoop();
11103            }
11104            if (typeof slides === 'object' && slides.length) {
11105                for (var i = 0; i < slides.length; i++) {
11106                    if (slides[i]) s.wrapper.append(slides[i]);
11107                }
11108            }
11109            else {
11110                s.wrapper.append(slides);
11111            }
11112            if (s.params.loop) {
11113                s.createLoop();
11114            }
11115            if (!(s.params.observer && s.support.observer)) {
11116                s.update(true);
11117            }
11118        };
11119        s.prependSlide = function (slides) {
11120            if (s.params.loop) {
11121                s.destroyLoop();
11122            }
11123            var newActiveIndex = s.activeIndex + 1;
11124            if (typeof slides === 'object' && slides.length) {
11125                for (var i = 0; i < slides.length; i++) {
11126                    if (slides[i]) s.wrapper.prepend(slides[i]);
11127                }
11128                newActiveIndex = s.activeIndex + slides.length;
11129            }
11130            else {
11131                s.wrapper.prepend(slides);
11132            }
11133            if (s.params.loop) {
11134                s.createLoop();
11135            }
11136            if (!(s.params.observer && s.support.observer)) {
11137                s.update(true);
11138            }
11139            s.slideTo(newActiveIndex, 0, false);
11140        };
11141        s.removeSlide = function (slidesIndexes) {
11142            if (s.params.loop) {
11143                s.destroyLoop();
11144                s.slides = s.wrapper.children('.' + s.params.slideClass);
11145            }
11146            var newActiveIndex = s.activeIndex,
11147                indexToRemove;
11148            if (typeof slidesIndexes === 'object' && slidesIndexes.length) {
11149                for (var i = 0; i < slidesIndexes.length; i++) {
11150                    indexToRemove = slidesIndexes[i];
11151                    if (s.slides[indexToRemove]) s.slides.eq(indexToRemove).remove();
11152                    if (indexToRemove < newActiveIndex) newActiveIndex--;
11153                }
11154                newActiveIndex = Math.max(newActiveIndex, 0);
11155            }
11156            else {
11157                indexToRemove = slidesIndexes;
11158                if (s.slides[indexToRemove]) s.slides.eq(indexToRemove).remove();
11159                if (indexToRemove < newActiveIndex) newActiveIndex--;
11160                newActiveIndex = Math.max(newActiveIndex, 0);
11161            }
11162
11163            if (s.params.loop) {
11164                s.createLoop();
11165            }
11166
11167            if (!(s.params.observer && s.support.observer)) {
11168                s.update(true);
11169            }
11170            if (s.params.loop) {
11171                s.slideTo(newActiveIndex + s.loopedSlides, 0, false);
11172            }
11173            else {
11174                s.slideTo(newActiveIndex, 0, false);
11175            }
11176
11177        };
11178        s.removeAllSlides = function () {
11179            var slidesIndexes = [];
11180            for (var i = 0; i < s.slides.length; i++) {
11181                slidesIndexes.push(i);
11182            }
11183            s.removeSlide(slidesIndexes);
11184        };
11185
11186
11187        /*=========================
11188          Effects
11189          ===========================*/
11190        s.effects = {
11191            fade: {
11192                setTranslate: function () {
11193                    for (var i = 0; i < s.slides.length; i++) {
11194                        var slide = s.slides.eq(i);
11195                        var offset = slide[0].swiperSlideOffset;
11196                        var tx = -offset;
11197                        if (!s.params.virtualTranslate) tx = tx - s.translate;
11198                        var ty = 0;
11199                        if (!isH()) {
11200                            ty = tx;
11201                            tx = 0;
11202                        }
11203                        var slideOpacity = s.params.fade.crossFade ?
11204                                Math.max(1 - Math.abs(slide[0].progress), 0) :
11205                                1 + Math.min(Math.max(slide[0].progress, -1), 0);
11206                        slide
11207                            .css({
11208                                opacity: slideOpacity
11209                            })
11210                            .transform('translate3d(' + tx + 'px, ' + ty + 'px, 0px)');
11211
11212                    }
11213
11214                },
11215                setTransition: function (duration) {
11216                    s.slides.transition(duration);
11217                    if (s.params.virtualTranslate && duration !== 0) {
11218                        var eventTriggered = false;
11219                        s.slides.transitionEnd(function () {
11220                            if (eventTriggered) return;
11221                            if (!s) return;
11222                            eventTriggered = true;
11223                            s.animating = false;
11224                            var triggerEvents = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'];
11225                            for (var i = 0; i < triggerEvents.length; i++) {
11226                                s.wrapper.trigger(triggerEvents[i]);
11227                            }
11228                        });
11229                    }
11230                }
11231            },
11232            cube: {
11233                setTranslate: function () {
11234                    var wrapperRotate = 0, cubeShadow;
11235                    if (s.params.cube.shadow) {
11236                        if (isH()) {
11237                            cubeShadow = s.wrapper.find('.swiper-cube-shadow');
11238                            if (cubeShadow.length === 0) {
11239                                cubeShadow = $('<div class="swiper-cube-shadow"></div>');
11240                                s.wrapper.append(cubeShadow);
11241                            }
11242                            cubeShadow.css({height: s.width + 'px'});
11243                        }
11244                        else {
11245                            cubeShadow = s.container.find('.swiper-cube-shadow');
11246                            if (cubeShadow.length === 0) {
11247                                cubeShadow = $('<div class="swiper-cube-shadow"></div>');
11248                                s.container.append(cubeShadow);
11249                            }
11250                        }
11251                    }
11252                    for (var i = 0; i < s.slides.length; i++) {
11253                        var slide = s.slides.eq(i);
11254                        var slideAngle = i * 90;
11255                        var round = Math.floor(slideAngle / 360);
11256                        if (s.rtl) {
11257                            slideAngle = -slideAngle;
11258                            round = Math.floor(-slideAngle / 360);
11259                        }
11260                        var progress = Math.max(Math.min(slide[0].progress, 1), -1);
11261                        var tx = 0, ty = 0, tz = 0;
11262                        if (i % 4 === 0) {
11263                            tx = - round * 4 * s.size;
11264                            tz = 0;
11265                        }
11266                        else if ((i - 1) % 4 === 0) {
11267                            tx = 0;
11268                            tz = - round * 4 * s.size;
11269                        }
11270                        else if ((i - 2) % 4 === 0) {
11271                            tx = s.size + round * 4 * s.size;
11272                            tz = s.size;
11273                        }
11274                        else if ((i - 3) % 4 === 0) {
11275                            tx = - s.size;
11276                            tz = 3 * s.size + s.size * 4 * round;
11277                        }
11278                        if (s.rtl) {
11279                            tx = -tx;
11280                        }
11281
11282                        if (!isH()) {
11283                            ty = tx;
11284                            tx = 0;
11285                        }
11286
11287                        var transform = 'rotateX(' + (isH() ? 0 : -slideAngle) + 'deg) rotateY(' + (isH() ? slideAngle : 0) + 'deg) translate3d(' + tx + 'px, ' + ty + 'px, ' + tz + 'px)';
11288                        if (progress <= 1 && progress > -1) {
11289                            wrapperRotate = i * 90 + progress * 90;
11290                            if (s.rtl) wrapperRotate = -i * 90 - progress * 90;
11291                        }
11292                        slide.transform(transform);
11293                        if (s.params.cube.slideShadows) {
11294                            //Set shadows
11295                            var shadowBefore = isH() ? slide.find('.swiper-slide-shadow-left') : slide.find('.swiper-slide-shadow-top');
11296                            var shadowAfter = isH() ? slide.find('.swiper-slide-shadow-right') : slide.find('.swiper-slide-shadow-bottom');
11297                            if (shadowBefore.length === 0) {
11298                                shadowBefore = $('<div class="swiper-slide-shadow-' + (isH() ? 'left' : 'top') + '"></div>');
11299                                slide.append(shadowBefore);
11300                            }
11301                            if (shadowAfter.length === 0) {
11302                                shadowAfter = $('<div class="swiper-slide-shadow-' + (isH() ? 'right' : 'bottom') + '"></div>');
11303                                slide.append(shadowAfter);
11304                            }
11305                            var shadowOpacity = slide[0].progress;
11306                            if (shadowBefore.length) shadowBefore[0].style.opacity = -slide[0].progress;
11307                            if (shadowAfter.length) shadowAfter[0].style.opacity = slide[0].progress;
11308                        }
11309                    }
11310                    s.wrapper.css({
11311                        '-webkit-transform-origin': '50% 50% -' + (s.size / 2) + 'px',
11312                        '-moz-transform-origin': '50% 50% -' + (s.size / 2) + 'px',
11313                        '-ms-transform-origin': '50% 50% -' + (s.size / 2) + 'px',
11314                        'transform-origin': '50% 50% -' + (s.size / 2) + 'px'
11315                    });
11316
11317                    if (s.params.cube.shadow) {
11318                        if (isH()) {
11319                            cubeShadow.transform('translate3d(0px, ' + (s.width / 2 + s.params.cube.shadowOffset) + 'px, ' + (-s.width / 2) + 'px) rotateX(90deg) rotateZ(0deg) scale(' + (s.params.cube.shadowScale) + ')');
11320                        }
11321                        else {
11322                            var shadowAngle = Math.abs(wrapperRotate) - Math.floor(Math.abs(wrapperRotate) / 90) * 90;
11323                            var multiplier = 1.5 - (Math.sin(shadowAngle * 2 * Math.PI / 360) / 2 + Math.cos(shadowAngle * 2 * Math.PI / 360) / 2);
11324                            var scale1 = s.params.cube.shadowScale,
11325                                scale2 = s.params.cube.shadowScale / multiplier,
11326                                offset = s.params.cube.shadowOffset;
11327                            cubeShadow.transform('scale3d(' + scale1 + ', 1, ' + scale2 + ') translate3d(0px, ' + (s.height / 2 + offset) + 'px, ' + (-s.height / 2 / scale2) + 'px) rotateX(-90deg)');
11328                        }
11329                    }
11330                    var zFactor = (s.isSafari || s.isUiWebView) ? (-s.size / 2) : 0;
11331                    s.wrapper.transform('translate3d(0px,0,' + zFactor + 'px) rotateX(' + (isH() ? 0 : wrapperRotate) + 'deg) rotateY(' + (isH() ? -wrapperRotate : 0) + 'deg)');
11332                },
11333                setTransition: function (duration) {
11334                    s.slides.transition(duration).find('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').transition(duration);
11335                    if (s.params.cube.shadow && !isH()) {
11336                        s.container.find('.swiper-cube-shadow').transition(duration);
11337                    }
11338                }
11339            },
11340            coverflow: {
11341                setTranslate: function () {
11342                    var transform = s.translate;
11343                    var center = isH() ? -transform + s.width / 2 : -transform + s.height / 2;
11344                    var rotate = isH() ? s.params.coverflow.rotate: -s.params.coverflow.rotate;
11345                    var translate = s.params.coverflow.depth;
11346                    //Each slide offset from center
11347                    for (var i = 0, length = s.slides.length; i < length; i++) {
11348                        var slide = s.slides.eq(i);
11349                        var slideSize = s.slidesSizesGrid[i];
11350                        var slideOffset = slide[0].swiperSlideOffset;
11351                        var offsetMultiplier = (center - slideOffset - slideSize / 2) / slideSize * s.params.coverflow.modifier;
11352
11353                        var rotateY = isH() ? rotate * offsetMultiplier : 0;
11354                        var rotateX = isH() ? 0 : rotate * offsetMultiplier;
11355                        // var rotateZ = 0
11356                        var translateZ = -translate * Math.abs(offsetMultiplier);
11357
11358                        var translateY = isH() ? 0 : s.params.coverflow.stretch * (offsetMultiplier);
11359                        var translateX = isH() ? s.params.coverflow.stretch * (offsetMultiplier) : 0;
11360
11361                        //Fix for ultra small values
11362                        if (Math.abs(translateX) < 0.001) translateX = 0;
11363                        if (Math.abs(translateY) < 0.001) translateY = 0;
11364                        if (Math.abs(translateZ) < 0.001) translateZ = 0;
11365                        if (Math.abs(rotateY) < 0.001) rotateY = 0;
11366                        if (Math.abs(rotateX) < 0.001) rotateX = 0;
11367
11368                        var slideTransform = 'translate3d(' + translateX + 'px,' + translateY + 'px,' + translateZ + 'px)  rotateX(' + rotateX + 'deg) rotateY(' + rotateY + 'deg)';
11369
11370                        slide.transform(slideTransform);
11371                        slide[0].style.zIndex = -Math.abs(Math.round(offsetMultiplier)) + 1;
11372                        if (s.params.coverflow.slideShadows) {
11373                            //Set shadows
11374                            var shadowBefore = isH() ? slide.find('.swiper-slide-shadow-left') : slide.find('.swiper-slide-shadow-top');
11375                            var shadowAfter = isH() ? slide.find('.swiper-slide-shadow-right') : slide.find('.swiper-slide-shadow-bottom');
11376                            if (shadowBefore.length === 0) {
11377                                shadowBefore = $('<div class="swiper-slide-shadow-' + (isH() ? 'left' : 'top') + '"></div>');
11378                                slide.append(shadowBefore);
11379                            }
11380                            if (shadowAfter.length === 0) {
11381                                shadowAfter = $('<div class="swiper-slide-shadow-' + (isH() ? 'right' : 'bottom') + '"></div>');
11382                                slide.append(shadowAfter);
11383                            }
11384                            if (shadowBefore.length) shadowBefore[0].style.opacity = offsetMultiplier > 0 ? offsetMultiplier : 0;
11385                            if (shadowAfter.length) shadowAfter[0].style.opacity = (-offsetMultiplier) > 0 ? -offsetMultiplier : 0;
11386                        }
11387                    }
11388
11389                    //Set correct perspective for IE10
11390                    if (s.browser.ie) {
11391                        var ws = s.wrapper[0].style;
11392                        ws.perspectiveOrigin = center + 'px 50%';
11393                    }
11394                },
11395                setTransition: function (duration) {
11396                    s.slides.transition(duration).find('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').transition(duration);
11397                }
11398            }
11399        };
11400
11401        /*=========================
11402          Images Lazy Loading
11403          ===========================*/
11404        s.lazy = {
11405            initialImageLoaded: false,
11406            loadImageInSlide: function (index, loadInDuplicate) {
11407                if (typeof index === 'undefined') return;
11408                if (typeof loadInDuplicate === 'undefined') loadInDuplicate = true;
11409                if (s.slides.length === 0) return;
11410
11411                var slide = s.slides.eq(index);
11412                var img = slide.find('.swiper-lazy:not(.swiper-lazy-loaded):not(.swiper-lazy-loading)');
11413                if (slide.hasClass('swiper-lazy') && !slide.hasClass('swiper-lazy-loaded') && !slide.hasClass('swiper-lazy-loading')) {
11414                    img = img.add(slide[0]);
11415                }
11416                if (img.length === 0) return;
11417
11418                img.each(function () {
11419                    var _img = $(this);
11420                    _img.addClass('swiper-lazy-loading');
11421                    var background = _img.attr('data-background');
11422                    var src = _img.attr('data-src'),
11423                        srcset = _img.attr('data-srcset');
11424                    s.loadImage(_img[0], (src || background), srcset, false, function () {
11425                        if (background) {
11426                            _img.css('background-image', 'url(' + background + ')');
11427                            _img.removeAttr('data-background');
11428                        }
11429                        else {
11430                            if (srcset) {
11431                                _img.attr('srcset', srcset);
11432                                _img.removeAttr('data-srcset');
11433                            }
11434                            if (src) {
11435                                _img.attr('src', src);
11436                                _img.removeAttr('data-src');
11437                            }
11438
11439                        }
11440
11441                        _img.addClass('swiper-lazy-loaded').removeClass('swiper-lazy-loading');
11442                        slide.find('.swiper-lazy-preloader, .preloader').remove();
11443                        if (s.params.loop && loadInDuplicate) {
11444                            var slideOriginalIndex = slide.attr('data-swiper-slide-index');
11445                            if (slide.hasClass(s.params.slideDuplicateClass)) {
11446                                var originalSlide = s.wrapper.children('[data-swiper-slide-index="' + slideOriginalIndex + '"]:not(.' + s.params.slideDuplicateClass + ')');
11447                                s.lazy.loadImageInSlide(originalSlide.index(), false);
11448                            }
11449                            else {
11450                                var duplicatedSlide = s.wrapper.children('.' + s.params.slideDuplicateClass + '[data-swiper-slide-index="' + slideOriginalIndex + '"]');
11451                                s.lazy.loadImageInSlide(duplicatedSlide.index(), false);
11452                            }
11453                        }
11454                        s.emit('onLazyImageReady', s, slide[0], _img[0]);
11455                    });
11456
11457                    s.emit('onLazyImageLoad', s, slide[0], _img[0]);
11458                });
11459
11460            },
11461            load: function () {
11462                var i;
11463                if (s.params.watchSlidesVisibility) {
11464                    s.wrapper.children('.' + s.params.slideVisibleClass).each(function () {
11465                        s.lazy.loadImageInSlide($(this).index());
11466                    });
11467                }
11468                else {
11469                    if (s.params.slidesPerView > 1) {
11470                        for (i = s.activeIndex; i < s.activeIndex + s.params.slidesPerView ; i++) {
11471                            if (s.slides[i]) s.lazy.loadImageInSlide(i);
11472                        }
11473                    }
11474                    else {
11475                        s.lazy.loadImageInSlide(s.activeIndex);
11476                    }
11477                }
11478                if (s.params.lazyLoadingInPrevNext) {
11479                    if (s.params.slidesPerView > 1) {
11480                        // Next Slides
11481                        for (i = s.activeIndex + s.params.slidesPerView; i < s.activeIndex + s.params.slidesPerView + s.params.slidesPerView; i++) {
11482                            if (s.slides[i]) s.lazy.loadImageInSlide(i);
11483                        }
11484                        // Prev Slides
11485                        for (i = s.activeIndex - s.params.slidesPerView; i < s.activeIndex ; i++) {
11486                            if (s.slides[i]) s.lazy.loadImageInSlide(i);
11487                        }
11488                    }
11489                    else {
11490                        var nextSlide = s.wrapper.children('.' + s.params.slideNextClass);
11491                        if (nextSlide.length > 0) s.lazy.loadImageInSlide(nextSlide.index());
11492
11493                        var prevSlide = s.wrapper.children('.' + s.params.slidePrevClass);
11494                        if (prevSlide.length > 0) s.lazy.loadImageInSlide(prevSlide.index());
11495                    }
11496                }
11497            },
11498            onTransitionStart: function () {
11499                if (s.params.lazyLoading) {
11500                    if (s.params.lazyLoadingOnTransitionStart || (!s.params.lazyLoadingOnTransitionStart && !s.lazy.initialImageLoaded)) {
11501                        s.lazy.load();
11502                    }
11503                }
11504            },
11505            onTransitionEnd: function () {
11506                if (s.params.lazyLoading && !s.params.lazyLoadingOnTransitionStart) {
11507                    s.lazy.load();
11508                }
11509            }
11510        };
11511
11512
11513        /*=========================
11514          Scrollbar
11515          ===========================*/
11516        s.scrollbar = {
11517            isTouched: false,
11518            setDragPosition: function (e) {
11519                var sb = s.scrollbar;
11520                var x = 0, y = 0;
11521                var translate;
11522                var pointerPosition = isH() ?
11523                    ((e.type === 'touchstart' || e.type === 'touchmove') ? e.targetTouches[0].pageX : e.pageX || e.clientX) :
11524                    ((e.type === 'touchstart' || e.type === 'touchmove') ? e.targetTouches[0].pageY : e.pageY || e.clientY) ;
11525                var position = (pointerPosition) - sb.track.offset()[isH() ? 'left' : 'top'] - sb.dragSize / 2;
11526                var positionMin = -s.minTranslate() * sb.moveDivider;
11527                var positionMax = -s.maxTranslate() * sb.moveDivider;
11528                if (position < positionMin) {
11529                    position = positionMin;
11530                }
11531                else if (position > positionMax) {
11532                    position = positionMax;
11533                }
11534                position = -position / sb.moveDivider;
11535                s.updateProgress(position);
11536                s.setWrapperTranslate(position, true);
11537            },
11538            dragStart: function (e) {
11539                var sb = s.scrollbar;
11540                sb.isTouched = true;
11541                e.preventDefault();
11542                e.stopPropagation();
11543
11544                sb.setDragPosition(e);
11545                clearTimeout(sb.dragTimeout);
11546
11547                sb.track.transition(0);
11548                if (s.params.scrollbarHide) {
11549                    sb.track.css('opacity', 1);
11550                }
11551                s.wrapper.transition(100);
11552                sb.drag.transition(100);
11553                s.emit('onScrollbarDragStart', s);
11554            },
11555            dragMove: function (e) {
11556                var sb = s.scrollbar;
11557                if (!sb.isTouched) return;
11558                if (e.preventDefault) e.preventDefault();
11559                else e.returnValue = false;
11560                sb.setDragPosition(e);
11561                s.wrapper.transition(0);
11562                sb.track.transition(0);
11563                sb.drag.transition(0);
11564                s.emit('onScrollbarDragMove', s);
11565            },
11566            dragEnd: function (e) {
11567                var sb = s.scrollbar;
11568                if (!sb.isTouched) return;
11569                sb.isTouched = false;
11570                if (s.params.scrollbarHide) {
11571                    clearTimeout(sb.dragTimeout);
11572                    sb.dragTimeout = setTimeout(function () {
11573                        sb.track.css('opacity', 0);
11574                        sb.track.transition(400);
11575                    }, 1000);
11576
11577                }
11578                s.emit('onScrollbarDragEnd', s);
11579                if (s.params.scrollbarSnapOnRelease) {
11580                    s.slideReset();
11581                }
11582            },
11583            enableDraggable: function () {
11584                var sb = s.scrollbar;
11585                var target = s.support.touch ? sb.track : document;
11586                $(sb.track).on(s.touchEvents.start, sb.dragStart);
11587                $(target).on(s.touchEvents.move, sb.dragMove);
11588                $(target).on(s.touchEvents.end, sb.dragEnd);
11589            },
11590            disableDraggable: function () {
11591                var sb = s.scrollbar;
11592                var target = s.support.touch ? sb.track : document;
11593                $(sb.track).off(s.touchEvents.start, sb.dragStart);
11594                $(target).off(s.touchEvents.move, sb.dragMove);
11595                $(target).off(s.touchEvents.end, sb.dragEnd);
11596            },
11597            set: function () {
11598                if (!s.params.scrollbar) return;
11599                var sb = s.scrollbar;
11600                sb.track = $(s.params.scrollbar);
11601                sb.drag = sb.track.find('.swiper-scrollbar-drag');
11602                if (sb.drag.length === 0) {
11603                    sb.drag = $('<div class="swiper-scrollbar-drag"></div>');
11604                    sb.track.append(sb.drag);
11605                }
11606                sb.drag[0].style.width = '';
11607                sb.drag[0].style.height = '';
11608                sb.trackSize = isH() ? sb.track[0].offsetWidth : sb.track[0].offsetHeight;
11609
11610                sb.divider = s.size / s.virtualSize;
11611                sb.moveDivider = sb.divider * (sb.trackSize / s.size);
11612                sb.dragSize = sb.trackSize * sb.divider;
11613
11614                if (isH()) {
11615                    sb.drag[0].style.width = sb.dragSize + 'px';
11616                }
11617                else {
11618                    sb.drag[0].style.height = sb.dragSize + 'px';
11619                }
11620
11621                if (sb.divider >= 1) {
11622                    sb.track[0].style.display = 'none';
11623                }
11624                else {
11625                    sb.track[0].style.display = '';
11626                }
11627                if (s.params.scrollbarHide) {
11628                    sb.track[0].style.opacity = 0;
11629                }
11630            },
11631            setTranslate: function () {
11632                if (!s.params.scrollbar) return;
11633                var diff;
11634                var sb = s.scrollbar;
11635                var translate = s.translate || 0;
11636                var newPos;
11637
11638                var newSize = sb.dragSize;
11639                newPos = (sb.trackSize - sb.dragSize) * s.progress;
11640                if (s.rtl && isH()) {
11641                    newPos = -newPos;
11642                    if (newPos > 0) {
11643                        newSize = sb.dragSize - newPos;
11644                        newPos = 0;
11645                    }
11646                    else if (-newPos + sb.dragSize > sb.trackSize) {
11647                        newSize = sb.trackSize + newPos;
11648                    }
11649                }
11650                else {
11651                    if (newPos < 0) {
11652                        newSize = sb.dragSize + newPos;
11653                        newPos = 0;
11654                    }
11655                    else if (newPos + sb.dragSize > sb.trackSize) {
11656                        newSize = sb.trackSize - newPos;
11657                    }
11658                }
11659                if (isH()) {
11660                    if (s.support.transforms3d) {
11661                        sb.drag.transform('translate3d(' + (newPos) + 'px, 0, 0)');
11662                    }
11663                    else {
11664                        sb.drag.transform('translateX(' + (newPos) + 'px)');
11665                    }
11666                    sb.drag[0].style.width = newSize + 'px';
11667                }
11668                else {
11669                    if (s.support.transforms3d) {
11670                        sb.drag.transform('translate3d(0px, ' + (newPos) + 'px, 0)');
11671                    }
11672                    else {
11673                        sb.drag.transform('translateY(' + (newPos) + 'px)');
11674                    }
11675                    sb.drag[0].style.height = newSize + 'px';
11676                }
11677                if (s.params.scrollbarHide) {
11678                    clearTimeout(sb.timeout);
11679                    sb.track[0].style.opacity = 1;
11680                    sb.timeout = setTimeout(function () {
11681                        sb.track[0].style.opacity = 0;
11682                        sb.track.transition(400);
11683                    }, 1000);
11684                }
11685            },
11686            setTransition: function (duration) {
11687                if (!s.params.scrollbar) return;
11688                s.scrollbar.drag.transition(duration);
11689            }
11690        };
11691
11692        /*=========================
11693          Controller
11694          ===========================*/
11695        s.controller = {
11696            LinearSpline: function (x, y) {
11697                this.x = x;
11698                this.y = y;
11699                this.lastIndex = x.length - 1;
11700                // Given an x value (x2), return the expected y2 value:
11701                // (x1,y1) is the known point before given value,
11702                // (x3,y3) is the known point after given value.
11703                var i1, i3;
11704                var l = this.x.length;
11705
11706                this.interpolate = function (x2) {
11707                    if (!x2) return 0;
11708
11709                    // Get the indexes of x1 and x3 (the array indexes before and after given x2):
11710                    i3 = binarySearch(this.x, x2);
11711                    i1 = i3 - 1;
11712
11713                    // We have our indexes i1 & i3, so we can calculate already:
11714                    // y2 := ((x2−x1) × (y3−y1)) ÷ (x3−x1) + y1
11715                    return ((x2 - this.x[i1]) * (this.y[i3] - this.y[i1])) / (this.x[i3] - this.x[i1]) + this.y[i1];
11716                };
11717
11718                var binarySearch = (function() {
11719                    var maxIndex, minIndex, guess;
11720                    return function(array, val) {
11721                        minIndex = -1;
11722                        maxIndex = array.length;
11723                        while (maxIndex - minIndex > 1)
11724                            if (array[guess = maxIndex + minIndex >> 1] <= val) {
11725                                minIndex = guess;
11726                            } else {
11727                                maxIndex = guess;
11728                            }
11729                        return maxIndex;
11730                    };
11731                })();
11732            },
11733            //xxx: for now i will just save one spline function to to
11734            getInterpolateFunction: function(c){
11735                if(!s.controller.spline) s.controller.spline = s.params.loop ?
11736                    new s.controller.LinearSpline(s.slidesGrid, c.slidesGrid) :
11737                    new s.controller.LinearSpline(s.snapGrid, c.snapGrid);
11738            },
11739            setTranslate: function (translate, byController) {
11740               var controlled = s.params.control;
11741               var multiplier, controlledTranslate;
11742               function setControlledTranslate(c) {
11743                    // this will create an Interpolate function based on the snapGrids
11744                    // x is the Grid of the scrolled scroller and y will be the controlled scroller
11745                    // it makes sense to create this only once and recall it for the interpolation
11746                    // the function does a lot of value caching for performance
11747                    translate = c.rtl && c.params.direction === 'horizontal' ? -s.translate : s.translate;
11748                    if (s.params.controlBy === 'slide') {
11749                        s.controller.getInterpolateFunction(c);
11750                        // i am not sure why the values have to be multiplicated this way, tried to invert the snapGrid
11751                        // but it did not work out
11752                        controlledTranslate = -s.controller.spline.interpolate(-translate);
11753                    }
11754
11755                    if(!controlledTranslate || s.params.controlBy === 'container'){
11756                        multiplier = (c.maxTranslate() - c.minTranslate()) / (s.maxTranslate() - s.minTranslate());
11757                        controlledTranslate = (translate - s.minTranslate()) * multiplier + c.minTranslate();
11758                    }
11759
11760                    if (s.params.controlInverse) {
11761                        controlledTranslate = c.maxTranslate() - controlledTranslate;
11762                    }
11763                    c.updateProgress(controlledTranslate);
11764                    c.setWrapperTranslate(controlledTranslate, false, s);
11765                    c.updateActiveIndex();
11766               }
11767               if (s.isArray(controlled)) {
11768                   for (var i = 0; i < controlled.length; i++) {
11769                       if (controlled[i] !== byController && controlled[i] instanceof Swiper) {
11770                           setControlledTranslate(controlled[i]);
11771                       }
11772                   }
11773               }
11774               else if (controlled instanceof Swiper && byController !== controlled) {
11775
11776                   setControlledTranslate(controlled);
11777               }
11778            },
11779            setTransition: function (duration, byController) {
11780                var controlled = s.params.control;
11781                var i;
11782                function setControlledTransition(c) {
11783                    c.setWrapperTransition(duration, s);
11784                    if (duration !== 0) {
11785                        c.onTransitionStart();
11786                        c.wrapper.transitionEnd(function(){
11787                            if (!controlled) return;
11788                            if (c.params.loop && s.params.controlBy === 'slide') {
11789                                c.fixLoop();
11790                            }
11791                            c.onTransitionEnd();
11792
11793                        });
11794                    }
11795                }
11796                if (s.isArray(controlled)) {
11797                    for (i = 0; i < controlled.length; i++) {
11798                        if (controlled[i] !== byController && controlled[i] instanceof Swiper) {
11799                            setControlledTransition(controlled[i]);
11800                        }
11801                    }
11802                }
11803                else if (controlled instanceof Swiper && byController !== controlled) {
11804                    setControlledTransition(controlled);
11805                }
11806            }
11807        };
11808
11809        /*=========================
11810          Hash Navigation
11811          ===========================*/
11812        s.hashnav = {
11813            init: function () {
11814                if (!s.params.hashnav) return;
11815                s.hashnav.initialized = true;
11816                var hash = document.location.hash.replace('#', '');
11817                if (!hash) return;
11818                var speed = 0;
11819                for (var i = 0, length = s.slides.length; i < length; i++) {
11820                    var slide = s.slides.eq(i);
11821                    var slideHash = slide.attr('data-hash');
11822                    if (slideHash === hash && !slide.hasClass(s.params.slideDuplicateClass)) {
11823                        var index = slide.index();
11824                        s.slideTo(index, speed, s.params.runCallbacksOnInit, true);
11825                    }
11826                }
11827            },
11828            setHash: function () {
11829                if (!s.hashnav.initialized || !s.params.hashnav) return;
11830                document.location.hash = s.slides.eq(s.activeIndex).attr('data-hash') || '';
11831            }
11832        };
11833
11834        /*=========================
11835          Keyboard Control
11836          ===========================*/
11837        function handleKeyboard(e) {
11838            if (e.originalEvent) e = e.originalEvent; //jquery fix
11839            var kc = e.keyCode || e.charCode;
11840            // Directions locks
11841            if (!s.params.allowSwipeToNext && (isH() && kc === 39 || !isH() && kc === 40)) {
11842                return false;
11843            }
11844            if (!s.params.allowSwipeToPrev && (isH() && kc === 37 || !isH() && kc === 38)) {
11845                return false;
11846            }
11847            if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) {
11848                return;
11849            }
11850            if (document.activeElement && document.activeElement.nodeName && (document.activeElement.nodeName.toLowerCase() === 'input' || document.activeElement.nodeName.toLowerCase() === 'textarea')) {
11851                return;
11852            }
11853            if (kc === 37 || kc === 39 || kc === 38 || kc === 40) {
11854                var inView = false;
11855                //Check that swiper should be inside of visible area of window
11856                if (s.container.parents('.swiper-slide').length > 0 && s.container.parents('.swiper-slide-active').length === 0) {
11857                    return;
11858                }
11859                var windowScroll = {
11860                    left: window.pageXOffset,
11861                    top: window.pageYOffset
11862                };
11863                var windowWidth = window.innerWidth;
11864                var windowHeight = window.innerHeight;
11865                var swiperOffset = s.container.offset();
11866                if (s.rtl) swiperOffset.left = swiperOffset.left - s.container[0].scrollLeft;
11867                var swiperCoord = [
11868                    [swiperOffset.left, swiperOffset.top],
11869                    [swiperOffset.left + s.width, swiperOffset.top],
11870                    [swiperOffset.left, swiperOffset.top + s.height],
11871                    [swiperOffset.left + s.width, swiperOffset.top + s.height]
11872                ];
11873                for (var i = 0; i < swiperCoord.length; i++) {
11874                    var point = swiperCoord[i];
11875                    if (
11876                        point[0] >= windowScroll.left && point[0] <= windowScroll.left + windowWidth &&
11877                        point[1] >= windowScroll.top && point[1] <= windowScroll.top + windowHeight
11878                    ) {
11879                        inView = true;
11880                    }
11881
11882                }
11883                if (!inView) return;
11884            }
11885            if (isH()) {
11886                if (kc === 37 || kc === 39) {
11887                    if (e.preventDefault) e.preventDefault();
11888                    else e.returnValue = false;
11889                }
11890                if ((kc === 39 && !s.rtl) || (kc === 37 && s.rtl)) s.slideNext();
11891                if ((kc === 37 && !s.rtl) || (kc === 39 && s.rtl)) s.slidePrev();
11892            }
11893            else {
11894                if (kc === 38 || kc === 40) {
11895                    if (e.preventDefault) e.preventDefault();
11896                    else e.returnValue = false;
11897                }
11898                if (kc === 40) s.slideNext();
11899                if (kc === 38) s.slidePrev();
11900            }
11901        }
11902        s.disableKeyboardControl = function () {
11903            s.params.keyboardControl = false;
11904            $(document).off('keydown', handleKeyboard);
11905        };
11906        s.enableKeyboardControl = function () {
11907            s.params.keyboardControl = true;
11908            $(document).on('keydown', handleKeyboard);
11909        };
11910
11911
11912        /*=========================
11913          Mousewheel Control
11914          ===========================*/
11915        s.mousewheel = {
11916            event: false,
11917            lastScrollTime: (new window.Date()).getTime()
11918        };
11919        if (s.params.mousewheelControl) {
11920            try {
11921                new window.WheelEvent('wheel');
11922                s.mousewheel.event = 'wheel';
11923            } catch (e) {}
11924
11925            if (!s.mousewheel.event && document.onmousewheel !== undefined) {
11926                s.mousewheel.event = 'mousewheel';
11927            }
11928            if (!s.mousewheel.event) {
11929                s.mousewheel.event = 'DOMMouseScroll';
11930            }
11931        }
11932        function handleMousewheel(e) {
11933            if (e.originalEvent) e = e.originalEvent; //jquery fix
11934            var we = s.mousewheel.event;
11935            var delta = 0;
11936            var rtlFactor = s.rtl ? -1 : 1;
11937            //Opera & IE
11938            if (e.detail) delta = -e.detail;
11939            //WebKits
11940            else if (we === 'mousewheel') {
11941                if (s.params.mousewheelForceToAxis) {
11942                    if (isH()) {
11943                        if (Math.abs(e.wheelDeltaX) > Math.abs(e.wheelDeltaY)) delta = e.wheelDeltaX * rtlFactor;
11944                        else return;
11945                    }
11946                    else {
11947                        if (Math.abs(e.wheelDeltaY) > Math.abs(e.wheelDeltaX)) delta = e.wheelDeltaY;
11948                        else return;
11949                    }
11950                }
11951                else {
11952                    delta = Math.abs(e.wheelDeltaX) > Math.abs(e.wheelDeltaY) ? - e.wheelDeltaX * rtlFactor : - e.wheelDeltaY;
11953                }
11954            }
11955            //Old FireFox
11956            else if (we === 'DOMMouseScroll') delta = -e.detail;
11957            //New FireFox
11958            else if (we === 'wheel') {
11959                if (s.params.mousewheelForceToAxis) {
11960                    if (isH()) {
11961                        if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) delta = -e.deltaX * rtlFactor;
11962                        else return;
11963                    }
11964                    else {
11965                        if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) delta = -e.deltaY;
11966                        else return;
11967                    }
11968                }
11969                else {
11970                    delta = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? - e.deltaX * rtlFactor : - e.deltaY;
11971                }
11972            }
11973            if (delta === 0) return;
11974
11975            if (s.params.mousewheelInvert) delta = -delta;
11976
11977            if (!s.params.freeMode) {
11978                if ((new window.Date()).getTime() - s.mousewheel.lastScrollTime > 60) {
11979                    if (delta < 0) {
11980                        if ((!s.isEnd || s.params.loop) && !s.animating) s.slideNext();
11981                        else if (s.params.mousewheelReleaseOnEdges) return true;
11982                    }
11983                    else {
11984                        if ((!s.isBeginning || s.params.loop) && !s.animating) s.slidePrev();
11985                        else if (s.params.mousewheelReleaseOnEdges) return true;
11986                    }
11987                }
11988                s.mousewheel.lastScrollTime = (new window.Date()).getTime();
11989
11990            }
11991            else {
11992                //Freemode or scrollContainer:
11993                var position = s.getWrapperTranslate() + delta * s.params.mousewheelSensitivity;
11994                var wasBeginning = s.isBeginning,
11995                    wasEnd = s.isEnd;
11996
11997                if (position >= s.minTranslate()) position = s.minTranslate();
11998                if (position <= s.maxTranslate()) position = s.maxTranslate();
11999
12000                s.setWrapperTransition(0);
12001                s.setWrapperTranslate(position);
12002                s.updateProgress();
12003                s.updateActiveIndex();
12004
12005                if (!wasBeginning && s.isBeginning || !wasEnd && s.isEnd) {
12006                    s.updateClasses();
12007                }
12008
12009                if (s.params.freeModeSticky) {
12010                    clearTimeout(s.mousewheel.timeout);
12011                    s.mousewheel.timeout = setTimeout(function () {
12012                        s.slideReset();
12013                    }, 300);
12014                }
12015
12016                // Return page scroll on edge positions
12017                if (position === 0 || position === s.maxTranslate()) return;
12018            }
12019            if (s.params.autoplay) s.stopAutoplay();
12020
12021            if (e.preventDefault) e.preventDefault();
12022            else e.returnValue = false;
12023            return false;
12024        }
12025        s.disableMousewheelControl = function () {
12026            if (!s.mousewheel.event) return false;
12027            s.container.off(s.mousewheel.event, handleMousewheel);
12028            return true;
12029        };
12030
12031        s.enableMousewheelControl = function () {
12032            if (!s.mousewheel.event) return false;
12033            s.container.on(s.mousewheel.event, handleMousewheel);
12034            return true;
12035        };
12036
12037
12038        /*=========================
12039          Parallax
12040          ===========================*/
12041        function setParallaxTransform(el, progress) {
12042            el = $(el);
12043            var p, pX, pY;
12044            var rtlFactor = s.rtl ? -1 : 1;
12045
12046            p = el.attr('data-swiper-parallax') || '0';
12047            pX = el.attr('data-swiper-parallax-x');
12048            pY = el.attr('data-swiper-parallax-y');
12049            if (pX || pY) {
12050                pX = pX || '0';
12051                pY = pY || '0';
12052            }
12053            else {
12054                if (isH()) {
12055                    pX = p;
12056                    pY = '0';
12057                }
12058                else {
12059                    pY = p;
12060                    pX = '0';
12061                }
12062            }
12063
12064            if ((pX).indexOf('%') >= 0) {
12065                pX = parseInt(pX, 10) * progress * rtlFactor + '%';
12066            }
12067            else {
12068                pX = pX * progress * rtlFactor + 'px' ;
12069            }
12070            if ((pY).indexOf('%') >= 0) {
12071                pY = parseInt(pY, 10) * progress + '%';
12072            }
12073            else {
12074                pY = pY * progress + 'px' ;
12075            }
12076
12077            el.transform('translate3d(' + pX + ', ' + pY + ',0px)');
12078        }
12079        s.parallax = {
12080            setTranslate: function () {
12081                s.container.children('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function(){
12082                    setParallaxTransform(this, s.progress);
12083
12084                });
12085                s.slides.each(function () {
12086                    var slide = $(this);
12087                    slide.find('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function () {
12088                        var progress = Math.min(Math.max(slide[0].progress, -1), 1);
12089                        setParallaxTransform(this, progress);
12090                    });
12091                });
12092            },
12093            setTransition: function (duration) {
12094                if (typeof duration === 'undefined') duration = s.params.speed;
12095                s.container.find('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function(){
12096                    var el = $(this);
12097                    var parallaxDuration = parseInt(el.attr('data-swiper-parallax-duration'), 10) || duration;
12098                    if (duration === 0) parallaxDuration = 0;
12099                    el.transition(parallaxDuration);
12100                });
12101            }
12102        };
12103
12104
12105        /*=========================
12106          Plugins API. Collect all and init all plugins
12107          ===========================*/
12108        s._plugins = [];
12109        for (var plugin in s.plugins) {
12110            var p = s.plugins[plugin](s, s.params[plugin]);
12111            if (p) s._plugins.push(p);
12112        }
12113        // Method to call all plugins event/method
12114        s.callPlugins = function (eventName) {
12115            for (var i = 0; i < s._plugins.length; i++) {
12116                if (eventName in s._plugins[i]) {
12117                    s._plugins[i][eventName](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
12118                }
12119            }
12120        };
12121
12122        /*=========================
12123          Events/Callbacks/Plugins Emitter
12124          ===========================*/
12125        function normalizeEventName (eventName) {
12126            if (eventName.indexOf('on') !== 0) {
12127                if (eventName[0] !== eventName[0].toUpperCase()) {
12128                    eventName = 'on' + eventName[0].toUpperCase() + eventName.substring(1);
12129                }
12130                else {
12131                    eventName = 'on' + eventName;
12132                }
12133            }
12134            return eventName;
12135        }
12136        s.emitterEventListeners = {
12137
12138        };
12139        s.emit = function (eventName) {
12140            // Trigger callbacks
12141            if (s.params[eventName]) {
12142                s.params[eventName](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
12143            }
12144            var i;
12145            // Trigger events
12146            if (s.emitterEventListeners[eventName]) {
12147                for (i = 0; i < s.emitterEventListeners[eventName].length; i++) {
12148                    s.emitterEventListeners[eventName][i](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
12149                }
12150            }
12151            // Trigger plugins
12152            if (s.callPlugins) s.callPlugins(eventName, arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
12153        };
12154        s.on = function (eventName, handler) {
12155            eventName = normalizeEventName(eventName);
12156            if (!s.emitterEventListeners[eventName]) s.emitterEventListeners[eventName] = [];
12157            s.emitterEventListeners[eventName].push(handler);
12158            return s;
12159        };
12160        s.off = function (eventName, handler) {
12161            var i;
12162            eventName = normalizeEventName(eventName);
12163            if (typeof handler === 'undefined') {
12164                // Remove all handlers for such event
12165                s.emitterEventListeners[eventName] = [];
12166                return s;
12167            }
12168            if (!s.emitterEventListeners[eventName] || s.emitterEventListeners[eventName].length === 0) return;
12169            for (i = 0; i < s.emitterEventListeners[eventName].length; i++) {
12170                if(s.emitterEventListeners[eventName][i] === handler) s.emitterEventListeners[eventName].splice(i, 1);
12171            }
12172            return s;
12173        };
12174        s.once = function (eventName, handler) {
12175            eventName = normalizeEventName(eventName);
12176            var _handler = function () {
12177                handler(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
12178                s.off(eventName, _handler);
12179            };
12180            s.on(eventName, _handler);
12181            return s;
12182        };
12183
12184        // Accessibility tools
12185        s.a11y = {
12186            makeFocusable: function ($el) {
12187                $el.attr('tabIndex', '0');
12188                return $el;
12189            },
12190            addRole: function ($el, role) {
12191                $el.attr('role', role);
12192                return $el;
12193            },
12194
12195            addLabel: function ($el, label) {
12196                $el.attr('aria-label', label);
12197                return $el;
12198            },
12199
12200            disable: function ($el) {
12201                $el.attr('aria-disabled', true);
12202                return $el;
12203            },
12204
12205            enable: function ($el) {
12206                $el.attr('aria-disabled', false);
12207                return $el;
12208            },
12209
12210            onEnterKey: function (event) {
12211                if (event.keyCode !== 13) return;
12212                if ($(event.target).is(s.params.nextButton)) {
12213                    s.onClickNext(event);
12214                    if (s.isEnd) {
12215                        s.a11y.notify(s.params.lastSlideMessage);
12216                    }
12217                    else {
12218                        s.a11y.notify(s.params.nextSlideMessage);
12219                    }
12220                }
12221                else if ($(event.target).is(s.params.prevButton)) {
12222                    s.onClickPrev(event);
12223                    if (s.isBeginning) {
12224                        s.a11y.notify(s.params.firstSlideMessage);
12225                    }
12226                    else {
12227                        s.a11y.notify(s.params.prevSlideMessage);
12228                    }
12229                }
12230                if ($(event.target).is('.' + s.params.bulletClass)) {
12231                    $(event.target)[0].click();
12232                }
12233            },
12234
12235            liveRegion: $('<span class="swiper-notification" aria-live="assertive" aria-atomic="true"></span>'),
12236
12237            notify: function (message) {
12238                var notification = s.a11y.liveRegion;
12239                if (notification.length === 0) return;
12240                notification.html('');
12241                notification.html(message);
12242            },
12243            init: function () {
12244                // Setup accessibility
12245                if (s.params.nextButton) {
12246                    var nextButton = $(s.params.nextButton);
12247                    s.a11y.makeFocusable(nextButton);
12248                    s.a11y.addRole(nextButton, 'button');
12249                    s.a11y.addLabel(nextButton, s.params.nextSlideMessage);
12250                }
12251                if (s.params.prevButton) {
12252                    var prevButton = $(s.params.prevButton);
12253                    s.a11y.makeFocusable(prevButton);
12254                    s.a11y.addRole(prevButton, 'button');
12255                    s.a11y.addLabel(prevButton, s.params.prevSlideMessage);
12256                }
12257
12258                $(s.container).append(s.a11y.liveRegion);
12259            },
12260            initPagination: function () {
12261                if (s.params.pagination && s.params.paginationClickable && s.bullets && s.bullets.length) {
12262                    s.bullets.each(function () {
12263                        var bullet = $(this);
12264                        s.a11y.makeFocusable(bullet);
12265                        s.a11y.addRole(bullet, 'button');
12266                        s.a11y.addLabel(bullet, s.params.paginationBulletMessage.replace(/{{index}}/, bullet.index() + 1));
12267                    });
12268                }
12269            },
12270            destroy: function () {
12271                if (s.a11y.liveRegion && s.a11y.liveRegion.length > 0) s.a11y.liveRegion.remove();
12272            }
12273        };
12274
12275
12276        /*=========================
12277          Init/Destroy
12278          ===========================*/
12279        s.init = function () {
12280            if (s.params.loop) s.createLoop();
12281            s.updateContainerSize();
12282            s.updateSlidesSize();
12283            s.updatePagination();
12284            if (s.params.scrollbar && s.scrollbar) {
12285                s.scrollbar.set();
12286                if (s.params.scrollbarDraggable) {
12287                    s.scrollbar.enableDraggable();
12288                }
12289            }
12290            if (s.params.effect !== 'slide' && s.effects[s.params.effect]) {
12291                if (!s.params.loop) s.updateProgress();
12292                s.effects[s.params.effect].setTranslate();
12293            }
12294            if (s.params.loop) {
12295                s.slideTo(s.params.initialSlide + s.loopedSlides, 0, s.params.runCallbacksOnInit);
12296            }
12297            else {
12298                s.slideTo(s.params.initialSlide, 0, s.params.runCallbacksOnInit);
12299                if (s.params.initialSlide === 0) {
12300                    if (s.parallax && s.params.parallax) s.parallax.setTranslate();
12301                    if (s.lazy && s.params.lazyLoading) {
12302                        s.lazy.load();
12303                        s.lazy.initialImageLoaded = true;
12304                    }
12305                }
12306            }
12307            s.attachEvents();
12308            if (s.params.observer && s.support.observer) {
12309                s.initObservers();
12310            }
12311            if (s.params.preloadImages && !s.params.lazyLoading) {
12312                s.preloadImages();
12313            }
12314            if (s.params.autoplay) {
12315                s.startAutoplay();
12316            }
12317            if (s.params.keyboardControl) {
12318                if (s.enableKeyboardControl) s.enableKeyboardControl();
12319            }
12320            if (s.params.mousewheelControl) {
12321                if (s.enableMousewheelControl) s.enableMousewheelControl();
12322            }
12323            if (s.params.hashnav) {
12324                if (s.hashnav) s.hashnav.init();
12325            }
12326            if (s.params.a11y && s.a11y) s.a11y.init();
12327            s.emit('onInit', s);
12328        };
12329
12330        // Cleanup dynamic styles
12331        s.cleanupStyles = function () {
12332            // Container
12333            s.container.removeClass(s.classNames.join(' ')).removeAttr('style');
12334
12335            // Wrapper
12336            s.wrapper.removeAttr('style');
12337
12338            // Slides
12339            if (s.slides && s.slides.length) {
12340                s.slides
12341                    .removeClass([
12342                      s.params.slideVisibleClass,
12343                      s.params.slideActiveClass,
12344                      s.params.slideNextClass,
12345                      s.params.slidePrevClass
12346                    ].join(' '))
12347                    .removeAttr('style')
12348                    .removeAttr('data-swiper-column')
12349                    .removeAttr('data-swiper-row');
12350            }
12351
12352            // Pagination/Bullets
12353            if (s.paginationContainer && s.paginationContainer.length) {
12354                s.paginationContainer.removeClass(s.params.paginationHiddenClass);
12355            }
12356            if (s.bullets && s.bullets.length) {
12357                s.bullets.removeClass(s.params.bulletActiveClass);
12358            }
12359
12360            // Buttons
12361            if (s.params.prevButton) $(s.params.prevButton).removeClass(s.params.buttonDisabledClass);
12362            if (s.params.nextButton) $(s.params.nextButton).removeClass(s.params.buttonDisabledClass);
12363
12364            // Scrollbar
12365            if (s.params.scrollbar && s.scrollbar) {
12366                if (s.scrollbar.track && s.scrollbar.track.length) s.scrollbar.track.removeAttr('style');
12367                if (s.scrollbar.drag && s.scrollbar.drag.length) s.scrollbar.drag.removeAttr('style');
12368            }
12369        };
12370
12371        // Destroy
12372        s.destroy = function (deleteInstance, cleanupStyles) {
12373            // Detach evebts
12374            s.detachEvents();
12375            // Stop autoplay
12376            s.stopAutoplay();
12377            // Disable draggable
12378            if (s.params.scrollbar && s.scrollbar) {
12379                if (s.params.scrollbarDraggable) {
12380                    s.scrollbar.disableDraggable();
12381                }
12382            }
12383            // Destroy loop
12384            if (s.params.loop) {
12385                s.destroyLoop();
12386            }
12387            // Cleanup styles
12388            if (cleanupStyles) {
12389                s.cleanupStyles();
12390            }
12391            // Disconnect observer
12392            s.disconnectObservers();
12393            // Disable keyboard/mousewheel
12394            if (s.params.keyboardControl) {
12395                if (s.disableKeyboardControl) s.disableKeyboardControl();
12396            }
12397            if (s.params.mousewheelControl) {
12398                if (s.disableMousewheelControl) s.disableMousewheelControl();
12399            }
12400            // Disable a11y
12401            if (s.params.a11y && s.a11y) s.a11y.destroy();
12402            // Destroy callback
12403            s.emit('onDestroy');
12404            // Delete instance
12405            if (deleteInstance !== false) s = null;
12406        };
12407
12408        s.init();
12409
12410
12411
12412        // Return swiper instance
12413        return s;
12414    };
12415
12416
12417    /*==================================================
12418        Prototype
12419    ====================================================*/
12420    Swiper.prototype = {
12421        isSafari: (function () {
12422            var ua = navigator.userAgent.toLowerCase();
12423            return (ua.indexOf('safari') >= 0 && ua.indexOf('chrome') < 0 && ua.indexOf('android') < 0);
12424        })(),
12425        isUiWebView: /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent),
12426        isArray: function (arr) {
12427            return Object.prototype.toString.apply(arr) === '[object Array]';
12428        },
12429        /*==================================================
12430        Browser
12431        ====================================================*/
12432        browser: {
12433            ie: window.navigator.pointerEnabled || window.navigator.msPointerEnabled,
12434            ieTouch: (window.navigator.msPointerEnabled && window.navigator.msMaxTouchPoints > 1) || (window.navigator.pointerEnabled && window.navigator.maxTouchPoints > 1)
12435        },
12436        /*==================================================
12437        Devices
12438        ====================================================*/
12439        device: (function () {
12440            var ua = navigator.userAgent;
12441            var android = ua.match(/(Android);?[\s\/]+([\d.]+)?/);
12442            var ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
12443            var ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/);
12444            var iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/);
12445            return {
12446                ios: ipad || iphone || ipod,
12447                android: android
12448            };
12449        })(),
12450        /*==================================================
12451        Feature Detection
12452        ====================================================*/
12453        support: {
12454            touch : (window.Modernizr && Modernizr.touch === true) || (function () {
12455                return !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch);
12456            })(),
12457
12458            transforms3d : (window.Modernizr && Modernizr.csstransforms3d === true) || (function () {
12459                var div = document.createElement('div').style;
12460                return ('webkitPerspective' in div || 'MozPerspective' in div || 'OPerspective' in div || 'MsPerspective' in div || 'perspective' in div);
12461            })(),
12462
12463            flexbox: (function () {
12464                var div = document.createElement('div').style;
12465                var styles = ('alignItems webkitAlignItems webkitBoxAlign msFlexAlign mozBoxAlign webkitFlexDirection msFlexDirection mozBoxDirection mozBoxOrient webkitBoxDirection webkitBoxOrient').split(' ');
12466                for (var i = 0; i < styles.length; i++) {
12467                    if (styles[i] in div) return true;
12468                }
12469            })(),
12470
12471            observer: (function () {
12472                return ('MutationObserver' in window || 'WebkitMutationObserver' in window);
12473            })()
12474        },
12475        /*==================================================
12476        Plugins
12477        ====================================================*/
12478        plugins: {}
12479    };
12480
12481
12482    /*===========================
12483    Dom7 Library
12484    ===========================*/
12485    var Dom7 = (function () {
12486        var Dom7 = function (arr) {
12487            var _this = this, i = 0;
12488            // Create array-like object
12489            for (i = 0; i < arr.length; i++) {
12490                _this[i] = arr[i];
12491            }
12492            _this.length = arr.length;
12493            // Return collection with methods
12494            return this;
12495        };
12496        var $ = function (selector, context) {
12497            var arr = [], i = 0;
12498            if (selector && !context) {
12499                if (selector instanceof Dom7) {
12500                    return selector;
12501                }
12502            }
12503            if (selector) {
12504                // String
12505                if (typeof selector === 'string') {
12506                    var els, tempParent, html = selector.trim();
12507                    if (html.indexOf('<') >= 0 && html.indexOf('>') >= 0) {
12508                        var toCreate = 'div';
12509                        if (html.indexOf('<li') === 0) toCreate = 'ul';
12510                        if (html.indexOf('<tr') === 0) toCreate = 'tbody';
12511                        if (html.indexOf('<td') === 0 || html.indexOf('<th') === 0) toCreate = 'tr';
12512                        if (html.indexOf('<tbody') === 0) toCreate = 'table';
12513                        if (html.indexOf('<option') === 0) toCreate = 'select';
12514                        tempParent = document.createElement(toCreate);
12515                        tempParent.innerHTML = selector;
12516                        for (i = 0; i < tempParent.childNodes.length; i++) {
12517                            arr.push(tempParent.childNodes[i]);
12518                        }
12519                    }
12520                    else {
12521                        if (!context && selector[0] === '#' && !selector.match(/[ .<>:~]/)) {
12522                            // Pure ID selector
12523                            els = [document.getElementById(selector.split('#')[1])];
12524                        }
12525                        else {
12526                            // Other selectors
12527                            els = (context || document).querySelectorAll(selector);
12528                        }
12529                        for (i = 0; i < els.length; i++) {
12530                            if (els[i]) arr.push(els[i]);
12531                        }
12532                    }
12533                }
12534                // Node/element
12535                else if (selector.nodeType || selector === window || selector === document) {
12536                    arr.push(selector);
12537                }
12538                //Array of elements or instance of Dom
12539                else if (selector.length > 0 && selector[0].nodeType) {
12540                    for (i = 0; i < selector.length; i++) {
12541                        arr.push(selector[i]);
12542                    }
12543                }
12544            }
12545            return new Dom7(arr);
12546        };
12547        Dom7.prototype = {
12548            // Classes and attriutes
12549            addClass: function (className) {
12550                if (typeof className === 'undefined') {
12551                    return this;
12552                }
12553                var classes = className.split(' ');
12554                for (var i = 0; i < classes.length; i++) {
12555                    for (var j = 0; j < this.length; j++) {
12556                        this[j].classList.add(classes[i]);
12557                    }
12558                }
12559                return this;
12560            },
12561            removeClass: function (className) {
12562                var classes = className.split(' ');
12563                for (var i = 0; i < classes.length; i++) {
12564                    for (var j = 0; j < this.length; j++) {
12565                        this[j].classList.remove(classes[i]);
12566                    }
12567                }
12568                return this;
12569            },
12570            hasClass: function (className) {
12571                if (!this[0]) return false;
12572                else return this[0].classList.contains(className);
12573            },
12574            toggleClass: function (className) {
12575                var classes = className.split(' ');
12576                for (var i = 0; i < classes.length; i++) {
12577                    for (var j = 0; j < this.length; j++) {
12578                        this[j].classList.toggle(classes[i]);
12579                    }
12580                }
12581                return this;
12582            },
12583            attr: function (attrs, value) {
12584                if (arguments.length === 1 && typeof attrs === 'string') {
12585                    // Get attr
12586                    if (this[0]) return this[0].getAttribute(attrs);
12587                    else return undefined;
12588                }
12589                else {
12590                    // Set attrs
12591                    for (var i = 0; i < this.length; i++) {
12592                        if (arguments.length === 2) {
12593                            // String
12594                            this[i].setAttribute(attrs, value);
12595                        }
12596                        else {
12597                            // Object
12598                            for (var attrName in attrs) {
12599                                this[i][attrName] = attrs[attrName];
12600                                this[i].setAttribute(attrName, attrs[attrName]);
12601                            }
12602                        }
12603                    }
12604                    return this;
12605                }
12606            },
12607            removeAttr: function (attr) {
12608                for (var i = 0; i < this.length; i++) {
12609                    this[i].removeAttribute(attr);
12610                }
12611                return this;
12612            },
12613            data: function (key, value) {
12614                if (typeof value === 'undefined') {
12615                    // Get value
12616                    if (this[0]) {
12617                        var dataKey = this[0].getAttribute('data-' + key);
12618                        if (dataKey) return dataKey;
12619                        else if (this[0].dom7ElementDataStorage && (key in this[0].dom7ElementDataStorage)) return this[0].dom7ElementDataStorage[key];
12620                        else return undefined;
12621                    }
12622                    else return undefined;
12623                }
12624                else {
12625                    // Set value
12626                    for (var i = 0; i < this.length; i++) {
12627                        var el = this[i];
12628                        if (!el.dom7ElementDataStorage) el.dom7ElementDataStorage = {};
12629                        el.dom7ElementDataStorage[key] = value;
12630                    }
12631                    return this;
12632                }
12633            },
12634            // Transforms
12635            transform : function (transform) {
12636                for (var i = 0; i < this.length; i++) {
12637                    var elStyle = this[i].style;
12638                    elStyle.webkitTransform = elStyle.MsTransform = elStyle.msTransform = elStyle.MozTransform = elStyle.OTransform = elStyle.transform = transform;
12639                }
12640                return this;
12641            },
12642            transition: function (duration) {
12643                if (typeof duration !== 'string') {
12644                    duration = duration + 'ms';
12645                }
12646                for (var i = 0; i < this.length; i++) {
12647                    var elStyle = this[i].style;
12648                    elStyle.webkitTransitionDuration = elStyle.MsTransitionDuration = elStyle.msTransitionDuration = elStyle.MozTransitionDuration = elStyle.OTransitionDuration = elStyle.transitionDuration = duration;
12649                }
12650                return this;
12651            },
12652            //Events
12653            on: function (eventName, targetSelector, listener, capture) {
12654                function handleLiveEvent(e) {
12655                    var target = e.target;
12656                    if ($(target).is(targetSelector)) listener.call(target, e);
12657                    else {
12658                        var parents = $(target).parents();
12659                        for (var k = 0; k < parents.length; k++) {
12660                            if ($(parents[k]).is(targetSelector)) listener.call(parents[k], e);
12661                        }
12662                    }
12663                }
12664                var events = eventName.split(' ');
12665                var i, j;
12666                for (i = 0; i < this.length; i++) {
12667                    if (typeof targetSelector === 'function' || targetSelector === false) {
12668                        // Usual events
12669                        if (typeof targetSelector === 'function') {
12670                            listener = arguments[1];
12671                            capture = arguments[2] || false;
12672                        }
12673                        for (j = 0; j < events.length; j++) {
12674                            this[i].addEventListener(events[j], listener, capture);
12675                        }
12676                    }
12677                    else {
12678                        //Live events
12679                        for (j = 0; j < events.length; j++) {
12680                            if (!this[i].dom7LiveListeners) this[i].dom7LiveListeners = [];
12681                            this[i].dom7LiveListeners.push({listener: listener, liveListener: handleLiveEvent});
12682                            this[i].addEventListener(events[j], handleLiveEvent, capture);
12683                        }
12684                    }
12685                }
12686
12687                return this;
12688            },
12689            off: function (eventName, targetSelector, listener, capture) {
12690                var events = eventName.split(' ');
12691                for (var i = 0; i < events.length; i++) {
12692                    for (var j = 0; j < this.length; j++) {
12693                        if (typeof targetSelector === 'function' || targetSelector === false) {
12694                            // Usual events
12695                            if (typeof targetSelector === 'function') {
12696                                listener = arguments[1];
12697                                capture = arguments[2] || false;
12698                            }
12699                            this[j].removeEventListener(events[i], listener, capture);
12700                        }
12701                        else {
12702                            // Live event
12703                            if (this[j].dom7LiveListeners) {
12704                                for (var k = 0; k < this[j].dom7LiveListeners.length; k++) {
12705                                    if (this[j].dom7LiveListeners[k].listener === listener) {
12706                                        this[j].removeEventListener(events[i], this[j].dom7LiveListeners[k].liveListener, capture);
12707                                    }
12708                                }
12709                            }
12710                        }
12711                    }
12712                }
12713                return this;
12714            },
12715            once: function (eventName, targetSelector, listener, capture) {
12716                var dom = this;
12717                if (typeof targetSelector === 'function') {
12718                    targetSelector = false;
12719                    listener = arguments[1];
12720                    capture = arguments[2];
12721                }
12722                function proxy(e) {
12723                    listener(e);
12724                    dom.off(eventName, targetSelector, proxy, capture);
12725                }
12726                dom.on(eventName, targetSelector, proxy, capture);
12727            },
12728            trigger: function (eventName, eventData) {
12729                for (var i = 0; i < this.length; i++) {
12730                    var evt;
12731                    try {
12732                        evt = new window.CustomEvent(eventName, {detail: eventData, bubbles: true, cancelable: true});
12733                    }
12734                    catch (e) {
12735                        evt = document.createEvent('Event');
12736                        evt.initEvent(eventName, true, true);
12737                        evt.detail = eventData;
12738                    }
12739                    this[i].dispatchEvent(evt);
12740                }
12741                return this;
12742            },
12743            transitionEnd: function (callback) {
12744                var events = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'],
12745                    i, j, dom = this;
12746                function fireCallBack(e) {
12747                    /*jshint validthis:true */
12748                    if (e.target !== this) return;
12749                    callback.call(this, e);
12750                    for (i = 0; i < events.length; i++) {
12751                        dom.off(events[i], fireCallBack);
12752                    }
12753                }
12754                if (callback) {
12755                    for (i = 0; i < events.length; i++) {
12756                        dom.on(events[i], fireCallBack);
12757                    }
12758                }
12759                return this;
12760            },
12761            // Sizing/Styles
12762            width: function () {
12763                if (this[0] === window) {
12764                    return window.innerWidth;
12765                }
12766                else {
12767                    if (this.length > 0) {
12768                        return parseFloat(this.css('width'));
12769                    }
12770                    else {
12771                        return null;
12772                    }
12773                }
12774            },
12775            outerWidth: function (includeMargins) {
12776                if (this.length > 0) {
12777                    if (includeMargins)
12778                        return this[0].offsetWidth + parseFloat(this.css('margin-right')) + parseFloat(this.css('margin-left'));
12779                    else
12780                        return this[0].offsetWidth;
12781                }
12782                else return null;
12783            },
12784            height: function () {
12785                if (this[0] === window) {
12786                    return window.innerHeight;
12787                }
12788                else {
12789                    if (this.length > 0) {
12790                        return parseFloat(this.css('height'));
12791                    }
12792                    else {
12793                        return null;
12794                    }
12795                }
12796            },
12797            outerHeight: function (includeMargins) {
12798                if (this.length > 0) {
12799                    if (includeMargins)
12800                        return this[0].offsetHeight + parseFloat(this.css('margin-top')) + parseFloat(this.css('margin-bottom'));
12801                    else
12802                        return this[0].offsetHeight;
12803                }
12804                else return null;
12805            },
12806            offset: function () {
12807                if (this.length > 0) {
12808                    var el = this[0];
12809                    var box = el.getBoundingClientRect();
12810                    var body = document.body;
12811                    var clientTop  = el.clientTop  || body.clientTop  || 0;
12812                    var clientLeft = el.clientLeft || body.clientLeft || 0;
12813                    var scrollTop  = window.pageYOffset || el.scrollTop;
12814                    var scrollLeft = window.pageXOffset || el.scrollLeft;
12815                    return {
12816                        top: box.top  + scrollTop  - clientTop,
12817                        left: box.left + scrollLeft - clientLeft
12818                    };
12819                }
12820                else {
12821                    return null;
12822                }
12823            },
12824            css: function (props, value) {
12825                var i;
12826                if (arguments.length === 1) {
12827                    if (typeof props === 'string') {
12828                        if (this[0]) return window.getComputedStyle(this[0], null).getPropertyValue(props);
12829                    }
12830                    else {
12831                        for (i = 0; i < this.length; i++) {
12832                            for (var prop in props) {
12833                                this[i].style[prop] = props[prop];
12834                            }
12835                        }
12836                        return this;
12837                    }
12838                }
12839                if (arguments.length === 2 && typeof props === 'string') {
12840                    for (i = 0; i < this.length; i++) {
12841                        this[i].style[props] = value;
12842                    }
12843                    return this;
12844                }
12845                return this;
12846            },
12847
12848            //Dom manipulation
12849            each: function (callback) {
12850                for (var i = 0; i < this.length; i++) {
12851                    callback.call(this[i], i, this[i]);
12852                }
12853                return this;
12854            },
12855            html: function (html) {
12856                if (typeof html === 'undefined') {
12857                    return this[0] ? this[0].innerHTML : undefined;
12858                }
12859                else {
12860                    for (var i = 0; i < this.length; i++) {
12861                        this[i].innerHTML = html;
12862                    }
12863                    return this;
12864                }
12865            },
12866            is: function (selector) {
12867                if (!this[0]) return false;
12868                var compareWith, i;
12869                if (typeof selector === 'string') {
12870                    var el = this[0];
12871                    if (el === document) return selector === document;
12872                    if (el === window) return selector === window;
12873
12874                    if (el.matches) return el.matches(selector);
12875                    else if (el.webkitMatchesSelector) return el.webkitMatchesSelector(selector);
12876                    else if (el.mozMatchesSelector) return el.mozMatchesSelector(selector);
12877                    else if (el.msMatchesSelector) return el.msMatchesSelector(selector);
12878                    else {
12879                        compareWith = $(selector);
12880                        for (i = 0; i < compareWith.length; i++) {
12881                            if (compareWith[i] === this[0]) return true;
12882                        }
12883                        return false;
12884                    }
12885                }
12886                else if (selector === document) return this[0] === document;
12887                else if (selector === window) return this[0] === window;
12888                else {
12889                    if (selector.nodeType || selector instanceof Dom7) {
12890                        compareWith = selector.nodeType ? [selector] : selector;
12891                        for (i = 0; i < compareWith.length; i++) {
12892                            if (compareWith[i] === this[0]) return true;
12893                        }
12894                        return false;
12895                    }
12896                    return false;
12897                }
12898
12899            },
12900            index: function () {
12901                if (this[0]) {
12902                    var child = this[0];
12903                    var i = 0;
12904                    while ((child = child.previousSibling) !== null) {
12905                        if (child.nodeType === 1) i++;
12906                    }
12907                    return i;
12908                }
12909                else return undefined;
12910            },
12911            eq: function (index) {
12912                if (typeof index === 'undefined') return this;
12913                var length = this.length;
12914                var returnIndex;
12915                if (index > length - 1) {
12916                    return new Dom7([]);
12917                }
12918                if (index < 0) {
12919                    returnIndex = length + index;
12920                    if (returnIndex < 0) return new Dom7([]);
12921                    else return new Dom7([this[returnIndex]]);
12922                }
12923                return new Dom7([this[index]]);
12924            },
12925            append: function (newChild) {
12926                var i, j;
12927                for (i = 0; i < this.length; i++) {
12928                    if (typeof newChild === 'string') {
12929                        var tempDiv = document.createElement('div');
12930                        tempDiv.innerHTML = newChild;
12931                        while (tempDiv.firstChild) {
12932                            this[i].appendChild(tempDiv.firstChild);
12933                        }
12934                    }
12935                    else if (newChild instanceof Dom7) {
12936                        for (j = 0; j < newChild.length; j++) {
12937                            this[i].appendChild(newChild[j]);
12938                        }
12939                    }
12940                    else {
12941                        this[i].appendChild(newChild);
12942                    }
12943                }
12944                return this;
12945            },
12946            prepend: function (newChild) {
12947                var i, j;
12948                for (i = 0; i < this.length; i++) {
12949                    if (typeof newChild === 'string') {
12950                        var tempDiv = document.createElement('div');
12951                        tempDiv.innerHTML = newChild;
12952                        for (j = tempDiv.childNodes.length - 1; j >= 0; j--) {
12953                            this[i].insertBefore(tempDiv.childNodes[j], this[i].childNodes[0]);
12954                        }
12955                        // this[i].insertAdjacentHTML('afterbegin', newChild);
12956                    }
12957                    else if (newChild instanceof Dom7) {
12958                        for (j = 0; j < newChild.length; j++) {
12959                            this[i].insertBefore(newChild[j], this[i].childNodes[0]);
12960                        }
12961                    }
12962                    else {
12963                        this[i].insertBefore(newChild, this[i].childNodes[0]);
12964                    }
12965                }
12966                return this;
12967            },
12968            insertBefore: function (selector) {
12969                var before = $(selector);
12970                for (var i = 0; i < this.length; i++) {
12971                    if (before.length === 1) {
12972                        before[0].parentNode.insertBefore(this[i], before[0]);
12973                    }
12974                    else if (before.length > 1) {
12975                        for (var j = 0; j < before.length; j++) {
12976                            before[j].parentNode.insertBefore(this[i].cloneNode(true), before[j]);
12977                        }
12978                    }
12979                }
12980            },
12981            insertAfter: function (selector) {
12982                var after = $(selector);
12983                for (var i = 0; i < this.length; i++) {
12984                    if (after.length === 1) {
12985                        after[0].parentNode.insertBefore(this[i], after[0].nextSibling);
12986                    }
12987                    else if (after.length > 1) {
12988                        for (var j = 0; j < after.length; j++) {
12989                            after[j].parentNode.insertBefore(this[i].cloneNode(true), after[j].nextSibling);
12990                        }
12991                    }
12992                }
12993            },
12994            next: function (selector) {
12995                if (this.length > 0) {
12996                    if (selector) {
12997                        if (this[0].nextElementSibling && $(this[0].nextElementSibling).is(selector)) return new Dom7([this[0].nextElementSibling]);
12998                        else return new Dom7([]);
12999                    }
13000                    else {
13001                        if (this[0].nextElementSibling) return new Dom7([this[0].nextElementSibling]);
13002                        else return new Dom7([]);
13003                    }
13004                }
13005                else return new Dom7([]);
13006            },
13007            nextAll: function (selector) {
13008                var nextEls = [];
13009                var el = this[0];
13010                if (!el) return new Dom7([]);
13011                while (el.nextElementSibling) {
13012                    var next = el.nextElementSibling;
13013                    if (selector) {
13014                        if($(next).is(selector)) nextEls.push(next);
13015                    }
13016                    else nextEls.push(next);
13017                    el = next;
13018                }
13019                return new Dom7(nextEls);
13020            },
13021            prev: function (selector) {
13022                if (this.length > 0) {
13023                    if (selector) {
13024                        if (this[0].previousElementSibling && $(this[0].previousElementSibling).is(selector)) return new Dom7([this[0].previousElementSibling]);
13025                        else return new Dom7([]);
13026                    }
13027                    else {
13028                        if (this[0].previousElementSibling) return new Dom7([this[0].previousElementSibling]);
13029                        else return new Dom7([]);
13030                    }
13031                }
13032                else return new Dom7([]);
13033            },
13034            prevAll: function (selector) {
13035                var prevEls = [];
13036                var el = this[0];
13037                if (!el) return new Dom7([]);
13038                while (el.previousElementSibling) {
13039                    var prev = el.previousElementSibling;
13040                    if (selector) {
13041                        if($(prev).is(selector)) prevEls.push(prev);
13042                    }
13043                    else prevEls.push(prev);
13044                    el = prev;
13045                }
13046                return new Dom7(prevEls);
13047            },
13048            parent: function (selector) {
13049                var parents = [];
13050                for (var i = 0; i < this.length; i++) {
13051                    if (selector) {
13052                        if ($(this[i].parentNode).is(selector)) parents.push(this[i].parentNode);
13053                    }
13054                    else {
13055                        parents.push(this[i].parentNode);
13056                    }
13057                }
13058                return $($.unique(parents));
13059            },
13060            parents: function (selector) {
13061                var parents = [];
13062                for (var i = 0; i < this.length; i++) {
13063                    var parent = this[i].parentNode;
13064                    while (parent) {
13065                        if (selector) {
13066                            if ($(parent).is(selector)) parents.push(parent);
13067                        }
13068                        else {
13069                            parents.push(parent);
13070                        }
13071                        parent = parent.parentNode;
13072                    }
13073                }
13074                return $($.unique(parents));
13075            },
13076            find : function (selector) {
13077                var foundElements = [];
13078                for (var i = 0; i < this.length; i++) {
13079                    var found = this[i].querySelectorAll(selector);
13080                    for (var j = 0; j < found.length; j++) {
13081                        foundElements.push(found[j]);
13082                    }
13083                }
13084                return new Dom7(foundElements);
13085            },
13086            children: function (selector) {
13087                var children = [];
13088                for (var i = 0; i < this.length; i++) {
13089                    var childNodes = this[i].childNodes;
13090
13091                    for (var j = 0; j < childNodes.length; j++) {
13092                        if (!selector) {
13093                            if (childNodes[j].nodeType === 1) children.push(childNodes[j]);
13094                        }
13095                        else {
13096                            if (childNodes[j].nodeType === 1 && $(childNodes[j]).is(selector)) children.push(childNodes[j]);
13097                        }
13098                    }
13099                }
13100                return new Dom7($.unique(children));
13101            },
13102            remove: function () {
13103                for (var i = 0; i < this.length; i++) {
13104                    if (this[i].parentNode) this[i].parentNode.removeChild(this[i]);
13105                }
13106                return this;
13107            },
13108            add: function () {
13109                var dom = this;
13110                var i, j;
13111                for (i = 0; i < arguments.length; i++) {
13112                    var toAdd = $(arguments[i]);
13113                    for (j = 0; j < toAdd.length; j++) {
13114                        dom[dom.length] = toAdd[j];
13115                        dom.length++;
13116                    }
13117                }
13118                return dom;
13119            }
13120        };
13121        $.fn = Dom7.prototype;
13122        $.unique = function (arr) {
13123            var unique = [];
13124            for (var i = 0; i < arr.length; i++) {
13125                if (unique.indexOf(arr[i]) === -1) unique.push(arr[i]);
13126            }
13127            return unique;
13128        };
13129
13130        return $;
13131    })();
13132
13133
13134    /*===========================
13135     Get Dom libraries
13136     ===========================*/
13137    var swiperDomPlugins = ['jQuery', 'Zepto', 'Dom7'];
13138    for (var i = 0; i < swiperDomPlugins.length; i++) {
13139    	if (window[swiperDomPlugins[i]]) {
13140    		addLibraryPlugin(window[swiperDomPlugins[i]]);
13141    	}
13142    }
13143    // Required DOM Plugins
13144    var domLib;
13145    if (typeof Dom7 === 'undefined') {
13146    	domLib = window.Dom7 || window.Zepto || window.jQuery;
13147    }
13148    else {
13149    	domLib = Dom7;
13150    }
13151
13152    /*===========================
13153    Add .swiper plugin from Dom libraries
13154    ===========================*/
13155    function addLibraryPlugin(lib) {
13156        lib.fn.swiper = function (params) {
13157            var firstInstance;
13158            lib(this).each(function () {
13159                var s = new Swiper(this, params);
13160                if (!firstInstance) firstInstance = s;
13161            });
13162            return firstInstance;
13163        };
13164    }
13165
13166    if (domLib) {
13167        if (!('transitionEnd' in domLib.fn)) {
13168            domLib.fn.transitionEnd = function (callback) {
13169                var events = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'],
13170                    i, j, dom = this;
13171                function fireCallBack(e) {
13172                    /*jshint validthis:true */
13173                    if (e.target !== this) return;
13174                    callback.call(this, e);
13175                    for (i = 0; i < events.length; i++) {
13176                        dom.off(events[i], fireCallBack);
13177                    }
13178                }
13179                if (callback) {
13180                    for (i = 0; i < events.length; i++) {
13181                        dom.on(events[i], fireCallBack);
13182                    }
13183                }
13184                return this;
13185            };
13186        }
13187        if (!('transform' in domLib.fn)) {
13188            domLib.fn.transform = function (transform) {
13189                for (var i = 0; i < this.length; i++) {
13190                    var elStyle = this[i].style;
13191                    elStyle.webkitTransform = elStyle.MsTransform = elStyle.msTransform = elStyle.MozTransform = elStyle.OTransform = elStyle.transform = transform;
13192                }
13193                return this;
13194            };
13195        }
13196        if (!('transition' in domLib.fn)) {
13197            domLib.fn.transition = function (duration) {
13198                if (typeof duration !== 'string') {
13199                    duration = duration + 'ms';
13200                }
13201                for (var i = 0; i < this.length; i++) {
13202                    var elStyle = this[i].style;
13203                    elStyle.webkitTransitionDuration = elStyle.MsTransitionDuration = elStyle.msTransitionDuration = elStyle.MozTransitionDuration = elStyle.OTransitionDuration = elStyle.transitionDuration = duration;
13204                }
13205                return this;
13206            };
13207        }
13208    }
13209
13210    ionic.views.Swiper = Swiper;
13211})();
13212
13213(function(ionic) {
13214'use strict';
13215
13216  ionic.views.Toggle = ionic.views.View.inherit({
13217    initialize: function(opts) {
13218      var self = this;
13219
13220      this.el = opts.el;
13221      this.checkbox = opts.checkbox;
13222      this.track = opts.track;
13223      this.handle = opts.handle;
13224      this.openPercent = -1;
13225      this.onChange = opts.onChange || function() {};
13226
13227      this.triggerThreshold = opts.triggerThreshold || 20;
13228
13229      this.dragStartHandler = function(e) {
13230        self.dragStart(e);
13231      };
13232      this.dragHandler = function(e) {
13233        self.drag(e);
13234      };
13235      this.holdHandler = function(e) {
13236        self.hold(e);
13237      };
13238      this.releaseHandler = function(e) {
13239        self.release(e);
13240      };
13241
13242      this.dragStartGesture = ionic.onGesture('dragstart', this.dragStartHandler, this.el);
13243      this.dragGesture = ionic.onGesture('drag', this.dragHandler, this.el);
13244      this.dragHoldGesture = ionic.onGesture('hold', this.holdHandler, this.el);
13245      this.dragReleaseGesture = ionic.onGesture('release', this.releaseHandler, this.el);
13246    },
13247
13248    destroy: function() {
13249      ionic.offGesture(this.dragStartGesture, 'dragstart', this.dragStartGesture);
13250      ionic.offGesture(this.dragGesture, 'drag', this.dragGesture);
13251      ionic.offGesture(this.dragHoldGesture, 'hold', this.holdHandler);
13252      ionic.offGesture(this.dragReleaseGesture, 'release', this.releaseHandler);
13253    },
13254
13255    tap: function() {
13256      if(this.el.getAttribute('disabled') !== 'disabled') {
13257        this.val( !this.checkbox.checked );
13258      }
13259    },
13260
13261    dragStart: function(e) {
13262      if(this.checkbox.disabled) return;
13263
13264      this._dragInfo = {
13265        width: this.el.offsetWidth,
13266        left: this.el.offsetLeft,
13267        right: this.el.offsetLeft + this.el.offsetWidth,
13268        triggerX: this.el.offsetWidth / 2,
13269        initialState: this.checkbox.checked
13270      };
13271
13272      // Stop any parent dragging
13273      e.gesture.srcEvent.preventDefault();
13274
13275      // Trigger hold styles
13276      this.hold(e);
13277    },
13278
13279    drag: function(e) {
13280      var self = this;
13281      if(!this._dragInfo) { return; }
13282
13283      // Stop any parent dragging
13284      e.gesture.srcEvent.preventDefault();
13285
13286      ionic.requestAnimationFrame(function () {
13287        if (!self._dragInfo) { return; }
13288
13289        var px = e.gesture.touches[0].pageX - self._dragInfo.left;
13290        var mx = self._dragInfo.width - self.triggerThreshold;
13291
13292        // The initial state was on, so "tend towards" on
13293        if(self._dragInfo.initialState) {
13294          if(px < self.triggerThreshold) {
13295            self.setOpenPercent(0);
13296          } else if(px > self._dragInfo.triggerX) {
13297            self.setOpenPercent(100);
13298          }
13299        } else {
13300          // The initial state was off, so "tend towards" off
13301          if(px < self._dragInfo.triggerX) {
13302            self.setOpenPercent(0);
13303          } else if(px > mx) {
13304            self.setOpenPercent(100);
13305          }
13306        }
13307      });
13308    },
13309
13310    endDrag: function() {
13311      this._dragInfo = null;
13312    },
13313
13314    hold: function() {
13315      this.el.classList.add('dragging');
13316    },
13317    release: function(e) {
13318      this.el.classList.remove('dragging');
13319      this.endDrag(e);
13320    },
13321
13322
13323    setOpenPercent: function(openPercent) {
13324      // only make a change if the new open percent has changed
13325      if(this.openPercent < 0 || (openPercent < (this.openPercent - 3) || openPercent > (this.openPercent + 3) ) ) {
13326        this.openPercent = openPercent;
13327
13328        if(openPercent === 0) {
13329          this.val(false);
13330        } else if(openPercent === 100) {
13331          this.val(true);
13332        } else {
13333          var openPixel = Math.round( (openPercent / 100) * this.track.offsetWidth - (this.handle.offsetWidth) );
13334          openPixel = (openPixel < 1 ? 0 : openPixel);
13335          this.handle.style[ionic.CSS.TRANSFORM] = 'translate3d(' + openPixel + 'px,0,0)';
13336        }
13337      }
13338    },
13339
13340    val: function(value) {
13341      if(value === true || value === false) {
13342        if(this.handle.style[ionic.CSS.TRANSFORM] !== "") {
13343          this.handle.style[ionic.CSS.TRANSFORM] = "";
13344        }
13345        this.checkbox.checked = value;
13346        this.openPercent = (value ? 100 : 0);
13347        this.onChange && this.onChange();
13348      }
13349      return this.checkbox.checked;
13350    }
13351
13352  });
13353
13354})(ionic);
13355
13356})();