arnaucube
arnaucube/commonroutesServerPublic Self-hosted repository
commonroutesServer/app/www/lib/ionic/js/ionic-angular.js
app/www/lib/ionic/js/ionic-angular.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/* eslint no-unused-vars:0 */
   17var IonicModule = angular.module('ionic', ['ngAnimate', 'ngSanitize', 'ui.router', 'ngIOS9UIWebViewPatch']),
   18  extend = angular.extend,
   19  forEach = angular.forEach,
   20  isDefined = angular.isDefined,
   21  isNumber = angular.isNumber,
   22  isString = angular.isString,
   23  jqLite = angular.element,
   24  noop = angular.noop;
   25
   26/**
   27 * @ngdoc service
   28 * @name $ionicActionSheet
   29 * @module ionic
   30 * @description
   31 * The Action Sheet is a slide-up pane that lets the user choose from a set of options.
   32 * Dangerous options are highlighted in red and made obvious.
   33 *
   34 * There are easy ways to cancel out of the action sheet, such as tapping the backdrop or even
   35 * hitting escape on the keyboard for desktop testing.
   36 *
   37 * ![Action Sheet](http://ionicframework.com.s3.amazonaws.com/docs/controllers/actionSheet.gif)
   38 *
   39 * @usage
   40 * To trigger an Action Sheet in your code, use the $ionicActionSheet service in your angular controllers:
   41 *
   42 * ```js
   43 * angular.module('mySuperApp', ['ionic'])
   44 * .controller(function($scope, $ionicActionSheet, $timeout) {
   45 *
   46 *  // Triggered on a button click, or some other target
   47 *  $scope.show = function() {
   48 *
   49 *    // Show the action sheet
   50 *    var hideSheet = $ionicActionSheet.show({
   51 *      buttons: [
   52 *        { text: '<b>Share</b> This' },
   53 *        { text: 'Move' }
   54 *      ],
   55 *      destructiveText: 'Delete',
   56 *      titleText: 'Modify your album',
   57 *      cancelText: 'Cancel',
   58 *      cancel: function() {
   59          // add cancel code..
   60        },
   61 *      buttonClicked: function(index) {
   62 *        return true;
   63 *      }
   64 *    });
   65 *
   66 *    // For example's sake, hide the sheet after two seconds
   67 *    $timeout(function() {
   68 *      hideSheet();
   69 *    }, 2000);
   70 *
   71 *  };
   72 * });
   73 * ```
   74 *
   75 */
   76IonicModule
   77.factory('$ionicActionSheet', [
   78  '$rootScope',
   79  '$compile',
   80  '$animate',
   81  '$timeout',
   82  '$ionicTemplateLoader',
   83  '$ionicPlatform',
   84  '$ionicBody',
   85  'IONIC_BACK_PRIORITY',
   86function($rootScope, $compile, $animate, $timeout, $ionicTemplateLoader, $ionicPlatform, $ionicBody, IONIC_BACK_PRIORITY) {
   87
   88  return {
   89    show: actionSheet
   90  };
   91
   92  /**
   93   * @ngdoc method
   94   * @name $ionicActionSheet#show
   95   * @description
   96   * Load and return a new action sheet.
   97   *
   98   * A new isolated scope will be created for the
   99   * action sheet and the new element will be appended into the body.
  100   *
  101   * @param {object} options The options for this ActionSheet. Properties:
  102   *
  103   *  - `[Object]` `buttons` Which buttons to show.  Each button is an object with a `text` field.
  104   *  - `{string}` `titleText` The title to show on the action sheet.
  105   *  - `{string=}` `cancelText` the text for a 'cancel' button on the action sheet.
  106   *  - `{string=}` `destructiveText` The text for a 'danger' on the action sheet.
  107   *  - `{function=}` `cancel` Called if the cancel button is pressed, the backdrop is tapped or
  108   *     the hardware back button is pressed.
  109   *  - `{function=}` `buttonClicked` Called when one of the non-destructive buttons is clicked,
  110   *     with the index of the button that was clicked and the button object. Return true to close
  111   *     the action sheet, or false to keep it opened.
  112   *  - `{function=}` `destructiveButtonClicked` Called when the destructive button is clicked.
  113   *     Return true to close the action sheet, or false to keep it opened.
  114   *  -  `{boolean=}` `cancelOnStateChange` Whether to cancel the actionSheet when navigating
  115   *     to a new state.  Default true.
  116   *  - `{string}` `cssClass` The custom CSS class name.
  117   *
  118   * @returns {function} `hideSheet` A function which, when called, hides & cancels the action sheet.
  119   */
  120  function actionSheet(opts) {
  121    var scope = $rootScope.$new(true);
  122
  123    extend(scope, {
  124      cancel: noop,
  125      destructiveButtonClicked: noop,
  126      buttonClicked: noop,
  127      $deregisterBackButton: noop,
  128      buttons: [],
  129      cancelOnStateChange: true
  130    }, opts || {});
  131
  132    function textForIcon(text) {
  133      if (text && /icon/.test(text)) {
  134        scope.$actionSheetHasIcon = true;
  135      }
  136    }
  137
  138    for (var x = 0; x < scope.buttons.length; x++) {
  139      textForIcon(scope.buttons[x].text);
  140    }
  141    textForIcon(scope.cancelText);
  142    textForIcon(scope.destructiveText);
  143
  144    // Compile the template
  145    var element = scope.element = $compile('<ion-action-sheet ng-class="cssClass" buttons="buttons"></ion-action-sheet>')(scope);
  146
  147    // Grab the sheet element for animation
  148    var sheetEl = jqLite(element[0].querySelector('.action-sheet-wrapper'));
  149
  150    var stateChangeListenDone = scope.cancelOnStateChange ?
  151      $rootScope.$on('$stateChangeSuccess', function() { scope.cancel(); }) :
  152      noop;
  153
  154    // removes the actionSheet from the screen
  155    scope.removeSheet = function(done) {
  156      if (scope.removed) return;
  157
  158      scope.removed = true;
  159      sheetEl.removeClass('action-sheet-up');
  160      $timeout(function() {
  161        // wait to remove this due to a 300ms delay native
  162        // click which would trigging whatever was underneath this
  163        $ionicBody.removeClass('action-sheet-open');
  164      }, 400);
  165      scope.$deregisterBackButton();
  166      stateChangeListenDone();
  167
  168      $animate.removeClass(element, 'active').then(function() {
  169        scope.$destroy();
  170        element.remove();
  171        // scope.cancel.$scope is defined near the bottom
  172        scope.cancel.$scope = sheetEl = null;
  173        (done || noop)(opts.buttons);
  174      });
  175    };
  176
  177    scope.showSheet = function(done) {
  178      if (scope.removed) return;
  179
  180      $ionicBody.append(element)
  181                .addClass('action-sheet-open');
  182
  183      $animate.addClass(element, 'active').then(function() {
  184        if (scope.removed) return;
  185        (done || noop)();
  186      });
  187      $timeout(function() {
  188        if (scope.removed) return;
  189        sheetEl.addClass('action-sheet-up');
  190      }, 20, false);
  191    };
  192
  193    // registerBackButtonAction returns a callback to deregister the action
  194    scope.$deregisterBackButton = $ionicPlatform.registerBackButtonAction(
  195      function() {
  196        $timeout(scope.cancel);
  197      },
  198      IONIC_BACK_PRIORITY.actionSheet
  199    );
  200
  201    // called when the user presses the cancel button
  202    scope.cancel = function() {
  203      // after the animation is out, call the cancel callback
  204      scope.removeSheet(opts.cancel);
  205    };
  206
  207    scope.buttonClicked = function(index) {
  208      // Check if the button click event returned true, which means
  209      // we can close the action sheet
  210      if (opts.buttonClicked(index, opts.buttons[index]) === true) {
  211        scope.removeSheet();
  212      }
  213    };
  214
  215    scope.destructiveButtonClicked = function() {
  216      // Check if the destructive button click event returned true, which means
  217      // we can close the action sheet
  218      if (opts.destructiveButtonClicked() === true) {
  219        scope.removeSheet();
  220      }
  221    };
  222
  223    scope.showSheet();
  224
  225    // Expose the scope on $ionicActionSheet's return value for the sake
  226    // of testing it.
  227    scope.cancel.$scope = scope;
  228
  229    return scope.cancel;
  230  }
  231}]);
  232
  233
  234jqLite.prototype.addClass = function(cssClasses) {
  235  var x, y, cssClass, el, splitClasses, existingClasses;
  236  if (cssClasses && cssClasses != 'ng-scope' && cssClasses != 'ng-isolate-scope') {
  237    for (x = 0; x < this.length; x++) {
  238      el = this[x];
  239      if (el.setAttribute) {
  240
  241        if (cssClasses.indexOf(' ') < 0 && el.classList.add) {
  242          el.classList.add(cssClasses);
  243        } else {
  244          existingClasses = (' ' + (el.getAttribute('class') || '') + ' ')
  245            .replace(/[\n\t]/g, " ");
  246          splitClasses = cssClasses.split(' ');
  247
  248          for (y = 0; y < splitClasses.length; y++) {
  249            cssClass = splitClasses[y].trim();
  250            if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
  251              existingClasses += cssClass + ' ';
  252            }
  253          }
  254          el.setAttribute('class', existingClasses.trim());
  255        }
  256      }
  257    }
  258  }
  259  return this;
  260};
  261
  262jqLite.prototype.removeClass = function(cssClasses) {
  263  var x, y, splitClasses, cssClass, el;
  264  if (cssClasses) {
  265    for (x = 0; x < this.length; x++) {
  266      el = this[x];
  267      if (el.getAttribute) {
  268        if (cssClasses.indexOf(' ') < 0 && el.classList.remove) {
  269          el.classList.remove(cssClasses);
  270        } else {
  271          splitClasses = cssClasses.split(' ');
  272
  273          for (y = 0; y < splitClasses.length; y++) {
  274            cssClass = splitClasses[y];
  275            el.setAttribute('class', (
  276                (" " + (el.getAttribute('class') || '') + " ")
  277                .replace(/[\n\t]/g, " ")
  278                .replace(" " + cssClass.trim() + " ", " ")).trim()
  279            );
  280          }
  281        }
  282      }
  283    }
  284  }
  285  return this;
  286};
  287
  288/**
  289 * @ngdoc service
  290 * @name $ionicBackdrop
  291 * @module ionic
  292 * @description
  293 * Shows and hides a backdrop over the UI.  Appears behind popups, loading,
  294 * and other overlays.
  295 *
  296 * Often, multiple UI components require a backdrop, but only one backdrop is
  297 * ever needed in the DOM at a time.
  298 *
  299 * Therefore, each component that requires the backdrop to be shown calls
  300 * `$ionicBackdrop.retain()` when it wants the backdrop, then `$ionicBackdrop.release()`
  301 * when it is done with the backdrop.
  302 *
  303 * For each time `retain` is called, the backdrop will be shown until `release` is called.
  304 *
  305 * For example, if `retain` is called three times, the backdrop will be shown until `release`
  306 * is called three times.
  307 *
  308 * **Notes:**
  309 * - The backdrop service will broadcast 'backdrop.shown' and 'backdrop.hidden' events from the root scope,
  310 * this is useful for alerting native components not in html.
  311 *
  312 * @usage
  313 *
  314 * ```js
  315 * function MyController($scope, $ionicBackdrop, $timeout, $rootScope) {
  316 *   //Show a backdrop for one second
  317 *   $scope.action = function() {
  318 *     $ionicBackdrop.retain();
  319 *     $timeout(function() {
  320 *       $ionicBackdrop.release();
  321 *     }, 1000);
  322 *   };
  323 *
  324 *   // Execute action on backdrop disappearing
  325 *   $scope.$on('backdrop.hidden', function() {
  326 *     // Execute action
  327 *   });
  328 *
  329 *   // Execute action on backdrop appearing
  330 *   $scope.$on('backdrop.shown', function() {
  331 *     // Execute action
  332 *   });
  333 *
  334 * }
  335 * ```
  336 */
  337IonicModule
  338.factory('$ionicBackdrop', [
  339  '$document', '$timeout', '$$rAF', '$rootScope',
  340function($document, $timeout, $$rAF, $rootScope) {
  341
  342  var el = jqLite('<div class="backdrop">');
  343  var backdropHolds = 0;
  344
  345  $document[0].body.appendChild(el[0]);
  346
  347  return {
  348    /**
  349     * @ngdoc method
  350     * @name $ionicBackdrop#retain
  351     * @description Retains the backdrop.
  352     */
  353    retain: retain,
  354    /**
  355     * @ngdoc method
  356     * @name $ionicBackdrop#release
  357     * @description
  358     * Releases the backdrop.
  359     */
  360    release: release,
  361
  362    getElement: getElement,
  363
  364    // exposed for testing
  365    _element: el
  366  };
  367
  368  function retain() {
  369    backdropHolds++;
  370    if (backdropHolds === 1) {
  371      el.addClass('visible');
  372      $rootScope.$broadcast('backdrop.shown');
  373      $$rAF(function() {
  374        // If we're still at >0 backdropHolds after async...
  375        if (backdropHolds >= 1) el.addClass('active');
  376      });
  377    }
  378  }
  379  function release() {
  380    if (backdropHolds === 1) {
  381      el.removeClass('active');
  382      $rootScope.$broadcast('backdrop.hidden');
  383      $timeout(function() {
  384        // If we're still at 0 backdropHolds after async...
  385        if (backdropHolds === 0) el.removeClass('visible');
  386      }, 400, false);
  387    }
  388    backdropHolds = Math.max(0, backdropHolds - 1);
  389  }
  390
  391  function getElement() {
  392    return el;
  393  }
  394
  395}]);
  396
  397/**
  398 * @private
  399 */
  400IonicModule
  401.factory('$ionicBind', ['$parse', '$interpolate', function($parse, $interpolate) {
  402  var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
  403  return function(scope, attrs, bindDefinition) {
  404    forEach(bindDefinition || {}, function(definition, scopeName) {
  405      //Adapted from angular.js $compile
  406      var match = definition.match(LOCAL_REGEXP) || [],
  407        attrName = match[3] || scopeName,
  408        mode = match[1], // @, =, or &
  409        parentGet,
  410        unwatch;
  411
  412      switch (mode) {
  413        case '@':
  414          if (!attrs[attrName]) {
  415            return;
  416          }
  417          attrs.$observe(attrName, function(value) {
  418            scope[scopeName] = value;
  419          });
  420          // we trigger an interpolation to ensure
  421          // the value is there for use immediately
  422          if (attrs[attrName]) {
  423            scope[scopeName] = $interpolate(attrs[attrName])(scope);
  424          }
  425          break;
  426
  427        case '=':
  428          if (!attrs[attrName]) {
  429            return;
  430          }
  431          unwatch = scope.$watch(attrs[attrName], function(value) {
  432            scope[scopeName] = value;
  433          });
  434          //Destroy parent scope watcher when this scope is destroyed
  435          scope.$on('$destroy', unwatch);
  436          break;
  437
  438        case '&':
  439          /* jshint -W044 */
  440          if (attrs[attrName] && attrs[attrName].match(RegExp(scopeName + '\(.*?\)'))) {
  441            throw new Error('& expression binding "' + scopeName + '" looks like it will recursively call "' +
  442                          attrs[attrName] + '" and cause a stack overflow! Please choose a different scopeName.');
  443          }
  444          parentGet = $parse(attrs[attrName]);
  445          scope[scopeName] = function(locals) {
  446            return parentGet(scope, locals);
  447          };
  448          break;
  449      }
  450    });
  451  };
  452}]);
  453
  454/**
  455 * @ngdoc service
  456 * @name $ionicBody
  457 * @module ionic
  458 * @description An angular utility service to easily and efficiently
  459 * add and remove CSS classes from the document's body element.
  460 */
  461IonicModule
  462.factory('$ionicBody', ['$document', function($document) {
  463  return {
  464    /**
  465     * @ngdoc method
  466     * @name $ionicBody#addClass
  467     * @description Add a class to the document's body element.
  468     * @param {string} class Each argument will be added to the body element.
  469     * @returns {$ionicBody} The $ionicBody service so methods can be chained.
  470     */
  471    addClass: function() {
  472      for (var x = 0; x < arguments.length; x++) {
  473        $document[0].body.classList.add(arguments[x]);
  474      }
  475      return this;
  476    },
  477    /**
  478     * @ngdoc method
  479     * @name $ionicBody#removeClass
  480     * @description Remove a class from the document's body element.
  481     * @param {string} class Each argument will be removed from the body element.
  482     * @returns {$ionicBody} The $ionicBody service so methods can be chained.
  483     */
  484    removeClass: function() {
  485      for (var x = 0; x < arguments.length; x++) {
  486        $document[0].body.classList.remove(arguments[x]);
  487      }
  488      return this;
  489    },
  490    /**
  491     * @ngdoc method
  492     * @name $ionicBody#enableClass
  493     * @description Similar to the `add` method, except the first parameter accepts a boolean
  494     * value determining if the class should be added or removed. Rather than writing user code,
  495     * such as "if true then add the class, else then remove the class", this method can be
  496     * given a true or false value which reduces redundant code.
  497     * @param {boolean} shouldEnableClass A true/false value if the class should be added or removed.
  498     * @param {string} class Each remaining argument would be added or removed depending on
  499     * the first argument.
  500     * @returns {$ionicBody} The $ionicBody service so methods can be chained.
  501     */
  502    enableClass: function(shouldEnableClass) {
  503      var args = Array.prototype.slice.call(arguments).slice(1);
  504      if (shouldEnableClass) {
  505        this.addClass.apply(this, args);
  506      } else {
  507        this.removeClass.apply(this, args);
  508      }
  509      return this;
  510    },
  511    /**
  512     * @ngdoc method
  513     * @name $ionicBody#append
  514     * @description Append a child to the document's body.
  515     * @param {element} element The element to be appended to the body. The passed in element
  516     * can be either a jqLite element, or a DOM element.
  517     * @returns {$ionicBody} The $ionicBody service so methods can be chained.
  518     */
  519    append: function(ele) {
  520      $document[0].body.appendChild(ele.length ? ele[0] : ele);
  521      return this;
  522    },
  523    /**
  524     * @ngdoc method
  525     * @name $ionicBody#get
  526     * @description Get the document's body element.
  527     * @returns {element} Returns the document's body element.
  528     */
  529    get: function() {
  530      return $document[0].body;
  531    }
  532  };
  533}]);
  534
  535IonicModule
  536.factory('$ionicClickBlock', [
  537  '$document',
  538  '$ionicBody',
  539  '$timeout',
  540function($document, $ionicBody, $timeout) {
  541  var CSS_HIDE = 'click-block-hide';
  542  var cbEle, fallbackTimer, pendingShow;
  543
  544  function preventClick(ev) {
  545    ev.preventDefault();
  546    ev.stopPropagation();
  547  }
  548
  549  function addClickBlock() {
  550    if (pendingShow) {
  551      if (cbEle) {
  552        cbEle.classList.remove(CSS_HIDE);
  553      } else {
  554        cbEle = $document[0].createElement('div');
  555        cbEle.className = 'click-block';
  556        $ionicBody.append(cbEle);
  557        cbEle.addEventListener('touchstart', preventClick);
  558        cbEle.addEventListener('mousedown', preventClick);
  559      }
  560      pendingShow = false;
  561    }
  562  }
  563
  564  function removeClickBlock() {
  565    cbEle && cbEle.classList.add(CSS_HIDE);
  566  }
  567
  568  return {
  569    show: function(autoExpire) {
  570      pendingShow = true;
  571      $timeout.cancel(fallbackTimer);
  572      fallbackTimer = $timeout(this.hide, autoExpire || 310, false);
  573      addClickBlock();
  574    },
  575    hide: function() {
  576      pendingShow = false;
  577      $timeout.cancel(fallbackTimer);
  578      removeClickBlock();
  579    }
  580  };
  581}]);
  582
  583/**
  584 * @ngdoc service
  585 * @name $ionicGesture
  586 * @module ionic
  587 * @description An angular service exposing ionic
  588 * {@link ionic.utility:ionic.EventController}'s gestures.
  589 */
  590IonicModule
  591.factory('$ionicGesture', [function() {
  592  return {
  593    /**
  594     * @ngdoc method
  595     * @name $ionicGesture#on
  596     * @description Add an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#onGesture}.
  597     * @param {string} eventType The gesture event to listen for.
  598     * @param {function(e)} callback The function to call when the gesture
  599     * happens.
  600     * @param {element} $element The angular element to listen for the event on.
  601     * @param {object} options object.
  602     * @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on).
  603     */
  604    on: function(eventType, cb, $element, options) {
  605      return window.ionic.onGesture(eventType, cb, $element[0], options);
  606    },
  607    /**
  608     * @ngdoc method
  609     * @name $ionicGesture#off
  610     * @description Remove an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#offGesture}.
  611     * @param {ionic.Gesture} gesture The gesture that should be removed.
  612     * @param {string} eventType The gesture event to remove the listener for.
  613     * @param {function(e)} callback The listener to remove.
  614     */
  615    off: function(gesture, eventType, cb) {
  616      return window.ionic.offGesture(gesture, eventType, cb);
  617    }
  618  };
  619}]);
  620
  621/**
  622 * @ngdoc service
  623 * @name $ionicHistory
  624 * @module ionic
  625 * @description
  626 * $ionicHistory keeps track of views as the user navigates through an app. Similar to the way a
  627 * browser behaves, an Ionic app is able to keep track of the previous view, the current view, and
  628 * the forward view (if there is one).  However, a typical web browser only keeps track of one
  629 * history stack in a linear fashion.
  630 *
  631 * Unlike a traditional browser environment, apps and webapps have parallel independent histories,
  632 * such as with tabs. Should a user navigate few pages deep on one tab, and then switch to a new
  633 * tab and back, the back button relates not to the previous tab, but to the previous pages
  634 * visited within _that_ tab.
  635 *
  636 * `$ionicHistory` facilitates this parallel history architecture.
  637 */
  638
  639IonicModule
  640.factory('$ionicHistory', [
  641  '$rootScope',
  642  '$state',
  643  '$location',
  644  '$window',
  645  '$timeout',
  646  '$ionicViewSwitcher',
  647  '$ionicNavViewDelegate',
  648function($rootScope, $state, $location, $window, $timeout, $ionicViewSwitcher, $ionicNavViewDelegate) {
  649
  650  // history actions while navigating views
  651  var ACTION_INITIAL_VIEW = 'initialView';
  652  var ACTION_NEW_VIEW = 'newView';
  653  var ACTION_MOVE_BACK = 'moveBack';
  654  var ACTION_MOVE_FORWARD = 'moveForward';
  655
  656  // direction of navigation
  657  var DIRECTION_BACK = 'back';
  658  var DIRECTION_FORWARD = 'forward';
  659  var DIRECTION_ENTER = 'enter';
  660  var DIRECTION_EXIT = 'exit';
  661  var DIRECTION_SWAP = 'swap';
  662  var DIRECTION_NONE = 'none';
  663
  664  var stateChangeCounter = 0;
  665  var lastStateId, nextViewOptions, deregisterStateChangeListener, nextViewExpireTimer, forcedNav;
  666
  667  var viewHistory = {
  668    histories: { root: { historyId: 'root', parentHistoryId: null, stack: [], cursor: -1 } },
  669    views: {},
  670    backView: null,
  671    forwardView: null,
  672    currentView: null
  673  };
  674
  675  var View = function() {};
  676  View.prototype.initialize = function(data) {
  677    if (data) {
  678      for (var name in data) this[name] = data[name];
  679      return this;
  680    }
  681    return null;
  682  };
  683  View.prototype.go = function() {
  684
  685    if (this.stateName) {
  686      return $state.go(this.stateName, this.stateParams);
  687    }
  688
  689    if (this.url && this.url !== $location.url()) {
  690
  691      if (viewHistory.backView === this) {
  692        return $window.history.go(-1);
  693      } else if (viewHistory.forwardView === this) {
  694        return $window.history.go(1);
  695      }
  696
  697      $location.url(this.url);
  698    }
  699
  700    return null;
  701  };
  702  View.prototype.destroy = function() {
  703    if (this.scope) {
  704      this.scope.$destroy && this.scope.$destroy();
  705      this.scope = null;
  706    }
  707  };
  708
  709
  710  function getViewById(viewId) {
  711    return (viewId ? viewHistory.views[ viewId ] : null);
  712  }
  713
  714  function getBackView(view) {
  715    return (view ? getViewById(view.backViewId) : null);
  716  }
  717
  718  function getForwardView(view) {
  719    return (view ? getViewById(view.forwardViewId) : null);
  720  }
  721
  722  function getHistoryById(historyId) {
  723    return (historyId ? viewHistory.histories[ historyId ] : null);
  724  }
  725
  726  function getHistory(scope) {
  727    var histObj = getParentHistoryObj(scope);
  728
  729    if (!viewHistory.histories[ histObj.historyId ]) {
  730      // this history object exists in parent scope, but doesn't
  731      // exist in the history data yet
  732      viewHistory.histories[ histObj.historyId ] = {
  733        historyId: histObj.historyId,
  734        parentHistoryId: getParentHistoryObj(histObj.scope.$parent).historyId,
  735        stack: [],
  736        cursor: -1
  737      };
  738    }
  739    return getHistoryById(histObj.historyId);
  740  }
  741
  742  function getParentHistoryObj(scope) {
  743    var parentScope = scope;
  744    while (parentScope) {
  745      if (parentScope.hasOwnProperty('$historyId')) {
  746        // this parent scope has a historyId
  747        return { historyId: parentScope.$historyId, scope: parentScope };
  748      }
  749      // nothing found keep climbing up
  750      parentScope = parentScope.$parent;
  751    }
  752    // no history for the parent, use the root
  753    return { historyId: 'root', scope: $rootScope };
  754  }
  755
  756  function setNavViews(viewId) {
  757    viewHistory.currentView = getViewById(viewId);
  758    viewHistory.backView = getBackView(viewHistory.currentView);
  759    viewHistory.forwardView = getForwardView(viewHistory.currentView);
  760  }
  761
  762  function getCurrentStateId() {
  763    var id;
  764    if ($state && $state.current && $state.current.name) {
  765      id = $state.current.name;
  766      if ($state.params) {
  767        for (var key in $state.params) {
  768          if ($state.params.hasOwnProperty(key) && $state.params[key]) {
  769            id += "_" + key + "=" + $state.params[key];
  770          }
  771        }
  772      }
  773      return id;
  774    }
  775    // if something goes wrong make sure its got a unique stateId
  776    return ionic.Utils.nextUid();
  777  }
  778
  779  function getCurrentStateParams() {
  780    var rtn;
  781    if ($state && $state.params) {
  782      for (var key in $state.params) {
  783        if ($state.params.hasOwnProperty(key)) {
  784          rtn = rtn || {};
  785          rtn[key] = $state.params[key];
  786        }
  787      }
  788    }
  789    return rtn;
  790  }
  791
  792
  793  return {
  794
  795    register: function(parentScope, viewLocals) {
  796
  797      var currentStateId = getCurrentStateId(),
  798          hist = getHistory(parentScope),
  799          currentView = viewHistory.currentView,
  800          backView = viewHistory.backView,
  801          forwardView = viewHistory.forwardView,
  802          viewId = null,
  803          action = null,
  804          direction = DIRECTION_NONE,
  805          historyId = hist.historyId,
  806          url = $location.url(),
  807          tmp, x, ele;
  808
  809      if (lastStateId !== currentStateId) {
  810        lastStateId = currentStateId;
  811        stateChangeCounter++;
  812      }
  813
  814      if (forcedNav) {
  815        // we've previously set exactly what to do
  816        viewId = forcedNav.viewId;
  817        action = forcedNav.action;
  818        direction = forcedNav.direction;
  819        forcedNav = null;
  820
  821      } else if (backView && backView.stateId === currentStateId) {
  822        // they went back one, set the old current view as a forward view
  823        viewId = backView.viewId;
  824        historyId = backView.historyId;
  825        action = ACTION_MOVE_BACK;
  826        if (backView.historyId === currentView.historyId) {
  827          // went back in the same history
  828          direction = DIRECTION_BACK;
  829
  830        } else if (currentView) {
  831          direction = DIRECTION_EXIT;
  832
  833          tmp = getHistoryById(backView.historyId);
  834          if (tmp && tmp.parentHistoryId === currentView.historyId) {
  835            direction = DIRECTION_ENTER;
  836
  837          } else {
  838            tmp = getHistoryById(currentView.historyId);
  839            if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
  840              direction = DIRECTION_SWAP;
  841            }
  842          }
  843        }
  844
  845      } else if (forwardView && forwardView.stateId === currentStateId) {
  846        // they went to the forward one, set the forward view to no longer a forward view
  847        viewId = forwardView.viewId;
  848        historyId = forwardView.historyId;
  849        action = ACTION_MOVE_FORWARD;
  850        if (forwardView.historyId === currentView.historyId) {
  851          direction = DIRECTION_FORWARD;
  852
  853        } else if (currentView) {
  854          direction = DIRECTION_EXIT;
  855
  856          if (currentView.historyId === hist.parentHistoryId) {
  857            direction = DIRECTION_ENTER;
  858
  859          } else {
  860            tmp = getHistoryById(currentView.historyId);
  861            if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
  862              direction = DIRECTION_SWAP;
  863            }
  864          }
  865        }
  866
  867        tmp = getParentHistoryObj(parentScope);
  868        if (forwardView.historyId && tmp.scope) {
  869          // if a history has already been created by the forward view then make sure it stays the same
  870          tmp.scope.$historyId = forwardView.historyId;
  871          historyId = forwardView.historyId;
  872        }
  873
  874      } else if (currentView && currentView.historyId !== historyId &&
  875                hist.cursor > -1 && hist.stack.length > 0 && hist.cursor < hist.stack.length &&
  876                hist.stack[hist.cursor].stateId === currentStateId) {
  877        // they just changed to a different history and the history already has views in it
  878        var switchToView = hist.stack[hist.cursor];
  879        viewId = switchToView.viewId;
  880        historyId = switchToView.historyId;
  881        action = ACTION_MOVE_BACK;
  882        direction = DIRECTION_SWAP;
  883
  884        tmp = getHistoryById(currentView.historyId);
  885        if (tmp && tmp.parentHistoryId === historyId) {
  886          direction = DIRECTION_EXIT;
  887
  888        } else {
  889          tmp = getHistoryById(historyId);
  890          if (tmp && tmp.parentHistoryId === currentView.historyId) {
  891            direction = DIRECTION_ENTER;
  892          }
  893        }
  894
  895        // if switching to a different history, and the history of the view we're switching
  896        // to has an existing back view from a different history than itself, then
  897        // it's back view would be better represented using the current view as its back view
  898        tmp = getViewById(switchToView.backViewId);
  899        if (tmp && switchToView.historyId !== tmp.historyId) {
  900          // the new view is being removed from it's old position in the history and being placed at the top,
  901          // so we need to update any views that reference it as a backview, otherwise there will be infinitely loops
  902          var viewIds = Object.keys(viewHistory.views);
  903          viewIds.forEach(function(viewId) {
  904            var view = viewHistory.views[viewId];
  905            if ( view.backViewId === switchToView.viewId ) {
  906              view.backViewId = null;
  907            }
  908          });
  909
  910          hist.stack[hist.cursor].backViewId = currentView.viewId;
  911        }
  912
  913      } else {
  914
  915        // create an element from the viewLocals template
  916        ele = $ionicViewSwitcher.createViewEle(viewLocals);
  917        if (this.isAbstractEle(ele, viewLocals)) {
  918          return {
  919            action: 'abstractView',
  920            direction: DIRECTION_NONE,
  921            ele: ele
  922          };
  923        }
  924
  925        // set a new unique viewId
  926        viewId = ionic.Utils.nextUid();
  927
  928        if (currentView) {
  929          // set the forward view if there is a current view (ie: if its not the first view)
  930          currentView.forwardViewId = viewId;
  931
  932          action = ACTION_NEW_VIEW;
  933
  934          // check if there is a new forward view within the same history
  935          if (forwardView && currentView.stateId !== forwardView.stateId &&
  936             currentView.historyId === forwardView.historyId) {
  937            // they navigated to a new view but the stack already has a forward view
  938            // since its a new view remove any forwards that existed
  939            tmp = getHistoryById(forwardView.historyId);
  940            if (tmp) {
  941              // the forward has a history
  942              for (x = tmp.stack.length - 1; x >= forwardView.index; x--) {
  943                // starting from the end destroy all forwards in this history from this point
  944                var stackItem = tmp.stack[x];
  945                stackItem && stackItem.destroy && stackItem.destroy();
  946                tmp.stack.splice(x);
  947              }
  948              historyId = forwardView.historyId;
  949            }
  950          }
  951
  952          // its only moving forward if its in the same history
  953          if (hist.historyId === currentView.historyId) {
  954            direction = DIRECTION_FORWARD;
  955
  956          } else if (currentView.historyId !== hist.historyId) {
  957            // DB: this is a new view in a different tab
  958            direction = DIRECTION_ENTER;
  959
  960            tmp = getHistoryById(currentView.historyId);
  961            if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
  962              direction = DIRECTION_SWAP;
  963
  964            } else {
  965              tmp = getHistoryById(tmp.parentHistoryId);
  966              if (tmp && tmp.historyId === hist.historyId) {
  967                direction = DIRECTION_EXIT;
  968              }
  969            }
  970          }
  971
  972        } else {
  973          // there's no current view, so this must be the initial view
  974          action = ACTION_INITIAL_VIEW;
  975        }
  976
  977        if (stateChangeCounter < 2) {
  978          // views that were spun up on the first load should not animate
  979          direction = DIRECTION_NONE;
  980        }
  981
  982        // add the new view
  983        viewHistory.views[viewId] = this.createView({
  984          viewId: viewId,
  985          index: hist.stack.length,
  986          historyId: hist.historyId,
  987          backViewId: (currentView && currentView.viewId ? currentView.viewId : null),
  988          forwardViewId: null,
  989          stateId: currentStateId,
  990          stateName: this.currentStateName(),
  991          stateParams: getCurrentStateParams(),
  992          url: url,
  993          canSwipeBack: canSwipeBack(ele, viewLocals)
  994        });
  995
  996        // add the new view to this history's stack
  997        hist.stack.push(viewHistory.views[viewId]);
  998      }
  999
 1000      deregisterStateChangeListener && deregisterStateChangeListener();
 1001      $timeout.cancel(nextViewExpireTimer);
 1002      if (nextViewOptions) {
 1003        if (nextViewOptions.disableAnimate) direction = DIRECTION_NONE;
 1004        if (nextViewOptions.disableBack) viewHistory.views[viewId].backViewId = null;
 1005        if (nextViewOptions.historyRoot) {
 1006          for (x = 0; x < hist.stack.length; x++) {
 1007            if (hist.stack[x].viewId === viewId) {
 1008              hist.stack[x].index = 0;
 1009              hist.stack[x].backViewId = hist.stack[x].forwardViewId = null;
 1010            } else {
 1011              delete viewHistory.views[hist.stack[x].viewId];
 1012            }
 1013          }
 1014          hist.stack = [viewHistory.views[viewId]];
 1015        }
 1016        nextViewOptions = null;
 1017      }
 1018
 1019      setNavViews(viewId);
 1020
 1021      if (viewHistory.backView && historyId == viewHistory.backView.historyId && currentStateId == viewHistory.backView.stateId && url == viewHistory.backView.url) {
 1022        for (x = 0; x < hist.stack.length; x++) {
 1023          if (hist.stack[x].viewId == viewId) {
 1024            action = 'dupNav';
 1025            direction = DIRECTION_NONE;
 1026            if (x > 0) {
 1027              hist.stack[x - 1].forwardViewId = null;
 1028            }
 1029            viewHistory.forwardView = null;
 1030            viewHistory.currentView.index = viewHistory.backView.index;
 1031            viewHistory.currentView.backViewId = viewHistory.backView.backViewId;
 1032            viewHistory.backView = getBackView(viewHistory.backView);
 1033            hist.stack.splice(x, 1);
 1034            break;
 1035          }
 1036        }
 1037      }
 1038
 1039      hist.cursor = viewHistory.currentView.index;
 1040
 1041      return {
 1042        viewId: viewId,
 1043        action: action,
 1044        direction: direction,
 1045        historyId: historyId,
 1046        enableBack: this.enabledBack(viewHistory.currentView),
 1047        isHistoryRoot: (viewHistory.currentView.index === 0),
 1048        ele: ele
 1049      };
 1050    },
 1051
 1052    registerHistory: function(scope) {
 1053      scope.$historyId = ionic.Utils.nextUid();
 1054    },
 1055
 1056    createView: function(data) {
 1057      var newView = new View();
 1058      return newView.initialize(data);
 1059    },
 1060
 1061    getViewById: getViewById,
 1062
 1063    /**
 1064     * @ngdoc method
 1065     * @name $ionicHistory#viewHistory
 1066     * @description The app's view history data, such as all the views and histories, along
 1067     * with how they are ordered and linked together within the navigation stack.
 1068     * @returns {object} Returns an object containing the apps view history data.
 1069     */
 1070    viewHistory: function() {
 1071      return viewHistory;
 1072    },
 1073
 1074    /**
 1075     * @ngdoc method
 1076     * @name $ionicHistory#currentView
 1077     * @description The app's current view.
 1078     * @returns {object} Returns the current view.
 1079     */
 1080    currentView: function(view) {
 1081      if (arguments.length) {
 1082        viewHistory.currentView = view;
 1083      }
 1084      return viewHistory.currentView;
 1085    },
 1086
 1087    /**
 1088     * @ngdoc method
 1089     * @name $ionicHistory#currentHistoryId
 1090     * @description The ID of the history stack which is the parent container of the current view.
 1091     * @returns {string} Returns the current history ID.
 1092     */
 1093    currentHistoryId: function() {
 1094      return viewHistory.currentView ? viewHistory.currentView.historyId : null;
 1095    },
 1096
 1097    /**
 1098     * @ngdoc method
 1099     * @name $ionicHistory#currentTitle
 1100     * @description Gets and sets the current view's title.
 1101     * @param {string=} val The title to update the current view with.
 1102     * @returns {string} Returns the current view's title.
 1103     */
 1104    currentTitle: function(val) {
 1105      if (viewHistory.currentView) {
 1106        if (arguments.length) {
 1107          viewHistory.currentView.title = val;
 1108        }
 1109        return viewHistory.currentView.title;
 1110      }
 1111    },
 1112
 1113    /**
 1114     * @ngdoc method
 1115     * @name $ionicHistory#backView
 1116     * @description Returns the view that was before the current view in the history stack.
 1117     * If the user navigated from View A to View B, then View A would be the back view, and
 1118     * View B would be the current view.
 1119     * @returns {object} Returns the back view.
 1120     */
 1121    backView: function(view) {
 1122      if (arguments.length) {
 1123        viewHistory.backView = view;
 1124      }
 1125      return viewHistory.backView;
 1126    },
 1127
 1128    /**
 1129     * @ngdoc method
 1130     * @name $ionicHistory#backTitle
 1131     * @description Gets the back view's title.
 1132     * @returns {string} Returns the back view's title.
 1133     */
 1134    backTitle: function(view) {
 1135      var backView = (view && getViewById(view.backViewId)) || viewHistory.backView;
 1136      return backView && backView.title;
 1137    },
 1138
 1139    /**
 1140     * @ngdoc method
 1141     * @name $ionicHistory#forwardView
 1142     * @description Returns the view that was in front of the current view in the history stack.
 1143     * A forward view would exist if the user navigated from View A to View B, then
 1144     * navigated back to View A. At this point then View B would be the forward view, and View
 1145     * A would be the current view.
 1146     * @returns {object} Returns the forward view.
 1147     */
 1148    forwardView: function(view) {
 1149      if (arguments.length) {
 1150        viewHistory.forwardView = view;
 1151      }
 1152      return viewHistory.forwardView;
 1153    },
 1154
 1155    /**
 1156     * @ngdoc method
 1157     * @name $ionicHistory#currentStateName
 1158     * @description Returns the current state name.
 1159     * @returns {string}
 1160     */
 1161    currentStateName: function() {
 1162      return ($state && $state.current ? $state.current.name : null);
 1163    },
 1164
 1165    isCurrentStateNavView: function(navView) {
 1166      return !!($state && $state.current && $state.current.views && $state.current.views[navView]);
 1167    },
 1168
 1169    goToHistoryRoot: function(historyId) {
 1170      if (historyId) {
 1171        var hist = getHistoryById(historyId);
 1172        if (hist && hist.stack.length) {
 1173          if (viewHistory.currentView && viewHistory.currentView.viewId === hist.stack[0].viewId) {
 1174            return;
 1175          }
 1176          forcedNav = {
 1177            viewId: hist.stack[0].viewId,
 1178            action: ACTION_MOVE_BACK,
 1179            direction: DIRECTION_BACK
 1180          };
 1181          hist.stack[0].go();
 1182        }
 1183      }
 1184    },
 1185
 1186    /**
 1187     * @ngdoc method
 1188     * @name $ionicHistory#goBack
 1189     * @param {number=} backCount Optional negative integer setting how many views to go
 1190     * back. By default it'll go back one view by using the value `-1`. To go back two
 1191     * views you would use `-2`. If the number goes farther back than the number of views
 1192     * in the current history's stack then it'll go to the first view in the current history's
 1193     * stack. If the number is zero or greater then it'll do nothing. It also does not
 1194     * cross history stacks, meaning it can only go as far back as the current history.
 1195     * @description Navigates the app to the back view, if a back view exists.
 1196     */
 1197    goBack: function(backCount) {
 1198      if (isDefined(backCount) && backCount !== -1) {
 1199        if (backCount > -1) return;
 1200
 1201        var currentHistory = viewHistory.histories[this.currentHistoryId()];
 1202        var newCursor = currentHistory.cursor + backCount + 1;
 1203        if (newCursor < 1) {
 1204          newCursor = 1;
 1205        }
 1206
 1207        currentHistory.cursor = newCursor;
 1208        setNavViews(currentHistory.stack[newCursor].viewId);
 1209
 1210        var cursor = newCursor - 1;
 1211        var clearStateIds = [];
 1212        var fwdView = getViewById(currentHistory.stack[cursor].forwardViewId);
 1213        while (fwdView) {
 1214          clearStateIds.push(fwdView.stateId || fwdView.viewId);
 1215          cursor++;
 1216          if (cursor >= currentHistory.stack.length) break;
 1217          fwdView = getViewById(currentHistory.stack[cursor].forwardViewId);
 1218        }
 1219
 1220        var self = this;
 1221        if (clearStateIds.length) {
 1222          $timeout(function() {
 1223            self.clearCache(clearStateIds);
 1224          }, 300);
 1225        }
 1226      }
 1227
 1228      viewHistory.backView && viewHistory.backView.go();
 1229    },
 1230
 1231    /**
 1232     * @ngdoc method
 1233     * @name $ionicHistory#removeBackView
 1234     * @description Remove the previous view from the history completely, including the
 1235     * cached element and scope (if they exist).
 1236     */
 1237    removeBackView: function() {
 1238      var self = this;
 1239      var currentHistory = viewHistory.histories[this.currentHistoryId()];
 1240      var currentCursor = currentHistory.cursor;
 1241
 1242      var currentView = currentHistory.stack[currentCursor];
 1243      var backView = currentHistory.stack[currentCursor - 1];
 1244      var replacementView = currentHistory.stack[currentCursor - 2];
 1245
 1246      // fail if we dont have enough views in the history
 1247      if (!backView || !replacementView) {
 1248        return;
 1249      }
 1250
 1251      // remove the old backView and the cached element/scope
 1252      currentHistory.stack.splice(currentCursor - 1, 1);
 1253      self.clearCache([backView.viewId]);
 1254      // make the replacementView and currentView point to each other (bypass the old backView)
 1255      currentView.backViewId = replacementView.viewId;
 1256      currentView.index = currentView.index - 1;
 1257      replacementView.forwardViewId = currentView.viewId;
 1258      // update the cursor and set new backView
 1259      viewHistory.backView = replacementView;
 1260      currentHistory.currentCursor += -1;
 1261    },
 1262
 1263    enabledBack: function(view) {
 1264      var backView = getBackView(view);
 1265      return !!(backView && backView.historyId === view.historyId);
 1266    },
 1267
 1268    /**
 1269     * @ngdoc method
 1270     * @name $ionicHistory#clearHistory
 1271     * @description Clears out the app's entire history, except for the current view.
 1272     */
 1273    clearHistory: function() {
 1274      var
 1275      histories = viewHistory.histories,
 1276      currentView = viewHistory.currentView;
 1277
 1278      if (histories) {
 1279        for (var historyId in histories) {
 1280
 1281          if (histories[historyId].stack) {
 1282            histories[historyId].stack = [];
 1283            histories[historyId].cursor = -1;
 1284          }
 1285
 1286          if (currentView && currentView.historyId === historyId) {
 1287            currentView.backViewId = currentView.forwardViewId = null;
 1288            histories[historyId].stack.push(currentView);
 1289          } else if (histories[historyId].destroy) {
 1290            histories[historyId].destroy();
 1291          }
 1292
 1293        }
 1294      }
 1295
 1296      for (var viewId in viewHistory.views) {
 1297        if (viewId !== currentView.viewId) {
 1298          delete viewHistory.views[viewId];
 1299        }
 1300      }
 1301
 1302      if (currentView) {
 1303        setNavViews(currentView.viewId);
 1304      }
 1305    },
 1306
 1307    /**
 1308     * @ngdoc method
 1309     * @name $ionicHistory#clearCache
 1310	 * @return promise
 1311     * @description Removes all cached views within every {@link ionic.directive:ionNavView}.
 1312     * This both removes the view element from the DOM, and destroy it's scope.
 1313     */
 1314    clearCache: function(stateIds) {
 1315      return $timeout(function() {
 1316        $ionicNavViewDelegate._instances.forEach(function(instance) {
 1317          instance.clearCache(stateIds);
 1318        });
 1319      });
 1320    },
 1321
 1322    /**
 1323     * @ngdoc method
 1324     * @name $ionicHistory#nextViewOptions
 1325     * @description Sets options for the next view. This method can be useful to override
 1326     * certain view/transition defaults right before a view transition happens. For example,
 1327     * the {@link ionic.directive:menuClose} directive uses this method internally to ensure
 1328     * an animated view transition does not happen when a side menu is open, and also sets
 1329     * the next view as the root of its history stack. After the transition these options
 1330     * are set back to null.
 1331     *
 1332     * Available options:
 1333     *
 1334     * * `disableAnimate`: Do not animate the next transition.
 1335     * * `disableBack`: The next view should forget its back view, and set it to null.
 1336     * * `historyRoot`: The next view should become the root view in its history stack.
 1337     *
 1338     * ```js
 1339     * $ionicHistory.nextViewOptions({
 1340     *   disableAnimate: true,
 1341     *   disableBack: true
 1342     * });
 1343     * ```
 1344     */
 1345    nextViewOptions: function(opts) {
 1346      deregisterStateChangeListener && deregisterStateChangeListener();
 1347      if (arguments.length) {
 1348        $timeout.cancel(nextViewExpireTimer);
 1349        if (opts === null) {
 1350          nextViewOptions = opts;
 1351        } else {
 1352          nextViewOptions = nextViewOptions || {};
 1353          extend(nextViewOptions, opts);
 1354          if (nextViewOptions.expire) {
 1355              deregisterStateChangeListener = $rootScope.$on('$stateChangeSuccess', function() {
 1356                nextViewExpireTimer = $timeout(function() {
 1357                  nextViewOptions = null;
 1358                  }, nextViewOptions.expire);
 1359              });
 1360          }
 1361        }
 1362      }
 1363      return nextViewOptions;
 1364    },
 1365
 1366    isAbstractEle: function(ele, viewLocals) {
 1367      if (viewLocals && viewLocals.$$state && viewLocals.$$state.self['abstract']) {
 1368        return true;
 1369      }
 1370      return !!(ele && (isAbstractTag(ele) || isAbstractTag(ele.children())));
 1371    },
 1372
 1373    isActiveScope: function(scope) {
 1374      if (!scope) return false;
 1375
 1376      var climbScope = scope;
 1377      var currentHistoryId = this.currentHistoryId();
 1378      var foundHistoryId;
 1379
 1380      while (climbScope) {
 1381        if (climbScope.$$disconnected) {
 1382          return false;
 1383        }
 1384
 1385        if (!foundHistoryId && climbScope.hasOwnProperty('$historyId')) {
 1386          foundHistoryId = true;
 1387        }
 1388
 1389        if (currentHistoryId) {
 1390          if (climbScope.hasOwnProperty('$historyId') && currentHistoryId == climbScope.$historyId) {
 1391            return true;
 1392          }
 1393          if (climbScope.hasOwnProperty('$activeHistoryId')) {
 1394            if (currentHistoryId == climbScope.$activeHistoryId) {
 1395              if (climbScope.hasOwnProperty('$historyId')) {
 1396                return true;
 1397              }
 1398              if (!foundHistoryId) {
 1399                return true;
 1400              }
 1401            }
 1402          }
 1403        }
 1404
 1405        if (foundHistoryId && climbScope.hasOwnProperty('$activeHistoryId')) {
 1406          foundHistoryId = false;
 1407        }
 1408
 1409        climbScope = climbScope.$parent;
 1410      }
 1411
 1412      return currentHistoryId ? currentHistoryId == 'root' : true;
 1413    }
 1414
 1415  };
 1416
 1417  function isAbstractTag(ele) {
 1418    return ele && ele.length && /ion-side-menus|ion-tabs/i.test(ele[0].tagName);
 1419  }
 1420
 1421  function canSwipeBack(ele, viewLocals) {
 1422    if (viewLocals && viewLocals.$$state && viewLocals.$$state.self.canSwipeBack === false) {
 1423      return false;
 1424    }
 1425    if (ele && ele.attr('can-swipe-back') === 'false') {
 1426      return false;
 1427    }
 1428    var eleChild = ele.find('ion-view');
 1429    if (eleChild && eleChild.attr('can-swipe-back') === 'false') {
 1430      return false;
 1431    }
 1432    return true;
 1433  }
 1434
 1435}])
 1436
 1437.run([
 1438  '$rootScope',
 1439  '$state',
 1440  '$location',
 1441  '$document',
 1442  '$ionicPlatform',
 1443  '$ionicHistory',
 1444  'IONIC_BACK_PRIORITY',
 1445function($rootScope, $state, $location, $document, $ionicPlatform, $ionicHistory, IONIC_BACK_PRIORITY) {
 1446
 1447  // always reset the keyboard state when change stage
 1448  $rootScope.$on('$ionicView.beforeEnter', function() {
 1449    ionic.keyboard && ionic.keyboard.hide && ionic.keyboard.hide();
 1450  });
 1451
 1452  $rootScope.$on('$ionicHistory.change', function(e, data) {
 1453    if (!data) return null;
 1454
 1455    var viewHistory = $ionicHistory.viewHistory();
 1456
 1457    var hist = (data.historyId ? viewHistory.histories[ data.historyId ] : null);
 1458    if (hist && hist.cursor > -1 && hist.cursor < hist.stack.length) {
 1459      // the history they're going to already exists
 1460      // go to it's last view in its stack
 1461      var view = hist.stack[ hist.cursor ];
 1462      return view.go(data);
 1463    }
 1464
 1465    // this history does not have a URL, but it does have a uiSref
 1466    // figure out its URL from the uiSref
 1467    if (!data.url && data.uiSref) {
 1468      data.url = $state.href(data.uiSref);
 1469    }
 1470
 1471    if (data.url) {
 1472      // don't let it start with a #, messes with $location.url()
 1473      if (data.url.indexOf('#') === 0) {
 1474        data.url = data.url.replace('#', '');
 1475      }
 1476      if (data.url !== $location.url()) {
 1477        // we've got a good URL, ready GO!
 1478        $location.url(data.url);
 1479      }
 1480    }
 1481  });
 1482
 1483  $rootScope.$ionicGoBack = function(backCount) {
 1484    $ionicHistory.goBack(backCount);
 1485  };
 1486
 1487  // Set the document title when a new view is shown
 1488  $rootScope.$on('$ionicView.afterEnter', function(ev, data) {
 1489    if (data && data.title) {
 1490      $document[0].title = data.title;
 1491    }
 1492  });
 1493
 1494  // Triggered when devices with a hardware back button (Android) is clicked by the user
 1495  // This is a Cordova/Phonegap platform specifc method
 1496  function onHardwareBackButton(e) {
 1497    var backView = $ionicHistory.backView();
 1498    if (backView) {
 1499      // there is a back view, go to it
 1500      backView.go();
 1501    } else {
 1502      // there is no back view, so close the app instead
 1503      ionic.Platform.exitApp();
 1504    }
 1505    e.preventDefault();
 1506    return false;
 1507  }
 1508  $ionicPlatform.registerBackButtonAction(
 1509    onHardwareBackButton,
 1510    IONIC_BACK_PRIORITY.view
 1511  );
 1512
 1513}]);
 1514
 1515/**
 1516 * @ngdoc provider
 1517 * @name $ionicConfigProvider
 1518 * @module ionic
 1519 * @description
 1520 * Ionic automatically takes platform configurations into account to adjust things like what
 1521 * transition style to use and whether tab icons should show on the top or bottom. For example,
 1522 * iOS will move forward by transitioning the entering view from right to center and the leaving
 1523 * view from center to left. However, Android will transition with the entering view going from
 1524 * bottom to center, covering the previous view, which remains stationary. It should be noted
 1525 * that when a platform is not iOS or Android, then it'll default to iOS. So if you are
 1526 * developing on a desktop browser, it's going to take on iOS default configs.
 1527 *
 1528 * These configs can be changed using the `$ionicConfigProvider` during the configuration phase
 1529 * of your app. Additionally, `$ionicConfig` can also set and get config values during the run
 1530 * phase and within the app itself.
 1531 *
 1532 * By default, all base config variables are set to `'platform'`, which means it'll take on the
 1533 * default config of the platform on which it's running. Config variables can be set at this
 1534 * level so all platforms follow the same setting, rather than its platform config.
 1535 * The following code would set the same config variable for all platforms:
 1536 *
 1537 * ```js
 1538 * $ionicConfigProvider.views.maxCache(10);
 1539 * ```
 1540 *
 1541 * Additionally, each platform can have it's own config within the `$ionicConfigProvider.platform`
 1542 * property. The config below would only apply to Android devices.
 1543 *
 1544 * ```js
 1545 * $ionicConfigProvider.platform.android.views.maxCache(5);
 1546 * ```
 1547 *
 1548 * @usage
 1549 * ```js
 1550 * var myApp = angular.module('reallyCoolApp', ['ionic']);
 1551 *
 1552 * myApp.config(function($ionicConfigProvider) {
 1553 *   $ionicConfigProvider.views.maxCache(5);
 1554 *
 1555 *   // note that you can also chain configs
 1556 *   $ionicConfigProvider.backButton.text('Go Back').icon('ion-chevron-left');
 1557 * });
 1558 * ```
 1559 */
 1560
 1561/**
 1562 * @ngdoc method
 1563 * @name $ionicConfigProvider#views.transition
 1564 * @description Animation style when transitioning between views. Default `platform`.
 1565 *
 1566 * @param {string} transition Which style of view transitioning to use.
 1567 *
 1568 * * `platform`: Dynamically choose the correct transition style depending on the platform
 1569 * the app is running from. If the platform is not `ios` or `android` then it will default
 1570 * to `ios`.
 1571 * * `ios`: iOS style transition.
 1572 * * `android`: Android style transition.
 1573 * * `none`: Do not perform animated transitions.
 1574 *
 1575 * @returns {string} value
 1576 */
 1577
 1578/**
 1579 * @ngdoc method
 1580 * @name $ionicConfigProvider#views.maxCache
 1581 * @description  Maximum number of view elements to cache in the DOM. When the max number is
 1582 * exceeded, the view with the longest time period since it was accessed is removed. Views that
 1583 * stay in the DOM cache the view's scope, current state, and scroll position. The scope is
 1584 * disconnected from the `$watch` cycle when it is cached and reconnected when it enters again.
 1585 * When the maximum cache is `0`, the leaving view's element will be removed from the DOM after
 1586 * each view transition, and the next time the same view is shown, it will have to re-compile,
 1587 * attach to the DOM, and link the element again. This disables caching, in effect.
 1588 * @param {number} maxNumber Maximum number of views to retain. Default `10`.
 1589 * @returns {number} How many views Ionic will hold onto until the a view is removed.
 1590 */
 1591
 1592/**
 1593 * @ngdoc method
 1594 * @name $ionicConfigProvider#views.forwardCache
 1595 * @description  By default, when navigating, views that were recently visited are cached, and
 1596 * the same instance data and DOM elements are referenced when navigating back. However, when
 1597 * navigating back in the history, the "forward" views are removed from the cache. If you
 1598 * navigate forward to the same view again, it'll create a new DOM element and controller
 1599 * instance. Basically, any forward views are reset each time. Set this config to `true` to have
 1600 * forward views cached and not reset on each load.
 1601 * @param {boolean} value
 1602 * @returns {boolean}
 1603 */
 1604
 1605 /**
 1606  * @ngdoc method
 1607  * @name $ionicConfigProvider#views.swipeBackEnabled
 1608  * @description  By default on iOS devices, swipe to go back functionality is enabled by default.
 1609  * This method can be used to disable it globally, or on a per-view basis.
 1610  * Note: This functionality is only supported on iOS.
 1611  * @param {boolean} value
 1612  * @returns {boolean}
 1613  */
 1614
 1615/**
 1616 * @ngdoc method
 1617 * @name $ionicConfigProvider#scrolling.jsScrolling
 1618 * @description  Whether to use JS or Native scrolling. Defaults to native scrolling. Setting this to
 1619 * `true` has the same effect as setting each `ion-content` to have `overflow-scroll='false'`.
 1620 * @param {boolean} value Defaults to `false` as of Ionic 1.2
 1621 * @returns {boolean}
 1622 */
 1623
 1624/**
 1625 * @ngdoc method
 1626 * @name $ionicConfigProvider#backButton.icon
 1627 * @description Back button icon.
 1628 * @param {string} value
 1629 * @returns {string}
 1630 */
 1631
 1632/**
 1633 * @ngdoc method
 1634 * @name $ionicConfigProvider#backButton.text
 1635 * @description Back button text.
 1636 * @param {string} value Defaults to `Back`.
 1637 * @returns {string}
 1638 */
 1639
 1640/**
 1641 * @ngdoc method
 1642 * @name $ionicConfigProvider#backButton.previousTitleText
 1643 * @description If the previous title text should become the back button text. This
 1644 * is the default for iOS.
 1645 * @param {boolean} value
 1646 * @returns {boolean}
 1647 */
 1648
 1649/**
 1650 * @ngdoc method
 1651 * @name $ionicConfigProvider#form.checkbox
 1652 * @description Checkbox style. Android defaults to `square` and iOS defaults to `circle`.
 1653 * @param {string} value
 1654 * @returns {string}
 1655 */
 1656
 1657/**
 1658 * @ngdoc method
 1659 * @name $ionicConfigProvider#form.toggle
 1660 * @description Toggle item style. Android defaults to `small` and iOS defaults to `large`.
 1661 * @param {string} value
 1662 * @returns {string}
 1663 */
 1664
 1665/**
 1666 * @ngdoc method
 1667 * @name $ionicConfigProvider#spinner.icon
 1668 * @description Default spinner icon to use.
 1669 * @param {string} value Can be: `android`, `ios`, `ios-small`, `bubbles`, `circles`, `crescent`,
 1670 * `dots`, `lines`, `ripple`, or `spiral`.
 1671 * @returns {string}
 1672 */
 1673
 1674/**
 1675 * @ngdoc method
 1676 * @name $ionicConfigProvider#tabs.style
 1677 * @description Tab style. Android defaults to `striped` and iOS defaults to `standard`.
 1678 * @param {string} value Available values include `striped` and `standard`.
 1679 * @returns {string}
 1680 */
 1681
 1682/**
 1683 * @ngdoc method
 1684 * @name $ionicConfigProvider#tabs.position
 1685 * @description Tab position. Android defaults to `top` and iOS defaults to `bottom`.
 1686 * @param {string} value Available values include `top` and `bottom`.
 1687 * @returns {string}
 1688 */
 1689
 1690/**
 1691 * @ngdoc method
 1692 * @name $ionicConfigProvider#templates.maxPrefetch
 1693 * @description Sets the maximum number of templates to prefetch from the templateUrls defined in
 1694 * $stateProvider.state. If set to `0`, the user will have to wait
 1695 * for a template to be fetched the first time when navigating to a new page. Default `30`.
 1696 * @param {integer} value Max number of template to prefetch from the templateUrls defined in
 1697 * `$stateProvider.state()`.
 1698 * @returns {integer}
 1699 */
 1700
 1701/**
 1702 * @ngdoc method
 1703 * @name $ionicConfigProvider#navBar.alignTitle
 1704 * @description Which side of the navBar to align the title. Default `center`.
 1705 *
 1706 * @param {string} value side of the navBar to align the title.
 1707 *
 1708 * * `platform`: Dynamically choose the correct title style depending on the platform
 1709 * the app is running from. If the platform is `ios`, it will default to `center`.
 1710 * If the platform is `android`, it will default to `left`. If the platform is not
 1711 * `ios` or `android`, it will default to `center`.
 1712 *
 1713 * * `left`: Left align the title in the navBar
 1714 * * `center`: Center align the title in the navBar
 1715 * * `right`: Right align the title in the navBar.
 1716 *
 1717 * @returns {string} value
 1718 */
 1719
 1720/**
 1721  * @ngdoc method
 1722  * @name $ionicConfigProvider#navBar.positionPrimaryButtons
 1723  * @description Which side of the navBar to align the primary navBar buttons. Default `left`.
 1724  *
 1725  * @param {string} value side of the navBar to align the primary navBar buttons.
 1726  *
 1727  * * `platform`: Dynamically choose the correct title style depending on the platform
 1728  * the app is running from. If the platform is `ios`, it will default to `left`.
 1729  * If the platform is `android`, it will default to `right`. If the platform is not
 1730  * `ios` or `android`, it will default to `left`.
 1731  *
 1732  * * `left`: Left align the primary navBar buttons in the navBar
 1733  * * `right`: Right align the primary navBar buttons in the navBar.
 1734  *
 1735  * @returns {string} value
 1736  */
 1737
 1738/**
 1739 * @ngdoc method
 1740 * @name $ionicConfigProvider#navBar.positionSecondaryButtons
 1741 * @description Which side of the navBar to align the secondary navBar buttons. Default `right`.
 1742 *
 1743 * @param {string} value side of the navBar to align the secondary navBar buttons.
 1744 *
 1745 * * `platform`: Dynamically choose the correct title style depending on the platform
 1746 * the app is running from. If the platform is `ios`, it will default to `right`.
 1747 * If the platform is `android`, it will default to `right`. If the platform is not
 1748 * `ios` or `android`, it will default to `right`.
 1749 *
 1750 * * `left`: Left align the secondary navBar buttons in the navBar
 1751 * * `right`: Right align the secondary navBar buttons in the navBar.
 1752 *
 1753 * @returns {string} value
 1754 */
 1755
 1756IonicModule
 1757.provider('$ionicConfig', function() {
 1758
 1759  var provider = this;
 1760  provider.platform = {};
 1761  var PLATFORM = 'platform';
 1762
 1763  var configProperties = {
 1764    views: {
 1765      maxCache: PLATFORM,
 1766      forwardCache: PLATFORM,
 1767      transition: PLATFORM,
 1768      swipeBackEnabled: PLATFORM,
 1769      swipeBackHitWidth: PLATFORM
 1770    },
 1771    navBar: {
 1772      alignTitle: PLATFORM,
 1773      positionPrimaryButtons: PLATFORM,
 1774      positionSecondaryButtons: PLATFORM,
 1775      transition: PLATFORM
 1776    },
 1777    backButton: {
 1778      icon: PLATFORM,
 1779      text: PLATFORM,
 1780      previousTitleText: PLATFORM
 1781    },
 1782    form: {
 1783      checkbox: PLATFORM,
 1784      toggle: PLATFORM
 1785    },
 1786    scrolling: {
 1787      jsScrolling: PLATFORM
 1788    },
 1789    spinner: {
 1790      icon: PLATFORM
 1791    },
 1792    tabs: {
 1793      style: PLATFORM,
 1794      position: PLATFORM
 1795    },
 1796    templates: {
 1797      maxPrefetch: PLATFORM
 1798    },
 1799    platform: {}
 1800  };
 1801  createConfig(configProperties, provider, '');
 1802
 1803
 1804
 1805  // Default
 1806  // -------------------------
 1807  setPlatformConfig('default', {
 1808
 1809    views: {
 1810      maxCache: 10,
 1811      forwardCache: false,
 1812      transition: 'ios',
 1813      swipeBackEnabled: true,
 1814      swipeBackHitWidth: 45
 1815    },
 1816
 1817    navBar: {
 1818      alignTitle: 'center',
 1819      positionPrimaryButtons: 'left',
 1820      positionSecondaryButtons: 'right',
 1821      transition: 'view'
 1822    },
 1823
 1824    backButton: {
 1825      icon: 'ion-ios-arrow-back',
 1826      text: 'Back',
 1827      previousTitleText: true
 1828    },
 1829
 1830    form: {
 1831      checkbox: 'circle',
 1832      toggle: 'large'
 1833    },
 1834
 1835    scrolling: {
 1836      jsScrolling: true
 1837    },
 1838
 1839    spinner: {
 1840      icon: 'ios'
 1841    },
 1842
 1843    tabs: {
 1844      style: 'standard',
 1845      position: 'bottom'
 1846    },
 1847
 1848    templates: {
 1849      maxPrefetch: 30
 1850    }
 1851
 1852  });
 1853
 1854
 1855
 1856  // iOS (it is the default already)
 1857  // -------------------------
 1858  setPlatformConfig('ios', {});
 1859
 1860
 1861
 1862  // Android
 1863  // -------------------------
 1864  setPlatformConfig('android', {
 1865
 1866    views: {
 1867      transition: 'android',
 1868      swipeBackEnabled: false
 1869    },
 1870
 1871    navBar: {
 1872      alignTitle: 'left',
 1873      positionPrimaryButtons: 'right',
 1874      positionSecondaryButtons: 'right'
 1875    },
 1876
 1877    backButton: {
 1878      icon: 'ion-android-arrow-back',
 1879      text: false,
 1880      previousTitleText: false
 1881    },
 1882
 1883    form: {
 1884      checkbox: 'square',
 1885      toggle: 'small'
 1886    },
 1887
 1888    spinner: {
 1889      icon: 'android'
 1890    },
 1891
 1892    tabs: {
 1893      style: 'striped',
 1894      position: 'top'
 1895    },
 1896
 1897    scrolling: {
 1898      jsScrolling: false
 1899    }
 1900  });
 1901
 1902  // Windows Phone
 1903  // -------------------------
 1904  setPlatformConfig('windowsphone', {
 1905    //scrolling: {
 1906    //  jsScrolling: false
 1907    //}
 1908    spinner: {
 1909      icon: 'android'
 1910    }
 1911  });
 1912
 1913
 1914  provider.transitions = {
 1915    views: {},
 1916    navBar: {}
 1917  };
 1918
 1919
 1920  // iOS Transitions
 1921  // -----------------------
 1922  provider.transitions.views.ios = function(enteringEle, leavingEle, direction, shouldAnimate) {
 1923
 1924    function setStyles(ele, opacity, x, boxShadowOpacity) {
 1925      var css = {};
 1926      css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : 0;
 1927      css.opacity = opacity;
 1928      if (boxShadowOpacity > -1) {
 1929        css.boxShadow = '0 0 10px rgba(0,0,0,' + (d.shouldAnimate ? boxShadowOpacity * 0.45 : 0.3) + ')';
 1930      }
 1931      css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';
 1932      ionic.DomUtil.cachedStyles(ele, css);
 1933    }
 1934
 1935    var d = {
 1936      run: function(step) {
 1937        if (direction == 'forward') {
 1938          setStyles(enteringEle, 1, (1 - step) * 99, 1 - step); // starting at 98% prevents a flicker
 1939          setStyles(leavingEle, (1 - 0.1 * step), step * -33, -1);
 1940
 1941        } else if (direction == 'back') {
 1942          setStyles(enteringEle, (1 - 0.1 * (1 - step)), (1 - step) * -33, -1);
 1943          setStyles(leavingEle, 1, step * 100, 1 - step);
 1944
 1945        } else {
 1946          // swap, enter, exit
 1947          setStyles(enteringEle, 1, 0, -1);
 1948          setStyles(leavingEle, 0, 0, -1);
 1949        }
 1950      },
 1951      shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
 1952    };
 1953
 1954    return d;
 1955  };
 1956
 1957  provider.transitions.navBar.ios = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {
 1958
 1959    function setStyles(ctrl, opacity, titleX, backTextX) {
 1960      var css = {};
 1961      css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : '0ms';
 1962      css.opacity = opacity === 1 ? '' : opacity;
 1963
 1964      ctrl.setCss('buttons-left', css);
 1965      ctrl.setCss('buttons-right', css);
 1966      ctrl.setCss('back-button', css);
 1967
 1968      css[ionic.CSS.TRANSFORM] = 'translate3d(' + backTextX + 'px,0,0)';
 1969      ctrl.setCss('back-text', css);
 1970
 1971      css[ionic.CSS.TRANSFORM] = 'translate3d(' + titleX + 'px,0,0)';
 1972      ctrl.setCss('title', css);
 1973    }
 1974
 1975    function enter(ctrlA, ctrlB, step) {
 1976      if (!ctrlA || !ctrlB) return;
 1977      var titleX = (ctrlA.titleTextX() + ctrlA.titleWidth()) * (1 - step);
 1978      var backTextX = (ctrlB && (ctrlB.titleTextX() - ctrlA.backButtonTextLeft()) * (1 - step)) || 0;
 1979      setStyles(ctrlA, step, titleX, backTextX);
 1980    }
 1981
 1982    function leave(ctrlA, ctrlB, step) {
 1983      if (!ctrlA || !ctrlB) return;
 1984      var titleX = (-(ctrlA.titleTextX() - ctrlB.backButtonTextLeft()) - (ctrlA.titleLeftRight())) * step;
 1985      setStyles(ctrlA, 1 - step, titleX, 0);
 1986    }
 1987
 1988    var d = {
 1989      run: function(step) {
 1990        var enteringHeaderCtrl = enteringHeaderBar.controller();
 1991        var leavingHeaderCtrl = leavingHeaderBar && leavingHeaderBar.controller();
 1992        if (d.direction == 'back') {
 1993          leave(enteringHeaderCtrl, leavingHeaderCtrl, 1 - step);
 1994          enter(leavingHeaderCtrl, enteringHeaderCtrl, 1 - step);
 1995        } else {
 1996          enter(enteringHeaderCtrl, leavingHeaderCtrl, step);
 1997          leave(leavingHeaderCtrl, enteringHeaderCtrl, step);
 1998        }
 1999      },
 2000      direction: direction,
 2001      shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
 2002    };
 2003
 2004    return d;
 2005  };
 2006
 2007
 2008  // Android Transitions
 2009  // -----------------------
 2010
 2011  provider.transitions.views.android = function(enteringEle, leavingEle, direction, shouldAnimate) {
 2012    shouldAnimate = shouldAnimate && (direction == 'forward' || direction == 'back');
 2013
 2014    function setStyles(ele, x, opacity) {
 2015      var css = {};
 2016      css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : 0;
 2017      css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';
 2018      css.opacity = opacity;
 2019      ionic.DomUtil.cachedStyles(ele, css);
 2020    }
 2021
 2022    var d = {
 2023      run: function(step) {
 2024        if (direction == 'forward') {
 2025          setStyles(enteringEle, (1 - step) * 99, 1); // starting at 98% prevents a flicker
 2026          setStyles(leavingEle, step * -100, 1);
 2027
 2028        } else if (direction == 'back') {
 2029          setStyles(enteringEle, (1 - step) * -100, 1);
 2030          setStyles(leavingEle, step * 100, 1);
 2031
 2032        } else {
 2033          // swap, enter, exit
 2034          setStyles(enteringEle, 0, 1);
 2035          setStyles(leavingEle, 0, 0);
 2036        }
 2037      },
 2038      shouldAnimate: shouldAnimate
 2039    };
 2040
 2041    return d;
 2042  };
 2043
 2044  provider.transitions.navBar.android = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {
 2045
 2046    function setStyles(ctrl, opacity) {
 2047      if (!ctrl) return;
 2048      var css = {};
 2049      css.opacity = opacity === 1 ? '' : opacity;
 2050
 2051      ctrl.setCss('buttons-left', css);
 2052      ctrl.setCss('buttons-right', css);
 2053      ctrl.setCss('back-button', css);
 2054      ctrl.setCss('back-text', css);
 2055      ctrl.setCss('title', css);
 2056    }
 2057
 2058    return {
 2059      run: function(step) {
 2060        setStyles(enteringHeaderBar.controller(), step);
 2061        setStyles(leavingHeaderBar && leavingHeaderBar.controller(), 1 - step);
 2062      },
 2063      shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
 2064    };
 2065  };
 2066
 2067
 2068  // No Transition
 2069  // -----------------------
 2070
 2071  provider.transitions.views.none = function(enteringEle, leavingEle) {
 2072    return {
 2073      run: function(step) {
 2074        provider.transitions.views.android(enteringEle, leavingEle, false, false).run(step);
 2075      },
 2076      shouldAnimate: false
 2077    };
 2078  };
 2079
 2080  provider.transitions.navBar.none = function(enteringHeaderBar, leavingHeaderBar) {
 2081    return {
 2082      run: function(step) {
 2083        provider.transitions.navBar.ios(enteringHeaderBar, leavingHeaderBar, false, false).run(step);
 2084        provider.transitions.navBar.android(enteringHeaderBar, leavingHeaderBar, false, false).run(step);
 2085      },
 2086      shouldAnimate: false
 2087    };
 2088  };
 2089
 2090
 2091  // private: used to set platform configs
 2092  function setPlatformConfig(platformName, platformConfigs) {
 2093    configProperties.platform[platformName] = platformConfigs;
 2094    provider.platform[platformName] = {};
 2095
 2096    addConfig(configProperties, configProperties.platform[platformName]);
 2097
 2098    createConfig(configProperties.platform[platformName], provider.platform[platformName], '');
 2099  }
 2100
 2101
 2102  // private: used to recursively add new platform configs
 2103  function addConfig(configObj, platformObj) {
 2104    for (var n in configObj) {
 2105      if (n != PLATFORM && configObj.hasOwnProperty(n)) {
 2106        if (angular.isObject(configObj[n])) {
 2107          if (!isDefined(platformObj[n])) {
 2108            platformObj[n] = {};
 2109          }
 2110          addConfig(configObj[n], platformObj[n]);
 2111
 2112        } else if (!isDefined(platformObj[n])) {
 2113          platformObj[n] = null;
 2114        }
 2115      }
 2116    }
 2117  }
 2118
 2119
 2120  // private: create methods for each config to get/set
 2121  function createConfig(configObj, providerObj, platformPath) {
 2122    forEach(configObj, function(value, namespace) {
 2123
 2124      if (angular.isObject(configObj[namespace])) {
 2125        // recursively drill down the config object so we can create a method for each one
 2126        providerObj[namespace] = {};
 2127        createConfig(configObj[namespace], providerObj[namespace], platformPath + '.' + namespace);
 2128
 2129      } else {
 2130        // create a method for the provider/config methods that will be exposed
 2131        providerObj[namespace] = function(newValue) {
 2132          if (arguments.length) {
 2133            configObj[namespace] = newValue;
 2134            return providerObj;
 2135          }
 2136          if (configObj[namespace] == PLATFORM) {
 2137            // if the config is set to 'platform', then get this config's platform value
 2138            var platformConfig = stringObj(configProperties.platform, ionic.Platform.platform() + platformPath + '.' + namespace);
 2139            if (platformConfig || platformConfig === false) {
 2140              return platformConfig;
 2141            }
 2142            // didnt find a specific platform config, now try the default
 2143            return stringObj(configProperties.platform, 'default' + platformPath + '.' + namespace);
 2144          }
 2145          return configObj[namespace];
 2146        };
 2147      }
 2148
 2149    });
 2150  }
 2151
 2152  function stringObj(obj, str) {
 2153    str = str.split(".");
 2154    for (var i = 0; i < str.length; i++) {
 2155      if (obj && isDefined(obj[str[i]])) {
 2156        obj = obj[str[i]];
 2157      } else {
 2158        return null;
 2159      }
 2160    }
 2161    return obj;
 2162  }
 2163
 2164  provider.setPlatformConfig = setPlatformConfig;
 2165
 2166
 2167  // private: Service definition for internal Ionic use
 2168  /**
 2169   * @ngdoc service
 2170   * @name $ionicConfig
 2171   * @module ionic
 2172   * @private
 2173   */
 2174  provider.$get = function() {
 2175    return provider;
 2176  };
 2177})
 2178// Fix for URLs in Cordova apps on Windows Phone
 2179// http://blogs.msdn.com/b/msdn_answers/archive/2015/02/10/
 2180// running-cordova-apps-on-windows-and-windows-phone-8-1-using-ionic-angularjs-and-other-frameworks.aspx
 2181.config(['$compileProvider', function($compileProvider) {
 2182  $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|sms|tel|geo|ftp|mailto|file|ghttps?|ms-appx-web|ms-appx|x-wmapp0):/);
 2183  $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|content|blob|ms-appx|ms-appx-web|x-wmapp0):|data:image\//);
 2184}]);
 2185
 2186
 2187var LOADING_TPL =
 2188  '<div class="loading-container">' +
 2189    '<div class="loading">' +
 2190    '</div>' +
 2191  '</div>';
 2192
 2193/**
 2194 * @ngdoc service
 2195 * @name $ionicLoading
 2196 * @module ionic
 2197 * @description
 2198 * An overlay that can be used to indicate activity while blocking user
 2199 * interaction.
 2200 *
 2201 * @usage
 2202 * ```js
 2203 * angular.module('LoadingApp', ['ionic'])
 2204 * .controller('LoadingCtrl', function($scope, $ionicLoading) {
 2205 *   $scope.show = function() {
 2206 *     $ionicLoading.show({
 2207 *       template: 'Loading...'
 2208 *     }).then(function(){
 2209 *        console.log("The loading indicator is now displayed");
 2210 *     });
 2211 *   };
 2212 *   $scope.hide = function(){
 2213 *     $ionicLoading.hide().then(function(){
 2214 *        console.log("The loading indicator is now hidden");
 2215 *     });
 2216 *   };
 2217 * });
 2218 * ```
 2219 */
 2220/**
 2221 * @ngdoc object
 2222 * @name $ionicLoadingConfig
 2223 * @module ionic
 2224 * @description
 2225 * Set the default options to be passed to the {@link ionic.service:$ionicLoading} service.
 2226 *
 2227 * @usage
 2228 * ```js
 2229 * var app = angular.module('myApp', ['ionic'])
 2230 * app.constant('$ionicLoadingConfig', {
 2231 *   template: 'Default Loading Template...'
 2232 * });
 2233 * app.controller('AppCtrl', function($scope, $ionicLoading) {
 2234 *   $scope.showLoading = function() {
 2235 *     //options default to values in $ionicLoadingConfig
 2236 *     $ionicLoading.show().then(function(){
 2237 *        console.log("The loading indicator is now displayed");
 2238 *     });
 2239 *   };
 2240 * });
 2241 * ```
 2242 */
 2243IonicModule
 2244.constant('$ionicLoadingConfig', {
 2245  template: '<ion-spinner></ion-spinner>'
 2246})
 2247.factory('$ionicLoading', [
 2248  '$ionicLoadingConfig',
 2249  '$ionicBody',
 2250  '$ionicTemplateLoader',
 2251  '$ionicBackdrop',
 2252  '$timeout',
 2253  '$q',
 2254  '$log',
 2255  '$compile',
 2256  '$ionicPlatform',
 2257  '$rootScope',
 2258  'IONIC_BACK_PRIORITY',
 2259function($ionicLoadingConfig, $ionicBody, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $compile, $ionicPlatform, $rootScope, IONIC_BACK_PRIORITY) {
 2260
 2261  var loaderInstance;
 2262  //default values
 2263  var deregisterBackAction = noop;
 2264  var deregisterStateListener1 = noop;
 2265  var deregisterStateListener2 = noop;
 2266  var loadingShowDelay = $q.when();
 2267
 2268  return {
 2269    /**
 2270     * @ngdoc method
 2271     * @name $ionicLoading#show
 2272     * @description Shows a loading indicator. If the indicator is already shown,
 2273     * it will set the options given and keep the indicator shown.
 2274     * @returns {promise} A promise which is resolved when the loading indicator is presented.
 2275     * @param {object} opts The options for the loading indicator. Available properties:
 2276     *  - `{string=}` `template` The html content of the indicator.
 2277     *  - `{string=}` `templateUrl` The url of an html template to load as the content of the indicator.
 2278     *  - `{object=}` `scope` The scope to be a child of. Default: creates a child of $rootScope.
 2279     *  - `{boolean=}` `noBackdrop` Whether to hide the backdrop. By default it will be shown.
 2280     *  - `{boolean=}` `hideOnStateChange` Whether to hide the loading spinner when navigating
 2281     *    to a new state. Default false.
 2282     *  - `{number=}` `delay` How many milliseconds to delay showing the indicator. By default there is no delay.
 2283     *  - `{number=}` `duration` How many milliseconds to wait until automatically
 2284     *  hiding the indicator. By default, the indicator will be shown until `.hide()` is called.
 2285     */
 2286    show: showLoader,
 2287    /**
 2288     * @ngdoc method
 2289     * @name $ionicLoading#hide
 2290     * @description Hides the loading indicator, if shown.
 2291     * @returns {promise} A promise which is resolved when the loading indicator is hidden.
 2292     */
 2293    hide: hideLoader,
 2294    /**
 2295     * @private for testing
 2296     */
 2297    _getLoader: getLoader
 2298  };
 2299
 2300  function getLoader() {
 2301    if (!loaderInstance) {
 2302      loaderInstance = $ionicTemplateLoader.compile({
 2303        template: LOADING_TPL,
 2304        appendTo: $ionicBody.get()
 2305      })
 2306      .then(function(self) {
 2307        self.show = function(options) {
 2308          var templatePromise = options.templateUrl ?
 2309            $ionicTemplateLoader.load(options.templateUrl) :
 2310            //options.content: deprecated
 2311            $q.when(options.template || options.content || '');
 2312
 2313          self.scope = options.scope || self.scope;
 2314
 2315          if (!self.isShown) {
 2316            //options.showBackdrop: deprecated
 2317            self.hasBackdrop = !options.noBackdrop && options.showBackdrop !== false;
 2318            if (self.hasBackdrop) {
 2319              $ionicBackdrop.retain();
 2320              $ionicBackdrop.getElement().addClass('backdrop-loading');
 2321            }
 2322          }
 2323
 2324          if (options.duration) {
 2325            $timeout.cancel(self.durationTimeout);
 2326            self.durationTimeout = $timeout(
 2327              angular.bind(self, self.hide),
 2328              +options.duration
 2329            );
 2330          }
 2331
 2332          deregisterBackAction();
 2333          //Disable hardware back button while loading
 2334          deregisterBackAction = $ionicPlatform.registerBackButtonAction(
 2335            noop,
 2336            IONIC_BACK_PRIORITY.loading
 2337          );
 2338
 2339          templatePromise.then(function(html) {
 2340            if (html) {
 2341              var loading = self.element.children();
 2342              loading.html(html);
 2343              $compile(loading.contents())(self.scope);
 2344            }
 2345
 2346            //Don't show until template changes
 2347            if (self.isShown) {
 2348              self.element.addClass('visible');
 2349              ionic.requestAnimationFrame(function() {
 2350                if (self.isShown) {
 2351                  self.element.addClass('active');
 2352                  $ionicBody.addClass('loading-active');
 2353                }
 2354              });
 2355            }
 2356          });
 2357
 2358          self.isShown = true;
 2359        };
 2360        self.hide = function() {
 2361
 2362          deregisterBackAction();
 2363          if (self.isShown) {
 2364            if (self.hasBackdrop) {
 2365              $ionicBackdrop.release();
 2366              $ionicBackdrop.getElement().removeClass('backdrop-loading');
 2367            }
 2368            self.element.removeClass('active');
 2369            $ionicBody.removeClass('loading-active');
 2370            self.element.removeClass('visible');
 2371            ionic.requestAnimationFrame(function() {
 2372              !self.isShown && self.element.removeClass('visible');
 2373            });
 2374          }
 2375          $timeout.cancel(self.durationTimeout);
 2376          self.isShown = false;
 2377          var loading = self.element.children();
 2378          loading.html("");
 2379        };
 2380
 2381        return self;
 2382      });
 2383    }
 2384    return loaderInstance;
 2385  }
 2386
 2387  function showLoader(options) {
 2388    options = extend({}, $ionicLoadingConfig || {}, options || {});
 2389    // use a default delay of 100 to avoid some issues reported on github
 2390    // https://github.com/driftyco/ionic/issues/3717
 2391    var delay = options.delay || options.showDelay || 0;
 2392
 2393    deregisterStateListener1();
 2394    deregisterStateListener2();
 2395    if (options.hideOnStateChange) {
 2396      deregisterStateListener1 = $rootScope.$on('$stateChangeSuccess', hideLoader);
 2397      deregisterStateListener2 = $rootScope.$on('$stateChangeError', hideLoader);
 2398    }
 2399
 2400    //If loading.show() was called previously, cancel it and show with our new options
 2401    $timeout.cancel(loadingShowDelay);
 2402    loadingShowDelay = $timeout(noop, delay);
 2403    return loadingShowDelay.then(getLoader).then(function(loader) {
 2404      return loader.show(options);
 2405    });
 2406  }
 2407
 2408  function hideLoader() {
 2409    deregisterStateListener1();
 2410    deregisterStateListener2();
 2411    $timeout.cancel(loadingShowDelay);
 2412    return getLoader().then(function(loader) {
 2413      return loader.hide();
 2414    });
 2415  }
 2416}]);
 2417
 2418/**
 2419 * @ngdoc service
 2420 * @name $ionicModal
 2421 * @module ionic
 2422 * @codepen gblny
 2423 * @description
 2424 *
 2425 * Related: {@link ionic.controller:ionicModal ionicModal controller}.
 2426 *
 2427 * The Modal is a content pane that can go over the user's main view
 2428 * temporarily.  Usually used for making a choice or editing an item.
 2429 *
 2430 * Put the content of the modal inside of an `<ion-modal-view>` element.
 2431 *
 2432 * **Notes:**
 2433 * - A modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating
 2434 * scope, passing in itself as an event argument. Both the modal.removed and modal.hidden events are
 2435 * called when the modal is removed.
 2436 *
 2437 * - This example assumes your modal is in your main index file or another template file. If it is in its own
 2438 * template file, remove the script tags and call it by file name.
 2439 *
 2440 * @usage
 2441 * ```html
 2442 * <script id="my-modal.html" type="text/ng-template">
 2443 *   <ion-modal-view>
 2444 *     <ion-header-bar>
 2445 *       <h1 class="title">My Modal title</h1>
 2446 *     </ion-header-bar>
 2447 *     <ion-content>
 2448 *       Hello!
 2449 *     </ion-content>
 2450 *   </ion-modal-view>
 2451 * </script>
 2452 * ```
 2453 * ```js
 2454 * angular.module('testApp', ['ionic'])
 2455 * .controller('MyController', function($scope, $ionicModal) {
 2456 *   $ionicModal.fromTemplateUrl('my-modal.html', {
 2457 *     scope: $scope,
 2458 *     animation: 'slide-in-up'
 2459 *   }).then(function(modal) {
 2460 *     $scope.modal = modal;
 2461 *   });
 2462 *   $scope.openModal = function() {
 2463 *     $scope.modal.show();
 2464 *   };
 2465 *   $scope.closeModal = function() {
 2466 *     $scope.modal.hide();
 2467 *   };
 2468 *   // Cleanup the modal when we're done with it!
 2469 *   $scope.$on('$destroy', function() {
 2470 *     $scope.modal.remove();
 2471 *   });
 2472 *   // Execute action on hide modal
 2473 *   $scope.$on('modal.hidden', function() {
 2474 *     // Execute action
 2475 *   });
 2476 *   // Execute action on remove modal
 2477 *   $scope.$on('modal.removed', function() {
 2478 *     // Execute action
 2479 *   });
 2480 * });
 2481 * ```
 2482 */
 2483IonicModule
 2484.factory('$ionicModal', [
 2485  '$rootScope',
 2486  '$ionicBody',
 2487  '$compile',
 2488  '$timeout',
 2489  '$ionicPlatform',
 2490  '$ionicTemplateLoader',
 2491  '$$q',
 2492  '$log',
 2493  '$ionicClickBlock',
 2494  '$window',
 2495  'IONIC_BACK_PRIORITY',
 2496function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $$q, $log, $ionicClickBlock, $window, IONIC_BACK_PRIORITY) {
 2497
 2498  /**
 2499   * @ngdoc controller
 2500   * @name ionicModal
 2501   * @module ionic
 2502   * @description
 2503   * Instantiated by the {@link ionic.service:$ionicModal} service.
 2504   *
 2505   * Be sure to call [remove()](#remove) when you are done with each modal
 2506   * to clean it up and avoid memory leaks.
 2507   *
 2508   * Note: a modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating
 2509   * scope, passing in itself as an event argument. Note: both modal.removed and modal.hidden are
 2510   * called when the modal is removed.
 2511   */
 2512  var ModalView = ionic.views.Modal.inherit({
 2513    /**
 2514     * @ngdoc method
 2515     * @name ionicModal#initialize
 2516     * @description Creates a new modal controller instance.
 2517     * @param {object} options An options object with the following properties:
 2518     *  - `{object=}` `scope` The scope to be a child of.
 2519     *    Default: creates a child of $rootScope.
 2520     *  - `{string=}` `animation` The animation to show & hide with.
 2521     *    Default: 'slide-in-up'
 2522     *  - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of
 2523     *    the modal when shown. Will only show the keyboard on iOS, to force the keyboard to show
 2524     *    on Android, please use the [Ionic keyboard plugin](https://github.com/driftyco/ionic-plugin-keyboard#keyboardshow).
 2525     *    Default: false.
 2526     *  - `{boolean=}` `backdropClickToClose` Whether to close the modal on clicking the backdrop.
 2527     *    Default: true.
 2528     *  - `{boolean=}` `hardwareBackButtonClose` Whether the modal can be closed using the hardware
 2529     *    back button on Android and similar devices.  Default: true.
 2530     */
 2531    initialize: function(opts) {
 2532      ionic.views.Modal.prototype.initialize.call(this, opts);
 2533      this.animation = opts.animation || 'slide-in-up';
 2534    },
 2535
 2536    /**
 2537     * @ngdoc method
 2538     * @name ionicModal#show
 2539     * @description Show this modal instance.
 2540     * @returns {promise} A promise which is resolved when the modal is finished animating in.
 2541     */
 2542    show: function(target) {
 2543      var self = this;
 2544
 2545      if (self.scope.$$destroyed) {
 2546        $log.error('Cannot call ' + self.viewType + '.show() after remove(). Please create a new ' + self.viewType + ' instance.');
 2547        return $$q.when();
 2548      }
 2549
 2550      // on iOS, clicks will sometimes bleed through/ghost click on underlying
 2551      // elements
 2552      $ionicClickBlock.show(600);
 2553      stack.add(self);
 2554
 2555      var modalEl = jqLite(self.modalEl);
 2556
 2557      self.el.classList.remove('hide');
 2558      $timeout(function() {
 2559        if (!self._isShown) return;
 2560        $ionicBody.addClass(self.viewType + '-open');
 2561      }, 400, false);
 2562
 2563      if (!self.el.parentElement) {
 2564        modalEl.addClass(self.animation);
 2565        $ionicBody.append(self.el);
 2566      }
 2567
 2568      // if modal was closed while the keyboard was up, reset scroll view on
 2569      // next show since we can only resize it once it's visible
 2570      var scrollCtrl = modalEl.data('$$ionicScrollController');
 2571      scrollCtrl && scrollCtrl.resize();
 2572
 2573      if (target && self.positionView) {
 2574        self.positionView(target, modalEl);
 2575        // set up a listener for in case the window size changes
 2576
 2577        self._onWindowResize = function() {
 2578          if (self._isShown) self.positionView(target, modalEl);
 2579        };
 2580        ionic.on('resize', self._onWindowResize, window);
 2581      }
 2582
 2583      modalEl.addClass('ng-enter active')
 2584             .removeClass('ng-leave ng-leave-active');
 2585
 2586      self._isShown = true;
 2587      self._deregisterBackButton = $ionicPlatform.registerBackButtonAction(
 2588        self.hardwareBackButtonClose ? angular.bind(self, self.hide) : noop,
 2589        IONIC_BACK_PRIORITY.modal
 2590      );
 2591
 2592      ionic.views.Modal.prototype.show.call(self);
 2593
 2594      $timeout(function() {
 2595        if (!self._isShown) return;
 2596        modalEl.addClass('ng-enter-active');
 2597        ionic.trigger('resize');
 2598        self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.shown', self);
 2599        self.el.classList.add('active');
 2600        self.scope.$broadcast('$ionicHeader.align');
 2601        self.scope.$broadcast('$ionicFooter.align');
 2602        self.scope.$broadcast('$ionic.modalPresented');
 2603      }, 20);
 2604
 2605      return $timeout(function() {
 2606        if (!self._isShown) return;
 2607        self.$el.on('touchmove', function(e) {
 2608          //Don't allow scrolling while open by dragging on backdrop
 2609          var isInScroll = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'scroll');
 2610          if (!isInScroll) {
 2611            e.preventDefault();
 2612          }
 2613        });
 2614        //After animating in, allow hide on backdrop click
 2615        self.$el.on('click', function(e) {
 2616          if (self.backdropClickToClose && e.target === self.el && stack.isHighest(self)) {
 2617            self.hide();
 2618          }
 2619        });
 2620      }, 400);
 2621    },
 2622
 2623    /**
 2624     * @ngdoc method
 2625     * @name ionicModal#hide
 2626     * @description Hide this modal instance.
 2627     * @returns {promise} A promise which is resolved when the modal is finished animating out.
 2628     */
 2629    hide: function() {
 2630      var self = this;
 2631      var modalEl = jqLite(self.modalEl);
 2632
 2633      // on iOS, clicks will sometimes bleed through/ghost click on underlying
 2634      // elements
 2635      $ionicClickBlock.show(600);
 2636      stack.remove(self);
 2637
 2638      self.el.classList.remove('active');
 2639      modalEl.addClass('ng-leave');
 2640
 2641      $timeout(function() {
 2642        if (self._isShown) return;
 2643        modalEl.addClass('ng-leave-active')
 2644               .removeClass('ng-enter ng-enter-active active');
 2645
 2646        self.scope.$broadcast('$ionic.modalRemoved');
 2647      }, 20, false);
 2648
 2649      self.$el.off('click');
 2650      self._isShown = false;
 2651      self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.hidden', self);
 2652      self._deregisterBackButton && self._deregisterBackButton();
 2653
 2654      ionic.views.Modal.prototype.hide.call(self);
 2655
 2656      // clean up event listeners
 2657      if (self.positionView) {
 2658        ionic.off('resize', self._onWindowResize, window);
 2659      }
 2660
 2661      return $timeout(function() {
 2662        $ionicBody.removeClass(self.viewType + '-open');
 2663        self.el.classList.add('hide');
 2664      }, self.hideDelay || 320);
 2665    },
 2666
 2667    /**
 2668     * @ngdoc method
 2669     * @name ionicModal#remove
 2670     * @description Remove this modal instance from the DOM and clean up.
 2671     * @returns {promise} A promise which is resolved when the modal is finished animating out.
 2672     */
 2673    remove: function() {
 2674      var self = this,
 2675          deferred, promise;
 2676      self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.removed', self);
 2677
 2678      // Only hide modal, when it is actually shown!
 2679      // The hide function shows a click-block-div for a split second, because on iOS,
 2680      // clicks will sometimes bleed through/ghost click on underlying elements.
 2681      // However, this will make the app unresponsive for short amount of time.
 2682      // We don't want that, if the modal window is already hidden.
 2683      if (self._isShown) {
 2684        promise = self.hide();
 2685      } else {
 2686        deferred = $$q.defer();
 2687        deferred.resolve();
 2688        promise = deferred.promise;
 2689      }
 2690
 2691      return promise.then(function() {
 2692        self.scope.$destroy();
 2693        self.$el.remove();
 2694      });
 2695    },
 2696
 2697    /**
 2698     * @ngdoc method
 2699     * @name ionicModal#isShown
 2700     * @returns boolean Whether this modal is currently shown.
 2701     */
 2702    isShown: function() {
 2703      return !!this._isShown;
 2704    }
 2705  });
 2706
 2707  var createModal = function(templateString, options) {
 2708    // Create a new scope for the modal
 2709    var scope = options.scope && options.scope.$new() || $rootScope.$new(true);
 2710
 2711    options.viewType = options.viewType || 'modal';
 2712
 2713    extend(scope, {
 2714      $hasHeader: false,
 2715      $hasSubheader: false,
 2716      $hasFooter: false,
 2717      $hasSubfooter: false,
 2718      $hasTabs: false,
 2719      $hasTabsTop: false
 2720    });
 2721
 2722    // Compile the template
 2723    var element = $compile('<ion-' + options.viewType + '>' + templateString + '</ion-' + options.viewType + '>')(scope);
 2724
 2725    options.$el = element;
 2726    options.el = element[0];
 2727    options.modalEl = options.el.querySelector('.' + options.viewType);
 2728    var modal = new ModalView(options);
 2729
 2730    modal.scope = scope;
 2731
 2732    // If this wasn't a defined scope, we can assign the viewType to the isolated scope
 2733    // we created
 2734    if (!options.scope) {
 2735      scope[ options.viewType ] = modal;
 2736    }
 2737
 2738    return modal;
 2739  };
 2740
 2741  var modalStack = [];
 2742  var stack = {
 2743    add: function(modal) {
 2744      modalStack.push(modal);
 2745    },
 2746    remove: function(modal) {
 2747      var index = modalStack.indexOf(modal);
 2748      if (index > -1 && index < modalStack.length) {
 2749        modalStack.splice(index, 1);
 2750      }
 2751    },
 2752    isHighest: function(modal) {
 2753      var index = modalStack.indexOf(modal);
 2754      return (index > -1 && index === modalStack.length - 1);
 2755    }
 2756  };
 2757
 2758  return {
 2759    /**
 2760     * @ngdoc method
 2761     * @name $ionicModal#fromTemplate
 2762     * @param {string} templateString The template string to use as the modal's
 2763     * content.
 2764     * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
 2765     * @returns {object} An instance of an {@link ionic.controller:ionicModal}
 2766     * controller.
 2767     */
 2768    fromTemplate: function(templateString, options) {
 2769      var modal = createModal(templateString, options || {});
 2770      return modal;
 2771    },
 2772    /**
 2773     * @ngdoc method
 2774     * @name $ionicModal#fromTemplateUrl
 2775     * @param {string} templateUrl The url to load the template from.
 2776     * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
 2777     * options object.
 2778     * @returns {promise} A promise that will be resolved with an instance of
 2779     * an {@link ionic.controller:ionicModal} controller.
 2780     */
 2781    fromTemplateUrl: function(url, options, _) {
 2782      var cb;
 2783      //Deprecated: allow a callback as second parameter. Now we return a promise.
 2784      if (angular.isFunction(options)) {
 2785        cb = options;
 2786        options = _;
 2787      }
 2788      return $ionicTemplateLoader.load(url).then(function(templateString) {
 2789        var modal = createModal(templateString, options || {});
 2790        cb && cb(modal);
 2791        return modal;
 2792      });
 2793    },
 2794
 2795    stack: stack
 2796  };
 2797}]);
 2798
 2799
 2800/**
 2801 * @ngdoc service
 2802 * @name $ionicNavBarDelegate
 2803 * @module ionic
 2804 * @description
 2805 * Delegate for controlling the {@link ionic.directive:ionNavBar} directive.
 2806 *
 2807 * @usage
 2808 *
 2809 * ```html
 2810 * <body ng-controller="MyCtrl">
 2811 *   <ion-nav-bar>
 2812 *     <button ng-click="setNavTitle('banana')">
 2813 *       Set title to banana!
 2814 *     </button>
 2815 *   </ion-nav-bar>
 2816 * </body>
 2817 * ```
 2818 * ```js
 2819 * function MyCtrl($scope, $ionicNavBarDelegate) {
 2820 *   $scope.setNavTitle = function(title) {
 2821 *     $ionicNavBarDelegate.title(title);
 2822 *   }
 2823 * }
 2824 * ```
 2825 */
 2826IonicModule
 2827.service('$ionicNavBarDelegate', ionic.DelegateService([
 2828  /**
 2829   * @ngdoc method
 2830   * @name $ionicNavBarDelegate#align
 2831   * @description Aligns the title with the buttons in a given direction.
 2832   * @param {string=} direction The direction to the align the title text towards.
 2833   * Available: 'left', 'right', 'center'. Default: 'center'.
 2834   */
 2835  'align',
 2836  /**
 2837   * @ngdoc method
 2838   * @name $ionicNavBarDelegate#showBackButton
 2839   * @description
 2840   * Set/get whether the {@link ionic.directive:ionNavBackButton} is shown
 2841   * (if it exists and there is a previous view that can be navigated to).
 2842   * @param {boolean=} show Whether to show the back button.
 2843   * @returns {boolean} Whether the back button is shown.
 2844   */
 2845  'showBackButton',
 2846  /**
 2847   * @ngdoc method
 2848   * @name $ionicNavBarDelegate#showBar
 2849   * @description
 2850   * Set/get whether the {@link ionic.directive:ionNavBar} is shown.
 2851   * @param {boolean} show Whether to show the bar.
 2852   * @returns {boolean} Whether the bar is shown.
 2853   */
 2854  'showBar',
 2855  /**
 2856   * @ngdoc method
 2857   * @name $ionicNavBarDelegate#title
 2858   * @description
 2859   * Set the title for the {@link ionic.directive:ionNavBar}.
 2860   * @param {string} title The new title to show.
 2861   */
 2862  'title',
 2863
 2864  // DEPRECATED, as of v1.0.0-beta14 -------
 2865  'changeTitle',
 2866  'setTitle',
 2867  'getTitle',
 2868  'back',
 2869  'getPreviousTitle'
 2870  // END DEPRECATED -------
 2871]));
 2872
 2873
 2874IonicModule
 2875.service('$ionicNavViewDelegate', ionic.DelegateService([
 2876  'clearCache'
 2877]));
 2878
 2879
 2880
 2881/**
 2882 * @ngdoc service
 2883 * @name $ionicPlatform
 2884 * @module ionic
 2885 * @description
 2886 * An angular abstraction of {@link ionic.utility:ionic.Platform}.
 2887 *
 2888 * Used to detect the current platform, as well as do things like override the
 2889 * Android back button in PhoneGap/Cordova.
 2890 */
 2891IonicModule
 2892.constant('IONIC_BACK_PRIORITY', {
 2893  view: 100,
 2894  sideMenu: 150,
 2895  modal: 200,
 2896  actionSheet: 300,
 2897  popup: 400,
 2898  loading: 500
 2899})
 2900.provider('$ionicPlatform', function() {
 2901  return {
 2902    $get: ['$q', '$ionicScrollDelegate', function($q, $ionicScrollDelegate) {
 2903      var self = {
 2904
 2905        /**
 2906         * @ngdoc method
 2907         * @name $ionicPlatform#onHardwareBackButton
 2908         * @description
 2909         * Some platforms have a hardware back button, so this is one way to
 2910         * bind to it.
 2911         * @param {function} callback the callback to trigger when this event occurs
 2912         */
 2913        onHardwareBackButton: function(cb) {
 2914          ionic.Platform.ready(function() {
 2915            document.addEventListener('backbutton', cb, false);
 2916          });
 2917        },
 2918
 2919        /**
 2920         * @ngdoc method
 2921         * @name $ionicPlatform#offHardwareBackButton
 2922         * @description
 2923         * Remove an event listener for the backbutton.
 2924         * @param {function} callback The listener function that was
 2925         * originally bound.
 2926         */
 2927        offHardwareBackButton: function(fn) {
 2928          ionic.Platform.ready(function() {
 2929            document.removeEventListener('backbutton', fn);
 2930          });
 2931        },
 2932
 2933        /**
 2934         * @ngdoc method
 2935         * @name $ionicPlatform#registerBackButtonAction
 2936         * @description
 2937         * Register a hardware back button action. Only one action will execute
 2938         * when the back button is clicked, so this method decides which of
 2939         * the registered back button actions has the highest priority.
 2940         *
 2941         * For example, if an actionsheet is showing, the back button should
 2942         * close the actionsheet, but it should not also go back a page view
 2943         * or close a modal which may be open.
 2944         *
 2945         * The priorities for the existing back button hooks are as follows:
 2946         *   Return to previous view = 100
 2947         *   Close side menu = 150
 2948         *   Dismiss modal = 200
 2949         *   Close action sheet = 300
 2950         *   Dismiss popup = 400
 2951         *   Dismiss loading overlay = 500
 2952         *
 2953         * Your back button action will override each of the above actions
 2954         * whose priority is less than the priority you provide. For example,
 2955         * an action assigned a priority of 101 will override the 'return to
 2956         * previous view' action, but not any of the other actions.
 2957         *
 2958         * @param {function} callback Called when the back button is pressed,
 2959         * if this listener is the highest priority.
 2960         * @param {number} priority Only the highest priority will execute.
 2961         * @param {*=} actionId The id to assign this action. Default: a
 2962         * random unique id.
 2963         * @returns {function} A function that, when called, will deregister
 2964         * this backButtonAction.
 2965         */
 2966        $backButtonActions: {},
 2967        registerBackButtonAction: function(fn, priority, actionId) {
 2968
 2969          if (!self._hasBackButtonHandler) {
 2970            // add a back button listener if one hasn't been setup yet
 2971            self.$backButtonActions = {};
 2972            self.onHardwareBackButton(self.hardwareBackButtonClick);
 2973            self._hasBackButtonHandler = true;
 2974          }
 2975
 2976          var action = {
 2977            id: (actionId ? actionId : ionic.Utils.nextUid()),
 2978            priority: (priority ? priority : 0),
 2979            fn: fn
 2980          };
 2981          self.$backButtonActions[action.id] = action;
 2982
 2983          // return a function to de-register this back button action
 2984          return function() {
 2985            delete self.$backButtonActions[action.id];
 2986          };
 2987        },
 2988
 2989        /**
 2990         * @private
 2991         */
 2992        hardwareBackButtonClick: function(e) {
 2993          // loop through all the registered back button actions
 2994          // and only run the last one of the highest priority
 2995          var priorityAction, actionId;
 2996          for (actionId in self.$backButtonActions) {
 2997            if (!priorityAction || self.$backButtonActions[actionId].priority >= priorityAction.priority) {
 2998              priorityAction = self.$backButtonActions[actionId];
 2999            }
 3000          }
 3001          if (priorityAction) {
 3002            priorityAction.fn(e);
 3003            return priorityAction;
 3004          }
 3005        },
 3006
 3007        is: function(type) {
 3008          return ionic.Platform.is(type);
 3009        },
 3010
 3011        /**
 3012         * @ngdoc method
 3013         * @name $ionicPlatform#on
 3014         * @description
 3015         * Add Cordova event listeners, such as `pause`, `resume`, `volumedownbutton`, `batterylow`,
 3016         * `offline`, etc. More information about available event types can be found in
 3017         * [Cordova's event documentation](https://cordova.apache.org/docs/en/edge/cordova_events_events.md.html#Events).
 3018         * @param {string} type Cordova [event type](https://cordova.apache.org/docs/en/edge/cordova_events_events.md.html#Events).
 3019         * @param {function} callback Called when the Cordova event is fired.
 3020         * @returns {function} Returns a deregistration function to remove the event listener.
 3021         */
 3022        on: function(type, cb) {
 3023          ionic.Platform.ready(function() {
 3024            document.addEventListener(type, cb, false);
 3025          });
 3026          return function() {
 3027            ionic.Platform.ready(function() {
 3028              document.removeEventListener(type, cb);
 3029            });
 3030          };
 3031        },
 3032
 3033        /**
 3034         * @ngdoc method
 3035         * @name $ionicPlatform#ready
 3036         * @description
 3037         * Trigger a callback once the device is ready,
 3038         * or immediately if the device is already ready.
 3039         * @param {function=} callback The function to call.
 3040         * @returns {promise} A promise which is resolved when the device is ready.
 3041         */
 3042        ready: function(cb) {
 3043          var q = $q.defer();
 3044
 3045          ionic.Platform.ready(function() {
 3046
 3047            window.addEventListener('statusTap', function() {
 3048              $ionicScrollDelegate.scrollTop(true);
 3049            });
 3050
 3051            q.resolve();
 3052            cb && cb();
 3053          });
 3054
 3055          return q.promise;
 3056        }
 3057      };
 3058
 3059      return self;
 3060    }]
 3061  };
 3062
 3063});
 3064
 3065/**
 3066 * @ngdoc service
 3067 * @name $ionicPopover
 3068 * @module ionic
 3069 * @description
 3070 *
 3071 * Related: {@link ionic.controller:ionicPopover ionicPopover controller}.
 3072 *
 3073 * The Popover is a view that floats above an app’s content. Popovers provide an
 3074 * easy way to present or gather information from the user and are
 3075 * commonly used in the following situations:
 3076 *
 3077 * - Show more info about the current view
 3078 * - Select a commonly used tool or configuration
 3079 * - Present a list of actions to perform inside one of your views
 3080 *
 3081 * Put the content of the popover inside of an `<ion-popover-view>` element.
 3082 *
 3083 * @usage
 3084 * ```html
 3085 * <p>
 3086 *   <button ng-click="openPopover($event)">Open Popover</button>
 3087 * </p>
 3088 *
 3089 * <script id="my-popover.html" type="text/ng-template">
 3090 *   <ion-popover-view>
 3091 *     <ion-header-bar>
 3092 *       <h1 class="title">My Popover Title</h1>
 3093 *     </ion-header-bar>
 3094 *     <ion-content>
 3095 *       Hello!
 3096 *     </ion-content>
 3097 *   </ion-popover-view>
 3098 * </script>
 3099 * ```
 3100 * ```js
 3101 * angular.module('testApp', ['ionic'])
 3102 * .controller('MyController', function($scope, $ionicPopover) {
 3103 *
 3104 *   // .fromTemplate() method
 3105 *   var template = '<ion-popover-view><ion-header-bar> <h1 class="title">My Popover Title</h1> </ion-header-bar> <ion-content> Hello! </ion-content></ion-popover-view>';
 3106 *
 3107 *   $scope.popover = $ionicPopover.fromTemplate(template, {
 3108 *     scope: $scope
 3109 *   });
 3110 *
 3111 *   // .fromTemplateUrl() method
 3112 *   $ionicPopover.fromTemplateUrl('my-popover.html', {
 3113 *     scope: $scope
 3114 *   }).then(function(popover) {
 3115 *     $scope.popover = popover;
 3116 *   });
 3117 *
 3118 *
 3119 *   $scope.openPopover = function($event) {
 3120 *     $scope.popover.show($event);
 3121 *   };
 3122 *   $scope.closePopover = function() {
 3123 *     $scope.popover.hide();
 3124 *   };
 3125 *   //Cleanup the popover when we're done with it!
 3126 *   $scope.$on('$destroy', function() {
 3127 *     $scope.popover.remove();
 3128 *   });
 3129 *   // Execute action on hide popover
 3130 *   $scope.$on('popover.hidden', function() {
 3131 *     // Execute action
 3132 *   });
 3133 *   // Execute action on remove popover
 3134 *   $scope.$on('popover.removed', function() {
 3135 *     // Execute action
 3136 *   });
 3137 * });
 3138 * ```
 3139 */
 3140
 3141
 3142IonicModule
 3143.factory('$ionicPopover', ['$ionicModal', '$ionicPosition', '$document', '$window',
 3144function($ionicModal, $ionicPosition, $document, $window) {
 3145
 3146  var POPOVER_BODY_PADDING = 6;
 3147
 3148  var POPOVER_OPTIONS = {
 3149    viewType: 'popover',
 3150    hideDelay: 1,
 3151    animation: 'none',
 3152    positionView: positionView
 3153  };
 3154
 3155  function positionView(target, popoverEle) {
 3156    var targetEle = jqLite(target.target || target);
 3157    var buttonOffset = $ionicPosition.offset(targetEle);
 3158    var popoverWidth = popoverEle.prop('offsetWidth');
 3159    var popoverHeight = popoverEle.prop('offsetHeight');
 3160    // Use innerWidth and innerHeight, because clientWidth and clientHeight
 3161    // doesn't work consistently for body on all platforms
 3162    var bodyWidth = $window.innerWidth;
 3163    var bodyHeight = $window.innerHeight;
 3164
 3165    var popoverCSS = {
 3166      left: buttonOffset.left + buttonOffset.width / 2 - popoverWidth / 2
 3167    };
 3168    var arrowEle = jqLite(popoverEle[0].querySelector('.popover-arrow'));
 3169
 3170    if (popoverCSS.left < POPOVER_BODY_PADDING) {
 3171      popoverCSS.left = POPOVER_BODY_PADDING;
 3172    } else if (popoverCSS.left + popoverWidth + POPOVER_BODY_PADDING > bodyWidth) {
 3173      popoverCSS.left = bodyWidth - popoverWidth - POPOVER_BODY_PADDING;
 3174    }
 3175
 3176    // If the popover when popped down stretches past bottom of screen,
 3177    // make it pop up if there's room above
 3178    if (buttonOffset.top + buttonOffset.height + popoverHeight > bodyHeight &&
 3179        buttonOffset.top - popoverHeight > 0) {
 3180      popoverCSS.top = buttonOffset.top - popoverHeight;
 3181      popoverEle.addClass('popover-bottom');
 3182    } else {
 3183      popoverCSS.top = buttonOffset.top + buttonOffset.height;
 3184      popoverEle.removeClass('popover-bottom');
 3185    }
 3186
 3187    arrowEle.css({
 3188      left: buttonOffset.left + buttonOffset.width / 2 -
 3189        arrowEle.prop('offsetWidth') / 2 - popoverCSS.left + 'px'
 3190    });
 3191
 3192    popoverEle.css({
 3193      top: popoverCSS.top + 'px',
 3194      left: popoverCSS.left + 'px',
 3195      marginLeft: '0',
 3196      opacity: '1'
 3197    });
 3198
 3199  }
 3200
 3201  /**
 3202   * @ngdoc controller
 3203   * @name ionicPopover
 3204   * @module ionic
 3205   * @description
 3206   * Instantiated by the {@link ionic.service:$ionicPopover} service.
 3207   *
 3208   * Be sure to call [remove()](#remove) when you are done with each popover
 3209   * to clean it up and avoid memory leaks.
 3210   *
 3211   * Note: a popover will broadcast 'popover.shown', 'popover.hidden', and 'popover.removed' events from its originating
 3212   * scope, passing in itself as an event argument. Both the popover.removed and popover.hidden events are
 3213   * called when the popover is removed.
 3214   */
 3215
 3216  /**
 3217   * @ngdoc method
 3218   * @name ionicPopover#initialize
 3219   * @description Creates a new popover controller instance.
 3220   * @param {object} options An options object with the following properties:
 3221   *  - `{object=}` `scope` The scope to be a child of.
 3222   *    Default: creates a child of $rootScope.
 3223   *  - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of
 3224   *    the popover when shown.  Default: false.
 3225   *  - `{boolean=}` `backdropClickToClose` Whether to close the popover on clicking the backdrop.
 3226   *    Default: true.
 3227   *  - `{boolean=}` `hardwareBackButtonClose` Whether the popover can be closed using the hardware
 3228   *    back button on Android and similar devices.  Default: true.
 3229   */
 3230
 3231  /**
 3232   * @ngdoc method
 3233   * @name ionicPopover#show
 3234   * @description Show this popover instance.
 3235   * @param {$event} $event The $event or target element which the popover should align
 3236   * itself next to.
 3237   * @returns {promise} A promise which is resolved when the popover is finished animating in.
 3238   */
 3239
 3240  /**
 3241   * @ngdoc method
 3242   * @name ionicPopover#hide
 3243   * @description Hide this popover instance.
 3244   * @returns {promise} A promise which is resolved when the popover is finished animating out.
 3245   */
 3246
 3247  /**
 3248   * @ngdoc method
 3249   * @name ionicPopover#remove
 3250   * @description Remove this popover instance from the DOM and clean up.
 3251   * @returns {promise} A promise which is resolved when the popover is finished animating out.
 3252   */
 3253
 3254  /**
 3255   * @ngdoc method
 3256   * @name ionicPopover#isShown
 3257   * @returns boolean Whether this popover is currently shown.
 3258   */
 3259
 3260  return {
 3261    /**
 3262     * @ngdoc method
 3263     * @name $ionicPopover#fromTemplate
 3264     * @param {string} templateString The template string to use as the popovers's
 3265     * content.
 3266     * @param {object} options Options to be passed to the initialize method.
 3267     * @returns {object} An instance of an {@link ionic.controller:ionicPopover}
 3268     * controller (ionicPopover is built on top of $ionicPopover).
 3269     */
 3270    fromTemplate: function(templateString, options) {
 3271      return $ionicModal.fromTemplate(templateString, ionic.Utils.extend({}, POPOVER_OPTIONS, options));
 3272    },
 3273    /**
 3274     * @ngdoc method
 3275     * @name $ionicPopover#fromTemplateUrl
 3276     * @param {string} templateUrl The url to load the template from.
 3277     * @param {object} options Options to be passed to the initialize method.
 3278     * @returns {promise} A promise that will be resolved with an instance of
 3279     * an {@link ionic.controller:ionicPopover} controller (ionicPopover is built on top of $ionicPopover).
 3280     */
 3281    fromTemplateUrl: function(url, options) {
 3282      return $ionicModal.fromTemplateUrl(url, ionic.Utils.extend({}, POPOVER_OPTIONS, options));
 3283    }
 3284  };
 3285
 3286}]);
 3287
 3288
 3289var POPUP_TPL =
 3290  '<div class="popup-container" ng-class="cssClass">' +
 3291    '<div class="popup">' +
 3292      '<div class="popup-head">' +
 3293        '<h3 class="popup-title" ng-bind-html="title"></h3>' +
 3294        '<h5 class="popup-sub-title" ng-bind-html="subTitle" ng-if="subTitle"></h5>' +
 3295      '</div>' +
 3296      '<div class="popup-body">' +
 3297      '</div>' +
 3298      '<div class="popup-buttons" ng-show="buttons.length">' +
 3299        '<button ng-repeat="button in buttons" ng-click="$buttonTapped(button, $event)" class="button" ng-class="button.type || \'button-default\'" ng-bind-html="button.text"></button>' +
 3300      '</div>' +
 3301    '</div>' +
 3302  '</div>';
 3303
 3304/**
 3305 * @ngdoc service
 3306 * @name $ionicPopup
 3307 * @module ionic
 3308 * @restrict E
 3309 * @codepen zkmhJ
 3310 * @description
 3311 *
 3312 * The Ionic Popup service allows programmatically creating and showing popup
 3313 * windows that require the user to respond in order to continue.
 3314 *
 3315 * The popup system has support for more flexible versions of the built in `alert()`, `prompt()`,
 3316 * and `confirm()` functions that users are used to, in addition to allowing popups with completely
 3317 * custom content and look.
 3318 *
 3319 * An input can be given an `autofocus` attribute so it automatically receives focus when
 3320 * the popup first shows. However, depending on certain use-cases this can cause issues with
 3321 * the tap/click system, which is why Ionic prefers using the `autofocus` attribute as
 3322 * an opt-in feature and not the default.
 3323 *
 3324 * @usage
 3325 * A few basic examples, see below for details about all of the options available.
 3326 *
 3327 * ```js
 3328 *angular.module('mySuperApp', ['ionic'])
 3329 *.controller('PopupCtrl',function($scope, $ionicPopup, $timeout) {
 3330 *
 3331 * // Triggered on a button click, or some other target
 3332 * $scope.showPopup = function() {
 3333 *   $scope.data = {};
 3334 *
 3335 *   // An elaborate, custom popup
 3336 *   var myPopup = $ionicPopup.show({
 3337 *     template: '<input type="password" ng-model="data.wifi">',
 3338 *     title: 'Enter Wi-Fi Password',
 3339 *     subTitle: 'Please use normal things',
 3340 *     scope: $scope,
 3341 *     buttons: [
 3342 *       { text: 'Cancel' },
 3343 *       {
 3344 *         text: '<b>Save</b>',
 3345 *         type: 'button-positive',
 3346 *         onTap: function(e) {
 3347 *           if (!$scope.data.wifi) {
 3348 *             //don't allow the user to close unless he enters wifi password
 3349 *             e.preventDefault();
 3350 *           } else {
 3351 *             return $scope.data.wifi;
 3352 *           }
 3353 *         }
 3354 *       }
 3355 *     ]
 3356 *   });
 3357 *
 3358 *   myPopup.then(function(res) {
 3359 *     console.log('Tapped!', res);
 3360 *   });
 3361 *
 3362 *   $timeout(function() {
 3363 *      myPopup.close(); //close the popup after 3 seconds for some reason
 3364 *   }, 3000);
 3365 *  };
 3366 *
 3367 *  // A confirm dialog
 3368 *  $scope.showConfirm = function() {
 3369 *    var confirmPopup = $ionicPopup.confirm({
 3370 *      title: 'Consume Ice Cream',
 3371 *      template: 'Are you sure you want to eat this ice cream?'
 3372 *    });
 3373 *
 3374 *    confirmPopup.then(function(res) {
 3375 *      if(res) {
 3376 *        console.log('You are sure');
 3377 *      } else {
 3378 *        console.log('You are not sure');
 3379 *      }
 3380 *    });
 3381 *  };
 3382 *
 3383 *  // An alert dialog
 3384 *  $scope.showAlert = function() {
 3385 *    var alertPopup = $ionicPopup.alert({
 3386 *      title: 'Don\'t eat that!',
 3387 *      template: 'It might taste good'
 3388 *    });
 3389 *
 3390 *    alertPopup.then(function(res) {
 3391 *      console.log('Thank you for not eating my delicious ice cream cone');
 3392 *    });
 3393 *  };
 3394 *});
 3395 *```
 3396 */
 3397
 3398IonicModule
 3399.factory('$ionicPopup', [
 3400  '$ionicTemplateLoader',
 3401  '$ionicBackdrop',
 3402  '$q',
 3403  '$timeout',
 3404  '$rootScope',
 3405  '$ionicBody',
 3406  '$compile',
 3407  '$ionicPlatform',
 3408  '$ionicModal',
 3409  'IONIC_BACK_PRIORITY',
 3410function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $ionicBody, $compile, $ionicPlatform, $ionicModal, IONIC_BACK_PRIORITY) {
 3411  //TODO allow this to be configured
 3412  var config = {
 3413    stackPushDelay: 75
 3414  };
 3415  var popupStack = [];
 3416
 3417  var $ionicPopup = {
 3418    /**
 3419     * @ngdoc method
 3420     * @description
 3421     * Show a complex popup. This is the master show function for all popups.
 3422     *
 3423     * A complex popup has a `buttons` array, with each button having a `text` and `type`
 3424     * field, in addition to an `onTap` function.  The `onTap` function, called when
 3425     * the corresponding button on the popup is tapped, will by default close the popup
 3426     * and resolve the popup promise with its return value.  If you wish to prevent the
 3427     * default and keep the popup open on button tap, call `event.preventDefault()` on the
 3428     * passed in tap event.  Details below.
 3429     *
 3430     * @name $ionicPopup#show
 3431     * @param {object} options The options for the new popup, of the form:
 3432     *
 3433     * ```
 3434     * {
 3435     *   title: '', // String. The title of the popup.
 3436     *   cssClass: '', // String, The custom CSS class name
 3437     *   subTitle: '', // String (optional). The sub-title of the popup.
 3438     *   template: '', // String (optional). The html template to place in the popup body.
 3439     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup   body.
 3440     *   scope: null, // Scope (optional). A scope to link to the popup content.
 3441     *   buttons: [{ // Array[Object] (optional). Buttons to place in the popup footer.
 3442     *     text: 'Cancel',
 3443     *     type: 'button-default',
 3444     *     onTap: function(e) {
 3445     *       // e.preventDefault() will stop the popup from closing when tapped.
 3446     *       e.preventDefault();
 3447     *     }
 3448     *   }, {
 3449     *     text: 'OK',
 3450     *     type: 'button-positive',
 3451     *     onTap: function(e) {
 3452     *       // Returning a value will cause the promise to resolve with the given value.
 3453     *       return scope.data.response;
 3454     *     }
 3455     *   }]
 3456     * }
 3457     * ```
 3458     *
 3459     * @returns {object} A promise which is resolved when the popup is closed. Has an additional
 3460     * `close` function, which can be used to programmatically close the popup.
 3461     */
 3462    show: showPopup,
 3463
 3464    /**
 3465     * @ngdoc method
 3466     * @name $ionicPopup#alert
 3467     * @description Show a simple alert popup with a message and one button that the user can
 3468     * tap to close the popup.
 3469     *
 3470     * @param {object} options The options for showing the alert, of the form:
 3471     *
 3472     * ```
 3473     * {
 3474     *   title: '', // String. The title of the popup.
 3475     *   cssClass: '', // String, The custom CSS class name
 3476     *   subTitle: '', // String (optional). The sub-title of the popup.
 3477     *   template: '', // String (optional). The html template to place in the popup body.
 3478     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup   body.
 3479     *   okText: '', // String (default: 'OK'). The text of the OK button.
 3480     *   okType: '', // String (default: 'button-positive'). The type of the OK button.
 3481     * }
 3482     * ```
 3483     *
 3484     * @returns {object} A promise which is resolved when the popup is closed. Has one additional
 3485     * function `close`, which can be called with any value to programmatically close the popup
 3486     * with the given value.
 3487     */
 3488    alert: showAlert,
 3489
 3490    /**
 3491     * @ngdoc method
 3492     * @name $ionicPopup#confirm
 3493     * @description
 3494     * Show a simple confirm popup with a Cancel and OK button.
 3495     *
 3496     * Resolves the promise with true if the user presses the OK button, and false if the
 3497     * user presses the Cancel button.
 3498     *
 3499     * @param {object} options The options for showing the confirm popup, of the form:
 3500     *
 3501     * ```
 3502     * {
 3503     *   title: '', // String. The title of the popup.
 3504     *   cssClass: '', // String, The custom CSS class name
 3505     *   subTitle: '', // String (optional). The sub-title of the popup.
 3506     *   template: '', // String (optional). The html template to place in the popup body.
 3507     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup   body.
 3508     *   cancelText: '', // String (default: 'Cancel'). The text of the Cancel button.
 3509     *   cancelType: '', // String (default: 'button-default'). The type of the Cancel button.
 3510     *   okText: '', // String (default: 'OK'). The text of the OK button.
 3511     *   okType: '', // String (default: 'button-positive'). The type of the OK button.
 3512     * }
 3513     * ```
 3514     *
 3515     * @returns {object} A promise which is resolved when the popup is closed. Has one additional
 3516     * function `close`, which can be called with any value to programmatically close the popup
 3517     * with the given value.
 3518     */
 3519    confirm: showConfirm,
 3520
 3521    /**
 3522     * @ngdoc method
 3523     * @name $ionicPopup#prompt
 3524     * @description Show a simple prompt popup, which has an input, OK button, and Cancel button.
 3525     * Resolves the promise with the value of the input if the user presses OK, and with undefined
 3526     * if the user presses Cancel.
 3527     *
 3528     * ```javascript
 3529     *  $ionicPopup.prompt({
 3530     *    title: 'Password Check',
 3531     *    template: 'Enter your secret password',
 3532     *    inputType: 'password',
 3533     *    inputPlaceholder: 'Your password'
 3534     *  }).then(function(res) {
 3535     *    console.log('Your password is', res);
 3536     *  });
 3537     * ```
 3538     * @param {object} options The options for showing the prompt popup, of the form:
 3539     *
 3540     * ```
 3541     * {
 3542     *   title: '', // String. The title of the popup.
 3543     *   cssClass: '', // String, The custom CSS class name
 3544     *   subTitle: '', // String (optional). The sub-title of the popup.
 3545     *   template: '', // String (optional). The html template to place in the popup body.
 3546     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup body.
 3547     *   inputType: // String (default: 'text'). The type of input to use
 3548     *   defaultText: // String (default: ''). The initial value placed into the input.
 3549     *   maxLength: // Integer (default: null). Specify a maxlength attribute for the input.
 3550     *   inputPlaceholder: // String (default: ''). A placeholder to use for the input.
 3551     *   cancelText: // String (default: 'Cancel'. The text of the Cancel button.
 3552     *   cancelType: // String (default: 'button-default'). The type of the Cancel button.
 3553     *   okText: // String (default: 'OK'). The text of the OK button.
 3554     *   okType: // String (default: 'button-positive'). The type of the OK button.
 3555     * }
 3556     * ```
 3557     *
 3558     * @returns {object} A promise which is resolved when the popup is closed. Has one additional
 3559     * function `close`, which can be called with any value to programmatically close the popup
 3560     * with the given value.
 3561     */
 3562    prompt: showPrompt,
 3563    /**
 3564     * @private for testing
 3565     */
 3566    _createPopup: createPopup,
 3567    _popupStack: popupStack
 3568  };
 3569
 3570  return $ionicPopup;
 3571
 3572  function createPopup(options) {
 3573    options = extend({
 3574      scope: null,
 3575      title: '',
 3576      buttons: []
 3577    }, options || {});
 3578
 3579    var self = {};
 3580    self.scope = (options.scope || $rootScope).$new();
 3581    self.element = jqLite(POPUP_TPL);
 3582    self.responseDeferred = $q.defer();
 3583
 3584    $ionicBody.get().appendChild(self.element[0]);
 3585    $compile(self.element)(self.scope);
 3586
 3587    extend(self.scope, {
 3588      title: options.title,
 3589      buttons: options.buttons,
 3590      subTitle: options.subTitle,
 3591      cssClass: options.cssClass,
 3592      $buttonTapped: function(button, event) {
 3593        var result = (button.onTap || noop).apply(self, [event]);
 3594        event = event.originalEvent || event; //jquery events
 3595
 3596        if (!event.defaultPrevented) {
 3597          self.responseDeferred.resolve(result);
 3598        }
 3599      }
 3600    });
 3601
 3602    $q.when(
 3603      options.templateUrl ?
 3604      $ionicTemplateLoader.load(options.templateUrl) :
 3605        (options.template || options.content || '')
 3606    ).then(function(template) {
 3607      var popupBody = jqLite(self.element[0].querySelector('.popup-body'));
 3608      if (template) {
 3609        popupBody.html(template);
 3610        $compile(popupBody.contents())(self.scope);
 3611      } else {
 3612        popupBody.remove();
 3613      }
 3614    });
 3615
 3616    self.show = function() {
 3617      if (self.isShown || self.removed) return;
 3618
 3619      $ionicModal.stack.add(self);
 3620      self.isShown = true;
 3621      ionic.requestAnimationFrame(function() {
 3622        //if hidden while waiting for raf, don't show
 3623        if (!self.isShown) return;
 3624
 3625        self.element.removeClass('popup-hidden');
 3626        self.element.addClass('popup-showing active');
 3627        focusInput(self.element);
 3628      });
 3629    };
 3630
 3631    self.hide = function(callback) {
 3632      callback = callback || noop;
 3633      if (!self.isShown) return callback();
 3634
 3635      $ionicModal.stack.remove(self);
 3636      self.isShown = false;
 3637      self.element.removeClass('active');
 3638      self.element.addClass('popup-hidden');
 3639      $timeout(callback, 250, false);
 3640    };
 3641
 3642    self.remove = function() {
 3643      if (self.removed) return;
 3644
 3645      self.hide(function() {
 3646        self.element.remove();
 3647        self.scope.$destroy();
 3648      });
 3649
 3650      self.removed = true;
 3651    };
 3652
 3653    return self;
 3654  }
 3655
 3656  function onHardwareBackButton() {
 3657    var last = popupStack[popupStack.length - 1];
 3658    last && last.responseDeferred.resolve();
 3659  }
 3660
 3661  function showPopup(options) {
 3662    var popup = $ionicPopup._createPopup(options);
 3663    var showDelay = 0;
 3664
 3665    if (popupStack.length > 0) {
 3666      showDelay = config.stackPushDelay;
 3667      $timeout(popupStack[popupStack.length - 1].hide, showDelay, false);
 3668    } else {
 3669      //Add popup-open & backdrop if this is first popup
 3670      $ionicBody.addClass('popup-open');
 3671      $ionicBackdrop.retain();
 3672      //only show the backdrop on the first popup
 3673      $ionicPopup._backButtonActionDone = $ionicPlatform.registerBackButtonAction(
 3674        onHardwareBackButton,
 3675        IONIC_BACK_PRIORITY.popup
 3676      );
 3677    }
 3678
 3679    // Expose a 'close' method on the returned promise
 3680    popup.responseDeferred.promise.close = function popupClose(result) {
 3681      if (!popup.removed) popup.responseDeferred.resolve(result);
 3682    };
 3683    //DEPRECATED: notify the promise with an object with a close method
 3684    popup.responseDeferred.notify({ close: popup.responseDeferred.close });
 3685
 3686    doShow();
 3687
 3688    return popup.responseDeferred.promise;
 3689
 3690    function doShow() {
 3691      popupStack.push(popup);
 3692      $timeout(popup.show, showDelay, false);
 3693
 3694      popup.responseDeferred.promise.then(function(result) {
 3695        var index = popupStack.indexOf(popup);
 3696        if (index !== -1) {
 3697          popupStack.splice(index, 1);
 3698        }
 3699
 3700        popup.remove();
 3701
 3702        if (popupStack.length > 0) {
 3703          popupStack[popupStack.length - 1].show();
 3704        } else {
 3705          $ionicBackdrop.release();
 3706          //Remove popup-open & backdrop if this is last popup
 3707          $timeout(function() {
 3708            // wait to remove this due to a 300ms delay native
 3709            // click which would trigging whatever was underneath this
 3710            if (!popupStack.length) {
 3711              $ionicBody.removeClass('popup-open');
 3712            }
 3713          }, 400, false);
 3714          ($ionicPopup._backButtonActionDone || noop)();
 3715        }
 3716
 3717
 3718        return result;
 3719      });
 3720
 3721    }
 3722
 3723  }
 3724
 3725  function focusInput(element) {
 3726    var focusOn = element[0].querySelector('[autofocus]');
 3727    if (focusOn) {
 3728      focusOn.focus();
 3729    }
 3730  }
 3731
 3732  function showAlert(opts) {
 3733    return showPopup(extend({
 3734      buttons: [{
 3735        text: opts.okText || 'OK',
 3736        type: opts.okType || 'button-positive',
 3737        onTap: function() {
 3738          return true;
 3739        }
 3740      }]
 3741    }, opts || {}));
 3742  }
 3743
 3744  function showConfirm(opts) {
 3745    return showPopup(extend({
 3746      buttons: [{
 3747        text: opts.cancelText || 'Cancel',
 3748        type: opts.cancelType || 'button-default',
 3749        onTap: function() { return false; }
 3750      }, {
 3751        text: opts.okText || 'OK',
 3752        type: opts.okType || 'button-positive',
 3753        onTap: function() { return true; }
 3754      }]
 3755    }, opts || {}));
 3756  }
 3757
 3758  function showPrompt(opts) {
 3759    var scope = $rootScope.$new(true);
 3760    scope.data = {};
 3761    scope.data.fieldtype = opts.inputType ? opts.inputType : 'text';
 3762    scope.data.response = opts.defaultText ? opts.defaultText : '';
 3763    scope.data.placeholder = opts.inputPlaceholder ? opts.inputPlaceholder : '';
 3764    scope.data.maxlength = opts.maxLength ? parseInt(opts.maxLength) : '';
 3765    var text = '';
 3766    if (opts.template && /<[a-z][\s\S]*>/i.test(opts.template) === false) {
 3767      text = '<span>' + opts.template + '</span>';
 3768      delete opts.template;
 3769    }
 3770    return showPopup(extend({
 3771      template: text + '<input ng-model="data.response" '
 3772        + 'type="{{ data.fieldtype }}"'
 3773        + 'maxlength="{{ data.maxlength }}"'
 3774        + 'placeholder="{{ data.placeholder }}"'
 3775        + '>',
 3776      scope: scope,
 3777      buttons: [{
 3778        text: opts.cancelText || 'Cancel',
 3779        type: opts.cancelType || 'button-default',
 3780        onTap: function() {}
 3781      }, {
 3782        text: opts.okText || 'OK',
 3783        type: opts.okType || 'button-positive',
 3784        onTap: function() {
 3785          return scope.data.response || '';
 3786        }
 3787      }]
 3788    }, opts || {}));
 3789  }
 3790}]);
 3791
 3792/**
 3793 * @ngdoc service
 3794 * @name $ionicPosition
 3795 * @module ionic
 3796 * @description
 3797 * A set of utility methods that can be use to retrieve position of DOM elements.
 3798 * It is meant to be used where we need to absolute-position DOM elements in
 3799 * relation to other, existing elements (this is the case for tooltips, popovers, etc.).
 3800 *
 3801 * Adapted from [AngularUI Bootstrap](https://github.com/angular-ui/bootstrap/blob/master/src/position/position.js),
 3802 * ([license](https://github.com/angular-ui/bootstrap/blob/master/LICENSE))
 3803 */
 3804IonicModule
 3805.factory('$ionicPosition', ['$document', '$window', function($document, $window) {
 3806
 3807  function getStyle(el, cssprop) {
 3808    if (el.currentStyle) { //IE
 3809      return el.currentStyle[cssprop];
 3810    } else if ($window.getComputedStyle) {
 3811      return $window.getComputedStyle(el)[cssprop];
 3812    }
 3813    // finally try and get inline style
 3814    return el.style[cssprop];
 3815  }
 3816
 3817  /**
 3818   * Checks if a given element is statically positioned
 3819   * @param element - raw DOM element
 3820   */
 3821  function isStaticPositioned(element) {
 3822    return (getStyle(element, 'position') || 'static') === 'static';
 3823  }
 3824
 3825  /**
 3826   * returns the closest, non-statically positioned parentOffset of a given element
 3827   * @param element
 3828   */
 3829  var parentOffsetEl = function(element) {
 3830    var docDomEl = $document[0];
 3831    var offsetParent = element.offsetParent || docDomEl;
 3832    while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent)) {
 3833      offsetParent = offsetParent.offsetParent;
 3834    }
 3835    return offsetParent || docDomEl;
 3836  };
 3837
 3838  return {
 3839    /**
 3840     * @ngdoc method
 3841     * @name $ionicPosition#position
 3842     * @description Get the current coordinates of the element, relative to the offset parent.
 3843     * Read-only equivalent of [jQuery's position function](http://api.jquery.com/position/).
 3844     * @param {element} element The element to get the position of.
 3845     * @returns {object} Returns an object containing the properties top, left, width and height.
 3846     */
 3847    position: function(element) {
 3848      var elBCR = this.offset(element);
 3849      var offsetParentBCR = { top: 0, left: 0 };
 3850      var offsetParentEl = parentOffsetEl(element[0]);
 3851      if (offsetParentEl != $document[0]) {
 3852        offsetParentBCR = this.offset(jqLite(offsetParentEl));
 3853        offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
 3854        offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
 3855      }
 3856
 3857      var boundingClientRect = element[0].getBoundingClientRect();
 3858      return {
 3859        width: boundingClientRect.width || element.prop('offsetWidth'),
 3860        height: boundingClientRect.height || element.prop('offsetHeight'),
 3861        top: elBCR.top - offsetParentBCR.top,
 3862        left: elBCR.left - offsetParentBCR.left
 3863      };
 3864    },
 3865
 3866    /**
 3867     * @ngdoc method
 3868     * @name $ionicPosition#offset
 3869     * @description Get the current coordinates of the element, relative to the document.
 3870     * Read-only equivalent of [jQuery's offset function](http://api.jquery.com/offset/).
 3871     * @param {element} element The element to get the offset of.
 3872     * @returns {object} Returns an object containing the properties top, left, width and height.
 3873     */
 3874    offset: function(element) {
 3875      var boundingClientRect = element[0].getBoundingClientRect();
 3876      return {
 3877        width: boundingClientRect.width || element.prop('offsetWidth'),
 3878        height: boundingClientRect.height || element.prop('offsetHeight'),
 3879        top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
 3880        left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
 3881      };
 3882    }
 3883
 3884  };
 3885}]);
 3886
 3887
 3888/**
 3889 * @ngdoc service
 3890 * @name $ionicScrollDelegate
 3891 * @module ionic
 3892 * @description
 3893 * Delegate for controlling scrollViews (created by
 3894 * {@link ionic.directive:ionContent} and
 3895 * {@link ionic.directive:ionScroll} directives).
 3896 *
 3897 * Methods called directly on the $ionicScrollDelegate service will control all scroll
 3898 * views.  Use the {@link ionic.service:$ionicScrollDelegate#$getByHandle $getByHandle}
 3899 * method to control specific scrollViews.
 3900 *
 3901 * @usage
 3902 *
 3903 * ```html
 3904 * <body ng-controller="MainCtrl">
 3905 *   <ion-content>
 3906 *     <button ng-click="scrollTop()">Scroll to Top!</button>
 3907 *   </ion-content>
 3908 * </body>
 3909 * ```
 3910 * ```js
 3911 * function MainCtrl($scope, $ionicScrollDelegate) {
 3912 *   $scope.scrollTop = function() {
 3913 *     $ionicScrollDelegate.scrollTop();
 3914 *   };
 3915 * }
 3916 * ```
 3917 *
 3918 * Example of advanced usage, with two scroll areas using `delegate-handle`
 3919 * for fine control.
 3920 *
 3921 * ```html
 3922 * <body ng-controller="MainCtrl">
 3923 *   <ion-content delegate-handle="mainScroll">
 3924 *     <button ng-click="scrollMainToTop()">
 3925 *       Scroll content to top!
 3926 *     </button>
 3927 *     <ion-scroll delegate-handle="small" style="height: 100px;">
 3928 *       <button ng-click="scrollSmallToTop()">
 3929 *         Scroll small area to top!
 3930 *       </button>
 3931 *     </ion-scroll>
 3932 *   </ion-content>
 3933 * </body>
 3934 * ```
 3935 * ```js
 3936 * function MainCtrl($scope, $ionicScrollDelegate) {
 3937 *   $scope.scrollMainToTop = function() {
 3938 *     $ionicScrollDelegate.$getByHandle('mainScroll').scrollTop();
 3939 *   };
 3940 *   $scope.scrollSmallToTop = function() {
 3941 *     $ionicScrollDelegate.$getByHandle('small').scrollTop();
 3942 *   };
 3943 * }
 3944 * ```
 3945 */
 3946IonicModule
 3947.service('$ionicScrollDelegate', ionic.DelegateService([
 3948  /**
 3949   * @ngdoc method
 3950   * @name $ionicScrollDelegate#resize
 3951   * @description Tell the scrollView to recalculate the size of its container.
 3952   */
 3953  'resize',
 3954  /**
 3955   * @ngdoc method
 3956   * @name $ionicScrollDelegate#scrollTop
 3957   * @param {boolean=} shouldAnimate Whether the scroll should animate.
 3958   */
 3959  'scrollTop',
 3960  /**
 3961   * @ngdoc method
 3962   * @name $ionicScrollDelegate#scrollBottom
 3963   * @param {boolean=} shouldAnimate Whether the scroll should animate.
 3964   */
 3965  'scrollBottom',
 3966  /**
 3967   * @ngdoc method
 3968   * @name $ionicScrollDelegate#scrollTo
 3969   * @param {number} left The x-value to scroll to.
 3970   * @param {number} top The y-value to scroll to.
 3971   * @param {boolean=} shouldAnimate Whether the scroll should animate.
 3972   */
 3973  'scrollTo',
 3974  /**
 3975   * @ngdoc method
 3976   * @name $ionicScrollDelegate#scrollBy
 3977   * @param {number} left The x-offset to scroll by.
 3978   * @param {number} top The y-offset to scroll by.
 3979   * @param {boolean=} shouldAnimate Whether the scroll should animate.
 3980   */
 3981  'scrollBy',
 3982  /**
 3983   * @ngdoc method
 3984   * @name $ionicScrollDelegate#zoomTo
 3985   * @param {number} level Level to zoom to.
 3986   * @param {boolean=} animate Whether to animate the zoom.
 3987   * @param {number=} originLeft Zoom in at given left coordinate.
 3988   * @param {number=} originTop Zoom in at given top coordinate.
 3989   */
 3990  'zoomTo',
 3991  /**
 3992   * @ngdoc method
 3993   * @name $ionicScrollDelegate#zoomBy
 3994   * @param {number} factor The factor to zoom by.
 3995   * @param {boolean=} animate Whether to animate the zoom.
 3996   * @param {number=} originLeft Zoom in at given left coordinate.
 3997   * @param {number=} originTop Zoom in at given top coordinate.
 3998   */
 3999  'zoomBy',
 4000  /**
 4001   * @ngdoc method
 4002   * @name $ionicScrollDelegate#getScrollPosition
 4003   * @returns {object} The scroll position of this view, with the following properties:
 4004   *  - `{number}` `left` The distance the user has scrolled from the left (starts at 0).
 4005   *  - `{number}` `top` The distance the user has scrolled from the top (starts at 0).
 4006   *  - `{number}` `zoom` The current zoom level.
 4007   */
 4008  'getScrollPosition',
 4009  /**
 4010   * @ngdoc method
 4011   * @name $ionicScrollDelegate#anchorScroll
 4012   * @description Tell the scrollView to scroll to the element with an id
 4013   * matching window.location.hash.
 4014   *
 4015   * If no matching element is found, it will scroll to top.
 4016   *
 4017   * @param {boolean=} shouldAnimate Whether the scroll should animate.
 4018   */
 4019  'anchorScroll',
 4020  /**
 4021   * @ngdoc method
 4022   * @name $ionicScrollDelegate#freezeScroll
 4023   * @description Does not allow this scroll view to scroll either x or y.
 4024   * @param {boolean=} shouldFreeze Should this scroll view be prevented from scrolling or not.
 4025   * @returns {boolean} If the scroll view is being prevented from scrolling or not.
 4026   */
 4027  'freezeScroll',
 4028  /**
 4029   * @ngdoc method
 4030   * @name $ionicScrollDelegate#freezeAllScrolls
 4031   * @description Does not allow any of the app's scroll views to scroll either x or y.
 4032   * @param {boolean=} shouldFreeze Should all app scrolls be prevented from scrolling or not.
 4033   */
 4034  'freezeAllScrolls',
 4035  /**
 4036   * @ngdoc method
 4037   * @name $ionicScrollDelegate#getScrollView
 4038   * @returns {object} The scrollView associated with this delegate.
 4039   */
 4040  'getScrollView'
 4041  /**
 4042   * @ngdoc method
 4043   * @name $ionicScrollDelegate#$getByHandle
 4044   * @param {string} handle
 4045   * @returns `delegateInstance` A delegate instance that controls only the
 4046   * scrollViews with `delegate-handle` matching the given handle.
 4047   *
 4048   * Example: `$ionicScrollDelegate.$getByHandle('my-handle').scrollTop();`
 4049   */
 4050]));
 4051
 4052
 4053/**
 4054 * @ngdoc service
 4055 * @name $ionicSideMenuDelegate
 4056 * @module ionic
 4057 *
 4058 * @description
 4059 * Delegate for controlling the {@link ionic.directive:ionSideMenus} directive.
 4060 *
 4061 * Methods called directly on the $ionicSideMenuDelegate service will control all side
 4062 * menus.  Use the {@link ionic.service:$ionicSideMenuDelegate#$getByHandle $getByHandle}
 4063 * method to control specific ionSideMenus instances.
 4064 *
 4065 * @usage
 4066 *
 4067 * ```html
 4068 * <body ng-controller="MainCtrl">
 4069 *   <ion-side-menus>
 4070 *     <ion-side-menu-content>
 4071 *       Content!
 4072 *       <button ng-click="toggleLeftSideMenu()">
 4073 *         Toggle Left Side Menu
 4074 *       </button>
 4075 *     </ion-side-menu-content>
 4076 *     <ion-side-menu side="left">
 4077 *       Left Menu!
 4078 *     <ion-side-menu>
 4079 *   </ion-side-menus>
 4080 * </body>
 4081 * ```
 4082 * ```js
 4083 * function MainCtrl($scope, $ionicSideMenuDelegate) {
 4084 *   $scope.toggleLeftSideMenu = function() {
 4085 *     $ionicSideMenuDelegate.toggleLeft();
 4086 *   };
 4087 * }
 4088 * ```
 4089 */
 4090IonicModule
 4091.service('$ionicSideMenuDelegate', ionic.DelegateService([
 4092  /**
 4093   * @ngdoc method
 4094   * @name $ionicSideMenuDelegate#toggleLeft
 4095   * @description Toggle the left side menu (if it exists).
 4096   * @param {boolean=} isOpen Whether to open or close the menu.
 4097   * Default: Toggles the menu.
 4098   */
 4099  'toggleLeft',
 4100  /**
 4101   * @ngdoc method
 4102   * @name $ionicSideMenuDelegate#toggleRight
 4103   * @description Toggle the right side menu (if it exists).
 4104   * @param {boolean=} isOpen Whether to open or close the menu.
 4105   * Default: Toggles the menu.
 4106   */
 4107  'toggleRight',
 4108  /**
 4109   * @ngdoc method
 4110   * @name $ionicSideMenuDelegate#getOpenRatio
 4111   * @description Gets the ratio of open amount over menu width. For example, a
 4112   * menu of width 100 that is opened by 50 pixels is 50% opened, and would return
 4113   * a ratio of 0.5.
 4114   *
 4115   * @returns {float} 0 if nothing is open, between 0 and 1 if left menu is
 4116   * opened/opening, and between 0 and -1 if right menu is opened/opening.
 4117   */
 4118  'getOpenRatio',
 4119  /**
 4120   * @ngdoc method
 4121   * @name $ionicSideMenuDelegate#isOpen
 4122   * @returns {boolean} Whether either the left or right menu is currently opened.
 4123   */
 4124  'isOpen',
 4125  /**
 4126   * @ngdoc method
 4127   * @name $ionicSideMenuDelegate#isOpenLeft
 4128   * @returns {boolean} Whether the left menu is currently opened.
 4129   */
 4130  'isOpenLeft',
 4131  /**
 4132   * @ngdoc method
 4133   * @name $ionicSideMenuDelegate#isOpenRight
 4134   * @returns {boolean} Whether the right menu is currently opened.
 4135   */
 4136  'isOpenRight',
 4137  /**
 4138   * @ngdoc method
 4139   * @name $ionicSideMenuDelegate#canDragContent
 4140   * @param {boolean=} canDrag Set whether the content can or cannot be dragged to open
 4141   * side menus.
 4142   * @returns {boolean} Whether the content can be dragged to open side menus.
 4143   */
 4144  'canDragContent',
 4145  /**
 4146   * @ngdoc method
 4147   * @name $ionicSideMenuDelegate#edgeDragThreshold
 4148   * @param {boolean|number=} value Set whether the content drag can only start if it is below a certain threshold distance from the edge of the screen. Accepts three different values:
 4149   *  - If a non-zero number is given, that many pixels is used as the maximum allowed distance from the edge that starts dragging the side menu.
 4150   *  - If true is given, the default number of pixels (25) is used as the maximum allowed distance.
 4151   *  - If false or 0 is given, the edge drag threshold is disabled, and dragging from anywhere on the content is allowed.
 4152   * @returns {boolean} Whether the drag can start only from within the edge of screen threshold.
 4153   */
 4154  'edgeDragThreshold'
 4155  /**
 4156   * @ngdoc method
 4157   * @name $ionicSideMenuDelegate#$getByHandle
 4158   * @param {string} handle
 4159   * @returns `delegateInstance` A delegate instance that controls only the
 4160   * {@link ionic.directive:ionSideMenus} directives with `delegate-handle` matching
 4161   * the given handle.
 4162   *
 4163   * Example: `$ionicSideMenuDelegate.$getByHandle('my-handle').toggleLeft();`
 4164   */
 4165]));
 4166
 4167
 4168/**
 4169 * @ngdoc service
 4170 * @name $ionicSlideBoxDelegate
 4171 * @module ionic
 4172 * @description
 4173 * Delegate that controls the {@link ionic.directive:ionSlideBox} directive.
 4174 *
 4175 * Methods called directly on the $ionicSlideBoxDelegate service will control all slide boxes.  Use the {@link ionic.service:$ionicSlideBoxDelegate#$getByHandle $getByHandle}
 4176 * method to control specific slide box instances.
 4177 *
 4178 * @usage
 4179 *
 4180 * ```html
 4181 * <ion-view>
 4182 *   <ion-slide-box>
 4183 *     <ion-slide>
 4184 *       <div class="box blue">
 4185 *         <button ng-click="nextSlide()">Next slide!</button>
 4186 *       </div>
 4187 *     </ion-slide>
 4188 *     <ion-slide>
 4189 *       <div class="box red">
 4190 *         Slide 2!
 4191 *       </div>
 4192 *     </ion-slide>
 4193 *   </ion-slide-box>
 4194 * </ion-view>
 4195 * ```
 4196 * ```js
 4197 * function MyCtrl($scope, $ionicSlideBoxDelegate) {
 4198 *   $scope.nextSlide = function() {
 4199 *     $ionicSlideBoxDelegate.next();
 4200 *   }
 4201 * }
 4202 * ```
 4203 */
 4204IonicModule
 4205.service('$ionicSlideBoxDelegate', ionic.DelegateService([
 4206  /**
 4207   * @ngdoc method
 4208   * @name $ionicSlideBoxDelegate#update
 4209   * @description
 4210   * Update the slidebox (for example if using Angular with ng-repeat,
 4211   * resize it for the elements inside).
 4212   */
 4213  'update',
 4214  /**
 4215   * @ngdoc method
 4216   * @name $ionicSlideBoxDelegate#slide
 4217   * @param {number} to The index to slide to.
 4218   * @param {number=} speed The number of milliseconds the change should take.
 4219   */
 4220  'slide',
 4221  'select',
 4222  /**
 4223   * @ngdoc method
 4224   * @name $ionicSlideBoxDelegate#enableSlide
 4225   * @param {boolean=} shouldEnable Whether to enable sliding the slidebox.
 4226   * @returns {boolean} Whether sliding is enabled.
 4227   */
 4228  'enableSlide',
 4229  /**
 4230   * @ngdoc method
 4231   * @name $ionicSlideBoxDelegate#previous
 4232   * @param {number=} speed The number of milliseconds the change should take.
 4233   * @description Go to the previous slide. Wraps around if at the beginning.
 4234   */
 4235  'previous',
 4236  /**
 4237   * @ngdoc method
 4238   * @name $ionicSlideBoxDelegate#next
 4239   * @param {number=} speed The number of milliseconds the change should take.
 4240   * @description Go to the next slide. Wraps around if at the end.
 4241   */
 4242  'next',
 4243  /**
 4244   * @ngdoc method
 4245   * @name $ionicSlideBoxDelegate#stop
 4246   * @description Stop sliding. The slideBox will not move again until
 4247   * explicitly told to do so.
 4248   */
 4249  'stop',
 4250  'autoPlay',
 4251  /**
 4252   * @ngdoc method
 4253   * @name $ionicSlideBoxDelegate#start
 4254   * @description Start sliding again if the slideBox was stopped.
 4255   */
 4256  'start',
 4257  /**
 4258   * @ngdoc method
 4259   * @name $ionicSlideBoxDelegate#currentIndex
 4260   * @returns number The index of the current slide.
 4261   */
 4262  'currentIndex',
 4263  'selected',
 4264  /**
 4265   * @ngdoc method
 4266   * @name $ionicSlideBoxDelegate#slidesCount
 4267   * @returns number The number of slides there are currently.
 4268   */
 4269  'slidesCount',
 4270  'count',
 4271  'loop'
 4272  /**
 4273   * @ngdoc method
 4274   * @name $ionicSlideBoxDelegate#$getByHandle
 4275   * @param {string} handle
 4276   * @returns `delegateInstance` A delegate instance that controls only the
 4277   * {@link ionic.directive:ionSlideBox} directives with `delegate-handle` matching
 4278   * the given handle.
 4279   *
 4280   * Example: `$ionicSlideBoxDelegate.$getByHandle('my-handle').stop();`
 4281   */
 4282]));
 4283
 4284
 4285/**
 4286 * @ngdoc service
 4287 * @name $ionicTabsDelegate
 4288 * @module ionic
 4289 *
 4290 * @description
 4291 * Delegate for controlling the {@link ionic.directive:ionTabs} directive.
 4292 *
 4293 * Methods called directly on the $ionicTabsDelegate service will control all ionTabs
 4294 * directives. Use the {@link ionic.service:$ionicTabsDelegate#$getByHandle $getByHandle}
 4295 * method to control specific ionTabs instances.
 4296 *
 4297 * @usage
 4298 *
 4299 * ```html
 4300 * <body ng-controller="MyCtrl">
 4301 *   <ion-tabs>
 4302 *
 4303 *     <ion-tab title="Tab 1">
 4304 *       Hello tab 1!
 4305 *       <button ng-click="selectTabWithIndex(1)">Select tab 2!</button>
 4306 *     </ion-tab>
 4307 *     <ion-tab title="Tab 2">Hello tab 2!</ion-tab>
 4308 *
 4309 *   </ion-tabs>
 4310 * </body>
 4311 * ```
 4312 * ```js
 4313 * function MyCtrl($scope, $ionicTabsDelegate) {
 4314 *   $scope.selectTabWithIndex = function(index) {
 4315 *     $ionicTabsDelegate.select(index);
 4316 *   }
 4317 * }
 4318 * ```
 4319 */
 4320IonicModule
 4321.service('$ionicTabsDelegate', ionic.DelegateService([
 4322  /**
 4323   * @ngdoc method
 4324   * @name $ionicTabsDelegate#select
 4325   * @description Select the tab matching the given index.
 4326   *
 4327   * @param {number} index Index of the tab to select.
 4328   */
 4329  'select',
 4330  /**
 4331   * @ngdoc method
 4332   * @name $ionicTabsDelegate#selectedIndex
 4333   * @returns `number` The index of the selected tab, or -1.
 4334   */
 4335  'selectedIndex',
 4336  /**
 4337   * @ngdoc method
 4338   * @name $ionicTabsDelegate#showBar
 4339   * @description
 4340   * Set/get whether the {@link ionic.directive:ionTabs} is shown
 4341   * @param {boolean} show Whether to show the bar.
 4342   * @returns {boolean} Whether the bar is shown.
 4343   */
 4344  'showBar'
 4345  /**
 4346   * @ngdoc method
 4347   * @name $ionicTabsDelegate#$getByHandle
 4348   * @param {string} handle
 4349   * @returns `delegateInstance` A delegate instance that controls only the
 4350   * {@link ionic.directive:ionTabs} directives with `delegate-handle` matching
 4351   * the given handle.
 4352   *
 4353   * Example: `$ionicTabsDelegate.$getByHandle('my-handle').select(0);`
 4354   */
 4355]));
 4356
 4357// closure to keep things neat
 4358(function() {
 4359  var templatesToCache = [];
 4360
 4361/**
 4362 * @ngdoc service
 4363 * @name $ionicTemplateCache
 4364 * @module ionic
 4365 * @description A service that preemptively caches template files to eliminate transition flicker and boost performance.
 4366 * @usage
 4367 * State templates are cached automatically, but you can optionally cache other templates.
 4368 *
 4369 * ```js
 4370 * $ionicTemplateCache('myNgIncludeTemplate.html');
 4371 * ```
 4372 *
 4373 * Optionally disable all preemptive caching with the `$ionicConfigProvider` or individual states by setting `prefetchTemplate`
 4374 * in the `$state` definition
 4375 *
 4376 * ```js
 4377 *   angular.module('myApp', ['ionic'])
 4378 *   .config(function($stateProvider, $ionicConfigProvider) {
 4379 *
 4380 *     // disable preemptive template caching globally
 4381 *     $ionicConfigProvider.templates.prefetch(false);
 4382 *
 4383 *     // disable individual states
 4384 *     $stateProvider
 4385 *       .state('tabs', {
 4386 *         url: "/tab",
 4387 *         abstract: true,
 4388 *         prefetchTemplate: false,
 4389 *         templateUrl: "tabs-templates/tabs.html"
 4390 *       })
 4391 *       .state('tabs.home', {
 4392 *         url: "/home",
 4393 *         views: {
 4394 *           'home-tab': {
 4395 *             prefetchTemplate: false,
 4396 *             templateUrl: "tabs-templates/home.html",
 4397 *             controller: 'HomeTabCtrl'
 4398 *           }
 4399 *         }
 4400 *       });
 4401 *   });
 4402 * ```
 4403 */
 4404IonicModule
 4405.factory('$ionicTemplateCache', [
 4406'$http',
 4407'$templateCache',
 4408'$timeout',
 4409function($http, $templateCache, $timeout) {
 4410  var toCache = templatesToCache,
 4411      hasRun;
 4412
 4413  function $ionicTemplateCache(templates) {
 4414    if (typeof templates === 'undefined') {
 4415      return run();
 4416    }
 4417    if (isString(templates)) {
 4418      templates = [templates];
 4419    }
 4420    forEach(templates, function(template) {
 4421      toCache.push(template);
 4422    });
 4423    if (hasRun) {
 4424      run();
 4425    }
 4426  }
 4427
 4428  // run through methods - internal method
 4429  function run() {
 4430    var template;
 4431    $ionicTemplateCache._runCount++;
 4432
 4433    hasRun = true;
 4434    // ignore if race condition already zeroed out array
 4435    if (toCache.length === 0) return;
 4436
 4437    var i = 0;
 4438    while (i < 4 && (template = toCache.pop())) {
 4439      // note that inline templates are ignored by this request
 4440      if (isString(template)) $http.get(template, { cache: $templateCache });
 4441      i++;
 4442    }
 4443    // only preload 3 templates a second
 4444    if (toCache.length) {
 4445      $timeout(run, 1000);
 4446    }
 4447  }
 4448
 4449  // exposing for testing
 4450  $ionicTemplateCache._runCount = 0;
 4451  // default method
 4452  return $ionicTemplateCache;
 4453}])
 4454
 4455// Intercepts the $stateprovider.state() command to look for templateUrls that can be cached
 4456.config([
 4457'$stateProvider',
 4458'$ionicConfigProvider',
 4459function($stateProvider, $ionicConfigProvider) {
 4460  var stateProviderState = $stateProvider.state;
 4461  $stateProvider.state = function(stateName, definition) {
 4462    // don't even bother if it's disabled. note, another config may run after this, so it's not a catch-all
 4463    if (typeof definition === 'object') {
 4464      var enabled = definition.prefetchTemplate !== false && templatesToCache.length < $ionicConfigProvider.templates.maxPrefetch();
 4465      if (enabled && isString(definition.templateUrl)) templatesToCache.push(definition.templateUrl);
 4466      if (angular.isObject(definition.views)) {
 4467        for (var key in definition.views) {
 4468          enabled = definition.views[key].prefetchTemplate !== false && templatesToCache.length < $ionicConfigProvider.templates.maxPrefetch();
 4469          if (enabled && isString(definition.views[key].templateUrl)) templatesToCache.push(definition.views[key].templateUrl);
 4470        }
 4471      }
 4472    }
 4473    return stateProviderState.call($stateProvider, stateName, definition);
 4474  };
 4475}])
 4476
 4477// process the templateUrls collected by the $stateProvider, adding them to the cache
 4478.run(['$ionicTemplateCache', function($ionicTemplateCache) {
 4479  $ionicTemplateCache();
 4480}]);
 4481
 4482})();
 4483
 4484IonicModule
 4485.factory('$ionicTemplateLoader', [
 4486  '$compile',
 4487  '$controller',
 4488  '$http',
 4489  '$q',
 4490  '$rootScope',
 4491  '$templateCache',
 4492function($compile, $controller, $http, $q, $rootScope, $templateCache) {
 4493
 4494  return {
 4495    load: fetchTemplate,
 4496    compile: loadAndCompile
 4497  };
 4498
 4499  function fetchTemplate(url) {
 4500    return $http.get(url, {cache: $templateCache})
 4501    .then(function(response) {
 4502      return response.data && response.data.trim();
 4503    });
 4504  }
 4505
 4506  function loadAndCompile(options) {
 4507    options = extend({
 4508      template: '',
 4509      templateUrl: '',
 4510      scope: null,
 4511      controller: null,
 4512      locals: {},
 4513      appendTo: null
 4514    }, options || {});
 4515
 4516    var templatePromise = options.templateUrl ?
 4517      this.load(options.templateUrl) :
 4518      $q.when(options.template);
 4519
 4520    return templatePromise.then(function(template) {
 4521      var controller;
 4522      var scope = options.scope || $rootScope.$new();
 4523
 4524      //Incase template doesn't have just one root element, do this
 4525      var element = jqLite('<div>').html(template).contents();
 4526
 4527      if (options.controller) {
 4528        controller = $controller(
 4529          options.controller,
 4530          extend(options.locals, {
 4531            $scope: scope
 4532          })
 4533        );
 4534        element.children().data('$ngControllerController', controller);
 4535      }
 4536      if (options.appendTo) {
 4537        jqLite(options.appendTo).append(element);
 4538      }
 4539
 4540      $compile(element)(scope);
 4541
 4542      return {
 4543        element: element,
 4544        scope: scope
 4545      };
 4546    });
 4547  }
 4548
 4549}]);
 4550
 4551/**
 4552 * @private
 4553 * DEPRECATED, as of v1.0.0-beta14 -------
 4554 */
 4555IonicModule
 4556.factory('$ionicViewService', ['$ionicHistory', '$log', function($ionicHistory, $log) {
 4557
 4558  function warn(oldMethod, newMethod) {
 4559    $log.warn('$ionicViewService' + oldMethod + ' is deprecated, please use $ionicHistory' + newMethod + ' instead: http://ionicframework.com/docs/nightly/api/service/$ionicHistory/');
 4560  }
 4561
 4562  warn('', '');
 4563
 4564  var methodsMap = {
 4565    getCurrentView: 'currentView',
 4566    getBackView: 'backView',
 4567    getForwardView: 'forwardView',
 4568    getCurrentStateName: 'currentStateName',
 4569    nextViewOptions: 'nextViewOptions',
 4570    clearHistory: 'clearHistory'
 4571  };
 4572
 4573  forEach(methodsMap, function(newMethod, oldMethod) {
 4574    methodsMap[oldMethod] = function() {
 4575      warn('.' + oldMethod, '.' + newMethod);
 4576      return $ionicHistory[newMethod].apply(this, arguments);
 4577    };
 4578  });
 4579
 4580  return methodsMap;
 4581
 4582}]);
 4583
 4584/**
 4585 * @private
 4586 * TODO document
 4587 */
 4588
 4589IonicModule.factory('$ionicViewSwitcher', [
 4590  '$timeout',
 4591  '$document',
 4592  '$q',
 4593  '$ionicClickBlock',
 4594  '$ionicConfig',
 4595  '$ionicNavBarDelegate',
 4596function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDelegate) {
 4597
 4598  var TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
 4599  var DATA_NO_CACHE = '$noCache';
 4600  var DATA_DESTROY_ELE = '$destroyEle';
 4601  var DATA_ELE_IDENTIFIER = '$eleId';
 4602  var DATA_VIEW_ACCESSED = '$accessed';
 4603  var DATA_FALLBACK_TIMER = '$fallbackTimer';
 4604  var DATA_VIEW = '$viewData';
 4605  var NAV_VIEW_ATTR = 'nav-view';
 4606  var VIEW_STATUS_ACTIVE = 'active';
 4607  var VIEW_STATUS_CACHED = 'cached';
 4608  var VIEW_STATUS_STAGED = 'stage';
 4609
 4610  var transitionCounter = 0;
 4611  var nextTransition, nextDirection;
 4612  ionic.transition = ionic.transition || {};
 4613  ionic.transition.isActive = false;
 4614  var isActiveTimer;
 4615  var cachedAttr = ionic.DomUtil.cachedAttr;
 4616  var transitionPromises = [];
 4617  var defaultTimeout = 1100;
 4618
 4619  var ionicViewSwitcher = {
 4620
 4621    create: function(navViewCtrl, viewLocals, enteringView, leavingView, renderStart, renderEnd) {
 4622      // get a reference to an entering/leaving element if they exist
 4623      // loop through to see if the view is already in the navViewElement
 4624      var enteringEle, leavingEle;
 4625      var transitionId = ++transitionCounter;
 4626      var alreadyInDom;
 4627
 4628      var switcher = {
 4629
 4630        init: function(registerData, callback) {
 4631          ionicViewSwitcher.isTransitioning(true);
 4632
 4633          switcher.loadViewElements(registerData);
 4634
 4635          switcher.render(registerData, function() {
 4636            callback && callback();
 4637          });
 4638        },
 4639
 4640        loadViewElements: function(registerData) {
 4641          var x, l, viewEle;
 4642          var viewElements = navViewCtrl.getViewElements();
 4643          var enteringEleIdentifier = getViewElementIdentifier(viewLocals, enteringView);
 4644          var navViewActiveEleId = navViewCtrl.activeEleId();
 4645
 4646          for (x = 0, l = viewElements.length; x < l; x++) {
 4647            viewEle = viewElements.eq(x);
 4648
 4649            if (viewEle.data(DATA_ELE_IDENTIFIER) === enteringEleIdentifier) {
 4650              // we found an existing element in the DOM that should be entering the view
 4651              if (viewEle.data(DATA_NO_CACHE)) {
 4652                // the existing element should not be cached, don't use it
 4653                viewEle.data(DATA_ELE_IDENTIFIER, enteringEleIdentifier + ionic.Utils.nextUid());
 4654                viewEle.data(DATA_DESTROY_ELE, true);
 4655
 4656              } else {
 4657                enteringEle = viewEle;
 4658              }
 4659
 4660            } else if (isDefined(navViewActiveEleId) && viewEle.data(DATA_ELE_IDENTIFIER) === navViewActiveEleId) {
 4661              leavingEle = viewEle;
 4662            }
 4663
 4664            if (enteringEle && leavingEle) break;
 4665          }
 4666
 4667          alreadyInDom = !!enteringEle;
 4668
 4669          if (!alreadyInDom) {
 4670            // still no existing element to use
 4671            // create it using existing template/scope/locals
 4672            enteringEle = registerData.ele || ionicViewSwitcher.createViewEle(viewLocals);
 4673
 4674            // existing elements in the DOM are looked up by their state name and state id
 4675            enteringEle.data(DATA_ELE_IDENTIFIER, enteringEleIdentifier);
 4676          }
 4677
 4678          if (renderEnd) {
 4679            navViewCtrl.activeEleId(enteringEleIdentifier);
 4680          }
 4681
 4682          registerData.ele = null;
 4683        },
 4684
 4685        render: function(registerData, callback) {
 4686          if (alreadyInDom) {
 4687            // it was already found in the DOM, just reconnect the scope
 4688            ionic.Utils.reconnectScope(enteringEle.scope());
 4689
 4690          } else {
 4691            // the entering element is not already in the DOM
 4692            // set that the entering element should be "staged" and its
 4693            // styles of where this element will go before it hits the DOM
 4694            navViewAttr(enteringEle, VIEW_STATUS_STAGED);
 4695
 4696            var enteringData = getTransitionData(viewLocals, enteringEle, registerData.direction, enteringView);
 4697            var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none;
 4698            transitionFn(enteringEle, null, enteringData.direction, true).run(0);
 4699
 4700            enteringEle.data(DATA_VIEW, {
 4701              viewId: enteringData.viewId,
 4702              historyId: enteringData.historyId,
 4703              stateName: enteringData.stateName,
 4704              stateParams: enteringData.stateParams
 4705            });
 4706
 4707            // if the current state has cache:false
 4708            // or the element has cache-view="false" attribute
 4709            if (viewState(viewLocals).cache === false || viewState(viewLocals).cache === 'false' ||
 4710                enteringEle.attr('cache-view') == 'false' || $ionicConfig.views.maxCache() === 0) {
 4711              enteringEle.data(DATA_NO_CACHE, true);
 4712            }
 4713
 4714            // append the entering element to the DOM, create a new scope and run link
 4715            var viewScope = navViewCtrl.appendViewElement(enteringEle, viewLocals);
 4716
 4717            delete enteringData.direction;
 4718            delete enteringData.transition;
 4719            viewScope.$emit('$ionicView.loaded', enteringData);
 4720          }
 4721
 4722          // update that this view was just accessed
 4723          enteringEle.data(DATA_VIEW_ACCESSED, Date.now());
 4724
 4725          callback && callback();
 4726        },
 4727
 4728        transition: function(direction, enableBack, allowAnimate) {
 4729          var deferred;
 4730          var enteringData = getTransitionData(viewLocals, enteringEle, direction, enteringView);
 4731          var leavingData = extend(extend({}, enteringData), getViewData(leavingView));
 4732          enteringData.transitionId = leavingData.transitionId = transitionId;
 4733          enteringData.fromCache = !!alreadyInDom;
 4734          enteringData.enableBack = !!enableBack;
 4735          enteringData.renderStart = renderStart;
 4736          enteringData.renderEnd = renderEnd;
 4737
 4738          cachedAttr(enteringEle.parent(), 'nav-view-transition', enteringData.transition);
 4739          cachedAttr(enteringEle.parent(), 'nav-view-direction', enteringData.direction);
 4740
 4741          // cancel any previous transition complete fallbacks
 4742          $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
 4743
 4744          // get the transition ready and see if it'll animate
 4745          var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none;
 4746          var viewTransition = transitionFn(enteringEle, leavingEle, enteringData.direction,
 4747                                            enteringData.shouldAnimate && allowAnimate && renderEnd);
 4748
 4749          if (viewTransition.shouldAnimate) {
 4750            // attach transitionend events (and fallback timer)
 4751            enteringEle.on(TRANSITIONEND_EVENT, completeOnTransitionEnd);
 4752            enteringEle.data(DATA_FALLBACK_TIMER, $timeout(transitionComplete, defaultTimeout));
 4753            $ionicClickBlock.show(defaultTimeout);
 4754          }
 4755
 4756          if (renderStart) {
 4757            // notify the views "before" the transition starts
 4758            switcher.emit('before', enteringData, leavingData);
 4759
 4760            // stage entering element, opacity 0, no transition duration
 4761            navViewAttr(enteringEle, VIEW_STATUS_STAGED);
 4762
 4763            // render the elements in the correct location for their starting point
 4764            viewTransition.run(0);
 4765          }
 4766
 4767          if (renderEnd) {
 4768            // create a promise so we can keep track of when all transitions finish
 4769            // only required if this transition should complete
 4770            deferred = $q.defer();
 4771            transitionPromises.push(deferred.promise);
 4772          }
 4773
 4774          if (renderStart && renderEnd) {
 4775            // CSS "auto" transitioned, not manually transitioned
 4776            // wait a frame so the styles apply before auto transitioning
 4777            $timeout(function() {
 4778              ionic.requestAnimationFrame(onReflow);
 4779            });
 4780          } else if (!renderEnd) {
 4781            // just the start of a manual transition
 4782            // but it will not render the end of the transition
 4783            navViewAttr(enteringEle, 'entering');
 4784            navViewAttr(leavingEle, 'leaving');
 4785
 4786            // return the transition run method so each step can be ran manually
 4787            return {
 4788              run: viewTransition.run,
 4789              cancel: function(shouldAnimate) {
 4790                if (shouldAnimate) {
 4791                  enteringEle.on(TRANSITIONEND_EVENT, cancelOnTransitionEnd);
 4792                  enteringEle.data(DATA_FALLBACK_TIMER, $timeout(cancelTransition, defaultTimeout));
 4793                  $ionicClickBlock.show(defaultTimeout);
 4794                } else {
 4795                  cancelTransition();
 4796                }
 4797                viewTransition.shouldAnimate = shouldAnimate;
 4798                viewTransition.run(0);
 4799                viewTransition = null;
 4800              }
 4801            };
 4802
 4803          } else if (renderEnd) {
 4804            // just the end of a manual transition
 4805            // happens after the manual transition has completed
 4806            // and a full history change has happened
 4807            onReflow();
 4808          }
 4809
 4810
 4811          function onReflow() {
 4812            // remove that we're staging the entering element so it can auto transition
 4813            navViewAttr(enteringEle, viewTransition.shouldAnimate ? 'entering' : VIEW_STATUS_ACTIVE);
 4814            navViewAttr(leavingEle, viewTransition.shouldAnimate ? 'leaving' : VIEW_STATUS_CACHED);
 4815
 4816            // start the auto transition and let the CSS take over
 4817            viewTransition.run(1);
 4818
 4819            // trigger auto transitions on the associated nav bars
 4820            $ionicNavBarDelegate._instances.forEach(function(instance) {
 4821              instance.triggerTransitionStart(transitionId);
 4822            });
 4823
 4824            if (!viewTransition.shouldAnimate) {
 4825              // no animated auto transition
 4826              transitionComplete();
 4827            }
 4828          }
 4829
 4830          // Make sure that transitionend events bubbling up from children won't fire
 4831          // transitionComplete. Will only go forward if ev.target == the element listening.
 4832          function completeOnTransitionEnd(ev) {
 4833            if (ev.target !== this) return;
 4834            transitionComplete();
 4835          }
 4836          function transitionComplete() {
 4837            if (transitionComplete.x) return;
 4838            transitionComplete.x = true;
 4839
 4840            enteringEle.off(TRANSITIONEND_EVENT, completeOnTransitionEnd);
 4841            $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
 4842            leavingEle && $timeout.cancel(leavingEle.data(DATA_FALLBACK_TIMER));
 4843
 4844            // resolve that this one transition (there could be many w/ nested views)
 4845            deferred && deferred.resolve(navViewCtrl);
 4846
 4847            // the most recent transition added has completed and all the active
 4848            // transition promises should be added to the services array of promises
 4849            if (transitionId === transitionCounter) {
 4850              $q.all(transitionPromises).then(ionicViewSwitcher.transitionEnd);
 4851
 4852              // emit that the views have finished transitioning
 4853              // each parent nav-view will update which views are active and cached
 4854              switcher.emit('after', enteringData, leavingData);
 4855              switcher.cleanup(enteringData);
 4856            }
 4857
 4858            // tell the nav bars that the transition has ended
 4859            $ionicNavBarDelegate._instances.forEach(function(instance) {
 4860              instance.triggerTransitionEnd();
 4861            });
 4862
 4863
 4864            // remove any references that could cause memory issues
 4865            nextTransition = nextDirection = enteringView = leavingView = enteringEle = leavingEle = null;
 4866          }
 4867
 4868          // Make sure that transitionend events bubbling up from children won't fire
 4869          // transitionComplete. Will only go forward if ev.target == the element listening.
 4870          function cancelOnTransitionEnd(ev) {
 4871            if (ev.target !== this) return;
 4872            cancelTransition();
 4873          }
 4874          function cancelTransition() {
 4875            navViewAttr(enteringEle, VIEW_STATUS_CACHED);
 4876            navViewAttr(leavingEle, VIEW_STATUS_ACTIVE);
 4877            enteringEle.off(TRANSITIONEND_EVENT, cancelOnTransitionEnd);
 4878            $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
 4879            ionicViewSwitcher.transitionEnd([navViewCtrl]);
 4880          }
 4881
 4882        },
 4883
 4884      emit: function(step, enteringData, leavingData) {
 4885          var enteringScope = getScopeForElement(enteringEle, enteringData);
 4886          var leavingScope = getScopeForElement(leavingEle, leavingData);
 4887
 4888          var prefixesAreEqual;
 4889
 4890          if ( !enteringData.viewId || enteringData.abstractView ) {
 4891            // it's an abstract view, so treat it accordingly
 4892
 4893            // we only get access to the leaving scope once in the transition,
 4894            // so dispatch all events right away if it exists
 4895            if ( leavingScope ) {
 4896              leavingScope.$emit('$ionicView.beforeLeave', leavingData);
 4897              leavingScope.$emit('$ionicView.leave', leavingData);
 4898              leavingScope.$emit('$ionicView.afterLeave', leavingData);
 4899              leavingScope.$broadcast('$ionicParentView.beforeLeave', leavingData);
 4900              leavingScope.$broadcast('$ionicParentView.leave', leavingData);
 4901              leavingScope.$broadcast('$ionicParentView.afterLeave', leavingData);
 4902            }
 4903          }
 4904          else {
 4905            // it's a regular view, so do the normal process
 4906            if (step == 'after') {
 4907              if (enteringScope) {
 4908                enteringScope.$emit('$ionicView.enter', enteringData);
 4909                enteringScope.$broadcast('$ionicParentView.enter', enteringData);
 4910              }
 4911
 4912              if (leavingScope) {
 4913                leavingScope.$emit('$ionicView.leave', leavingData);
 4914                leavingScope.$broadcast('$ionicParentView.leave', leavingData);
 4915              }
 4916              else if (enteringScope && leavingData && leavingData.viewId && enteringData.stateName !== leavingData.stateName) {
 4917                // we only want to dispatch this when we are doing a single-tier
 4918                // state change such as changing a tab, so compare the state
 4919                // for the same state-prefix but different suffix
 4920                prefixesAreEqual = compareStatePrefixes(enteringData.stateName, leavingData.stateName);
 4921                if ( prefixesAreEqual ) {
 4922                  enteringScope.$emit('$ionicNavView.leave', leavingData);
 4923                }
 4924              }
 4925            }
 4926
 4927            if (enteringScope) {
 4928              enteringScope.$emit('$ionicView.' + step + 'Enter', enteringData);
 4929              enteringScope.$broadcast('$ionicParentView.' + step + 'Enter', enteringData);
 4930            }
 4931
 4932            if (leavingScope) {
 4933              leavingScope.$emit('$ionicView.' + step + 'Leave', leavingData);
 4934              leavingScope.$broadcast('$ionicParentView.' + step + 'Leave', leavingData);
 4935
 4936            } else if (enteringScope && leavingData && leavingData.viewId && enteringData.stateName !== leavingData.stateName) {
 4937              // we only want to dispatch this when we are doing a single-tier
 4938              // state change such as changing a tab, so compare the state
 4939              // for the same state-prefix but different suffix
 4940              prefixesAreEqual = compareStatePrefixes(enteringData.stateName, leavingData.stateName);
 4941              if ( prefixesAreEqual ) {
 4942                enteringScope.$emit('$ionicNavView.' + step + 'Leave', leavingData);
 4943              }
 4944            }
 4945          }
 4946        },
 4947
 4948        cleanup: function(transData) {
 4949          // check if any views should be removed
 4950          if (leavingEle && transData.direction == 'back' && !$ionicConfig.views.forwardCache()) {
 4951            // if they just navigated back we can destroy the forward view
 4952            // do not remove forward views if cacheForwardViews config is true
 4953            destroyViewEle(leavingEle);
 4954          }
 4955
 4956          var viewElements = navViewCtrl.getViewElements();
 4957          var viewElementsLength = viewElements.length;
 4958          var x, viewElement;
 4959          var removeOldestAccess = (viewElementsLength - 1) > $ionicConfig.views.maxCache();
 4960          var removableEle;
 4961          var oldestAccess = Date.now();
 4962
 4963          for (x = 0; x < viewElementsLength; x++) {
 4964            viewElement = viewElements.eq(x);
 4965
 4966            if (removeOldestAccess && viewElement.data(DATA_VIEW_ACCESSED) < oldestAccess) {
 4967              // remember what was the oldest element to be accessed so it can be destroyed
 4968              oldestAccess = viewElement.data(DATA_VIEW_ACCESSED);
 4969              removableEle = viewElements.eq(x);
 4970
 4971            } else if (viewElement.data(DATA_DESTROY_ELE) && navViewAttr(viewElement) != VIEW_STATUS_ACTIVE) {
 4972              destroyViewEle(viewElement);
 4973            }
 4974          }
 4975
 4976          destroyViewEle(removableEle);
 4977
 4978          if (enteringEle.data(DATA_NO_CACHE)) {
 4979            enteringEle.data(DATA_DESTROY_ELE, true);
 4980          }
 4981        },
 4982
 4983        enteringEle: function() { return enteringEle; },
 4984        leavingEle: function() { return leavingEle; }
 4985
 4986      };
 4987
 4988      return switcher;
 4989    },
 4990
 4991    transitionEnd: function(navViewCtrls) {
 4992      forEach(navViewCtrls, function(navViewCtrl) {
 4993        navViewCtrl.transitionEnd();
 4994      });
 4995
 4996      ionicViewSwitcher.isTransitioning(false);
 4997      $ionicClickBlock.hide();
 4998      transitionPromises = [];
 4999    },
 5000
 5001    nextTransition: function(val) {
 5002      nextTransition = val;
 5003    },
 5004
 5005    nextDirection: function(val) {
 5006      nextDirection = val;
 5007    },
 5008
 5009    isTransitioning: function(val) {
 5010      if (arguments.length) {
 5011        ionic.transition.isActive = !!val;
 5012        $timeout.cancel(isActiveTimer);
 5013        if (val) {
 5014          isActiveTimer = $timeout(function() {
 5015            ionicViewSwitcher.isTransitioning(false);
 5016          }, 999);
 5017        }
 5018      }
 5019      return ionic.transition.isActive;
 5020    },
 5021
 5022    createViewEle: function(viewLocals) {
 5023      var containerEle = $document[0].createElement('div');
 5024      if (viewLocals && viewLocals.$template) {
 5025        containerEle.innerHTML = viewLocals.$template;
 5026        if (containerEle.children.length === 1) {
 5027          containerEle.children[0].classList.add('pane');
 5028          if ( viewLocals.$$state && viewLocals.$$state.self && viewLocals.$$state.self['abstract'] ) {
 5029            angular.element(containerEle.children[0]).attr("abstract", "true");
 5030          }
 5031          else {
 5032            if ( viewLocals.$$state && viewLocals.$$state.self ) {
 5033              angular.element(containerEle.children[0]).attr("state", viewLocals.$$state.self.name);
 5034            }
 5035
 5036          }
 5037          return jqLite(containerEle.children[0]);
 5038        }
 5039      }
 5040      containerEle.className = "pane";
 5041      return jqLite(containerEle);
 5042    },
 5043
 5044    viewEleIsActive: function(viewEle, isActiveAttr) {
 5045      navViewAttr(viewEle, isActiveAttr ? VIEW_STATUS_ACTIVE : VIEW_STATUS_CACHED);
 5046    },
 5047
 5048    getTransitionData: getTransitionData,
 5049    navViewAttr: navViewAttr,
 5050    destroyViewEle: destroyViewEle
 5051
 5052  };
 5053
 5054  return ionicViewSwitcher;
 5055
 5056
 5057  function getViewElementIdentifier(locals, view) {
 5058    if (viewState(locals)['abstract']) return viewState(locals).name;
 5059    if (view) return view.stateId || view.viewId;
 5060    return ionic.Utils.nextUid();
 5061  }
 5062
 5063  function viewState(locals) {
 5064    return locals && locals.$$state && locals.$$state.self || {};
 5065  }
 5066
 5067  function getTransitionData(viewLocals, enteringEle, direction, view) {
 5068    // Priority
 5069    // 1) attribute directive on the button/link to this view
 5070    // 2) entering element's attribute
 5071    // 3) entering view's $state config property
 5072    // 4) view registration data
 5073    // 5) global config
 5074    // 6) fallback value
 5075
 5076    var state = viewState(viewLocals);
 5077    var viewTransition = nextTransition || cachedAttr(enteringEle, 'view-transition') || state.viewTransition || $ionicConfig.views.transition() || 'ios';
 5078    var navBarTransition = $ionicConfig.navBar.transition();
 5079    direction = nextDirection || cachedAttr(enteringEle, 'view-direction') || state.viewDirection || direction || 'none';
 5080
 5081    return extend(getViewData(view), {
 5082      transition: viewTransition,
 5083      navBarTransition: navBarTransition === 'view' ? viewTransition : navBarTransition,
 5084      direction: direction,
 5085      shouldAnimate: (viewTransition !== 'none' && direction !== 'none')
 5086    });
 5087  }
 5088
 5089  function getViewData(view) {
 5090    view = view || {};
 5091    return {
 5092      viewId: view.viewId,
 5093      historyId: view.historyId,
 5094      stateId: view.stateId,
 5095      stateName: view.stateName,
 5096      stateParams: view.stateParams
 5097    };
 5098  }
 5099
 5100  function navViewAttr(ele, value) {
 5101    if (arguments.length > 1) {
 5102      cachedAttr(ele, NAV_VIEW_ATTR, value);
 5103    } else {
 5104      return cachedAttr(ele, NAV_VIEW_ATTR);
 5105    }
 5106  }
 5107
 5108  function destroyViewEle(ele) {
 5109    // we found an element that should be removed
 5110    // destroy its scope, then remove the element
 5111    if (ele && ele.length) {
 5112      var viewScope = ele.scope();
 5113      if (viewScope) {
 5114        viewScope.$emit('$ionicView.unloaded', ele.data(DATA_VIEW));
 5115        viewScope.$destroy();
 5116      }
 5117      ele.remove();
 5118    }
 5119  }
 5120
 5121  function compareStatePrefixes(enteringStateName, exitingStateName) {
 5122    var enteringStateSuffixIndex = enteringStateName.lastIndexOf('.');
 5123    var exitingStateSuffixIndex = exitingStateName.lastIndexOf('.');
 5124
 5125    // if either of the prefixes are empty, just return false
 5126    if ( enteringStateSuffixIndex < 0 || exitingStateSuffixIndex < 0 ) {
 5127      return false;
 5128    }
 5129
 5130    var enteringPrefix = enteringStateName.substring(0, enteringStateSuffixIndex);
 5131    var exitingPrefix = exitingStateName.substring(0, exitingStateSuffixIndex);
 5132
 5133    return enteringPrefix === exitingPrefix;
 5134  }
 5135
 5136  function getScopeForElement(element, stateData) {
 5137    if ( !element ) {
 5138      return null;
 5139    }
 5140    // check if it's abstract
 5141    var attributeValue = angular.element(element).attr("abstract");
 5142    var stateValue = angular.element(element).attr("state");
 5143
 5144    if ( attributeValue !== "true" ) {
 5145      // it's not an abstract view, so make sure the element
 5146      // matches the state.  Due to abstract view weirdness,
 5147      // sometimes it doesn't. If it doesn't, don't dispatch events
 5148      // so leave the scope undefined
 5149      if ( stateValue === stateData.stateName ) {
 5150        return angular.element(element).scope();
 5151      }
 5152      return null;
 5153    }
 5154    else {
 5155      // it is an abstract element, so look for element with the "state" attributeValue
 5156      // set to the name of the stateData state
 5157      var elements = aggregateNavViewChildren(element);
 5158      for ( var i = 0; i < elements.length; i++ ) {
 5159          var state = angular.element(elements[i]).attr("state");
 5160          if ( state === stateData.stateName ) {
 5161            stateData.abstractView = true;
 5162            return angular.element(elements[i]).scope();
 5163          }
 5164      }
 5165      // we didn't find a match, so return null
 5166      return null;
 5167    }
 5168  }
 5169
 5170  function aggregateNavViewChildren(element) {
 5171    var aggregate = [];
 5172    var navViews = angular.element(element).find("ion-nav-view");
 5173    for ( var i = 0; i < navViews.length; i++ ) {
 5174      var children = angular.element(navViews[i]).children();
 5175      var childrenAggregated = [];
 5176      for ( var j = 0; j < children.length; j++ ) {
 5177        childrenAggregated = childrenAggregated.concat(children[j]);
 5178      }
 5179      aggregate = aggregate.concat(childrenAggregated);
 5180    }
 5181    return aggregate;
 5182  }
 5183
 5184}]);
 5185
 5186/**
 5187 * ==================  angular-ios9-uiwebview.patch.js v1.1.1 ==================
 5188 *
 5189 * This patch works around iOS9 UIWebView regression that causes infinite digest
 5190 * errors in Angular.
 5191 *
 5192 * The patch can be applied to Angular 1.2.0 – 1.4.5. Newer versions of Angular
 5193 * have the workaround baked in.
 5194 *
 5195 * To apply this patch load/bundle this file with your application and add a
 5196 * dependency on the "ngIOS9UIWebViewPatch" module to your main app module.
 5197 *
 5198 * For example:
 5199 *
 5200 * ```
 5201 * angular.module('myApp', ['ngRoute'])`
 5202 * ```
 5203 *
 5204 * becomes
 5205 *
 5206 * ```
 5207 * angular.module('myApp', ['ngRoute', 'ngIOS9UIWebViewPatch'])
 5208 * ```
 5209 *
 5210 *
 5211 * More info:
 5212 * - https://openradar.appspot.com/22186109
 5213 * - https://github.com/angular/angular.js/issues/12241
 5214 * - https://github.com/driftyco/ionic/issues/4082
 5215 *
 5216 *
 5217 * @license AngularJS
 5218 * (c) 2010-2015 Google, Inc. http://angularjs.org
 5219 * License: MIT
 5220 */
 5221
 5222angular.module('ngIOS9UIWebViewPatch', ['ng']).config(['$provide', function($provide) {
 5223  'use strict';
 5224
 5225  $provide.decorator('$browser', ['$delegate', '$window', function($delegate, $window) {
 5226
 5227    if (isIOS9UIWebView($window.navigator.userAgent)) {
 5228      return applyIOS9Shim($delegate);
 5229    }
 5230
 5231    return $delegate;
 5232
 5233    function isIOS9UIWebView(userAgent) {
 5234      return /(iPhone|iPad|iPod).* OS 9_\d/.test(userAgent) && !/Version\/9\./.test(userAgent);
 5235    }
 5236
 5237    function applyIOS9Shim(browser) {
 5238      var pendingLocationUrl = null;
 5239      var originalUrlFn = browser.url;
 5240
 5241      browser.url = function() {
 5242        if (arguments.length) {
 5243          pendingLocationUrl = arguments[0];
 5244          return originalUrlFn.apply(browser, arguments);
 5245        }
 5246
 5247        return pendingLocationUrl || originalUrlFn.apply(browser, arguments);
 5248      };
 5249
 5250      window.addEventListener('popstate', clearPendingLocationUrl, false);
 5251      window.addEventListener('hashchange', clearPendingLocationUrl, false);
 5252
 5253      function clearPendingLocationUrl() {
 5254        pendingLocationUrl = null;
 5255      }
 5256
 5257      return browser;
 5258    }
 5259  }]);
 5260}]);
 5261
 5262/**
 5263 * @private
 5264 * Parts of Ionic requires that $scope data is attached to the element.
 5265 * We do not want to disable adding $scope data to the $element when
 5266 * $compileProvider.debugInfoEnabled(false) is used.
 5267 */
 5268IonicModule.config(['$provide', function($provide) {
 5269  $provide.decorator('$compile', ['$delegate', function($compile) {
 5270     $compile.$$addScopeInfo = function $$addScopeInfo($element, scope, isolated, noTemplate) {
 5271       var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
 5272       $element.data(dataName, scope);
 5273     };
 5274     return $compile;
 5275  }]);
 5276}]);
 5277
 5278/**
 5279 * @private
 5280 */
 5281IonicModule.config([
 5282  '$provide',
 5283function($provide) {
 5284  function $LocationDecorator($location, $timeout) {
 5285
 5286    $location.__hash = $location.hash;
 5287    //Fix: when window.location.hash is set, the scrollable area
 5288    //found nearest to body's scrollTop is set to scroll to an element
 5289    //with that ID.
 5290    $location.hash = function(value) {
 5291      if (isDefined(value) && value.length > 0) {
 5292        $timeout(function() {
 5293          var scroll = document.querySelector('.scroll-content');
 5294          if (scroll) {
 5295            scroll.scrollTop = 0;
 5296          }
 5297        }, 0, false);
 5298      }
 5299      return $location.__hash(value);
 5300    };
 5301
 5302    return $location;
 5303  }
 5304
 5305  $provide.decorator('$location', ['$delegate', '$timeout', $LocationDecorator]);
 5306}]);
 5307
 5308IonicModule
 5309
 5310.controller('$ionicHeaderBar', [
 5311  '$scope',
 5312  '$element',
 5313  '$attrs',
 5314  '$q',
 5315  '$ionicConfig',
 5316  '$ionicHistory',
 5317function($scope, $element, $attrs, $q, $ionicConfig, $ionicHistory) {
 5318  var TITLE = 'title';
 5319  var BACK_TEXT = 'back-text';
 5320  var BACK_BUTTON = 'back-button';
 5321  var DEFAULT_TITLE = 'default-title';
 5322  var PREVIOUS_TITLE = 'previous-title';
 5323  var HIDE = 'hide';
 5324
 5325  var self = this;
 5326  var titleText = '';
 5327  var previousTitleText = '';
 5328  var titleLeft = 0;
 5329  var titleRight = 0;
 5330  var titleCss = '';
 5331  var isBackEnabled = false;
 5332  var isBackShown = true;
 5333  var isNavBackShown = true;
 5334  var isBackElementShown = false;
 5335  var titleTextWidth = 0;
 5336
 5337
 5338  self.beforeEnter = function(viewData) {
 5339    $scope.$broadcast('$ionicView.beforeEnter', viewData);
 5340  };
 5341
 5342
 5343  self.title = function(newTitleText) {
 5344    if (arguments.length && newTitleText !== titleText) {
 5345      getEle(TITLE).innerHTML = newTitleText;
 5346      titleText = newTitleText;
 5347      titleTextWidth = 0;
 5348    }
 5349    return titleText;
 5350  };
 5351
 5352
 5353  self.enableBack = function(shouldEnable, disableReset) {
 5354    // whether or not the back button show be visible, according
 5355    // to the navigation and history
 5356    if (arguments.length) {
 5357      isBackEnabled = shouldEnable;
 5358      if (!disableReset) self.updateBackButton();
 5359    }
 5360    return isBackEnabled;
 5361  };
 5362
 5363
 5364  self.showBack = function(shouldShow, disableReset) {
 5365    // different from enableBack() because this will always have the back
 5366    // visually hidden if false, even if the history says it should show
 5367    if (arguments.length) {
 5368      isBackShown = shouldShow;
 5369      if (!disableReset) self.updateBackButton();
 5370    }
 5371    return isBackShown;
 5372  };
 5373
 5374
 5375  self.showNavBack = function(shouldShow) {
 5376    // different from showBack() because this is for the entire nav bar's
 5377    // setting for all of it's child headers. For internal use.
 5378    isNavBackShown = shouldShow;
 5379    self.updateBackButton();
 5380  };
 5381
 5382
 5383  self.updateBackButton = function() {
 5384    var ele;
 5385    if ((isBackShown && isNavBackShown && isBackEnabled) !== isBackElementShown) {
 5386      isBackElementShown = isBackShown && isNavBackShown && isBackEnabled;
 5387      ele = getEle(BACK_BUTTON);
 5388      ele && ele.classList[ isBackElementShown ? 'remove' : 'add' ](HIDE);
 5389    }
 5390
 5391    if (isBackEnabled) {
 5392      ele = ele || getEle(BACK_BUTTON);
 5393      if (ele) {
 5394        if (self.backButtonIcon !== $ionicConfig.backButton.icon()) {
 5395          ele = getEle(BACK_BUTTON + ' .icon');
 5396          if (ele) {
 5397            self.backButtonIcon = $ionicConfig.backButton.icon();
 5398            ele.className = 'icon ' + self.backButtonIcon;
 5399          }
 5400        }
 5401
 5402        if (self.backButtonText !== $ionicConfig.backButton.text()) {
 5403          ele = getEle(BACK_BUTTON + ' .back-text');
 5404          if (ele) {
 5405            ele.textContent = self.backButtonText = $ionicConfig.backButton.text();
 5406          }
 5407        }
 5408      }
 5409    }
 5410  };
 5411
 5412
 5413  self.titleTextWidth = function() {
 5414    var element = getEle(TITLE);
 5415    if ( element ) {
 5416      // If the element has a nav-bar-title, use that instead
 5417      // to calculate the width of the title
 5418      var children = angular.element(element).children();
 5419      for ( var i = 0; i < children.length; i++ ) {
 5420        if ( angular.element(children[i]).hasClass('nav-bar-title') ) {
 5421          element = children[i];
 5422          break;
 5423        }
 5424      }
 5425    }
 5426    var bounds = ionic.DomUtil.getTextBounds(element);
 5427    titleTextWidth = Math.min(bounds && bounds.width || 30);
 5428    return titleTextWidth;
 5429  };
 5430
 5431
 5432  self.titleWidth = function() {
 5433    var titleWidth = self.titleTextWidth();
 5434    var offsetWidth = getEle(TITLE).offsetWidth;
 5435    if (offsetWidth < titleWidth) {
 5436      titleWidth = offsetWidth + (titleLeft - titleRight - 5);
 5437    }
 5438    return titleWidth;
 5439  };
 5440
 5441
 5442  self.titleTextX = function() {
 5443    return ($element[0].offsetWidth / 2) - (self.titleWidth() / 2);
 5444  };
 5445
 5446
 5447  self.titleLeftRight = function() {
 5448    return titleLeft - titleRight;
 5449  };
 5450
 5451
 5452  self.backButtonTextLeft = function() {
 5453    var offsetLeft = 0;
 5454    var ele = getEle(BACK_TEXT);
 5455    while (ele) {
 5456      offsetLeft += ele.offsetLeft;
 5457      ele = ele.parentElement;
 5458    }
 5459    return offsetLeft;
 5460  };
 5461
 5462
 5463  self.resetBackButton = function(viewData) {
 5464    if ($ionicConfig.backButton.previousTitleText()) {
 5465      var previousTitleEle = getEle(PREVIOUS_TITLE);
 5466      if (previousTitleEle) {
 5467        previousTitleEle.classList.remove(HIDE);
 5468
 5469        var view = (viewData && $ionicHistory.getViewById(viewData.viewId));
 5470        var newPreviousTitleText = $ionicHistory.backTitle(view);
 5471
 5472        if (newPreviousTitleText !== previousTitleText) {
 5473          previousTitleText = previousTitleEle.innerHTML = newPreviousTitleText;
 5474        }
 5475      }
 5476      var defaultTitleEle = getEle(DEFAULT_TITLE);
 5477      if (defaultTitleEle) {
 5478        defaultTitleEle.classList.remove(HIDE);
 5479      }
 5480    }
 5481  };
 5482
 5483
 5484  self.align = function(textAlign) {
 5485    var titleEle = getEle(TITLE);
 5486
 5487    textAlign = textAlign || $attrs.alignTitle || $ionicConfig.navBar.alignTitle();
 5488
 5489    var widths = self.calcWidths(textAlign, false);
 5490
 5491    if (isBackShown && previousTitleText && $ionicConfig.backButton.previousTitleText()) {
 5492      var previousTitleWidths = self.calcWidths(textAlign, true);
 5493
 5494      var availableTitleWidth = $element[0].offsetWidth - previousTitleWidths.titleLeft - previousTitleWidths.titleRight;
 5495
 5496      if (self.titleTextWidth() <= availableTitleWidth) {
 5497        widths = previousTitleWidths;
 5498      }
 5499    }
 5500
 5501    return self.updatePositions(titleEle, widths.titleLeft, widths.titleRight, widths.buttonsLeft, widths.buttonsRight, widths.css, widths.showPrevTitle);
 5502  };
 5503
 5504
 5505  self.calcWidths = function(textAlign, isPreviousTitle) {
 5506    var titleEle = getEle(TITLE);
 5507    var backBtnEle = getEle(BACK_BUTTON);
 5508    var x, y, z, b, c, d, childSize, bounds;
 5509    var childNodes = $element[0].childNodes;
 5510    var buttonsLeft = 0;
 5511    var buttonsRight = 0;
 5512    var isCountRightOfTitle;
 5513    var updateTitleLeft = 0;
 5514    var updateTitleRight = 0;
 5515    var updateCss = '';
 5516    var backButtonWidth = 0;
 5517
 5518    // Compute how wide the left children are
 5519    // Skip all titles (there may still be two titles, one leaving the dom)
 5520    // Once we encounter a titleEle, realize we are now counting the right-buttons, not left
 5521    for (x = 0; x < childNodes.length; x++) {
 5522      c = childNodes[x];
 5523
 5524      childSize = 0;
 5525      if (c.nodeType == 1) {
 5526        // element node
 5527        if (c === titleEle) {
 5528          isCountRightOfTitle = true;
 5529          continue;
 5530        }
 5531
 5532        if (c.classList.contains(HIDE)) {
 5533          continue;
 5534        }
 5535
 5536        if (isBackShown && c === backBtnEle) {
 5537
 5538          for (y = 0; y < c.childNodes.length; y++) {
 5539            b = c.childNodes[y];
 5540
 5541            if (b.nodeType == 1) {
 5542
 5543              if (b.classList.contains(BACK_TEXT)) {
 5544                for (z = 0; z < b.children.length; z++) {
 5545                  d = b.children[z];
 5546
 5547                  if (isPreviousTitle) {
 5548                    if (d.classList.contains(DEFAULT_TITLE)) continue;
 5549                    backButtonWidth += d.offsetWidth;
 5550                  } else {
 5551                    if (d.classList.contains(PREVIOUS_TITLE)) continue;
 5552                    backButtonWidth += d.offsetWidth;
 5553                  }
 5554                }
 5555
 5556              } else {
 5557                backButtonWidth += b.offsetWidth;
 5558              }
 5559
 5560            } else if (b.nodeType == 3 && b.nodeValue.trim()) {
 5561              bounds = ionic.DomUtil.getTextBounds(b);
 5562              backButtonWidth += bounds && bounds.width || 0;
 5563            }
 5564
 5565          }
 5566          childSize = backButtonWidth || c.offsetWidth;
 5567
 5568        } else {
 5569          // not the title, not the back button, not a hidden element
 5570          childSize = c.offsetWidth;
 5571        }
 5572
 5573      } else if (c.nodeType == 3 && c.nodeValue.trim()) {
 5574        // text node
 5575        bounds = ionic.DomUtil.getTextBounds(c);
 5576        childSize = bounds && bounds.width || 0;
 5577      }
 5578
 5579      if (isCountRightOfTitle) {
 5580        buttonsRight += childSize;
 5581      } else {
 5582        buttonsLeft += childSize;
 5583      }
 5584    }
 5585
 5586    // Size and align the header titleEle based on the sizes of the left and
 5587    // right children, and the desired alignment mode
 5588    if (textAlign == 'left') {
 5589      updateCss = 'title-left';
 5590      if (buttonsLeft) {
 5591        updateTitleLeft = buttonsLeft + 15;
 5592      }
 5593      if (buttonsRight) {
 5594        updateTitleRight = buttonsRight + 15;
 5595      }
 5596
 5597    } else if (textAlign == 'right') {
 5598      updateCss = 'title-right';
 5599      if (buttonsLeft) {
 5600        updateTitleLeft = buttonsLeft + 15;
 5601      }
 5602      if (buttonsRight) {
 5603        updateTitleRight = buttonsRight + 15;
 5604      }
 5605
 5606    } else {
 5607      // center the default
 5608      var margin = Math.max(buttonsLeft, buttonsRight) + 10;
 5609      if (margin > 10) {
 5610        updateTitleLeft = updateTitleRight = margin;
 5611      }
 5612    }
 5613
 5614    return {
 5615      backButtonWidth: backButtonWidth,
 5616      buttonsLeft: buttonsLeft,
 5617      buttonsRight: buttonsRight,
 5618      titleLeft: updateTitleLeft,
 5619      titleRight: updateTitleRight,
 5620      showPrevTitle: isPreviousTitle,
 5621      css: updateCss
 5622    };
 5623  };
 5624
 5625
 5626  self.updatePositions = function(titleEle, updateTitleLeft, updateTitleRight, buttonsLeft, buttonsRight, updateCss, showPreviousTitle) {
 5627    var deferred = $q.defer();
 5628
 5629    // only make DOM updates when there are actual changes
 5630    if (titleEle) {
 5631      if (updateTitleLeft !== titleLeft) {
 5632        titleEle.style.left = updateTitleLeft ? updateTitleLeft + 'px' : '';
 5633        titleLeft = updateTitleLeft;
 5634      }
 5635      if (updateTitleRight !== titleRight) {
 5636        titleEle.style.right = updateTitleRight ? updateTitleRight + 'px' : '';
 5637        titleRight = updateTitleRight;
 5638      }
 5639
 5640      if (updateCss !== titleCss) {
 5641        updateCss && titleEle.classList.add(updateCss);
 5642        titleCss && titleEle.classList.remove(titleCss);
 5643        titleCss = updateCss;
 5644      }
 5645    }
 5646
 5647    if ($ionicConfig.backButton.previousTitleText()) {
 5648      var prevTitle = getEle(PREVIOUS_TITLE);
 5649      var defaultTitle = getEle(DEFAULT_TITLE);
 5650
 5651      prevTitle && prevTitle.classList[ showPreviousTitle ? 'remove' : 'add'](HIDE);
 5652      defaultTitle && defaultTitle.classList[ showPreviousTitle ? 'add' : 'remove'](HIDE);
 5653    }
 5654
 5655    ionic.requestAnimationFrame(function() {
 5656      if (titleEle && titleEle.offsetWidth + 10 < titleEle.scrollWidth) {
 5657        var minRight = buttonsRight + 5;
 5658        var testRight = $element[0].offsetWidth - titleLeft - self.titleTextWidth() - 20;
 5659        updateTitleRight = testRight < minRight ? minRight : testRight;
 5660        if (updateTitleRight !== titleRight) {
 5661          titleEle.style.right = updateTitleRight + 'px';
 5662          titleRight = updateTitleRight;
 5663        }
 5664      }
 5665      deferred.resolve();
 5666    });
 5667
 5668    return deferred.promise;
 5669  };
 5670
 5671
 5672  self.setCss = function(elementClassname, css) {
 5673    ionic.DomUtil.cachedStyles(getEle(elementClassname), css);
 5674  };
 5675
 5676
 5677  var eleCache = {};
 5678  function getEle(className) {
 5679    if (!eleCache[className]) {
 5680      eleCache[className] = $element[0].querySelector('.' + className);
 5681    }
 5682    return eleCache[className];
 5683  }
 5684
 5685
 5686  $scope.$on('$destroy', function() {
 5687    for (var n in eleCache) eleCache[n] = null;
 5688  });
 5689
 5690}]);
 5691
 5692IonicModule
 5693.controller('$ionInfiniteScroll', [
 5694  '$scope',
 5695  '$attrs',
 5696  '$element',
 5697  '$timeout',
 5698function($scope, $attrs, $element, $timeout) {
 5699  var self = this;
 5700  self.isLoading = false;
 5701
 5702  $scope.icon = function() {
 5703    return isDefined($attrs.icon) ? $attrs.icon : 'ion-load-d';
 5704  };
 5705
 5706  $scope.spinner = function() {
 5707    return isDefined($attrs.spinner) ? $attrs.spinner : '';
 5708  };
 5709
 5710  $scope.$on('scroll.infiniteScrollComplete', function() {
 5711    finishInfiniteScroll();
 5712  });
 5713
 5714  $scope.$on('$destroy', function() {
 5715    if (self.scrollCtrl && self.scrollCtrl.$element) self.scrollCtrl.$element.off('scroll', self.checkBounds);
 5716    if (self.scrollEl && self.scrollEl.removeEventListener) {
 5717      self.scrollEl.removeEventListener('scroll', self.checkBounds);
 5718    }
 5719  });
 5720
 5721  // debounce checking infinite scroll events
 5722  self.checkBounds = ionic.Utils.throttle(checkInfiniteBounds, 300);
 5723
 5724  function onInfinite() {
 5725    ionic.requestAnimationFrame(function() {
 5726      $element[0].classList.add('active');
 5727    });
 5728    self.isLoading = true;
 5729    $scope.$parent && $scope.$parent.$apply($attrs.onInfinite || '');
 5730  }
 5731
 5732  function finishInfiniteScroll() {
 5733    ionic.requestAnimationFrame(function() {
 5734      $element[0].classList.remove('active');
 5735    });
 5736    $timeout(function() {
 5737      if (self.jsScrolling) self.scrollView.resize();
 5738      // only check bounds again immediately if the page isn't cached (scroll el has height)
 5739      if ((self.jsScrolling && self.scrollView.__container && self.scrollView.__container.offsetHeight > 0) ||
 5740      !self.jsScrolling) {
 5741        self.checkBounds();
 5742      }
 5743    }, 30, false);
 5744    self.isLoading = false;
 5745  }
 5746
 5747  // check if we've scrolled far enough to trigger an infinite scroll
 5748  function checkInfiniteBounds() {
 5749    if (self.isLoading) return;
 5750    var maxScroll = {};
 5751
 5752    if (self.jsScrolling) {
 5753      maxScroll = self.getJSMaxScroll();
 5754      var scrollValues = self.scrollView.getValues();
 5755      if ((maxScroll.left !== -1 && scrollValues.left >= maxScroll.left) ||
 5756        (maxScroll.top !== -1 && scrollValues.top >= maxScroll.top)) {
 5757        onInfinite();
 5758      }
 5759    } else {
 5760      maxScroll = self.getNativeMaxScroll();
 5761      if ((
 5762        maxScroll.left !== -1 &&
 5763        self.scrollEl.scrollLeft >= maxScroll.left - self.scrollEl.clientWidth
 5764        ) || (
 5765        maxScroll.top !== -1 &&
 5766        self.scrollEl.scrollTop >= maxScroll.top - self.scrollEl.clientHeight
 5767        )) {
 5768        onInfinite();
 5769      }
 5770    }
 5771  }
 5772
 5773  // determine the threshold at which we should fire an infinite scroll
 5774  // note: this gets processed every scroll event, can it be cached?
 5775  self.getJSMaxScroll = function() {
 5776    var maxValues = self.scrollView.getScrollMax();
 5777    return {
 5778      left: self.scrollView.options.scrollingX ?
 5779        calculateMaxValue(maxValues.left) :
 5780        -1,
 5781      top: self.scrollView.options.scrollingY ?
 5782        calculateMaxValue(maxValues.top) :
 5783        -1
 5784    };
 5785  };
 5786
 5787  self.getNativeMaxScroll = function() {
 5788    var maxValues = {
 5789      left: self.scrollEl.scrollWidth,
 5790      top: self.scrollEl.scrollHeight
 5791    };
 5792    var computedStyle = window.getComputedStyle(self.scrollEl) || {};
 5793    return {
 5794      left: maxValues.left &&
 5795        (computedStyle.overflowX === 'scroll' ||
 5796        computedStyle.overflowX === 'auto' ||
 5797        self.scrollEl.style['overflow-x'] === 'scroll') ?
 5798        calculateMaxValue(maxValues.left) : -1,
 5799      top: maxValues.top &&
 5800        (computedStyle.overflowY === 'scroll' ||
 5801        computedStyle.overflowY === 'auto' ||
 5802        self.scrollEl.style['overflow-y'] === 'scroll' ) ?
 5803        calculateMaxValue(maxValues.top) : -1
 5804    };
 5805  };
 5806
 5807  // determine pixel refresh distance based on % or value
 5808  function calculateMaxValue(maximum) {
 5809    var distance = ($attrs.distance || '2.5%').trim();
 5810    var isPercent = distance.indexOf('%') !== -1;
 5811    return isPercent ?
 5812    maximum * (1 - parseFloat(distance) / 100) :
 5813    maximum - parseFloat(distance);
 5814  }
 5815
 5816  //for testing
 5817  self.__finishInfiniteScroll = finishInfiniteScroll;
 5818
 5819}]);
 5820
 5821/**
 5822 * @ngdoc service
 5823 * @name $ionicListDelegate
 5824 * @module ionic
 5825 *
 5826 * @description
 5827 * Delegate for controlling the {@link ionic.directive:ionList} directive.
 5828 *
 5829 * Methods called directly on the $ionicListDelegate service will control all lists.
 5830 * Use the {@link ionic.service:$ionicListDelegate#$getByHandle $getByHandle}
 5831 * method to control specific ionList instances.
 5832 *
 5833 * @usage
 5834 * ```html
 5835 * {% raw %}
 5836 * <ion-content ng-controller="MyCtrl">
 5837 *   <button class="button" ng-click="showDeleteButtons()"></button>
 5838 *   <ion-list>
 5839 *     <ion-item ng-repeat="i in items">
 5840 *       Hello, {{i}}!
 5841 *       <ion-delete-button class="ion-minus-circled"></ion-delete-button>
 5842 *     </ion-item>
 5843 *   </ion-list>
 5844 * </ion-content>
 5845 * {% endraw %}
 5846 * ```
 5847
 5848 * ```js
 5849 * function MyCtrl($scope, $ionicListDelegate) {
 5850 *   $scope.showDeleteButtons = function() {
 5851 *     $ionicListDelegate.showDelete(true);
 5852 *   };
 5853 * }
 5854 * ```
 5855 */
 5856IonicModule.service('$ionicListDelegate', ionic.DelegateService([
 5857  /**
 5858   * @ngdoc method
 5859   * @name $ionicListDelegate#showReorder
 5860   * @param {boolean=} showReorder Set whether or not this list is showing its reorder buttons.
 5861   * @returns {boolean} Whether the reorder buttons are shown.
 5862   */
 5863  'showReorder',
 5864  /**
 5865   * @ngdoc method
 5866   * @name $ionicListDelegate#showDelete
 5867   * @param {boolean=} showDelete Set whether or not this list is showing its delete buttons.
 5868   * @returns {boolean} Whether the delete buttons are shown.
 5869   */
 5870  'showDelete',
 5871  /**
 5872   * @ngdoc method
 5873   * @name $ionicListDelegate#canSwipeItems
 5874   * @param {boolean=} canSwipeItems Set whether or not this list is able to swipe to show
 5875   * option buttons.
 5876   * @returns {boolean} Whether the list is able to swipe to show option buttons.
 5877   */
 5878  'canSwipeItems',
 5879  /**
 5880   * @ngdoc method
 5881   * @name $ionicListDelegate#closeOptionButtons
 5882   * @description Closes any option buttons on the list that are swiped open.
 5883   */
 5884  'closeOptionButtons'
 5885  /**
 5886   * @ngdoc method
 5887   * @name $ionicListDelegate#$getByHandle
 5888   * @param {string} handle
 5889   * @returns `delegateInstance` A delegate instance that controls only the
 5890   * {@link ionic.directive:ionList} directives with `delegate-handle` matching
 5891   * the given handle.
 5892   *
 5893   * Example: `$ionicListDelegate.$getByHandle('my-handle').showReorder(true);`
 5894   */
 5895]))
 5896
 5897.controller('$ionicList', [
 5898  '$scope',
 5899  '$attrs',
 5900  '$ionicListDelegate',
 5901  '$ionicHistory',
 5902function($scope, $attrs, $ionicListDelegate, $ionicHistory) {
 5903  var self = this;
 5904  var isSwipeable = true;
 5905  var isReorderShown = false;
 5906  var isDeleteShown = false;
 5907
 5908  var deregisterInstance = $ionicListDelegate._registerInstance(
 5909    self, $attrs.delegateHandle, function() {
 5910      return $ionicHistory.isActiveScope($scope);
 5911    }
 5912  );
 5913  $scope.$on('$destroy', deregisterInstance);
 5914
 5915  self.showReorder = function(show) {
 5916    if (arguments.length) {
 5917      isReorderShown = !!show;
 5918    }
 5919    return isReorderShown;
 5920  };
 5921
 5922  self.showDelete = function(show) {
 5923    if (arguments.length) {
 5924      isDeleteShown = !!show;
 5925    }
 5926    return isDeleteShown;
 5927  };
 5928
 5929  self.canSwipeItems = function(can) {
 5930    if (arguments.length) {
 5931      isSwipeable = !!can;
 5932    }
 5933    return isSwipeable;
 5934  };
 5935
 5936  self.closeOptionButtons = function() {
 5937    self.listView && self.listView.clearDragEffects();
 5938  };
 5939}]);
 5940
 5941IonicModule
 5942
 5943.controller('$ionicNavBar', [
 5944  '$scope',
 5945  '$element',
 5946  '$attrs',
 5947  '$compile',
 5948  '$timeout',
 5949  '$ionicNavBarDelegate',
 5950  '$ionicConfig',
 5951  '$ionicHistory',
 5952function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $ionicConfig, $ionicHistory) {
 5953
 5954  var CSS_HIDE = 'hide';
 5955  var DATA_NAV_BAR_CTRL = '$ionNavBarController';
 5956  var PRIMARY_BUTTONS = 'primaryButtons';
 5957  var SECONDARY_BUTTONS = 'secondaryButtons';
 5958  var BACK_BUTTON = 'backButton';
 5959  var ITEM_TYPES = 'primaryButtons secondaryButtons leftButtons rightButtons title'.split(' ');
 5960
 5961  var self = this;
 5962  var headerBars = [];
 5963  var navElementHtml = {};
 5964  var isVisible = true;
 5965  var queuedTransitionStart, queuedTransitionEnd, latestTransitionId;
 5966
 5967  $element.parent().data(DATA_NAV_BAR_CTRL, self);
 5968
 5969  var delegateHandle = $attrs.delegateHandle || 'navBar' + ionic.Utils.nextUid();
 5970
 5971  var deregisterInstance = $ionicNavBarDelegate._registerInstance(self, delegateHandle);
 5972
 5973
 5974  self.init = function() {
 5975    $element.addClass('nav-bar-container');
 5976    ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', $ionicConfig.views.transition());
 5977
 5978    // create two nav bar blocks which will trade out which one is shown
 5979    self.createHeaderBar(false);
 5980    self.createHeaderBar(true);
 5981
 5982    $scope.$emit('ionNavBar.init', delegateHandle);
 5983  };
 5984
 5985
 5986  self.createHeaderBar = function(isActive) {
 5987    var containerEle = jqLite('<div class="nav-bar-block">');
 5988    ionic.DomUtil.cachedAttr(containerEle, 'nav-bar', isActive ? 'active' : 'cached');
 5989
 5990    var alignTitle = $attrs.alignTitle || $ionicConfig.navBar.alignTitle();
 5991    var headerBarEle = jqLite('<ion-header-bar>').addClass($attrs['class']).attr('align-title', alignTitle);
 5992    if (isDefined($attrs.noTapScroll)) headerBarEle.attr('no-tap-scroll', $attrs.noTapScroll);
 5993    var titleEle = jqLite('<div class="title title-' + alignTitle + '">');
 5994    var navEle = {};
 5995    var lastViewItemEle = {};
 5996    var leftButtonsEle, rightButtonsEle;
 5997
 5998    navEle[BACK_BUTTON] = createNavElement(BACK_BUTTON);
 5999    navEle[BACK_BUTTON] && headerBarEle.append(navEle[BACK_BUTTON]);
 6000
 6001    // append title in the header, this is the rock to where buttons append
 6002    headerBarEle.append(titleEle);
 6003
 6004    forEach(ITEM_TYPES, function(itemType) {
 6005      // create default button elements
 6006      navEle[itemType] = createNavElement(itemType);
 6007      // append and position buttons
 6008      positionItem(navEle[itemType], itemType);
 6009    });
 6010
 6011    // add header-item to the root children
 6012    for (var x = 0; x < headerBarEle[0].children.length; x++) {
 6013      headerBarEle[0].children[x].classList.add('header-item');
 6014    }
 6015
 6016    // compile header and append to the DOM
 6017    containerEle.append(headerBarEle);
 6018    $element.append($compile(containerEle)($scope.$new()));
 6019
 6020    var headerBarCtrl = headerBarEle.data('$ionHeaderBarController');
 6021    headerBarCtrl.backButtonIcon = $ionicConfig.backButton.icon();
 6022    headerBarCtrl.backButtonText = $ionicConfig.backButton.text();
 6023
 6024    var headerBarInstance = {
 6025      isActive: isActive,
 6026      title: function(newTitleText) {
 6027        headerBarCtrl.title(newTitleText);
 6028      },
 6029      setItem: function(navBarItemEle, itemType) {
 6030        // first make sure any exiting nav bar item has been removed
 6031        headerBarInstance.removeItem(itemType);
 6032
 6033        if (navBarItemEle) {
 6034          if (itemType === 'title') {
 6035            // clear out the text based title
 6036            headerBarInstance.title("");
 6037          }
 6038
 6039          // there's a custom nav bar item
 6040          positionItem(navBarItemEle, itemType);
 6041
 6042          if (navEle[itemType]) {
 6043            // make sure the default on this itemType is hidden
 6044            navEle[itemType].addClass(CSS_HIDE);
 6045          }
 6046          lastViewItemEle[itemType] = navBarItemEle;
 6047
 6048        } else if (navEle[itemType]) {
 6049          // there's a default button for this side and no view button
 6050          navEle[itemType].removeClass(CSS_HIDE);
 6051        }
 6052      },
 6053      removeItem: function(itemType) {
 6054        if (lastViewItemEle[itemType]) {
 6055          lastViewItemEle[itemType].scope().$destroy();
 6056          lastViewItemEle[itemType].remove();
 6057          lastViewItemEle[itemType] = null;
 6058        }
 6059      },
 6060      containerEle: function() {
 6061        return containerEle;
 6062      },
 6063      headerBarEle: function() {
 6064        return headerBarEle;
 6065      },
 6066      afterLeave: function() {
 6067        forEach(ITEM_TYPES, function(itemType) {
 6068          headerBarInstance.removeItem(itemType);
 6069        });
 6070        headerBarCtrl.resetBackButton();
 6071      },
 6072      controller: function() {
 6073        return headerBarCtrl;
 6074      },
 6075      destroy: function() {
 6076        forEach(ITEM_TYPES, function(itemType) {
 6077          headerBarInstance.removeItem(itemType);
 6078        });
 6079        containerEle.scope().$destroy();
 6080        for (var n in navEle) {
 6081          if (navEle[n]) {
 6082            navEle[n].removeData();
 6083            navEle[n] = null;
 6084          }
 6085        }
 6086        leftButtonsEle && leftButtonsEle.removeData();
 6087        rightButtonsEle && rightButtonsEle.removeData();
 6088        titleEle.removeData();
 6089        headerBarEle.removeData();
 6090        containerEle.remove();
 6091        containerEle = headerBarEle = titleEle = leftButtonsEle = rightButtonsEle = null;
 6092      }
 6093    };
 6094
 6095    function positionItem(ele, itemType) {
 6096      if (!ele) return;
 6097
 6098      if (itemType === 'title') {
 6099        // title element
 6100        titleEle.append(ele);
 6101
 6102      } else if (itemType == 'rightButtons' ||
 6103                (itemType == SECONDARY_BUTTONS && $ionicConfig.navBar.positionSecondaryButtons() != 'left') ||
 6104                (itemType == PRIMARY_BUTTONS && $ionicConfig.navBar.positionPrimaryButtons() == 'right')) {
 6105        // right side
 6106        if (!rightButtonsEle) {
 6107          rightButtonsEle = jqLite('<div class="buttons buttons-right">');
 6108          headerBarEle.append(rightButtonsEle);
 6109        }
 6110        if (itemType == SECONDARY_BUTTONS) {
 6111          rightButtonsEle.append(ele);
 6112        } else {
 6113          rightButtonsEle.prepend(ele);
 6114        }
 6115
 6116      } else {
 6117        // left side
 6118        if (!leftButtonsEle) {
 6119          leftButtonsEle = jqLite('<div class="buttons buttons-left">');
 6120          if (navEle[BACK_BUTTON]) {
 6121            navEle[BACK_BUTTON].after(leftButtonsEle);
 6122          } else {
 6123            headerBarEle.prepend(leftButtonsEle);
 6124          }
 6125        }
 6126        if (itemType == SECONDARY_BUTTONS) {
 6127          leftButtonsEle.append(ele);
 6128        } else {
 6129          leftButtonsEle.prepend(ele);
 6130        }
 6131      }
 6132
 6133    }
 6134
 6135    headerBars.push(headerBarInstance);
 6136
 6137    return headerBarInstance;
 6138  };
 6139
 6140
 6141  self.navElement = function(type, html) {
 6142    if (isDefined(html)) {
 6143      navElementHtml[type] = html;
 6144    }
 6145    return navElementHtml[type];
 6146  };
 6147
 6148
 6149  self.update = function(viewData) {
 6150    var showNavBar = !viewData.hasHeaderBar && viewData.showNavBar;
 6151    viewData.transition = $ionicConfig.views.transition();
 6152
 6153    if (!showNavBar) {
 6154      viewData.direction = 'none';
 6155    }
 6156
 6157    self.enable(showNavBar);
 6158    var enteringHeaderBar = self.isInitialized ? getOffScreenHeaderBar() : getOnScreenHeaderBar();
 6159    var leavingHeaderBar = self.isInitialized ? getOnScreenHeaderBar() : null;
 6160    var enteringHeaderCtrl = enteringHeaderBar.controller();
 6161
 6162    // update if the entering header should show the back button or not
 6163    enteringHeaderCtrl.enableBack(viewData.enableBack, true);
 6164    enteringHeaderCtrl.showBack(viewData.showBack, true);
 6165    enteringHeaderCtrl.updateBackButton();
 6166
 6167    // update the entering header bar's title
 6168    self.title(viewData.title, enteringHeaderBar);
 6169
 6170    self.showBar(showNavBar);
 6171
 6172    // update the nav bar items, depending if the view has their own or not
 6173    if (viewData.navBarItems) {
 6174      forEach(ITEM_TYPES, function(itemType) {
 6175        enteringHeaderBar.setItem(viewData.navBarItems[itemType], itemType);
 6176      });
 6177    }
 6178
 6179    // begin transition of entering and leaving header bars
 6180    self.transition(enteringHeaderBar, leavingHeaderBar, viewData);
 6181
 6182    self.isInitialized = true;
 6183    navSwipeAttr('');
 6184  };
 6185
 6186
 6187  self.transition = function(enteringHeaderBar, leavingHeaderBar, viewData) {
 6188    var enteringHeaderBarCtrl = enteringHeaderBar.controller();
 6189    var transitionFn = $ionicConfig.transitions.navBar[viewData.navBarTransition] || $ionicConfig.transitions.navBar.none;
 6190    var transitionId = viewData.transitionId;
 6191
 6192    enteringHeaderBarCtrl.beforeEnter(viewData);
 6193
 6194    var navBarTransition = transitionFn(enteringHeaderBar, leavingHeaderBar, viewData.direction, viewData.shouldAnimate && self.isInitialized);
 6195
 6196    ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', viewData.navBarTransition);
 6197    ionic.DomUtil.cachedAttr($element, 'nav-bar-direction', viewData.direction);
 6198
 6199    if (navBarTransition.shouldAnimate && viewData.renderEnd) {
 6200      navBarAttr(enteringHeaderBar, 'stage');
 6201    } else {
 6202      navBarAttr(enteringHeaderBar, 'entering');
 6203      navBarAttr(leavingHeaderBar, 'leaving');
 6204    }
 6205
 6206    enteringHeaderBarCtrl.resetBackButton(viewData);
 6207
 6208    navBarTransition.run(0);
 6209
 6210    self.activeTransition = {
 6211      run: function(step) {
 6212        navBarTransition.shouldAnimate = false;
 6213        navBarTransition.direction = 'back';
 6214        navBarTransition.run(step);
 6215      },
 6216      cancel: function(shouldAnimate, speed, cancelData) {
 6217        navSwipeAttr(speed);
 6218        navBarAttr(leavingHeaderBar, 'active');
 6219        navBarAttr(enteringHeaderBar, 'cached');
 6220        navBarTransition.shouldAnimate = shouldAnimate;
 6221        navBarTransition.run(0);
 6222        self.activeTransition = navBarTransition = null;
 6223
 6224        var runApply;
 6225        if (cancelData.showBar !== self.showBar()) {
 6226          self.showBar(cancelData.showBar);
 6227        }
 6228        if (cancelData.showBackButton !== self.showBackButton()) {
 6229          self.showBackButton(cancelData.showBackButton);
 6230        }
 6231        if (runApply) {
 6232          $scope.$apply();
 6233        }
 6234      },
 6235      complete: function(shouldAnimate, speed) {
 6236        navSwipeAttr(speed);
 6237        navBarTransition.shouldAnimate = shouldAnimate;
 6238        navBarTransition.run(1);
 6239        queuedTransitionEnd = transitionEnd;
 6240      }
 6241    };
 6242
 6243    $timeout(enteringHeaderBarCtrl.align, 16);
 6244
 6245    queuedTransitionStart = function() {
 6246      if (latestTransitionId !== transitionId) return;
 6247
 6248      navBarAttr(enteringHeaderBar, 'entering');
 6249      navBarAttr(leavingHeaderBar, 'leaving');
 6250
 6251      navBarTransition.run(1);
 6252
 6253      queuedTransitionEnd = function() {
 6254        if (latestTransitionId == transitionId || !navBarTransition.shouldAnimate) {
 6255          transitionEnd();
 6256        }
 6257      };
 6258
 6259      queuedTransitionStart = null;
 6260    };
 6261
 6262    function transitionEnd() {
 6263      for (var x = 0; x < headerBars.length; x++) {
 6264        headerBars[x].isActive = false;
 6265      }
 6266      enteringHeaderBar.isActive = true;
 6267
 6268      navBarAttr(enteringHeaderBar, 'active');
 6269      navBarAttr(leavingHeaderBar, 'cached');
 6270
 6271      self.activeTransition = navBarTransition = queuedTransitionEnd = null;
 6272    }
 6273
 6274    queuedTransitionStart();
 6275  };
 6276
 6277
 6278  self.triggerTransitionStart = function(triggerTransitionId) {
 6279    latestTransitionId = triggerTransitionId;
 6280    queuedTransitionStart && queuedTransitionStart();
 6281  };
 6282
 6283
 6284  self.triggerTransitionEnd = function() {
 6285    queuedTransitionEnd && queuedTransitionEnd();
 6286  };
 6287
 6288
 6289  self.showBar = function(shouldShow) {
 6290    if (arguments.length) {
 6291      self.visibleBar(shouldShow);
 6292      $scope.$parent.$hasHeader = !!shouldShow;
 6293    }
 6294    return !!$scope.$parent.$hasHeader;
 6295  };
 6296
 6297
 6298  self.visibleBar = function(shouldShow) {
 6299    if (shouldShow && !isVisible) {
 6300      $element.removeClass(CSS_HIDE);
 6301      self.align();
 6302    } else if (!shouldShow && isVisible) {
 6303      $element.addClass(CSS_HIDE);
 6304    }
 6305    isVisible = shouldShow;
 6306  };
 6307
 6308
 6309  self.enable = function(val) {
 6310    // set primary to show first
 6311    self.visibleBar(val);
 6312
 6313    // set non primary to hide second
 6314    for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {
 6315      if ($ionicNavBarDelegate._instances[x] !== self) $ionicNavBarDelegate._instances[x].visibleBar(false);
 6316    }
 6317  };
 6318
 6319
 6320  /**
 6321   * @ngdoc method
 6322   * @name $ionicNavBar#showBackButton
 6323   * @description Show/hide the nav bar back button when there is a
 6324   * back view. If the back button is not possible, for example, the
 6325   * first view in the stack, then this will not force the back button
 6326   * to show.
 6327   */
 6328  self.showBackButton = function(shouldShow) {
 6329    if (arguments.length) {
 6330      for (var x = 0; x < headerBars.length; x++) {
 6331        headerBars[x].controller().showNavBack(!!shouldShow);
 6332      }
 6333      $scope.$isBackButtonShown = !!shouldShow;
 6334    }
 6335    return $scope.$isBackButtonShown;
 6336  };
 6337
 6338
 6339  /**
 6340   * @ngdoc method
 6341   * @name $ionicNavBar#showActiveBackButton
 6342   * @description Show/hide only the active header bar's back button.
 6343   */
 6344  self.showActiveBackButton = function(shouldShow) {
 6345    var headerBar = getOnScreenHeaderBar();
 6346    if (headerBar) {
 6347      if (arguments.length) {
 6348        return headerBar.controller().showBack(shouldShow);
 6349      }
 6350      return headerBar.controller().showBack();
 6351    }
 6352  };
 6353
 6354
 6355  self.title = function(newTitleText, headerBar) {
 6356    if (isDefined(newTitleText)) {
 6357      newTitleText = newTitleText || '';
 6358      headerBar = headerBar || getOnScreenHeaderBar();
 6359      headerBar && headerBar.title(newTitleText);
 6360      $scope.$title = newTitleText;
 6361      $ionicHistory.currentTitle(newTitleText);
 6362    }
 6363    return $scope.$title;
 6364  };
 6365
 6366
 6367  self.align = function(val, headerBar) {
 6368    headerBar = headerBar || getOnScreenHeaderBar();
 6369    headerBar && headerBar.controller().align(val);
 6370  };
 6371
 6372
 6373  self.hasTabsTop = function(isTabsTop) {
 6374    $element[isTabsTop ? 'addClass' : 'removeClass']('nav-bar-tabs-top');
 6375  };
 6376
 6377  self.hasBarSubheader = function(isBarSubheader) {
 6378    $element[isBarSubheader ? 'addClass' : 'removeClass']('nav-bar-has-subheader');
 6379  };
 6380
 6381  // DEPRECATED, as of v1.0.0-beta14 -------
 6382  self.changeTitle = function(val) {
 6383    deprecatedWarning('changeTitle(val)', 'title(val)');
 6384    self.title(val);
 6385  };
 6386  self.setTitle = function(val) {
 6387    deprecatedWarning('setTitle(val)', 'title(val)');
 6388    self.title(val);
 6389  };
 6390  self.getTitle = function() {
 6391    deprecatedWarning('getTitle()', 'title()');
 6392    return self.title();
 6393  };
 6394  self.back = function() {
 6395    deprecatedWarning('back()', '$ionicHistory.goBack()');
 6396    $ionicHistory.goBack();
 6397  };
 6398  self.getPreviousTitle = function() {
 6399    deprecatedWarning('getPreviousTitle()', '$ionicHistory.backTitle()');
 6400    $ionicHistory.goBack();
 6401  };
 6402  function deprecatedWarning(oldMethod, newMethod) {
 6403    var warn = console.warn || console.log;
 6404    warn && warn.call(console, 'navBarController.' + oldMethod + ' is deprecated, please use ' + newMethod + ' instead');
 6405  }
 6406  // END DEPRECATED -------
 6407
 6408
 6409  function createNavElement(type) {
 6410    if (navElementHtml[type]) {
 6411      return jqLite(navElementHtml[type]);
 6412    }
 6413  }
 6414
 6415
 6416  function getOnScreenHeaderBar() {
 6417    for (var x = 0; x < headerBars.length; x++) {
 6418      if (headerBars[x].isActive) return headerBars[x];
 6419    }
 6420  }
 6421
 6422
 6423  function getOffScreenHeaderBar() {
 6424    for (var x = 0; x < headerBars.length; x++) {
 6425      if (!headerBars[x].isActive) return headerBars[x];
 6426    }
 6427  }
 6428
 6429
 6430  function navBarAttr(ctrl, val) {
 6431    ctrl && ionic.DomUtil.cachedAttr(ctrl.containerEle(), 'nav-bar', val);
 6432  }
 6433
 6434  function navSwipeAttr(val) {
 6435    ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);
 6436  }
 6437
 6438
 6439  $scope.$on('$destroy', function() {
 6440    $scope.$parent.$hasHeader = false;
 6441    $element.parent().removeData(DATA_NAV_BAR_CTRL);
 6442    for (var x = 0; x < headerBars.length; x++) {
 6443      headerBars[x].destroy();
 6444    }
 6445    $element.remove();
 6446    $element = headerBars = null;
 6447    deregisterInstance();
 6448  });
 6449
 6450}]);
 6451
 6452IonicModule
 6453.controller('$ionicNavView', [
 6454  '$scope',
 6455  '$element',
 6456  '$attrs',
 6457  '$compile',
 6458  '$controller',
 6459  '$ionicNavBarDelegate',
 6460  '$ionicNavViewDelegate',
 6461  '$ionicHistory',
 6462  '$ionicViewSwitcher',
 6463  '$ionicConfig',
 6464  '$ionicScrollDelegate',
 6465  '$ionicSideMenuDelegate',
 6466function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, $ionicNavViewDelegate, $ionicHistory, $ionicViewSwitcher, $ionicConfig, $ionicScrollDelegate, $ionicSideMenuDelegate) {
 6467
 6468  var DATA_ELE_IDENTIFIER = '$eleId';
 6469  var DATA_DESTROY_ELE = '$destroyEle';
 6470  var DATA_NO_CACHE = '$noCache';
 6471  var VIEW_STATUS_ACTIVE = 'active';
 6472  var VIEW_STATUS_CACHED = 'cached';
 6473
 6474  var self = this;
 6475  var direction;
 6476  var isPrimary = false;
 6477  var navBarDelegate;
 6478  var activeEleId;
 6479  var navViewAttr = $ionicViewSwitcher.navViewAttr;
 6480  var disableRenderStartViewId, disableAnimation;
 6481
 6482  self.scope = $scope;
 6483  self.element = $element;
 6484
 6485  self.init = function() {
 6486    var navViewName = $attrs.name || '';
 6487
 6488    // Find the details of the parent view directive (if any) and use it
 6489    // to derive our own qualified view name, then hang our own details
 6490    // off the DOM so child directives can find it.
 6491    var parent = $element.parent().inheritedData('$uiView');
 6492    var parentViewName = ((parent && parent.state) ? parent.state.name : '');
 6493    if (navViewName.indexOf('@') < 0) navViewName = navViewName + '@' + parentViewName;
 6494
 6495    var viewData = { name: navViewName, state: null };
 6496    $element.data('$uiView', viewData);
 6497
 6498    var deregisterInstance = $ionicNavViewDelegate._registerInstance(self, $attrs.delegateHandle);
 6499    $scope.$on('$destroy', function() {
 6500      deregisterInstance();
 6501
 6502      // ensure no scrolls have been left frozen
 6503      if (self.isSwipeFreeze) {
 6504        $ionicScrollDelegate.freezeAllScrolls(false);
 6505      }
 6506    });
 6507
 6508    $scope.$on('$ionicHistory.deselect', self.cacheCleanup);
 6509    $scope.$on('$ionicTabs.top', onTabsTop);
 6510    $scope.$on('$ionicSubheader', onBarSubheader);
 6511
 6512    $scope.$on('$ionicTabs.beforeLeave', onTabsLeave);
 6513    $scope.$on('$ionicTabs.afterLeave', onTabsLeave);
 6514    $scope.$on('$ionicTabs.leave', onTabsLeave);
 6515
 6516    ionic.Platform.ready(function() {
 6517      if ( ionic.Platform.isWebView() && ionic.Platform.isIOS() ) {
 6518          self.initSwipeBack();
 6519      }
 6520    });
 6521
 6522    return viewData;
 6523  };
 6524
 6525
 6526  self.register = function(viewLocals) {
 6527    var leavingView = extend({}, $ionicHistory.currentView());
 6528
 6529    // register that a view is coming in and get info on how it should transition
 6530    var registerData = $ionicHistory.register($scope, viewLocals);
 6531
 6532    // update which direction
 6533    self.update(registerData);
 6534
 6535    // begin rendering and transitioning
 6536    var enteringView = $ionicHistory.getViewById(registerData.viewId) || {};
 6537
 6538    var renderStart = (disableRenderStartViewId !== registerData.viewId);
 6539    self.render(registerData, viewLocals, enteringView, leavingView, renderStart, true);
 6540  };
 6541
 6542
 6543  self.update = function(registerData) {
 6544    // always reset that this is the primary navView
 6545    isPrimary = true;
 6546
 6547    // remember what direction this navView should use
 6548    // this may get updated later by a child navView
 6549    direction = registerData.direction;
 6550
 6551    var parentNavViewCtrl = $element.parent().inheritedData('$ionNavViewController');
 6552    if (parentNavViewCtrl) {
 6553      // this navView is nested inside another one
 6554      // update the parent to use this direction and not
 6555      // the other it originally was set to
 6556
 6557      // inform the parent navView that it is not the primary navView
 6558      parentNavViewCtrl.isPrimary(false);
 6559
 6560      if (direction === 'enter' || direction === 'exit') {
 6561        // they're entering/exiting a history
 6562        // find parent navViewController
 6563        parentNavViewCtrl.direction(direction);
 6564
 6565        if (direction === 'enter') {
 6566          // reset the direction so this navView doesn't animate
 6567          // because it's parent will
 6568          direction = 'none';
 6569        }
 6570      }
 6571    }
 6572  };
 6573
 6574
 6575  self.render = function(registerData, viewLocals, enteringView, leavingView, renderStart, renderEnd) {
 6576    // register the view and figure out where it lives in the various
 6577    // histories and nav stacks, along with how views should enter/leave
 6578    var switcher = $ionicViewSwitcher.create(self, viewLocals, enteringView, leavingView, renderStart, renderEnd);
 6579
 6580    // init the rendering of views for this navView directive
 6581    switcher.init(registerData, function() {
 6582      // the view is now compiled, in the dom and linked, now lets transition the views.
 6583      // this uses a callback incase THIS nav-view has a nested nav-view, and after the NESTED
 6584      // nav-view links, the NESTED nav-view would update which direction THIS nav-view should use
 6585
 6586      // kick off the transition of views
 6587      switcher.transition(self.direction(), registerData.enableBack, !disableAnimation);
 6588
 6589      // reset private vars for next time
 6590      disableRenderStartViewId = disableAnimation = null;
 6591    });
 6592
 6593  };
 6594
 6595
 6596  self.beforeEnter = function(transitionData) {
 6597    if (isPrimary) {
 6598      // only update this nav-view's nav-bar if this is the primary nav-view
 6599      navBarDelegate = transitionData.navBarDelegate;
 6600      var associatedNavBarCtrl = getAssociatedNavBarCtrl();
 6601      associatedNavBarCtrl && associatedNavBarCtrl.update(transitionData);
 6602      navSwipeAttr('');
 6603    }
 6604  };
 6605
 6606
 6607  self.activeEleId = function(eleId) {
 6608    if (arguments.length) {
 6609      activeEleId = eleId;
 6610    }
 6611    return activeEleId;
 6612  };
 6613
 6614
 6615  self.transitionEnd = function() {
 6616    var viewElements = $element.children();
 6617    var x, l, viewElement;
 6618
 6619    for (x = 0, l = viewElements.length; x < l; x++) {
 6620      viewElement = viewElements.eq(x);
 6621
 6622      if (viewElement.data(DATA_ELE_IDENTIFIER) === activeEleId) {
 6623        // this is the active element
 6624        navViewAttr(viewElement, VIEW_STATUS_ACTIVE);
 6625
 6626      } else if (navViewAttr(viewElement) === 'leaving' || navViewAttr(viewElement) === VIEW_STATUS_ACTIVE || navViewAttr(viewElement) === VIEW_STATUS_CACHED) {
 6627        // this is a leaving element or was the former active element, or is an cached element
 6628        if (viewElement.data(DATA_DESTROY_ELE) || viewElement.data(DATA_NO_CACHE)) {
 6629          // this element shouldn't stay cached
 6630          $ionicViewSwitcher.destroyViewEle(viewElement);
 6631
 6632        } else {
 6633          // keep in the DOM, mark as cached
 6634          navViewAttr(viewElement, VIEW_STATUS_CACHED);
 6635
 6636          // disconnect the leaving scope
 6637          ionic.Utils.disconnectScope(viewElement.scope());
 6638        }
 6639      }
 6640    }
 6641
 6642    navSwipeAttr('');
 6643
 6644    // ensure no scrolls have been left frozen
 6645    if (self.isSwipeFreeze) {
 6646      $ionicScrollDelegate.freezeAllScrolls(false);
 6647    }
 6648  };
 6649
 6650
 6651  function onTabsLeave(ev, data) {
 6652    var viewElements = $element.children();
 6653    var viewElement, viewScope;
 6654
 6655    for (var x = 0, l = viewElements.length; x < l; x++) {
 6656      viewElement = viewElements.eq(x);
 6657      if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
 6658        viewScope = viewElement.scope();
 6659        viewScope && viewScope.$emit(ev.name.replace('Tabs', 'View'), data);
 6660        viewScope && viewScope.$broadcast(ev.name.replace('Tabs', 'ParentView'), data);
 6661        break;
 6662      }
 6663    }
 6664  }
 6665
 6666
 6667  self.cacheCleanup = function() {
 6668    var viewElements = $element.children();
 6669    for (var x = 0, l = viewElements.length; x < l; x++) {
 6670      if (viewElements.eq(x).data(DATA_DESTROY_ELE)) {
 6671        $ionicViewSwitcher.destroyViewEle(viewElements.eq(x));
 6672      }
 6673    }
 6674  };
 6675
 6676
 6677  self.clearCache = function(stateIds) {
 6678    var viewElements = $element.children();
 6679    var viewElement, viewScope, x, l, y, eleIdentifier;
 6680
 6681    for (x = 0, l = viewElements.length; x < l; x++) {
 6682      viewElement = viewElements.eq(x);
 6683
 6684      if (stateIds) {
 6685        eleIdentifier = viewElement.data(DATA_ELE_IDENTIFIER);
 6686
 6687        for (y = 0; y < stateIds.length; y++) {
 6688          if (eleIdentifier === stateIds[y]) {
 6689            $ionicViewSwitcher.destroyViewEle(viewElement);
 6690          }
 6691        }
 6692        continue;
 6693      }
 6694
 6695      if (navViewAttr(viewElement) == VIEW_STATUS_CACHED) {
 6696        $ionicViewSwitcher.destroyViewEle(viewElement);
 6697
 6698      } else if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
 6699        viewScope = viewElement.scope();
 6700        viewScope && viewScope.$broadcast('$ionicView.clearCache');
 6701      }
 6702
 6703    }
 6704  };
 6705
 6706
 6707  self.getViewElements = function() {
 6708    return $element.children();
 6709  };
 6710
 6711
 6712  self.appendViewElement = function(viewEle, viewLocals) {
 6713    // compile the entering element and get the link function
 6714    var linkFn = $compile(viewEle);
 6715
 6716    $element.append(viewEle);
 6717
 6718    var viewScope = $scope.$new();
 6719
 6720    if (viewLocals && viewLocals.$$controller) {
 6721      viewLocals.$scope = viewScope;
 6722      var controller = $controller(viewLocals.$$controller, viewLocals);
 6723      if (viewLocals.$$controllerAs) {
 6724        viewScope[viewLocals.$$controllerAs] = controller;
 6725      }
 6726      $element.children().data('$ngControllerController', controller);
 6727    }
 6728
 6729    linkFn(viewScope);
 6730
 6731    return viewScope;
 6732  };
 6733
 6734
 6735  self.title = function(val) {
 6736    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
 6737    associatedNavBarCtrl && associatedNavBarCtrl.title(val);
 6738  };
 6739
 6740
 6741  /**
 6742   * @ngdoc method
 6743   * @name $ionicNavView#enableBackButton
 6744   * @description Enable/disable if the back button can be shown or not. For
 6745   * example, the very first view in the navigation stack would not have a
 6746   * back view, so the back button would be disabled.
 6747   */
 6748  self.enableBackButton = function(shouldEnable) {
 6749    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
 6750    associatedNavBarCtrl && associatedNavBarCtrl.enableBackButton(shouldEnable);
 6751  };
 6752
 6753
 6754  /**
 6755   * @ngdoc method
 6756   * @name $ionicNavView#showBackButton
 6757   * @description Show/hide the nav bar active back button. If the back button
 6758   * is not possible this will not force the back button to show. The
 6759   * `enableBackButton()` method handles if a back button is even possible or not.
 6760   */
 6761  self.showBackButton = function(shouldShow) {
 6762    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
 6763    if (associatedNavBarCtrl) {
 6764      if (arguments.length) {
 6765        return associatedNavBarCtrl.showActiveBackButton(shouldShow);
 6766      }
 6767      return associatedNavBarCtrl.showActiveBackButton();
 6768    }
 6769    return true;
 6770  };
 6771
 6772
 6773  self.showBar = function(val) {
 6774    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
 6775    if (associatedNavBarCtrl) {
 6776      if (arguments.length) {
 6777        return associatedNavBarCtrl.showBar(val);
 6778      }
 6779      return associatedNavBarCtrl.showBar();
 6780    }
 6781    return true;
 6782  };
 6783
 6784
 6785  self.isPrimary = function(val) {
 6786    if (arguments.length) {
 6787      isPrimary = val;
 6788    }
 6789    return isPrimary;
 6790  };
 6791
 6792
 6793  self.direction = function(val) {
 6794    if (arguments.length) {
 6795      direction = val;
 6796    }
 6797    return direction;
 6798  };
 6799
 6800
 6801  self.initSwipeBack = function() {
 6802    var swipeBackHitWidth = $ionicConfig.views.swipeBackHitWidth();
 6803    var viewTransition, associatedNavBarCtrl, backView;
 6804    var deregDragStart, deregDrag, deregRelease;
 6805    var windowWidth, startDragX, dragPoints;
 6806    var cancelData = {};
 6807
 6808    function onDragStart(ev) {
 6809      if (!isPrimary || !$ionicConfig.views.swipeBackEnabled() || $ionicSideMenuDelegate.isOpenRight() ) return;
 6810
 6811
 6812      startDragX = getDragX(ev);
 6813      if (startDragX > swipeBackHitWidth) return;
 6814
 6815      backView = $ionicHistory.backView();
 6816
 6817      var currentView = $ionicHistory.currentView();
 6818
 6819      if (!backView || backView.historyId !== currentView.historyId || currentView.canSwipeBack === false) return;
 6820
 6821      if (!windowWidth) windowWidth = window.innerWidth;
 6822
 6823      self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(true);
 6824
 6825      var registerData = {
 6826        direction: 'back'
 6827      };
 6828
 6829      dragPoints = [];
 6830
 6831      cancelData = {
 6832        showBar: self.showBar(),
 6833        showBackButton: self.showBackButton()
 6834      };
 6835
 6836      var switcher = $ionicViewSwitcher.create(self, registerData, backView, currentView, true, false);
 6837      switcher.loadViewElements(registerData);
 6838      switcher.render(registerData);
 6839
 6840      viewTransition = switcher.transition('back', $ionicHistory.enabledBack(backView), true);
 6841
 6842      associatedNavBarCtrl = getAssociatedNavBarCtrl();
 6843
 6844      deregDrag = ionic.onGesture('drag', onDrag, $element[0]);
 6845      deregRelease = ionic.onGesture('release', onRelease, $element[0]);
 6846    }
 6847
 6848    function onDrag(ev) {
 6849      if (isPrimary && viewTransition) {
 6850        var dragX = getDragX(ev);
 6851
 6852        dragPoints.push({
 6853          t: Date.now(),
 6854          x: dragX
 6855        });
 6856
 6857        if (dragX >= windowWidth - 15) {
 6858          onRelease(ev);
 6859
 6860        } else {
 6861          var step = Math.min(Math.max(getSwipeCompletion(dragX), 0), 1);
 6862          viewTransition.run(step);
 6863          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.run(step);
 6864        }
 6865
 6866      }
 6867    }
 6868
 6869    function onRelease(ev) {
 6870      if (isPrimary && viewTransition && dragPoints && dragPoints.length > 1) {
 6871
 6872        var now = Date.now();
 6873        var releaseX = getDragX(ev);
 6874        var startDrag = dragPoints[dragPoints.length - 1];
 6875
 6876        for (var x = dragPoints.length - 2; x >= 0; x--) {
 6877          if (now - startDrag.t > 200) {
 6878            break;
 6879          }
 6880          startDrag = dragPoints[x];
 6881        }
 6882
 6883        var isSwipingRight = (releaseX >= dragPoints[dragPoints.length - 2].x);
 6884        var releaseSwipeCompletion = getSwipeCompletion(releaseX);
 6885        var velocity = Math.abs(startDrag.x - releaseX) / (now - startDrag.t);
 6886
 6887        // private variables because ui-router has no way to pass custom data using $state.go
 6888        disableRenderStartViewId = backView.viewId;
 6889        disableAnimation = (releaseSwipeCompletion < 0.03 || releaseSwipeCompletion > 0.97);
 6890
 6891        if (isSwipingRight && (releaseSwipeCompletion > 0.5 || velocity > 0.1)) {
 6892          // complete view transition on release
 6893          var speed = (velocity > 0.5 || velocity < 0.05 || releaseX > windowWidth - 45) ? 'fast' : 'slow';
 6894          navSwipeAttr(disableAnimation ? '' : speed);
 6895          backView.go();
 6896          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.complete(!disableAnimation, speed);
 6897
 6898        } else {
 6899          // cancel view transition on release
 6900          navSwipeAttr(disableAnimation ? '' : 'fast');
 6901          disableRenderStartViewId = null;
 6902          viewTransition.cancel(!disableAnimation);
 6903          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.cancel(!disableAnimation, 'fast', cancelData);
 6904          disableAnimation = null;
 6905        }
 6906
 6907      }
 6908
 6909      ionic.offGesture(deregDrag, 'drag', onDrag);
 6910      ionic.offGesture(deregRelease, 'release', onRelease);
 6911
 6912      windowWidth = viewTransition = dragPoints = null;
 6913
 6914      self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(false);
 6915    }
 6916
 6917    function getDragX(ev) {
 6918      return ionic.tap.pointerCoord(ev.gesture.srcEvent).x;
 6919    }
 6920
 6921    function getSwipeCompletion(dragX) {
 6922      return (dragX - startDragX) / windowWidth;
 6923    }
 6924
 6925    deregDragStart = ionic.onGesture('dragstart', onDragStart, $element[0]);
 6926
 6927    $scope.$on('$destroy', function() {
 6928      ionic.offGesture(deregDragStart, 'dragstart', onDragStart);
 6929      ionic.offGesture(deregDrag, 'drag', onDrag);
 6930      ionic.offGesture(deregRelease, 'release', onRelease);
 6931      self.element = viewTransition = associatedNavBarCtrl = null;
 6932    });
 6933  };
 6934
 6935
 6936  function navSwipeAttr(val) {
 6937    ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);
 6938  }
 6939
 6940
 6941  function onTabsTop(ev, isTabsTop) {
 6942    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
 6943    associatedNavBarCtrl && associatedNavBarCtrl.hasTabsTop(isTabsTop);
 6944  }
 6945
 6946  function onBarSubheader(ev, isBarSubheader) {
 6947    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
 6948    associatedNavBarCtrl && associatedNavBarCtrl.hasBarSubheader(isBarSubheader);
 6949  }
 6950
 6951  function getAssociatedNavBarCtrl() {
 6952    if (navBarDelegate) {
 6953      for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {
 6954        if ($ionicNavBarDelegate._instances[x].$$delegateHandle == navBarDelegate) {
 6955          return $ionicNavBarDelegate._instances[x];
 6956        }
 6957      }
 6958    }
 6959    return $element.inheritedData('$ionNavBarController');
 6960  }
 6961
 6962}]);
 6963
 6964IonicModule
 6965.controller('$ionicRefresher', [
 6966  '$scope',
 6967  '$attrs',
 6968  '$element',
 6969  '$ionicBind',
 6970  '$timeout',
 6971  function($scope, $attrs, $element, $ionicBind, $timeout) {
 6972    var self = this,
 6973        isDragging = false,
 6974        isOverscrolling = false,
 6975        dragOffset = 0,
 6976        lastOverscroll = 0,
 6977        ptrThreshold = 60,
 6978        activated = false,
 6979        scrollTime = 500,
 6980        startY = null,
 6981        deltaY = null,
 6982        canOverscroll = true,
 6983        scrollParent,
 6984        scrollChild;
 6985
 6986    if (!isDefined($attrs.pullingIcon)) {
 6987      $attrs.$set('pullingIcon', 'ion-android-arrow-down');
 6988    }
 6989
 6990    $scope.showSpinner = !isDefined($attrs.refreshingIcon) && $attrs.spinner != 'none';
 6991
 6992    $scope.showIcon = isDefined($attrs.refreshingIcon);
 6993
 6994    $ionicBind($scope, $attrs, {
 6995      pullingIcon: '@',
 6996      pullingText: '@',
 6997      refreshingIcon: '@',
 6998      refreshingText: '@',
 6999      spinner: '@',
 7000      disablePullingRotation: '@',
 7001      $onRefresh: '&onRefresh',
 7002      $onPulling: '&onPulling'
 7003    });
 7004
 7005    function handleMousedown(e) {
 7006      e.touches = e.touches || [{
 7007        screenX: e.screenX,
 7008        screenY: e.screenY
 7009      }];
 7010      // Mouse needs this
 7011      startY = Math.floor(e.touches[0].screenY);
 7012    }
 7013
 7014    function handleTouchstart(e) {
 7015      e.touches = e.touches || [{
 7016        screenX: e.screenX,
 7017        screenY: e.screenY
 7018      }];
 7019
 7020      startY = e.touches[0].screenY;
 7021    }
 7022
 7023    function handleTouchend() {
 7024      // reset Y
 7025      startY = null;
 7026      // if this wasn't an overscroll, get out immediately
 7027      if (!canOverscroll && !isDragging) {
 7028        return;
 7029      }
 7030      // the user has overscrolled but went back to native scrolling
 7031      if (!isDragging) {
 7032        dragOffset = 0;
 7033        isOverscrolling = false;
 7034        setScrollLock(false);
 7035      } else {
 7036        isDragging = false;
 7037        dragOffset = 0;
 7038
 7039        // the user has scroll far enough to trigger a refresh
 7040        if (lastOverscroll > ptrThreshold) {
 7041          start();
 7042          scrollTo(ptrThreshold, scrollTime);
 7043
 7044        // the user has overscrolled but not far enough to trigger a refresh
 7045        } else {
 7046          scrollTo(0, scrollTime, deactivate);
 7047          isOverscrolling = false;
 7048        }
 7049      }
 7050    }
 7051
 7052    function handleTouchmove(e) {
 7053      e.touches = e.touches || [{
 7054        screenX: e.screenX,
 7055        screenY: e.screenY
 7056      }];
 7057
 7058      // Force mouse events to have had a down event first
 7059      if (!startY && e.type == 'mousemove') {
 7060        return;
 7061      }
 7062
 7063      // if multitouch or regular scroll event, get out immediately
 7064      if (!canOverscroll || e.touches.length > 1) {
 7065        return;
 7066      }
 7067      //if this is a new drag, keep track of where we start
 7068      if (startY === null) {
 7069        startY = e.touches[0].screenY;
 7070      }
 7071
 7072      deltaY = e.touches[0].screenY - startY;
 7073
 7074      // how far have we dragged so far?
 7075      // kitkat fix for touchcancel events http://updates.html5rocks.com/2014/05/A-More-Compatible-Smoother-Touch
 7076      // Only do this if we're not on crosswalk
 7077      if (ionic.Platform.isAndroid() && ionic.Platform.version() === 4.4 && !ionic.Platform.isCrosswalk() && scrollParent.scrollTop === 0 && deltaY > 0) {
 7078        isDragging = true;
 7079        e.preventDefault();
 7080      }
 7081
 7082
 7083      // if we've dragged up and back down in to native scroll territory
 7084      if (deltaY - dragOffset <= 0 || scrollParent.scrollTop !== 0) {
 7085
 7086        if (isOverscrolling) {
 7087          isOverscrolling = false;
 7088          setScrollLock(false);
 7089        }
 7090
 7091        if (isDragging) {
 7092          nativescroll(scrollParent, deltaY - dragOffset * -1);
 7093        }
 7094
 7095        // if we're not at overscroll 0 yet, 0 out
 7096        if (lastOverscroll !== 0) {
 7097          overscroll(0);
 7098        }
 7099        return;
 7100
 7101      } else if (deltaY > 0 && scrollParent.scrollTop === 0 && !isOverscrolling) {
 7102        // starting overscroll, but drag started below scrollTop 0, so we need to offset the position
 7103        dragOffset = deltaY;
 7104      }
 7105
 7106      // prevent native scroll events while overscrolling
 7107      e.preventDefault();
 7108
 7109      // if not overscrolling yet, initiate overscrolling
 7110      if (!isOverscrolling) {
 7111        isOverscrolling = true;
 7112        setScrollLock(true);
 7113      }
 7114
 7115      isDragging = true;
 7116      // overscroll according to the user's drag so far
 7117      overscroll((deltaY - dragOffset) / 3);
 7118
 7119      // update the icon accordingly
 7120      if (!activated && lastOverscroll > ptrThreshold) {
 7121        activated = true;
 7122        ionic.requestAnimationFrame(activate);
 7123
 7124      } else if (activated && lastOverscroll < ptrThreshold) {
 7125        activated = false;
 7126        ionic.requestAnimationFrame(deactivate);
 7127      }
 7128    }
 7129
 7130    function handleScroll(e) {
 7131      // canOverscrol is used to greatly simplify the drag handler during normal scrolling
 7132      canOverscroll = (e.target.scrollTop === 0) || isDragging;
 7133    }
 7134
 7135    function overscroll(val) {
 7136      scrollChild.style[ionic.CSS.TRANSFORM] = 'translate3d(0px, ' + val + 'px, 0px)';
 7137      lastOverscroll = val;
 7138    }
 7139
 7140    function nativescroll(target, newScrollTop) {
 7141      // creates a scroll event that bubbles, can be cancelled, and with its view
 7142      // and detail property initialized to window and 1, respectively
 7143      target.scrollTop = newScrollTop;
 7144      var e = document.createEvent("UIEvents");
 7145      e.initUIEvent("scroll", true, true, window, 1);
 7146      target.dispatchEvent(e);
 7147    }
 7148
 7149    function setScrollLock(enabled) {
 7150      // set the scrollbar to be position:fixed in preparation to overscroll
 7151      // or remove it so the app can be natively scrolled
 7152      if (enabled) {
 7153        ionic.requestAnimationFrame(function() {
 7154          scrollChild.classList.add('overscroll');
 7155          show();
 7156        });
 7157
 7158      } else {
 7159        ionic.requestAnimationFrame(function() {
 7160          scrollChild.classList.remove('overscroll');
 7161          hide();
 7162          deactivate();
 7163        });
 7164      }
 7165    }
 7166
 7167    $scope.$on('scroll.refreshComplete', function() {
 7168      // prevent the complete from firing before the scroll has started
 7169      $timeout(function() {
 7170
 7171        ionic.requestAnimationFrame(tail);
 7172
 7173        // scroll back to home during tail animation
 7174        scrollTo(0, scrollTime, deactivate);
 7175
 7176        // return to native scrolling after tail animation has time to finish
 7177        $timeout(function() {
 7178
 7179          if (isOverscrolling) {
 7180            isOverscrolling = false;
 7181            setScrollLock(false);
 7182          }
 7183
 7184        }, scrollTime);
 7185
 7186      }, scrollTime);
 7187    });
 7188
 7189    function scrollTo(Y, duration, callback) {
 7190      // scroll animation loop w/ easing
 7191      // credit https://gist.github.com/dezinezync/5487119
 7192      var start = Date.now(),
 7193          from = lastOverscroll;
 7194
 7195      if (from === Y) {
 7196        callback();
 7197        return; /* Prevent scrolling to the Y point if already there */
 7198      }
 7199
 7200      // decelerating to zero velocity
 7201      function easeOutCubic(t) {
 7202        return (--t) * t * t + 1;
 7203      }
 7204
 7205      // scroll loop
 7206      function scroll() {
 7207        var currentTime = Date.now(),
 7208          time = Math.min(1, ((currentTime - start) / duration)),
 7209          // where .5 would be 50% of time on a linear scale easedT gives a
 7210          // fraction based on the easing method
 7211          easedT = easeOutCubic(time);
 7212
 7213        overscroll(Math.floor((easedT * (Y - from)) + from));
 7214
 7215        if (time < 1) {
 7216          ionic.requestAnimationFrame(scroll);
 7217
 7218        } else {
 7219
 7220          if (Y < 5 && Y > -5) {
 7221            isOverscrolling = false;
 7222            setScrollLock(false);
 7223          }
 7224
 7225          callback && callback();
 7226        }
 7227      }
 7228
 7229      // start scroll loop
 7230      ionic.requestAnimationFrame(scroll);
 7231    }
 7232
 7233
 7234    var touchStartEvent, touchMoveEvent, touchEndEvent;
 7235    if (window.navigator.pointerEnabled) {
 7236      touchStartEvent = 'pointerdown';
 7237      touchMoveEvent = 'pointermove';
 7238      touchEndEvent = 'pointerup';
 7239    } else if (window.navigator.msPointerEnabled) {
 7240      touchStartEvent = 'MSPointerDown';
 7241      touchMoveEvent = 'MSPointerMove';
 7242      touchEndEvent = 'MSPointerUp';
 7243    } else {
 7244      touchStartEvent = 'touchstart';
 7245      touchMoveEvent = 'touchmove';
 7246      touchEndEvent = 'touchend';
 7247    }
 7248
 7249    self.init = function() {
 7250      scrollParent = $element.parent().parent()[0];
 7251      scrollChild = $element.parent()[0];
 7252
 7253      if (!scrollParent || !scrollParent.classList.contains('ionic-scroll') ||
 7254        !scrollChild || !scrollChild.classList.contains('scroll')) {
 7255        throw new Error('Refresher must be immediate child of ion-content or ion-scroll');
 7256      }
 7257
 7258
 7259      ionic.on(touchStartEvent, handleTouchstart, scrollChild);
 7260      ionic.on(touchMoveEvent, handleTouchmove, scrollChild);
 7261      ionic.on(touchEndEvent, handleTouchend, scrollChild);
 7262      ionic.on('mousedown', handleMousedown, scrollChild);
 7263      ionic.on('mousemove', handleTouchmove, scrollChild);
 7264      ionic.on('mouseup', handleTouchend, scrollChild);
 7265      ionic.on('scroll', handleScroll, scrollParent);
 7266
 7267      // cleanup when done
 7268      $scope.$on('$destroy', destroy);
 7269    };
 7270
 7271    function destroy() {
 7272      if ( scrollChild ) {
 7273        ionic.off(touchStartEvent, handleTouchstart, scrollChild);
 7274        ionic.off(touchMoveEvent, handleTouchmove, scrollChild);
 7275        ionic.off(touchEndEvent, handleTouchend, scrollChild);
 7276        ionic.off('mousedown', handleMousedown, scrollChild);
 7277        ionic.off('mousemove', handleTouchmove, scrollChild);
 7278        ionic.off('mouseup', handleTouchend, scrollChild);
 7279      }
 7280      if ( scrollParent ) {
 7281        ionic.off('scroll', handleScroll, scrollParent);
 7282      }
 7283      scrollParent = null;
 7284      scrollChild = null;
 7285    }
 7286
 7287    // DOM manipulation and broadcast methods shared by JS and Native Scrolling
 7288    // getter used by JS Scrolling
 7289    self.getRefresherDomMethods = function() {
 7290      return {
 7291        activate: activate,
 7292        deactivate: deactivate,
 7293        start: start,
 7294        show: show,
 7295        hide: hide,
 7296        tail: tail
 7297      };
 7298    };
 7299
 7300    function activate() {
 7301      $element[0].classList.add('active');
 7302      $scope.$onPulling();
 7303    }
 7304
 7305    function deactivate() {
 7306      // give tail 150ms to finish
 7307      $timeout(function() {
 7308        // deactivateCallback
 7309        $element.removeClass('active refreshing refreshing-tail');
 7310        if (activated) activated = false;
 7311      }, 150);
 7312    }
 7313
 7314    function start() {
 7315      // startCallback
 7316      $element[0].classList.add('refreshing');
 7317      var q = $scope.$onRefresh();
 7318
 7319      if (q && q.then) {
 7320        q['finally'](function() {
 7321          $scope.$broadcast('scroll.refreshComplete');
 7322        });
 7323      }
 7324    }
 7325
 7326    function show() {
 7327      // showCallback
 7328      $element[0].classList.remove('invisible');
 7329    }
 7330
 7331    function hide() {
 7332      // showCallback
 7333      $element[0].classList.add('invisible');
 7334    }
 7335
 7336    function tail() {
 7337      // tailCallback
 7338      $element[0].classList.add('refreshing-tail');
 7339    }
 7340
 7341    // for testing
 7342    self.__handleTouchmove = handleTouchmove;
 7343    self.__getScrollChild = function() { return scrollChild; };
 7344    self.__getScrollParent = function() { return scrollParent; };
 7345  }
 7346]);
 7347
 7348/**
 7349 * @private
 7350 */
 7351IonicModule
 7352
 7353.controller('$ionicScroll', [
 7354  '$scope',
 7355  'scrollViewOptions',
 7356  '$timeout',
 7357  '$window',
 7358  '$location',
 7359  '$document',
 7360  '$ionicScrollDelegate',
 7361  '$ionicHistory',
 7362function($scope,
 7363         scrollViewOptions,
 7364         $timeout,
 7365         $window,
 7366         $location,
 7367         $document,
 7368         $ionicScrollDelegate,
 7369         $ionicHistory) {
 7370
 7371  var self = this;
 7372  // for testing
 7373  self.__timeout = $timeout;
 7374
 7375  self._scrollViewOptions = scrollViewOptions; //for testing
 7376  self.isNative = function() {
 7377    return !!scrollViewOptions.nativeScrolling;
 7378  };
 7379
 7380  var element = self.element = scrollViewOptions.el;
 7381  var $element = self.$element = jqLite(element);
 7382  var scrollView;
 7383  if (self.isNative()) {
 7384    scrollView = self.scrollView = new ionic.views.ScrollNative(scrollViewOptions);
 7385  } else {
 7386    scrollView = self.scrollView = new ionic.views.Scroll(scrollViewOptions);
 7387  }
 7388
 7389
 7390  //Attach self to element as a controller so other directives can require this controller
 7391  //through `require: '$ionicScroll'
 7392  //Also attach to parent so that sibling elements can require this
 7393  ($element.parent().length ? $element.parent() : $element)
 7394    .data('$$ionicScrollController', self);
 7395
 7396  var deregisterInstance = $ionicScrollDelegate._registerInstance(
 7397    self, scrollViewOptions.delegateHandle, function() {
 7398      return $ionicHistory.isActiveScope($scope);
 7399    }
 7400  );
 7401
 7402  if (!isDefined(scrollViewOptions.bouncing)) {
 7403    ionic.Platform.ready(function() {
 7404      if (scrollView && scrollView.options) {
 7405        scrollView.options.bouncing = true;
 7406        if (ionic.Platform.isAndroid()) {
 7407          // No bouncing by default on Android
 7408          scrollView.options.bouncing = false;
 7409          // Faster scroll decel
 7410          scrollView.options.deceleration = 0.95;
 7411        }
 7412      }
 7413    });
 7414  }
 7415
 7416  var resize = angular.bind(scrollView, scrollView.resize);
 7417  angular.element($window).on('resize', resize);
 7418
 7419  var scrollFunc = function(e) {
 7420    var detail = (e.originalEvent || e).detail || {};
 7421    $scope.$onScroll && $scope.$onScroll({
 7422      event: e,
 7423      scrollTop: detail.scrollTop || 0,
 7424      scrollLeft: detail.scrollLeft || 0
 7425    });
 7426  };
 7427
 7428  $element.on('scroll', scrollFunc);
 7429
 7430  $scope.$on('$destroy', function() {
 7431    deregisterInstance();
 7432    scrollView && scrollView.__cleanup && scrollView.__cleanup();
 7433    angular.element($window).off('resize', resize);
 7434    if ( $element ) {
 7435      $element.off('scroll', scrollFunc);
 7436    }
 7437    if ( self._scrollViewOptions ) {
 7438      self._scrollViewOptions.el = null;
 7439    }
 7440    if ( scrollViewOptions ) {
 7441        scrollViewOptions.el = null;
 7442    }
 7443
 7444    scrollView = self.scrollView = scrollViewOptions = self._scrollViewOptions = element = self.$element = $element = null;
 7445  });
 7446
 7447  $timeout(function() {
 7448    scrollView && scrollView.run && scrollView.run();
 7449  });
 7450
 7451  self.getScrollView = function() {
 7452    return scrollView;
 7453  };
 7454
 7455  self.getScrollPosition = function() {
 7456    return scrollView.getValues();
 7457  };
 7458
 7459  self.resize = function() {
 7460    return $timeout(resize, 0, false).then(function() {
 7461      $element && $element.triggerHandler('scroll-resize');
 7462    });
 7463  };
 7464
 7465  self.scrollTop = function(shouldAnimate) {
 7466    self.resize().then(function() {
 7467      if (!scrollView) {
 7468        return;
 7469      }
 7470      scrollView.scrollTo(0, 0, !!shouldAnimate);
 7471    });
 7472  };
 7473
 7474  self.scrollBottom = function(shouldAnimate) {
 7475    self.resize().then(function() {
 7476      if (!scrollView) {
 7477        return;
 7478      }
 7479      var max = scrollView.getScrollMax();
 7480      scrollView.scrollTo(max.left, max.top, !!shouldAnimate);
 7481    });
 7482  };
 7483
 7484  self.scrollTo = function(left, top, shouldAnimate) {
 7485    self.resize().then(function() {
 7486      if (!scrollView) {
 7487        return;
 7488      }
 7489      scrollView.scrollTo(left, top, !!shouldAnimate);
 7490    });
 7491  };
 7492
 7493  self.zoomTo = function(zoom, shouldAnimate, originLeft, originTop) {
 7494    self.resize().then(function() {
 7495      if (!scrollView) {
 7496        return;
 7497      }
 7498      scrollView.zoomTo(zoom, !!shouldAnimate, originLeft, originTop);
 7499    });
 7500  };
 7501
 7502  self.zoomBy = function(zoom, shouldAnimate, originLeft, originTop) {
 7503    self.resize().then(function() {
 7504      if (!scrollView) {
 7505        return;
 7506      }
 7507      scrollView.zoomBy(zoom, !!shouldAnimate, originLeft, originTop);
 7508    });
 7509  };
 7510
 7511  self.scrollBy = function(left, top, shouldAnimate) {
 7512    self.resize().then(function() {
 7513      if (!scrollView) {
 7514        return;
 7515      }
 7516      scrollView.scrollBy(left, top, !!shouldAnimate);
 7517    });
 7518  };
 7519
 7520  self.anchorScroll = function(shouldAnimate) {
 7521    self.resize().then(function() {
 7522      if (!scrollView) {
 7523        return;
 7524      }
 7525      var hash = $location.hash();
 7526      var elm = hash && $document[0].getElementById(hash);
 7527      if (!(hash && elm)) {
 7528        scrollView.scrollTo(0, 0, !!shouldAnimate);
 7529        return;
 7530      }
 7531      var curElm = elm;
 7532      var scrollLeft = 0, scrollTop = 0;
 7533      do {
 7534        if (curElm !== null) scrollLeft += curElm.offsetLeft;
 7535        if (curElm !== null) scrollTop += curElm.offsetTop;
 7536        curElm = curElm.offsetParent;
 7537      } while (curElm.attributes != self.element.attributes && curElm.offsetParent);
 7538      scrollView.scrollTo(scrollLeft, scrollTop, !!shouldAnimate);
 7539    });
 7540  };
 7541
 7542  self.freezeScroll = scrollView.freeze;
 7543  self.freezeScrollShut = scrollView.freezeShut;
 7544
 7545  self.freezeAllScrolls = function(shouldFreeze) {
 7546    for (var i = 0; i < $ionicScrollDelegate._instances.length; i++) {
 7547      $ionicScrollDelegate._instances[i].freezeScroll(shouldFreeze);
 7548    }
 7549  };
 7550
 7551
 7552  /**
 7553   * @private
 7554   */
 7555  self._setRefresher = function(refresherScope, refresherElement, refresherMethods) {
 7556    self.refresher = refresherElement;
 7557    var refresherHeight = self.refresher.clientHeight || 60;
 7558    scrollView.activatePullToRefresh(
 7559      refresherHeight,
 7560      refresherMethods
 7561    );
 7562  };
 7563
 7564}]);
 7565
 7566IonicModule
 7567.controller('$ionicSideMenus', [
 7568  '$scope',
 7569  '$attrs',
 7570  '$ionicSideMenuDelegate',
 7571  '$ionicPlatform',
 7572  '$ionicBody',
 7573  '$ionicHistory',
 7574  '$ionicScrollDelegate',
 7575  'IONIC_BACK_PRIORITY',
 7576  '$rootScope',
 7577function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform, $ionicBody, $ionicHistory, $ionicScrollDelegate, IONIC_BACK_PRIORITY, $rootScope) {
 7578  var self = this;
 7579  var rightShowing, leftShowing, isDragging;
 7580  var startX, lastX, offsetX, isAsideExposed;
 7581  var enableMenuWithBackViews = true;
 7582
 7583  self.$scope = $scope;
 7584
 7585  self.initialize = function(options) {
 7586    self.left = options.left;
 7587    self.right = options.right;
 7588    self.setContent(options.content);
 7589    self.dragThresholdX = options.dragThresholdX || 10;
 7590    $ionicHistory.registerHistory(self.$scope);
 7591  };
 7592
 7593  /**
 7594   * Set the content view controller if not passed in the constructor options.
 7595   *
 7596   * @param {object} content
 7597   */
 7598  self.setContent = function(content) {
 7599    if (content) {
 7600      self.content = content;
 7601
 7602      self.content.onDrag = function(e) {
 7603        self._handleDrag(e);
 7604      };
 7605
 7606      self.content.endDrag = function(e) {
 7607        self._endDrag(e);
 7608      };
 7609    }
 7610  };
 7611
 7612  self.isOpenLeft = function() {
 7613    return self.getOpenAmount() > 0;
 7614  };
 7615
 7616  self.isOpenRight = function() {
 7617    return self.getOpenAmount() < 0;
 7618  };
 7619
 7620  /**
 7621   * Toggle the left menu to open 100%
 7622   */
 7623  self.toggleLeft = function(shouldOpen) {
 7624    if (isAsideExposed || !self.left.isEnabled) return;
 7625    var openAmount = self.getOpenAmount();
 7626    if (arguments.length === 0) {
 7627      shouldOpen = openAmount <= 0;
 7628    }
 7629    self.content.enableAnimation();
 7630    if (!shouldOpen) {
 7631      self.openPercentage(0);
 7632      $rootScope.$emit('$ionicSideMenuClose', 'left');
 7633    } else {
 7634      self.openPercentage(100);
 7635      $rootScope.$emit('$ionicSideMenuOpen', 'left');
 7636    }
 7637  };
 7638
 7639  /**
 7640   * Toggle the right menu to open 100%
 7641   */
 7642  self.toggleRight = function(shouldOpen) {
 7643    if (isAsideExposed || !self.right.isEnabled) return;
 7644    var openAmount = self.getOpenAmount();
 7645    if (arguments.length === 0) {
 7646      shouldOpen = openAmount >= 0;
 7647    }
 7648    self.content.enableAnimation();
 7649    if (!shouldOpen) {
 7650      self.openPercentage(0);
 7651      $rootScope.$emit('$ionicSideMenuClose', 'right');
 7652    } else {
 7653      self.openPercentage(-100);
 7654      $rootScope.$emit('$ionicSideMenuOpen', 'right');
 7655    }
 7656  };
 7657
 7658  self.toggle = function(side) {
 7659    if (side == 'right') {
 7660      self.toggleRight();
 7661    } else {
 7662      self.toggleLeft();
 7663    }
 7664  };
 7665
 7666  /**
 7667   * Close all menus.
 7668   */
 7669  self.close = function() {
 7670    self.openPercentage(0);
 7671    $rootScope.$emit('$ionicSideMenuClose', 'left');
 7672    $rootScope.$emit('$ionicSideMenuClose', 'right');
 7673  };
 7674
 7675  /**
 7676   * @return {float} The amount the side menu is open, either positive or negative for left (positive), or right (negative)
 7677   */
 7678  self.getOpenAmount = function() {
 7679    return self.content && self.content.getTranslateX() || 0;
 7680  };
 7681
 7682  /**
 7683   * @return {float} The ratio of open amount over menu width. For example, a
 7684   * menu of width 100 open 50 pixels would be open 50% or a ratio of 0.5. Value is negative
 7685   * for right menu.
 7686   */
 7687  self.getOpenRatio = function() {
 7688    var amount = self.getOpenAmount();
 7689    if (amount >= 0) {
 7690      return amount / self.left.width;
 7691    }
 7692    return amount / self.right.width;
 7693  };
 7694
 7695  self.isOpen = function() {
 7696    return self.getOpenAmount() !== 0;
 7697  };
 7698
 7699  /**
 7700   * @return {float} The percentage of open amount over menu width. For example, a
 7701   * menu of width 100 open 50 pixels would be open 50%. Value is negative
 7702   * for right menu.
 7703   */
 7704  self.getOpenPercentage = function() {
 7705    return self.getOpenRatio() * 100;
 7706  };
 7707
 7708  /**
 7709   * Open the menu with a given percentage amount.
 7710   * @param {float} percentage The percentage (positive or negative for left/right) to open the menu.
 7711   */
 7712  self.openPercentage = function(percentage) {
 7713    var p = percentage / 100;
 7714
 7715    if (self.left && percentage >= 0) {
 7716      self.openAmount(self.left.width * p);
 7717    } else if (self.right && percentage < 0) {
 7718      self.openAmount(self.right.width * p);
 7719    }
 7720
 7721    // add the CSS class "menu-open" if the percentage does not
 7722    // equal 0, otherwise remove the class from the body element
 7723    $ionicBody.enableClass((percentage !== 0), 'menu-open');
 7724
 7725    self.content.setCanScroll(percentage == 0);
 7726  };
 7727
 7728  /*
 7729  function freezeAllScrolls(shouldFreeze) {
 7730    if (shouldFreeze && !self.isScrollFreeze) {
 7731      $ionicScrollDelegate.freezeAllScrolls(shouldFreeze);
 7732
 7733    } else if (!shouldFreeze && self.isScrollFreeze) {
 7734      $ionicScrollDelegate.freezeAllScrolls(false);
 7735    }
 7736    self.isScrollFreeze = shouldFreeze;
 7737  }
 7738  */
 7739
 7740  /**
 7741   * Open the menu the given pixel amount.
 7742   * @param {float} amount the pixel amount to open the menu. Positive value for left menu,
 7743   * negative value for right menu (only one menu will be visible at a time).
 7744   */
 7745  self.openAmount = function(amount) {
 7746    var maxLeft = self.left && self.left.width || 0;
 7747    var maxRight = self.right && self.right.width || 0;
 7748
 7749    // Check if we can move to that side, depending if the left/right panel is enabled
 7750    if (!(self.left && self.left.isEnabled) && amount > 0) {
 7751      self.content.setTranslateX(0);
 7752      return;
 7753    }
 7754
 7755    if (!(self.right && self.right.isEnabled) && amount < 0) {
 7756      self.content.setTranslateX(0);
 7757      return;
 7758    }
 7759
 7760    if (leftShowing && amount > maxLeft) {
 7761      self.content.setTranslateX(maxLeft);
 7762      return;
 7763    }
 7764
 7765    if (rightShowing && amount < -maxRight) {
 7766      self.content.setTranslateX(-maxRight);
 7767      return;
 7768    }
 7769
 7770    self.content.setTranslateX(amount);
 7771
 7772    if (amount >= 0) {
 7773      leftShowing = true;
 7774      rightShowing = false;
 7775
 7776      if (amount > 0) {
 7777        // Push the z-index of the right menu down
 7778        self.right && self.right.pushDown && self.right.pushDown();
 7779        // Bring the z-index of the left menu up
 7780        self.left && self.left.bringUp && self.left.bringUp();
 7781      }
 7782    } else {
 7783      rightShowing = true;
 7784      leftShowing = false;
 7785
 7786      // Bring the z-index of the right menu up
 7787      self.right && self.right.bringUp && self.right.bringUp();
 7788      // Push the z-index of the left menu down
 7789      self.left && self.left.pushDown && self.left.pushDown();
 7790    }
 7791  };
 7792
 7793  /**
 7794   * Given an event object, find the final resting position of this side
 7795   * menu. For example, if the user "throws" the content to the right and
 7796   * releases the touch, the left menu should snap open (animated, of course).
 7797   *
 7798   * @param {Event} e the gesture event to use for snapping
 7799   */
 7800  self.snapToRest = function(e) {
 7801    // We want to animate at the end of this
 7802    self.content.enableAnimation();
 7803    isDragging = false;
 7804
 7805    // Check how much the panel is open after the drag, and
 7806    // what the drag velocity is
 7807    var ratio = self.getOpenRatio();
 7808
 7809    if (ratio === 0) {
 7810      // Just to be safe
 7811      self.openPercentage(0);
 7812      return;
 7813    }
 7814
 7815    var velocityThreshold = 0.3;
 7816    var velocityX = e.gesture.velocityX;
 7817    var direction = e.gesture.direction;
 7818
 7819    // Going right, less than half, too slow (snap back)
 7820    if (ratio > 0 && ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {
 7821      self.openPercentage(0);
 7822    }
 7823
 7824    // Going left, more than half, too slow (snap back)
 7825    else if (ratio > 0.5 && direction == 'left' && velocityX < velocityThreshold) {
 7826      self.openPercentage(100);
 7827    }
 7828
 7829    // Going left, less than half, too slow (snap back)
 7830    else if (ratio < 0 && ratio > -0.5 && direction == 'left' && velocityX < velocityThreshold) {
 7831      self.openPercentage(0);
 7832    }
 7833
 7834    // Going right, more than half, too slow (snap back)
 7835    else if (ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {
 7836      self.openPercentage(-100);
 7837    }
 7838
 7839    // Going right, more than half, or quickly (snap open)
 7840    else if (direction == 'right' && ratio >= 0 && (ratio >= 0.5 || velocityX > velocityThreshold)) {
 7841      self.openPercentage(100);
 7842    }
 7843
 7844    // Going left, more than half, or quickly (span open)
 7845    else if (direction == 'left' && ratio <= 0 && (ratio <= -0.5 || velocityX > velocityThreshold)) {
 7846      self.openPercentage(-100);
 7847    }
 7848
 7849    // Snap back for safety
 7850    else {
 7851      self.openPercentage(0);
 7852    }
 7853  };
 7854
 7855  self.enableMenuWithBackViews = function(val) {
 7856    if (arguments.length) {
 7857      enableMenuWithBackViews = !!val;
 7858    }
 7859    return enableMenuWithBackViews;
 7860  };
 7861
 7862  self.isAsideExposed = function() {
 7863    return !!isAsideExposed;
 7864  };
 7865
 7866  self.exposeAside = function(shouldExposeAside) {
 7867    if (!(self.left && self.left.isEnabled) && !(self.right && self.right.isEnabled)) return;
 7868    self.close();
 7869
 7870    isAsideExposed = shouldExposeAside;
 7871    if ((self.left && self.left.isEnabled) && (self.right && self.right.isEnabled)) {
 7872      self.content.setMarginLeftAndRight(isAsideExposed ? self.left.width : 0, isAsideExposed ? self.right.width : 0);
 7873    } else if (self.left && self.left.isEnabled) {
 7874      // set the left marget width if it should be exposed
 7875      // otherwise set false so there's no left margin
 7876      self.content.setMarginLeft(isAsideExposed ? self.left.width : 0);
 7877    } else if (self.right && self.right.isEnabled) {
 7878      self.content.setMarginRight(isAsideExposed ? self.right.width : 0);
 7879    }
 7880    self.$scope.$emit('$ionicExposeAside', isAsideExposed);
 7881  };
 7882
 7883  self.activeAsideResizing = function(isResizing) {
 7884    $ionicBody.enableClass(isResizing, 'aside-resizing');
 7885  };
 7886
 7887  // End a drag with the given event
 7888  self._endDrag = function(e) {
 7889    if (isAsideExposed) return;
 7890
 7891    if (isDragging) {
 7892      self.snapToRest(e);
 7893    }
 7894    startX = null;
 7895    lastX = null;
 7896    offsetX = null;
 7897  };
 7898
 7899  // Handle a drag event
 7900  self._handleDrag = function(e) {
 7901    if (isAsideExposed || !$scope.dragContent) return;
 7902
 7903    // If we don't have start coords, grab and store them
 7904    if (!startX) {
 7905      startX = e.gesture.touches[0].pageX;
 7906      lastX = startX;
 7907    } else {
 7908      // Grab the current tap coords
 7909      lastX = e.gesture.touches[0].pageX;
 7910    }
 7911
 7912    // Calculate difference from the tap points
 7913    if (!isDragging && Math.abs(lastX - startX) > self.dragThresholdX) {
 7914      // if the difference is greater than threshold, start dragging using the current
 7915      // point as the starting point
 7916      startX = lastX;
 7917
 7918      isDragging = true;
 7919      // Initialize dragging
 7920      self.content.disableAnimation();
 7921      offsetX = self.getOpenAmount();
 7922    }
 7923
 7924    if (isDragging) {
 7925      self.openAmount(offsetX + (lastX - startX));
 7926      //self.content.setCanScroll(false);
 7927    }
 7928  };
 7929
 7930  self.canDragContent = function(canDrag) {
 7931    if (arguments.length) {
 7932      $scope.dragContent = !!canDrag;
 7933    }
 7934    return $scope.dragContent;
 7935  };
 7936
 7937  self.edgeThreshold = 25;
 7938  self.edgeThresholdEnabled = false;
 7939  self.edgeDragThreshold = function(value) {
 7940    if (arguments.length) {
 7941      if (isNumber(value) && value > 0) {
 7942        self.edgeThreshold = value;
 7943        self.edgeThresholdEnabled = true;
 7944      } else {
 7945        self.edgeThresholdEnabled = !!value;
 7946      }
 7947    }
 7948    return self.edgeThresholdEnabled;
 7949  };
 7950
 7951  self.isDraggableTarget = function(e) {
 7952    //Only restrict edge when sidemenu is closed and restriction is enabled
 7953    var shouldOnlyAllowEdgeDrag = self.edgeThresholdEnabled && !self.isOpen();
 7954    var startX = e.gesture.startEvent && e.gesture.startEvent.center &&
 7955      e.gesture.startEvent.center.pageX;
 7956
 7957    var dragIsWithinBounds = !shouldOnlyAllowEdgeDrag ||
 7958      startX <= self.edgeThreshold ||
 7959      startX >= self.content.element.offsetWidth - self.edgeThreshold;
 7960
 7961    var backView = $ionicHistory.backView();
 7962    var menuEnabled = enableMenuWithBackViews ? true : !backView;
 7963    if (!menuEnabled) {
 7964      var currentView = $ionicHistory.currentView() || {};
 7965      return (dragIsWithinBounds && (backView.historyId !== currentView.historyId));
 7966    }
 7967
 7968    return ($scope.dragContent || self.isOpen()) &&
 7969      dragIsWithinBounds &&
 7970      !e.gesture.srcEvent.defaultPrevented &&
 7971      menuEnabled &&
 7972      !e.target.tagName.match(/input|textarea|select|object|embed/i) &&
 7973      !e.target.isContentEditable &&
 7974      !(e.target.dataset ? e.target.dataset.preventScroll : e.target.getAttribute('data-prevent-scroll') == 'true');
 7975  };
 7976
 7977  $scope.sideMenuContentTranslateX = 0;
 7978
 7979  var deregisterBackButtonAction = noop;
 7980  var closeSideMenu = angular.bind(self, self.close);
 7981
 7982  $scope.$watch(function() {
 7983    return self.getOpenAmount() !== 0;
 7984  }, function(isOpen) {
 7985    deregisterBackButtonAction();
 7986    if (isOpen) {
 7987      deregisterBackButtonAction = $ionicPlatform.registerBackButtonAction(
 7988        closeSideMenu,
 7989        IONIC_BACK_PRIORITY.sideMenu
 7990      );
 7991    }
 7992  });
 7993
 7994  var deregisterInstance = $ionicSideMenuDelegate._registerInstance(
 7995    self, $attrs.delegateHandle, function() {
 7996      return $ionicHistory.isActiveScope($scope);
 7997    }
 7998  );
 7999
 8000  $scope.$on('$destroy', function() {
 8001    deregisterInstance();
 8002    deregisterBackButtonAction();
 8003    self.$scope = null;
 8004    if (self.content) {
 8005      self.content.setCanScroll(true);
 8006      self.content.element = null;
 8007      self.content = null;
 8008    }
 8009  });
 8010
 8011  self.initialize({
 8012    left: {
 8013      width: 275
 8014    },
 8015    right: {
 8016      width: 275
 8017    }
 8018  });
 8019
 8020}]);
 8021
 8022(function(ionic) {
 8023
 8024  var TRANSLATE32 = 'translate(32,32)';
 8025  var STROKE_OPACITY = 'stroke-opacity';
 8026  var ROUND = 'round';
 8027  var INDEFINITE = 'indefinite';
 8028  var DURATION = '750ms';
 8029  var NONE = 'none';
 8030  var SHORTCUTS = {
 8031    a: 'animate',
 8032    an: 'attributeName',
 8033    at: 'animateTransform',
 8034    c: 'circle',
 8035    da: 'stroke-dasharray',
 8036    os: 'stroke-dashoffset',
 8037    f: 'fill',
 8038    lc: 'stroke-linecap',
 8039    rc: 'repeatCount',
 8040    sw: 'stroke-width',
 8041    t: 'transform',
 8042    v: 'values'
 8043  };
 8044
 8045  var SPIN_ANIMATION = {
 8046    v: '0,32,32;360,32,32',
 8047    an: 'transform',
 8048    type: 'rotate',
 8049    rc: INDEFINITE,
 8050    dur: DURATION
 8051  };
 8052
 8053  function createSvgElement(tagName, data, parent, spinnerName) {
 8054    var ele = document.createElement(SHORTCUTS[tagName] || tagName);
 8055    var k, x, y;
 8056
 8057    for (k in data) {
 8058
 8059      if (angular.isArray(data[k])) {
 8060        for (x = 0; x < data[k].length; x++) {
 8061          if (data[k][x].fn) {
 8062            for (y = 0; y < data[k][x].t; y++) {
 8063              createSvgElement(k, data[k][x].fn(y, spinnerName), ele, spinnerName);
 8064            }
 8065          } else {
 8066            createSvgElement(k, data[k][x], ele, spinnerName);
 8067          }
 8068        }
 8069
 8070      } else {
 8071        setSvgAttribute(ele, k, data[k]);
 8072      }
 8073    }
 8074
 8075    parent.appendChild(ele);
 8076  }
 8077
 8078  function setSvgAttribute(ele, k, v) {
 8079    ele.setAttribute(SHORTCUTS[k] || k, v);
 8080  }
 8081
 8082  function animationValues(strValues, i) {
 8083    var values = strValues.split(';');
 8084    var back = values.slice(i);
 8085    var front = values.slice(0, values.length - back.length);
 8086    values = back.concat(front).reverse();
 8087    return values.join(';') + ';' + values[0];
 8088  }
 8089
 8090  var IOS_SPINNER = {
 8091    sw: 4,
 8092    lc: ROUND,
 8093    line: [{
 8094      fn: function(i, spinnerName) {
 8095        return {
 8096          y1: spinnerName == 'ios' ? 17 : 12,
 8097          y2: spinnerName == 'ios' ? 29 : 20,
 8098          t: TRANSLATE32 + ' rotate(' + (30 * i + (i < 6 ? 180 : -180)) + ')',
 8099          a: [{
 8100            fn: function() {
 8101              return {
 8102                an: STROKE_OPACITY,
 8103                dur: DURATION,
 8104                v: animationValues('0;.1;.15;.25;.35;.45;.55;.65;.7;.85;1', i),
 8105                rc: INDEFINITE
 8106              };
 8107            },
 8108            t: 1
 8109          }]
 8110        };
 8111      },
 8112      t: 12
 8113    }]
 8114  };
 8115
 8116  var spinners = {
 8117
 8118    android: {
 8119      c: [{
 8120        sw: 6,
 8121        da: 128,
 8122        os: 82,
 8123        r: 26,
 8124        cx: 32,
 8125        cy: 32,
 8126        f: NONE
 8127      }]
 8128    },
 8129
 8130    ios: IOS_SPINNER,
 8131
 8132    'ios-small': IOS_SPINNER,
 8133
 8134    bubbles: {
 8135      sw: 0,
 8136      c: [{
 8137        fn: function(i) {
 8138          return {
 8139            cx: 24 * Math.cos(2 * Math.PI * i / 8),
 8140            cy: 24 * Math.sin(2 * Math.PI * i / 8),
 8141            t: TRANSLATE32,
 8142            a: [{
 8143              fn: function() {
 8144                return {
 8145                  an: 'r',
 8146                  dur: DURATION,
 8147                  v: animationValues('1;2;3;4;5;6;7;8', i),
 8148                  rc: INDEFINITE
 8149                };
 8150              },
 8151              t: 1
 8152            }]
 8153          };
 8154        },
 8155        t: 8
 8156      }]
 8157    },
 8158
 8159    circles: {
 8160
 8161      c: [{
 8162        fn: function(i) {
 8163          return {
 8164            r: 5,
 8165            cx: 24 * Math.cos(2 * Math.PI * i / 8),
 8166            cy: 24 * Math.sin(2 * Math.PI * i / 8),
 8167            t: TRANSLATE32,
 8168            sw: 0,
 8169            a: [{
 8170              fn: function() {
 8171                return {
 8172                  an: 'fill-opacity',
 8173                  dur: DURATION,
 8174                  v: animationValues('.3;.3;.3;.4;.7;.85;.9;1', i),
 8175                  rc: INDEFINITE
 8176                };
 8177              },
 8178              t: 1
 8179            }]
 8180          };
 8181        },
 8182        t: 8
 8183      }]
 8184    },
 8185
 8186    crescent: {
 8187      c: [{
 8188        sw: 4,
 8189        da: 128,
 8190        os: 82,
 8191        r: 26,
 8192        cx: 32,
 8193        cy: 32,
 8194        f: NONE,
 8195        at: [SPIN_ANIMATION]
 8196      }]
 8197    },
 8198
 8199    dots: {
 8200
 8201      c: [{
 8202        fn: function(i) {
 8203          return {
 8204            cx: 16 + (16 * i),
 8205            cy: 32,
 8206            sw: 0,
 8207            a: [{
 8208              fn: function() {
 8209                return {
 8210                  an: 'fill-opacity',
 8211                  dur: DURATION,
 8212                  v: animationValues('.5;.6;.8;1;.8;.6;.5', i),
 8213                  rc: INDEFINITE
 8214                };
 8215              },
 8216              t: 1
 8217            }, {
 8218              fn: function() {
 8219                return {
 8220                  an: 'r',
 8221                  dur: DURATION,
 8222                  v: animationValues('4;5;6;5;4;3;3', i),
 8223                  rc: INDEFINITE
 8224                };
 8225              },
 8226              t: 1
 8227            }]
 8228          };
 8229        },
 8230        t: 3
 8231      }]
 8232    },
 8233
 8234    lines: {
 8235      sw: 7,
 8236      lc: ROUND,
 8237      line: [{
 8238        fn: function(i) {
 8239          return {
 8240            x1: 10 + (i * 14),
 8241            x2: 10 + (i * 14),
 8242            a: [{
 8243              fn: function() {
 8244                return {
 8245                  an: 'y1',
 8246                  dur: DURATION,
 8247                  v: animationValues('16;18;28;18;16', i),
 8248                  rc: INDEFINITE
 8249                };
 8250              },
 8251              t: 1
 8252            }, {
 8253              fn: function() {
 8254                return {
 8255                  an: 'y2',
 8256                  dur: DURATION,
 8257                  v: animationValues('48;44;36;46;48', i),
 8258                  rc: INDEFINITE
 8259                };
 8260              },
 8261              t: 1
 8262            }, {
 8263              fn: function() {
 8264                return {
 8265                  an: STROKE_OPACITY,
 8266                  dur: DURATION,
 8267                  v: animationValues('1;.8;.5;.4;1', i),
 8268                  rc: INDEFINITE
 8269                };
 8270              },
 8271              t: 1
 8272            }]
 8273          };
 8274        },
 8275        t: 4
 8276      }]
 8277    },
 8278
 8279    ripple: {
 8280      f: NONE,
 8281      'fill-rule': 'evenodd',
 8282      sw: 3,
 8283      circle: [{
 8284        fn: function(i) {
 8285          return {
 8286            cx: 32,
 8287            cy: 32,
 8288            a: [{
 8289              fn: function() {
 8290                return {
 8291                  an: 'r',
 8292                  begin: (i * -1) + 's',
 8293                  dur: '2s',
 8294                  v: '0;24',
 8295                  keyTimes: '0;1',
 8296                  keySplines: '0.1,0.2,0.3,1',
 8297                  calcMode: 'spline',
 8298                  rc: INDEFINITE
 8299                };
 8300              },
 8301              t: 1
 8302            }, {
 8303              fn: function() {
 8304                return {
 8305                  an: STROKE_OPACITY,
 8306                  begin: (i * -1) + 's',
 8307                  dur: '2s',
 8308                  v: '.2;1;.2;0',
 8309                  rc: INDEFINITE
 8310                };
 8311              },
 8312              t: 1
 8313            }]
 8314          };
 8315        },
 8316        t: 2
 8317      }]
 8318    },
 8319
 8320    spiral: {
 8321      defs: [{
 8322        linearGradient: [{
 8323          id: 'sGD',
 8324          gradientUnits: 'userSpaceOnUse',
 8325          x1: 55, y1: 46, x2: 2, y2: 46,
 8326          stop: [{
 8327            offset: 0.1,
 8328            class: 'stop1'
 8329          }, {
 8330            offset: 1,
 8331            class: 'stop2'
 8332          }]
 8333        }]
 8334      }],
 8335      g: [{
 8336        sw: 4,
 8337        lc: ROUND,
 8338        f: NONE,
 8339        path: [{
 8340          stroke: 'url(#sGD)',
 8341          d: 'M4,32 c0,15,12,28,28,28c8,0,16-4,21-9'
 8342        }, {
 8343          d: 'M60,32 C60,16,47.464,4,32,4S4,16,4,32'
 8344        }],
 8345        at: [SPIN_ANIMATION]
 8346      }]
 8347    }
 8348
 8349  };
 8350
 8351  var animations = {
 8352
 8353    android: function(ele) {
 8354      // Note that this is called as a function, not a constructor.
 8355      var self = {};
 8356
 8357      this.stop = false;
 8358
 8359      var rIndex = 0;
 8360      var rotateCircle = 0;
 8361      var startTime;
 8362      var svgEle = ele.querySelector('g');
 8363      var circleEle = ele.querySelector('circle');
 8364
 8365      function run() {
 8366        if (self.stop) return;
 8367
 8368        var v = easeInOutCubic(Date.now() - startTime, 650);
 8369        var scaleX = 1;
 8370        var translateX = 0;
 8371        var dasharray = (188 - (58 * v));
 8372        var dashoffset = (182 - (182 * v));
 8373
 8374        if (rIndex % 2) {
 8375          scaleX = -1;
 8376          translateX = -64;
 8377          dasharray = (128 - (-58 * v));
 8378          dashoffset = (182 * v);
 8379        }
 8380
 8381        var rotateLine = [0, -101, -90, -11, -180, 79, -270, -191][rIndex];
 8382
 8383        setSvgAttribute(circleEle, 'da', Math.max(Math.min(dasharray, 188), 128));
 8384        setSvgAttribute(circleEle, 'os', Math.max(Math.min(dashoffset, 182), 0));
 8385        setSvgAttribute(circleEle, 't', 'scale(' + scaleX + ',1) translate(' + translateX + ',0) rotate(' + rotateLine + ',32,32)');
 8386
 8387        rotateCircle += 4.1;
 8388        if (rotateCircle > 359) rotateCircle = 0;
 8389        setSvgAttribute(svgEle, 't', 'rotate(' + rotateCircle + ',32,32)');
 8390
 8391        if (v >= 1) {
 8392          rIndex++;
 8393          if (rIndex > 7) rIndex = 0;
 8394          startTime = Date.now();
 8395        }
 8396
 8397        ionic.requestAnimationFrame(run);
 8398      }
 8399
 8400      return function() {
 8401        startTime = Date.now();
 8402        run();
 8403        return self;
 8404      };
 8405
 8406    }
 8407
 8408  };
 8409
 8410  function easeInOutCubic(t, c) {
 8411    t /= c / 2;
 8412    if (t < 1) return 1 / 2 * t * t * t;
 8413    t -= 2;
 8414    return 1 / 2 * (t * t * t + 2);
 8415  }
 8416
 8417
 8418  IonicModule
 8419  .controller('$ionicSpinner', [
 8420    '$element',
 8421    '$attrs',
 8422    '$ionicConfig',
 8423  function($element, $attrs, $ionicConfig) {
 8424    var spinnerName, anim;
 8425
 8426    this.init = function() {
 8427      spinnerName = $attrs.icon || $ionicConfig.spinner.icon();
 8428
 8429      var container = document.createElement('div');
 8430      createSvgElement('svg', {
 8431        viewBox: '0 0 64 64',
 8432        g: [spinners[spinnerName]]
 8433      }, container, spinnerName);
 8434
 8435      // Specifically for animations to work,
 8436      // Android 4.3 and below requires the element to be
 8437      // added as an html string, rather than dynmically
 8438      // building up the svg element and appending it.
 8439      $element.html(container.innerHTML);
 8440
 8441      this.start();
 8442
 8443      return spinnerName;
 8444    };
 8445
 8446    this.start = function() {
 8447      animations[spinnerName] && (anim = animations[spinnerName]($element[0])());
 8448    };
 8449
 8450    this.stop = function() {
 8451      animations[spinnerName] && (anim.stop = true);
 8452    };
 8453
 8454  }]);
 8455
 8456})(ionic);
 8457
 8458IonicModule
 8459.controller('$ionicTab', [
 8460  '$scope',
 8461  '$ionicHistory',
 8462  '$attrs',
 8463  '$location',
 8464  '$state',
 8465function($scope, $ionicHistory, $attrs, $location, $state) {
 8466  this.$scope = $scope;
 8467
 8468  //All of these exposed for testing
 8469  this.hrefMatchesState = function() {
 8470    return $attrs.href && $location.path().indexOf(
 8471      $attrs.href.replace(/^#/, '').replace(/\/$/, '')
 8472    ) === 0;
 8473  };
 8474  this.srefMatchesState = function() {
 8475    return $attrs.uiSref && $state.includes($attrs.uiSref.split('(')[0]);
 8476  };
 8477  this.navNameMatchesState = function() {
 8478    return this.navViewName && $ionicHistory.isCurrentStateNavView(this.navViewName);
 8479  };
 8480
 8481  this.tabMatchesState = function() {
 8482    return this.hrefMatchesState() || this.srefMatchesState() || this.navNameMatchesState();
 8483  };
 8484}]);
 8485
 8486IonicModule
 8487.controller('$ionicTabs', [
 8488  '$scope',
 8489  '$element',
 8490  '$ionicHistory',
 8491function($scope, $element, $ionicHistory) {
 8492  var self = this;
 8493  var selectedTab = null;
 8494  var previousSelectedTab = null;
 8495  var selectedTabIndex;
 8496  var isVisible = true;
 8497  self.tabs = [];
 8498
 8499  self.selectedIndex = function() {
 8500    return self.tabs.indexOf(selectedTab);
 8501  };
 8502  self.selectedTab = function() {
 8503    return selectedTab;
 8504  };
 8505  self.previousSelectedTab = function() {
 8506    return previousSelectedTab;
 8507  };
 8508
 8509  self.add = function(tab) {
 8510    $ionicHistory.registerHistory(tab);
 8511    self.tabs.push(tab);
 8512  };
 8513
 8514  self.remove = function(tab) {
 8515    var tabIndex = self.tabs.indexOf(tab);
 8516    if (tabIndex === -1) {
 8517      return;
 8518    }
 8519    //Use a field like '$tabSelected' so developers won't accidentally set it in controllers etc
 8520    if (tab.$tabSelected) {
 8521      self.deselect(tab);
 8522      //Try to select a new tab if we're removing a tab
 8523      if (self.tabs.length === 1) {
 8524        //Do nothing if there are no other tabs to select
 8525      } else {
 8526        //Select previous tab if it's the last tab, else select next tab
 8527        var newTabIndex = tabIndex === self.tabs.length - 1 ? tabIndex - 1 : tabIndex + 1;
 8528        self.select(self.tabs[newTabIndex]);
 8529      }
 8530    }
 8531    self.tabs.splice(tabIndex, 1);
 8532  };
 8533
 8534  self.deselect = function(tab) {
 8535    if (tab.$tabSelected) {
 8536      previousSelectedTab = selectedTab;
 8537      selectedTab = selectedTabIndex = null;
 8538      tab.$tabSelected = false;
 8539      (tab.onDeselect || noop)();
 8540      tab.$broadcast && tab.$broadcast('$ionicHistory.deselect');
 8541    }
 8542  };
 8543
 8544  self.select = function(tab, shouldEmitEvent) {
 8545    var tabIndex;
 8546    if (isNumber(tab)) {
 8547      tabIndex = tab;
 8548      if (tabIndex >= self.tabs.length) return;
 8549      tab = self.tabs[tabIndex];
 8550    } else {
 8551      tabIndex = self.tabs.indexOf(tab);
 8552    }
 8553
 8554    if (arguments.length === 1) {
 8555      shouldEmitEvent = !!(tab.navViewName || tab.uiSref);
 8556    }
 8557
 8558    if (selectedTab && selectedTab.$historyId == tab.$historyId) {
 8559      if (shouldEmitEvent) {
 8560        $ionicHistory.goToHistoryRoot(tab.$historyId);
 8561      }
 8562
 8563    } else if (selectedTabIndex !== tabIndex) {
 8564      forEach(self.tabs, function(tab) {
 8565        self.deselect(tab);
 8566      });
 8567
 8568      selectedTab = tab;
 8569      selectedTabIndex = tabIndex;
 8570
 8571      if (self.$scope && self.$scope.$parent) {
 8572        self.$scope.$parent.$activeHistoryId = tab.$historyId;
 8573      }
 8574
 8575      //Use a funny name like $tabSelected so the developer doesn't overwrite the var in a child scope
 8576      tab.$tabSelected = true;
 8577      (tab.onSelect || noop)();
 8578
 8579      if (shouldEmitEvent) {
 8580        $scope.$emit('$ionicHistory.change', {
 8581          type: 'tab',
 8582          tabIndex: tabIndex,
 8583          historyId: tab.$historyId,
 8584          navViewName: tab.navViewName,
 8585          hasNavView: !!tab.navViewName,
 8586          title: tab.title,
 8587          url: tab.href,
 8588          uiSref: tab.uiSref
 8589        });
 8590      }
 8591
 8592      $scope.$broadcast("tabSelected", { selectedTab: tab, selectedTabIndex: tabIndex});
 8593    }
 8594  };
 8595
 8596  self.hasActiveScope = function() {
 8597    for (var x = 0; x < self.tabs.length; x++) {
 8598      if ($ionicHistory.isActiveScope(self.tabs[x])) {
 8599        return true;
 8600      }
 8601    }
 8602    return false;
 8603  };
 8604
 8605  self.showBar = function(show) {
 8606    if (arguments.length) {
 8607      if (show) {
 8608        $element.removeClass('tabs-item-hide');
 8609      } else {
 8610        $element.addClass('tabs-item-hide');
 8611      }
 8612      isVisible = !!show;
 8613    }
 8614    return isVisible;
 8615  };
 8616}]);
 8617
 8618IonicModule
 8619.controller('$ionicView', [
 8620  '$scope',
 8621  '$element',
 8622  '$attrs',
 8623  '$compile',
 8624  '$rootScope',
 8625function($scope, $element, $attrs, $compile, $rootScope) {
 8626  var self = this;
 8627  var navElementHtml = {};
 8628  var navViewCtrl;
 8629  var navBarDelegateHandle;
 8630  var hasViewHeaderBar;
 8631  var deregisters = [];
 8632  var viewTitle;
 8633
 8634  var deregIonNavBarInit = $scope.$on('ionNavBar.init', function(ev, delegateHandle) {
 8635    // this view has its own ion-nav-bar, remember the navBarDelegateHandle for this view
 8636    ev.stopPropagation();
 8637    navBarDelegateHandle = delegateHandle;
 8638  });
 8639
 8640
 8641  self.init = function() {
 8642    deregIonNavBarInit();
 8643
 8644    var modalCtrl = $element.inheritedData('$ionModalController');
 8645    navViewCtrl = $element.inheritedData('$ionNavViewController');
 8646
 8647    // don't bother if inside a modal or there's no parent navView
 8648    if (!navViewCtrl || modalCtrl) return;
 8649
 8650    // add listeners for when this view changes
 8651    $scope.$on('$ionicView.beforeEnter', self.beforeEnter);
 8652    $scope.$on('$ionicView.afterEnter', afterEnter);
 8653    $scope.$on('$ionicView.beforeLeave', deregisterFns);
 8654  };
 8655
 8656  self.beforeEnter = function(ev, transData) {
 8657    // this event was emitted, starting at intial ion-view, then bubbles up
 8658    // only the first ion-view should do something with it, parent ion-views should ignore
 8659    if (transData && !transData.viewNotified) {
 8660      transData.viewNotified = true;
 8661
 8662      if (!$rootScope.$$phase) $scope.$digest();
 8663      viewTitle = isDefined($attrs.viewTitle) ? $attrs.viewTitle : $attrs.title;
 8664
 8665      var navBarItems = {};
 8666      for (var n in navElementHtml) {
 8667        navBarItems[n] = generateNavBarItem(navElementHtml[n]);
 8668      }
 8669
 8670      navViewCtrl.beforeEnter(extend(transData, {
 8671        title: viewTitle,
 8672        showBack: !attrTrue('hideBackButton'),
 8673        navBarItems: navBarItems,
 8674        navBarDelegate: navBarDelegateHandle || null,
 8675        showNavBar: !attrTrue('hideNavBar'),
 8676        hasHeaderBar: !!hasViewHeaderBar
 8677      }));
 8678
 8679      // make sure any existing observers are cleaned up
 8680      deregisterFns();
 8681    }
 8682  };
 8683
 8684
 8685  function afterEnter() {
 8686    // only listen for title updates after it has entered
 8687    // but also deregister the observe before it leaves
 8688    var viewTitleAttr = isDefined($attrs.viewTitle) && 'viewTitle' || isDefined($attrs.title) && 'title';
 8689    if (viewTitleAttr) {
 8690      titleUpdate($attrs[viewTitleAttr]);
 8691      deregisters.push($attrs.$observe(viewTitleAttr, titleUpdate));
 8692    }
 8693
 8694    if (isDefined($attrs.hideBackButton)) {
 8695      deregisters.push($scope.$watch($attrs.hideBackButton, function(val) {
 8696        navViewCtrl.showBackButton(!val);
 8697      }));
 8698    }
 8699
 8700    if (isDefined($attrs.hideNavBar)) {
 8701      deregisters.push($scope.$watch($attrs.hideNavBar, function(val) {
 8702        navViewCtrl.showBar(!val);
 8703      }));
 8704    }
 8705  }
 8706
 8707
 8708  function titleUpdate(newTitle) {
 8709    if (isDefined(newTitle) && newTitle !== viewTitle) {
 8710      viewTitle = newTitle;
 8711      navViewCtrl.title(viewTitle);
 8712    }
 8713  }
 8714
 8715
 8716  function deregisterFns() {
 8717    // remove all existing $attrs.$observe's
 8718    for (var x = 0; x < deregisters.length; x++) {
 8719      deregisters[x]();
 8720    }
 8721    deregisters = [];
 8722  }
 8723
 8724
 8725  function generateNavBarItem(html) {
 8726    if (html) {
 8727      // every time a view enters we need to recreate its view buttons if they exist
 8728      return $compile(html)($scope.$new());
 8729    }
 8730  }
 8731
 8732
 8733  function attrTrue(key) {
 8734    return !!$scope.$eval($attrs[key]);
 8735  }
 8736
 8737
 8738  self.navElement = function(type, html) {
 8739    navElementHtml[type] = html;
 8740  };
 8741
 8742}]);
 8743
 8744/*
 8745 * We don't document the ionActionSheet directive, we instead document
 8746 * the $ionicActionSheet service
 8747 */
 8748IonicModule
 8749.directive('ionActionSheet', ['$document', function($document) {
 8750  return {
 8751    restrict: 'E',
 8752    scope: true,
 8753    replace: true,
 8754    link: function($scope, $element) {
 8755
 8756      var keyUp = function(e) {
 8757        if (e.which == 27) {
 8758          $scope.cancel();
 8759          $scope.$apply();
 8760        }
 8761      };
 8762
 8763      var backdropClick = function(e) {
 8764        if (e.target == $element[0]) {
 8765          $scope.cancel();
 8766          $scope.$apply();
 8767        }
 8768      };
 8769      $scope.$on('$destroy', function() {
 8770        $element.remove();
 8771        $document.unbind('keyup', keyUp);
 8772      });
 8773
 8774      $document.bind('keyup', keyUp);
 8775      $element.bind('click', backdropClick);
 8776    },
 8777    template: '<div class="action-sheet-backdrop">' +
 8778                '<div class="action-sheet-wrapper">' +
 8779                  '<div class="action-sheet" ng-class="{\'action-sheet-has-icons\': $actionSheetHasIcon}">' +
 8780                    '<div class="action-sheet-group action-sheet-options">' +
 8781                      '<div class="action-sheet-title" ng-if="titleText" ng-bind-html="titleText"></div>' +
 8782                      '<button class="button action-sheet-option" ng-click="buttonClicked($index)" ng-class="b.className" ng-repeat="b in buttons" ng-bind-html="b.text"></button>' +
 8783                      '<button class="button destructive action-sheet-destructive" ng-if="destructiveText" ng-click="destructiveButtonClicked()" ng-bind-html="destructiveText"></button>' +
 8784                    '</div>' +
 8785                    '<div class="action-sheet-group action-sheet-cancel" ng-if="cancelText">' +
 8786                      '<button class="button" ng-click="cancel()" ng-bind-html="cancelText"></button>' +
 8787                    '</div>' +
 8788                  '</div>' +
 8789                '</div>' +
 8790              '</div>'
 8791  };
 8792}]);
 8793
 8794
 8795/**
 8796 * @ngdoc directive
 8797 * @name ionCheckbox
 8798 * @module ionic
 8799 * @restrict E
 8800 * @codepen hqcju
 8801 * @description
 8802 * The checkbox is no different than the HTML checkbox input, except it's styled differently.
 8803 *
 8804 * The checkbox behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]).
 8805 *
 8806 * @usage
 8807 * ```html
 8808 * <ion-checkbox ng-model="isChecked">Checkbox Label</ion-checkbox>
 8809 * ```
 8810 */
 8811
 8812IonicModule
 8813.directive('ionCheckbox', ['$ionicConfig', function($ionicConfig) {
 8814  return {
 8815    restrict: 'E',
 8816    replace: true,
 8817    require: '?ngModel',
 8818    transclude: true,
 8819    template:
 8820      '<label class="item item-checkbox">' +
 8821        '<div class="checkbox checkbox-input-hidden disable-pointer-events">' +
 8822          '<input type="checkbox">' +
 8823          '<i class="checkbox-icon"></i>' +
 8824        '</div>' +
 8825        '<div class="item-content disable-pointer-events" ng-transclude></div>' +
 8826      '</label>',
 8827    compile: function(element, attr) {
 8828      var input = element.find('input');
 8829      forEach({
 8830        'name': attr.name,
 8831        'ng-value': attr.ngValue,
 8832        'ng-model': attr.ngModel,
 8833        'ng-checked': attr.ngChecked,
 8834        'ng-disabled': attr.ngDisabled,
 8835        'ng-true-value': attr.ngTrueValue,
 8836        'ng-false-value': attr.ngFalseValue,
 8837        'ng-change': attr.ngChange,
 8838        'ng-required': attr.ngRequired,
 8839        'required': attr.required
 8840      }, function(value, name) {
 8841        if (isDefined(value)) {
 8842          input.attr(name, value);
 8843        }
 8844      });
 8845      var checkboxWrapper = element[0].querySelector('.checkbox');
 8846      checkboxWrapper.classList.add('checkbox-' + $ionicConfig.form.checkbox());
 8847    }
 8848  };
 8849}]);
 8850
 8851
 8852/**
 8853 * @ngdoc directive
 8854 * @restrict A
 8855 * @name collectionRepeat
 8856 * @module ionic
 8857 * @codepen 7ec1ec58f2489ab8f359fa1a0fe89c15
 8858 * @description
 8859 * `collection-repeat` allows an app to show huge lists of items much more performantly than
 8860 * `ng-repeat`.
 8861 *
 8862 * It renders into the DOM only as many items as are currently visible.
 8863 *
 8864 * This means that on a phone screen that can fit eight items, only the eight items matching
 8865 * the current scroll position will be rendered.
 8866 *
 8867 * **The Basics**:
 8868 *
 8869 * - The data given to collection-repeat must be an array.
 8870 * - If the `item-height` and `item-width` attributes are not supplied, it will be assumed that
 8871 *   every item in the list has the same dimensions as the first item.
 8872 * - Don't use angular one-time binding (`::`) with collection-repeat. The scope of each item is
 8873 *   assigned new data and re-digested as you scroll. Bindings need to update, and one-time bindings
 8874 *   won't.
 8875 *
 8876 * **Performance Tips**:
 8877 *
 8878 * - The iOS webview has a performance bottleneck when switching out `<img src>` attributes.
 8879 *   To increase performance of images on iOS, cache your images in advance and,
 8880 *   if possible, lower the number of unique images. We're working on [a solution](https://github.com/driftyco/ionic/issues/3194).
 8881 *
 8882 * @usage
 8883 * #### Basic Item List ([codepen](http://codepen.io/ionic/pen/0c2c35a34a8b18ad4d793fef0b081693))
 8884 * ```html
 8885 * <ion-content>
 8886 *   <ion-item collection-repeat="item in items">
 8887 *     {% raw %}{{item}}{% endraw %}
 8888 *   </ion-item>
 8889 * </ion-content>
 8890 * ```
 8891 *
 8892 * #### Grid of Images ([codepen](http://codepen.io/ionic/pen/5515d4efd9d66f780e96787387f41664))
 8893 * ```html
 8894 * <ion-content>
 8895 *   <img collection-repeat="photo in photos"
 8896 *     item-width="33%"
 8897 *     item-height="200px"
 8898 *     ng-src="{% raw %}{{photo.url}}{% endraw %}">
 8899 * </ion-content>
 8900 * ```
 8901 *
 8902 * #### Horizontal Scroller, Dynamic Item Width ([codepen](http://codepen.io/ionic/pen/67cc56b349124a349acb57a0740e030e))
 8903 * ```html
 8904 * <ion-content>
 8905 *   <h2>Available Kittens:</h2>
 8906 *   <ion-scroll direction="x" class="available-scroller">
 8907 *     <div class="photo" collection-repeat="photo in main.photos"
 8908 *        item-height="250" item-width="photo.width + 30">
 8909 *        <img ng-src="{% raw %}{{photo.src}}{% endraw %}">
 8910 *     </div>
 8911 *   </ion-scroll>
 8912 * </ion-content>
 8913 * ```
 8914 *
 8915 * @param {expression} collection-repeat The expression indicating how to enumerate a collection,
 8916 *   of the format  `variable in expression` – where variable is the user defined loop variable
 8917 *   and `expression` is a scope expression giving the collection to enumerate.
 8918 *   For example: `album in artist.albums` or `album in artist.albums | orderBy:'name'`.
 8919 * @param {expression=} item-width The width of the repeated element. The expression must return
 8920 *   a number (pixels) or a percentage. Defaults to the width of the first item in the list.
 8921 *   (previously named collection-item-width)
 8922 * @param {expression=} item-height The height of the repeated element. The expression must return
 8923 *   a number (pixels) or a percentage. Defaults to the height of the first item in the list.
 8924 *   (previously named collection-item-height)
 8925 * @param {number=} item-render-buffer The number of items to load before and after the visible
 8926 *   items in the list. Default 3. Tip: set this higher if you have lots of images to preload, but
 8927 *   don't set it too high or you'll see performance loss.
 8928 * @param {boolean=} force-refresh-images Force images to refresh as you scroll. This fixes a problem
 8929 *   where, when an element is interchanged as scrolling, its image will still have the old src
 8930 *   while the new src loads. Setting this to true comes with a small performance loss.
 8931 */
 8932
 8933IonicModule
 8934.directive('collectionRepeat', CollectionRepeatDirective)
 8935.factory('$ionicCollectionManager', RepeatManagerFactory);
 8936
 8937var ONE_PX_TRANSPARENT_IMG_SRC = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
 8938var WIDTH_HEIGHT_REGEX = /height:.*?px;\s*width:.*?px/;
 8939var DEFAULT_RENDER_BUFFER = 3;
 8940
 8941CollectionRepeatDirective.$inject = ['$ionicCollectionManager', '$parse', '$window', '$$rAF', '$rootScope', '$timeout'];
 8942function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$rAF, $rootScope, $timeout) {
 8943  return {
 8944    restrict: 'A',
 8945    priority: 1000,
 8946    transclude: 'element',
 8947    $$tlb: true,
 8948    require: '^^$ionicScroll',
 8949    link: postLink
 8950  };
 8951
 8952  function postLink(scope, element, attr, scrollCtrl, transclude) {
 8953    var scrollView = scrollCtrl.scrollView;
 8954    var node = element[0];
 8955    var containerNode = angular.element('<div class="collection-repeat-container">')[0];
 8956    node.parentNode.replaceChild(containerNode, node);
 8957
 8958    if (scrollView.options.scrollingX && scrollView.options.scrollingY) {
 8959      throw new Error("collection-repeat expected a parent x or y scrollView, not " +
 8960                      "an xy scrollView.");
 8961    }
 8962
 8963    var repeatExpr = attr.collectionRepeat;
 8964    var match = repeatExpr.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
 8965    if (!match) {
 8966      throw new Error("collection-repeat expected expression in form of '_item_ in " +
 8967                      "_collection_[ track by _id_]' but got '" + attr.collectionRepeat + "'.");
 8968    }
 8969    var keyExpr = match[1];
 8970    var listExpr = match[2];
 8971    var listGetter = $parse(listExpr);
 8972    var heightData = {};
 8973    var widthData = {};
 8974    var computedStyleDimensions = {};
 8975    var data = [];
 8976    var repeatManager;
 8977
 8978    // attr.collectionBufferSize is deprecated
 8979    var renderBufferExpr = attr.itemRenderBuffer || attr.collectionBufferSize;
 8980    var renderBuffer = angular.isDefined(renderBufferExpr) ?
 8981      parseInt(renderBufferExpr) :
 8982      DEFAULT_RENDER_BUFFER;
 8983
 8984    // attr.collectionItemHeight is deprecated
 8985    var heightExpr = attr.itemHeight || attr.collectionItemHeight;
 8986    // attr.collectionItemWidth is deprecated
 8987    var widthExpr = attr.itemWidth || attr.collectionItemWidth;
 8988
 8989    var afterItemsContainer = initAfterItemsContainer();
 8990
 8991    var changeValidator = makeChangeValidator();
 8992    initDimensions();
 8993
 8994    // Dimensions are refreshed on resize or data change.
 8995    scrollCtrl.$element.on('scroll-resize', refreshDimensions);
 8996
 8997    angular.element($window).on('resize', onResize);
 8998    var unlistenToExposeAside = $rootScope.$on('$ionicExposeAside', ionic.animationFrameThrottle(function() {
 8999      scrollCtrl.scrollView.resize();
 9000      onResize();
 9001    }));
 9002    $timeout(refreshDimensions, 0, false);
 9003
 9004    function onResize() {
 9005      if (changeValidator.resizeRequiresRefresh(scrollView.__clientWidth, scrollView.__clientHeight)) {
 9006        refreshDimensions();
 9007      }
 9008    }
 9009
 9010    scope.$watchCollection(listGetter, function(newValue) {
 9011      data = newValue || (newValue = []);
 9012      if (!angular.isArray(newValue)) {
 9013        throw new Error("collection-repeat expected an array for '" + listExpr + "', " +
 9014          "but got a " + typeof value);
 9015      }
 9016      // Wait for this digest to end before refreshing everything.
 9017      scope.$$postDigest(function() {
 9018        getRepeatManager().setData(data);
 9019        if (changeValidator.dataChangeRequiresRefresh(data)) refreshDimensions();
 9020      });
 9021    });
 9022
 9023    scope.$on('$destroy', function() {
 9024      angular.element($window).off('resize', onResize);
 9025      unlistenToExposeAside();
 9026      scrollCtrl.$element && scrollCtrl.$element.off('scroll-resize', refreshDimensions);
 9027
 9028      computedStyleNode && computedStyleNode.parentNode &&
 9029        computedStyleNode.parentNode.removeChild(computedStyleNode);
 9030      computedStyleScope && computedStyleScope.$destroy();
 9031      computedStyleScope = computedStyleNode = null;
 9032
 9033      repeatManager && repeatManager.destroy();
 9034      repeatManager = null;
 9035    });
 9036
 9037    function makeChangeValidator() {
 9038      var self;
 9039      return (self = {
 9040        dataLength: 0,
 9041        width: 0,
 9042        height: 0,
 9043        // A resize triggers a refresh only if we have data, the scrollView has size,
 9044        // and the size has changed.
 9045        resizeRequiresRefresh: function(newWidth, newHeight) {
 9046          var requiresRefresh = self.dataLength && newWidth && newHeight &&
 9047            (newWidth !== self.width || newHeight !== self.height);
 9048
 9049          self.width = newWidth;
 9050          self.height = newHeight;
 9051
 9052          return !!requiresRefresh;
 9053        },
 9054        // A change in data only triggers a refresh if the data has length, or if the data's
 9055        // length is less than before.
 9056        dataChangeRequiresRefresh: function(newData) {
 9057          var requiresRefresh = newData.length > 0 || newData.length < self.dataLength;
 9058
 9059          self.dataLength = newData.length;
 9060
 9061          return !!requiresRefresh;
 9062        }
 9063      });
 9064    }
 9065
 9066    function getRepeatManager() {
 9067      return repeatManager || (repeatManager = new $ionicCollectionManager({
 9068        afterItemsNode: afterItemsContainer[0],
 9069        containerNode: containerNode,
 9070        heightData: heightData,
 9071        widthData: widthData,
 9072        forceRefreshImages: !!(isDefined(attr.forceRefreshImages) && attr.forceRefreshImages !== 'false'),
 9073        keyExpression: keyExpr,
 9074        renderBuffer: renderBuffer,
 9075        scope: scope,
 9076        scrollView: scrollCtrl.scrollView,
 9077        transclude: transclude
 9078      }));
 9079    }
 9080
 9081    function initAfterItemsContainer() {
 9082      var container = angular.element(
 9083        scrollView.__content.querySelector('.collection-repeat-after-container')
 9084      );
 9085      // Put everything in the view after the repeater into a container.
 9086      if (!container.length) {
 9087        var elementIsAfterRepeater = false;
 9088        var afterNodes = [].filter.call(scrollView.__content.childNodes, function(node) {
 9089          if (ionic.DomUtil.contains(node, containerNode)) {
 9090            elementIsAfterRepeater = true;
 9091            return false;
 9092          }
 9093          return elementIsAfterRepeater;
 9094        });
 9095        container = angular.element('<span class="collection-repeat-after-container">');
 9096        if (scrollView.options.scrollingX) {
 9097          container.addClass('horizontal');
 9098        }
 9099        container.append(afterNodes);
 9100        scrollView.__content.appendChild(container[0]);
 9101      }
 9102      return container;
 9103    }
 9104
 9105    function initDimensions() {
 9106      //Height and width have four 'modes':
 9107      //1) Computed Mode
 9108      //  - Nothing is supplied, so we getComputedStyle() on one element in the list and use
 9109      //    that width and height value for the width and height of every item. This is re-computed
 9110      //    every resize.
 9111      //2) Constant Mode, Static Integer
 9112      //  - The user provides a constant number for width or height, in pixels. We parse it,
 9113      //    store it on the `value` field, and it never changes
 9114      //3) Constant Mode, Percent
 9115      //  - The user provides a percent string for width or height. The getter for percent is
 9116      //    stored on the `getValue()` field, and is re-evaluated once every resize. The result
 9117      //    is stored on the `value` field.
 9118      //4) Dynamic Mode
 9119      //  - The user provides a dynamic expression for the width or height.  This is re-evaluated
 9120      //    for every item, stored on the `.getValue()` field.
 9121      if (heightExpr) {
 9122        parseDimensionAttr(heightExpr, heightData);
 9123      } else {
 9124        heightData.computed = true;
 9125      }
 9126      if (widthExpr) {
 9127        parseDimensionAttr(widthExpr, widthData);
 9128      } else {
 9129        widthData.computed = true;
 9130      }
 9131    }
 9132
 9133    function refreshDimensions() {
 9134      var hasData = data.length > 0;
 9135
 9136      if (hasData && (heightData.computed || widthData.computed)) {
 9137        computeStyleDimensions();
 9138      }
 9139
 9140      if (hasData && heightData.computed) {
 9141        heightData.value = computedStyleDimensions.height;
 9142        if (!heightData.value) {
 9143          throw new Error('collection-repeat tried to compute the height of repeated elements "' +
 9144            repeatExpr + '", but was unable to. Please provide the "item-height" attribute. ' +
 9145            'http://ionicframework.com/docs/api/directive/collectionRepeat/');
 9146        }
 9147      } else if (!heightData.dynamic && heightData.getValue) {
 9148        // If it's a constant with a getter (eg percent), we just refresh .value after resize
 9149        heightData.value = heightData.getValue();
 9150      }
 9151
 9152      if (hasData && widthData.computed) {
 9153        widthData.value = computedStyleDimensions.width;
 9154        if (!widthData.value) {
 9155          throw new Error('collection-repeat tried to compute the width of repeated elements "' +
 9156            repeatExpr + '", but was unable to. Please provide the "item-width" attribute. ' +
 9157            'http://ionicframework.com/docs/api/directive/collectionRepeat/');
 9158        }
 9159      } else if (!widthData.dynamic && widthData.getValue) {
 9160        // If it's a constant with a getter (eg percent), we just refresh .value after resize
 9161        widthData.value = widthData.getValue();
 9162      }
 9163      // Dynamic dimensions aren't updated on resize. Since they're already dynamic anyway,
 9164      // .getValue() will be used.
 9165
 9166      getRepeatManager().refreshLayout();
 9167    }
 9168
 9169    function parseDimensionAttr(attrValue, dimensionData) {
 9170      if (!attrValue) return;
 9171
 9172      var parsedValue;
 9173      // Try to just parse the plain attr value
 9174      try {
 9175        parsedValue = $parse(attrValue);
 9176      } catch (e) {
 9177        // If the parse fails and the value has `px` or `%` in it, surround the attr in
 9178        // quotes, to attempt to let the user provide a simple `attr="100%"` or `attr="100px"`
 9179        if (attrValue.trim().match(/\d+(px|%)$/)) {
 9180          attrValue = '"' + attrValue + '"';
 9181        }
 9182        parsedValue = $parse(attrValue);
 9183      }
 9184
 9185      var constantAttrValue = attrValue.replace(/(\'|\"|px|%)/g, '').trim();
 9186      var isConstant = constantAttrValue.length && !/([a-zA-Z]|\$|:|\?)/.test(constantAttrValue);
 9187      dimensionData.attrValue = attrValue;
 9188
 9189      // If it's a constant, it's either a percent or just a constant pixel number.
 9190      if (isConstant) {
 9191        // For percents, store the percent getter on .getValue()
 9192        if (attrValue.indexOf('%') > -1) {
 9193          var decimalValue = parseFloat(parsedValue()) / 100;
 9194          dimensionData.getValue = dimensionData === heightData ?
 9195            function() { return Math.floor(decimalValue * scrollView.__clientHeight); } :
 9196            function() { return Math.floor(decimalValue * scrollView.__clientWidth); };
 9197        } else {
 9198          // For static constants, just store the static constant.
 9199          dimensionData.value = parseInt(parsedValue());
 9200        }
 9201
 9202      } else {
 9203        dimensionData.dynamic = true;
 9204        dimensionData.getValue = dimensionData === heightData ?
 9205          function heightGetter(scope, locals) {
 9206            var result = parsedValue(scope, locals);
 9207            if (result.charAt && result.charAt(result.length - 1) === '%') {
 9208              return Math.floor(parseFloat(result) / 100 * scrollView.__clientHeight);
 9209            }
 9210            return parseInt(result);
 9211          } :
 9212          function widthGetter(scope, locals) {
 9213            var result = parsedValue(scope, locals);
 9214            if (result.charAt && result.charAt(result.length - 1) === '%') {
 9215              return Math.floor(parseFloat(result) / 100 * scrollView.__clientWidth);
 9216            }
 9217            return parseInt(result);
 9218          };
 9219      }
 9220    }
 9221
 9222    var computedStyleNode;
 9223    var computedStyleScope;
 9224    function computeStyleDimensions() {
 9225      if (!computedStyleNode) {
 9226        transclude(computedStyleScope = scope.$new(), function(clone) {
 9227          clone[0].removeAttribute('collection-repeat'); // remove absolute position styling
 9228          computedStyleNode = clone[0];
 9229        });
 9230      }
 9231
 9232      computedStyleScope[keyExpr] = (listGetter(scope) || [])[0];
 9233      if (!$rootScope.$$phase) computedStyleScope.$digest();
 9234      containerNode.appendChild(computedStyleNode);
 9235
 9236      var style = $window.getComputedStyle(computedStyleNode);
 9237      computedStyleDimensions.width = parseInt(style.width);
 9238      computedStyleDimensions.height = parseInt(style.height);
 9239
 9240      containerNode.removeChild(computedStyleNode);
 9241    }
 9242
 9243  }
 9244
 9245}
 9246
 9247RepeatManagerFactory.$inject = ['$rootScope', '$window', '$$rAF'];
 9248function RepeatManagerFactory($rootScope, $window, $$rAF) {
 9249  var EMPTY_DIMENSION = { primaryPos: 0, secondaryPos: 0, primarySize: 0, secondarySize: 0, rowPrimarySize: 0 };
 9250
 9251  return function RepeatController(options) {
 9252    var afterItemsNode = options.afterItemsNode;
 9253    var containerNode = options.containerNode;
 9254    var forceRefreshImages = options.forceRefreshImages;
 9255    var heightData = options.heightData;
 9256    var widthData = options.widthData;
 9257    var keyExpression = options.keyExpression;
 9258    var renderBuffer = options.renderBuffer;
 9259    var scope = options.scope;
 9260    var scrollView = options.scrollView;
 9261    var transclude = options.transclude;
 9262
 9263    var data = [];
 9264
 9265    var getterLocals = {};
 9266    var heightFn = heightData.getValue || function() { return heightData.value; };
 9267    var heightGetter = function(index, value) {
 9268      getterLocals[keyExpression] = value;
 9269      getterLocals.$index = index;
 9270      return heightFn(scope, getterLocals);
 9271    };
 9272
 9273    var widthFn = widthData.getValue || function() { return widthData.value; };
 9274    var widthGetter = function(index, value) {
 9275      getterLocals[keyExpression] = value;
 9276      getterLocals.$index = index;
 9277      return widthFn(scope, getterLocals);
 9278    };
 9279
 9280    var isVertical = !!scrollView.options.scrollingY;
 9281
 9282    // We say it's a grid view if we're either dynamic or not 100% width
 9283    var isGridView = isVertical ?
 9284      (widthData.dynamic || widthData.value !== scrollView.__clientWidth) :
 9285      (heightData.dynamic || heightData.value !== scrollView.__clientHeight);
 9286
 9287    var isStaticView = !heightData.dynamic && !widthData.dynamic;
 9288
 9289    var PRIMARY = 'PRIMARY';
 9290    var SECONDARY = 'SECONDARY';
 9291    var TRANSLATE_TEMPLATE_STR = isVertical ?
 9292      'translate3d(SECONDARYpx,PRIMARYpx,0)' :
 9293      'translate3d(PRIMARYpx,SECONDARYpx,0)';
 9294    var WIDTH_HEIGHT_TEMPLATE_STR = isVertical ?
 9295      'height: PRIMARYpx; width: SECONDARYpx;' :
 9296      'height: SECONDARYpx; width: PRIMARYpx;';
 9297
 9298    var estimatedHeight;
 9299    var estimatedWidth;
 9300
 9301    var repeaterBeforeSize = 0;
 9302    var repeaterAfterSize = 0;
 9303
 9304    var renderStartIndex = -1;
 9305    var renderEndIndex = -1;
 9306    var renderAfterBoundary = -1;
 9307    var renderBeforeBoundary = -1;
 9308
 9309    var itemsPool = [];
 9310    var itemsLeaving = [];
 9311    var itemsEntering = [];
 9312    var itemsShownMap = {};
 9313    var nextItemId = 0;
 9314
 9315    var scrollViewSetDimensions = isVertical ?
 9316      function() { scrollView.setDimensions(null, null, null, view.getContentSize(), true); } :
 9317      function() { scrollView.setDimensions(null, null, view.getContentSize(), null, true); };
 9318
 9319    // view is a mix of list/grid methods + static/dynamic methods.
 9320    // See bottom for implementations. Available methods:
 9321    //
 9322    // getEstimatedPrimaryPos(i), getEstimatedSecondaryPos(i), getEstimatedIndex(scrollTop),
 9323    // calculateDimensions(toIndex), getDimensions(index),
 9324    // updateRenderRange(scrollTop, scrollValueEnd), onRefreshLayout(), onRefreshData()
 9325    var view = isVertical ? new VerticalViewType() : new HorizontalViewType();
 9326    (isGridView ? GridViewType : ListViewType).call(view);
 9327    (isStaticView ? StaticViewType : DynamicViewType).call(view);
 9328
 9329    var contentSizeStr = isVertical ? 'getContentHeight' : 'getContentWidth';
 9330    var originalGetContentSize = scrollView.options[contentSizeStr];
 9331    scrollView.options[contentSizeStr] = angular.bind(view, view.getContentSize);
 9332
 9333    scrollView.__$callback = scrollView.__callback;
 9334    scrollView.__callback = function(transformLeft, transformTop, zoom, wasResize) {
 9335      var scrollValue = view.getScrollValue();
 9336      if (renderStartIndex === -1 ||
 9337          scrollValue + view.scrollPrimarySize > renderAfterBoundary ||
 9338          scrollValue < renderBeforeBoundary) {
 9339        render();
 9340      }
 9341      scrollView.__$callback(transformLeft, transformTop, zoom, wasResize);
 9342    };
 9343
 9344    var isLayoutReady = false;
 9345    var isDataReady = false;
 9346    this.refreshLayout = function() {
 9347      if (data.length) {
 9348        estimatedHeight = heightGetter(0, data[0]);
 9349        estimatedWidth = widthGetter(0, data[0]);
 9350      } else {
 9351        // If we don't have any data in our array, just guess.
 9352        estimatedHeight = 100;
 9353        estimatedWidth = 100;
 9354      }
 9355
 9356      // Get the size of every element AFTER the repeater. We have to get the margin before and
 9357      // after the first/last element to fix a browser bug with getComputedStyle() not counting
 9358      // the first/last child's margins into height.
 9359      var style = getComputedStyle(afterItemsNode) || {};
 9360      var firstStyle = afterItemsNode.firstElementChild && getComputedStyle(afterItemsNode.firstElementChild) || {};
 9361      var lastStyle = afterItemsNode.lastElementChild && getComputedStyle(afterItemsNode.lastElementChild) || {};
 9362      repeaterAfterSize = (parseInt(style[isVertical ? 'height' : 'width']) || 0) +
 9363        (firstStyle && parseInt(firstStyle[isVertical ? 'marginTop' : 'marginLeft']) || 0) +
 9364        (lastStyle && parseInt(lastStyle[isVertical ? 'marginBottom' : 'marginRight']) || 0);
 9365
 9366      // Get the offsetTop of the repeater.
 9367      repeaterBeforeSize = 0;
 9368      var current = containerNode;
 9369      do {
 9370        repeaterBeforeSize += current[isVertical ? 'offsetTop' : 'offsetLeft'];
 9371      } while ( ionic.DomUtil.contains(scrollView.__content, current = current.offsetParent) );
 9372
 9373      var containerPrevNode = containerNode.previousElementSibling;
 9374      var beforeStyle = containerPrevNode ? $window.getComputedStyle(containerPrevNode) : {};
 9375      var beforeMargin = parseInt(beforeStyle[isVertical ? 'marginBottom' : 'marginRight'] || 0);
 9376
 9377      // Because we position the collection container with position: relative, it doesn't take
 9378      // into account where to position itself relative to the previous element's marginBottom.
 9379      // To compensate, we translate the container up by the previous element's margin.
 9380      containerNode.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
 9381        .replace(PRIMARY, -beforeMargin)
 9382        .replace(SECONDARY, 0);
 9383      repeaterBeforeSize -= beforeMargin;
 9384
 9385      if (!scrollView.__clientHeight || !scrollView.__clientWidth) {
 9386        scrollView.__clientWidth = scrollView.__container.clientWidth;
 9387        scrollView.__clientHeight = scrollView.__container.clientHeight;
 9388      }
 9389
 9390      (view.onRefreshLayout || angular.noop)();
 9391      view.refreshDirection();
 9392      scrollViewSetDimensions();
 9393
 9394      // Create the pool of items for reuse, setting the size to (estimatedItemsOnScreen) * 2,
 9395      // plus the size of the renderBuffer.
 9396      if (!isLayoutReady) {
 9397        var poolSize = Math.max(20, renderBuffer * 3);
 9398        for (var i = 0; i < poolSize; i++) {
 9399          itemsPool.push(new RepeatItem());
 9400        }
 9401      }
 9402
 9403      isLayoutReady = true;
 9404      if (isLayoutReady && isDataReady) {
 9405        // If the resize or latest data change caused the scrollValue to
 9406        // now be out of bounds, resize the scrollView.
 9407        if (scrollView.__scrollLeft > scrollView.__maxScrollLeft ||
 9408            scrollView.__scrollTop > scrollView.__maxScrollTop) {
 9409          scrollView.resize();
 9410        }
 9411        forceRerender(true);
 9412      }
 9413    };
 9414
 9415    this.setData = function(newData) {
 9416      data = newData;
 9417      (view.onRefreshData || angular.noop)();
 9418      isDataReady = true;
 9419    };
 9420
 9421    this.destroy = function() {
 9422      render.destroyed = true;
 9423
 9424      itemsPool.forEach(function(item) {
 9425        item.scope.$destroy();
 9426        item.scope = item.element = item.node = item.images = null;
 9427      });
 9428      itemsPool.length = itemsEntering.length = itemsLeaving.length = 0;
 9429      itemsShownMap = {};
 9430
 9431      //Restore the scrollView's normal behavior and resize it to normal size.
 9432      scrollView.options[contentSizeStr] = originalGetContentSize;
 9433      scrollView.__callback = scrollView.__$callback;
 9434      scrollView.resize();
 9435
 9436      (view.onDestroy || angular.noop)();
 9437    };
 9438
 9439    function forceRerender() {
 9440      return render(true);
 9441    }
 9442    function render(forceRerender) {
 9443      if (render.destroyed) return;
 9444      var i;
 9445      var ii;
 9446      var item;
 9447      var dim;
 9448      var scope;
 9449      var scrollValue = view.getScrollValue();
 9450      var scrollValueEnd = scrollValue + view.scrollPrimarySize;
 9451
 9452      view.updateRenderRange(scrollValue, scrollValueEnd);
 9453
 9454      renderStartIndex = Math.max(0, renderStartIndex - renderBuffer);
 9455      renderEndIndex = Math.min(data.length - 1, renderEndIndex + renderBuffer);
 9456
 9457      for (i in itemsShownMap) {
 9458        if (i < renderStartIndex || i > renderEndIndex) {
 9459          item = itemsShownMap[i];
 9460          delete itemsShownMap[i];
 9461          itemsLeaving.push(item);
 9462          item.isShown = false;
 9463        }
 9464      }
 9465
 9466      // Render indicies that aren't shown yet
 9467      //
 9468      // NOTE(ajoslin): this may sound crazy, but calling any other functions during this render
 9469      // loop will often push the render time over the edge from less than one frame to over
 9470      // one frame, causing visible jank.
 9471      // DON'T call any other functions inside this loop unless it's vital.
 9472      for (i = renderStartIndex; i <= renderEndIndex; i++) {
 9473        // We only go forward with render if the index is in data, the item isn't already shown,
 9474        // or forceRerender is on.
 9475        if (i >= data.length || (itemsShownMap[i] && !forceRerender)) continue;
 9476
 9477        item = itemsShownMap[i] || (itemsShownMap[i] = itemsLeaving.length ? itemsLeaving.pop() :
 9478                                    itemsPool.length ? itemsPool.shift() :
 9479                                    new RepeatItem());
 9480        itemsEntering.push(item);
 9481        item.isShown = true;
 9482
 9483        scope = item.scope;
 9484        scope.$index = i;
 9485        scope[keyExpression] = data[i];
 9486        scope.$first = (i === 0);
 9487        scope.$last = (i === (data.length - 1));
 9488        scope.$middle = !(scope.$first || scope.$last);
 9489        scope.$odd = !(scope.$even = (i & 1) === 0);
 9490
 9491        if (scope.$$disconnected) ionic.Utils.reconnectScope(item.scope);
 9492
 9493        dim = view.getDimensions(i);
 9494        if (item.secondaryPos !== dim.secondaryPos || item.primaryPos !== dim.primaryPos) {
 9495          item.node.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
 9496            .replace(PRIMARY, (item.primaryPos = dim.primaryPos))
 9497            .replace(SECONDARY, (item.secondaryPos = dim.secondaryPos));
 9498        }
 9499        if (item.secondarySize !== dim.secondarySize || item.primarySize !== dim.primarySize) {
 9500          item.node.style.cssText = item.node.style.cssText
 9501            .replace(WIDTH_HEIGHT_REGEX, WIDTH_HEIGHT_TEMPLATE_STR
 9502              //TODO fix item.primarySize + 1 hack
 9503              .replace(PRIMARY, (item.primarySize = dim.primarySize) + 1)
 9504              .replace(SECONDARY, (item.secondarySize = dim.secondarySize))
 9505            );
 9506        }
 9507
 9508      }
 9509
 9510      // If we reach the end of the list, render the afterItemsNode - this contains all the
 9511      // elements the developer placed after the collection-repeat
 9512      if (renderEndIndex === data.length - 1) {
 9513        dim = view.getDimensions(data.length - 1) || EMPTY_DIMENSION;
 9514        afterItemsNode.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
 9515          .replace(PRIMARY, dim.primaryPos + dim.primarySize)
 9516          .replace(SECONDARY, 0);
 9517      }
 9518
 9519      while (itemsLeaving.length) {
 9520        item = itemsLeaving.pop();
 9521        item.scope.$broadcast('$collectionRepeatLeave');
 9522        ionic.Utils.disconnectScope(item.scope);
 9523        itemsPool.push(item);
 9524        item.node.style[ionic.CSS.TRANSFORM] = 'translate3d(-9999px,-9999px,0)';
 9525        item.primaryPos = item.secondaryPos = null;
 9526      }
 9527
 9528      if (forceRefreshImages) {
 9529        for (i = 0, ii = itemsEntering.length; i < ii && (item = itemsEntering[i]); i++) {
 9530          if (!item.images) continue;
 9531          for (var j = 0, jj = item.images.length, img; j < jj && (img = item.images[j]); j++) {
 9532            var src = img.src;
 9533            img.src = ONE_PX_TRANSPARENT_IMG_SRC;
 9534            img.src = src;
 9535          }
 9536        }
 9537      }
 9538      if (forceRerender) {
 9539        var rootScopePhase = $rootScope.$$phase;
 9540        while (itemsEntering.length) {
 9541          item = itemsEntering.pop();
 9542          if (!rootScopePhase) item.scope.$digest();
 9543        }
 9544      } else {
 9545        digestEnteringItems();
 9546      }
 9547    }
 9548
 9549    function digestEnteringItems() {
 9550      var item;
 9551      if (digestEnteringItems.running) return;
 9552      digestEnteringItems.running = true;
 9553
 9554      $$rAF(function process() {
 9555        var rootScopePhase = $rootScope.$$phase;
 9556        while (itemsEntering.length) {
 9557          item = itemsEntering.pop();
 9558          if (item.isShown) {
 9559            if (!rootScopePhase) item.scope.$digest();
 9560          }
 9561        }
 9562        digestEnteringItems.running = false;
 9563      });
 9564    }
 9565
 9566    function RepeatItem() {
 9567      var self = this;
 9568      this.scope = scope.$new();
 9569      this.id = 'item' + (nextItemId++);
 9570      transclude(this.scope, function(clone) {
 9571        self.element = clone;
 9572        self.element.data('$$collectionRepeatItem', self);
 9573        // TODO destroy
 9574        self.node = clone[0];
 9575        // Batch style setting to lower repaints
 9576        self.node.style[ionic.CSS.TRANSFORM] = 'translate3d(-9999px,-9999px,0)';
 9577        self.node.style.cssText += ' height: 0px; width: 0px;';
 9578        ionic.Utils.disconnectScope(self.scope);
 9579        containerNode.appendChild(self.node);
 9580        self.images = clone[0].getElementsByTagName('img');
 9581      });
 9582    }
 9583
 9584    function VerticalViewType() {
 9585      this.getItemPrimarySize = heightGetter;
 9586      this.getItemSecondarySize = widthGetter;
 9587
 9588      this.getScrollValue = function() {
 9589        return Math.max(0, Math.min(scrollView.__scrollTop - repeaterBeforeSize,
 9590          scrollView.__maxScrollTop - repeaterBeforeSize - repeaterAfterSize));
 9591      };
 9592
 9593      this.refreshDirection = function() {
 9594        this.scrollPrimarySize = scrollView.__clientHeight;
 9595        this.scrollSecondarySize = scrollView.__clientWidth;
 9596
 9597        this.estimatedPrimarySize = estimatedHeight;
 9598        this.estimatedSecondarySize = estimatedWidth;
 9599        this.estimatedItemsAcross = isGridView &&
 9600          Math.floor(scrollView.__clientWidth / estimatedWidth) ||
 9601          1;
 9602      };
 9603    }
 9604    function HorizontalViewType() {
 9605      this.getItemPrimarySize = widthGetter;
 9606      this.getItemSecondarySize = heightGetter;
 9607
 9608      this.getScrollValue = function() {
 9609        return Math.max(0, Math.min(scrollView.__scrollLeft - repeaterBeforeSize,
 9610          scrollView.__maxScrollLeft - repeaterBeforeSize - repeaterAfterSize));
 9611      };
 9612
 9613      this.refreshDirection = function() {
 9614        this.scrollPrimarySize = scrollView.__clientWidth;
 9615        this.scrollSecondarySize = scrollView.__clientHeight;
 9616
 9617        this.estimatedPrimarySize = estimatedWidth;
 9618        this.estimatedSecondarySize = estimatedHeight;
 9619        this.estimatedItemsAcross = isGridView &&
 9620          Math.floor(scrollView.__clientHeight / estimatedHeight) ||
 9621          1;
 9622      };
 9623    }
 9624
 9625    function GridViewType() {
 9626      this.getEstimatedSecondaryPos = function(index) {
 9627        return (index % this.estimatedItemsAcross) * this.estimatedSecondarySize;
 9628      };
 9629      this.getEstimatedPrimaryPos = function(index) {
 9630        return Math.floor(index / this.estimatedItemsAcross) * this.estimatedPrimarySize;
 9631      };
 9632      this.getEstimatedIndex = function(scrollValue) {
 9633        return Math.floor(scrollValue / this.estimatedPrimarySize) *
 9634          this.estimatedItemsAcross;
 9635      };
 9636    }
 9637
 9638    function ListViewType() {
 9639      this.getEstimatedSecondaryPos = function() {
 9640        return 0;
 9641      };
 9642      this.getEstimatedPrimaryPos = function(index) {
 9643        return index * this.estimatedPrimarySize;
 9644      };
 9645      this.getEstimatedIndex = function(scrollValue) {
 9646        return Math.floor((scrollValue) / this.estimatedPrimarySize);
 9647      };
 9648    }
 9649
 9650    function StaticViewType() {
 9651      this.getContentSize = function() {
 9652        return this.getEstimatedPrimaryPos(data.length - 1) + this.estimatedPrimarySize +
 9653          repeaterBeforeSize + repeaterAfterSize;
 9654      };
 9655      // static view always returns the same object for getDimensions, to avoid memory allocation
 9656      // while scrolling. This could be dangerous if this was a public function, but it's not.
 9657      // Only we use it.
 9658      var dim = {};
 9659      this.getDimensions = function(index) {
 9660        dim.primaryPos = this.getEstimatedPrimaryPos(index);
 9661        dim.secondaryPos = this.getEstimatedSecondaryPos(index);
 9662        dim.primarySize = this.estimatedPrimarySize;
 9663        dim.secondarySize = this.estimatedSecondarySize;
 9664        return dim;
 9665      };
 9666      this.updateRenderRange = function(scrollValue, scrollValueEnd) {
 9667        renderStartIndex = Math.max(0, this.getEstimatedIndex(scrollValue));
 9668
 9669        // Make sure the renderEndIndex takes into account all the items on the row
 9670        renderEndIndex = Math.min(data.length - 1,
 9671          this.getEstimatedIndex(scrollValueEnd) + this.estimatedItemsAcross - 1);
 9672
 9673        renderBeforeBoundary = Math.max(0,
 9674          this.getEstimatedPrimaryPos(renderStartIndex));
 9675        renderAfterBoundary = this.getEstimatedPrimaryPos(renderEndIndex) +
 9676          this.estimatedPrimarySize;
 9677      };
 9678    }
 9679
 9680    function DynamicViewType() {
 9681      var self = this;
 9682      var debouncedScrollViewSetDimensions = ionic.debounce(scrollViewSetDimensions, 25, true);
 9683      var calculateDimensions = isGridView ? calculateDimensionsGrid : calculateDimensionsList;
 9684      var dimensionsIndex;
 9685      var dimensions = [];
 9686
 9687
 9688      // Get the dimensions at index. {width, height, left, top}.
 9689      // We start with no dimensions calculated, then any time dimensions are asked for at an
 9690      // index we calculate dimensions up to there.
 9691      function calculateDimensionsList(toIndex) {
 9692        var i, prevDimension, dim;
 9693        for (i = Math.max(0, dimensionsIndex); i <= toIndex && (dim = dimensions[i]); i++) {
 9694          prevDimension = dimensions[i - 1] || EMPTY_DIMENSION;
 9695          dim.primarySize = self.getItemPrimarySize(i, data[i]);
 9696          dim.secondarySize = self.scrollSecondarySize;
 9697          dim.primaryPos = prevDimension.primaryPos + prevDimension.primarySize;
 9698          dim.secondaryPos = 0;
 9699        }
 9700      }
 9701      function calculateDimensionsGrid(toIndex) {
 9702        var i, prevDimension, dim;
 9703        for (i = Math.max(dimensionsIndex, 0); i <= toIndex && (dim = dimensions[i]); i++) {
 9704          prevDimension = dimensions[i - 1] || EMPTY_DIMENSION;
 9705          dim.secondarySize = Math.min(
 9706            self.getItemSecondarySize(i, data[i]),
 9707            self.scrollSecondarySize
 9708          );
 9709          dim.secondaryPos = prevDimension.secondaryPos + prevDimension.secondarySize;
 9710
 9711          if (i === 0 || dim.secondaryPos + dim.secondarySize > self.scrollSecondarySize) {
 9712            dim.secondaryPos = 0;
 9713            dim.primarySize = self.getItemPrimarySize(i, data[i]);
 9714            dim.primaryPos = prevDimension.primaryPos + prevDimension.rowPrimarySize;
 9715
 9716            dim.rowStartIndex = i;
 9717            dim.rowPrimarySize = dim.primarySize;
 9718          } else {
 9719            dim.primarySize = self.getItemPrimarySize(i, data[i]);
 9720            dim.primaryPos = prevDimension.primaryPos;
 9721            dim.rowStartIndex = prevDimension.rowStartIndex;
 9722
 9723            dimensions[dim.rowStartIndex].rowPrimarySize = dim.rowPrimarySize = Math.max(
 9724              dimensions[dim.rowStartIndex].rowPrimarySize,
 9725              dim.primarySize
 9726            );
 9727            dim.rowPrimarySize = Math.max(dim.primarySize, dim.rowPrimarySize);
 9728          }
 9729        }
 9730      }
 9731
 9732      this.getContentSize = function() {
 9733        var dim = dimensions[dimensionsIndex] || EMPTY_DIMENSION;
 9734        return ((dim.primaryPos + dim.primarySize) || 0) +
 9735          this.getEstimatedPrimaryPos(data.length - dimensionsIndex - 1) +
 9736          repeaterBeforeSize + repeaterAfterSize;
 9737      };
 9738      this.onDestroy = function() {
 9739        dimensions.length = 0;
 9740      };
 9741
 9742      this.onRefreshData = function() {
 9743        var i;
 9744        var ii;
 9745        // Make sure dimensions has as many items as data.length.
 9746        // This is to be sure we don't have to allocate objects while scrolling.
 9747        for (i = dimensions.length, ii = data.length; i < ii; i++) {
 9748          dimensions.push({});
 9749        }
 9750        dimensionsIndex = -1;
 9751      };
 9752      this.onRefreshLayout = function() {
 9753        dimensionsIndex = -1;
 9754      };
 9755      this.getDimensions = function(index) {
 9756        index = Math.min(index, data.length - 1);
 9757
 9758        if (dimensionsIndex < index) {
 9759          // Once we start asking for dimensions near the end of the list, go ahead and calculate
 9760          // everything. This is to make sure when the user gets to the end of the list, the
 9761          // scroll height of the list is 100% accurate (not estimated anymore).
 9762          if (index > data.length * 0.9) {
 9763            calculateDimensions(data.length - 1);
 9764            dimensionsIndex = data.length - 1;
 9765            scrollViewSetDimensions();
 9766          } else {
 9767            calculateDimensions(index);
 9768            dimensionsIndex = index;
 9769            debouncedScrollViewSetDimensions();
 9770          }
 9771
 9772        }
 9773        return dimensions[index];
 9774      };
 9775
 9776      var oldRenderStartIndex = -1;
 9777      var oldScrollValue = -1;
 9778      this.updateRenderRange = function(scrollValue, scrollValueEnd) {
 9779        var i;
 9780        var len;
 9781        var dim;
 9782
 9783        // Calculate more dimensions than we estimate we'll need, to be sure.
 9784        this.getDimensions( this.getEstimatedIndex(scrollValueEnd) * 2 );
 9785
 9786        // -- Calculate renderStartIndex
 9787        // base case: start at 0
 9788        if (oldRenderStartIndex === -1 || scrollValue === 0) {
 9789          i = 0;
 9790        // scrolling down
 9791        } else if (scrollValue >= oldScrollValue) {
 9792          for (i = oldRenderStartIndex, len = data.length; i < len; i++) {
 9793            if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.rowPrimarySize >= scrollValue) {
 9794              break;
 9795            }
 9796          }
 9797        // scrolling up
 9798        } else {
 9799          for (i = oldRenderStartIndex; i >= 0; i--) {
 9800            if ((dim = this.getDimensions(i)) && dim.primaryPos <= scrollValue) {
 9801              // when grid view, make sure the render starts at the beginning of a row.
 9802              i = isGridView ? dim.rowStartIndex : i;
 9803              break;
 9804            }
 9805          }
 9806        }
 9807
 9808        renderStartIndex = Math.min(Math.max(0, i), data.length - 1);
 9809        renderBeforeBoundary = renderStartIndex !== -1 ? this.getDimensions(renderStartIndex).primaryPos : -1;
 9810
 9811        // -- Calculate renderEndIndex
 9812        var lastRowDim;
 9813        for (i = renderStartIndex + 1, len = data.length; i < len; i++) {
 9814          if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.rowPrimarySize > scrollValueEnd) {
 9815
 9816            // Go all the way to the end of the row if we're in a grid
 9817            if (isGridView) {
 9818              lastRowDim = dim;
 9819              while (i < len - 1 &&
 9820                    (dim = this.getDimensions(i + 1)).primaryPos === lastRowDim.primaryPos) {
 9821                i++;
 9822              }
 9823            }
 9824            break;
 9825          }
 9826        }
 9827
 9828        renderEndIndex = Math.min(i, data.length - 1);
 9829        renderAfterBoundary = renderEndIndex !== -1 ?
 9830          ((dim = this.getDimensions(renderEndIndex)).primaryPos + (dim.rowPrimarySize || dim.primarySize)) :
 9831          -1;
 9832
 9833        oldScrollValue = scrollValue;
 9834        oldRenderStartIndex = renderStartIndex;
 9835      };
 9836    }
 9837
 9838
 9839  };
 9840
 9841}
 9842
 9843/**
 9844 * @ngdoc directive
 9845 * @name ionContent
 9846 * @module ionic
 9847 * @delegate ionic.service:$ionicScrollDelegate
 9848 * @restrict E
 9849 *
 9850 * @description
 9851 * The ionContent directive provides an easy to use content area that can be configured
 9852 * to use Ionic's custom Scroll View, or the built in overflow scrolling of the browser.
 9853 *
 9854 * While we recommend using the custom Scroll features in Ionic in most cases, sometimes
 9855 * (for performance reasons) only the browser's native overflow scrolling will suffice,
 9856 * and so we've made it easy to toggle between the Ionic scroll implementation and
 9857 * overflow scrolling.
 9858 *
 9859 * You can implement pull-to-refresh with the {@link ionic.directive:ionRefresher}
 9860 * directive, and infinite scrolling with the {@link ionic.directive:ionInfiniteScroll}
 9861 * directive.
 9862 *
 9863 * If there is any dynamic content inside the ion-content, be sure to call `.resize()` with {@link ionic.service:$ionicScrollDelegate}
 9864 * after the content has been added.
 9865 *
 9866 * Be aware that this directive gets its own child scope. If you do not understand why this
 9867 * is important, you can read [https://docs.angularjs.org/guide/scope](https://docs.angularjs.org/guide/scope).
 9868 *
 9869 * @param {string=} delegate-handle The handle used to identify this scrollView
 9870 * with {@link ionic.service:$ionicScrollDelegate}.
 9871 * @param {string=} direction Which way to scroll. 'x' or 'y' or 'xy'. Default 'y'.
 9872 * @param {boolean=} locking Whether to lock scrolling in one direction at a time. Useful to set to false when zoomed in or scrolling in two directions. Default true.
 9873 * @param {boolean=} padding Whether to add padding to the content.
 9874 * Defaults to true on iOS, false on Android.
 9875 * @param {boolean=} scroll Whether to allow scrolling of content.  Defaults to true.
 9876 * @param {boolean=} overflow-scroll Whether to use overflow-scrolling instead of
 9877 * Ionic scroll. See {@link ionic.provider:$ionicConfigProvider} to set this as the global default.
 9878 * @param {boolean=} scrollbar-x Whether to show the horizontal scrollbar. Default true.
 9879 * @param {boolean=} scrollbar-y Whether to show the vertical scrollbar. Default true.
 9880 * @param {string=} start-x Initial horizontal scroll position. Default 0.
 9881 * @param {string=} start-y Initial vertical scroll position. Default 0.
 9882 * @param {expression=} on-scroll Expression to evaluate when the content is scrolled.
 9883 * @param {expression=} on-scroll-complete Expression to evaluate when a scroll action completes. Has access to 'scrollLeft' and 'scrollTop' locals.
 9884 * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges
 9885 * of the content.  Defaults to true on iOS, false on Android.
 9886 * @param {number=} scroll-event-interval Number of milliseconds between each firing of the 'on-scroll' expression. Default 10.
 9887 */
 9888IonicModule
 9889.directive('ionContent', [
 9890  '$timeout',
 9891  '$controller',
 9892  '$ionicBind',
 9893  '$ionicConfig',
 9894function($timeout, $controller, $ionicBind, $ionicConfig) {
 9895  return {
 9896    restrict: 'E',
 9897    require: '^?ionNavView',
 9898    scope: true,
 9899    priority: 800,
 9900    compile: function(element, attr) {
 9901      var innerElement;
 9902      var scrollCtrl;
 9903
 9904      element.addClass('scroll-content ionic-scroll');
 9905
 9906      if (attr.scroll != 'false') {
 9907        //We cannot use normal transclude here because it breaks element.data()
 9908        //inheritance on compile
 9909        innerElement = jqLite('<div class="scroll"></div>');
 9910        innerElement.append(element.contents());
 9911        element.append(innerElement);
 9912      } else {
 9913        element.addClass('scroll-content-false');
 9914      }
 9915
 9916      var nativeScrolling = attr.overflowScroll !== "false" && (attr.overflowScroll === "true" || !$ionicConfig.scrolling.jsScrolling());
 9917
 9918      // collection-repeat requires JS scrolling
 9919      if (nativeScrolling) {
 9920        nativeScrolling = !element[0].querySelector('[collection-repeat]');
 9921      }
 9922      return { pre: prelink };
 9923      function prelink($scope, $element, $attr) {
 9924        var parentScope = $scope.$parent;
 9925        $scope.$watch(function() {
 9926          return (parentScope.$hasHeader ? ' has-header' : '') +
 9927            (parentScope.$hasSubheader ? ' has-subheader' : '') +
 9928            (parentScope.$hasFooter ? ' has-footer' : '') +
 9929            (parentScope.$hasSubfooter ? ' has-subfooter' : '') +
 9930            (parentScope.$hasTabs ? ' has-tabs' : '') +
 9931            (parentScope.$hasTabsTop ? ' has-tabs-top' : '');
 9932        }, function(className, oldClassName) {
 9933          $element.removeClass(oldClassName);
 9934          $element.addClass(className);
 9935        });
 9936
 9937        //Only this ionContent should use these variables from parent scopes
 9938        $scope.$hasHeader = $scope.$hasSubheader =
 9939          $scope.$hasFooter = $scope.$hasSubfooter =
 9940          $scope.$hasTabs = $scope.$hasTabsTop =
 9941          false;
 9942        $ionicBind($scope, $attr, {
 9943          $onScroll: '&onScroll',
 9944          $onScrollComplete: '&onScrollComplete',
 9945          hasBouncing: '@',
 9946          padding: '@',
 9947          direction: '@',
 9948          scrollbarX: '@',
 9949          scrollbarY: '@',
 9950          startX: '@',
 9951          startY: '@',
 9952          scrollEventInterval: '@'
 9953        });
 9954        $scope.direction = $scope.direction || 'y';
 9955
 9956        if (isDefined($attr.padding)) {
 9957          $scope.$watch($attr.padding, function(newVal) {
 9958              (innerElement || $element).toggleClass('padding', !!newVal);
 9959          });
 9960        }
 9961
 9962        if ($attr.scroll === "false") {
 9963          //do nothing
 9964        } else {
 9965          var scrollViewOptions = {};
 9966
 9967          // determined in compile phase above
 9968          if (nativeScrolling) {
 9969            // use native scrolling
 9970            $element.addClass('overflow-scroll');
 9971
 9972            scrollViewOptions = {
 9973              el: $element[0],
 9974              delegateHandle: attr.delegateHandle,
 9975              startX: $scope.$eval($scope.startX) || 0,
 9976              startY: $scope.$eval($scope.startY) || 0,
 9977              nativeScrolling: true
 9978            };
 9979
 9980          } else {
 9981            // Use JS scrolling
 9982            scrollViewOptions = {
 9983              el: $element[0],
 9984              delegateHandle: attr.delegateHandle,
 9985              locking: (attr.locking || 'true') === 'true',
 9986              bouncing: $scope.$eval($scope.hasBouncing),
 9987              startX: $scope.$eval($scope.startX) || 0,
 9988              startY: $scope.$eval($scope.startY) || 0,
 9989              scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
 9990              scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
 9991              scrollingX: $scope.direction.indexOf('x') >= 0,
 9992              scrollingY: $scope.direction.indexOf('y') >= 0,
 9993              scrollEventInterval: parseInt($scope.scrollEventInterval, 10) || 10,
 9994              scrollingComplete: onScrollComplete
 9995            };
 9996          }
 9997
 9998          // init scroll controller with appropriate options
 9999          scrollCtrl = $controller('$ionicScroll', {
10000            $scope: $scope,
10001            scrollViewOptions: scrollViewOptions
10002          });
10003
10004          $scope.scrollCtrl = scrollCtrl;
10005
10006          $scope.$on('$destroy', function() {
10007            if (scrollViewOptions) {
10008              scrollViewOptions.scrollingComplete = noop;
10009              delete scrollViewOptions.el;
10010            }
10011            innerElement = null;
10012            $element = null;
10013            attr.$$element = null;
10014          });
10015        }
10016
10017        function onScrollComplete() {
10018          $scope.$onScrollComplete({
10019            scrollTop: scrollCtrl.scrollView.__scrollTop,
10020            scrollLeft: scrollCtrl.scrollView.__scrollLeft
10021          });
10022        }
10023
10024      }
10025    }
10026  };
10027}]);
10028
10029/**
10030 * @ngdoc directive
10031 * @name exposeAsideWhen
10032 * @module ionic
10033 * @restrict A
10034 * @parent ionic.directive:ionSideMenus
10035 *
10036 * @description
10037 * It is common for a tablet application to hide a menu when in portrait mode, but to show the
10038 * same menu on the left side when the tablet is in landscape mode. The `exposeAsideWhen` attribute
10039 * directive can be used to accomplish a similar interface.
10040 *
10041 * By default, side menus are hidden underneath its side menu content, and can be opened by either
10042 * swiping the content left or right, or toggling a button to show the side menu. However, by adding the
10043 * `exposeAsideWhen` attribute directive to an {@link ionic.directive:ionSideMenu} element directive,
10044 * a side menu can be given instructions on "when" the menu should be exposed (always viewable). For
10045 * example, the `expose-aside-when="large"` attribute will keep the side menu hidden when the viewport's
10046 * width is less than `768px`, but when the viewport's width is `768px` or greater, the menu will then
10047 * always be shown and can no longer be opened or closed like it could when it was hidden for smaller
10048 * viewports.
10049 *
10050 * Using `large` as the attribute's value is a shortcut value to `(min-width:768px)` since it is
10051 * the most common use-case. However, for added flexibility, any valid media query could be added
10052 * as the value, such as `(min-width:600px)` or even multiple queries such as
10053 * `(min-width:750px) and (max-width:1200px)`.
10054 * @usage
10055 * ```html
10056 * <ion-side-menus>
10057 *   <!-- Center content -->
10058 *   <ion-side-menu-content>
10059 *   </ion-side-menu-content>
10060 *
10061 *   <!-- Left menu -->
10062 *   <ion-side-menu expose-aside-when="large">
10063 *   </ion-side-menu>
10064 * </ion-side-menus>
10065 * ```
10066 * For a complete side menu example, see the
10067 * {@link ionic.directive:ionSideMenus} documentation.
10068 */
10069
10070IonicModule.directive('exposeAsideWhen', ['$window', function($window) {
10071  return {
10072    restrict: 'A',
10073    require: '^ionSideMenus',
10074    link: function($scope, $element, $attr, sideMenuCtrl) {
10075
10076      var prevInnerWidth = $window.innerWidth;
10077      var prevInnerHeight = $window.innerHeight;
10078
10079      ionic.on('resize', function() {
10080        if (prevInnerWidth === $window.innerWidth && prevInnerHeight === $window.innerHeight) {
10081          return;
10082        }
10083        prevInnerWidth = $window.innerWidth;
10084        prevInnerHeight = $window.innerHeight;
10085        onResize();
10086      }, $window);
10087
10088      function checkAsideExpose() {
10089        var mq = $attr.exposeAsideWhen == 'large' ? '(min-width:768px)' : $attr.exposeAsideWhen;
10090        sideMenuCtrl.exposeAside($window.matchMedia(mq).matches);
10091        sideMenuCtrl.activeAsideResizing(false);
10092      }
10093
10094      function onResize() {
10095        sideMenuCtrl.activeAsideResizing(true);
10096        debouncedCheck();
10097      }
10098
10099      var debouncedCheck = ionic.debounce(function() {
10100        $scope.$apply(checkAsideExpose);
10101      }, 300, false);
10102
10103      $scope.$evalAsync(checkAsideExpose);
10104    }
10105  };
10106}]);
10107
10108var GESTURE_DIRECTIVES = 'onHold onTap onDoubleTap onTouch onRelease onDragStart onDrag onDragEnd onDragUp onDragRight onDragDown onDragLeft onSwipe onSwipeUp onSwipeRight onSwipeDown onSwipeLeft'.split(' ');
10109
10110GESTURE_DIRECTIVES.forEach(function(name) {
10111  IonicModule.directive(name, gestureDirective(name));
10112});
10113
10114
10115/**
10116 * @ngdoc directive
10117 * @name onHold
10118 * @module ionic
10119 * @restrict A
10120 *
10121 * @description
10122 * Touch stays at the same location for 500ms. Similar to long touch events available for AngularJS and jQuery.
10123 *
10124 * @usage
10125 * ```html
10126 * <button on-hold="onHold()" class="button">Test</button>
10127 * ```
10128 */
10129
10130
10131/**
10132 * @ngdoc directive
10133 * @name onTap
10134 * @module ionic
10135 * @restrict A
10136 *
10137 * @description
10138 * Quick touch at a location. If the duration of the touch goes
10139 * longer than 250ms it is no longer a tap gesture.
10140 *
10141 * @usage
10142 * ```html
10143 * <button on-tap="onTap()" class="button">Test</button>
10144 * ```
10145 */
10146
10147
10148/**
10149 * @ngdoc directive
10150 * @name onDoubleTap
10151 * @module ionic
10152 * @restrict A
10153 *
10154 * @description
10155 * Double tap touch at a location.
10156 *
10157 * @usage
10158 * ```html
10159 * <button on-double-tap="onDoubleTap()" class="button">Test</button>
10160 * ```
10161 */
10162
10163
10164/**
10165 * @ngdoc directive
10166 * @name onTouch
10167 * @module ionic
10168 * @restrict A
10169 *
10170 * @description
10171 * Called immediately when the user first begins a touch. This
10172 * gesture does not wait for a touchend/mouseup.
10173 *
10174 * @usage
10175 * ```html
10176 * <button on-touch="onTouch()" class="button">Test</button>
10177 * ```
10178 */
10179
10180
10181/**
10182 * @ngdoc directive
10183 * @name onRelease
10184 * @module ionic
10185 * @restrict A
10186 *
10187 * @description
10188 * Called when the user ends a touch.
10189 *
10190 * @usage
10191 * ```html
10192 * <button on-release="onRelease()" class="button">Test</button>
10193 * ```
10194 */
10195
10196/**
10197 * @ngdoc directive
10198 * @name onDragStart
10199 * @module ionic
10200 * @restrict A
10201 *
10202 * @description
10203 * Called when a drag gesture has started.
10204 *
10205 * @usage
10206 * ```html
10207 * <button on-drag-start="onDragStart()" class="button">Test</button>
10208 * ```
10209 */
10210
10211
10212/**
10213 * @ngdoc directive
10214 * @name onDrag
10215 * @module ionic
10216 * @restrict A
10217 *
10218 * @description
10219 * Move with one touch around on the page. Blocking the scrolling when
10220 * moving left and right is a good practice. When all the drag events are
10221 * blocking you disable scrolling on that area.
10222 *
10223 * @usage
10224 * ```html
10225 * <button on-drag="onDrag()" class="button">Test</button>
10226 * ```
10227 */
10228
10229/**
10230 * @ngdoc directive
10231 * @name onDragEnd
10232 * @module ionic
10233 * @restrict A
10234 *
10235 * @description
10236 * Called when a drag gesture has ended.
10237 *
10238 * @usage
10239 * ```html
10240 * <button on-drag-end="onDragEnd()" class="button">Test</button>
10241 * ```
10242 */
10243
10244/**
10245 * @ngdoc directive
10246 * @name onDragUp
10247 * @module ionic
10248 * @restrict A
10249 *
10250 * @description
10251 * Called when the element is dragged up.
10252 *
10253 * @usage
10254 * ```html
10255 * <button on-drag-up="onDragUp()" class="button">Test</button>
10256 * ```
10257 */
10258
10259
10260/**
10261 * @ngdoc directive
10262 * @name onDragRight
10263 * @module ionic
10264 * @restrict A
10265 *
10266 * @description
10267 * Called when the element is dragged to the right.
10268 *
10269 * @usage
10270 * ```html
10271 * <button on-drag-right="onDragRight()" class="button">Test</button>
10272 * ```
10273 */
10274
10275
10276/**
10277 * @ngdoc directive
10278 * @name onDragDown
10279 * @module ionic
10280 * @restrict A
10281 *
10282 * @description
10283 * Called when the element is dragged down.
10284 *
10285 * @usage
10286 * ```html
10287 * <button on-drag-down="onDragDown()" class="button">Test</button>
10288 * ```
10289 */
10290
10291
10292/**
10293 * @ngdoc directive
10294 * @name onDragLeft
10295 * @module ionic
10296 * @restrict A
10297 *
10298 * @description
10299 * Called when the element is dragged to the left.
10300 *
10301 * @usage
10302 * ```html
10303 * <button on-drag-left="onDragLeft()" class="button">Test</button>
10304 * ```
10305 */
10306
10307
10308/**
10309 * @ngdoc directive
10310 * @name onSwipe
10311 * @module ionic
10312 * @restrict A
10313 *
10314 * @description
10315 * Called when a moving touch has a high velocity in any direction.
10316 *
10317 * @usage
10318 * ```html
10319 * <button on-swipe="onSwipe()" class="button">Test</button>
10320 * ```
10321 */
10322
10323
10324/**
10325 * @ngdoc directive
10326 * @name onSwipeUp
10327 * @module ionic
10328 * @restrict A
10329 *
10330 * @description
10331 * Called when a moving touch has a high velocity moving up.
10332 *
10333 * @usage
10334 * ```html
10335 * <button on-swipe-up="onSwipeUp()" class="button">Test</button>
10336 * ```
10337 */
10338
10339
10340/**
10341 * @ngdoc directive
10342 * @name onSwipeRight
10343 * @module ionic
10344 * @restrict A
10345 *
10346 * @description
10347 * Called when a moving touch has a high velocity moving to the right.
10348 *
10349 * @usage
10350 * ```html
10351 * <button on-swipe-right="onSwipeRight()" class="button">Test</button>
10352 * ```
10353 */
10354
10355
10356/**
10357 * @ngdoc directive
10358 * @name onSwipeDown
10359 * @module ionic
10360 * @restrict A
10361 *
10362 * @description
10363 * Called when a moving touch has a high velocity moving down.
10364 *
10365 * @usage
10366 * ```html
10367 * <button on-swipe-down="onSwipeDown()" class="button">Test</button>
10368 * ```
10369 */
10370
10371
10372/**
10373 * @ngdoc directive
10374 * @name onSwipeLeft
10375 * @module ionic
10376 * @restrict A
10377 *
10378 * @description
10379 * Called when a moving touch has a high velocity moving to the left.
10380 *
10381 * @usage
10382 * ```html
10383 * <button on-swipe-left="onSwipeLeft()" class="button">Test</button>
10384 * ```
10385 */
10386
10387
10388function gestureDirective(directiveName) {
10389  return ['$ionicGesture', '$parse', function($ionicGesture, $parse) {
10390    var eventType = directiveName.substr(2).toLowerCase();
10391
10392    return function(scope, element, attr) {
10393      var fn = $parse( attr[directiveName] );
10394
10395      var listener = function(ev) {
10396        scope.$apply(function() {
10397          fn(scope, {
10398            $event: ev
10399          });
10400        });
10401      };
10402
10403      var gesture = $ionicGesture.on(eventType, listener, element);
10404
10405      scope.$on('$destroy', function() {
10406        $ionicGesture.off(gesture, eventType, listener);
10407      });
10408    };
10409  }];
10410}
10411
10412
10413IonicModule
10414//.directive('ionHeaderBar', tapScrollToTopDirective())
10415
10416/**
10417 * @ngdoc directive
10418 * @name ionHeaderBar
10419 * @module ionic
10420 * @restrict E
10421 *
10422 * @description
10423 * Adds a fixed header bar above some content.
10424 *
10425 * Can also be a subheader (lower down) if the 'bar-subheader' class is applied.
10426 * See [the header CSS docs](/docs/components/#subheader).
10427 *
10428 * @param {string=} align-title How to align the title. By default the title
10429 * will be aligned the same as how the platform aligns its titles (iOS centers
10430 * titles, Android aligns them left).
10431 * Available: 'left', 'right', or 'center'.  Defaults to the same as the platform.
10432 * @param {boolean=} no-tap-scroll By default, the header bar will scroll the
10433 * content to the top when tapped.  Set no-tap-scroll to true to disable this
10434 * behavior.
10435 * Available: true or false.  Defaults to false.
10436 *
10437 * @usage
10438 * ```html
10439 * <ion-header-bar align-title="left" class="bar-positive">
10440 *   <div class="buttons">
10441 *     <button class="button" ng-click="doSomething()">Left Button</button>
10442 *   </div>
10443 *   <h1 class="title">Title!</h1>
10444 *   <div class="buttons">
10445 *     <button class="button">Right Button</button>
10446 *   </div>
10447 * </ion-header-bar>
10448 * <ion-content class="has-header">
10449 *   Some content!
10450 * </ion-content>
10451 * ```
10452 */
10453.directive('ionHeaderBar', headerFooterBarDirective(true))
10454
10455/**
10456 * @ngdoc directive
10457 * @name ionFooterBar
10458 * @module ionic
10459 * @restrict E
10460 *
10461 * @description
10462 * Adds a fixed footer bar below some content.
10463 *
10464 * Can also be a subfooter (higher up) if the 'bar-subfooter' class is applied.
10465 * See [the footer CSS docs](/docs/components/#footer).
10466 *
10467 * Note: If you use ionFooterBar in combination with ng-if, the surrounding content
10468 * will not align correctly.  This will be fixed soon.
10469 *
10470 * @param {string=} align-title Where to align the title.
10471 * Available: 'left', 'right', or 'center'.  Defaults to 'center'.
10472 *
10473 * @usage
10474 * ```html
10475 * <ion-content class="has-footer">
10476 *   Some content!
10477 * </ion-content>
10478 * <ion-footer-bar align-title="left" class="bar-assertive">
10479 *   <div class="buttons">
10480 *     <button class="button">Left Button</button>
10481 *   </div>
10482 *   <h1 class="title">Title!</h1>
10483 *   <div class="buttons" ng-click="doSomething()">
10484 *     <button class="button">Right Button</button>
10485 *   </div>
10486 * </ion-footer-bar>
10487 * ```
10488 */
10489.directive('ionFooterBar', headerFooterBarDirective(false));
10490
10491function tapScrollToTopDirective() { //eslint-disable-line no-unused-vars
10492  return ['$ionicScrollDelegate', function($ionicScrollDelegate) {
10493    return {
10494      restrict: 'E',
10495      link: function($scope, $element, $attr) {
10496        if ($attr.noTapScroll == 'true') {
10497          return;
10498        }
10499        ionic.on('tap', onTap, $element[0]);
10500        $scope.$on('$destroy', function() {
10501          ionic.off('tap', onTap, $element[0]);
10502        });
10503
10504        function onTap(e) {
10505          var depth = 3;
10506          var current = e.target;
10507          //Don't scroll to top in certain cases
10508          while (depth-- && current) {
10509            if (current.classList.contains('button') ||
10510                current.tagName.match(/input|textarea|select/i) ||
10511                current.isContentEditable) {
10512              return;
10513            }
10514            current = current.parentNode;
10515          }
10516          var touch = e.gesture && e.gesture.touches[0] || e.detail.touches[0];
10517          var bounds = $element[0].getBoundingClientRect();
10518          if (ionic.DomUtil.rectContains(
10519            touch.pageX, touch.pageY,
10520            bounds.left, bounds.top - 20,
10521            bounds.left + bounds.width, bounds.top + bounds.height
10522          )) {
10523            $ionicScrollDelegate.scrollTop(true);
10524          }
10525        }
10526      }
10527    };
10528  }];
10529}
10530
10531function headerFooterBarDirective(isHeader) {
10532  return ['$document', '$timeout', function($document, $timeout) {
10533    return {
10534      restrict: 'E',
10535      controller: '$ionicHeaderBar',
10536      compile: function(tElement) {
10537        tElement.addClass(isHeader ? 'bar bar-header' : 'bar bar-footer');
10538        // top style tabs? if so, remove bottom border for seamless display
10539        $timeout(function() {
10540          if (isHeader && $document[0].getElementsByClassName('tabs-top').length) tElement.addClass('has-tabs-top');
10541        });
10542
10543        return { pre: prelink };
10544        function prelink($scope, $element, $attr, ctrl) {
10545          if (isHeader) {
10546            $scope.$watch(function() { return $element[0].className; }, function(value) {
10547              var isShown = value.indexOf('ng-hide') === -1;
10548              var isSubheader = value.indexOf('bar-subheader') !== -1;
10549              $scope.$hasHeader = isShown && !isSubheader;
10550              $scope.$hasSubheader = isShown && isSubheader;
10551              $scope.$emit('$ionicSubheader', $scope.$hasSubheader);
10552            });
10553            $scope.$on('$destroy', function() {
10554              delete $scope.$hasHeader;
10555              delete $scope.$hasSubheader;
10556            });
10557            ctrl.align();
10558            $scope.$on('$ionicHeader.align', function() {
10559              ionic.requestAnimationFrame(function() {
10560                ctrl.align();
10561              });
10562            });
10563
10564          } else {
10565            $scope.$watch(function() { return $element[0].className; }, function(value) {
10566              var isShown = value.indexOf('ng-hide') === -1;
10567              var isSubfooter = value.indexOf('bar-subfooter') !== -1;
10568              $scope.$hasFooter = isShown && !isSubfooter;
10569              $scope.$hasSubfooter = isShown && isSubfooter;
10570            });
10571            $scope.$on('$destroy', function() {
10572              delete $scope.$hasFooter;
10573              delete $scope.$hasSubfooter;
10574            });
10575            $scope.$watch('$hasTabs', function(val) {
10576              $element.toggleClass('has-tabs', !!val);
10577            });
10578            ctrl.align();
10579            $scope.$on('$ionicFooter.align', function() {
10580              ionic.requestAnimationFrame(function() {
10581                ctrl.align();
10582              });
10583            });
10584          }
10585        }
10586      }
10587    };
10588  }];
10589}
10590
10591/**
10592 * @ngdoc directive
10593 * @name ionInfiniteScroll
10594 * @module ionic
10595 * @parent ionic.directive:ionContent, ionic.directive:ionScroll
10596 * @restrict E
10597 *
10598 * @description
10599 * The ionInfiniteScroll directive allows you to call a function whenever
10600 * the user gets to the bottom of the page or near the bottom of the page.
10601 *
10602 * The expression you pass in for `on-infinite` is called when the user scrolls
10603 * greater than `distance` away from the bottom of the content.  Once `on-infinite`
10604 * is done loading new data, it should broadcast the `scroll.infiniteScrollComplete`
10605 * event from your controller (see below example).
10606 *
10607 * @param {expression} on-infinite What to call when the scroller reaches the
10608 * bottom.
10609 * @param {string=} distance The distance from the bottom that the scroll must
10610 * reach to trigger the on-infinite expression. Default: 1%.
10611 * @param {string=} spinner The {@link ionic.directive:ionSpinner} to show while loading. The SVG
10612 * {@link ionic.directive:ionSpinner} is now the default, replacing rotating font icons.
10613 * @param {string=} icon The icon to show while loading. Default: 'ion-load-d'.  This is depreicated
10614 * in favor of the SVG {@link ionic.directive:ionSpinner}.
10615 * @param {boolean=} immediate-check Whether to check the infinite scroll bounds immediately on load.
10616 *
10617 * @usage
10618 * ```html
10619 * <ion-content ng-controller="MyController">
10620 *   <ion-list>
10621 *   ....
10622 *   ....
10623 *   </ion-list>
10624 *
10625 *   <ion-infinite-scroll
10626 *     on-infinite="loadMore()"
10627 *     distance="1%">
10628 *   </ion-infinite-scroll>
10629 * </ion-content>
10630 * ```
10631 * ```js
10632 * function MyController($scope, $http) {
10633 *   $scope.items = [];
10634 *   $scope.loadMore = function() {
10635 *     $http.get('/more-items').success(function(items) {
10636 *       useItems(items);
10637 *       $scope.$broadcast('scroll.infiniteScrollComplete');
10638 *     });
10639 *   };
10640 *
10641 *   $scope.$on('$stateChangeSuccess', function() {
10642 *     $scope.loadMore();
10643 *   });
10644 * }
10645 * ```
10646 *
10647 * An easy to way to stop infinite scroll once there is no more data to load
10648 * is to use angular's `ng-if` directive:
10649 *
10650 * ```html
10651 * <ion-infinite-scroll
10652 *   ng-if="moreDataCanBeLoaded()"
10653 *   icon="ion-loading-c"
10654 *   on-infinite="loadMoreData()">
10655 * </ion-infinite-scroll>
10656 * ```
10657 */
10658IonicModule
10659.directive('ionInfiniteScroll', ['$timeout', function($timeout) {
10660  return {
10661    restrict: 'E',
10662    require: ['?^$ionicScroll', 'ionInfiniteScroll'],
10663    template: function($element, $attrs) {
10664      if ($attrs.icon) return '<i class="icon {{icon()}} icon-refreshing {{scrollingType}}"></i>';
10665      return '<ion-spinner icon="{{spinner()}}"></ion-spinner>';
10666    },
10667    scope: true,
10668    controller: '$ionInfiniteScroll',
10669    link: function($scope, $element, $attrs, ctrls) {
10670      var infiniteScrollCtrl = ctrls[1];
10671      var scrollCtrl = infiniteScrollCtrl.scrollCtrl = ctrls[0];
10672      var jsScrolling = infiniteScrollCtrl.jsScrolling = !scrollCtrl.isNative();
10673
10674      // if this view is not beneath a scrollCtrl, it can't be injected, proceed w/ native scrolling
10675      if (jsScrolling) {
10676        infiniteScrollCtrl.scrollView = scrollCtrl.scrollView;
10677        $scope.scrollingType = 'js-scrolling';
10678        //bind to JS scroll events
10679        scrollCtrl.$element.on('scroll', infiniteScrollCtrl.checkBounds);
10680      } else {
10681        // grabbing the scrollable element, to determine dimensions, and current scroll pos
10682        var scrollEl = ionic.DomUtil.getParentOrSelfWithClass($element[0].parentNode, 'overflow-scroll');
10683        infiniteScrollCtrl.scrollEl = scrollEl;
10684        // if there's no scroll controller, and no overflow scroll div, infinite scroll wont work
10685        if (!scrollEl) {
10686          throw 'Infinite scroll must be used inside a scrollable div';
10687        }
10688        //bind to native scroll events
10689        infiniteScrollCtrl.scrollEl.addEventListener('scroll', infiniteScrollCtrl.checkBounds);
10690      }
10691
10692      // Optionally check bounds on start after scrollView is fully rendered
10693      var doImmediateCheck = isDefined($attrs.immediateCheck) ? $scope.$eval($attrs.immediateCheck) : true;
10694      if (doImmediateCheck) {
10695        $timeout(function() { infiniteScrollCtrl.checkBounds(); });
10696      }
10697    }
10698  };
10699}]);
10700
10701/**
10702* @ngdoc directive
10703* @name ionInput
10704* @parent ionic.directive:ionList
10705* @module ionic
10706* @restrict E
10707* Creates a text input group that can easily be focused
10708*
10709* @usage
10710*
10711* ```html
10712* <ion-list>
10713*   <ion-input>
10714*     <input type="text" placeholder="First Name">
10715*   </ion-input>
10716*
10717*   <ion-input>
10718*     <ion-label>Username</ion-label>
10719*     <input type="text">
10720*   </ion-input>
10721* </ion-list>
10722* ```
10723*/
10724
10725var labelIds = -1;
10726
10727IonicModule
10728.directive('ionInput', [function() {
10729  return {
10730    restrict: 'E',
10731    controller: ['$scope', '$element', function($scope, $element) {
10732      this.$scope = $scope;
10733      this.$element = $element;
10734
10735      this.setInputAriaLabeledBy = function(id) {
10736        var inputs = $element[0].querySelectorAll('input,textarea');
10737        inputs.length && inputs[0].setAttribute('aria-labelledby', id);
10738      };
10739
10740      this.focus = function() {
10741        var inputs = $element[0].querySelectorAll('input,textarea');
10742        inputs.length && inputs[0].focus();
10743      };
10744    }]
10745  };
10746}]);
10747
10748/**
10749* @ngdoc directive
10750* @name ionLabel
10751* @parent ionic.directive:ionList
10752* @module ionic
10753* @restrict E
10754*
10755* New in Ionic 1.2. It is strongly recommended that you use `<ion-label>` in place
10756* of any `<label>` elements for maximum cross-browser support and performance.
10757*
10758* Creates a label for a form input.
10759*
10760* @usage
10761*
10762* ```html
10763* <ion-list>
10764*   <ion-input>
10765*     <ion-label>Username</ion-label>
10766*     <input type="text">
10767*   </ion-input>
10768* </ion-list>
10769* ```
10770*/
10771IonicModule
10772.directive('ionLabel', [function() {
10773  return {
10774    restrict: 'E',
10775    require: '?^ionInput',
10776    compile: function() {
10777
10778      return function link($scope, $element, $attrs, ionInputCtrl) {
10779        var element = $element[0];
10780
10781        $element.addClass('input-label');
10782
10783        $element.attr('aria-label', $element.text());
10784        var id = element.id || '_label-' + ++labelIds;
10785
10786        if (!element.id) {
10787          $element.attr('id', id);
10788        }
10789
10790        if (ionInputCtrl) {
10791
10792          ionInputCtrl.setInputAriaLabeledBy(id);
10793
10794          $element.on('click', function() {
10795            ionInputCtrl.focus();
10796          });
10797        }
10798      };
10799    }
10800  };
10801}]);
10802
10803/**
10804 * Input label adds accessibility to <span class="input-label">.
10805 */
10806IonicModule
10807.directive('inputLabel', [function() {
10808  return {
10809    restrict: 'C',
10810    require: '?^ionInput',
10811    compile: function() {
10812
10813      return function link($scope, $element, $attrs, ionInputCtrl) {
10814        var element = $element[0];
10815
10816        $element.attr('aria-label', $element.text());
10817        var id = element.id || '_label-' + ++labelIds;
10818
10819        if (!element.id) {
10820          $element.attr('id', id);
10821        }
10822
10823        if (ionInputCtrl) {
10824          ionInputCtrl.setInputAriaLabeledBy(id);
10825        }
10826
10827      };
10828    }
10829  };
10830}]);
10831
10832/**
10833* @ngdoc directive
10834* @name ionItem
10835* @parent ionic.directive:ionList
10836* @module ionic
10837* @restrict E
10838* Creates a list-item that can easily be swiped,
10839* deleted, reordered, edited, and more.
10840*
10841* See {@link ionic.directive:ionList} for a complete example & explanation.
10842*
10843* Can be assigned any item class name. See the
10844* [list CSS documentation](/docs/components/#list).
10845*
10846* @usage
10847*
10848* ```html
10849* <ion-list>
10850*   <ion-item>Hello!</ion-item>
10851*   <ion-item href="#/detail">
10852*     Link to detail page
10853*   </ion-item>
10854* </ion-list>
10855* ```
10856*/
10857IonicModule
10858.directive('ionItem', ['$$rAF', function($$rAF) {
10859  return {
10860    restrict: 'E',
10861    controller: ['$scope', '$element', function($scope, $element) {
10862      this.$scope = $scope;
10863      this.$element = $element;
10864    }],
10865    scope: true,
10866    compile: function($element, $attrs) {
10867      var isAnchor = isDefined($attrs.href) ||
10868                     isDefined($attrs.ngHref) ||
10869                     isDefined($attrs.uiSref);
10870      var isComplexItem = isAnchor ||
10871        //Lame way of testing, but we have to know at compile what to do with the element
10872        /ion-(delete|option|reorder)-button/i.test($element.html());
10873
10874      if (isComplexItem) {
10875        var innerElement = jqLite(isAnchor ? '<a></a>' : '<div></div>');
10876        innerElement.addClass('item-content');
10877
10878        if (isDefined($attrs.href) || isDefined($attrs.ngHref)) {
10879          innerElement.attr('ng-href', '{{$href()}}');
10880          if (isDefined($attrs.target)) {
10881            innerElement.attr('target', '{{$target()}}');
10882          }
10883        }
10884
10885        innerElement.append($element.contents());
10886
10887        $element.addClass('item item-complex')
10888                .append(innerElement);
10889      } else {
10890        $element.addClass('item');
10891      }
10892
10893      return function link($scope, $element, $attrs) {
10894        $scope.$href = function() {
10895          return $attrs.href || $attrs.ngHref;
10896        };
10897        $scope.$target = function() {
10898          return $attrs.target;
10899        };
10900
10901        var content = $element[0].querySelector('.item-content');
10902        if (content) {
10903          $scope.$on('$collectionRepeatLeave', function() {
10904            if (content && content.$$ionicOptionsOpen) {
10905              content.style[ionic.CSS.TRANSFORM] = '';
10906              content.style[ionic.CSS.TRANSITION] = 'none';
10907              $$rAF(function() {
10908                content.style[ionic.CSS.TRANSITION] = '';
10909              });
10910              content.$$ionicOptionsOpen = false;
10911            }
10912          });
10913        }
10914      };
10915
10916    }
10917  };
10918}]);
10919
10920var ITEM_TPL_DELETE_BUTTON =
10921  '<div class="item-left-edit item-delete enable-pointer-events">' +
10922  '</div>';
10923/**
10924* @ngdoc directive
10925* @name ionDeleteButton
10926* @parent ionic.directive:ionItem
10927* @module ionic
10928* @restrict E
10929* Creates a delete button inside a list item, that is visible when the
10930* {@link ionic.directive:ionList ionList parent's} `show-delete` evaluates to true or
10931* `$ionicListDelegate.showDelete(true)` is called.
10932*
10933* Takes any ionicon as a class.
10934*
10935* See {@link ionic.directive:ionList} for a complete example & explanation.
10936*
10937* @usage
10938*
10939* ```html
10940* <ion-list show-delete="shouldShowDelete">
10941*   <ion-item>
10942*     <ion-delete-button class="ion-minus-circled"></ion-delete-button>
10943*     Hello, list item!
10944*   </ion-item>
10945* </ion-list>
10946* <ion-toggle ng-model="shouldShowDelete">
10947*   Show Delete?
10948* </ion-toggle>
10949* ```
10950*/
10951IonicModule
10952.directive('ionDeleteButton', function() {
10953
10954  function stopPropagation(ev) {
10955    ev.stopPropagation();
10956  }
10957
10958  return {
10959    restrict: 'E',
10960    require: ['^^ionItem', '^?ionList'],
10961    //Run before anything else, so we can move it before other directives process
10962    //its location (eg ngIf relies on the location of the directive in the dom)
10963    priority: Number.MAX_VALUE,
10964    compile: function($element, $attr) {
10965      //Add the classes we need during the compile phase, so that they stay
10966      //even if something else like ngIf removes the element and re-addss it
10967      $attr.$set('class', ($attr['class'] || '') + ' button icon button-icon', true);
10968      return function($scope, $element, $attr, ctrls) {
10969        var itemCtrl = ctrls[0];
10970        var listCtrl = ctrls[1];
10971        var container = jqLite(ITEM_TPL_DELETE_BUTTON);
10972        container.append($element);
10973        itemCtrl.$element.append(container).addClass('item-left-editable');
10974
10975        //Don't bubble click up to main .item
10976        $element.on('click', stopPropagation);
10977
10978        init();
10979        $scope.$on('$ionic.reconnectScope', init);
10980        function init() {
10981          listCtrl = listCtrl || $element.controller('ionList');
10982          if (listCtrl && listCtrl.showDelete()) {
10983            container.addClass('visible active');
10984          }
10985        }
10986      };
10987    }
10988  };
10989});
10990
10991
10992IonicModule
10993.directive('itemFloatingLabel', function() {
10994  return {
10995    restrict: 'C',
10996    link: function(scope, element) {
10997      var el = element[0];
10998      var input = el.querySelector('input, textarea');
10999      var inputLabel = el.querySelector('.input-label');
11000
11001      if (!input || !inputLabel) return;
11002
11003      var onInput = function() {
11004        if (input.value) {
11005          inputLabel.classList.add('has-input');
11006        } else {
11007          inputLabel.classList.remove('has-input');
11008        }
11009      };
11010
11011      input.addEventListener('input', onInput);
11012
11013      var ngModelCtrl = jqLite(input).controller('ngModel');
11014      if (ngModelCtrl) {
11015        ngModelCtrl.$render = function() {
11016          input.value = ngModelCtrl.$viewValue || '';
11017          onInput();
11018        };
11019      }
11020
11021      scope.$on('$destroy', function() {
11022        input.removeEventListener('input', onInput);
11023      });
11024    }
11025  };
11026});
11027
11028var ITEM_TPL_OPTION_BUTTONS =
11029  '<div class="item-options invisible">' +
11030  '</div>';
11031/**
11032* @ngdoc directive
11033* @name ionOptionButton
11034* @parent ionic.directive:ionItem
11035* @module ionic
11036* @restrict E
11037* @description
11038* Creates an option button inside a list item, that is visible when the item is swiped
11039* to the left by the user.  Swiped open option buttons can be hidden with
11040* {@link ionic.service:$ionicListDelegate#closeOptionButtons $ionicListDelegate.closeOptionButtons}.
11041*
11042* Can be assigned any button class.
11043*
11044* See {@link ionic.directive:ionList} for a complete example & explanation.
11045*
11046* @usage
11047*
11048* ```html
11049* <ion-list>
11050*   <ion-item>
11051*     I love kittens!
11052*     <ion-option-button class="button-positive">Share</ion-option-button>
11053*     <ion-option-button class="button-assertive">Edit</ion-option-button>
11054*   </ion-item>
11055* </ion-list>
11056* ```
11057*/
11058IonicModule.directive('ionOptionButton', [function() {
11059  function stopPropagation(e) {
11060    e.stopPropagation();
11061  }
11062  return {
11063    restrict: 'E',
11064    require: '^ionItem',
11065    priority: Number.MAX_VALUE,
11066    compile: function($element, $attr) {
11067      $attr.$set('class', ($attr['class'] || '') + ' button', true);
11068      return function($scope, $element, $attr, itemCtrl) {
11069        if (!itemCtrl.optionsContainer) {
11070          itemCtrl.optionsContainer = jqLite(ITEM_TPL_OPTION_BUTTONS);
11071          itemCtrl.$element.append(itemCtrl.optionsContainer);
11072        }
11073        itemCtrl.optionsContainer.append($element);
11074
11075        itemCtrl.$element.addClass('item-right-editable');
11076
11077        //Don't bubble click up to main .item
11078        $element.on('click', stopPropagation);
11079      };
11080    }
11081  };
11082}]);
11083
11084var ITEM_TPL_REORDER_BUTTON =
11085  '<div data-prevent-scroll="true" class="item-right-edit item-reorder enable-pointer-events">' +
11086  '</div>';
11087
11088/**
11089* @ngdoc directive
11090* @name ionReorderButton
11091* @parent ionic.directive:ionItem
11092* @module ionic
11093* @restrict E
11094* Creates a reorder button inside a list item, that is visible when the
11095* {@link ionic.directive:ionList ionList parent's} `show-reorder` evaluates to true or
11096* `$ionicListDelegate.showReorder(true)` is called.
11097*
11098* Can be dragged to reorder items in the list. Takes any ionicon class.
11099*
11100* Note: Reordering works best when used with `ng-repeat`.  Be sure that all `ion-item` children of an `ion-list` are part of the same `ng-repeat` expression.
11101*
11102* When an item reorder is complete, the expression given in the `on-reorder` attribute is called. The `on-reorder` expression is given two locals that can be used: `$fromIndex` and `$toIndex`.  See below for an example.
11103*
11104* Look at {@link ionic.directive:ionList} for more examples.
11105*
11106* @usage
11107*
11108* ```html
11109* <ion-list ng-controller="MyCtrl" show-reorder="true">
11110*   <ion-item ng-repeat="item in items">
11111*     Item {{item}}
11112*     <ion-reorder-button class="ion-navicon"
11113*                         on-reorder="moveItem(item, $fromIndex, $toIndex)">
11114*     </ion-reorder-button>
11115*   </ion-item>
11116* </ion-list>
11117* ```
11118* ```js
11119* function MyCtrl($scope) {
11120*   $scope.items = [1, 2, 3, 4];
11121*   $scope.moveItem = function(item, fromIndex, toIndex) {
11122*     //Move the item in the array
11123*     $scope.items.splice(fromIndex, 1);
11124*     $scope.items.splice(toIndex, 0, item);
11125*   };
11126* }
11127* ```
11128*
11129* @param {expression=} on-reorder Expression to call when an item is reordered.
11130* Parameters given: $fromIndex, $toIndex.
11131*/
11132IonicModule
11133.directive('ionReorderButton', ['$parse', function($parse) {
11134  return {
11135    restrict: 'E',
11136    require: ['^ionItem', '^?ionList'],
11137    priority: Number.MAX_VALUE,
11138    compile: function($element, $attr) {
11139      $attr.$set('class', ($attr['class'] || '') + ' button icon button-icon', true);
11140      $element[0].setAttribute('data-prevent-scroll', true);
11141      return function($scope, $element, $attr, ctrls) {
11142        var itemCtrl = ctrls[0];
11143        var listCtrl = ctrls[1];
11144        var onReorderFn = $parse($attr.onReorder);
11145
11146        $scope.$onReorder = function(oldIndex, newIndex) {
11147          onReorderFn($scope, {
11148            $fromIndex: oldIndex,
11149            $toIndex: newIndex
11150          });
11151        };
11152
11153        // prevent clicks from bubbling up to the item
11154        if (!$attr.ngClick && !$attr.onClick && !$attr.onclick) {
11155          $element[0].onclick = function(e) {
11156            e.stopPropagation();
11157            return false;
11158          };
11159        }
11160
11161        var container = jqLite(ITEM_TPL_REORDER_BUTTON);
11162        container.append($element);
11163        itemCtrl.$element.append(container).addClass('item-right-editable');
11164
11165        if (listCtrl && listCtrl.showReorder()) {
11166          container.addClass('visible active');
11167        }
11168      };
11169    }
11170  };
11171}]);
11172
11173/**
11174 * @ngdoc directive
11175 * @name keyboardAttach
11176 * @module ionic
11177 * @restrict A
11178 *
11179 * @description
11180 * keyboard-attach is an attribute directive which will cause an element to float above
11181 * the keyboard when the keyboard shows. Currently only supports the
11182 * [ion-footer-bar]({{ page.versionHref }}/api/directive/ionFooterBar/) directive.
11183 *
11184 * ### Notes
11185 * - This directive requires the
11186 * [Ionic Keyboard Plugin](https://github.com/driftyco/ionic-plugins-keyboard).
11187 * - On Android not in fullscreen mode, i.e. you have
11188 *   `<preference name="Fullscreen" value="false" />` or no preference in your `config.xml` file,
11189 *   this directive is unnecessary since it is the default behavior.
11190 * - On iOS, if there is an input in your footer, you will need to set
11191 *   `cordova.plugins.Keyboard.disableScroll(true)`.
11192 *
11193 * @usage
11194 *
11195 * ```html
11196 *  <ion-footer-bar align-title="left" keyboard-attach class="bar-assertive">
11197 *    <h1 class="title">Title!</h1>
11198 *  </ion-footer-bar>
11199 * ```
11200 */
11201
11202IonicModule
11203.directive('keyboardAttach', function() {
11204  return function(scope, element) {
11205    ionic.on('native.keyboardshow', onShow, window);
11206    ionic.on('native.keyboardhide', onHide, window);
11207
11208    //deprecated
11209    ionic.on('native.showkeyboard', onShow, window);
11210    ionic.on('native.hidekeyboard', onHide, window);
11211
11212
11213    var scrollCtrl;
11214
11215    function onShow(e) {
11216      if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {
11217        return;
11218      }
11219
11220      //for testing
11221      var keyboardHeight = e.keyboardHeight || (e.detail && e.detail.keyboardHeight);
11222      element.css('bottom', keyboardHeight + "px");
11223      scrollCtrl = element.controller('$ionicScroll');
11224      if (scrollCtrl) {
11225        scrollCtrl.scrollView.__container.style.bottom = keyboardHeight + keyboardAttachGetClientHeight(element[0]) + "px";
11226      }
11227    }
11228
11229    function onHide() {
11230      if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {
11231        return;
11232      }
11233
11234      element.css('bottom', '');
11235      if (scrollCtrl) {
11236        scrollCtrl.scrollView.__container.style.bottom = '';
11237      }
11238    }
11239
11240    scope.$on('$destroy', function() {
11241      ionic.off('native.keyboardshow', onShow, window);
11242      ionic.off('native.keyboardhide', onHide, window);
11243
11244      //deprecated
11245      ionic.off('native.showkeyboard', onShow, window);
11246      ionic.off('native.hidekeyboard', onHide, window);
11247    });
11248  };
11249});
11250
11251function keyboardAttachGetClientHeight(element) {
11252  return element.clientHeight;
11253}
11254
11255/**
11256* @ngdoc directive
11257* @name ionList
11258* @module ionic
11259* @delegate ionic.service:$ionicListDelegate
11260* @codepen JsHjf
11261* @restrict E
11262* @description
11263* The List is a widely used interface element in almost any mobile app, and can include
11264* content ranging from basic text all the way to buttons, toggles, icons, and thumbnails.
11265*
11266* Both the list, which contains items, and the list items themselves can be any HTML
11267* element. The containing element requires the `list` class and each list item requires
11268* the `item` class.
11269*
11270* However, using the ionList and ionItem directives make it easy to support various
11271* interaction modes such as swipe to edit, drag to reorder, and removing items.
11272*
11273* Related: {@link ionic.directive:ionItem}, {@link ionic.directive:ionOptionButton}
11274* {@link ionic.directive:ionReorderButton}, {@link ionic.directive:ionDeleteButton}, [`list CSS documentation`](/docs/components/#list).
11275*
11276* @usage
11277*
11278* Basic Usage:
11279*
11280* ```html
11281* <ion-list>
11282*   <ion-item ng-repeat="item in items">
11283*     {% raw %}Hello, {{item}}!{% endraw %}
11284*   </ion-item>
11285* </ion-list>
11286* ```
11287*
11288* Advanced Usage: Thumbnails, Delete buttons, Reordering, Swiping
11289*
11290* ```html
11291* <ion-list ng-controller="MyCtrl"
11292*           show-delete="shouldShowDelete"
11293*           show-reorder="shouldShowReorder"
11294*           can-swipe="listCanSwipe">
11295*   <ion-item ng-repeat="item in items"
11296*             class="item-thumbnail-left">
11297*
11298*     {% raw %}<img ng-src="{{item.img}}">
11299*     <h2>{{item.title}}</h2>
11300*     <p>{{item.description}}</p>{% endraw %}
11301*     <ion-option-button class="button-positive"
11302*                        ng-click="share(item)">
11303*       Share
11304*     </ion-option-button>
11305*     <ion-option-button class="button-info"
11306*                        ng-click="edit(item)">
11307*       Edit
11308*     </ion-option-button>
11309*     <ion-delete-button class="ion-minus-circled"
11310*                        ng-click="items.splice($index, 1)">
11311*     </ion-delete-button>
11312*     <ion-reorder-button class="ion-navicon"
11313*                         on-reorder="reorderItem(item, $fromIndex, $toIndex)">
11314*     </ion-reorder-button>
11315*
11316*   </ion-item>
11317* </ion-list>
11318* ```
11319*
11320*```javascript
11321* app.controller('MyCtrl', function($scope) {
11322*  $scope.shouldShowDelete = false;
11323*  $scope.shouldShowReorder = false;
11324*  $scope.listCanSwipe = true
11325* });
11326*```
11327*
11328* @param {string=} delegate-handle The handle used to identify this list with
11329* {@link ionic.service:$ionicListDelegate}.
11330* @param type {string=} The type of list to use (list-inset or card)
11331* @param show-delete {boolean=} Whether the delete buttons for the items in the list are
11332* currently shown or hidden.
11333* @param show-reorder {boolean=} Whether the reorder buttons for the items in the list are
11334* currently shown or hidden.
11335* @param can-swipe {boolean=} Whether the items in the list are allowed to be swiped to reveal
11336* option buttons. Default: true.
11337*/
11338IonicModule
11339.directive('ionList', [
11340  '$timeout',
11341function($timeout) {
11342  return {
11343    restrict: 'E',
11344    require: ['ionList', '^?$ionicScroll'],
11345    controller: '$ionicList',
11346    compile: function($element, $attr) {
11347      var listEl = jqLite('<div class="list">')
11348        .append($element.contents())
11349        .addClass($attr.type);
11350
11351      $element.append(listEl);
11352
11353      return function($scope, $element, $attrs, ctrls) {
11354        var listCtrl = ctrls[0];
11355        var scrollCtrl = ctrls[1];
11356
11357        // Wait for child elements to render...
11358        $timeout(init);
11359
11360        function init() {
11361          var listView = listCtrl.listView = new ionic.views.ListView({
11362            el: $element[0],
11363            listEl: $element.children()[0],
11364            scrollEl: scrollCtrl && scrollCtrl.element,
11365            scrollView: scrollCtrl && scrollCtrl.scrollView,
11366            onReorder: function(el, oldIndex, newIndex) {
11367              var itemScope = jqLite(el).scope();
11368              if (itemScope && itemScope.$onReorder) {
11369                // Make sure onReorder is called in apply cycle,
11370                // but also make sure it has no conflicts by doing
11371                // $evalAsync
11372                $timeout(function() {
11373                  itemScope.$onReorder(oldIndex, newIndex);
11374                });
11375              }
11376            },
11377            canSwipe: function() {
11378              return listCtrl.canSwipeItems();
11379            }
11380          });
11381
11382          $scope.$on('$destroy', function() {
11383            if (listView) {
11384              listView.deregister && listView.deregister();
11385              listView = null;
11386            }
11387          });
11388
11389          if (isDefined($attr.canSwipe)) {
11390            $scope.$watch('!!(' + $attr.canSwipe + ')', function(value) {
11391              listCtrl.canSwipeItems(value);
11392            });
11393          }
11394          if (isDefined($attr.showDelete)) {
11395            $scope.$watch('!!(' + $attr.showDelete + ')', function(value) {
11396              listCtrl.showDelete(value);
11397            });
11398          }
11399          if (isDefined($attr.showReorder)) {
11400            $scope.$watch('!!(' + $attr.showReorder + ')', function(value) {
11401              listCtrl.showReorder(value);
11402            });
11403          }
11404
11405          $scope.$watch(function() {
11406            return listCtrl.showDelete();
11407          }, function(isShown, wasShown) {
11408            //Only use isShown=false if it was already shown
11409            if (!isShown && !wasShown) { return; }
11410
11411            if (isShown) listCtrl.closeOptionButtons();
11412            listCtrl.canSwipeItems(!isShown);
11413
11414            $element.children().toggleClass('list-left-editing', isShown);
11415            $element.toggleClass('disable-pointer-events', isShown);
11416
11417            var deleteButton = jqLite($element[0].getElementsByClassName('item-delete'));
11418            setButtonShown(deleteButton, listCtrl.showDelete);
11419          });
11420
11421          $scope.$watch(function() {
11422            return listCtrl.showReorder();
11423          }, function(isShown, wasShown) {
11424            //Only use isShown=false if it was already shown
11425            if (!isShown && !wasShown) { return; }
11426
11427            if (isShown) listCtrl.closeOptionButtons();
11428            listCtrl.canSwipeItems(!isShown);
11429
11430            $element.children().toggleClass('list-right-editing', isShown);
11431            $element.toggleClass('disable-pointer-events', isShown);
11432
11433            var reorderButton = jqLite($element[0].getElementsByClassName('item-reorder'));
11434            setButtonShown(reorderButton, listCtrl.showReorder);
11435          });
11436
11437          function setButtonShown(el, shown) {
11438            shown() && el.addClass('visible') || el.removeClass('active');
11439            ionic.requestAnimationFrame(function() {
11440              shown() && el.addClass('active') || el.removeClass('visible');
11441            });
11442          }
11443        }
11444
11445      };
11446    }
11447  };
11448}]);
11449
11450/**
11451 * @ngdoc directive
11452 * @name menuClose
11453 * @module ionic
11454 * @restrict AC
11455 *
11456 * @description
11457 * `menu-close` is an attribute directive that closes a currently opened side menu.
11458 * Note that by default, navigation transitions will not animate between views when
11459 * the menu is open. Additionally, this directive will reset the entering view's
11460 * history stack, making the new page the root of the history stack. This is done
11461 * to replicate the user experience seen in most side menu implementations, which is
11462 * to not show the back button at the root of the stack and show only the
11463 * menu button. We recommend that you also use the `enable-menu-with-back-views="false"`
11464 * {@link ionic.directive:ionSideMenus} attribute when using the menuClose directive.
11465 *
11466 * @usage
11467 * Below is an example of a link within a side menu. Tapping this link would
11468 * automatically close the currently opened menu.
11469 *
11470 * ```html
11471 * <a menu-close href="#/home" class="item">Home</a>
11472 * ```
11473 *
11474 * Note that if your destination state uses a resolve and that resolve asynchronously
11475 * takes longer than a standard transition (300ms), you'll need to set the
11476 * `nextViewOptions` manually as your resolve completes.
11477 *
11478 * ```js
11479 * $ionicHistory.nextViewOptions({
11480 *  historyRoot: true,
11481 *  disableAnimate: true,
11482 *  expire: 300
11483 * });
11484 * ```
11485 */
11486IonicModule
11487.directive('menuClose', ['$ionicHistory', '$timeout', function($ionicHistory, $timeout) {
11488  return {
11489    restrict: 'AC',
11490    link: function($scope, $element) {
11491      $element.bind('click', function() {
11492        var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
11493        if (sideMenuCtrl) {
11494          $ionicHistory.nextViewOptions({
11495            historyRoot: true,
11496            disableAnimate: true,
11497            expire: 300
11498          });
11499          // if no transition in 300ms, reset nextViewOptions
11500          // the expire should take care of it, but will be cancelled in some
11501          // cases. This directive is an exception to the rules of history.js
11502          $timeout( function() {
11503            $ionicHistory.nextViewOptions({
11504              historyRoot: false,
11505              disableAnimate: false
11506            });
11507          }, 300);
11508          sideMenuCtrl.close();
11509        }
11510      });
11511    }
11512  };
11513}]);
11514
11515/**
11516 * @ngdoc directive
11517 * @name menuToggle
11518 * @module ionic
11519 * @restrict AC
11520 *
11521 * @description
11522 * Toggle a side menu on the given side.
11523 *
11524 * @usage
11525 * Below is an example of a link within a nav bar. Tapping this button
11526 * would open the given side menu, and tapping it again would close it.
11527 *
11528 * ```html
11529 * <ion-nav-bar>
11530 *   <ion-nav-buttons side="left">
11531 *    <!-- Toggle left side menu -->
11532 *    <button menu-toggle="left" class="button button-icon icon ion-navicon"></button>
11533 *   </ion-nav-buttons>
11534 *   <ion-nav-buttons side="right">
11535 *    <!-- Toggle right side menu -->
11536 *    <button menu-toggle="right" class="button button-icon icon ion-navicon"></button>
11537 *   </ion-nav-buttons>
11538 * </ion-nav-bar>
11539 * ```
11540 *
11541 * ### Button Hidden On Child Views
11542 * By default, the menu toggle button will only appear on a root
11543 * level side-menu page. Navigating in to child views will hide the menu-
11544 * toggle button. They can be made visible on child pages by setting the
11545 * enable-menu-with-back-views attribute of the {@link ionic.directive:ionSideMenus}
11546 * directive to true.
11547 *
11548 * ```html
11549 * <ion-side-menus enable-menu-with-back-views="true">
11550 * ```
11551 */
11552IonicModule
11553.directive('menuToggle', function() {
11554  return {
11555    restrict: 'AC',
11556    link: function($scope, $element, $attr) {
11557      $scope.$on('$ionicView.beforeEnter', function(ev, viewData) {
11558        if (viewData.enableBack) {
11559          var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
11560          if (!sideMenuCtrl.enableMenuWithBackViews()) {
11561            $element.addClass('hide');
11562          }
11563        } else {
11564          $element.removeClass('hide');
11565        }
11566      });
11567
11568      $element.bind('click', function() {
11569        var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
11570        sideMenuCtrl && sideMenuCtrl.toggle($attr.menuToggle);
11571      });
11572    }
11573  };
11574});
11575
11576/*
11577 * We don't document the ionModal directive, we instead document
11578 * the $ionicModal service
11579 */
11580IonicModule
11581.directive('ionModal', [function() {
11582  return {
11583    restrict: 'E',
11584    transclude: true,
11585    replace: true,
11586    controller: [function() {}],
11587    template: '<div class="modal-backdrop">' +
11588                '<div class="modal-backdrop-bg"></div>' +
11589                '<div class="modal-wrapper" ng-transclude></div>' +
11590              '</div>'
11591  };
11592}]);
11593
11594IonicModule
11595.directive('ionModalView', function() {
11596  return {
11597    restrict: 'E',
11598    compile: function(element) {
11599      element.addClass('modal');
11600    }
11601  };
11602});
11603
11604/**
11605 * @ngdoc directive
11606 * @name ionNavBackButton
11607 * @module ionic
11608 * @restrict E
11609 * @parent ionNavBar
11610 * @description
11611 * Creates a back button inside an {@link ionic.directive:ionNavBar}.
11612 *
11613 * The back button will appear when the user is able to go back in the current navigation stack. By
11614 * default, the markup of the back button is automatically built using platform-appropriate defaults
11615 * (iOS back button icon on iOS and Android icon on Android).
11616 *
11617 * Additionally, the button is automatically set to `$ionicGoBack()` on click/tap. By default, the
11618 * app will navigate back one view when the back button is clicked.  More advanced behavior is also
11619 * possible, as outlined below.
11620 *
11621 * @usage
11622 *
11623 * Recommended markup for default settings:
11624 *
11625 * ```html
11626 * <ion-nav-bar>
11627 *   <ion-nav-back-button>
11628 *   </ion-nav-back-button>
11629 * </ion-nav-bar>
11630 * ```
11631 *
11632 * With custom inner markup, and automatically adds a default click action:
11633 *
11634 * ```html
11635 * <ion-nav-bar>
11636 *   <ion-nav-back-button class="button-clear">
11637 *     <i class="ion-arrow-left-c"></i> Back
11638 *   </ion-nav-back-button>
11639 * </ion-nav-bar>
11640 * ```
11641 *
11642 * With custom inner markup and custom click action, using {@link ionic.service:$ionicHistory}:
11643 *
11644 * ```html
11645 * <ion-nav-bar ng-controller="MyCtrl">
11646 *   <ion-nav-back-button class="button-clear"
11647 *     ng-click="myGoBack()">
11648 *     <i class="ion-arrow-left-c"></i> Back
11649 *   </ion-nav-back-button>
11650 * </ion-nav-bar>
11651 * ```
11652 * ```js
11653 * function MyCtrl($scope, $ionicHistory) {
11654 *   $scope.myGoBack = function() {
11655 *     $ionicHistory.goBack();
11656 *   };
11657 * }
11658 * ```
11659 */
11660IonicModule
11661.directive('ionNavBackButton', ['$ionicConfig', '$document', function($ionicConfig, $document) {
11662  return {
11663    restrict: 'E',
11664    require: '^ionNavBar',
11665    compile: function(tElement, tAttrs) {
11666
11667      // clone the back button, but as a <div>
11668      var buttonEle = $document[0].createElement('button');
11669      for (var n in tAttrs.$attr) {
11670        buttonEle.setAttribute(tAttrs.$attr[n], tAttrs[n]);
11671      }
11672
11673      if (!tAttrs.ngClick) {
11674        buttonEle.setAttribute('ng-click', '$ionicGoBack()');
11675      }
11676
11677      buttonEle.className = 'button back-button hide buttons ' + (tElement.attr('class') || '');
11678      buttonEle.innerHTML = tElement.html() || '';
11679
11680      var childNode;
11681      var hasIcon = hasIconClass(tElement[0]);
11682      var hasInnerText;
11683      var hasButtonText;
11684      var hasPreviousTitle;
11685
11686      for (var x = 0; x < tElement[0].childNodes.length; x++) {
11687        childNode = tElement[0].childNodes[x];
11688        if (childNode.nodeType === 1) {
11689          if (hasIconClass(childNode)) {
11690            hasIcon = true;
11691          } else if (childNode.classList.contains('default-title')) {
11692            hasButtonText = true;
11693          } else if (childNode.classList.contains('previous-title')) {
11694            hasPreviousTitle = true;
11695          }
11696        } else if (!hasInnerText && childNode.nodeType === 3) {
11697          hasInnerText = !!childNode.nodeValue.trim();
11698        }
11699      }
11700
11701      function hasIconClass(ele) {
11702        return /ion-|icon/.test(ele.className);
11703      }
11704
11705      var defaultIcon = $ionicConfig.backButton.icon();
11706      if (!hasIcon && defaultIcon && defaultIcon !== 'none') {
11707        buttonEle.innerHTML = '<i class="icon ' + defaultIcon + '"></i> ' + buttonEle.innerHTML;
11708        buttonEle.className += ' button-clear';
11709      }
11710
11711      if (!hasInnerText) {
11712        var buttonTextEle = $document[0].createElement('span');
11713        buttonTextEle.className = 'back-text';
11714
11715        if (!hasButtonText && $ionicConfig.backButton.text()) {
11716          buttonTextEle.innerHTML += '<span class="default-title">' + $ionicConfig.backButton.text() + '</span>';
11717        }
11718        if (!hasPreviousTitle && $ionicConfig.backButton.previousTitleText()) {
11719          buttonTextEle.innerHTML += '<span class="previous-title"></span>';
11720        }
11721        buttonEle.appendChild(buttonTextEle);
11722
11723      }
11724
11725      tElement.attr('class', 'hide');
11726      tElement.empty();
11727
11728      return {
11729        pre: function($scope, $element, $attr, navBarCtrl) {
11730          // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
11731          navBarCtrl.navElement('backButton', buttonEle.outerHTML);
11732          buttonEle = null;
11733        }
11734      };
11735    }
11736  };
11737}]);
11738
11739
11740/**
11741 * @ngdoc directive
11742 * @name ionNavBar
11743 * @module ionic
11744 * @delegate ionic.service:$ionicNavBarDelegate
11745 * @restrict E
11746 *
11747 * @description
11748 * If we have an {@link ionic.directive:ionNavView} directive, we can also create an
11749 * `<ion-nav-bar>`, which will create a topbar that updates as the application state changes.
11750 *
11751 * We can add a back button by putting an {@link ionic.directive:ionNavBackButton} inside.
11752 *
11753 * We can add buttons depending on the currently visible view using
11754 * {@link ionic.directive:ionNavButtons}.
11755 *
11756 * Note that the ion-nav-bar element will only work correctly if your content has an
11757 * ionView around it.
11758 *
11759 * @usage
11760 *
11761 * ```html
11762 * <body ng-app="starter">
11763 *   <!-- The nav bar that will be updated as we navigate -->
11764 *   <ion-nav-bar class="bar-positive">
11765 *   </ion-nav-bar>
11766 *
11767 *   <!-- where the initial view template will be rendered -->
11768 *   <ion-nav-view>
11769 *     <ion-view>
11770 *       <ion-content>Hello!</ion-content>
11771 *     </ion-view>
11772 *   </ion-nav-view>
11773 * </body>
11774 * ```
11775 *
11776 * @param {string=} delegate-handle The handle used to identify this navBar
11777 * with {@link ionic.service:$ionicNavBarDelegate}.
11778 * @param align-title {string=} Where to align the title of the navbar.
11779 * Available: 'left', 'right', 'center'. Defaults to 'center'.
11780 * @param {boolean=} no-tap-scroll By default, the navbar will scroll the content
11781 * to the top when tapped.  Set no-tap-scroll to true to disable this behavior.
11782 *
11783 * </table><br/>
11784 */
11785IonicModule
11786.directive('ionNavBar', function() {
11787  return {
11788    restrict: 'E',
11789    controller: '$ionicNavBar',
11790    scope: true,
11791    link: function($scope, $element, $attr, ctrl) {
11792      ctrl.init();
11793    }
11794  };
11795});
11796
11797
11798/**
11799 * @ngdoc directive
11800 * @name ionNavButtons
11801 * @module ionic
11802 * @restrict E
11803 * @parent ionNavView
11804 *
11805 * @description
11806 * Use nav buttons to set the buttons on your {@link ionic.directive:ionNavBar}
11807 * from within an {@link ionic.directive:ionView}. This gives each
11808 * view template the ability to specify which buttons should show in the nav bar,
11809 * overriding any default buttons already placed in the nav bar.
11810 *
11811 * Any buttons you declare will be positioned on the navbar's corresponding side. Primary
11812 * buttons generally map to the left side of the header, and secondary buttons are
11813 * generally on the right side. However, their exact locations are platform-specific.
11814 * For example, in iOS, the primary buttons are on the far left of the header, and
11815 * secondary buttons are on the far right, with the header title centered between them.
11816 * For Android, however, both groups of buttons are on the far right of the header,
11817 * with the header title aligned left.
11818 *
11819 * We recommend always using `primary` and `secondary`, so the buttons correctly map
11820 * to the side familiar to users of each platform. However, in cases where buttons should
11821 * always be on an exact side, both `left` and `right` sides are still available. For
11822 * example, a toggle button for a left side menu should be on the left side; in this case,
11823 * we'd recommend using `side="left"`, so it's always on the left, no matter the platform.
11824 *
11825 * ***Note*** that `ion-nav-buttons` must be immediate descendants of the `ion-view` or
11826 * `ion-nav-bar` element (basically, don't wrap it in another div).
11827 *
11828 * @usage
11829 * ```html
11830 * <ion-nav-bar>
11831 * </ion-nav-bar>
11832 * <ion-nav-view>
11833 *   <ion-view>
11834 *     <ion-nav-buttons side="primary">
11835 *       <button class="button" ng-click="doSomething()">
11836 *         I'm a button on the primary of the navbar!
11837 *       </button>
11838 *     </ion-nav-buttons>
11839 *     <ion-content>
11840 *       Some super content here!
11841 *     </ion-content>
11842 *   </ion-view>
11843 * </ion-nav-view>
11844 * ```
11845 *
11846 * @param {string} side The side to place the buttons in the
11847 * {@link ionic.directive:ionNavBar}. Available sides: `primary`, `secondary`, `left`, and `right`.
11848 */
11849IonicModule
11850.directive('ionNavButtons', ['$document', function($document) {
11851  return {
11852    require: '^ionNavBar',
11853    restrict: 'E',
11854    compile: function(tElement, tAttrs) {
11855      var side = 'left';
11856
11857      if (/^primary|secondary|right$/i.test(tAttrs.side || '')) {
11858        side = tAttrs.side.toLowerCase();
11859      }
11860
11861      var spanEle = $document[0].createElement('span');
11862      spanEle.className = side + '-buttons';
11863      spanEle.innerHTML = tElement.html();
11864
11865      var navElementType = side + 'Buttons';
11866
11867      tElement.attr('class', 'hide');
11868      tElement.empty();
11869
11870      return {
11871        pre: function($scope, $element, $attrs, navBarCtrl) {
11872          // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
11873
11874          var parentViewCtrl = $element.parent().data('$ionViewController');
11875          if (parentViewCtrl) {
11876            // if the parent is an ion-view, then these are ion-nav-buttons for JUST this ion-view
11877            parentViewCtrl.navElement(navElementType, spanEle.outerHTML);
11878
11879          } else {
11880            // these are buttons for all views that do not have their own ion-nav-buttons
11881            navBarCtrl.navElement(navElementType, spanEle.outerHTML);
11882          }
11883
11884          spanEle = null;
11885        }
11886      };
11887    }
11888  };
11889}]);
11890
11891/**
11892 * @ngdoc directive
11893 * @name navDirection
11894 * @module ionic
11895 * @restrict A
11896 *
11897 * @description
11898 * The direction which the nav view transition should animate. Available options
11899 * are: `forward`, `back`, `enter`, `exit`, `swap`.
11900 *
11901 * @usage
11902 *
11903 * ```html
11904 * <a nav-direction="forward" href="#/home">Home</a>
11905 * ```
11906 */
11907IonicModule
11908.directive('navDirection', ['$ionicViewSwitcher', function($ionicViewSwitcher) {
11909  return {
11910    restrict: 'A',
11911    priority: 1000,
11912    link: function($scope, $element, $attr) {
11913      $element.bind('click', function() {
11914        $ionicViewSwitcher.nextDirection($attr.navDirection);
11915      });
11916    }
11917  };
11918}]);
11919
11920/**
11921 * @ngdoc directive
11922 * @name ionNavTitle
11923 * @module ionic
11924 * @restrict E
11925 * @parent ionNavView
11926 *
11927 * @description
11928 *
11929 * The nav title directive replaces an {@link ionic.directive:ionNavBar} title text with
11930 * custom HTML from within an {@link ionic.directive:ionView} template. This gives each
11931 * view the ability to specify its own custom title element, such as an image or any HTML,
11932 * rather than being text-only. Alternatively, text-only titles can be updated using the
11933 * `view-title` {@link ionic.directive:ionView} attribute.
11934 *
11935 * Note that `ion-nav-title` must be an immediate descendant of the `ion-view` or
11936 * `ion-nav-bar` element (basically don't wrap it in another div).
11937 *
11938 * @usage
11939 * ```html
11940 * <ion-nav-bar>
11941 * </ion-nav-bar>
11942 * <ion-nav-view>
11943 *   <ion-view>
11944 *     <ion-nav-title>
11945 *       <img src="logo.svg">
11946 *     </ion-nav-title>
11947 *     <ion-content>
11948 *       Some super content here!
11949 *     </ion-content>
11950 *   </ion-view>
11951 * </ion-nav-view>
11952 * ```
11953 *
11954 */
11955IonicModule
11956.directive('ionNavTitle', ['$document', function($document) {
11957  return {
11958    require: '^ionNavBar',
11959    restrict: 'E',
11960    compile: function(tElement, tAttrs) {
11961      var navElementType = 'title';
11962      var spanEle = $document[0].createElement('span');
11963      for (var n in tAttrs.$attr) {
11964        spanEle.setAttribute(tAttrs.$attr[n], tAttrs[n]);
11965      }
11966      spanEle.classList.add('nav-bar-title');
11967      spanEle.innerHTML = tElement.html();
11968
11969      tElement.attr('class', 'hide');
11970      tElement.empty();
11971
11972      return {
11973        pre: function($scope, $element, $attrs, navBarCtrl) {
11974          // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
11975
11976          var parentViewCtrl = $element.parent().data('$ionViewController');
11977          if (parentViewCtrl) {
11978            // if the parent is an ion-view, then these are ion-nav-buttons for JUST this ion-view
11979            parentViewCtrl.navElement(navElementType, spanEle.outerHTML);
11980
11981          } else {
11982            // these are buttons for all views that do not have their own ion-nav-buttons
11983            navBarCtrl.navElement(navElementType, spanEle.outerHTML);
11984          }
11985
11986          spanEle = null;
11987        }
11988      };
11989    }
11990  };
11991}]);
11992
11993/**
11994 * @ngdoc directive
11995 * @name navTransition
11996 * @module ionic
11997 * @restrict A
11998 *
11999 * @description
12000 * The transition type which the nav view transition should use when it animates.
12001 * Current, options are `ios`, `android`, and `none`. More options coming soon.
12002 *
12003 * @usage
12004 *
12005 * ```html
12006 * <a nav-transition="none" href="#/home">Home</a>
12007 * ```
12008 */
12009IonicModule
12010.directive('navTransition', ['$ionicViewSwitcher', function($ionicViewSwitcher) {
12011  return {
12012    restrict: 'A',
12013    priority: 1000,
12014    link: function($scope, $element, $attr) {
12015      $element.bind('click', function() {
12016        $ionicViewSwitcher.nextTransition($attr.navTransition);
12017      });
12018    }
12019  };
12020}]);
12021
12022/**
12023 * @ngdoc directive
12024 * @name ionNavView
12025 * @module ionic
12026 * @restrict E
12027 * @codepen odqCz
12028 *
12029 * @description
12030 * As a user navigates throughout your app, Ionic is able to keep track of their
12031 * navigation history. By knowing their history, transitions between views
12032 * correctly enter and exit using the platform's transition style. An additional
12033 * benefit to Ionic's navigation system is its ability to manage multiple
12034 * histories. For example, each tab can have it's own navigation history stack.
12035 *
12036 * Ionic uses the AngularUI Router module so app interfaces can be organized
12037 * into various "states". Like Angular's core $route service, URLs can be used
12038 * to control the views. However, the AngularUI Router provides a more powerful
12039 * state manager in that states are bound to named, nested, and parallel views,
12040 * allowing more than one template to be rendered on the same page.
12041 * Additionally, each state is not required to be bound to a URL, and data can
12042 * be pushed to each state which allows much flexibility.
12043 *
12044 * The ionNavView directive is used to render templates in your application. Each template
12045 * is part of a state. States are usually mapped to a url, and are defined programatically
12046 * using angular-ui-router (see [their docs](https://github.com/angular-ui/ui-router/wiki),
12047 * and remember to replace ui-view with ion-nav-view in examples).
12048 *
12049 * @usage
12050 * In this example, we will create a navigation view that contains our different states for the app.
12051 *
12052 * To do this, in our markup we use ionNavView top level directive. To display a header bar we use
12053 * the {@link ionic.directive:ionNavBar} directive that updates as we navigate through the
12054 * navigation stack.
12055 *
12056 * Next, we need to setup our states that will be rendered.
12057 *
12058 * ```js
12059 * var app = angular.module('myApp', ['ionic']);
12060 * app.config(function($stateProvider) {
12061 *   $stateProvider
12062 *   .state('index', {
12063 *     url: '/',
12064 *     templateUrl: 'home.html'
12065 *   })
12066 *   .state('music', {
12067 *     url: '/music',
12068 *     templateUrl: 'music.html'
12069 *   });
12070 * });
12071 * ```
12072 * Then on app start, $stateProvider will look at the url, see if it matches the index state,
12073 * and then try to load home.html into the `<ion-nav-view>`.
12074 *
12075 * Pages are loaded by the URLs given. One simple way to create templates in Angular is to put
12076 * them directly into your HTML file and use the `<script type="text/ng-template">` syntax.
12077 * So here is one way to put home.html into our app:
12078 *
12079 * ```html
12080 * <script id="home" type="text/ng-template">
12081 *   <!-- The title of the ion-view will be shown on the navbar -->
12082 *   <ion-view view-title="Home">
12083 *     <ion-content ng-controller="HomeCtrl">
12084 *       <!-- The content of the page -->
12085 *       <a href="#/music">Go to music page!</a>
12086 *     </ion-content>
12087 *   </ion-view>
12088 * </script>
12089 * ```
12090 *
12091 * This is good to do because the template will be cached for very fast loading, instead of
12092 * having to fetch them from the network.
12093 *
12094 * ## Caching
12095 *
12096 * By default, views are cached to improve performance. When a view is navigated away from, its
12097 * element is left in the DOM, and its scope is disconnected from the `$watch` cycle. When
12098 * navigating to a view that is already cached, its scope is then reconnected, and the existing
12099 * element that was left in the DOM becomes the active view. This also allows for the scroll
12100 * position of previous views to be maintained.
12101 *
12102 * Caching can be disabled and enabled in multiple ways. By default, Ionic will cache a maximum of
12103 * 10 views, and not only can this be configured, but apps can also explicitly state which views
12104 * should and should not be cached.
12105 *
12106 * Note that because we are caching these views, *we aren’t destroying scopes*. Instead, scopes
12107 * are being disconnected from the watch cycle. Because scopes are not being destroyed and
12108 * recreated, controllers are not loading again on a subsequent viewing. If the app/controller
12109 * needs to know when a view has entered or has left, then view events emitted from the
12110 * {@link ionic.directive:ionView} scope, such as `$ionicView.enter`, may be useful.
12111 *
12112 * By default, when navigating back in the history, the "forward" views are removed from the cache.
12113 * If you navigate forward to the same view again, it'll create a new DOM element and controller
12114 * instance. Basically, any forward views are reset each time. This can be configured using the
12115 * {@link ionic.provider:$ionicConfigProvider}:
12116 *
12117 * ```js
12118 * $ionicConfigProvider.views.forwardCache(true);
12119 * ```
12120 *
12121 * #### Disable cache globally
12122 *
12123 * The {@link ionic.provider:$ionicConfigProvider} can be used to set the maximum allowable views
12124 * which can be cached, but this can also be use to disable all caching by setting it to 0.
12125 *
12126 * ```js
12127 * $ionicConfigProvider.views.maxCache(0);
12128 * ```
12129 *
12130 * #### Disable cache within state provider
12131 *
12132 * ```js
12133 * $stateProvider.state('myState', {
12134 *    cache: false,
12135 *    url : '/myUrl',
12136 *    templateUrl : 'my-template.html'
12137 * })
12138 * ```
12139 *
12140 * #### Disable cache with an attribute
12141 *
12142 * ```html
12143 * <ion-view cache-view="false" view-title="My Title!">
12144 *   ...
12145 * </ion-view>
12146 * ```
12147 *
12148 *
12149 * ## AngularUI Router
12150 *
12151 * Please visit [AngularUI Router's docs](https://github.com/angular-ui/ui-router/wiki) for
12152 * more info. Below is a great video by the AngularUI Router team that may help to explain
12153 * how it all works:
12154 *
12155 * <iframe width="560" height="315" src="//www.youtube.com/embed/dqJRoh8MnBo"
12156 * frameborder="0" allowfullscreen></iframe>
12157 *
12158 * Note: We do not recommend using [resolve](https://github.com/angular-ui/ui-router/wiki#resolve)
12159 * of AngularUI Router. The recommended approach is to execute any logic needed before beginning the state transition.
12160 *
12161 * @param {string=} name A view name. The name should be unique amongst the other views in the
12162 * same state. You can have views of the same name that live in different states. For more
12163 * information, see ui-router's
12164 * [ui-view documentation](http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.directive:ui-view).
12165 */
12166IonicModule
12167.directive('ionNavView', [
12168  '$state',
12169  '$ionicConfig',
12170function($state, $ionicConfig) {
12171  // IONIC's fork of Angular UI Router, v0.2.10
12172  // the navView handles registering views in the history and how to transition between them
12173  return {
12174    restrict: 'E',
12175    terminal: true,
12176    priority: 2000,
12177    transclude: true,
12178    controller: '$ionicNavView',
12179    compile: function(tElement, tAttrs, transclude) {
12180
12181      // a nav view element is a container for numerous views
12182      tElement.addClass('view-container');
12183      ionic.DomUtil.cachedAttr(tElement, 'nav-view-transition', $ionicConfig.views.transition());
12184
12185      return function($scope, $element, $attr, navViewCtrl) {
12186        var latestLocals;
12187
12188        // Put in the compiled initial view
12189        transclude($scope, function(clone) {
12190          $element.append(clone);
12191        });
12192
12193        var viewData = navViewCtrl.init();
12194
12195        // listen for $stateChangeSuccess
12196        $scope.$on('$stateChangeSuccess', function() {
12197          updateView(false);
12198        });
12199        $scope.$on('$viewContentLoading', function() {
12200          updateView(false);
12201        });
12202
12203        // initial load, ready go
12204        updateView(true);
12205
12206
12207        function updateView(firstTime) {
12208          // get the current local according to the $state
12209          var viewLocals = $state.$current && $state.$current.locals[viewData.name];
12210
12211          // do not update THIS nav-view if its is not the container for the given state
12212          // if the viewLocals are the same as THIS latestLocals, then nothing to do
12213          if (!viewLocals || (!firstTime && viewLocals === latestLocals)) return;
12214
12215          // update the latestLocals
12216          latestLocals = viewLocals;
12217          viewData.state = viewLocals.$$state;
12218
12219          // register, update and transition to the new view
12220          navViewCtrl.register(viewLocals);
12221        }
12222
12223      };
12224    }
12225  };
12226}]);
12227
12228IonicModule
12229
12230.config(['$provide', function($provide) {
12231  $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
12232    // drop the default ngClick directive
12233    $delegate.shift();
12234    return $delegate;
12235  }]);
12236}])
12237
12238/**
12239 * @private
12240 */
12241.factory('$ionicNgClick', ['$parse', function($parse) {
12242  return function(scope, element, clickExpr) {
12243    var clickHandler = angular.isFunction(clickExpr) ?
12244      clickExpr :
12245      $parse(clickExpr);
12246
12247    element.on('click', function(event) {
12248      scope.$apply(function() {
12249        clickHandler(scope, {$event: (event)});
12250      });
12251    });
12252
12253    // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
12254    // something else nearby.
12255    element.onclick = noop;
12256  };
12257}])
12258
12259.directive('ngClick', ['$ionicNgClick', function($ionicNgClick) {
12260  return function(scope, element, attr) {
12261    $ionicNgClick(scope, element, attr.ngClick);
12262  };
12263}])
12264
12265.directive('ionStopEvent', function() {
12266  return {
12267    restrict: 'A',
12268    link: function(scope, element, attr) {
12269      element.bind(attr.ionStopEvent, eventStopPropagation);
12270    }
12271  };
12272});
12273function eventStopPropagation(e) {
12274  e.stopPropagation();
12275}
12276
12277
12278/**
12279 * @ngdoc directive
12280 * @name ionPane
12281 * @module ionic
12282 * @restrict E
12283 *
12284 * @description A simple container that fits content, with no side effects.  Adds the 'pane' class to the element.
12285 */
12286IonicModule
12287.directive('ionPane', function() {
12288  return {
12289    restrict: 'E',
12290    link: function(scope, element) {
12291      element.addClass('pane');
12292    }
12293  };
12294});
12295
12296/*
12297 * We don't document the ionPopover directive, we instead document
12298 * the $ionicPopover service
12299 */
12300IonicModule
12301.directive('ionPopover', [function() {
12302  return {
12303    restrict: 'E',
12304    transclude: true,
12305    replace: true,
12306    controller: [function() {}],
12307    template: '<div class="popover-backdrop">' +
12308                '<div class="popover-wrapper" ng-transclude></div>' +
12309              '</div>'
12310  };
12311}]);
12312
12313IonicModule
12314.directive('ionPopoverView', function() {
12315  return {
12316    restrict: 'E',
12317    compile: function(element) {
12318      element.append(jqLite('<div class="popover-arrow">'));
12319      element.addClass('popover');
12320    }
12321  };
12322});
12323
12324/**
12325 * @ngdoc directive
12326 * @name ionRadio
12327 * @module ionic
12328 * @restrict E
12329 * @codepen saoBG
12330 * @description
12331 * The radio directive is no different than the HTML radio input, except it's styled differently.
12332 *
12333 * Radio behaves like [AngularJS radio](http://docs.angularjs.org/api/ng/input/input[radio]).
12334 *
12335 * @usage
12336 * ```html
12337 * <ion-radio ng-model="choice" ng-value="'A'">Choose A</ion-radio>
12338 * <ion-radio ng-model="choice" ng-value="'B'">Choose B</ion-radio>
12339 * <ion-radio ng-model="choice" ng-value="'C'">Choose C</ion-radio>
12340 * ```
12341 *
12342 * @param {string=} name The name of the radio input.
12343 * @param {expression=} value The value of the radio input.
12344 * @param {boolean=} disabled The state of the radio input.
12345 * @param {string=} icon The icon to use when the radio input is selected.
12346 * @param {expression=} ng-value Angular equivalent of the value attribute.
12347 * @param {expression=} ng-model The angular model for the radio input.
12348 * @param {boolean=} ng-disabled Angular equivalent of the disabled attribute.
12349 * @param {expression=} ng-change Triggers given expression when radio input's model changes
12350 */
12351IonicModule
12352.directive('ionRadio', function() {
12353  return {
12354    restrict: 'E',
12355    replace: true,
12356    require: '?ngModel',
12357    transclude: true,
12358    template:
12359      '<label class="item item-radio">' +
12360        '<input type="radio" name="radio-group">' +
12361        '<div class="radio-content">' +
12362          '<div class="item-content disable-pointer-events" ng-transclude></div>' +
12363          '<i class="radio-icon disable-pointer-events icon ion-checkmark"></i>' +
12364        '</div>' +
12365      '</label>',
12366
12367    compile: function(element, attr) {
12368      if (attr.icon) {
12369        var iconElm = element.find('i');
12370        iconElm.removeClass('ion-checkmark').addClass(attr.icon);
12371      }
12372
12373      var input = element.find('input');
12374      forEach({
12375          'name': attr.name,
12376          'value': attr.value,
12377          'disabled': attr.disabled,
12378          'ng-value': attr.ngValue,
12379          'ng-model': attr.ngModel,
12380          'ng-disabled': attr.ngDisabled,
12381          'ng-change': attr.ngChange,
12382          'ng-required': attr.ngRequired,
12383          'required': attr.required
12384      }, function(value, name) {
12385        if (isDefined(value)) {
12386            input.attr(name, value);
12387          }
12388      });
12389
12390      return function(scope, element, attr) {
12391        scope.getValue = function() {
12392          return scope.ngValue || attr.value;
12393        };
12394      };
12395    }
12396  };
12397});
12398
12399
12400/**
12401 * @ngdoc directive
12402 * @name ionRefresher
12403 * @module ionic
12404 * @restrict E
12405 * @parent ionic.directive:ionContent, ionic.directive:ionScroll
12406 * @description
12407 * Allows you to add pull-to-refresh to a scrollView.
12408 *
12409 * Place it as the first child of your {@link ionic.directive:ionContent} or
12410 * {@link ionic.directive:ionScroll} element.
12411 *
12412 * When refreshing is complete, $broadcast the 'scroll.refreshComplete' event
12413 * from your controller.
12414 *
12415 * @usage
12416 *
12417 * ```html
12418 * <ion-content ng-controller="MyController">
12419 *   <ion-refresher
12420 *     pulling-text="Pull to refresh..."
12421 *     on-refresh="doRefresh()">
12422 *   </ion-refresher>
12423 *   <ion-list>
12424 *     <ion-item ng-repeat="item in items"></ion-item>
12425 *   </ion-list>
12426 * </ion-content>
12427 * ```
12428 * ```js
12429 * angular.module('testApp', ['ionic'])
12430 * .controller('MyController', function($scope, $http) {
12431 *   $scope.items = [1,2,3];
12432 *   $scope.doRefresh = function() {
12433 *     $http.get('/new-items')
12434 *      .success(function(newItems) {
12435 *        $scope.items = newItems;
12436 *      })
12437 *      .finally(function() {
12438 *        // Stop the ion-refresher from spinning
12439 *        $scope.$broadcast('scroll.refreshComplete');
12440 *      });
12441 *   };
12442 * });
12443 * ```
12444 *
12445 * @param {expression=} on-refresh Called when the user pulls down enough and lets go
12446 * of the refresher.
12447 * @param {expression=} on-pulling Called when the user starts to pull down
12448 * on the refresher.
12449 * @param {string=} pulling-text The text to display while the user is pulling down.
12450 * @param {string=} pulling-icon The icon to display while the user is pulling down.
12451 * Default: 'ion-android-arrow-down'.
12452 * @param {string=} spinner The {@link ionic.directive:ionSpinner} icon to display
12453 * after user lets go of the refresher. The SVG {@link ionic.directive:ionSpinner}
12454 * is now the default, replacing rotating font icons. Set to `none` to disable both the
12455 * spinner and the icon.
12456 * @param {string=} refreshing-icon The font icon to display after user lets go of the
12457 * refresher. This is deprecated in favor of the SVG {@link ionic.directive:ionSpinner}.
12458 * @param {boolean=} disable-pulling-rotation Disables the rotation animation of the pulling
12459 * icon when it reaches its activated threshold. To be used with a custom `pulling-icon`.
12460 *
12461 */
12462IonicModule
12463.directive('ionRefresher', [function() {
12464  return {
12465    restrict: 'E',
12466    replace: true,
12467    require: ['?^$ionicScroll', 'ionRefresher'],
12468    controller: '$ionicRefresher',
12469    template:
12470    '<div class="scroll-refresher invisible" collection-repeat-ignore>' +
12471      '<div class="ionic-refresher-content" ' +
12472      'ng-class="{\'ionic-refresher-with-text\': pullingText || refreshingText}">' +
12473        '<div class="icon-pulling" ng-class="{\'pulling-rotation-disabled\':disablePullingRotation}">' +
12474          '<i class="icon {{pullingIcon}}"></i>' +
12475        '</div>' +
12476        '<div class="text-pulling" ng-bind-html="pullingText"></div>' +
12477        '<div class="icon-refreshing">' +
12478          '<ion-spinner ng-if="showSpinner" icon="{{spinner}}"></ion-spinner>' +
12479          '<i ng-if="showIcon" class="icon {{refreshingIcon}}"></i>' +
12480        '</div>' +
12481        '<div class="text-refreshing" ng-bind-html="refreshingText"></div>' +
12482      '</div>' +
12483    '</div>',
12484    link: function($scope, $element, $attrs, ctrls) {
12485
12486      // JS Scrolling uses the scroll controller
12487      var scrollCtrl = ctrls[0],
12488          refresherCtrl = ctrls[1];
12489      if (!scrollCtrl || scrollCtrl.isNative()) {
12490        // Kick off native scrolling
12491        refresherCtrl.init();
12492      } else {
12493        $element[0].classList.add('js-scrolling');
12494        scrollCtrl._setRefresher(
12495          $scope,
12496          $element[0],
12497          refresherCtrl.getRefresherDomMethods()
12498        );
12499
12500        $scope.$on('scroll.refreshComplete', function() {
12501          $scope.$evalAsync(function() {
12502            scrollCtrl.scrollView.finishPullToRefresh();
12503          });
12504        });
12505      }
12506
12507    }
12508  };
12509}]);
12510
12511/**
12512 * @ngdoc directive
12513 * @name ionScroll
12514 * @module ionic
12515 * @delegate ionic.service:$ionicScrollDelegate
12516 * @codepen mwFuh
12517 * @restrict E
12518 *
12519 * @description
12520 * Creates a scrollable container for all content inside.
12521 *
12522 * @usage
12523 *
12524 * Basic usage:
12525 *
12526 * ```html
12527 * <ion-scroll zooming="true" direction="xy" style="width: 500px; height: 500px">
12528 *   <div style="width: 5000px; height: 5000px; background: url('https://upload.wikimedia.org/wikipedia/commons/a/ad/Europe_geological_map-en.jpg') repeat"></div>
12529 *  </ion-scroll>
12530 * ```
12531 *
12532 * Note that it's important to set the height of the scroll box as well as the height of the inner
12533 * content to enable scrolling. This makes it possible to have full control over scrollable areas.
12534 *
12535 * If you'd just like to have a center content scrolling area, use {@link ionic.directive:ionContent} instead.
12536 *
12537 * @param {string=} delegate-handle The handle used to identify this scrollView
12538 * with {@link ionic.service:$ionicScrollDelegate}.
12539 * @param {string=} direction Which way to scroll. 'x' or 'y' or 'xy'. Default 'y'.
12540 * @param {boolean=} locking Whether to lock scrolling in one direction at a time. Useful to set to false when zoomed in or scrolling in two directions. Default true.
12541 * @param {boolean=} paging Whether to scroll with paging.
12542 * @param {expression=} on-refresh Called on pull-to-refresh, triggered by an {@link ionic.directive:ionRefresher}.
12543 * @param {expression=} on-scroll Called whenever the user scrolls.
12544 * @param {boolean=} scrollbar-x Whether to show the horizontal scrollbar. Default true.
12545 * @param {boolean=} scrollbar-y Whether to show the vertical scrollbar. Default true.
12546 * @param {boolean=} zooming Whether to support pinch-to-zoom
12547 * @param {integer=} min-zoom The smallest zoom amount allowed (default is 0.5)
12548 * @param {integer=} max-zoom The largest zoom amount allowed (default is 3)
12549 * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges
12550 * of the content.  Defaults to true on iOS, false on Android.
12551 */
12552IonicModule
12553.directive('ionScroll', [
12554  '$timeout',
12555  '$controller',
12556  '$ionicBind',
12557  '$ionicConfig',
12558function($timeout, $controller, $ionicBind, $ionicConfig) {
12559  return {
12560    restrict: 'E',
12561    scope: true,
12562    controller: function() {},
12563    compile: function(element, attr) {
12564      element.addClass('scroll-view ionic-scroll');
12565
12566      //We cannot transclude here because it breaks element.data() inheritance on compile
12567      var innerElement = jqLite('<div class="scroll"></div>');
12568      innerElement.append(element.contents());
12569      element.append(innerElement);
12570
12571      var nativeScrolling = attr.overflowScroll !== "false" && (attr.overflowScroll === "true" || !$ionicConfig.scrolling.jsScrolling());
12572
12573      return { pre: prelink };
12574      function prelink($scope, $element, $attr) {
12575        $ionicBind($scope, $attr, {
12576          direction: '@',
12577          paging: '@',
12578          $onScroll: '&onScroll',
12579          scroll: '@',
12580          scrollbarX: '@',
12581          scrollbarY: '@',
12582          zooming: '@',
12583          minZoom: '@',
12584          maxZoom: '@'
12585        });
12586        $scope.direction = $scope.direction || 'y';
12587
12588        if (isDefined($attr.padding)) {
12589          $scope.$watch($attr.padding, function(newVal) {
12590            innerElement.toggleClass('padding', !!newVal);
12591          });
12592        }
12593        if ($scope.$eval($scope.paging) === true) {
12594          innerElement.addClass('scroll-paging');
12595        }
12596
12597        if (!$scope.direction) { $scope.direction = 'y'; }
12598        var isPaging = $scope.$eval($scope.paging) === true;
12599
12600        if (nativeScrolling) {
12601          $element.addClass('overflow-scroll');
12602        }
12603
12604        $element.addClass('scroll-' + $scope.direction);
12605
12606        var scrollViewOptions = {
12607          el: $element[0],
12608          delegateHandle: $attr.delegateHandle,
12609          locking: ($attr.locking || 'true') === 'true',
12610          bouncing: $scope.$eval($attr.hasBouncing),
12611          paging: isPaging,
12612          scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
12613          scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
12614          scrollingX: $scope.direction.indexOf('x') >= 0,
12615          scrollingY: $scope.direction.indexOf('y') >= 0,
12616          zooming: $scope.$eval($scope.zooming) === true,
12617          maxZoom: $scope.$eval($scope.maxZoom) || 3,
12618          minZoom: $scope.$eval($scope.minZoom) || 0.5,
12619          preventDefault: true,
12620          nativeScrolling: nativeScrolling
12621        };
12622
12623        if (isPaging) {
12624          scrollViewOptions.speedMultiplier = 0.8;
12625          scrollViewOptions.bouncing = false;
12626        }
12627
12628        $controller('$ionicScroll', {
12629          $scope: $scope,
12630          scrollViewOptions: scrollViewOptions
12631        });
12632      }
12633    }
12634  };
12635}]);
12636
12637/**
12638 * @ngdoc directive
12639 * @name ionSideMenu
12640 * @module ionic
12641 * @restrict E
12642 * @parent ionic.directive:ionSideMenus
12643 *
12644 * @description
12645 * A container for a side menu, sibling to an {@link ionic.directive:ionSideMenuContent} directive.
12646 *
12647 * @usage
12648 * ```html
12649 * <ion-side-menu
12650 *   side="left"
12651 *   width="myWidthValue + 20"
12652 *   is-enabled="shouldLeftSideMenuBeEnabled()">
12653 * </ion-side-menu>
12654 * ```
12655 * For a complete side menu example, see the
12656 * {@link ionic.directive:ionSideMenus} documentation.
12657 *
12658 * @param {string} side Which side the side menu is currently on.  Allowed values: 'left' or 'right'.
12659 * @param {boolean=} is-enabled Whether this side menu is enabled.
12660 * @param {number=} width How many pixels wide the side menu should be.  Defaults to 275.
12661 */
12662IonicModule
12663.directive('ionSideMenu', function() {
12664  return {
12665    restrict: 'E',
12666    require: '^ionSideMenus',
12667    scope: true,
12668    compile: function(element, attr) {
12669      angular.isUndefined(attr.isEnabled) && attr.$set('isEnabled', 'true');
12670      angular.isUndefined(attr.width) && attr.$set('width', '275');
12671
12672      element.addClass('menu menu-' + attr.side);
12673
12674      return function($scope, $element, $attr, sideMenuCtrl) {
12675        $scope.side = $attr.side || 'left';
12676
12677        var sideMenu = sideMenuCtrl[$scope.side] = new ionic.views.SideMenu({
12678          width: attr.width,
12679          el: $element[0],
12680          isEnabled: true
12681        });
12682
12683        $scope.$watch($attr.width, function(val) {
12684          var numberVal = +val;
12685          if (numberVal && numberVal == val) {
12686            sideMenu.setWidth(+val);
12687          }
12688        });
12689        $scope.$watch($attr.isEnabled, function(val) {
12690          sideMenu.setIsEnabled(!!val);
12691        });
12692      };
12693    }
12694  };
12695});
12696
12697
12698/**
12699 * @ngdoc directive
12700 * @name ionSideMenuContent
12701 * @module ionic
12702 * @restrict E
12703 * @parent ionic.directive:ionSideMenus
12704 *
12705 * @description
12706 * A container for the main visible content, sibling to one or more
12707 * {@link ionic.directive:ionSideMenu} directives.
12708 *
12709 * @usage
12710 * ```html
12711 * <ion-side-menu-content
12712 *   edge-drag-threshold="true"
12713 *   drag-content="true">
12714 * </ion-side-menu-content>
12715 * ```
12716 * For a complete side menu example, see the
12717 * {@link ionic.directive:ionSideMenus} documentation.
12718 *
12719 * @param {boolean=} drag-content Whether the content can be dragged. Default true.
12720 * @param {boolean|number=} edge-drag-threshold Whether the content drag can only start if it is below a certain threshold distance from the edge of the screen.  Default false. Accepts three types of values:
12721   *  - If a non-zero number is given, that many pixels is used as the maximum allowed distance from the edge that starts dragging the side menu.
12722   *  - If true is given, the default number of pixels (25) is used as the maximum allowed distance.
12723   *  - If false or 0 is given, the edge drag threshold is disabled, and dragging from anywhere on the content is allowed.
12724 *
12725 */
12726IonicModule
12727.directive('ionSideMenuContent', [
12728  '$timeout',
12729  '$ionicGesture',
12730  '$window',
12731function($timeout, $ionicGesture, $window) {
12732
12733  return {
12734    restrict: 'EA', //DEPRECATED 'A'
12735    require: '^ionSideMenus',
12736    scope: true,
12737    compile: function(element, attr) {
12738      element.addClass('menu-content pane');
12739
12740      return { pre: prelink };
12741      function prelink($scope, $element, $attr, sideMenuCtrl) {
12742        var startCoord = null;
12743        var primaryScrollAxis = null;
12744
12745        if (isDefined(attr.dragContent)) {
12746          $scope.$watch(attr.dragContent, function(value) {
12747            sideMenuCtrl.canDragContent(value);
12748          });
12749        } else {
12750          sideMenuCtrl.canDragContent(true);
12751        }
12752
12753        if (isDefined(attr.edgeDragThreshold)) {
12754          $scope.$watch(attr.edgeDragThreshold, function(value) {
12755            sideMenuCtrl.edgeDragThreshold(value);
12756          });
12757        }
12758
12759        // Listen for taps on the content to close the menu
12760        function onContentTap(gestureEvt) {
12761          if (sideMenuCtrl.getOpenAmount() !== 0) {
12762            sideMenuCtrl.close();
12763            gestureEvt.gesture.srcEvent.preventDefault();
12764            startCoord = null;
12765            primaryScrollAxis = null;
12766          } else if (!startCoord) {
12767            startCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
12768          }
12769        }
12770
12771        function onDragX(e) {
12772          if (!sideMenuCtrl.isDraggableTarget(e)) return;
12773
12774          if (getPrimaryScrollAxis(e) == 'x') {
12775            sideMenuCtrl._handleDrag(e);
12776            e.gesture.srcEvent.preventDefault();
12777          }
12778        }
12779
12780        function onDragY(e) {
12781          if (getPrimaryScrollAxis(e) == 'x') {
12782            e.gesture.srcEvent.preventDefault();
12783          }
12784        }
12785
12786        function onDragRelease(e) {
12787          sideMenuCtrl._endDrag(e);
12788          startCoord = null;
12789          primaryScrollAxis = null;
12790        }
12791
12792        function getPrimaryScrollAxis(gestureEvt) {
12793          // gets whether the user is primarily scrolling on the X or Y
12794          // If a majority of the drag has been on the Y since the start of
12795          // the drag, but the X has moved a little bit, it's still a Y drag
12796
12797          if (primaryScrollAxis) {
12798            // we already figured out which way they're scrolling
12799            return primaryScrollAxis;
12800          }
12801
12802          if (gestureEvt && gestureEvt.gesture) {
12803
12804            if (!startCoord) {
12805              // get the starting point
12806              startCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
12807
12808            } else {
12809              // we already have a starting point, figure out which direction they're going
12810              var endCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
12811
12812              var xDistance = Math.abs(endCoord.x - startCoord.x);
12813              var yDistance = Math.abs(endCoord.y - startCoord.y);
12814
12815              var scrollAxis = (xDistance < yDistance ? 'y' : 'x');
12816
12817              if (Math.max(xDistance, yDistance) > 30) {
12818                // ok, we pretty much know which way they're going
12819                // let's lock it in
12820                primaryScrollAxis = scrollAxis;
12821              }
12822
12823              return scrollAxis;
12824            }
12825          }
12826          return 'y';
12827        }
12828
12829        var content = {
12830          element: element[0],
12831          onDrag: function() {},
12832          endDrag: function() {},
12833          setCanScroll: function(canScroll) {
12834            var c = $element[0].querySelector('.scroll');
12835
12836            if (!c) {
12837              return;
12838            }
12839
12840            var content = angular.element(c.parentElement);
12841            if (!content) {
12842              return;
12843            }
12844
12845            // freeze our scroll container if we have one
12846            var scrollScope = content.scope();
12847            scrollScope.scrollCtrl && scrollScope.scrollCtrl.freezeScrollShut(!canScroll);
12848          },
12849          getTranslateX: function() {
12850            return $scope.sideMenuContentTranslateX || 0;
12851          },
12852          setTranslateX: ionic.animationFrameThrottle(function(amount) {
12853            var xTransform = content.offsetX + amount;
12854            $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + xTransform + 'px,0,0)';
12855            $timeout(function() {
12856              $scope.sideMenuContentTranslateX = amount;
12857            });
12858          }),
12859          setMarginLeft: ionic.animationFrameThrottle(function(amount) {
12860            if (amount) {
12861              amount = parseInt(amount, 10);
12862              $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amount + 'px,0,0)';
12863              $element[0].style.width = ($window.innerWidth - amount) + 'px';
12864              content.offsetX = amount;
12865            } else {
12866              $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
12867              $element[0].style.width = '';
12868              content.offsetX = 0;
12869            }
12870          }),
12871          setMarginRight: ionic.animationFrameThrottle(function(amount) {
12872            if (amount) {
12873              amount = parseInt(amount, 10);
12874              $element[0].style.width = ($window.innerWidth - amount) + 'px';
12875              content.offsetX = amount;
12876            } else {
12877              $element[0].style.width = '';
12878              content.offsetX = 0;
12879            }
12880            // reset incase left gets grabby
12881            $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
12882          }),
12883          setMarginLeftAndRight: ionic.animationFrameThrottle(function(amountLeft, amountRight) {
12884            amountLeft = amountLeft && parseInt(amountLeft, 10) || 0;
12885            amountRight = amountRight && parseInt(amountRight, 10) || 0;
12886
12887            var amount = amountLeft + amountRight;
12888
12889            if (amount > 0) {
12890              $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amountLeft + 'px,0,0)';
12891              $element[0].style.width = ($window.innerWidth - amount) + 'px';
12892              content.offsetX = amountLeft;
12893            } else {
12894              $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
12895              $element[0].style.width = '';
12896              content.offsetX = 0;
12897            }
12898            // reset incase left gets grabby
12899            //$element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
12900          }),
12901          enableAnimation: function() {
12902            $scope.animationEnabled = true;
12903            $element[0].classList.add('menu-animated');
12904          },
12905          disableAnimation: function() {
12906            $scope.animationEnabled = false;
12907            $element[0].classList.remove('menu-animated');
12908          },
12909          offsetX: 0
12910        };
12911
12912        sideMenuCtrl.setContent(content);
12913
12914        // add gesture handlers
12915        var gestureOpts = { stop_browser_behavior: false };
12916        gestureOpts.prevent_default_directions = ['left', 'right'];
12917        var contentTapGesture = $ionicGesture.on('tap', onContentTap, $element, gestureOpts);
12918        var dragRightGesture = $ionicGesture.on('dragright', onDragX, $element, gestureOpts);
12919        var dragLeftGesture = $ionicGesture.on('dragleft', onDragX, $element, gestureOpts);
12920        var dragUpGesture = $ionicGesture.on('dragup', onDragY, $element, gestureOpts);
12921        var dragDownGesture = $ionicGesture.on('dragdown', onDragY, $element, gestureOpts);
12922        var releaseGesture = $ionicGesture.on('release', onDragRelease, $element, gestureOpts);
12923
12924        // Cleanup
12925        $scope.$on('$destroy', function() {
12926          if (content) {
12927            content.element = null;
12928            content = null;
12929          }
12930          $ionicGesture.off(dragLeftGesture, 'dragleft', onDragX);
12931          $ionicGesture.off(dragRightGesture, 'dragright', onDragX);
12932          $ionicGesture.off(dragUpGesture, 'dragup', onDragY);
12933          $ionicGesture.off(dragDownGesture, 'dragdown', onDragY);
12934          $ionicGesture.off(releaseGesture, 'release', onDragRelease);
12935          $ionicGesture.off(contentTapGesture, 'tap', onContentTap);
12936        });
12937      }
12938    }
12939  };
12940}]);
12941
12942IonicModule
12943
12944/**
12945 * @ngdoc directive
12946 * @name ionSideMenus
12947 * @module ionic
12948 * @delegate ionic.service:$ionicSideMenuDelegate
12949 * @restrict E
12950 *
12951 * @description
12952 * A container element for side menu(s) and the main content. Allows the left and/or right side menu
12953 * to be toggled by dragging the main content area side to side.
12954 *
12955 * To automatically close an opened menu, you can add the {@link ionic.directive:menuClose} attribute
12956 * directive. The `menu-close` attribute is usually added to links and buttons within
12957 * `ion-side-menu-content`, so that when the element is clicked, the opened side menu will
12958 * automatically close.
12959 *
12960 * "Burger Icon" toggles can be added to the header with the {@link ionic.directive:menuToggle}
12961 * attribute directive. Clicking the toggle will open and close the side menu like the `menu-close`
12962 * directive. The side menu will automatically hide on child pages, but can be overridden with the
12963 * enable-menu-with-back-views attribute mentioned below.
12964 *
12965 * By default, side menus are hidden underneath their side menu content and can be opened by swiping
12966 * the content left or right or by toggling a button to show the side menu. Additionally, by adding the
12967 * {@link ionic.directive:exposeAsideWhen} attribute directive to an
12968 * {@link ionic.directive:ionSideMenu} element directive, a side menu can be given instructions about
12969 * "when" the menu should be exposed (always viewable).
12970 *
12971 * ![Side Menu](http://ionicframework.com.s3.amazonaws.com/docs/controllers/sidemenu.gif)
12972 *
12973 * For more information on side menus, check out:
12974 *
12975 * - {@link ionic.directive:ionSideMenuContent}
12976 * - {@link ionic.directive:ionSideMenu}
12977 * - {@link ionic.directive:menuToggle}
12978 * - {@link ionic.directive:menuClose}
12979 * - {@link ionic.directive:exposeAsideWhen}
12980 *
12981 * @usage
12982 * To use side menus, add an `<ion-side-menus>` parent element. This will encompass all pages that have a
12983 * side menu, and have at least 2 child elements: 1 `<ion-side-menu-content>` for the center content,
12984 * and one or more `<ion-side-menu>` directives for each side menu(left/right) that you wish to place.
12985 *
12986 * ```html
12987 * <ion-side-menus>
12988 *   <!-- Left menu -->
12989 *   <ion-side-menu side="left">
12990 *   </ion-side-menu>
12991 *
12992 *   <ion-side-menu-content>
12993 *   <!-- Main content, usually <ion-nav-view> -->
12994 *   </ion-side-menu-content>
12995 *
12996 *   <!-- Right menu -->
12997 *   <ion-side-menu side="right">
12998 *   </ion-side-menu>
12999 *
13000 * </ion-side-menus>
13001 * ```
13002 * ```js
13003 * function ContentController($scope, $ionicSideMenuDelegate) {
13004 *   $scope.toggleLeft = function() {
13005 *     $ionicSideMenuDelegate.toggleLeft();
13006 *   };
13007 * }
13008 * ```
13009 *
13010 * @param {bool=} enable-menu-with-back-views Determines whether the side menu is enabled when the
13011 * back button is showing. When set to `false`, any {@link ionic.directive:menuToggle} will be hidden,
13012 * and the user cannot swipe to open the menu. When going back to the root page of the side menu (the
13013 * page without a back button visible), then any menuToggle buttons will show again, and menus will be
13014 * enabled again.
13015 * @param {string=} delegate-handle The handle used to identify this side menu
13016 * with {@link ionic.service:$ionicSideMenuDelegate}.
13017 *
13018 */
13019.directive('ionSideMenus', ['$ionicBody', function($ionicBody) {
13020  return {
13021    restrict: 'ECA',
13022    controller: '$ionicSideMenus',
13023    compile: function(element, attr) {
13024      attr.$set('class', (attr['class'] || '') + ' view');
13025
13026      return { pre: prelink };
13027      function prelink($scope, $element, $attrs, ctrl) {
13028
13029        ctrl.enableMenuWithBackViews($scope.$eval($attrs.enableMenuWithBackViews));
13030
13031        $scope.$on('$ionicExposeAside', function(evt, isAsideExposed) {
13032          if (!$scope.$exposeAside) $scope.$exposeAside = {};
13033          $scope.$exposeAside.active = isAsideExposed;
13034          $ionicBody.enableClass(isAsideExposed, 'aside-open');
13035        });
13036
13037        $scope.$on('$ionicView.beforeEnter', function(ev, d) {
13038          if (d.historyId) {
13039            $scope.$activeHistoryId = d.historyId;
13040          }
13041        });
13042
13043        $scope.$on('$destroy', function() {
13044          $ionicBody.removeClass('menu-open', 'aside-open');
13045        });
13046
13047      }
13048    }
13049  };
13050}]);
13051
13052
13053/**
13054 * @ngdoc directive
13055 * @name ionSlideBox
13056 * @module ionic
13057 * @codepen AjgEB
13058 * @deprecated will be removed in the next Ionic release in favor of the new ion-slides component.
13059 * Don't depend on the internal behavior of this widget.
13060 * @delegate ionic.service:$ionicSlideBoxDelegate
13061 * @restrict E
13062 * @description
13063 * The Slide Box is a multi-page container where each page can be swiped or dragged between:
13064 *
13065 *
13066 * @usage
13067 * ```html
13068 * <ion-slide-box on-slide-changed="slideHasChanged($index)">
13069 *   <ion-slide>
13070 *     <div class="box blue"><h1>BLUE</h1></div>
13071 *   </ion-slide>
13072 *   <ion-slide>
13073 *     <div class="box yellow"><h1>YELLOW</h1></div>
13074 *   </ion-slide>
13075 *   <ion-slide>
13076 *     <div class="box pink"><h1>PINK</h1></div>
13077 *   </ion-slide>
13078 * </ion-slide-box>
13079 * ```
13080 *
13081 * @param {string=} delegate-handle The handle used to identify this slideBox
13082 * with {@link ionic.service:$ionicSlideBoxDelegate}.
13083 * @param {boolean=} does-continue Whether the slide box should loop.
13084 * @param {boolean=} auto-play Whether the slide box should automatically slide. Default true if does-continue is true.
13085 * @param {number=} slide-interval How many milliseconds to wait to change slides (if does-continue is true). Defaults to 4000.
13086 * @param {boolean=} show-pager Whether a pager should be shown for this slide box. Accepts expressions via `show-pager="{{shouldShow()}}"`. Defaults to true.
13087 * @param {expression=} pager-click Expression to call when a pager is clicked (if show-pager is true). Is passed the 'index' variable.
13088 * @param {expression=} on-slide-changed Expression called whenever the slide is changed.  Is passed an '$index' variable.
13089 * @param {expression=} active-slide Model to bind the current slide index to.
13090 */
13091IonicModule
13092.directive('ionSlideBox', [
13093  '$animate',
13094  '$timeout',
13095  '$compile',
13096  '$ionicSlideBoxDelegate',
13097  '$ionicHistory',
13098  '$ionicScrollDelegate',
13099function($animate, $timeout, $compile, $ionicSlideBoxDelegate, $ionicHistory, $ionicScrollDelegate) {
13100  return {
13101    restrict: 'E',
13102    replace: true,
13103    transclude: true,
13104    scope: {
13105      autoPlay: '=',
13106      doesContinue: '@',
13107      slideInterval: '@',
13108      showPager: '@',
13109      pagerClick: '&',
13110      disableScroll: '@',
13111      onSlideChanged: '&',
13112      activeSlide: '=?',
13113      bounce: '@'
13114    },
13115    controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
13116      var _this = this;
13117
13118      var continuous = $scope.$eval($scope.doesContinue) === true;
13119      var bouncing = ($scope.$eval($scope.bounce) !== false); //Default to true
13120      var shouldAutoPlay = isDefined($attrs.autoPlay) ? !!$scope.autoPlay : false;
13121      var slideInterval = shouldAutoPlay ? $scope.$eval($scope.slideInterval) || 4000 : 0;
13122
13123      var slider = new ionic.views.Slider({
13124        el: $element[0],
13125        auto: slideInterval,
13126        continuous: continuous,
13127        startSlide: $scope.activeSlide,
13128        bouncing: bouncing,
13129        slidesChanged: function() {
13130          $scope.currentSlide = slider.currentIndex();
13131
13132          // Try to trigger a digest
13133          $timeout(function() {});
13134        },
13135        callback: function(slideIndex) {
13136          $scope.currentSlide = slideIndex;
13137          $scope.onSlideChanged({ index: $scope.currentSlide, $index: $scope.currentSlide});
13138          $scope.$parent.$broadcast('slideBox.slideChanged', slideIndex);
13139          $scope.activeSlide = slideIndex;
13140          // Try to trigger a digest
13141          $timeout(function() {});
13142        },
13143        onDrag: function() {
13144          freezeAllScrolls(true);
13145        },
13146        onDragEnd: function() {
13147          freezeAllScrolls(false);
13148        }
13149      });
13150
13151      function freezeAllScrolls(shouldFreeze) {
13152        if (shouldFreeze && !_this.isScrollFreeze) {
13153          $ionicScrollDelegate.freezeAllScrolls(shouldFreeze);
13154
13155        } else if (!shouldFreeze && _this.isScrollFreeze) {
13156          $ionicScrollDelegate.freezeAllScrolls(false);
13157        }
13158        _this.isScrollFreeze = shouldFreeze;
13159      }
13160
13161      slider.enableSlide($scope.$eval($attrs.disableScroll) !== true);
13162
13163      $scope.$watch('activeSlide', function(nv) {
13164        if (isDefined(nv)) {
13165          slider.slide(nv);
13166        }
13167      });
13168
13169      $scope.$on('slideBox.nextSlide', function() {
13170        slider.next();
13171      });
13172
13173      $scope.$on('slideBox.prevSlide', function() {
13174        slider.prev();
13175      });
13176
13177      $scope.$on('slideBox.setSlide', function(e, index) {
13178        slider.slide(index);
13179      });
13180
13181      //Exposed for testing
13182      this.__slider = slider;
13183
13184      var deregisterInstance = $ionicSlideBoxDelegate._registerInstance(
13185        slider, $attrs.delegateHandle, function() {
13186          return $ionicHistory.isActiveScope($scope);
13187        }
13188      );
13189      $scope.$on('$destroy', function() {
13190        deregisterInstance();
13191        slider.kill();
13192      });
13193
13194      this.slidesCount = function() {
13195        return slider.slidesCount();
13196      };
13197
13198      this.onPagerClick = function(index) {
13199        $scope.pagerClick({index: index});
13200      };
13201
13202      $timeout(function() {
13203        slider.load();
13204      });
13205    }],
13206    template: '<div class="slider">' +
13207      '<div class="slider-slides" ng-transclude>' +
13208      '</div>' +
13209    '</div>',
13210
13211    link: function($scope, $element, $attr) {
13212      // Disable ngAnimate for slidebox and its children
13213      $animate.enabled($element, false);
13214
13215      // if showPager is undefined, show the pager
13216      if (!isDefined($attr.showPager)) {
13217        $scope.showPager = true;
13218        getPager().toggleClass('hide', !true);
13219      }
13220
13221      $attr.$observe('showPager', function(show) {
13222        if (show === undefined) return;
13223        show = $scope.$eval(show);
13224        getPager().toggleClass('hide', !show);
13225      });
13226
13227      var pager;
13228      function getPager() {
13229        if (!pager) {
13230          var childScope = $scope.$new();
13231          pager = jqLite('<ion-pager></ion-pager>');
13232          $element.append(pager);
13233          pager = $compile(pager)(childScope);
13234        }
13235        return pager;
13236      }
13237    }
13238  };
13239}])
13240.directive('ionSlide', function() {
13241  return {
13242    restrict: 'E',
13243    require: '?^ionSlideBox',
13244    compile: function(element) {
13245      element.addClass('slider-slide');
13246    }
13247  };
13248})
13249
13250.directive('ionPager', function() {
13251  return {
13252    restrict: 'E',
13253    replace: true,
13254    require: '^ionSlideBox',
13255    template: '<div class="slider-pager"><span class="slider-pager-page" ng-repeat="slide in numSlides() track by $index" ng-class="{active: $index == currentSlide}" ng-click="pagerClick($index)"><i class="icon ion-record"></i></span></div>',
13256    link: function($scope, $element, $attr, slideBox) {
13257      var selectPage = function(index) {
13258        var children = $element[0].children;
13259        var length = children.length;
13260        for (var i = 0; i < length; i++) {
13261          if (i == index) {
13262            children[i].classList.add('active');
13263          } else {
13264            children[i].classList.remove('active');
13265          }
13266        }
13267      };
13268
13269      $scope.pagerClick = function(index) {
13270        slideBox.onPagerClick(index);
13271      };
13272
13273      $scope.numSlides = function() {
13274        return new Array(slideBox.slidesCount());
13275      };
13276
13277      $scope.$watch('currentSlide', function(v) {
13278        selectPage(v);
13279      });
13280    }
13281  };
13282
13283});
13284
13285
13286/**
13287 * @ngdoc directive
13288 * @name ionSlides
13289 * @module ionic
13290 * @delegate ionic.service:$ionicSlideBoxDelegate
13291 * @restrict E
13292 * @description
13293 * The Slides component is a powerful multi-page container where each page can be swiped or dragged between.
13294 *
13295 * Note: this is a new version of the Ionic Slide Box based on the [Swiper](http://www.idangero.us/swiper/#.Vmc1J-ODFBc) widget from
13296 * [idangerous](http://www.idangero.us/).
13297 *
13298 * ![SlideBox](http://ionicframework.com.s3.amazonaws.com/docs/controllers/slideBox.gif)
13299 *
13300 * @usage
13301 * ```html
13302 * <ion-content scroll="false">
13303 *   <ion-slides  options="options" slider="data.slider">
13304 *     <ion-slide-page>
13305 *       <div class="box blue"><h1>BLUE</h1></div>
13306 *     </ion-slide-page>
13307 *     <ion-slide-page>
13308 *       <div class="box yellow"><h1>YELLOW</h1></div>
13309 *     </ion-slide-page>
13310 *     <ion-slide-page>
13311 *       <div class="box pink"><h1>PINK</h1></div>
13312 *     </ion-slide-page>
13313 *   </ion-slides>
13314 * </ion-content>
13315 * ```
13316 *
13317 * ```js
13318 * $scope.options = {
13319 *   loop: false,
13320 *   effect: 'fade',
13321 *   speed: 500,
13322 * }
13323 *
13324 * $scope.$on("$ionicSlides.sliderInitialized", function(event, data){
13325 *   // data.slider is the instance of Swiper
13326 *   $scope.slider = data.slider;
13327 * });
13328 *
13329 * $scope.$on("$ionicSlides.slideChangeStart", function(event, data){
13330 *   console.log('Slide change is beginning');
13331 * });
13332 *
13333 * $scope.$on("$ionicSlides.slideChangeEnd", function(event, data){
13334 *   // note: the indexes are 0-based
13335 *   $scope.activeIndex = data.activeIndex;
13336 *   $scope.previousIndex = data.previousIndex;
13337 * });
13338 *
13339 * ```
13340 *
13341 * ## Slide Events
13342 *
13343 * The slides component dispatches events when the active slide changes
13344 *
13345 * <table class="table">
13346 *   <tr>
13347 *     <td><code>$ionicSlides.slideChangeStart</code></td>
13348 *     <td>This event is emitted when a slide change begins</td>
13349 *   </tr>
13350 *   <tr>
13351 *     <td><code>$ionicSlides.slideChangeEnd</code></td>
13352 *     <td>This event is emitted when a slide change completes</td>
13353 *   </tr>
13354 *   <tr>
13355 *     <td><code>$ionicSlides.sliderInitialized</code></td>
13356 *     <td>This event is emitted when the slider is initialized. It provides access to an instance of the slider.</td>
13357 *   </tr>
13358 * </table>
13359 *
13360 *
13361 * ## Updating Slides Dynamically
13362 * When applying data to the slider at runtime, typically everything will work as expected.
13363 *
13364 * In the event that the slides are looped, use the `updateLoop` method on the slider to ensure the slides update correctly.
13365 *
13366 * ```
13367 * $scope.$on("$ionicSlides.sliderInitialized", function(event, data){
13368 *   // grab an instance of the slider
13369 *   $scope.slider = data.slider;
13370 * });
13371 *
13372 * function dataChangeHandler(){
13373 *   // call this function when data changes, such as an HTTP request, etc
13374 *   if ( $scope.slider ){
13375 *     $scope.slider.updateLoop();
13376 *   }
13377 * }
13378 * ```
13379 *
13380 */
13381IonicModule
13382.directive('ionSlides', [
13383  '$animate',
13384  '$timeout',
13385  '$compile',
13386function($animate, $timeout, $compile) {
13387  return {
13388    restrict: 'E',
13389    transclude: true,
13390    scope: {
13391      options: '=',
13392      slider: '='
13393    },
13394    template: '<div class="swiper-container">' +
13395      '<div class="swiper-wrapper" ng-transclude>' +
13396      '</div>' +
13397        '<div ng-hide="!showPager" class="swiper-pagination"></div>' +
13398      '</div>',
13399    controller: ['$scope', '$element', function($scope, $element) {
13400      var _this = this;
13401
13402      this.update = function() {
13403        $timeout(function() {
13404          if (!_this.__slider) {
13405            return;
13406          }
13407
13408          _this.__slider.update();
13409          if (_this._options.loop) {
13410            _this.__slider.createLoop();
13411          }
13412
13413          var slidesLength = _this.__slider.slides.length;
13414
13415          // Don't allow pager to show with > 10 slides
13416          if (slidesLength > 10) {
13417            $scope.showPager = false;
13418          }
13419
13420          // When slide index is greater than total then slide to last index
13421          if (_this.__slider.activeIndex > slidesLength - 1) {
13422            _this.__slider.slideTo(slidesLength - 1);
13423          }
13424        });
13425      };
13426
13427      this.rapidUpdate = ionic.debounce(function() {
13428        _this.update();
13429      }, 50);
13430
13431      this.getSlider = function() {
13432        return _this.__slider;
13433      };
13434
13435      var options = $scope.options || {};
13436
13437      var newOptions = angular.extend({
13438        pagination: $element.children().children()[1],
13439        paginationClickable: true,
13440        lazyLoading: true,
13441        preloadImages: false
13442      }, options);
13443
13444      this._options = newOptions;
13445
13446      $timeout(function() {
13447        var slider = new ionic.views.Swiper($element.children()[0], newOptions, $scope, $compile);
13448
13449        $scope.$emit("$ionicSlides.sliderInitialized", { slider: slider });
13450
13451        _this.__slider = slider;
13452        $scope.slider = _this.__slider;
13453
13454        $scope.$on('$destroy', function() {
13455          slider.destroy();
13456          _this.__slider = null;
13457        });
13458      });
13459
13460      $timeout(function() {
13461        // if it's a loop, render the slides again just incase
13462        _this.rapidUpdate();
13463      }, 200);
13464
13465    }],
13466
13467    link: function($scope) {
13468      $scope.showPager = true;
13469      // Disable ngAnimate for slidebox and its children
13470      //$animate.enabled(false, $element);
13471    }
13472  };
13473}])
13474.directive('ionSlidePage', [function() {
13475  return {
13476    restrict: 'E',
13477    require: '?^ionSlides',
13478    transclude: true,
13479    replace: true,
13480    template: '<div class="swiper-slide" ng-transclude></div>',
13481    link: function($scope, $element, $attr, ionSlidesCtrl) {
13482      ionSlidesCtrl.rapidUpdate();
13483
13484      $scope.$on('$destroy', function() {
13485        ionSlidesCtrl.rapidUpdate();
13486      });
13487    }
13488  };
13489}]);
13490
13491/**
13492* @ngdoc directive
13493* @name ionSpinner
13494* @module ionic
13495* @restrict E
13496 *
13497 * @description
13498 * The `ionSpinner` directive provides a variety of animated spinners.
13499 * Spinners enables you to give your users feedback that the app is
13500 * processing/thinking/waiting/chillin' out, or whatever you'd like it to indicate.
13501 * By default, the {@link ionic.directive:ionRefresher} feature uses this spinner, rather
13502 * than rotating font icons (previously included in [ionicons](http://ionicons.com/)).
13503 * While font icons are great for simple or stationary graphics, they're not suited to
13504 * provide great animations, which is why Ionic uses SVG instead.
13505 *
13506 * Ionic offers ten spinners out of the box, and by default, it will use the appropriate spinner
13507 * for the platform on which it's running. Under the hood, the `ionSpinner` directive dynamically
13508 * builds the required SVG element, which allows Ionic to provide all ten of the animated SVGs
13509 * within 3KB.
13510 *
13511 * <style>
13512 * .spinner-table {
13513 *   max-width: 280px;
13514 * }
13515 * .spinner-table tbody > tr > th, .spinner-table tbody > tr > td {
13516 *   vertical-align: middle;
13517 *   width: 42px;
13518 *   height: 42px;
13519 * }
13520 * .spinner {
13521 *   stroke: #444;
13522 *   fill: #444; }
13523 *   .spinner svg {
13524 *     width: 28px;
13525 *     height: 28px; }
13526 *   .spinner.spinner-inverse {
13527 *     stroke: #fff;
13528 *     fill: #fff; }
13529 *
13530 * .spinner-android {
13531 *   stroke: #4b8bf4; }
13532 *
13533 * .spinner-ios, .spinner-ios-small {
13534 *   stroke: #69717d; }
13535 *
13536 * .spinner-spiral .stop1 {
13537 *   stop-color: #fff;
13538 *   stop-opacity: 0; }
13539 * .spinner-spiral.spinner-inverse .stop1 {
13540 *   stop-color: #000; }
13541 * .spinner-spiral.spinner-inverse .stop2 {
13542 *   stop-color: #fff; }
13543 * </style>
13544 *
13545 * <script src="http://code.ionicframework.com/nightly/js/ionic.bundle.min.js"></script>
13546 * <table class="table spinner-table" ng-app="ionic">
13547 *  <tr>
13548 *    <th>
13549 *      <code>android</code>
13550 *    </th>
13551 *    <td>
13552 *      <ion-spinner icon="android"></ion-spinner>
13553 *    </td>
13554 *  </tr>
13555 *  <tr>
13556 *    <th>
13557 *      <code>ios</code>
13558 *    </th>
13559 *    <td>
13560 *      <ion-spinner icon="ios"></ion-spinner>
13561 *    </td>
13562 *  </tr>
13563 *  <tr>
13564 *    <th>
13565 *      <code>ios-small</code>
13566 *    </th>
13567 *    <td>
13568 *      <ion-spinner icon="ios-small"></ion-spinner>
13569 *    </td>
13570 *  </tr>
13571 *  <tr>
13572 *    <th>
13573 *      <code>bubbles</code>
13574 *    </th>
13575 *    <td>
13576 *      <ion-spinner icon="bubbles"></ion-spinner>
13577 *    </td>
13578 *  </tr>
13579 *  <tr>
13580 *    <th>
13581 *      <code>circles</code>
13582 *    </th>
13583 *    <td>
13584 *      <ion-spinner icon="circles"></ion-spinner>
13585 *    </td>
13586 *  </tr>
13587 *  <tr>
13588 *    <th>
13589 *      <code>crescent</code>
13590 *    </th>
13591 *    <td>
13592 *      <ion-spinner icon="crescent"></ion-spinner>
13593 *    </td>
13594 *  </tr>
13595 *  <tr>
13596 *    <th>
13597 *      <code>dots</code>
13598 *    </th>
13599 *    <td>
13600 *      <ion-spinner icon="dots"></ion-spinner>
13601 *    </td>
13602 *  </tr>
13603 *  <tr>
13604 *    <th>
13605 *      <code>lines</code>
13606 *    </th>
13607 *    <td>
13608 *      <ion-spinner icon="lines"></ion-spinner>
13609 *    </td>
13610 *  </tr>
13611 *  <tr>
13612 *    <th>
13613 *      <code>ripple</code>
13614 *    </th>
13615 *    <td>
13616 *      <ion-spinner icon="ripple"></ion-spinner>
13617 *    </td>
13618 *  </tr>
13619 *  <tr>
13620 *    <th>
13621 *      <code>spiral</code>
13622 *    </th>
13623 *    <td>
13624 *      <ion-spinner icon="spiral"></ion-spinner>
13625 *    </td>
13626 *  </tr>
13627 * </table>
13628 *
13629 * Each spinner uses SVG with SMIL animations, however, the Android spinner also uses JavaScript
13630 * so it also works on Android 4.0-4.3. Additionally, each spinner can be styled with CSS,
13631 * and scaled to any size.
13632 *
13633 *
13634 * @usage
13635 * The following code would use the default spinner for the platform it's running from. If it's neither
13636 * iOS or Android, it'll default to use `ios`.
13637 *
13638 * ```html
13639 * <ion-spinner></ion-spinner>
13640 * ```
13641 *
13642 * By setting the `icon` attribute, you can specify which spinner to use, no matter what
13643 * the platform is.
13644 *
13645 * ```html
13646 * <ion-spinner icon="spiral"></ion-spinner>
13647 * ```
13648 *
13649 * ## Spinner Colors
13650 * Like with most of Ionic's other components, spinners can also be styled using
13651 * Ionic's standard color naming convention. For example:
13652 *
13653 * ```html
13654 * <ion-spinner class="spinner-energized"></ion-spinner>
13655 * ```
13656 *
13657 *
13658 * ## Styling SVG with CSS
13659 * One cool thing about SVG is its ability to be styled with CSS! Some of the properties
13660 * have different names, for example, SVG uses the term `stroke` instead of `border`, and
13661 * `fill` instead of `background-color`.
13662 *
13663 * ```css
13664 * .spinner svg {
13665 *   width: 28px;
13666 *   height: 28px;
13667 *   stroke: #444;
13668 *   fill: #444;
13669 * }
13670 * ```
13671 *
13672*/
13673IonicModule
13674.directive('ionSpinner', function() {
13675  return {
13676    restrict: 'E',
13677    controller: '$ionicSpinner',
13678    link: function($scope, $element, $attrs, ctrl) {
13679      var spinnerName = ctrl.init();
13680      $element.addClass('spinner spinner-' + spinnerName);
13681
13682      $element.on('$destroy', function onDestroy() {
13683        ctrl.stop();
13684      });
13685    }
13686  };
13687});
13688
13689/**
13690 * @ngdoc directive
13691 * @name ionTab
13692 * @module ionic
13693 * @restrict E
13694 * @parent ionic.directive:ionTabs
13695 *
13696 * @description
13697 * Contains a tab's content.  The content only exists while the given tab is selected.
13698 *
13699 * Each ionTab has its own view history.
13700 *
13701 * @usage
13702 * ```html
13703 * <ion-tab
13704 *   title="Tab!"
13705 *   icon="my-icon"
13706 *   href="#/tab/tab-link"
13707 *   on-select="onTabSelected()"
13708 *   on-deselect="onTabDeselected()">
13709 * </ion-tab>
13710 * ```
13711 * For a complete, working tab bar example, see the {@link ionic.directive:ionTabs} documentation.
13712 *
13713 * @param {string} title The title of the tab.
13714 * @param {string=} href The link that this tab will navigate to when tapped.
13715 * @param {string=} icon The icon of the tab. If given, this will become the default for icon-on and icon-off.
13716 * @param {string=} icon-on The icon of the tab while it is selected.
13717 * @param {string=} icon-off The icon of the tab while it is not selected.
13718 * @param {expression=} badge The badge to put on this tab (usually a number).
13719 * @param {expression=} badge-style The style of badge to put on this tab (eg: badge-positive).
13720 * @param {expression=} on-select Called when this tab is selected.
13721 * @param {expression=} on-deselect Called when this tab is deselected.
13722 * @param {expression=} ng-click By default, the tab will be selected on click. If ngClick is set, it will not.  You can explicitly switch tabs using {@link ionic.service:$ionicTabsDelegate#select $ionicTabsDelegate.select()}.
13723 * @param {expression=} hidden Whether the tab is to be hidden or not.
13724 * @param {expression=} disabled Whether the tab is to be disabled or not.
13725 */
13726IonicModule
13727.directive('ionTab', [
13728  '$compile',
13729  '$ionicConfig',
13730  '$ionicBind',
13731  '$ionicViewSwitcher',
13732function($compile, $ionicConfig, $ionicBind, $ionicViewSwitcher) {
13733
13734  //Returns ' key="value"' if value exists
13735  function attrStr(k, v) {
13736    return isDefined(v) ? ' ' + k + '="' + v + '"' : '';
13737  }
13738  return {
13739    restrict: 'E',
13740    require: ['^ionTabs', 'ionTab'],
13741    controller: '$ionicTab',
13742    scope: true,
13743    compile: function(element, attr) {
13744
13745      //We create the tabNavTemplate in the compile phase so that the
13746      //attributes we pass down won't be interpolated yet - we want
13747      //to pass down the 'raw' versions of the attributes
13748      var tabNavTemplate = '<ion-tab-nav' +
13749        attrStr('ng-click', attr.ngClick) +
13750        attrStr('title', attr.title) +
13751        attrStr('icon', attr.icon) +
13752        attrStr('icon-on', attr.iconOn) +
13753        attrStr('icon-off', attr.iconOff) +
13754        attrStr('badge', attr.badge) +
13755        attrStr('badge-style', attr.badgeStyle) +
13756        attrStr('hidden', attr.hidden) +
13757        attrStr('disabled', attr.disabled) +
13758        attrStr('class', attr['class']) +
13759        '></ion-tab-nav>';
13760
13761      //Remove the contents of the element so we can compile them later, if tab is selected
13762      var tabContentEle = document.createElement('div');
13763      for (var x = 0; x < element[0].children.length; x++) {
13764        tabContentEle.appendChild(element[0].children[x].cloneNode(true));
13765      }
13766      var childElementCount = tabContentEle.childElementCount;
13767      element.empty();
13768
13769      var navViewName, isNavView;
13770      if (childElementCount) {
13771        if (tabContentEle.children[0].tagName === 'ION-NAV-VIEW') {
13772          // get the name if it's a nav-view
13773          navViewName = tabContentEle.children[0].getAttribute('name');
13774          tabContentEle.children[0].classList.add('view-container');
13775          isNavView = true;
13776        }
13777        if (childElementCount === 1) {
13778          // make the 1 child element the primary tab content container
13779          tabContentEle = tabContentEle.children[0];
13780        }
13781        if (!isNavView) tabContentEle.classList.add('pane');
13782        tabContentEle.classList.add('tab-content');
13783      }
13784
13785      return function link($scope, $element, $attr, ctrls) {
13786        var childScope;
13787        var childElement;
13788        var tabsCtrl = ctrls[0];
13789        var tabCtrl = ctrls[1];
13790        var isTabContentAttached = false;
13791        $scope.$tabSelected = false;
13792
13793        $ionicBind($scope, $attr, {
13794          onSelect: '&',
13795          onDeselect: '&',
13796          title: '@',
13797          uiSref: '@',
13798          href: '@'
13799        });
13800
13801        tabsCtrl.add($scope);
13802        $scope.$on('$destroy', function() {
13803          if (!$scope.$tabsDestroy) {
13804            // if the containing ionTabs directive is being destroyed
13805            // then don't bother going through the controllers remove
13806            // method, since remove will reset the active tab as each tab
13807            // is being destroyed, causing unnecessary view loads and transitions
13808            tabsCtrl.remove($scope);
13809          }
13810          tabNavElement.isolateScope().$destroy();
13811          tabNavElement.remove();
13812          tabNavElement = tabContentEle = childElement = null;
13813        });
13814
13815        //Remove title attribute so browser-tooltip does not apear
13816        $element[0].removeAttribute('title');
13817
13818        if (navViewName) {
13819          tabCtrl.navViewName = $scope.navViewName = navViewName;
13820        }
13821        $scope.$on('$stateChangeSuccess', selectIfMatchesState);
13822        selectIfMatchesState();
13823        function selectIfMatchesState() {
13824          if (tabCtrl.tabMatchesState()) {
13825            tabsCtrl.select($scope, false);
13826          }
13827        }
13828
13829        var tabNavElement = jqLite(tabNavTemplate);
13830        tabNavElement.data('$ionTabsController', tabsCtrl);
13831        tabNavElement.data('$ionTabController', tabCtrl);
13832        tabsCtrl.$tabsElement.append($compile(tabNavElement)($scope));
13833
13834
13835        function tabSelected(isSelected) {
13836          if (isSelected && childElementCount) {
13837            // this tab is being selected
13838
13839            // check if the tab is already in the DOM
13840            // only do this if the tab has child elements
13841            if (!isTabContentAttached) {
13842              // tab should be selected and is NOT in the DOM
13843              // create a new scope and append it
13844              childScope = $scope.$new();
13845              childElement = jqLite(tabContentEle);
13846              $ionicViewSwitcher.viewEleIsActive(childElement, true);
13847              tabsCtrl.$element.append(childElement);
13848              $compile(childElement)(childScope);
13849              isTabContentAttached = true;
13850            }
13851
13852            // remove the hide class so the tabs content shows up
13853            $ionicViewSwitcher.viewEleIsActive(childElement, true);
13854
13855          } else if (isTabContentAttached && childElement) {
13856            // this tab should NOT be selected, and it is already in the DOM
13857
13858            if ($ionicConfig.views.maxCache() > 0) {
13859              // keep the tabs in the DOM, only css hide it
13860              $ionicViewSwitcher.viewEleIsActive(childElement, false);
13861
13862            } else {
13863              // do not keep tabs in the DOM
13864              destroyTab();
13865            }
13866
13867          }
13868        }
13869
13870        function destroyTab() {
13871          childScope && childScope.$destroy();
13872          isTabContentAttached && childElement && childElement.remove();
13873          tabContentEle.innerHTML = '';
13874          isTabContentAttached = childScope = childElement = null;
13875        }
13876
13877        $scope.$watch('$tabSelected', tabSelected);
13878
13879        $scope.$on('$ionicView.afterEnter', function() {
13880          $ionicViewSwitcher.viewEleIsActive(childElement, $scope.$tabSelected);
13881        });
13882
13883        $scope.$on('$ionicView.clearCache', function() {
13884          if (!$scope.$tabSelected) {
13885            destroyTab();
13886          }
13887        });
13888
13889      };
13890    }
13891  };
13892}]);
13893
13894IonicModule
13895.directive('ionTabNav', [function() {
13896  return {
13897    restrict: 'E',
13898    replace: true,
13899    require: ['^ionTabs', '^ionTab'],
13900    template:
13901    '<a ng-class="{\'has-badge\':badge, \'tab-hidden\':isHidden(), \'tab-item-active\': isTabActive()}" ' +
13902      ' ng-disabled="disabled()" class="tab-item">' +
13903      '<span class="badge {{badgeStyle}}" ng-if="badge">{{badge}}</span>' +
13904      '<i class="icon {{getIcon()}}" ng-if="getIcon()"></i>' +
13905      '<span class="tab-title" ng-bind-html="title"></span>' +
13906    '</a>',
13907    scope: {
13908      title: '@',
13909      icon: '@',
13910      iconOn: '@',
13911      iconOff: '@',
13912      badge: '=',
13913      hidden: '@',
13914      disabled: '&',
13915      badgeStyle: '@',
13916      'class': '@'
13917    },
13918    link: function($scope, $element, $attrs, ctrls) {
13919      var tabsCtrl = ctrls[0],
13920        tabCtrl = ctrls[1];
13921
13922      //Remove title attribute so browser-tooltip does not apear
13923      $element[0].removeAttribute('title');
13924
13925      $scope.selectTab = function(e) {
13926        e.preventDefault();
13927        tabsCtrl.select(tabCtrl.$scope, true);
13928      };
13929      if (!$attrs.ngClick) {
13930        $element.on('click', function(event) {
13931          $scope.$apply(function() {
13932            $scope.selectTab(event);
13933          });
13934        });
13935      }
13936
13937      $scope.isHidden = function() {
13938        if ($attrs.hidden === 'true' || $attrs.hidden === true) return true;
13939        return false;
13940      };
13941
13942      $scope.getIconOn = function() {
13943        return $scope.iconOn || $scope.icon;
13944      };
13945      $scope.getIconOff = function() {
13946        return $scope.iconOff || $scope.icon;
13947      };
13948
13949      $scope.isTabActive = function() {
13950        return tabsCtrl.selectedTab() === tabCtrl.$scope;
13951      };
13952
13953      $scope.getIcon = function() {
13954        if ( tabsCtrl.selectedTab() === tabCtrl.$scope ) {
13955          // active
13956          return $scope.iconOn || $scope.icon;
13957        }
13958        else {
13959          // inactive
13960          return $scope.iconOff || $scope.icon;
13961        }
13962      };
13963    }
13964  };
13965}]);
13966
13967/**
13968 * @ngdoc directive
13969 * @name ionTabs
13970 * @module ionic
13971 * @delegate ionic.service:$ionicTabsDelegate
13972 * @restrict E
13973 * @codepen odqCz
13974 *
13975 * @description
13976 * Powers a multi-tabbed interface with a Tab Bar and a set of "pages" that can be tabbed
13977 * through.
13978 *
13979 * Assign any [tabs class](/docs/components#tabs) to the element to define
13980 * its look and feel.
13981 *
13982 * For iOS, tabs will appear at the bottom of the screen. For Android, tabs will be at the top
13983 * of the screen, below the nav-bar. This follows each OS's design specification, but can be
13984 * configured with the {@link ionic.provider:$ionicConfigProvider}.
13985 *
13986 * See the {@link ionic.directive:ionTab} directive's documentation for more details on
13987 * individual tabs.
13988 *
13989 * Note: do not place ion-tabs inside of an ion-content element; it has been known to cause a
13990 * certain CSS bug.
13991 *
13992 * @usage
13993 * ```html
13994 * <ion-tabs class="tabs-positive tabs-icon-top">
13995 *
13996 *   <ion-tab title="Home" icon-on="ion-ios-filing" icon-off="ion-ios-filing-outline">
13997 *     <!-- Tab 1 content -->
13998 *   </ion-tab>
13999 *
14000 *   <ion-tab title="About" icon-on="ion-ios-clock" icon-off="ion-ios-clock-outline">
14001 *     <!-- Tab 2 content -->
14002 *   </ion-tab>
14003 *
14004 *   <ion-tab title="Settings" icon-on="ion-ios-gear" icon-off="ion-ios-gear-outline">
14005 *     <!-- Tab 3 content -->
14006 *   </ion-tab>
14007 *
14008 * </ion-tabs>
14009 * ```
14010 *
14011 * @param {string=} delegate-handle The handle used to identify these tabs
14012 * with {@link ionic.service:$ionicTabsDelegate}.
14013 */
14014
14015IonicModule
14016.directive('ionTabs', [
14017  '$ionicTabsDelegate',
14018  '$ionicConfig',
14019function($ionicTabsDelegate, $ionicConfig) {
14020  return {
14021    restrict: 'E',
14022    scope: true,
14023    controller: '$ionicTabs',
14024    compile: function(tElement) {
14025      //We cannot use regular transclude here because it breaks element.data()
14026      //inheritance on compile
14027      var innerElement = jqLite('<div class="tab-nav tabs">');
14028      innerElement.append(tElement.contents());
14029
14030      tElement.append(innerElement)
14031              .addClass('tabs-' + $ionicConfig.tabs.position() + ' tabs-' + $ionicConfig.tabs.style());
14032
14033      return { pre: prelink, post: postLink };
14034      function prelink($scope, $element, $attr, tabsCtrl) {
14035        var deregisterInstance = $ionicTabsDelegate._registerInstance(
14036          tabsCtrl, $attr.delegateHandle, tabsCtrl.hasActiveScope
14037        );
14038
14039        tabsCtrl.$scope = $scope;
14040        tabsCtrl.$element = $element;
14041        tabsCtrl.$tabsElement = jqLite($element[0].querySelector('.tabs'));
14042
14043        $scope.$watch(function() { return $element[0].className; }, function(value) {
14044          var isTabsTop = value.indexOf('tabs-top') !== -1;
14045          var isHidden = value.indexOf('tabs-item-hide') !== -1;
14046          $scope.$hasTabs = !isTabsTop && !isHidden;
14047          $scope.$hasTabsTop = isTabsTop && !isHidden;
14048          $scope.$emit('$ionicTabs.top', $scope.$hasTabsTop);
14049        });
14050
14051        function emitLifecycleEvent(ev, data) {
14052          ev.stopPropagation();
14053          var previousSelectedTab = tabsCtrl.previousSelectedTab();
14054          if (previousSelectedTab) {
14055            previousSelectedTab.$broadcast(ev.name.replace('NavView', 'Tabs'), data);
14056          }
14057        }
14058
14059        $scope.$on('$ionicNavView.beforeLeave', emitLifecycleEvent);
14060        $scope.$on('$ionicNavView.afterLeave', emitLifecycleEvent);
14061        $scope.$on('$ionicNavView.leave', emitLifecycleEvent);
14062
14063        $scope.$on('$destroy', function() {
14064          // variable to inform child tabs that they're all being blown away
14065          // used so that while destorying an individual tab, each one
14066          // doesn't select the next tab as the active one, which causes unnecessary
14067          // loading of tab views when each will eventually all go away anyway
14068          $scope.$tabsDestroy = true;
14069          deregisterInstance();
14070          tabsCtrl.$tabsElement = tabsCtrl.$element = tabsCtrl.$scope = innerElement = null;
14071          delete $scope.$hasTabs;
14072          delete $scope.$hasTabsTop;
14073        });
14074      }
14075
14076      function postLink($scope, $element, $attr, tabsCtrl) {
14077        if (!tabsCtrl.selectedTab()) {
14078          // all the tabs have been added
14079          // but one hasn't been selected yet
14080          tabsCtrl.select(0);
14081        }
14082      }
14083    }
14084  };
14085}]);
14086
14087/**
14088* @ngdoc directive
14089* @name ionTitle
14090* @module ionic
14091* @restrict E
14092*
14093* Used for titles in header and nav bars. New in 1.2
14094*
14095* Identical to <div class="title"> but with future compatibility for Ionic 2
14096*
14097* @usage
14098*
14099* ```html
14100* <ion-nav-bar>
14101*   <ion-title>Hello</ion-title>
14102* <ion-nav-bar>
14103* ```
14104*/
14105IonicModule
14106.directive('ionTitle', [function() {
14107  return {
14108    restrict: 'E',
14109    compile: function(element) {
14110      element.addClass('title');
14111    }
14112  };
14113}]);
14114
14115/**
14116 * @ngdoc directive
14117 * @name ionToggle
14118 * @module ionic
14119 * @codepen tfAzj
14120 * @restrict E
14121 *
14122 * @description
14123 * A toggle is an animated switch which binds a given model to a boolean.
14124 *
14125 * Allows dragging of the switch's nub.
14126 *
14127 * The toggle behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]) otherwise.
14128 *
14129 * @param toggle-class {string=} Sets the CSS class on the inner `label.toggle` element created by the directive.
14130 *
14131 * @usage
14132 * Below is an example of a toggle directive which is wired up to the `airplaneMode` model
14133 * and has the `toggle-calm` CSS class assigned to the inner element.
14134 *
14135 * ```html
14136 * <ion-toggle ng-model="airplaneMode" toggle-class="toggle-calm">Airplane Mode</ion-toggle>
14137 * ```
14138 */
14139IonicModule
14140.directive('ionToggle', [
14141  '$timeout',
14142  '$ionicConfig',
14143function($timeout, $ionicConfig) {
14144
14145  return {
14146    restrict: 'E',
14147    replace: true,
14148    require: '?ngModel',
14149    transclude: true,
14150    template:
14151      '<div class="item item-toggle">' +
14152        '<div ng-transclude></div>' +
14153        '<label class="toggle">' +
14154          '<input type="checkbox">' +
14155          '<div class="track">' +
14156            '<div class="handle"></div>' +
14157          '</div>' +
14158        '</label>' +
14159      '</div>',
14160
14161    compile: function(element, attr) {
14162      var input = element.find('input');
14163      forEach({
14164        'name': attr.name,
14165        'ng-value': attr.ngValue,
14166        'ng-model': attr.ngModel,
14167        'ng-checked': attr.ngChecked,
14168        'ng-disabled': attr.ngDisabled,
14169        'ng-true-value': attr.ngTrueValue,
14170        'ng-false-value': attr.ngFalseValue,
14171        'ng-change': attr.ngChange,
14172        'ng-required': attr.ngRequired,
14173        'required': attr.required
14174      }, function(value, name) {
14175        if (isDefined(value)) {
14176          input.attr(name, value);
14177        }
14178      });
14179
14180      if (attr.toggleClass) {
14181        element[0].getElementsByTagName('label')[0].classList.add(attr.toggleClass);
14182      }
14183
14184      element.addClass('toggle-' + $ionicConfig.form.toggle());
14185
14186      return function($scope, $element) {
14187        var el = $element[0].getElementsByTagName('label')[0];
14188        var checkbox = el.children[0];
14189        var track = el.children[1];
14190        var handle = track.children[0];
14191
14192        var ngModelController = jqLite(checkbox).controller('ngModel');
14193
14194        $scope.toggle = new ionic.views.Toggle({
14195          el: el,
14196          track: track,
14197          checkbox: checkbox,
14198          handle: handle,
14199          onChange: function() {
14200            if (ngModelController) {
14201              ngModelController.$setViewValue(checkbox.checked);
14202              $scope.$apply();
14203            }
14204          }
14205        });
14206
14207        $scope.$on('$destroy', function() {
14208          $scope.toggle.destroy();
14209        });
14210      };
14211    }
14212
14213  };
14214}]);
14215
14216/**
14217 * @ngdoc directive
14218 * @name ionView
14219 * @module ionic
14220 * @restrict E
14221 * @parent ionNavView
14222 *
14223 * @description
14224 * A container for view content and any navigational and header bar information. When a view
14225 * enters and exits its parent {@link ionic.directive:ionNavView}, the view also emits view
14226 * information, such as its title, whether the back button should be displayed or not, whether the
14227 * corresponding {@link ionic.directive:ionNavBar} should be displayed or not, which transition the view
14228 * should use to animate, and which direction to animate.
14229 *
14230 * *Views are cached to improve performance.* When a view is navigated away from, its element is
14231 * left in the DOM, and its scope is disconnected from the `$watch` cycle. When navigating to a
14232 * view that is already cached, its scope is reconnected, and the existing element, which was
14233 * left in the DOM, becomes active again. This can be disabled, or the maximum number of cached
14234 * views changed in {@link ionic.provider:$ionicConfigProvider}, in the view's `$state` configuration, or
14235 * as an attribute on the view itself (see below).
14236 *
14237 * @usage
14238 * Below is an example where our page will load with a {@link ionic.directive:ionNavBar} containing
14239 * "My Page" as the title.
14240 *
14241 * ```html
14242 * <ion-nav-bar></ion-nav-bar>
14243 * <ion-nav-view>
14244 *   <ion-view view-title="My Page">
14245 *     <ion-content>
14246 *       Hello!
14247 *     </ion-content>
14248 *   </ion-view>
14249 * </ion-nav-view>
14250 * ```
14251 *
14252 * ## View LifeCycle and Events
14253 *
14254 * Views can be cached, which means ***controllers normally only load once***, which may
14255 * affect your controller logic. To know when a view has entered or left, events
14256 * have been added that are emitted from the view's scope. These events also
14257 * contain data about the view, such as the title and whether the back button should
14258 * show. Also contained is transition data, such as the transition type and
14259 * direction that will be or was used.
14260 *
14261 * Life cycle events are emitted upwards from the transitioning view's scope. In some cases, it is
14262 * desirable for a child/nested view to be notified of the event.
14263 * For this use case, `$ionicParentView` life cycle events are broadcast downwards.
14264 *
14265 * <table class="table">
14266 *  <tr>
14267 *   <td><code>$ionicView.loaded</code></td>
14268 *   <td>The view has loaded. This event only happens once per
14269 * view being created and added to the DOM. If a view leaves but is cached,
14270 * then this event will not fire again on a subsequent viewing. The loaded event
14271 * is good place to put your setup code for the view; however, it is not the
14272 * recommended event to listen to when a view becomes active.</td>
14273 *  </tr>
14274 *  <tr>
14275 *   <td><code>$ionicView.enter</code></td>
14276 *   <td>The view has fully entered and is now the active view.
14277 * This event will fire, whether it was the first load or a cached view.</td>
14278 *  </tr>
14279 *  <tr>
14280 *   <td><code>$ionicView.leave</code></td>
14281 *   <td>The view has finished leaving and is no longer the
14282 * active view. This event will fire, whether it is cached or destroyed.</td>
14283 *  </tr>
14284 *  <tr>
14285 *   <td><code>$ionicView.beforeEnter</code></td>
14286 *   <td>The view is about to enter and become the active view.</td>
14287 *  </tr>
14288 *  <tr>
14289 *   <td><code>$ionicView.beforeLeave</code></td>
14290 *   <td>The view is about to leave and no longer be the active view.</td>
14291 *  </tr>
14292 *  <tr>
14293 *   <td><code>$ionicView.afterEnter</code></td>
14294 *   <td>The view has fully entered and is now the active view.</td>
14295 *  </tr>
14296 *  <tr>
14297 *   <td><code>$ionicView.afterLeave</code></td>
14298 *   <td>The view has finished leaving and is no longer the active view.</td>
14299 *  </tr>
14300 *  <tr>
14301 *   <td><code>$ionicView.unloaded</code></td>
14302 *   <td>The view's controller has been destroyed and its element has been
14303 * removed from the DOM.</td>
14304 *  </tr>
14305 *  <tr>
14306 *   <td><code>$ionicParentView.enter</code></td>
14307 *   <td>The parent view has fully entered and is now the active view.
14308 * This event will fire, whether it was the first load or a cached view.</td>
14309 *  </tr>
14310 *  <tr>
14311 *   <td><code>$ionicParentView.leave</code></td>
14312 *   <td>The parent view has finished leaving and is no longer the
14313 * active view. This event will fire, whether it is cached or destroyed.</td>
14314 *  </tr>
14315 *  <tr>
14316 *   <td><code>$ionicParentView.beforeEnter</code></td>
14317 *   <td>The parent view is about to enter and become the active view.</td>
14318 *  </tr>
14319 *  <tr>
14320 *   <td><code>$ionicParentView.beforeLeave</code></td>
14321 *   <td>The parent view is about to leave and no longer be the active view.</td>
14322 *  </tr>
14323 *  <tr>
14324 *   <td><code>$ionicParentView.afterEnter</code></td>
14325 *   <td>The parent view has fully entered and is now the active view.</td>
14326 *  </tr>
14327 *  <tr>
14328 *   <td><code>$ionicParentView.afterLeave</code></td>
14329 *   <td>The parent view has finished leaving and is no longer the active view.</td>
14330 *  </tr>
14331 * </table>
14332 *
14333 * ## LifeCycle Event Usage
14334 *
14335 * Below is an example of how to listen to life cycle events and
14336 * access state parameter data
14337 *
14338 * ```js
14339 * $scope.$on("$ionicView.beforeEnter", function(event, data){
14340 *    // handle event
14341 *    console.log("State Params: ", data.stateParams);
14342 * });
14343 *
14344 * $scope.$on("$ionicView.enter", function(event, data){
14345 *    // handle event
14346 *    console.log("State Params: ", data.stateParams);
14347 * });
14348 *
14349 * $scope.$on("$ionicView.afterEnter", function(event, data){
14350 *    // handle event
14351 *    console.log("State Params: ", data.stateParams);
14352 * });
14353 * ```
14354 *
14355 * ## Caching
14356 *
14357 * Caching can be disabled and enabled in multiple ways. By default, Ionic will
14358 * cache a maximum of 10 views. You can optionally choose to disable caching at
14359 * either an individual view basis, or by global configuration. Please see the
14360 * _Caching_ section in {@link ionic.directive:ionNavView} for more info.
14361 *
14362 * @param {string=} view-title A text-only title to display on the parent {@link ionic.directive:ionNavBar}.
14363 * For an HTML title, such as an image, see {@link ionic.directive:ionNavTitle} instead.
14364 * @param {boolean=} cache-view If this view should be allowed to be cached or not.
14365 * Please see the _Caching_ section in {@link ionic.directive:ionNavView} for
14366 * more info. Default `true`
14367 * @param {boolean=} can-swipe-back If this view should be allowed to use the swipe to go back gesture or not.
14368 * This does not enable the swipe to go back feature if it is not available for the platform it's running
14369 * from, or there isn't a previous view. Default `true`
14370 * @param {boolean=} hide-back-button Whether to hide the back button on the parent
14371 * {@link ionic.directive:ionNavBar} by default.
14372 * @param {boolean=} hide-nav-bar Whether to hide the parent
14373 * {@link ionic.directive:ionNavBar} by default.
14374 */
14375IonicModule
14376.directive('ionView', function() {
14377  return {
14378    restrict: 'EA',
14379    priority: 1000,
14380    controller: '$ionicView',
14381    compile: function(tElement) {
14382      tElement.addClass('pane');
14383      tElement[0].removeAttribute('title');
14384      return function link($scope, $element, $attrs, viewCtrl) {
14385        viewCtrl.init();
14386      };
14387    }
14388  };
14389});
14390
14391})();