Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
15747 anikendra 1
/*!
2
 * Copyright 2014 Drifty Co.
3
 * http://drifty.com/
4
 *
5
 * Ionic, v1.0.0
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 */
17
var IonicModule = angular.module('ionic', ['ngAnimate', 'ngSanitize', 'ui.router']),
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
 */
76
IonicModule
77
.factory('$ionicActionSheet', [
78
  '$rootScope',
79
  '$compile',
80
  '$animate',
81
  '$timeout',
82
  '$ionicTemplateLoader',
83
  '$ionicPlatform',
84
  '$ionicBody',
85
  'IONIC_BACK_PRIORITY',
86
function($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)();
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
 
234
jqLite.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
 
262
jqLite.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
 * @usage
309
 *
310
 * ```js
311
 * function MyController($scope, $ionicBackdrop, $timeout) {
312
 *   //Show a backdrop for one second
313
 *   $scope.action = function() {
314
 *     $ionicBackdrop.retain();
315
 *     $timeout(function() {
316
 *       $ionicBackdrop.release();
317
 *     }, 1000);
318
 *   };
319
 * }
320
 * ```
321
 */
322
IonicModule
323
.factory('$ionicBackdrop', [
324
  '$document', '$timeout', '$$rAF',
325
function($document, $timeout, $$rAF) {
326
 
327
  var el = jqLite('<div class="backdrop">');
328
  var backdropHolds = 0;
329
 
330
  $document[0].body.appendChild(el[0]);
331
 
332
  return {
333
    /**
334
     * @ngdoc method
335
     * @name $ionicBackdrop#retain
336
     * @description Retains the backdrop.
337
     */
338
    retain: retain,
339
    /**
340
     * @ngdoc method
341
     * @name $ionicBackdrop#release
342
     * @description
343
     * Releases the backdrop.
344
     */
345
    release: release,
346
 
347
    getElement: getElement,
348
 
349
    // exposed for testing
350
    _element: el
351
  };
352
 
353
  function retain() {
354
    backdropHolds++;
355
    if (backdropHolds === 1) {
356
      el.addClass('visible');
357
      $$rAF(function() {
358
        // If we're still at >0 backdropHolds after async...
359
        if (backdropHolds >= 1) el.addClass('active');
360
      });
361
    }
362
  }
363
  function release() {
364
    if (backdropHolds === 1) {
365
      el.removeClass('active');
366
      $timeout(function() {
367
        // If we're still at 0 backdropHolds after async...
368
        if (backdropHolds === 0) el.removeClass('visible');
369
      }, 400, false);
370
    }
371
    backdropHolds = Math.max(0, backdropHolds - 1);
372
  }
373
 
374
  function getElement() {
375
    return el;
376
  }
377
 
378
}]);
379
 
380
/**
381
 * @private
382
 */
383
IonicModule
384
.factory('$ionicBind', ['$parse', '$interpolate', function($parse, $interpolate) {
385
  var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
386
  return function(scope, attrs, bindDefinition) {
387
    forEach(bindDefinition || {}, function(definition, scopeName) {
388
      //Adapted from angular.js $compile
389
      var match = definition.match(LOCAL_REGEXP) || [],
390
        attrName = match[3] || scopeName,
391
        mode = match[1], // @, =, or &
392
        parentGet,
393
        unwatch;
394
 
395
      switch (mode) {
396
        case '@':
397
          if (!attrs[attrName]) {
398
            return;
399
          }
400
          attrs.$observe(attrName, function(value) {
401
            scope[scopeName] = value;
402
          });
403
          // we trigger an interpolation to ensure
404
          // the value is there for use immediately
405
          if (attrs[attrName]) {
406
            scope[scopeName] = $interpolate(attrs[attrName])(scope);
407
          }
408
          break;
409
 
410
        case '=':
411
          if (!attrs[attrName]) {
412
            return;
413
          }
414
          unwatch = scope.$watch(attrs[attrName], function(value) {
415
            scope[scopeName] = value;
416
          });
417
          //Destroy parent scope watcher when this scope is destroyed
418
          scope.$on('$destroy', unwatch);
419
          break;
420
 
421
        case '&':
422
          /* jshint -W044 */
423
          if (attrs[attrName] && attrs[attrName].match(RegExp(scopeName + '\(.*?\)'))) {
424
            throw new Error('& expression binding "' + scopeName + '" looks like it will recursively call "' +
425
                          attrs[attrName] + '" and cause a stack overflow! Please choose a different scopeName.');
426
          }
427
          parentGet = $parse(attrs[attrName]);
428
          scope[scopeName] = function(locals) {
429
            return parentGet(scope, locals);
430
          };
431
          break;
432
      }
433
    });
434
  };
435
}]);
436
 
437
/**
438
 * @ngdoc service
439
 * @name $ionicBody
440
 * @module ionic
441
 * @description An angular utility service to easily and efficiently
442
 * add and remove CSS classes from the document's body element.
443
 */
444
IonicModule
445
.factory('$ionicBody', ['$document', function($document) {
446
  return {
447
    /**
448
     * @ngdoc method
449
     * @name $ionicBody#add
450
     * @description Add a class to the document's body element.
451
     * @param {string} class Each argument will be added to the body element.
452
     * @returns {$ionicBody} The $ionicBody service so methods can be chained.
453
     */
454
    addClass: function() {
455
      for (var x = 0; x < arguments.length; x++) {
456
        $document[0].body.classList.add(arguments[x]);
457
      }
458
      return this;
459
    },
460
    /**
461
     * @ngdoc method
462
     * @name $ionicBody#removeClass
463
     * @description Remove a class from the document's body element.
464
     * @param {string} class Each argument will be removed from the body element.
465
     * @returns {$ionicBody} The $ionicBody service so methods can be chained.
466
     */
467
    removeClass: function() {
468
      for (var x = 0; x < arguments.length; x++) {
469
        $document[0].body.classList.remove(arguments[x]);
470
      }
471
      return this;
472
    },
473
    /**
474
     * @ngdoc method
475
     * @name $ionicBody#enableClass
476
     * @description Similar to the `add` method, except the first parameter accepts a boolean
477
     * value determining if the class should be added or removed. Rather than writing user code,
478
     * such as "if true then add the class, else then remove the class", this method can be
479
     * given a true or false value which reduces redundant code.
480
     * @param {boolean} shouldEnableClass A true/false value if the class should be added or removed.
481
     * @param {string} class Each remaining argument would be added or removed depending on
482
     * the first argument.
483
     * @returns {$ionicBody} The $ionicBody service so methods can be chained.
484
     */
485
    enableClass: function(shouldEnableClass) {
486
      var args = Array.prototype.slice.call(arguments).slice(1);
487
      if (shouldEnableClass) {
488
        this.addClass.apply(this, args);
489
      } else {
490
        this.removeClass.apply(this, args);
491
      }
492
      return this;
493
    },
494
    /**
495
     * @ngdoc method
496
     * @name $ionicBody#append
497
     * @description Append a child to the document's body.
498
     * @param {element} element The element to be appended to the body. The passed in element
499
     * can be either a jqLite element, or a DOM element.
500
     * @returns {$ionicBody} The $ionicBody service so methods can be chained.
501
     */
502
    append: function(ele) {
503
      $document[0].body.appendChild(ele.length ? ele[0] : ele);
504
      return this;
505
    },
506
    /**
507
     * @ngdoc method
508
     * @name $ionicBody#get
509
     * @description Get the document's body element.
510
     * @returns {element} Returns the document's body element.
511
     */
512
    get: function() {
513
      return $document[0].body;
514
    }
515
  };
516
}]);
517
 
518
IonicModule
519
.factory('$ionicClickBlock', [
520
  '$document',
521
  '$ionicBody',
522
  '$timeout',
523
function($document, $ionicBody, $timeout) {
524
  var CSS_HIDE = 'click-block-hide';
525
  var cbEle, fallbackTimer, pendingShow;
526
 
527
  function preventClick(ev) {
528
    ev.preventDefault();
529
    ev.stopPropagation();
530
  }
531
 
532
  function addClickBlock() {
533
    if (pendingShow) {
534
      if (cbEle) {
535
        cbEle.classList.remove(CSS_HIDE);
536
      } else {
537
        cbEle = $document[0].createElement('div');
538
        cbEle.className = 'click-block';
539
        $ionicBody.append(cbEle);
540
        cbEle.addEventListener('touchstart', preventClick);
541
        cbEle.addEventListener('mousedown', preventClick);
542
      }
543
      pendingShow = false;
544
    }
545
  }
546
 
547
  function removeClickBlock() {
548
    cbEle && cbEle.classList.add(CSS_HIDE);
549
  }
550
 
551
  return {
552
    show: function(autoExpire) {
553
      pendingShow = true;
554
      $timeout.cancel(fallbackTimer);
555
      fallbackTimer = $timeout(this.hide, autoExpire || 310, false);
556
      addClickBlock();
557
    },
558
    hide: function() {
559
      pendingShow = false;
560
      $timeout.cancel(fallbackTimer);
561
      removeClickBlock();
562
    }
563
  };
564
}]);
565
 
566
/**
567
 * @ngdoc service
568
 * @name $ionicGesture
569
 * @module ionic
570
 * @description An angular service exposing ionic
571
 * {@link ionic.utility:ionic.EventController}'s gestures.
572
 */
573
IonicModule
574
.factory('$ionicGesture', [function() {
575
  return {
576
    /**
577
     * @ngdoc method
578
     * @name $ionicGesture#on
579
     * @description Add an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#onGesture}.
580
     * @param {string} eventType The gesture event to listen for.
581
     * @param {function(e)} callback The function to call when the gesture
582
     * happens.
583
     * @param {element} $element The angular element to listen for the event on.
584
     * @param {object} options object.
585
     * @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on).
586
     */
587
    on: function(eventType, cb, $element, options) {
588
      return window.ionic.onGesture(eventType, cb, $element[0], options);
589
    },
590
    /**
591
     * @ngdoc method
592
     * @name $ionicGesture#off
593
     * @description Remove an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#offGesture}.
594
     * @param {ionic.Gesture} gesture The gesture that should be removed.
595
     * @param {string} eventType The gesture event to remove the listener for.
596
     * @param {function(e)} callback The listener to remove.
597
     */
598
    off: function(gesture, eventType, cb) {
599
      return window.ionic.offGesture(gesture, eventType, cb);
600
    }
601
  };
602
}]);
603
 
604
/**
605
 * @ngdoc service
606
 * @name $ionicHistory
607
 * @module ionic
608
 * @description
609
 * $ionicHistory keeps track of views as the user navigates through an app. Similar to the way a
610
 * browser behaves, an Ionic app is able to keep track of the previous view, the current view, and
611
 * the forward view (if there is one).  However, a typical web browser only keeps track of one
612
 * history stack in a linear fashion.
613
 *
614
 * Unlike a traditional browser environment, apps and webapps have parallel independent histories,
615
 * such as with tabs. Should a user navigate few pages deep on one tab, and then switch to a new
616
 * tab and back, the back button relates not to the previous tab, but to the previous pages
617
 * visited within _that_ tab.
618
 *
619
 * `$ionicHistory` facilitates this parallel history architecture.
620
 */
621
 
622
IonicModule
623
.factory('$ionicHistory', [
624
  '$rootScope',
625
  '$state',
626
  '$location',
627
  '$window',
628
  '$timeout',
629
  '$ionicViewSwitcher',
630
  '$ionicNavViewDelegate',
631
function($rootScope, $state, $location, $window, $timeout, $ionicViewSwitcher, $ionicNavViewDelegate) {
632
 
633
  // history actions while navigating views
634
  var ACTION_INITIAL_VIEW = 'initialView';
635
  var ACTION_NEW_VIEW = 'newView';
636
  var ACTION_MOVE_BACK = 'moveBack';
637
  var ACTION_MOVE_FORWARD = 'moveForward';
638
 
639
  // direction of navigation
640
  var DIRECTION_BACK = 'back';
641
  var DIRECTION_FORWARD = 'forward';
642
  var DIRECTION_ENTER = 'enter';
643
  var DIRECTION_EXIT = 'exit';
644
  var DIRECTION_SWAP = 'swap';
645
  var DIRECTION_NONE = 'none';
646
 
647
  var stateChangeCounter = 0;
648
  var lastStateId, nextViewOptions, nextViewExpireTimer, forcedNav;
649
 
650
  var viewHistory = {
651
    histories: { root: { historyId: 'root', parentHistoryId: null, stack: [], cursor: -1 } },
652
    views: {},
653
    backView: null,
654
    forwardView: null,
655
    currentView: null
656
  };
657
 
658
  var View = function() {};
659
  View.prototype.initialize = function(data) {
660
    if (data) {
661
      for (var name in data) this[name] = data[name];
662
      return this;
663
    }
664
    return null;
665
  };
666
  View.prototype.go = function() {
667
 
668
    if (this.stateName) {
669
      return $state.go(this.stateName, this.stateParams);
670
    }
671
 
672
    if (this.url && this.url !== $location.url()) {
673
 
674
      if (viewHistory.backView === this) {
675
        return $window.history.go(-1);
676
      } else if (viewHistory.forwardView === this) {
677
        return $window.history.go(1);
678
      }
679
 
680
      $location.url(this.url);
681
    }
682
 
683
    return null;
684
  };
685
  View.prototype.destroy = function() {
686
    if (this.scope) {
687
      this.scope.$destroy && this.scope.$destroy();
688
      this.scope = null;
689
    }
690
  };
691
 
692
 
693
  function getViewById(viewId) {
694
    return (viewId ? viewHistory.views[ viewId ] : null);
695
  }
696
 
697
  function getBackView(view) {
698
    return (view ? getViewById(view.backViewId) : null);
699
  }
700
 
701
  function getForwardView(view) {
702
    return (view ? getViewById(view.forwardViewId) : null);
703
  }
704
 
705
  function getHistoryById(historyId) {
706
    return (historyId ? viewHistory.histories[ historyId ] : null);
707
  }
708
 
709
  function getHistory(scope) {
710
    var histObj = getParentHistoryObj(scope);
711
 
712
    if (!viewHistory.histories[ histObj.historyId ]) {
713
      // this history object exists in parent scope, but doesn't
714
      // exist in the history data yet
715
      viewHistory.histories[ histObj.historyId ] = {
716
        historyId: histObj.historyId,
717
        parentHistoryId: getParentHistoryObj(histObj.scope.$parent).historyId,
718
        stack: [],
719
        cursor: -1
720
      };
721
    }
722
    return getHistoryById(histObj.historyId);
723
  }
724
 
725
  function getParentHistoryObj(scope) {
726
    var parentScope = scope;
727
    while (parentScope) {
728
      if (parentScope.hasOwnProperty('$historyId')) {
729
        // this parent scope has a historyId
730
        return { historyId: parentScope.$historyId, scope: parentScope };
731
      }
732
      // nothing found keep climbing up
733
      parentScope = parentScope.$parent;
734
    }
735
    // no history for the parent, use the root
736
    return { historyId: 'root', scope: $rootScope };
737
  }
738
 
739
  function setNavViews(viewId) {
740
    viewHistory.currentView = getViewById(viewId);
741
    viewHistory.backView = getBackView(viewHistory.currentView);
742
    viewHistory.forwardView = getForwardView(viewHistory.currentView);
743
  }
744
 
745
  function getCurrentStateId() {
746
    var id;
747
    if ($state && $state.current && $state.current.name) {
748
      id = $state.current.name;
749
      if ($state.params) {
750
        for (var key in $state.params) {
751
          if ($state.params.hasOwnProperty(key) && $state.params[key]) {
752
            id += "_" + key + "=" + $state.params[key];
753
          }
754
        }
755
      }
756
      return id;
757
    }
758
    // if something goes wrong make sure its got a unique stateId
759
    return ionic.Utils.nextUid();
760
  }
761
 
762
  function getCurrentStateParams() {
763
    var rtn;
764
    if ($state && $state.params) {
765
      for (var key in $state.params) {
766
        if ($state.params.hasOwnProperty(key)) {
767
          rtn = rtn || {};
768
          rtn[key] = $state.params[key];
769
        }
770
      }
771
    }
772
    return rtn;
773
  }
774
 
775
 
776
  return {
777
 
778
    register: function(parentScope, viewLocals) {
779
 
780
      var currentStateId = getCurrentStateId(),
781
          hist = getHistory(parentScope),
782
          currentView = viewHistory.currentView,
783
          backView = viewHistory.backView,
784
          forwardView = viewHistory.forwardView,
785
          viewId = null,
786
          action = null,
787
          direction = DIRECTION_NONE,
788
          historyId = hist.historyId,
789
          url = $location.url(),
790
          tmp, x, ele;
791
 
792
      if (lastStateId !== currentStateId) {
793
        lastStateId = currentStateId;
794
        stateChangeCounter++;
795
      }
796
 
797
      if (forcedNav) {
798
        // we've previously set exactly what to do
799
        viewId = forcedNav.viewId;
800
        action = forcedNav.action;
801
        direction = forcedNav.direction;
802
        forcedNav = null;
803
 
804
      } else if (backView && backView.stateId === currentStateId) {
805
        // they went back one, set the old current view as a forward view
806
        viewId = backView.viewId;
807
        historyId = backView.historyId;
808
        action = ACTION_MOVE_BACK;
809
        if (backView.historyId === currentView.historyId) {
810
          // went back in the same history
811
          direction = DIRECTION_BACK;
812
 
813
        } else if (currentView) {
814
          direction = DIRECTION_EXIT;
815
 
816
          tmp = getHistoryById(backView.historyId);
817
          if (tmp && tmp.parentHistoryId === currentView.historyId) {
818
            direction = DIRECTION_ENTER;
819
 
820
          } else {
821
            tmp = getHistoryById(currentView.historyId);
822
            if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
823
              direction = DIRECTION_SWAP;
824
            }
825
          }
826
        }
827
 
828
      } else if (forwardView && forwardView.stateId === currentStateId) {
829
        // they went to the forward one, set the forward view to no longer a forward view
830
        viewId = forwardView.viewId;
831
        historyId = forwardView.historyId;
832
        action = ACTION_MOVE_FORWARD;
833
        if (forwardView.historyId === currentView.historyId) {
834
          direction = DIRECTION_FORWARD;
835
 
836
        } else if (currentView) {
837
          direction = DIRECTION_EXIT;
838
 
839
          if (currentView.historyId === hist.parentHistoryId) {
840
            direction = DIRECTION_ENTER;
841
 
842
          } else {
843
            tmp = getHistoryById(currentView.historyId);
844
            if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
845
              direction = DIRECTION_SWAP;
846
            }
847
          }
848
        }
849
 
850
        tmp = getParentHistoryObj(parentScope);
851
        if (forwardView.historyId && tmp.scope) {
852
          // if a history has already been created by the forward view then make sure it stays the same
853
          tmp.scope.$historyId = forwardView.historyId;
854
          historyId = forwardView.historyId;
855
        }
856
 
857
      } else if (currentView && currentView.historyId !== historyId &&
858
                hist.cursor > -1 && hist.stack.length > 0 && hist.cursor < hist.stack.length &&
859
                hist.stack[hist.cursor].stateId === currentStateId) {
860
        // they just changed to a different history and the history already has views in it
861
        var switchToView = hist.stack[hist.cursor];
862
        viewId = switchToView.viewId;
863
        historyId = switchToView.historyId;
864
        action = ACTION_MOVE_BACK;
865
        direction = DIRECTION_SWAP;
866
 
867
        tmp = getHistoryById(currentView.historyId);
868
        if (tmp && tmp.parentHistoryId === historyId) {
869
          direction = DIRECTION_EXIT;
870
 
871
        } else {
872
          tmp = getHistoryById(historyId);
873
          if (tmp && tmp.parentHistoryId === currentView.historyId) {
874
            direction = DIRECTION_ENTER;
875
          }
876
        }
877
 
878
        // if switching to a different history, and the history of the view we're switching
879
        // to has an existing back view from a different history than itself, then
880
        // it's back view would be better represented using the current view as its back view
881
        tmp = getViewById(switchToView.backViewId);
882
        if (tmp && switchToView.historyId !== tmp.historyId) {
883
          hist.stack[hist.cursor].backViewId = currentView.viewId;
884
        }
885
 
886
      } else {
887
 
888
        // create an element from the viewLocals template
889
        ele = $ionicViewSwitcher.createViewEle(viewLocals);
890
        if (this.isAbstractEle(ele, viewLocals)) {
891
          void 0;
892
          return {
893
            action: 'abstractView',
894
            direction: DIRECTION_NONE,
895
            ele: ele
896
          };
897
        }
898
 
899
        // set a new unique viewId
900
        viewId = ionic.Utils.nextUid();
901
 
902
        if (currentView) {
903
          // set the forward view if there is a current view (ie: if its not the first view)
904
          currentView.forwardViewId = viewId;
905
 
906
          action = ACTION_NEW_VIEW;
907
 
908
          // check if there is a new forward view within the same history
909
          if (forwardView && currentView.stateId !== forwardView.stateId &&
910
             currentView.historyId === forwardView.historyId) {
911
            // they navigated to a new view but the stack already has a forward view
912
            // since its a new view remove any forwards that existed
913
            tmp = getHistoryById(forwardView.historyId);
914
            if (tmp) {
915
              // the forward has a history
916
              for (x = tmp.stack.length - 1; x >= forwardView.index; x--) {
917
                // starting from the end destroy all forwards in this history from this point
918
                var stackItem = tmp.stack[x];
919
                stackItem && stackItem.destroy && stackItem.destroy();
920
                tmp.stack.splice(x);
921
              }
922
              historyId = forwardView.historyId;
923
            }
924
          }
925
 
926
          // its only moving forward if its in the same history
927
          if (hist.historyId === currentView.historyId) {
928
            direction = DIRECTION_FORWARD;
929
 
930
          } else if (currentView.historyId !== hist.historyId) {
931
            direction = DIRECTION_ENTER;
932
 
933
            tmp = getHistoryById(currentView.historyId);
934
            if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
935
              direction = DIRECTION_SWAP;
936
 
937
            } else {
938
              tmp = getHistoryById(tmp.parentHistoryId);
939
              if (tmp && tmp.historyId === hist.historyId) {
940
                direction = DIRECTION_EXIT;
941
              }
942
            }
943
          }
944
 
945
        } else {
946
          // there's no current view, so this must be the initial view
947
          action = ACTION_INITIAL_VIEW;
948
        }
949
 
950
        if (stateChangeCounter < 2) {
951
          // views that were spun up on the first load should not animate
952
          direction = DIRECTION_NONE;
953
        }
954
 
955
        // add the new view
956
        viewHistory.views[viewId] = this.createView({
957
          viewId: viewId,
958
          index: hist.stack.length,
959
          historyId: hist.historyId,
960
          backViewId: (currentView && currentView.viewId ? currentView.viewId : null),
961
          forwardViewId: null,
962
          stateId: currentStateId,
963
          stateName: this.currentStateName(),
964
          stateParams: getCurrentStateParams(),
965
          url: url,
966
          canSwipeBack: canSwipeBack(ele, viewLocals)
967
        });
968
 
969
        // add the new view to this history's stack
970
        hist.stack.push(viewHistory.views[viewId]);
971
      }
972
 
973
      $timeout.cancel(nextViewExpireTimer);
974
      if (nextViewOptions) {
975
        if (nextViewOptions.disableAnimate) direction = DIRECTION_NONE;
976
        if (nextViewOptions.disableBack) viewHistory.views[viewId].backViewId = null;
977
        if (nextViewOptions.historyRoot) {
978
          for (x = 0; x < hist.stack.length; x++) {
979
            if (hist.stack[x].viewId === viewId) {
980
              hist.stack[x].index = 0;
981
              hist.stack[x].backViewId = hist.stack[x].forwardViewId = null;
982
            } else {
983
              delete viewHistory.views[hist.stack[x].viewId];
984
            }
985
          }
986
          hist.stack = [viewHistory.views[viewId]];
987
        }
988
        nextViewOptions = null;
989
      }
990
 
991
      setNavViews(viewId);
992
 
993
      if (viewHistory.backView && historyId == viewHistory.backView.historyId && currentStateId == viewHistory.backView.stateId && url == viewHistory.backView.url) {
994
        for (x = 0; x < hist.stack.length; x++) {
995
          if (hist.stack[x].viewId == viewId) {
996
            action = 'dupNav';
997
            direction = DIRECTION_NONE;
998
            if (x > 0) {
999
              hist.stack[x - 1].forwardViewId = null;
1000
            }
1001
            viewHistory.forwardView = null;
1002
            viewHistory.currentView.index = viewHistory.backView.index;
1003
            viewHistory.currentView.backViewId = viewHistory.backView.backViewId;
1004
            viewHistory.backView = getBackView(viewHistory.backView);
1005
            hist.stack.splice(x, 1);
1006
            break;
1007
          }
1008
        }
1009
      }
1010
 
1011
      void 0;
1012
 
1013
      hist.cursor = viewHistory.currentView.index;
1014
 
1015
      return {
1016
        viewId: viewId,
1017
        action: action,
1018
        direction: direction,
1019
        historyId: historyId,
1020
        enableBack: this.enabledBack(viewHistory.currentView),
1021
        isHistoryRoot: (viewHistory.currentView.index === 0),
1022
        ele: ele
1023
      };
1024
    },
1025
 
1026
    registerHistory: function(scope) {
1027
      scope.$historyId = ionic.Utils.nextUid();
1028
    },
1029
 
1030
    createView: function(data) {
1031
      var newView = new View();
1032
      return newView.initialize(data);
1033
    },
1034
 
1035
    getViewById: getViewById,
1036
 
1037
    /**
1038
     * @ngdoc method
1039
     * @name $ionicHistory#viewHistory
1040
     * @description The app's view history data, such as all the views and histories, along
1041
     * with how they are ordered and linked together within the navigation stack.
1042
     * @returns {object} Returns an object containing the apps view history data.
1043
     */
1044
    viewHistory: function() {
1045
      return viewHistory;
1046
    },
1047
 
1048
    /**
1049
     * @ngdoc method
1050
     * @name $ionicHistory#currentView
1051
     * @description The app's current view.
1052
     * @returns {object} Returns the current view.
1053
     */
1054
    currentView: function(view) {
1055
      if (arguments.length) {
1056
        viewHistory.currentView = view;
1057
      }
1058
      return viewHistory.currentView;
1059
    },
1060
 
1061
    /**
1062
     * @ngdoc method
1063
     * @name $ionicHistory#currentHistoryId
1064
     * @description The ID of the history stack which is the parent container of the current view.
1065
     * @returns {string} Returns the current history ID.
1066
     */
1067
    currentHistoryId: function() {
1068
      return viewHistory.currentView ? viewHistory.currentView.historyId : null;
1069
    },
1070
 
1071
    /**
1072
     * @ngdoc method
1073
     * @name $ionicHistory#currentTitle
1074
     * @description Gets and sets the current view's title.
1075
     * @param {string=} val The title to update the current view with.
1076
     * @returns {string} Returns the current view's title.
1077
     */
1078
    currentTitle: function(val) {
1079
      if (viewHistory.currentView) {
1080
        if (arguments.length) {
1081
          viewHistory.currentView.title = val;
1082
        }
1083
        return viewHistory.currentView.title;
1084
      }
1085
    },
1086
 
1087
    /**
1088
     * @ngdoc method
1089
     * @name $ionicHistory#backView
1090
     * @description Returns the view that was before the current view in the history stack.
1091
     * If the user navigated from View A to View B, then View A would be the back view, and
1092
     * View B would be the current view.
1093
     * @returns {object} Returns the back view.
1094
     */
1095
    backView: function(view) {
1096
      if (arguments.length) {
1097
        viewHistory.backView = view;
1098
      }
1099
      return viewHistory.backView;
1100
    },
1101
 
1102
    /**
1103
     * @ngdoc method
1104
     * @name $ionicHistory#backTitle
1105
     * @description Gets the back view's title.
1106
     * @returns {string} Returns the back view's title.
1107
     */
1108
    backTitle: function(view) {
1109
      var backView = (view && getViewById(view.backViewId)) || viewHistory.backView;
1110
      return backView && backView.title;
1111
    },
1112
 
1113
    /**
1114
     * @ngdoc method
1115
     * @name $ionicHistory#forwardView
1116
     * @description Returns the view that was in front of the current view in the history stack.
1117
     * A forward view would exist if the user navigated from View A to View B, then
1118
     * navigated back to View A. At this point then View B would be the forward view, and View
1119
     * A would be the current view.
1120
     * @returns {object} Returns the forward view.
1121
     */
1122
    forwardView: function(view) {
1123
      if (arguments.length) {
1124
        viewHistory.forwardView = view;
1125
      }
1126
      return viewHistory.forwardView;
1127
    },
1128
 
1129
    /**
1130
     * @ngdoc method
1131
     * @name $ionicHistory#currentStateName
1132
     * @description Returns the current state name.
1133
     * @returns {string}
1134
     */
1135
    currentStateName: function() {
1136
      return ($state && $state.current ? $state.current.name : null);
1137
    },
1138
 
1139
    isCurrentStateNavView: function(navView) {
1140
      return !!($state && $state.current && $state.current.views && $state.current.views[navView]);
1141
    },
1142
 
1143
    goToHistoryRoot: function(historyId) {
1144
      if (historyId) {
1145
        var hist = getHistoryById(historyId);
1146
        if (hist && hist.stack.length) {
1147
          if (viewHistory.currentView && viewHistory.currentView.viewId === hist.stack[0].viewId) {
1148
            return;
1149
          }
1150
          forcedNav = {
1151
            viewId: hist.stack[0].viewId,
1152
            action: ACTION_MOVE_BACK,
1153
            direction: DIRECTION_BACK
1154
          };
1155
          hist.stack[0].go();
1156
        }
1157
      }
1158
    },
1159
 
1160
    /**
1161
     * @ngdoc method
1162
     * @name $ionicHistory#goBack
1163
     * @param {number=} backCount Optional negative integer setting how many views to go
1164
     * back. By default it'll go back one view by using the value `-1`. To go back two
1165
     * views you would use `-2`. If the number goes farther back than the number of views
1166
     * in the current history's stack then it'll go to the first view in the current history's
1167
     * stack. If the number is zero or greater then it'll do nothing. It also does not
1168
     * cross history stacks, meaning it can only go as far back as the current history.
1169
     * @description Navigates the app to the back view, if a back view exists.
1170
     */
1171
    goBack: function(backCount) {
1172
      if (isDefined(backCount) && backCount !== -1) {
1173
        if (backCount > -1) return;
1174
 
1175
        var currentHistory = viewHistory.histories[this.currentHistoryId()];
1176
        var newCursor = currentHistory.cursor + backCount + 1;
1177
        if (newCursor < 1) {
1178
          newCursor = 1;
1179
        }
1180
 
1181
        currentHistory.cursor = newCursor;
1182
        setNavViews(currentHistory.stack[newCursor].viewId);
1183
 
1184
        var cursor = newCursor - 1;
1185
        var clearStateIds = [];
1186
        var fwdView = getViewById(currentHistory.stack[cursor].forwardViewId);
1187
        while (fwdView) {
1188
          clearStateIds.push(fwdView.stateId || fwdView.viewId);
1189
          cursor++;
1190
          if (cursor >= currentHistory.stack.length) break;
1191
          fwdView = getViewById(currentHistory.stack[cursor].forwardViewId);
1192
        }
1193
 
1194
        var self = this;
1195
        if (clearStateIds.length) {
1196
          $timeout(function() {
1197
            self.clearCache(clearStateIds);
1198
          }, 600);
1199
        }
1200
      }
1201
 
1202
      viewHistory.backView && viewHistory.backView.go();
1203
    },
1204
 
1205
 
1206
    enabledBack: function(view) {
1207
      var backView = getBackView(view);
1208
      return !!(backView && backView.historyId === view.historyId);
1209
    },
1210
 
1211
    /**
1212
     * @ngdoc method
1213
     * @name $ionicHistory#clearHistory
1214
     * @description Clears out the app's entire history, except for the current view.
1215
     */
1216
    clearHistory: function() {
1217
      var
1218
      histories = viewHistory.histories,
1219
      currentView = viewHistory.currentView;
1220
 
1221
      if (histories) {
1222
        for (var historyId in histories) {
1223
 
1224
          if (histories[historyId].stack) {
1225
            histories[historyId].stack = [];
1226
            histories[historyId].cursor = -1;
1227
          }
1228
 
1229
          if (currentView && currentView.historyId === historyId) {
1230
            currentView.backViewId = currentView.forwardViewId = null;
1231
            histories[historyId].stack.push(currentView);
1232
          } else if (histories[historyId].destroy) {
1233
            histories[historyId].destroy();
1234
          }
1235
 
1236
        }
1237
      }
1238
 
1239
      for (var viewId in viewHistory.views) {
1240
        if (viewId !== currentView.viewId) {
1241
          delete viewHistory.views[viewId];
1242
        }
1243
      }
1244
 
1245
      if (currentView) {
1246
        setNavViews(currentView.viewId);
1247
      }
1248
    },
1249
 
1250
    /**
1251
     * @ngdoc method
1252
     * @name $ionicHistory#clearCache
1253
     * @description Removes all cached views within every {@link ionic.directive:ionNavView}.
1254
     * This both removes the view element from the DOM, and destroy it's scope.
1255
     */
1256
    clearCache: function(stateIds) {
1257
      $timeout(function() {
1258
        $ionicNavViewDelegate._instances.forEach(function(instance) {
1259
          instance.clearCache(stateIds);
1260
        });
1261
      });
1262
    },
1263
 
1264
    /**
1265
     * @ngdoc method
1266
     * @name $ionicHistory#nextViewOptions
1267
     * @description Sets options for the next view. This method can be useful to override
1268
     * certain view/transition defaults right before a view transition happens. For example,
1269
     * the {@link ionic.directive:menuClose} directive uses this method internally to ensure
1270
     * an animated view transition does not happen when a side menu is open, and also sets
1271
     * the next view as the root of its history stack. After the transition these options
1272
     * are set back to null.
1273
     *
1274
     * Available options:
1275
     *
1276
     * * `disableAnimate`: Do not animate the next transition.
1277
     * * `disableBack`: The next view should forget its back view, and set it to null.
1278
     * * `historyRoot`: The next view should become the root view in its history stack.
1279
     *
1280
     * ```js
1281
     * $ionicHistory.nextViewOptions({
1282
     *   disableAnimate: true,
1283
     *   disableBack: true
1284
     * });
1285
     * ```
1286
     */
1287
    nextViewOptions: function(opts) {
1288
      if (arguments.length) {
1289
        $timeout.cancel(nextViewExpireTimer);
1290
        if (opts === null) {
1291
          nextViewOptions = opts;
1292
        } else {
1293
          nextViewOptions = nextViewOptions || {};
1294
          extend(nextViewOptions, opts);
1295
          if (nextViewOptions.expire) {
1296
            nextViewExpireTimer = $timeout(function() {
1297
              nextViewOptions = null;
1298
            }, nextViewOptions.expire);
1299
          }
1300
        }
1301
      }
1302
      return nextViewOptions;
1303
    },
1304
 
1305
    isAbstractEle: function(ele, viewLocals) {
1306
      if (viewLocals && viewLocals.$$state && viewLocals.$$state.self['abstract']) {
1307
        return true;
1308
      }
1309
      return !!(ele && (isAbstractTag(ele) || isAbstractTag(ele.children())));
1310
    },
1311
 
1312
    isActiveScope: function(scope) {
1313
      if (!scope) return false;
1314
 
1315
      var climbScope = scope;
1316
      var currentHistoryId = this.currentHistoryId();
1317
      var foundHistoryId;
1318
 
1319
      while (climbScope) {
1320
        if (climbScope.$$disconnected) {
1321
          return false;
1322
        }
1323
 
1324
        if (!foundHistoryId && climbScope.hasOwnProperty('$historyId')) {
1325
          foundHistoryId = true;
1326
        }
1327
 
1328
        if (currentHistoryId) {
1329
          if (climbScope.hasOwnProperty('$historyId') && currentHistoryId == climbScope.$historyId) {
1330
            return true;
1331
          }
1332
          if (climbScope.hasOwnProperty('$activeHistoryId')) {
1333
            if (currentHistoryId == climbScope.$activeHistoryId) {
1334
              if (climbScope.hasOwnProperty('$historyId')) {
1335
                return true;
1336
              }
1337
              if (!foundHistoryId) {
1338
                return true;
1339
              }
1340
            }
1341
          }
1342
        }
1343
 
1344
        if (foundHistoryId && climbScope.hasOwnProperty('$activeHistoryId')) {
1345
          foundHistoryId = false;
1346
        }
1347
 
1348
        climbScope = climbScope.$parent;
1349
      }
1350
 
1351
      return currentHistoryId ? currentHistoryId == 'root' : true;
1352
    }
1353
 
1354
  };
1355
 
1356
  function isAbstractTag(ele) {
1357
    return ele && ele.length && /ion-side-menus|ion-tabs/i.test(ele[0].tagName);
1358
  }
1359
 
1360
  function canSwipeBack(ele, viewLocals) {
1361
    if (viewLocals && viewLocals.$$state && viewLocals.$$state.self.canSwipeBack === false) {
1362
      return false;
1363
    }
1364
    if (ele && ele.attr('can-swipe-back') === 'false') {
1365
      return false;
1366
    }
1367
    return true;
1368
  }
1369
 
1370
}])
1371
 
1372
.run([
1373
  '$rootScope',
1374
  '$state',
1375
  '$location',
1376
  '$document',
1377
  '$ionicPlatform',
1378
  '$ionicHistory',
1379
  'IONIC_BACK_PRIORITY',
1380
function($rootScope, $state, $location, $document, $ionicPlatform, $ionicHistory, IONIC_BACK_PRIORITY) {
1381
 
1382
  // always reset the keyboard state when change stage
1383
  $rootScope.$on('$ionicView.beforeEnter', function() {
1384
    ionic.keyboard && ionic.keyboard.hide && ionic.keyboard.hide();
1385
  });
1386
 
1387
  $rootScope.$on('$ionicHistory.change', function(e, data) {
1388
    if (!data) return null;
1389
 
1390
    var viewHistory = $ionicHistory.viewHistory();
1391
 
1392
    var hist = (data.historyId ? viewHistory.histories[ data.historyId ] : null);
1393
    if (hist && hist.cursor > -1 && hist.cursor < hist.stack.length) {
1394
      // the history they're going to already exists
1395
      // go to it's last view in its stack
1396
      var view = hist.stack[ hist.cursor ];
1397
      return view.go(data);
1398
    }
1399
 
1400
    // this history does not have a URL, but it does have a uiSref
1401
    // figure out its URL from the uiSref
1402
    if (!data.url && data.uiSref) {
1403
      data.url = $state.href(data.uiSref);
1404
    }
1405
 
1406
    if (data.url) {
1407
      // don't let it start with a #, messes with $location.url()
1408
      if (data.url.indexOf('#') === 0) {
1409
        data.url = data.url.replace('#', '');
1410
      }
1411
      if (data.url !== $location.url()) {
1412
        // we've got a good URL, ready GO!
1413
        $location.url(data.url);
1414
      }
1415
    }
1416
  });
1417
 
1418
  $rootScope.$ionicGoBack = function(backCount) {
1419
    $ionicHistory.goBack(backCount);
1420
  };
1421
 
1422
  // Set the document title when a new view is shown
1423
  $rootScope.$on('$ionicView.afterEnter', function(ev, data) {
1424
    if (data && data.title) {
1425
      $document[0].title = data.title;
1426
    }
1427
  });
1428
 
1429
  // Triggered when devices with a hardware back button (Android) is clicked by the user
1430
  // This is a Cordova/Phonegap platform specifc method
1431
  function onHardwareBackButton(e) {
1432
    var backView = $ionicHistory.backView();
1433
    if (backView) {
1434
      // there is a back view, go to it
1435
      backView.go();
1436
    } else {
1437
      // there is no back view, so close the app instead
1438
      ionic.Platform.exitApp();
1439
    }
1440
    e.preventDefault();
1441
    return false;
1442
  }
1443
  $ionicPlatform.registerBackButtonAction(
1444
    onHardwareBackButton,
1445
    IONIC_BACK_PRIORITY.view
1446
  );
1447
 
1448
}]);
1449
 
1450
/**
1451
 * @ngdoc provider
1452
 * @name $ionicConfigProvider
1453
 * @module ionic
1454
 * @description
1455
 * Ionic automatically takes platform configurations into account to adjust things like what
1456
 * transition style to use and whether tab icons should show on the top or bottom. For example,
1457
 * iOS will move forward by transitioning the entering view from right to center and the leaving
1458
 * view from center to left. However, Android will transition with the entering view going from
1459
 * bottom to center, covering the previous view, which remains stationary. It should be noted
1460
 * that when a platform is not iOS or Android, then it'll default to iOS. So if you are
1461
 * developing on a desktop browser, it's going to take on iOS default configs.
1462
 *
1463
 * These configs can be changed using the `$ionicConfigProvider` during the configuration phase
1464
 * of your app. Additionally, `$ionicConfig` can also set and get config values during the run
1465
 * phase and within the app itself.
1466
 *
1467
 * By default, all base config variables are set to `'platform'`, which means it'll take on the
1468
 * default config of the platform on which it's running. Config variables can be set at this
1469
 * level so all platforms follow the same setting, rather than its platform config.
1470
 * The following code would set the same config variable for all platforms:
1471
 *
1472
 * ```js
1473
 * $ionicConfigProvider.views.maxCache(10);
1474
 * ```
1475
 *
1476
 * Additionally, each platform can have it's own config within the `$ionicConfigProvider.platform`
1477
 * property. The config below would only apply to Android devices.
1478
 *
1479
 * ```js
1480
 * $ionicConfigProvider.platform.android.views.maxCache(5);
1481
 * ```
1482
 *
1483
 * @usage
1484
 * ```js
1485
 * var myApp = angular.module('reallyCoolApp', ['ionic']);
1486
 *
1487
 * myApp.config(function($ionicConfigProvider) {
1488
 *   $ionicConfigProvider.views.maxCache(5);
1489
 *
1490
 *   // note that you can also chain configs
1491
 *   $ionicConfigProvider.backButton.text('Go Back').icon('ion-chevron-left');
1492
 * });
1493
 * ```
1494
 */
1495
 
1496
/**
1497
 * @ngdoc method
1498
 * @name $ionicConfigProvider#views.transition
1499
 * @description Animation style when transitioning between views. Default `platform`.
1500
 *
1501
 * @param {string} transition Which style of view transitioning to use.
1502
 *
1503
 * * `platform`: Dynamically choose the correct transition style depending on the platform
1504
 * the app is running from. If the platform is not `ios` or `android` then it will default
1505
 * to `ios`.
1506
 * * `ios`: iOS style transition.
1507
 * * `android`: Android style transition.
1508
 * * `none`: Do not perform animated transitions.
1509
 *
1510
 * @returns {string} value
1511
 */
1512
 
1513
/**
1514
 * @ngdoc method
1515
 * @name $ionicConfigProvider#views.maxCache
1516
 * @description  Maximum number of view elements to cache in the DOM. When the max number is
1517
 * exceeded, the view with the longest time period since it was accessed is removed. Views that
1518
 * stay in the DOM cache the view's scope, current state, and scroll position. The scope is
1519
 * disconnected from the `$watch` cycle when it is cached and reconnected when it enters again.
1520
 * When the maximum cache is `0`, the leaving view's element will be removed from the DOM after
1521
 * each view transition, and the next time the same view is shown, it will have to re-compile,
1522
 * attach to the DOM, and link the element again. This disables caching, in effect.
1523
 * @param {number} maxNumber Maximum number of views to retain. Default `10`.
1524
 * @returns {number} How many views Ionic will hold onto until the a view is removed.
1525
 */
1526
 
1527
/**
1528
 * @ngdoc method
1529
 * @name $ionicConfigProvider#views.forwardCache
1530
 * @description  By default, when navigating, views that were recently visited are cached, and
1531
 * the same instance data and DOM elements are referenced when navigating back. However, when
1532
 * navigating back in the history, the "forward" views are removed from the cache. If you
1533
 * navigate forward to the same view again, it'll create a new DOM element and controller
1534
 * instance. Basically, any forward views are reset each time. Set this config to `true` to have
1535
 * forward views cached and not reset on each load.
1536
 * @param {boolean} value
1537
 * @returns {boolean}
1538
 */
1539
 
1540
/**
1541
 * @ngdoc method
1542
 * @name $ionicConfigProvider#scrolling.jsScrolling
1543
 * @description  Whether to use JS or Native scrolling. Defaults to JS scrolling. Setting this to
1544
 * `false` has the same effect as setting each `ion-content` to have `overflow-scroll='true'`.
1545
 * @param {boolean} value Defaults to `true`
1546
 * @returns {boolean}
1547
 */
1548
 
1549
/**
1550
 * @ngdoc method
1551
 * @name $ionicConfigProvider#backButton.icon
1552
 * @description Back button icon.
1553
 * @param {string} value
1554
 * @returns {string}
1555
 */
1556
 
1557
/**
1558
 * @ngdoc method
1559
 * @name $ionicConfigProvider#backButton.text
1560
 * @description Back button text.
1561
 * @param {string} value Defaults to `Back`.
1562
 * @returns {string}
1563
 */
1564
 
1565
/**
1566
 * @ngdoc method
1567
 * @name $ionicConfigProvider#backButton.previousTitleText
1568
 * @description If the previous title text should become the back button text. This
1569
 * is the default for iOS.
1570
 * @param {boolean} value
1571
 * @returns {boolean}
1572
 */
1573
 
1574
/**
1575
 * @ngdoc method
1576
 * @name $ionicConfigProvider#form.checkbox
1577
 * @description Checkbox style. Android defaults to `square` and iOS defaults to `circle`.
1578
 * @param {string} value
1579
 * @returns {string}
1580
 */
1581
 
1582
/**
1583
 * @ngdoc method
1584
 * @name $ionicConfigProvider#form.toggle
1585
 * @description Toggle item style. Android defaults to `small` and iOS defaults to `large`.
1586
 * @param {string} value
1587
 * @returns {string}
1588
 */
1589
 
1590
/**
1591
 * @ngdoc method
1592
 * @name $ionicConfigProvider#tabs.style
1593
 * @description Tab style. Android defaults to `striped` and iOS defaults to `standard`.
1594
 * @param {string} value Available values include `striped` and `standard`.
1595
 * @returns {string}
1596
 */
1597
 
1598
/**
1599
 * @ngdoc method
1600
 * @name $ionicConfigProvider#tabs.position
1601
 * @description Tab position. Android defaults to `top` and iOS defaults to `bottom`.
1602
 * @param {string} value Available values include `top` and `bottom`.
1603
 * @returns {string}
1604
 */
1605
 
1606
/**
1607
 * @ngdoc method
1608
 * @name $ionicConfigProvider#templates.maxPrefetch
1609
 * @description Sets the maximum number of templates to prefetch from the templateUrls defined in
1610
 * $stateProvider.state. If set to `0`, the user will have to wait
1611
 * for a template to be fetched the first time when navigating to a new page. Default `30`.
1612
 * @param {integer} value Max number of template to prefetch from the templateUrls defined in
1613
 * `$stateProvider.state()`.
1614
 * @returns {integer}
1615
 */
1616
 
1617
/**
1618
 * @ngdoc method
1619
 * @name $ionicConfigProvider#navBar.alignTitle
1620
 * @description Which side of the navBar to align the title. Default `center`.
1621
 *
1622
 * @param {string} value side of the navBar to align the title.
1623
 *
1624
 * * `platform`: Dynamically choose the correct title style depending on the platform
1625
 * the app is running from. If the platform is `ios`, it will default to `center`.
1626
 * If the platform is `android`, it will default to `left`. If the platform is not
1627
 * `ios` or `android`, it will default to `center`.
1628
 *
1629
 * * `left`: Left align the title in the navBar
1630
 * * `center`: Center align the title in the navBar
1631
 * * `right`: Right align the title in the navBar.
1632
 *
1633
 * @returns {string} value
1634
 */
1635
 
1636
/**
1637
  * @ngdoc method
1638
  * @name $ionicConfigProvider#navBar.positionPrimaryButtons
1639
  * @description Which side of the navBar to align the primary navBar buttons. Default `left`.
1640
  *
1641
  * @param {string} value side of the navBar to align the primary navBar buttons.
1642
  *
1643
  * * `platform`: Dynamically choose the correct title style depending on the platform
1644
  * the app is running from. If the platform is `ios`, it will default to `left`.
1645
  * If the platform is `android`, it will default to `right`. If the platform is not
1646
  * `ios` or `android`, it will default to `left`.
1647
  *
1648
  * * `left`: Left align the primary navBar buttons in the navBar
1649
  * * `right`: Right align the primary navBar buttons in the navBar.
1650
  *
1651
  * @returns {string} value
1652
  */
1653
 
1654
/**
1655
 * @ngdoc method
1656
 * @name $ionicConfigProvider#navBar.positionSecondaryButtons
1657
 * @description Which side of the navBar to align the secondary navBar buttons. Default `right`.
1658
 *
1659
 * @param {string} value side of the navBar to align the secondary navBar buttons.
1660
 *
1661
 * * `platform`: Dynamically choose the correct title style depending on the platform
1662
 * the app is running from. If the platform is `ios`, it will default to `right`.
1663
 * If the platform is `android`, it will default to `right`. If the platform is not
1664
 * `ios` or `android`, it will default to `right`.
1665
 *
1666
 * * `left`: Left align the secondary navBar buttons in the navBar
1667
 * * `right`: Right align the secondary navBar buttons in the navBar.
1668
 *
1669
 * @returns {string} value
1670
 */
1671
 
1672
IonicModule
1673
.provider('$ionicConfig', function() {
1674
 
1675
  var provider = this;
1676
  provider.platform = {};
1677
  var PLATFORM = 'platform';
1678
 
1679
  var configProperties = {
1680
    views: {
1681
      maxCache: PLATFORM,
1682
      forwardCache: PLATFORM,
1683
      transition: PLATFORM,
1684
      swipeBackEnabled: PLATFORM,
1685
      swipeBackHitWidth: PLATFORM
1686
    },
1687
    navBar: {
1688
      alignTitle: PLATFORM,
1689
      positionPrimaryButtons: PLATFORM,
1690
      positionSecondaryButtons: PLATFORM,
1691
      transition: PLATFORM
1692
    },
1693
    backButton: {
1694
      icon: PLATFORM,
1695
      text: PLATFORM,
1696
      previousTitleText: PLATFORM
1697
    },
1698
    form: {
1699
      checkbox: PLATFORM,
1700
      toggle: PLATFORM
1701
    },
1702
    scrolling: {
1703
      jsScrolling: PLATFORM
1704
    },
1705
    tabs: {
1706
      style: PLATFORM,
1707
      position: PLATFORM
1708
    },
1709
    templates: {
1710
      maxPrefetch: PLATFORM
1711
    },
1712
    platform: {}
1713
  };
1714
  createConfig(configProperties, provider, '');
1715
 
1716
 
1717
 
1718
  // Default
1719
  // -------------------------
1720
  setPlatformConfig('default', {
1721
 
1722
    views: {
1723
      maxCache: 10,
1724
      forwardCache: false,
1725
      transition: 'ios',
1726
      swipeBackEnabled: true,
1727
      swipeBackHitWidth: 45
1728
    },
1729
 
1730
    navBar: {
1731
      alignTitle: 'center',
1732
      positionPrimaryButtons: 'left',
1733
      positionSecondaryButtons: 'right',
1734
      transition: 'view'
1735
    },
1736
 
1737
    backButton: {
1738
      icon: 'ion-ios-arrow-back',
1739
      text: 'Back',
1740
      previousTitleText: true
1741
    },
1742
 
1743
    form: {
1744
      checkbox: 'circle',
1745
      toggle: 'large'
1746
    },
1747
 
1748
    scrolling: {
1749
      jsScrolling: true
1750
    },
1751
 
1752
    tabs: {
1753
      style: 'standard',
1754
      position: 'bottom'
1755
    },
1756
 
1757
    templates: {
1758
      maxPrefetch: 30
1759
    }
1760
 
1761
  });
1762
 
1763
 
1764
 
1765
  // iOS (it is the default already)
1766
  // -------------------------
1767
  setPlatformConfig('ios', {});
1768
 
1769
 
1770
 
1771
  // Android
1772
  // -------------------------
1773
  setPlatformConfig('android', {
1774
 
1775
    views: {
1776
      transition: 'android',
1777
      swipeBackEnabled: false
1778
    },
1779
 
1780
    navBar: {
1781
      alignTitle: 'left',
1782
      positionPrimaryButtons: 'right',
1783
      positionSecondaryButtons: 'right'
1784
    },
1785
 
1786
    backButton: {
1787
      icon: 'ion-android-arrow-back',
1788
      text: false,
1789
      previousTitleText: false
1790
    },
1791
 
1792
    form: {
1793
      checkbox: 'square',
1794
      toggle: 'small'
1795
    },
1796
 
1797
    tabs: {
1798
      style: 'striped',
1799
      position: 'top'
1800
    }
1801
 
1802
  });
1803
 
1804
  // Windows Phone
1805
  // -------------------------
1806
  setPlatformConfig('windowsphone', {
1807
    //scrolling: {
1808
    //  jsScrolling: false
1809
    //}
1810
  });
1811
 
1812
 
1813
  provider.transitions = {
1814
    views: {},
1815
    navBar: {}
1816
  };
1817
 
1818
 
1819
  // iOS Transitions
1820
  // -----------------------
1821
  provider.transitions.views.ios = function(enteringEle, leavingEle, direction, shouldAnimate) {
1822
 
1823
    function setStyles(ele, opacity, x, boxShadowOpacity) {
1824
      var css = {};
1825
      css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : 0;
1826
      css.opacity = opacity;
1827
      if (boxShadowOpacity > -1) {
1828
        css.boxShadow = '0 0 10px rgba(0,0,0,' + (d.shouldAnimate ? boxShadowOpacity * 0.45 : 0.3) + ')';
1829
      }
1830
      css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';
1831
      ionic.DomUtil.cachedStyles(ele, css);
1832
    }
1833
 
1834
    var d = {
1835
      run: function(step) {
1836
        if (direction == 'forward') {
1837
          setStyles(enteringEle, 1, (1 - step) * 99, 1 - step); // starting at 98% prevents a flicker
1838
          setStyles(leavingEle, (1 - 0.1 * step), step * -33, -1);
1839
 
1840
        } else if (direction == 'back') {
1841
          setStyles(enteringEle, (1 - 0.1 * (1 - step)), (1 - step) * -33, -1);
1842
          setStyles(leavingEle, 1, step * 100, 1 - step);
1843
 
1844
        } else {
1845
          // swap, enter, exit
1846
          setStyles(enteringEle, 1, 0, -1);
1847
          setStyles(leavingEle, 0, 0, -1);
1848
        }
1849
      },
1850
      shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
1851
    };
1852
 
1853
    return d;
1854
  };
1855
 
1856
  provider.transitions.navBar.ios = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {
1857
 
1858
    function setStyles(ctrl, opacity, titleX, backTextX) {
1859
      var css = {};
1860
      css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : '0ms';
1861
      css.opacity = opacity === 1 ? '' : opacity;
1862
 
1863
      ctrl.setCss('buttons-left', css);
1864
      ctrl.setCss('buttons-right', css);
1865
      ctrl.setCss('back-button', css);
1866
 
1867
      css[ionic.CSS.TRANSFORM] = 'translate3d(' + backTextX + 'px,0,0)';
1868
      ctrl.setCss('back-text', css);
1869
 
1870
      css[ionic.CSS.TRANSFORM] = 'translate3d(' + titleX + 'px,0,0)';
1871
      ctrl.setCss('title', css);
1872
    }
1873
 
1874
    function enter(ctrlA, ctrlB, step) {
1875
      if (!ctrlA || !ctrlB) return;
1876
      var titleX = (ctrlA.titleTextX() + ctrlA.titleWidth()) * (1 - step);
1877
      var backTextX = (ctrlB && (ctrlB.titleTextX() - ctrlA.backButtonTextLeft()) * (1 - step)) || 0;
1878
      setStyles(ctrlA, step, titleX, backTextX);
1879
    }
1880
 
1881
    function leave(ctrlA, ctrlB, step) {
1882
      if (!ctrlA || !ctrlB) return;
1883
      var titleX = (-(ctrlA.titleTextX() - ctrlB.backButtonTextLeft()) - (ctrlA.titleLeftRight())) * step;
1884
      setStyles(ctrlA, 1 - step, titleX, 0);
1885
    }
1886
 
1887
    var d = {
1888
      run: function(step) {
1889
        var enteringHeaderCtrl = enteringHeaderBar.controller();
1890
        var leavingHeaderCtrl = leavingHeaderBar && leavingHeaderBar.controller();
1891
        if (d.direction == 'back') {
1892
          leave(enteringHeaderCtrl, leavingHeaderCtrl, 1 - step);
1893
          enter(leavingHeaderCtrl, enteringHeaderCtrl, 1 - step);
1894
        } else {
1895
          enter(enteringHeaderCtrl, leavingHeaderCtrl, step);
1896
          leave(leavingHeaderCtrl, enteringHeaderCtrl, step);
1897
        }
1898
      },
1899
      direction: direction,
1900
      shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
1901
    };
1902
 
1903
    return d;
1904
  };
1905
 
1906
 
1907
  // Android Transitions
1908
  // -----------------------
1909
 
1910
  provider.transitions.views.android = function(enteringEle, leavingEle, direction, shouldAnimate) {
1911
    shouldAnimate = shouldAnimate && (direction == 'forward' || direction == 'back');
1912
 
1913
    function setStyles(ele, x) {
1914
      var css = {};
1915
      css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : 0;
1916
      css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';
1917
      ionic.DomUtil.cachedStyles(ele, css);
1918
    }
1919
 
1920
    var d = {
1921
      run: function(step) {
1922
        if (direction == 'forward') {
1923
          setStyles(enteringEle, (1 - step) * 99); // starting at 98% prevents a flicker
1924
          setStyles(leavingEle, step * -100);
1925
 
1926
        } else if (direction == 'back') {
1927
          setStyles(enteringEle, (1 - step) * -100);
1928
          setStyles(leavingEle, step * 100);
1929
 
1930
        } else {
1931
          // swap, enter, exit
1932
          setStyles(enteringEle, 0);
1933
          setStyles(leavingEle, 0);
1934
        }
1935
      },
1936
      shouldAnimate: shouldAnimate
1937
    };
1938
 
1939
    return d;
1940
  };
1941
 
1942
  provider.transitions.navBar.android = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {
1943
 
1944
    function setStyles(ctrl, opacity) {
1945
      if (!ctrl) return;
1946
      var css = {};
1947
      css.opacity = opacity === 1 ? '' : opacity;
1948
 
1949
      ctrl.setCss('buttons-left', css);
1950
      ctrl.setCss('buttons-right', css);
1951
      ctrl.setCss('back-button', css);
1952
      ctrl.setCss('back-text', css);
1953
      ctrl.setCss('title', css);
1954
    }
1955
 
1956
    return {
1957
      run: function(step) {
1958
        setStyles(enteringHeaderBar.controller(), step);
1959
        setStyles(leavingHeaderBar && leavingHeaderBar.controller(), 1 - step);
1960
      },
1961
      shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
1962
    };
1963
  };
1964
 
1965
 
1966
  // No Transition
1967
  // -----------------------
1968
 
1969
  provider.transitions.views.none = function(enteringEle, leavingEle) {
1970
    return {
1971
      run: function(step) {
1972
        provider.transitions.views.android(enteringEle, leavingEle, false, false).run(step);
1973
      },
1974
      shouldAnimate: false
1975
    };
1976
  };
1977
 
1978
  provider.transitions.navBar.none = function(enteringHeaderBar, leavingHeaderBar) {
1979
    return {
1980
      run: function(step) {
1981
        provider.transitions.navBar.ios(enteringHeaderBar, leavingHeaderBar, false, false).run(step);
1982
        provider.transitions.navBar.android(enteringHeaderBar, leavingHeaderBar, false, false).run(step);
1983
      },
1984
      shouldAnimate: false
1985
    };
1986
  };
1987
 
1988
 
1989
  // private: used to set platform configs
1990
  function setPlatformConfig(platformName, platformConfigs) {
1991
    configProperties.platform[platformName] = platformConfigs;
1992
    provider.platform[platformName] = {};
1993
 
1994
    addConfig(configProperties, configProperties.platform[platformName]);
1995
 
1996
    createConfig(configProperties.platform[platformName], provider.platform[platformName], '');
1997
  }
1998
 
1999
 
2000
  // private: used to recursively add new platform configs
2001
  function addConfig(configObj, platformObj) {
2002
    for (var n in configObj) {
2003
      if (n != PLATFORM && configObj.hasOwnProperty(n)) {
2004
        if (angular.isObject(configObj[n])) {
2005
          if (!isDefined(platformObj[n])) {
2006
            platformObj[n] = {};
2007
          }
2008
          addConfig(configObj[n], platformObj[n]);
2009
 
2010
        } else if (!isDefined(platformObj[n])) {
2011
          platformObj[n] = null;
2012
        }
2013
      }
2014
    }
2015
  }
2016
 
2017
 
2018
  // private: create methods for each config to get/set
2019
  function createConfig(configObj, providerObj, platformPath) {
2020
    forEach(configObj, function(value, namespace) {
2021
 
2022
      if (angular.isObject(configObj[namespace])) {
2023
        // recursively drill down the config object so we can create a method for each one
2024
        providerObj[namespace] = {};
2025
        createConfig(configObj[namespace], providerObj[namespace], platformPath + '.' + namespace);
2026
 
2027
      } else {
2028
        // create a method for the provider/config methods that will be exposed
2029
        providerObj[namespace] = function(newValue) {
2030
          if (arguments.length) {
2031
            configObj[namespace] = newValue;
2032
            return providerObj;
2033
          }
2034
          if (configObj[namespace] == PLATFORM) {
2035
            // if the config is set to 'platform', then get this config's platform value
2036
            var platformConfig = stringObj(configProperties.platform, ionic.Platform.platform() + platformPath + '.' + namespace);
2037
            if (platformConfig || platformConfig === false) {
2038
              return platformConfig;
2039
            }
2040
            // didnt find a specific platform config, now try the default
2041
            return stringObj(configProperties.platform, 'default' + platformPath + '.' + namespace);
2042
          }
2043
          return configObj[namespace];
2044
        };
2045
      }
2046
 
2047
    });
2048
  }
2049
 
2050
  function stringObj(obj, str) {
2051
    str = str.split(".");
2052
    for (var i = 0; i < str.length; i++) {
2053
      if (obj && isDefined(obj[str[i]])) {
2054
        obj = obj[str[i]];
2055
      } else {
2056
        return null;
2057
      }
2058
    }
2059
    return obj;
2060
  }
2061
 
2062
  provider.setPlatformConfig = setPlatformConfig;
2063
 
2064
 
2065
  // private: Service definition for internal Ionic use
2066
  /**
2067
   * @ngdoc service
2068
   * @name $ionicConfig
2069
   * @module ionic
2070
   * @private
2071
   */
2072
  provider.$get = function() {
2073
    return provider;
2074
  };
2075
})
2076
// Fix for URLs in Cordova apps on Windows Phone
2077
// http://blogs.msdn.com/b/msdn_answers/archive/2015/02/10/
2078
// running-cordova-apps-on-windows-and-windows-phone-8-1-using-ionic-angularjs-and-other-frameworks.aspx
2079
.config(['$compileProvider', function($compileProvider) {
2080
  $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|tel|ftp|mailto|file|ghttps?|ms-appx|x-wmapp0):/);
2081
  $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|tel|ftp|file|blob|ms-appx|x-wmapp0):|data:image\//);
2082
}]);
2083
 
2084
 
2085
var LOADING_TPL =
2086
  '<div class="loading-container">' +
2087
    '<div class="loading">' +
2088
    '</div>' +
2089
  '</div>';
2090
 
2091
var LOADING_HIDE_DEPRECATED = '$ionicLoading instance.hide() has been deprecated. Use $ionicLoading.hide().';
2092
var LOADING_SHOW_DEPRECATED = '$ionicLoading instance.show() has been deprecated. Use $ionicLoading.show().';
2093
var LOADING_SET_DEPRECATED = '$ionicLoading instance.setContent() has been deprecated. Use $ionicLoading.show({ template: \'my content\' }).';
2094
 
2095
/**
2096
 * @ngdoc service
2097
 * @name $ionicLoading
2098
 * @module ionic
2099
 * @description
2100
 * An overlay that can be used to indicate activity while blocking user
2101
 * interaction.
2102
 *
2103
 * @usage
2104
 * ```js
2105
 * angular.module('LoadingApp', ['ionic'])
2106
 * .controller('LoadingCtrl', function($scope, $ionicLoading) {
2107
 *   $scope.show = function() {
2108
 *     $ionicLoading.show({
2109
 *       template: 'Loading...'
2110
 *     });
2111
 *   };
2112
 *   $scope.hide = function(){
2113
 *     $ionicLoading.hide();
2114
 *   };
2115
 * });
2116
 * ```
2117
 */
2118
/**
2119
 * @ngdoc object
2120
 * @name $ionicLoadingConfig
2121
 * @module ionic
2122
 * @description
2123
 * Set the default options to be passed to the {@link ionic.service:$ionicLoading} service.
2124
 *
2125
 * @usage
2126
 * ```js
2127
 * var app = angular.module('myApp', ['ionic'])
2128
 * app.constant('$ionicLoadingConfig', {
2129
 *   template: 'Default Loading Template...'
2130
 * });
2131
 * app.controller('AppCtrl', function($scope, $ionicLoading) {
2132
 *   $scope.showLoading = function() {
2133
 *     $ionicLoading.show(); //options default to values in $ionicLoadingConfig
2134
 *   };
2135
 * });
2136
 * ```
2137
 */
2138
IonicModule
2139
.constant('$ionicLoadingConfig', {
2140
  template: '<ion-spinner></ion-spinner>'
2141
})
2142
.factory('$ionicLoading', [
2143
  '$ionicLoadingConfig',
2144
  '$ionicBody',
2145
  '$ionicTemplateLoader',
2146
  '$ionicBackdrop',
2147
  '$timeout',
2148
  '$q',
2149
  '$log',
2150
  '$compile',
2151
  '$ionicPlatform',
2152
  '$rootScope',
2153
  'IONIC_BACK_PRIORITY',
2154
function($ionicLoadingConfig, $ionicBody, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $compile, $ionicPlatform, $rootScope, IONIC_BACK_PRIORITY) {
2155
 
2156
  var loaderInstance;
2157
  //default values
2158
  var deregisterBackAction = noop;
2159
  var deregisterStateListener1 = noop;
2160
  var deregisterStateListener2 = noop;
2161
  var loadingShowDelay = $q.when();
2162
 
2163
  return {
2164
    /**
2165
     * @ngdoc method
2166
     * @name $ionicLoading#show
2167
     * @description Shows a loading indicator. If the indicator is already shown,
2168
     * it will set the options given and keep the indicator shown.
2169
     * @param {object} opts The options for the loading indicator. Available properties:
2170
     *  - `{string=}` `template` The html content of the indicator.
2171
     *  - `{string=}` `templateUrl` The url of an html template to load as the content of the indicator.
2172
     *  - `{object=}` `scope` The scope to be a child of. Default: creates a child of $rootScope.
2173
     *  - `{boolean=}` `noBackdrop` Whether to hide the backdrop. By default it will be shown.
2174
     *  - `{boolean=}` `hideOnStateChange` Whether to hide the loading spinner when navigating
2175
     *    to a new state. Default false.
2176
     *  - `{number=}` `delay` How many milliseconds to delay showing the indicator. By default there is no delay.
2177
     *  - `{number=}` `duration` How many milliseconds to wait until automatically
2178
     *  hiding the indicator. By default, the indicator will be shown until `.hide()` is called.
2179
     */
2180
    show: showLoader,
2181
    /**
2182
     * @ngdoc method
2183
     * @name $ionicLoading#hide
2184
     * @description Hides the loading indicator, if shown.
2185
     */
2186
    hide: hideLoader,
2187
    /**
2188
     * @private for testing
2189
     */
2190
    _getLoader: getLoader
2191
  };
2192
 
2193
  function getLoader() {
2194
    if (!loaderInstance) {
2195
      loaderInstance = $ionicTemplateLoader.compile({
2196
        template: LOADING_TPL,
2197
        appendTo: $ionicBody.get()
2198
      })
2199
      .then(function(self) {
2200
        self.show = function(options) {
2201
          var templatePromise = options.templateUrl ?
2202
            $ionicTemplateLoader.load(options.templateUrl) :
2203
            //options.content: deprecated
2204
            $q.when(options.template || options.content || '');
2205
 
2206
          self.scope = options.scope || self.scope;
2207
 
2208
          if (!self.isShown) {
2209
            //options.showBackdrop: deprecated
2210
            self.hasBackdrop = !options.noBackdrop && options.showBackdrop !== false;
2211
            if (self.hasBackdrop) {
2212
              $ionicBackdrop.retain();
2213
              $ionicBackdrop.getElement().addClass('backdrop-loading');
2214
            }
2215
          }
2216
 
2217
          if (options.duration) {
2218
            $timeout.cancel(self.durationTimeout);
2219
            self.durationTimeout = $timeout(
2220
              angular.bind(self, self.hide),
2221
              +options.duration
2222
            );
2223
          }
2224
 
2225
          deregisterBackAction();
2226
          //Disable hardware back button while loading
2227
          deregisterBackAction = $ionicPlatform.registerBackButtonAction(
2228
            noop,
2229
            IONIC_BACK_PRIORITY.loading
2230
          );
2231
 
2232
          templatePromise.then(function(html) {
2233
            if (html) {
2234
              var loading = self.element.children();
2235
              loading.html(html);
2236
              $compile(loading.contents())(self.scope);
2237
            }
2238
 
2239
            //Don't show until template changes
2240
            if (self.isShown) {
2241
              self.element.addClass('visible');
2242
              ionic.requestAnimationFrame(function() {
2243
                if (self.isShown) {
2244
                  self.element.addClass('active');
2245
                  $ionicBody.addClass('loading-active');
2246
                }
2247
              });
2248
            }
2249
          });
2250
 
2251
          self.isShown = true;
2252
        };
2253
        self.hide = function() {
2254
 
2255
          deregisterBackAction();
2256
          if (self.isShown) {
2257
            if (self.hasBackdrop) {
2258
              $ionicBackdrop.release();
2259
              $ionicBackdrop.getElement().removeClass('backdrop-loading');
2260
            }
2261
            self.element.removeClass('active');
2262
            $ionicBody.removeClass('loading-active');
2263
            setTimeout(function() {
2264
              !self.isShown && self.element.removeClass('visible');
2265
            }, 200);
2266
          }
2267
          $timeout.cancel(self.durationTimeout);
2268
          self.isShown = false;
2269
        };
2270
 
2271
        return self;
2272
      });
2273
    }
2274
    return loaderInstance;
2275
  }
2276
 
2277
  function showLoader(options) {
2278
    options = extend({}, $ionicLoadingConfig || {}, options || {});
2279
    var delay = options.delay || options.showDelay || 0;
2280
 
2281
    deregisterStateListener1();
2282
    deregisterStateListener2();
2283
    if (options.hideOnStateChange) {
2284
      deregisterStateListener1 = $rootScope.$on('$stateChangeSuccess', hideLoader);
2285
      deregisterStateListener2 = $rootScope.$on('$stateChangeError', hideLoader);
2286
    }
2287
 
2288
    //If loading.show() was called previously, cancel it and show with our new options
2289
    $timeout.cancel(loadingShowDelay);
2290
    loadingShowDelay = $timeout(noop, delay);
2291
    loadingShowDelay.then(getLoader).then(function(loader) {
2292
      return loader.show(options);
2293
    });
2294
 
2295
    return {
2296
      hide: function deprecatedHide() {
2297
        $log.error(LOADING_HIDE_DEPRECATED);
2298
        return hideLoader.apply(this, arguments);
2299
      },
2300
      show: function deprecatedShow() {
2301
        $log.error(LOADING_SHOW_DEPRECATED);
2302
        return showLoader.apply(this, arguments);
2303
      },
2304
      setContent: function deprecatedSetContent(content) {
2305
        $log.error(LOADING_SET_DEPRECATED);
2306
        return getLoader().then(function(loader) {
2307
          loader.show({ template: content });
2308
        });
2309
      }
2310
    };
2311
  }
2312
 
2313
  function hideLoader() {
2314
    deregisterStateListener1();
2315
    deregisterStateListener2();
2316
    $timeout.cancel(loadingShowDelay);
2317
    getLoader().then(function(loader) {
2318
      loader.hide();
2319
    });
2320
  }
2321
}]);
2322
 
2323
/**
2324
 * @ngdoc service
2325
 * @name $ionicModal
2326
 * @module ionic
2327
 * @description
2328
 *
2329
 * Related: {@link ionic.controller:ionicModal ionicModal controller}.
2330
 *
2331
 * The Modal is a content pane that can go over the user's main view
2332
 * temporarily.  Usually used for making a choice or editing an item.
2333
 *
2334
 * Put the content of the modal inside of an `<ion-modal-view>` element.
2335
 *
2336
 * **Notes:**
2337
 * - A modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating
2338
 * scope, passing in itself as an event argument. Both the modal.removed and modal.hidden events are
2339
 * called when the modal is removed.
2340
 *
2341
 * - This example assumes your modal is in your main index file or another template file. If it is in its own
2342
 * template file, remove the script tags and call it by file name.
2343
 *
2344
 * @usage
2345
 * ```html
2346
 * <script id="my-modal.html" type="text/ng-template">
2347
 *   <ion-modal-view>
2348
 *     <ion-header-bar>
2349
 *       <h1 class="title">My Modal title</h1>
2350
 *     </ion-header-bar>
2351
 *     <ion-content>
2352
 *       Hello!
2353
 *     </ion-content>
2354
 *   </ion-modal-view>
2355
 * </script>
2356
 * ```
2357
 * ```js
2358
 * angular.module('testApp', ['ionic'])
2359
 * .controller('MyController', function($scope, $ionicModal) {
2360
 *   $ionicModal.fromTemplateUrl('my-modal.html', {
2361
 *     scope: $scope,
2362
 *     animation: 'slide-in-up'
2363
 *   }).then(function(modal) {
2364
 *     $scope.modal = modal;
2365
 *   });
2366
 *   $scope.openModal = function() {
2367
 *     $scope.modal.show();
2368
 *   };
2369
 *   $scope.closeModal = function() {
2370
 *     $scope.modal.hide();
2371
 *   };
2372
 *   //Cleanup the modal when we're done with it!
2373
 *   $scope.$on('$destroy', function() {
2374
 *     $scope.modal.remove();
2375
 *   });
2376
 *   // Execute action on hide modal
2377
 *   $scope.$on('modal.hidden', function() {
2378
 *     // Execute action
2379
 *   });
2380
 *   // Execute action on remove modal
2381
 *   $scope.$on('modal.removed', function() {
2382
 *     // Execute action
2383
 *   });
2384
 * });
2385
 * ```
2386
 */
2387
IonicModule
2388
.factory('$ionicModal', [
2389
  '$rootScope',
2390
  '$ionicBody',
2391
  '$compile',
2392
  '$timeout',
2393
  '$ionicPlatform',
2394
  '$ionicTemplateLoader',
2395
  '$$q',
2396
  '$log',
2397
  '$ionicClickBlock',
2398
  '$window',
2399
  'IONIC_BACK_PRIORITY',
2400
function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $$q, $log, $ionicClickBlock, $window, IONIC_BACK_PRIORITY) {
2401
 
2402
  /**
2403
   * @ngdoc controller
2404
   * @name ionicModal
2405
   * @module ionic
2406
   * @description
2407
   * Instantiated by the {@link ionic.service:$ionicModal} service.
2408
   *
2409
   * Be sure to call [remove()](#remove) when you are done with each modal
2410
   * to clean it up and avoid memory leaks.
2411
   *
2412
   * Note: a modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating
2413
   * scope, passing in itself as an event argument. Note: both modal.removed and modal.hidden are
2414
   * called when the modal is removed.
2415
   */
2416
  var ModalView = ionic.views.Modal.inherit({
2417
    /**
2418
     * @ngdoc method
2419
     * @name ionicModal#initialize
2420
     * @description Creates a new modal controller instance.
2421
     * @param {object} options An options object with the following properties:
2422
     *  - `{object=}` `scope` The scope to be a child of.
2423
     *    Default: creates a child of $rootScope.
2424
     *  - `{string=}` `animation` The animation to show & hide with.
2425
     *    Default: 'slide-in-up'
2426
     *  - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of
2427
     *    the modal when shown. Will only show the keyboard on iOS, to force the keyboard to show
2428
     *    on Android, please use the [Ionic keyboard plugin](https://github.com/driftyco/ionic-plugin-keyboard#keyboardshow).
2429
     *    Default: false.
2430
     *  - `{boolean=}` `backdropClickToClose` Whether to close the modal on clicking the backdrop.
2431
     *    Default: true.
2432
     *  - `{boolean=}` `hardwareBackButtonClose` Whether the modal can be closed using the hardware
2433
     *    back button on Android and similar devices.  Default: true.
2434
     */
2435
    initialize: function(opts) {
2436
      ionic.views.Modal.prototype.initialize.call(this, opts);
2437
      this.animation = opts.animation || 'slide-in-up';
2438
    },
2439
 
2440
    /**
2441
     * @ngdoc method
2442
     * @name ionicModal#show
2443
     * @description Show this modal instance.
2444
     * @returns {promise} A promise which is resolved when the modal is finished animating in.
2445
     */
2446
    show: function(target) {
2447
      var self = this;
2448
 
2449
      if (self.scope.$$destroyed) {
2450
        $log.error('Cannot call ' + self.viewType + '.show() after remove(). Please create a new ' + self.viewType + ' instance.');
2451
        return $$q.when();
2452
      }
2453
 
2454
      // on iOS, clicks will sometimes bleed through/ghost click on underlying
2455
      // elements
2456
      $ionicClickBlock.show(600);
2457
 
2458
      var modalEl = jqLite(self.modalEl);
2459
 
2460
      self.el.classList.remove('hide');
2461
      $timeout(function() {
2462
        if (!self._isShown) return;
2463
        $ionicBody.addClass(self.viewType + '-open');
2464
      }, 400, false);
2465
 
2466
      if (!self.el.parentElement) {
2467
        modalEl.addClass(self.animation);
2468
        $ionicBody.append(self.el);
2469
      }
2470
 
2471
      // if modal was closed while the keyboard was up, reset scroll view on
2472
      // next show since we can only resize it once it's visible
2473
      var scrollCtrl = modalEl.data('$$ionicScrollController');
2474
      scrollCtrl && scrollCtrl.resize();
2475
 
2476
      if (target && self.positionView) {
2477
        self.positionView(target, modalEl);
2478
        // set up a listener for in case the window size changes
2479
 
2480
        self._onWindowResize = function() {
2481
          if (self._isShown) self.positionView(target, modalEl);
2482
        };
2483
        ionic.on('resize', self._onWindowResize, window);
2484
      }
2485
 
2486
      modalEl.addClass('ng-enter active')
2487
             .removeClass('ng-leave ng-leave-active');
2488
 
2489
      self._isShown = true;
2490
      self._deregisterBackButton = $ionicPlatform.registerBackButtonAction(
2491
        self.hardwareBackButtonClose ? angular.bind(self, self.hide) : noop,
2492
        IONIC_BACK_PRIORITY.modal
2493
      );
2494
 
2495
      ionic.views.Modal.prototype.show.call(self);
2496
 
2497
      $timeout(function() {
2498
        if (!self._isShown) return;
2499
        modalEl.addClass('ng-enter-active');
2500
        ionic.trigger('resize');
2501
        self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.shown', self);
2502
        self.el.classList.add('active');
2503
        self.scope.$broadcast('$ionicHeader.align');
2504
      }, 20);
2505
 
2506
      return $timeout(function() {
2507
        if (!self._isShown) return;
2508
        //After animating in, allow hide on backdrop click
2509
        self.$el.on('click', function(e) {
2510
          if (self.backdropClickToClose && e.target === self.el) {
2511
            self.hide();
2512
          }
2513
        });
2514
      }, 400);
2515
    },
2516
 
2517
    /**
2518
     * @ngdoc method
2519
     * @name ionicModal#hide
2520
     * @description Hide this modal instance.
2521
     * @returns {promise} A promise which is resolved when the modal is finished animating out.
2522
     */
2523
    hide: function() {
2524
      var self = this;
2525
      var modalEl = jqLite(self.modalEl);
2526
 
2527
      // on iOS, clicks will sometimes bleed through/ghost click on underlying
2528
      // elements
2529
      $ionicClickBlock.show(600);
2530
 
2531
      self.el.classList.remove('active');
2532
      modalEl.addClass('ng-leave');
2533
 
2534
      $timeout(function() {
2535
        if (self._isShown) return;
2536
        modalEl.addClass('ng-leave-active')
2537
               .removeClass('ng-enter ng-enter-active active');
2538
      }, 20, false);
2539
 
2540
      self.$el.off('click');
2541
      self._isShown = false;
2542
      self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.hidden', self);
2543
      self._deregisterBackButton && self._deregisterBackButton();
2544
 
2545
      ionic.views.Modal.prototype.hide.call(self);
2546
 
2547
      // clean up event listeners
2548
      if (self.positionView) {
2549
        ionic.off('resize', self._onWindowResize, window);
2550
      }
2551
 
2552
      return $timeout(function() {
2553
        $ionicBody.removeClass(self.viewType + '-open');
2554
        self.el.classList.add('hide');
2555
      }, self.hideDelay || 320);
2556
    },
2557
 
2558
    /**
2559
     * @ngdoc method
2560
     * @name ionicModal#remove
2561
     * @description Remove this modal instance from the DOM and clean up.
2562
     * @returns {promise} A promise which is resolved when the modal is finished animating out.
2563
     */
2564
    remove: function() {
2565
      var self = this;
2566
      self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.removed', self);
2567
 
2568
      return self.hide().then(function() {
2569
        self.scope.$destroy();
2570
        self.$el.remove();
2571
      });
2572
    },
2573
 
2574
    /**
2575
     * @ngdoc method
2576
     * @name ionicModal#isShown
2577
     * @returns boolean Whether this modal is currently shown.
2578
     */
2579
    isShown: function() {
2580
      return !!this._isShown;
2581
    }
2582
  });
2583
 
2584
  var createModal = function(templateString, options) {
2585
    // Create a new scope for the modal
2586
    var scope = options.scope && options.scope.$new() || $rootScope.$new(true);
2587
 
2588
    options.viewType = options.viewType || 'modal';
2589
 
2590
    extend(scope, {
2591
      $hasHeader: false,
2592
      $hasSubheader: false,
2593
      $hasFooter: false,
2594
      $hasSubfooter: false,
2595
      $hasTabs: false,
2596
      $hasTabsTop: false
2597
    });
2598
 
2599
    // Compile the template
2600
    var element = $compile('<ion-' + options.viewType + '>' + templateString + '</ion-' + options.viewType + '>')(scope);
2601
 
2602
    options.$el = element;
2603
    options.el = element[0];
2604
    options.modalEl = options.el.querySelector('.' + options.viewType);
2605
    var modal = new ModalView(options);
2606
 
2607
    modal.scope = scope;
2608
 
2609
    // If this wasn't a defined scope, we can assign the viewType to the isolated scope
2610
    // we created
2611
    if (!options.scope) {
2612
      scope[ options.viewType ] = modal;
2613
    }
2614
 
2615
    return modal;
2616
  };
2617
 
2618
  return {
2619
    /**
2620
     * @ngdoc method
2621
     * @name $ionicModal#fromTemplate
2622
     * @param {string} templateString The template string to use as the modal's
2623
     * content.
2624
     * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
2625
     * @returns {object} An instance of an {@link ionic.controller:ionicModal}
2626
     * controller.
2627
     */
2628
    fromTemplate: function(templateString, options) {
2629
      var modal = createModal(templateString, options || {});
2630
      return modal;
2631
    },
2632
    /**
2633
     * @ngdoc method
2634
     * @name $ionicModal#fromTemplateUrl
2635
     * @param {string} templateUrl The url to load the template from.
2636
     * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
2637
     * options object.
2638
     * @returns {promise} A promise that will be resolved with an instance of
2639
     * an {@link ionic.controller:ionicModal} controller.
2640
     */
2641
    fromTemplateUrl: function(url, options, _) {
2642
      var cb;
2643
      //Deprecated: allow a callback as second parameter. Now we return a promise.
2644
      if (angular.isFunction(options)) {
2645
        cb = options;
2646
        options = _;
2647
      }
2648
      return $ionicTemplateLoader.load(url).then(function(templateString) {
2649
        var modal = createModal(templateString, options || {});
2650
        cb && cb(modal);
2651
        return modal;
2652
      });
2653
    }
2654
  };
2655
}]);
2656
 
2657
 
2658
/**
2659
 * @ngdoc service
2660
 * @name $ionicNavBarDelegate
2661
 * @module ionic
2662
 * @description
2663
 * Delegate for controlling the {@link ionic.directive:ionNavBar} directive.
2664
 *
2665
 * @usage
2666
 *
2667
 * ```html
2668
 * <body ng-controller="MyCtrl">
2669
 *   <ion-nav-bar>
2670
 *     <button ng-click="setNavTitle('banana')">
2671
 *       Set title to banana!
2672
 *     </button>
2673
 *   </ion-nav-bar>
2674
 * </body>
2675
 * ```
2676
 * ```js
2677
 * function MyCtrl($scope, $ionicNavBarDelegate) {
2678
 *   $scope.setNavTitle = function(title) {
2679
 *     $ionicNavBarDelegate.title(title);
2680
 *   }
2681
 * }
2682
 * ```
2683
 */
2684
IonicModule
2685
.service('$ionicNavBarDelegate', ionic.DelegateService([
2686
  /**
2687
   * @ngdoc method
2688
   * @name $ionicNavBarDelegate#align
2689
   * @description Aligns the title with the buttons in a given direction.
2690
   * @param {string=} direction The direction to the align the title text towards.
2691
   * Available: 'left', 'right', 'center'. Default: 'center'.
2692
   */
2693
  'align',
2694
  /**
2695
   * @ngdoc method
2696
   * @name $ionicNavBarDelegate#showBackButton
2697
   * @description
2698
   * Set/get whether the {@link ionic.directive:ionNavBackButton} is shown
2699
   * (if it exists and there is a previous view that can be navigated to).
2700
   * @param {boolean=} show Whether to show the back button.
2701
   * @returns {boolean} Whether the back button is shown.
2702
   */
2703
  'showBackButton',
2704
  /**
2705
   * @ngdoc method
2706
   * @name $ionicNavBarDelegate#showBar
2707
   * @description
2708
   * Set/get whether the {@link ionic.directive:ionNavBar} is shown.
2709
   * @param {boolean} show Whether to show the bar.
2710
   * @returns {boolean} Whether the bar is shown.
2711
   */
2712
  'showBar',
2713
  /**
2714
   * @ngdoc method
2715
   * @name $ionicNavBarDelegate#title
2716
   * @description
2717
   * Set the title for the {@link ionic.directive:ionNavBar}.
2718
   * @param {string} title The new title to show.
2719
   */
2720
  'title',
2721
 
2722
  // DEPRECATED, as of v1.0.0-beta14 -------
2723
  'changeTitle',
2724
  'setTitle',
2725
  'getTitle',
2726
  'back',
2727
  'getPreviousTitle'
2728
  // END DEPRECATED -------
2729
]));
2730
 
2731
 
2732
IonicModule
2733
.service('$ionicNavViewDelegate', ionic.DelegateService([
2734
  'clearCache'
2735
]));
2736
 
2737
 
2738
 
2739
/**
2740
 * @ngdoc service
2741
 * @name $ionicPlatform
2742
 * @module ionic
2743
 * @description
2744
 * An angular abstraction of {@link ionic.utility:ionic.Platform}.
2745
 *
2746
 * Used to detect the current platform, as well as do things like override the
2747
 * Android back button in PhoneGap/Cordova.
2748
 */
2749
IonicModule
2750
.constant('IONIC_BACK_PRIORITY', {
2751
  view: 100,
2752
  sideMenu: 150,
2753
  modal: 200,
2754
  actionSheet: 300,
2755
  popup: 400,
2756
  loading: 500
2757
})
2758
.provider('$ionicPlatform', function() {
2759
  return {
2760
    $get: ['$q', function($q) {
2761
      var self = {
2762
 
2763
        /**
2764
         * @ngdoc method
2765
         * @name $ionicPlatform#onHardwareBackButton
2766
         * @description
2767
         * Some platforms have a hardware back button, so this is one way to
2768
         * bind to it.
2769
         * @param {function} callback the callback to trigger when this event occurs
2770
         */
2771
        onHardwareBackButton: function(cb) {
2772
          ionic.Platform.ready(function() {
2773
            document.addEventListener('backbutton', cb, false);
2774
          });
2775
        },
2776
 
2777
        /**
2778
         * @ngdoc method
2779
         * @name $ionicPlatform#offHardwareBackButton
2780
         * @description
2781
         * Remove an event listener for the backbutton.
2782
         * @param {function} callback The listener function that was
2783
         * originally bound.
2784
         */
2785
        offHardwareBackButton: function(fn) {
2786
          ionic.Platform.ready(function() {
2787
            document.removeEventListener('backbutton', fn);
2788
          });
2789
        },
2790
 
2791
        /**
2792
         * @ngdoc method
2793
         * @name $ionicPlatform#registerBackButtonAction
2794
         * @description
2795
         * Register a hardware back button action. Only one action will execute
2796
         * when the back button is clicked, so this method decides which of
2797
         * the registered back button actions has the highest priority.
2798
         *
2799
         * For example, if an actionsheet is showing, the back button should
2800
         * close the actionsheet, but it should not also go back a page view
2801
         * or close a modal which may be open.
2802
         *
2803
         * The priorities for the existing back button hooks are as follows:
2804
         *   Return to previous view = 100
2805
         *   Close side menu = 150
2806
         *   Dismiss modal = 200
2807
         *   Close action sheet = 300
2808
         *   Dismiss popup = 400
2809
         *   Dismiss loading overlay = 500
2810
         *
2811
         * Your back button action will override each of the above actions
2812
         * whose priority is less than the priority you provide. For example,
2813
         * an action assigned a priority of 101 will override the 'return to
2814
         * previous view' action, but not any of the other actions.
2815
         *
2816
         * @param {function} callback Called when the back button is pressed,
2817
         * if this listener is the highest priority.
2818
         * @param {number} priority Only the highest priority will execute.
2819
         * @param {*=} actionId The id to assign this action. Default: a
2820
         * random unique id.
2821
         * @returns {function} A function that, when called, will deregister
2822
         * this backButtonAction.
2823
         */
2824
        $backButtonActions: {},
2825
        registerBackButtonAction: function(fn, priority, actionId) {
2826
 
2827
          if (!self._hasBackButtonHandler) {
2828
            // add a back button listener if one hasn't been setup yet
2829
            self.$backButtonActions = {};
2830
            self.onHardwareBackButton(self.hardwareBackButtonClick);
2831
            self._hasBackButtonHandler = true;
2832
          }
2833
 
2834
          var action = {
2835
            id: (actionId ? actionId : ionic.Utils.nextUid()),
2836
            priority: (priority ? priority : 0),
2837
            fn: fn
2838
          };
2839
          self.$backButtonActions[action.id] = action;
2840
 
2841
          // return a function to de-register this back button action
2842
          return function() {
2843
            delete self.$backButtonActions[action.id];
2844
          };
2845
        },
2846
 
2847
        /**
2848
         * @private
2849
         */
2850
        hardwareBackButtonClick: function(e) {
2851
          // loop through all the registered back button actions
2852
          // and only run the last one of the highest priority
2853
          var priorityAction, actionId;
2854
          for (actionId in self.$backButtonActions) {
2855
            if (!priorityAction || self.$backButtonActions[actionId].priority >= priorityAction.priority) {
2856
              priorityAction = self.$backButtonActions[actionId];
2857
            }
2858
          }
2859
          if (priorityAction) {
2860
            priorityAction.fn(e);
2861
            return priorityAction;
2862
          }
2863
        },
2864
 
2865
        is: function(type) {
2866
          return ionic.Platform.is(type);
2867
        },
2868
 
2869
        /**
2870
         * @ngdoc method
2871
         * @name $ionicPlatform#on
2872
         * @description
2873
         * Add Cordova event listeners, such as `pause`, `resume`, `volumedownbutton`, `batterylow`,
2874
         * `offline`, etc. More information about available event types can be found in
2875
         * [Cordova's event documentation](https://cordova.apache.org/docs/en/edge/cordova_events_events.md.html#Events).
2876
         * @param {string} type Cordova [event type](https://cordova.apache.org/docs/en/edge/cordova_events_events.md.html#Events).
2877
         * @param {function} callback Called when the Cordova event is fired.
2878
         * @returns {function} Returns a deregistration function to remove the event listener.
2879
         */
2880
        on: function(type, cb) {
2881
          ionic.Platform.ready(function() {
2882
            document.addEventListener(type, cb, false);
2883
          });
2884
          return function() {
2885
            ionic.Platform.ready(function() {
2886
              document.removeEventListener(type, cb);
2887
            });
2888
          };
2889
        },
2890
 
2891
        /**
2892
         * @ngdoc method
2893
         * @name $ionicPlatform#ready
2894
         * @description
2895
         * Trigger a callback once the device is ready,
2896
         * or immediately if the device is already ready.
2897
         * @param {function=} callback The function to call.
2898
         * @returns {promise} A promise which is resolved when the device is ready.
2899
         */
2900
        ready: function(cb) {
2901
          var q = $q.defer();
2902
 
2903
          ionic.Platform.ready(function() {
2904
            q.resolve();
2905
            cb && cb();
2906
          });
2907
 
2908
          return q.promise;
2909
        }
2910
      };
2911
      return self;
2912
    }]
2913
  };
2914
 
2915
});
2916
 
2917
/**
2918
 * @ngdoc service
2919
 * @name $ionicPopover
2920
 * @module ionic
2921
 * @description
2922
 *
2923
 * Related: {@link ionic.controller:ionicPopover ionicPopover controller}.
2924
 *
2925
 * The Popover is a view that floats above an app’s content. Popovers provide an
2926
 * easy way to present or gather information from the user and are
2927
 * commonly used in the following situations:
2928
 *
2929
 * - Show more info about the current view
2930
 * - Select a commonly used tool or configuration
2931
 * - Present a list of actions to perform inside one of your views
2932
 *
2933
 * Put the content of the popover inside of an `<ion-popover-view>` element.
2934
 *
2935
 * @usage
2936
 * ```html
2937
 * <p>
2938
 *   <button ng-click="openPopover($event)">Open Popover</button>
2939
 * </p>
2940
 *
2941
 * <script id="my-popover.html" type="text/ng-template">
2942
 *   <ion-popover-view>
2943
 *     <ion-header-bar>
2944
 *       <h1 class="title">My Popover Title</h1>
2945
 *     </ion-header-bar>
2946
 *     <ion-content>
2947
 *       Hello!
2948
 *     </ion-content>
2949
 *   </ion-popover-view>
2950
 * </script>
2951
 * ```
2952
 * ```js
2953
 * angular.module('testApp', ['ionic'])
2954
 * .controller('MyController', function($scope, $ionicPopover) {
2955
 *
2956
 *   // .fromTemplate() method
2957
 *   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>';
2958
 *
2959
 *   $scope.popover = $ionicPopover.fromTemplate(template, {
2960
 *     scope: $scope
2961
 *   });
2962
 *
2963
 *   // .fromTemplateUrl() method
2964
 *   $ionicPopover.fromTemplateUrl('my-popover.html', {
2965
 *     scope: $scope
2966
 *   }).then(function(popover) {
2967
 *     $scope.popover = popover;
2968
 *   });
2969
 *
2970
 *
2971
 *   $scope.openPopover = function($event) {
2972
 *     $scope.popover.show($event);
2973
 *   };
2974
 *   $scope.closePopover = function() {
2975
 *     $scope.popover.hide();
2976
 *   };
2977
 *   //Cleanup the popover when we're done with it!
2978
 *   $scope.$on('$destroy', function() {
2979
 *     $scope.popover.remove();
2980
 *   });
2981
 *   // Execute action on hide popover
2982
 *   $scope.$on('popover.hidden', function() {
2983
 *     // Execute action
2984
 *   });
2985
 *   // Execute action on remove popover
2986
 *   $scope.$on('popover.removed', function() {
2987
 *     // Execute action
2988
 *   });
2989
 * });
2990
 * ```
2991
 */
2992
 
2993
 
2994
IonicModule
2995
.factory('$ionicPopover', ['$ionicModal', '$ionicPosition', '$document', '$window',
2996
function($ionicModal, $ionicPosition, $document, $window) {
2997
 
2998
  var POPOVER_BODY_PADDING = 6;
2999
 
3000
  var POPOVER_OPTIONS = {
3001
    viewType: 'popover',
3002
    hideDelay: 1,
3003
    animation: 'none',
3004
    positionView: positionView
3005
  };
3006
 
3007
  function positionView(target, popoverEle) {
3008
    var targetEle = jqLite(target.target || target);
3009
    var buttonOffset = $ionicPosition.offset(targetEle);
3010
    var popoverWidth = popoverEle.prop('offsetWidth');
3011
    var popoverHeight = popoverEle.prop('offsetHeight');
3012
    // Use innerWidth and innerHeight, because clientWidth and clientHeight
3013
    // doesn't work consistently for body on all platforms
3014
    var bodyWidth = $window.innerWidth;
3015
    var bodyHeight = $window.innerHeight;
3016
 
3017
    var popoverCSS = {
3018
      left: buttonOffset.left + buttonOffset.width / 2 - popoverWidth / 2
3019
    };
3020
    var arrowEle = jqLite(popoverEle[0].querySelector('.popover-arrow'));
3021
 
3022
    if (popoverCSS.left < POPOVER_BODY_PADDING) {
3023
      popoverCSS.left = POPOVER_BODY_PADDING;
3024
    } else if (popoverCSS.left + popoverWidth + POPOVER_BODY_PADDING > bodyWidth) {
3025
      popoverCSS.left = bodyWidth - popoverWidth - POPOVER_BODY_PADDING;
3026
    }
3027
 
3028
    // If the popover when popped down stretches past bottom of screen,
3029
    // make it pop up if there's room above
3030
    if (buttonOffset.top + buttonOffset.height + popoverHeight > bodyHeight &&
3031
        buttonOffset.top - popoverHeight > 0) {
3032
      popoverCSS.top = buttonOffset.top - popoverHeight;
3033
      popoverEle.addClass('popover-bottom');
3034
    } else {
3035
      popoverCSS.top = buttonOffset.top + buttonOffset.height;
3036
      popoverEle.removeClass('popover-bottom');
3037
    }
3038
 
3039
    arrowEle.css({
3040
      left: buttonOffset.left + buttonOffset.width / 2 -
3041
        arrowEle.prop('offsetWidth') / 2 - popoverCSS.left + 'px'
3042
    });
3043
 
3044
    popoverEle.css({
3045
      top: popoverCSS.top + 'px',
3046
      left: popoverCSS.left + 'px',
3047
      marginLeft: '0',
3048
      opacity: '1'
3049
    });
3050
 
3051
  }
3052
 
3053
  /**
3054
   * @ngdoc controller
3055
   * @name ionicPopover
3056
   * @module ionic
3057
   * @description
3058
   * Instantiated by the {@link ionic.service:$ionicPopover} service.
3059
   *
3060
   * Be sure to call [remove()](#remove) when you are done with each popover
3061
   * to clean it up and avoid memory leaks.
3062
   *
3063
   * Note: a popover will broadcast 'popover.shown', 'popover.hidden', and 'popover.removed' events from its originating
3064
   * scope, passing in itself as an event argument. Both the popover.removed and popover.hidden events are
3065
   * called when the popover is removed.
3066
   */
3067
 
3068
  /**
3069
   * @ngdoc method
3070
   * @name ionicPopover#initialize
3071
   * @description Creates a new popover controller instance.
3072
   * @param {object} options An options object with the following properties:
3073
   *  - `{object=}` `scope` The scope to be a child of.
3074
   *    Default: creates a child of $rootScope.
3075
   *  - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of
3076
   *    the popover when shown.  Default: false.
3077
   *  - `{boolean=}` `backdropClickToClose` Whether to close the popover on clicking the backdrop.
3078
   *    Default: true.
3079
   *  - `{boolean=}` `hardwareBackButtonClose` Whether the popover can be closed using the hardware
3080
   *    back button on Android and similar devices.  Default: true.
3081
   */
3082
 
3083
  /**
3084
   * @ngdoc method
3085
   * @name ionicPopover#show
3086
   * @description Show this popover instance.
3087
   * @param {$event} $event The $event or target element which the popover should align
3088
   * itself next to.
3089
   * @returns {promise} A promise which is resolved when the popover is finished animating in.
3090
   */
3091
 
3092
  /**
3093
   * @ngdoc method
3094
   * @name ionicPopover#hide
3095
   * @description Hide this popover instance.
3096
   * @returns {promise} A promise which is resolved when the popover is finished animating out.
3097
   */
3098
 
3099
  /**
3100
   * @ngdoc method
3101
   * @name ionicPopover#remove
3102
   * @description Remove this popover instance from the DOM and clean up.
3103
   * @returns {promise} A promise which is resolved when the popover is finished animating out.
3104
   */
3105
 
3106
  /**
3107
   * @ngdoc method
3108
   * @name ionicPopover#isShown
3109
   * @returns boolean Whether this popover is currently shown.
3110
   */
3111
 
3112
  return {
3113
    /**
3114
     * @ngdoc method
3115
     * @name $ionicPopover#fromTemplate
3116
     * @param {string} templateString The template string to use as the popovers's
3117
     * content.
3118
     * @param {object} options Options to be passed to the initialize method.
3119
     * @returns {object} An instance of an {@link ionic.controller:ionicPopover}
3120
     * controller (ionicPopover is built on top of $ionicPopover).
3121
     */
3122
    fromTemplate: function(templateString, options) {
3123
      return $ionicModal.fromTemplate(templateString, ionic.Utils.extend(POPOVER_OPTIONS, options || {}));
3124
    },
3125
    /**
3126
     * @ngdoc method
3127
     * @name $ionicPopover#fromTemplateUrl
3128
     * @param {string} templateUrl The url to load the template from.
3129
     * @param {object} options Options to be passed to the initialize method.
3130
     * @returns {promise} A promise that will be resolved with an instance of
3131
     * an {@link ionic.controller:ionicPopover} controller (ionicPopover is built on top of $ionicPopover).
3132
     */
3133
    fromTemplateUrl: function(url, options) {
3134
      return $ionicModal.fromTemplateUrl(url, ionic.Utils.extend(POPOVER_OPTIONS, options || {}));
3135
    }
3136
  };
3137
 
3138
}]);
3139
 
3140
 
3141
var POPUP_TPL =
3142
  '<div class="popup-container" ng-class="cssClass">' +
3143
    '<div class="popup">' +
3144
      '<div class="popup-head">' +
3145
        '<h3 class="popup-title" ng-bind-html="title"></h3>' +
3146
        '<h5 class="popup-sub-title" ng-bind-html="subTitle" ng-if="subTitle"></h5>' +
3147
      '</div>' +
3148
      '<div class="popup-body">' +
3149
      '</div>' +
3150
      '<div class="popup-buttons" ng-show="buttons.length">' +
3151
        '<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>' +
3152
      '</div>' +
3153
    '</div>' +
3154
  '</div>';
3155
 
3156
/**
3157
 * @ngdoc service
3158
 * @name $ionicPopup
3159
 * @module ionic
3160
 * @restrict E
3161
 * @codepen zkmhJ
3162
 * @description
3163
 *
3164
 * The Ionic Popup service allows programmatically creating and showing popup
3165
 * windows that require the user to respond in order to continue.
3166
 *
3167
 * The popup system has support for more flexible versions of the built in `alert()`, `prompt()`,
3168
 * and `confirm()` functions that users are used to, in addition to allowing popups with completely
3169
 * custom content and look.
3170
 *
3171
 * An input can be given an `autofocus` attribute so it automatically receives focus when
3172
 * the popup first shows. However, depending on certain use-cases this can cause issues with
3173
 * the tap/click system, which is why Ionic prefers using the `autofocus` attribute as
3174
 * an opt-in feature and not the default.
3175
 *
3176
 * @usage
3177
 * A few basic examples, see below for details about all of the options available.
3178
 *
3179
 * ```js
3180
 *angular.module('mySuperApp', ['ionic'])
3181
 *.controller('PopupCtrl',function($scope, $ionicPopup, $timeout) {
3182
 *
3183
 * // Triggered on a button click, or some other target
3184
 * $scope.showPopup = function() {
3185
 *   $scope.data = {}
3186
 *
3187
 *   // An elaborate, custom popup
3188
 *   var myPopup = $ionicPopup.show({
3189
 *     template: '<input type="password" ng-model="data.wifi">',
3190
 *     title: 'Enter Wi-Fi Password',
3191
 *     subTitle: 'Please use normal things',
3192
 *     scope: $scope,
3193
 *     buttons: [
3194
 *       { text: 'Cancel' },
3195
 *       {
3196
 *         text: '<b>Save</b>',
3197
 *         type: 'button-positive',
3198
 *         onTap: function(e) {
3199
 *           if (!$scope.data.wifi) {
3200
 *             //don't allow the user to close unless he enters wifi password
3201
 *             e.preventDefault();
3202
 *           } else {
3203
 *             return $scope.data.wifi;
3204
 *           }
3205
 *         }
3206
 *       }
3207
 *     ]
3208
 *   });
3209
 *   myPopup.then(function(res) {
3210
 *     console.log('Tapped!', res);
3211
 *   });
3212
 *   $timeout(function() {
3213
 *      myPopup.close(); //close the popup after 3 seconds for some reason
3214
 *   }, 3000);
3215
 *  };
3216
 *  // A confirm dialog
3217
 *  $scope.showConfirm = function() {
3218
 *    var confirmPopup = $ionicPopup.confirm({
3219
 *      title: 'Consume Ice Cream',
3220
 *      template: 'Are you sure you want to eat this ice cream?'
3221
 *    });
3222
 *    confirmPopup.then(function(res) {
3223
 *      if(res) {
3224
 *        console.log('You are sure');
3225
 *      } else {
3226
 *        console.log('You are not sure');
3227
 *      }
3228
 *    });
3229
 *  };
3230
 *
3231
 *  // An alert dialog
3232
 *  $scope.showAlert = function() {
3233
 *    var alertPopup = $ionicPopup.alert({
3234
 *      title: 'Don\'t eat that!',
3235
 *      template: 'It might taste good'
3236
 *    });
3237
 *    alertPopup.then(function(res) {
3238
 *      console.log('Thank you for not eating my delicious ice cream cone');
3239
 *    });
3240
 *  };
3241
 *});
3242
 *```
3243
 */
3244
 
3245
IonicModule
3246
.factory('$ionicPopup', [
3247
  '$ionicTemplateLoader',
3248
  '$ionicBackdrop',
3249
  '$q',
3250
  '$timeout',
3251
  '$rootScope',
3252
  '$ionicBody',
3253
  '$compile',
3254
  '$ionicPlatform',
3255
  'IONIC_BACK_PRIORITY',
3256
function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $ionicBody, $compile, $ionicPlatform, IONIC_BACK_PRIORITY) {
3257
  //TODO allow this to be configured
3258
  var config = {
3259
    stackPushDelay: 75
3260
  };
3261
  var popupStack = [];
3262
 
3263
  var $ionicPopup = {
3264
    /**
3265
     * @ngdoc method
3266
     * @description
3267
     * Show a complex popup. This is the master show function for all popups.
3268
     *
3269
     * A complex popup has a `buttons` array, with each button having a `text` and `type`
3270
     * field, in addition to an `onTap` function.  The `onTap` function, called when
3271
     * the corresponding button on the popup is tapped, will by default close the popup
3272
     * and resolve the popup promise with its return value.  If you wish to prevent the
3273
     * default and keep the popup open on button tap, call `event.preventDefault()` on the
3274
     * passed in tap event.  Details below.
3275
     *
3276
     * @name $ionicPopup#show
3277
     * @param {object} options The options for the new popup, of the form:
3278
     *
3279
     * ```
3280
     * {
3281
     *   title: '', // String. The title of the popup.
3282
     *   cssClass: '', // String, The custom CSS class name
3283
     *   subTitle: '', // String (optional). The sub-title of the popup.
3284
     *   template: '', // String (optional). The html template to place in the popup body.
3285
     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup   body.
3286
     *   scope: null, // Scope (optional). A scope to link to the popup content.
3287
     *   buttons: [{ // Array[Object] (optional). Buttons to place in the popup footer.
3288
     *     text: 'Cancel',
3289
     *     type: 'button-default',
3290
     *     onTap: function(e) {
3291
     *       // e.preventDefault() will stop the popup from closing when tapped.
3292
     *       e.preventDefault();
3293
     *     }
3294
     *   }, {
3295
     *     text: 'OK',
3296
     *     type: 'button-positive',
3297
     *     onTap: function(e) {
3298
     *       // Returning a value will cause the promise to resolve with the given value.
3299
     *       return scope.data.response;
3300
     *     }
3301
     *   }]
3302
     * }
3303
     * ```
3304
     *
3305
     * @returns {object} A promise which is resolved when the popup is closed. Has an additional
3306
     * `close` function, which can be used to programmatically close the popup.
3307
     */
3308
    show: showPopup,
3309
 
3310
    /**
3311
     * @ngdoc method
3312
     * @name $ionicPopup#alert
3313
     * @description Show a simple alert popup with a message and one button that the user can
3314
     * tap to close the popup.
3315
     *
3316
     * @param {object} options The options for showing the alert, of the form:
3317
     *
3318
     * ```
3319
     * {
3320
     *   title: '', // String. The title of the popup.
3321
     *   cssClass: '', // String, The custom CSS class name
3322
     *   subTitle: '', // String (optional). The sub-title of the popup.
3323
     *   template: '', // String (optional). The html template to place in the popup body.
3324
     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup   body.
3325
     *   okText: '', // String (default: 'OK'). The text of the OK button.
3326
     *   okType: '', // String (default: 'button-positive'). The type of the OK button.
3327
     * }
3328
     * ```
3329
     *
3330
     * @returns {object} A promise which is resolved when the popup is closed. Has one additional
3331
     * function `close`, which can be called with any value to programmatically close the popup
3332
     * with the given value.
3333
     */
3334
    alert: showAlert,
3335
 
3336
    /**
3337
     * @ngdoc method
3338
     * @name $ionicPopup#confirm
3339
     * @description
3340
     * Show a simple confirm popup with a Cancel and OK button.
3341
     *
3342
     * Resolves the promise with true if the user presses the OK button, and false if the
3343
     * user presses the Cancel button.
3344
     *
3345
     * @param {object} options The options for showing the confirm popup, of the form:
3346
     *
3347
     * ```
3348
     * {
3349
     *   title: '', // String. The title of the popup.
3350
     *   cssClass: '', // String, The custom CSS class name
3351
     *   subTitle: '', // String (optional). The sub-title of the popup.
3352
     *   template: '', // String (optional). The html template to place in the popup body.
3353
     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup   body.
3354
     *   cancelText: '', // String (default: 'Cancel'). The text of the Cancel button.
3355
     *   cancelType: '', // String (default: 'button-default'). The type of the Cancel button.
3356
     *   okText: '', // String (default: 'OK'). The text of the OK button.
3357
     *   okType: '', // String (default: 'button-positive'). The type of the OK button.
3358
     * }
3359
     * ```
3360
     *
3361
     * @returns {object} A promise which is resolved when the popup is closed. Has one additional
3362
     * function `close`, which can be called with any value to programmatically close the popup
3363
     * with the given value.
3364
     */
3365
    confirm: showConfirm,
3366
 
3367
    /**
3368
     * @ngdoc method
3369
     * @name $ionicPopup#prompt
3370
     * @description Show a simple prompt popup, which has an input, OK button, and Cancel button.
3371
     * Resolves the promise with the value of the input if the user presses OK, and with undefined
3372
     * if the user presses Cancel.
3373
     *
3374
     * ```javascript
3375
     *  $ionicPopup.prompt({
3376
     *    title: 'Password Check',
3377
     *    template: 'Enter your secret password',
3378
     *    inputType: 'password',
3379
     *    inputPlaceholder: 'Your password'
3380
     *  }).then(function(res) {
3381
     *    console.log('Your password is', res);
3382
     *  });
3383
     * ```
3384
     * @param {object} options The options for showing the prompt popup, of the form:
3385
     *
3386
     * ```
3387
     * {
3388
     *   title: '', // String. The title of the popup.
3389
     *   cssClass: '', // String, The custom CSS class name
3390
     *   subTitle: '', // String (optional). The sub-title of the popup.
3391
     *   template: '', // String (optional). The html template to place in the popup body.
3392
     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup   body.
3393
     *   inputType: // String (default: 'text'). The type of input to use
3394
     *   inputPlaceholder: // String (default: ''). A placeholder to use for the input.
3395
     *   cancelText: // String (default: 'Cancel'. The text of the Cancel button.
3396
     *   cancelType: // String (default: 'button-default'). The type of the Cancel button.
3397
     *   okText: // String (default: 'OK'). The text of the OK button.
3398
     *   okType: // String (default: 'button-positive'). The type of the OK button.
3399
     * }
3400
     * ```
3401
     *
3402
     * @returns {object} A promise which is resolved when the popup is closed. Has one additional
3403
     * function `close`, which can be called with any value to programmatically close the popup
3404
     * with the given value.
3405
     */
3406
    prompt: showPrompt,
3407
    /**
3408
     * @private for testing
3409
     */
3410
    _createPopup: createPopup,
3411
    _popupStack: popupStack
3412
  };
3413
 
3414
  return $ionicPopup;
3415
 
3416
  function createPopup(options) {
3417
    options = extend({
3418
      scope: null,
3419
      title: '',
3420
      buttons: []
3421
    }, options || {});
3422
 
3423
    var self = {};
3424
    self.scope = (options.scope || $rootScope).$new();
3425
    self.element = jqLite(POPUP_TPL);
3426
    self.responseDeferred = $q.defer();
3427
 
3428
    $ionicBody.get().appendChild(self.element[0]);
3429
    $compile(self.element)(self.scope);
3430
 
3431
    extend(self.scope, {
3432
      title: options.title,
3433
      buttons: options.buttons,
3434
      subTitle: options.subTitle,
3435
      cssClass: options.cssClass,
3436
      $buttonTapped: function(button, event) {
3437
        var result = (button.onTap || noop)(event);
3438
        event = event.originalEvent || event; //jquery events
3439
 
3440
        if (!event.defaultPrevented) {
3441
          self.responseDeferred.resolve(result);
3442
        }
3443
      }
3444
    });
3445
 
3446
    $q.when(
3447
      options.templateUrl ?
3448
      $ionicTemplateLoader.load(options.templateUrl) :
3449
        (options.template || options.content || '')
3450
    ).then(function(template) {
3451
      var popupBody = jqLite(self.element[0].querySelector('.popup-body'));
3452
      if (template) {
3453
        popupBody.html(template);
3454
        $compile(popupBody.contents())(self.scope);
3455
      } else {
3456
        popupBody.remove();
3457
      }
3458
    });
3459
 
3460
    self.show = function() {
3461
      if (self.isShown || self.removed) return;
3462
 
3463
      self.isShown = true;
3464
      ionic.requestAnimationFrame(function() {
3465
        //if hidden while waiting for raf, don't show
3466
        if (!self.isShown) return;
3467
 
3468
        self.element.removeClass('popup-hidden');
3469
        self.element.addClass('popup-showing active');
3470
        focusInput(self.element);
3471
      });
3472
    };
3473
 
3474
    self.hide = function(callback) {
3475
      callback = callback || noop;
3476
      if (!self.isShown) return callback();
3477
 
3478
      self.isShown = false;
3479
      self.element.removeClass('active');
3480
      self.element.addClass('popup-hidden');
3481
      $timeout(callback, 250, false);
3482
    };
3483
 
3484
    self.remove = function() {
3485
      if (self.removed) return;
3486
 
3487
      self.hide(function() {
3488
        self.element.remove();
3489
        self.scope.$destroy();
3490
      });
3491
 
3492
      self.removed = true;
3493
    };
3494
 
3495
    return self;
3496
  }
3497
 
3498
  function onHardwareBackButton() {
3499
    var last = popupStack[popupStack.length - 1];
3500
    last && last.responseDeferred.resolve();
3501
  }
3502
 
3503
  function showPopup(options) {
3504
    var popup = $ionicPopup._createPopup(options);
3505
    var showDelay = 0;
3506
 
3507
    if (popupStack.length > 0) {
3508
      popupStack[popupStack.length - 1].hide();
3509
      showDelay = config.stackPushDelay;
3510
    } else {
3511
      //Add popup-open & backdrop if this is first popup
3512
      $ionicBody.addClass('popup-open');
3513
      $ionicBackdrop.retain();
3514
      //only show the backdrop on the first popup
3515
      $ionicPopup._backButtonActionDone = $ionicPlatform.registerBackButtonAction(
3516
        onHardwareBackButton,
3517
        IONIC_BACK_PRIORITY.popup
3518
      );
3519
    }
3520
 
3521
    // Expose a 'close' method on the returned promise
3522
    popup.responseDeferred.promise.close = function popupClose(result) {
3523
      if (!popup.removed) popup.responseDeferred.resolve(result);
3524
    };
3525
    //DEPRECATED: notify the promise with an object with a close method
3526
    popup.responseDeferred.notify({ close: popup.responseDeferred.close });
3527
 
3528
    doShow();
3529
 
3530
    return popup.responseDeferred.promise;
3531
 
3532
    function doShow() {
3533
      popupStack.push(popup);
3534
      $timeout(popup.show, showDelay, false);
3535
 
3536
      popup.responseDeferred.promise.then(function(result) {
3537
        var index = popupStack.indexOf(popup);
3538
        if (index !== -1) {
3539
          popupStack.splice(index, 1);
3540
        }
3541
 
3542
        if (popupStack.length > 0) {
3543
          popupStack[popupStack.length - 1].show();
3544
        } else {
3545
          $ionicBackdrop.release();
3546
          //Remove popup-open & backdrop if this is last popup
3547
          $timeout(function() {
3548
            // wait to remove this due to a 300ms delay native
3549
            // click which would trigging whatever was underneath this
3550
            if (!popupStack.length) {
3551
              $ionicBody.removeClass('popup-open');
3552
            }
3553
          }, 400, false);
3554
          ($ionicPopup._backButtonActionDone || noop)();
3555
        }
3556
 
3557
        popup.remove();
3558
 
3559
        return result;
3560
      });
3561
 
3562
    }
3563
 
3564
  }
3565
 
3566
  function focusInput(element) {
3567
    var focusOn = element[0].querySelector('[autofocus]');
3568
    if (focusOn) {
3569
      focusOn.focus();
3570
    }
3571
  }
3572
 
3573
  function showAlert(opts) {
3574
    return showPopup(extend({
3575
      buttons: [{
3576
        text: opts.okText || 'OK',
3577
        type: opts.okType || 'button-positive',
3578
        onTap: function() {
3579
          return true;
3580
        }
3581
      }]
3582
    }, opts || {}));
3583
  }
3584
 
3585
  function showConfirm(opts) {
3586
    return showPopup(extend({
3587
      buttons: [{
3588
        text: opts.cancelText || 'Cancel',
3589
        type: opts.cancelType || 'button-default',
3590
        onTap: function() { return false; }
3591
      }, {
3592
        text: opts.okText || 'OK',
3593
        type: opts.okType || 'button-positive',
3594
        onTap: function() { return true; }
3595
      }]
3596
    }, opts || {}));
3597
  }
3598
 
3599
  function showPrompt(opts) {
3600
    var scope = $rootScope.$new(true);
3601
    scope.data = {};
3602
    var text = '';
3603
    if (opts.template && /<[a-z][\s\S]*>/i.test(opts.template) === false) {
3604
      text = '<span>' + opts.template + '</span>';
3605
      delete opts.template;
3606
    }
3607
    return showPopup(extend({
3608
      template: text + '<input ng-model="data.response" type="' + (opts.inputType || 'text') +
3609
        '" placeholder="' + (opts.inputPlaceholder || '') + '">',
3610
      scope: scope,
3611
      buttons: [{
3612
        text: opts.cancelText || 'Cancel',
3613
        type: opts.cancelType || 'button-default',
3614
        onTap: function() {}
3615
      }, {
3616
        text: opts.okText || 'OK',
3617
        type: opts.okType || 'button-positive',
3618
        onTap: function() {
3619
          return scope.data.response || '';
3620
        }
3621
      }]
3622
    }, opts || {}));
3623
  }
3624
}]);
3625
 
3626
/**
3627
 * @ngdoc service
3628
 * @name $ionicPosition
3629
 * @module ionic
3630
 * @description
3631
 * A set of utility methods that can be use to retrieve position of DOM elements.
3632
 * It is meant to be used where we need to absolute-position DOM elements in
3633
 * relation to other, existing elements (this is the case for tooltips, popovers, etc.).
3634
 *
3635
 * Adapted from [AngularUI Bootstrap](https://github.com/angular-ui/bootstrap/blob/master/src/position/position.js),
3636
 * ([license](https://github.com/angular-ui/bootstrap/blob/master/LICENSE))
3637
 */
3638
IonicModule
3639
.factory('$ionicPosition', ['$document', '$window', function($document, $window) {
3640
 
3641
  function getStyle(el, cssprop) {
3642
    if (el.currentStyle) { //IE
3643
      return el.currentStyle[cssprop];
3644
    } else if ($window.getComputedStyle) {
3645
      return $window.getComputedStyle(el)[cssprop];
3646
    }
3647
    // finally try and get inline style
3648
    return el.style[cssprop];
3649
  }
3650
 
3651
  /**
3652
   * Checks if a given element is statically positioned
3653
   * @param element - raw DOM element
3654
   */
3655
  function isStaticPositioned(element) {
3656
    return (getStyle(element, 'position') || 'static') === 'static';
3657
  }
3658
 
3659
  /**
3660
   * returns the closest, non-statically positioned parentOffset of a given element
3661
   * @param element
3662
   */
3663
  var parentOffsetEl = function(element) {
3664
    var docDomEl = $document[0];
3665
    var offsetParent = element.offsetParent || docDomEl;
3666
    while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent)) {
3667
      offsetParent = offsetParent.offsetParent;
3668
    }
3669
    return offsetParent || docDomEl;
3670
  };
3671
 
3672
  return {
3673
    /**
3674
     * @ngdoc method
3675
     * @name $ionicPosition#position
3676
     * @description Get the current coordinates of the element, relative to the offset parent.
3677
     * Read-only equivalent of [jQuery's position function](http://api.jquery.com/position/).
3678
     * @param {element} element The element to get the position of.
3679
     * @returns {object} Returns an object containing the properties top, left, width and height.
3680
     */
3681
    position: function(element) {
3682
      var elBCR = this.offset(element);
3683
      var offsetParentBCR = { top: 0, left: 0 };
3684
      var offsetParentEl = parentOffsetEl(element[0]);
3685
      if (offsetParentEl != $document[0]) {
3686
        offsetParentBCR = this.offset(jqLite(offsetParentEl));
3687
        offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
3688
        offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
3689
      }
3690
 
3691
      var boundingClientRect = element[0].getBoundingClientRect();
3692
      return {
3693
        width: boundingClientRect.width || element.prop('offsetWidth'),
3694
        height: boundingClientRect.height || element.prop('offsetHeight'),
3695
        top: elBCR.top - offsetParentBCR.top,
3696
        left: elBCR.left - offsetParentBCR.left
3697
      };
3698
    },
3699
 
3700
    /**
3701
     * @ngdoc method
3702
     * @name $ionicPosition#offset
3703
     * @description Get the current coordinates of the element, relative to the document.
3704
     * Read-only equivalent of [jQuery's offset function](http://api.jquery.com/offset/).
3705
     * @param {element} element The element to get the offset of.
3706
     * @returns {object} Returns an object containing the properties top, left, width and height.
3707
     */
3708
    offset: function(element) {
3709
      var boundingClientRect = element[0].getBoundingClientRect();
3710
      return {
3711
        width: boundingClientRect.width || element.prop('offsetWidth'),
3712
        height: boundingClientRect.height || element.prop('offsetHeight'),
3713
        top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
3714
        left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
3715
      };
3716
    }
3717
 
3718
  };
3719
}]);
3720
 
3721
 
3722
/**
3723
 * @ngdoc service
3724
 * @name $ionicScrollDelegate
3725
 * @module ionic
3726
 * @description
3727
 * Delegate for controlling scrollViews (created by
3728
 * {@link ionic.directive:ionContent} and
3729
 * {@link ionic.directive:ionScroll} directives).
3730
 *
3731
 * Methods called directly on the $ionicScrollDelegate service will control all scroll
3732
 * views.  Use the {@link ionic.service:$ionicScrollDelegate#$getByHandle $getByHandle}
3733
 * method to control specific scrollViews.
3734
 *
3735
 * @usage
3736
 *
3737
 * ```html
3738
 * <body ng-controller="MainCtrl">
3739
 *   <ion-content>
3740
 *     <button ng-click="scrollTop()">Scroll to Top!</button>
3741
 *   </ion-content>
3742
 * </body>
3743
 * ```
3744
 * ```js
3745
 * function MainCtrl($scope, $ionicScrollDelegate) {
3746
 *   $scope.scrollTop = function() {
3747
 *     $ionicScrollDelegate.scrollTop();
3748
 *   };
3749
 * }
3750
 * ```
3751
 *
3752
 * Example of advanced usage, with two scroll areas using `delegate-handle`
3753
 * for fine control.
3754
 *
3755
 * ```html
3756
 * <body ng-controller="MainCtrl">
3757
 *   <ion-content delegate-handle="mainScroll">
3758
 *     <button ng-click="scrollMainToTop()">
3759
 *       Scroll content to top!
3760
 *     </button>
3761
 *     <ion-scroll delegate-handle="small" style="height: 100px;">
3762
 *       <button ng-click="scrollSmallToTop()">
3763
 *         Scroll small area to top!
3764
 *       </button>
3765
 *     </ion-scroll>
3766
 *   </ion-content>
3767
 * </body>
3768
 * ```
3769
 * ```js
3770
 * function MainCtrl($scope, $ionicScrollDelegate) {
3771
 *   $scope.scrollMainToTop = function() {
3772
 *     $ionicScrollDelegate.$getByHandle('mainScroll').scrollTop();
3773
 *   };
3774
 *   $scope.scrollSmallToTop = function() {
3775
 *     $ionicScrollDelegate.$getByHandle('small').scrollTop();
3776
 *   };
3777
 * }
3778
 * ```
3779
 */
3780
IonicModule
3781
.service('$ionicScrollDelegate', ionic.DelegateService([
3782
  /**
3783
   * @ngdoc method
3784
   * @name $ionicScrollDelegate#resize
3785
   * @description Tell the scrollView to recalculate the size of its container.
3786
   */
3787
  'resize',
3788
  /**
3789
   * @ngdoc method
3790
   * @name $ionicScrollDelegate#scrollTop
3791
   * @param {boolean=} shouldAnimate Whether the scroll should animate.
3792
   */
3793
  'scrollTop',
3794
  /**
3795
   * @ngdoc method
3796
   * @name $ionicScrollDelegate#scrollBottom
3797
   * @param {boolean=} shouldAnimate Whether the scroll should animate.
3798
   */
3799
  'scrollBottom',
3800
  /**
3801
   * @ngdoc method
3802
   * @name $ionicScrollDelegate#scrollTo
3803
   * @param {number} left The x-value to scroll to.
3804
   * @param {number} top The y-value to scroll to.
3805
   * @param {boolean=} shouldAnimate Whether the scroll should animate.
3806
   */
3807
  'scrollTo',
3808
  /**
3809
   * @ngdoc method
3810
   * @name $ionicScrollDelegate#scrollBy
3811
   * @param {number} left The x-offset to scroll by.
3812
   * @param {number} top The y-offset to scroll by.
3813
   * @param {boolean=} shouldAnimate Whether the scroll should animate.
3814
   */
3815
  'scrollBy',
3816
  /**
3817
   * @ngdoc method
3818
   * @name $ionicScrollDelegate#zoomTo
3819
   * @param {number} level Level to zoom to.
3820
   * @param {boolean=} animate Whether to animate the zoom.
3821
   * @param {number=} originLeft Zoom in at given left coordinate.
3822
   * @param {number=} originTop Zoom in at given top coordinate.
3823
   */
3824
  'zoomTo',
3825
  /**
3826
   * @ngdoc method
3827
   * @name $ionicScrollDelegate#zoomBy
3828
   * @param {number} factor The factor to zoom by.
3829
   * @param {boolean=} animate Whether to animate the zoom.
3830
   * @param {number=} originLeft Zoom in at given left coordinate.
3831
   * @param {number=} originTop Zoom in at given top coordinate.
3832
   */
3833
  'zoomBy',
3834
  /**
3835
   * @ngdoc method
3836
   * @name $ionicScrollDelegate#getScrollPosition
3837
   * @returns {object} The scroll position of this view, with the following properties:
3838
   *  - `{number}` `left` The distance the user has scrolled from the left (starts at 0).
3839
   *  - `{number}` `top` The distance the user has scrolled from the top (starts at 0).
3840
   */
3841
  'getScrollPosition',
3842
  /**
3843
   * @ngdoc method
3844
   * @name $ionicScrollDelegate#anchorScroll
3845
   * @description Tell the scrollView to scroll to the element with an id
3846
   * matching window.location.hash.
3847
   *
3848
   * If no matching element is found, it will scroll to top.
3849
   *
3850
   * @param {boolean=} shouldAnimate Whether the scroll should animate.
3851
   */
3852
  'anchorScroll',
3853
  /**
3854
   * @ngdoc method
3855
   * @name $ionicScrollDelegate#freezeScroll
3856
   * @description Does not allow this scroll view to scroll either x or y.
3857
   * @param {boolean=} shouldFreeze Should this scroll view be prevented from scrolling or not.
3858
   * @returns {object} If the scroll view is being prevented from scrolling or not.
3859
   */
3860
  'freezeScroll',
3861
  /**
3862
   * @ngdoc method
3863
   * @name $ionicScrollDelegate#freezeAllScrolls
3864
   * @description Does not allow any of the app's scroll views to scroll either x or y.
3865
   * @param {boolean=} shouldFreeze Should all app scrolls be prevented from scrolling or not.
3866
   */
3867
  'freezeAllScrolls',
3868
  /**
3869
   * @ngdoc method
3870
   * @name $ionicScrollDelegate#getScrollView
3871
   * @returns {object} The scrollView associated with this delegate.
3872
   */
3873
  'getScrollView'
3874
  /**
3875
   * @ngdoc method
3876
   * @name $ionicScrollDelegate#$getByHandle
3877
   * @param {string} handle
3878
   * @returns `delegateInstance` A delegate instance that controls only the
3879
   * scrollViews with `delegate-handle` matching the given handle.
3880
   *
3881
   * Example: `$ionicScrollDelegate.$getByHandle('my-handle').scrollTop();`
3882
   */
3883
]));
3884
 
3885
 
3886
/**
3887
 * @ngdoc service
3888
 * @name $ionicSideMenuDelegate
3889
 * @module ionic
3890
 *
3891
 * @description
3892
 * Delegate for controlling the {@link ionic.directive:ionSideMenus} directive.
3893
 *
3894
 * Methods called directly on the $ionicSideMenuDelegate service will control all side
3895
 * menus.  Use the {@link ionic.service:$ionicSideMenuDelegate#$getByHandle $getByHandle}
3896
 * method to control specific ionSideMenus instances.
3897
 *
3898
 * @usage
3899
 *
3900
 * ```html
3901
 * <body ng-controller="MainCtrl">
3902
 *   <ion-side-menus>
3903
 *     <ion-side-menu-content>
3904
 *       Content!
3905
 *       <button ng-click="toggleLeftSideMenu()">
3906
 *         Toggle Left Side Menu
3907
 *       </button>
3908
 *     </ion-side-menu-content>
3909
 *     <ion-side-menu side="left">
3910
 *       Left Menu!
3911
 *     <ion-side-menu>
3912
 *   </ion-side-menus>
3913
 * </body>
3914
 * ```
3915
 * ```js
3916
 * function MainCtrl($scope, $ionicSideMenuDelegate) {
3917
 *   $scope.toggleLeftSideMenu = function() {
3918
 *     $ionicSideMenuDelegate.toggleLeft();
3919
 *   };
3920
 * }
3921
 * ```
3922
 */
3923
IonicModule
3924
.service('$ionicSideMenuDelegate', ionic.DelegateService([
3925
  /**
3926
   * @ngdoc method
3927
   * @name $ionicSideMenuDelegate#toggleLeft
3928
   * @description Toggle the left side menu (if it exists).
3929
   * @param {boolean=} isOpen Whether to open or close the menu.
3930
   * Default: Toggles the menu.
3931
   */
3932
  'toggleLeft',
3933
  /**
3934
   * @ngdoc method
3935
   * @name $ionicSideMenuDelegate#toggleRight
3936
   * @description Toggle the right side menu (if it exists).
3937
   * @param {boolean=} isOpen Whether to open or close the menu.
3938
   * Default: Toggles the menu.
3939
   */
3940
  'toggleRight',
3941
  /**
3942
   * @ngdoc method
3943
   * @name $ionicSideMenuDelegate#getOpenRatio
3944
   * @description Gets the ratio of open amount over menu width. For example, a
3945
   * menu of width 100 that is opened by 50 pixels is 50% opened, and would return
3946
   * a ratio of 0.5.
3947
   *
3948
   * @returns {float} 0 if nothing is open, between 0 and 1 if left menu is
3949
   * opened/opening, and between 0 and -1 if right menu is opened/opening.
3950
   */
3951
  'getOpenRatio',
3952
  /**
3953
   * @ngdoc method
3954
   * @name $ionicSideMenuDelegate#isOpen
3955
   * @returns {boolean} Whether either the left or right menu is currently opened.
3956
   */
3957
  'isOpen',
3958
  /**
3959
   * @ngdoc method
3960
   * @name $ionicSideMenuDelegate#isOpenLeft
3961
   * @returns {boolean} Whether the left menu is currently opened.
3962
   */
3963
  'isOpenLeft',
3964
  /**
3965
   * @ngdoc method
3966
   * @name $ionicSideMenuDelegate#isOpenRight
3967
   * @returns {boolean} Whether the right menu is currently opened.
3968
   */
3969
  'isOpenRight',
3970
  /**
3971
   * @ngdoc method
3972
   * @name $ionicSideMenuDelegate#canDragContent
3973
   * @param {boolean=} canDrag Set whether the content can or cannot be dragged to open
3974
   * side menus.
3975
   * @returns {boolean} Whether the content can be dragged to open side menus.
3976
   */
3977
  'canDragContent',
3978
  /**
3979
   * @ngdoc method
3980
   * @name $ionicSideMenuDelegate#edgeDragThreshold
3981
   * @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:
3982
   *  - 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.
3983
   *  - If true is given, the default number of pixels (25) is used as the maximum allowed distance.
3984
   *  - If false or 0 is given, the edge drag threshold is disabled, and dragging from anywhere on the content is allowed.
3985
   * @returns {boolean} Whether the drag can start only from within the edge of screen threshold.
3986
   */
3987
  'edgeDragThreshold'
3988
  /**
3989
   * @ngdoc method
3990
   * @name $ionicSideMenuDelegate#$getByHandle
3991
   * @param {string} handle
3992
   * @returns `delegateInstance` A delegate instance that controls only the
3993
   * {@link ionic.directive:ionSideMenus} directives with `delegate-handle` matching
3994
   * the given handle.
3995
   *
3996
   * Example: `$ionicSideMenuDelegate.$getByHandle('my-handle').toggleLeft();`
3997
   */
3998
]));
3999
 
4000
 
4001
/**
4002
 * @ngdoc service
4003
 * @name $ionicSlideBoxDelegate
4004
 * @module ionic
4005
 * @description
4006
 * Delegate that controls the {@link ionic.directive:ionSlideBox} directive.
4007
 *
4008
 * Methods called directly on the $ionicSlideBoxDelegate service will control all slide boxes.  Use the {@link ionic.service:$ionicSlideBoxDelegate#$getByHandle $getByHandle}
4009
 * method to control specific slide box instances.
4010
 *
4011
 * @usage
4012
 *
4013
 * ```html
4014
 * <ion-view>
4015
 *   <ion-slide-box>
4016
 *     <ion-slide>
4017
 *       <div class="box blue">
4018
 *         <button ng-click="nextSlide()">Next slide!</button>
4019
 *       </div>
4020
 *     </ion-slide>
4021
 *     <ion-slide>
4022
 *       <div class="box red">
4023
 *         Slide 2!
4024
 *       </div>
4025
 *     </ion-slide>
4026
 *   </ion-slide-box>
4027
 * </ion-view>
4028
 * ```
4029
 * ```js
4030
 * function MyCtrl($scope, $ionicSlideBoxDelegate) {
4031
 *   $scope.nextSlide = function() {
4032
 *     $ionicSlideBoxDelegate.next();
4033
 *   }
4034
 * }
4035
 * ```
4036
 */
4037
IonicModule
4038
.service('$ionicSlideBoxDelegate', ionic.DelegateService([
4039
  /**
4040
   * @ngdoc method
4041
   * @name $ionicSlideBoxDelegate#update
4042
   * @description
4043
   * Update the slidebox (for example if using Angular with ng-repeat,
4044
   * resize it for the elements inside).
4045
   */
4046
  'update',
4047
  /**
4048
   * @ngdoc method
4049
   * @name $ionicSlideBoxDelegate#slide
4050
   * @param {number} to The index to slide to.
4051
   * @param {number=} speed The number of milliseconds the change should take.
4052
   */
4053
  'slide',
4054
  'select',
4055
  /**
4056
   * @ngdoc method
4057
   * @name $ionicSlideBoxDelegate#enableSlide
4058
   * @param {boolean=} shouldEnable Whether to enable sliding the slidebox.
4059
   * @returns {boolean} Whether sliding is enabled.
4060
   */
4061
  'enableSlide',
4062
  /**
4063
   * @ngdoc method
4064
   * @name $ionicSlideBoxDelegate#previous
4065
   * @param {number=} speed The number of milliseconds the change should take.
4066
   * @description Go to the previous slide. Wraps around if at the beginning.
4067
   */
4068
  'previous',
4069
  /**
4070
   * @ngdoc method
4071
   * @name $ionicSlideBoxDelegate#next
4072
   * @param {number=} speed The number of milliseconds the change should take.
4073
   * @description Go to the next slide. Wraps around if at the end.
4074
   */
4075
  'next',
4076
  /**
4077
   * @ngdoc method
4078
   * @name $ionicSlideBoxDelegate#stop
4079
   * @description Stop sliding. The slideBox will not move again until
4080
   * explicitly told to do so.
4081
   */
4082
  'stop',
4083
  'autoPlay',
4084
  /**
4085
   * @ngdoc method
4086
   * @name $ionicSlideBoxDelegate#start
4087
   * @description Start sliding again if the slideBox was stopped.
4088
   */
4089
  'start',
4090
  /**
4091
   * @ngdoc method
4092
   * @name $ionicSlideBoxDelegate#currentIndex
4093
   * @returns number The index of the current slide.
4094
   */
4095
  'currentIndex',
4096
  'selected',
4097
  /**
4098
   * @ngdoc method
4099
   * @name $ionicSlideBoxDelegate#slidesCount
4100
   * @returns number The number of slides there are currently.
4101
   */
4102
  'slidesCount',
4103
  'count',
4104
  'loop'
4105
  /**
4106
   * @ngdoc method
4107
   * @name $ionicSlideBoxDelegate#$getByHandle
4108
   * @param {string} handle
4109
   * @returns `delegateInstance` A delegate instance that controls only the
4110
   * {@link ionic.directive:ionSlideBox} directives with `delegate-handle` matching
4111
   * the given handle.
4112
   *
4113
   * Example: `$ionicSlideBoxDelegate.$getByHandle('my-handle').stop();`
4114
   */
4115
]));
4116
 
4117
 
4118
/**
4119
 * @ngdoc service
4120
 * @name $ionicTabsDelegate
4121
 * @module ionic
4122
 *
4123
 * @description
4124
 * Delegate for controlling the {@link ionic.directive:ionTabs} directive.
4125
 *
4126
 * Methods called directly on the $ionicTabsDelegate service will control all ionTabs
4127
 * directives. Use the {@link ionic.service:$ionicTabsDelegate#$getByHandle $getByHandle}
4128
 * method to control specific ionTabs instances.
4129
 *
4130
 * @usage
4131
 *
4132
 * ```html
4133
 * <body ng-controller="MyCtrl">
4134
 *   <ion-tabs>
4135
 *
4136
 *     <ion-tab title="Tab 1">
4137
 *       Hello tab 1!
4138
 *       <button ng-click="selectTabWithIndex(1)">Select tab 2!</button>
4139
 *     </ion-tab>
4140
 *     <ion-tab title="Tab 2">Hello tab 2!</ion-tab>
4141
 *
4142
 *   </ion-tabs>
4143
 * </body>
4144
 * ```
4145
 * ```js
4146
 * function MyCtrl($scope, $ionicTabsDelegate) {
4147
 *   $scope.selectTabWithIndex = function(index) {
4148
 *     $ionicTabsDelegate.select(index);
4149
 *   }
4150
 * }
4151
 * ```
4152
 */
4153
IonicModule
4154
.service('$ionicTabsDelegate', ionic.DelegateService([
4155
  /**
4156
   * @ngdoc method
4157
   * @name $ionicTabsDelegate#select
4158
   * @description Select the tab matching the given index.
4159
   *
4160
   * @param {number} index Index of the tab to select.
4161
   */
4162
  'select',
4163
  /**
4164
   * @ngdoc method
4165
   * @name $ionicTabsDelegate#selectedIndex
4166
   * @returns `number` The index of the selected tab, or -1.
4167
   */
4168
  'selectedIndex'
4169
  /**
4170
   * @ngdoc method
4171
   * @name $ionicTabsDelegate#$getByHandle
4172
   * @param {string} handle
4173
   * @returns `delegateInstance` A delegate instance that controls only the
4174
   * {@link ionic.directive:ionTabs} directives with `delegate-handle` matching
4175
   * the given handle.
4176
   *
4177
   * Example: `$ionicTabsDelegate.$getByHandle('my-handle').select(0);`
4178
   */
4179
]));
4180
 
4181
 
4182
// closure to keep things neat
4183
(function() {
4184
  var templatesToCache = [];
4185
 
4186
/**
4187
 * @ngdoc service
4188
 * @name $ionicTemplateCache
4189
 * @module ionic
4190
 * @description A service that preemptively caches template files to eliminate transition flicker and boost performance.
4191
 * @usage
4192
 * State templates are cached automatically, but you can optionally cache other templates.
4193
 *
4194
 * ```js
4195
 * $ionicTemplateCache('myNgIncludeTemplate.html');
4196
 * ```
4197
 *
4198
 * Optionally disable all preemptive caching with the `$ionicConfigProvider` or individual states by setting `prefetchTemplate`
4199
 * in the `$state` definition
4200
 *
4201
 * ```js
4202
 *   angular.module('myApp', ['ionic'])
4203
 *   .config(function($stateProvider, $ionicConfigProvider) {
4204
 *
4205
 *     // disable preemptive template caching globally
4206
 *     $ionicConfigProvider.templates.prefetch(false);
4207
 *
4208
 *     // disable individual states
4209
 *     $stateProvider
4210
 *       .state('tabs', {
4211
 *         url: "/tab",
4212
 *         abstract: true,
4213
 *         prefetchTemplate: false,
4214
 *         templateUrl: "tabs-templates/tabs.html"
4215
 *       })
4216
 *       .state('tabs.home', {
4217
 *         url: "/home",
4218
 *         views: {
4219
 *           'home-tab': {
4220
 *             prefetchTemplate: false,
4221
 *             templateUrl: "tabs-templates/home.html",
4222
 *             controller: 'HomeTabCtrl'
4223
 *           }
4224
 *         }
4225
 *       });
4226
 *   });
4227
 * ```
4228
 */
4229
IonicModule
4230
.factory('$ionicTemplateCache', [
4231
'$http',
4232
'$templateCache',
4233
'$timeout',
4234
function($http, $templateCache, $timeout) {
4235
  var toCache = templatesToCache,
4236
      hasRun;
4237
 
4238
  function $ionicTemplateCache(templates) {
4239
    if (typeof templates === 'undefined') {
4240
      return run();
4241
    }
4242
    if (isString(templates)) {
4243
      templates = [templates];
4244
    }
4245
    forEach(templates, function(template) {
4246
      toCache.push(template);
4247
    });
4248
    if (hasRun) {
4249
      run();
4250
    }
4251
  }
4252
 
4253
  // run through methods - internal method
4254
  function run() {
4255
    var template;
4256
    $ionicTemplateCache._runCount++;
4257
 
4258
    hasRun = true;
4259
    // ignore if race condition already zeroed out array
4260
    if (toCache.length === 0) return;
4261
 
4262
    var i = 0;
4263
    while (i < 4 && (template = toCache.pop())) {
4264
      // note that inline templates are ignored by this request
4265
      if (isString(template)) $http.get(template, { cache: $templateCache });
4266
      i++;
4267
    }
4268
    // only preload 3 templates a second
4269
    if (toCache.length) {
4270
      $timeout(run, 1000);
4271
    }
4272
  }
4273
 
4274
  // exposing for testing
4275
  $ionicTemplateCache._runCount = 0;
4276
  // default method
4277
  return $ionicTemplateCache;
4278
}])
4279
 
4280
// Intercepts the $stateprovider.state() command to look for templateUrls that can be cached
4281
.config([
4282
'$stateProvider',
4283
'$ionicConfigProvider',
4284
function($stateProvider, $ionicConfigProvider) {
4285
  var stateProviderState = $stateProvider.state;
4286
  $stateProvider.state = function(stateName, definition) {
4287
    // don't even bother if it's disabled. note, another config may run after this, so it's not a catch-all
4288
    if (typeof definition === 'object') {
4289
      var enabled = definition.prefetchTemplate !== false && templatesToCache.length < $ionicConfigProvider.templates.maxPrefetch();
4290
      if (enabled && isString(definition.templateUrl)) templatesToCache.push(definition.templateUrl);
4291
      if (angular.isObject(definition.views)) {
4292
        for (var key in definition.views) {
4293
          enabled = definition.views[key].prefetchTemplate !== false && templatesToCache.length < $ionicConfigProvider.templates.maxPrefetch();
4294
          if (enabled && isString(definition.views[key].templateUrl)) templatesToCache.push(definition.views[key].templateUrl);
4295
        }
4296
      }
4297
    }
4298
    return stateProviderState.call($stateProvider, stateName, definition);
4299
  };
4300
}])
4301
 
4302
// process the templateUrls collected by the $stateProvider, adding them to the cache
4303
.run(['$ionicTemplateCache', function($ionicTemplateCache) {
4304
  $ionicTemplateCache();
4305
}]);
4306
 
4307
})();
4308
 
4309
IonicModule
4310
.factory('$ionicTemplateLoader', [
4311
  '$compile',
4312
  '$controller',
4313
  '$http',
4314
  '$q',
4315
  '$rootScope',
4316
  '$templateCache',
4317
function($compile, $controller, $http, $q, $rootScope, $templateCache) {
4318
 
4319
  return {
4320
    load: fetchTemplate,
4321
    compile: loadAndCompile
4322
  };
4323
 
4324
  function fetchTemplate(url) {
4325
    return $http.get(url, {cache: $templateCache})
4326
    .then(function(response) {
4327
      return response.data && response.data.trim();
4328
    });
4329
  }
4330
 
4331
  function loadAndCompile(options) {
4332
    options = extend({
4333
      template: '',
4334
      templateUrl: '',
4335
      scope: null,
4336
      controller: null,
4337
      locals: {},
4338
      appendTo: null
4339
    }, options || {});
4340
 
4341
    var templatePromise = options.templateUrl ?
4342
      this.load(options.templateUrl) :
4343
      $q.when(options.template);
4344
 
4345
    return templatePromise.then(function(template) {
4346
      var controller;
4347
      var scope = options.scope || $rootScope.$new();
4348
 
4349
      //Incase template doesn't have just one root element, do this
4350
      var element = jqLite('<div>').html(template).contents();
4351
 
4352
      if (options.controller) {
4353
        controller = $controller(
4354
          options.controller,
4355
          extend(options.locals, {
4356
            $scope: scope
4357
          })
4358
        );
4359
        element.children().data('$ngControllerController', controller);
4360
      }
4361
      if (options.appendTo) {
4362
        jqLite(options.appendTo).append(element);
4363
      }
4364
 
4365
      $compile(element)(scope);
4366
 
4367
      return {
4368
        element: element,
4369
        scope: scope
4370
      };
4371
    });
4372
  }
4373
 
4374
}]);
4375
 
4376
/**
4377
 * @private
4378
 * DEPRECATED, as of v1.0.0-beta14 -------
4379
 */
4380
IonicModule
4381
.factory('$ionicViewService', ['$ionicHistory', '$log', function($ionicHistory, $log) {
4382
 
4383
  function warn(oldMethod, newMethod) {
4384
    $log.warn('$ionicViewService' + oldMethod + ' is deprecated, please use $ionicHistory' + newMethod + ' instead: http://ionicframework.com/docs/nightly/api/service/$ionicHistory/');
4385
  }
4386
 
4387
  warn('', '');
4388
 
4389
  var methodsMap = {
4390
    getCurrentView: 'currentView',
4391
    getBackView: 'backView',
4392
    getForwardView: 'forwardView',
4393
    getCurrentStateName: 'currentStateName',
4394
    nextViewOptions: 'nextViewOptions',
4395
    clearHistory: 'clearHistory'
4396
  };
4397
 
4398
  forEach(methodsMap, function(newMethod, oldMethod) {
4399
    methodsMap[oldMethod] = function() {
4400
      warn('.' + oldMethod, '.' + newMethod);
4401
      return $ionicHistory[newMethod].apply(this, arguments);
4402
    };
4403
  });
4404
 
4405
  return methodsMap;
4406
 
4407
}]);
4408
 
4409
/**
4410
 * @private
4411
 * TODO document
4412
 */
4413
 
4414
IonicModule.factory('$ionicViewSwitcher', [
4415
  '$timeout',
4416
  '$document',
4417
  '$q',
4418
  '$ionicClickBlock',
4419
  '$ionicConfig',
4420
  '$ionicNavBarDelegate',
4421
function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDelegate) {
4422
 
4423
  var TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
4424
  var DATA_NO_CACHE = '$noCache';
4425
  var DATA_DESTROY_ELE = '$destroyEle';
4426
  var DATA_ELE_IDENTIFIER = '$eleId';
4427
  var DATA_VIEW_ACCESSED = '$accessed';
4428
  var DATA_FALLBACK_TIMER = '$fallbackTimer';
4429
  var DATA_VIEW = '$viewData';
4430
  var NAV_VIEW_ATTR = 'nav-view';
4431
  var VIEW_STATUS_ACTIVE = 'active';
4432
  var VIEW_STATUS_CACHED = 'cached';
4433
  var VIEW_STATUS_STAGED = 'stage';
4434
 
4435
  var transitionCounter = 0;
4436
  var nextTransition, nextDirection;
4437
  ionic.transition = ionic.transition || {};
4438
  ionic.transition.isActive = false;
4439
  var isActiveTimer;
4440
  var cachedAttr = ionic.DomUtil.cachedAttr;
4441
  var transitionPromises = [];
4442
  var defaultTimeout = 1100;
4443
 
4444
  var ionicViewSwitcher = {
4445
 
4446
    create: function(navViewCtrl, viewLocals, enteringView, leavingView, renderStart, renderEnd) {
4447
      // get a reference to an entering/leaving element if they exist
4448
      // loop through to see if the view is already in the navViewElement
4449
      var enteringEle, leavingEle;
4450
      var transitionId = ++transitionCounter;
4451
      var alreadyInDom;
4452
 
4453
      var switcher = {
4454
 
4455
        init: function(registerData, callback) {
4456
          ionicViewSwitcher.isTransitioning(true);
4457
 
4458
          switcher.loadViewElements(registerData);
4459
 
4460
          switcher.render(registerData, function() {
4461
            callback && callback();
4462
          });
4463
        },
4464
 
4465
        loadViewElements: function(registerData) {
4466
          var x, l, viewEle;
4467
          var viewElements = navViewCtrl.getViewElements();
4468
          var enteringEleIdentifier = getViewElementIdentifier(viewLocals, enteringView);
4469
          var navViewActiveEleId = navViewCtrl.activeEleId();
4470
 
4471
          for (x = 0, l = viewElements.length; x < l; x++) {
4472
            viewEle = viewElements.eq(x);
4473
 
4474
            if (viewEle.data(DATA_ELE_IDENTIFIER) === enteringEleIdentifier) {
4475
              // we found an existing element in the DOM that should be entering the view
4476
              if (viewEle.data(DATA_NO_CACHE)) {
4477
                // the existing element should not be cached, don't use it
4478
                viewEle.data(DATA_ELE_IDENTIFIER, enteringEleIdentifier + ionic.Utils.nextUid());
4479
                viewEle.data(DATA_DESTROY_ELE, true);
4480
 
4481
              } else {
4482
                enteringEle = viewEle;
4483
              }
4484
 
4485
            } else if (isDefined(navViewActiveEleId) && viewEle.data(DATA_ELE_IDENTIFIER) === navViewActiveEleId) {
4486
              leavingEle = viewEle;
4487
            }
4488
 
4489
            if (enteringEle && leavingEle) break;
4490
          }
4491
 
4492
          alreadyInDom = !!enteringEle;
4493
 
4494
          if (!alreadyInDom) {
4495
            // still no existing element to use
4496
            // create it using existing template/scope/locals
4497
            enteringEle = registerData.ele || ionicViewSwitcher.createViewEle(viewLocals);
4498
 
4499
            // existing elements in the DOM are looked up by their state name and state id
4500
            enteringEle.data(DATA_ELE_IDENTIFIER, enteringEleIdentifier);
4501
          }
4502
 
4503
          if (renderEnd) {
4504
            navViewCtrl.activeEleId(enteringEleIdentifier);
4505
          }
4506
 
4507
          registerData.ele = null;
4508
        },
4509
 
4510
        render: function(registerData, callback) {
4511
          if (alreadyInDom) {
4512
            // it was already found in the DOM, just reconnect the scope
4513
            ionic.Utils.reconnectScope(enteringEle.scope());
4514
 
4515
          } else {
4516
            // the entering element is not already in the DOM
4517
            // set that the entering element should be "staged" and its
4518
            // styles of where this element will go before it hits the DOM
4519
            navViewAttr(enteringEle, VIEW_STATUS_STAGED);
4520
 
4521
            var enteringData = getTransitionData(viewLocals, enteringEle, registerData.direction, enteringView);
4522
            var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none;
4523
            transitionFn(enteringEle, null, enteringData.direction, true).run(0);
4524
 
4525
            enteringEle.data(DATA_VIEW, {
4526
              viewId: enteringData.viewId,
4527
              historyId: enteringData.historyId,
4528
              stateName: enteringData.stateName,
4529
              stateParams: enteringData.stateParams
4530
            });
4531
 
4532
            // if the current state has cache:false
4533
            // or the element has cache-view="false" attribute
4534
            if (viewState(viewLocals).cache === false || viewState(viewLocals).cache === 'false' ||
4535
                enteringEle.attr('cache-view') == 'false' || $ionicConfig.views.maxCache() === 0) {
4536
              enteringEle.data(DATA_NO_CACHE, true);
4537
            }
4538
 
4539
            // append the entering element to the DOM, create a new scope and run link
4540
            var viewScope = navViewCtrl.appendViewElement(enteringEle, viewLocals);
4541
 
4542
            delete enteringData.direction;
4543
            delete enteringData.transition;
4544
            viewScope.$emit('$ionicView.loaded', enteringData);
4545
          }
4546
 
4547
          // update that this view was just accessed
4548
          enteringEle.data(DATA_VIEW_ACCESSED, Date.now());
4549
 
4550
          callback && callback();
4551
        },
4552
 
4553
        transition: function(direction, enableBack, allowAnimate) {
4554
          var deferred;
4555
          var enteringData = getTransitionData(viewLocals, enteringEle, direction, enteringView);
4556
          var leavingData = extend(extend({}, enteringData), getViewData(leavingView));
4557
          enteringData.transitionId = leavingData.transitionId = transitionId;
4558
          enteringData.fromCache = !!alreadyInDom;
4559
          enteringData.enableBack = !!enableBack;
4560
          enteringData.renderStart = renderStart;
4561
          enteringData.renderEnd = renderEnd;
4562
 
4563
          cachedAttr(enteringEle.parent(), 'nav-view-transition', enteringData.transition);
4564
          cachedAttr(enteringEle.parent(), 'nav-view-direction', enteringData.direction);
4565
 
4566
          // cancel any previous transition complete fallbacks
4567
          $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
4568
 
4569
          // get the transition ready and see if it'll animate
4570
          var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none;
4571
          var viewTransition = transitionFn(enteringEle, leavingEle, enteringData.direction,
4572
                                            enteringData.shouldAnimate && allowAnimate && renderEnd);
4573
 
4574
          if (viewTransition.shouldAnimate) {
4575
            // attach transitionend events (and fallback timer)
4576
            enteringEle.on(TRANSITIONEND_EVENT, completeOnTransitionEnd);
4577
            enteringEle.data(DATA_FALLBACK_TIMER, $timeout(transitionComplete, defaultTimeout));
4578
            $ionicClickBlock.show(defaultTimeout);
4579
          }
4580
 
4581
          if (renderStart) {
4582
            // notify the views "before" the transition starts
4583
            switcher.emit('before', enteringData, leavingData);
4584
 
4585
            // stage entering element, opacity 0, no transition duration
4586
            navViewAttr(enteringEle, VIEW_STATUS_STAGED);
4587
 
4588
            // render the elements in the correct location for their starting point
4589
            viewTransition.run(0);
4590
          }
4591
 
4592
          if (renderEnd) {
4593
            // create a promise so we can keep track of when all transitions finish
4594
            // only required if this transition should complete
4595
            deferred = $q.defer();
4596
            transitionPromises.push(deferred.promise);
4597
          }
4598
 
4599
          if (renderStart && renderEnd) {
4600
            // CSS "auto" transitioned, not manually transitioned
4601
            // wait a frame so the styles apply before auto transitioning
4602
            $timeout(onReflow, 16);
4603
 
4604
          } else if (!renderEnd) {
4605
            // just the start of a manual transition
4606
            // but it will not render the end of the transition
4607
            navViewAttr(enteringEle, 'entering');
4608
            navViewAttr(leavingEle, 'leaving');
4609
 
4610
            // return the transition run method so each step can be ran manually
4611
            return {
4612
              run: viewTransition.run,
4613
              cancel: function(shouldAnimate) {
4614
                if (shouldAnimate) {
4615
                  enteringEle.on(TRANSITIONEND_EVENT, cancelOnTransitionEnd);
4616
                  enteringEle.data(DATA_FALLBACK_TIMER, $timeout(cancelTransition, defaultTimeout));
4617
                  $ionicClickBlock.show(defaultTimeout);
4618
                } else {
4619
                  cancelTransition();
4620
                }
4621
                viewTransition.shouldAnimate = shouldAnimate;
4622
                viewTransition.run(0);
4623
                viewTransition = null;
4624
              }
4625
            };
4626
 
4627
          } else if (renderEnd) {
4628
            // just the end of a manual transition
4629
            // happens after the manual transition has completed
4630
            // and a full history change has happened
4631
            onReflow();
4632
          }
4633
 
4634
 
4635
          function onReflow() {
4636
            // remove that we're staging the entering element so it can auto transition
4637
            navViewAttr(enteringEle, viewTransition.shouldAnimate ? 'entering' : VIEW_STATUS_ACTIVE);
4638
            navViewAttr(leavingEle, viewTransition.shouldAnimate ? 'leaving' : VIEW_STATUS_CACHED);
4639
 
4640
            // start the auto transition and let the CSS take over
4641
            viewTransition.run(1);
4642
 
4643
            // trigger auto transitions on the associated nav bars
4644
            $ionicNavBarDelegate._instances.forEach(function(instance) {
4645
              instance.triggerTransitionStart(transitionId);
4646
            });
4647
 
4648
            if (!viewTransition.shouldAnimate) {
4649
              // no animated auto transition
4650
              transitionComplete();
4651
            }
4652
          }
4653
 
4654
          // Make sure that transitionend events bubbling up from children won't fire
4655
          // transitionComplete. Will only go forward if ev.target == the element listening.
4656
          function completeOnTransitionEnd(ev) {
4657
            if (ev.target !== this) return;
4658
            transitionComplete();
4659
          }
4660
          function transitionComplete() {
4661
            if (transitionComplete.x) return;
4662
            transitionComplete.x = true;
4663
 
4664
            enteringEle.off(TRANSITIONEND_EVENT, completeOnTransitionEnd);
4665
            $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
4666
            leavingEle && $timeout.cancel(leavingEle.data(DATA_FALLBACK_TIMER));
4667
 
4668
            // emit that the views have finished transitioning
4669
            // each parent nav-view will update which views are active and cached
4670
            switcher.emit('after', enteringData, leavingData);
4671
 
4672
            // resolve that this one transition (there could be many w/ nested views)
4673
            deferred && deferred.resolve(navViewCtrl);
4674
 
4675
            // the most recent transition added has completed and all the active
4676
            // transition promises should be added to the services array of promises
4677
            if (transitionId === transitionCounter) {
4678
              $q.all(transitionPromises).then(ionicViewSwitcher.transitionEnd);
4679
              switcher.cleanup(enteringData);
4680
            }
4681
 
4682
            // tell the nav bars that the transition has ended
4683
            $ionicNavBarDelegate._instances.forEach(function(instance) {
4684
              instance.triggerTransitionEnd();
4685
            });
4686
 
4687
            // remove any references that could cause memory issues
4688
            nextTransition = nextDirection = enteringView = leavingView = enteringEle = leavingEle = null;
4689
          }
4690
 
4691
          // Make sure that transitionend events bubbling up from children won't fire
4692
          // transitionComplete. Will only go forward if ev.target == the element listening.
4693
          function cancelOnTransitionEnd(ev) {
4694
            if (ev.target !== this) return;
4695
            cancelTransition();
4696
          }
4697
          function cancelTransition() {
4698
            navViewAttr(enteringEle, VIEW_STATUS_CACHED);
4699
            navViewAttr(leavingEle, VIEW_STATUS_ACTIVE);
4700
            enteringEle.off(TRANSITIONEND_EVENT, cancelOnTransitionEnd);
4701
            $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
4702
            ionicViewSwitcher.transitionEnd([navViewCtrl]);
4703
          }
4704
 
4705
        },
4706
 
4707
        emit: function(step, enteringData, leavingData) {
4708
          var scope = enteringEle.scope();
4709
          if (scope) {
4710
            scope.$emit('$ionicView.' + step + 'Enter', enteringData);
4711
            if (step == 'after') {
4712
              scope.$emit('$ionicView.enter', enteringData);
4713
            }
4714
          }
4715
 
4716
          if (leavingEle) {
4717
            scope = leavingEle.scope();
4718
            if (scope) {
4719
              scope.$emit('$ionicView.' + step + 'Leave', leavingData);
4720
              if (step == 'after') {
4721
                scope.$emit('$ionicView.leave', leavingData);
4722
              }
4723
            }
4724
 
4725
          } else if (scope && leavingData && leavingData.viewId) {
4726
            scope.$emit('$ionicNavView.' + step + 'Leave', leavingData);
4727
            if (step == 'after') {
4728
              scope.$emit('$ionicNavView.leave', leavingData);
4729
            }
4730
          }
4731
        },
4732
 
4733
        cleanup: function(transData) {
4734
          // check if any views should be removed
4735
          if (leavingEle && transData.direction == 'back' && !$ionicConfig.views.forwardCache()) {
4736
            // if they just navigated back we can destroy the forward view
4737
            // do not remove forward views if cacheForwardViews config is true
4738
            destroyViewEle(leavingEle);
4739
          }
4740
 
4741
          var viewElements = navViewCtrl.getViewElements();
4742
          var viewElementsLength = viewElements.length;
4743
          var x, viewElement;
4744
          var removeOldestAccess = (viewElementsLength - 1) > $ionicConfig.views.maxCache();
4745
          var removableEle;
4746
          var oldestAccess = Date.now();
4747
 
4748
          for (x = 0; x < viewElementsLength; x++) {
4749
            viewElement = viewElements.eq(x);
4750
 
4751
            if (removeOldestAccess && viewElement.data(DATA_VIEW_ACCESSED) < oldestAccess) {
4752
              // remember what was the oldest element to be accessed so it can be destroyed
4753
              oldestAccess = viewElement.data(DATA_VIEW_ACCESSED);
4754
              removableEle = viewElements.eq(x);
4755
 
4756
            } else if (viewElement.data(DATA_DESTROY_ELE) && navViewAttr(viewElement) != VIEW_STATUS_ACTIVE) {
4757
              destroyViewEle(viewElement);
4758
            }
4759
          }
4760
 
4761
          destroyViewEle(removableEle);
4762
 
4763
          if (enteringEle.data(DATA_NO_CACHE)) {
4764
            enteringEle.data(DATA_DESTROY_ELE, true);
4765
          }
4766
        },
4767
 
4768
        enteringEle: function() { return enteringEle; },
4769
        leavingEle: function() { return leavingEle; }
4770
 
4771
      };
4772
 
4773
      return switcher;
4774
    },
4775
 
4776
    transitionEnd: function(navViewCtrls) {
4777
      forEach(navViewCtrls, function(navViewCtrl) {
4778
        navViewCtrl.transitionEnd();
4779
      });
4780
 
4781
      ionicViewSwitcher.isTransitioning(false);
4782
      $ionicClickBlock.hide();
4783
      transitionPromises = [];
4784
    },
4785
 
4786
    nextTransition: function(val) {
4787
      nextTransition = val;
4788
    },
4789
 
4790
    nextDirection: function(val) {
4791
      nextDirection = val;
4792
    },
4793
 
4794
    isTransitioning: function(val) {
4795
      if (arguments.length) {
4796
        ionic.transition.isActive = !!val;
4797
        $timeout.cancel(isActiveTimer);
4798
        if (val) {
4799
          isActiveTimer = $timeout(function() {
4800
            ionicViewSwitcher.isTransitioning(false);
4801
          }, 999);
4802
        }
4803
      }
4804
      return ionic.transition.isActive;
4805
    },
4806
 
4807
    createViewEle: function(viewLocals) {
4808
      var containerEle = $document[0].createElement('div');
4809
      if (viewLocals && viewLocals.$template) {
4810
        containerEle.innerHTML = viewLocals.$template;
4811
        if (containerEle.children.length === 1) {
4812
          containerEle.children[0].classList.add('pane');
4813
          return jqLite(containerEle.children[0]);
4814
        }
4815
      }
4816
      containerEle.className = "pane";
4817
      return jqLite(containerEle);
4818
    },
4819
 
4820
    viewEleIsActive: function(viewEle, isActiveAttr) {
4821
      navViewAttr(viewEle, isActiveAttr ? VIEW_STATUS_ACTIVE : VIEW_STATUS_CACHED);
4822
    },
4823
 
4824
    getTransitionData: getTransitionData,
4825
    navViewAttr: navViewAttr,
4826
    destroyViewEle: destroyViewEle
4827
 
4828
  };
4829
 
4830
  return ionicViewSwitcher;
4831
 
4832
 
4833
  function getViewElementIdentifier(locals, view) {
4834
    if (viewState(locals)['abstract']) return viewState(locals).name;
4835
    if (view) return view.stateId || view.viewId;
4836
    return ionic.Utils.nextUid();
4837
  }
4838
 
4839
  function viewState(locals) {
4840
    return locals && locals.$$state && locals.$$state.self || {};
4841
  }
4842
 
4843
  function getTransitionData(viewLocals, enteringEle, direction, view) {
4844
    // Priority
4845
    // 1) attribute directive on the button/link to this view
4846
    // 2) entering element's attribute
4847
    // 3) entering view's $state config property
4848
    // 4) view registration data
4849
    // 5) global config
4850
    // 6) fallback value
4851
 
4852
    var state = viewState(viewLocals);
4853
    var viewTransition = nextTransition || cachedAttr(enteringEle, 'view-transition') || state.viewTransition || $ionicConfig.views.transition() || 'ios';
4854
    var navBarTransition = $ionicConfig.navBar.transition();
4855
    direction = nextDirection || cachedAttr(enteringEle, 'view-direction') || state.viewDirection || direction || 'none';
4856
 
4857
    return extend(getViewData(view), {
4858
      transition: viewTransition,
4859
      navBarTransition: navBarTransition === 'view' ? viewTransition : navBarTransition,
4860
      direction: direction,
4861
      shouldAnimate: (viewTransition !== 'none' && direction !== 'none')
4862
    });
4863
  }
4864
 
4865
  function getViewData(view) {
4866
    view = view || {};
4867
    return {
4868
      viewId: view.viewId,
4869
      historyId: view.historyId,
4870
      stateId: view.stateId,
4871
      stateName: view.stateName,
4872
      stateParams: view.stateParams
4873
    };
4874
  }
4875
 
4876
  function navViewAttr(ele, value) {
4877
    if (arguments.length > 1) {
4878
      cachedAttr(ele, NAV_VIEW_ATTR, value);
4879
    } else {
4880
      return cachedAttr(ele, NAV_VIEW_ATTR);
4881
    }
4882
  }
4883
 
4884
  function destroyViewEle(ele) {
4885
    // we found an element that should be removed
4886
    // destroy its scope, then remove the element
4887
    if (ele && ele.length) {
4888
      var viewScope = ele.scope();
4889
      if (viewScope) {
4890
        viewScope.$emit('$ionicView.unloaded', ele.data(DATA_VIEW));
4891
        viewScope.$destroy();
4892
      }
4893
      ele.remove();
4894
    }
4895
  }
4896
 
4897
}]);
4898
 
4899
/**
4900
 * @private
4901
 * Parts of Ionic requires that $scope data is attached to the element.
4902
 * We do not want to disable adding $scope data to the $element when
4903
 * $compileProvider.debugInfoEnabled(false) is used.
4904
 */
4905
IonicModule.config(['$provide', function($provide) {
4906
  $provide.decorator('$compile', ['$delegate', function($compile) {
4907
     $compile.$$addScopeInfo = function $$addScopeInfo($element, scope, isolated, noTemplate) {
4908
       var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
4909
       $element.data(dataName, scope);
4910
     };
4911
     return $compile;
4912
  }]);
4913
}]);
4914
 
4915
/**
4916
 * @private
4917
 */
4918
IonicModule.config([
4919
  '$provide',
4920
function($provide) {
4921
  function $LocationDecorator($location, $timeout) {
4922
 
4923
    $location.__hash = $location.hash;
4924
    //Fix: when window.location.hash is set, the scrollable area
4925
    //found nearest to body's scrollTop is set to scroll to an element
4926
    //with that ID.
4927
    $location.hash = function(value) {
4928
      if (isDefined(value)) {
4929
        $timeout(function() {
4930
          var scroll = document.querySelector('.scroll-content');
4931
          if (scroll) {
4932
            scroll.scrollTop = 0;
4933
          }
4934
        }, 0, false);
4935
      }
4936
      return $location.__hash(value);
4937
    };
4938
 
4939
    return $location;
4940
  }
4941
 
4942
  $provide.decorator('$location', ['$delegate', '$timeout', $LocationDecorator]);
4943
}]);
4944
 
4945
IonicModule
4946
 
4947
.controller('$ionicHeaderBar', [
4948
  '$scope',
4949
  '$element',
4950
  '$attrs',
4951
  '$q',
4952
  '$ionicConfig',
4953
  '$ionicHistory',
4954
function($scope, $element, $attrs, $q, $ionicConfig, $ionicHistory) {
4955
  var TITLE = 'title';
4956
  var BACK_TEXT = 'back-text';
4957
  var BACK_BUTTON = 'back-button';
4958
  var DEFAULT_TITLE = 'default-title';
4959
  var PREVIOUS_TITLE = 'previous-title';
4960
  var HIDE = 'hide';
4961
 
4962
  var self = this;
4963
  var titleText = '';
4964
  var previousTitleText = '';
4965
  var titleLeft = 0;
4966
  var titleRight = 0;
4967
  var titleCss = '';
4968
  var isBackEnabled = false;
4969
  var isBackShown = true;
4970
  var isNavBackShown = true;
4971
  var isBackElementShown = false;
4972
  var titleTextWidth = 0;
4973
 
4974
 
4975
  self.beforeEnter = function(viewData) {
4976
    $scope.$broadcast('$ionicView.beforeEnter', viewData);
4977
  };
4978
 
4979
 
4980
  self.title = function(newTitleText) {
4981
    if (arguments.length && newTitleText !== titleText) {
4982
      getEle(TITLE).innerHTML = newTitleText;
4983
      titleText = newTitleText;
4984
      titleTextWidth = 0;
4985
    }
4986
    return titleText;
4987
  };
4988
 
4989
 
4990
  self.enableBack = function(shouldEnable, disableReset) {
4991
    // whether or not the back button show be visible, according
4992
    // to the navigation and history
4993
    if (arguments.length) {
4994
      isBackEnabled = shouldEnable;
4995
      if (!disableReset) self.updateBackButton();
4996
    }
4997
    return isBackEnabled;
4998
  };
4999
 
5000
 
5001
  self.showBack = function(shouldShow, disableReset) {
5002
    // different from enableBack() because this will always have the back
5003
    // visually hidden if false, even if the history says it should show
5004
    if (arguments.length) {
5005
      isBackShown = shouldShow;
5006
      if (!disableReset) self.updateBackButton();
5007
    }
5008
    return isBackShown;
5009
  };
5010
 
5011
 
5012
  self.showNavBack = function(shouldShow) {
5013
    // different from showBack() because this is for the entire nav bar's
5014
    // setting for all of it's child headers. For internal use.
5015
    isNavBackShown = shouldShow;
5016
    self.updateBackButton();
5017
  };
5018
 
5019
 
5020
  self.updateBackButton = function() {
5021
    var ele;
5022
    if ((isBackShown && isNavBackShown && isBackEnabled) !== isBackElementShown) {
5023
      isBackElementShown = isBackShown && isNavBackShown && isBackEnabled;
5024
      ele = getEle(BACK_BUTTON);
5025
      ele && ele.classList[ isBackElementShown ? 'remove' : 'add' ](HIDE);
5026
    }
5027
 
5028
    if (isBackEnabled) {
5029
      ele = ele || getEle(BACK_BUTTON);
5030
      if (ele) {
5031
        if (self.backButtonIcon !== $ionicConfig.backButton.icon()) {
5032
          ele = getEle(BACK_BUTTON + ' .icon');
5033
          if (ele) {
5034
            self.backButtonIcon = $ionicConfig.backButton.icon();
5035
            ele.className = 'icon ' + self.backButtonIcon;
5036
          }
5037
        }
5038
 
5039
        if (self.backButtonText !== $ionicConfig.backButton.text()) {
5040
          ele = getEle(BACK_BUTTON + ' .back-text');
5041
          if (ele) {
5042
            ele.textContent = self.backButtonText = $ionicConfig.backButton.text();
5043
          }
5044
        }
5045
      }
5046
    }
5047
  };
5048
 
5049
 
5050
  self.titleTextWidth = function() {
5051
    if (!titleTextWidth) {
5052
      var bounds = ionic.DomUtil.getTextBounds(getEle(TITLE));
5053
      titleTextWidth = Math.min(bounds && bounds.width || 30);
5054
    }
5055
    return titleTextWidth;
5056
  };
5057
 
5058
 
5059
  self.titleWidth = function() {
5060
    var titleWidth = self.titleTextWidth();
5061
    var offsetWidth = getEle(TITLE).offsetWidth;
5062
    if (offsetWidth < titleWidth) {
5063
      titleWidth = offsetWidth + (titleLeft - titleRight - 5);
5064
    }
5065
    return titleWidth;
5066
  };
5067
 
5068
 
5069
  self.titleTextX = function() {
5070
    return ($element[0].offsetWidth / 2) - (self.titleWidth() / 2);
5071
  };
5072
 
5073
 
5074
  self.titleLeftRight = function() {
5075
    return titleLeft - titleRight;
5076
  };
5077
 
5078
 
5079
  self.backButtonTextLeft = function() {
5080
    var offsetLeft = 0;
5081
    var ele = getEle(BACK_TEXT);
5082
    while (ele) {
5083
      offsetLeft += ele.offsetLeft;
5084
      ele = ele.parentElement;
5085
    }
5086
    return offsetLeft;
5087
  };
5088
 
5089
 
5090
  self.resetBackButton = function(viewData) {
5091
    if ($ionicConfig.backButton.previousTitleText()) {
5092
      var previousTitleEle = getEle(PREVIOUS_TITLE);
5093
      if (previousTitleEle) {
5094
        previousTitleEle.classList.remove(HIDE);
5095
 
5096
        var view = (viewData && $ionicHistory.getViewById(viewData.viewId));
5097
        var newPreviousTitleText = $ionicHistory.backTitle(view);
5098
 
5099
        if (newPreviousTitleText !== previousTitleText) {
5100
          previousTitleText = previousTitleEle.innerHTML = newPreviousTitleText;
5101
        }
5102
      }
5103
      var defaultTitleEle = getEle(DEFAULT_TITLE);
5104
      if (defaultTitleEle) {
5105
        defaultTitleEle.classList.remove(HIDE);
5106
      }
5107
    }
5108
  };
5109
 
5110
 
5111
  self.align = function(textAlign) {
5112
    var titleEle = getEle(TITLE);
5113
 
5114
    textAlign = textAlign || $attrs.alignTitle || $ionicConfig.navBar.alignTitle();
5115
 
5116
    var widths = self.calcWidths(textAlign, false);
5117
 
5118
    if (isBackShown && previousTitleText && $ionicConfig.backButton.previousTitleText()) {
5119
      var previousTitleWidths = self.calcWidths(textAlign, true);
5120
 
5121
      var availableTitleWidth = $element[0].offsetWidth - previousTitleWidths.titleLeft - previousTitleWidths.titleRight;
5122
 
5123
      if (self.titleTextWidth() <= availableTitleWidth) {
5124
        widths = previousTitleWidths;
5125
      }
5126
    }
5127
 
5128
    return self.updatePositions(titleEle, widths.titleLeft, widths.titleRight, widths.buttonsLeft, widths.buttonsRight, widths.css, widths.showPrevTitle);
5129
  };
5130
 
5131
 
5132
  self.calcWidths = function(textAlign, isPreviousTitle) {
5133
    var titleEle = getEle(TITLE);
5134
    var backBtnEle = getEle(BACK_BUTTON);
5135
    var x, y, z, b, c, d, childSize, bounds;
5136
    var childNodes = $element[0].childNodes;
5137
    var buttonsLeft = 0;
5138
    var buttonsRight = 0;
5139
    var isCountRightOfTitle;
5140
    var updateTitleLeft = 0;
5141
    var updateTitleRight = 0;
5142
    var updateCss = '';
5143
    var backButtonWidth = 0;
5144
 
5145
    // Compute how wide the left children are
5146
    // Skip all titles (there may still be two titles, one leaving the dom)
5147
    // Once we encounter a titleEle, realize we are now counting the right-buttons, not left
5148
    for (x = 0; x < childNodes.length; x++) {
5149
      c = childNodes[x];
5150
 
5151
      childSize = 0;
5152
      if (c.nodeType == 1) {
5153
        // element node
5154
        if (c === titleEle) {
5155
          isCountRightOfTitle = true;
5156
          continue;
5157
        }
5158
 
5159
        if (c.classList.contains(HIDE)) {
5160
          continue;
5161
        }
5162
 
5163
        if (isBackShown && c === backBtnEle) {
5164
 
5165
          for (y = 0; y < c.childNodes.length; y++) {
5166
            b = c.childNodes[y];
5167
 
5168
            if (b.nodeType == 1) {
5169
 
5170
              if (b.classList.contains(BACK_TEXT)) {
5171
                for (z = 0; z < b.children.length; z++) {
5172
                  d = b.children[z];
5173
 
5174
                  if (isPreviousTitle) {
5175
                    if (d.classList.contains(DEFAULT_TITLE)) continue;
5176
                    backButtonWidth += d.offsetWidth;
5177
                  } else {
5178
                    if (d.classList.contains(PREVIOUS_TITLE)) continue;
5179
                    backButtonWidth += d.offsetWidth;
5180
                  }
5181
                }
5182
 
5183
              } else {
5184
                backButtonWidth += b.offsetWidth;
5185
              }
5186
 
5187
            } else if (b.nodeType == 3 && b.nodeValue.trim()) {
5188
              bounds = ionic.DomUtil.getTextBounds(b);
5189
              backButtonWidth += bounds && bounds.width || 0;
5190
            }
5191
 
5192
          }
5193
          childSize = backButtonWidth || c.offsetWidth;
5194
 
5195
        } else {
5196
          // not the title, not the back button, not a hidden element
5197
          childSize = c.offsetWidth;
5198
        }
5199
 
5200
      } else if (c.nodeType == 3 && c.nodeValue.trim()) {
5201
        // text node
5202
        bounds = ionic.DomUtil.getTextBounds(c);
5203
        childSize = bounds && bounds.width || 0;
5204
      }
5205
 
5206
      if (isCountRightOfTitle) {
5207
        buttonsRight += childSize;
5208
      } else {
5209
        buttonsLeft += childSize;
5210
      }
5211
    }
5212
 
5213
    // Size and align the header titleEle based on the sizes of the left and
5214
    // right children, and the desired alignment mode
5215
    if (textAlign == 'left') {
5216
      updateCss = 'title-left';
5217
      if (buttonsLeft) {
5218
        updateTitleLeft = buttonsLeft + 15;
5219
      }
5220
      if (buttonsRight) {
5221
        updateTitleRight = buttonsRight + 15;
5222
      }
5223
 
5224
    } else if (textAlign == 'right') {
5225
      updateCss = 'title-right';
5226
      if (buttonsLeft) {
5227
        updateTitleLeft = buttonsLeft + 15;
5228
      }
5229
      if (buttonsRight) {
5230
        updateTitleRight = buttonsRight + 15;
5231
      }
5232
 
5233
    } else {
5234
      // center the default
5235
      var margin = Math.max(buttonsLeft, buttonsRight) + 10;
5236
      if (margin > 10) {
5237
        updateTitleLeft = updateTitleRight = margin;
5238
      }
5239
    }
5240
 
5241
    return {
5242
      backButtonWidth: backButtonWidth,
5243
      buttonsLeft: buttonsLeft,
5244
      buttonsRight: buttonsRight,
5245
      titleLeft: updateTitleLeft,
5246
      titleRight: updateTitleRight,
5247
      showPrevTitle: isPreviousTitle,
5248
      css: updateCss
5249
    };
5250
  };
5251
 
5252
 
5253
  self.updatePositions = function(titleEle, updateTitleLeft, updateTitleRight, buttonsLeft, buttonsRight, updateCss, showPreviousTitle) {
5254
    var deferred = $q.defer();
5255
 
5256
    // only make DOM updates when there are actual changes
5257
    if (titleEle) {
5258
      if (updateTitleLeft !== titleLeft) {
5259
        titleEle.style.left = updateTitleLeft ? updateTitleLeft + 'px' : '';
5260
        titleLeft = updateTitleLeft;
5261
      }
5262
      if (updateTitleRight !== titleRight) {
5263
        titleEle.style.right = updateTitleRight ? updateTitleRight + 'px' : '';
5264
        titleRight = updateTitleRight;
5265
      }
5266
 
5267
      if (updateCss !== titleCss) {
5268
        updateCss && titleEle.classList.add(updateCss);
5269
        titleCss && titleEle.classList.remove(titleCss);
5270
        titleCss = updateCss;
5271
      }
5272
    }
5273
 
5274
    if ($ionicConfig.backButton.previousTitleText()) {
5275
      var prevTitle = getEle(PREVIOUS_TITLE);
5276
      var defaultTitle = getEle(DEFAULT_TITLE);
5277
 
5278
      prevTitle && prevTitle.classList[ showPreviousTitle ? 'remove' : 'add'](HIDE);
5279
      defaultTitle && defaultTitle.classList[ showPreviousTitle ? 'add' : 'remove'](HIDE);
5280
    }
5281
 
5282
    ionic.requestAnimationFrame(function() {
5283
      if (titleEle && titleEle.offsetWidth + 10 < titleEle.scrollWidth) {
5284
        var minRight = buttonsRight + 5;
5285
        var testRight = $element[0].offsetWidth - titleLeft - self.titleTextWidth() - 20;
5286
        updateTitleRight = testRight < minRight ? minRight : testRight;
5287
        if (updateTitleRight !== titleRight) {
5288
          titleEle.style.right = updateTitleRight + 'px';
5289
          titleRight = updateTitleRight;
5290
        }
5291
      }
5292
      deferred.resolve();
5293
    });
5294
 
5295
    return deferred.promise;
5296
  };
5297
 
5298
 
5299
  self.setCss = function(elementClassname, css) {
5300
    ionic.DomUtil.cachedStyles(getEle(elementClassname), css);
5301
  };
5302
 
5303
 
5304
  var eleCache = {};
5305
  function getEle(className) {
5306
    if (!eleCache[className]) {
5307
      eleCache[className] = $element[0].querySelector('.' + className);
5308
    }
5309
    return eleCache[className];
5310
  }
5311
 
5312
 
5313
  $scope.$on('$destroy', function() {
5314
    for (var n in eleCache) eleCache[n] = null;
5315
  });
5316
 
5317
}]);
5318
 
5319
IonicModule
5320
.controller('$ionInfiniteScroll', [
5321
  '$scope',
5322
  '$attrs',
5323
  '$element',
5324
  '$timeout',
5325
function($scope, $attrs, $element, $timeout) {
5326
  var self = this;
5327
  self.isLoading = false;
5328
 
5329
  $scope.icon = function() {
5330
    return isDefined($attrs.icon) ? $attrs.icon : 'ion-load-d';
5331
  };
5332
 
5333
  $scope.spinner = function() {
5334
    return isDefined($attrs.spinner) ? $attrs.spinner : '';
5335
  };
5336
 
5337
  $scope.$on('scroll.infiniteScrollComplete', function() {
5338
    finishInfiniteScroll();
5339
  });
5340
 
5341
  $scope.$on('$destroy', function() {
5342
    if (self.scrollCtrl && self.scrollCtrl.$element) self.scrollCtrl.$element.off('scroll', self.checkBounds);
5343
    if (self.scrollEl && self.scrollEl.removeEventListener) {
5344
      self.scrollEl.removeEventListener('scroll', self.checkBounds);
5345
    }
5346
  });
5347
 
5348
  // debounce checking infinite scroll events
5349
  self.checkBounds = ionic.Utils.throttle(checkInfiniteBounds, 300);
5350
 
5351
  function onInfinite() {
5352
    ionic.requestAnimationFrame(function() {
5353
      $element[0].classList.add('active');
5354
    });
5355
    self.isLoading = true;
5356
    $scope.$parent && $scope.$parent.$apply($attrs.onInfinite || '');
5357
  }
5358
 
5359
  function finishInfiniteScroll() {
5360
    ionic.requestAnimationFrame(function() {
5361
      $element[0].classList.remove('active');
5362
    });
5363
    $timeout(function() {
5364
      if (self.jsScrolling) self.scrollView.resize();
5365
      // only check bounds again immediately if the page isn't cached (scroll el has height)
5366
      if ((self.jsScrolling && self.scrollView.__container && self.scrollView.__container.offsetHeight > 0) ||
5367
      !self.jsScrolling) {
5368
        self.checkBounds();
5369
      }
5370
    }, 30, false);
5371
    self.isLoading = false;
5372
  }
5373
 
5374
  // check if we've scrolled far enough to trigger an infinite scroll
5375
  function checkInfiniteBounds() {
5376
    if (self.isLoading) return;
5377
    var maxScroll = {};
5378
 
5379
    if (self.jsScrolling) {
5380
      maxScroll = self.getJSMaxScroll();
5381
      var scrollValues = self.scrollView.getValues();
5382
      if ((maxScroll.left !== -1 && scrollValues.left >= maxScroll.left) ||
5383
        (maxScroll.top !== -1 && scrollValues.top >= maxScroll.top)) {
5384
        onInfinite();
5385
      }
5386
    } else {
5387
      maxScroll = self.getNativeMaxScroll();
5388
      if ((
5389
        maxScroll.left !== -1 &&
5390
        self.scrollEl.scrollLeft >= maxScroll.left - self.scrollEl.clientWidth
5391
        ) || (
5392
        maxScroll.top !== -1 &&
5393
        self.scrollEl.scrollTop >= maxScroll.top - self.scrollEl.clientHeight
5394
        )) {
5395
        onInfinite();
5396
      }
5397
    }
5398
  }
5399
 
5400
  // determine the threshold at which we should fire an infinite scroll
5401
  // note: this gets processed every scroll event, can it be cached?
5402
  self.getJSMaxScroll = function() {
5403
    var maxValues = self.scrollView.getScrollMax();
5404
    return {
5405
      left: self.scrollView.options.scrollingX ?
5406
        calculateMaxValue(maxValues.left) :
5407
        -1,
5408
      top: self.scrollView.options.scrollingY ?
5409
        calculateMaxValue(maxValues.top) :
5410
        -1
5411
    };
5412
  };
5413
 
5414
  self.getNativeMaxScroll = function() {
5415
    var maxValues = {
5416
      left: self.scrollEl.scrollWidth,
5417
      top: self.scrollEl.scrollHeight
5418
    };
5419
    var computedStyle = window.getComputedStyle(self.scrollEl) || {};
5420
    return {
5421
      left: computedStyle.overflowX === 'scroll' ||
5422
      computedStyle.overflowX === 'auto' ||
5423
      self.scrollEl.style['overflow-x'] === 'scroll' ?
5424
        calculateMaxValue(maxValues.left) : -1,
5425
      top: computedStyle.overflowY === 'scroll' ||
5426
      computedStyle.overflowY === 'auto' ||
5427
      self.scrollEl.style['overflow-y'] === 'scroll' ?
5428
        calculateMaxValue(maxValues.top) : -1
5429
    };
5430
  };
5431
 
5432
  // determine pixel refresh distance based on % or value
5433
  function calculateMaxValue(maximum) {
5434
    var distance = ($attrs.distance || '2.5%').trim();
5435
    var isPercent = distance.indexOf('%') !== -1;
5436
    return isPercent ?
5437
    maximum * (1 - parseFloat(distance) / 100) :
5438
    maximum - parseFloat(distance);
5439
  }
5440
 
5441
  //for testing
5442
  self.__finishInfiniteScroll = finishInfiniteScroll;
5443
 
5444
}]);
5445
 
5446
/**
5447
 * @ngdoc service
5448
 * @name $ionicListDelegate
5449
 * @module ionic
5450
 *
5451
 * @description
5452
 * Delegate for controlling the {@link ionic.directive:ionList} directive.
5453
 *
5454
 * Methods called directly on the $ionicListDelegate service will control all lists.
5455
 * Use the {@link ionic.service:$ionicListDelegate#$getByHandle $getByHandle}
5456
 * method to control specific ionList instances.
5457
 *
5458
 * @usage
5459
 *
5460
 * ````html
5461
 * <ion-content ng-controller="MyCtrl">
5462
 *   <button class="button" ng-click="showDeleteButtons()"></button>
5463
 *   <ion-list>
5464
 *     <ion-item ng-repeat="i in items">
5465
 *       {% raw %}Hello, {{i}}!{% endraw %}
5466
 *       <ion-delete-button class="ion-minus-circled"></ion-delete-button>
5467
 *     </ion-item>
5468
 *   </ion-list>
5469
 * </ion-content>
5470
 * ```
5471
 * ```js
5472
 * function MyCtrl($scope, $ionicListDelegate) {
5473
 *   $scope.showDeleteButtons = function() {
5474
 *     $ionicListDelegate.showDelete(true);
5475
 *   };
5476
 * }
5477
 * ```
5478
 */
5479
IonicModule.service('$ionicListDelegate', ionic.DelegateService([
5480
  /**
5481
   * @ngdoc method
5482
   * @name $ionicListDelegate#showReorder
5483
   * @param {boolean=} showReorder Set whether or not this list is showing its reorder buttons.
5484
   * @returns {boolean} Whether the reorder buttons are shown.
5485
   */
5486
  'showReorder',
5487
  /**
5488
   * @ngdoc method
5489
   * @name $ionicListDelegate#showDelete
5490
   * @param {boolean=} showDelete Set whether or not this list is showing its delete buttons.
5491
   * @returns {boolean} Whether the delete buttons are shown.
5492
   */
5493
  'showDelete',
5494
  /**
5495
   * @ngdoc method
5496
   * @name $ionicListDelegate#canSwipeItems
5497
   * @param {boolean=} canSwipeItems Set whether or not this list is able to swipe to show
5498
   * option buttons.
5499
   * @returns {boolean} Whether the list is able to swipe to show option buttons.
5500
   */
5501
  'canSwipeItems',
5502
  /**
5503
   * @ngdoc method
5504
   * @name $ionicListDelegate#closeOptionButtons
5505
   * @description Closes any option buttons on the list that are swiped open.
5506
   */
5507
  'closeOptionButtons'
5508
  /**
5509
   * @ngdoc method
5510
   * @name $ionicListDelegate#$getByHandle
5511
   * @param {string} handle
5512
   * @returns `delegateInstance` A delegate instance that controls only the
5513
   * {@link ionic.directive:ionList} directives with `delegate-handle` matching
5514
   * the given handle.
5515
   *
5516
   * Example: `$ionicListDelegate.$getByHandle('my-handle').showReorder(true);`
5517
   */
5518
]))
5519
 
5520
.controller('$ionicList', [
5521
  '$scope',
5522
  '$attrs',
5523
  '$ionicListDelegate',
5524
  '$ionicHistory',
5525
function($scope, $attrs, $ionicListDelegate, $ionicHistory) {
5526
  var self = this;
5527
  var isSwipeable = true;
5528
  var isReorderShown = false;
5529
  var isDeleteShown = false;
5530
 
5531
  var deregisterInstance = $ionicListDelegate._registerInstance(
5532
    self, $attrs.delegateHandle, function() {
5533
      return $ionicHistory.isActiveScope($scope);
5534
    }
5535
  );
5536
  $scope.$on('$destroy', deregisterInstance);
5537
 
5538
  self.showReorder = function(show) {
5539
    if (arguments.length) {
5540
      isReorderShown = !!show;
5541
    }
5542
    return isReorderShown;
5543
  };
5544
 
5545
  self.showDelete = function(show) {
5546
    if (arguments.length) {
5547
      isDeleteShown = !!show;
5548
    }
5549
    return isDeleteShown;
5550
  };
5551
 
5552
  self.canSwipeItems = function(can) {
5553
    if (arguments.length) {
5554
      isSwipeable = !!can;
5555
    }
5556
    return isSwipeable;
5557
  };
5558
 
5559
  self.closeOptionButtons = function() {
5560
    self.listView && self.listView.clearDragEffects();
5561
  };
5562
}]);
5563
 
5564
IonicModule
5565
 
5566
.controller('$ionicNavBar', [
5567
  '$scope',
5568
  '$element',
5569
  '$attrs',
5570
  '$compile',
5571
  '$timeout',
5572
  '$ionicNavBarDelegate',
5573
  '$ionicConfig',
5574
  '$ionicHistory',
5575
function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $ionicConfig, $ionicHistory) {
5576
 
5577
  var CSS_HIDE = 'hide';
5578
  var DATA_NAV_BAR_CTRL = '$ionNavBarController';
5579
  var PRIMARY_BUTTONS = 'primaryButtons';
5580
  var SECONDARY_BUTTONS = 'secondaryButtons';
5581
  var BACK_BUTTON = 'backButton';
5582
  var ITEM_TYPES = 'primaryButtons secondaryButtons leftButtons rightButtons title'.split(' ');
5583
 
5584
  var self = this;
5585
  var headerBars = [];
5586
  var navElementHtml = {};
5587
  var isVisible = true;
5588
  var queuedTransitionStart, queuedTransitionEnd, latestTransitionId;
5589
 
5590
  $element.parent().data(DATA_NAV_BAR_CTRL, self);
5591
 
5592
  var delegateHandle = $attrs.delegateHandle || 'navBar' + ionic.Utils.nextUid();
5593
 
5594
  var deregisterInstance = $ionicNavBarDelegate._registerInstance(self, delegateHandle);
5595
 
5596
 
5597
  self.init = function() {
5598
    $element.addClass('nav-bar-container');
5599
    ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', $ionicConfig.views.transition());
5600
 
5601
    // create two nav bar blocks which will trade out which one is shown
5602
    self.createHeaderBar(false);
5603
    self.createHeaderBar(true);
5604
 
5605
    $scope.$emit('ionNavBar.init', delegateHandle);
5606
  };
5607
 
5608
 
5609
  self.createHeaderBar = function(isActive) {
5610
    var containerEle = jqLite('<div class="nav-bar-block">');
5611
    ionic.DomUtil.cachedAttr(containerEle, 'nav-bar', isActive ? 'active' : 'cached');
5612
 
5613
    var alignTitle = $attrs.alignTitle || $ionicConfig.navBar.alignTitle();
5614
    var headerBarEle = jqLite('<ion-header-bar>').addClass($attrs['class']).attr('align-title', alignTitle);
5615
    if (isDefined($attrs.noTapScroll)) headerBarEle.attr('no-tap-scroll', $attrs.noTapScroll);
5616
    var titleEle = jqLite('<div class="title title-' + alignTitle + '">');
5617
    var navEle = {};
5618
    var lastViewItemEle = {};
5619
    var leftButtonsEle, rightButtonsEle;
5620
 
5621
    navEle[BACK_BUTTON] = createNavElement(BACK_BUTTON);
5622
    navEle[BACK_BUTTON] && headerBarEle.append(navEle[BACK_BUTTON]);
5623
 
5624
    // append title in the header, this is the rock to where buttons append
5625
    headerBarEle.append(titleEle);
5626
 
5627
    forEach(ITEM_TYPES, function(itemType) {
5628
      // create default button elements
5629
      navEle[itemType] = createNavElement(itemType);
5630
      // append and position buttons
5631
      positionItem(navEle[itemType], itemType);
5632
    });
5633
 
5634
    // add header-item to the root children
5635
    for (var x = 0; x < headerBarEle[0].children.length; x++) {
5636
      headerBarEle[0].children[x].classList.add('header-item');
5637
    }
5638
 
5639
    // compile header and append to the DOM
5640
    containerEle.append(headerBarEle);
5641
    $element.append($compile(containerEle)($scope.$new()));
5642
 
5643
    var headerBarCtrl = headerBarEle.data('$ionHeaderBarController');
5644
    headerBarCtrl.backButtonIcon = $ionicConfig.backButton.icon();
5645
    headerBarCtrl.backButtonText = $ionicConfig.backButton.text();
5646
 
5647
    var headerBarInstance = {
5648
      isActive: isActive,
5649
      title: function(newTitleText) {
5650
        headerBarCtrl.title(newTitleText);
5651
      },
5652
      setItem: function(navBarItemEle, itemType) {
5653
        // first make sure any exiting nav bar item has been removed
5654
        headerBarInstance.removeItem(itemType);
5655
 
5656
        if (navBarItemEle) {
5657
          if (itemType === 'title') {
5658
            // clear out the text based title
5659
            headerBarInstance.title("");
5660
          }
5661
 
5662
          // there's a custom nav bar item
5663
          positionItem(navBarItemEle, itemType);
5664
 
5665
          if (navEle[itemType]) {
5666
            // make sure the default on this itemType is hidden
5667
            navEle[itemType].addClass(CSS_HIDE);
5668
          }
5669
          lastViewItemEle[itemType] = navBarItemEle;
5670
 
5671
        } else if (navEle[itemType]) {
5672
          // there's a default button for this side and no view button
5673
          navEle[itemType].removeClass(CSS_HIDE);
5674
        }
5675
      },
5676
      removeItem: function(itemType) {
5677
        if (lastViewItemEle[itemType]) {
5678
          lastViewItemEle[itemType].scope().$destroy();
5679
          lastViewItemEle[itemType].remove();
5680
          lastViewItemEle[itemType] = null;
5681
        }
5682
      },
5683
      containerEle: function() {
5684
        return containerEle;
5685
      },
5686
      headerBarEle: function() {
5687
        return headerBarEle;
5688
      },
5689
      afterLeave: function() {
5690
        forEach(ITEM_TYPES, function(itemType) {
5691
          headerBarInstance.removeItem(itemType);
5692
        });
5693
        headerBarCtrl.resetBackButton();
5694
      },
5695
      controller: function() {
5696
        return headerBarCtrl;
5697
      },
5698
      destroy: function() {
5699
        forEach(ITEM_TYPES, function(itemType) {
5700
          headerBarInstance.removeItem(itemType);
5701
        });
5702
        containerEle.scope().$destroy();
5703
        for (var n in navEle) {
5704
          if (navEle[n]) {
5705
            navEle[n].removeData();
5706
            navEle[n] = null;
5707
          }
5708
        }
5709
        leftButtonsEle && leftButtonsEle.removeData();
5710
        rightButtonsEle && rightButtonsEle.removeData();
5711
        titleEle.removeData();
5712
        headerBarEle.removeData();
5713
        containerEle.remove();
5714
        containerEle = headerBarEle = titleEle = leftButtonsEle = rightButtonsEle = null;
5715
      }
5716
    };
5717
 
5718
    function positionItem(ele, itemType) {
5719
      if (!ele) return;
5720
 
5721
      if (itemType === 'title') {
5722
        // title element
5723
        titleEle.append(ele);
5724
 
5725
      } else if (itemType == 'rightButtons' ||
5726
                (itemType == SECONDARY_BUTTONS && $ionicConfig.navBar.positionSecondaryButtons() != 'left') ||
5727
                (itemType == PRIMARY_BUTTONS && $ionicConfig.navBar.positionPrimaryButtons() == 'right')) {
5728
        // right side
5729
        if (!rightButtonsEle) {
5730
          rightButtonsEle = jqLite('<div class="buttons buttons-right">');
5731
          headerBarEle.append(rightButtonsEle);
5732
        }
5733
        if (itemType == SECONDARY_BUTTONS) {
5734
          rightButtonsEle.append(ele);
5735
        } else {
5736
          rightButtonsEle.prepend(ele);
5737
        }
5738
 
5739
      } else {
5740
        // left side
5741
        if (!leftButtonsEle) {
5742
          leftButtonsEle = jqLite('<div class="buttons buttons-left">');
5743
          if (navEle[BACK_BUTTON]) {
5744
            navEle[BACK_BUTTON].after(leftButtonsEle);
5745
          } else {
5746
            headerBarEle.prepend(leftButtonsEle);
5747
          }
5748
        }
5749
        if (itemType == SECONDARY_BUTTONS) {
5750
          leftButtonsEle.append(ele);
5751
        } else {
5752
          leftButtonsEle.prepend(ele);
5753
        }
5754
      }
5755
 
5756
    }
5757
 
5758
    headerBars.push(headerBarInstance);
5759
 
5760
    return headerBarInstance;
5761
  };
5762
 
5763
 
5764
  self.navElement = function(type, html) {
5765
    if (isDefined(html)) {
5766
      navElementHtml[type] = html;
5767
    }
5768
    return navElementHtml[type];
5769
  };
5770
 
5771
 
5772
  self.update = function(viewData) {
5773
    var showNavBar = !viewData.hasHeaderBar && viewData.showNavBar;
5774
    viewData.transition = $ionicConfig.views.transition();
5775
 
5776
    if (!showNavBar) {
5777
      viewData.direction = 'none';
5778
    }
5779
 
5780
    self.enable(showNavBar);
5781
    var enteringHeaderBar = self.isInitialized ? getOffScreenHeaderBar() : getOnScreenHeaderBar();
5782
    var leavingHeaderBar = self.isInitialized ? getOnScreenHeaderBar() : null;
5783
    var enteringHeaderCtrl = enteringHeaderBar.controller();
5784
 
5785
    // update if the entering header should show the back button or not
5786
    enteringHeaderCtrl.enableBack(viewData.enableBack, true);
5787
    enteringHeaderCtrl.showBack(viewData.showBack, true);
5788
    enteringHeaderCtrl.updateBackButton();
5789
 
5790
    // update the entering header bar's title
5791
    self.title(viewData.title, enteringHeaderBar);
5792
 
5793
    self.showBar(showNavBar);
5794
 
5795
    // update the nav bar items, depending if the view has their own or not
5796
    if (viewData.navBarItems) {
5797
      forEach(ITEM_TYPES, function(itemType) {
5798
        enteringHeaderBar.setItem(viewData.navBarItems[itemType], itemType);
5799
      });
5800
    }
5801
 
5802
    // begin transition of entering and leaving header bars
5803
    self.transition(enteringHeaderBar, leavingHeaderBar, viewData);
5804
 
5805
    self.isInitialized = true;
5806
    navSwipeAttr('');
5807
  };
5808
 
5809
 
5810
  self.transition = function(enteringHeaderBar, leavingHeaderBar, viewData) {
5811
    var enteringHeaderBarCtrl = enteringHeaderBar.controller();
5812
    var transitionFn = $ionicConfig.transitions.navBar[viewData.navBarTransition] || $ionicConfig.transitions.navBar.none;
5813
    var transitionId = viewData.transitionId;
5814
 
5815
    enteringHeaderBarCtrl.beforeEnter(viewData);
5816
 
5817
    var navBarTransition = transitionFn(enteringHeaderBar, leavingHeaderBar, viewData.direction, viewData.shouldAnimate && self.isInitialized);
5818
 
5819
    ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', viewData.navBarTransition);
5820
    ionic.DomUtil.cachedAttr($element, 'nav-bar-direction', viewData.direction);
5821
 
5822
    if (navBarTransition.shouldAnimate && viewData.renderEnd) {
5823
      navBarAttr(enteringHeaderBar, 'stage');
5824
    } else {
5825
      navBarAttr(enteringHeaderBar, 'entering');
5826
      navBarAttr(leavingHeaderBar, 'leaving');
5827
    }
5828
 
5829
    enteringHeaderBarCtrl.resetBackButton(viewData);
5830
 
5831
    navBarTransition.run(0);
5832
 
5833
    self.activeTransition = {
5834
      run: function(step) {
5835
        navBarTransition.shouldAnimate = false;
5836
        navBarTransition.direction = 'back';
5837
        navBarTransition.run(step);
5838
      },
5839
      cancel: function(shouldAnimate, speed, cancelData) {
5840
        navSwipeAttr(speed);
5841
        navBarAttr(leavingHeaderBar, 'active');
5842
        navBarAttr(enteringHeaderBar, 'cached');
5843
        navBarTransition.shouldAnimate = shouldAnimate;
5844
        navBarTransition.run(0);
5845
        self.activeTransition = navBarTransition = null;
5846
 
5847
        var runApply;
5848
        if (cancelData.showBar !== self.showBar()) {
5849
          self.showBar(cancelData.showBar);
5850
        }
5851
        if (cancelData.showBackButton !== self.showBackButton()) {
5852
          self.showBackButton(cancelData.showBackButton);
5853
        }
5854
        if (runApply) {
5855
          $scope.$apply();
5856
        }
5857
      },
5858
      complete: function(shouldAnimate, speed) {
5859
        navSwipeAttr(speed);
5860
        navBarTransition.shouldAnimate = shouldAnimate;
5861
        navBarTransition.run(1);
5862
        queuedTransitionEnd = transitionEnd;
5863
      }
5864
    };
5865
 
5866
    $timeout(enteringHeaderBarCtrl.align, 16);
5867
 
5868
    queuedTransitionStart = function() {
5869
      if (latestTransitionId !== transitionId) return;
5870
 
5871
      navBarAttr(enteringHeaderBar, 'entering');
5872
      navBarAttr(leavingHeaderBar, 'leaving');
5873
 
5874
      navBarTransition.run(1);
5875
 
5876
      queuedTransitionEnd = function() {
5877
        if (latestTransitionId == transitionId || !navBarTransition.shouldAnimate) {
5878
          transitionEnd();
5879
        }
5880
      };
5881
 
5882
      queuedTransitionStart = null;
5883
    };
5884
 
5885
    function transitionEnd() {
5886
      for (var x = 0; x < headerBars.length; x++) {
5887
        headerBars[x].isActive = false;
5888
      }
5889
      enteringHeaderBar.isActive = true;
5890
 
5891
      navBarAttr(enteringHeaderBar, 'active');
5892
      navBarAttr(leavingHeaderBar, 'cached');
5893
 
5894
      self.activeTransition = navBarTransition = queuedTransitionEnd = null;
5895
    }
5896
 
5897
    queuedTransitionStart();
5898
  };
5899
 
5900
 
5901
  self.triggerTransitionStart = function(triggerTransitionId) {
5902
    latestTransitionId = triggerTransitionId;
5903
    queuedTransitionStart && queuedTransitionStart();
5904
  };
5905
 
5906
 
5907
  self.triggerTransitionEnd = function() {
5908
    queuedTransitionEnd && queuedTransitionEnd();
5909
  };
5910
 
5911
 
5912
  self.showBar = function(shouldShow) {
5913
    if (arguments.length) {
5914
      self.visibleBar(shouldShow);
5915
      $scope.$parent.$hasHeader = !!shouldShow;
5916
    }
5917
    return !!$scope.$parent.$hasHeader;
5918
  };
5919
 
5920
 
5921
  self.visibleBar = function(shouldShow) {
5922
    if (shouldShow && !isVisible) {
5923
      $element.removeClass(CSS_HIDE);
5924
      self.align();
5925
    } else if (!shouldShow && isVisible) {
5926
      $element.addClass(CSS_HIDE);
5927
    }
5928
    isVisible = shouldShow;
5929
  };
5930
 
5931
 
5932
  self.enable = function(val) {
5933
    // set primary to show first
5934
    self.visibleBar(val);
5935
 
5936
    // set non primary to hide second
5937
    for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {
5938
      if ($ionicNavBarDelegate._instances[x] !== self) $ionicNavBarDelegate._instances[x].visibleBar(false);
5939
    }
5940
  };
5941
 
5942
 
5943
  /**
5944
   * @ngdoc method
5945
   * @name $ionicNavBar#showBackButton
5946
   * @description Show/hide the nav bar back button when there is a
5947
   * back view. If the back button is not possible, for example, the
5948
   * first view in the stack, then this will not force the back button
5949
   * to show.
5950
   */
5951
  self.showBackButton = function(shouldShow) {
5952
    if (arguments.length) {
5953
      for (var x = 0; x < headerBars.length; x++) {
5954
        headerBars[x].controller().showNavBack(!!shouldShow);
5955
      }
5956
      $scope.$isBackButtonShown = !!shouldShow;
5957
    }
5958
    return $scope.$isBackButtonShown;
5959
  };
5960
 
5961
 
5962
  /**
5963
   * @ngdoc method
5964
   * @name $ionicNavBar#showActiveBackButton
5965
   * @description Show/hide only the active header bar's back button.
5966
   */
5967
  self.showActiveBackButton = function(shouldShow) {
5968
    var headerBar = getOnScreenHeaderBar();
5969
    if (headerBar) {
5970
      if (arguments.length) {
5971
        return headerBar.controller().showBack(shouldShow);
5972
      }
5973
      return headerBar.controller().showBack();
5974
    }
5975
  };
5976
 
5977
 
5978
  self.title = function(newTitleText, headerBar) {
5979
    if (isDefined(newTitleText)) {
5980
      newTitleText = newTitleText || '';
5981
      headerBar = headerBar || getOnScreenHeaderBar();
5982
      headerBar && headerBar.title(newTitleText);
5983
      $scope.$title = newTitleText;
5984
      $ionicHistory.currentTitle(newTitleText);
5985
    }
5986
    return $scope.$title;
5987
  };
5988
 
5989
 
5990
  self.align = function(val, headerBar) {
5991
    headerBar = headerBar || getOnScreenHeaderBar();
5992
    headerBar && headerBar.controller().align(val);
5993
  };
5994
 
5995
 
5996
  self.hasTabsTop = function(isTabsTop) {
5997
    $element[isTabsTop ? 'addClass' : 'removeClass']('nav-bar-tabs-top');
5998
  };
5999
 
6000
  self.hasBarSubheader = function(isBarSubheader) {
6001
    $element[isBarSubheader ? 'addClass' : 'removeClass']('nav-bar-has-subheader');
6002
  };
6003
 
6004
  // DEPRECATED, as of v1.0.0-beta14 -------
6005
  self.changeTitle = function(val) {
6006
    deprecatedWarning('changeTitle(val)', 'title(val)');
6007
    self.title(val);
6008
  };
6009
  self.setTitle = function(val) {
6010
    deprecatedWarning('setTitle(val)', 'title(val)');
6011
    self.title(val);
6012
  };
6013
  self.getTitle = function() {
6014
    deprecatedWarning('getTitle()', 'title()');
6015
    return self.title();
6016
  };
6017
  self.back = function() {
6018
    deprecatedWarning('back()', '$ionicHistory.goBack()');
6019
    $ionicHistory.goBack();
6020
  };
6021
  self.getPreviousTitle = function() {
6022
    deprecatedWarning('getPreviousTitle()', '$ionicHistory.backTitle()');
6023
    $ionicHistory.goBack();
6024
  };
6025
  function deprecatedWarning(oldMethod, newMethod) {
6026
    var warn = console.warn || console.log;
6027
    warn && warn.call(console, 'navBarController.' + oldMethod + ' is deprecated, please use ' + newMethod + ' instead');
6028
  }
6029
  // END DEPRECATED -------
6030
 
6031
 
6032
  function createNavElement(type) {
6033
    if (navElementHtml[type]) {
6034
      return jqLite(navElementHtml[type]);
6035
    }
6036
  }
6037
 
6038
 
6039
  function getOnScreenHeaderBar() {
6040
    for (var x = 0; x < headerBars.length; x++) {
6041
      if (headerBars[x].isActive) return headerBars[x];
6042
    }
6043
  }
6044
 
6045
 
6046
  function getOffScreenHeaderBar() {
6047
    for (var x = 0; x < headerBars.length; x++) {
6048
      if (!headerBars[x].isActive) return headerBars[x];
6049
    }
6050
  }
6051
 
6052
 
6053
  function navBarAttr(ctrl, val) {
6054
    ctrl && ionic.DomUtil.cachedAttr(ctrl.containerEle(), 'nav-bar', val);
6055
  }
6056
 
6057
  function navSwipeAttr(val) {
6058
    ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);
6059
  }
6060
 
6061
 
6062
  $scope.$on('$destroy', function() {
6063
    $scope.$parent.$hasHeader = false;
6064
    $element.parent().removeData(DATA_NAV_BAR_CTRL);
6065
    for (var x = 0; x < headerBars.length; x++) {
6066
      headerBars[x].destroy();
6067
    }
6068
    $element.remove();
6069
    $element = headerBars = null;
6070
    deregisterInstance();
6071
  });
6072
 
6073
}]);
6074
 
6075
IonicModule
6076
.controller('$ionicNavView', [
6077
  '$scope',
6078
  '$element',
6079
  '$attrs',
6080
  '$compile',
6081
  '$controller',
6082
  '$ionicNavBarDelegate',
6083
  '$ionicNavViewDelegate',
6084
  '$ionicHistory',
6085
  '$ionicViewSwitcher',
6086
  '$ionicConfig',
6087
  '$ionicScrollDelegate',
6088
function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, $ionicNavViewDelegate, $ionicHistory, $ionicViewSwitcher, $ionicConfig, $ionicScrollDelegate) {
6089
 
6090
  var DATA_ELE_IDENTIFIER = '$eleId';
6091
  var DATA_DESTROY_ELE = '$destroyEle';
6092
  var DATA_NO_CACHE = '$noCache';
6093
  var VIEW_STATUS_ACTIVE = 'active';
6094
  var VIEW_STATUS_CACHED = 'cached';
6095
 
6096
  var self = this;
6097
  var direction;
6098
  var isPrimary = false;
6099
  var navBarDelegate;
6100
  var activeEleId;
6101
  var navViewAttr = $ionicViewSwitcher.navViewAttr;
6102
  var disableRenderStartViewId, disableAnimation;
6103
 
6104
  self.scope = $scope;
6105
  self.element = $element;
6106
 
6107
  self.init = function() {
6108
    var navViewName = $attrs.name || '';
6109
 
6110
    // Find the details of the parent view directive (if any) and use it
6111
    // to derive our own qualified view name, then hang our own details
6112
    // off the DOM so child directives can find it.
6113
    var parent = $element.parent().inheritedData('$uiView');
6114
    var parentViewName = ((parent && parent.state) ? parent.state.name : '');
6115
    if (navViewName.indexOf('@') < 0) navViewName = navViewName + '@' + parentViewName;
6116
 
6117
    var viewData = { name: navViewName, state: null };
6118
    $element.data('$uiView', viewData);
6119
 
6120
    var deregisterInstance = $ionicNavViewDelegate._registerInstance(self, $attrs.delegateHandle);
6121
    $scope.$on('$destroy', function() {
6122
      deregisterInstance();
6123
 
6124
      // ensure no scrolls have been left frozen
6125
      if (self.isSwipeFreeze) {
6126
        $ionicScrollDelegate.freezeAllScrolls(false);
6127
      }
6128
    });
6129
 
6130
    $scope.$on('$ionicHistory.deselect', self.cacheCleanup);
6131
    $scope.$on('$ionicTabs.top', onTabsTop);
6132
    $scope.$on('$ionicSubheader', onBarSubheader);
6133
 
6134
    $scope.$on('$ionicTabs.beforeLeave', onTabsLeave);
6135
    $scope.$on('$ionicTabs.afterLeave', onTabsLeave);
6136
    $scope.$on('$ionicTabs.leave', onTabsLeave);
6137
 
6138
    ionic.Platform.ready(function() {
6139
      if (ionic.Platform.isWebView() && $ionicConfig.views.swipeBackEnabled()) {
6140
        self.initSwipeBack();
6141
      }
6142
    });
6143
 
6144
    return viewData;
6145
  };
6146
 
6147
 
6148
  self.register = function(viewLocals) {
6149
    var leavingView = extend({}, $ionicHistory.currentView());
6150
 
6151
    // register that a view is coming in and get info on how it should transition
6152
    var registerData = $ionicHistory.register($scope, viewLocals);
6153
 
6154
    // update which direction
6155
    self.update(registerData);
6156
 
6157
    // begin rendering and transitioning
6158
    var enteringView = $ionicHistory.getViewById(registerData.viewId) || {};
6159
 
6160
    var renderStart = (disableRenderStartViewId !== registerData.viewId);
6161
    self.render(registerData, viewLocals, enteringView, leavingView, renderStart, true);
6162
  };
6163
 
6164
 
6165
  self.update = function(registerData) {
6166
    // always reset that this is the primary navView
6167
    isPrimary = true;
6168
 
6169
    // remember what direction this navView should use
6170
    // this may get updated later by a child navView
6171
    direction = registerData.direction;
6172
 
6173
    var parentNavViewCtrl = $element.parent().inheritedData('$ionNavViewController');
6174
    if (parentNavViewCtrl) {
6175
      // this navView is nested inside another one
6176
      // update the parent to use this direction and not
6177
      // the other it originally was set to
6178
 
6179
      // inform the parent navView that it is not the primary navView
6180
      parentNavViewCtrl.isPrimary(false);
6181
 
6182
      if (direction === 'enter' || direction === 'exit') {
6183
        // they're entering/exiting a history
6184
        // find parent navViewController
6185
        parentNavViewCtrl.direction(direction);
6186
 
6187
        if (direction === 'enter') {
6188
          // reset the direction so this navView doesn't animate
6189
          // because it's parent will
6190
          direction = 'none';
6191
        }
6192
      }
6193
    }
6194
  };
6195
 
6196
 
6197
  self.render = function(registerData, viewLocals, enteringView, leavingView, renderStart, renderEnd) {
6198
    // register the view and figure out where it lives in the various
6199
    // histories and nav stacks, along with how views should enter/leave
6200
    var switcher = $ionicViewSwitcher.create(self, viewLocals, enteringView, leavingView, renderStart, renderEnd);
6201
 
6202
    // init the rendering of views for this navView directive
6203
    switcher.init(registerData, function() {
6204
      // the view is now compiled, in the dom and linked, now lets transition the views.
6205
      // this uses a callback incase THIS nav-view has a nested nav-view, and after the NESTED
6206
      // nav-view links, the NESTED nav-view would update which direction THIS nav-view should use
6207
 
6208
      // kick off the transition of views
6209
      switcher.transition(self.direction(), registerData.enableBack, !disableAnimation);
6210
 
6211
      // reset private vars for next time
6212
      disableRenderStartViewId = disableAnimation = null;
6213
    });
6214
 
6215
  };
6216
 
6217
 
6218
  self.beforeEnter = function(transitionData) {
6219
    if (isPrimary) {
6220
      // only update this nav-view's nav-bar if this is the primary nav-view
6221
      navBarDelegate = transitionData.navBarDelegate;
6222
      var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6223
      associatedNavBarCtrl && associatedNavBarCtrl.update(transitionData);
6224
      navSwipeAttr('');
6225
    }
6226
  };
6227
 
6228
 
6229
  self.activeEleId = function(eleId) {
6230
    if (arguments.length) {
6231
      activeEleId = eleId;
6232
    }
6233
    return activeEleId;
6234
  };
6235
 
6236
 
6237
  self.transitionEnd = function() {
6238
    var viewElements = $element.children();
6239
    var x, l, viewElement;
6240
 
6241
    for (x = 0, l = viewElements.length; x < l; x++) {
6242
      viewElement = viewElements.eq(x);
6243
 
6244
      if (viewElement.data(DATA_ELE_IDENTIFIER) === activeEleId) {
6245
        // this is the active element
6246
        navViewAttr(viewElement, VIEW_STATUS_ACTIVE);
6247
 
6248
      } else if (navViewAttr(viewElement) === 'leaving' || navViewAttr(viewElement) === VIEW_STATUS_ACTIVE || navViewAttr(viewElement) === VIEW_STATUS_CACHED) {
6249
        // this is a leaving element or was the former active element, or is an cached element
6250
        if (viewElement.data(DATA_DESTROY_ELE) || viewElement.data(DATA_NO_CACHE)) {
6251
          // this element shouldn't stay cached
6252
          $ionicViewSwitcher.destroyViewEle(viewElement);
6253
 
6254
        } else {
6255
          // keep in the DOM, mark as cached
6256
          navViewAttr(viewElement, VIEW_STATUS_CACHED);
6257
 
6258
          // disconnect the leaving scope
6259
          ionic.Utils.disconnectScope(viewElement.scope());
6260
        }
6261
      }
6262
    }
6263
 
6264
    navSwipeAttr('');
6265
 
6266
    // ensure no scrolls have been left frozen
6267
    if (self.isSwipeFreeze) {
6268
      $ionicScrollDelegate.freezeAllScrolls(false);
6269
    }
6270
  };
6271
 
6272
 
6273
  function onTabsLeave(ev, data) {
6274
    var viewElements = $element.children();
6275
    var viewElement, viewScope;
6276
 
6277
    for (var x = 0, l = viewElements.length; x < l; x++) {
6278
      viewElement = viewElements.eq(x);
6279
      if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
6280
        viewScope = viewElement.scope();
6281
        viewScope && viewScope.$emit(ev.name.replace('Tabs', 'View'), data);
6282
        break;
6283
      }
6284
    }
6285
  }
6286
 
6287
 
6288
  self.cacheCleanup = function() {
6289
    var viewElements = $element.children();
6290
    for (var x = 0, l = viewElements.length; x < l; x++) {
6291
      if (viewElements.eq(x).data(DATA_DESTROY_ELE)) {
6292
        $ionicViewSwitcher.destroyViewEle(viewElements.eq(x));
6293
      }
6294
    }
6295
  };
6296
 
6297
 
6298
  self.clearCache = function(stateIds) {
6299
    var viewElements = $element.children();
6300
    var viewElement, viewScope, x, l, y, eleIdentifier;
6301
 
6302
    for (x = 0, l = viewElements.length; x < l; x++) {
6303
      viewElement = viewElements.eq(x);
6304
 
6305
      if (stateIds) {
6306
        eleIdentifier = viewElement.data(DATA_ELE_IDENTIFIER);
6307
 
6308
        for (y = 0; y < stateIds.length; y++) {
6309
          if (eleIdentifier === stateIds[y]) {
6310
            $ionicViewSwitcher.destroyViewEle(viewElement);
6311
          }
6312
        }
6313
        continue;
6314
      }
6315
 
6316
      if (navViewAttr(viewElement) == VIEW_STATUS_CACHED) {
6317
        $ionicViewSwitcher.destroyViewEle(viewElement);
6318
 
6319
      } else if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
6320
        viewScope = viewElement.scope();
6321
        viewScope && viewScope.$broadcast('$ionicView.clearCache');
6322
      }
6323
 
6324
    }
6325
  };
6326
 
6327
 
6328
  self.getViewElements = function() {
6329
    return $element.children();
6330
  };
6331
 
6332
 
6333
  self.appendViewElement = function(viewEle, viewLocals) {
6334
    // compile the entering element and get the link function
6335
    var linkFn = $compile(viewEle);
6336
 
6337
    $element.append(viewEle);
6338
 
6339
    var viewScope = $scope.$new();
6340
 
6341
    if (viewLocals && viewLocals.$$controller) {
6342
      viewLocals.$scope = viewScope;
6343
      var controller = $controller(viewLocals.$$controller, viewLocals);
6344
      $element.children().data('$ngControllerController', controller);
6345
    }
6346
 
6347
    linkFn(viewScope);
6348
 
6349
    return viewScope;
6350
  };
6351
 
6352
 
6353
  self.title = function(val) {
6354
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6355
    associatedNavBarCtrl && associatedNavBarCtrl.title(val);
6356
  };
6357
 
6358
 
6359
  /**
6360
   * @ngdoc method
6361
   * @name $ionicNavView#enableBackButton
6362
   * @description Enable/disable if the back button can be shown or not. For
6363
   * example, the very first view in the navigation stack would not have a
6364
   * back view, so the back button would be disabled.
6365
   */
6366
  self.enableBackButton = function(shouldEnable) {
6367
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6368
    associatedNavBarCtrl && associatedNavBarCtrl.enableBackButton(shouldEnable);
6369
  };
6370
 
6371
 
6372
  /**
6373
   * @ngdoc method
6374
   * @name $ionicNavView#showBackButton
6375
   * @description Show/hide the nav bar active back button. If the back button
6376
   * is not possible this will not force the back button to show. The
6377
   * `enableBackButton()` method handles if a back button is even possible or not.
6378
   */
6379
  self.showBackButton = function(shouldShow) {
6380
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6381
    if (associatedNavBarCtrl) {
6382
      if (arguments.length) {
6383
        return associatedNavBarCtrl.showActiveBackButton(shouldShow);
6384
      }
6385
      return associatedNavBarCtrl.showActiveBackButton();
6386
    }
6387
    return true;
6388
  };
6389
 
6390
 
6391
  self.showBar = function(val) {
6392
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6393
    if (associatedNavBarCtrl) {
6394
      if (arguments.length) {
6395
        return associatedNavBarCtrl.showBar(val);
6396
      }
6397
      return associatedNavBarCtrl.showBar();
6398
    }
6399
    return true;
6400
  };
6401
 
6402
 
6403
  self.isPrimary = function(val) {
6404
    if (arguments.length) {
6405
      isPrimary = val;
6406
    }
6407
    return isPrimary;
6408
  };
6409
 
6410
 
6411
  self.direction = function(val) {
6412
    if (arguments.length) {
6413
      direction = val;
6414
    }
6415
    return direction;
6416
  };
6417
 
6418
 
6419
  self.initSwipeBack = function() {
6420
    var swipeBackHitWidth = $ionicConfig.views.swipeBackHitWidth();
6421
    var viewTransition, associatedNavBarCtrl, backView;
6422
    var deregDragStart, deregDrag, deregRelease;
6423
    var windowWidth, startDragX, dragPoints;
6424
    var cancelData = {};
6425
 
6426
    function onDragStart(ev) {
6427
      if (!isPrimary) return;
6428
 
6429
      startDragX = getDragX(ev);
6430
      if (startDragX > swipeBackHitWidth) return;
6431
 
6432
      backView = $ionicHistory.backView();
6433
 
6434
      var currentView = $ionicHistory.currentView();
6435
 
6436
      if (!backView || backView.historyId !== currentView.historyId || currentView.canSwipeBack === false) return;
6437
 
6438
      if (!windowWidth) windowWidth = window.innerWidth;
6439
 
6440
      self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(true);
6441
 
6442
      var registerData = {
6443
        direction: 'back'
6444
      };
6445
 
6446
      dragPoints = [];
6447
 
6448
      cancelData = {
6449
        showBar: self.showBar(),
6450
        showBackButton: self.showBackButton()
6451
      };
6452
 
6453
      var switcher = $ionicViewSwitcher.create(self, registerData, backView, currentView, true, false);
6454
      switcher.loadViewElements(registerData);
6455
      switcher.render(registerData);
6456
 
6457
      viewTransition = switcher.transition('back', $ionicHistory.enabledBack(backView), true);
6458
 
6459
      associatedNavBarCtrl = getAssociatedNavBarCtrl();
6460
 
6461
      deregDrag = ionic.onGesture('drag', onDrag, $element[0]);
6462
      deregRelease = ionic.onGesture('release', onRelease, $element[0]);
6463
    }
6464
 
6465
    function onDrag(ev) {
6466
      if (isPrimary && viewTransition) {
6467
        var dragX = getDragX(ev);
6468
 
6469
        dragPoints.push({
6470
          t: Date.now(),
6471
          x: dragX
6472
        });
6473
 
6474
        if (dragX >= windowWidth - 15) {
6475
          onRelease(ev);
6476
 
6477
        } else {
6478
          var step = Math.min(Math.max(getSwipeCompletion(dragX), 0), 1);
6479
          viewTransition.run(step);
6480
          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.run(step);
6481
        }
6482
 
6483
      }
6484
    }
6485
 
6486
    function onRelease(ev) {
6487
      if (isPrimary && viewTransition && dragPoints && dragPoints.length > 1) {
6488
 
6489
        var now = Date.now();
6490
        var releaseX = getDragX(ev);
6491
        var startDrag = dragPoints[dragPoints.length - 1];
6492
 
6493
        for (var x = dragPoints.length - 2; x >= 0; x--) {
6494
          if (now - startDrag.t > 200) {
6495
            break;
6496
          }
6497
          startDrag = dragPoints[x];
6498
        }
6499
 
6500
        var isSwipingRight = (releaseX >= dragPoints[dragPoints.length - 2].x);
6501
        var releaseSwipeCompletion = getSwipeCompletion(releaseX);
6502
        var velocity = Math.abs(startDrag.x - releaseX) / (now - startDrag.t);
6503
 
6504
        // private variables because ui-router has no way to pass custom data using $state.go
6505
        disableRenderStartViewId = backView.viewId;
6506
        disableAnimation = (releaseSwipeCompletion < 0.03 || releaseSwipeCompletion > 0.97);
6507
 
6508
        if (isSwipingRight && (releaseSwipeCompletion > 0.5 || velocity > 0.1)) {
6509
          // complete view transition on release
6510
          var speed = (velocity > 0.5 || velocity < 0.05 || releaseX > windowWidth - 45) ? 'fast' : 'slow';
6511
          navSwipeAttr(disableAnimation ? '' : speed);
6512
          backView.go();
6513
          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.complete(!disableAnimation, speed);
6514
 
6515
        } else {
6516
          // cancel view transition on release
6517
          navSwipeAttr(disableAnimation ? '' : 'fast');
6518
          disableRenderStartViewId = null;
6519
          viewTransition.cancel(!disableAnimation);
6520
          associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.cancel(!disableAnimation, 'fast', cancelData);
6521
          disableAnimation = null;
6522
        }
6523
 
6524
      }
6525
 
6526
      ionic.offGesture(deregDrag, 'drag', onDrag);
6527
      ionic.offGesture(deregRelease, 'release', onRelease);
6528
 
6529
      windowWidth = viewTransition = dragPoints = null;
6530
 
6531
      self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(false);
6532
    }
6533
 
6534
    function getDragX(ev) {
6535
      return ionic.tap.pointerCoord(ev.gesture.srcEvent).x;
6536
    }
6537
 
6538
    function getSwipeCompletion(dragX) {
6539
      return (dragX - startDragX) / windowWidth;
6540
    }
6541
 
6542
    deregDragStart = ionic.onGesture('dragstart', onDragStart, $element[0]);
6543
 
6544
    $scope.$on('$destroy', function() {
6545
      ionic.offGesture(deregDragStart, 'dragstart', onDragStart);
6546
      ionic.offGesture(deregDrag, 'drag', onDrag);
6547
      ionic.offGesture(deregRelease, 'release', onRelease);
6548
      self.element = viewTransition = associatedNavBarCtrl = null;
6549
    });
6550
  };
6551
 
6552
 
6553
  function navSwipeAttr(val) {
6554
    ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);
6555
  }
6556
 
6557
 
6558
  function onTabsTop(ev, isTabsTop) {
6559
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6560
    associatedNavBarCtrl && associatedNavBarCtrl.hasTabsTop(isTabsTop);
6561
  }
6562
 
6563
  function onBarSubheader(ev, isBarSubheader) {
6564
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6565
    associatedNavBarCtrl && associatedNavBarCtrl.hasBarSubheader(isBarSubheader);
6566
  }
6567
 
6568
  function getAssociatedNavBarCtrl() {
6569
    if (navBarDelegate) {
6570
      for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {
6571
        if ($ionicNavBarDelegate._instances[x].$$delegateHandle == navBarDelegate) {
6572
          return $ionicNavBarDelegate._instances[x];
6573
        }
6574
      }
6575
    }
6576
    return $element.inheritedData('$ionNavBarController');
6577
  }
6578
 
6579
}]);
6580
 
6581
IonicModule
6582
.controller('$ionicRefresher', [
6583
  '$scope',
6584
  '$attrs',
6585
  '$element',
6586
  '$ionicBind',
6587
  '$timeout',
6588
  function($scope, $attrs, $element, $ionicBind, $timeout) {
6589
    var self = this,
6590
        isDragging = false,
6591
        isOverscrolling = false,
6592
        dragOffset = 0,
6593
        lastOverscroll = 0,
6594
        ptrThreshold = 60,
6595
        activated = false,
6596
        scrollTime = 500,
6597
        startY = null,
6598
        deltaY = null,
6599
        canOverscroll = true,
6600
        scrollParent,
6601
        scrollChild;
6602
 
6603
    if (!isDefined($attrs.pullingIcon)) {
6604
      $attrs.$set('pullingIcon', 'ion-android-arrow-down');
6605
    }
6606
 
6607
    $scope.showSpinner = !isDefined($attrs.refreshingIcon) && $attrs.spinner != 'none';
6608
 
6609
    $scope.showIcon = isDefined($attrs.refreshingIcon);
6610
 
6611
    $ionicBind($scope, $attrs, {
6612
      pullingIcon: '@',
6613
      pullingText: '@',
6614
      refreshingIcon: '@',
6615
      refreshingText: '@',
6616
      spinner: '@',
6617
      disablePullingRotation: '@',
6618
      $onRefresh: '&onRefresh',
6619
      $onPulling: '&onPulling'
6620
    });
6621
 
6622
    function handleTouchend() {
6623
      // if this wasn't an overscroll, get out immediately
6624
      if (!canOverscroll && !isDragging) {
6625
        return;
6626
      }
6627
      // reset Y
6628
      startY = null;
6629
      // the user has overscrolled but went back to native scrolling
6630
      if (!isDragging) {
6631
        dragOffset = 0;
6632
        isOverscrolling = false;
6633
        setScrollLock(false);
6634
      } else {
6635
        isDragging = false;
6636
        dragOffset = 0;
6637
 
6638
        // the user has scroll far enough to trigger a refresh
6639
        if (lastOverscroll > ptrThreshold) {
6640
          start();
6641
          scrollTo(ptrThreshold, scrollTime);
6642
 
6643
        // the user has overscrolled but not far enough to trigger a refresh
6644
        } else {
6645
          scrollTo(0, scrollTime, deactivate);
6646
          isOverscrolling = false;
6647
        }
6648
      }
6649
    }
6650
 
6651
    function handleTouchmove(e) {
6652
      // if multitouch or regular scroll event, get out immediately
6653
      if (!canOverscroll || e.touches.length > 1) {
6654
        return;
6655
      }
6656
      //if this is a new drag, keep track of where we start
6657
      if (startY === null) {
6658
        startY = parseInt(e.touches[0].screenY, 10);
6659
      }
6660
 
6661
      // kitkat fix for touchcancel events http://updates.html5rocks.com/2014/05/A-More-Compatible-Smoother-Touch
6662
      if (ionic.Platform.isAndroid() && ionic.Platform.version() === 4.4 && scrollParent.scrollTop === 0) {
6663
        isDragging = true;
6664
        e.preventDefault();
6665
      }
6666
 
6667
      // how far have we dragged so far?
6668
      deltaY = parseInt(e.touches[0].screenY, 10) - startY;
6669
 
6670
      // if we've dragged up and back down in to native scroll territory
6671
      if (deltaY - dragOffset <= 0 || scrollParent.scrollTop !== 0) {
6672
 
6673
        if (isOverscrolling) {
6674
          isOverscrolling = false;
6675
          setScrollLock(false);
6676
        }
6677
 
6678
        if (isDragging) {
6679
          nativescroll(scrollParent, parseInt(deltaY - dragOffset, 10) * -1);
6680
        }
6681
 
6682
        // if we're not at overscroll 0 yet, 0 out
6683
        if (lastOverscroll !== 0) {
6684
          overscroll(0);
6685
        }
6686
        return;
6687
 
6688
      } else if (deltaY > 0 && scrollParent.scrollTop === 0 && !isOverscrolling) {
6689
        // starting overscroll, but drag started below scrollTop 0, so we need to offset the position
6690
        dragOffset = deltaY;
6691
      }
6692
 
6693
      // prevent native scroll events while overscrolling
6694
      e.preventDefault();
6695
 
6696
      // if not overscrolling yet, initiate overscrolling
6697
      if (!isOverscrolling) {
6698
        isOverscrolling = true;
6699
        setScrollLock(true);
6700
      }
6701
 
6702
      isDragging = true;
6703
      // overscroll according to the user's drag so far
6704
      overscroll(parseInt((deltaY - dragOffset) / 3, 10));
6705
 
6706
      // update the icon accordingly
6707
      if (!activated && lastOverscroll > ptrThreshold) {
6708
        activated = true;
6709
        ionic.requestAnimationFrame(activate);
6710
 
6711
      } else if (activated && lastOverscroll < ptrThreshold) {
6712
        activated = false;
6713
        ionic.requestAnimationFrame(deactivate);
6714
      }
6715
    }
6716
 
6717
    function handleScroll(e) {
6718
      // canOverscrol is used to greatly simplify the drag handler during normal scrolling
6719
      canOverscroll = (e.target.scrollTop === 0) || isDragging;
6720
    }
6721
 
6722
    function overscroll(val) {
6723
      scrollChild.style[ionic.CSS.TRANSFORM] = 'translateY(' + val + 'px)';
6724
      lastOverscroll = val;
6725
    }
6726
 
6727
    function nativescroll(target, newScrollTop) {
6728
      // creates a scroll event that bubbles, can be cancelled, and with its view
6729
      // and detail property initialized to window and 1, respectively
6730
      target.scrollTop = newScrollTop;
6731
      var e = document.createEvent("UIEvents");
6732
      e.initUIEvent("scroll", true, true, window, 1);
6733
      target.dispatchEvent(e);
6734
    }
6735
 
6736
    function setScrollLock(enabled) {
6737
      // set the scrollbar to be position:fixed in preparation to overscroll
6738
      // or remove it so the app can be natively scrolled
6739
      if (enabled) {
6740
        ionic.requestAnimationFrame(function() {
6741
          scrollChild.classList.add('overscroll');
6742
          show();
6743
        });
6744
 
6745
      } else {
6746
        ionic.requestAnimationFrame(function() {
6747
          scrollChild.classList.remove('overscroll');
6748
          hide();
6749
          deactivate();
6750
        });
6751
      }
6752
    }
6753
 
6754
    $scope.$on('scroll.refreshComplete', function() {
6755
      // prevent the complete from firing before the scroll has started
6756
      $timeout(function() {
6757
 
6758
        ionic.requestAnimationFrame(tail);
6759
 
6760
        // scroll back to home during tail animation
6761
        scrollTo(0, scrollTime, deactivate);
6762
 
6763
        // return to native scrolling after tail animation has time to finish
6764
        $timeout(function() {
6765
 
6766
          if (isOverscrolling) {
6767
            isOverscrolling = false;
6768
            setScrollLock(false);
6769
          }
6770
 
6771
        }, scrollTime);
6772
 
6773
      }, scrollTime);
6774
    });
6775
 
6776
    function scrollTo(Y, duration, callback) {
6777
      // scroll animation loop w/ easing
6778
      // credit https://gist.github.com/dezinezync/5487119
6779
      var start = Date.now(),
6780
          from = lastOverscroll;
6781
 
6782
      if (from === Y) {
6783
        callback();
6784
        return; /* Prevent scrolling to the Y point if already there */
6785
      }
6786
 
6787
      // decelerating to zero velocity
6788
      function easeOutCubic(t) {
6789
        return (--t) * t * t + 1;
6790
      }
6791
 
6792
      // scroll loop
6793
      function scroll() {
6794
        var currentTime = Date.now(),
6795
          time = Math.min(1, ((currentTime - start) / duration)),
6796
          // where .5 would be 50% of time on a linear scale easedT gives a
6797
          // fraction based on the easing method
6798
          easedT = easeOutCubic(time);
6799
 
6800
        overscroll(parseInt((easedT * (Y - from)) + from, 10));
6801
 
6802
        if (time < 1) {
6803
          ionic.requestAnimationFrame(scroll);
6804
 
6805
        } else {
6806
 
6807
          if (Y < 5 && Y > -5) {
6808
            isOverscrolling = false;
6809
            setScrollLock(false);
6810
          }
6811
 
6812
          callback && callback();
6813
        }
6814
      }
6815
 
6816
      // start scroll loop
6817
      ionic.requestAnimationFrame(scroll);
6818
    }
6819
 
6820
 
6821
    self.init = function() {
6822
      scrollParent = $element.parent().parent()[0];
6823
      scrollChild = $element.parent()[0];
6824
 
6825
      if (!scrollParent || !scrollParent.classList.contains('ionic-scroll') ||
6826
        !scrollChild || !scrollChild.classList.contains('scroll')) {
6827
        throw new Error('Refresher must be immediate child of ion-content or ion-scroll');
6828
      }
6829
 
6830
      ionic.on('touchmove', handleTouchmove, scrollChild);
6831
      ionic.on('touchend', handleTouchend, scrollChild);
6832
      ionic.on('scroll', handleScroll, scrollParent);
6833
 
6834
      // cleanup when done
6835
      $scope.$on('$destroy', destroy);
6836
    };
6837
 
6838
    function destroy() {
6839
      ionic.off('touchmove', handleTouchmove, scrollChild);
6840
      ionic.off('touchend', handleTouchend, scrollChild);
6841
      ionic.off('scroll', handleScroll, scrollParent);
6842
      scrollParent = null;
6843
      scrollChild = null;
6844
    }
6845
 
6846
    // DOM manipulation and broadcast methods shared by JS and Native Scrolling
6847
    // getter used by JS Scrolling
6848
    self.getRefresherDomMethods = function() {
6849
      return {
6850
        activate: activate,
6851
        deactivate: deactivate,
6852
        start: start,
6853
        show: show,
6854
        hide: hide,
6855
        tail: tail
6856
      };
6857
    };
6858
 
6859
    function activate() {
6860
      $element[0].classList.add('active');
6861
      $scope.$onPulling();
6862
    }
6863
 
6864
    function deactivate() {
6865
      // give tail 150ms to finish
6866
      $timeout(function() {
6867
        // deactivateCallback
6868
        $element.removeClass('active refreshing refreshing-tail');
6869
        if (activated) activated = false;
6870
      }, 150);
6871
    }
6872
 
6873
    function start() {
6874
      // startCallback
6875
      $element[0].classList.add('refreshing');
6876
      $scope.$onRefresh();
6877
    }
6878
 
6879
    function show() {
6880
      // showCallback
6881
      $element[0].classList.remove('invisible');
6882
    }
6883
 
6884
    function hide() {
6885
      // showCallback
6886
      $element[0].classList.add('invisible');
6887
    }
6888
 
6889
    function tail() {
6890
      // tailCallback
6891
      $element[0].classList.add('refreshing-tail');
6892
    }
6893
 
6894
    // for testing
6895
    self.__handleTouchmove = handleTouchmove;
6896
    self.__getScrollChild = function() { return scrollChild; };
6897
    self.__getScrollParent = function() { return scrollParent; };
6898
  }
6899
]);
6900
 
6901
/**
6902
 * @private
6903
 */
6904
IonicModule
6905
 
6906
.controller('$ionicScroll', [
6907
  '$scope',
6908
  'scrollViewOptions',
6909
  '$timeout',
6910
  '$window',
6911
  '$location',
6912
  '$document',
6913
  '$ionicScrollDelegate',
6914
  '$ionicHistory',
6915
function($scope,
6916
         scrollViewOptions,
6917
         $timeout,
6918
         $window,
6919
         $location,
6920
         $document,
6921
         $ionicScrollDelegate,
6922
         $ionicHistory) {
6923
 
6924
  var self = this;
6925
  // for testing
6926
  self.__timeout = $timeout;
6927
 
6928
  self._scrollViewOptions = scrollViewOptions; //for testing
6929
  self.isNative = function() {
6930
    return !!scrollViewOptions.nativeScrolling;
6931
  };
6932
 
6933
  var element = self.element = scrollViewOptions.el;
6934
  var $element = self.$element = jqLite(element);
6935
  var scrollView;
6936
  if (self.isNative()) {
6937
    scrollView = self.scrollView = new ionic.views.ScrollNative(scrollViewOptions);
6938
  } else {
6939
    scrollView = self.scrollView = new ionic.views.Scroll(scrollViewOptions);
6940
  }
6941
 
6942
 
6943
  //Attach self to element as a controller so other directives can require this controller
6944
  //through `require: '$ionicScroll'
6945
  //Also attach to parent so that sibling elements can require this
6946
  ($element.parent().length ? $element.parent() : $element)
6947
    .data('$$ionicScrollController', self);
6948
 
6949
  var deregisterInstance = $ionicScrollDelegate._registerInstance(
6950
    self, scrollViewOptions.delegateHandle, function() {
6951
      return $ionicHistory.isActiveScope($scope);
6952
    }
6953
  );
6954
 
6955
  if (!isDefined(scrollViewOptions.bouncing)) {
6956
    ionic.Platform.ready(function() {
6957
      if (scrollView.options) {
6958
        scrollView.options.bouncing = true;
6959
        if (ionic.Platform.isAndroid()) {
6960
          // No bouncing by default on Android
6961
          scrollView.options.bouncing = false;
6962
          // Faster scroll decel
6963
          scrollView.options.deceleration = 0.95;
6964
        }
6965
      }
6966
    });
6967
  }
6968
 
6969
  var resize = angular.bind(scrollView, scrollView.resize);
6970
  angular.element($window).on('resize', resize);
6971
 
6972
  var scrollFunc = function(e) {
6973
    var detail = (e.originalEvent || e).detail || {};
6974
    $scope.$onScroll && $scope.$onScroll({
6975
      event: e,
6976
      scrollTop: detail.scrollTop || 0,
6977
      scrollLeft: detail.scrollLeft || 0
6978
    });
6979
  };
6980
 
6981
  $element.on('scroll', scrollFunc);
6982
 
6983
  $scope.$on('$destroy', function() {
6984
    deregisterInstance();
6985
    scrollView && scrollView.__cleanup && scrollView.__cleanup();
6986
    angular.element($window).off('resize', resize);
6987
    $element.off('scroll', scrollFunc);
6988
    scrollView = self.scrollView = scrollViewOptions = self._scrollViewOptions = scrollViewOptions.el = self._scrollViewOptions.el = $element = self.$element = element = null;
6989
  });
6990
 
6991
  $timeout(function() {
6992
    scrollView && scrollView.run && scrollView.run();
6993
  });
6994
 
6995
  self.getScrollView = function() {
6996
    return scrollView;
6997
  };
6998
 
6999
  self.getScrollPosition = function() {
7000
    return scrollView.getValues();
7001
  };
7002
 
7003
  self.resize = function() {
7004
    return $timeout(resize, 0, false).then(function() {
7005
      $element && $element.triggerHandler('scroll-resize');
7006
    });
7007
  };
7008
 
7009
  self.scrollTop = function(shouldAnimate) {
7010
    self.resize().then(function() {
7011
      scrollView.scrollTo(0, 0, !!shouldAnimate);
7012
    });
7013
  };
7014
 
7015
  self.scrollBottom = function(shouldAnimate) {
7016
    self.resize().then(function() {
7017
      var max = scrollView.getScrollMax();
7018
      scrollView.scrollTo(max.left, max.top, !!shouldAnimate);
7019
    });
7020
  };
7021
 
7022
  self.scrollTo = function(left, top, shouldAnimate) {
7023
    self.resize().then(function() {
7024
      scrollView.scrollTo(left, top, !!shouldAnimate);
7025
    });
7026
  };
7027
 
7028
  self.zoomTo = function(zoom, shouldAnimate, originLeft, originTop) {
7029
    self.resize().then(function() {
7030
      scrollView.zoomTo(zoom, !!shouldAnimate, originLeft, originTop);
7031
    });
7032
  };
7033
 
7034
  self.zoomBy = function(zoom, shouldAnimate, originLeft, originTop) {
7035
    self.resize().then(function() {
7036
      scrollView.zoomBy(zoom, !!shouldAnimate, originLeft, originTop);
7037
    });
7038
  };
7039
 
7040
  self.scrollBy = function(left, top, shouldAnimate) {
7041
    self.resize().then(function() {
7042
      scrollView.scrollBy(left, top, !!shouldAnimate);
7043
    });
7044
  };
7045
 
7046
  self.anchorScroll = function(shouldAnimate) {
7047
    self.resize().then(function() {
7048
      var hash = $location.hash();
7049
      var elm = hash && $document[0].getElementById(hash);
7050
      if (!(hash && elm)) {
7051
        scrollView.scrollTo(0, 0, !!shouldAnimate);
7052
        return;
7053
      }
7054
      var curElm = elm;
7055
      var scrollLeft = 0, scrollTop = 0;
7056
      do {
7057
        if (curElm !== null) scrollLeft += curElm.offsetLeft;
7058
        if (curElm !== null) scrollTop += curElm.offsetTop;
7059
        curElm = curElm.offsetParent;
7060
      } while (curElm.attributes != self.element.attributes && curElm.offsetParent);
7061
      scrollView.scrollTo(scrollLeft, scrollTop, !!shouldAnimate);
7062
    });
7063
  };
7064
 
7065
  self.freezeScroll = scrollView.freeze;
7066
 
7067
  self.freezeAllScrolls = function(shouldFreeze) {
7068
    for (var i = 0; i < $ionicScrollDelegate._instances.length; i++) {
7069
      $ionicScrollDelegate._instances[i].freezeScroll(shouldFreeze);
7070
    }
7071
  };
7072
 
7073
 
7074
  /**
7075
   * @private
7076
   */
7077
  self._setRefresher = function(refresherScope, refresherElement, refresherMethods) {
7078
    self.refresher = refresherElement;
7079
    var refresherHeight = self.refresher.clientHeight || 60;
7080
    scrollView.activatePullToRefresh(
7081
      refresherHeight,
7082
      refresherMethods
7083
    );
7084
  };
7085
 
7086
}]);
7087
 
7088
IonicModule
7089
.controller('$ionicSideMenus', [
7090
  '$scope',
7091
  '$attrs',
7092
  '$ionicSideMenuDelegate',
7093
  '$ionicPlatform',
7094
  '$ionicBody',
7095
  '$ionicHistory',
7096
  '$ionicScrollDelegate',
7097
  'IONIC_BACK_PRIORITY',
7098
function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform, $ionicBody, $ionicHistory, $ionicScrollDelegate, IONIC_BACK_PRIORITY) {
7099
  var self = this;
7100
  var rightShowing, leftShowing, isDragging;
7101
  var startX, lastX, offsetX, isAsideExposed;
7102
  var enableMenuWithBackViews = true;
7103
 
7104
  self.$scope = $scope;
7105
 
7106
  self.initialize = function(options) {
7107
    self.left = options.left;
7108
    self.right = options.right;
7109
    self.setContent(options.content);
7110
    self.dragThresholdX = options.dragThresholdX || 10;
7111
    $ionicHistory.registerHistory(self.$scope);
7112
  };
7113
 
7114
  /**
7115
   * Set the content view controller if not passed in the constructor options.
7116
   *
7117
   * @param {object} content
7118
   */
7119
  self.setContent = function(content) {
7120
    if (content) {
7121
      self.content = content;
7122
 
7123
      self.content.onDrag = function(e) {
7124
        self._handleDrag(e);
7125
      };
7126
 
7127
      self.content.endDrag = function(e) {
7128
        self._endDrag(e);
7129
      };
7130
    }
7131
  };
7132
 
7133
  self.isOpenLeft = function() {
7134
    return self.getOpenAmount() > 0;
7135
  };
7136
 
7137
  self.isOpenRight = function() {
7138
    return self.getOpenAmount() < 0;
7139
  };
7140
 
7141
  /**
7142
   * Toggle the left menu to open 100%
7143
   */
7144
  self.toggleLeft = function(shouldOpen) {
7145
    if (isAsideExposed || !self.left.isEnabled) return;
7146
    var openAmount = self.getOpenAmount();
7147
    if (arguments.length === 0) {
7148
      shouldOpen = openAmount <= 0;
7149
    }
7150
    self.content.enableAnimation();
7151
    if (!shouldOpen) {
7152
      self.openPercentage(0);
7153
    } else {
7154
      self.openPercentage(100);
7155
    }
7156
  };
7157
 
7158
  /**
7159
   * Toggle the right menu to open 100%
7160
   */
7161
  self.toggleRight = function(shouldOpen) {
7162
    if (isAsideExposed || !self.right.isEnabled) return;
7163
    var openAmount = self.getOpenAmount();
7164
    if (arguments.length === 0) {
7165
      shouldOpen = openAmount >= 0;
7166
    }
7167
    self.content.enableAnimation();
7168
    if (!shouldOpen) {
7169
      self.openPercentage(0);
7170
    } else {
7171
      self.openPercentage(-100);
7172
    }
7173
  };
7174
 
7175
  self.toggle = function(side) {
7176
    if (side == 'right') {
7177
      self.toggleRight();
7178
    } else {
7179
      self.toggleLeft();
7180
    }
7181
  };
7182
 
7183
  /**
7184
   * Close all menus.
7185
   */
7186
  self.close = function() {
7187
    self.openPercentage(0);
7188
  };
7189
 
7190
  /**
7191
   * @return {float} The amount the side menu is open, either positive or negative for left (positive), or right (negative)
7192
   */
7193
  self.getOpenAmount = function() {
7194
    return self.content && self.content.getTranslateX() || 0;
7195
  };
7196
 
7197
  /**
7198
   * @return {float} The ratio of open amount over menu width. For example, a
7199
   * menu of width 100 open 50 pixels would be open 50% or a ratio of 0.5. Value is negative
7200
   * for right menu.
7201
   */
7202
  self.getOpenRatio = function() {
7203
    var amount = self.getOpenAmount();
7204
    if (amount >= 0) {
7205
      return amount / self.left.width;
7206
    }
7207
    return amount / self.right.width;
7208
  };
7209
 
7210
  self.isOpen = function() {
7211
    return self.getOpenAmount() !== 0;
7212
  };
7213
 
7214
  /**
7215
   * @return {float} The percentage of open amount over menu width. For example, a
7216
   * menu of width 100 open 50 pixels would be open 50%. Value is negative
7217
   * for right menu.
7218
   */
7219
  self.getOpenPercentage = function() {
7220
    return self.getOpenRatio() * 100;
7221
  };
7222
 
7223
  /**
7224
   * Open the menu with a given percentage amount.
7225
   * @param {float} percentage The percentage (positive or negative for left/right) to open the menu.
7226
   */
7227
  self.openPercentage = function(percentage) {
7228
    var p = percentage / 100;
7229
 
7230
    if (self.left && percentage >= 0) {
7231
      self.openAmount(self.left.width * p);
7232
    } else if (self.right && percentage < 0) {
7233
      self.openAmount(self.right.width * p);
7234
    }
7235
 
7236
    // add the CSS class "menu-open" if the percentage does not
7237
    // equal 0, otherwise remove the class from the body element
7238
    $ionicBody.enableClass((percentage !== 0), 'menu-open');
7239
 
7240
    freezeAllScrolls(false);
7241
  };
7242
 
7243
  function freezeAllScrolls(shouldFreeze) {
7244
    if (shouldFreeze && !self.isScrollFreeze) {
7245
      $ionicScrollDelegate.freezeAllScrolls(shouldFreeze);
7246
 
7247
    } else if (!shouldFreeze && self.isScrollFreeze) {
7248
      $ionicScrollDelegate.freezeAllScrolls(false);
7249
    }
7250
    self.isScrollFreeze = shouldFreeze;
7251
  }
7252
 
7253
  /**
7254
   * Open the menu the given pixel amount.
7255
   * @param {float} amount the pixel amount to open the menu. Positive value for left menu,
7256
   * negative value for right menu (only one menu will be visible at a time).
7257
   */
7258
  self.openAmount = function(amount) {
7259
    var maxLeft = self.left && self.left.width || 0;
7260
    var maxRight = self.right && self.right.width || 0;
7261
 
7262
    // Check if we can move to that side, depending if the left/right panel is enabled
7263
    if (!(self.left && self.left.isEnabled) && amount > 0) {
7264
      self.content.setTranslateX(0);
7265
      return;
7266
    }
7267
 
7268
    if (!(self.right && self.right.isEnabled) && amount < 0) {
7269
      self.content.setTranslateX(0);
7270
      return;
7271
    }
7272
 
7273
    if (leftShowing && amount > maxLeft) {
7274
      self.content.setTranslateX(maxLeft);
7275
      return;
7276
    }
7277
 
7278
    if (rightShowing && amount < -maxRight) {
7279
      self.content.setTranslateX(-maxRight);
7280
      return;
7281
    }
7282
 
7283
    self.content.setTranslateX(amount);
7284
 
7285
    if (amount >= 0) {
7286
      leftShowing = true;
7287
      rightShowing = false;
7288
 
7289
      if (amount > 0) {
7290
        // Push the z-index of the right menu down
7291
        self.right && self.right.pushDown && self.right.pushDown();
7292
        // Bring the z-index of the left menu up
7293
        self.left && self.left.bringUp && self.left.bringUp();
7294
      }
7295
    } else {
7296
      rightShowing = true;
7297
      leftShowing = false;
7298
 
7299
      // Bring the z-index of the right menu up
7300
      self.right && self.right.bringUp && self.right.bringUp();
7301
      // Push the z-index of the left menu down
7302
      self.left && self.left.pushDown && self.left.pushDown();
7303
    }
7304
  };
7305
 
7306
  /**
7307
   * Given an event object, find the final resting position of this side
7308
   * menu. For example, if the user "throws" the content to the right and
7309
   * releases the touch, the left menu should snap open (animated, of course).
7310
   *
7311
   * @param {Event} e the gesture event to use for snapping
7312
   */
7313
  self.snapToRest = function(e) {
7314
    // We want to animate at the end of this
7315
    self.content.enableAnimation();
7316
    isDragging = false;
7317
 
7318
    // Check how much the panel is open after the drag, and
7319
    // what the drag velocity is
7320
    var ratio = self.getOpenRatio();
7321
 
7322
    if (ratio === 0) {
7323
      // Just to be safe
7324
      self.openPercentage(0);
7325
      return;
7326
    }
7327
 
7328
    var velocityThreshold = 0.3;
7329
    var velocityX = e.gesture.velocityX;
7330
    var direction = e.gesture.direction;
7331
 
7332
    // Going right, less than half, too slow (snap back)
7333
    if (ratio > 0 && ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {
7334
      self.openPercentage(0);
7335
    }
7336
 
7337
    // Going left, more than half, too slow (snap back)
7338
    else if (ratio > 0.5 && direction == 'left' && velocityX < velocityThreshold) {
7339
      self.openPercentage(100);
7340
    }
7341
 
7342
    // Going left, less than half, too slow (snap back)
7343
    else if (ratio < 0 && ratio > -0.5 && direction == 'left' && velocityX < velocityThreshold) {
7344
      self.openPercentage(0);
7345
    }
7346
 
7347
    // Going right, more than half, too slow (snap back)
7348
    else if (ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {
7349
      self.openPercentage(-100);
7350
    }
7351
 
7352
    // Going right, more than half, or quickly (snap open)
7353
    else if (direction == 'right' && ratio >= 0 && (ratio >= 0.5 || velocityX > velocityThreshold)) {
7354
      self.openPercentage(100);
7355
    }
7356
 
7357
    // Going left, more than half, or quickly (span open)
7358
    else if (direction == 'left' && ratio <= 0 && (ratio <= -0.5 || velocityX > velocityThreshold)) {
7359
      self.openPercentage(-100);
7360
    }
7361
 
7362
    // Snap back for safety
7363
    else {
7364
      self.openPercentage(0);
7365
    }
7366
  };
7367
 
7368
  self.enableMenuWithBackViews = function(val) {
7369
    if (arguments.length) {
7370
      enableMenuWithBackViews = !!val;
7371
    }
7372
    return enableMenuWithBackViews;
7373
  };
7374
 
7375
  self.isAsideExposed = function() {
7376
    return !!isAsideExposed;
7377
  };
7378
 
7379
  self.exposeAside = function(shouldExposeAside) {
7380
    if (!(self.left && self.left.isEnabled) && !(self.right && self.right.isEnabled)) return;
7381
    self.close();
7382
 
7383
    isAsideExposed = shouldExposeAside;
7384
    if (self.left && self.left.isEnabled) {
7385
      // set the left marget width if it should be exposed
7386
      // otherwise set false so there's no left margin
7387
      self.content.setMarginLeft(isAsideExposed ? self.left.width : 0);
7388
    } else if (self.right && self.right.isEnabled) {
7389
      self.content.setMarginRight(isAsideExposed ? self.right.width : 0);
7390
    }
7391
 
7392
    self.$scope.$emit('$ionicExposeAside', isAsideExposed);
7393
  };
7394
 
7395
  self.activeAsideResizing = function(isResizing) {
7396
    $ionicBody.enableClass(isResizing, 'aside-resizing');
7397
  };
7398
 
7399
  // End a drag with the given event
7400
  self._endDrag = function(e) {
7401
    freezeAllScrolls(false);
7402
 
7403
    if (isAsideExposed) return;
7404
 
7405
    if (isDragging) {
7406
      self.snapToRest(e);
7407
    }
7408
    startX = null;
7409
    lastX = null;
7410
    offsetX = null;
7411
  };
7412
 
7413
  // Handle a drag event
7414
  self._handleDrag = function(e) {
7415
    if (isAsideExposed || !$scope.dragContent) return;
7416
 
7417
    // If we don't have start coords, grab and store them
7418
    if (!startX) {
7419
      startX = e.gesture.touches[0].pageX;
7420
      lastX = startX;
7421
    } else {
7422
      // Grab the current tap coords
7423
      lastX = e.gesture.touches[0].pageX;
7424
    }
7425
 
7426
    // Calculate difference from the tap points
7427
    if (!isDragging && Math.abs(lastX - startX) > self.dragThresholdX) {
7428
      // if the difference is greater than threshold, start dragging using the current
7429
      // point as the starting point
7430
      startX = lastX;
7431
 
7432
      isDragging = true;
7433
      // Initialize dragging
7434
      self.content.disableAnimation();
7435
      offsetX = self.getOpenAmount();
7436
    }
7437
 
7438
    if (isDragging) {
7439
      self.openAmount(offsetX + (lastX - startX));
7440
      freezeAllScrolls(true);
7441
    }
7442
  };
7443
 
7444
  self.canDragContent = function(canDrag) {
7445
    if (arguments.length) {
7446
      $scope.dragContent = !!canDrag;
7447
    }
7448
    return $scope.dragContent;
7449
  };
7450
 
7451
  self.edgeThreshold = 25;
7452
  self.edgeThresholdEnabled = false;
7453
  self.edgeDragThreshold = function(value) {
7454
    if (arguments.length) {
7455
      if (isNumber(value) && value > 0) {
7456
        self.edgeThreshold = value;
7457
        self.edgeThresholdEnabled = true;
7458
      } else {
7459
        self.edgeThresholdEnabled = !!value;
7460
      }
7461
    }
7462
    return self.edgeThresholdEnabled;
7463
  };
7464
 
7465
  self.isDraggableTarget = function(e) {
7466
    //Only restrict edge when sidemenu is closed and restriction is enabled
7467
    var shouldOnlyAllowEdgeDrag = self.edgeThresholdEnabled && !self.isOpen();
7468
    var startX = e.gesture.startEvent && e.gesture.startEvent.center &&
7469
      e.gesture.startEvent.center.pageX;
7470
 
7471
    var dragIsWithinBounds = !shouldOnlyAllowEdgeDrag ||
7472
      startX <= self.edgeThreshold ||
7473
      startX >= self.content.element.offsetWidth - self.edgeThreshold;
7474
 
7475
    var backView = $ionicHistory.backView();
7476
    var menuEnabled = enableMenuWithBackViews ? true : !backView;
7477
    if (!menuEnabled) {
7478
      var currentView = $ionicHistory.currentView() || {};
7479
      return backView.historyId !== currentView.historyId;
7480
    }
7481
 
7482
    return ($scope.dragContent || self.isOpen()) &&
7483
      dragIsWithinBounds &&
7484
      !e.gesture.srcEvent.defaultPrevented &&
7485
      menuEnabled &&
7486
      !e.target.tagName.match(/input|textarea|select|object|embed/i) &&
7487
      !e.target.isContentEditable &&
7488
      !(e.target.dataset ? e.target.dataset.preventScroll : e.target.getAttribute('data-prevent-scroll') == 'true');
7489
  };
7490
 
7491
  $scope.sideMenuContentTranslateX = 0;
7492
 
7493
  var deregisterBackButtonAction = noop;
7494
  var closeSideMenu = angular.bind(self, self.close);
7495
 
7496
  $scope.$watch(function() {
7497
    return self.getOpenAmount() !== 0;
7498
  }, function(isOpen) {
7499
    deregisterBackButtonAction();
7500
    if (isOpen) {
7501
      deregisterBackButtonAction = $ionicPlatform.registerBackButtonAction(
7502
        closeSideMenu,
7503
        IONIC_BACK_PRIORITY.sideMenu
7504
      );
7505
    }
7506
  });
7507
 
7508
  var deregisterInstance = $ionicSideMenuDelegate._registerInstance(
7509
    self, $attrs.delegateHandle, function() {
7510
      return $ionicHistory.isActiveScope($scope);
7511
    }
7512
  );
7513
 
7514
  $scope.$on('$destroy', function() {
7515
    deregisterInstance();
7516
    deregisterBackButtonAction();
7517
    self.$scope = null;
7518
    if (self.content) {
7519
      self.content.element = null;
7520
      self.content = null;
7521
    }
7522
 
7523
    // ensure scrolls are unfrozen
7524
    freezeAllScrolls(false);
7525
  });
7526
 
7527
  self.initialize({
7528
    left: {
7529
      width: 275
7530
    },
7531
    right: {
7532
      width: 275
7533
    }
7534
  });
7535
 
7536
}]);
7537
 
7538
(function(ionic) {
7539
 
7540
  var TRANSLATE32 = 'translate(32,32)';
7541
  var STROKE_OPACITY = 'stroke-opacity';
7542
  var ROUND = 'round';
7543
  var INDEFINITE = 'indefinite';
7544
  var DURATION = '750ms';
7545
  var NONE = 'none';
7546
  var SHORTCUTS = {
7547
    a: 'animate',
7548
    an: 'attributeName',
7549
    at: 'animateTransform',
7550
    c: 'circle',
7551
    da: 'stroke-dasharray',
7552
    os: 'stroke-dashoffset',
7553
    f: 'fill',
7554
    lc: 'stroke-linecap',
7555
    rc: 'repeatCount',
7556
    sw: 'stroke-width',
7557
    t: 'transform',
7558
    v: 'values'
7559
  };
7560
 
7561
  var SPIN_ANIMATION = {
7562
    v: '0,32,32;360,32,32',
7563
    an: 'transform',
7564
    type: 'rotate',
7565
    rc: INDEFINITE,
7566
    dur: DURATION
7567
  };
7568
 
7569
  function createSvgElement(tagName, data, parent, spinnerName) {
7570
    var ele = document.createElement(SHORTCUTS[tagName] || tagName);
7571
    var k, x, y;
7572
 
7573
    for (k in data) {
7574
 
7575
      if (angular.isArray(data[k])) {
7576
        for (x = 0; x < data[k].length; x++) {
7577
          if (data[k][x].fn) {
7578
            for (y = 0; y < data[k][x].t; y++) {
7579
              createSvgElement(k, data[k][x].fn(y, spinnerName), ele, spinnerName);
7580
            }
7581
          } else {
7582
            createSvgElement(k, data[k][x], ele, spinnerName);
7583
          }
7584
        }
7585
 
7586
      } else {
7587
        setSvgAttribute(ele, k, data[k]);
7588
      }
7589
    }
7590
 
7591
    parent.appendChild(ele);
7592
  }
7593
 
7594
  function setSvgAttribute(ele, k, v) {
7595
    ele.setAttribute(SHORTCUTS[k] || k, v);
7596
  }
7597
 
7598
  function animationValues(strValues, i) {
7599
    var values = strValues.split(';');
7600
    var back = values.slice(i);
7601
    var front = values.slice(0, values.length - back.length);
7602
    values = back.concat(front).reverse();
7603
    return values.join(';') + ';' + values[0];
7604
  }
7605
 
7606
  var IOS_SPINNER = {
7607
    sw: 4,
7608
    lc: ROUND,
7609
    line: [{
7610
      fn: function(i, spinnerName) {
7611
        return {
7612
          y1: spinnerName == 'ios' ? 17 : 12,
7613
          y2: spinnerName == 'ios' ? 29 : 20,
7614
          t: TRANSLATE32 + ' rotate(' + (30 * i + (i < 6 ? 180 : -180)) + ')',
7615
          a: [{
7616
            fn: function() {
7617
              return {
7618
                an: STROKE_OPACITY,
7619
                dur: DURATION,
7620
                v: animationValues('0;.1;.15;.25;.35;.45;.55;.65;.7;.85;1', i),
7621
                rc: INDEFINITE
7622
              };
7623
            },
7624
            t: 1
7625
          }]
7626
        };
7627
      },
7628
      t: 12
7629
    }]
7630
  };
7631
 
7632
  var spinners = {
7633
 
7634
    android: {
7635
      c: [{
7636
        sw: 6,
7637
        da: 128,
7638
        os: 82,
7639
        r: 26,
7640
        cx: 32,
7641
        cy: 32,
7642
        f: NONE
7643
      }]
7644
    },
7645
 
7646
    ios: IOS_SPINNER,
7647
 
7648
    'ios-small': IOS_SPINNER,
7649
 
7650
    bubbles: {
7651
      sw: 0,
7652
      c: [{
7653
        fn: function(i) {
7654
          return {
7655
            cx: 24 * Math.cos(2 * Math.PI * i / 8),
7656
            cy: 24 * Math.sin(2 * Math.PI * i / 8),
7657
            t: TRANSLATE32,
7658
            a: [{
7659
              fn: function() {
7660
                return {
7661
                  an: 'r',
7662
                  dur: DURATION,
7663
                  v: animationValues('1;2;3;4;5;6;7;8', i),
7664
                  rc: INDEFINITE
7665
                };
7666
              },
7667
              t: 1
7668
            }]
7669
          };
7670
        },
7671
        t: 8
7672
      }]
7673
    },
7674
 
7675
    circles: {
7676
 
7677
      c: [{
7678
        fn: function(i) {
7679
          return {
7680
            r: 5,
7681
            cx: 24 * Math.cos(2 * Math.PI * i / 8),
7682
            cy: 24 * Math.sin(2 * Math.PI * i / 8),
7683
            t: TRANSLATE32,
7684
            sw: 0,
7685
            a: [{
7686
              fn: function() {
7687
                return {
7688
                  an: 'fill-opacity',
7689
                  dur: DURATION,
7690
                  v: animationValues('.3;.3;.3;.4;.7;.85;.9;1', i),
7691
                  rc: INDEFINITE
7692
                };
7693
              },
7694
              t: 1
7695
            }]
7696
          };
7697
        },
7698
        t: 8
7699
      }]
7700
    },
7701
 
7702
    crescent: {
7703
      c: [{
7704
        sw: 4,
7705
        da: 128,
7706
        os: 82,
7707
        r: 26,
7708
        cx: 32,
7709
        cy: 32,
7710
        f: NONE,
7711
        at: [SPIN_ANIMATION]
7712
      }]
7713
    },
7714
 
7715
    dots: {
7716
 
7717
      c: [{
7718
        fn: function(i) {
7719
          return {
7720
            cx: 16 + (16 * i),
7721
            cy: 32,
7722
            sw: 0,
7723
            a: [{
7724
              fn: function() {
7725
                return {
7726
                  an: 'fill-opacity',
7727
                  dur: DURATION,
7728
                  v: animationValues('.5;.6;.8;1;.8;.6;.5', i),
7729
                  rc: INDEFINITE
7730
                };
7731
              },
7732
              t: 1
7733
            }, {
7734
              fn: function() {
7735
                return {
7736
                  an: 'r',
7737
                  dur: DURATION,
7738
                  v: animationValues('4;5;6;5;4;3;3', i),
7739
                  rc: INDEFINITE
7740
                };
7741
              },
7742
              t: 1
7743
            }]
7744
          };
7745
        },
7746
        t: 3
7747
      }]
7748
    },
7749
 
7750
    lines: {
7751
      sw: 7,
7752
      lc: ROUND,
7753
      line: [{
7754
        fn: function(i) {
7755
          return {
7756
            x1: 10 + (i * 14),
7757
            x2: 10 + (i * 14),
7758
            a: [{
7759
              fn: function() {
7760
                return {
7761
                  an: 'y1',
7762
                  dur: DURATION,
7763
                  v: animationValues('16;18;28;18;16', i),
7764
                  rc: INDEFINITE
7765
                };
7766
              },
7767
              t: 1
7768
            }, {
7769
              fn: function() {
7770
                return {
7771
                  an: 'y2',
7772
                  dur: DURATION,
7773
                  v: animationValues('48;44;36;46;48', i),
7774
                  rc: INDEFINITE
7775
                };
7776
              },
7777
              t: 1
7778
            }, {
7779
              fn: function() {
7780
                return {
7781
                  an: STROKE_OPACITY,
7782
                  dur: DURATION,
7783
                  v: animationValues('1;.8;.5;.4;1', i),
7784
                  rc: INDEFINITE
7785
                };
7786
              },
7787
              t: 1
7788
            }]
7789
          };
7790
        },
7791
        t: 4
7792
      }]
7793
    },
7794
 
7795
    ripple: {
7796
      f: NONE,
7797
      'fill-rule': 'evenodd',
7798
      sw: 3,
7799
      circle: [{
7800
        fn: function(i) {
7801
          return {
7802
            cx: 32,
7803
            cy: 32,
7804
            a: [{
7805
              fn: function() {
7806
                return {
7807
                  an: 'r',
7808
                  begin: (i * -1) + 's',
7809
                  dur: '2s',
7810
                  v: '0;24',
7811
                  keyTimes: '0;1',
7812
                  keySplines: '0.1,0.2,0.3,1',
7813
                  calcMode: 'spline',
7814
                  rc: INDEFINITE
7815
                };
7816
              },
7817
              t: 1
7818
            }, {
7819
              fn: function() {
7820
                return {
7821
                  an: STROKE_OPACITY,
7822
                  begin: (i * -1) + 's',
7823
                  dur: '2s',
7824
                  v: '.2;1;.2;0',
7825
                  rc: INDEFINITE
7826
                };
7827
              },
7828
              t: 1
7829
            }]
7830
          };
7831
        },
7832
        t: 2
7833
      }]
7834
    },
7835
 
7836
    spiral: {
7837
      defs: [{
7838
        linearGradient: [{
7839
          id: 'sGD',
7840
          gradientUnits: 'userSpaceOnUse',
7841
          x1: 55, y1: 46, x2: 2, y2: 46,
7842
          stop: [{
7843
            offset: 0.1,
7844
            class: 'stop1'
7845
          }, {
7846
            offset: 1,
7847
            class: 'stop2'
7848
          }]
7849
        }]
7850
      }],
7851
      g: [{
7852
        sw: 4,
7853
        lc: ROUND,
7854
        f: NONE,
7855
        path: [{
7856
          stroke: 'url(#sGD)',
7857
          d: 'M4,32 c0,15,12,28,28,28c8,0,16-4,21-9'
7858
        }, {
7859
          d: 'M60,32 C60,16,47.464,4,32,4S4,16,4,32'
7860
        }],
7861
        at: [SPIN_ANIMATION]
7862
      }]
7863
    }
7864
 
7865
  };
7866
 
7867
  var animations = {
7868
 
7869
    android: function(ele) {
7870
      var rIndex = 0;
7871
      var rotateCircle = 0;
7872
      var startTime;
7873
      var svgEle = ele.querySelector('g');
7874
      var circleEle = ele.querySelector('circle');
7875
 
7876
      function run() {
7877
        var v = easeInOutCubic(Date.now() - startTime, 650);
7878
        var scaleX = 1;
7879
        var translateX = 0;
7880
        var dasharray = (188 - (58 * v));
7881
        var dashoffset = (182 - (182 * v));
7882
 
7883
        if (rIndex % 2) {
7884
          scaleX = -1;
7885
          translateX = -64;
7886
          dasharray = (128 - (-58 * v));
7887
          dashoffset = (182 * v);
7888
        }
7889
 
7890
        var rotateLine = [0, -101, -90, -11, -180, 79, -270, -191][rIndex];
7891
 
7892
        setSvgAttribute(circleEle, 'da', Math.max(Math.min(dasharray, 188), 128));
7893
        setSvgAttribute(circleEle, 'os', Math.max(Math.min(dashoffset, 182), 0));
7894
        setSvgAttribute(circleEle, 't', 'scale(' + scaleX + ',1) translate(' + translateX + ',0) rotate(' + rotateLine + ',32,32)');
7895
 
7896
        rotateCircle += 4.1;
7897
        if (rotateCircle > 359) rotateCircle = 0;
7898
        setSvgAttribute(svgEle, 't', 'rotate(' + rotateCircle + ',32,32)');
7899
 
7900
        if (v >= 1) {
7901
          rIndex++;
7902
          if (rIndex > 7) rIndex = 0;
7903
          startTime = Date.now();
7904
        }
7905
 
7906
        ionic.requestAnimationFrame(run);
7907
      }
7908
 
7909
      return function() {
7910
        startTime = Date.now();
7911
        run();
7912
      };
7913
 
7914
    }
7915
 
7916
  };
7917
 
7918
  function easeInOutCubic(t, c) {
7919
    t /= c / 2;
7920
    if (t < 1) return 1 / 2 * t * t * t;
7921
    t -= 2;
7922
    return 1 / 2 * (t * t * t + 2);
7923
  }
7924
 
7925
 
7926
  IonicModule
7927
  .controller('$ionicSpinner', [
7928
    '$element',
7929
    '$attrs',
7930
  function($element, $attrs) {
7931
    var spinnerName, spinner;
7932
 
7933
    this.init = function() {
7934
      var override = null;
7935
      if (ionic.Platform.platform() === 'windowsphone') {
7936
        override = 'android';
7937
      }
7938
      spinnerName = $attrs.icon || override || ionic.Platform.platform();
7939
      spinner = spinners[spinnerName];
7940
      if (!spinner) {
7941
        spinnerName = 'ios';
7942
        spinner = spinners.ios;
7943
      }
7944
 
7945
      var container = document.createElement('div');
7946
      createSvgElement('svg', {
7947
        viewBox: '0 0 64 64',
7948
        g: [spinners[spinnerName]]
7949
      }, container, spinnerName);
7950
 
7951
      // Specifically for animations to work,
7952
      // Android 4.3 and below requires the element to be
7953
      // added as an html string, rather than dynmically
7954
      // building up the svg element and appending it.
7955
      $element.html(container.innerHTML);
7956
 
7957
      this.start();
7958
 
7959
      return spinnerName;
7960
    };
7961
 
7962
    this.start = function() {
7963
      animations[spinnerName] && animations[spinnerName]($element[0])();
7964
    };
7965
 
7966
  }]);
7967
 
7968
})(ionic);
7969
 
7970
IonicModule
7971
.controller('$ionicTab', [
7972
  '$scope',
7973
  '$ionicHistory',
7974
  '$attrs',
7975
  '$location',
7976
  '$state',
7977
function($scope, $ionicHistory, $attrs, $location, $state) {
7978
  this.$scope = $scope;
7979
 
7980
  //All of these exposed for testing
7981
  this.hrefMatchesState = function() {
7982
    return $attrs.href && $location.path().indexOf(
7983
      $attrs.href.replace(/^#/, '').replace(/\/$/, '')
7984
    ) === 0;
7985
  };
7986
  this.srefMatchesState = function() {
7987
    return $attrs.uiSref && $state.includes($attrs.uiSref.split('(')[0]);
7988
  };
7989
  this.navNameMatchesState = function() {
7990
    return this.navViewName && $ionicHistory.isCurrentStateNavView(this.navViewName);
7991
  };
7992
 
7993
  this.tabMatchesState = function() {
7994
    return this.hrefMatchesState() || this.srefMatchesState() || this.navNameMatchesState();
7995
  };
7996
}]);
7997
 
7998
IonicModule
7999
.controller('$ionicTabs', [
8000
  '$scope',
8001
  '$element',
8002
  '$ionicHistory',
8003
function($scope, $element, $ionicHistory) {
8004
  var self = this;
8005
  var selectedTab = null;
8006
  var previousSelectedTab = null;
8007
  var selectedTabIndex;
8008
  self.tabs = [];
8009
 
8010
  self.selectedIndex = function() {
8011
    return self.tabs.indexOf(selectedTab);
8012
  };
8013
  self.selectedTab = function() {
8014
    return selectedTab;
8015
  };
8016
  self.previousSelectedTab = function() {
8017
    return previousSelectedTab;
8018
  };
8019
 
8020
  self.add = function(tab) {
8021
    $ionicHistory.registerHistory(tab);
8022
    self.tabs.push(tab);
8023
  };
8024
 
8025
  self.remove = function(tab) {
8026
    var tabIndex = self.tabs.indexOf(tab);
8027
    if (tabIndex === -1) {
8028
      return;
8029
    }
8030
    //Use a field like '$tabSelected' so developers won't accidentally set it in controllers etc
8031
    if (tab.$tabSelected) {
8032
      self.deselect(tab);
8033
      //Try to select a new tab if we're removing a tab
8034
      if (self.tabs.length === 1) {
8035
        //Do nothing if there are no other tabs to select
8036
      } else {
8037
        //Select previous tab if it's the last tab, else select next tab
8038
        var newTabIndex = tabIndex === self.tabs.length - 1 ? tabIndex - 1 : tabIndex + 1;
8039
        self.select(self.tabs[newTabIndex]);
8040
      }
8041
    }
8042
    self.tabs.splice(tabIndex, 1);
8043
  };
8044
 
8045
  self.deselect = function(tab) {
8046
    if (tab.$tabSelected) {
8047
      previousSelectedTab = selectedTab;
8048
      selectedTab = selectedTabIndex = null;
8049
      tab.$tabSelected = false;
8050
      (tab.onDeselect || noop)();
8051
      tab.$broadcast && tab.$broadcast('$ionicHistory.deselect');
8052
    }
8053
  };
8054
 
8055
  self.select = function(tab, shouldEmitEvent) {
8056
    var tabIndex;
8057
    if (isNumber(tab)) {
8058
      tabIndex = tab;
8059
      if (tabIndex >= self.tabs.length) return;
8060
      tab = self.tabs[tabIndex];
8061
    } else {
8062
      tabIndex = self.tabs.indexOf(tab);
8063
    }
8064
 
8065
    if (arguments.length === 1) {
8066
      shouldEmitEvent = !!(tab.navViewName || tab.uiSref);
8067
    }
8068
 
8069
    if (selectedTab && selectedTab.$historyId == tab.$historyId) {
8070
      if (shouldEmitEvent) {
8071
        $ionicHistory.goToHistoryRoot(tab.$historyId);
8072
      }
8073
 
8074
    } else if (selectedTabIndex !== tabIndex) {
8075
      forEach(self.tabs, function(tab) {
8076
        self.deselect(tab);
8077
      });
8078
 
8079
      selectedTab = tab;
8080
      selectedTabIndex = tabIndex;
8081
 
8082
      if (self.$scope && self.$scope.$parent) {
8083
        self.$scope.$parent.$activeHistoryId = tab.$historyId;
8084
      }
8085
 
8086
      //Use a funny name like $tabSelected so the developer doesn't overwrite the var in a child scope
8087
      tab.$tabSelected = true;
8088
      (tab.onSelect || noop)();
8089
 
8090
      if (shouldEmitEvent) {
8091
        $scope.$emit('$ionicHistory.change', {
8092
          type: 'tab',
8093
          tabIndex: tabIndex,
8094
          historyId: tab.$historyId,
8095
          navViewName: tab.navViewName,
8096
          hasNavView: !!tab.navViewName,
8097
          title: tab.title,
8098
          url: tab.href,
8099
          uiSref: tab.uiSref
8100
        });
8101
      }
8102
    }
8103
  };
8104
 
8105
  self.hasActiveScope = function() {
8106
    for (var x = 0; x < self.tabs.length; x++) {
8107
      if ($ionicHistory.isActiveScope(self.tabs[x])) {
8108
        return true;
8109
      }
8110
    }
8111
    return false;
8112
  };
8113
 
8114
}]);
8115
 
8116
IonicModule
8117
.controller('$ionicView', [
8118
  '$scope',
8119
  '$element',
8120
  '$attrs',
8121
  '$compile',
8122
  '$rootScope',
8123
function($scope, $element, $attrs, $compile, $rootScope) {
8124
  var self = this;
8125
  var navElementHtml = {};
8126
  var navViewCtrl;
8127
  var navBarDelegateHandle;
8128
  var hasViewHeaderBar;
8129
  var deregisters = [];
8130
  var viewTitle;
8131
 
8132
  var deregIonNavBarInit = $scope.$on('ionNavBar.init', function(ev, delegateHandle) {
8133
    // this view has its own ion-nav-bar, remember the navBarDelegateHandle for this view
8134
    ev.stopPropagation();
8135
    navBarDelegateHandle = delegateHandle;
8136
  });
8137
 
8138
 
8139
  self.init = function() {
8140
    deregIonNavBarInit();
8141
 
8142
    var modalCtrl = $element.inheritedData('$ionModalController');
8143
    navViewCtrl = $element.inheritedData('$ionNavViewController');
8144
 
8145
    // don't bother if inside a modal or there's no parent navView
8146
    if (!navViewCtrl || modalCtrl) return;
8147
 
8148
    // add listeners for when this view changes
8149
    $scope.$on('$ionicView.beforeEnter', self.beforeEnter);
8150
    $scope.$on('$ionicView.afterEnter', afterEnter);
8151
    $scope.$on('$ionicView.beforeLeave', deregisterFns);
8152
  };
8153
 
8154
  self.beforeEnter = function(ev, transData) {
8155
    // this event was emitted, starting at intial ion-view, then bubbles up
8156
    // only the first ion-view should do something with it, parent ion-views should ignore
8157
    if (transData && !transData.viewNotified) {
8158
      transData.viewNotified = true;
8159
 
8160
      if (!$rootScope.$$phase) $scope.$digest();
8161
      viewTitle = isDefined($attrs.viewTitle) ? $attrs.viewTitle : $attrs.title;
8162
 
8163
      var navBarItems = {};
8164
      for (var n in navElementHtml) {
8165
        navBarItems[n] = generateNavBarItem(navElementHtml[n]);
8166
      }
8167
 
8168
      navViewCtrl.beforeEnter(extend(transData, {
8169
        title: viewTitle,
8170
        showBack: !attrTrue('hideBackButton'),
8171
        navBarItems: navBarItems,
8172
        navBarDelegate: navBarDelegateHandle || null,
8173
        showNavBar: !attrTrue('hideNavBar'),
8174
        hasHeaderBar: !!hasViewHeaderBar
8175
      }));
8176
 
8177
      // make sure any existing observers are cleaned up
8178
      deregisterFns();
8179
    }
8180
  };
8181
 
8182
 
8183
  function afterEnter() {
8184
    // only listen for title updates after it has entered
8185
    // but also deregister the observe before it leaves
8186
    var viewTitleAttr = isDefined($attrs.viewTitle) && 'viewTitle' || isDefined($attrs.title) && 'title';
8187
    if (viewTitleAttr) {
8188
      titleUpdate($attrs[viewTitleAttr]);
8189
      deregisters.push($attrs.$observe(viewTitleAttr, titleUpdate));
8190
    }
8191
 
8192
    if (isDefined($attrs.hideBackButton)) {
8193
      deregisters.push($scope.$watch($attrs.hideBackButton, function(val) {
8194
        navViewCtrl.showBackButton(!val);
8195
      }));
8196
    }
8197
 
8198
    if (isDefined($attrs.hideNavBar)) {
8199
      deregisters.push($scope.$watch($attrs.hideNavBar, function(val) {
8200
        navViewCtrl.showBar(!val);
8201
      }));
8202
    }
8203
  }
8204
 
8205
 
8206
  function titleUpdate(newTitle) {
8207
    if (isDefined(newTitle) && newTitle !== viewTitle) {
8208
      viewTitle = newTitle;
8209
      navViewCtrl.title(viewTitle);
8210
    }
8211
  }
8212
 
8213
 
8214
  function deregisterFns() {
8215
    // remove all existing $attrs.$observe's
8216
    for (var x = 0; x < deregisters.length; x++) {
8217
      deregisters[x]();
8218
    }
8219
    deregisters = [];
8220
  }
8221
 
8222
 
8223
  function generateNavBarItem(html) {
8224
    if (html) {
8225
      // every time a view enters we need to recreate its view buttons if they exist
8226
      return $compile(html)($scope.$new());
8227
    }
8228
  }
8229
 
8230
 
8231
  function attrTrue(key) {
8232
    return !!$scope.$eval($attrs[key]);
8233
  }
8234
 
8235
 
8236
  self.navElement = function(type, html) {
8237
    navElementHtml[type] = html;
8238
  };
8239
 
8240
}]);
8241
 
8242
/*
8243
 * We don't document the ionActionSheet directive, we instead document
8244
 * the $ionicActionSheet service
8245
 */
8246
IonicModule
8247
.directive('ionActionSheet', ['$document', function($document) {
8248
  return {
8249
    restrict: 'E',
8250
    scope: true,
8251
    replace: true,
8252
    link: function($scope, $element) {
8253
 
8254
      var keyUp = function(e) {
8255
        if (e.which == 27) {
8256
          $scope.cancel();
8257
          $scope.$apply();
8258
        }
8259
      };
8260
 
8261
      var backdropClick = function(e) {
8262
        if (e.target == $element[0]) {
8263
          $scope.cancel();
8264
          $scope.$apply();
8265
        }
8266
      };
8267
      $scope.$on('$destroy', function() {
8268
        $element.remove();
8269
        $document.unbind('keyup', keyUp);
8270
      });
8271
 
8272
      $document.bind('keyup', keyUp);
8273
      $element.bind('click', backdropClick);
8274
    },
8275
    template: '<div class="action-sheet-backdrop">' +
8276
                '<div class="action-sheet-wrapper">' +
8277
                  '<div class="action-sheet" ng-class="{\'action-sheet-has-icons\': $actionSheetHasIcon}">' +
8278
                    '<div class="action-sheet-group action-sheet-options">' +
8279
                      '<div class="action-sheet-title" ng-if="titleText" ng-bind-html="titleText"></div>' +
8280
                      '<button class="button action-sheet-option" ng-click="buttonClicked($index)" ng-repeat="b in buttons" ng-bind-html="b.text"></button>' +
8281
                      '<button class="button destructive action-sheet-destructive" ng-if="destructiveText" ng-click="destructiveButtonClicked()" ng-bind-html="destructiveText"></button>' +
8282
                    '</div>' +
8283
                    '<div class="action-sheet-group action-sheet-cancel" ng-if="cancelText">' +
8284
                      '<button class="button" ng-click="cancel()" ng-bind-html="cancelText"></button>' +
8285
                    '</div>' +
8286
                  '</div>' +
8287
                '</div>' +
8288
              '</div>'
8289
  };
8290
}]);
8291
 
8292
 
8293
/**
8294
 * @ngdoc directive
8295
 * @name ionCheckbox
8296
 * @module ionic
8297
 * @restrict E
8298
 * @codepen hqcju
8299
 * @description
8300
 * The checkbox is no different than the HTML checkbox input, except it's styled differently.
8301
 *
8302
 * The checkbox behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]).
8303
 *
8304
 * @usage
8305
 * ```html
8306
 * <ion-checkbox ng-model="isChecked">Checkbox Label</ion-checkbox>
8307
 * ```
8308
 */
8309
 
8310
IonicModule
8311
.directive('ionCheckbox', ['$ionicConfig', function($ionicConfig) {
8312
  return {
8313
    restrict: 'E',
8314
    replace: true,
8315
    require: '?ngModel',
8316
    transclude: true,
8317
    template:
8318
      '<label class="item item-checkbox">' +
8319
        '<div class="checkbox checkbox-input-hidden disable-pointer-events">' +
8320
          '<input type="checkbox">' +
8321
          '<i class="checkbox-icon"></i>' +
8322
        '</div>' +
8323
        '<div class="item-content disable-pointer-events" ng-transclude></div>' +
8324
      '</label>',
8325
    compile: function(element, attr) {
8326
      var input = element.find('input');
8327
      forEach({
8328
        'name': attr.name,
8329
        'ng-value': attr.ngValue,
8330
        'ng-model': attr.ngModel,
8331
        'ng-checked': attr.ngChecked,
8332
        'ng-disabled': attr.ngDisabled,
8333
        'ng-true-value': attr.ngTrueValue,
8334
        'ng-false-value': attr.ngFalseValue,
8335
        'ng-change': attr.ngChange,
8336
        'ng-required': attr.ngRequired,
8337
        'required': attr.required
8338
      }, function(value, name) {
8339
        if (isDefined(value)) {
8340
          input.attr(name, value);
8341
        }
8342
      });
8343
      var checkboxWrapper = element[0].querySelector('.checkbox');
8344
      checkboxWrapper.classList.add('checkbox-' + $ionicConfig.form.checkbox());
8345
    }
8346
  };
8347
}]);
8348
 
8349
 
8350
/**
8351
 * @ngdoc directive
8352
 * @restrict A
8353
 * @name collectionRepeat
8354
 * @module ionic
8355
 * @codepen 7ec1ec58f2489ab8f359fa1a0fe89c15
8356
 * @description
8357
 * `collection-repeat` allows an app to show huge lists of items much more performantly than
8358
 * `ng-repeat`.
8359
 *
8360
 * It renders into the DOM only as many items as are currently visible.
8361
 *
8362
 * This means that on a phone screen that can fit eight items, only the eight items matching
8363
 * the current scroll position will be rendered.
8364
 *
8365
 * **The Basics**:
8366
 *
8367
 * - The data given to collection-repeat must be an array.
8368
 * - If the `item-height` and `item-width` attributes are not supplied, it will be assumed that
8369
 *   every item in the list has the same dimensions as the first item.
8370
 * - Don't use angular one-time binding (`::`) with collection-repeat. The scope of each item is
8371
 *   assigned new data and re-digested as you scroll. Bindings need to update, and one-time bindings
8372
 *   won't.
8373
 *
8374
 * **Performance Tips**:
8375
 *
8376
 * - The iOS webview has a performance bottleneck when switching out `<img src>` attributes.
8377
 *   To increase performance of images on iOS, cache your images in advance and,
8378
 *   if possible, lower the number of unique images. We're working on [a solution](https://github.com/driftyco/ionic/issues/3194).
8379
 *
8380
 * @usage
8381
 * #### Basic Item List ([codepen](http://codepen.io/ionic/pen/0c2c35a34a8b18ad4d793fef0b081693))
8382
 * ```html
8383
 * <ion-content>
8384
 *   <ion-item collection-repeat="item in items">
8385
 *     {% raw %}{{item}}{% endraw %}
8386
 *   </ion-item>
8387
 * </ion-content>
8388
 * ```
8389
 *
8390
 * #### Grid of Images ([codepen](http://codepen.io/ionic/pen/5515d4efd9d66f780e96787387f41664))
8391
 * ```html
8392
 * <ion-content>
8393
 *   <img collection-repeat="photo in photos"
8394
 *     item-width="33%"
8395
 *     item-height="200px"
8396
 *     ng-src="{% raw %}{{photo.url}}{% endraw %}">
8397
 * </ion-content>
8398
 * ```
8399
 *
8400
 * #### Horizontal Scroller, Dynamic Item Width ([codepen](http://codepen.io/ionic/pen/67cc56b349124a349acb57a0740e030e))
8401
 * ```html
8402
 * <ion-content>
8403
 *   <h2>Available Kittens:</h2>
8404
 *   <ion-scroll direction="x" class="available-scroller">
8405
 *     <div class="photo" collection-repeat="photo in main.photos"
8406
 *        item-height="250" item-width="photo.width + 30">
8407
 *        <img ng-src="{{photo.src}}">
8408
 *     </div>
8409
 *   </ion-scroll>
8410
 * </ion-content>
8411
 * ```
8412
 *
8413
 * @param {expression} collection-repeat The expression indicating how to enumerate a collection,
8414
 *   of the format  `variable in expression` – where variable is the user defined loop variable
8415
 *   and `expression` is a scope expression giving the collection to enumerate.
8416
 *   For example: `album in artist.albums` or `album in artist.albums | orderBy:'name'`.
8417
 * @param {expression=} item-width The width of the repeated element. The expression must return
8418
 *   a number (pixels) or a percentage. Defaults to the width of the first item in the list.
8419
 *   (previously named collection-item-width)
8420
 * @param {expression=} item-height The height of the repeated element. The expression must return
8421
 *   a number (pixels) or a percentage. Defaults to the height of the first item in the list.
8422
 *   (previously named collection-item-height)
8423
 * @param {number=} item-render-buffer The number of items to load before and after the visible
8424
 *   items in the list. Default 3. Tip: set this higher if you have lots of images to preload, but
8425
 *   don't set it too high or you'll see performance loss.
8426
 * @param {boolean=} force-refresh-images Force images to refresh as you scroll. This fixes a problem
8427
 *   where, when an element is interchanged as scrolling, its image will still have the old src
8428
 *   while the new src loads. Setting this to true comes with a small performance loss.
8429
 */
8430
 
8431
IonicModule
8432
.directive('collectionRepeat', CollectionRepeatDirective)
8433
.factory('$ionicCollectionManager', RepeatManagerFactory);
8434
 
8435
var ONE_PX_TRANSPARENT_IMG_SRC = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
8436
var WIDTH_HEIGHT_REGEX = /height:.*?px;\s*width:.*?px/;
8437
var DEFAULT_RENDER_BUFFER = 3;
8438
 
8439
CollectionRepeatDirective.$inject = ['$ionicCollectionManager', '$parse', '$window', '$$rAF', '$rootScope', '$timeout'];
8440
function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$rAF, $rootScope, $timeout) {
8441
  return {
8442
    restrict: 'A',
8443
    priority: 1000,
8444
    transclude: 'element',
8445
    $$tlb: true,
8446
    require: '^^$ionicScroll',
8447
    link: postLink
8448
  };
8449
 
8450
  function postLink(scope, element, attr, scrollCtrl, transclude) {
8451
    var scrollView = scrollCtrl.scrollView;
8452
    var node = element[0];
8453
    var containerNode = angular.element('<div class="collection-repeat-container">')[0];
8454
    node.parentNode.replaceChild(containerNode, node);
8455
 
8456
    if (scrollView.options.scrollingX && scrollView.options.scrollingY) {
8457
      throw new Error("collection-repeat expected a parent x or y scrollView, not " +
8458
                      "an xy scrollView.");
8459
    }
8460
 
8461
    var repeatExpr = attr.collectionRepeat;
8462
    var match = repeatExpr.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
8463
    if (!match) {
8464
      throw new Error("collection-repeat expected expression in form of '_item_ in " +
8465
                      "_collection_[ track by _id_]' but got '" + attr.collectionRepeat + "'.");
8466
    }
8467
    var keyExpr = match[1];
8468
    var listExpr = match[2];
8469
    var listGetter = $parse(listExpr);
8470
    var heightData = {};
8471
    var widthData = {};
8472
    var computedStyleDimensions = {};
8473
    var data = [];
8474
    var repeatManager;
8475
 
8476
    // attr.collectionBufferSize is deprecated
8477
    var renderBufferExpr = attr.itemRenderBuffer || attr.collectionBufferSize;
8478
    var renderBuffer = angular.isDefined(renderBufferExpr) ?
8479
      parseInt(renderBufferExpr) :
8480
      DEFAULT_RENDER_BUFFER;
8481
 
8482
    // attr.collectionItemHeight is deprecated
8483
    var heightExpr = attr.itemHeight || attr.collectionItemHeight;
8484
    // attr.collectionItemWidth is deprecated
8485
    var widthExpr = attr.itemWidth || attr.collectionItemWidth;
8486
 
8487
    var afterItemsContainer = initAfterItemsContainer();
8488
 
8489
    var changeValidator = makeChangeValidator();
8490
    initDimensions();
8491
 
8492
    // Dimensions are refreshed on resize or data change.
8493
    scrollCtrl.$element.on('scroll-resize', refreshDimensions);
8494
 
8495
    angular.element($window).on('resize', onResize);
8496
    var unlistenToExposeAside = $rootScope.$on('$ionicExposeAside', ionic.animationFrameThrottle(function() {
8497
      scrollCtrl.scrollView.resize();
8498
      onResize();
8499
    }));
8500
    $timeout(refreshDimensions, 0, false);
8501
 
8502
    function onResize() {
8503
      if (changeValidator.resizeRequiresRefresh(scrollView.__clientWidth, scrollView.__clientHeight)) {
8504
        refreshDimensions();
8505
      }
8506
    }
8507
 
8508
    scope.$watchCollection(listGetter, function(newValue) {
8509
      data = newValue || (newValue = []);
8510
      if (!angular.isArray(newValue)) {
8511
        throw new Error("collection-repeat expected an array for '" + listExpr + "', " +
8512
          "but got a " + typeof value);
8513
      }
8514
      // Wait for this digest to end before refreshing everything.
8515
      scope.$$postDigest(function() {
8516
        getRepeatManager().setData(data);
8517
        if (changeValidator.dataChangeRequiresRefresh(data)) refreshDimensions();
8518
      });
8519
    });
8520
 
8521
    scope.$on('$destroy', function() {
8522
      angular.element($window).off('resize', onResize);
8523
      unlistenToExposeAside();
8524
      scrollCtrl.$element && scrollCtrl.$element.off('scroll-resize', refreshDimensions);
8525
 
8526
      computedStyleNode && computedStyleNode.parentNode &&
8527
        computedStyleNode.parentNode.removeChild(computedStyleNode);
8528
      computedStyleScope && computedStyleScope.$destroy();
8529
      computedStyleScope = computedStyleNode = null;
8530
 
8531
      repeatManager && repeatManager.destroy();
8532
      repeatManager = null;
8533
    });
8534
 
8535
    function makeChangeValidator() {
8536
      var self;
8537
      return (self = {
8538
        dataLength: 0,
8539
        width: 0,
8540
        height: 0,
8541
        // A resize triggers a refresh only if we have data, the scrollView has size,
8542
        // and the size has changed.
8543
        resizeRequiresRefresh: function(newWidth, newHeight) {
8544
          var requiresRefresh = self.dataLength && newWidth && newHeight &&
8545
            (newWidth !== self.width || newHeight !== self.height);
8546
 
8547
          self.width = newWidth;
8548
          self.height = newHeight;
8549
 
8550
          return !!requiresRefresh;
8551
        },
8552
        // A change in data only triggers a refresh if the data has length, or if the data's
8553
        // length is less than before.
8554
        dataChangeRequiresRefresh: function(newData) {
8555
          var requiresRefresh = newData.length > 0 || newData.length < self.dataLength;
8556
 
8557
          self.dataLength = newData.length;
8558
 
8559
          return !!requiresRefresh;
8560
        }
8561
      });
8562
    }
8563
 
8564
    function getRepeatManager() {
8565
      return repeatManager || (repeatManager = new $ionicCollectionManager({
8566
        afterItemsNode: afterItemsContainer[0],
8567
        containerNode: containerNode,
8568
        heightData: heightData,
8569
        widthData: widthData,
8570
        forceRefreshImages: !!(isDefined(attr.forceRefreshImages) && attr.forceRefreshImages !== 'false'),
8571
        keyExpression: keyExpr,
8572
        renderBuffer: renderBuffer,
8573
        scope: scope,
8574
        scrollView: scrollCtrl.scrollView,
8575
        transclude: transclude
8576
      }));
8577
    }
8578
 
8579
    function initAfterItemsContainer() {
8580
      var container = angular.element(
8581
        scrollView.__content.querySelector('.collection-repeat-after-container')
8582
      );
8583
      // Put everything in the view after the repeater into a container.
8584
      if (!container.length) {
8585
        var elementIsAfterRepeater = false;
8586
        var afterNodes = [].filter.call(scrollView.__content.childNodes, function(node) {
8587
          if (ionic.DomUtil.contains(node, containerNode)) {
8588
            elementIsAfterRepeater = true;
8589
            return false;
8590
          }
8591
          return elementIsAfterRepeater;
8592
        });
8593
        container = angular.element('<span class="collection-repeat-after-container">');
8594
        if (scrollView.options.scrollingX) {
8595
          container.addClass('horizontal');
8596
        }
8597
        container.append(afterNodes);
8598
        scrollView.__content.appendChild(container[0]);
8599
      }
8600
      return container;
8601
    }
8602
 
8603
    function initDimensions() {
8604
      //Height and width have four 'modes':
8605
      //1) Computed Mode
8606
      //  - Nothing is supplied, so we getComputedStyle() on one element in the list and use
8607
      //    that width and height value for the width and height of every item. This is re-computed
8608
      //    every resize.
8609
      //2) Constant Mode, Static Integer
8610
      //  - The user provides a constant number for width or height, in pixels. We parse it,
8611
      //    store it on the `value` field, and it never changes
8612
      //3) Constant Mode, Percent
8613
      //  - The user provides a percent string for width or height. The getter for percent is
8614
      //    stored on the `getValue()` field, and is re-evaluated once every resize. The result
8615
      //    is stored on the `value` field.
8616
      //4) Dynamic Mode
8617
      //  - The user provides a dynamic expression for the width or height.  This is re-evaluated
8618
      //    for every item, stored on the `.getValue()` field.
8619
      if (heightExpr) {
8620
        parseDimensionAttr(heightExpr, heightData);
8621
      } else {
8622
        heightData.computed = true;
8623
      }
8624
      if (widthExpr) {
8625
        parseDimensionAttr(widthExpr, widthData);
8626
      } else {
8627
        widthData.computed = true;
8628
      }
8629
    }
8630
 
8631
    function refreshDimensions() {
8632
      var hasData = data.length > 0;
8633
 
8634
      if (hasData && (heightData.computed || widthData.computed)) {
8635
        computeStyleDimensions();
8636
      }
8637
 
8638
      if (hasData && heightData.computed) {
8639
        heightData.value = computedStyleDimensions.height;
8640
        if (!heightData.value) {
8641
          throw new Error('collection-repeat tried to compute the height of repeated elements "' +
8642
            repeatExpr + '", but was unable to. Please provide the "item-height" attribute. ' +
8643
            'http://ionicframework.com/docs/api/directive/collectionRepeat/');
8644
        }
8645
      } else if (!heightData.dynamic && heightData.getValue) {
8646
        // If it's a constant with a getter (eg percent), we just refresh .value after resize
8647
        heightData.value = heightData.getValue();
8648
      }
8649
 
8650
      if (hasData && widthData.computed) {
8651
        widthData.value = computedStyleDimensions.width;
8652
        if (!widthData.value) {
8653
          throw new Error('collection-repeat tried to compute the width of repeated elements "' +
8654
            repeatExpr + '", but was unable to. Please provide the "item-width" attribute. ' +
8655
            'http://ionicframework.com/docs/api/directive/collectionRepeat/');
8656
        }
8657
      } else if (!widthData.dynamic && widthData.getValue) {
8658
        // If it's a constant with a getter (eg percent), we just refresh .value after resize
8659
        widthData.value = widthData.getValue();
8660
      }
8661
      // Dynamic dimensions aren't updated on resize. Since they're already dynamic anyway,
8662
      // .getValue() will be used.
8663
 
8664
      getRepeatManager().refreshLayout();
8665
    }
8666
 
8667
    function parseDimensionAttr(attrValue, dimensionData) {
8668
      if (!attrValue) return;
8669
 
8670
      var parsedValue;
8671
      // Try to just parse the plain attr value
8672
      try {
8673
        parsedValue = $parse(attrValue);
8674
      } catch (e) {
8675
        // If the parse fails and the value has `px` or `%` in it, surround the attr in
8676
        // quotes, to attempt to let the user provide a simple `attr="100%"` or `attr="100px"`
8677
        if (attrValue.trim().match(/\d+(px|%)$/)) {
8678
          attrValue = '"' + attrValue + '"';
8679
        }
8680
        parsedValue = $parse(attrValue);
8681
      }
8682
 
8683
      var constantAttrValue = attrValue.replace(/(\'|\"|px|%)/g, '').trim();
8684
      var isConstant = constantAttrValue.length && !/([a-zA-Z]|\$|:|\?)/.test(constantAttrValue);
8685
      dimensionData.attrValue = attrValue;
8686
 
8687
      // If it's a constant, it's either a percent or just a constant pixel number.
8688
      if (isConstant) {
8689
        var intValue = parseInt(parsedValue());
8690
 
8691
        // For percents, store the percent getter on .getValue()
8692
        if (attrValue.indexOf('%') > -1) {
8693
          var decimalValue = intValue / 100;
8694
          dimensionData.getValue = dimensionData === heightData ?
8695
            function() { return Math.floor(decimalValue * scrollView.__clientHeight); } :
8696
            function() { return Math.floor(decimalValue * scrollView.__clientWidth); };
8697
        } else {
8698
          // For static constants, just store the static constant.
8699
          dimensionData.value = intValue;
8700
        }
8701
 
8702
      } else {
8703
        dimensionData.dynamic = true;
8704
        dimensionData.getValue = dimensionData === heightData ?
8705
          function heightGetter(scope, locals) {
8706
            var result = parsedValue(scope, locals);
8707
            if (result.charAt && result.charAt(result.length - 1) === '%') {
8708
              return Math.floor(parseInt(result) / 100 * scrollView.__clientHeight);
8709
            }
8710
            return parseInt(result);
8711
          } :
8712
          function widthGetter(scope, locals) {
8713
            var result = parsedValue(scope, locals);
8714
            if (result.charAt && result.charAt(result.length - 1) === '%') {
8715
              return Math.floor(parseInt(result) / 100 * scrollView.__clientWidth);
8716
            }
8717
            return parseInt(result);
8718
          };
8719
      }
8720
    }
8721
 
8722
    var computedStyleNode;
8723
    var computedStyleScope;
8724
    function computeStyleDimensions() {
8725
      if (!computedStyleNode) {
8726
        transclude(computedStyleScope = scope.$new(), function(clone) {
8727
          clone[0].removeAttribute('collection-repeat'); // remove absolute position styling
8728
          computedStyleNode = clone[0];
8729
        });
8730
      }
8731
 
8732
      computedStyleScope[keyExpr] = (listGetter(scope) || [])[0];
8733
      if (!$rootScope.$$phase) computedStyleScope.$digest();
8734
      containerNode.appendChild(computedStyleNode);
8735
 
8736
      var style = $window.getComputedStyle(computedStyleNode);
8737
      computedStyleDimensions.width = parseInt(style.width);
8738
      computedStyleDimensions.height = parseInt(style.height);
8739
 
8740
      containerNode.removeChild(computedStyleNode);
8741
    }
8742
 
8743
  }
8744
 
8745
}
8746
 
8747
RepeatManagerFactory.$inject = ['$rootScope', '$window', '$$rAF'];
8748
function RepeatManagerFactory($rootScope, $window, $$rAF) {
8749
  var EMPTY_DIMENSION = { primaryPos: 0, secondaryPos: 0, primarySize: 0, secondarySize: 0, rowPrimarySize: 0 };
8750
 
8751
  return function RepeatController(options) {
8752
    var afterItemsNode = options.afterItemsNode;
8753
    var containerNode = options.containerNode;
8754
    var forceRefreshImages = options.forceRefreshImages;
8755
    var heightData = options.heightData;
8756
    var widthData = options.widthData;
8757
    var keyExpression = options.keyExpression;
8758
    var renderBuffer = options.renderBuffer;
8759
    var scope = options.scope;
8760
    var scrollView = options.scrollView;
8761
    var transclude = options.transclude;
8762
 
8763
    var data = [];
8764
 
8765
    var getterLocals = {};
8766
    var heightFn = heightData.getValue || function() { return heightData.value; };
8767
    var heightGetter = function(index, value) {
8768
      getterLocals[keyExpression] = value;
8769
      getterLocals.$index = index;
8770
      return heightFn(scope, getterLocals);
8771
    };
8772
 
8773
    var widthFn = widthData.getValue || function() { return widthData.value; };
8774
    var widthGetter = function(index, value) {
8775
      getterLocals[keyExpression] = value;
8776
      getterLocals.$index = index;
8777
      return widthFn(scope, getterLocals);
8778
    };
8779
 
8780
    var isVertical = !!scrollView.options.scrollingY;
8781
 
8782
    // We say it's a grid view if we're either dynamic or not 100% width
8783
    var isGridView = isVertical ?
8784
      (widthData.dynamic || widthData.value !== scrollView.__clientWidth) :
8785
      (heightData.dynamic || heightData.value !== scrollView.__clientHeight);
8786
 
8787
    var isStaticView = !heightData.dynamic && !widthData.dynamic;
8788
 
8789
    var PRIMARY = 'PRIMARY';
8790
    var SECONDARY = 'SECONDARY';
8791
    var TRANSLATE_TEMPLATE_STR = isVertical ?
8792
      'translate3d(SECONDARYpx,PRIMARYpx,0)' :
8793
      'translate3d(PRIMARYpx,SECONDARYpx,0)';
8794
    var WIDTH_HEIGHT_TEMPLATE_STR = isVertical ?
8795
      'height: PRIMARYpx; width: SECONDARYpx;' :
8796
      'height: SECONDARYpx; width: PRIMARYpx;';
8797
 
8798
    var estimatedHeight;
8799
    var estimatedWidth;
8800
 
8801
    var repeaterBeforeSize = 0;
8802
    var repeaterAfterSize = 0;
8803
 
8804
    var renderStartIndex = -1;
8805
    var renderEndIndex = -1;
8806
    var renderAfterBoundary = -1;
8807
    var renderBeforeBoundary = -1;
8808
 
8809
    var itemsPool = [];
8810
    var itemsLeaving = [];
8811
    var itemsEntering = [];
8812
    var itemsShownMap = {};
8813
    var nextItemId = 0;
8814
 
8815
    var scrollViewSetDimensions = isVertical ?
8816
      function() { scrollView.setDimensions(null, null, null, view.getContentSize(), true); } :
8817
      function() { scrollView.setDimensions(null, null, view.getContentSize(), null, true); };
8818
 
8819
    // view is a mix of list/grid methods + static/dynamic methods.
8820
    // See bottom for implementations. Available methods:
8821
    //
8822
    // getEstimatedPrimaryPos(i), getEstimatedSecondaryPos(i), getEstimatedIndex(scrollTop),
8823
    // calculateDimensions(toIndex), getDimensions(index),
8824
    // updateRenderRange(scrollTop, scrollValueEnd), onRefreshLayout(), onRefreshData()
8825
    var view = isVertical ? new VerticalViewType() : new HorizontalViewType();
8826
    (isGridView ? GridViewType : ListViewType).call(view);
8827
    (isStaticView ? StaticViewType : DynamicViewType).call(view);
8828
 
8829
    var contentSizeStr = isVertical ? 'getContentHeight' : 'getContentWidth';
8830
    var originalGetContentSize = scrollView.options[contentSizeStr];
8831
    scrollView.options[contentSizeStr] = angular.bind(view, view.getContentSize);
8832
 
8833
    scrollView.__$callback = scrollView.__callback;
8834
    scrollView.__callback = function(transformLeft, transformTop, zoom, wasResize) {
8835
      var scrollValue = view.getScrollValue();
8836
      if (renderStartIndex === -1 ||
8837
          scrollValue + view.scrollPrimarySize > renderAfterBoundary ||
8838
          scrollValue < renderBeforeBoundary) {
8839
        render();
8840
      }
8841
      scrollView.__$callback(transformLeft, transformTop, zoom, wasResize);
8842
    };
8843
 
8844
    var isLayoutReady = false;
8845
    var isDataReady = false;
8846
    this.refreshLayout = function() {
8847
      if (data.length) {
8848
        estimatedHeight = heightGetter(0, data[0]);
8849
        estimatedWidth = widthGetter(0, data[0]);
8850
      } else {
8851
        // If we don't have any data in our array, just guess.
8852
        estimatedHeight = 100;
8853
        estimatedWidth = 100;
8854
      }
8855
 
8856
      // Get the size of every element AFTER the repeater. We have to get the margin before and
8857
      // after the first/last element to fix a browser bug with getComputedStyle() not counting
8858
      // the first/last child's margins into height.
8859
      var style = getComputedStyle(afterItemsNode) || {};
8860
      var firstStyle = afterItemsNode.firstElementChild && getComputedStyle(afterItemsNode.firstElementChild) || {};
8861
      var lastStyle = afterItemsNode.lastElementChild && getComputedStyle(afterItemsNode.lastElementChild) || {};
8862
      repeaterAfterSize = (parseInt(style[isVertical ? 'height' : 'width']) || 0) +
8863
        (firstStyle && parseInt(firstStyle[isVertical ? 'marginTop' : 'marginLeft']) || 0) +
8864
        (lastStyle && parseInt(lastStyle[isVertical ? 'marginBottom' : 'marginRight']) || 0);
8865
 
8866
      // Get the offsetTop of the repeater.
8867
      repeaterBeforeSize = 0;
8868
      var current = containerNode;
8869
      do {
8870
        repeaterBeforeSize += current[isVertical ? 'offsetTop' : 'offsetLeft'];
8871
      } while ( ionic.DomUtil.contains(scrollView.__content, current = current.offsetParent) );
8872
 
8873
      var containerPrevNode = containerNode.previousElementSibling;
8874
      var beforeStyle = containerPrevNode ? $window.getComputedStyle(containerPrevNode) : {};
8875
      var beforeMargin = parseInt(beforeStyle[isVertical ? 'marginBottom' : 'marginRight'] || 0);
8876
 
8877
      // Because we position the collection container with position: relative, it doesn't take
8878
      // into account where to position itself relative to the previous element's marginBottom.
8879
      // To compensate, we translate the container up by the previous element's margin.
8880
      containerNode.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
8881
        .replace(PRIMARY, -beforeMargin)
8882
        .replace(SECONDARY, 0);
8883
      repeaterBeforeSize -= beforeMargin;
8884
 
8885
      if (!scrollView.__clientHeight || !scrollView.__clientWidth) {
8886
        scrollView.__clientWidth = scrollView.__container.clientWidth;
8887
        scrollView.__clientHeight = scrollView.__container.clientHeight;
8888
      }
8889
 
8890
      (view.onRefreshLayout || angular.noop)();
8891
      view.refreshDirection();
8892
      scrollViewSetDimensions();
8893
 
8894
      // Create the pool of items for reuse, setting the size to (estimatedItemsOnScreen) * 2,
8895
      // plus the size of the renderBuffer.
8896
      if (!isLayoutReady) {
8897
        var poolSize = Math.max(20, renderBuffer * 3);
8898
        for (var i = 0; i < poolSize; i++) {
8899
          itemsPool.push(new RepeatItem());
8900
        }
8901
      }
8902
 
8903
      isLayoutReady = true;
8904
      if (isLayoutReady && isDataReady) {
8905
        // If the resize or latest data change caused the scrollValue to
8906
        // now be out of bounds, resize the scrollView.
8907
        if (scrollView.__scrollLeft > scrollView.__maxScrollLeft ||
8908
            scrollView.__scrollTop > scrollView.__maxScrollTop) {
8909
          scrollView.resize();
8910
        }
8911
        forceRerender(true);
8912
      }
8913
    };
8914
 
8915
    this.setData = function(newData) {
8916
      data = newData;
8917
      (view.onRefreshData || angular.noop)();
8918
      isDataReady = true;
8919
    };
8920
 
8921
    this.destroy = function() {
8922
      render.destroyed = true;
8923
 
8924
      itemsPool.forEach(function(item) {
8925
        item.scope.$destroy();
8926
        item.scope = item.element = item.node = item.images = null;
8927
      });
8928
      itemsPool.length = itemsEntering.length = itemsLeaving.length = 0;
8929
      itemsShownMap = {};
8930
 
8931
      //Restore the scrollView's normal behavior and resize it to normal size.
8932
      scrollView.options[contentSizeStr] = originalGetContentSize;
8933
      scrollView.__callback = scrollView.__$callback;
8934
      scrollView.resize();
8935
 
8936
      (view.onDestroy || angular.noop)();
8937
    };
8938
 
8939
    function forceRerender() {
8940
      return render(true);
8941
    }
8942
    function render(forceRerender) {
8943
      if (render.destroyed) return;
8944
      var i;
8945
      var ii;
8946
      var item;
8947
      var dim;
8948
      var scope;
8949
      var scrollValue = view.getScrollValue();
8950
      var scrollValueEnd = scrollValue + view.scrollPrimarySize;
8951
 
8952
      view.updateRenderRange(scrollValue, scrollValueEnd);
8953
 
8954
      renderStartIndex = Math.max(0, renderStartIndex - renderBuffer);
8955
      renderEndIndex = Math.min(data.length - 1, renderEndIndex + renderBuffer);
8956
 
8957
      for (i in itemsShownMap) {
8958
        if (i < renderStartIndex || i > renderEndIndex) {
8959
          item = itemsShownMap[i];
8960
          delete itemsShownMap[i];
8961
          itemsLeaving.push(item);
8962
          item.isShown = false;
8963
        }
8964
      }
8965
 
8966
      // Render indicies that aren't shown yet
8967
      //
8968
      // NOTE(ajoslin): this may sound crazy, but calling any other functions during this render
8969
      // loop will often push the render time over the edge from less than one frame to over
8970
      // one frame, causing visible jank.
8971
      // DON'T call any other functions inside this loop unless it's vital.
8972
      for (i = renderStartIndex; i <= renderEndIndex; i++) {
8973
        // We only go forward with render if the index is in data, the item isn't already shown,
8974
        // or forceRerender is on.
8975
        if (i >= data.length || (itemsShownMap[i] && !forceRerender)) continue;
8976
 
8977
        item = itemsShownMap[i] || (itemsShownMap[i] = itemsLeaving.length ? itemsLeaving.pop() :
8978
                                    itemsPool.length ? itemsPool.shift() :
8979
                                    new RepeatItem());
8980
        itemsEntering.push(item);
8981
        item.isShown = true;
8982
 
8983
        scope = item.scope;
8984
        scope.$index = i;
8985
        scope[keyExpression] = data[i];
8986
        scope.$first = (i === 0);
8987
        scope.$last = (i === (data.length - 1));
8988
        scope.$middle = !(scope.$first || scope.$last);
8989
        scope.$odd = !(scope.$even = (i & 1) === 0);
8990
 
8991
        if (scope.$$disconnected) ionic.Utils.reconnectScope(item.scope);
8992
 
8993
        dim = view.getDimensions(i);
8994
        if (item.secondaryPos !== dim.secondaryPos || item.primaryPos !== dim.primaryPos) {
8995
          item.node.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
8996
            .replace(PRIMARY, (item.primaryPos = dim.primaryPos))
8997
            .replace(SECONDARY, (item.secondaryPos = dim.secondaryPos));
8998
        }
8999
        if (item.secondarySize !== dim.secondarySize || item.primarySize !== dim.primarySize) {
9000
          item.node.style.cssText = item.node.style.cssText
9001
            .replace(WIDTH_HEIGHT_REGEX, WIDTH_HEIGHT_TEMPLATE_STR
9002
              //TODO fix item.primarySize + 1 hack
9003
              .replace(PRIMARY, (item.primarySize = dim.primarySize) + 1)
9004
              .replace(SECONDARY, (item.secondarySize = dim.secondarySize))
9005
            );
9006
        }
9007
 
9008
      }
9009
 
9010
      // If we reach the end of the list, render the afterItemsNode - this contains all the
9011
      // elements the developer placed after the collection-repeat
9012
      if (renderEndIndex === data.length - 1) {
9013
        dim = view.getDimensions(data.length - 1) || EMPTY_DIMENSION;
9014
        afterItemsNode.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
9015
          .replace(PRIMARY, dim.primaryPos + dim.primarySize)
9016
          .replace(SECONDARY, 0);
9017
      }
9018
 
9019
      while (itemsLeaving.length) {
9020
        item = itemsLeaving.pop();
9021
        item.scope.$broadcast('$collectionRepeatLeave');
9022
        ionic.Utils.disconnectScope(item.scope);
9023
        itemsPool.push(item);
9024
        item.node.style[ionic.CSS.TRANSFORM] = 'translate3d(-9999px,-9999px,0)';
9025
        item.primaryPos = item.secondaryPos = null;
9026
      }
9027
 
9028
      if (forceRefreshImages) {
9029
        for (i = 0, ii = itemsEntering.length; i < ii && (item = itemsEntering[i]); i++) {
9030
          if (!item.images) continue;
9031
          for (var j = 0, jj = item.images.length, img; j < jj && (img = item.images[j]); j++) {
9032
            var src = img.src;
9033
            img.src = ONE_PX_TRANSPARENT_IMG_SRC;
9034
            img.src = src;
9035
          }
9036
        }
9037
      }
9038
      if (forceRerender) {
9039
        var rootScopePhase = $rootScope.$$phase;
9040
        while (itemsEntering.length) {
9041
          item = itemsEntering.pop();
9042
          if (!rootScopePhase) item.scope.$digest();
9043
        }
9044
      } else {
9045
        digestEnteringItems();
9046
      }
9047
    }
9048
 
9049
    function digestEnteringItems() {
9050
      var item;
9051
      if (digestEnteringItems.running) return;
9052
      digestEnteringItems.running = true;
9053
 
9054
      $$rAF(function process() {
9055
        var rootScopePhase = $rootScope.$$phase;
9056
        while (itemsEntering.length) {
9057
          item = itemsEntering.pop();
9058
          if (item.isShown) {
9059
            if (!rootScopePhase) item.scope.$digest();
9060
          }
9061
        }
9062
        digestEnteringItems.running = false;
9063
      });
9064
    }
9065
 
9066
    function RepeatItem() {
9067
      var self = this;
9068
      this.scope = scope.$new();
9069
      this.id = 'item' + (nextItemId++);
9070
      transclude(this.scope, function(clone) {
9071
        self.element = clone;
9072
        self.element.data('$$collectionRepeatItem', self);
9073
        // TODO destroy
9074
        self.node = clone[0];
9075
        // Batch style setting to lower repaints
9076
        self.node.style[ionic.CSS.TRANSFORM] = 'translate3d(-9999px,-9999px,0)';
9077
        self.node.style.cssText += ' height: 0px; width: 0px;';
9078
        ionic.Utils.disconnectScope(self.scope);
9079
        containerNode.appendChild(self.node);
9080
        self.images = clone[0].getElementsByTagName('img');
9081
      });
9082
    }
9083
 
9084
    function VerticalViewType() {
9085
      this.getItemPrimarySize = heightGetter;
9086
      this.getItemSecondarySize = widthGetter;
9087
 
9088
      this.getScrollValue = function() {
9089
        return Math.max(0, Math.min(scrollView.__scrollTop - repeaterBeforeSize,
9090
          scrollView.__maxScrollTop - repeaterBeforeSize - repeaterAfterSize));
9091
      };
9092
 
9093
      this.refreshDirection = function() {
9094
        this.scrollPrimarySize = scrollView.__clientHeight;
9095
        this.scrollSecondarySize = scrollView.__clientWidth;
9096
 
9097
        this.estimatedPrimarySize = estimatedHeight;
9098
        this.estimatedSecondarySize = estimatedWidth;
9099
        this.estimatedItemsAcross = isGridView &&
9100
          Math.floor(scrollView.__clientWidth / estimatedWidth) ||
9101
          1;
9102
      };
9103
    }
9104
    function HorizontalViewType() {
9105
      this.getItemPrimarySize = widthGetter;
9106
      this.getItemSecondarySize = heightGetter;
9107
 
9108
      this.getScrollValue = function() {
9109
        return Math.max(0, Math.min(scrollView.__scrollLeft - repeaterBeforeSize,
9110
          scrollView.__maxScrollLeft - repeaterBeforeSize - repeaterAfterSize));
9111
      };
9112
 
9113
      this.refreshDirection = function() {
9114
        this.scrollPrimarySize = scrollView.__clientWidth;
9115
        this.scrollSecondarySize = scrollView.__clientHeight;
9116
 
9117
        this.estimatedPrimarySize = estimatedWidth;
9118
        this.estimatedSecondarySize = estimatedHeight;
9119
        this.estimatedItemsAcross = isGridView &&
9120
          Math.floor(scrollView.__clientHeight / estimatedHeight) ||
9121
          1;
9122
      };
9123
    }
9124
 
9125
    function GridViewType() {
9126
      this.getEstimatedSecondaryPos = function(index) {
9127
        return (index % this.estimatedItemsAcross) * this.estimatedSecondarySize;
9128
      };
9129
      this.getEstimatedPrimaryPos = function(index) {
9130
        return Math.floor(index / this.estimatedItemsAcross) * this.estimatedPrimarySize;
9131
      };
9132
      this.getEstimatedIndex = function(scrollValue) {
9133
        return Math.floor(scrollValue / this.estimatedPrimarySize) *
9134
          this.estimatedItemsAcross;
9135
      };
9136
    }
9137
 
9138
    function ListViewType() {
9139
      this.getEstimatedSecondaryPos = function() {
9140
        return 0;
9141
      };
9142
      this.getEstimatedPrimaryPos = function(index) {
9143
        return index * this.estimatedPrimarySize;
9144
      };
9145
      this.getEstimatedIndex = function(scrollValue) {
9146
        return Math.floor((scrollValue) / this.estimatedPrimarySize);
9147
      };
9148
    }
9149
 
9150
    function StaticViewType() {
9151
      this.getContentSize = function() {
9152
        return this.getEstimatedPrimaryPos(data.length - 1) + this.estimatedPrimarySize +
9153
          repeaterBeforeSize + repeaterAfterSize;
9154
      };
9155
      // static view always returns the same object for getDimensions, to avoid memory allocation
9156
      // while scrolling. This could be dangerous if this was a public function, but it's not.
9157
      // Only we use it.
9158
      var dim = {};
9159
      this.getDimensions = function(index) {
9160
        dim.primaryPos = this.getEstimatedPrimaryPos(index);
9161
        dim.secondaryPos = this.getEstimatedSecondaryPos(index);
9162
        dim.primarySize = this.estimatedPrimarySize;
9163
        dim.secondarySize = this.estimatedSecondarySize;
9164
        return dim;
9165
      };
9166
      this.updateRenderRange = function(scrollValue, scrollValueEnd) {
9167
        renderStartIndex = Math.max(0, this.getEstimatedIndex(scrollValue));
9168
 
9169
        // Make sure the renderEndIndex takes into account all the items on the row
9170
        renderEndIndex = Math.min(data.length - 1,
9171
          this.getEstimatedIndex(scrollValueEnd) + this.estimatedItemsAcross - 1);
9172
 
9173
        renderBeforeBoundary = Math.max(0,
9174
          this.getEstimatedPrimaryPos(renderStartIndex));
9175
        renderAfterBoundary = this.getEstimatedPrimaryPos(renderEndIndex) +
9176
          this.estimatedPrimarySize;
9177
      };
9178
    }
9179
 
9180
    function DynamicViewType() {
9181
      var self = this;
9182
      var debouncedScrollViewSetDimensions = ionic.debounce(scrollViewSetDimensions, 25, true);
9183
      var calculateDimensions = isGridView ? calculateDimensionsGrid : calculateDimensionsList;
9184
      var dimensionsIndex;
9185
      var dimensions = [];
9186
 
9187
 
9188
      // Get the dimensions at index. {width, height, left, top}.
9189
      // We start with no dimensions calculated, then any time dimensions are asked for at an
9190
      // index we calculate dimensions up to there.
9191
      function calculateDimensionsList(toIndex) {
9192
        var i, prevDimension, dim;
9193
        for (i = Math.max(0, dimensionsIndex); i <= toIndex && (dim = dimensions[i]); i++) {
9194
          prevDimension = dimensions[i - 1] || EMPTY_DIMENSION;
9195
          dim.primarySize = self.getItemPrimarySize(i, data[i]);
9196
          dim.secondarySize = self.scrollSecondarySize;
9197
          dim.primaryPos = prevDimension.primaryPos + prevDimension.primarySize;
9198
          dim.secondaryPos = 0;
9199
        }
9200
      }
9201
      function calculateDimensionsGrid(toIndex) {
9202
        var i, prevDimension, dim;
9203
        for (i = Math.max(dimensionsIndex, 0); i <= toIndex && (dim = dimensions[i]); i++) {
9204
          prevDimension = dimensions[i - 1] || EMPTY_DIMENSION;
9205
          dim.secondarySize = Math.min(
9206
            self.getItemSecondarySize(i, data[i]),
9207
            self.scrollSecondarySize
9208
          );
9209
          dim.secondaryPos = prevDimension.secondaryPos + prevDimension.secondarySize;
9210
 
9211
          if (i === 0 || dim.secondaryPos + dim.secondarySize > self.scrollSecondarySize) {
9212
            dim.secondaryPos = 0;
9213
            dim.primarySize = self.getItemPrimarySize(i, data[i]);
9214
            dim.primaryPos = prevDimension.primaryPos + prevDimension.rowPrimarySize;
9215
 
9216
            dim.rowStartIndex = i;
9217
            dim.rowPrimarySize = dim.primarySize;
9218
          } else {
9219
            dim.primarySize = self.getItemPrimarySize(i, data[i]);
9220
            dim.primaryPos = prevDimension.primaryPos;
9221
            dim.rowStartIndex = prevDimension.rowStartIndex;
9222
 
9223
            dimensions[dim.rowStartIndex].rowPrimarySize = dim.rowPrimarySize = Math.max(
9224
              dimensions[dim.rowStartIndex].rowPrimarySize,
9225
              dim.primarySize
9226
            );
9227
            dim.rowPrimarySize = Math.max(dim.primarySize, dim.rowPrimarySize);
9228
          }
9229
        }
9230
      }
9231
 
9232
      this.getContentSize = function() {
9233
        var dim = dimensions[dimensionsIndex] || EMPTY_DIMENSION;
9234
        return ((dim.primaryPos + dim.primarySize) || 0) +
9235
          this.getEstimatedPrimaryPos(data.length - dimensionsIndex - 1) +
9236
          repeaterBeforeSize + repeaterAfterSize;
9237
      };
9238
      this.onDestroy = function() {
9239
        dimensions.length = 0;
9240
      };
9241
 
9242
      this.onRefreshData = function() {
9243
        var i;
9244
        var ii;
9245
        // Make sure dimensions has as many items as data.length.
9246
        // This is to be sure we don't have to allocate objects while scrolling.
9247
        for (i = dimensions.length, ii = data.length; i < ii; i++) {
9248
          dimensions.push({});
9249
        }
9250
        dimensionsIndex = -1;
9251
      };
9252
      this.onRefreshLayout = function() {
9253
        dimensionsIndex = -1;
9254
      };
9255
      this.getDimensions = function(index) {
9256
        index = Math.min(index, data.length - 1);
9257
 
9258
        if (dimensionsIndex < index) {
9259
          // Once we start asking for dimensions near the end of the list, go ahead and calculate
9260
          // everything. This is to make sure when the user gets to the end of the list, the
9261
          // scroll height of the list is 100% accurate (not estimated anymore).
9262
          if (index > data.length * 0.9) {
9263
            calculateDimensions(data.length - 1);
9264
            dimensionsIndex = data.length - 1;
9265
            scrollViewSetDimensions();
9266
          } else {
9267
            calculateDimensions(index);
9268
            dimensionsIndex = index;
9269
            debouncedScrollViewSetDimensions();
9270
          }
9271
 
9272
        }
9273
        return dimensions[index];
9274
      };
9275
 
9276
      var oldRenderStartIndex = -1;
9277
      var oldScrollValue = -1;
9278
      this.updateRenderRange = function(scrollValue, scrollValueEnd) {
9279
        var i;
9280
        var len;
9281
        var dim;
9282
 
9283
        // Calculate more dimensions than we estimate we'll need, to be sure.
9284
        this.getDimensions( this.getEstimatedIndex(scrollValueEnd) * 2 );
9285
 
9286
        // -- Calculate renderStartIndex
9287
        // base case: start at 0
9288
        if (oldRenderStartIndex === -1 || scrollValue === 0) {
9289
          i = 0;
9290
        // scrolling down
9291
        } else if (scrollValue >= oldScrollValue) {
9292
          for (i = oldRenderStartIndex, len = data.length; i < len; i++) {
9293
            if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.rowPrimarySize >= scrollValue) {
9294
              break;
9295
            }
9296
          }
9297
        // scrolling up
9298
        } else {
9299
          for (i = oldRenderStartIndex; i >= 0; i--) {
9300
            if ((dim = this.getDimensions(i)) && dim.primaryPos <= scrollValue) {
9301
              // when grid view, make sure the render starts at the beginning of a row.
9302
              i = isGridView ? dim.rowStartIndex : i;
9303
              break;
9304
            }
9305
          }
9306
        }
9307
 
9308
        renderStartIndex = Math.min(Math.max(0, i), data.length - 1);
9309
        renderBeforeBoundary = renderStartIndex !== -1 ? this.getDimensions(renderStartIndex).primaryPos : -1;
9310
 
9311
        // -- Calculate renderEndIndex
9312
        var lastRowDim;
9313
        for (i = renderStartIndex + 1, len = data.length; i < len; i++) {
9314
          if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.rowPrimarySize > scrollValueEnd) {
9315
 
9316
            // Go all the way to the end of the row if we're in a grid
9317
            if (isGridView) {
9318
              lastRowDim = dim;
9319
              while (i < len - 1 &&
9320
                    (dim = this.getDimensions(i + 1)).primaryPos === lastRowDim.primaryPos) {
9321
                i++;
9322
              }
9323
            }
9324
            break;
9325
          }
9326
        }
9327
 
9328
        renderEndIndex = Math.min(i, data.length - 1);
9329
        renderAfterBoundary = renderEndIndex !== -1 ?
9330
          ((dim = this.getDimensions(renderEndIndex)).primaryPos + (dim.rowPrimarySize || dim.primarySize)) :
9331
          -1;
9332
 
9333
        oldScrollValue = scrollValue;
9334
        oldRenderStartIndex = renderStartIndex;
9335
      };
9336
    }
9337
 
9338
 
9339
  };
9340
 
9341
}
9342
 
9343
/**
9344
 * @ngdoc directive
9345
 * @name ionContent
9346
 * @module ionic
9347
 * @delegate ionic.service:$ionicScrollDelegate
9348
 * @restrict E
9349
 *
9350
 * @description
9351
 * The ionContent directive provides an easy to use content area that can be configured
9352
 * to use Ionic's custom Scroll View, or the built in overflow scrolling of the browser.
9353
 *
9354
 * While we recommend using the custom Scroll features in Ionic in most cases, sometimes
9355
 * (for performance reasons) only the browser's native overflow scrolling will suffice,
9356
 * and so we've made it easy to toggle between the Ionic scroll implementation and
9357
 * overflow scrolling.
9358
 *
9359
 * You can implement pull-to-refresh with the {@link ionic.directive:ionRefresher}
9360
 * directive, and infinite scrolling with the {@link ionic.directive:ionInfiniteScroll}
9361
 * directive.
9362
 *
9363
 * If there is any dynamic content inside the ion-content, be sure to call `.resize()` with {@link ionic.service:$ionicScrollDelegate}
9364
 * after the content has been added.
9365
 *
9366
 * Be aware that this directive gets its own child scope. If you do not understand why this
9367
 * is important, you can read [https://docs.angularjs.org/guide/scope](https://docs.angularjs.org/guide/scope).
9368
 *
9369
 * @param {string=} delegate-handle The handle used to identify this scrollView
9370
 * with {@link ionic.service:$ionicScrollDelegate}.
9371
 * @param {string=} direction Which way to scroll. 'x' or 'y' or 'xy'. Default 'y'.
9372
 * @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.
9373
 * @param {boolean=} padding Whether to add padding to the content.
9374
 * of the content.  Defaults to true on iOS, false on Android.
9375
 * @param {boolean=} scroll Whether to allow scrolling of content.  Defaults to true.
9376
 * @param {boolean=} overflow-scroll Whether to use overflow-scrolling instead of
9377
 * Ionic scroll. See {@link ionic.provider:$ionicConfigProvider} to set this as the global default.
9378
 * @param {boolean=} scrollbar-x Whether to show the horizontal scrollbar. Default true.
9379
 * @param {boolean=} scrollbar-y Whether to show the vertical scrollbar. Default true.
9380
 * @param {string=} start-x Initial horizontal scroll position. Default 0.
9381
 * @param {string=} start-y Initial vertical scroll position. Default 0.
9382
 * @param {expression=} on-scroll Expression to evaluate when the content is scrolled.
9383
 * @param {expression=} on-scroll-complete Expression to evaluate when a scroll action completes. Has access to 'scrollLeft' and 'scrollTop' locals.
9384
 * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges
9385
 * of the content.  Defaults to true on iOS, false on Android.
9386
 * @param {number=} scroll-event-interval Number of milliseconds between each firing of the 'on-scroll' expression. Default 10.
9387
 */
9388
IonicModule
9389
.directive('ionContent', [
9390
  '$timeout',
9391
  '$controller',
9392
  '$ionicBind',
9393
  '$ionicConfig',
9394
function($timeout, $controller, $ionicBind, $ionicConfig) {
9395
  return {
9396
    restrict: 'E',
9397
    require: '^?ionNavView',
9398
    scope: true,
9399
    priority: 800,
9400
    compile: function(element, attr) {
9401
      var innerElement;
9402
      var scrollCtrl;
9403
 
9404
      element.addClass('scroll-content ionic-scroll');
9405
 
9406
      if (attr.scroll != 'false') {
9407
        //We cannot use normal transclude here because it breaks element.data()
9408
        //inheritance on compile
9409
        innerElement = jqLite('<div class="scroll"></div>');
9410
        innerElement.append(element.contents());
9411
        element.append(innerElement);
9412
      } else {
9413
        element.addClass('scroll-content-false');
9414
      }
9415
 
9416
      var nativeScrolling = attr.overflowScroll === "true" || !$ionicConfig.scrolling.jsScrolling();
9417
 
9418
      // collection-repeat requires JS scrolling
9419
      if (nativeScrolling) {
9420
        nativeScrolling = !element[0].querySelector('[collection-repeat]');
9421
      }
9422
 
9423
      return { pre: prelink };
9424
      function prelink($scope, $element, $attr) {
9425
        var parentScope = $scope.$parent;
9426
        $scope.$watch(function() {
9427
          return (parentScope.$hasHeader ? ' has-header' : '') +
9428
            (parentScope.$hasSubheader ? ' has-subheader' : '') +
9429
            (parentScope.$hasFooter ? ' has-footer' : '') +
9430
            (parentScope.$hasSubfooter ? ' has-subfooter' : '') +
9431
            (parentScope.$hasTabs ? ' has-tabs' : '') +
9432
            (parentScope.$hasTabsTop ? ' has-tabs-top' : '');
9433
        }, function(className, oldClassName) {
9434
          $element.removeClass(oldClassName);
9435
          $element.addClass(className);
9436
        });
9437
 
9438
        //Only this ionContent should use these variables from parent scopes
9439
        $scope.$hasHeader = $scope.$hasSubheader =
9440
          $scope.$hasFooter = $scope.$hasSubfooter =
9441
          $scope.$hasTabs = $scope.$hasTabsTop =
9442
          false;
9443
        $ionicBind($scope, $attr, {
9444
          $onScroll: '&onScroll',
9445
          $onScrollComplete: '&onScrollComplete',
9446
          hasBouncing: '@',
9447
          padding: '@',
9448
          direction: '@',
9449
          scrollbarX: '@',
9450
          scrollbarY: '@',
9451
          startX: '@',
9452
          startY: '@',
9453
          scrollEventInterval: '@'
9454
        });
9455
        $scope.direction = $scope.direction || 'y';
9456
 
9457
        if (isDefined($attr.padding)) {
9458
          $scope.$watch($attr.padding, function(newVal) {
9459
              (innerElement || $element).toggleClass('padding', !!newVal);
9460
          });
9461
        }
9462
 
9463
        if ($attr.scroll === "false") {
9464
          //do nothing
9465
        } else {
9466
          var scrollViewOptions = {};
9467
 
9468
          // determined in compile phase above
9469
          if (nativeScrolling) {
9470
            // use native scrolling
9471
            $element.addClass('overflow-scroll');
9472
 
9473
            scrollViewOptions = {
9474
              el: $element[0],
9475
              delegateHandle: attr.delegateHandle,
9476
              startX: $scope.$eval($scope.startX) || 0,
9477
              startY: $scope.$eval($scope.startY) || 0,
9478
              nativeScrolling: true
9479
            };
9480
 
9481
          } else {
9482
            // Use JS scrolling
9483
            scrollViewOptions = {
9484
              el: $element[0],
9485
              delegateHandle: attr.delegateHandle,
9486
              locking: (attr.locking || 'true') === 'true',
9487
              bouncing: $scope.$eval($scope.hasBouncing),
9488
              startX: $scope.$eval($scope.startX) || 0,
9489
              startY: $scope.$eval($scope.startY) || 0,
9490
              scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
9491
              scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
9492
              scrollingX: $scope.direction.indexOf('x') >= 0,
9493
              scrollingY: $scope.direction.indexOf('y') >= 0,
9494
              scrollEventInterval: parseInt($scope.scrollEventInterval, 10) || 10,
9495
              scrollingComplete: onScrollComplete
9496
            };
9497
          }
9498
 
9499
          // init scroll controller with appropriate options
9500
          scrollCtrl = $controller('$ionicScroll', {
9501
            $scope: $scope,
9502
            scrollViewOptions: scrollViewOptions
9503
          });
9504
 
9505
          $scope.$on('$destroy', function() {
9506
            if (scrollViewOptions) {
9507
              scrollViewOptions.scrollingComplete = noop;
9508
              delete scrollViewOptions.el;
9509
            }
9510
            innerElement = null;
9511
            $element = null;
9512
            attr.$$element = null;
9513
          });
9514
        }
9515
 
9516
        function onScrollComplete() {
9517
          $scope.$onScrollComplete({
9518
            scrollTop: scrollCtrl.scrollView.__scrollTop,
9519
            scrollLeft: scrollCtrl.scrollView.__scrollLeft
9520
          });
9521
        }
9522
 
9523
      }
9524
    }
9525
  };
9526
}]);
9527
 
9528
/**
9529
 * @ngdoc directive
9530
 * @name exposeAsideWhen
9531
 * @module ionic
9532
 * @restrict A
9533
 * @parent ionic.directive:ionSideMenus
9534
 *
9535
 * @description
9536
 * It is common for a tablet application to hide a menu when in portrait mode, but to show the
9537
 * same menu on the left side when the tablet is in landscape mode. The `exposeAsideWhen` attribute
9538
 * directive can be used to accomplish a similar interface.
9539
 *
9540
 * By default, side menus are hidden underneath its side menu content, and can be opened by either
9541
 * swiping the content left or right, or toggling a button to show the side menu. However, by adding the
9542
 * `exposeAsideWhen` attribute directive to an {@link ionic.directive:ionSideMenu} element directive,
9543
 * a side menu can be given instructions on "when" the menu should be exposed (always viewable). For
9544
 * example, the `expose-aside-when="large"` attribute will keep the side menu hidden when the viewport's
9545
 * width is less than `768px`, but when the viewport's width is `768px` or greater, the menu will then
9546
 * always be shown and can no longer be opened or closed like it could when it was hidden for smaller
9547
 * viewports.
9548
 *
9549
 * Using `large` as the attribute's value is a shortcut value to `(min-width:768px)` since it is
9550
 * the most common use-case. However, for added flexibility, any valid media query could be added
9551
 * as the value, such as `(min-width:600px)` or even multiple queries such as
9552
 * `(min-width:750px) and (max-width:1200px)`.
9553
 
9554
 * @usage
9555
 * ```html
9556
 * <ion-side-menus>
9557
 *   <!-- Center content -->
9558
 *   <ion-side-menu-content>
9559
 *   </ion-side-menu-content>
9560
 *
9561
 *   <!-- Left menu -->
9562
 *   <ion-side-menu expose-aside-when="large">
9563
 *   </ion-side-menu>
9564
 * </ion-side-menus>
9565
 * ```
9566
 * For a complete side menu example, see the
9567
 * {@link ionic.directive:ionSideMenus} documentation.
9568
 */
9569
IonicModule.directive('exposeAsideWhen', ['$window', function($window) {
9570
  return {
9571
    restrict: 'A',
9572
    require: '^ionSideMenus',
9573
    link: function($scope, $element, $attr, sideMenuCtrl) {
9574
 
9575
      function checkAsideExpose() {
9576
        var mq = $attr.exposeAsideWhen == 'large' ? '(min-width:768px)' : $attr.exposeAsideWhen;
9577
        sideMenuCtrl.exposeAside($window.matchMedia(mq).matches);
9578
        sideMenuCtrl.activeAsideResizing(false);
9579
      }
9580
 
9581
      function onResize() {
9582
        sideMenuCtrl.activeAsideResizing(true);
9583
        debouncedCheck();
9584
      }
9585
 
9586
      var debouncedCheck = ionic.debounce(function() {
9587
        $scope.$apply(checkAsideExpose);
9588
      }, 300, false);
9589
 
9590
      $scope.$evalAsync(checkAsideExpose);
9591
 
9592
      ionic.on('resize', onResize, $window);
9593
 
9594
      $scope.$on('$destroy', function() {
9595
        ionic.off('resize', onResize, $window);
9596
      });
9597
 
9598
    }
9599
  };
9600
}]);
9601
 
9602
 
9603
var GESTURE_DIRECTIVES = 'onHold onTap onDoubleTap onTouch onRelease onDrag onDragUp onDragRight onDragDown onDragLeft onSwipe onSwipeUp onSwipeRight onSwipeDown onSwipeLeft'.split(' ');
9604
 
9605
GESTURE_DIRECTIVES.forEach(function(name) {
9606
  IonicModule.directive(name, gestureDirective(name));
9607
});
9608
 
9609
 
9610
/**
9611
 * @ngdoc directive
9612
 * @name onHold
9613
 * @module ionic
9614
 * @restrict A
9615
 *
9616
 * @description
9617
 * Touch stays at the same location for 500ms. Similar to long touch events available for AngularJS and jQuery.
9618
 *
9619
 * @usage
9620
 * ```html
9621
 * <button on-hold="onHold()" class="button">Test</button>
9622
 * ```
9623
 */
9624
 
9625
 
9626
/**
9627
 * @ngdoc directive
9628
 * @name onTap
9629
 * @module ionic
9630
 * @restrict A
9631
 *
9632
 * @description
9633
 * Quick touch at a location. If the duration of the touch goes
9634
 * longer than 250ms it is no longer a tap gesture.
9635
 *
9636
 * @usage
9637
 * ```html
9638
 * <button on-tap="onTap()" class="button">Test</button>
9639
 * ```
9640
 */
9641
 
9642
 
9643
/**
9644
 * @ngdoc directive
9645
 * @name onDoubleTap
9646
 * @module ionic
9647
 * @restrict A
9648
 *
9649
 * @description
9650
 * Double tap touch at a location.
9651
 *
9652
 * @usage
9653
 * ```html
9654
 * <button on-double-tap="onDoubleTap()" class="button">Test</button>
9655
 * ```
9656
 */
9657
 
9658
 
9659
/**
9660
 * @ngdoc directive
9661
 * @name onTouch
9662
 * @module ionic
9663
 * @restrict A
9664
 *
9665
 * @description
9666
 * Called immediately when the user first begins a touch. This
9667
 * gesture does not wait for a touchend/mouseup.
9668
 *
9669
 * @usage
9670
 * ```html
9671
 * <button on-touch="onTouch()" class="button">Test</button>
9672
 * ```
9673
 */
9674
 
9675
 
9676
/**
9677
 * @ngdoc directive
9678
 * @name onRelease
9679
 * @module ionic
9680
 * @restrict A
9681
 *
9682
 * @description
9683
 * Called when the user ends a touch.
9684
 *
9685
 * @usage
9686
 * ```html
9687
 * <button on-release="onRelease()" class="button">Test</button>
9688
 * ```
9689
 */
9690
 
9691
 
9692
/**
9693
 * @ngdoc directive
9694
 * @name onDrag
9695
 * @module ionic
9696
 * @restrict A
9697
 *
9698
 * @description
9699
 * Move with one touch around on the page. Blocking the scrolling when
9700
 * moving left and right is a good practice. When all the drag events are
9701
 * blocking you disable scrolling on that area.
9702
 *
9703
 * @usage
9704
 * ```html
9705
 * <button on-drag="onDrag()" class="button">Test</button>
9706
 * ```
9707
 */
9708
 
9709
 
9710
/**
9711
 * @ngdoc directive
9712
 * @name onDragUp
9713
 * @module ionic
9714
 * @restrict A
9715
 *
9716
 * @description
9717
 * Called when the element is dragged up.
9718
 *
9719
 * @usage
9720
 * ```html
9721
 * <button on-drag-up="onDragUp()" class="button">Test</button>
9722
 * ```
9723
 */
9724
 
9725
 
9726
/**
9727
 * @ngdoc directive
9728
 * @name onDragRight
9729
 * @module ionic
9730
 * @restrict A
9731
 *
9732
 * @description
9733
 * Called when the element is dragged to the right.
9734
 *
9735
 * @usage
9736
 * ```html
9737
 * <button on-drag-right="onDragRight()" class="button">Test</button>
9738
 * ```
9739
 */
9740
 
9741
 
9742
/**
9743
 * @ngdoc directive
9744
 * @name onDragDown
9745
 * @module ionic
9746
 * @restrict A
9747
 *
9748
 * @description
9749
 * Called when the element is dragged down.
9750
 *
9751
 * @usage
9752
 * ```html
9753
 * <button on-drag-down="onDragDown()" class="button">Test</button>
9754
 * ```
9755
 */
9756
 
9757
 
9758
/**
9759
 * @ngdoc directive
9760
 * @name onDragLeft
9761
 * @module ionic
9762
 * @restrict A
9763
 *
9764
 * @description
9765
 * Called when the element is dragged to the left.
9766
 *
9767
 * @usage
9768
 * ```html
9769
 * <button on-drag-left="onDragLeft()" class="button">Test</button>
9770
 * ```
9771
 */
9772
 
9773
 
9774
/**
9775
 * @ngdoc directive
9776
 * @name onSwipe
9777
 * @module ionic
9778
 * @restrict A
9779
 *
9780
 * @description
9781
 * Called when a moving touch has a high velocity in any direction.
9782
 *
9783
 * @usage
9784
 * ```html
9785
 * <button on-swipe="onSwipe()" class="button">Test</button>
9786
 * ```
9787
 */
9788
 
9789
 
9790
/**
9791
 * @ngdoc directive
9792
 * @name onSwipeUp
9793
 * @module ionic
9794
 * @restrict A
9795
 *
9796
 * @description
9797
 * Called when a moving touch has a high velocity moving up.
9798
 *
9799
 * @usage
9800
 * ```html
9801
 * <button on-swipe-up="onSwipeUp()" class="button">Test</button>
9802
 * ```
9803
 */
9804
 
9805
 
9806
/**
9807
 * @ngdoc directive
9808
 * @name onSwipeRight
9809
 * @module ionic
9810
 * @restrict A
9811
 *
9812
 * @description
9813
 * Called when a moving touch has a high velocity moving to the right.
9814
 *
9815
 * @usage
9816
 * ```html
9817
 * <button on-swipe-right="onSwipeRight()" class="button">Test</button>
9818
 * ```
9819
 */
9820
 
9821
 
9822
/**
9823
 * @ngdoc directive
9824
 * @name onSwipeDown
9825
 * @module ionic
9826
 * @restrict A
9827
 *
9828
 * @description
9829
 * Called when a moving touch has a high velocity moving down.
9830
 *
9831
 * @usage
9832
 * ```html
9833
 * <button on-swipe-down="onSwipeDown()" class="button">Test</button>
9834
 * ```
9835
 */
9836
 
9837
 
9838
/**
9839
 * @ngdoc directive
9840
 * @name onSwipeLeft
9841
 * @module ionic
9842
 * @restrict A
9843
 *
9844
 * @description
9845
 * Called when a moving touch has a high velocity moving to the left.
9846
 *
9847
 * @usage
9848
 * ```html
9849
 * <button on-swipe-left="onSwipeLeft()" class="button">Test</button>
9850
 * ```
9851
 */
9852
 
9853
 
9854
function gestureDirective(directiveName) {
9855
  return ['$ionicGesture', '$parse', function($ionicGesture, $parse) {
9856
    var eventType = directiveName.substr(2).toLowerCase();
9857
 
9858
    return function(scope, element, attr) {
9859
      var fn = $parse( attr[directiveName] );
9860
 
9861
      var listener = function(ev) {
9862
        scope.$apply(function() {
9863
          fn(scope, {
9864
            $event: ev
9865
          });
9866
        });
9867
      };
9868
 
9869
      var gesture = $ionicGesture.on(eventType, listener, element);
9870
 
9871
      scope.$on('$destroy', function() {
9872
        $ionicGesture.off(gesture, eventType, listener);
9873
      });
9874
    };
9875
  }];
9876
}
9877
 
9878
 
9879
IonicModule
9880
.directive('ionHeaderBar', tapScrollToTopDirective())
9881
 
9882
/**
9883
 * @ngdoc directive
9884
 * @name ionHeaderBar
9885
 * @module ionic
9886
 * @restrict E
9887
 *
9888
 * @description
9889
 * Adds a fixed header bar above some content.
9890
 *
9891
 * Can also be a subheader (lower down) if the 'bar-subheader' class is applied.
9892
 * See [the header CSS docs](/docs/components/#subheader).
9893
 *
9894
 * @param {string=} align-title How to align the title. By default the title
9895
 * will be aligned the same as how the platform aligns its titles (iOS centers
9896
 * titles, Android aligns them left).
9897
 * Available: 'left', 'right', or 'center'.  Defaults to the same as the platform.
9898
 * @param {boolean=} no-tap-scroll By default, the header bar will scroll the
9899
 * content to the top when tapped.  Set no-tap-scroll to true to disable this
9900
 * behavior.
9901
 * Available: true or false.  Defaults to false.
9902
 *
9903
 * @usage
9904
 * ```html
9905
 * <ion-header-bar align-title="left" class="bar-positive">
9906
 *   <div class="buttons">
9907
 *     <button class="button" ng-click="doSomething()">Left Button</button>
9908
 *   </div>
9909
 *   <h1 class="title">Title!</h1>
9910
 *   <div class="buttons">
9911
 *     <button class="button">Right Button</button>
9912
 *   </div>
9913
 * </ion-header-bar>
9914
 * <ion-content>
9915
 *   Some content!
9916
 * </ion-content>
9917
 * ```
9918
 */
9919
.directive('ionHeaderBar', headerFooterBarDirective(true))
9920
 
9921
/**
9922
 * @ngdoc directive
9923
 * @name ionFooterBar
9924
 * @module ionic
9925
 * @restrict E
9926
 *
9927
 * @description
9928
 * Adds a fixed footer bar below some content.
9929
 *
9930
 * Can also be a subfooter (higher up) if the 'bar-subfooter' class is applied.
9931
 * See [the footer CSS docs](/docs/components/#footer).
9932
 *
9933
 * Note: If you use ionFooterBar in combination with ng-if, the surrounding content
9934
 * will not align correctly.  This will be fixed soon.
9935
 *
9936
 * @param {string=} align-title Where to align the title.
9937
 * Available: 'left', 'right', or 'center'.  Defaults to 'center'.
9938
 *
9939
 * @usage
9940
 * ```html
9941
 * <ion-content>
9942
 *   Some content!
9943
 * </ion-content>
9944
 * <ion-footer-bar align-title="left" class="bar-assertive">
9945
 *   <div class="buttons">
9946
 *     <button class="button">Left Button</button>
9947
 *   </div>
9948
 *   <h1 class="title">Title!</h1>
9949
 *   <div class="buttons" ng-click="doSomething()">
9950
 *     <button class="button">Right Button</button>
9951
 *   </div>
9952
 * </ion-footer-bar>
9953
 * ```
9954
 */
9955
.directive('ionFooterBar', headerFooterBarDirective(false));
9956
 
9957
function tapScrollToTopDirective() {
9958
  return ['$ionicScrollDelegate', function($ionicScrollDelegate) {
9959
    return {
9960
      restrict: 'E',
9961
      link: function($scope, $element, $attr) {
9962
        if ($attr.noTapScroll == 'true') {
9963
          return;
9964
        }
9965
        ionic.on('tap', onTap, $element[0]);
9966
        $scope.$on('$destroy', function() {
9967
          ionic.off('tap', onTap, $element[0]);
9968
        });
9969
 
9970
        function onTap(e) {
9971
          var depth = 3;
9972
          var current = e.target;
9973
          //Don't scroll to top in certain cases
9974
          while (depth-- && current) {
9975
            if (current.classList.contains('button') ||
9976
                current.tagName.match(/input|textarea|select/i) ||
9977
                current.isContentEditable) {
9978
              return;
9979
            }
9980
            current = current.parentNode;
9981
          }
9982
          var touch = e.gesture && e.gesture.touches[0] || e.detail.touches[0];
9983
          var bounds = $element[0].getBoundingClientRect();
9984
          if (ionic.DomUtil.rectContains(
9985
            touch.pageX, touch.pageY,
9986
            bounds.left, bounds.top - 20,
9987
            bounds.left + bounds.width, bounds.top + bounds.height
9988
          )) {
9989
            $ionicScrollDelegate.scrollTop(true);
9990
          }
9991
        }
9992
      }
9993
    };
9994
  }];
9995
}
9996
 
9997
function headerFooterBarDirective(isHeader) {
9998
  return ['$document', '$timeout', function($document, $timeout) {
9999
    return {
10000
      restrict: 'E',
10001
      controller: '$ionicHeaderBar',
10002
      compile: function(tElement) {
10003
        tElement.addClass(isHeader ? 'bar bar-header' : 'bar bar-footer');
10004
        // top style tabs? if so, remove bottom border for seamless display
10005
        $timeout(function() {
10006
          if (isHeader && $document[0].getElementsByClassName('tabs-top').length) tElement.addClass('has-tabs-top');
10007
        });
10008
 
10009
        return { pre: prelink };
10010
        function prelink($scope, $element, $attr, ctrl) {
10011
          if (isHeader) {
10012
            $scope.$watch(function() { return $element[0].className; }, function(value) {
10013
              var isShown = value.indexOf('ng-hide') === -1;
10014
              var isSubheader = value.indexOf('bar-subheader') !== -1;
10015
              $scope.$hasHeader = isShown && !isSubheader;
10016
              $scope.$hasSubheader = isShown && isSubheader;
10017
              $scope.$emit('$ionicSubheader', $scope.$hasSubheader);
10018
            });
10019
            $scope.$on('$destroy', function() {
10020
              delete $scope.$hasHeader;
10021
              delete $scope.$hasSubheader;
10022
            });
10023
            ctrl.align();
10024
            $scope.$on('$ionicHeader.align', function() {
10025
              ionic.requestAnimationFrame(function() {
10026
                ctrl.align();
10027
              });
10028
            });
10029
 
10030
          } else {
10031
            $scope.$watch(function() { return $element[0].className; }, function(value) {
10032
              var isShown = value.indexOf('ng-hide') === -1;
10033
              var isSubfooter = value.indexOf('bar-subfooter') !== -1;
10034
              $scope.$hasFooter = isShown && !isSubfooter;
10035
              $scope.$hasSubfooter = isShown && isSubfooter;
10036
            });
10037
            $scope.$on('$destroy', function() {
10038
              delete $scope.$hasFooter;
10039
              delete $scope.$hasSubfooter;
10040
            });
10041
            $scope.$watch('$hasTabs', function(val) {
10042
              $element.toggleClass('has-tabs', !!val);
10043
            });
10044
          }
10045
        }
10046
      }
10047
    };
10048
  }];
10049
}
10050
 
10051
/**
10052
 * @ngdoc directive
10053
 * @name ionInfiniteScroll
10054
 * @module ionic
10055
 * @parent ionic.directive:ionContent, ionic.directive:ionScroll
10056
 * @restrict E
10057
 *
10058
 * @description
10059
 * The ionInfiniteScroll directive allows you to call a function whenever
10060
 * the user gets to the bottom of the page or near the bottom of the page.
10061
 *
10062
 * The expression you pass in for `on-infinite` is called when the user scrolls
10063
 * greater than `distance` away from the bottom of the content.  Once `on-infinite`
10064
 * is done loading new data, it should broadcast the `scroll.infiniteScrollComplete`
10065
 * event from your controller (see below example).
10066
 *
10067
 * @param {expression} on-infinite What to call when the scroller reaches the
10068
 * bottom.
10069
 * @param {string=} distance The distance from the bottom that the scroll must
10070
 * reach to trigger the on-infinite expression. Default: 1%.
10071
 * @param {string=} spinner The {@link ionic.directive:ionSpinner} to show while loading. The SVG
10072
 * {@link ionic.directive:ionSpinner} is now the default, replacing rotating font icons.
10073
 * @param {string=} icon The icon to show while loading. Default: 'ion-load-d'.  This is depreicated
10074
 * in favor of the SVG {@link ionic.directive:ionSpinner}.
10075
 * @param {boolean=} immediate-check Whether to check the infinite scroll bounds immediately on load.
10076
 *
10077
 * @usage
10078
 * ```html
10079
 * <ion-content ng-controller="MyController">
10080
 *   <ion-list>
10081
 *   ....
10082
 *   ....
10083
 *   </ion-list>
10084
 *
10085
 *   <ion-infinite-scroll
10086
 *     on-infinite="loadMore()"
10087
 *     distance="1%">
10088
 *   </ion-infinite-scroll>
10089
 * </ion-content>
10090
 * ```
10091
 * ```js
10092
 * function MyController($scope, $http) {
10093
 *   $scope.items = [];
10094
 *   $scope.loadMore = function() {
10095
 *     $http.get('/more-items').success(function(items) {
10096
 *       useItems(items);
10097
 *       $scope.$broadcast('scroll.infiniteScrollComplete');
10098
 *     });
10099
 *   };
10100
 *
10101
 *   $scope.$on('$stateChangeSuccess', function() {
10102
 *     $scope.loadMore();
10103
 *   });
10104
 * }
10105
 * ```
10106
 *
10107
 * An easy to way to stop infinite scroll once there is no more data to load
10108
 * is to use angular's `ng-if` directive:
10109
 *
10110
 * ```html
10111
 * <ion-infinite-scroll
10112
 *   ng-if="moreDataCanBeLoaded()"
10113
 *   icon="ion-loading-c"
10114
 *   on-infinite="loadMoreData()">
10115
 * </ion-infinite-scroll>
10116
 * ```
10117
 */
10118
IonicModule
10119
.directive('ionInfiniteScroll', ['$timeout', function($timeout) {
10120
  return {
10121
    restrict: 'E',
10122
    require: ['?^$ionicScroll', 'ionInfiniteScroll'],
10123
    template: function($element, $attrs) {
10124
      if ($attrs.icon) return '<i class="icon {{icon()}} icon-refreshing {{scrollingType}}"></i>';
10125
      return '<ion-spinner icon="{{spinner()}}"></ion-spinner>';
10126
    },
10127
    scope: true,
10128
    controller: '$ionInfiniteScroll',
10129
    link: function($scope, $element, $attrs, ctrls) {
10130
      var infiniteScrollCtrl = ctrls[1];
10131
      var scrollCtrl = infiniteScrollCtrl.scrollCtrl = ctrls[0];
10132
      var jsScrolling = infiniteScrollCtrl.jsScrolling = !scrollCtrl.isNative();
10133
 
10134
      // if this view is not beneath a scrollCtrl, it can't be injected, proceed w/ native scrolling
10135
      if (jsScrolling) {
10136
        infiniteScrollCtrl.scrollView = scrollCtrl.scrollView;
10137
        $scope.scrollingType = 'js-scrolling';
10138
        //bind to JS scroll events
10139
        scrollCtrl.$element.on('scroll', infiniteScrollCtrl.checkBounds);
10140
      } else {
10141
        // grabbing the scrollable element, to determine dimensions, and current scroll pos
10142
        var scrollEl = ionic.DomUtil.getParentOrSelfWithClass($element[0].parentNode, 'overflow-scroll');
10143
        infiniteScrollCtrl.scrollEl = scrollEl;
10144
        // if there's no scroll controller, and no overflow scroll div, infinite scroll wont work
10145
        if (!scrollEl) {
10146
          throw 'Infinite scroll must be used inside a scrollable div';
10147
        }
10148
        //bind to native scroll events
10149
        infiniteScrollCtrl.scrollEl.addEventListener('scroll', infiniteScrollCtrl.checkBounds);
10150
      }
10151
 
10152
      // Optionally check bounds on start after scrollView is fully rendered
10153
      var doImmediateCheck = isDefined($attrs.immediateCheck) ? $scope.$eval($attrs.immediateCheck) : true;
10154
      if (doImmediateCheck) {
10155
        $timeout(function() { infiniteScrollCtrl.checkBounds(); });
10156
      }
10157
    }
10158
  };
10159
}]);
10160
 
10161
/**
10162
* @ngdoc directive
10163
* @name ionItem
10164
* @parent ionic.directive:ionList
10165
* @module ionic
10166
* @restrict E
10167
* Creates a list-item that can easily be swiped,
10168
* deleted, reordered, edited, and more.
10169
*
10170
* See {@link ionic.directive:ionList} for a complete example & explanation.
10171
*
10172
* Can be assigned any item class name. See the
10173
* [list CSS documentation](/docs/components/#list).
10174
*
10175
* @usage
10176
*
10177
* ```html
10178
* <ion-list>
10179
*   <ion-item>Hello!</ion-item>
10180
*   <ion-item href="#/detail">
10181
*     Link to detail page
10182
*   </ion-item>
10183
* </ion-list>
10184
* ```
10185
*/
10186
IonicModule
10187
.directive('ionItem', ['$$rAF', function($$rAF) {
10188
  return {
10189
    restrict: 'E',
10190
    controller: ['$scope', '$element', function($scope, $element) {
10191
      this.$scope = $scope;
10192
      this.$element = $element;
10193
    }],
10194
    scope: true,
10195
    compile: function($element, $attrs) {
10196
      var isAnchor = isDefined($attrs.href) ||
10197
                     isDefined($attrs.ngHref) ||
10198
                     isDefined($attrs.uiSref);
10199
      var isComplexItem = isAnchor ||
10200
        //Lame way of testing, but we have to know at compile what to do with the element
10201
        /ion-(delete|option|reorder)-button/i.test($element.html());
10202
 
10203
      if (isComplexItem) {
10204
        var innerElement = jqLite(isAnchor ? '<a></a>' : '<div></div>');
10205
        innerElement.addClass('item-content');
10206
 
10207
        if (isDefined($attrs.href) || isDefined($attrs.ngHref)) {
10208
          innerElement.attr('ng-href', '{{$href()}}');
10209
          if (isDefined($attrs.target)) {
10210
            innerElement.attr('target', '{{$target()}}');
10211
          }
10212
        }
10213
 
10214
        innerElement.append($element.contents());
10215
 
10216
        $element.addClass('item item-complex')
10217
                .append(innerElement);
10218
      } else {
10219
        $element.addClass('item');
10220
      }
10221
 
10222
      return function link($scope, $element, $attrs) {
10223
        $scope.$href = function() {
10224
          return $attrs.href || $attrs.ngHref;
10225
        };
10226
        $scope.$target = function() {
10227
          return $attrs.target;
10228
        };
10229
 
10230
        var content = $element[0].querySelector('.item-content');
10231
        if (content) {
10232
          $scope.$on('$collectionRepeatLeave', function() {
10233
            if (content && content.$$ionicOptionsOpen) {
10234
              content.style[ionic.CSS.TRANSFORM] = '';
10235
              content.style[ionic.CSS.TRANSITION] = 'none';
10236
              $$rAF(function() {
10237
                content.style[ionic.CSS.TRANSITION] = '';
10238
              });
10239
              content.$$ionicOptionsOpen = false;
10240
            }
10241
          });
10242
        }
10243
      };
10244
 
10245
    }
10246
  };
10247
}]);
10248
 
10249
var ITEM_TPL_DELETE_BUTTON =
10250
  '<div class="item-left-edit item-delete enable-pointer-events">' +
10251
  '</div>';
10252
/**
10253
* @ngdoc directive
10254
* @name ionDeleteButton
10255
* @parent ionic.directive:ionItem
10256
* @module ionic
10257
* @restrict E
10258
* Creates a delete button inside a list item, that is visible when the
10259
* {@link ionic.directive:ionList ionList parent's} `show-delete` evaluates to true or
10260
* `$ionicListDelegate.showDelete(true)` is called.
10261
*
10262
* Takes any ionicon as a class.
10263
*
10264
* See {@link ionic.directive:ionList} for a complete example & explanation.
10265
*
10266
* @usage
10267
*
10268
* ```html
10269
* <ion-list show-delete="shouldShowDelete">
10270
*   <ion-item>
10271
*     <ion-delete-button class="ion-minus-circled"></ion-delete-button>
10272
*     Hello, list item!
10273
*   </ion-item>
10274
* </ion-list>
10275
* <ion-toggle ng-model="shouldShowDelete">
10276
*   Show Delete?
10277
* </ion-toggle>
10278
* ```
10279
*/
10280
IonicModule
10281
.directive('ionDeleteButton', function() {
10282
 
10283
  function stopPropagation(ev) {
10284
    ev.stopPropagation();
10285
  }
10286
 
10287
  return {
10288
    restrict: 'E',
10289
    require: ['^^ionItem', '^?ionList'],
10290
    //Run before anything else, so we can move it before other directives process
10291
    //its location (eg ngIf relies on the location of the directive in the dom)
10292
    priority: Number.MAX_VALUE,
10293
    compile: function($element, $attr) {
10294
      //Add the classes we need during the compile phase, so that they stay
10295
      //even if something else like ngIf removes the element and re-addss it
10296
      $attr.$set('class', ($attr['class'] || '') + ' button icon button-icon', true);
10297
      return function($scope, $element, $attr, ctrls) {
10298
        var itemCtrl = ctrls[0];
10299
        var listCtrl = ctrls[1];
10300
        var container = jqLite(ITEM_TPL_DELETE_BUTTON);
10301
        container.append($element);
10302
        itemCtrl.$element.append(container).addClass('item-left-editable');
10303
 
10304
        //Don't bubble click up to main .item
10305
        $element.on('click', stopPropagation);
10306
 
10307
        init();
10308
        $scope.$on('$ionic.reconnectScope', init);
10309
        function init() {
10310
          listCtrl = listCtrl || $element.controller('ionList');
10311
          if (listCtrl && listCtrl.showDelete()) {
10312
            container.addClass('visible active');
10313
          }
10314
        }
10315
      };
10316
    }
10317
  };
10318
});
10319
 
10320
 
10321
IonicModule
10322
.directive('itemFloatingLabel', function() {
10323
  return {
10324
    restrict: 'C',
10325
    link: function(scope, element) {
10326
      var el = element[0];
10327
      var input = el.querySelector('input, textarea');
10328
      var inputLabel = el.querySelector('.input-label');
10329
 
10330
      if (!input || !inputLabel) return;
10331
 
10332
      var onInput = function() {
10333
        if (input.value) {
10334
          inputLabel.classList.add('has-input');
10335
        } else {
10336
          inputLabel.classList.remove('has-input');
10337
        }
10338
      };
10339
 
10340
      input.addEventListener('input', onInput);
10341
 
10342
      var ngModelCtrl = jqLite(input).controller('ngModel');
10343
      if (ngModelCtrl) {
10344
        ngModelCtrl.$render = function() {
10345
          input.value = ngModelCtrl.$viewValue || '';
10346
          onInput();
10347
        };
10348
      }
10349
 
10350
      scope.$on('$destroy', function() {
10351
        input.removeEventListener('input', onInput);
10352
      });
10353
    }
10354
  };
10355
});
10356
 
10357
var ITEM_TPL_OPTION_BUTTONS =
10358
  '<div class="item-options invisible">' +
10359
  '</div>';
10360
/**
10361
* @ngdoc directive
10362
* @name ionOptionButton
10363
* @parent ionic.directive:ionItem
10364
* @module ionic
10365
* @restrict E
10366
* Creates an option button inside a list item, that is visible when the item is swiped
10367
* to the left by the user.  Swiped open option buttons can be hidden with
10368
* {@link ionic.service:$ionicListDelegate#closeOptionButtons $ionicListDelegate#closeOptionButtons}.
10369
*
10370
* Can be assigned any button class.
10371
*
10372
* See {@link ionic.directive:ionList} for a complete example & explanation.
10373
*
10374
* @usage
10375
*
10376
* ```html
10377
* <ion-list>
10378
*   <ion-item>
10379
*     I love kittens!
10380
*     <ion-option-button class="button-positive">Share</ion-option-button>
10381
*     <ion-option-button class="button-assertive">Edit</ion-option-button>
10382
*   </ion-item>
10383
* </ion-list>
10384
* ```
10385
*/
10386
IonicModule.directive('ionOptionButton', [function() {
10387
  function stopPropagation(e) {
10388
    e.stopPropagation();
10389
  }
10390
  return {
10391
    restrict: 'E',
10392
    require: '^ionItem',
10393
    priority: Number.MAX_VALUE,
10394
    compile: function($element, $attr) {
10395
      $attr.$set('class', ($attr['class'] || '') + ' button', true);
10396
      return function($scope, $element, $attr, itemCtrl) {
10397
        if (!itemCtrl.optionsContainer) {
10398
          itemCtrl.optionsContainer = jqLite(ITEM_TPL_OPTION_BUTTONS);
10399
          itemCtrl.$element.append(itemCtrl.optionsContainer);
10400
        }
10401
        itemCtrl.optionsContainer.append($element);
10402
 
10403
        itemCtrl.$element.addClass('item-right-editable');
10404
 
10405
        //Don't bubble click up to main .item
10406
        $element.on('click', stopPropagation);
10407
      };
10408
    }
10409
  };
10410
}]);
10411
 
10412
var ITEM_TPL_REORDER_BUTTON =
10413
  '<div data-prevent-scroll="true" class="item-right-edit item-reorder enable-pointer-events">' +
10414
  '</div>';
10415
 
10416
/**
10417
* @ngdoc directive
10418
* @name ionReorderButton
10419
* @parent ionic.directive:ionItem
10420
* @module ionic
10421
* @restrict E
10422
* Creates a reorder button inside a list item, that is visible when the
10423
* {@link ionic.directive:ionList ionList parent's} `show-reorder` evaluates to true or
10424
* `$ionicListDelegate.showReorder(true)` is called.
10425
*
10426
* Can be dragged to reorder items in the list. Takes any ionicon class.
10427
*
10428
* 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.
10429
*
10430
* 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.
10431
*
10432
* Look at {@link ionic.directive:ionList} for more examples.
10433
*
10434
* @usage
10435
*
10436
* ```html
10437
* <ion-list ng-controller="MyCtrl" show-reorder="true">
10438
*   <ion-item ng-repeat="item in items">
10439
*     Item {{item}}
10440
*     <ion-reorder-button class="ion-navicon"
10441
*                         on-reorder="moveItem(item, $fromIndex, $toIndex)">
10442
*     </ion-reorder-button>
10443
*   </ion-item>
10444
* </ion-list>
10445
* ```
10446
* ```js
10447
* function MyCtrl($scope) {
10448
*   $scope.items = [1, 2, 3, 4];
10449
*   $scope.moveItem = function(item, fromIndex, toIndex) {
10450
*     //Move the item in the array
10451
*     $scope.items.splice(fromIndex, 1);
10452
*     $scope.items.splice(toIndex, 0, item);
10453
*   };
10454
* }
10455
* ```
10456
*
10457
* @param {expression=} on-reorder Expression to call when an item is reordered.
10458
* Parameters given: $fromIndex, $toIndex.
10459
*/
10460
IonicModule
10461
.directive('ionReorderButton', ['$parse', function($parse) {
10462
  return {
10463
    restrict: 'E',
10464
    require: ['^ionItem', '^?ionList'],
10465
    priority: Number.MAX_VALUE,
10466
    compile: function($element, $attr) {
10467
      $attr.$set('class', ($attr['class'] || '') + ' button icon button-icon', true);
10468
      $element[0].setAttribute('data-prevent-scroll', true);
10469
      return function($scope, $element, $attr, ctrls) {
10470
        var itemCtrl = ctrls[0];
10471
        var listCtrl = ctrls[1];
10472
        var onReorderFn = $parse($attr.onReorder);
10473
 
10474
        $scope.$onReorder = function(oldIndex, newIndex) {
10475
          onReorderFn($scope, {
10476
            $fromIndex: oldIndex,
10477
            $toIndex: newIndex
10478
          });
10479
        };
10480
 
10481
        // prevent clicks from bubbling up to the item
10482
        if (!$attr.ngClick && !$attr.onClick && !$attr.onclick) {
10483
          $element[0].onclick = function(e) {
10484
            e.stopPropagation();
10485
            return false;
10486
          };
10487
        }
10488
 
10489
        var container = jqLite(ITEM_TPL_REORDER_BUTTON);
10490
        container.append($element);
10491
        itemCtrl.$element.append(container).addClass('item-right-editable');
10492
 
10493
        if (listCtrl && listCtrl.showReorder()) {
10494
          container.addClass('visible active');
10495
        }
10496
      };
10497
    }
10498
  };
10499
}]);
10500
 
10501
/**
10502
 * @ngdoc directive
10503
 * @name keyboardAttach
10504
 * @module ionic
10505
 * @restrict A
10506
 *
10507
 * @description
10508
 * keyboard-attach is an attribute directive which will cause an element to float above
10509
 * the keyboard when the keyboard shows. Currently only supports the
10510
 * [ion-footer-bar]({{ page.versionHref }}/api/directive/ionFooterBar/) directive.
10511
 *
10512
 * ### Notes
10513
 * - This directive requires the
10514
 * [Ionic Keyboard Plugin](https://github.com/driftyco/ionic-plugins-keyboard).
10515
 * - On Android not in fullscreen mode, i.e. you have
10516
 *   `<preference name="Fullscreen" value="false" />` or no preference in your `config.xml` file,
10517
 *   this directive is unnecessary since it is the default behavior.
10518
 * - On iOS, if there is an input in your footer, you will need to set
10519
 *   `cordova.plugins.Keyboard.disableScroll(true)`.
10520
 *
10521
 * @usage
10522
 *
10523
 * ```html
10524
 *  <ion-footer-bar align-title="left" keyboard-attach class="bar-assertive">
10525
 *    <h1 class="title">Title!</h1>
10526
 *  </ion-footer-bar>
10527
 * ```
10528
 */
10529
 
10530
IonicModule
10531
.directive('keyboardAttach', function() {
10532
  return function(scope, element) {
10533
    ionic.on('native.keyboardshow', onShow, window);
10534
    ionic.on('native.keyboardhide', onHide, window);
10535
 
10536
    //deprecated
10537
    ionic.on('native.showkeyboard', onShow, window);
10538
    ionic.on('native.hidekeyboard', onHide, window);
10539
 
10540
 
10541
    var scrollCtrl;
10542
 
10543
    function onShow(e) {
10544
      if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {
10545
        return;
10546
      }
10547
 
10548
      //for testing
10549
      var keyboardHeight = e.keyboardHeight || e.detail.keyboardHeight;
10550
      element.css('bottom', keyboardHeight + "px");
10551
      scrollCtrl = element.controller('$ionicScroll');
10552
      if (scrollCtrl) {
10553
        scrollCtrl.scrollView.__container.style.bottom = keyboardHeight + keyboardAttachGetClientHeight(element[0]) + "px";
10554
      }
10555
    }
10556
 
10557
    function onHide() {
10558
      if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {
10559
        return;
10560
      }
10561
 
10562
      element.css('bottom', '');
10563
      if (scrollCtrl) {
10564
        scrollCtrl.scrollView.__container.style.bottom = '';
10565
      }
10566
    }
10567
 
10568
    scope.$on('$destroy', function() {
10569
      ionic.off('native.keyboardshow', onShow, window);
10570
      ionic.off('native.keyboardhide', onHide, window);
10571
 
10572
      //deprecated
10573
      ionic.off('native.showkeyboard', onShow, window);
10574
      ionic.off('native.hidekeyboard', onHide, window);
10575
    });
10576
  };
10577
});
10578
 
10579
function keyboardAttachGetClientHeight(element) {
10580
  return element.clientHeight;
10581
}
10582
 
10583
/**
10584
* @ngdoc directive
10585
* @name ionList
10586
* @module ionic
10587
* @delegate ionic.service:$ionicListDelegate
10588
* @codepen JsHjf
10589
* @restrict E
10590
* @description
10591
* The List is a widely used interface element in almost any mobile app, and can include
10592
* content ranging from basic text all the way to buttons, toggles, icons, and thumbnails.
10593
*
10594
* Both the list, which contains items, and the list items themselves can be any HTML
10595
* element. The containing element requires the `list` class and each list item requires
10596
* the `item` class.
10597
*
10598
* However, using the ionList and ionItem directives make it easy to support various
10599
* interaction modes such as swipe to edit, drag to reorder, and removing items.
10600
*
10601
* Related: {@link ionic.directive:ionItem}, {@link ionic.directive:ionOptionButton}
10602
* {@link ionic.directive:ionReorderButton}, {@link ionic.directive:ionDeleteButton}, [`list CSS documentation`](/docs/components/#list).
10603
*
10604
* @usage
10605
*
10606
* Basic Usage:
10607
*
10608
* ```html
10609
* <ion-list>
10610
*   <ion-item ng-repeat="item in items">
10611
*     {% raw %}Hello, {{item}}!{% endraw %}
10612
*   </ion-item>
10613
* </ion-list>
10614
* ```
10615
*
10616
* Advanced Usage: Thumbnails, Delete buttons, Reordering, Swiping
10617
*
10618
* ```html
10619
* <ion-list ng-controller="MyCtrl"
10620
*           show-delete="shouldShowDelete"
10621
*           show-reorder="shouldShowReorder"
10622
*           can-swipe="listCanSwipe">
10623
*   <ion-item ng-repeat="item in items"
10624
*             class="item-thumbnail-left">
10625
*
10626
*     {% raw %}<img ng-src="{{item.img}}">
10627
*     <h2>{{item.title}}</h2>
10628
*     <p>{{item.description}}</p>{% endraw %}
10629
*     <ion-option-button class="button-positive"
10630
*                        ng-click="share(item)">
10631
*       Share
10632
*     </ion-option-button>
10633
*     <ion-option-button class="button-info"
10634
*                        ng-click="edit(item)">
10635
*       Edit
10636
*     </ion-option-button>
10637
*     <ion-delete-button class="ion-minus-circled"
10638
*                        ng-click="items.splice($index, 1)">
10639
*     </ion-delete-button>
10640
*     <ion-reorder-button class="ion-navicon"
10641
*                         on-reorder="reorderItem(item, $fromIndex, $toIndex)">
10642
*     </ion-reorder-button>
10643
*
10644
*   </ion-item>
10645
* </ion-list>
10646
* ```
10647
*
10648
*```javascript
10649
* app.controller('MyCtrl', function($scope) {
10650
*  $scope.shouldShowDelete = false;
10651
*  $scope.shouldShowReorder = false;
10652
*  $scope.listCanSwipe = true
10653
* });
10654
*```
10655
*
10656
* @param {string=} delegate-handle The handle used to identify this list with
10657
* {@link ionic.service:$ionicListDelegate}.
10658
* @param type {string=} The type of list to use (list-inset or card)
10659
* @param show-delete {boolean=} Whether the delete buttons for the items in the list are
10660
* currently shown or hidden.
10661
* @param show-reorder {boolean=} Whether the reorder buttons for the items in the list are
10662
* currently shown or hidden.
10663
* @param can-swipe {boolean=} Whether the items in the list are allowed to be swiped to reveal
10664
* option buttons. Default: true.
10665
*/
10666
IonicModule
10667
.directive('ionList', [
10668
  '$timeout',
10669
function($timeout) {
10670
  return {
10671
    restrict: 'E',
10672
    require: ['ionList', '^?$ionicScroll'],
10673
    controller: '$ionicList',
10674
    compile: function($element, $attr) {
10675
      var listEl = jqLite('<div class="list">')
10676
        .append($element.contents())
10677
        .addClass($attr.type);
10678
 
10679
      $element.append(listEl);
10680
 
10681
      return function($scope, $element, $attrs, ctrls) {
10682
        var listCtrl = ctrls[0];
10683
        var scrollCtrl = ctrls[1];
10684
 
10685
        // Wait for child elements to render...
10686
        $timeout(init);
10687
 
10688
        function init() {
10689
          var listView = listCtrl.listView = new ionic.views.ListView({
10690
            el: $element[0],
10691
            listEl: $element.children()[0],
10692
            scrollEl: scrollCtrl && scrollCtrl.element,
10693
            scrollView: scrollCtrl && scrollCtrl.scrollView,
10694
            onReorder: function(el, oldIndex, newIndex) {
10695
              var itemScope = jqLite(el).scope();
10696
              if (itemScope && itemScope.$onReorder) {
10697
                // Make sure onReorder is called in apply cycle,
10698
                // but also make sure it has no conflicts by doing
10699
                // $evalAsync
10700
                $timeout(function() {
10701
                  itemScope.$onReorder(oldIndex, newIndex);
10702
                });
10703
              }
10704
            },
10705
            canSwipe: function() {
10706
              return listCtrl.canSwipeItems();
10707
            }
10708
          });
10709
 
10710
          $scope.$on('$destroy', function() {
10711
            if (listView) {
10712
              listView.deregister && listView.deregister();
10713
              listView = null;
10714
            }
10715
          });
10716
 
10717
          if (isDefined($attr.canSwipe)) {
10718
            $scope.$watch('!!(' + $attr.canSwipe + ')', function(value) {
10719
              listCtrl.canSwipeItems(value);
10720
            });
10721
          }
10722
          if (isDefined($attr.showDelete)) {
10723
            $scope.$watch('!!(' + $attr.showDelete + ')', function(value) {
10724
              listCtrl.showDelete(value);
10725
            });
10726
          }
10727
          if (isDefined($attr.showReorder)) {
10728
            $scope.$watch('!!(' + $attr.showReorder + ')', function(value) {
10729
              listCtrl.showReorder(value);
10730
            });
10731
          }
10732
 
10733
          $scope.$watch(function() {
10734
            return listCtrl.showDelete();
10735
          }, function(isShown, wasShown) {
10736
            //Only use isShown=false if it was already shown
10737
            if (!isShown && !wasShown) { return; }
10738
 
10739
            if (isShown) listCtrl.closeOptionButtons();
10740
            listCtrl.canSwipeItems(!isShown);
10741
 
10742
            $element.children().toggleClass('list-left-editing', isShown);
10743
            $element.toggleClass('disable-pointer-events', isShown);
10744
 
10745
            var deleteButton = jqLite($element[0].getElementsByClassName('item-delete'));
10746
            setButtonShown(deleteButton, listCtrl.showDelete);
10747
          });
10748
 
10749
          $scope.$watch(function() {
10750
            return listCtrl.showReorder();
10751
          }, function(isShown, wasShown) {
10752
            //Only use isShown=false if it was already shown
10753
            if (!isShown && !wasShown) { return; }
10754
 
10755
            if (isShown) listCtrl.closeOptionButtons();
10756
            listCtrl.canSwipeItems(!isShown);
10757
 
10758
            $element.children().toggleClass('list-right-editing', isShown);
10759
            $element.toggleClass('disable-pointer-events', isShown);
10760
 
10761
            var reorderButton = jqLite($element[0].getElementsByClassName('item-reorder'));
10762
            setButtonShown(reorderButton, listCtrl.showReorder);
10763
          });
10764
 
10765
          function setButtonShown(el, shown) {
10766
            shown() && el.addClass('visible') || el.removeClass('active');
10767
            ionic.requestAnimationFrame(function() {
10768
              shown() && el.addClass('active') || el.removeClass('visible');
10769
            });
10770
          }
10771
        }
10772
 
10773
      };
10774
    }
10775
  };
10776
}]);
10777
 
10778
/**
10779
 * @ngdoc directive
10780
 * @name menuClose
10781
 * @module ionic
10782
 * @restrict AC
10783
 *
10784
 * @description
10785
 * `menu-close` is an attribute directive that closes a currently opened side menu.
10786
 * Note that by default, navigation transitions will not animate between views when
10787
 * the menu is open. Additionally, this directive will reset the entering view's
10788
 * history stack, making the new page the root of the history stack. This is done
10789
 * to replicate the user experience seen in most side menu implementations, which is
10790
 * to not show the back button at the root of the stack and show only the
10791
 * menu button. We recommend that you also use the `enable-menu-with-back-views="false"`
10792
 * {@link ionic.directive:ionSideMenus} attribute when using the menuClose directive.
10793
 *
10794
 * @usage
10795
 * Below is an example of a link within a side menu. Tapping this link would
10796
 * automatically close the currently opened menu.
10797
 *
10798
 * ```html
10799
 * <a menu-close href="#/home" class="item">Home</a>
10800
 * ```
10801
 */
10802
IonicModule
10803
.directive('menuClose', ['$ionicHistory', function($ionicHistory) {
10804
  return {
10805
    restrict: 'AC',
10806
    link: function($scope, $element) {
10807
      $element.bind('click', function() {
10808
        var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
10809
        if (sideMenuCtrl) {
10810
          $ionicHistory.nextViewOptions({
10811
            historyRoot: true,
10812
            disableAnimate: true,
10813
            expire: 300
10814
          });
10815
          sideMenuCtrl.close();
10816
        }
10817
      });
10818
    }
10819
  };
10820
}]);
10821
 
10822
/**
10823
 * @ngdoc directive
10824
 * @name menuToggle
10825
 * @module ionic
10826
 * @restrict AC
10827
 *
10828
 * @description
10829
 * Toggle a side menu on the given side.
10830
 *
10831
 * @usage
10832
 * Below is an example of a link within a nav bar. Tapping this button
10833
 * would open the given side menu, and tapping it again would close it.
10834
 *
10835
 * ```html
10836
 * <ion-nav-bar>
10837
 *   <ion-nav-buttons side="left">
10838
 *    <button menu-toggle="left" class="button button-icon icon ion-navicon"></button>
10839
 *   </ion-nav-buttons>
10840
 * </ion-nav-bar>
10841
 * ```
10842
 *
10843
 * ### Button Hidden On Child Views
10844
 * By default, the menu toggle button will only appear on a root
10845
 * level side-menu page. Navigating in to child views will hide the menu-
10846
 * toggle button. They can be made visible on child pages by setting the
10847
 * enable-menu-with-back-views attribute of the {@link ionic.directive:ionSideMenus}
10848
 * directive to true.
10849
 *
10850
 * ```html
10851
 * <ion-side-menus enable-menu-with-back-views="true">
10852
 * ```
10853
 */
10854
IonicModule
10855
.directive('menuToggle', function() {
10856
  return {
10857
    restrict: 'AC',
10858
    link: function($scope, $element, $attr) {
10859
      $scope.$on('$ionicView.beforeEnter', function(ev, viewData) {
10860
        if (viewData.enableBack) {
10861
          var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
10862
          if (!sideMenuCtrl.enableMenuWithBackViews()) {
10863
            $element.addClass('hide');
10864
          }
10865
        } else {
10866
          $element.removeClass('hide');
10867
        }
10868
      });
10869
 
10870
      $element.bind('click', function() {
10871
        var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
10872
        sideMenuCtrl && sideMenuCtrl.toggle($attr.menuToggle);
10873
      });
10874
    }
10875
  };
10876
});
10877
 
10878
/*
10879
 * We don't document the ionModal directive, we instead document
10880
 * the $ionicModal service
10881
 */
10882
IonicModule
10883
.directive('ionModal', [function() {
10884
  return {
10885
    restrict: 'E',
10886
    transclude: true,
10887
    replace: true,
10888
    controller: [function() {}],
10889
    template: '<div class="modal-backdrop">' +
10890
                '<div class="modal-backdrop-bg"></div>' +
10891
                '<div class="modal-wrapper" ng-transclude></div>' +
10892
              '</div>'
10893
  };
10894
}]);
10895
 
10896
IonicModule
10897
.directive('ionModalView', function() {
10898
  return {
10899
    restrict: 'E',
10900
    compile: function(element) {
10901
      element.addClass('modal');
10902
    }
10903
  };
10904
});
10905
 
10906
/**
10907
 * @ngdoc directive
10908
 * @name ionNavBackButton
10909
 * @module ionic
10910
 * @restrict E
10911
 * @parent ionNavBar
10912
 * @description
10913
 * Creates a back button inside an {@link ionic.directive:ionNavBar}.
10914
 *
10915
 * The back button will appear when the user is able to go back in the current navigation stack. By
10916
 * default, the markup of the back button is automatically built using platform-appropriate defaults
10917
 * (iOS back button icon on iOS and Android icon on Android).
10918
 *
10919
 * Additionally, the button is automatically set to `$ionicGoBack()` on click/tap. By default, the
10920
 * app will navigate back one view when the back button is clicked.  More advanced behavior is also
10921
 * possible, as outlined below.
10922
 *
10923
 * @usage
10924
 *
10925
 * Recommended markup for default settings:
10926
 *
10927
 * ```html
10928
 * <ion-nav-bar>
10929
 *   <ion-nav-back-button>
10930
 *   </ion-nav-back-button>
10931
 * </ion-nav-bar>
10932
 * ```
10933
 *
10934
 * With custom inner markup, and automatically adds a default click action:
10935
 *
10936
 * ```html
10937
 * <ion-nav-bar>
10938
 *   <ion-nav-back-button class="button-clear">
10939
 *     <i class="ion-arrow-left-c"></i> Back
10940
 *   </ion-nav-back-button>
10941
 * </ion-nav-bar>
10942
 * ```
10943
 *
10944
 * With custom inner markup and custom click action, using {@link ionic.service:$ionicHistory}:
10945
 *
10946
 * ```html
10947
 * <ion-nav-bar ng-controller="MyCtrl">
10948
 *   <ion-nav-back-button class="button-clear"
10949
 *     ng-click="myGoBack()">
10950
 *     <i class="ion-arrow-left-c"></i> Back
10951
 *   </ion-nav-back-button>
10952
 * </ion-nav-bar>
10953
 * ```
10954
 * ```js
10955
 * function MyCtrl($scope, $ionicHistory) {
10956
 *   $scope.myGoBack = function() {
10957
 *     $ionicHistory.goBack();
10958
 *   };
10959
 * }
10960
 * ```
10961
 */
10962
IonicModule
10963
.directive('ionNavBackButton', ['$ionicConfig', '$document', function($ionicConfig, $document) {
10964
  return {
10965
    restrict: 'E',
10966
    require: '^ionNavBar',
10967
    compile: function(tElement, tAttrs) {
10968
 
10969
      // clone the back button, but as a <div>
10970
      var buttonEle = $document[0].createElement('button');
10971
      for (var n in tAttrs.$attr) {
10972
        buttonEle.setAttribute(tAttrs.$attr[n], tAttrs[n]);
10973
      }
10974
 
10975
      if (!tAttrs.ngClick) {
10976
        buttonEle.setAttribute('ng-click', '$ionicGoBack()');
10977
      }
10978
 
10979
      buttonEle.className = 'button back-button hide buttons ' + (tElement.attr('class') || '');
10980
      buttonEle.innerHTML = tElement.html() || '';
10981
 
10982
      var childNode;
10983
      var hasIcon = hasIconClass(tElement[0]);
10984
      var hasInnerText;
10985
      var hasButtonText;
10986
      var hasPreviousTitle;
10987
 
10988
      for (var x = 0; x < tElement[0].childNodes.length; x++) {
10989
        childNode = tElement[0].childNodes[x];
10990
        if (childNode.nodeType === 1) {
10991
          if (hasIconClass(childNode)) {
10992
            hasIcon = true;
10993
          } else if (childNode.classList.contains('default-title')) {
10994
            hasButtonText = true;
10995
          } else if (childNode.classList.contains('previous-title')) {
10996
            hasPreviousTitle = true;
10997
          }
10998
        } else if (!hasInnerText && childNode.nodeType === 3) {
10999
          hasInnerText = !!childNode.nodeValue.trim();
11000
        }
11001
      }
11002
 
11003
      function hasIconClass(ele) {
11004
        return /ion-|icon/.test(ele.className);
11005
      }
11006
 
11007
      var defaultIcon = $ionicConfig.backButton.icon();
11008
      if (!hasIcon && defaultIcon && defaultIcon !== 'none') {
11009
        buttonEle.innerHTML = '<i class="icon ' + defaultIcon + '"></i> ' + buttonEle.innerHTML;
11010
        buttonEle.className += ' button-clear';
11011
      }
11012
 
11013
      if (!hasInnerText) {
11014
        var buttonTextEle = $document[0].createElement('span');
11015
        buttonTextEle.className = 'back-text';
11016
 
11017
        if (!hasButtonText && $ionicConfig.backButton.text()) {
11018
          buttonTextEle.innerHTML += '<span class="default-title">' + $ionicConfig.backButton.text() + '</span>';
11019
        }
11020
        if (!hasPreviousTitle && $ionicConfig.backButton.previousTitleText()) {
11021
          buttonTextEle.innerHTML += '<span class="previous-title"></span>';
11022
        }
11023
        buttonEle.appendChild(buttonTextEle);
11024
 
11025
      }
11026
 
11027
      tElement.attr('class', 'hide');
11028
      tElement.empty();
11029
 
11030
      return {
11031
        pre: function($scope, $element, $attr, navBarCtrl) {
11032
          // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
11033
          navBarCtrl.navElement('backButton', buttonEle.outerHTML);
11034
          buttonEle = null;
11035
        }
11036
      };
11037
    }
11038
  };
11039
}]);
11040
 
11041
 
11042
/**
11043
 * @ngdoc directive
11044
 * @name ionNavBar
11045
 * @module ionic
11046
 * @delegate ionic.service:$ionicNavBarDelegate
11047
 * @restrict E
11048
 *
11049
 * @description
11050
 * If we have an {@link ionic.directive:ionNavView} directive, we can also create an
11051
 * `<ion-nav-bar>`, which will create a topbar that updates as the application state changes.
11052
 *
11053
 * We can add a back button by putting an {@link ionic.directive:ionNavBackButton} inside.
11054
 *
11055
 * We can add buttons depending on the currently visible view using
11056
 * {@link ionic.directive:ionNavButtons}.
11057
 *
11058
 * Note that the ion-nav-bar element will only work correctly if your content has an
11059
 * ionView around it.
11060
 *
11061
 * @usage
11062
 *
11063
 * ```html
11064
 * <body ng-app="starter">
11065
 *   <!-- The nav bar that will be updated as we navigate -->
11066
 *   <ion-nav-bar class="bar-positive">
11067
 *   </ion-nav-bar>
11068
 *
11069
 *   <!-- where the initial view template will be rendered -->
11070
 *   <ion-nav-view>
11071
 *     <ion-view>
11072
 *       <ion-content>Hello!</ion-content>
11073
 *     </ion-view>
11074
 *   </ion-nav-view>
11075
 * </body>
11076
 * ```
11077
 *
11078
 * @param {string=} delegate-handle The handle used to identify this navBar
11079
 * with {@link ionic.service:$ionicNavBarDelegate}.
11080
 * @param align-title {string=} Where to align the title of the navbar.
11081
 * Available: 'left', 'right', 'center'. Defaults to 'center'.
11082
 * @param {boolean=} no-tap-scroll By default, the navbar will scroll the content
11083
 * to the top when tapped.  Set no-tap-scroll to true to disable this behavior.
11084
 *
11085
 * </table><br/>
11086
 */
11087
IonicModule
11088
.directive('ionNavBar', function() {
11089
  return {
11090
    restrict: 'E',
11091
    controller: '$ionicNavBar',
11092
    scope: true,
11093
    link: function($scope, $element, $attr, ctrl) {
11094
      ctrl.init();
11095
    }
11096
  };
11097
});
11098
 
11099
 
11100
/**
11101
 * @ngdoc directive
11102
 * @name ionNavButtons
11103
 * @module ionic
11104
 * @restrict E
11105
 * @parent ionNavView
11106
 *
11107
 * @description
11108
 * Use nav buttons to set the buttons on your {@link ionic.directive:ionNavBar}
11109
 * from within an {@link ionic.directive:ionView}. This gives each
11110
 * view template the ability to specify which buttons should show in the nav bar,
11111
 * overriding any default buttons already placed in the nav bar.
11112
 *
11113
 * Any buttons you declare will be positioned on the navbar's corresponding side. Primary
11114
 * buttons generally map to the left side of the header, and secondary buttons are
11115
 * generally on the right side. However, their exact locations are platform-specific.
11116
 * For example, in iOS, the primary buttons are on the far left of the header, and
11117
 * secondary buttons are on the far right, with the header title centered between them.
11118
 * For Android, however, both groups of buttons are on the far right of the header,
11119
 * with the header title aligned left.
11120
 *
11121
 * We recommend always using `primary` and `secondary`, so the buttons correctly map
11122
 * to the side familiar to users of each platform. However, in cases where buttons should
11123
 * always be on an exact side, both `left` and `right` sides are still available. For
11124
 * example, a toggle button for a left side menu should be on the left side; in this case,
11125
 * we'd recommend using `side="left"`, so it's always on the left, no matter the platform.
11126
 *
11127
 * ***Note*** that `ion-nav-buttons` must be immediate descendants of the `ion-view` or
11128
 * `ion-nav-bar` element (basically, don't wrap it in another div).
11129
 *
11130
 * @usage
11131
 * ```html
11132
 * <ion-nav-bar>
11133
 * </ion-nav-bar>
11134
 * <ion-nav-view>
11135
 *   <ion-view>
11136
 *     <ion-nav-buttons side="primary">
11137
 *       <button class="button" ng-click="doSomething()">
11138
 *         I'm a button on the primary of the navbar!
11139
 *       </button>
11140
 *     </ion-nav-buttons>
11141
 *     <ion-content>
11142
 *       Some super content here!
11143
 *     </ion-content>
11144
 *   </ion-view>
11145
 * </ion-nav-view>
11146
 * ```
11147
 *
11148
 * @param {string} side The side to place the buttons in the
11149
 * {@link ionic.directive:ionNavBar}. Available sides: `primary`, `secondary`, `left`, and `right`.
11150
 */
11151
IonicModule
11152
.directive('ionNavButtons', ['$document', function($document) {
11153
  return {
11154
    require: '^ionNavBar',
11155
    restrict: 'E',
11156
    compile: function(tElement, tAttrs) {
11157
      var side = 'left';
11158
 
11159
      if (/^primary|secondary|right$/i.test(tAttrs.side || '')) {
11160
        side = tAttrs.side.toLowerCase();
11161
      }
11162
 
11163
      var spanEle = $document[0].createElement('span');
11164
      spanEle.className = side + '-buttons';
11165
      spanEle.innerHTML = tElement.html();
11166
 
11167
      var navElementType = side + 'Buttons';
11168
 
11169
      tElement.attr('class', 'hide');
11170
      tElement.empty();
11171
 
11172
      return {
11173
        pre: function($scope, $element, $attrs, navBarCtrl) {
11174
          // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
11175
 
11176
          var parentViewCtrl = $element.parent().data('$ionViewController');
11177
          if (parentViewCtrl) {
11178
            // if the parent is an ion-view, then these are ion-nav-buttons for JUST this ion-view
11179
            parentViewCtrl.navElement(navElementType, spanEle.outerHTML);
11180
 
11181
          } else {
11182
            // these are buttons for all views that do not have their own ion-nav-buttons
11183
            navBarCtrl.navElement(navElementType, spanEle.outerHTML);
11184
          }
11185
 
11186
          spanEle = null;
11187
        }
11188
      };
11189
    }
11190
  };
11191
}]);
11192
 
11193
/**
11194
 * @ngdoc directive
11195
 * @name navDirection
11196
 * @module ionic
11197
 * @restrict A
11198
 *
11199
 * @description
11200
 * The direction which the nav view transition should animate. Available options
11201
 * are: `forward`, `back`, `enter`, `exit`, `swap`.
11202
 *
11203
 * @usage
11204
 *
11205
 * ```html
11206
 * <a nav-direction="forward" href="#/home">Home</a>
11207
 * ```
11208
 */
11209
IonicModule
11210
.directive('navDirection', ['$ionicViewSwitcher', function($ionicViewSwitcher) {
11211
  return {
11212
    restrict: 'A',
11213
    priority: 1000,
11214
    link: function($scope, $element, $attr) {
11215
      $element.bind('click', function() {
11216
        $ionicViewSwitcher.nextDirection($attr.navDirection);
11217
      });
11218
    }
11219
  };
11220
}]);
11221
 
11222
/**
11223
 * @ngdoc directive
11224
 * @name ionNavTitle
11225
 * @module ionic
11226
 * @restrict E
11227
 * @parent ionNavView
11228
 *
11229
 * @description
11230
 *
11231
 * The nav title directive replaces an {@link ionic.directive:ionNavBar} title text with
11232
 * custom HTML from within an {@link ionic.directive:ionView} template. This gives each
11233
 * view the ability to specify its own custom title element, such as an image or any HTML,
11234
 * rather than being text-only. Alternatively, text-only titles can be updated using the
11235
 * `view-title` {@link ionic.directive:ionView} attribute.
11236
 *
11237
 * Note that `ion-nav-title` must be an immediate descendant of the `ion-view` or
11238
 * `ion-nav-bar` element (basically don't wrap it in another div).
11239
 *
11240
 * @usage
11241
 * ```html
11242
 * <ion-nav-bar>
11243
 * </ion-nav-bar>
11244
 * <ion-nav-view>
11245
 *   <ion-view>
11246
 *     <ion-nav-title>
11247
 *       <img src="logo.svg">
11248
 *     </ion-nav-title>
11249
 *     <ion-content>
11250
 *       Some super content here!
11251
 *     </ion-content>
11252
 *   </ion-view>
11253
 * </ion-nav-view>
11254
 * ```
11255
 *
11256
 */
11257
IonicModule
11258
.directive('ionNavTitle', ['$document', function($document) {
11259
  return {
11260
    require: '^ionNavBar',
11261
    restrict: 'E',
11262
    compile: function(tElement, tAttrs) {
11263
      var navElementType = 'title';
11264
      var spanEle = $document[0].createElement('span');
11265
      for (var n in tAttrs.$attr) {
11266
        spanEle.setAttribute(tAttrs.$attr[n], tAttrs[n]);
11267
      }
11268
      spanEle.classList.add('nav-bar-title');
11269
      spanEle.innerHTML = tElement.html();
11270
 
11271
      tElement.attr('class', 'hide');
11272
      tElement.empty();
11273
 
11274
      return {
11275
        pre: function($scope, $element, $attrs, navBarCtrl) {
11276
          // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
11277
 
11278
          var parentViewCtrl = $element.parent().data('$ionViewController');
11279
          if (parentViewCtrl) {
11280
            // if the parent is an ion-view, then these are ion-nav-buttons for JUST this ion-view
11281
            parentViewCtrl.navElement(navElementType, spanEle.outerHTML);
11282
 
11283
          } else {
11284
            // these are buttons for all views that do not have their own ion-nav-buttons
11285
            navBarCtrl.navElement(navElementType, spanEle.outerHTML);
11286
          }
11287
 
11288
          spanEle = null;
11289
        }
11290
      };
11291
    }
11292
  };
11293
}]);
11294
 
11295
/**
11296
 * @ngdoc directive
11297
 * @name navTransition
11298
 * @module ionic
11299
 * @restrict A
11300
 *
11301
 * @description
11302
 * The transition type which the nav view transition should use when it animates.
11303
 * Current, options are `ios`, `android`, and `none`. More options coming soon.
11304
 *
11305
 * @usage
11306
 *
11307
 * ```html
11308
 * <a nav-transition="none" href="#/home">Home</a>
11309
 * ```
11310
 */
11311
IonicModule
11312
.directive('navTransition', ['$ionicViewSwitcher', function($ionicViewSwitcher) {
11313
  return {
11314
    restrict: 'A',
11315
    priority: 1000,
11316
    link: function($scope, $element, $attr) {
11317
      $element.bind('click', function() {
11318
        $ionicViewSwitcher.nextTransition($attr.navTransition);
11319
      });
11320
    }
11321
  };
11322
}]);
11323
 
11324
/**
11325
 * @ngdoc directive
11326
 * @name ionNavView
11327
 * @module ionic
11328
 * @restrict E
11329
 * @codepen odqCz
11330
 *
11331
 * @description
11332
 * As a user navigates throughout your app, Ionic is able to keep track of their
11333
 * navigation history. By knowing their history, transitions between views
11334
 * correctly enter and exit using the platform's transition style. An additional
11335
 * benefit to Ionic's navigation system is its ability to manage multiple
11336
 * histories. For example, each tab can have it's own navigation history stack.
11337
 *
11338
 * Ionic uses the AngularUI Router module so app interfaces can be organized
11339
 * into various "states". Like Angular's core $route service, URLs can be used
11340
 * to control the views. However, the AngularUI Router provides a more powerful
11341
 * state manager in that states are bound to named, nested, and parallel views,
11342
 * allowing more than one template to be rendered on the same page.
11343
 * Additionally, each state is not required to be bound to a URL, and data can
11344
 * be pushed to each state which allows much flexibility.
11345
 *
11346
 * The ionNavView directive is used to render templates in your application. Each template
11347
 * is part of a state. States are usually mapped to a url, and are defined programatically
11348
 * using angular-ui-router (see [their docs](https://github.com/angular-ui/ui-router/wiki),
11349
 * and remember to replace ui-view with ion-nav-view in examples).
11350
 *
11351
 * @usage
11352
 * In this example, we will create a navigation view that contains our different states for the app.
11353
 *
11354
 * To do this, in our markup we use ionNavView top level directive. To display a header bar we use
11355
 * the {@link ionic.directive:ionNavBar} directive that updates as we navigate through the
11356
 * navigation stack.
11357
 *
11358
 * Next, we need to setup our states that will be rendered.
11359
 *
11360
 * ```js
11361
 * var app = angular.module('myApp', ['ionic']);
11362
 * app.config(function($stateProvider) {
11363
 *   $stateProvider
11364
 *   .state('index', {
11365
 *     url: '/',
11366
 *     templateUrl: 'home.html'
11367
 *   })
11368
 *   .state('music', {
11369
 *     url: '/music',
11370
 *     templateUrl: 'music.html'
11371
 *   });
11372
 * });
11373
 * ```
11374
 * Then on app start, $stateProvider will look at the url, see it matches the index state,
11375
 * and then try to load home.html into the `<ion-nav-view>`.
11376
 *
11377
 * Pages are loaded by the URLs given. One simple way to create templates in Angular is to put
11378
 * them directly into your HTML file and use the `<script type="text/ng-template">` syntax.
11379
 * So here is one way to put home.html into our app:
11380
 *
11381
 * ```html
11382
 * <script id="home" type="text/ng-template">
11383
 *   <!-- The title of the ion-view will be shown on the navbar -->
11384
 *   <ion-view view-title="Home">
11385
 *     <ion-content ng-controller="HomeCtrl">
11386
 *       <!-- The content of the page -->
11387
 *       <a href="#/music">Go to music page!</a>
11388
 *     </ion-content>
11389
 *   </ion-view>
11390
 * </script>
11391
 * ```
11392
 *
11393
 * This is good to do because the template will be cached for very fast loading, instead of
11394
 * having to fetch them from the network.
11395
 *
11396
 * ## Caching
11397
 *
11398
 * By default, views are cached to improve performance. When a view is navigated away from, its
11399
 * element is left in the DOM, and its scope is disconnected from the `$watch` cycle. When
11400
 * navigating to a view that is already cached, its scope is then reconnected, and the existing
11401
 * element that was left in the DOM becomes the active view. This also allows for the scroll
11402
 * position of previous views to be maintained.
11403
 *
11404
 * Caching can be disabled and enabled in multiple ways. By default, Ionic will cache a maximum of
11405
 * 10 views, and not only can this be configured, but apps can also explicitly state which views
11406
 * should and should not be cached.
11407
 *
11408
 * Note that because we are caching these views, *we aren’t destroying scopes*. Instead, scopes
11409
 * are being disconnected from the watch cycle. Because scopes are not being destroyed and
11410
 * recreated, controllers are not loading again on a subsequent viewing. If the app/controller
11411
 * needs to know when a view has entered or has left, then view events emitted from the
11412
 * {@link ionic.directive:ionView} scope, such as `$ionicView.enter`, may be useful.
11413
 *
11414
 * By default, when navigating back in the history, the "forward" views are removed from the cache.
11415
 * If you navigate forward to the same view again, it'll create a new DOM element and controller
11416
 * instance. Basically, any forward views are reset each time. This can be configured using the
11417
 * {@link ionic.provider:$ionicConfigProvider}:
11418
 *
11419
 * ```js
11420
 * $ionicConfigProvider.views.forwardCache(true);
11421
 * ```
11422
 *
11423
 * #### Disable cache globally
11424
 *
11425
 * The {@link ionic.provider:$ionicConfigProvider} can be used to set the maximum allowable views
11426
 * which can be cached, but this can also be use to disable all caching by setting it to 0.
11427
 *
11428
 * ```js
11429
 * $ionicConfigProvider.views.maxCache(0);
11430
 * ```
11431
 *
11432
 * #### Disable cache within state provider
11433
 *
11434
 * ```js
11435
 * $stateProvider.state('myState', {
11436
 *    cache: false,
11437
 *    url : '/myUrl',
11438
 *    templateUrl : 'my-template.html'
11439
 * })
11440
 * ```
11441
 *
11442
 * #### Disable cache with an attribute
11443
 *
11444
 * ```html
11445
 * <ion-view cache-view="false" view-title="My Title!">
11446
 *   ...
11447
 * </ion-view>
11448
 * ```
11449
 *
11450
 *
11451
 * ## AngularUI Router
11452
 *
11453
 * Please visit [AngularUI Router's docs](https://github.com/angular-ui/ui-router/wiki) for
11454
 * more info. Below is a great video by the AngularUI Router team that may help to explain
11455
 * how it all works:
11456
 *
11457
 * <iframe width="560" height="315" src="//www.youtube.com/embed/dqJRoh8MnBo"
11458
 * frameborder="0" allowfullscreen></iframe>
11459
 *
11460
 * @param {string=} name A view name. The name should be unique amongst the other views in the
11461
 * same state. You can have views of the same name that live in different states. For more
11462
 * information, see ui-router's
11463
 * [ui-view documentation](http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.directive:ui-view).
11464
 */
11465
IonicModule
11466
.directive('ionNavView', [
11467
  '$state',
11468
  '$ionicConfig',
11469
function($state, $ionicConfig) {
11470
  // IONIC's fork of Angular UI Router, v0.2.10
11471
  // the navView handles registering views in the history and how to transition between them
11472
  return {
11473
    restrict: 'E',
11474
    terminal: true,
11475
    priority: 2000,
11476
    transclude: true,
11477
    controller: '$ionicNavView',
11478
    compile: function(tElement, tAttrs, transclude) {
11479
 
11480
      // a nav view element is a container for numerous views
11481
      tElement.addClass('view-container');
11482
      ionic.DomUtil.cachedAttr(tElement, 'nav-view-transition', $ionicConfig.views.transition());
11483
 
11484
      return function($scope, $element, $attr, navViewCtrl) {
11485
        var latestLocals;
11486
 
11487
        // Put in the compiled initial view
11488
        transclude($scope, function(clone) {
11489
          $element.append(clone);
11490
        });
11491
 
11492
        var viewData = navViewCtrl.init();
11493
 
11494
        // listen for $stateChangeSuccess
11495
        $scope.$on('$stateChangeSuccess', function() {
11496
          updateView(false);
11497
        });
11498
        $scope.$on('$viewContentLoading', function() {
11499
          updateView(false);
11500
        });
11501
 
11502
        // initial load, ready go
11503
        updateView(true);
11504
 
11505
 
11506
        function updateView(firstTime) {
11507
          // get the current local according to the $state
11508
          var viewLocals = $state.$current && $state.$current.locals[viewData.name];
11509
 
11510
          // do not update THIS nav-view if its is not the container for the given state
11511
          // if the viewLocals are the same as THIS latestLocals, then nothing to do
11512
          if (!viewLocals || (!firstTime && viewLocals === latestLocals)) return;
11513
 
11514
          // update the latestLocals
11515
          latestLocals = viewLocals;
11516
          viewData.state = viewLocals.$$state;
11517
 
11518
          // register, update and transition to the new view
11519
          navViewCtrl.register(viewLocals);
11520
        }
11521
 
11522
      };
11523
    }
11524
  };
11525
}]);
11526
 
11527
IonicModule
11528
 
11529
.config(['$provide', function($provide) {
11530
  $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
11531
    // drop the default ngClick directive
11532
    $delegate.shift();
11533
    return $delegate;
11534
  }]);
11535
}])
11536
 
11537
/**
11538
 * @private
11539
 */
11540
.factory('$ionicNgClick', ['$parse', function($parse) {
11541
  return function(scope, element, clickExpr) {
11542
    var clickHandler = angular.isFunction(clickExpr) ?
11543
      clickExpr :
11544
      $parse(clickExpr);
11545
 
11546
    element.on('click', function(event) {
11547
      scope.$apply(function() {
11548
        clickHandler(scope, {$event: (event)});
11549
      });
11550
    });
11551
 
11552
    // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
11553
    // something else nearby.
11554
    element.onclick = noop;
11555
  };
11556
}])
11557
 
11558
.directive('ngClick', ['$ionicNgClick', function($ionicNgClick) {
11559
  return function(scope, element, attr) {
11560
    $ionicNgClick(scope, element, attr.ngClick);
11561
  };
11562
}])
11563
 
11564
.directive('ionStopEvent', function() {
11565
  return {
11566
    restrict: 'A',
11567
    link: function(scope, element, attr) {
11568
      element.bind(attr.ionStopEvent, eventStopPropagation);
11569
    }
11570
  };
11571
});
11572
function eventStopPropagation(e) {
11573
  e.stopPropagation();
11574
}
11575
 
11576
 
11577
/**
11578
 * @ngdoc directive
11579
 * @name ionPane
11580
 * @module ionic
11581
 * @restrict E
11582
 *
11583
 * @description A simple container that fits content, with no side effects.  Adds the 'pane' class to the element.
11584
 */
11585
IonicModule
11586
.directive('ionPane', function() {
11587
  return {
11588
    restrict: 'E',
11589
    link: function(scope, element) {
11590
      element.addClass('pane');
11591
    }
11592
  };
11593
});
11594
 
11595
/*
11596
 * We don't document the ionPopover directive, we instead document
11597
 * the $ionicPopover service
11598
 */
11599
IonicModule
11600
.directive('ionPopover', [function() {
11601
  return {
11602
    restrict: 'E',
11603
    transclude: true,
11604
    replace: true,
11605
    controller: [function() {}],
11606
    template: '<div class="popover-backdrop">' +
11607
                '<div class="popover-wrapper" ng-transclude></div>' +
11608
              '</div>'
11609
  };
11610
}]);
11611
 
11612
IonicModule
11613
.directive('ionPopoverView', function() {
11614
  return {
11615
    restrict: 'E',
11616
    compile: function(element) {
11617
      element.append(jqLite('<div class="popover-arrow">'));
11618
      element.addClass('popover');
11619
    }
11620
  };
11621
});
11622
 
11623
/**
11624
 * @ngdoc directive
11625
 * @name ionRadio
11626
 * @module ionic
11627
 * @restrict E
11628
 * @codepen saoBG
11629
 * @description
11630
 * The radio directive is no different than the HTML radio input, except it's styled differently.
11631
 *
11632
 * Radio behaves like any [AngularJS radio](http://docs.angularjs.org/api/ng/input/input[radio]).
11633
 *
11634
 * @usage
11635
 * ```html
11636
 * <ion-radio ng-model="choice" ng-value="'A'">Choose A</ion-radio>
11637
 * <ion-radio ng-model="choice" ng-value="'B'">Choose B</ion-radio>
11638
 * <ion-radio ng-model="choice" ng-value="'C'">Choose C</ion-radio>
11639
 * ```
11640
 *
11641
 * @param {string=} name The name of the radio input.
11642
 * @param {expression=} value The value of the radio input.
11643
 * @param {boolean=} disabled The state of the radio input.
11644
 * @param {string=} icon The icon to use when the radio input is selected.
11645
 * @param {expression=} ng-value Angular equivalent of the value attribute.
11646
 * @param {expression=} ng-model The angular model for the radio input.
11647
 * @param {boolean=} ng-disabled Angular equivalent of the disabled attribute.
11648
 * @param {expression=} ng-change Triggers given expression when radio input's model changes
11649
 */
11650
IonicModule
11651
.directive('ionRadio', function() {
11652
  return {
11653
    restrict: 'E',
11654
    replace: true,
11655
    require: '?ngModel',
11656
    transclude: true,
11657
    template:
11658
      '<label class="item item-radio">' +
11659
        '<input type="radio" name="radio-group">' +
11660
        '<div class="item-content disable-pointer-events" ng-transclude></div>' +
11661
        '<i class="radio-icon disable-pointer-events icon ion-checkmark"></i>' +
11662
      '</label>',
11663
 
11664
    compile: function(element, attr) {
11665
      if (attr.icon) {
11666
        element.children().eq(2).removeClass('ion-checkmark').addClass(attr.icon);
11667
      }
11668
 
11669
      var input = element.find('input');
11670
      forEach({
11671
          'name': attr.name,
11672
          'value': attr.value,
11673
          'disabled': attr.disabled,
11674
          'ng-value': attr.ngValue,
11675
          'ng-model': attr.ngModel,
11676
          'ng-disabled': attr.ngDisabled,
11677
          'ng-change': attr.ngChange,
11678
          'ng-required': attr.ngRequired,
11679
          'required': attr.required
11680
      }, function(value, name) {
11681
        if (isDefined(value)) {
11682
            input.attr(name, value);
11683
          }
11684
      });
11685
 
11686
      return function(scope, element, attr) {
11687
        scope.getValue = function() {
11688
          return scope.ngValue || attr.value;
11689
        };
11690
      };
11691
    }
11692
  };
11693
});
11694
 
11695
 
11696
/**
11697
 * @ngdoc directive
11698
 * @name ionRefresher
11699
 * @module ionic
11700
 * @restrict E
11701
 * @parent ionic.directive:ionContent, ionic.directive:ionScroll
11702
 * @description
11703
 * Allows you to add pull-to-refresh to a scrollView.
11704
 *
11705
 * Place it as the first child of your {@link ionic.directive:ionContent} or
11706
 * {@link ionic.directive:ionScroll} element.
11707
 *
11708
 * When refreshing is complete, $broadcast the 'scroll.refreshComplete' event
11709
 * from your controller.
11710
 *
11711
 * @usage
11712
 *
11713
 * ```html
11714
 * <ion-content ng-controller="MyController">
11715
 *   <ion-refresher
11716
 *     pulling-text="Pull to refresh..."
11717
 *     on-refresh="doRefresh()">
11718
 *   </ion-refresher>
11719
 *   <ion-list>
11720
 *     <ion-item ng-repeat="item in items"></ion-item>
11721
 *   </ion-list>
11722
 * </ion-content>
11723
 * ```
11724
 * ```js
11725
 * angular.module('testApp', ['ionic'])
11726
 * .controller('MyController', function($scope, $http) {
11727
 *   $scope.items = [1,2,3];
11728
 *   $scope.doRefresh = function() {
11729
 *     $http.get('/new-items')
11730
 *      .success(function(newItems) {
11731
 *        $scope.items = newItems;
11732
 *      })
11733
 *      .finally(function() {
11734
 *        // Stop the ion-refresher from spinning
11735
 *        $scope.$broadcast('scroll.refreshComplete');
11736
 *      });
11737
 *   };
11738
 * });
11739
 * ```
11740
 *
11741
 * @param {expression=} on-refresh Called when the user pulls down enough and lets go
11742
 * of the refresher.
11743
 * @param {expression=} on-pulling Called when the user starts to pull down
11744
 * on the refresher.
11745
 * @param {string=} pulling-text The text to display while the user is pulling down.
11746
 * @param {string=} pulling-icon The icon to display while the user is pulling down.
11747
 * Default: 'ion-android-arrow-down'.
11748
 * @param {string=} spinner The {@link ionic.directive:ionSpinner} icon to display
11749
 * after user lets go of the refresher. The SVG {@link ionic.directive:ionSpinner}
11750
 * is now the default, replacing rotating font icons. Set to `none` to disable both the
11751
 * spinner and the icon.
11752
 * @param {string=} refreshing-icon The font icon to display after user lets go of the
11753
 * refresher. This is depreicated in favor of the SVG {@link ionic.directive:ionSpinner}.
11754
 * @param {boolean=} disable-pulling-rotation Disables the rotation animation of the pulling
11755
 * icon when it reaches its activated threshold. To be used with a custom `pulling-icon`.
11756
 *
11757
 */
11758
IonicModule
11759
.directive('ionRefresher', [function() {
11760
  return {
11761
    restrict: 'E',
11762
    replace: true,
11763
    require: ['?^$ionicScroll', 'ionRefresher'],
11764
    controller: '$ionicRefresher',
11765
    template:
11766
    '<div class="scroll-refresher invisible" collection-repeat-ignore>' +
11767
      '<div class="ionic-refresher-content" ' +
11768
      'ng-class="{\'ionic-refresher-with-text\': pullingText || refreshingText}">' +
11769
        '<div class="icon-pulling" ng-class="{\'pulling-rotation-disabled\':disablePullingRotation}">' +
11770
          '<i class="icon {{pullingIcon}}"></i>' +
11771
        '</div>' +
11772
        '<div class="text-pulling" ng-bind-html="pullingText"></div>' +
11773
        '<div class="icon-refreshing">' +
11774
          '<ion-spinner ng-if="showSpinner" icon="{{spinner}}"></ion-spinner>' +
11775
          '<i ng-if="showIcon" class="icon {{refreshingIcon}}"></i>' +
11776
        '</div>' +
11777
        '<div class="text-refreshing" ng-bind-html="refreshingText"></div>' +
11778
      '</div>' +
11779
    '</div>',
11780
    link: function($scope, $element, $attrs, ctrls) {
11781
 
11782
      // JS Scrolling uses the scroll controller
11783
      var scrollCtrl = ctrls[0],
11784
          refresherCtrl = ctrls[1];
11785
      if (!scrollCtrl || scrollCtrl.isNative()) {
11786
        // Kick off native scrolling
11787
        refresherCtrl.init();
11788
      } else {
11789
        $element[0].classList.add('js-scrolling');
11790
        scrollCtrl._setRefresher(
11791
          $scope,
11792
          $element[0],
11793
          refresherCtrl.getRefresherDomMethods()
11794
        );
11795
 
11796
        $scope.$on('scroll.refreshComplete', function() {
11797
          $scope.$evalAsync(function() {
11798
            scrollCtrl.scrollView.finishPullToRefresh();
11799
          });
11800
        });
11801
      }
11802
 
11803
    }
11804
  };
11805
}]);
11806
 
11807
/**
11808
 * @ngdoc directive
11809
 * @name ionScroll
11810
 * @module ionic
11811
 * @delegate ionic.service:$ionicScrollDelegate
11812
 * @codepen mwFuh
11813
 * @restrict E
11814
 *
11815
 * @description
11816
 * Creates a scrollable container for all content inside.
11817
 *
11818
 * @usage
11819
 *
11820
 * Basic usage:
11821
 *
11822
 * ```html
11823
 * <ion-scroll zooming="true" direction="xy" style="width: 500px; height: 500px">
11824
 *   <div style="width: 5000px; height: 5000px; background: url('https://upload.wikimedia.org/wikipedia/commons/a/ad/Europe_geological_map-en.jpg') repeat"></div>
11825
 *  </ion-scroll>
11826
 * ```
11827
 *
11828
 * Note that it's important to set the height of the scroll box as well as the height of the inner
11829
 * content to enable scrolling. This makes it possible to have full control over scrollable areas.
11830
 *
11831
 * If you'd just like to have a center content scrolling area, use {@link ionic.directive:ionContent} instead.
11832
 *
11833
 * @param {string=} delegate-handle The handle used to identify this scrollView
11834
 * with {@link ionic.service:$ionicScrollDelegate}.
11835
 * @param {string=} direction Which way to scroll. 'x' or 'y' or 'xy'. Default 'y'.
11836
 * @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.
11837
 * @param {boolean=} paging Whether to scroll with paging.
11838
 * @param {expression=} on-refresh Called on pull-to-refresh, triggered by an {@link ionic.directive:ionRefresher}.
11839
 * @param {expression=} on-scroll Called whenever the user scrolls.
11840
 * @param {boolean=} scrollbar-x Whether to show the horizontal scrollbar. Default true.
11841
 * @param {boolean=} scrollbar-y Whether to show the vertical scrollbar. Default true.
11842
 * @param {boolean=} zooming Whether to support pinch-to-zoom
11843
 * @param {integer=} min-zoom The smallest zoom amount allowed (default is 0.5)
11844
 * @param {integer=} max-zoom The largest zoom amount allowed (default is 3)
11845
 * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges
11846
 * of the content.  Defaults to true on iOS, false on Android.
11847
 */
11848
IonicModule
11849
.directive('ionScroll', [
11850
  '$timeout',
11851
  '$controller',
11852
  '$ionicBind',
11853
function($timeout, $controller, $ionicBind) {
11854
  return {
11855
    restrict: 'E',
11856
    scope: true,
11857
    controller: function() {},
11858
    compile: function(element) {
11859
      element.addClass('scroll-view ionic-scroll');
11860
 
11861
      //We cannot transclude here because it breaks element.data() inheritance on compile
11862
      var innerElement = jqLite('<div class="scroll"></div>');
11863
      innerElement.append(element.contents());
11864
      element.append(innerElement);
11865
 
11866
      return { pre: prelink };
11867
      function prelink($scope, $element, $attr) {
11868
        $ionicBind($scope, $attr, {
11869
          direction: '@',
11870
          paging: '@',
11871
          $onScroll: '&onScroll',
11872
          scroll: '@',
11873
          scrollbarX: '@',
11874
          scrollbarY: '@',
11875
          zooming: '@',
11876
          minZoom: '@',
11877
          maxZoom: '@'
11878
        });
11879
        $scope.direction = $scope.direction || 'y';
11880
 
11881
        if (isDefined($attr.padding)) {
11882
          $scope.$watch($attr.padding, function(newVal) {
11883
            innerElement.toggleClass('padding', !!newVal);
11884
          });
11885
        }
11886
        if ($scope.$eval($scope.paging) === true) {
11887
          innerElement.addClass('scroll-paging');
11888
        }
11889
 
11890
        if (!$scope.direction) { $scope.direction = 'y'; }
11891
        var isPaging = $scope.$eval($scope.paging) === true;
11892
 
11893
        var scrollViewOptions = {
11894
          el: $element[0],
11895
          delegateHandle: $attr.delegateHandle,
11896
          locking: ($attr.locking || 'true') === 'true',
11897
          bouncing: $scope.$eval($attr.hasBouncing),
11898
          paging: isPaging,
11899
          scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
11900
          scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
11901
          scrollingX: $scope.direction.indexOf('x') >= 0,
11902
          scrollingY: $scope.direction.indexOf('y') >= 0,
11903
          zooming: $scope.$eval($scope.zooming) === true,
11904
          maxZoom: $scope.$eval($scope.maxZoom) || 3,
11905
          minZoom: $scope.$eval($scope.minZoom) || 0.5,
11906
          preventDefault: true
11907
        };
11908
        if (isPaging) {
11909
          scrollViewOptions.speedMultiplier = 0.8;
11910
          scrollViewOptions.bouncing = false;
11911
        }
11912
 
11913
        $controller('$ionicScroll', {
11914
          $scope: $scope,
11915
          scrollViewOptions: scrollViewOptions
11916
        });
11917
      }
11918
    }
11919
  };
11920
}]);
11921
 
11922
/**
11923
 * @ngdoc directive
11924
 * @name ionSideMenu
11925
 * @module ionic
11926
 * @restrict E
11927
 * @parent ionic.directive:ionSideMenus
11928
 *
11929
 * @description
11930
 * A container for a side menu, sibling to an {@link ionic.directive:ionSideMenuContent} directive.
11931
 *
11932
 * @usage
11933
 * ```html
11934
 * <ion-side-menu
11935
 *   side="left"
11936
 *   width="myWidthValue + 20"
11937
 *   is-enabled="shouldLeftSideMenuBeEnabled()">
11938
 * </ion-side-menu>
11939
 * ```
11940
 * For a complete side menu example, see the
11941
 * {@link ionic.directive:ionSideMenus} documentation.
11942
 *
11943
 * @param {string} side Which side the side menu is currently on.  Allowed values: 'left' or 'right'.
11944
 * @param {boolean=} is-enabled Whether this side menu is enabled.
11945
 * @param {number=} width How many pixels wide the side menu should be.  Defaults to 275.
11946
 */
11947
IonicModule
11948
.directive('ionSideMenu', function() {
11949
  return {
11950
    restrict: 'E',
11951
    require: '^ionSideMenus',
11952
    scope: true,
11953
    compile: function(element, attr) {
11954
      angular.isUndefined(attr.isEnabled) && attr.$set('isEnabled', 'true');
11955
      angular.isUndefined(attr.width) && attr.$set('width', '275');
11956
 
11957
      element.addClass('menu menu-' + attr.side);
11958
 
11959
      return function($scope, $element, $attr, sideMenuCtrl) {
11960
        $scope.side = $attr.side || 'left';
11961
 
11962
        var sideMenu = sideMenuCtrl[$scope.side] = new ionic.views.SideMenu({
11963
          width: attr.width,
11964
          el: $element[0],
11965
          isEnabled: true
11966
        });
11967
 
11968
        $scope.$watch($attr.width, function(val) {
11969
          var numberVal = +val;
11970
          if (numberVal && numberVal == val) {
11971
            sideMenu.setWidth(+val);
11972
          }
11973
        });
11974
        $scope.$watch($attr.isEnabled, function(val) {
11975
          sideMenu.setIsEnabled(!!val);
11976
        });
11977
      };
11978
    }
11979
  };
11980
});
11981
 
11982
 
11983
/**
11984
 * @ngdoc directive
11985
 * @name ionSideMenuContent
11986
 * @module ionic
11987
 * @restrict E
11988
 * @parent ionic.directive:ionSideMenus
11989
 *
11990
 * @description
11991
 * A container for the main visible content, sibling to one or more
11992
 * {@link ionic.directive:ionSideMenu} directives.
11993
 *
11994
 * @usage
11995
 * ```html
11996
 * <ion-side-menu-content
11997
 *   edge-drag-threshold="true"
11998
 *   drag-content="true">
11999
 * </ion-side-menu-content>
12000
 * ```
12001
 * For a complete side menu example, see the
12002
 * {@link ionic.directive:ionSideMenus} documentation.
12003
 *
12004
 * @param {boolean=} drag-content Whether the content can be dragged. Default true.
12005
 * @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:
12006
   *  - 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.
12007
   *  - If true is given, the default number of pixels (25) is used as the maximum allowed distance.
12008
   *  - If false or 0 is given, the edge drag threshold is disabled, and dragging from anywhere on the content is allowed.
12009
 *
12010
 */
12011
IonicModule
12012
.directive('ionSideMenuContent', [
12013
  '$timeout',
12014
  '$ionicGesture',
12015
  '$window',
12016
function($timeout, $ionicGesture, $window) {
12017
 
12018
  return {
12019
    restrict: 'EA', //DEPRECATED 'A'
12020
    require: '^ionSideMenus',
12021
    scope: true,
12022
    compile: function(element, attr) {
12023
      element.addClass('menu-content pane');
12024
 
12025
      return { pre: prelink };
12026
      function prelink($scope, $element, $attr, sideMenuCtrl) {
12027
        var startCoord = null;
12028
        var primaryScrollAxis = null;
12029
 
12030
        if (isDefined(attr.dragContent)) {
12031
          $scope.$watch(attr.dragContent, function(value) {
12032
            sideMenuCtrl.canDragContent(value);
12033
          });
12034
        } else {
12035
          sideMenuCtrl.canDragContent(true);
12036
        }
12037
 
12038
        if (isDefined(attr.edgeDragThreshold)) {
12039
          $scope.$watch(attr.edgeDragThreshold, function(value) {
12040
            sideMenuCtrl.edgeDragThreshold(value);
12041
          });
12042
        }
12043
 
12044
        // Listen for taps on the content to close the menu
12045
        function onContentTap(gestureEvt) {
12046
          if (sideMenuCtrl.getOpenAmount() !== 0) {
12047
            sideMenuCtrl.close();
12048
            gestureEvt.gesture.srcEvent.preventDefault();
12049
            startCoord = null;
12050
            primaryScrollAxis = null;
12051
          } else if (!startCoord) {
12052
            startCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
12053
          }
12054
        }
12055
 
12056
        function onDragX(e) {
12057
          if (!sideMenuCtrl.isDraggableTarget(e)) return;
12058
 
12059
          if (getPrimaryScrollAxis(e) == 'x') {
12060
            sideMenuCtrl._handleDrag(e);
12061
            e.gesture.srcEvent.preventDefault();
12062
          }
12063
        }
12064
 
12065
        function onDragY(e) {
12066
          if (getPrimaryScrollAxis(e) == 'x') {
12067
            e.gesture.srcEvent.preventDefault();
12068
          }
12069
        }
12070
 
12071
        function onDragRelease(e) {
12072
          sideMenuCtrl._endDrag(e);
12073
          startCoord = null;
12074
          primaryScrollAxis = null;
12075
        }
12076
 
12077
        function getPrimaryScrollAxis(gestureEvt) {
12078
          // gets whether the user is primarily scrolling on the X or Y
12079
          // If a majority of the drag has been on the Y since the start of
12080
          // the drag, but the X has moved a little bit, it's still a Y drag
12081
 
12082
          if (primaryScrollAxis) {
12083
            // we already figured out which way they're scrolling
12084
            return primaryScrollAxis;
12085
          }
12086
 
12087
          if (gestureEvt && gestureEvt.gesture) {
12088
 
12089
            if (!startCoord) {
12090
              // get the starting point
12091
              startCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
12092
 
12093
            } else {
12094
              // we already have a starting point, figure out which direction they're going
12095
              var endCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
12096
 
12097
              var xDistance = Math.abs(endCoord.x - startCoord.x);
12098
              var yDistance = Math.abs(endCoord.y - startCoord.y);
12099
 
12100
              var scrollAxis = (xDistance < yDistance ? 'y' : 'x');
12101
 
12102
              if (Math.max(xDistance, yDistance) > 30) {
12103
                // ok, we pretty much know which way they're going
12104
                // let's lock it in
12105
                primaryScrollAxis = scrollAxis;
12106
              }
12107
 
12108
              return scrollAxis;
12109
            }
12110
          }
12111
          return 'y';
12112
        }
12113
 
12114
        var content = {
12115
          element: element[0],
12116
          onDrag: function() {},
12117
          endDrag: function() {},
12118
          getTranslateX: function() {
12119
            return $scope.sideMenuContentTranslateX || 0;
12120
          },
12121
          setTranslateX: ionic.animationFrameThrottle(function(amount) {
12122
            var xTransform = content.offsetX + amount;
12123
            $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + xTransform + 'px,0,0)';
12124
            $timeout(function() {
12125
              $scope.sideMenuContentTranslateX = amount;
12126
            });
12127
          }),
12128
          setMarginLeft: ionic.animationFrameThrottle(function(amount) {
12129
            if (amount) {
12130
              amount = parseInt(amount, 10);
12131
              $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amount + 'px,0,0)';
12132
              $element[0].style.width = ($window.innerWidth - amount) + 'px';
12133
              content.offsetX = amount;
12134
            } else {
12135
              $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
12136
              $element[0].style.width = '';
12137
              content.offsetX = 0;
12138
            }
12139
          }),
12140
          setMarginRight: ionic.animationFrameThrottle(function(amount) {
12141
            if (amount) {
12142
              amount = parseInt(amount, 10);
12143
              $element[0].style.width = ($window.innerWidth - amount) + 'px';
12144
              content.offsetX = amount;
12145
            } else {
12146
              $element[0].style.width = '';
12147
              content.offsetX = 0;
12148
            }
12149
            // reset incase left gets grabby
12150
            $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
12151
          }),
12152
          enableAnimation: function() {
12153
            $scope.animationEnabled = true;
12154
            $element[0].classList.add('menu-animated');
12155
          },
12156
          disableAnimation: function() {
12157
            $scope.animationEnabled = false;
12158
            $element[0].classList.remove('menu-animated');
12159
          },
12160
          offsetX: 0
12161
        };
12162
 
12163
        sideMenuCtrl.setContent(content);
12164
 
12165
        // add gesture handlers
12166
        var gestureOpts = { stop_browser_behavior: false };
12167
        if (ionic.DomUtil.getParentOrSelfWithClass($element[0], 'overflow-scroll')) {
12168
          gestureOpts.prevent_default_directions = ['left', 'right'];
12169
        }
12170
        var contentTapGesture = $ionicGesture.on('tap', onContentTap, $element, gestureOpts);
12171
        var dragRightGesture = $ionicGesture.on('dragright', onDragX, $element, gestureOpts);
12172
        var dragLeftGesture = $ionicGesture.on('dragleft', onDragX, $element, gestureOpts);
12173
        var dragUpGesture = $ionicGesture.on('dragup', onDragY, $element, gestureOpts);
12174
        var dragDownGesture = $ionicGesture.on('dragdown', onDragY, $element, gestureOpts);
12175
        var releaseGesture = $ionicGesture.on('release', onDragRelease, $element, gestureOpts);
12176
 
12177
        // Cleanup
12178
        $scope.$on('$destroy', function() {
12179
          if (content) {
12180
            content.element = null;
12181
            content = null;
12182
          }
12183
          $ionicGesture.off(dragLeftGesture, 'dragleft', onDragX);
12184
          $ionicGesture.off(dragRightGesture, 'dragright', onDragX);
12185
          $ionicGesture.off(dragUpGesture, 'dragup', onDragY);
12186
          $ionicGesture.off(dragDownGesture, 'dragdown', onDragY);
12187
          $ionicGesture.off(releaseGesture, 'release', onDragRelease);
12188
          $ionicGesture.off(contentTapGesture, 'tap', onContentTap);
12189
        });
12190
      }
12191
    }
12192
  };
12193
}]);
12194
 
12195
IonicModule
12196
 
12197
/**
12198
 * @ngdoc directive
12199
 * @name ionSideMenus
12200
 * @module ionic
12201
 * @delegate ionic.service:$ionicSideMenuDelegate
12202
 * @restrict E
12203
 *
12204
 * @description
12205
 * A container element for side menu(s) and the main content. Allows the left and/or right side menu
12206
 * to be toggled by dragging the main content area side to side.
12207
 *
12208
 * To automatically close an opened menu, you can add the {@link ionic.directive:menuClose} attribute
12209
 * directive. The `menu-close` attribute is usually added to links and buttons within
12210
 * `ion-side-menu-content`, so that when the element is clicked, the opened side menu will
12211
 * automatically close.
12212
 *
12213
 * "Burger Icon" toggles can be added to the header with the {@link ionic.directive:menuToggle}
12214
 * attribute directive. Clicking the toggle will open and close the side menu like the `menu-close`
12215
 * directive. The side menu will automatically hide on child pages, but can be overridden with the
12216
 * enable-menu-with-back-views attribute mentioned below.
12217
 *
12218
 * By default, side menus are hidden underneath their side menu content and can be opened by swiping
12219
 * the content left or right or by toggling a button to show the side menu. Additionally, by adding the
12220
 * {@link ionic.directive:exposeAsideWhen} attribute directive to an
12221
 * {@link ionic.directive:ionSideMenu} element directive, a side menu can be given instructions about
12222
 * "when" the menu should be exposed (always viewable).
12223
 *
12224
 * ![Side Menu](http://ionicframework.com.s3.amazonaws.com/docs/controllers/sidemenu.gif)
12225
 *
12226
 * For more information on side menus, check out:
12227
 *
12228
 * - {@link ionic.directive:ionSideMenuContent}
12229
 * - {@link ionic.directive:ionSideMenu}
12230
 * - {@link ionic.directive:menuToggle}
12231
 * - {@link ionic.directive:menuClose}
12232
 * - {@link ionic.directive:exposeAsideWhen}
12233
 *
12234
 * @usage
12235
 * To use side menus, add an `<ion-side-menus>` parent element. This will encompass all pages that have a
12236
 * side menu, and have at least 2 child elements: 1 `<ion-side-menu-content>` for the center content,
12237
 * and one or more `<ion-side-menu>` directives for each side menu(left/right) that you wish to place.
12238
 *
12239
 * ```html
12240
 * <ion-side-menus>
12241
 *   <!-- Center content -->
12242
 *   <ion-side-menu-content ng-controller="ContentController">
12243
 *   </ion-side-menu-content>
12244
 *
12245
 *   <!-- Left menu -->
12246
 *   <ion-side-menu side="left">
12247
 *   </ion-side-menu>
12248
 *
12249
 *   <!-- Right menu -->
12250
 *   <ion-side-menu side="right">
12251
 *   </ion-side-menu>
12252
 *
12253
 *   <ion-side-menu-content>
12254
 *   <!-- Main content, usually <ion-nav-view> -->
12255
 *   </ion-side-menu-content>
12256
 * </ion-side-menus>
12257
 * ```
12258
 * ```js
12259
 * function ContentController($scope, $ionicSideMenuDelegate) {
12260
 *   $scope.toggleLeft = function() {
12261
 *     $ionicSideMenuDelegate.toggleLeft();
12262
 *   };
12263
 * }
12264
 * ```
12265
 *
12266
 * @param {bool=} enable-menu-with-back-views Determines whether the side menu is enabled when the
12267
 * back button is showing. When set to `false`, any {@link ionic.directive:menuToggle} will be hidden,
12268
 * and the user cannot swipe to open the menu. When going back to the root page of the side menu (the
12269
 * page without a back button visible), then any menuToggle buttons will show again, and menus will be
12270
 * enabled again.
12271
 * @param {string=} delegate-handle The handle used to identify this side menu
12272
 * with {@link ionic.service:$ionicSideMenuDelegate}.
12273
 *
12274
 */
12275
.directive('ionSideMenus', ['$ionicBody', function($ionicBody) {
12276
  return {
12277
    restrict: 'ECA',
12278
    controller: '$ionicSideMenus',
12279
    compile: function(element, attr) {
12280
      attr.$set('class', (attr['class'] || '') + ' view');
12281
 
12282
      return { pre: prelink };
12283
      function prelink($scope, $element, $attrs, ctrl) {
12284
 
12285
        ctrl.enableMenuWithBackViews($scope.$eval($attrs.enableMenuWithBackViews));
12286
 
12287
        $scope.$on('$ionicExposeAside', function(evt, isAsideExposed) {
12288
          if (!$scope.$exposeAside) $scope.$exposeAside = {};
12289
          $scope.$exposeAside.active = isAsideExposed;
12290
          $ionicBody.enableClass(isAsideExposed, 'aside-open');
12291
        });
12292
 
12293
        $scope.$on('$ionicView.beforeEnter', function(ev, d) {
12294
          if (d.historyId) {
12295
            $scope.$activeHistoryId = d.historyId;
12296
          }
12297
        });
12298
 
12299
        $scope.$on('$destroy', function() {
12300
          $ionicBody.removeClass('menu-open', 'aside-open');
12301
        });
12302
 
12303
      }
12304
    }
12305
  };
12306
}]);
12307
 
12308
 
12309
/**
12310
 * @ngdoc directive
12311
 * @name ionSlideBox
12312
 * @module ionic
12313
 * @delegate ionic.service:$ionicSlideBoxDelegate
12314
 * @restrict E
12315
 * @description
12316
 * The Slide Box is a multi-page container where each page can be swiped or dragged between:
12317
 *
12318
 * ![SlideBox](http://ionicframework.com.s3.amazonaws.com/docs/controllers/slideBox.gif)
12319
 *
12320
 * @usage
12321
 * ```html
12322
 * <ion-slide-box on-slide-changed="slideHasChanged($index)">
12323
 *   <ion-slide>
12324
 *     <div class="box blue"><h1>BLUE</h1></div>
12325
 *   </ion-slide>
12326
 *   <ion-slide>
12327
 *     <div class="box yellow"><h1>YELLOW</h1></div>
12328
 *   </ion-slide>
12329
 *   <ion-slide>
12330
 *     <div class="box pink"><h1>PINK</h1></div>
12331
 *   </ion-slide>
12332
 * </ion-slide-box>
12333
 * ```
12334
 *
12335
 * @param {string=} delegate-handle The handle used to identify this slideBox
12336
 * with {@link ionic.service:$ionicSlideBoxDelegate}.
12337
 * @param {boolean=} does-continue Whether the slide box should loop.
12338
 * @param {boolean=} auto-play Whether the slide box should automatically slide. Default true if does-continue is true.
12339
 * @param {number=} slide-interval How many milliseconds to wait to change slides (if does-continue is true). Defaults to 4000.
12340
 * @param {boolean=} show-pager Whether a pager should be shown for this slide box. Accepts expressions via `show-pager="{{shouldShow()}}"`. Defaults to true.
12341
 * @param {expression=} pager-click Expression to call when a pager is clicked (if show-pager is true). Is passed the 'index' variable.
12342
 * @param {expression=} on-slide-changed Expression called whenever the slide is changed.  Is passed an '$index' variable.
12343
 * @param {expression=} active-slide Model to bind the current slide to.
12344
 */
12345
IonicModule
12346
.directive('ionSlideBox', [
12347
  '$timeout',
12348
  '$compile',
12349
  '$ionicSlideBoxDelegate',
12350
  '$ionicHistory',
12351
  '$ionicScrollDelegate',
12352
function($timeout, $compile, $ionicSlideBoxDelegate, $ionicHistory, $ionicScrollDelegate) {
12353
  return {
12354
    restrict: 'E',
12355
    replace: true,
12356
    transclude: true,
12357
    scope: {
12358
      autoPlay: '=',
12359
      doesContinue: '@',
12360
      slideInterval: '@',
12361
      showPager: '@',
12362
      pagerClick: '&',
12363
      disableScroll: '@',
12364
      onSlideChanged: '&',
12365
      activeSlide: '=?'
12366
    },
12367
    controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
12368
      var _this = this;
12369
 
12370
      var continuous = $scope.$eval($scope.doesContinue) === true;
12371
      var shouldAutoPlay = isDefined($attrs.autoPlay) ? !!$scope.autoPlay : false;
12372
      var slideInterval = shouldAutoPlay ? $scope.$eval($scope.slideInterval) || 4000 : 0;
12373
 
12374
      var slider = new ionic.views.Slider({
12375
        el: $element[0],
12376
        auto: slideInterval,
12377
        continuous: continuous,
12378
        startSlide: $scope.activeSlide,
12379
        slidesChanged: function() {
12380
          $scope.currentSlide = slider.currentIndex();
12381
 
12382
          // Try to trigger a digest
12383
          $timeout(function() {});
12384
        },
12385
        callback: function(slideIndex) {
12386
          $scope.currentSlide = slideIndex;
12387
          $scope.onSlideChanged({ index: $scope.currentSlide, $index: $scope.currentSlide});
12388
          $scope.$parent.$broadcast('slideBox.slideChanged', slideIndex);
12389
          $scope.activeSlide = slideIndex;
12390
          // Try to trigger a digest
12391
          $timeout(function() {});
12392
        },
12393
        onDrag: function() {
12394
          freezeAllScrolls(true);
12395
        },
12396
        onDragEnd: function() {
12397
          freezeAllScrolls(false);
12398
        }
12399
      });
12400
 
12401
      function freezeAllScrolls(shouldFreeze) {
12402
        if (shouldFreeze && !_this.isScrollFreeze) {
12403
          $ionicScrollDelegate.freezeAllScrolls(shouldFreeze);
12404
 
12405
        } else if (!shouldFreeze && _this.isScrollFreeze) {
12406
          $ionicScrollDelegate.freezeAllScrolls(false);
12407
        }
12408
        _this.isScrollFreeze = shouldFreeze;
12409
      }
12410
 
12411
      slider.enableSlide($scope.$eval($attrs.disableScroll) !== true);
12412
 
12413
      $scope.$watch('activeSlide', function(nv) {
12414
        if (isDefined(nv)) {
12415
          slider.slide(nv);
12416
        }
12417
      });
12418
 
12419
      $scope.$on('slideBox.nextSlide', function() {
12420
        slider.next();
12421
      });
12422
 
12423
      $scope.$on('slideBox.prevSlide', function() {
12424
        slider.prev();
12425
      });
12426
 
12427
      $scope.$on('slideBox.setSlide', function(e, index) {
12428
        slider.slide(index);
12429
      });
12430
 
12431
      //Exposed for testing
12432
      this.__slider = slider;
12433
 
12434
      var deregisterInstance = $ionicSlideBoxDelegate._registerInstance(
12435
        slider, $attrs.delegateHandle, function() {
12436
          return $ionicHistory.isActiveScope($scope);
12437
        }
12438
      );
12439
      $scope.$on('$destroy', function() {
12440
        deregisterInstance();
12441
        slider.kill();
12442
      });
12443
 
12444
      this.slidesCount = function() {
12445
        return slider.slidesCount();
12446
      };
12447
 
12448
      this.onPagerClick = function(index) {
12449
        void 0;
12450
        $scope.pagerClick({index: index});
12451
      };
12452
 
12453
      $timeout(function() {
12454
        slider.load();
12455
      });
12456
    }],
12457
    template: '<div class="slider">' +
12458
      '<div class="slider-slides" ng-transclude>' +
12459
      '</div>' +
12460
    '</div>',
12461
 
12462
    link: function($scope, $element, $attr) {
12463
      // if showPager is undefined, show the pager
12464
      if (!isDefined($attr.showPager)) {
12465
        $scope.showPager = true;
12466
        getPager().toggleClass('hide', !true);
12467
      }
12468
 
12469
      $attr.$observe('showPager', function(show) {
12470
        show = $scope.$eval(show);
12471
        getPager().toggleClass('hide', !show);
12472
      });
12473
 
12474
      var pager;
12475
      function getPager() {
12476
        if (!pager) {
12477
          var childScope = $scope.$new();
12478
          pager = jqLite('<ion-pager></ion-pager>');
12479
          $element.append(pager);
12480
          pager = $compile(pager)(childScope);
12481
        }
12482
        return pager;
12483
      }
12484
    }
12485
  };
12486
}])
12487
.directive('ionSlide', function() {
12488
  return {
12489
    restrict: 'E',
12490
    require: '^ionSlideBox',
12491
    compile: function(element) {
12492
      element.addClass('slider-slide');
12493
    }
12494
  };
12495
})
12496
 
12497
.directive('ionPager', function() {
12498
  return {
12499
    restrict: 'E',
12500
    replace: true,
12501
    require: '^ionSlideBox',
12502
    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>',
12503
    link: function($scope, $element, $attr, slideBox) {
12504
      var selectPage = function(index) {
12505
        var children = $element[0].children;
12506
        var length = children.length;
12507
        for (var i = 0; i < length; i++) {
12508
          if (i == index) {
12509
            children[i].classList.add('active');
12510
          } else {
12511
            children[i].classList.remove('active');
12512
          }
12513
        }
12514
      };
12515
 
12516
      $scope.pagerClick = function(index) {
12517
        slideBox.onPagerClick(index);
12518
      };
12519
 
12520
      $scope.numSlides = function() {
12521
        return new Array(slideBox.slidesCount());
12522
      };
12523
 
12524
      $scope.$watch('currentSlide', function(v) {
12525
        selectPage(v);
12526
      });
12527
    }
12528
  };
12529
 
12530
});
12531
 
12532
/**
12533
* @ngdoc directive
12534
* @name ionSpinner
12535
* @module ionic
12536
* @restrict E
12537
 *
12538
 * @description
12539
 * The `ionSpinner` directive provides a variety of animated spinners.
12540
 * Spinners enables you to give your users feedback that the app is
12541
 * processing/thinking/waiting/chillin' out, or whatever you'd like it to indicate.
12542
 * By default, the {@link ionic.directive:ionRefresher} feature uses this spinner, rather
12543
 * than rotating font icons (previously included in [ionicons](http://ionicons.com/)).
12544
 * While font icons are great for simple or stationary graphics, they're not suited to
12545
 * provide great animations, which is why Ionic uses SVG instead.
12546
 *
12547
 * Ionic offers ten spinners out of the box, and by default, it will use the appropriate spinner
12548
 * for the platform on which it's running. Under the hood, the `ionSpinner` directive dynamically
12549
 * builds the required SVG element, which allows Ionic to provide all ten of the animated SVGs
12550
 * within 3KB.
12551
 *
12552
 * <style>
12553
 * .spinner-table {
12554
 *   max-width: 280px;
12555
 * }
12556
 * .spinner-table tbody > tr > th, .spinner-table tbody > tr > td {
12557
 *   vertical-align: middle;
12558
 *   width: 42px;
12559
 *   height: 42px;
12560
 * }
12561
 * .spinner {
12562
 *   stroke: #444;
12563
 *   fill: #444; }
12564
 *   .spinner svg {
12565
 *     width: 28px;
12566
 *     height: 28px; }
12567
 *   .spinner.spinner-inverse {
12568
 *     stroke: #fff;
12569
 *     fill: #fff; }
12570
 *
12571
 * .spinner-android {
12572
 *   stroke: #4b8bf4; }
12573
 *
12574
 * .spinner-ios, .spinner-ios-small {
12575
 *   stroke: #69717d; }
12576
 *
12577
 * .spinner-spiral .stop1 {
12578
 *   stop-color: #fff;
12579
 *   stop-opacity: 0; }
12580
 * .spinner-spiral.spinner-inverse .stop1 {
12581
 *   stop-color: #000; }
12582
 * .spinner-spiral.spinner-inverse .stop2 {
12583
 *   stop-color: #fff; }
12584
 * </style>
12585
 *
12586
 * <script src="http://code.ionicframework.com/nightly/js/ionic.bundle.min.js"></script>
12587
 * <table class="table spinner-table" ng-app="ionic">
12588
 *  <tr>
12589
 *    <th>
12590
 *      <code>android</code>
12591
 *    </th>
12592
 *    <td>
12593
 *      <ion-spinner icon="android"></ion-spinner>
12594
 *    </td>
12595
 *  </tr>
12596
 *  <tr>
12597
 *    <th>
12598
 *      <code>ios</code>
12599
 *    </th>
12600
 *    <td>
12601
 *      <ion-spinner icon="ios"></ion-spinner>
12602
 *    </td>
12603
 *  </tr>
12604
 *  <tr>
12605
 *    <th>
12606
 *      <code>ios-small</code>
12607
 *    </th>
12608
 *    <td>
12609
 *      <ion-spinner icon="ios-small"></ion-spinner>
12610
 *    </td>
12611
 *  </tr>
12612
 *  <tr>
12613
 *    <th>
12614
 *      <code>bubbles</code>
12615
 *    </th>
12616
 *    <td>
12617
 *      <ion-spinner icon="bubbles"></ion-spinner>
12618
 *    </td>
12619
 *  </tr>
12620
 *  <tr>
12621
 *    <th>
12622
 *      <code>circles</code>
12623
 *    </th>
12624
 *    <td>
12625
 *      <ion-spinner icon="circles"></ion-spinner>
12626
 *    </td>
12627
 *  </tr>
12628
 *  <tr>
12629
 *    <th>
12630
 *      <code>crescent</code>
12631
 *    </th>
12632
 *    <td>
12633
 *      <ion-spinner icon="crescent"></ion-spinner>
12634
 *    </td>
12635
 *  </tr>
12636
 *  <tr>
12637
 *    <th>
12638
 *      <code>dots</code>
12639
 *    </th>
12640
 *    <td>
12641
 *      <ion-spinner icon="dots"></ion-spinner>
12642
 *    </td>
12643
 *  </tr>
12644
 *  <tr>
12645
 *    <th>
12646
 *      <code>lines</code>
12647
 *    </th>
12648
 *    <td>
12649
 *      <ion-spinner icon="lines"></ion-spinner>
12650
 *    </td>
12651
 *  </tr>
12652
 *  <tr>
12653
 *    <th>
12654
 *      <code>ripple</code>
12655
 *    </th>
12656
 *    <td>
12657
 *      <ion-spinner icon="ripple"></ion-spinner>
12658
 *    </td>
12659
 *  </tr>
12660
 *  <tr>
12661
 *    <th>
12662
 *      <code>spiral</code>
12663
 *    </th>
12664
 *    <td>
12665
 *      <ion-spinner icon="spiral"></ion-spinner>
12666
 *    </td>
12667
 *  </tr>
12668
 * </table>
12669
 *
12670
 * Each spinner uses SVG with SMIL animations, however, the Android spinner also uses JavaScript
12671
 * so it also works on Android 4.0-4.3. Additionally, each spinner can be styled with CSS,
12672
 * and scaled to any size.
12673
 *
12674
 *
12675
 * @usage
12676
 * The following code would use the default spinner for the platform it's running from. If it's neither
12677
 * iOS or Android, it'll default to use `ios`.
12678
 *
12679
 * ```html
12680
 * <ion-spinner></ion-spinner>
12681
 * ```
12682
 *
12683
 * By setting the `icon` attribute, you can specify which spinner to use, no matter what
12684
 * the platform is.
12685
 *
12686
 * ```html
12687
 * <ion-spinner icon="spiral"></ion-spinner>
12688
 * ```
12689
 *
12690
 * ## Spinner Colors
12691
 * Like with most of Ionic's other components, spinners can also be styled using
12692
 * Ionic's standard color naming convention. For example:
12693
 *
12694
 * ```html
12695
 * <ion-spinner class="spinner-energized"></ion-spinner>
12696
 * ```
12697
 *
12698
 *
12699
 * ## Styling SVG with CSS
12700
 * One cool thing about SVG is its ability to be styled with CSS! Some of the properties
12701
 * have different names, for example, SVG uses the term `stroke` instead of `border`, and
12702
 * `fill` instead of `background-color`.
12703
 *
12704
 * ```css
12705
 * .spinner svg {
12706
 *   width: 28px;
12707
 *   height: 28px;
12708
 *   stroke: #444;
12709
 *   fill: #444;
12710
 * }
12711
 * ```
12712
 *
12713
*/
12714
IonicModule
12715
.directive('ionSpinner', function() {
12716
  return {
12717
    restrict: 'E',
12718
    controller: '$ionicSpinner',
12719
    link: function($scope, $element, $attrs, ctrl) {
12720
      var spinnerName = ctrl.init();
12721
      $element.addClass('spinner spinner-' + spinnerName);
12722
    }
12723
  };
12724
});
12725
 
12726
/**
12727
 * @ngdoc directive
12728
 * @name ionTab
12729
 * @module ionic
12730
 * @restrict E
12731
 * @parent ionic.directive:ionTabs
12732
 *
12733
 * @description
12734
 * Contains a tab's content.  The content only exists while the given tab is selected.
12735
 *
12736
 * Each ionTab has its own view history.
12737
 *
12738
 * @usage
12739
 * ```html
12740
 * <ion-tab
12741
 *   title="Tab!"
12742
 *   icon="my-icon"
12743
 *   href="#/tab/tab-link"
12744
 *   on-select="onTabSelected()"
12745
 *   on-deselect="onTabDeselected()">
12746
 * </ion-tab>
12747
 * ```
12748
 * For a complete, working tab bar example, see the {@link ionic.directive:ionTabs} documentation.
12749
 *
12750
 * @param {string} title The title of the tab.
12751
 * @param {string=} href The link that this tab will navigate to when tapped.
12752
 * @param {string=} icon The icon of the tab. If given, this will become the default for icon-on and icon-off.
12753
 * @param {string=} icon-on The icon of the tab while it is selected.
12754
 * @param {string=} icon-off The icon of the tab while it is not selected.
12755
 * @param {expression=} badge The badge to put on this tab (usually a number).
12756
 * @param {expression=} badge-style The style of badge to put on this tab (eg: badge-positive).
12757
 * @param {expression=} on-select Called when this tab is selected.
12758
 * @param {expression=} on-deselect Called when this tab is deselected.
12759
 * @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()}.
12760
 * @param {expression=} hidden Whether the tab is to be hidden or not.
12761
 * @param {expression=} disabled Whether the tab is to be disabled or not.
12762
 */
12763
IonicModule
12764
.directive('ionTab', [
12765
  '$compile',
12766
  '$ionicConfig',
12767
  '$ionicBind',
12768
  '$ionicViewSwitcher',
12769
function($compile, $ionicConfig, $ionicBind, $ionicViewSwitcher) {
12770
 
12771
  //Returns ' key="value"' if value exists
12772
  function attrStr(k, v) {
12773
    return isDefined(v) ? ' ' + k + '="' + v + '"' : '';
12774
  }
12775
  return {
12776
    restrict: 'E',
12777
    require: ['^ionTabs', 'ionTab'],
12778
    controller: '$ionicTab',
12779
    scope: true,
12780
    compile: function(element, attr) {
12781
 
12782
      //We create the tabNavTemplate in the compile phase so that the
12783
      //attributes we pass down won't be interpolated yet - we want
12784
      //to pass down the 'raw' versions of the attributes
12785
      var tabNavTemplate = '<ion-tab-nav' +
12786
        attrStr('ng-click', attr.ngClick) +
12787
        attrStr('title', attr.title) +
12788
        attrStr('icon', attr.icon) +
12789
        attrStr('icon-on', attr.iconOn) +
12790
        attrStr('icon-off', attr.iconOff) +
12791
        attrStr('badge', attr.badge) +
12792
        attrStr('badge-style', attr.badgeStyle) +
12793
        attrStr('hidden', attr.hidden) +
12794
        attrStr('disabled', attr.disabled) +
12795
        attrStr('class', attr['class']) +
12796
        '></ion-tab-nav>';
12797
 
12798
      //Remove the contents of the element so we can compile them later, if tab is selected
12799
      var tabContentEle = document.createElement('div');
12800
      for (var x = 0; x < element[0].children.length; x++) {
12801
        tabContentEle.appendChild(element[0].children[x].cloneNode(true));
12802
      }
12803
      var childElementCount = tabContentEle.childElementCount;
12804
      element.empty();
12805
 
12806
      var navViewName, isNavView;
12807
      if (childElementCount) {
12808
        if (tabContentEle.children[0].tagName === 'ION-NAV-VIEW') {
12809
          // get the name if it's a nav-view
12810
          navViewName = tabContentEle.children[0].getAttribute('name');
12811
          tabContentEle.children[0].classList.add('view-container');
12812
          isNavView = true;
12813
        }
12814
        if (childElementCount === 1) {
12815
          // make the 1 child element the primary tab content container
12816
          tabContentEle = tabContentEle.children[0];
12817
        }
12818
        if (!isNavView) tabContentEle.classList.add('pane');
12819
        tabContentEle.classList.add('tab-content');
12820
      }
12821
 
12822
      return function link($scope, $element, $attr, ctrls) {
12823
        var childScope;
12824
        var childElement;
12825
        var tabsCtrl = ctrls[0];
12826
        var tabCtrl = ctrls[1];
12827
        var isTabContentAttached = false;
12828
        $scope.$tabSelected = false;
12829
 
12830
        $ionicBind($scope, $attr, {
12831
          onSelect: '&',
12832
          onDeselect: '&',
12833
          title: '@',
12834
          uiSref: '@',
12835
          href: '@'
12836
        });
12837
 
12838
        tabsCtrl.add($scope);
12839
        $scope.$on('$destroy', function() {
12840
          if (!$scope.$tabsDestroy) {
12841
            // if the containing ionTabs directive is being destroyed
12842
            // then don't bother going through the controllers remove
12843
            // method, since remove will reset the active tab as each tab
12844
            // is being destroyed, causing unnecessary view loads and transitions
12845
            tabsCtrl.remove($scope);
12846
          }
12847
          tabNavElement.isolateScope().$destroy();
12848
          tabNavElement.remove();
12849
          tabNavElement = tabContentEle = childElement = null;
12850
        });
12851
 
12852
        //Remove title attribute so browser-tooltip does not apear
12853
        $element[0].removeAttribute('title');
12854
 
12855
        if (navViewName) {
12856
          tabCtrl.navViewName = $scope.navViewName = navViewName;
12857
        }
12858
        $scope.$on('$stateChangeSuccess', selectIfMatchesState);
12859
        selectIfMatchesState();
12860
        function selectIfMatchesState() {
12861
          if (tabCtrl.tabMatchesState()) {
12862
            tabsCtrl.select($scope, false);
12863
          }
12864
        }
12865
 
12866
        var tabNavElement = jqLite(tabNavTemplate);
12867
        tabNavElement.data('$ionTabsController', tabsCtrl);
12868
        tabNavElement.data('$ionTabController', tabCtrl);
12869
        tabsCtrl.$tabsElement.append($compile(tabNavElement)($scope));
12870
 
12871
 
12872
        function tabSelected(isSelected) {
12873
          if (isSelected && childElementCount) {
12874
            // this tab is being selected
12875
 
12876
            // check if the tab is already in the DOM
12877
            // only do this if the tab has child elements
12878
            if (!isTabContentAttached) {
12879
              // tab should be selected and is NOT in the DOM
12880
              // create a new scope and append it
12881
              childScope = $scope.$new();
12882
              childElement = jqLite(tabContentEle);
12883
              $ionicViewSwitcher.viewEleIsActive(childElement, true);
12884
              tabsCtrl.$element.append(childElement);
12885
              $compile(childElement)(childScope);
12886
              isTabContentAttached = true;
12887
            }
12888
 
12889
            // remove the hide class so the tabs content shows up
12890
            $ionicViewSwitcher.viewEleIsActive(childElement, true);
12891
 
12892
          } else if (isTabContentAttached && childElement) {
12893
            // this tab should NOT be selected, and it is already in the DOM
12894
 
12895
            if ($ionicConfig.views.maxCache() > 0) {
12896
              // keep the tabs in the DOM, only css hide it
12897
              $ionicViewSwitcher.viewEleIsActive(childElement, false);
12898
 
12899
            } else {
12900
              // do not keep tabs in the DOM
12901
              destroyTab();
12902
            }
12903
 
12904
          }
12905
        }
12906
 
12907
        function destroyTab() {
12908
          childScope && childScope.$destroy();
12909
          isTabContentAttached && childElement && childElement.remove();
12910
          tabContentEle.innerHTML = '';
12911
          isTabContentAttached = childScope = childElement = null;
12912
        }
12913
 
12914
        $scope.$watch('$tabSelected', tabSelected);
12915
 
12916
        $scope.$on('$ionicView.afterEnter', function() {
12917
          $ionicViewSwitcher.viewEleIsActive(childElement, $scope.$tabSelected);
12918
        });
12919
 
12920
        $scope.$on('$ionicView.clearCache', function() {
12921
          if (!$scope.$tabSelected) {
12922
            destroyTab();
12923
          }
12924
        });
12925
 
12926
      };
12927
    }
12928
  };
12929
}]);
12930
 
12931
IonicModule
12932
.directive('ionTabNav', [function() {
12933
  return {
12934
    restrict: 'E',
12935
    replace: true,
12936
    require: ['^ionTabs', '^ionTab'],
12937
    template:
12938
    '<a ng-class="{\'tab-item-active\': isTabActive(), \'has-badge\':badge, \'tab-hidden\':isHidden()}" ' +
12939
      ' ng-disabled="disabled()" class="tab-item">' +
12940
      '<span class="badge {{badgeStyle}}" ng-if="badge">{{badge}}</span>' +
12941
      '<i class="icon {{getIconOn()}}" ng-if="getIconOn() && isTabActive()"></i>' +
12942
      '<i class="icon {{getIconOff()}}" ng-if="getIconOff() && !isTabActive()"></i>' +
12943
      '<span class="tab-title" ng-bind-html="title"></span>' +
12944
    '</a>',
12945
    scope: {
12946
      title: '@',
12947
      icon: '@',
12948
      iconOn: '@',
12949
      iconOff: '@',
12950
      badge: '=',
12951
      hidden: '@',
12952
      disabled: '&',
12953
      badgeStyle: '@',
12954
      'class': '@'
12955
    },
12956
    link: function($scope, $element, $attrs, ctrls) {
12957
      var tabsCtrl = ctrls[0],
12958
        tabCtrl = ctrls[1];
12959
 
12960
      //Remove title attribute so browser-tooltip does not apear
12961
      $element[0].removeAttribute('title');
12962
 
12963
      $scope.selectTab = function(e) {
12964
        e.preventDefault();
12965
        tabsCtrl.select(tabCtrl.$scope, true);
12966
      };
12967
      if (!$attrs.ngClick) {
12968
        $element.on('click', function(event) {
12969
          $scope.$apply(function() {
12970
            $scope.selectTab(event);
12971
          });
12972
        });
12973
      }
12974
 
12975
      $scope.isHidden = function() {
12976
        if ($attrs.hidden === 'true' || $attrs.hidden === true) return true;
12977
        return false;
12978
      };
12979
 
12980
      $scope.getIconOn = function() {
12981
        return $scope.iconOn || $scope.icon;
12982
      };
12983
      $scope.getIconOff = function() {
12984
        return $scope.iconOff || $scope.icon;
12985
      };
12986
 
12987
      $scope.isTabActive = function() {
12988
        return tabsCtrl.selectedTab() === tabCtrl.$scope;
12989
      };
12990
    }
12991
  };
12992
}]);
12993
 
12994
/**
12995
 * @ngdoc directive
12996
 * @name ionTabs
12997
 * @module ionic
12998
 * @delegate ionic.service:$ionicTabsDelegate
12999
 * @restrict E
13000
 * @codepen odqCz
13001
 *
13002
 * @description
13003
 * Powers a multi-tabbed interface with a Tab Bar and a set of "pages" that can be tabbed
13004
 * through.
13005
 *
13006
 * Assign any [tabs class](/docs/components#tabs) to the element to define
13007
 * its look and feel.
13008
 *
13009
 * For iOS, tabs will appear at the bottom of the screen. For Android, tabs will be at the top
13010
 * of the screen, below the nav-bar. This follows each OS's design specification, but can be
13011
 * configured with the {@link ionic.provider:$ionicConfigProvider}.
13012
 *
13013
 * See the {@link ionic.directive:ionTab} directive's documentation for more details on
13014
 * individual tabs.
13015
 *
13016
 * Note: do not place ion-tabs inside of an ion-content element; it has been known to cause a
13017
 * certain CSS bug.
13018
 *
13019
 * @usage
13020
 * ```html
13021
 * <ion-tabs class="tabs-positive tabs-icon-only">
13022
 *
13023
 *   <ion-tab title="Home" icon-on="ion-ios-filing" icon-off="ion-ios-filing-outline">
13024
 *     <!-- Tab 1 content -->
13025
 *   </ion-tab>
13026
 *
13027
 *   <ion-tab title="About" icon-on="ion-ios-clock" icon-off="ion-ios-clock-outline">
13028
 *     <!-- Tab 2 content -->
13029
 *   </ion-tab>
13030
 *
13031
 *   <ion-tab title="Settings" icon-on="ion-ios-gear" icon-off="ion-ios-gear-outline">
13032
 *     <!-- Tab 3 content -->
13033
 *   </ion-tab>
13034
 *
13035
 * </ion-tabs>
13036
 * ```
13037
 *
13038
 * @param {string=} delegate-handle The handle used to identify these tabs
13039
 * with {@link ionic.service:$ionicTabsDelegate}.
13040
 */
13041
 
13042
IonicModule
13043
.directive('ionTabs', [
13044
  '$ionicTabsDelegate',
13045
  '$ionicConfig',
13046
function($ionicTabsDelegate, $ionicConfig) {
13047
  return {
13048
    restrict: 'E',
13049
    scope: true,
13050
    controller: '$ionicTabs',
13051
    compile: function(tElement) {
13052
      //We cannot use regular transclude here because it breaks element.data()
13053
      //inheritance on compile
13054
      var innerElement = jqLite('<div class="tab-nav tabs">');
13055
      innerElement.append(tElement.contents());
13056
 
13057
      tElement.append(innerElement)
13058
              .addClass('tabs-' + $ionicConfig.tabs.position() + ' tabs-' + $ionicConfig.tabs.style());
13059
 
13060
      return { pre: prelink, post: postLink };
13061
      function prelink($scope, $element, $attr, tabsCtrl) {
13062
        var deregisterInstance = $ionicTabsDelegate._registerInstance(
13063
          tabsCtrl, $attr.delegateHandle, tabsCtrl.hasActiveScope
13064
        );
13065
 
13066
        tabsCtrl.$scope = $scope;
13067
        tabsCtrl.$element = $element;
13068
        tabsCtrl.$tabsElement = jqLite($element[0].querySelector('.tabs'));
13069
 
13070
        $scope.$watch(function() { return $element[0].className; }, function(value) {
13071
          var isTabsTop = value.indexOf('tabs-top') !== -1;
13072
          var isHidden = value.indexOf('tabs-item-hide') !== -1;
13073
          $scope.$hasTabs = !isTabsTop && !isHidden;
13074
          $scope.$hasTabsTop = isTabsTop && !isHidden;
13075
          $scope.$emit('$ionicTabs.top', $scope.$hasTabsTop);
13076
        });
13077
 
13078
        function emitLifecycleEvent(ev, data) {
13079
          ev.stopPropagation();
13080
          var previousSelectedTab = tabsCtrl.previousSelectedTab();
13081
          if (previousSelectedTab) {
13082
            previousSelectedTab.$broadcast(ev.name.replace('NavView', 'Tabs'), data);
13083
          }
13084
        }
13085
 
13086
        $scope.$on('$ionicNavView.beforeLeave', emitLifecycleEvent);
13087
        $scope.$on('$ionicNavView.afterLeave', emitLifecycleEvent);
13088
        $scope.$on('$ionicNavView.leave', emitLifecycleEvent);
13089
 
13090
        $scope.$on('$destroy', function() {
13091
          // variable to inform child tabs that they're all being blown away
13092
          // used so that while destorying an individual tab, each one
13093
          // doesn't select the next tab as the active one, which causes unnecessary
13094
          // loading of tab views when each will eventually all go away anyway
13095
          $scope.$tabsDestroy = true;
13096
          deregisterInstance();
13097
          tabsCtrl.$tabsElement = tabsCtrl.$element = tabsCtrl.$scope = innerElement = null;
13098
          delete $scope.$hasTabs;
13099
          delete $scope.$hasTabsTop;
13100
        });
13101
      }
13102
 
13103
      function postLink($scope, $element, $attr, tabsCtrl) {
13104
        if (!tabsCtrl.selectedTab()) {
13105
          // all the tabs have been added
13106
          // but one hasn't been selected yet
13107
          tabsCtrl.select(0);
13108
        }
13109
      }
13110
    }
13111
  };
13112
}]);
13113
 
13114
/**
13115
 * @ngdoc directive
13116
 * @name ionToggle
13117
 * @module ionic
13118
 * @codepen tfAzj
13119
 * @restrict E
13120
 *
13121
 * @description
13122
 * A toggle is an animated switch which binds a given model to a boolean.
13123
 *
13124
 * Allows dragging of the switch's nub.
13125
 *
13126
 * The toggle behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]) otherwise.
13127
 *
13128
 * @param toggle-class {string=} Sets the CSS class on the inner `label.toggle` element created by the directive.
13129
 *
13130
 * @usage
13131
 * Below is an example of a toggle directive which is wired up to the `airplaneMode` model
13132
 * and has the `toggle-calm` CSS class assigned to the inner element.
13133
 *
13134
 * ```html
13135
 * <ion-toggle ng-model="airplaneMode" toggle-class="toggle-calm">Airplane Mode</ion-toggle>
13136
 * ```
13137
 */
13138
IonicModule
13139
.directive('ionToggle', [
13140
  '$timeout',
13141
  '$ionicConfig',
13142
function($timeout, $ionicConfig) {
13143
 
13144
  return {
13145
    restrict: 'E',
13146
    replace: true,
13147
    require: '?ngModel',
13148
    transclude: true,
13149
    template:
13150
      '<div class="item item-toggle">' +
13151
        '<div ng-transclude></div>' +
13152
        '<label class="toggle">' +
13153
          '<input type="checkbox">' +
13154
          '<div class="track">' +
13155
            '<div class="handle"></div>' +
13156
          '</div>' +
13157
        '</label>' +
13158
      '</div>',
13159
 
13160
    compile: function(element, attr) {
13161
      var input = element.find('input');
13162
      forEach({
13163
        'name': attr.name,
13164
        'ng-value': attr.ngValue,
13165
        'ng-model': attr.ngModel,
13166
        'ng-checked': attr.ngChecked,
13167
        'ng-disabled': attr.ngDisabled,
13168
        'ng-true-value': attr.ngTrueValue,
13169
        'ng-false-value': attr.ngFalseValue,
13170
        'ng-change': attr.ngChange,
13171
        'ng-required': attr.ngRequired,
13172
        'required': attr.required
13173
      }, function(value, name) {
13174
        if (isDefined(value)) {
13175
          input.attr(name, value);
13176
        }
13177
      });
13178
 
13179
      if (attr.toggleClass) {
13180
        element[0].getElementsByTagName('label')[0].classList.add(attr.toggleClass);
13181
      }
13182
 
13183
      element.addClass('toggle-' + $ionicConfig.form.toggle());
13184
 
13185
      return function($scope, $element) {
13186
        var el = $element[0].getElementsByTagName('label')[0];
13187
        var checkbox = el.children[0];
13188
        var track = el.children[1];
13189
        var handle = track.children[0];
13190
 
13191
        var ngModelController = jqLite(checkbox).controller('ngModel');
13192
 
13193
        $scope.toggle = new ionic.views.Toggle({
13194
          el: el,
13195
          track: track,
13196
          checkbox: checkbox,
13197
          handle: handle,
13198
          onChange: function() {
13199
            if (ngModelController) {
13200
              ngModelController.$setViewValue(checkbox.checked);
13201
              $scope.$apply();
13202
            }
13203
          }
13204
        });
13205
 
13206
        $scope.$on('$destroy', function() {
13207
          $scope.toggle.destroy();
13208
        });
13209
      };
13210
    }
13211
 
13212
  };
13213
}]);
13214
 
13215
/**
13216
 * @ngdoc directive
13217
 * @name ionView
13218
 * @module ionic
13219
 * @restrict E
13220
 * @parent ionNavView
13221
 *
13222
 * @description
13223
 * A container for view content and any navigational and header bar information. When a view
13224
 * enters and exits its parent {@link ionic.directive:ionNavView}, the view also emits view
13225
 * information, such as its title, whether the back button should be displayed or not, whether the
13226
 * corresponding {@link ionic.directive:ionNavBar} should be displayed or not, which transition the view
13227
 * should use to animate, and which direction to animate.
13228
 *
13229
 * *Views are cached to improve performance.* When a view is navigated away from, its element is
13230
 * left in the DOM, and its scope is disconnected from the `$watch` cycle. When navigating to a
13231
 * view that is already cached, its scope is reconnected, and the existing element, which was
13232
 * left in the DOM, becomes active again. This can be disabled, or the maximum number of cached
13233
 * views changed in {@link ionic.provider:$ionicConfigProvider}, in the view's `$state` configuration, or
13234
 * as an attribute on the view itself (see below).
13235
 *
13236
 * @usage
13237
 * Below is an example where our page will load with a {@link ionic.directive:ionNavBar} containing
13238
 * "My Page" as the title.
13239
 *
13240
 * ```html
13241
 * <ion-nav-bar></ion-nav-bar>
13242
 * <ion-nav-view>
13243
 *   <ion-view view-title="My Page">
13244
 *     <ion-content>
13245
 *       Hello!
13246
 *     </ion-content>
13247
 *   </ion-view>
13248
 * </ion-nav-view>
13249
 * ```
13250
 *
13251
 * ## View LifeCycle and Events
13252
 *
13253
 * Views can be cached, which means ***controllers normally only load once***, which may
13254
 * affect your controller logic. To know when a view has entered or left, events
13255
 * have been added that are emitted from the view's scope. These events also
13256
 * contain data about the view, such as the title and whether the back button should
13257
 * show. Also contained is transition data, such as the transition type and
13258
 * direction that will be or was used.
13259
 *
13260
 * <table class="table">
13261
 *  <tr>
13262
 *   <td><code>$ionicView.loaded</code></td>
13263
 *   <td>The view has loaded. This event only happens once per
13264
 * view being created and added to the DOM. If a view leaves but is cached,
13265
 * then this event will not fire again on a subsequent viewing. The loaded event
13266
 * is good place to put your setup code for the view; however, it is not the
13267
 * recommended event to listen to when a view becomes active.</td>
13268
 *  </tr>
13269
 *  <tr>
13270
 *   <td><code>$ionicView.enter</code></td>
13271
 *   <td>The view has fully entered and is now the active view.
13272
 * This event will fire, whether it was the first load or a cached view.</td>
13273
 *  </tr>
13274
 *  <tr>
13275
 *   <td><code>$ionicView.leave</code></td>
13276
 *   <td>The view has finished leaving and is no longer the
13277
 * active view. This event will fire, whether it is cached or destroyed.</td>
13278
 *  </tr>
13279
 *  <tr>
13280
 *   <td><code>$ionicView.beforeEnter</code></td>
13281
 *   <td>The view is about to enter and become the active view.</td>
13282
 *  </tr>
13283
 *  <tr>
13284
 *   <td><code>$ionicView.beforeLeave</code></td>
13285
 *   <td>The view is about to leave and no longer be the active view.</td>
13286
 *  </tr>
13287
 *  <tr>
13288
 *   <td><code>$ionicView.afterEnter</code></td>
13289
 *   <td>The view has fully entered and is now the active view.</td>
13290
 *  </tr>
13291
 *  <tr>
13292
 *   <td><code>$ionicView.afterLeave</code></td>
13293
 *   <td>The view has finished leaving and is no longer the active view.</td>
13294
 *  </tr>
13295
 *  <tr>
13296
 *   <td><code>$ionicView.unloaded</code></td>
13297
 *   <td>The view's controller has been destroyed and its element has been
13298
 * removed from the DOM.</td>
13299
 *  </tr>
13300
 * </table>
13301
 *
13302
 * ## Caching
13303
 *
13304
 * Caching can be disabled and enabled in multiple ways. By default, Ionic will
13305
 * cache a maximum of 10 views. You can optionally choose to disable caching at
13306
 * either an individual view basis, or by global configuration. Please see the
13307
 * _Caching_ section in {@link ionic.directive:ionNavView} for more info.
13308
 *
13309
 * @param {string=} view-title A text-only title to display on the parent {@link ionic.directive:ionNavBar}.
13310
 * For an HTML title, such as an image, see {@link ionic.directive:ionNavTitle} instead.
13311
 * @param {boolean=} cache-view If this view should be allowed to be cached or not.
13312
 * Please see the _Caching_ section in {@link ionic.directive:ionNavView} for
13313
 * more info. Default `true`
13314
 * @param {boolean=} can-swipe-back If this view should be allowed to use the swipe to go back gesture or not.
13315
 * This does not enable the swipe to go back feature if it is not available for the platform it's running
13316
 * from, or there isn't a previous view. Default `true`
13317
 * @param {boolean=} hide-back-button Whether to hide the back button on the parent
13318
 * {@link ionic.directive:ionNavBar} by default.
13319
 * @param {boolean=} hide-nav-bar Whether to hide the parent
13320
 * {@link ionic.directive:ionNavBar} by default.
13321
 */
13322
IonicModule
13323
.directive('ionView', function() {
13324
  return {
13325
    restrict: 'EA',
13326
    priority: 1000,
13327
    controller: '$ionicView',
13328
    compile: function(tElement) {
13329
      tElement.addClass('pane');
13330
      tElement[0].removeAttribute('title');
13331
      return function link($scope, $element, $attrs, viewCtrl) {
13332
        viewCtrl.init();
13333
      };
13334
    }
13335
  };
13336
});
13337
 
13338
})();