Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
13542 anikendra 1
/*!
2
 * Copyright 2014 Drifty Co.
3
 * http://drifty.com/
4
 *
5
 * Ionic, v1.0.0-beta.14
6
 * A powerful HTML5 mobile app framework.
7
 * http://ionicframework.com/
8
 *
9
 * By @maxlynch, @benjsperry, @adamdbradley <3
10
 *
11
 * Licensed under the MIT license. Please see LICENSE for more information.
12
 *
13
 */
14
 
15
(function() {
16
/*
17
 * deprecated.js
18
 * https://github.com/wearefractal/deprecated/
19
 * Copyright (c) 2014 Fractal <contact@wearefractal.com>
20
 * License MIT
21
 */
22
//Interval object
23
var deprecated = {
24
  method: function(msg, log, fn) {
25
    var called = false;
26
    return function deprecatedMethod() {
27
      if (!called) {
28
        called = true;
29
        log(msg);
30
      }
31
      return fn.apply(this, arguments);
32
    };
33
  },
34
 
35
  field: function(msg, log, parent, field, val) {
36
    var called = false;
37
    var getter = function() {
38
      if (!called) {
39
        called = true;
40
        log(msg);
41
      }
42
      return val;
43
    };
44
    var setter = function(v) {
45
      if (!called) {
46
        called = true;
47
        log(msg);
48
      }
49
      val = v;
50
      return v;
51
    };
52
    Object.defineProperty(parent, field, {
53
      get: getter,
54
      set: setter,
55
      enumerable: true
56
    });
57
    return;
58
  }
59
};
60
 
61
var IonicModule = angular.module('ionic', ['ngAnimate', 'ngSanitize', 'ui.router']),
62
  extend = angular.extend,
63
  forEach = angular.forEach,
64
  isDefined = angular.isDefined,
65
  isNumber = angular.isNumber,
66
  isString = angular.isString,
67
  jqLite = angular.element;
68
 
69
 
70
/**
71
 * @ngdoc service
72
 * @name $ionicActionSheet
73
 * @module ionic
74
 * @description
75
 * The Action Sheet is a slide-up pane that lets the user choose from a set of options.
76
 * Dangerous options are highlighted in red and made obvious.
77
 *
78
 * There are easy ways to cancel out of the action sheet, such as tapping the backdrop or even
79
 * hitting escape on the keyboard for desktop testing.
80
 *
81
 * ![Action Sheet](http://ionicframework.com.s3.amazonaws.com/docs/controllers/actionSheet.gif)
82
 *
83
 * @usage
84
 * To trigger an Action Sheet in your code, use the $ionicActionSheet service in your angular controllers:
85
 *
86
 * ```js
87
 * angular.module('mySuperApp', ['ionic'])
88
 * .controller(function($scope, $ionicActionSheet, $timeout) {
89
 *
90
 *  // Triggered on a button click, or some other target
91
 *  $scope.show = function() {
92
 *
93
 *    // Show the action sheet
94
 *    var hideSheet = $ionicActionSheet.show({
95
 *      buttons: [
96
 *        { text: '<b>Share</b> This' },
97
 *        { text: 'Move' }
98
 *      ],
99
 *      destructiveText: 'Delete',
100
 *      titleText: 'Modify your album',
101
 *      cancelText: 'Cancel',
102
 *      cancel: function() {
103
          // add cancel code..
104
        },
105
 *      buttonClicked: function(index) {
106
 *        return true;
107
 *      }
108
 *    });
109
 *
110
 *    // For example's sake, hide the sheet after two seconds
111
 *    $timeout(function() {
112
 *      hideSheet();
113
 *    }, 2000);
114
 *
115
 *  };
116
 * });
117
 * ```
118
 *
119
 */
120
IonicModule
121
.factory('$ionicActionSheet', [
122
  '$rootScope',
123
  '$compile',
124
  '$animate',
125
  '$timeout',
126
  '$ionicTemplateLoader',
127
  '$ionicPlatform',
128
  '$ionicBody',
129
function($rootScope, $compile, $animate, $timeout, $ionicTemplateLoader, $ionicPlatform, $ionicBody) {
130
 
131
  return {
132
    show: actionSheet
133
  };
134
 
135
  /**
136
   * @ngdoc method
137
   * @name $ionicActionSheet#show
138
   * @description
139
   * Load and return a new action sheet.
140
   *
141
   * A new isolated scope will be created for the
142
   * action sheet and the new element will be appended into the body.
143
   *
144
   * @param {object} options The options for this ActionSheet. Properties:
145
   *
146
   *  - `[Object]` `buttons` Which buttons to show.  Each button is an object with a `text` field.
147
   *  - `{string}` `titleText` The title to show on the action sheet.
148
   *  - `{string=}` `cancelText` the text for a 'cancel' button on the action sheet.
149
   *  - `{string=}` `destructiveText` The text for a 'danger' on the action sheet.
150
   *  - `{function=}` `cancel` Called if the cancel button is pressed, the backdrop is tapped or
151
   *     the hardware back button is pressed.
152
   *  - `{function=}` `buttonClicked` Called when one of the non-destructive buttons is clicked,
153
   *     with the index of the button that was clicked and the button object. Return true to close
154
   *     the action sheet, or false to keep it opened.
155
   *  - `{function=}` `destructiveButtonClicked` Called when the destructive button is clicked.
156
   *     Return true to close the action sheet, or false to keep it opened.
157
   *  -  `{boolean=}` `cancelOnStateChange` Whether to cancel the actionSheet when navigating
158
   *     to a new state.  Default true.
159
   *  - `{string}` `cssClass` The custom CSS class name.
160
   *
161
   * @returns {function} `hideSheet` A function which, when called, hides & cancels the action sheet.
162
   */
163
  function actionSheet(opts) {
164
    var scope = $rootScope.$new(true);
165
 
166
    angular.extend(scope, {
167
      cancel: angular.noop,
168
      destructiveButtonClicked: angular.noop,
169
      buttonClicked: angular.noop,
170
      $deregisterBackButton: angular.noop,
171
      buttons: [],
172
      cancelOnStateChange: true
173
    }, opts || {});
174
 
175
 
176
    // Compile the template
177
    var element = scope.element = $compile('<ion-action-sheet ng-class="cssClass" buttons="buttons"></ion-action-sheet>')(scope);
178
 
179
    // Grab the sheet element for animation
180
    var sheetEl = jqLite(element[0].querySelector('.action-sheet-wrapper'));
181
 
182
    var stateChangeListenDone = scope.cancelOnStateChange ?
183
      $rootScope.$on('$stateChangeSuccess', function() { scope.cancel(); }) :
184
      angular.noop;
185
 
186
    // removes the actionSheet from the screen
187
    scope.removeSheet = function(done) {
188
      if (scope.removed) return;
189
 
190
      scope.removed = true;
191
      sheetEl.removeClass('action-sheet-up');
192
      $timeout(function() {
193
        // wait to remove this due to a 300ms delay native
194
        // click which would trigging whatever was underneath this
195
        $ionicBody.removeClass('action-sheet-open');
196
      }, 400);
197
      scope.$deregisterBackButton();
198
      stateChangeListenDone();
199
 
200
      $animate.removeClass(element, 'active').then(function() {
201
        scope.$destroy();
202
        element.remove();
203
        // scope.cancel.$scope is defined near the bottom
204
        scope.cancel.$scope = sheetEl = null;
205
        (done || angular.noop)();
206
      });
207
    };
208
 
209
    scope.showSheet = function(done) {
210
      if (scope.removed) return;
211
 
212
      $ionicBody.append(element)
213
                .addClass('action-sheet-open');
214
 
215
      $animate.addClass(element, 'active').then(function() {
216
        if (scope.removed) return;
217
        (done || angular.noop)();
218
      });
219
      $timeout(function() {
220
        if (scope.removed) return;
221
        sheetEl.addClass('action-sheet-up');
222
      }, 20, false);
223
    };
224
 
225
    // registerBackButtonAction returns a callback to deregister the action
226
    scope.$deregisterBackButton = $ionicPlatform.registerBackButtonAction(
227
      function() {
228
        $timeout(scope.cancel);
229
      },
230
      PLATFORM_BACK_BUTTON_PRIORITY_ACTION_SHEET
231
    );
232
 
233
    // called when the user presses the cancel button
234
    scope.cancel = function() {
235
      // after the animation is out, call the cancel callback
236
      scope.removeSheet(opts.cancel);
237
    };
238
 
239
    scope.buttonClicked = function(index) {
240
      // Check if the button click event returned true, which means
241
      // we can close the action sheet
242
      if (opts.buttonClicked(index, opts.buttons[index]) === true) {
243
        scope.removeSheet();
244
      }
245
    };
246
 
247
    scope.destructiveButtonClicked = function() {
248
      // Check if the destructive button click event returned true, which means
249
      // we can close the action sheet
250
      if (opts.destructiveButtonClicked() === true) {
251
        scope.removeSheet();
252
      }
253
    };
254
 
255
    scope.showSheet();
256
 
257
    // Expose the scope on $ionicActionSheet's return value for the sake
258
    // of testing it.
259
    scope.cancel.$scope = scope;
260
 
261
    return scope.cancel;
262
  }
263
}]);
264
 
265
 
266
jqLite.prototype.addClass = function(cssClasses) {
267
  var x, y, cssClass, el, splitClasses, existingClasses;
268
  if (cssClasses && cssClasses != 'ng-scope' && cssClasses != 'ng-isolate-scope') {
269
    for (x = 0; x < this.length; x++) {
270
      el = this[x];
271
      if (el.setAttribute) {
272
 
273
        if (cssClasses.indexOf(' ') < 0 && el.classList.add) {
274
          el.classList.add(cssClasses);
275
        } else {
276
          existingClasses = (' ' + (el.getAttribute('class') || '') + ' ')
277
            .replace(/[\n\t]/g, " ");
278
          splitClasses = cssClasses.split(' ');
279
 
280
          for (y = 0; y < splitClasses.length; y++) {
281
            cssClass = splitClasses[y].trim();
282
            if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
283
              existingClasses += cssClass + ' ';
284
            }
285
          }
286
          el.setAttribute('class', existingClasses.trim());
287
        }
288
      }
289
    }
290
  }
291
  return this;
292
};
293
 
294
jqLite.prototype.removeClass = function(cssClasses) {
295
  var x, y, splitClasses, cssClass, el;
296
  if (cssClasses) {
297
    for (x = 0; x < this.length; x++) {
298
      el = this[x];
299
      if (el.getAttribute) {
300
        if (cssClasses.indexOf(' ') < 0 && el.classList.remove) {
301
          el.classList.remove(cssClasses);
302
        } else {
303
          splitClasses = cssClasses.split(' ');
304
 
305
          for (y = 0; y < splitClasses.length; y++) {
306
            cssClass = splitClasses[y];
307
            el.setAttribute('class', (
308
                (" " + (el.getAttribute('class') || '') + " ")
309
                .replace(/[\n\t]/g, " ")
310
                .replace(" " + cssClass.trim() + " ", " ")).trim()
311
            );
312
          }
313
        }
314
      }
315
    }
316
  }
317
  return this;
318
};
319
 
320
 
321
/**
322
 * @private
323
 */
324
IonicModule
325
.factory('$$ionicAttachDrag', [function() {
326
 
327
  return attachDrag;
328
 
329
  function attachDrag(scope, element, options) {
330
    var opts = extend({}, {
331
      getDistance: function() { return opts.element.prop('offsetWidth'); },
332
      onDragStart: angular.noop,
333
      onDrag: angular.noop,
334
      onDragEnd: angular.noop
335
    }, options);
336
 
337
    var dragStartGesture = ionic.onGesture('dragstart', handleDragStart, element[0]);
338
    var dragGesture = ionic.onGesture('drag', handleDrag, element[0]);
339
    var dragEndGesture = ionic.onGesture('dragend', handleDragEnd, element[0]);
340
 
341
    scope.$on('$destroy', function() {
342
      ionic.offGesture(dragStartGesture, 'dragstart', handleDragStart);
343
      ionic.offGesture(dragGesture, 'drag', handleDrag);
344
      ionic.offGesture(dragEndGesture, 'dragend', handleDragEnd);
345
    });
346
 
347
    var isDragging = false;
348
    element.on('touchmove pointermove mousemove', function(ev) {
349
      if (isDragging) ev.preventDefault();
350
    });
351
    element.on('touchend mouseup mouseleave', function(ev) {
352
      isDragging = false;
353
    });
354
 
355
    var dragState;
356
    function handleDragStart(ev) {
357
      if (dragState) return;
358
      if (opts.onDragStart() !== false) {
359
        dragState = {
360
          startX: ev.gesture.center.pageX,
361
          startY: ev.gesture.center.pageY,
362
          distance: opts.getDistance()
363
        };
364
      }
365
    }
366
    function handleDrag(ev) {
367
      if (!dragState) return;
368
      var deltaX = dragState.startX - ev.gesture.center.pageX;
369
      var deltaY = dragState.startY - ev.gesture.center.pageY;
370
      var isVertical = ev.gesture.direction === 'up' || ev.gesture.direction === 'down';
371
 
372
      if (isVertical && Math.abs(deltaY) > Math.abs(deltaX) * 2) {
373
        handleDragEnd(ev);
374
        return;
375
      }
376
      if (Math.abs(deltaX) > Math.abs(deltaY) * 2) {
377
        isDragging = true;
378
      }
379
 
380
      var percent = getDragPercent(ev.gesture.center.pageX);
381
      opts.onDrag(percent);
382
    }
383
    function handleDragEnd(ev) {
384
      if (!dragState) return;
385
      var percent = getDragPercent(ev.gesture.center.pageX);
386
      options.onDragEnd(percent, ev.gesture.velocityX);
387
 
388
      dragState = null;
389
    }
390
 
391
    function getDragPercent(x) {
392
      var delta = dragState.startX - x;
393
      var percent = delta / dragState.distance;
394
      return percent;
395
    }
396
  }
397
 
398
}]);
399
 
400
/**
401
 * @ngdoc service
402
 * @name $ionicBackdrop
403
 * @module ionic
404
 * @description
405
 * Shows and hides a backdrop over the UI.  Appears behind popups, loading,
406
 * and other overlays.
407
 *
408
 * Often, multiple UI components require a backdrop, but only one backdrop is
409
 * ever needed in the DOM at a time.
410
 *
411
 * Therefore, each component that requires the backdrop to be shown calls
412
 * `$ionicBackdrop.retain()` when it wants the backdrop, then `$ionicBackdrop.release()`
413
 * when it is done with the backdrop.
414
 *
415
 * For each time `retain` is called, the backdrop will be shown until `release` is called.
416
 *
417
 * For example, if `retain` is called three times, the backdrop will be shown until `release`
418
 * is called three times.
419
 *
420
 * @usage
421
 *
422
 * ```js
423
 * function MyController($scope, $ionicBackdrop, $timeout) {
424
 *   //Show a backdrop for one second
425
 *   $scope.action = function() {
426
 *     $ionicBackdrop.retain();
427
 *     $timeout(function() {
428
 *       $ionicBackdrop.release();
429
 *     }, 1000);
430
 *   };
431
 * }
432
 * ```
433
 */
434
IonicModule
435
.factory('$ionicBackdrop', [
436
  '$document', '$timeout',
437
function($document, $timeout) {
438
 
439
  var el = jqLite('<div class="backdrop">');
440
  var backdropHolds = 0;
441
 
442
  $document[0].body.appendChild(el[0]);
443
 
444
  return {
445
    /**
446
     * @ngdoc method
447
     * @name $ionicBackdrop#retain
448
     * @description Retains the backdrop.
449
     */
450
    retain: retain,
451
    /**
452
     * @ngdoc method
453
     * @name $ionicBackdrop#release
454
     * @description
455
     * Releases the backdrop.
456
     */
457
    release: release,
458
 
459
    getElement: getElement,
460
 
461
    // exposed for testing
462
    _element: el
463
  };
464
 
465
  function retain() {
466
    if ((++backdropHolds) === 1) {
467
      el.addClass('visible');
468
      ionic.requestAnimationFrame(function() {
469
        backdropHolds && el.addClass('active');
470
      });
471
    }
472
  }
473
  function release() {
474
    if ((--backdropHolds) === 0) {
475
      el.removeClass('active');
476
      $timeout(function() {
477
        !backdropHolds && el.removeClass('visible');
478
      }, 400, false);
479
    }
480
  }
481
 
482
  function getElement() {
483
    return el;
484
  }
485
 
486
}]);
487
 
488
/**
489
 * @private
490
 */
491
IonicModule
492
.factory('$ionicBind', ['$parse', '$interpolate', function($parse, $interpolate) {
493
  var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
494
  return function(scope, attrs, bindDefinition) {
495
    forEach(bindDefinition || {}, function (definition, scopeName) {
496
      //Adapted from angular.js $compile
497
      var match = definition.match(LOCAL_REGEXP) || [],
498
        attrName = match[3] || scopeName,
499
        mode = match[1], // @, =, or &
500
        parentGet,
501
        unwatch;
502
 
503
      switch(mode) {
504
        case '@':
505
          if (!attrs[attrName]) {
506
            return;
507
          }
508
          attrs.$observe(attrName, function(value) {
509
            scope[scopeName] = value;
510
          });
511
          // we trigger an interpolation to ensure
512
          // the value is there for use immediately
513
          if (attrs[attrName]) {
514
            scope[scopeName] = $interpolate(attrs[attrName])(scope);
515
          }
516
          break;
517
 
518
        case '=':
519
          if (!attrs[attrName]) {
520
            return;
521
          }
522
          unwatch = scope.$watch(attrs[attrName], function(value) {
523
            scope[scopeName] = value;
524
          });
525
          //Destroy parent scope watcher when this scope is destroyed
526
          scope.$on('$destroy', unwatch);
527
          break;
528
 
529
        case '&':
530
          /* jshint -W044 */
531
          if (attrs[attrName] && attrs[attrName].match(RegExp(scopeName + '\(.*?\)'))) {
532
            throw new Error('& expression binding "' + scopeName + '" looks like it will recursively call "' +
533
                          attrs[attrName] + '" and cause a stack overflow! Please choose a different scopeName.');
534
          }
535
          parentGet = $parse(attrs[attrName]);
536
          scope[scopeName] = function(locals) {
537
            return parentGet(scope, locals);
538
          };
539
          break;
540
      }
541
    });
542
  };
543
}]);
544
 
545
/**
546
 * @ngdoc service
547
 * @name $ionicBody
548
 * @module ionic
549
 * @description An angular utility service to easily and efficiently
550
 * add and remove CSS classes from the document's body element.
551
 */
552
IonicModule
553
.factory('$ionicBody', ['$document', function($document) {
554
  return {
555
    /**
556
     * @ngdoc method
557
     * @name $ionicBody#add
558
     * @description Add a class to the document's body element.
559
     * @param {string} class Each argument will be added to the body element.
560
     * @returns {$ionicBody} The $ionicBody service so methods can be chained.
561
     */
562
    addClass: function() {
563
      for (var x = 0; x < arguments.length; x++) {
564
        $document[0].body.classList.add(arguments[x]);
565
      }
566
      return this;
567
    },
568
    /**
569
     * @ngdoc method
570
     * @name $ionicBody#removeClass
571
     * @description Remove a class from the document's body element.
572
     * @param {string} class Each argument will be removed from the body element.
573
     * @returns {$ionicBody} The $ionicBody service so methods can be chained.
574
     */
575
    removeClass: function() {
576
      for (var x = 0; x < arguments.length; x++) {
577
        $document[0].body.classList.remove(arguments[x]);
578
      }
579
      return this;
580
    },
581
    /**
582
     * @ngdoc method
583
     * @name $ionicBody#enableClass
584
     * @description Similar to the `add` method, except the first parameter accepts a boolean
585
     * value determining if the class should be added or removed. Rather than writing user code,
586
     * such as "if true then add the class, else then remove the class", this method can be
587
     * given a true or false value which reduces redundant code.
588
     * @param {boolean} shouldEnableClass A true/false value if the class should be added or removed.
589
     * @param {string} class Each remaining argument would be added or removed depending on
590
     * the first argument.
591
     * @returns {$ionicBody} The $ionicBody service so methods can be chained.
592
     */
593
    enableClass: function(shouldEnableClass) {
594
      var args = Array.prototype.slice.call(arguments).slice(1);
595
      if (shouldEnableClass) {
596
        this.addClass.apply(this, args);
597
      } else {
598
        this.removeClass.apply(this, args);
599
      }
600
      return this;
601
    },
602
    /**
603
     * @ngdoc method
604
     * @name $ionicBody#append
605
     * @description Append a child to the document's body.
606
     * @param {element} element The element to be appended to the body. The passed in element
607
     * can be either a jqLite element, or a DOM element.
608
     * @returns {$ionicBody} The $ionicBody service so methods can be chained.
609
     */
610
    append: function(ele) {
611
      $document[0].body.appendChild(ele.length ? ele[0] : ele);
612
      return this;
613
    },
614
    /**
615
     * @ngdoc method
616
     * @name $ionicBody#get
617
     * @description Get the document's body element.
618
     * @returns {element} Returns the document's body element.
619
     */
620
    get: function() {
621
      return $document[0].body;
622
    }
623
  };
624
}]);
625
 
626
IonicModule
627
.factory('$ionicClickBlock', [
628
  '$document',
629
  '$ionicBody',
630
  '$timeout',
631
function($document, $ionicBody, $timeout) {
632
  var CSS_HIDE = 'click-block-hide';
633
  var cbEle, fallbackTimer, pendingShow;
634
 
635
  function addClickBlock() {
636
    if (pendingShow) {
637
      if (cbEle) {
638
        cbEle.classList.remove(CSS_HIDE);
639
      } else {
640
        cbEle = $document[0].createElement('div');
641
        cbEle.className = 'click-block';
642
        $ionicBody.append(cbEle);
643
      }
644
      pendingShow = false;
645
    }
646
  }
647
 
648
  function removeClickBlock() {
649
    cbEle && cbEle.classList.add(CSS_HIDE);
650
  }
651
 
652
  return {
653
    show: function(autoExpire) {
654
      pendingShow = true;
655
      $timeout.cancel(fallbackTimer);
656
      fallbackTimer = $timeout(this.hide, autoExpire || 310);
657
      ionic.requestAnimationFrame(addClickBlock);
658
    },
659
    hide: function() {
660
      pendingShow = false;
661
      $timeout.cancel(fallbackTimer);
662
      ionic.requestAnimationFrame(removeClickBlock);
663
    }
664
  };
665
}]);
666
 
667
IonicModule
668
.factory('$collectionDataSource', [
669
  '$cacheFactory',
670
  '$parse',
671
  '$rootScope',
672
function($cacheFactory, $parse, $rootScope) {
673
  function hideWithTransform(element) {
674
    element.css(ionic.CSS.TRANSFORM, 'translate3d(-2000px,-2000px,0)');
675
  }
676
 
677
  function CollectionRepeatDataSource(options) {
678
    var self = this;
679
    this.scope = options.scope;
680
    this.transcludeFn = options.transcludeFn;
681
    this.transcludeParent = options.transcludeParent;
682
    this.element = options.element;
683
 
684
    this.keyExpr = options.keyExpr;
685
    this.listExpr = options.listExpr;
686
    this.trackByExpr = options.trackByExpr;
687
 
688
    this.heightGetter = options.heightGetter;
689
    this.widthGetter = options.widthGetter;
690
 
691
    this.dimensions = [];
692
    this.data = [];
693
 
694
    this.attachedItems = {};
695
    this.BACKUP_ITEMS_LENGTH = 20;
696
    this.backupItemsArray = [];
697
  }
698
  CollectionRepeatDataSource.prototype = {
699
    setup: function() {
700
      if (this.isSetup) return;
701
      this.isSetup = true;
702
      for (var i = 0; i < this.BACKUP_ITEMS_LENGTH; i++) {
703
        this.detachItem(this.createItem());
704
      }
705
    },
706
    destroy: function() {
707
      this.dimensions.length = 0;
708
      this.data = null;
709
      this.backupItemsArray.length = 0;
710
      this.attachedItems = {};
711
    },
712
    calculateDataDimensions: function() {
713
      var locals = {};
714
      this.dimensions = this.data.map(function(value, index) {
715
        locals[this.keyExpr] = value;
716
        locals.$index = index;
717
        return {
718
          width: this.widthGetter(this.scope, locals),
719
          height: this.heightGetter(this.scope, locals)
720
        };
721
      }, this);
722
      this.dimensions = this.beforeSiblings.concat(this.dimensions).concat(this.afterSiblings);
723
      this.dataStartIndex = this.beforeSiblings.length;
724
    },
725
    createItem: function() {
726
      var item = {};
727
 
728
      item.scope = this.scope.$new();
729
      this.transcludeFn(item.scope, function(clone) {
730
        clone.css('position', 'absolute');
731
        item.element = clone;
732
      });
733
      this.transcludeParent.append(item.element);
734
 
735
      return item;
736
    },
737
    getItem: function(index) {
738
      var item;
739
      if ( (item = this.attachedItems[index]) ) {
740
        //do nothing, the item is good
741
      } else if ( (item = this.backupItemsArray.pop()) ) {
742
        ionic.Utils.reconnectScope(item.scope);
743
      } else {
744
        item = this.createItem();
745
      }
746
      return item;
747
    },
748
    attachItemAtIndex: function(index) {
749
      if (index < this.dataStartIndex) {
750
        return this.beforeSiblings[index];
751
      }
752
      // Subtract so we start at the beginning of this.data, after
753
      // this.beforeSiblings.
754
      index -= this.dataStartIndex;
755
 
756
      if (index > this.data.length - 1) {
757
        return this.afterSiblings[index - this.dataStartIndex];
758
      }
759
 
760
      var item = this.getItem(index);
761
      var value = this.data[index];
762
 
763
      if (item.index !== index || item.scope[this.keyExpr] !== value) {
764
        item.index = item.scope.$index = index;
765
        item.scope[this.keyExpr] = value;
766
        item.scope.$first = (index === 0);
767
        item.scope.$last = (index === (this.getLength() - 1));
768
        item.scope.$middle = !(item.scope.$first || item.scope.$last);
769
        item.scope.$odd = !(item.scope.$even = (index&1) === 0);
770
 
771
        //We changed the scope, so digest if needed
772
        if (!$rootScope.$$phase) {
773
          item.scope.$digest();
774
        }
775
      }
776
      this.attachedItems[index] = item;
777
 
778
      return item;
779
    },
780
    destroyItem: function(item) {
781
      item.element.remove();
782
      item.scope.$destroy();
783
      item.scope = null;
784
      item.element = null;
785
    },
786
    detachItem: function(item) {
787
      delete this.attachedItems[item.index];
788
 
789
      //If it's an outside item, only hide it. These items aren't part of collection
790
      //repeat's list, only sit outside
791
      if (item.isOutside) {
792
        hideWithTransform(item.element);
793
      // If we are at the limit of backup items, just get rid of the this element
794
      } else if (this.backupItemsArray.length >= this.BACKUP_ITEMS_LENGTH) {
795
        this.destroyItem(item);
796
      // Otherwise, add it to our backup items
797
      } else {
798
        this.backupItemsArray.push(item);
799
        hideWithTransform(item.element);
800
        //Don't .$destroy(), just stop watchers and events firing
801
        ionic.Utils.disconnectScope(item.scope);
802
      }
803
 
804
    },
805
    getLength: function() {
806
      return this.dimensions && this.dimensions.length || 0;
807
    },
808
    setData: function(value, beforeSiblings, afterSiblings) {
809
      this.data = value || [];
810
      this.beforeSiblings = beforeSiblings || [];
811
      this.afterSiblings = afterSiblings || [];
812
      this.calculateDataDimensions();
813
 
814
      this.afterSiblings.forEach(function(item) {
815
        item.element.css({position: 'absolute', top: '0', left: '0' });
816
        hideWithTransform(item.element);
817
      });
818
    },
819
  };
820
 
821
  return CollectionRepeatDataSource;
822
}]);
823
 
824
 
825
IonicModule
826
.factory('$collectionRepeatManager', [
827
  '$rootScope',
828
  '$timeout',
829
function($rootScope, $timeout) {
830
  /**
831
   * Vocabulary: "primary" and "secondary" size/direction/position mean
832
   * "y" and "x" for vertical scrolling, or "x" and "y" for horizontal scrolling.
833
   */
834
  function CollectionRepeatManager(options) {
835
    var self = this;
836
    this.dataSource = options.dataSource;
837
    this.element = options.element;
838
    this.scrollView = options.scrollView;
839
 
840
    this.isVertical = !!this.scrollView.options.scrollingY;
841
    this.renderedItems = {};
842
    this.dimensions = [];
843
    this.setCurrentIndex(0);
844
 
845
    //Override scrollview's render callback
846
    this.scrollView.__$callback = this.scrollView.__callback;
847
    this.scrollView.__callback = angular.bind(this, this.renderScroll);
848
 
849
    function getViewportSize() { return self.viewportSize; }
850
    //Set getters and setters to match whether this scrollview is vertical or not
851
    if (this.isVertical) {
852
      this.scrollView.options.getContentHeight = getViewportSize;
853
 
854
      this.scrollValue = function() {
855
        return this.scrollView.__scrollTop;
856
      };
857
      this.scrollMaxValue = function() {
858
        return this.scrollView.__maxScrollTop;
859
      };
860
      this.scrollSize = function() {
861
        return this.scrollView.__clientHeight;
862
      };
863
      this.secondaryScrollSize = function() {
864
        return this.scrollView.__clientWidth;
865
      };
866
      this.transformString = function(y, x) {
867
        return 'translate3d('+x+'px,'+y+'px,0)';
868
      };
869
      this.primaryDimension = function(dim) {
870
        return dim.height;
871
      };
872
      this.secondaryDimension = function(dim) {
873
        return dim.width;
874
      };
875
    } else {
876
      this.scrollView.options.getContentWidth = getViewportSize;
877
 
878
      this.scrollValue = function() {
879
        return this.scrollView.__scrollLeft;
880
      };
881
      this.scrollMaxValue = function() {
882
        return this.scrollView.__maxScrollLeft;
883
      };
884
      this.scrollSize = function() {
885
        return this.scrollView.__clientWidth;
886
      };
887
      this.secondaryScrollSize = function() {
888
        return this.scrollView.__clientHeight;
889
      };
890
      this.transformString = function(x, y) {
891
        return 'translate3d('+x+'px,'+y+'px,0)';
892
      };
893
      this.primaryDimension = function(dim) {
894
        return dim.width;
895
      };
896
      this.secondaryDimension = function(dim) {
897
        return dim.height;
898
      };
899
    }
900
  }
901
 
902
  CollectionRepeatManager.prototype = {
903
    destroy: function() {
904
      this.renderedItems = {};
905
      this.render = angular.noop;
906
      this.calculateDimensions = angular.noop;
907
      this.dimensions = [];
908
    },
909
 
910
    /*
911
     * Pre-calculate the position of all items in the data list.
912
     * Do this using the provided width and height (primarySize and secondarySize)
913
     * provided by the dataSource.
914
     */
915
    calculateDimensions: function() {
916
      /*
917
       * For the sake of explanations below, we're going to pretend we are scrolling
918
       * vertically: Items are laid out with primarySize being height,
919
       * secondarySize being width.
920
       */
921
      var primaryPos = 0;
922
      var secondaryPos = 0;
923
      var secondaryScrollSize = this.secondaryScrollSize();
924
      var previousItem;
925
 
926
      this.dataSource.beforeSiblings && this.dataSource.beforeSiblings.forEach(calculateSize, this);
927
      var beforeSize = primaryPos + (previousItem ? previousItem.primarySize : 0);
928
 
929
      primaryPos = secondaryPos = 0;
930
      previousItem = null;
931
 
932
      var dimensions = this.dataSource.dimensions.map(calculateSize, this);
933
      var totalSize = primaryPos + (previousItem ? previousItem.primarySize : 0);
934
 
935
      return {
936
        beforeSize: beforeSize,
937
        totalSize: totalSize,
938
        dimensions: dimensions
939
      };
940
 
941
      function calculateSize(dim) {
942
 
943
        //Each dimension is an object {width: Number, height: Number} provided by
944
        //the dataSource
945
        var rect = {
946
          //Get the height out of the dimension object
947
          primarySize: this.primaryDimension(dim),
948
          //Max out the item's width to the width of the scrollview
949
          secondarySize: Math.min(this.secondaryDimension(dim), secondaryScrollSize)
950
        };
951
 
952
        //If this isn't the first item
953
        if (previousItem) {
954
          //Move the item's x position over by the width of the previous item
955
          secondaryPos += previousItem.secondarySize;
956
          //If the y position is the same as the previous item and
957
          //the x position is bigger than the scroller's width
958
          if (previousItem.primaryPos === primaryPos &&
959
              secondaryPos + rect.secondarySize > secondaryScrollSize) {
960
            //Then go to the next row, with x position 0
961
            secondaryPos = 0;
962
            primaryPos += previousItem.primarySize;
963
          }
964
        }
965
 
966
        rect.primaryPos = primaryPos;
967
        rect.secondaryPos = secondaryPos;
968
 
969
        previousItem = rect;
970
        return rect;
971
      }
972
    },
973
    resize: function() {
974
      var result = this.calculateDimensions();
975
      this.dimensions = result.dimensions;
976
      this.viewportSize = result.totalSize;
977
      this.beforeSize = result.beforeSize;
978
      this.setCurrentIndex(0);
979
      this.render(true);
980
      this.dataSource.setup();
981
    },
982
    /*
983
     * setCurrentIndex sets the index in the list that matches the scroller's position.
984
     * Also save the position in the scroller for next and previous items (if they exist)
985
     */
986
    setCurrentIndex: function(index, height) {
987
      var currentPos = (this.dimensions[index] || {}).primaryPos || 0;
988
      this.currentIndex = index;
989
 
990
      this.hasPrevIndex = index > 0;
991
      if (this.hasPrevIndex) {
992
        this.previousPos = Math.max(
993
          currentPos - this.dimensions[index - 1].primarySize,
994
          this.dimensions[index - 1].primaryPos
995
        );
996
      }
997
      this.hasNextIndex = index + 1 < this.dataSource.getLength();
998
      if (this.hasNextIndex) {
999
        this.nextPos = Math.min(
1000
          currentPos + this.dimensions[index + 1].primarySize,
1001
          this.dimensions[index + 1].primaryPos
1002
        );
1003
      }
1004
    },
1005
    /**
1006
     * override the scroller's render callback to check if we need to
1007
     * re-render our collection
1008
     */
1009
    renderScroll: ionic.animationFrameThrottle(function(transformLeft, transformTop, zoom, wasResize) {
1010
      if (this.isVertical) {
1011
        this.renderIfNeeded(transformTop);
1012
      } else {
1013
        this.renderIfNeeded(transformLeft);
1014
      }
1015
      return this.scrollView.__$callback(transformLeft, transformTop, zoom, wasResize);
1016
    }),
1017
 
1018
    renderIfNeeded: function(scrollPos) {
1019
      if ((this.hasNextIndex && scrollPos >= this.nextPos) ||
1020
          (this.hasPrevIndex && scrollPos < this.previousPos)) {
1021
           // Math.abs(transformPos - this.lastRenderScrollValue) > 100) {
1022
        this.render();
1023
      }
1024
    },
1025
    /*
1026
     * getIndexForScrollValue: Given the most recent data index and a new scrollValue,
1027
     * find the data index that matches that scrollValue.
1028
     *
1029
     * Strategy (if we are scrolling down): keep going forward in the dimensions list,
1030
     * starting at the given index, until an item with height matching the new scrollValue
1031
     * is found.
1032
     *
1033
     * This is a while loop. In the worst case it will have to go through the whole list
1034
     * (eg to scroll from top to bottom).  The most common case is to scroll
1035
     * down 1-3 items at a time.
1036
     *
1037
     * While this is not as efficient as it could be, optimizing it gives no noticeable
1038
     * benefit.  We would have to use a new memory-intensive data structure for dimensions
1039
     * to fully optimize it.
1040
     */
1041
    getIndexForScrollValue: function(i, scrollValue) {
1042
      var rect;
1043
      //Scrolling up
1044
      if (scrollValue <= this.dimensions[i].primaryPos) {
1045
        while ( (rect = this.dimensions[i - 1]) && rect.primaryPos > scrollValue) {
1046
          i--;
1047
        }
1048
      //Scrolling down
1049
      } else {
1050
        while ( (rect = this.dimensions[i + 1]) && rect.primaryPos < scrollValue) {
1051
          i++;
1052
        }
1053
      }
1054
      return i;
1055
    },
1056
    /*
1057
     * render: Figure out the scroll position, the index matching it, and then tell
1058
     * the data source to render the correct items into the DOM.
1059
     */
1060
    render: function(shouldRedrawAll) {
1061
      var self = this;
1062
      var i;
1063
      var isOutOfBounds = ( this.currentIndex >= this.dataSource.getLength() );
1064
      // We want to remove all the items and redraw everything if we're out of bounds
1065
      // or a flag is passed in.
1066
      if (isOutOfBounds || shouldRedrawAll) {
1067
        for (i in this.renderedItems) {
1068
          this.removeItem(i);
1069
        }
1070
        // Just don't render anything if we're out of bounds
1071
        if (isOutOfBounds) return;
1072
      }
1073
 
1074
      var rect;
1075
      var scrollValue = this.scrollValue();
1076
      // Scroll size = how many pixels are visible in the scroller at one time
1077
      var scrollSize = this.scrollSize();
1078
      // We take the current scroll value and add it to the scrollSize to get
1079
      // what scrollValue the current visible scroll area ends at.
1080
      var scrollSizeEnd = scrollSize + scrollValue;
1081
      // Get the new start index for scrolling, based on the current scrollValue and
1082
      // the most recent known index
1083
      var startIndex = this.getIndexForScrollValue(this.currentIndex, scrollValue);
1084
 
1085
      // If we aren't on the first item, add one row of items before so that when the user is
1086
      // scrolling up he sees the previous item
1087
      var renderStartIndex = Math.max(startIndex - 1, 0);
1088
      // Keep adding items to the 'extra row above' until we get to a new row.
1089
      // This is for the case where there are multiple items on one row above
1090
      // the current item; we want to keep adding items above until
1091
      // a new row is reached.
1092
      while (renderStartIndex > 0 &&
1093
         (rect = this.dimensions[renderStartIndex]) &&
1094
         rect.primaryPos === this.dimensions[startIndex - 1].primaryPos) {
1095
        renderStartIndex--;
1096
      }
1097
 
1098
      // Keep rendering items, adding them until we are past the end of the visible scroll area
1099
      i = renderStartIndex;
1100
      while ((rect = this.dimensions[i]) && (rect.primaryPos - rect.primarySize < scrollSizeEnd)) {
1101
        doRender(i, rect);
1102
        i++;
1103
      }
1104
 
1105
      // Render two extra items at the end as a buffer
1106
      if (self.dimensions[i]) {
1107
        doRender(i, self.dimensions[i]);
1108
        i++;
1109
      }
1110
      if (self.dimensions[i]) {
1111
        doRender(i, self.dimensions[i]);
1112
      }
1113
      var renderEndIndex = i;
1114
 
1115
      // Remove any items that were rendered and aren't visible anymore
1116
      for (var renderIndex in this.renderedItems) {
1117
        if (renderIndex < renderStartIndex || renderIndex > renderEndIndex) {
1118
          this.removeItem(renderIndex);
1119
        }
1120
      }
1121
 
1122
      this.setCurrentIndex(startIndex);
1123
 
1124
      function doRender(dataIndex, rect) {
1125
        if (dataIndex < self.dataSource.dataStartIndex) {
1126
          // do nothing
1127
        } else {
1128
          self.renderItem(dataIndex, rect.primaryPos - self.beforeSize, rect.secondaryPos);
1129
        }
1130
      }
1131
    },
1132
    renderItem: function(dataIndex, primaryPos, secondaryPos) {
1133
      // Attach an item, and set its transform position to the required value
1134
      var item = this.dataSource.attachItemAtIndex(dataIndex);
1135
      //console.log(dataIndex, item);
1136
      if (item && item.element) {
1137
        if (item.primaryPos !== primaryPos || item.secondaryPos !== secondaryPos) {
1138
          item.element.css(ionic.CSS.TRANSFORM, this.transformString(
1139
            primaryPos, secondaryPos
1140
          ));
1141
          item.primaryPos = primaryPos;
1142
          item.secondaryPos = secondaryPos;
1143
        }
1144
        // Save the item in rendered items
1145
        this.renderedItems[dataIndex] = item;
1146
      } else {
1147
        // If an item at this index doesn't exist anymore, be sure to delete
1148
        // it from rendered items
1149
        delete this.renderedItems[dataIndex];
1150
      }
1151
    },
1152
    removeItem: function(dataIndex) {
1153
      // Detach a given item
1154
      var item = this.renderedItems[dataIndex];
1155
      if (item) {
1156
        item.primaryPos = item.secondaryPos = null;
1157
        this.dataSource.detachItem(item);
1158
        delete this.renderedItems[dataIndex];
1159
      }
1160
    }
1161
  };
1162
 
1163
  return CollectionRepeatManager;
1164
}]);
1165
 
1166
 
1167
/**
1168
 * @ngdoc service
1169
 * @name $ionicGesture
1170
 * @module ionic
1171
 * @description An angular service exposing ionic
1172
 * {@link ionic.utility:ionic.EventController}'s gestures.
1173
 */
1174
IonicModule
1175
.factory('$ionicGesture', [function() {
1176
  return {
1177
    /**
1178
     * @ngdoc method
1179
     * @name $ionicGesture#on
1180
     * @description Add an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#onGesture}.
1181
     * @param {string} eventType The gesture event to listen for.
1182
     * @param {function(e)} callback The function to call when the gesture
1183
     * happens.
1184
     * @param {element} $element The angular element to listen for the event on.
1185
     * @param {object} options object.
1186
     * @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on).
1187
     */
1188
    on: function(eventType, cb, $element, options) {
1189
      return window.ionic.onGesture(eventType, cb, $element[0], options);
1190
    },
1191
    /**
1192
     * @ngdoc method
1193
     * @name $ionicGesture#off
1194
     * @description Remove an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#offGesture}.
1195
     * @param {ionic.Gesture} gesture The gesture that should be removed.
1196
     * @param {string} eventType The gesture event to remove the listener for.
1197
     * @param {function(e)} callback The listener to remove.
1198
     */
1199
    off: function(gesture, eventType, cb) {
1200
      return window.ionic.offGesture(gesture, eventType, cb);
1201
    }
1202
  };
1203
}]);
1204
 
1205
/**
1206
 * @ngdoc service
1207
 * @name $ionicHistory
1208
 * @module ionic
1209
 * @description
1210
 * $ionicHistory keeps track of views as the user navigates through an app. Similar to the way a
1211
 * browser behaves, an Ionic app is able to keep track of the previous view, the current view, and
1212
 * the forward view (if there is one).  However, a typical web browser only keeps track of one
1213
 * history stack in a linear fashion.
1214
 *
1215
 * Unlike a traditional browser environment, apps and webapps have parallel independent histories,
1216
 * such as with tabs. Should a user navigate few pages deep on one tab, and then switch to a new
1217
 * tab and back, the back button relates not to the previous tab, but to the previous pages
1218
 * visited within _that_ tab.
1219
 *
1220
 * `$ionicHistory` facilitates this parallel history architecture.
1221
 */
1222
 
1223
IonicModule
1224
.factory('$ionicHistory', [
1225
  '$rootScope',
1226
  '$state',
1227
  '$location',
1228
  '$window',
1229
  '$timeout',
1230
  '$ionicViewSwitcher',
1231
  '$ionicNavViewDelegate',
1232
function($rootScope, $state, $location, $window, $timeout, $ionicViewSwitcher, $ionicNavViewDelegate) {
1233
 
1234
  // history actions while navigating views
1235
  var ACTION_INITIAL_VIEW = 'initialView';
1236
  var ACTION_NEW_VIEW = 'newView';
1237
  var ACTION_MOVE_BACK = 'moveBack';
1238
  var ACTION_MOVE_FORWARD = 'moveForward';
1239
 
1240
  // direction of navigation
1241
  var DIRECTION_BACK = 'back';
1242
  var DIRECTION_FORWARD = 'forward';
1243
  var DIRECTION_ENTER = 'enter';
1244
  var DIRECTION_EXIT = 'exit';
1245
  var DIRECTION_SWAP = 'swap';
1246
  var DIRECTION_NONE = 'none';
1247
 
1248
  var stateChangeCounter = 0;
1249
  var lastStateId, nextViewOptions, nextViewExpireTimer, forcedNav;
1250
 
1251
  var viewHistory = {
1252
    histories: { root: { historyId: 'root', parentHistoryId: null, stack: [], cursor: -1 } },
1253
    views: {},
1254
    backView: null,
1255
    forwardView: null,
1256
    currentView: null
1257
  };
1258
 
1259
  var View = function() {};
1260
  View.prototype.initialize = function(data) {
1261
    if (data) {
1262
      for (var name in data) this[name] = data[name];
1263
      return this;
1264
    }
1265
    return null;
1266
  };
1267
  View.prototype.go = function() {
1268
 
1269
    if (this.stateName) {
1270
      return $state.go(this.stateName, this.stateParams);
1271
    }
1272
 
1273
    if (this.url && this.url !== $location.url()) {
1274
 
1275
      if (viewHistory.backView === this) {
1276
        return $window.history.go(-1);
1277
      } else if (viewHistory.forwardView === this) {
1278
        return $window.history.go(1);
1279
      }
1280
 
1281
      $location.url(this.url);
1282
      return;
1283
    }
1284
 
1285
    return null;
1286
  };
1287
  View.prototype.destroy = function() {
1288
    if (this.scope) {
1289
      this.scope.$destroy && this.scope.$destroy();
1290
      this.scope = null;
1291
    }
1292
  };
1293
 
1294
 
1295
  function getViewById(viewId) {
1296
    return (viewId ? viewHistory.views[ viewId ] : null);
1297
  }
1298
 
1299
  function getBackView(view) {
1300
    return (view ? getViewById(view.backViewId) : null);
1301
  }
1302
 
1303
  function getForwardView(view) {
1304
    return (view ? getViewById(view.forwardViewId) : null);
1305
  }
1306
 
1307
  function getHistoryById(historyId) {
1308
    return (historyId ? viewHistory.histories[ historyId ] : null);
1309
  }
1310
 
1311
  function getHistory(scope) {
1312
    var histObj = getParentHistoryObj(scope);
1313
 
1314
    if (!viewHistory.histories[ histObj.historyId ]) {
1315
      // this history object exists in parent scope, but doesn't
1316
      // exist in the history data yet
1317
      viewHistory.histories[ histObj.historyId ] = {
1318
        historyId: histObj.historyId,
1319
        parentHistoryId: getParentHistoryObj(histObj.scope.$parent).historyId,
1320
        stack: [],
1321
        cursor: -1
1322
      };
1323
    }
1324
    return getHistoryById(histObj.historyId);
1325
  }
1326
 
1327
  function getParentHistoryObj(scope) {
1328
    var parentScope = scope;
1329
    while (parentScope) {
1330
      if (parentScope.hasOwnProperty('$historyId')) {
1331
        // this parent scope has a historyId
1332
        return { historyId: parentScope.$historyId, scope: parentScope };
1333
      }
1334
      // nothing found keep climbing up
1335
      parentScope = parentScope.$parent;
1336
    }
1337
    // no history for the parent, use the root
1338
    return { historyId: 'root', scope: $rootScope };
1339
  }
1340
 
1341
  function setNavViews(viewId) {
1342
    viewHistory.currentView = getViewById(viewId);
1343
    viewHistory.backView = getBackView(viewHistory.currentView);
1344
    viewHistory.forwardView = getForwardView(viewHistory.currentView);
1345
  }
1346
 
1347
  function getCurrentStateId() {
1348
    var id;
1349
    if ($state && $state.current && $state.current.name) {
1350
      id = $state.current.name;
1351
      if ($state.params) {
1352
        for (var key in $state.params) {
1353
          if ($state.params.hasOwnProperty(key) && $state.params[key]) {
1354
            id += "_" + key + "=" + $state.params[key];
1355
          }
1356
        }
1357
      }
1358
      return id;
1359
    }
1360
    // if something goes wrong make sure its got a unique stateId
1361
    return ionic.Utils.nextUid();
1362
  }
1363
 
1364
  function getCurrentStateParams() {
1365
    var rtn;
1366
    if ($state && $state.params) {
1367
      for (var key in $state.params) {
1368
        if ($state.params.hasOwnProperty(key)) {
1369
          rtn = rtn || {};
1370
          rtn[key] = $state.params[key];
1371
        }
1372
      }
1373
    }
1374
    return rtn;
1375
  }
1376
 
1377
 
1378
  return {
1379
 
1380
    register: function(parentScope, viewLocals) {
1381
 
1382
      var currentStateId = getCurrentStateId(),
1383
          hist = getHistory(parentScope),
1384
          currentView = viewHistory.currentView,
1385
          backView = viewHistory.backView,
1386
          forwardView = viewHistory.forwardView,
1387
          viewId = null,
1388
          action = null,
1389
          direction = DIRECTION_NONE,
1390
          historyId = hist.historyId,
1391
          url = $location.url(),
1392
          tmp, x, ele;
1393
 
1394
      if (lastStateId !== currentStateId) {
1395
        lastStateId = currentStateId;
1396
        stateChangeCounter++;
1397
      }
1398
 
1399
      if (forcedNav) {
1400
        // we've previously set exactly what to do
1401
        viewId = forcedNav.viewId;
1402
        action = forcedNav.action;
1403
        direction = forcedNav.direction;
1404
        forcedNav = null;
1405
 
1406
      } else if (backView && backView.stateId === currentStateId) {
1407
        // they went back one, set the old current view as a forward view
1408
        viewId = backView.viewId;
1409
        historyId = backView.historyId;
1410
        action = ACTION_MOVE_BACK;
1411
        if (backView.historyId === currentView.historyId) {
1412
          // went back in the same history
1413
          direction = DIRECTION_BACK;
1414
 
1415
        } else if (currentView) {
1416
          direction = DIRECTION_EXIT;
1417
 
1418
          tmp = getHistoryById(backView.historyId);
1419
          if (tmp && tmp.parentHistoryId === currentView.historyId) {
1420
            direction = DIRECTION_ENTER;
1421
 
1422
          } else {
1423
            tmp = getHistoryById(currentView.historyId);
1424
            if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
1425
              direction = DIRECTION_SWAP;
1426
            }
1427
          }
1428
        }
1429
 
1430
      } else if (forwardView && forwardView.stateId === currentStateId) {
1431
        // they went to the forward one, set the forward view to no longer a forward view
1432
        viewId = forwardView.viewId;
1433
        historyId = forwardView.historyId;
1434
        action = ACTION_MOVE_FORWARD;
1435
        if (forwardView.historyId === currentView.historyId) {
1436
          direction = DIRECTION_FORWARD;
1437
 
1438
        } else if (currentView) {
1439
          direction = DIRECTION_EXIT;
1440
 
1441
          if (currentView.historyId === hist.parentHistoryId) {
1442
            direction = DIRECTION_ENTER;
1443
 
1444
          } else {
1445
            tmp = getHistoryById(currentView.historyId);
1446
            if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
1447
              direction = DIRECTION_SWAP;
1448
            }
1449
          }
1450
        }
1451
 
1452
        tmp = getParentHistoryObj(parentScope);
1453
        if (forwardView.historyId && tmp.scope) {
1454
          // if a history has already been created by the forward view then make sure it stays the same
1455
          tmp.scope.$historyId = forwardView.historyId;
1456
          historyId = forwardView.historyId;
1457
        }
1458
 
1459
      } else if (currentView && currentView.historyId !== historyId &&
1460
                hist.cursor > -1 && hist.stack.length > 0 && hist.cursor < hist.stack.length &&
1461
                hist.stack[hist.cursor].stateId === currentStateId) {
1462
        // they just changed to a different history and the history already has views in it
1463
        var switchToView = hist.stack[hist.cursor];
1464
        viewId = switchToView.viewId;
1465
        historyId = switchToView.historyId;
1466
        action = ACTION_MOVE_BACK;
1467
        direction = DIRECTION_SWAP;
1468
 
1469
        tmp = getHistoryById(currentView.historyId);
1470
        if (tmp && tmp.parentHistoryId === historyId) {
1471
          direction = DIRECTION_EXIT;
1472
 
1473
        } else {
1474
          tmp = getHistoryById(historyId);
1475
          if (tmp && tmp.parentHistoryId === currentView.historyId) {
1476
            direction = DIRECTION_ENTER;
1477
          }
1478
        }
1479
 
1480
        // if switching to a different history, and the history of the view we're switching
1481
        // to has an existing back view from a different history than itself, then
1482
        // it's back view would be better represented using the current view as its back view
1483
        tmp = getViewById(switchToView.backViewId);
1484
        if (tmp && switchToView.historyId !== tmp.historyId) {
1485
          hist.stack[hist.cursor].backViewId = currentView.viewId;
1486
        }
1487
 
1488
      } else {
1489
 
1490
        // create an element from the viewLocals template
1491
        ele = $ionicViewSwitcher.createViewEle(viewLocals);
1492
        if (this.isAbstractEle(ele, viewLocals)) {
1493
          void 0;
1494
          return {
1495
            action: 'abstractView',
1496
            direction: DIRECTION_NONE,
1497
            ele: ele
1498
          };
1499
        }
1500
 
1501
        // set a new unique viewId
1502
        viewId = ionic.Utils.nextUid();
1503
 
1504
        if (currentView) {
1505
          // set the forward view if there is a current view (ie: if its not the first view)
1506
          currentView.forwardViewId = viewId;
1507
 
1508
          action = ACTION_NEW_VIEW;
1509
 
1510
          // check if there is a new forward view within the same history
1511
          if (forwardView && currentView.stateId !== forwardView.stateId &&
1512
             currentView.historyId === forwardView.historyId) {
1513
            // they navigated to a new view but the stack already has a forward view
1514
            // since its a new view remove any forwards that existed
1515
            tmp = getHistoryById(forwardView.historyId);
1516
            if (tmp) {
1517
              // the forward has a history
1518
              for (x = tmp.stack.length - 1; x >= forwardView.index; x--) {
1519
                // starting from the end destroy all forwards in this history from this point
1520
                tmp.stack[x].destroy();
1521
                tmp.stack.splice(x);
1522
              }
1523
              historyId = forwardView.historyId;
1524
            }
1525
          }
1526
 
1527
          // its only moving forward if its in the same history
1528
          if (hist.historyId === currentView.historyId) {
1529
            direction = DIRECTION_FORWARD;
1530
 
1531
          } else if (currentView.historyId !== hist.historyId) {
1532
            direction = DIRECTION_ENTER;
1533
 
1534
            tmp = getHistoryById(currentView.historyId);
1535
            if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
1536
              direction = DIRECTION_SWAP;
1537
 
1538
            } else {
1539
              tmp = getHistoryById(tmp.parentHistoryId);
1540
              if (tmp && tmp.historyId === hist.historyId) {
1541
                direction = DIRECTION_EXIT;
1542
              }
1543
            }
1544
          }
1545
 
1546
        } else {
1547
          // there's no current view, so this must be the initial view
1548
          action = ACTION_INITIAL_VIEW;
1549
        }
1550
 
1551
        if (stateChangeCounter < 2) {
1552
          // views that were spun up on the first load should not animate
1553
          direction = DIRECTION_NONE;
1554
        }
1555
 
1556
        // add the new view
1557
        viewHistory.views[viewId] = this.createView({
1558
          viewId: viewId,
1559
          index: hist.stack.length,
1560
          historyId: hist.historyId,
1561
          backViewId: (currentView && currentView.viewId ? currentView.viewId : null),
1562
          forwardViewId: null,
1563
          stateId: currentStateId,
1564
          stateName: this.currentStateName(),
1565
          stateParams: getCurrentStateParams(),
1566
          url: url
1567
        });
1568
 
1569
        // add the new view to this history's stack
1570
        hist.stack.push(viewHistory.views[viewId]);
1571
      }
1572
 
1573
      $timeout.cancel(nextViewExpireTimer);
1574
      if (nextViewOptions) {
1575
        if (nextViewOptions.disableAnimate) direction = DIRECTION_NONE;
1576
        if (nextViewOptions.disableBack) viewHistory.views[viewId].backViewId = null;
1577
        if (nextViewOptions.historyRoot) {
1578
          for (x = 0; x < hist.stack.length; x++) {
1579
            if (hist.stack[x].viewId === viewId) {
1580
              hist.stack[x].index = 0;
1581
              hist.stack[x].backViewId = hist.stack[x].forwardViewId = null;
1582
            } else {
1583
              delete viewHistory.views[hist.stack[x].viewId];
1584
            }
1585
          }
1586
          hist.stack = [viewHistory.views[viewId]];
1587
        }
1588
        nextViewOptions = null;
1589
      }
1590
 
1591
      setNavViews(viewId);
1592
 
1593
      if (viewHistory.backView && historyId == viewHistory.backView.historyId && currentStateId == viewHistory.backView.stateId && url == viewHistory.backView.url) {
1594
        for (x = 0; x < hist.stack.length; x++) {
1595
          if (hist.stack[x].viewId == viewId) {
1596
            action = 'dupNav';
1597
            direction = DIRECTION_NONE;
1598
            hist.stack[x - 1].forwardViewId = viewHistory.forwardView = null;
1599
            viewHistory.currentView.index = viewHistory.backView.index;
1600
            viewHistory.currentView.backViewId = viewHistory.backView.backViewId;
1601
            viewHistory.backView = getBackView(viewHistory.backView);
1602
            hist.stack.splice(x, 1);
1603
            break;
1604
          }
1605
        }
1606
      }
1607
 
1608
      void 0;
1609
 
1610
      hist.cursor = viewHistory.currentView.index;
1611
 
1612
      return {
1613
        viewId: viewId,
1614
        action: action,
1615
        direction: direction,
1616
        historyId: historyId,
1617
        enableBack: !!(viewHistory.backView && viewHistory.backView.historyId === viewHistory.currentView.historyId),
1618
        isHistoryRoot: (viewHistory.currentView.index === 0),
1619
        ele: ele
1620
      };
1621
    },
1622
 
1623
    registerHistory: function(scope) {
1624
      scope.$historyId = ionic.Utils.nextUid();
1625
    },
1626
 
1627
    createView: function(data) {
1628
      var newView = new View();
1629
      return newView.initialize(data);
1630
    },
1631
 
1632
    getViewById: getViewById,
1633
 
1634
    /**
1635
     * @ngdoc method
1636
     * @name $ionicHistory#viewHistory
1637
     * @description The app's view history data, such as all the views and histories, along
1638
     * with how they are ordered and linked together within the navigation stack.
1639
     * @returns {object} Returns an object containing the apps view history data.
1640
     */
1641
    viewHistory: function() {
1642
      return viewHistory;
1643
    },
1644
 
1645
    /**
1646
     * @ngdoc method
1647
     * @name $ionicHistory#currentView
1648
     * @description The app's current view.
1649
     * @returns {object} Returns the current view.
1650
     */
1651
    currentView: function(view) {
1652
      if (arguments.length) {
1653
        viewHistory.currentView = view;
1654
      }
1655
      return viewHistory.currentView;
1656
    },
1657
 
1658
    /**
1659
     * @ngdoc method
1660
     * @name $ionicHistory#currentHistoryId
1661
     * @description The ID of the history stack which is the parent container of the current view.
1662
     * @returns {string} Returns the current history ID.
1663
     */
1664
    currentHistoryId: function() {
1665
      return viewHistory.currentView ? viewHistory.currentView.historyId : null;
1666
    },
1667
 
1668
    /**
1669
     * @ngdoc method
1670
     * @name $ionicHistory#currentTitle
1671
     * @description Gets and sets the current view's title.
1672
     * @param {string=} val The title to update the current view with.
1673
     * @returns {string} Returns the current view's title.
1674
     */
1675
    currentTitle: function(val) {
1676
      if (viewHistory.currentView) {
1677
        if (arguments.length) {
1678
          viewHistory.currentView.title = val;
1679
        }
1680
        return viewHistory.currentView.title;
1681
      }
1682
    },
1683
 
1684
    /**
1685
     * @ngdoc method
1686
     * @name $ionicHistory#backView
1687
     * @description Returns the view that was before the current view in the history stack.
1688
     * If the user navigated from View A to View B, then View A would be the back view, and
1689
     * View B would be the current view.
1690
     * @returns {object} Returns the back view.
1691
     */
1692
    backView: function(view) {
1693
      if (arguments.length) {
1694
        viewHistory.backView = view;
1695
      }
1696
      return viewHistory.backView;
1697
    },
1698
 
1699
    /**
1700
     * @ngdoc method
1701
     * @name $ionicHistory#backTitle
1702
     * @description Gets the back view's title.
1703
     * @returns {string} Returns the back view's title.
1704
     */
1705
    backTitle: function() {
1706
      if (viewHistory.backView) {
1707
        return viewHistory.backView.title;
1708
      }
1709
    },
1710
 
1711
    /**
1712
     * @ngdoc method
1713
     * @name $ionicHistory#forwardView
1714
     * @description Returns the view that was in front of the current view in the history stack.
1715
     * A forward view would exist if the user navigated from View A to View B, then
1716
     * navigated back to View A. At this point then View B would be the forward view, and View
1717
     * A would be the current view.
1718
     * @returns {object} Returns the forward view.
1719
     */
1720
    forwardView: function(view) {
1721
      if (arguments.length) {
1722
        viewHistory.forwardView = view;
1723
      }
1724
      return viewHistory.forwardView;
1725
    },
1726
 
1727
    /**
1728
     * @ngdoc method
1729
     * @name $ionicHistory#currentStateName
1730
     * @description Returns the current state name.
1731
     * @returns {string}
1732
     */
1733
    currentStateName: function() {
1734
      return ($state && $state.current ? $state.current.name : null);
1735
    },
1736
 
1737
    isCurrentStateNavView: function(navView) {
1738
      return !!($state && $state.current && $state.current.views && $state.current.views[navView]);
1739
    },
1740
 
1741
    goToHistoryRoot: function(historyId) {
1742
      if (historyId) {
1743
        var hist = getHistoryById(historyId);
1744
        if (hist && hist.stack.length) {
1745
          if (viewHistory.currentView && viewHistory.currentView.viewId === hist.stack[0].viewId) {
1746
            return;
1747
          }
1748
          forcedNav = {
1749
            viewId: hist.stack[0].viewId,
1750
            action: ACTION_MOVE_BACK,
1751
            direction: DIRECTION_BACK
1752
          };
1753
          hist.stack[0].go();
1754
        }
1755
      }
1756
    },
1757
 
1758
    /**
1759
     * @ngdoc method
1760
     * @name $ionicHistory#goBack
1761
     * @description Navigates the app to the back view, if a back view exists.
1762
     */
1763
    goBack: function() {
1764
      viewHistory.backView && viewHistory.backView.go();
1765
    },
1766
 
1767
    /**
1768
     * @ngdoc method
1769
     * @name $ionicHistory#clearHistory
1770
     * @description Clears out the app's entire history, except for the current view.
1771
     */
1772
    clearHistory: function() {
1773
      var
1774
      histories = viewHistory.histories,
1775
      currentView = viewHistory.currentView;
1776
 
1777
      if (histories) {
1778
        for (var historyId in histories) {
1779
 
1780
          if (histories[historyId].stack) {
1781
            histories[historyId].stack = [];
1782
            histories[historyId].cursor = -1;
1783
          }
1784
 
1785
          if (currentView && currentView.historyId === historyId) {
1786
            currentView.backViewId = currentView.forwardViewId = null;
1787
            histories[historyId].stack.push(currentView);
1788
          } else if (histories[historyId].destroy) {
1789
            histories[historyId].destroy();
1790
          }
1791
 
1792
        }
1793
      }
1794
 
1795
      for (var viewId in viewHistory.views) {
1796
        if (viewId !== currentView.viewId) {
1797
          delete viewHistory.views[viewId];
1798
        }
1799
      }
1800
 
1801
      if (currentView) {
1802
        setNavViews(currentView.viewId);
1803
      }
1804
    },
1805
 
1806
    /**
1807
     * @ngdoc method
1808
     * @name $ionicHistory#clearCache
1809
     * @description Removes all cached views within every {@link ionic.directive:ionNavView}.
1810
     * This both removes the view element from the DOM, and destroy it's scope.
1811
     */
1812
    clearCache: function() {
1813
      $ionicNavViewDelegate._instances.forEach(function(instance) {
1814
        instance.clearCache();
1815
      });
1816
    },
1817
 
1818
    /**
1819
     * @ngdoc method
1820
     * @name $ionicHistory#nextViewOptions
1821
     * @description Sets options for the next view. This method can be useful to override
1822
     * certain view/transition defaults right before a view transition happens. For example,
1823
     * the {@link ionic.directive:menuClose} directive uses this method internally to ensure
1824
     * an animated view transition does not happen when a side menu is open, and also sets
1825
     * the next view as the root of its history stack. After the transition these options
1826
     * are set back to null.
1827
     *
1828
     * Available options:
1829
     *
1830
     * * `disableAnimate`: Do not animate the next transition.
1831
     * * `disableBack`: The next view should forget its back view, and set it to null.
1832
     * * `historyRoot`: The next view should become the root view in its history stack.
1833
     *
1834
     * ```js
1835
     * $ionicHistory.nextViewOptions({
1836
     *   disableAnimate: true,
1837
     *   disableBack: true
1838
     * });
1839
     * ```
1840
     */
1841
    nextViewOptions: function(opts) {
1842
      if (arguments.length) {
1843
        $timeout.cancel(nextViewExpireTimer);
1844
        if (opts === null) {
1845
          nextViewOptions = opts;
1846
        } else {
1847
          nextViewOptions = nextViewOptions || {};
1848
          extend(nextViewOptions, opts);
1849
          if (nextViewOptions.expire) {
1850
            nextViewExpireTimer = $timeout(function(){
1851
              nextViewOptions = null;
1852
            }, nextViewOptions.expire);
1853
          }
1854
        }
1855
      }
1856
      return nextViewOptions;
1857
    },
1858
 
1859
    isAbstractEle: function(ele, viewLocals) {
1860
      if (viewLocals && viewLocals.$$state && viewLocals.$$state.self.abstract) {
1861
        return true;
1862
      }
1863
      return !!(ele && (isAbstractTag(ele) || isAbstractTag(ele.children())));
1864
    },
1865
 
1866
    isActiveScope: function(scope) {
1867
      if (!scope) return false;
1868
 
1869
      var climbScope = scope;
1870
      var currentHistoryId = this.currentHistoryId();
1871
      var foundHistoryId;
1872
 
1873
      while (climbScope) {
1874
        if (climbScope.$$disconnected) {
1875
          return false;
1876
        }
1877
 
1878
        if (!foundHistoryId && climbScope.hasOwnProperty('$historyId')) {
1879
          foundHistoryId = true;
1880
        }
1881
 
1882
        if (currentHistoryId) {
1883
          if (climbScope.hasOwnProperty('$historyId') && currentHistoryId == climbScope.$historyId) {
1884
            return true;
1885
          }
1886
          if (climbScope.hasOwnProperty('$activeHistoryId')) {
1887
            if (currentHistoryId == climbScope.$activeHistoryId) {
1888
              if (climbScope.hasOwnProperty('$historyId')) {
1889
                return true;
1890
              }
1891
              if (!foundHistoryId) {
1892
                return true;
1893
              }
1894
            }
1895
          }
1896
        }
1897
 
1898
        if (foundHistoryId && climbScope.hasOwnProperty('$activeHistoryId')) {
1899
          foundHistoryId = false;
1900
        }
1901
 
1902
        climbScope = climbScope.$parent;
1903
      }
1904
 
1905
      return currentHistoryId ? currentHistoryId == 'root' : true;
1906
    }
1907
 
1908
  };
1909
 
1910
  function isAbstractTag(ele) {
1911
    return ele && ele.length && /ion-side-menus|ion-tabs/i.test(ele[0].tagName);
1912
  }
1913
 
1914
}])
1915
 
1916
.run([
1917
  '$rootScope',
1918
  '$state',
1919
  '$location',
1920
  '$document',
1921
  '$ionicPlatform',
1922
  '$ionicHistory',
1923
function($rootScope, $state, $location, $document, $ionicPlatform, $ionicHistory) {
1924
 
1925
  // always reset the keyboard state when change stage
1926
  $rootScope.$on('$ionicView.beforeEnter', function() {
1927
    ionic.keyboard && ionic.keyboard.hide && ionic.keyboard.hide();
1928
  });
1929
 
1930
  $rootScope.$on('$ionicHistory.change', function(e, data) {
1931
    if (!data) return;
1932
 
1933
    var viewHistory = $ionicHistory.viewHistory();
1934
 
1935
    var hist = (data.historyId ? viewHistory.histories[ data.historyId ] : null);
1936
    if (hist && hist.cursor > -1 && hist.cursor < hist.stack.length) {
1937
      // the history they're going to already exists
1938
      // go to it's last view in its stack
1939
      var view = hist.stack[ hist.cursor ];
1940
      return view.go(data);
1941
    }
1942
 
1943
    // this history does not have a URL, but it does have a uiSref
1944
    // figure out its URL from the uiSref
1945
    if (!data.url && data.uiSref) {
1946
      data.url = $state.href(data.uiSref);
1947
    }
1948
 
1949
    if (data.url) {
1950
      // don't let it start with a #, messes with $location.url()
1951
      if (data.url.indexOf('#') === 0) {
1952
        data.url = data.url.replace('#', '');
1953
      }
1954
      if (data.url !== $location.url()) {
1955
        // we've got a good URL, ready GO!
1956
        $location.url(data.url);
1957
      }
1958
    }
1959
  });
1960
 
1961
  $rootScope.$ionicGoBack = function() {
1962
    $ionicHistory.goBack();
1963
  };
1964
 
1965
  // Set the document title when a new view is shown
1966
  $rootScope.$on('$ionicView.afterEnter', function(ev, data) {
1967
    if (data && data.title) {
1968
      $document[0].title = data.title;
1969
    }
1970
  });
1971
 
1972
  // Triggered when devices with a hardware back button (Android) is clicked by the user
1973
  // This is a Cordova/Phonegap platform specifc method
1974
  function onHardwareBackButton(e) {
1975
    var backView = $ionicHistory.backView();
1976
    if (backView) {
1977
      // there is a back view, go to it
1978
      backView.go();
1979
    } else {
1980
      // there is no back view, so close the app instead
1981
      ionic.Platform.exitApp();
1982
    }
1983
    e.preventDefault();
1984
    return false;
1985
  }
1986
  $ionicPlatform.registerBackButtonAction(
1987
    onHardwareBackButton,
1988
    PLATFORM_BACK_BUTTON_PRIORITY_VIEW
1989
  );
1990
 
1991
}]);
1992
 
1993
/**
1994
 * @ngdoc provider
1995
 * @name $ionicConfigProvider
1996
 * @module ionic
1997
 * @description
1998
 * Ionic automatically takes platform configurations into account to adjust things like what
1999
 * transition style to use and whether tab icons should show on the top or bottom. For example,
2000
 * iOS will move forward by transitioning the entering view from right to center and the leaving
2001
 * view from center to left. However, Android will transition with the entering view going from
2002
 * bottom to center, covering the previous view, which remains stationary. It should be noted
2003
 * that when a platform is not iOS or Android, then it'll default to iOS. So if you are
2004
 * developing on a desktop browser, it's going to take on iOS default configs.
2005
 *
2006
 * These configs can be changed using the `$ionicConfigProvider` during the configuration phase
2007
 * of your app. Additionally, `$ionicConfig` can also set and get config values during the run
2008
 * phase and within the app itself.
2009
 *
2010
 * By default, all base config variables are set to `'platform'`, which means it'll take on the
2011
 * default config of the platform on which it's running. Config variables can be set at this
2012
 * level so all platforms follow the same setting, rather than its platform config.
2013
 * The following code would set the same config variable for all platforms:
2014
 *
2015
 * ```js
2016
 * $ionicConfigProvider.views.maxCache(10);
2017
 * ```
2018
 *
2019
 * Additionally, each platform can have it's own config within the `$ionicConfigProvider.platform`
2020
 * property. The config below would only apply to Android devices.
2021
 *
2022
 * ```js
2023
 * $ionicConfigProvider.platform.android.views.maxCache(5);
2024
 * ```
2025
 *
2026
 * @usage
2027
 * ```js
2028
 * var myApp = angular.module('reallyCoolApp', ['ionic']);
2029
 *
2030
 * myApp.config(function($ionicConfigProvider) {
2031
 *   $ionicConfigProvider.views.maxCache(5);
2032
 *
2033
 *   // note that you can also chain configs
2034
 *   $ionicConfigProvider.backButton.text('Go Back').icon('ion-chevron-left');
2035
 * });
2036
 * ```
2037
 */
2038
 
2039
/**
2040
 * @ngdoc method
2041
 * @name $ionicConfigProvider#views.transition
2042
 * @description Animation style when transitioning between views. Default `platform`.
2043
 *
2044
 * @param {string} transition Which style of view transitioning to use.
2045
 *
2046
 * * `platform`: Dynamically choose the correct transition style depending on the platform
2047
 * the app is running from. If the platform is not `ios` or `android` then it will default
2048
 * to `ios`.
2049
 * * `ios`: iOS style transition.
2050
 * * `android`: Android style transition.
2051
 * * `none`: Do not preform animated transitions.
2052
 *
2053
 * @returns {string} value
2054
 */
2055
 
2056
/**
2057
 * @ngdoc method
2058
 * @name $ionicConfigProvider#views.maxCache
2059
 * @description  Maximum number of view elements to cache in the DOM. When the max number is
2060
 * exceeded, the view with the longest time period since it was accessed is removed. Views that
2061
 * stay in the DOM cache the view's scope, current state, and scroll position. The scope is
2062
 * disconnected from the `$watch` cycle when it is cached and reconnected when it enters again.
2063
 * When the maximum cache is `0`, the leaving view's element will be removed from the DOM after
2064
 * each view transition, and the next time the same view is shown, it will have to re-compile,
2065
 * attach to the DOM, and link the element again. This disables caching, in effect.
2066
 * @param {number} maxNumber Maximum number of views to retain. Default `10`.
2067
 * @returns {number} How many views Ionic will hold onto until the a view is removed.
2068
 */
2069
 
2070
/**
2071
 * @ngdoc method
2072
 * @name $ionicConfigProvider#views.forwardCache
2073
 * @description  By default, when navigating, views that were recently visited are cached, and
2074
 * the same instance data and DOM elements are referenced when navigating back. However, when
2075
 * navigating back in the history, the "forward" views are removed from the cache. If you
2076
 * navigate forward to the same view again, it'll create a new DOM element and controller
2077
 * instance. Basically, any forward views are reset each time. Set this config to `true` to have
2078
 * forward views cached and not reset on each load.
2079
 * @param {boolean} value
2080
 * @returns {boolean}
2081
 */
2082
 
2083
/**
2084
 * @ngdoc method
2085
 * @name $ionicConfigProvider#backButton.icon
2086
 * @description Back button icon.
2087
 * @param {string} value
2088
 * @returns {string}
2089
 */
2090
 
2091
/**
2092
 * @ngdoc method
2093
 * @name $ionicConfigProvider#backButton.text
2094
 * @description Back button text.
2095
 * @param {string} value Defaults to `Back`.
2096
 * @returns {string}
2097
 */
2098
 
2099
/**
2100
 * @ngdoc method
2101
 * @name $ionicConfigProvider#backButton.previousTitleText
2102
 * @description If the previous title text should become the back button text. This
2103
 * is the default for iOS.
2104
 * @param {boolean} value
2105
 * @returns {boolean}
2106
 */
2107
 
2108
/**
2109
 * @ngdoc method
2110
 * @name $ionicConfigProvider#tabs.style
2111
 * @description Tab style. Android defaults to `striped` and iOS defaults to `standard`.
2112
 * @param {string} value Available values include `striped` and `standard`.
2113
 * @returns {string}
2114
 */
2115
 
2116
/**
2117
 * @ngdoc method
2118
 * @name $ionicConfigProvider#tabs.position
2119
 * @description Tab position. Android defaults to `top` and iOS defaults to `bottom`.
2120
 * @param {string} value Available values include `top` and `bottom`.
2121
 * @returns {string}
2122
 */
2123
 
2124
/**
2125
 * @ngdoc method
2126
 * @name $ionicConfigProvider#templates.maxPrefetch
2127
 * @description Sets the maximum number of templates to prefetch from the templateUrls defined in
2128
 * $stateProvider.state. If set to `0`, the user will have to wait
2129
 * for a template to be fetched the first time when navigating to a new page. Default `30`.
2130
 * @param {integer} value Max number of template to prefetch from the templateUrls defined in
2131
 * `$stateProvider.state()`.
2132
 * @returns {integer}
2133
 */
2134
 
2135
/**
2136
 * @ngdoc method
2137
 * @name $ionicConfigProvider#navBar.alignTitle
2138
 * @description Which side of the navBar to align the title. Default `center`.
2139
 *
2140
 * @param {string} value side of the navBar to align the title.
2141
 *
2142
 * * `platform`: Dynamically choose the correct title style depending on the platform
2143
 * the app is running from. If the platform is `ios`, it will default to `center`.
2144
 * If the platform is `android`, it will default to `left`. If the platform is not
2145
 * `ios` or `android`, it will default to `center`.
2146
 *
2147
 * * `left`: Left align the title in the navBar
2148
 * * `center`: Center align the title in the navBar
2149
 * * `right`: Right align the title in the navBar.
2150
 *
2151
 * @returns {string} value
2152
 */
2153
 
2154
/**
2155
  * @ngdoc method
2156
  * @name $ionicConfigProvider#navBar.positionPrimaryButtons
2157
  * @description Which side of the navBar to align the primary navBar buttons. Default `left`.
2158
  *
2159
  * @param {string} value side of the navBar to align the primary navBar buttons.
2160
  *
2161
  * * `platform`: Dynamically choose the correct title style depending on the platform
2162
  * the app is running from. If the platform is `ios`, it will default to `left`.
2163
  * If the platform is `android`, it will default to `right`. If the platform is not
2164
  * `ios` or `android`, it will default to `left`.
2165
  *
2166
  * * `left`: Left align the primary navBar buttons in the navBar
2167
  * * `right`: Right align the primary navBar buttons in the navBar.
2168
  *
2169
  * @returns {string} value
2170
  */
2171
 
2172
/**
2173
 * @ngdoc method
2174
 * @name $ionicConfigProvider#navBar.positionSecondaryButtons
2175
 * @description Which side of the navBar to align the secondary navBar buttons. Default `right`.
2176
 *
2177
 * @param {string} value side of the navBar to align the secondary navBar buttons.
2178
 *
2179
 * * `platform`: Dynamically choose the correct title style depending on the platform
2180
 * the app is running from. If the platform is `ios`, it will default to `right`.
2181
 * If the platform is `android`, it will default to `right`. If the platform is not
2182
 * `ios` or `android`, it will default to `right`.
2183
 *
2184
 * * `left`: Left align the secondary navBar buttons in the navBar
2185
 * * `right`: Right align the secondary navBar buttons in the navBar.
2186
 *
2187
 * @returns {string} value
2188
 */
2189
 
2190
IonicModule
2191
.provider('$ionicConfig', function() {
2192
 
2193
  var provider = this;
2194
  provider.platform = {};
2195
  var PLATFORM = 'platform';
2196
 
2197
  var configProperties = {
2198
    views: {
2199
      maxCache: PLATFORM,
2200
      forwardCache: PLATFORM,
2201
      transition: PLATFORM
2202
    },
2203
    navBar: {
2204
      alignTitle: PLATFORM,
2205
      positionPrimaryButtons: PLATFORM,
2206
      positionSecondaryButtons: PLATFORM,
2207
      transition: PLATFORM
2208
    },
2209
    backButton: {
2210
      icon: PLATFORM,
2211
      text: PLATFORM,
2212
      previousTitleText: PLATFORM
2213
    },
2214
    form: {
2215
      checkbox: PLATFORM
2216
    },
2217
    tabs: {
2218
      style: PLATFORM,
2219
      position: PLATFORM
2220
    },
2221
    templates: {
2222
      maxPrefetch: PLATFORM
2223
    },
2224
    platform: {}
2225
  };
2226
  createConfig(configProperties, provider, '');
2227
 
2228
 
2229
 
2230
  // Default
2231
  // -------------------------
2232
  setPlatformConfig('default', {
2233
 
2234
    views: {
2235
      maxCache: 10,
2236
      forwardCache: false,
2237
      transition: 'ios'
2238
    },
2239
 
2240
    navBar: {
2241
      alignTitle: 'center',
2242
      positionPrimaryButtons: 'left',
2243
      positionSecondaryButtons: 'right',
2244
      transition: 'view'
2245
    },
2246
 
2247
    backButton: {
2248
      icon: 'ion-ios7-arrow-back',
2249
      text: 'Back',
2250
      previousTitleText: true
2251
    },
2252
 
2253
    form: {
2254
      checkbox: 'circle'
2255
    },
2256
 
2257
    tabs: {
2258
      style: 'standard',
2259
      position: 'bottom'
2260
    },
2261
 
2262
    templates: {
2263
      maxPrefetch: 30
2264
    }
2265
 
2266
  });
2267
 
2268
 
2269
 
2270
  // iOS (it is the default already)
2271
  // -------------------------
2272
  setPlatformConfig('ios', {});
2273
 
2274
 
2275
 
2276
  // Android
2277
  // -------------------------
2278
  setPlatformConfig('android', {
2279
 
2280
    views: {
2281
      transition: 'android'
2282
    },
2283
 
2284
    navBar: {
2285
      alignTitle: 'left',
2286
      positionPrimaryButtons: 'right',
2287
      positionSecondaryButtons: 'right'
2288
    },
2289
 
2290
    backButton: {
2291
      icon: 'ion-arrow-left-c',
2292
      text: false,
2293
      previousTitleText: false
2294
    },
2295
 
2296
    form: {
2297
      checkbox: 'square'
2298
    },
2299
 
2300
    tabs: {
2301
      style: 'striped',
2302
      position: 'top'
2303
    }
2304
 
2305
  });
2306
 
2307
 
2308
  provider.transitions = {
2309
    views: {},
2310
    navBar: {}
2311
  };
2312
 
2313
 
2314
  // iOS Transitions
2315
  // -----------------------
2316
  provider.transitions.views.ios = function(enteringEle, leavingEle, direction, shouldAnimate) {
2317
    shouldAnimate = shouldAnimate && (direction == 'forward' || direction == 'back');
2318
 
2319
    function setStyles(ele, opacity, x) {
2320
      var css = {};
2321
      css[ionic.CSS.TRANSITION_DURATION] = shouldAnimate ? '' : 0;
2322
      css.opacity = opacity;
2323
      css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';
2324
      ionic.DomUtil.cachedStyles(ele, css);
2325
    }
2326
 
2327
    return {
2328
      run: function(step) {
2329
        if (direction == 'forward') {
2330
          setStyles(enteringEle, 1, (1 - step) * 99); // starting at 98% prevents a flicker
2331
          setStyles(leavingEle, (1 - 0.1 * step), step * -33);
2332
 
2333
        } else if (direction == 'back') {
2334
          setStyles(enteringEle, (1 - 0.1 * (1 - step)), (1 - step) * -33);
2335
          setStyles(leavingEle, 1, step * 100);
2336
 
2337
        } else {
2338
          // swap, enter, exit
2339
          setStyles(enteringEle, 1, 0);
2340
          setStyles(leavingEle, 0, 0);
2341
        }
2342
      },
2343
      shouldAnimate: shouldAnimate
2344
    };
2345
  };
2346
 
2347
  provider.transitions.navBar.ios = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {
2348
    shouldAnimate = shouldAnimate && (direction == 'forward' || direction == 'back');
2349
 
2350
    function setStyles(ctrl, opacity, titleX, backTextX) {
2351
      var css = {};
2352
      css[ionic.CSS.TRANSITION_DURATION] = shouldAnimate ? '' : 0;
2353
      css.opacity = opacity === 1 ? '' : opacity;
2354
 
2355
      ctrl.setCss('buttons-left', css);
2356
      ctrl.setCss('buttons-right', css);
2357
      ctrl.setCss('back-button', css);
2358
 
2359
      css[ionic.CSS.TRANSFORM] = 'translate3d(' + backTextX + 'px,0,0)';
2360
      ctrl.setCss('back-text', css);
2361
 
2362
      css[ionic.CSS.TRANSFORM] = 'translate3d(' + titleX + 'px,0,0)';
2363
      ctrl.setCss('title', css);
2364
    }
2365
 
2366
    function enter(ctrlA, ctrlB, step) {
2367
      if (!ctrlA) return;
2368
      var titleX = (ctrlA.titleTextX() + ctrlA.titleWidth()) * (1 - step);
2369
      var backTextX = (ctrlB && (ctrlB.titleTextX() - ctrlA.backButtonTextLeft()) * (1 - step)) || 0;
2370
      setStyles(ctrlA, step, titleX, backTextX);
2371
    }
2372
 
2373
    function leave(ctrlA, ctrlB, step) {
2374
      if (!ctrlA) return;
2375
      var titleX = (-(ctrlA.titleTextX() - ctrlB.backButtonTextLeft()) - (ctrlA.titleLeftRight())) * step;
2376
      setStyles(ctrlA, 1 - step, titleX, 0);
2377
    }
2378
 
2379
    return {
2380
      run: function(step) {
2381
        var enteringHeaderCtrl = enteringHeaderBar.controller();
2382
        var leavingHeaderCtrl = leavingHeaderBar && leavingHeaderBar.controller();
2383
        if (direction == 'back') {
2384
          leave(enteringHeaderCtrl, leavingHeaderCtrl, 1 - step);
2385
          enter(leavingHeaderCtrl, enteringHeaderCtrl, 1 - step);
2386
        } else {
2387
          enter(enteringHeaderCtrl, leavingHeaderCtrl, step);
2388
          leave(leavingHeaderCtrl, enteringHeaderCtrl, step);
2389
        }
2390
      },
2391
      shouldAnimate: shouldAnimate
2392
    };
2393
  };
2394
 
2395
 
2396
  // Android Transitions
2397
  // -----------------------
2398
 
2399
  provider.transitions.views.android = function(enteringEle, leavingEle, direction, shouldAnimate) {
2400
    shouldAnimate = shouldAnimate && (direction == 'forward' || direction == 'back');
2401
 
2402
    function setStyles(ele, x) {
2403
      var css = {};
2404
      css[ionic.CSS.TRANSITION_DURATION] = shouldAnimate ? '' : 0;
2405
      css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';
2406
      ionic.DomUtil.cachedStyles(ele, css);
2407
    }
2408
 
2409
    return {
2410
      run: function(step) {
2411
        if (direction == 'forward') {
2412
          setStyles(enteringEle, (1 - step) * 99); // starting at 98% prevents a flicker
2413
          setStyles(leavingEle, step * -100);
2414
 
2415
        } else if (direction == 'back') {
2416
          setStyles(enteringEle, (1 - step) * -100);
2417
          setStyles(leavingEle, step * 100);
2418
 
2419
        } else {
2420
          // swap, enter, exit
2421
          setStyles(enteringEle, 0);
2422
          setStyles(leavingEle, 0);
2423
        }
2424
      },
2425
      shouldAnimate: shouldAnimate
2426
    };
2427
  };
2428
 
2429
  provider.transitions.navBar.android = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {
2430
    shouldAnimate = shouldAnimate && (direction == 'forward' || direction == 'back');
2431
 
2432
    function setStyles(ctrl, opacity) {
2433
      if (!ctrl) return;
2434
      var css = {};
2435
      css.opacity = opacity === 1 ? '' : opacity;
2436
 
2437
      ctrl.setCss('buttons-left', css);
2438
      ctrl.setCss('buttons-right', css);
2439
      ctrl.setCss('back-button', css);
2440
      ctrl.setCss('back-text', css);
2441
      ctrl.setCss('title', css);
2442
    }
2443
 
2444
    return {
2445
      run: function(step) {
2446
        setStyles(enteringHeaderBar.controller(), step);
2447
        setStyles(leavingHeaderBar && leavingHeaderBar.controller(), 1 - step);
2448
      },
2449
      shouldAnimate: true
2450
    };
2451
  };
2452
 
2453
 
2454
  // No Transition
2455
  // -----------------------
2456
 
2457
  provider.transitions.views.none = function(enteringEle, leavingEle) {
2458
    return {
2459
      run: function(step) {
2460
        provider.transitions.views.android(enteringEle, leavingEle, false, false).run(step);
2461
      }
2462
    };
2463
  };
2464
 
2465
  provider.transitions.navBar.none = function(enteringHeaderBar, leavingHeaderBar) {
2466
    return {
2467
      run: function(step) {
2468
        provider.transitions.navBar.ios(enteringHeaderBar, leavingHeaderBar, false, false).run(step);
2469
        provider.transitions.navBar.android(enteringHeaderBar, leavingHeaderBar, false, false).run(step);
2470
      }
2471
    };
2472
  };
2473
 
2474
 
2475
  // private: used to set platform configs
2476
  function setPlatformConfig(platformName, platformConfigs) {
2477
    configProperties.platform[platformName] = platformConfigs;
2478
    provider.platform[platformName] = {};
2479
 
2480
    addConfig(configProperties, configProperties.platform[platformName]);
2481
 
2482
    createConfig(configProperties.platform[platformName], provider.platform[platformName], '');
2483
  }
2484
 
2485
 
2486
  // private: used to recursively add new platform configs
2487
  function addConfig(configObj, platformObj) {
2488
    for (var n in configObj) {
2489
      if (n != PLATFORM && configObj.hasOwnProperty(n)) {
2490
        if (angular.isObject(configObj[n])) {
2491
          if (!isDefined(platformObj[n])) {
2492
            platformObj[n] = {};
2493
          }
2494
          addConfig(configObj[n], platformObj[n]);
2495
 
2496
        } else if (!isDefined(platformObj[n])) {
2497
          platformObj[n] = null;
2498
        }
2499
      }
2500
    }
2501
  }
2502
 
2503
 
2504
  // private: create methods for each config to get/set
2505
  function createConfig(configObj, providerObj, platformPath) {
2506
    forEach(configObj, function(value, namespace) {
2507
 
2508
      if (angular.isObject(configObj[namespace])) {
2509
        // recursively drill down the config object so we can create a method for each one
2510
        providerObj[namespace] = {};
2511
        createConfig(configObj[namespace], providerObj[namespace], platformPath + '.' + namespace);
2512
 
2513
      } else {
2514
        // create a method for the provider/config methods that will be exposed
2515
        providerObj[namespace] = function(newValue) {
2516
          if (arguments.length) {
2517
            configObj[namespace] = newValue;
2518
            return providerObj;
2519
          }
2520
          if (configObj[namespace] == PLATFORM) {
2521
            // if the config is set to 'platform', then get this config's platform value
2522
            var platformConfig = stringObj(configProperties.platform, ionic.Platform.platform() + platformPath + '.' + namespace);
2523
            if (platformConfig || platformConfig === false) {
2524
              return platformConfig;
2525
            }
2526
            // didnt find a specific platform config, now try the default
2527
            return stringObj(configProperties.platform, 'default' + platformPath + '.' + namespace);
2528
          }
2529
          return configObj[namespace];
2530
        };
2531
      }
2532
 
2533
    });
2534
  }
2535
 
2536
  function stringObj(obj, str) {
2537
    str = str.split(".");
2538
    for (var i = 0; i < str.length; i++) {
2539
      if (obj && isDefined(obj[str[i]])) {
2540
        obj = obj[str[i]];
2541
      } else {
2542
        return null;
2543
      }
2544
    }
2545
    return obj;
2546
  }
2547
 
2548
  provider.setPlatformConfig = setPlatformConfig;
2549
 
2550
 
2551
  // private: Service definition for internal Ionic use
2552
  /**
2553
   * @ngdoc service
2554
   * @name $ionicConfig
2555
   * @module ionic
2556
   * @private
2557
   */
2558
  provider.$get = function() {
2559
    return provider;
2560
  };
2561
});
2562
 
2563
 
2564
var LOADING_TPL =
2565
  '<div class="loading-container">' +
2566
    '<div class="loading">' +
2567
    '</div>' +
2568
  '</div>';
2569
 
2570
var LOADING_HIDE_DEPRECATED = '$ionicLoading instance.hide() has been deprecated. Use $ionicLoading.hide().';
2571
var LOADING_SHOW_DEPRECATED = '$ionicLoading instance.show() has been deprecated. Use $ionicLoading.show().';
2572
var LOADING_SET_DEPRECATED = '$ionicLoading instance.setContent() has been deprecated. Use $ionicLoading.show({ template: \'my content\' }).';
2573
 
2574
/**
2575
 * @ngdoc service
2576
 * @name $ionicLoading
2577
 * @module ionic
2578
 * @description
2579
 * An overlay that can be used to indicate activity while blocking user
2580
 * interaction.
2581
 *
2582
 * @usage
2583
 * ```js
2584
 * angular.module('LoadingApp', ['ionic'])
2585
 * .controller('LoadingCtrl', function($scope, $ionicLoading) {
2586
 *   $scope.show = function() {
2587
 *     $ionicLoading.show({
2588
 *       template: 'Loading...'
2589
 *     });
2590
 *   };
2591
 *   $scope.hide = function(){
2592
 *     $ionicLoading.hide();
2593
 *   };
2594
 * });
2595
 * ```
2596
 */
2597
/**
2598
 * @ngdoc object
2599
 * @name $ionicLoadingConfig
2600
 * @module ionic
2601
 * @description
2602
 * Set the default options to be passed to the {@link ionic.service:$ionicLoading} service.
2603
 *
2604
 * @usage
2605
 * ```js
2606
 * var app = angular.module('myApp', ['ionic'])
2607
 * app.constant('$ionicLoadingConfig', {
2608
 *   template: 'Default Loading Template...'
2609
 * });
2610
 * app.controller('AppCtrl', function($scope, $ionicLoading) {
2611
 *   $scope.showLoading = function() {
2612
 *     $ionicLoading.show(); //options default to values in $ionicLoadingConfig
2613
 *   };
2614
 * });
2615
 * ```
2616
 */
2617
IonicModule
2618
.constant('$ionicLoadingConfig', {
2619
  template: '<i class="icon ion-loading-d"></i>'
2620
})
2621
.factory('$ionicLoading', [
2622
  '$ionicLoadingConfig',
2623
  '$ionicBody',
2624
  '$ionicTemplateLoader',
2625
  '$ionicBackdrop',
2626
  '$timeout',
2627
  '$q',
2628
  '$log',
2629
  '$compile',
2630
  '$ionicPlatform',
2631
  '$rootScope',
2632
function($ionicLoadingConfig, $ionicBody, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $compile, $ionicPlatform, $rootScope) {
2633
 
2634
  var loaderInstance;
2635
  //default values
2636
  var deregisterBackAction = angular.noop;
2637
  var deregisterStateListener = angular.noop;
2638
  var loadingShowDelay = $q.when();
2639
 
2640
  return {
2641
    /**
2642
     * @ngdoc method
2643
     * @name $ionicLoading#show
2644
     * @description Shows a loading indicator. If the indicator is already shown,
2645
     * it will set the options given and keep the indicator shown.
2646
     * @param {object} opts The options for the loading indicator. Available properties:
2647
     *  - `{string=}` `template` The html content of the indicator.
2648
     *  - `{string=}` `templateUrl` The url of an html template to load as the content of the indicator.
2649
     *  - `{object=}` `scope` The scope to be a child of. Default: creates a child of $rootScope.
2650
     *  - `{boolean=}` `noBackdrop` Whether to hide the backdrop. By default it will be shown.
2651
     *  - `{boolean=}` `hideOnStateChange` Whether to hide the loading spinner when navigating
2652
     *    to a new state. Default false.
2653
     *  - `{number=}` `delay` How many milliseconds to delay showing the indicator. By default there is no delay.
2654
     *  - `{number=}` `duration` How many milliseconds to wait until automatically
2655
     *  hiding the indicator. By default, the indicator will be shown until `.hide()` is called.
2656
     */
2657
    show: showLoader,
2658
    /**
2659
     * @ngdoc method
2660
     * @name $ionicLoading#hide
2661
     * @description Hides the loading indicator, if shown.
2662
     */
2663
    hide: hideLoader,
2664
    /**
2665
     * @private for testing
2666
     */
2667
    _getLoader: getLoader
2668
  };
2669
 
2670
  function getLoader() {
2671
    if (!loaderInstance) {
2672
      loaderInstance = $ionicTemplateLoader.compile({
2673
        template: LOADING_TPL,
2674
        appendTo: $ionicBody.get()
2675
      })
2676
      .then(function(loader) {
2677
        var self = loader;
2678
 
2679
        loader.show = function(options) {
2680
          var templatePromise = options.templateUrl ?
2681
            $ionicTemplateLoader.load(options.templateUrl) :
2682
            //options.content: deprecated
2683
            $q.when(options.template || options.content || '');
2684
 
2685
          self.scope = options.scope || self.scope;
2686
 
2687
          if (!this.isShown) {
2688
            //options.showBackdrop: deprecated
2689
            this.hasBackdrop = !options.noBackdrop && options.showBackdrop !== false;
2690
            if (this.hasBackdrop) {
2691
              $ionicBackdrop.retain();
2692
              $ionicBackdrop.getElement().addClass('backdrop-loading');
2693
            }
2694
          }
2695
 
2696
          if (options.duration) {
2697
            $timeout.cancel(this.durationTimeout);
2698
            this.durationTimeout = $timeout(
2699
              angular.bind(this, this.hide),
2700
              +options.duration
2701
            );
2702
          }
2703
 
2704
          deregisterBackAction();
2705
          //Disable hardware back button while loading
2706
          deregisterBackAction = $ionicPlatform.registerBackButtonAction(
2707
            angular.noop,
2708
            PLATFORM_BACK_BUTTON_PRIORITY_LOADING
2709
          );
2710
 
2711
          templatePromise.then(function(html) {
2712
            if (html) {
2713
              var loading = self.element.children();
2714
              loading.html(html);
2715
              $compile(loading.contents())(self.scope);
2716
            }
2717
 
2718
            //Don't show until template changes
2719
            if (self.isShown) {
2720
              self.element.addClass('visible');
2721
              ionic.requestAnimationFrame(function() {
2722
                if(self.isShown) {
2723
                  self.element.addClass('active');
2724
                  $ionicBody.addClass('loading-active');
2725
                }
2726
              });
2727
            }
2728
          });
2729
 
2730
          this.isShown = true;
2731
        };
2732
        loader.hide = function() {
2733
 
2734
          deregisterBackAction();
2735
          if (this.isShown) {
2736
            if (this.hasBackdrop) {
2737
              $ionicBackdrop.release();
2738
              $ionicBackdrop.getElement().removeClass('backdrop-loading');
2739
            }
2740
            self.element.removeClass('active');
2741
            $ionicBody.removeClass('loading-active');
2742
            setTimeout(function() {
2743
              !self.isShown && self.element.removeClass('visible');
2744
            }, 200);
2745
          }
2746
          $timeout.cancel(this.durationTimeout);
2747
          this.isShown = false;
2748
        };
2749
 
2750
        return loader;
2751
      });
2752
    }
2753
    return loaderInstance;
2754
  }
2755
 
2756
  function showLoader(options) {
2757
    options = extend({}, $ionicLoadingConfig || {}, options || {});
2758
    var delay = options.delay || options.showDelay || 0;
2759
 
2760
    //If loading.show() was called previously, cancel it and show with our new options
2761
    loadingShowDelay && $timeout.cancel(loadingShowDelay);
2762
    loadingShowDelay = $timeout(angular.noop, delay);
2763
 
2764
    loadingShowDelay.then(getLoader).then(function(loader) {
2765
      if (options.hideOnStateChange) {
2766
        deregisterStateListener = $rootScope.$on('$stateChangeSuccess', hideLoader);
2767
      }
2768
      return loader.show(options);
2769
    });
2770
 
2771
    return {
2772
      hide: deprecated.method(LOADING_HIDE_DEPRECATED, $log.error, hideLoader),
2773
      show: deprecated.method(LOADING_SHOW_DEPRECATED, $log.error, function() {
2774
        showLoader(options);
2775
      }),
2776
      setContent: deprecated.method(LOADING_SET_DEPRECATED, $log.error, function(content) {
2777
        getLoader().then(function(loader) {
2778
          loader.show({ template: content });
2779
        });
2780
      })
2781
    };
2782
  }
2783
 
2784
  function hideLoader() {
2785
    deregisterStateListener();
2786
    $timeout.cancel(loadingShowDelay);
2787
    getLoader().then(function(loader) {
2788
      loader.hide();
2789
    });
2790
  }
2791
}]);
2792
 
2793
/**
2794
 * @ngdoc service
2795
 * @name $ionicModal
2796
 * @module ionic
2797
 * @description
2798
 *
2799
 * Related: {@link ionic.controller:ionicModal ionicModal controller}.
2800
 *
2801
 * The Modal is a content pane that can go over the user's main view
2802
 * temporarily.  Usually used for making a choice or editing an item.
2803
 *
2804
 * Put the content of the modal inside of an `<ion-modal-view>` element.
2805
 *
2806
 * **Notes:**
2807
 * - A modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating
2808
 * scope, passing in itself as an event argument. Both the modal.removed and modal.hidden events are
2809
 * called when the modal is removed.
2810
 *
2811
 * - This example assumes your modal is in your main index file or another template file. If it is in its own
2812
 * template file, remove the script tags and call it by file name.
2813
 *
2814
 * @usage
2815
 * ```html
2816
 * <script id="my-modal.html" type="text/ng-template">
2817
 *   <ion-modal-view>
2818
 *     <ion-header-bar>
2819
 *       <h1 class="title">My Modal title</h1>
2820
 *     </ion-header-bar>
2821
 *     <ion-content>
2822
 *       Hello!
2823
 *     </ion-content>
2824
 *   </ion-modal-view>
2825
 * </script>
2826
 * ```
2827
 * ```js
2828
 * angular.module('testApp', ['ionic'])
2829
 * .controller('MyController', function($scope, $ionicModal) {
2830
 *   $ionicModal.fromTemplateUrl('my-modal.html', {
2831
 *     scope: $scope,
2832
 *     animation: 'slide-in-up'
2833
 *   }).then(function(modal) {
2834
 *     $scope.modal = modal;
2835
 *   });
2836
 *   $scope.openModal = function() {
2837
 *     $scope.modal.show();
2838
 *   };
2839
 *   $scope.closeModal = function() {
2840
 *     $scope.modal.hide();
2841
 *   };
2842
 *   //Cleanup the modal when we're done with it!
2843
 *   $scope.$on('$destroy', function() {
2844
 *     $scope.modal.remove();
2845
 *   });
2846
 *   // Execute action on hide modal
2847
 *   $scope.$on('modal.hidden', function() {
2848
 *     // Execute action
2849
 *   });
2850
 *   // Execute action on remove modal
2851
 *   $scope.$on('modal.removed', function() {
2852
 *     // Execute action
2853
 *   });
2854
 * });
2855
 * ```
2856
 */
2857
IonicModule
2858
.factory('$ionicModal', [
2859
  '$rootScope',
2860
  '$ionicBody',
2861
  '$compile',
2862
  '$timeout',
2863
  '$ionicPlatform',
2864
  '$ionicTemplateLoader',
2865
  '$q',
2866
  '$log',
2867
function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $q, $log) {
2868
 
2869
  /**
2870
   * @ngdoc controller
2871
   * @name ionicModal
2872
   * @module ionic
2873
   * @description
2874
   * Instantiated by the {@link ionic.service:$ionicModal} service.
2875
   *
2876
   * Be sure to call [remove()](#remove) when you are done with each modal
2877
   * to clean it up and avoid memory leaks.
2878
   *
2879
   * Note: a modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating
2880
   * scope, passing in itself as an event argument. Note: both modal.removed and modal.hidden are
2881
   * called when the modal is removed.
2882
   */
2883
  var ModalView = ionic.views.Modal.inherit({
2884
    /**
2885
     * @ngdoc method
2886
     * @name ionicModal#initialize
2887
     * @description Creates a new modal controller instance.
2888
     * @param {object} options An options object with the following properties:
2889
     *  - `{object=}` `scope` The scope to be a child of.
2890
     *    Default: creates a child of $rootScope.
2891
     *  - `{string=}` `animation` The animation to show & hide with.
2892
     *    Default: 'slide-in-up'
2893
     *  - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of
2894
     *    the modal when shown.  Default: false.
2895
     *  - `{boolean=}` `backdropClickToClose` Whether to close the modal on clicking the backdrop.
2896
     *    Default: true.
2897
     *  - `{boolean=}` `hardwareBackButtonClose` Whether the modal can be closed using the hardware
2898
     *    back button on Android and similar devices.  Default: true.
2899
     */
2900
    initialize: function(opts) {
2901
      ionic.views.Modal.prototype.initialize.call(this, opts);
2902
      this.animation = opts.animation || 'slide-in-up';
2903
    },
2904
 
2905
    /**
2906
     * @ngdoc method
2907
     * @name ionicModal#show
2908
     * @description Show this modal instance.
2909
     * @returns {promise} A promise which is resolved when the modal is finished animating in.
2910
     */
2911
    show: function(target) {
2912
      var self = this;
2913
 
2914
      if (self.scope.$$destroyed) {
2915
        $log.error('Cannot call ' +  self.viewType + '.show() after remove(). Please create a new ' +  self.viewType + ' instance.');
2916
        return;
2917
      }
2918
 
2919
      var modalEl = jqLite(self.modalEl);
2920
 
2921
      self.el.classList.remove('hide');
2922
      $timeout(function() {
2923
        $ionicBody.addClass(self.viewType + '-open');
2924
      }, 400);
2925
 
2926
      if (!self.el.parentElement) {
2927
        modalEl.addClass(self.animation);
2928
        $ionicBody.append(self.el);
2929
      }
2930
 
2931
      if (target && self.positionView) {
2932
        self.positionView(target, modalEl);
2933
        // set up a listener for in case the window size changes
2934
        ionic.on('resize',function() {
2935
          ionic.off('resize',null,window);
2936
          self.positionView(target,modalEl);
2937
        },window);
2938
      }
2939
 
2940
      modalEl.addClass('ng-enter active')
2941
             .removeClass('ng-leave ng-leave-active');
2942
 
2943
      self._isShown = true;
2944
      self._deregisterBackButton = $ionicPlatform.registerBackButtonAction(
2945
        self.hardwareBackButtonClose ? angular.bind(self, self.hide) : angular.noop,
2946
        PLATFORM_BACK_BUTTON_PRIORITY_MODAL
2947
      );
2948
 
2949
      self._isOpenPromise = $q.defer();
2950
 
2951
      ionic.views.Modal.prototype.show.call(self);
2952
 
2953
      $timeout(function() {
2954
        modalEl.addClass('ng-enter-active');
2955
        ionic.trigger('resize');
2956
        self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.shown', self);
2957
        self.el.classList.add('active');
2958
        self.scope.$broadcast('$ionicHeader.align');
2959
      }, 20);
2960
 
2961
      return $timeout(function() {
2962
        //After animating in, allow hide on backdrop click
2963
        self.$el.on('click', function(e) {
2964
          if (self.backdropClickToClose && e.target === self.el) {
2965
            self.hide();
2966
          }
2967
        });
2968
      }, 400);
2969
    },
2970
 
2971
    /**
2972
     * @ngdoc method
2973
     * @name ionicModal#hide
2974
     * @description Hide this modal instance.
2975
     * @returns {promise} A promise which is resolved when the modal is finished animating out.
2976
     */
2977
    hide: function() {
2978
      var self = this;
2979
      var modalEl = jqLite(self.modalEl);
2980
 
2981
      self.el.classList.remove('active');
2982
      modalEl.addClass('ng-leave');
2983
 
2984
      $timeout(function() {
2985
        modalEl.addClass('ng-leave-active')
2986
               .removeClass('ng-enter ng-enter-active active');
2987
      }, 20);
2988
 
2989
      self.$el.off('click');
2990
      self._isShown = false;
2991
      self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.hidden', self);
2992
      self._deregisterBackButton && self._deregisterBackButton();
2993
 
2994
      ionic.views.Modal.prototype.hide.call(self);
2995
 
2996
      // clean up event listeners
2997
      if (self.positionView) {
2998
        ionic.off('resize',null,window);
2999
      }
3000
 
3001
      return $timeout(function() {
3002
        $ionicBody.removeClass(self.viewType + '-open');
3003
        self.el.classList.add('hide');
3004
      }, self.hideDelay || 320);
3005
    },
3006
 
3007
    /**
3008
     * @ngdoc method
3009
     * @name ionicModal#remove
3010
     * @description Remove this modal instance from the DOM and clean up.
3011
     * @returns {promise} A promise which is resolved when the modal is finished animating out.
3012
     */
3013
    remove: function() {
3014
      var self = this;
3015
      self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.removed', self);
3016
 
3017
      return self.hide().then(function() {
3018
        self.scope.$destroy();
3019
        self.$el.remove();
3020
      });
3021
    },
3022
 
3023
    /**
3024
     * @ngdoc method
3025
     * @name ionicModal#isShown
3026
     * @returns boolean Whether this modal is currently shown.
3027
     */
3028
    isShown: function() {
3029
      return !!this._isShown;
3030
    }
3031
  });
3032
 
3033
  var createModal = function(templateString, options) {
3034
    // Create a new scope for the modal
3035
    var scope = options.scope && options.scope.$new() || $rootScope.$new(true);
3036
 
3037
    options.viewType = options.viewType || 'modal';
3038
 
3039
    extend(scope, {
3040
      $hasHeader: false,
3041
      $hasSubheader: false,
3042
      $hasFooter: false,
3043
      $hasSubfooter: false,
3044
      $hasTabs: false,
3045
      $hasTabsTop: false
3046
    });
3047
 
3048
    // Compile the template
3049
    var element = $compile('<ion-' + options.viewType + '>' + templateString + '</ion-' + options.viewType + '>')(scope);
3050
 
3051
    options.$el = element;
3052
    options.el = element[0];
3053
    options.modalEl = options.el.querySelector('.' + options.viewType);
3054
    var modal = new ModalView(options);
3055
 
3056
    modal.scope = scope;
3057
 
3058
    // If this wasn't a defined scope, we can assign the viewType to the isolated scope
3059
    // we created
3060
    if (!options.scope) {
3061
      scope[ options.viewType ] = modal;
3062
    }
3063
 
3064
    return modal;
3065
  };
3066
 
3067
  return {
3068
    /**
3069
     * @ngdoc method
3070
     * @name $ionicModal#fromTemplate
3071
     * @param {string} templateString The template string to use as the modal's
3072
     * content.
3073
     * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
3074
     * @returns {object} An instance of an {@link ionic.controller:ionicModal}
3075
     * controller.
3076
     */
3077
    fromTemplate: function(templateString, options) {
3078
      var modal = createModal(templateString, options || {});
3079
      return modal;
3080
    },
3081
    /**
3082
     * @ngdoc method
3083
     * @name $ionicModal#fromTemplateUrl
3084
     * @param {string} templateUrl The url to load the template from.
3085
     * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
3086
     * options object.
3087
     * @returns {promise} A promise that will be resolved with an instance of
3088
     * an {@link ionic.controller:ionicModal} controller.
3089
     */
3090
    fromTemplateUrl: function(url, options, _) {
3091
      var cb;
3092
      //Deprecated: allow a callback as second parameter. Now we return a promise.
3093
      if (angular.isFunction(options)) {
3094
        cb = options;
3095
        options = _;
3096
      }
3097
      return $ionicTemplateLoader.load(url).then(function(templateString) {
3098
        var modal = createModal(templateString, options || {});
3099
        cb && cb(modal);
3100
        return modal;
3101
      });
3102
    }
3103
  };
3104
}]);
3105
 
3106
 
3107
/**
3108
 * @ngdoc service
3109
 * @name $ionicNavBarDelegate
3110
 * @module ionic
3111
 * @description
3112
 * Delegate for controlling the {@link ionic.directive:ionNavBar} directive.
3113
 *
3114
 * @usage
3115
 *
3116
 * ```html
3117
 * <body ng-controller="MyCtrl">
3118
 *   <ion-nav-bar>
3119
 *     <button ng-click="setNavTitle('banana')">
3120
 *       Set title to banana!
3121
 *     </button>
3122
 *   </ion-nav-bar>
3123
 * </body>
3124
 * ```
3125
 * ```js
3126
 * function MyCtrl($scope, $ionicNavBarDelegate) {
3127
 *   $scope.setNavTitle = function(title) {
3128
 *     $ionicNavBarDelegate.title(title);
3129
 *   }
3130
 * }
3131
 * ```
3132
 */
3133
IonicModule
3134
.service('$ionicNavBarDelegate', ionic.DelegateService([
3135
  /**
3136
   * @ngdoc method
3137
   * @name $ionicNavBarDelegate#align
3138
   * @description Aligns the title with the buttons in a given direction.
3139
   * @param {string=} direction The direction to the align the title text towards.
3140
   * Available: 'left', 'right', 'center'. Default: 'center'.
3141
   */
3142
  'align',
3143
  /**
3144
   * @ngdoc method
3145
   * @name $ionicNavBarDelegate#showBackButton
3146
   * @description
3147
   * Set/get whether the {@link ionic.directive:ionNavBackButton} is shown
3148
   * (if it exists and there is a previous view that can be navigated to).
3149
   * @param {boolean=} show Whether to show the back button.
3150
   * @returns {boolean} Whether the back button is shown.
3151
   */
3152
  'showBackButton',
3153
  /**
3154
   * @ngdoc method
3155
   * @name $ionicNavBarDelegate#showBar
3156
   * @description
3157
   * Set/get whether the {@link ionic.directive:ionNavBar} is shown.
3158
   * @param {boolean} show Whether to show the bar.
3159
   * @returns {boolean} Whether the bar is shown.
3160
   */
3161
  'showBar',
3162
  /**
3163
   * @ngdoc method
3164
   * @name $ionicNavBarDelegate#title
3165
   * @description
3166
   * Set the title for the {@link ionic.directive:ionNavBar}.
3167
   * @param {string} title The new title to show.
3168
   */
3169
  'title',
3170
 
3171
  // DEPRECATED, as of v1.0.0-beta14 -------
3172
  'changeTitle',
3173
  'setTitle',
3174
  'getTitle',
3175
  'back',
3176
  'getPreviousTitle'
3177
  // END DEPRECATED -------
3178
]));
3179
 
3180
 
3181
IonicModule
3182
.service('$ionicNavViewDelegate', ionic.DelegateService([
3183
  'clearCache'
3184
]));
3185
 
3186
 
3187
var PLATFORM_BACK_BUTTON_PRIORITY_VIEW = 100;
3188
var PLATFORM_BACK_BUTTON_PRIORITY_SIDE_MENU = 150;
3189
var PLATFORM_BACK_BUTTON_PRIORITY_MODAL = 200;
3190
var PLATFORM_BACK_BUTTON_PRIORITY_ACTION_SHEET = 300;
3191
var PLATFORM_BACK_BUTTON_PRIORITY_POPUP = 400;
3192
var PLATFORM_BACK_BUTTON_PRIORITY_LOADING = 500;
3193
 
3194
/**
3195
 * @ngdoc service
3196
 * @name $ionicPlatform
3197
 * @module ionic
3198
 * @description
3199
 * An angular abstraction of {@link ionic.utility:ionic.Platform}.
3200
 *
3201
 * Used to detect the current platform, as well as do things like override the
3202
 * Android back button in PhoneGap/Cordova.
3203
 */
3204
IonicModule
3205
.provider('$ionicPlatform', function() {
3206
  return {
3207
    $get: ['$q', '$rootScope', function($q, $rootScope) {
3208
      var self = {
3209
 
3210
        /**
3211
         * @ngdoc method
3212
         * @name $ionicPlatform#onHardwareBackButton
3213
         * @description
3214
         * Some platforms have a hardware back button, so this is one way to
3215
         * bind to it.
3216
         * @param {function} callback the callback to trigger when this event occurs
3217
         */
3218
        onHardwareBackButton: function(cb) {
3219
          ionic.Platform.ready(function() {
3220
            document.addEventListener('backbutton', cb, false);
3221
          });
3222
        },
3223
 
3224
        /**
3225
         * @ngdoc method
3226
         * @name $ionicPlatform#offHardwareBackButton
3227
         * @description
3228
         * Remove an event listener for the backbutton.
3229
         * @param {function} callback The listener function that was
3230
         * originally bound.
3231
         */
3232
        offHardwareBackButton: function(fn) {
3233
          ionic.Platform.ready(function() {
3234
            document.removeEventListener('backbutton', fn);
3235
          });
3236
        },
3237
 
3238
        /**
3239
         * @ngdoc method
3240
         * @name $ionicPlatform#registerBackButtonAction
3241
         * @description
3242
         * Register a hardware back button action. Only one action will execute
3243
         * when the back button is clicked, so this method decides which of
3244
         * the registered back button actions has the highest priority.
3245
         *
3246
         * For example, if an actionsheet is showing, the back button should
3247
         * close the actionsheet, but it should not also go back a page view
3248
         * or close a modal which may be open.
3249
         *
3250
         * @param {function} callback Called when the back button is pressed,
3251
         * if this listener is the highest priority.
3252
         * @param {number} priority Only the highest priority will execute.
3253
         * @param {*=} actionId The id to assign this action. Default: a
3254
         * random unique id.
3255
         * @returns {function} A function that, when called, will deregister
3256
         * this backButtonAction.
3257
         */
3258
        $backButtonActions: {},
3259
        registerBackButtonAction: function(fn, priority, actionId) {
3260
 
3261
          if (!self._hasBackButtonHandler) {
3262
            // add a back button listener if one hasn't been setup yet
3263
            self.$backButtonActions = {};
3264
            self.onHardwareBackButton(self.hardwareBackButtonClick);
3265
            self._hasBackButtonHandler = true;
3266
          }
3267
 
3268
          var action = {
3269
            id: (actionId ? actionId : ionic.Utils.nextUid()),
3270
            priority: (priority ? priority : 0),
3271
            fn: fn
3272
          };
3273
          self.$backButtonActions[action.id] = action;
3274
 
3275
          // return a function to de-register this back button action
3276
          return function() {
3277
            delete self.$backButtonActions[action.id];
3278
          };
3279
        },
3280
 
3281
        /**
3282
         * @private
3283
         */
3284
        hardwareBackButtonClick: function(e) {
3285
          // loop through all the registered back button actions
3286
          // and only run the last one of the highest priority
3287
          var priorityAction, actionId;
3288
          for (actionId in self.$backButtonActions) {
3289
            if (!priorityAction || self.$backButtonActions[actionId].priority >= priorityAction.priority) {
3290
              priorityAction = self.$backButtonActions[actionId];
3291
            }
3292
          }
3293
          if (priorityAction) {
3294
            priorityAction.fn(e);
3295
            return priorityAction;
3296
          }
3297
        },
3298
 
3299
        is: function(type) {
3300
          return ionic.Platform.is(type);
3301
        },
3302
 
3303
        /**
3304
         * @ngdoc method
3305
         * @name $ionicPlatform#on
3306
         * @description
3307
         * Add Cordova event listeners, such as `pause`, `resume`, `volumedownbutton`, `batterylow`,
3308
         * `offline`, etc. More information about available event types can be found in
3309
         * [Cordova's event documentation](https://cordova.apache.org/docs/en/edge/cordova_events_events.md.html#Events).
3310
         * @param {string} type Cordova [event type](https://cordova.apache.org/docs/en/edge/cordova_events_events.md.html#Events).
3311
         * @param {function} callback Called when the Cordova event is fired.
3312
         * @returns {function} Returns a deregistration function to remove the event listener.
3313
         */
3314
        on: function(type, cb) {
3315
          ionic.Platform.ready(function() {
3316
            document.addEventListener(type, cb, false);
3317
          });
3318
          return function() {
3319
            ionic.Platform.ready(function() {
3320
              document.removeEventListener(type, cb);
3321
            });
3322
          };
3323
        },
3324
 
3325
        /**
3326
         * @ngdoc method
3327
         * @name $ionicPlatform#ready
3328
         * @description
3329
         * Trigger a callback once the device is ready,
3330
         * or immediately if the device is already ready.
3331
         * @param {function=} callback The function to call.
3332
         * @returns {promise} A promise which is resolved when the device is ready.
3333
         */
3334
        ready: function(cb) {
3335
          var q = $q.defer();
3336
 
3337
          ionic.Platform.ready(function() {
3338
            q.resolve();
3339
            cb && cb();
3340
          });
3341
 
3342
          return q.promise;
3343
        }
3344
      };
3345
      return self;
3346
    }]
3347
  };
3348
 
3349
});
3350
 
3351
/**
3352
 * @ngdoc service
3353
 * @name $ionicPopover
3354
 * @module ionic
3355
 * @description
3356
 *
3357
 * Related: {@link ionic.controller:ionicPopover ionicPopover controller}.
3358
 *
3359
 * The Popover is a view that floats above an app’s content. Popovers provide an
3360
 * easy way to present or gather information from the user and are
3361
 * commonly used in the following situations:
3362
 *
3363
 * - Show more info about the current view
3364
 * - Select a commonly used tool or configuration
3365
 * - Present a list of actions to perform inside one of your views
3366
 *
3367
 * Put the content of the popover inside of an `<ion-popover-view>` element.
3368
 *
3369
 * @usage
3370
 * ```html
3371
 * <p>
3372
 *   <button ng-click="openPopover($event)">Open Popover</button>
3373
 * </p>
3374
 *
3375
 * <script id="my-popover.html" type="text/ng-template">
3376
 *   <ion-popover-view>
3377
 *     <ion-header-bar>
3378
 *       <h1 class="title">My Popover Title</h1>
3379
 *     </ion-header-bar>
3380
 *     <ion-content>
3381
 *       Hello!
3382
 *     </ion-content>
3383
 *   </ion-popover-view>
3384
 * </script>
3385
 * ```
3386
 * ```js
3387
 * angular.module('testApp', ['ionic'])
3388
 * .controller('MyController', function($scope, $ionicPopover) {
3389
 *
3390
 *   // .fromTemplate() method
3391
 *   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>';
3392
 *
3393
 *   $scope.popover = $ionicPopover.fromTemplate(template, {
3394
 *     scope: $scope,
3395
 *   });
3396
 *
3397
 *   // .fromTemplateUrl() method
3398
 *   $ionicPopover.fromTemplateUrl('my-popover.html', {
3399
 *     scope: $scope,
3400
 *   }).then(function(popover) {
3401
 *     $scope.popover = popover;
3402
 *   });
3403
 *
3404
 *
3405
 *   $scope.openPopover = function($event) {
3406
 *     $scope.popover.show($event);
3407
 *   };
3408
 *   $scope.closePopover = function() {
3409
 *     $scope.popover.hide();
3410
 *   };
3411
 *   //Cleanup the popover when we're done with it!
3412
 *   $scope.$on('$destroy', function() {
3413
 *     $scope.popover.remove();
3414
 *   });
3415
 *   // Execute action on hide popover
3416
 *   $scope.$on('popover.hidden', function() {
3417
 *     // Execute action
3418
 *   });
3419
 *   // Execute action on remove popover
3420
 *   $scope.$on('popover.removed', function() {
3421
 *     // Execute action
3422
 *   });
3423
 * });
3424
 * ```
3425
 */
3426
 
3427
 
3428
IonicModule
3429
.factory('$ionicPopover', ['$ionicModal', '$ionicPosition', '$document', '$window',
3430
function($ionicModal, $ionicPosition, $document, $window) {
3431
 
3432
  var POPOVER_BODY_PADDING = 6;
3433
 
3434
  var POPOVER_OPTIONS = {
3435
    viewType: 'popover',
3436
    hideDelay: 1,
3437
    animation: 'none',
3438
    positionView: positionView
3439
  };
3440
 
3441
  function positionView(target, popoverEle) {
3442
    var targetEle = angular.element(target.target || target);
3443
    var buttonOffset = $ionicPosition.offset(targetEle);
3444
    var popoverWidth = popoverEle.prop('offsetWidth');
3445
    var popoverHeight = popoverEle.prop('offsetHeight');
3446
    var bodyWidth = $document[0].body.clientWidth;
3447
    // clientHeight doesn't work on all platforms for body
3448
    var bodyHeight = $window.innerHeight;
3449
 
3450
    var popoverCSS = {
3451
      left: buttonOffset.left + buttonOffset.width / 2 - popoverWidth / 2
3452
    };
3453
    var arrowEle = jqLite(popoverEle[0].querySelector('.popover-arrow'));
3454
 
3455
    if (popoverCSS.left < POPOVER_BODY_PADDING) {
3456
      popoverCSS.left = POPOVER_BODY_PADDING;
3457
    } else if (popoverCSS.left + popoverWidth + POPOVER_BODY_PADDING > bodyWidth) {
3458
      popoverCSS.left = bodyWidth - popoverWidth - POPOVER_BODY_PADDING;
3459
    }
3460
 
3461
    // If the popover when popped down stretches past bottom of screen,
3462
    // make it pop up
3463
    if (buttonOffset.top + buttonOffset.height + popoverHeight > bodyHeight) {
3464
      popoverCSS.top = buttonOffset.top - popoverHeight;
3465
      popoverEle.addClass('popover-bottom');
3466
    } else {
3467
      popoverCSS.top = buttonOffset.top + buttonOffset.height;
3468
      popoverEle.removeClass('popover-bottom');
3469
    }
3470
 
3471
    arrowEle.css({
3472
      left: buttonOffset.left + buttonOffset.width / 2 -
3473
        arrowEle.prop('offsetWidth') / 2 - popoverCSS.left + 'px'
3474
    });
3475
 
3476
    popoverEle.css({
3477
      top: popoverCSS.top + 'px',
3478
      left: popoverCSS.left + 'px',
3479
      marginLeft: '0',
3480
      opacity: '1'
3481
    });
3482
 
3483
  }
3484
 
3485
  /**
3486
   * @ngdoc controller
3487
   * @name ionicPopover
3488
   * @module ionic
3489
   * @description
3490
   * Instantiated by the {@link ionic.service:$ionicPopover} service.
3491
   *
3492
   * Be sure to call [remove()](#remove) when you are done with each popover
3493
   * to clean it up and avoid memory leaks.
3494
   *
3495
   * Note: a popover will broadcast 'popover.shown', 'popover.hidden', and 'popover.removed' events from its originating
3496
   * scope, passing in itself as an event argument. Both the popover.removed and popover.hidden events are
3497
   * called when the popover is removed.
3498
   */
3499
 
3500
  /**
3501
   * @ngdoc method
3502
   * @name ionicPopover#initialize
3503
   * @description Creates a new popover controller instance.
3504
   * @param {object} options An options object with the following properties:
3505
   *  - `{object=}` `scope` The scope to be a child of.
3506
   *    Default: creates a child of $rootScope.
3507
   *  - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of
3508
   *    the popover when shown.  Default: false.
3509
   *  - `{boolean=}` `backdropClickToClose` Whether to close the popover on clicking the backdrop.
3510
   *    Default: true.
3511
   *  - `{boolean=}` `hardwareBackButtonClose` Whether the popover can be closed using the hardware
3512
   *    back button on Android and similar devices.  Default: true.
3513
   */
3514
 
3515
  /**
3516
   * @ngdoc method
3517
   * @name ionicPopover#show
3518
   * @description Show this popover instance.
3519
   * @param {$event} $event The $event or target element which the popover should align
3520
   * itself next to.
3521
   * @returns {promise} A promise which is resolved when the popover is finished animating in.
3522
   */
3523
 
3524
  /**
3525
   * @ngdoc method
3526
   * @name ionicPopover#hide
3527
   * @description Hide this popover instance.
3528
   * @returns {promise} A promise which is resolved when the popover is finished animating out.
3529
   */
3530
 
3531
  /**
3532
   * @ngdoc method
3533
   * @name ionicPopover#remove
3534
   * @description Remove this popover instance from the DOM and clean up.
3535
   * @returns {promise} A promise which is resolved when the popover is finished animating out.
3536
   */
3537
 
3538
  /**
3539
   * @ngdoc method
3540
   * @name ionicPopover#isShown
3541
   * @returns boolean Whether this popover is currently shown.
3542
   */
3543
 
3544
  return {
3545
    /**
3546
     * @ngdoc method
3547
     * @name $ionicPopover#fromTemplate
3548
     * @param {string} templateString The template string to use as the popovers's
3549
     * content.
3550
     * @param {object} options Options to be passed to the initialize method.
3551
     * @returns {object} An instance of an {@link ionic.controller:ionicPopover}
3552
     * controller (ionicPopover is built on top of $ionicPopover).
3553
     */
3554
    fromTemplate: function(templateString, options) {
3555
      return $ionicModal.fromTemplate(templateString, ionic.Utils.extend(POPOVER_OPTIONS, options || {}));
3556
    },
3557
    /**
3558
     * @ngdoc method
3559
     * @name $ionicPopover#fromTemplateUrl
3560
     * @param {string} templateUrl The url to load the template from.
3561
     * @param {object} options Options to be passed to the initialize method.
3562
     * @returns {promise} A promise that will be resolved with an instance of
3563
     * an {@link ionic.controller:ionicPopover} controller (ionicPopover is built on top of $ionicPopover).
3564
     */
3565
    fromTemplateUrl: function(url, options) {
3566
      return $ionicModal.fromTemplateUrl(url, ionic.Utils.extend(POPOVER_OPTIONS, options || {}));
3567
    }
3568
  };
3569
 
3570
}]);
3571
 
3572
 
3573
var POPUP_TPL =
3574
  '<div class="popup-container" ng-class="cssClass">' +
3575
    '<div class="popup">' +
3576
      '<div class="popup-head">' +
3577
        '<h3 class="popup-title" ng-bind-html="title"></h3>' +
3578
        '<h5 class="popup-sub-title" ng-bind-html="subTitle" ng-if="subTitle"></h5>' +
3579
      '</div>' +
3580
      '<div class="popup-body">' +
3581
      '</div>' +
3582
      '<div class="popup-buttons" ng-show="buttons.length">' +
3583
        '<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>' +
3584
      '</div>' +
3585
    '</div>' +
3586
  '</div>';
3587
 
3588
/**
3589
 * @ngdoc service
3590
 * @name $ionicPopup
3591
 * @module ionic
3592
 * @restrict E
3593
 * @codepen zkmhJ
3594
 * @description
3595
 *
3596
 * The Ionic Popup service allows programmatically creating and showing popup
3597
 * windows that require the user to respond in order to continue.
3598
 *
3599
 * The popup system has support for more flexible versions of the built in `alert()`, `prompt()`,
3600
 * and `confirm()` functions that users are used to, in addition to allowing popups with completely
3601
 * custom content and look.
3602
 *
3603
 * An input can be given an `autofocus` attribute so it automatically receives focus when
3604
 * the popup first shows. However, depending on certain use-cases this can cause issues with
3605
 * the tap/click system, which is why Ionic prefers using the `autofocus` attribute as
3606
 * an opt-in feature and not the default.
3607
 *
3608
 * @usage
3609
 * A few basic examples, see below for details about all of the options available.
3610
 *
3611
 * ```js
3612
 *angular.module('mySuperApp', ['ionic'])
3613
 *.controller('PopupCtrl',function($scope, $ionicPopup, $timeout) {
3614
 *
3615
 * // Triggered on a button click, or some other target
3616
 * $scope.showPopup = function() {
3617
 *   $scope.data = {}
3618
 *
3619
 *   // An elaborate, custom popup
3620
 *   var myPopup = $ionicPopup.show({
3621
 *     template: '<input type="password" ng-model="data.wifi">',
3622
 *     title: 'Enter Wi-Fi Password',
3623
 *     subTitle: 'Please use normal things',
3624
 *     scope: $scope,
3625
 *     buttons: [
3626
 *       { text: 'Cancel' },
3627
 *       {
3628
 *         text: '<b>Save</b>',
3629
 *         type: 'button-positive',
3630
 *         onTap: function(e) {
3631
 *           if (!$scope.data.wifi) {
3632
 *             //don't allow the user to close unless he enters wifi password
3633
 *             e.preventDefault();
3634
 *           } else {
3635
 *             return $scope.data.wifi;
3636
 *           }
3637
 *         }
3638
 *       }
3639
 *     ]
3640
 *   });
3641
 *   myPopup.then(function(res) {
3642
 *     console.log('Tapped!', res);
3643
 *   });
3644
 *   $timeout(function() {
3645
 *      myPopup.close(); //close the popup after 3 seconds for some reason
3646
 *   }, 3000);
3647
 *  };
3648
 *  // A confirm dialog
3649
 *  $scope.showConfirm = function() {
3650
 *    var confirmPopup = $ionicPopup.confirm({
3651
 *      title: 'Consume Ice Cream',
3652
 *      template: 'Are you sure you want to eat this ice cream?'
3653
 *    });
3654
 *    confirmPopup.then(function(res) {
3655
 *      if(res) {
3656
 *        console.log('You are sure');
3657
 *      } else {
3658
 *        console.log('You are not sure');
3659
 *      }
3660
 *    });
3661
 *  };
3662
 *
3663
 *  // An alert dialog
3664
 *  $scope.showAlert = function() {
3665
 *    var alertPopup = $ionicPopup.alert({
3666
 *      title: 'Don\'t eat that!',
3667
 *      template: 'It might taste good'
3668
 *    });
3669
 *    alertPopup.then(function(res) {
3670
 *      console.log('Thank you for not eating my delicious ice cream cone');
3671
 *    });
3672
 *  };
3673
 *});
3674
 *```
3675
 */
3676
 
3677
IonicModule
3678
.factory('$ionicPopup', [
3679
  '$ionicTemplateLoader',
3680
  '$ionicBackdrop',
3681
  '$q',
3682
  '$timeout',
3683
  '$rootScope',
3684
  '$ionicBody',
3685
  '$compile',
3686
  '$ionicPlatform',
3687
function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $ionicBody, $compile, $ionicPlatform) {
3688
  //TODO allow this to be configured
3689
  var config = {
3690
    stackPushDelay: 75
3691
  };
3692
  var popupStack = [];
3693
  var $ionicPopup = {
3694
    /**
3695
     * @ngdoc method
3696
     * @description
3697
     * Show a complex popup. This is the master show function for all popups.
3698
     *
3699
     * A complex popup has a `buttons` array, with each button having a `text` and `type`
3700
     * field, in addition to an `onTap` function.  The `onTap` function, called when
3701
     * the corresponding button on the popup is tapped, will by default close the popup
3702
     * and resolve the popup promise with its return value.  If you wish to prevent the
3703
     * default and keep the popup open on button tap, call `event.preventDefault()` on the
3704
     * passed in tap event.  Details below.
3705
     *
3706
     * @name $ionicPopup#show
3707
     * @param {object} options The options for the new popup, of the form:
3708
     *
3709
     * ```
3710
     * {
3711
     *   title: '', // String. The title of the popup.
3712
     *   cssClass: '', // String, The custom CSS class name
3713
     *   subTitle: '', // String (optional). The sub-title of the popup.
3714
     *   template: '', // String (optional). The html template to place in the popup body.
3715
     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup   body.
3716
     *   scope: null, // Scope (optional). A scope to link to the popup content.
3717
     *   buttons: [{ // Array[Object] (optional). Buttons to place in the popup footer.
3718
     *     text: 'Cancel',
3719
     *     type: 'button-default',
3720
     *     onTap: function(e) {
3721
     *       // e.preventDefault() will stop the popup from closing when tapped.
3722
     *       e.preventDefault();
3723
     *     }
3724
     *   }, {
3725
     *     text: 'OK',
3726
     *     type: 'button-positive',
3727
     *     onTap: function(e) {
3728
     *       // Returning a value will cause the promise to resolve with the given value.
3729
     *       return scope.data.response;
3730
     *     }
3731
     *   }]
3732
     * }
3733
     * ```
3734
     *
3735
     * @returns {object} A promise which is resolved when the popup is closed. Has an additional
3736
     * `close` function, which can be used to programmatically close the popup.
3737
     */
3738
    show: showPopup,
3739
 
3740
    /**
3741
     * @ngdoc method
3742
     * @name $ionicPopup#alert
3743
     * @description Show a simple alert popup with a message and one button that the user can
3744
     * tap to close the popup.
3745
     *
3746
     * @param {object} options The options for showing the alert, of the form:
3747
     *
3748
     * ```
3749
     * {
3750
     *   title: '', // String. The title of the popup.
3751
     *   cssClass: '', // String, The custom CSS class name
3752
     *   subTitle: '', // String (optional). The sub-title of the popup.
3753
     *   template: '', // String (optional). The html template to place in the popup body.
3754
     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup   body.
3755
     *   okText: '', // String (default: 'OK'). The text of the OK button.
3756
     *   okType: '', // String (default: 'button-positive'). The type of the OK button.
3757
     * }
3758
     * ```
3759
     *
3760
     * @returns {object} A promise which is resolved when the popup is closed. Has one additional
3761
     * function `close`, which can be called with any value to programmatically close the popup
3762
     * with the given value.
3763
     */
3764
    alert: showAlert,
3765
 
3766
    /**
3767
     * @ngdoc method
3768
     * @name $ionicPopup#confirm
3769
     * @description
3770
     * Show a simple confirm popup with a Cancel and OK button.
3771
     *
3772
     * Resolves the promise with true if the user presses the OK button, and false if the
3773
     * user presses the Cancel button.
3774
     *
3775
     * @param {object} options The options for showing the confirm popup, of the form:
3776
     *
3777
     * ```
3778
     * {
3779
     *   title: '', // String. The title of the popup.
3780
     *   cssClass: '', // String, The custom CSS class name
3781
     *   subTitle: '', // String (optional). The sub-title of the popup.
3782
     *   template: '', // String (optional). The html template to place in the popup body.
3783
     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup   body.
3784
     *   cancelText: '', // String (default: 'Cancel'). The text of the Cancel button.
3785
     *   cancelType: '', // String (default: 'button-default'). The type of the Cancel button.
3786
     *   okText: '', // String (default: 'OK'). The text of the OK button.
3787
     *   okType: '', // String (default: 'button-positive'). The type of the OK button.
3788
     * }
3789
     * ```
3790
     *
3791
     * @returns {object} A promise which is resolved when the popup is closed. Has one additional
3792
     * function `close`, which can be called with any value to programmatically close the popup
3793
     * with the given value.
3794
     */
3795
    confirm: showConfirm,
3796
 
3797
    /**
3798
     * @ngdoc method
3799
     * @name $ionicPopup#prompt
3800
     * @description Show a simple prompt popup, which has an input, OK button, and Cancel button.
3801
     * Resolves the promise with the value of the input if the user presses OK, and with undefined
3802
     * if the user presses Cancel.
3803
     *
3804
     * ```javascript
3805
     *  $ionicPopup.prompt({
3806
     *    title: 'Password Check',
3807
     *    template: 'Enter your secret password',
3808
     *    inputType: 'password',
3809
     *    inputPlaceholder: 'Your password'
3810
     *  }).then(function(res) {
3811
     *    console.log('Your password is', res);
3812
     *  });
3813
     * ```
3814
     * @param {object} options The options for showing the prompt popup, of the form:
3815
     *
3816
     * ```
3817
     * {
3818
     *   title: '', // String. The title of the popup.
3819
     *   cssClass: '', // String, The custom CSS class name
3820
     *   subTitle: '', // String (optional). The sub-title of the popup.
3821
     *   template: '', // String (optional). The html template to place in the popup body.
3822
     *   templateUrl: '', // String (optional). The URL of an html template to place in the popup   body.
3823
     *   inputType: // String (default: 'text'). The type of input to use
3824
     *   inputPlaceholder: // String (default: ''). A placeholder to use for the input.
3825
     *   cancelText: // String (default: 'Cancel'. The text of the Cancel button.
3826
     *   cancelType: // String (default: 'button-default'). The type of the Cancel button.
3827
     *   okText: // String (default: 'OK'). The text of the OK button.
3828
     *   okType: // String (default: 'button-positive'). The type of the OK button.
3829
     * }
3830
     * ```
3831
     *
3832
     * @returns {object} A promise which is resolved when the popup is closed. Has one additional
3833
     * function `close`, which can be called with any value to programmatically close the popup
3834
     * with the given value.
3835
     */
3836
    prompt: showPrompt,
3837
    /**
3838
     * @private for testing
3839
     */
3840
    _createPopup: createPopup,
3841
    _popupStack: popupStack
3842
  };
3843
 
3844
  return $ionicPopup;
3845
 
3846
  function createPopup(options) {
3847
    options = extend({
3848
      scope: null,
3849
      title: '',
3850
      buttons: []
3851
    }, options || {});
3852
 
3853
    var popupPromise = $ionicTemplateLoader.compile({
3854
      template: POPUP_TPL,
3855
      scope: options.scope && options.scope.$new(),
3856
      appendTo: $ionicBody.get()
3857
    });
3858
    var contentPromise = options.templateUrl ?
3859
      $ionicTemplateLoader.load(options.templateUrl) :
3860
      $q.when(options.template || options.content || '');
3861
 
3862
    return $q.all([popupPromise, contentPromise])
3863
    .then(function(results) {
3864
      var self = results[0];
3865
      var content = results[1];
3866
      var responseDeferred = $q.defer();
3867
 
3868
      self.responseDeferred = responseDeferred;
3869
 
3870
      //Can't ng-bind-html for popup-body because it can be insecure html
3871
      //(eg an input in case of prompt)
3872
      var body = jqLite(self.element[0].querySelector('.popup-body'));
3873
      if (content) {
3874
        body.html(content);
3875
        $compile(body.contents())(self.scope);
3876
      } else {
3877
        body.remove();
3878
      }
3879
 
3880
      extend(self.scope, {
3881
        title: options.title,
3882
        buttons: options.buttons,
3883
        subTitle: options.subTitle,
3884
        cssClass: options.cssClass,
3885
        $buttonTapped: function(button, event) {
3886
          var result = (button.onTap || angular.noop)(event);
3887
          event = event.originalEvent || event; //jquery events
3888
 
3889
          if (!event.defaultPrevented) {
3890
            responseDeferred.resolve(result);
3891
          }
3892
        }
3893
      });
3894
 
3895
      self.show = function() {
3896
        if (self.isShown) return;
3897
 
3898
        self.isShown = true;
3899
        ionic.requestAnimationFrame(function() {
3900
          //if hidden while waiting for raf, don't show
3901
          if (!self.isShown) return;
3902
 
3903
          self.element.removeClass('popup-hidden');
3904
          self.element.addClass('popup-showing active');
3905
          focusInput(self.element);
3906
        });
3907
      };
3908
      self.hide = function(callback) {
3909
        callback = callback || angular.noop;
3910
        if (!self.isShown) return callback();
3911
 
3912
        self.isShown = false;
3913
        self.element.removeClass('active');
3914
        self.element.addClass('popup-hidden');
3915
        $timeout(callback, 250);
3916
      };
3917
      self.remove = function() {
3918
        if (self.removed) return;
3919
 
3920
        self.hide(function() {
3921
          self.element.remove();
3922
          self.scope.$destroy();
3923
        });
3924
 
3925
        self.removed = true;
3926
      };
3927
 
3928
      return self;
3929
    });
3930
  }
3931
 
3932
  function onHardwareBackButton(e) {
3933
    popupStack[0] && popupStack[0].responseDeferred.resolve();
3934
  }
3935
 
3936
  function showPopup(options) {
3937
    var popupPromise = $ionicPopup._createPopup(options);
3938
    var previousPopup = popupStack[0];
3939
 
3940
    if (previousPopup) {
3941
      previousPopup.hide();
3942
    }
3943
 
3944
    var resultPromise = $timeout(angular.noop, previousPopup ? config.stackPushDelay : 0)
3945
    .then(function() { return popupPromise; })
3946
    .then(function(popup) {
3947
      if (!previousPopup) {
3948
        //Add popup-open & backdrop if this is first popup
3949
        $ionicBody.addClass('popup-open');
3950
        $ionicBackdrop.retain();
3951
        //only show the backdrop on the first popup
3952
        $ionicPopup._backButtonActionDone = $ionicPlatform.registerBackButtonAction(
3953
          onHardwareBackButton,
3954
          PLATFORM_BACK_BUTTON_PRIORITY_POPUP
3955
        );
3956
      }
3957
      popupStack.unshift(popup);
3958
      popup.show();
3959
 
3960
      //DEPRECATED: notify the promise with an object with a close method
3961
      popup.responseDeferred.notify({
3962
        close: resultPromise.close
3963
      });
3964
 
3965
      return popup.responseDeferred.promise.then(function(result) {
3966
        var index = popupStack.indexOf(popup);
3967
        if (index !== -1) {
3968
          popupStack.splice(index, 1);
3969
        }
3970
        popup.remove();
3971
 
3972
        var previousPopup = popupStack[0];
3973
        if (previousPopup) {
3974
          previousPopup.show();
3975
        } else {
3976
          //Remove popup-open & backdrop if this is last popup
3977
          $timeout(function() {
3978
            // wait to remove this due to a 300ms delay native
3979
            // click which would trigging whatever was underneath this
3980
            $ionicBody.removeClass('popup-open');
3981
          }, 400);
3982
          $timeout(function() {
3983
            $ionicBackdrop.release();
3984
          }, config.stackPushDelay || 0);
3985
          ($ionicPopup._backButtonActionDone || angular.noop)();
3986
        }
3987
        return result;
3988
      });
3989
    });
3990
 
3991
    function close(result) {
3992
      popupPromise.then(function(popup) {
3993
        if (!popup.removed) {
3994
          popup.responseDeferred.resolve(result);
3995
        }
3996
      });
3997
    }
3998
    resultPromise.close = close;
3999
 
4000
    return resultPromise;
4001
  }
4002
 
4003
  function focusInput(element) {
4004
    var focusOn = element[0].querySelector('[autofocus]');
4005
    if (focusOn) {
4006
      focusOn.focus();
4007
    }
4008
  }
4009
 
4010
  function showAlert(opts) {
4011
    return showPopup(extend({
4012
      buttons: [{
4013
        text: opts.okText || 'OK',
4014
        type: opts.okType || 'button-positive',
4015
        onTap: function(e) {
4016
          return true;
4017
        }
4018
      }]
4019
    }, opts || {}));
4020
  }
4021
 
4022
  function showConfirm(opts) {
4023
    return showPopup(extend({
4024
      buttons: [{
4025
        text: opts.cancelText || 'Cancel',
4026
        type: opts.cancelType || 'button-default',
4027
        onTap: function(e) { return false; }
4028
      }, {
4029
        text: opts.okText || 'OK',
4030
        type: opts.okType || 'button-positive',
4031
        onTap: function(e) { return true; }
4032
      }]
4033
    }, opts || {}));
4034
  }
4035
 
4036
  function showPrompt(opts) {
4037
    var scope = $rootScope.$new(true);
4038
    scope.data = {};
4039
    var text = '';
4040
    if (opts.template && /<[a-z][\s\S]*>/i.test(opts.template) === false) {
4041
      text = '<span>' + opts.template + '</span>';
4042
      delete opts.template;
4043
    }
4044
    return showPopup(extend({
4045
      template: text + '<input ng-model="data.response" type="' + (opts.inputType || 'text') +
4046
        '" placeholder="' + (opts.inputPlaceholder || '') + '">',
4047
      scope: scope,
4048
      buttons: [{
4049
        text: opts.cancelText || 'Cancel',
4050
        type: opts.cancelType || 'button-default',
4051
        onTap: function(e) {}
4052
      }, {
4053
        text: opts.okText || 'OK',
4054
        type: opts.okType || 'button-positive',
4055
        onTap: function(e) {
4056
          return scope.data.response || '';
4057
        }
4058
      }]
4059
    }, opts || {}));
4060
  }
4061
}]);
4062
 
4063
/**
4064
 * @ngdoc service
4065
 * @name $ionicPosition
4066
 * @module ionic
4067
 * @description
4068
 * A set of utility methods that can be use to retrieve position of DOM elements.
4069
 * It is meant to be used where we need to absolute-position DOM elements in
4070
 * relation to other, existing elements (this is the case for tooltips, popovers, etc.).
4071
 *
4072
 * Adapted from [AngularUI Bootstrap](https://github.com/angular-ui/bootstrap/blob/master/src/position/position.js),
4073
 * ([license](https://github.com/angular-ui/bootstrap/blob/master/LICENSE))
4074
 */
4075
IonicModule
4076
.factory('$ionicPosition', ['$document', '$window', function($document, $window) {
4077
 
4078
  function getStyle(el, cssprop) {
4079
    if (el.currentStyle) { //IE
4080
      return el.currentStyle[cssprop];
4081
    } else if ($window.getComputedStyle) {
4082
      return $window.getComputedStyle(el)[cssprop];
4083
    }
4084
    // finally try and get inline style
4085
    return el.style[cssprop];
4086
  }
4087
 
4088
  /**
4089
   * Checks if a given element is statically positioned
4090
   * @param element - raw DOM element
4091
   */
4092
  function isStaticPositioned(element) {
4093
    return (getStyle(element, 'position') || 'static') === 'static';
4094
  }
4095
 
4096
  /**
4097
   * returns the closest, non-statically positioned parentOffset of a given element
4098
   * @param element
4099
   */
4100
  var parentOffsetEl = function(element) {
4101
    var docDomEl = $document[0];
4102
    var offsetParent = element.offsetParent || docDomEl;
4103
    while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent)) {
4104
      offsetParent = offsetParent.offsetParent;
4105
    }
4106
    return offsetParent || docDomEl;
4107
  };
4108
 
4109
  return {
4110
    /**
4111
     * @ngdoc method
4112
     * @name $ionicPosition#position
4113
     * @description Get the current coordinates of the element, relative to the offset parent.
4114
     * Read-only equivalent of [jQuery's position function](http://api.jquery.com/position/).
4115
     * @param {element} element The element to get the position of.
4116
     * @returns {object} Returns an object containing the properties top, left, width and height.
4117
     */
4118
    position: function(element) {
4119
      var elBCR = this.offset(element);
4120
      var offsetParentBCR = { top: 0, left: 0 };
4121
      var offsetParentEl = parentOffsetEl(element[0]);
4122
      if (offsetParentEl != $document[0]) {
4123
        offsetParentBCR = this.offset(angular.element(offsetParentEl));
4124
        offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
4125
        offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
4126
      }
4127
 
4128
      var boundingClientRect = element[0].getBoundingClientRect();
4129
      return {
4130
        width: boundingClientRect.width || element.prop('offsetWidth'),
4131
        height: boundingClientRect.height || element.prop('offsetHeight'),
4132
        top: elBCR.top - offsetParentBCR.top,
4133
        left: elBCR.left - offsetParentBCR.left
4134
      };
4135
    },
4136
 
4137
    /**
4138
     * @ngdoc method
4139
     * @name $ionicPosition#offset
4140
     * @description Get the current coordinates of the element, relative to the document.
4141
     * Read-only equivalent of [jQuery's offset function](http://api.jquery.com/offset/).
4142
     * @param {element} element The element to get the offset of.
4143
     * @returns {object} Returns an object containing the properties top, left, width and height.
4144
     */
4145
    offset: function(element) {
4146
      var boundingClientRect = element[0].getBoundingClientRect();
4147
      return {
4148
        width: boundingClientRect.width || element.prop('offsetWidth'),
4149
        height: boundingClientRect.height || element.prop('offsetHeight'),
4150
        top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
4151
        left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
4152
      };
4153
    }
4154
 
4155
  };
4156
}]);
4157
 
4158
 
4159
/**
4160
 * @ngdoc service
4161
 * @name $ionicScrollDelegate
4162
 * @module ionic
4163
 * @description
4164
 * Delegate for controlling scrollViews (created by
4165
 * {@link ionic.directive:ionContent} and
4166
 * {@link ionic.directive:ionScroll} directives).
4167
 *
4168
 * Methods called directly on the $ionicScrollDelegate service will control all scroll
4169
 * views.  Use the {@link ionic.service:$ionicScrollDelegate#$getByHandle $getByHandle}
4170
 * method to control specific scrollViews.
4171
 *
4172
 * @usage
4173
 *
4174
 * ```html
4175
 * <body ng-controller="MainCtrl">
4176
 *   <ion-content>
4177
 *     <button ng-click="scrollTop()">Scroll to Top!</button>
4178
 *   </ion-content>
4179
 * </body>
4180
 * ```
4181
 * ```js
4182
 * function MainCtrl($scope, $ionicScrollDelegate) {
4183
 *   $scope.scrollTop = function() {
4184
 *     $ionicScrollDelegate.scrollTop();
4185
 *   };
4186
 * }
4187
 * ```
4188
 *
4189
 * Example of advanced usage, with two scroll areas using `delegate-handle`
4190
 * for fine control.
4191
 *
4192
 * ```html
4193
 * <body ng-controller="MainCtrl">
4194
 *   <ion-content delegate-handle="mainScroll">
4195
 *     <button ng-click="scrollMainToTop()">
4196
 *       Scroll content to top!
4197
 *     </button>
4198
 *     <ion-scroll delegate-handle="small" style="height: 100px;">
4199
 *       <button ng-click="scrollSmallToTop()">
4200
 *         Scroll small area to top!
4201
 *       </button>
4202
 *     </ion-scroll>
4203
 *   </ion-content>
4204
 * </body>
4205
 * ```
4206
 * ```js
4207
 * function MainCtrl($scope, $ionicScrollDelegate) {
4208
 *   $scope.scrollMainToTop = function() {
4209
 *     $ionicScrollDelegate.$getByHandle('mainScroll').scrollTop();
4210
 *   };
4211
 *   $scope.scrollSmallToTop = function() {
4212
 *     $ionicScrollDelegate.$getByHandle('small').scrollTop();
4213
 *   };
4214
 * }
4215
 * ```
4216
 */
4217
IonicModule
4218
.service('$ionicScrollDelegate', ionic.DelegateService([
4219
  /**
4220
   * @ngdoc method
4221
   * @name $ionicScrollDelegate#resize
4222
   * @description Tell the scrollView to recalculate the size of its container.
4223
   */
4224
  'resize',
4225
  /**
4226
   * @ngdoc method
4227
   * @name $ionicScrollDelegate#scrollTop
4228
   * @param {boolean=} shouldAnimate Whether the scroll should animate.
4229
   */
4230
  'scrollTop',
4231
  /**
4232
   * @ngdoc method
4233
   * @name $ionicScrollDelegate#scrollBottom
4234
   * @param {boolean=} shouldAnimate Whether the scroll should animate.
4235
   */
4236
  'scrollBottom',
4237
  /**
4238
   * @ngdoc method
4239
   * @name $ionicScrollDelegate#scrollTo
4240
   * @param {number} left The x-value to scroll to.
4241
   * @param {number} top The y-value to scroll to.
4242
   * @param {boolean=} shouldAnimate Whether the scroll should animate.
4243
   */
4244
  'scrollTo',
4245
  /**
4246
   * @ngdoc method
4247
   * @name $ionicScrollDelegate#scrollBy
4248
   * @param {number} left The x-offset to scroll by.
4249
   * @param {number} top The y-offset to scroll by.
4250
   * @param {boolean=} shouldAnimate Whether the scroll should animate.
4251
   */
4252
  'scrollBy',
4253
  /**
4254
   * @ngdoc method
4255
   * @name $ionicScrollDelegate#zoomTo
4256
   * @param {number} level Level to zoom to.
4257
   * @param {boolean=} animate Whether to animate the zoom.
4258
   * @param {number=} originLeft Zoom in at given left coordinate.
4259
   * @param {number=} originTop Zoom in at given top coordinate.
4260
   */
4261
  'zoomTo',
4262
  /**
4263
   * @ngdoc method
4264
   * @name $ionicScrollDelegate#zoomBy
4265
   * @param {number} factor The factor to zoom by.
4266
   * @param {boolean=} animate Whether to animate the zoom.
4267
   * @param {number=} originLeft Zoom in at given left coordinate.
4268
   * @param {number=} originTop Zoom in at given top coordinate.
4269
   */
4270
  'zoomBy',
4271
  /**
4272
   * @ngdoc method
4273
   * @name $ionicScrollDelegate#getScrollPosition
4274
   * @returns {object} The scroll position of this view, with the following properties:
4275
   *  - `{number}` `left` The distance the user has scrolled from the left (starts at 0).
4276
   *  - `{number}` `top` The distance the user has scrolled from the top (starts at 0).
4277
   */
4278
  'getScrollPosition',
4279
  /**
4280
   * @ngdoc method
4281
   * @name $ionicScrollDelegate#anchorScroll
4282
   * @description Tell the scrollView to scroll to the element with an id
4283
   * matching window.location.hash.
4284
   *
4285
   * If no matching element is found, it will scroll to top.
4286
   *
4287
   * @param {boolean=} shouldAnimate Whether the scroll should animate.
4288
   */
4289
  'anchorScroll',
4290
  /**
4291
   * @ngdoc method
4292
   * @name $ionicScrollDelegate#getScrollView
4293
   * @returns {object} The scrollView associated with this delegate.
4294
   */
4295
  'getScrollView',
4296
  /**
4297
   * @ngdoc method
4298
   * @name $ionicScrollDelegate#$getByHandle
4299
   * @param {string} handle
4300
   * @returns `delegateInstance` A delegate instance that controls only the
4301
   * scrollViews with `delegate-handle` matching the given handle.
4302
   *
4303
   * Example: `$ionicScrollDelegate.$getByHandle('my-handle').scrollTop();`
4304
   */
4305
]));
4306
 
4307
 
4308
/**
4309
 * @ngdoc service
4310
 * @name $ionicSideMenuDelegate
4311
 * @module ionic
4312
 *
4313
 * @description
4314
 * Delegate for controlling the {@link ionic.directive:ionSideMenus} directive.
4315
 *
4316
 * Methods called directly on the $ionicSideMenuDelegate service will control all side
4317
 * menus.  Use the {@link ionic.service:$ionicSideMenuDelegate#$getByHandle $getByHandle}
4318
 * method to control specific ionSideMenus instances.
4319
 *
4320
 * @usage
4321
 *
4322
 * ```html
4323
 * <body ng-controller="MainCtrl">
4324
 *   <ion-side-menus>
4325
 *     <ion-side-menu-content>
4326
 *       Content!
4327
 *       <button ng-click="toggleLeftSideMenu()">
4328
 *         Toggle Left Side Menu
4329
 *       </button>
4330
 *     </ion-side-menu-content>
4331
 *     <ion-side-menu side="left">
4332
 *       Left Menu!
4333
 *     <ion-side-menu>
4334
 *   </ion-side-menus>
4335
 * </body>
4336
 * ```
4337
 * ```js
4338
 * function MainCtrl($scope, $ionicSideMenuDelegate) {
4339
 *   $scope.toggleLeftSideMenu = function() {
4340
 *     $ionicSideMenuDelegate.toggleLeft();
4341
 *   };
4342
 * }
4343
 * ```
4344
 */
4345
IonicModule
4346
.service('$ionicSideMenuDelegate', ionic.DelegateService([
4347
  /**
4348
   * @ngdoc method
4349
   * @name $ionicSideMenuDelegate#toggleLeft
4350
   * @description Toggle the left side menu (if it exists).
4351
   * @param {boolean=} isOpen Whether to open or close the menu.
4352
   * Default: Toggles the menu.
4353
   */
4354
  'toggleLeft',
4355
  /**
4356
   * @ngdoc method
4357
   * @name $ionicSideMenuDelegate#toggleRight
4358
   * @description Toggle the right side menu (if it exists).
4359
   * @param {boolean=} isOpen Whether to open or close the menu.
4360
   * Default: Toggles the menu.
4361
   */
4362
  'toggleRight',
4363
  /**
4364
   * @ngdoc method
4365
   * @name $ionicSideMenuDelegate#getOpenRatio
4366
   * @description Gets the ratio of open amount over menu width. For example, a
4367
   * menu of width 100 that is opened by 50 pixels is 50% opened, and would return
4368
   * a ratio of 0.5.
4369
   *
4370
   * @returns {float} 0 if nothing is open, between 0 and 1 if left menu is
4371
   * opened/opening, and between 0 and -1 if right menu is opened/opening.
4372
   */
4373
  'getOpenRatio',
4374
  /**
4375
   * @ngdoc method
4376
   * @name $ionicSideMenuDelegate#isOpen
4377
   * @returns {boolean} Whether either the left or right menu is currently opened.
4378
   */
4379
  'isOpen',
4380
  /**
4381
   * @ngdoc method
4382
   * @name $ionicSideMenuDelegate#isOpenLeft
4383
   * @returns {boolean} Whether the left menu is currently opened.
4384
   */
4385
  'isOpenLeft',
4386
  /**
4387
   * @ngdoc method
4388
   * @name $ionicSideMenuDelegate#isOpenRight
4389
   * @returns {boolean} Whether the right menu is currently opened.
4390
   */
4391
  'isOpenRight',
4392
  /**
4393
   * @ngdoc method
4394
   * @name $ionicSideMenuDelegate#canDragContent
4395
   * @param {boolean=} canDrag Set whether the content can or cannot be dragged to open
4396
   * side menus.
4397
   * @returns {boolean} Whether the content can be dragged to open side menus.
4398
   */
4399
  'canDragContent',
4400
  /**
4401
   * @ngdoc method
4402
   * @name $ionicSideMenuDelegate#edgeDragThreshold
4403
   * @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:
4404
   *  - 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.
4405
   *  - If true is given, the default number of pixels (25) is used as the maximum allowed distance.
4406
   *  - If false or 0 is given, the edge drag threshold is disabled, and dragging from anywhere on the content is allowed.
4407
   * @returns {boolean} Whether the drag can start only from within the edge of screen threshold.
4408
   */
4409
  'edgeDragThreshold',
4410
  /**
4411
   * @ngdoc method
4412
   * @name $ionicSideMenuDelegate#$getByHandle
4413
   * @param {string} handle
4414
   * @returns `delegateInstance` A delegate instance that controls only the
4415
   * {@link ionic.directive:ionSideMenus} directives with `delegate-handle` matching
4416
   * the given handle.
4417
   *
4418
   * Example: `$ionicSideMenuDelegate.$getByHandle('my-handle').toggleLeft();`
4419
   */
4420
]));
4421
 
4422
 
4423
/**
4424
 * @ngdoc service
4425
 * @name $ionicSlideBoxDelegate
4426
 * @module ionic
4427
 * @description
4428
 * Delegate that controls the {@link ionic.directive:ionSlideBox} directive.
4429
 *
4430
 * Methods called directly on the $ionicSlideBoxDelegate service will control all slide boxes.  Use the {@link ionic.service:$ionicSlideBoxDelegate#$getByHandle $getByHandle}
4431
 * method to control specific slide box instances.
4432
 *
4433
 * @usage
4434
 *
4435
 * ```html
4436
 * <body ng-controller="MyCtrl">
4437
 *   <ion-slide-box>
4438
 *     <ion-slide>
4439
 *       <div class="box blue">
4440
 *         <button ng-click="nextSlide()">Next slide!</button>
4441
 *       </div>
4442
 *     </ion-slide>
4443
 *     <ion-slide>
4444
 *       <div class="box red">
4445
 *         Slide 2!
4446
 *       </div>
4447
 *     </ion-slide>
4448
 *   </ion-slide-box>
4449
 * </body>
4450
 * ```
4451
 * ```js
4452
 * function MyCtrl($scope, $ionicSlideBoxDelegate) {
4453
 *   $scope.nextSlide = function() {
4454
 *     $ionicSlideBoxDelegate.next();
4455
 *   }
4456
 * }
4457
 * ```
4458
 */
4459
IonicModule
4460
.service('$ionicSlideBoxDelegate', ionic.DelegateService([
4461
  /**
4462
   * @ngdoc method
4463
   * @name $ionicSlideBoxDelegate#update
4464
   * @description
4465
   * Update the slidebox (for example if using Angular with ng-repeat,
4466
   * resize it for the elements inside).
4467
   */
4468
  'update',
4469
  /**
4470
   * @ngdoc method
4471
   * @name $ionicSlideBoxDelegate#slide
4472
   * @param {number} to The index to slide to.
4473
   * @param {number=} speed The number of milliseconds for the change to take.
4474
   */
4475
  'slide',
4476
  'select',
4477
  /**
4478
   * @ngdoc method
4479
   * @name $ionicSlideBoxDelegate#enableSlide
4480
   * @param {boolean=} shouldEnable Whether to enable sliding the slidebox.
4481
   * @returns {boolean} Whether sliding is enabled.
4482
   */
4483
  'enableSlide',
4484
  /**
4485
   * @ngdoc method
4486
   * @name $ionicSlideBoxDelegate#previous
4487
   * @description Go to the previous slide. Wraps around if at the beginning.
4488
   */
4489
  'previous',
4490
  /**
4491
   * @ngdoc method
4492
   * @name $ionicSlideBoxDelegate#next
4493
   * @description Go to the next slide. Wraps around if at the end.
4494
   */
4495
  'next',
4496
  /**
4497
   * @ngdoc method
4498
   * @name $ionicSlideBoxDelegate#stop
4499
   * @description Stop sliding. The slideBox will not move again until
4500
   * explicitly told to do so.
4501
   */
4502
  'stop',
4503
  'autoPlay',
4504
  /**
4505
   * @ngdoc method
4506
   * @name $ionicSlideBoxDelegate#start
4507
   * @description Start sliding again if the slideBox was stopped.
4508
   */
4509
  'start',
4510
  /**
4511
   * @ngdoc method
4512
   * @name $ionicSlideBoxDelegate#currentIndex
4513
   * @returns number The index of the current slide.
4514
   */
4515
  'currentIndex',
4516
  'selected',
4517
  /**
4518
   * @ngdoc method
4519
   * @name $ionicSlideBoxDelegate#slidesCount
4520
   * @returns number The number of slides there are currently.
4521
   */
4522
  'slidesCount',
4523
  'count',
4524
  'loop',
4525
  /**
4526
   * @ngdoc method
4527
   * @name $ionicSlideBoxDelegate#$getByHandle
4528
   * @param {string} handle
4529
   * @returns `delegateInstance` A delegate instance that controls only the
4530
   * {@link ionic.directive:ionSlideBox} directives with `delegate-handle` matching
4531
   * the given handle.
4532
   *
4533
   * Example: `$ionicSlideBoxDelegate.$getByHandle('my-handle').stop();`
4534
   */
4535
]));
4536
 
4537
 
4538
/**
4539
 * @ngdoc service
4540
 * @name $ionicTabsDelegate
4541
 * @module ionic
4542
 *
4543
 * @description
4544
 * Delegate for controlling the {@link ionic.directive:ionTabs} directive.
4545
 *
4546
 * Methods called directly on the $ionicTabsDelegate service will control all ionTabs
4547
 * directives. Use the {@link ionic.service:$ionicTabsDelegate#$getByHandle $getByHandle}
4548
 * method to control specific ionTabs instances.
4549
 *
4550
 * @usage
4551
 *
4552
 * ```html
4553
 * <body ng-controller="MyCtrl">
4554
 *   <ion-tabs>
4555
 *
4556
 *     <ion-tab title="Tab 1">
4557
 *       Hello tab 1!
4558
 *       <button ng-click="selectTabWithIndex(1)">Select tab 2!</button>
4559
 *     </ion-tab>
4560
 *     <ion-tab title="Tab 2">Hello tab 2!</ion-tab>
4561
 *
4562
 *   </ion-tabs>
4563
 * </body>
4564
 * ```
4565
 * ```js
4566
 * function MyCtrl($scope, $ionicTabsDelegate) {
4567
 *   $scope.selectTabWithIndex = function(index) {
4568
 *     $ionicTabsDelegate.select(index);
4569
 *   }
4570
 * }
4571
 * ```
4572
 */
4573
IonicModule
4574
.service('$ionicTabsDelegate', ionic.DelegateService([
4575
  /**
4576
   * @ngdoc method
4577
   * @name $ionicTabsDelegate#select
4578
   * @description Select the tab matching the given index.
4579
   *
4580
   * @param {number} index Index of the tab to select.
4581
   */
4582
  'select',
4583
  /**
4584
   * @ngdoc method
4585
   * @name $ionicTabsDelegate#selectedIndex
4586
   * @returns `number` The index of the selected tab, or -1.
4587
   */
4588
  'selectedIndex'
4589
  /**
4590
   * @ngdoc method
4591
   * @name $ionicTabsDelegate#$getByHandle
4592
   * @param {string} handle
4593
   * @returns `delegateInstance` A delegate instance that controls only the
4594
   * {@link ionic.directive:ionTabs} directives with `delegate-handle` matching
4595
   * the given handle.
4596
   *
4597
   * Example: `$ionicTabsDelegate.$getByHandle('my-handle').select(0);`
4598
   */
4599
]));
4600
 
4601
 
4602
// closure to keep things neat
4603
(function() {
4604
  var templatesToCache = [];
4605
 
4606
/**
4607
 * @ngdoc service
4608
 * @name $ionicTemplateCache
4609
 * @module ionic
4610
 * @description A service that preemptively caches template files to eliminate transition flicker and boost performance.
4611
 * @usage
4612
 * State templates are cached automatically, but you can optionally cache other templates.
4613
 *
4614
 * ```js
4615
 * $ionicTemplateCache('myNgIncludeTemplate.html');
4616
 * ```
4617
 *
4618
 * Optionally disable all preemptive caching with the `$ionicConfigProvider` or individual states by setting `prefetchTemplate`
4619
 * in the `$state` definition
4620
 *
4621
 * ```js
4622
 *   angular.module('myApp', ['ionic'])
4623
 *   .config(function($stateProvider, $ionicConfigProvider) {
4624
 *
4625
 *     // disable preemptive template caching globally
4626
 *     $ionicConfigProvider.templates.prefetch(false);
4627
 *
4628
 *     // disable individual states
4629
 *     $stateProvider
4630
 *       .state('tabs', {
4631
 *         url: "/tab",
4632
 *         abstract: true,
4633
 *         prefetchTemplate: false,
4634
 *         templateUrl: "tabs-templates/tabs.html"
4635
 *       })
4636
 *       .state('tabs.home', {
4637
 *         url: "/home",
4638
 *         views: {
4639
 *           'home-tab': {
4640
 *             prefetchTemplate: false,
4641
 *             templateUrl: "tabs-templates/home.html",
4642
 *             controller: 'HomeTabCtrl'
4643
 *           }
4644
 *         }
4645
 *       });
4646
 *   });
4647
 * ```
4648
 */
4649
IonicModule
4650
.factory('$ionicTemplateCache', [
4651
'$http',
4652
'$templateCache',
4653
'$timeout',
4654
function($http, $templateCache, $timeout) {
4655
  var toCache = templatesToCache,
4656
      hasRun;
4657
 
4658
  function $ionicTemplateCache(templates) {
4659
    if (typeof templates === 'undefined') {
4660
      return run();
4661
    }
4662
    if (isString(templates)) {
4663
      templates = [templates];
4664
    }
4665
    forEach(templates, function(template) {
4666
      toCache.push(template);
4667
    });
4668
    if (hasRun) {
4669
      run();
4670
    }
4671
  }
4672
 
4673
  // run through methods - internal method
4674
  function run() {
4675
    $ionicTemplateCache._runCount++;
4676
 
4677
    hasRun = true;
4678
    // ignore if race condition already zeroed out array
4679
    if (toCache.length === 0) return;
4680
 
4681
    var i = 0;
4682
    while (i < 4 && (template = toCache.pop())) {
4683
      // note that inline templates are ignored by this request
4684
      if (isString(template)) $http.get(template, { cache: $templateCache });
4685
      i++;
4686
    }
4687
    // only preload 3 templates a second
4688
    if (toCache.length) {
4689
      $timeout(run, 1000);
4690
    }
4691
  }
4692
 
4693
  // exposing for testing
4694
  $ionicTemplateCache._runCount = 0;
4695
  // default method
4696
  return $ionicTemplateCache;
4697
}])
4698
 
4699
// Intercepts the $stateprovider.state() command to look for templateUrls that can be cached
4700
.config([
4701
'$stateProvider',
4702
'$ionicConfigProvider',
4703
function($stateProvider, $ionicConfigProvider) {
4704
  var stateProviderState = $stateProvider.state;
4705
  $stateProvider.state = function(stateName, definition) {
4706
    // don't even bother if it's disabled. note, another config may run after this, so it's not a catch-all
4707
    if (typeof definition === 'object') {
4708
      var enabled = definition.prefetchTemplate !== false && templatesToCache.length < $ionicConfigProvider.templates.maxPrefetch();
4709
      if (enabled && isString(definition.templateUrl)) templatesToCache.push(definition.templateUrl);
4710
      if (angular.isObject(definition.views)) {
4711
        for (var key in definition.views) {
4712
          enabled = definition.views[key].prefetchTemplate !== false && templatesToCache.length < $ionicConfigProvider.templates.maxPrefetch();
4713
          if (enabled && isString(definition.views[key].templateUrl)) templatesToCache.push(definition.views[key].templateUrl);
4714
        }
4715
      }
4716
    }
4717
    return stateProviderState.call($stateProvider, stateName, definition);
4718
  };
4719
}])
4720
 
4721
// process the templateUrls collected by the $stateProvider, adding them to the cache
4722
.run(['$ionicTemplateCache', function($ionicTemplateCache) {
4723
  $ionicTemplateCache();
4724
}]);
4725
 
4726
})();
4727
 
4728
IonicModule
4729
.factory('$ionicTemplateLoader', [
4730
  '$compile',
4731
  '$controller',
4732
  '$http',
4733
  '$q',
4734
  '$rootScope',
4735
  '$templateCache',
4736
function($compile, $controller, $http, $q, $rootScope, $templateCache) {
4737
 
4738
  return {
4739
    load: fetchTemplate,
4740
    compile: loadAndCompile
4741
  };
4742
 
4743
  function fetchTemplate(url) {
4744
    return $http.get(url, {cache: $templateCache})
4745
    .then(function(response) {
4746
      return response.data && response.data.trim();
4747
    });
4748
  }
4749
 
4750
  function loadAndCompile(options) {
4751
    options = extend({
4752
      template: '',
4753
      templateUrl: '',
4754
      scope: null,
4755
      controller: null,
4756
      locals: {},
4757
      appendTo: null
4758
    }, options || {});
4759
 
4760
    var templatePromise = options.templateUrl ?
4761
      this.load(options.templateUrl) :
4762
      $q.when(options.template);
4763
 
4764
    return templatePromise.then(function(template) {
4765
      var controller;
4766
      var scope = options.scope || $rootScope.$new();
4767
 
4768
      //Incase template doesn't have just one root element, do this
4769
      var element = jqLite('<div>').html(template).contents();
4770
 
4771
      if (options.controller) {
4772
        controller = $controller(
4773
          options.controller,
4774
          extend(options.locals, {
4775
            $scope: scope
4776
          })
4777
        );
4778
        element.children().data('$ngControllerController', controller);
4779
      }
4780
      if (options.appendTo) {
4781
        jqLite(options.appendTo).append(element);
4782
      }
4783
 
4784
      $compile(element)(scope);
4785
 
4786
      return {
4787
        element: element,
4788
        scope: scope
4789
      };
4790
    });
4791
  }
4792
 
4793
}]);
4794
 
4795
/**
4796
 * @private
4797
 * DEPRECATED, as of v1.0.0-beta14 -------
4798
 */
4799
IonicModule
4800
.factory('$ionicViewService', ['$ionicHistory', '$log', function($ionicHistory, $log) {
4801
 
4802
  function warn(oldMethod, newMethod) {
4803
    $log.warn('$ionicViewService' + oldMethod + ' is deprecated, please use $ionicHistory' + newMethod + ' instead: http://ionicframework.com/docs/nightly/api/service/$ionicHistory/');
4804
  }
4805
 
4806
  warn('', '');
4807
 
4808
  var methodsMap = {
4809
    getCurrentView: 'currentView',
4810
    getBackView: 'backView',
4811
    getForwardView: 'forwardView',
4812
    getCurrentStateName: 'currentStateName',
4813
    nextViewOptions: 'nextViewOptions',
4814
    clearHistory: 'clearHistory'
4815
  };
4816
 
4817
  forEach(methodsMap, function(newMethod, oldMethod) {
4818
    methodsMap[oldMethod] = function() {
4819
      warn('.' + oldMethod, '.' + newMethod);
4820
      return $ionicHistory[newMethod].apply(this, arguments);
4821
    };
4822
  });
4823
 
4824
  return methodsMap;
4825
 
4826
}]);
4827
 
4828
/**
4829
 * @private
4830
 * TODO document
4831
 */
4832
 
4833
IonicModule
4834
.factory('$ionicViewSwitcher',[
4835
  '$timeout',
4836
  '$document',
4837
  '$q',
4838
  '$ionicClickBlock',
4839
  '$ionicConfig',
4840
  '$ionicNavBarDelegate',
4841
function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDelegate) {
4842
 
4843
  var TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
4844
  var DATA_NO_CACHE = '$noCache';
4845
  var DATA_DESTROY_ELE = '$destroyEle';
4846
  var DATA_ELE_IDENTIFIER = '$eleId';
4847
  var DATA_VIEW_ACCESSED = '$accessed';
4848
  var DATA_FALLBACK_TIMER = '$fallbackTimer';
4849
  var DATA_VIEW = '$viewData';
4850
  var NAV_VIEW_ATTR = 'nav-view';
4851
  var HISTORY_CURSOR_ATTR = 'history-cursor';
4852
  var VIEW_STATUS_ACTIVE = 'active';
4853
  var VIEW_STATUS_CACHED = 'cached';
4854
  var VIEW_STATUS_STAGED = 'stage';
4855
 
4856
  var transitionCounter = 0;
4857
  var nextTransition, nextDirection;
4858
  ionic.transition = ionic.transition || {};
4859
  ionic.transition.isActive = false;
4860
  var isActiveTimer;
4861
  var cachedAttr = ionic.DomUtil.cachedAttr;
4862
  var transitionPromises = [];
4863
 
4864
  var ionicViewSwitcher = {
4865
 
4866
    create: function(navViewCtrl, viewLocals, enteringView, leavingView) {
4867
      // get a reference to an entering/leaving element if they exist
4868
      // loop through to see if the view is already in the navViewElement
4869
      var enteringEle, leavingEle;
4870
      var transitionId = ++transitionCounter;
4871
      var alreadyInDom;
4872
 
4873
      var switcher = {
4874
 
4875
        init: function(registerData, callback) {
4876
          ionicViewSwitcher.isTransitioning(true);
4877
 
4878
          switcher.loadViewElements(registerData);
4879
 
4880
          switcher.render(registerData, function() {
4881
            callback && callback();
4882
          });
4883
        },
4884
 
4885
        loadViewElements: function(registerData) {
4886
          var viewEle, viewElements = navViewCtrl.getViewElements();
4887
          var enteringEleIdentifier = getViewElementIdentifier(viewLocals, enteringView);
4888
          var navViewActiveEleId = navViewCtrl.activeEleId();
4889
 
4890
          for (var x = 0, l = viewElements.length; x < l; x++) {
4891
            viewEle = viewElements.eq(x);
4892
 
4893
            if (viewEle.data(DATA_ELE_IDENTIFIER) === enteringEleIdentifier) {
4894
              // we found an existing element in the DOM that should be entering the view
4895
              if (viewEle.data(DATA_NO_CACHE)) {
4896
                // the existing element should not be cached, don't use it
4897
                viewEle.data(DATA_ELE_IDENTIFIER, enteringEleIdentifier + ionic.Utils.nextUid());
4898
                viewEle.data(DATA_DESTROY_ELE, true);
4899
 
4900
              } else {
4901
                enteringEle = viewEle;
4902
              }
4903
 
4904
            } else if (viewEle.data(DATA_ELE_IDENTIFIER) === navViewActiveEleId) {
4905
              leavingEle = viewEle;
4906
            }
4907
 
4908
            if (enteringEle && leavingEle) break;
4909
          }
4910
 
4911
          alreadyInDom = !!enteringEle;
4912
 
4913
          if (!alreadyInDom) {
4914
            // still no existing element to use
4915
            // create it using existing template/scope/locals
4916
            enteringEle = registerData.ele || ionicViewSwitcher.createViewEle(viewLocals);
4917
 
4918
            // existing elements in the DOM are looked up by their state name and state id
4919
            enteringEle.data(DATA_ELE_IDENTIFIER, enteringEleIdentifier);
4920
          }
4921
 
4922
          navViewCtrl.activeEleId(enteringEleIdentifier);
4923
 
4924
          registerData.ele = null;
4925
        },
4926
 
4927
        render: function(registerData, callback) {
4928
          // disconnect the leaving scope before reconnecting or creating a scope for the entering view
4929
          leavingEle && ionic.Utils.disconnectScope(leavingEle.scope());
4930
 
4931
          if (alreadyInDom) {
4932
            // it was already found in the DOM, just reconnect the scope
4933
            ionic.Utils.reconnectScope(enteringEle.scope());
4934
 
4935
          } else {
4936
            // the entering element is not already in the DOM
4937
            // set that the entering element should be "staged" and its
4938
            // styles of where this element will go before it hits the DOM
4939
            navViewAttr(enteringEle, VIEW_STATUS_STAGED);
4940
 
4941
            var enteringData = getTransitionData(viewLocals, enteringEle, registerData.direction, enteringView);
4942
            var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none;
4943
            transitionFn(enteringEle, null, enteringData.direction, true).run(0);
4944
 
4945
            enteringEle.data(DATA_VIEW, {
4946
              viewId: enteringData.viewId,
4947
              historyId: enteringData.historyId,
4948
              stateName: enteringData.stateName,
4949
              stateParams: enteringData.stateParams
4950
            });
4951
 
4952
            // if the current state has cache:false
4953
            // or the element has cache-view="false" attribute
4954
            if (viewState(viewLocals).cache === false || viewState(viewLocals).cache === 'false' ||
4955
                enteringEle.attr('cache-view') == 'false' || $ionicConfig.views.maxCache() === 0) {
4956
              enteringEle.data(DATA_NO_CACHE, true);
4957
            }
4958
 
4959
            // append the entering element to the DOM, create a new scope and run link
4960
            var viewScope = navViewCtrl.appendViewElement(enteringEle, viewLocals);
4961
 
4962
            delete enteringData.direction;
4963
            delete enteringData.transition;
4964
            viewScope.$emit('$ionicView.loaded', enteringData);
4965
          }
4966
 
4967
          // update that this view was just accessed
4968
          enteringEle.data(DATA_VIEW_ACCESSED, Date.now());
4969
 
4970
          callback && callback();
4971
        },
4972
 
4973
        transition: function(direction, enableBack) {
4974
          var deferred = $q.defer();
4975
          transitionPromises.push(deferred.promise);
4976
 
4977
          var enteringData = getTransitionData(viewLocals, enteringEle, direction, enteringView);
4978
          var leavingData = extend(extend({}, enteringData), getViewData(leavingView));
4979
          enteringData.transitionId = leavingData.transitionId = transitionId;
4980
          enteringData.fromCache = !!alreadyInDom;
4981
          enteringData.enableBack = !!enableBack;
4982
 
4983
          cachedAttr(enteringEle.parent(), 'nav-view-transition', enteringData.transition);
4984
          cachedAttr(enteringEle.parent(), 'nav-view-direction', enteringData.direction);
4985
 
4986
          // cancel any previous transition complete fallbacks
4987
          $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
4988
 
4989
          switcher.emit('before', enteringData, leavingData);
4990
 
4991
          // 1) get the transition ready and see if it'll animate
4992
          var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none;
4993
          var viewTransition = transitionFn(enteringEle, leavingEle, enteringData.direction, enteringData.shouldAnimate);
4994
 
4995
          if (viewTransition.shouldAnimate) {
4996
            // 2) attach transitionend events (and fallback timer)
4997
            enteringEle.on(TRANSITIONEND_EVENT, transitionComplete);
4998
            enteringEle.data(DATA_FALLBACK_TIMER, $timeout(transitionComplete, 1000));
4999
            $ionicClickBlock.show();
5000
          }
5001
 
5002
          // 3) stage entering element, opacity 0, no transition duration
5003
          navViewAttr(enteringEle, VIEW_STATUS_STAGED);
5004
 
5005
          // 4) place the elements in the correct step to begin
5006
          viewTransition.run(0);
5007
 
5008
          // 5) wait a frame so the styles apply
5009
          $timeout(onReflow, 16);
5010
 
5011
          function onReflow() {
5012
            // 6) remove that we're staging the entering element so it can transition
5013
            navViewAttr(enteringEle, viewTransition.shouldAnimate ? 'entering' : VIEW_STATUS_ACTIVE);
5014
            navViewAttr(leavingEle, viewTransition.shouldAnimate ? 'leaving' : VIEW_STATUS_CACHED);
5015
 
5016
            // 7) start the transition
5017
            viewTransition.run(1);
5018
 
5019
            $ionicNavBarDelegate._instances.forEach(function(instance) {
5020
              instance.triggerTransitionStart(transitionId);
5021
            });
5022
 
5023
            if (!viewTransition.shouldAnimate) {
5024
              // no animated transition
5025
              transitionComplete();
5026
            }
5027
          }
5028
 
5029
          function transitionComplete() {
5030
            if (transitionComplete.x) return;
5031
            transitionComplete.x = true;
5032
 
5033
            enteringEle.off(TRANSITIONEND_EVENT, transitionComplete);
5034
            $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
5035
            leavingEle && $timeout.cancel(leavingEle.data(DATA_FALLBACK_TIMER));
5036
 
5037
            // 8) emit that the views have finished transitioning
5038
            // each parent nav-view will update which views are active and cached
5039
            switcher.emit('after', enteringData, leavingData);
5040
 
5041
            // 9) resolve that this one transition (there could be many w/ nested views)
5042
            deferred.resolve(navViewCtrl);
5043
 
5044
            // 10) the most recent transition added has completed and all the active
5045
            // transition promises should be added to the services array of promises
5046
            if (transitionId === transitionCounter) {
5047
              $q.all(transitionPromises).then(ionicViewSwitcher.transitionEnd);
5048
              switcher.cleanup(enteringData);
5049
            }
5050
 
5051
            $ionicNavBarDelegate._instances.forEach(function(instance) {
5052
              instance.triggerTransitionEnd();
5053
            });
5054
 
5055
            // remove any references that could cause memory issues
5056
            nextTransition = nextDirection = enteringView = leavingView = enteringEle = leavingEle = null;
5057
          }
5058
 
5059
        },
5060
 
5061
        emit: function(step, enteringData, leavingData) {
5062
          var scope = enteringEle.scope();
5063
          if (scope) {
5064
            scope.$emit('$ionicView.' + step + 'Enter', enteringData);
5065
            if (step == 'after') {
5066
              scope.$emit('$ionicView.enter', enteringData);
5067
            }
5068
          }
5069
 
5070
          if (leavingEle) {
5071
            scope = leavingEle.scope();
5072
            if (scope) {
5073
              scope.$emit('$ionicView.' + step + 'Leave', leavingData);
5074
              if (step == 'after') {
5075
                scope.$emit('$ionicView.leave', leavingData);
5076
              }
5077
            }
5078
          }
5079
        },
5080
 
5081
        cleanup: function(transData) {
5082
          // check if any views should be removed
5083
          if (leavingEle && transData.direction == 'back' && !$ionicConfig.views.forwardCache()) {
5084
            // if they just navigated back we can destroy the forward view
5085
            // do not remove forward views if cacheForwardViews config is true
5086
            destroyViewEle(leavingEle);
5087
          }
5088
 
5089
          var viewElements = navViewCtrl.getViewElements();
5090
          var viewElementsLength = viewElements.length;
5091
          var x, viewElement;
5092
          var removeOldestAccess = (viewElementsLength - 1) > $ionicConfig.views.maxCache();
5093
          var removableEle;
5094
          var oldestAccess = Date.now();
5095
 
5096
          for (x = 0; x < viewElementsLength; x++) {
5097
            viewElement = viewElements.eq(x);
5098
 
5099
            if (removeOldestAccess && viewElement.data(DATA_VIEW_ACCESSED) < oldestAccess) {
5100
              // remember what was the oldest element to be accessed so it can be destroyed
5101
              oldestAccess = viewElement.data(DATA_VIEW_ACCESSED);
5102
              removableEle = viewElements.eq(x);
5103
 
5104
            } else if (viewElement.data(DATA_DESTROY_ELE) && navViewAttr(viewElement) != VIEW_STATUS_ACTIVE) {
5105
              destroyViewEle(viewElement);
5106
            }
5107
          }
5108
 
5109
          destroyViewEle(removableEle);
5110
 
5111
          if (enteringEle.data(DATA_NO_CACHE)) {
5112
            enteringEle.data(DATA_DESTROY_ELE, true);
5113
          }
5114
        },
5115
 
5116
        enteringEle: function() { return enteringEle; },
5117
        leavingEle: function() { return leavingEle; }
5118
 
5119
      };
5120
 
5121
      return switcher;
5122
    },
5123
 
5124
    transitionEnd: function(navViewCtrls) {
5125
      forEach(navViewCtrls, function(navViewCtrl){
5126
        navViewCtrl.transitionEnd();
5127
      });
5128
 
5129
      ionicViewSwitcher.isTransitioning(false);
5130
      $ionicClickBlock.hide();
5131
      transitionPromises = [];
5132
    },
5133
 
5134
    nextTransition: function(val) {
5135
      nextTransition = val;
5136
    },
5137
 
5138
    nextDirection: function(val) {
5139
      nextDirection = val;
5140
    },
5141
 
5142
    isTransitioning: function(val) {
5143
      if (arguments.length) {
5144
        ionic.transition.isActive = !!val;
5145
        $timeout.cancel(isActiveTimer);
5146
        if (val) {
5147
          isActiveTimer = $timeout(function() {
5148
            ionicViewSwitcher.isTransitioning(false);
5149
          }, 999);
5150
        }
5151
      }
5152
      return ionic.transition.isActive;
5153
    },
5154
 
5155
    createViewEle: function(viewLocals) {
5156
      var containerEle = $document[0].createElement('div');
5157
      if (viewLocals && viewLocals.$template) {
5158
        containerEle.innerHTML = viewLocals.$template;
5159
        if (containerEle.children.length === 1) {
5160
          containerEle.children[0].classList.add('pane');
5161
          return jqLite(containerEle.children[0]);
5162
        }
5163
      }
5164
      containerEle.className = "pane";
5165
      return jqLite(containerEle);
5166
    },
5167
 
5168
    viewEleIsActive: function(viewEle, isActiveAttr) {
5169
      navViewAttr(viewEle, isActiveAttr ? VIEW_STATUS_ACTIVE : VIEW_STATUS_CACHED);
5170
    },
5171
 
5172
    getTransitionData: getTransitionData,
5173
    navViewAttr: navViewAttr,
5174
    destroyViewEle: destroyViewEle
5175
 
5176
  };
5177
 
5178
  return ionicViewSwitcher;
5179
 
5180
 
5181
  function getViewElementIdentifier(locals, view) {
5182
    if (viewState(locals).abstract) return viewState(locals).name;
5183
    if (view) return view.stateId || view.viewId;
5184
    return ionic.Utils.nextUid();
5185
  }
5186
 
5187
  function viewState(locals) {
5188
    return locals && locals.$$state && locals.$$state.self || {};
5189
  }
5190
 
5191
  function getTransitionData(viewLocals, enteringEle, direction, view) {
5192
    // Priority
5193
    // 1) attribute directive on the button/link to this view
5194
    // 2) entering element's attribute
5195
    // 3) entering view's $state config property
5196
    // 4) view registration data
5197
    // 5) global config
5198
    // 6) fallback value
5199
 
5200
    var state = viewState(viewLocals);
5201
    var viewTransition = nextTransition || cachedAttr(enteringEle, 'view-transition') || state.viewTransition || $ionicConfig.views.transition() || 'ios';
5202
    var navBarTransition = $ionicConfig.navBar.transition();
5203
    direction = nextDirection || cachedAttr(enteringEle, 'view-direction') || state.viewDirection || direction || 'none';
5204
 
5205
    return extend(getViewData(view), {
5206
      transition: viewTransition,
5207
      navBarTransition: navBarTransition === 'view' ? viewTransition : navBarTransition,
5208
      direction: direction,
5209
      shouldAnimate: (viewTransition !== 'none' && direction !== 'none')
5210
    });
5211
  }
5212
 
5213
  function getViewData(view) {
5214
    view = view || {};
5215
    return {
5216
      viewId: view.viewId,
5217
      historyId: view.historyId,
5218
      stateId: view.stateId,
5219
      stateName: view.stateName,
5220
      stateParams: view.stateParams
5221
    };
5222
  }
5223
 
5224
  function navViewAttr(ele, value) {
5225
    if (arguments.length > 1) {
5226
      cachedAttr(ele, NAV_VIEW_ATTR, value);
5227
    } else {
5228
      return cachedAttr(ele, NAV_VIEW_ATTR);
5229
    }
5230
  }
5231
 
5232
  function destroyViewEle(ele) {
5233
    // we found an element that should be removed
5234
    // destroy its scope, then remove the element
5235
    if (ele && ele.length) {
5236
      var viewScope = ele.scope();
5237
      if (viewScope) {
5238
        viewScope.$emit('$ionicView.unloaded', ele.data(DATA_VIEW));
5239
        viewScope.$destroy();
5240
      }
5241
      ele.remove();
5242
    }
5243
  }
5244
 
5245
}]);
5246
 
5247
/**
5248
 * @private
5249
 * Parts of Ionic requires that $scope data is attached to the element.
5250
 * We do not want to disable adding $scope data to the $element when
5251
 * $compileProvider.debugInfoEnabled(false) is used.
5252
 */
5253
IonicModule.config(['$provide', function($provide) {
5254
  $provide.decorator('$compile', ['$delegate', function($compile) {
5255
     $compile.$$addScopeInfo = function $$addScopeInfo($element, scope, isolated, noTemplate) {
5256
       var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
5257
       $element.data(dataName, scope);
5258
     };
5259
     return $compile;
5260
  }]);
5261
}]);
5262
 
5263
/**
5264
 * @private
5265
 */
5266
IonicModule.config([
5267
  '$provide',
5268
function($provide) {
5269
  function $LocationDecorator($location, $timeout) {
5270
 
5271
    $location.__hash = $location.hash;
5272
    //Fix: when window.location.hash is set, the scrollable area
5273
    //found nearest to body's scrollTop is set to scroll to an element
5274
    //with that ID.
5275
    $location.hash = function(value) {
5276
      if (angular.isDefined(value)) {
5277
        $timeout(function() {
5278
          var scroll = document.querySelector('.scroll-content');
5279
          if (scroll)
5280
            scroll.scrollTop = 0;
5281
        }, 0, false);
5282
      }
5283
      return $location.__hash(value);
5284
    };
5285
 
5286
    return $location;
5287
  }
5288
 
5289
  $provide.decorator('$location', ['$delegate', '$timeout', $LocationDecorator]);
5290
}]);
5291
 
5292
IonicModule
5293
 
5294
.controller('$ionicHeaderBar', [
5295
  '$scope',
5296
  '$element',
5297
  '$attrs',
5298
  '$q',
5299
  '$ionicConfig',
5300
  '$ionicHistory',
5301
function($scope, $element, $attrs, $q, $ionicConfig, $ionicHistory) {
5302
  var TITLE = 'title';
5303
  var BACK_TEXT = 'back-text';
5304
  var BACK_BUTTON = 'back-button';
5305
  var DEFAULT_TITLE = 'default-title';
5306
  var PREVIOUS_TITLE = 'previous-title';
5307
  var HIDE = 'hide';
5308
 
5309
  var self = this;
5310
  var titleText = '';
5311
  var previousTitleText = '';
5312
  var titleLeft = 0;
5313
  var titleRight = 0;
5314
  var titleCss = '';
5315
  var isBackEnabled = false;
5316
  var isBackShown = true;
5317
  var isNavBackShown = true;
5318
  var isBackElementShown = false;
5319
  var titleTextWidth = 0;
5320
 
5321
 
5322
  self.beforeEnter = function(viewData) {
5323
    $scope.$broadcast('$ionicView.beforeEnter', viewData);
5324
  };
5325
 
5326
 
5327
  self.title = function(newTitleText) {
5328
    if (arguments.length && newTitleText !== titleText) {
5329
      getEle(TITLE).innerHTML = newTitleText;
5330
      titleText = newTitleText;
5331
      titleTextWidth = 0;
5332
    }
5333
    return titleText;
5334
  };
5335
 
5336
 
5337
  self.enableBack = function(shouldEnable, disableReset) {
5338
    // whether or not the back button show be visible, according
5339
    // to the navigation and history
5340
    if (arguments.length) {
5341
      isBackEnabled = shouldEnable;
5342
      if (!disableReset) self.updateBackButton();
5343
    }
5344
    return isBackEnabled;
5345
  };
5346
 
5347
 
5348
  self.showBack = function(shouldShow, disableReset) {
5349
    // different from enableBack() because this will always have the back
5350
    // visually hidden if false, even if the history says it should show
5351
    if (arguments.length) {
5352
      isBackShown = shouldShow;
5353
      if (!disableReset) self.updateBackButton();
5354
    }
5355
    return isBackShown;
5356
  };
5357
 
5358
 
5359
  self.showNavBack = function(shouldShow) {
5360
    // different from showBack() because this is for the entire nav bar's
5361
    // setting for all of it's child headers. For internal use.
5362
    isNavBackShown = shouldShow;
5363
    self.updateBackButton();
5364
  };
5365
 
5366
 
5367
  self.updateBackButton = function() {
5368
    if ((isBackShown && isNavBackShown && isBackEnabled) !== isBackElementShown) {
5369
      isBackElementShown = isBackShown && isNavBackShown && isBackEnabled;
5370
      var backBtnEle = getEle(BACK_BUTTON);
5371
      backBtnEle && backBtnEle.classList[ isBackElementShown ? 'remove' : 'add' ](HIDE);
5372
    }
5373
  };
5374
 
5375
 
5376
  self.titleTextWidth = function() {
5377
    if (!titleTextWidth) {
5378
      var bounds = ionic.DomUtil.getTextBounds(getEle(TITLE));
5379
      titleTextWidth = Math.min(bounds && bounds.width || 30);
5380
    }
5381
    return titleTextWidth;
5382
  };
5383
 
5384
 
5385
  self.titleWidth = function() {
5386
    var titleWidth = self.titleTextWidth();
5387
    var offsetWidth = getEle(TITLE).offsetWidth;
5388
    if (offsetWidth < titleWidth) {
5389
      titleWidth = offsetWidth + (titleLeft - titleRight - 5);
5390
    }
5391
    return titleWidth;
5392
  };
5393
 
5394
 
5395
  self.titleTextX = function() {
5396
    return ($element[0].offsetWidth / 2) - (self.titleWidth() / 2);
5397
  };
5398
 
5399
 
5400
  self.titleLeftRight = function() {
5401
    return titleLeft - titleRight;
5402
  };
5403
 
5404
 
5405
  self.backButtonTextLeft = function() {
5406
    var offsetLeft = 0;
5407
    var ele = getEle(BACK_TEXT);
5408
    while (ele) {
5409
      offsetLeft += ele.offsetLeft;
5410
      ele = ele.parentElement;
5411
    }
5412
    return offsetLeft;
5413
  };
5414
 
5415
 
5416
  self.resetBackButton = function() {
5417
    if ($ionicConfig.backButton.previousTitleText()) {
5418
      var previousTitleEle = getEle(PREVIOUS_TITLE);
5419
      if (previousTitleEle) {
5420
        previousTitleEle.classList.remove(HIDE);
5421
 
5422
        var newPreviousTitleText = $ionicHistory.backTitle();
5423
 
5424
        if (newPreviousTitleText !== previousTitleText) {
5425
          previousTitleText = previousTitleEle.innerHTML = newPreviousTitleText;
5426
        }
5427
      }
5428
      var defaultTitleEle = getEle(DEFAULT_TITLE);
5429
      if (defaultTitleEle) {
5430
        defaultTitleEle.classList.remove(HIDE);
5431
      }
5432
    }
5433
  };
5434
 
5435
 
5436
  self.align = function(textAlign) {
5437
    var titleEle = getEle(TITLE);
5438
 
5439
    textAlign = textAlign || $attrs.alignTitle || $ionicConfig.navBar.alignTitle();
5440
 
5441
    var widths = self.calcWidths(textAlign, false);
5442
 
5443
    if (isBackShown && previousTitleText && $ionicConfig.backButton.previousTitleText()) {
5444
      var previousTitleWidths = self.calcWidths(textAlign, true);
5445
 
5446
      var availableTitleWidth = $element[0].offsetWidth - previousTitleWidths.titleLeft - previousTitleWidths.titleRight;
5447
 
5448
      if (self.titleTextWidth() <= availableTitleWidth) {
5449
        widths = previousTitleWidths;
5450
      }
5451
    }
5452
 
5453
    return self.updatePositions(titleEle, widths.titleLeft, widths.titleRight, widths.buttonsLeft, widths.buttonsRight, widths.css, widths.showPrevTitle);
5454
  };
5455
 
5456
 
5457
  self.calcWidths = function(textAlign, isPreviousTitle) {
5458
    var titleEle = getEle(TITLE);
5459
    var backBtnEle = getEle(BACK_BUTTON);
5460
    var x, y, z, b, c, d, childSize, bounds;
5461
    var childNodes = $element[0].childNodes;
5462
    var buttonsLeft = 0;
5463
    var buttonsRight = 0;
5464
    var isCountRightOfTitle;
5465
    var updateTitleLeft = 0;
5466
    var updateTitleRight = 0;
5467
    var updateCss = '';
5468
    var backButtonWidth = 0;
5469
 
5470
    // Compute how wide the left children are
5471
    // Skip all titles (there may still be two titles, one leaving the dom)
5472
    // Once we encounter a titleEle, realize we are now counting the right-buttons, not left
5473
    for (x = 0; x < childNodes.length; x++) {
5474
      c = childNodes[x];
5475
 
5476
      childSize = 0;
5477
      if (c.nodeType == 1) {
5478
        // element node
5479
        if (c === titleEle) {
5480
          isCountRightOfTitle = true;
5481
          continue;
5482
        }
5483
 
5484
        if (c.classList.contains(HIDE)) {
5485
          continue;
5486
        }
5487
 
5488
        if (isBackShown && c === backBtnEle) {
5489
 
5490
          for (y = 0; y < c.childNodes.length; y++) {
5491
            b = c.childNodes[y];
5492
 
5493
            if (b.nodeType == 1) {
5494
 
5495
              if (b.classList.contains(BACK_TEXT)) {
5496
                for (z = 0; z < b.children.length; z++) {
5497
                  d = b.children[z];
5498
 
5499
                  if (isPreviousTitle) {
5500
                    if (d.classList.contains(DEFAULT_TITLE)) continue;
5501
                    backButtonWidth += d.offsetWidth;
5502
                  } else {
5503
                    if (d.classList.contains(PREVIOUS_TITLE)) continue;
5504
                    backButtonWidth += d.offsetWidth;
5505
                  }
5506
                }
5507
 
5508
              } else {
5509
                backButtonWidth += b.offsetWidth;
5510
              }
5511
 
5512
            } else if (b.nodeType == 3 && b.nodeValue.trim()) {
5513
              bounds = ionic.DomUtil.getTextBounds(b);
5514
              backButtonWidth += bounds && bounds.width || 0;
5515
            }
5516
 
5517
          }
5518
          childSize = backButtonWidth || c.offsetWidth;
5519
 
5520
        } else {
5521
          // not the title, not the back button, not a hidden element
5522
          childSize = c.offsetWidth;
5523
        }
5524
 
5525
      } else if (c.nodeType == 3 && c.nodeValue.trim()) {
5526
        // text node
5527
        bounds = ionic.DomUtil.getTextBounds(c);
5528
        childSize = bounds && bounds.width || 0;
5529
      }
5530
 
5531
      if (isCountRightOfTitle) {
5532
        buttonsRight += childSize;
5533
      } else {
5534
        buttonsLeft += childSize;
5535
      }
5536
    }
5537
 
5538
    // Size and align the header titleEle based on the sizes of the left and
5539
    // right children, and the desired alignment mode
5540
    if (textAlign == 'left') {
5541
      updateCss = 'title-left';
5542
      if (buttonsLeft) {
5543
        updateTitleLeft = buttonsLeft + 15;
5544
      }
5545
      if (buttonsRight) {
5546
        updateTitleRight = buttonsRight + 15;
5547
      }
5548
 
5549
    } else if (textAlign == 'right') {
5550
      updateCss = 'title-right';
5551
      if (buttonsLeft) {
5552
        updateTitleLeft = buttonsLeft + 15;
5553
      }
5554
      if (buttonsRight) {
5555
        updateTitleRight = buttonsRight + 15;
5556
      }
5557
 
5558
    } else {
5559
      // center the default
5560
      var margin = Math.max(buttonsLeft, buttonsRight) + 10;
5561
      if (margin > 10) {
5562
        updateTitleLeft = updateTitleRight = margin;
5563
      }
5564
    }
5565
 
5566
    return {
5567
      backButtonWidth: backButtonWidth,
5568
      buttonsLeft: buttonsLeft,
5569
      buttonsRight: buttonsRight,
5570
      titleLeft: updateTitleLeft,
5571
      titleRight: updateTitleRight,
5572
      showPrevTitle: isPreviousTitle,
5573
      css: updateCss
5574
    };
5575
  };
5576
 
5577
 
5578
  self.updatePositions = function(titleEle, updateTitleLeft, updateTitleRight, buttonsLeft, buttonsRight, updateCss, showPreviousTitle) {
5579
    var deferred = $q.defer();
5580
 
5581
    // only make DOM updates when there are actual changes
5582
    if (titleEle) {
5583
      if (updateTitleLeft !== titleLeft) {
5584
        titleEle.style.left = updateTitleLeft ? updateTitleLeft + 'px' : '';
5585
        titleLeft = updateTitleLeft;
5586
      }
5587
      if (updateTitleRight !== titleRight) {
5588
        titleEle.style.right = updateTitleRight ? updateTitleRight + 'px' : '';
5589
        titleRight = updateTitleRight;
5590
      }
5591
 
5592
      if (updateCss !== titleCss) {
5593
        updateCss && titleEle.classList.add(updateCss);
5594
        titleCss && titleEle.classList.remove(titleCss);
5595
        titleCss = updateCss;
5596
      }
5597
    }
5598
 
5599
    if ($ionicConfig.backButton.previousTitleText()) {
5600
      var prevTitle = getEle(PREVIOUS_TITLE);
5601
      var defaultTitle = getEle(DEFAULT_TITLE);
5602
 
5603
      prevTitle && prevTitle.classList[ showPreviousTitle ? 'remove' : 'add'](HIDE);
5604
      defaultTitle && defaultTitle.classList[ showPreviousTitle ? 'add' : 'remove'](HIDE);
5605
    }
5606
 
5607
    ionic.requestAnimationFrame(function() {
5608
      if (titleEle && titleEle.offsetWidth + 10 < titleEle.scrollWidth) {
5609
        var minRight = buttonsRight + 5;
5610
        var testRight = $element[0].offsetWidth - titleLeft - self.titleTextWidth() - 20;
5611
        updateTitleRight = testRight < minRight ? minRight : testRight;
5612
        if (updateTitleRight !== titleRight) {
5613
          titleEle.style.right = updateTitleRight + 'px';
5614
          titleRight = updateTitleRight;
5615
        }
5616
      }
5617
      deferred.resolve();
5618
    });
5619
 
5620
    return deferred.promise;
5621
  };
5622
 
5623
 
5624
  self.setCss = function(elementClassname, css) {
5625
    ionic.DomUtil.cachedStyles(getEle(elementClassname), css);
5626
  };
5627
 
5628
 
5629
  var eleCache = {};
5630
  function getEle(className) {
5631
    if (!eleCache[className]) {
5632
      eleCache[className] = $element[0].querySelector('.' + className);
5633
    }
5634
    return eleCache[className];
5635
  }
5636
 
5637
 
5638
  $scope.$on('$destroy', function() {
5639
    for (var n in eleCache) eleCache[n] = null;
5640
  });
5641
 
5642
}]);
5643
 
5644
 
5645
/**
5646
 * @ngdoc service
5647
 * @name $ionicListDelegate
5648
 * @module ionic
5649
 *
5650
 * @description
5651
 * Delegate for controlling the {@link ionic.directive:ionList} directive.
5652
 *
5653
 * Methods called directly on the $ionicListDelegate service will control all lists.
5654
 * Use the {@link ionic.service:$ionicListDelegate#$getByHandle $getByHandle}
5655
 * method to control specific ionList instances.
5656
 *
5657
 * @usage
5658
 *
5659
 * ````html
5660
 * <ion-content ng-controller="MyCtrl">
5661
 *   <button class="button" ng-click="showDeleteButtons()"></button>
5662
 *   <ion-list>
5663
 *     <ion-item ng-repeat="i in items">
5664
 *       {% raw %}Hello, {{i}}!{% endraw %}
5665
 *       <ion-delete-button class="ion-minus-circled"></ion-delete-button>
5666
 *     </ion-item>
5667
 *   </ion-list>
5668
 * </ion-content>
5669
 * ```
5670
 * ```js
5671
 * function MyCtrl($scope, $ionicListDelegate) {
5672
 *   $scope.showDeleteButtons = function() {
5673
 *     $ionicListDelegate.showDelete(true);
5674
 *   };
5675
 * }
5676
 * ```
5677
 */
5678
IonicModule
5679
.service('$ionicListDelegate', ionic.DelegateService([
5680
  /**
5681
   * @ngdoc method
5682
   * @name $ionicListDelegate#showReorder
5683
   * @param {boolean=} showReorder Set whether or not this list is showing its reorder buttons.
5684
   * @returns {boolean} Whether the reorder buttons are shown.
5685
   */
5686
  'showReorder',
5687
  /**
5688
   * @ngdoc method
5689
   * @name $ionicListDelegate#showDelete
5690
   * @param {boolean=} showDelete Set whether or not this list is showing its delete buttons.
5691
   * @returns {boolean} Whether the delete buttons are shown.
5692
   */
5693
  'showDelete',
5694
  /**
5695
   * @ngdoc method
5696
   * @name $ionicListDelegate#canSwipeItems
5697
   * @param {boolean=} canSwipeItems Set whether or not this list is able to swipe to show
5698
   * option buttons.
5699
   * @returns {boolean} Whether the list is able to swipe to show option buttons.
5700
   */
5701
  'canSwipeItems',
5702
  /**
5703
   * @ngdoc method
5704
   * @name $ionicListDelegate#closeOptionButtons
5705
   * @description Closes any option buttons on the list that are swiped open.
5706
   */
5707
  'closeOptionButtons',
5708
  /**
5709
   * @ngdoc method
5710
   * @name $ionicListDelegate#$getByHandle
5711
   * @param {string} handle
5712
   * @returns `delegateInstance` A delegate instance that controls only the
5713
   * {@link ionic.directive:ionList} directives with `delegate-handle` matching
5714
   * the given handle.
5715
   *
5716
   * Example: `$ionicListDelegate.$getByHandle('my-handle').showReorder(true);`
5717
   */
5718
]))
5719
 
5720
.controller('$ionicList', [
5721
  '$scope',
5722
  '$attrs',
5723
  '$ionicListDelegate',
5724
  '$ionicHistory',
5725
function($scope, $attrs, $ionicListDelegate, $ionicHistory) {
5726
  var self = this;
5727
  var isSwipeable = true;
5728
  var isReorderShown = false;
5729
  var isDeleteShown = false;
5730
 
5731
  var deregisterInstance = $ionicListDelegate._registerInstance(
5732
    self, $attrs.delegateHandle, function() {
5733
      return $ionicHistory.isActiveScope($scope);
5734
    }
5735
  );
5736
  $scope.$on('$destroy', deregisterInstance);
5737
 
5738
  self.showReorder = function(show) {
5739
    if (arguments.length) {
5740
      isReorderShown = !!show;
5741
    }
5742
    return isReorderShown;
5743
  };
5744
 
5745
  self.showDelete = function(show) {
5746
    if (arguments.length) {
5747
      isDeleteShown = !!show;
5748
    }
5749
    return isDeleteShown;
5750
  };
5751
 
5752
  self.canSwipeItems = function(can) {
5753
    if (arguments.length) {
5754
      isSwipeable = !!can;
5755
    }
5756
    return isSwipeable;
5757
  };
5758
 
5759
  self.closeOptionButtons = function() {
5760
    self.listView && self.listView.clearDragEffects();
5761
  };
5762
}]);
5763
 
5764
IonicModule
5765
 
5766
.controller('$ionicNavBar', [
5767
  '$scope',
5768
  '$element',
5769
  '$attrs',
5770
  '$compile',
5771
  '$timeout',
5772
  '$ionicNavBarDelegate',
5773
  '$ionicConfig',
5774
  '$ionicHistory',
5775
function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $ionicConfig, $ionicHistory) {
5776
 
5777
  var CSS_HIDE = 'hide';
5778
  var DATA_NAV_BAR_CTRL = '$ionNavBarController';
5779
  var PRIMARY_BUTTONS = 'primaryButtons';
5780
  var SECONDARY_BUTTONS = 'secondaryButtons';
5781
  var BACK_BUTTON = 'backButton';
5782
  var ITEM_TYPES = 'primaryButtons secondaryButtons leftButtons rightButtons title'.split(' ');
5783
 
5784
  var self = this;
5785
  var headerBars = [];
5786
  var navElementHtml = {};
5787
  var isVisible = true;
5788
  var queuedTransitionStart, queuedTransitionEnd, latestTransitionId;
5789
 
5790
  $element.parent().data(DATA_NAV_BAR_CTRL, self);
5791
 
5792
  var delegateHandle = $attrs.delegateHandle || 'navBar' + ionic.Utils.nextUid();
5793
 
5794
  var deregisterInstance = $ionicNavBarDelegate._registerInstance(self, delegateHandle);
5795
 
5796
 
5797
  self.init = function() {
5798
    $element.addClass('nav-bar-container');
5799
    ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', $ionicConfig.views.transition());
5800
 
5801
    // create two nav bar blocks which will trade out which one is shown
5802
    self.createHeaderBar(false);
5803
    self.createHeaderBar(true);
5804
 
5805
    $scope.$emit('ionNavBar.init', delegateHandle);
5806
  };
5807
 
5808
 
5809
  self.createHeaderBar = function(isActive, navBarClass) {
5810
    var containerEle = jqLite('<div class="nav-bar-block">');
5811
    ionic.DomUtil.cachedAttr(containerEle, 'nav-bar', isActive ? 'active' : 'cached');
5812
 
5813
    var alignTitle = $attrs.alignTitle || $ionicConfig.navBar.alignTitle();
5814
    var headerBarEle = jqLite('<ion-header-bar>').addClass($attrs.class).attr('align-title', alignTitle);
5815
    if (isDefined($attrs.noTapScroll)) headerBarEle.attr('no-tap-scroll', $attrs.noTapScroll);
5816
    var titleEle = jqLite('<div class="title title-' + alignTitle + '">');
5817
    var navEle = {};
5818
    var lastViewItemEle = {};
5819
    var leftButtonsEle, rightButtonsEle;
5820
 
5821
    //navEle[BACK_BUTTON] = self.createBackButtonElement(headerBarEle);
5822
    navEle[BACK_BUTTON] = createNavElement(BACK_BUTTON);
5823
    navEle[BACK_BUTTON] && headerBarEle.append(navEle[BACK_BUTTON]);
5824
 
5825
    // append title in the header, this is the rock to where buttons append
5826
    headerBarEle.append(titleEle);
5827
 
5828
    forEach(ITEM_TYPES, function(itemType) {
5829
      // create default button elements
5830
      navEle[itemType] = createNavElement(itemType);
5831
      // append and position buttons
5832
      positionItem(navEle[itemType], itemType);
5833
    });
5834
 
5835
    // add header-item to the root children
5836
    for (var x = 0; x < headerBarEle[0].children.length; x++) {
5837
      headerBarEle[0].children[x].classList.add('header-item');
5838
    }
5839
 
5840
    // compile header and append to the DOM
5841
    containerEle.append(headerBarEle);
5842
    $element.append($compile(containerEle)($scope.$new()));
5843
 
5844
    var headerBarCtrl = headerBarEle.data('$ionHeaderBarController');
5845
 
5846
    var headerBarInstance = {
5847
      isActive: isActive,
5848
      title: function(newTitleText) {
5849
        headerBarCtrl.title(newTitleText);
5850
      },
5851
      setItem: function(navBarItemEle, itemType) {
5852
        // first make sure any exiting nav bar item has been removed
5853
        headerBarInstance.removeItem(itemType);
5854
 
5855
        if (navBarItemEle) {
5856
          if (itemType === 'title') {
5857
            // clear out the text based title
5858
            headerBarInstance.title("");
5859
          }
5860
 
5861
          // there's a custom nav bar item
5862
          positionItem(navBarItemEle, itemType);
5863
 
5864
          if (navEle[itemType]) {
5865
            // make sure the default on this itemType is hidden
5866
            navEle[itemType].addClass(CSS_HIDE);
5867
          }
5868
          lastViewItemEle[itemType] = navBarItemEle;
5869
 
5870
        } else if (navEle[itemType]) {
5871
          // there's a default button for this side and no view button
5872
          navEle[itemType].removeClass(CSS_HIDE);
5873
        }
5874
      },
5875
      removeItem: function(itemType) {
5876
        if (lastViewItemEle[itemType]) {
5877
          lastViewItemEle[itemType].scope().$destroy();
5878
          lastViewItemEle[itemType].remove();
5879
          lastViewItemEle[itemType] = null;
5880
        }
5881
      },
5882
      containerEle: function() {
5883
        return containerEle;
5884
      },
5885
      headerBarEle: function() {
5886
        return headerBarEle;
5887
      },
5888
      afterLeave: function() {
5889
        forEach(ITEM_TYPES, function(itemType) {
5890
          headerBarInstance.removeItem(itemType);
5891
        });
5892
        headerBarCtrl.resetBackButton();
5893
      },
5894
      controller: function() {
5895
        return headerBarCtrl;
5896
      },
5897
      destroy: function() {
5898
        forEach(ITEM_TYPES, function(itemType) {
5899
          headerBarInstance.removeItem(itemType);
5900
        });
5901
        containerEle.scope().$destroy();
5902
        for (var n in navEle) {
5903
          if (navEle[n]) {
5904
            navEle[n].removeData();
5905
            navEle[n] = null;
5906
          }
5907
        }
5908
        leftButtonsEle && leftButtonsEle.removeData();
5909
        rightButtonsEle && rightButtonsEle.removeData();
5910
        titleEle.removeData();
5911
        headerBarEle.removeData();
5912
        containerEle.remove();
5913
        containerEle = headerBarEle = titleEle = leftButtonsEle = rightButtonsEle = null;
5914
      }
5915
    };
5916
 
5917
    function positionItem(ele, itemType) {
5918
      if (!ele) return;
5919
 
5920
      if (itemType === 'title') {
5921
        // title element
5922
        titleEle.append(ele);
5923
 
5924
      } else if (itemType == 'rightButtons' ||
5925
                (itemType == SECONDARY_BUTTONS && $ionicConfig.navBar.positionSecondaryButtons() != 'left') ||
5926
                (itemType == PRIMARY_BUTTONS && $ionicConfig.navBar.positionPrimaryButtons() == 'right')) {
5927
        // right side
5928
        if (!rightButtonsEle) {
5929
          rightButtonsEle = jqLite('<div class="buttons buttons-right">');
5930
          headerBarEle.append(rightButtonsEle);
5931
        }
5932
        if (itemType == SECONDARY_BUTTONS) {
5933
          rightButtonsEle.append(ele);
5934
        } else {
5935
          rightButtonsEle.prepend(ele);
5936
        }
5937
 
5938
      } else {
5939
        // left side
5940
        if (!leftButtonsEle) {
5941
          leftButtonsEle = jqLite('<div class="buttons buttons-left">');
5942
          if (navEle[BACK_BUTTON]) {
5943
            navEle[BACK_BUTTON].after(leftButtonsEle);
5944
          } else {
5945
            headerBarEle.prepend(leftButtonsEle);
5946
          }
5947
        }
5948
        if (itemType == SECONDARY_BUTTONS) {
5949
          leftButtonsEle.append(ele);
5950
        } else {
5951
          leftButtonsEle.prepend(ele);
5952
        }
5953
      }
5954
 
5955
    }
5956
 
5957
    headerBars.push(headerBarInstance);
5958
 
5959
    return headerBarInstance;
5960
  };
5961
 
5962
 
5963
  self.navElement = function(type, html) {
5964
    if (isDefined(html)) {
5965
      navElementHtml[type] = html;
5966
    }
5967
    return navElementHtml[type];
5968
  };
5969
 
5970
 
5971
  self.update = function(viewData) {
5972
    var showNavBar = !viewData.hasHeaderBar && viewData.showNavBar;
5973
    viewData.transition = $ionicConfig.views.transition();
5974
 
5975
    if (!showNavBar) {
5976
      viewData.direction = 'none';
5977
    }
5978
 
5979
    self.enable(showNavBar);
5980
    var enteringHeaderBar = self.isInitialized ? getOffScreenHeaderBar() : getOnScreenHeaderBar();
5981
    var leavingHeaderBar = self.isInitialized ? getOnScreenHeaderBar() : null;
5982
    var enteringHeaderCtrl = enteringHeaderBar.controller();
5983
 
5984
    // update if the entering header should show the back button or not
5985
    enteringHeaderCtrl.enableBack(viewData.enableBack, true);
5986
    enteringHeaderCtrl.showBack(viewData.showBack, true);
5987
    enteringHeaderCtrl.updateBackButton();
5988
 
5989
    // update the entering header bar's title
5990
    self.title(viewData.title, enteringHeaderBar);
5991
 
5992
    self.showBar(showNavBar);
5993
 
5994
    // update the nav bar items, depending if the view has their own or not
5995
    if (viewData.navBarItems) {
5996
      forEach(ITEM_TYPES, function(itemType) {
5997
        enteringHeaderBar.setItem(viewData.navBarItems[itemType], itemType);
5998
      });
5999
    }
6000
 
6001
    // begin transition of entering and leaving header bars
6002
    self.transition(enteringHeaderBar, leavingHeaderBar, viewData);
6003
 
6004
    self.isInitialized = true;
6005
  };
6006
 
6007
 
6008
  self.transition = function(enteringHeaderBar, leavingHeaderBar, viewData) {
6009
    var enteringHeaderBarCtrl = enteringHeaderBar.controller();
6010
    var transitionFn = $ionicConfig.transitions.navBar[viewData.navBarTransition] || $ionicConfig.transitions.navBar.none;
6011
    var transitionId = viewData.transitionId;
6012
 
6013
    enteringHeaderBarCtrl.beforeEnter(viewData);
6014
 
6015
    var navBarTransition = transitionFn(enteringHeaderBar, leavingHeaderBar, viewData.direction, viewData.shouldAnimate && self.isInitialized);
6016
 
6017
    ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', viewData.navBarTransition);
6018
    ionic.DomUtil.cachedAttr($element, 'nav-bar-direction', viewData.direction);
6019
 
6020
    if (navBarTransition.shouldAnimate) {
6021
      navBarAttr(enteringHeaderBar, 'stage');
6022
    } else {
6023
      navBarAttr(enteringHeaderBar, 'entering');
6024
      navBarAttr(leavingHeaderBar, 'leaving');
6025
    }
6026
 
6027
    enteringHeaderBarCtrl.resetBackButton();
6028
 
6029
    navBarTransition.run(0);
6030
 
6031
    $timeout(enteringHeaderBarCtrl.align, 16);
6032
 
6033
    queuedTransitionStart = function() {
6034
      if (latestTransitionId !== transitionId) return;
6035
 
6036
      navBarAttr(enteringHeaderBar, 'entering');
6037
      navBarAttr(leavingHeaderBar, 'leaving');
6038
 
6039
      navBarTransition.run(1);
6040
 
6041
      queuedTransitionEnd = function() {
6042
        if (latestTransitionId == transitionId || !navBarTransition.shouldAnimate) {
6043
          for (var x = 0; x < headerBars.length; x++) {
6044
            headerBars[x].isActive = false;
6045
          }
6046
          enteringHeaderBar.isActive = true;
6047
 
6048
          navBarAttr(enteringHeaderBar, 'active');
6049
          navBarAttr(leavingHeaderBar, 'cached');
6050
 
6051
          queuedTransitionEnd = null;
6052
        }
6053
      };
6054
 
6055
      queuedTransitionStart = null;
6056
    };
6057
 
6058
    queuedTransitionStart();
6059
 
6060
  };
6061
 
6062
 
6063
  self.triggerTransitionStart = function(triggerTransitionId) {
6064
    latestTransitionId = triggerTransitionId;
6065
    queuedTransitionStart && queuedTransitionStart();
6066
  };
6067
 
6068
 
6069
  self.triggerTransitionEnd = function() {
6070
    queuedTransitionEnd && queuedTransitionEnd();
6071
  };
6072
 
6073
 
6074
  self.showBar = function(shouldShow) {
6075
    if (arguments.length) {
6076
      self.visibleBar(shouldShow);
6077
      $scope.$parent.$hasHeader = !!shouldShow;
6078
    }
6079
    return !!$scope.$parent.$hasHeader;
6080
  };
6081
 
6082
 
6083
  self.visibleBar = function(shouldShow) {
6084
    if (shouldShow && !isVisible) {
6085
      $element.removeClass(CSS_HIDE);
6086
    } else if (!shouldShow && isVisible) {
6087
      $element.addClass(CSS_HIDE);
6088
    }
6089
    isVisible = shouldShow;
6090
  };
6091
 
6092
 
6093
  self.enable = function(val) {
6094
    // set primary to show first
6095
    self.visibleBar(val);
6096
 
6097
    // set non primary to hide second
6098
    for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {
6099
      if ($ionicNavBarDelegate._instances[x] !== self) $ionicNavBarDelegate._instances[x].visibleBar(false);
6100
    }
6101
  };
6102
 
6103
 
6104
  /**
6105
   * @ngdoc method
6106
   * @name $ionicNavBar#showBackButton
6107
   * @description Show/hide the nav bar back button when there is a
6108
   * back view. If the back button is not possible, for example, the
6109
   * first view in the stack, then this will not force the back button
6110
   * to show.
6111
   */
6112
  self.showBackButton = function(shouldShow) {
6113
    for (var x = 0; x < headerBars.length; x++) {
6114
      headerBars[x].controller().showNavBack(!!shouldShow);
6115
    }
6116
    $scope.$isBackButtonShown = !!shouldShow;
6117
    return $scope.$isBackButtonShown;
6118
  };
6119
 
6120
 
6121
  /**
6122
   * @ngdoc method
6123
   * @name $ionicNavBar#showActiveBackButton
6124
   * @description Show/hide only the active header bar's back button.
6125
   */
6126
  self.showActiveBackButton = function(shouldShow) {
6127
    var headerBar = getOnScreenHeaderBar();
6128
    headerBar && headerBar.controller().showBack(shouldShow);
6129
  };
6130
 
6131
 
6132
  self.title = function(newTitleText, headerBar) {
6133
    if (isDefined(newTitleText)) {
6134
      newTitleText = newTitleText || '';
6135
      headerBar = headerBar || getOnScreenHeaderBar();
6136
      headerBar && headerBar.title(newTitleText);
6137
      $scope.$title = newTitleText;
6138
      $ionicHistory.currentTitle(newTitleText);
6139
    }
6140
    return $scope.$title;
6141
  };
6142
 
6143
 
6144
  self.align = function(val, headerBar) {
6145
    headerBar = headerBar || getOnScreenHeaderBar();
6146
    headerBar && headerBar.controller().align(val);
6147
  };
6148
 
6149
 
6150
  // DEPRECATED, as of v1.0.0-beta14 -------
6151
  self.changeTitle = function(val) {
6152
    deprecatedWarning('changeTitle(val)', 'title(val)');
6153
    self.title(val);
6154
  };
6155
  self.setTitle = function(val) {
6156
    deprecatedWarning('setTitle(val)', 'title(val)');
6157
    self.title(val);
6158
  };
6159
  self.getTitle = function() {
6160
    deprecatedWarning('getTitle()', 'title()');
6161
    return self.title();
6162
  };
6163
  self.back = function() {
6164
    deprecatedWarning('back()', '$ionicHistory.goBack()');
6165
    $ionicHistory.goBack();
6166
  };
6167
  self.getPreviousTitle = function() {
6168
    deprecatedWarning('getPreviousTitle()', '$ionicHistory.backTitle()');
6169
    $ionicHistory.goBack();
6170
  };
6171
  function deprecatedWarning(oldMethod, newMethod) {
6172
    var warn = console.warn || console.log;
6173
    warn && warn('navBarController.' + oldMethod + ' is deprecated, please use ' + newMethod + ' instead');
6174
  }
6175
  // END DEPRECATED -------
6176
 
6177
 
6178
  function createNavElement(type) {
6179
    if (navElementHtml[type]) {
6180
      return jqLite(navElementHtml[type]);
6181
    }
6182
  }
6183
 
6184
 
6185
  function getOnScreenHeaderBar() {
6186
    for (var x = 0; x < headerBars.length; x++) {
6187
      if (headerBars[x].isActive) return headerBars[x];
6188
    }
6189
  }
6190
 
6191
 
6192
  function getOffScreenHeaderBar() {
6193
    for (var x = 0; x < headerBars.length; x++) {
6194
      if (!headerBars[x].isActive) return headerBars[x];
6195
    }
6196
  }
6197
 
6198
 
6199
  function navBarAttr(ctrl, val) {
6200
    ctrl && ionic.DomUtil.cachedAttr(ctrl.containerEle(), 'nav-bar', val);
6201
  }
6202
 
6203
 
6204
  $scope.$on('$destroy', function() {
6205
    $scope.$parent.$hasHeader = false;
6206
    $element.parent().removeData(DATA_NAV_BAR_CTRL);
6207
    for (var x = 0; x < headerBars.length; x++) {
6208
      headerBars[x].destroy();
6209
    }
6210
    $element.remove();
6211
    $element = headerBars = null;
6212
    deregisterInstance();
6213
  });
6214
 
6215
}]);
6216
 
6217
IonicModule
6218
.controller('$ionicNavView', [
6219
  '$scope',
6220
  '$element',
6221
  '$attrs',
6222
  '$compile',
6223
  '$controller',
6224
  '$ionicNavBarDelegate',
6225
  '$ionicNavViewDelegate',
6226
  '$ionicHistory',
6227
  '$ionicViewSwitcher',
6228
function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, $ionicNavViewDelegate, $ionicHistory, $ionicViewSwitcher) {
6229
 
6230
  var DATA_ELE_IDENTIFIER = '$eleId';
6231
  var DATA_DESTROY_ELE = '$destroyEle';
6232
  var DATA_NO_CACHE = '$noCache';
6233
  var VIEW_STATUS_ACTIVE = 'active';
6234
  var VIEW_STATUS_CACHED = 'cached';
6235
 
6236
  var self = this;
6237
  var direction;
6238
  var isPrimary = false;
6239
  var navBarDelegate;
6240
  var activeEleId;
6241
  var navViewAttr = $ionicViewSwitcher.navViewAttr;
6242
 
6243
  self.scope = $scope;
6244
 
6245
  self.init = function() {
6246
    var navViewName = $attrs.name || '';
6247
 
6248
    // Find the details of the parent view directive (if any) and use it
6249
    // to derive our own qualified view name, then hang our own details
6250
    // off the DOM so child directives can find it.
6251
    var parent = $element.parent().inheritedData('$uiView');
6252
    var parentViewName = ((parent && parent.state) ? parent.state.name : '');
6253
    if (navViewName.indexOf('@') < 0) navViewName  = navViewName + '@' + parentViewName;
6254
 
6255
    var viewData = { name: navViewName, state: null };
6256
    $element.data('$uiView', viewData);
6257
 
6258
    var deregisterInstance = $ionicNavViewDelegate._registerInstance(self, $attrs.delegateHandle);
6259
    $scope.$on('$destroy', deregisterInstance);
6260
 
6261
    $scope.$on('$ionicHistory.deselect', self.cacheCleanup);
6262
 
6263
    return viewData;
6264
  };
6265
 
6266
 
6267
  self.register = function(viewLocals) {
6268
    var leavingView = extend({}, $ionicHistory.currentView());
6269
 
6270
    // register that a view is coming in and get info on how it should transition
6271
    var registerData = $ionicHistory.register($scope, viewLocals);
6272
 
6273
    // update which direction
6274
    self.update(registerData);
6275
 
6276
    // begin rendering and transitioning
6277
    self.render(registerData, viewLocals, leavingView);
6278
  };
6279
 
6280
 
6281
  self.update = function(registerData) {
6282
    // always reset that this is the primary navView
6283
    isPrimary = true;
6284
 
6285
    // remember what direction this navView should use
6286
    // this may get updated later by a child navView
6287
    direction = registerData.direction;
6288
 
6289
    var parentNavViewCtrl = $element.parent().inheritedData('$ionNavViewController');
6290
    if (parentNavViewCtrl) {
6291
      // this navView is nested inside another one
6292
      // update the parent to use this direction and not
6293
      // the other it originally was set to
6294
 
6295
      // inform the parent navView that it is not the primary navView
6296
      parentNavViewCtrl.isPrimary(false);
6297
 
6298
      if (direction === 'enter' || direction === 'exit') {
6299
        // they're entering/exiting a history
6300
        // find parent navViewController
6301
        parentNavViewCtrl.direction(direction);
6302
 
6303
        if (direction === 'enter') {
6304
          // reset the direction so this navView doesn't animate
6305
          // because it's parent will
6306
          direction = 'none';
6307
        }
6308
      }
6309
    }
6310
  };
6311
 
6312
 
6313
  self.render = function(registerData, viewLocals, leavingView) {
6314
    var enteringView = $ionicHistory.getViewById(registerData.viewId) || {};
6315
 
6316
    // register the view and figure out where it lives in the various
6317
    // histories and nav stacks, along with how views should enter/leave
6318
    var switcher = $ionicViewSwitcher.create(self, viewLocals, enteringView, leavingView);
6319
 
6320
    // init the rendering of views for this navView directive
6321
    switcher.init(registerData, function() {
6322
      // the view is now compiled, in the dom and linked, now lets transition the views.
6323
      // this uses a callback incase THIS nav-view has a nested nav-view, and after the NESTED
6324
      // nav-view links, the NESTED nav-view would update which direction THIS nav-view should use
6325
      switcher.transition(self.direction(), registerData.enableBack);
6326
    });
6327
 
6328
  };
6329
 
6330
 
6331
  self.beforeEnter = function(transitionData) {
6332
    if (isPrimary) {
6333
      // only update this nav-view's nav-bar if this is the primary nav-view
6334
      navBarDelegate = transitionData.navBarDelegate;
6335
      var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6336
      associatedNavBarCtrl && associatedNavBarCtrl.update(transitionData);
6337
    }
6338
  };
6339
 
6340
 
6341
  self.activeEleId = function(eleId) {
6342
    if (arguments.length) {
6343
      activeEleId = eleId;
6344
    }
6345
    return activeEleId;
6346
  };
6347
 
6348
 
6349
  self.transitionEnd = function() {
6350
    var viewElements = $element.children();
6351
    var x, l, viewElement;
6352
 
6353
    for (x = 0, l = viewElements.length; x < l; x++) {
6354
      viewElement = viewElements.eq(x);
6355
 
6356
      if (viewElement.data(DATA_ELE_IDENTIFIER) === activeEleId) {
6357
        // this is the active element
6358
        navViewAttr(viewElement, VIEW_STATUS_ACTIVE);
6359
 
6360
      } else if (navViewAttr(viewElement) === 'leaving' || navViewAttr(viewElement) === VIEW_STATUS_ACTIVE || navViewAttr(viewElement) === VIEW_STATUS_CACHED) {
6361
        // this is a leaving element or was the former active element, or is an cached element
6362
        if (viewElement.data(DATA_DESTROY_ELE) || viewElement.data(DATA_NO_CACHE)) {
6363
          // this element shouldn't stay cached
6364
          $ionicViewSwitcher.destroyViewEle(viewElement);
6365
        } else {
6366
          // keep in the DOM, mark as cached
6367
          navViewAttr(viewElement, VIEW_STATUS_CACHED);
6368
        }
6369
      }
6370
    }
6371
  };
6372
 
6373
 
6374
  self.cacheCleanup = function() {
6375
    var viewElements = $element.children();
6376
    for (var x = 0, l = viewElements.length; x < l; x++) {
6377
      if (viewElements.eq(x).data(DATA_DESTROY_ELE)) {
6378
        $ionicViewSwitcher.destroyViewEle(viewElements.eq(x));
6379
      }
6380
    }
6381
  };
6382
 
6383
 
6384
  self.clearCache = function() {
6385
    var viewElements = $element.children();
6386
    var viewElement, viewScope;
6387
 
6388
    for (var x = 0, l = viewElements.length; x < l; x++) {
6389
      viewElement = viewElements.eq(x);
6390
      if (navViewAttr(viewElement) == VIEW_STATUS_CACHED) {
6391
        $ionicViewSwitcher.destroyViewEle(viewElement);
6392
      } else if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
6393
        viewScope = viewElement.scope();
6394
        viewScope && viewScope.$broadcast('$ionicView.clearCache');
6395
      }
6396
    }
6397
 
6398
  };
6399
 
6400
 
6401
  self.getViewElements = function() {
6402
    return $element.children();
6403
  };
6404
 
6405
 
6406
  self.appendViewElement = function(viewEle, viewLocals) {
6407
    // compile the entering element and get the link function
6408
    var linkFn = $compile(viewEle);
6409
 
6410
    $element.append(viewEle);
6411
 
6412
    var viewScope = $scope.$new();
6413
 
6414
    if (viewLocals && viewLocals.$$controller) {
6415
      viewLocals.$scope = viewScope;
6416
      var controller = $controller(viewLocals.$$controller, viewLocals);
6417
      $element.children().data('$ngControllerController', controller);
6418
    }
6419
 
6420
    linkFn(viewScope);
6421
 
6422
    return viewScope;
6423
  };
6424
 
6425
 
6426
  self.title = function(val) {
6427
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6428
    associatedNavBarCtrl && associatedNavBarCtrl.title(val);
6429
  };
6430
 
6431
 
6432
  /**
6433
   * @ngdoc method
6434
   * @name $ionicNavView#enableBackButton
6435
   * @description Enable/disable if the back button can be shown or not. For
6436
   * example, the very first view in the navigation stack would not have a
6437
   * back view, so the back button would be disabled.
6438
   */
6439
  self.enableBackButton = function(shouldEnable) {
6440
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6441
    associatedNavBarCtrl && associatedNavBarCtrl.enableBackButton(shouldEnable);
6442
  };
6443
 
6444
 
6445
  /**
6446
   * @ngdoc method
6447
   * @name $ionicNavView#showBackButton
6448
   * @description Show/hide the nav bar active back button. If the back button
6449
   * is not possible this will not force the back button to show. The
6450
   * `enableBackButton()` method handles if a back button is even possible or not.
6451
   */
6452
  self.showBackButton = function(shouldShow) {
6453
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6454
    associatedNavBarCtrl && associatedNavBarCtrl.showActiveBackButton(shouldShow);
6455
  };
6456
 
6457
 
6458
  self.showBar = function(val) {
6459
    var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6460
    associatedNavBarCtrl && associatedNavBarCtrl.showBar(val);
6461
  };
6462
 
6463
 
6464
  self.isPrimary = function(val) {
6465
    if (arguments.length) {
6466
      isPrimary = val;
6467
    }
6468
    return isPrimary;
6469
  };
6470
 
6471
 
6472
  self.direction = function(val) {
6473
    if (arguments.length) {
6474
      direction = val;
6475
    }
6476
    return direction;
6477
  };
6478
 
6479
 
6480
  function getAssociatedNavBarCtrl() {
6481
    if (navBarDelegate) {
6482
      for (var x=0; x < $ionicNavBarDelegate._instances.length; x++) {
6483
        if ($ionicNavBarDelegate._instances[x].$$delegateHandle == navBarDelegate) {
6484
          return $ionicNavBarDelegate._instances[x];
6485
        }
6486
      }
6487
    }
6488
    return $element.inheritedData('$ionNavBarController');
6489
  }
6490
 
6491
}]);
6492
 
6493
/**
6494
 * @private
6495
 */
6496
IonicModule
6497
 
6498
.controller('$ionicScroll', [
6499
  '$scope',
6500
  'scrollViewOptions',
6501
  '$timeout',
6502
  '$window',
6503
  '$location',
6504
  '$document',
6505
  '$ionicScrollDelegate',
6506
  '$ionicHistory',
6507
function($scope, scrollViewOptions, $timeout, $window, $location, $document, $ionicScrollDelegate, $ionicHistory) {
6508
 
6509
  var self = this;
6510
  // for testing
6511
  self.__timeout = $timeout;
6512
 
6513
  self._scrollViewOptions = scrollViewOptions; //for testing
6514
 
6515
  var element = self.element = scrollViewOptions.el;
6516
  var $element = self.$element = jqLite(element);
6517
  var scrollView = self.scrollView = new ionic.views.Scroll(scrollViewOptions);
6518
 
6519
  //Attach self to element as a controller so other directives can require this controller
6520
  //through `require: '$ionicScroll'
6521
  //Also attach to parent so that sibling elements can require this
6522
  ($element.parent().length ? $element.parent() : $element)
6523
    .data('$$ionicScrollController', self);
6524
 
6525
  var deregisterInstance = $ionicScrollDelegate._registerInstance(
6526
    self, scrollViewOptions.delegateHandle, function() {
6527
      return $ionicHistory.isActiveScope($scope);
6528
    }
6529
  );
6530
 
6531
  if (!angular.isDefined(scrollViewOptions.bouncing)) {
6532
    ionic.Platform.ready(function() {
6533
      if (scrollView.options) {
6534
        scrollView.options.bouncing = true;
6535
        if (ionic.Platform.isAndroid()) {
6536
          // No bouncing by default on Android
6537
          scrollView.options.bouncing = false;
6538
          // Faster scroll decel
6539
          scrollView.options.deceleration = 0.95;
6540
        }
6541
      }
6542
    });
6543
  }
6544
 
6545
  var resize = angular.bind(scrollView, scrollView.resize);
6546
  ionic.on('resize', resize, $window);
6547
 
6548
 
6549
  var scrollFunc = function(e) {
6550
    var detail = (e.originalEvent || e).detail || {};
6551
    $scope.$onScroll && $scope.$onScroll({
6552
      event: e,
6553
      scrollTop: detail.scrollTop || 0,
6554
      scrollLeft: detail.scrollLeft || 0
6555
    });
6556
  };
6557
 
6558
  $element.on('scroll', scrollFunc);
6559
 
6560
  $scope.$on('$destroy', function() {
6561
    deregisterInstance();
6562
    scrollView.__cleanup();
6563
    ionic.off('resize', resize, $window);
6564
    $window.removeEventListener('resize', resize);
6565
    scrollViewOptions = null;
6566
    self._scrollViewOptions.el = null;
6567
    self._scrollViewOptions = null;
6568
    $element.off('scroll', scrollFunc);
6569
    $element = null;
6570
    self.$element = null;
6571
    element = null;
6572
    self.element = null;
6573
    self.scrollView = null;
6574
    scrollView = null;
6575
  });
6576
 
6577
  $timeout(function() {
6578
    scrollView && scrollView.run && scrollView.run();
6579
  });
6580
 
6581
  self.getScrollView = function() {
6582
    return self.scrollView;
6583
  };
6584
 
6585
  self.getScrollPosition = function() {
6586
    return self.scrollView.getValues();
6587
  };
6588
 
6589
  self.resize = function() {
6590
    return $timeout(resize).then(function() {
6591
      $element && $element.triggerHandler('scroll.resize');
6592
    });
6593
  };
6594
 
6595
  self.scrollTop = function(shouldAnimate) {
6596
    ionic.DomUtil.blurAll();
6597
    self.resize().then(function() {
6598
      scrollView.scrollTo(0, 0, !!shouldAnimate);
6599
    });
6600
  };
6601
 
6602
  self.scrollBottom = function(shouldAnimate) {
6603
    ionic.DomUtil.blurAll();
6604
    self.resize().then(function() {
6605
      var max = scrollView.getScrollMax();
6606
      scrollView.scrollTo(max.left, max.top, !!shouldAnimate);
6607
    });
6608
  };
6609
 
6610
  self.scrollTo = function(left, top, shouldAnimate) {
6611
    ionic.DomUtil.blurAll();
6612
    self.resize().then(function() {
6613
      scrollView.scrollTo(left, top, !!shouldAnimate);
6614
    });
6615
  };
6616
 
6617
  self.zoomTo = function(zoom, shouldAnimate, originLeft, originTop) {
6618
    ionic.DomUtil.blurAll();
6619
    self.resize().then(function() {
6620
      scrollView.zoomTo(zoom, !!shouldAnimate, originLeft, originTop);
6621
    });
6622
  };
6623
 
6624
  self.zoomBy = function(zoom, shouldAnimate, originLeft, originTop) {
6625
    ionic.DomUtil.blurAll();
6626
    self.resize().then(function() {
6627
      scrollView.zoomBy(zoom, !!shouldAnimate, originLeft, originTop);
6628
    });
6629
  };
6630
 
6631
  self.scrollBy = function(left, top, shouldAnimate) {
6632
    ionic.DomUtil.blurAll();
6633
    self.resize().then(function() {
6634
      scrollView.scrollBy(left, top, !!shouldAnimate);
6635
    });
6636
  };
6637
 
6638
  self.anchorScroll = function(shouldAnimate) {
6639
    ionic.DomUtil.blurAll();
6640
    self.resize().then(function() {
6641
      var hash = $location.hash();
6642
      var elm = hash && $document[0].getElementById(hash);
6643
      if (!(hash && elm)) {
6644
        scrollView.scrollTo(0,0, !!shouldAnimate);
6645
        return;
6646
      }
6647
      var curElm = elm;
6648
      var scrollLeft = 0, scrollTop = 0, levelsClimbed = 0;
6649
      do {
6650
        if (curElm !== null) scrollLeft += curElm.offsetLeft;
6651
        if (curElm !== null) scrollTop += curElm.offsetTop;
6652
        curElm = curElm.offsetParent;
6653
        levelsClimbed++;
6654
      } while (curElm.attributes != self.element.attributes && curElm.offsetParent);
6655
      scrollView.scrollTo(scrollLeft, scrollTop, !!shouldAnimate);
6656
    });
6657
  };
6658
 
6659
 
6660
  /**
6661
   * @private
6662
   */
6663
  self._setRefresher = function(refresherScope, refresherElement) {
6664
    var refresher = self.refresher = refresherElement;
6665
    var refresherHeight = self.refresher.clientHeight || 60;
6666
    scrollView.activatePullToRefresh(refresherHeight, function() {
6667
      // activateCallback
6668
      refresher.classList.add('active');
6669
      refresherScope.$onPulling();
6670
    }, function() {
6671
        refresher.classList.remove('active');
6672
        refresher.classList.remove('refreshing');
6673
        refresher.classList.remove('refreshing-tail');
6674
    }, function() {
6675
      // startCallback
6676
      refresher.classList.add('refreshing');
6677
      refresherScope.$onRefresh();
6678
    }, function() {
6679
      // showCallback
6680
      refresher.classList.remove('invisible');
6681
    }, function() {
6682
      // hideCallback
6683
      refresher.classList.add('invisible');
6684
    }, function() {
6685
      // tailCallback
6686
      refresher.classList.add('refreshing-tail');
6687
    });
6688
  };
6689
}]);
6690
 
6691
IonicModule
6692
.controller('$ionicSideMenus', [
6693
  '$scope',
6694
  '$attrs',
6695
  '$ionicSideMenuDelegate',
6696
  '$ionicPlatform',
6697
  '$ionicBody',
6698
  '$ionicHistory',
6699
function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform, $ionicBody, $ionicHistory) {
6700
  var self = this;
6701
  var rightShowing, leftShowing, isDragging;
6702
  var startX, lastX, offsetX, isAsideExposed;
6703
  var enableMenuWithBackViews = true;
6704
 
6705
  self.$scope = $scope;
6706
 
6707
  self.initialize = function(options) {
6708
    self.left = options.left;
6709
    self.right = options.right;
6710
    self.setContent(options.content);
6711
    self.dragThresholdX = options.dragThresholdX || 10;
6712
    $ionicHistory.registerHistory(self.$scope);
6713
  };
6714
 
6715
  /**
6716
   * Set the content view controller if not passed in the constructor options.
6717
   *
6718
   * @param {object} content
6719
   */
6720
  self.setContent = function(content) {
6721
    if (content) {
6722
      self.content = content;
6723
 
6724
      self.content.onDrag = function(e) {
6725
        self._handleDrag(e);
6726
      };
6727
 
6728
      self.content.endDrag = function(e) {
6729
        self._endDrag(e);
6730
      };
6731
    }
6732
  };
6733
 
6734
  self.isOpenLeft = function() {
6735
    return self.getOpenAmount() > 0;
6736
  };
6737
 
6738
  self.isOpenRight = function() {
6739
    return self.getOpenAmount() < 0;
6740
  };
6741
 
6742
  /**
6743
   * Toggle the left menu to open 100%
6744
   */
6745
  self.toggleLeft = function(shouldOpen) {
6746
    if (isAsideExposed || !self.left.isEnabled) return;
6747
    var openAmount = self.getOpenAmount();
6748
    if (arguments.length === 0) {
6749
      shouldOpen = openAmount <= 0;
6750
    }
6751
    self.content.enableAnimation();
6752
    if (!shouldOpen) {
6753
      self.openPercentage(0);
6754
    } else {
6755
      self.openPercentage(100);
6756
    }
6757
  };
6758
 
6759
  /**
6760
   * Toggle the right menu to open 100%
6761
   */
6762
  self.toggleRight = function(shouldOpen) {
6763
    if (isAsideExposed || !self.right.isEnabled) return;
6764
    var openAmount = self.getOpenAmount();
6765
    if (arguments.length === 0) {
6766
      shouldOpen = openAmount >= 0;
6767
    }
6768
    self.content.enableAnimation();
6769
    if (!shouldOpen) {
6770
      self.openPercentage(0);
6771
    } else {
6772
      self.openPercentage(-100);
6773
    }
6774
  };
6775
 
6776
  self.toggle = function(side) {
6777
    if (side == 'right') {
6778
      self.toggleRight();
6779
    } else {
6780
      self.toggleLeft();
6781
    }
6782
  };
6783
 
6784
  /**
6785
   * Close all menus.
6786
   */
6787
  self.close = function() {
6788
    self.openPercentage(0);
6789
  };
6790
 
6791
  /**
6792
   * @return {float} The amount the side menu is open, either positive or negative for left (positive), or right (negative)
6793
   */
6794
  self.getOpenAmount = function() {
6795
    return self.content && self.content.getTranslateX() || 0;
6796
  };
6797
 
6798
  /**
6799
   * @return {float} The ratio of open amount over menu width. For example, a
6800
   * menu of width 100 open 50 pixels would be open 50% or a ratio of 0.5. Value is negative
6801
   * for right menu.
6802
   */
6803
  self.getOpenRatio = function() {
6804
    var amount = self.getOpenAmount();
6805
    if (amount >= 0) {
6806
      return amount / self.left.width;
6807
    }
6808
    return amount / self.right.width;
6809
  };
6810
 
6811
  self.isOpen = function() {
6812
    return self.getOpenAmount() !== 0;
6813
  };
6814
 
6815
  /**
6816
   * @return {float} The percentage of open amount over menu width. For example, a
6817
   * menu of width 100 open 50 pixels would be open 50%. Value is negative
6818
   * for right menu.
6819
   */
6820
  self.getOpenPercentage = function() {
6821
    return self.getOpenRatio() * 100;
6822
  };
6823
 
6824
  /**
6825
   * Open the menu with a given percentage amount.
6826
   * @param {float} percentage The percentage (positive or negative for left/right) to open the menu.
6827
   */
6828
  self.openPercentage = function(percentage) {
6829
    var p = percentage / 100;
6830
 
6831
    if (self.left && percentage >= 0) {
6832
      self.openAmount(self.left.width * p);
6833
    } else if (self.right && percentage < 0) {
6834
      var maxRight = self.right.width;
6835
      self.openAmount(self.right.width * p);
6836
    }
6837
 
6838
    // add the CSS class "menu-open" if the percentage does not
6839
    // equal 0, otherwise remove the class from the body element
6840
    $ionicBody.enableClass((percentage !== 0), 'menu-open');
6841
  };
6842
 
6843
  /**
6844
   * Open the menu the given pixel amount.
6845
   * @param {float} amount the pixel amount to open the menu. Positive value for left menu,
6846
   * negative value for right menu (only one menu will be visible at a time).
6847
   */
6848
  self.openAmount = function(amount) {
6849
    var maxLeft = self.left && self.left.width || 0;
6850
    var maxRight = self.right && self.right.width || 0;
6851
 
6852
    // Check if we can move to that side, depending if the left/right panel is enabled
6853
    if (!(self.left && self.left.isEnabled) && amount > 0) {
6854
      self.content.setTranslateX(0);
6855
      return;
6856
    }
6857
 
6858
    if (!(self.right && self.right.isEnabled) && amount < 0) {
6859
      self.content.setTranslateX(0);
6860
      return;
6861
    }
6862
 
6863
    if (leftShowing && amount > maxLeft) {
6864
      self.content.setTranslateX(maxLeft);
6865
      return;
6866
    }
6867
 
6868
    if (rightShowing && amount < -maxRight) {
6869
      self.content.setTranslateX(-maxRight);
6870
      return;
6871
    }
6872
 
6873
    self.content.setTranslateX(amount);
6874
 
6875
    if (amount >= 0) {
6876
      leftShowing = true;
6877
      rightShowing = false;
6878
 
6879
      if (amount > 0) {
6880
        // Push the z-index of the right menu down
6881
        self.right && self.right.pushDown && self.right.pushDown();
6882
        // Bring the z-index of the left menu up
6883
        self.left && self.left.bringUp && self.left.bringUp();
6884
      }
6885
    } else {
6886
      rightShowing = true;
6887
      leftShowing = false;
6888
 
6889
      // Bring the z-index of the right menu up
6890
      self.right && self.right.bringUp && self.right.bringUp();
6891
      // Push the z-index of the left menu down
6892
      self.left && self.left.pushDown && self.left.pushDown();
6893
    }
6894
  };
6895
 
6896
  /**
6897
   * Given an event object, find the final resting position of this side
6898
   * menu. For example, if the user "throws" the content to the right and
6899
   * releases the touch, the left menu should snap open (animated, of course).
6900
   *
6901
   * @param {Event} e the gesture event to use for snapping
6902
   */
6903
  self.snapToRest = function(e) {
6904
    // We want to animate at the end of this
6905
    self.content.enableAnimation();
6906
    isDragging = false;
6907
 
6908
    // Check how much the panel is open after the drag, and
6909
    // what the drag velocity is
6910
    var ratio = self.getOpenRatio();
6911
 
6912
    if (ratio === 0) {
6913
      // Just to be safe
6914
      self.openPercentage(0);
6915
      return;
6916
    }
6917
 
6918
    var velocityThreshold = 0.3;
6919
    var velocityX = e.gesture.velocityX;
6920
    var direction = e.gesture.direction;
6921
 
6922
    // Going right, less than half, too slow (snap back)
6923
    if (ratio > 0 && ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {
6924
      self.openPercentage(0);
6925
    }
6926
 
6927
    // Going left, more than half, too slow (snap back)
6928
    else if (ratio > 0.5 && direction == 'left' && velocityX < velocityThreshold) {
6929
      self.openPercentage(100);
6930
    }
6931
 
6932
    // Going left, less than half, too slow (snap back)
6933
    else if (ratio < 0 && ratio > -0.5 && direction == 'left' && velocityX < velocityThreshold) {
6934
      self.openPercentage(0);
6935
    }
6936
 
6937
    // Going right, more than half, too slow (snap back)
6938
    else if (ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {
6939
      self.openPercentage(-100);
6940
    }
6941
 
6942
    // Going right, more than half, or quickly (snap open)
6943
    else if (direction == 'right' && ratio >= 0 && (ratio >= 0.5 || velocityX > velocityThreshold)) {
6944
      self.openPercentage(100);
6945
    }
6946
 
6947
    // Going left, more than half, or quickly (span open)
6948
    else if (direction == 'left' && ratio <= 0 && (ratio <= -0.5 || velocityX > velocityThreshold)) {
6949
      self.openPercentage(-100);
6950
    }
6951
 
6952
    // Snap back for safety
6953
    else {
6954
      self.openPercentage(0);
6955
    }
6956
  };
6957
 
6958
  self.enableMenuWithBackViews = function(val) {
6959
    if (arguments.length) {
6960
      enableMenuWithBackViews = !!val;
6961
    }
6962
    return enableMenuWithBackViews;
6963
  };
6964
 
6965
  self.isAsideExposed = function() {
6966
    return !!isAsideExposed;
6967
  };
6968
 
6969
  self.exposeAside = function(shouldExposeAside) {
6970
    if (!(self.left && self.left.isEnabled) && !(self.right && self.right.isEnabled)) return;
6971
    self.close();
6972
    isAsideExposed = shouldExposeAside;
6973
    if (self.left && self.left.isEnabled) {
6974
      // set the left marget width if it should be exposed
6975
      // otherwise set false so there's no left margin
6976
      self.content.setMarginLeft(isAsideExposed ? self.left.width : 0);
6977
    } else if (self.right && self.right.isEnabled) {
6978
      self.content.setMarginRight(isAsideExposed ? self.right.width : 0);
6979
    }
6980
 
6981
    self.$scope.$emit('$ionicExposeAside', isAsideExposed);
6982
  };
6983
 
6984
  self.activeAsideResizing = function(isResizing) {
6985
    $ionicBody.enableClass(isResizing, 'aside-resizing');
6986
  };
6987
 
6988
  // End a drag with the given event
6989
  self._endDrag = function(e) {
6990
    if (isAsideExposed) return;
6991
 
6992
    if (isDragging) {
6993
      self.snapToRest(e);
6994
    }
6995
    startX = null;
6996
    lastX = null;
6997
    offsetX = null;
6998
  };
6999
 
7000
  // Handle a drag event
7001
  self._handleDrag = function(e) {
7002
    if (isAsideExposed) return;
7003
 
7004
    // If we don't have start coords, grab and store them
7005
    if (!startX) {
7006
      startX = e.gesture.touches[0].pageX;
7007
      lastX = startX;
7008
    } else {
7009
      // Grab the current tap coords
7010
      lastX = e.gesture.touches[0].pageX;
7011
    }
7012
 
7013
    // Calculate difference from the tap points
7014
    if (!isDragging && Math.abs(lastX - startX) > self.dragThresholdX) {
7015
      // if the difference is greater than threshold, start dragging using the current
7016
      // point as the starting point
7017
      startX = lastX;
7018
 
7019
      isDragging = true;
7020
      // Initialize dragging
7021
      self.content.disableAnimation();
7022
      offsetX = self.getOpenAmount();
7023
    }
7024
 
7025
    if (isDragging) {
7026
      self.openAmount(offsetX + (lastX - startX));
7027
    }
7028
  };
7029
 
7030
  self.canDragContent = function(canDrag) {
7031
    if (arguments.length) {
7032
      $scope.dragContent = !!canDrag;
7033
    }
7034
    return $scope.dragContent;
7035
  };
7036
 
7037
  self.edgeThreshold = 25;
7038
  self.edgeThresholdEnabled = false;
7039
  self.edgeDragThreshold = function(value) {
7040
    if (arguments.length) {
7041
      if (angular.isNumber(value) && value > 0) {
7042
        self.edgeThreshold = value;
7043
        self.edgeThresholdEnabled = true;
7044
      } else {
7045
        self.edgeThresholdEnabled = !!value;
7046
      }
7047
    }
7048
    return self.edgeThresholdEnabled;
7049
  };
7050
 
7051
  self.isDraggableTarget = function(e) {
7052
    //Only restrict edge when sidemenu is closed and restriction is enabled
7053
    var shouldOnlyAllowEdgeDrag = self.edgeThresholdEnabled && !self.isOpen();
7054
    var startX = e.gesture.startEvent && e.gesture.startEvent.center &&
7055
      e.gesture.startEvent.center.pageX;
7056
 
7057
    var dragIsWithinBounds = !shouldOnlyAllowEdgeDrag ||
7058
      startX <= self.edgeThreshold ||
7059
      startX >= self.content.element.offsetWidth - self.edgeThreshold;
7060
 
7061
    var backView = $ionicHistory.backView();
7062
    var menuEnabled = enableMenuWithBackViews ? true : !backView;
7063
    if (!menuEnabled) {
7064
      var currentView = $ionicHistory.currentView() || {};
7065
      return backView.historyId !== currentView.historyId;
7066
    }
7067
 
7068
    return ($scope.dragContent || self.isOpen()) &&
7069
      dragIsWithinBounds &&
7070
      !e.gesture.srcEvent.defaultPrevented &&
7071
      menuEnabled &&
7072
      !e.target.tagName.match(/input|textarea|select|object|embed/i) &&
7073
      !e.target.isContentEditable &&
7074
      !(e.target.dataset ? e.target.dataset.preventScroll : e.target.getAttribute('data-prevent-scroll') == 'true');
7075
  };
7076
 
7077
  $scope.sideMenuContentTranslateX = 0;
7078
 
7079
  var deregisterBackButtonAction = angular.noop;
7080
  var closeSideMenu = angular.bind(self, self.close);
7081
 
7082
  $scope.$watch(function() {
7083
    return self.getOpenAmount() !== 0;
7084
  }, function(isOpen) {
7085
    deregisterBackButtonAction();
7086
    if (isOpen) {
7087
      deregisterBackButtonAction = $ionicPlatform.registerBackButtonAction(
7088
        closeSideMenu,
7089
        PLATFORM_BACK_BUTTON_PRIORITY_SIDE_MENU
7090
      );
7091
    }
7092
  });
7093
 
7094
  var deregisterInstance = $ionicSideMenuDelegate._registerInstance(
7095
    self, $attrs.delegateHandle, function() {
7096
      return $ionicHistory.isActiveScope($scope);
7097
    }
7098
  );
7099
 
7100
  $scope.$on('$destroy', function() {
7101
    deregisterInstance();
7102
    deregisterBackButtonAction();
7103
    self.$scope = null;
7104
    if (self.content) {
7105
      self.content.element = null;
7106
      self.content = null;
7107
    }
7108
  });
7109
 
7110
  self.initialize({
7111
    left: {
7112
      width: 275
7113
    },
7114
    right: {
7115
      width: 275
7116
    }
7117
  });
7118
 
7119
}]);
7120
 
7121
IonicModule
7122
.controller('$ionicTab', [
7123
  '$scope',
7124
  '$ionicHistory',
7125
  '$attrs',
7126
  '$location',
7127
  '$state',
7128
function($scope, $ionicHistory, $attrs, $location, $state) {
7129
  this.$scope = $scope;
7130
 
7131
  //All of these exposed for testing
7132
  this.hrefMatchesState = function() {
7133
    return $attrs.href && $location.path().indexOf(
7134
      $attrs.href.replace(/^#/, '').replace(/\/$/, '')
7135
    ) === 0;
7136
  };
7137
  this.srefMatchesState = function() {
7138
    return $attrs.uiSref && $state.includes($attrs.uiSref.split('(')[0]);
7139
  };
7140
  this.navNameMatchesState = function() {
7141
    return this.navViewName && $ionicHistory.isCurrentStateNavView(this.navViewName);
7142
  };
7143
 
7144
  this.tabMatchesState = function() {
7145
    return this.hrefMatchesState() || this.srefMatchesState() || this.navNameMatchesState();
7146
  };
7147
}]);
7148
 
7149
IonicModule
7150
.controller('$ionicTabs', [
7151
  '$scope',
7152
  '$element',
7153
  '$ionicHistory',
7154
function($scope, $element, $ionicHistory) {
7155
  var self = this;
7156
  var selectedTab = null;
7157
  var selectedTabIndex;
7158
  self.tabs = [];
7159
 
7160
  self.selectedIndex = function() {
7161
    return self.tabs.indexOf(selectedTab);
7162
  };
7163
  self.selectedTab = function() {
7164
    return selectedTab;
7165
  };
7166
 
7167
  self.add = function(tab) {
7168
    $ionicHistory.registerHistory(tab);
7169
    self.tabs.push(tab);
7170
  };
7171
 
7172
  self.remove = function(tab) {
7173
    var tabIndex = self.tabs.indexOf(tab);
7174
    if (tabIndex === -1) {
7175
      return;
7176
    }
7177
    //Use a field like '$tabSelected' so developers won't accidentally set it in controllers etc
7178
    if (tab.$tabSelected) {
7179
      self.deselect(tab);
7180
      //Try to select a new tab if we're removing a tab
7181
      if (self.tabs.length === 1) {
7182
        //do nothing if there are no other tabs to select
7183
      } else {
7184
        //Select previous tab if it's the last tab, else select next tab
7185
        var newTabIndex = tabIndex === self.tabs.length - 1 ? tabIndex - 1 : tabIndex + 1;
7186
        self.select(self.tabs[newTabIndex]);
7187
      }
7188
    }
7189
    self.tabs.splice(tabIndex, 1);
7190
  };
7191
 
7192
  self.deselect = function(tab) {
7193
    if (tab.$tabSelected) {
7194
      selectedTab = selectedTabIndex = null;
7195
      tab.$tabSelected = false;
7196
      (tab.onDeselect || angular.noop)();
7197
      tab.$broadcast && tab.$broadcast('$ionicHistory.deselect');
7198
    }
7199
  };
7200
 
7201
  self.select = function(tab, shouldEmitEvent) {
7202
    var tabIndex;
7203
    if (angular.isNumber(tab)) {
7204
      tabIndex = tab;
7205
      if (tabIndex >= self.tabs.length) return;
7206
      tab = self.tabs[tabIndex];
7207
    } else {
7208
      tabIndex = self.tabs.indexOf(tab);
7209
    }
7210
 
7211
    if (arguments.length === 1) {
7212
      shouldEmitEvent = !!(tab.navViewName || tab.uiSref);
7213
    }
7214
 
7215
    if (selectedTab && selectedTab.$historyId == tab.$historyId) {
7216
      if (shouldEmitEvent) {
7217
        $ionicHistory.goToHistoryRoot(tab.$historyId);
7218
      }
7219
 
7220
    } else if (selectedTabIndex !== tabIndex) {
7221
      forEach(self.tabs, function(tab) {
7222
        self.deselect(tab);
7223
      });
7224
 
7225
      selectedTab = tab;
7226
      selectedTabIndex = tabIndex;
7227
 
7228
      if (self.$scope && self.$scope.$parent) {
7229
        self.$scope.$parent.$activeHistoryId = tab.$historyId;
7230
      }
7231
 
7232
      //Use a funny name like $tabSelected so the developer doesn't overwrite the var in a child scope
7233
      tab.$tabSelected = true;
7234
      (tab.onSelect || angular.noop)();
7235
 
7236
      if (shouldEmitEvent) {
7237
        $scope.$emit('$ionicHistory.change', {
7238
          type: 'tab',
7239
          tabIndex: tabIndex,
7240
          historyId: tab.$historyId,
7241
          navViewName: tab.navViewName,
7242
          hasNavView: !!tab.navViewName,
7243
          title: tab.title,
7244
          url: tab.href,
7245
          uiSref: tab.uiSref
7246
        });
7247
      }
7248
    }
7249
  };
7250
 
7251
  self.hasActiveScope = function() {
7252
    for (var x = 0; x < self.tabs.length; x++) {
7253
      if ($ionicHistory.isActiveScope(self.tabs[x])) {
7254
        return true;
7255
      }
7256
    }
7257
    return false;
7258
  };
7259
 
7260
}]);
7261
 
7262
IonicModule
7263
.controller('$ionicView', [
7264
  '$scope',
7265
  '$element',
7266
  '$attrs',
7267
  '$compile',
7268
  '$rootScope',
7269
  '$ionicViewSwitcher',
7270
function($scope, $element, $attrs, $compile, $rootScope, $ionicViewSwitcher) {
7271
  var self = this;
7272
  var navElementHtml = {};
7273
  var navViewCtrl;
7274
  var navBarDelegateHandle;
7275
  var hasViewHeaderBar;
7276
  var deregisters = [];
7277
  var viewTitle;
7278
 
7279
  var deregIonNavBarInit = $scope.$on('ionNavBar.init', function(ev, delegateHandle) {
7280
    // this view has its own ion-nav-bar, remember the navBarDelegateHandle for this view
7281
    ev.stopPropagation();
7282
    navBarDelegateHandle = delegateHandle;
7283
  });
7284
 
7285
 
7286
  self.init = function() {
7287
    deregIonNavBarInit();
7288
 
7289
    var modalCtrl = $element.inheritedData('$ionModalController');
7290
    navViewCtrl = $element.inheritedData('$ionNavViewController');
7291
 
7292
    // don't bother if inside a modal or there's no parent navView
7293
    if (!navViewCtrl || modalCtrl) return;
7294
 
7295
    // add listeners for when this view changes
7296
    $scope.$on('$ionicView.beforeEnter', self.beforeEnter);
7297
    $scope.$on('$ionicView.afterEnter', afterEnter);
7298
    $scope.$on('$ionicView.beforeLeave', deregisterFns);
7299
  };
7300
 
7301
  self.beforeEnter = function(ev, transData) {
7302
    // this event was emitted, starting at intial ion-view, then bubbles up
7303
    // only the first ion-view should do something with it, parent ion-views should ignore
7304
    if (transData && !transData.viewNotified) {
7305
      transData.viewNotified = true;
7306
 
7307
      if (!$rootScope.$$phase) $scope.$digest();
7308
      viewTitle = isDefined($attrs.viewTitle) ? $attrs.viewTitle : $attrs.title;
7309
 
7310
      var navBarItems = {};
7311
      for (var n in navElementHtml) {
7312
        navBarItems[n] = generateNavBarItem(navElementHtml[n]);
7313
      }
7314
 
7315
      navViewCtrl.beforeEnter({
7316
        title: viewTitle,
7317
        direction: transData.direction,
7318
        transition: transData.transition,
7319
        navBarTransition: transData.navBarTransition,
7320
        transitionId: transData.transitionId,
7321
        shouldAnimate: transData.shouldAnimate,
7322
        enableBack: transData.enableBack,
7323
        showBack: !attrTrue('hideBackButton'),
7324
        navBarItems: navBarItems,
7325
        navBarDelegate: navBarDelegateHandle || null,
7326
        showNavBar: !attrTrue('hideNavBar'),
7327
        hasHeaderBar: !!hasViewHeaderBar
7328
      });
7329
 
7330
      // make sure any existing observers are cleaned up
7331
      deregisterFns();
7332
    }
7333
  };
7334
 
7335
 
7336
  function afterEnter() {
7337
    // only listen for title updates after it has entered
7338
    // but also deregister the observe before it leaves
7339
    var viewTitleAttr = isDefined($attrs.viewTitle) && 'viewTitle' || isDefined($attrs.title) && 'title';
7340
    if (viewTitleAttr) {
7341
      titleUpdate($attrs[viewTitleAttr]);
7342
      deregisters.push($attrs.$observe(viewTitleAttr, titleUpdate));
7343
    }
7344
 
7345
    if (isDefined($attrs.hideBackButton)) {
7346
      deregisters.push($scope.$watch($attrs.hideBackButton, function(val) {
7347
        navViewCtrl.showBackButton(!val);
7348
      }));
7349
    }
7350
 
7351
    if (isDefined($attrs.hideNavBar)) {
7352
      deregisters.push($scope.$watch($attrs.hideNavBar, function(val) {
7353
        navViewCtrl.showBar(!val);
7354
      }));
7355
    }
7356
  }
7357
 
7358
 
7359
  function titleUpdate(newTitle) {
7360
    if (isDefined(newTitle) && newTitle !== viewTitle) {
7361
      viewTitle = newTitle;
7362
      navViewCtrl.title(viewTitle);
7363
    }
7364
  }
7365
 
7366
 
7367
  function deregisterFns() {
7368
    // remove all existing $attrs.$observe's
7369
    for (var x = 0; x < deregisters.length; x++) {
7370
      deregisters[x]();
7371
    }
7372
    deregisters = [];
7373
  }
7374
 
7375
 
7376
  function generateNavBarItem(html) {
7377
    if (html) {
7378
      // every time a view enters we need to recreate its view buttons if they exist
7379
      return $compile(html)($scope.$new());
7380
    }
7381
  }
7382
 
7383
 
7384
  function attrTrue(key) {
7385
    return !!$scope.$eval($attrs[key]);
7386
  }
7387
 
7388
 
7389
  self.navElement = function(type, html) {
7390
    navElementHtml[type] = html;
7391
  };
7392
 
7393
}]);
7394
 
7395
/*
7396
 * We don't document the ionActionSheet directive, we instead document
7397
 * the $ionicActionSheet service
7398
 */
7399
IonicModule
7400
.directive('ionActionSheet', ['$document', function($document) {
7401
  return {
7402
    restrict: 'E',
7403
    scope: true,
7404
    replace: true,
7405
    link: function($scope, $element){
7406
      var keyUp = function(e) {
7407
        if(e.which == 27) {
7408
          $scope.cancel();
7409
          $scope.$apply();
7410
        }
7411
      };
7412
 
7413
      var backdropClick = function(e) {
7414
        if(e.target == $element[0]) {
7415
          $scope.cancel();
7416
          $scope.$apply();
7417
        }
7418
      };
7419
      $scope.$on('$destroy', function() {
7420
        $element.remove();
7421
        $document.unbind('keyup', keyUp);
7422
      });
7423
 
7424
      $document.bind('keyup', keyUp);
7425
      $element.bind('click', backdropClick);
7426
    },
7427
    template: '<div class="action-sheet-backdrop">' +
7428
                '<div class="action-sheet-wrapper">' +
7429
                  '<div class="action-sheet">' +
7430
                    '<div class="action-sheet-group">' +
7431
                      '<div class="action-sheet-title" ng-if="titleText" ng-bind-html="titleText"></div>' +
7432
                      '<button class="button" ng-click="buttonClicked($index)" ng-repeat="button in buttons" ng-bind-html="button.text"></button>' +
7433
                    '</div>' +
7434
                    '<div class="action-sheet-group" ng-if="destructiveText">' +
7435
                      '<button class="button destructive" ng-click="destructiveButtonClicked()" ng-bind-html="destructiveText"></button>' +
7436
                    '</div>' +
7437
                    '<div class="action-sheet-group" ng-if="cancelText">' +
7438
                      '<button class="button" ng-click="cancel()" ng-bind-html="cancelText"></button>' +
7439
                    '</div>' +
7440
                  '</div>' +
7441
                '</div>' +
7442
              '</div>'
7443
  };
7444
}]);
7445
 
7446
 
7447
/**
7448
 * @ngdoc directive
7449
 * @name ionCheckbox
7450
 * @module ionic
7451
 * @restrict E
7452
 * @codepen hqcju
7453
 * @description
7454
 * The checkbox is no different than the HTML checkbox input, except it's styled differently.
7455
 *
7456
 * The checkbox behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]).
7457
 *
7458
 * @usage
7459
 * ```html
7460
 * <ion-checkbox ng-model="isChecked">Checkbox Label</ion-checkbox>
7461
 * ```
7462
 */
7463
 
7464
IonicModule
7465
.directive('ionCheckbox', ['$ionicConfig', function($ionicConfig) {
7466
  return {
7467
    restrict: 'E',
7468
    replace: true,
7469
    require: '?ngModel',
7470
    transclude: true,
7471
    template:
7472
      '<label class="item item-checkbox">' +
7473
        '<div class="checkbox checkbox-input-hidden disable-pointer-events">' +
7474
          '<input type="checkbox">' +
7475
          '<i class="checkbox-icon"></i>' +
7476
        '</div>' +
7477
        '<div class="item-content disable-pointer-events" ng-transclude></div>' +
7478
      '</label>',
7479
    compile: function(element, attr) {
7480
      var input = element.find('input');
7481
      forEach({
7482
        'name': attr.name,
7483
        'ng-value': attr.ngValue,
7484
        'ng-model': attr.ngModel,
7485
        'ng-checked': attr.ngChecked,
7486
        'ng-disabled': attr.ngDisabled,
7487
        'ng-true-value': attr.ngTrueValue,
7488
        'ng-false-value': attr.ngFalseValue,
7489
        'ng-change': attr.ngChange
7490
      }, function(value, name) {
7491
        if (isDefined(value)) {
7492
          input.attr(name, value);
7493
        }
7494
      });
7495
      var checkboxWrapper = element[0].querySelector('.checkbox');
7496
      checkboxWrapper.classList.add('checkbox-' + $ionicConfig.form.checkbox());
7497
    }
7498
  };
7499
}]);
7500
 
7501
/**
7502
 * @ngdoc directive
7503
 * @module ionic
7504
 * @name collectionRepeat
7505
 * @restrict A
7506
 * @codepen mFygh
7507
 * @description
7508
 * `collection-repeat` is a directive that allows you to render lists with
7509
 * thousands of items in them, and experience little to no performance penalty.
7510
 *
7511
 * Demo:
7512
 *
7513
 * The directive renders onto the screen only the items that should be currently visible.
7514
 * So if you have 1,000 items in your list but only ten fit on your screen,
7515
 * collection-repeat will only render into the DOM the ten that are in the current
7516
 * scroll position.
7517
 *
7518
 * Here are a few things to keep in mind while using collection-repeat:
7519
 *
7520
 * 1. The data supplied to collection-repeat must be an array.
7521
 * 2. You must explicitly tell the directive what size your items will be in the DOM, using directive attributes.
7522
 * Pixel amounts or percentages are allowed (see below).
7523
 * 3. The elements rendered will be absolutely positioned: be sure to let your CSS work with
7524
 * this (see below).
7525
 * 4. Each collection-repeat list will take up all of its parent scrollView's space.
7526
 * If you wish to have multiple lists on one page, put each list within its own
7527
 * {@link ionic.directive:ionScroll ionScroll} container.
7528
 * 5. You should not use the ng-show and ng-hide directives on your ion-content/ion-scroll elements that
7529
 * have a collection-repeat inside.  ng-show and ng-hide apply the `display: none` css rule to the content's
7530
 * style, causing the scrollView to read the width and height of the content as 0.  Resultingly,
7531
 * collection-repeat will render elements that have just been un-hidden incorrectly.
7532
 *
7533
 *
7534
 * @usage
7535
 *
7536
 * #### Basic Usage (single rows of items)
7537
 *
7538
 * Notice two things here: we use ng-style to set the height of the item to match
7539
 * what the repeater thinks our item height is.  Additionally, we add a css rule
7540
 * to make our item stretch to fit the full screen (since it will be absolutely
7541
 * positioned).
7542
 *
7543
 * ```html
7544
 * <ion-content ng-controller="ContentCtrl">
7545
 *   <div class="list">
7546
 *     <div class="item my-item"
7547
 *       collection-repeat="item in items"
7548
 *       collection-item-width="'100%'"
7549
 *       collection-item-height="getItemHeight(item, $index)"
7550
 *       ng-style="{height: getItemHeight(item, $index)}">
7551
 *       {% raw %}{{item}}{% endraw %}
7552
 *     </div>
7553
 *   </div>
7554
 * </ion-content>
7555
 * ```
7556
 * ```js
7557
 * function ContentCtrl($scope) {
7558
 *   $scope.items = [];
7559
 *   for (var i = 0; i < 1000; i++) {
7560
 *     $scope.items.push('Item ' + i);
7561
 *   }
7562
 *
7563
 *   $scope.getItemHeight = function(item, index) {
7564
 *     //Make evenly indexed items be 10px taller, for the sake of example
7565
 *     return (index % 2) === 0 ? 50 : 60;
7566
 *   };
7567
 * }
7568
 * ```
7569
 * ```css
7570
 * .my-item {
7571
 *   left: 0;
7572
 *   right: 0;
7573
 * }
7574
 * ```
7575
 *
7576
 * #### Grid Usage (three items per row)
7577
 *
7578
 * ```html
7579
 * <ion-content>
7580
 *   <div class="item item-avatar my-image-item"
7581
 *     collection-repeat="image in images"
7582
 *     collection-item-width="'33%'"
7583
 *     collection-item-height="'33%'">
7584
 *     <img ng-src="{{image.src}}">
7585
 *   </div>
7586
 * </ion-content>
7587
 * ```
7588
 * Percentage of total visible list dimensions. This example shows a 3 by 3 matrix that fits on the screen (3 rows and 3 colums). Note that dimensions are used in the creation of the element and therefore a measurement of the item cannnot be used as an input dimension.
7589
 * ```css
7590
 * .my-image-item img {
7591
 *   height: 33%;
7592
 *   width: 33%;
7593
 * }
7594
 * ```
7595
 *
7596
 * @param {expression} collection-repeat The expression indicating how to enumerate a collection. These
7597
 *   formats are currently supported:
7598
 *
7599
 *   * `variable in expression` – where variable is the user defined loop variable and `expression`
7600
 *     is a scope expression giving the collection to enumerate.
7601
 *
7602
 *     For example: `album in artist.albums`.
7603
 *
7604
 *   * `variable in expression track by tracking_expression` – You can also provide an optional tracking function
7605
 *     which can be used to associate the objects in the collection with the DOM elements. If no tracking function
7606
 *     is specified the collection-repeat associates elements by identity in the collection. It is an error to have
7607
 *     more than one tracking function to resolve to the same key. (This would mean that two distinct objects are
7608
 *     mapped to the same DOM element, which is not possible.)  Filters should be applied to the expression,
7609
 *     before specifying a tracking expression.
7610
 *
7611
 *     For example: `item in items` is equivalent to `item in items track by $id(item)'. This implies that the DOM elements
7612
 *     will be associated by item identity in the array.
7613
 *
7614
 *     For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique
7615
 *     `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements
7616
 *     with the corresponding item in the array by identity. Moving the same object in array would move the DOM
7617
 *     element in the same way in the DOM.
7618
 *
7619
 *     For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this
7620
 *     case the object identity does not matter. Two objects are considered equivalent as long as their `id`
7621
 *     property is same.
7622
 *
7623
 *     For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter
7624
 *     to items in conjunction with a tracking expression.
7625
 *
7626
 * @param {expression} collection-item-width The width of the repeated element.  Can be a number (in pixels) or a percentage.
7627
 * @param {expression} collection-item-height The height of the repeated element.  Can be a number (in pixels), or a percentage.
7628
 *
7629
 */
7630
var COLLECTION_REPEAT_SCROLLVIEW_XY_ERROR = "Cannot create a collection-repeat within a scrollView that is scrollable on both x and y axis.  Choose either x direction or y direction.";
7631
var COLLECTION_REPEAT_ATTR_HEIGHT_ERROR = "collection-repeat expected attribute collection-item-height to be a an expression that returns a number (in pixels) or percentage.";
7632
var COLLECTION_REPEAT_ATTR_WIDTH_ERROR = "collection-repeat expected attribute collection-item-width to be a an expression that returns a number (in pixels) or percentage.";
7633
var COLLECTION_REPEAT_ATTR_REPEAT_ERROR = "collection-repeat expected expression in form of '_item_ in _collection_[ track by _id_]' but got '%'";
7634
 
7635
IonicModule
7636
.directive('collectionRepeat', [
7637
  '$collectionRepeatManager',
7638
  '$collectionDataSource',
7639
  '$parse',
7640
function($collectionRepeatManager, $collectionDataSource, $parse) {
7641
  return {
7642
    priority: 1000,
7643
    transclude: 'element',
7644
    terminal: true,
7645
    $$tlb: true,
7646
    require: ['^$ionicScroll', '^?ionNavView'],
7647
    controller: [function(){}],
7648
    link: function($scope, $element, $attr, ctrls, $transclude) {
7649
      var scrollCtrl = ctrls[0];
7650
      var navViewCtrl = ctrls[1];
7651
      var wrap = jqLite('<div style="position:relative;">');
7652
      $element.parent()[0].insertBefore(wrap[0], $element[0]);
7653
      wrap.append($element);
7654
 
7655
      var scrollView = scrollCtrl.scrollView;
7656
      if (scrollView.options.scrollingX && scrollView.options.scrollingY) {
7657
        throw new Error(COLLECTION_REPEAT_SCROLLVIEW_XY_ERROR);
7658
      }
7659
 
7660
      var isVertical = !!scrollView.options.scrollingY;
7661
      if (isVertical && !$attr.collectionItemHeight) {
7662
        throw new Error(COLLECTION_REPEAT_ATTR_HEIGHT_ERROR);
7663
      } else if (!isVertical && !$attr.collectionItemWidth) {
7664
        throw new Error(COLLECTION_REPEAT_ATTR_WIDTH_ERROR);
7665
      }
7666
 
7667
      var heightParsed = $parse($attr.collectionItemHeight || '"100%"');
7668
      var widthParsed = $parse($attr.collectionItemWidth || '"100%"');
7669
 
7670
      var heightGetter = function(scope, locals) {
7671
        var result = heightParsed(scope, locals);
7672
        if (isString(result) && result.indexOf('%') > -1) {
7673
          return Math.floor(parseInt(result) / 100 * scrollView.__clientHeight);
7674
        }
7675
        return parseInt(result);
7676
      };
7677
      var widthGetter = function(scope, locals) {
7678
        var result = widthParsed(scope, locals);
7679
        if (isString(result) && result.indexOf('%') > -1) {
7680
          return Math.floor(parseInt(result) / 100 * scrollView.__clientWidth);
7681
        }
7682
        return parseInt(result);
7683
      };
7684
 
7685
      var match = $attr.collectionRepeat.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
7686
      if (!match) {
7687
        throw new Error(COLLECTION_REPEAT_ATTR_REPEAT_ERROR
7688
                        .replace('%', $attr.collectionRepeat));
7689
      }
7690
      var keyExpr = match[1];
7691
      var listExpr = match[2];
7692
      var trackByExpr = match[3];
7693
 
7694
      var dataSource = new $collectionDataSource({
7695
        scope: $scope,
7696
        transcludeFn: $transclude,
7697
        transcludeParent: $element.parent(),
7698
        keyExpr: keyExpr,
7699
        listExpr: listExpr,
7700
        trackByExpr: trackByExpr,
7701
        heightGetter: heightGetter,
7702
        widthGetter: widthGetter
7703
      });
7704
      var collectionRepeatManager = new $collectionRepeatManager({
7705
        dataSource: dataSource,
7706
        element: scrollCtrl.$element,
7707
        scrollView: scrollCtrl.scrollView,
7708
      });
7709
 
7710
      var listExprParsed = $parse(listExpr);
7711
      $scope.$watchCollection(listExprParsed, function(value) {
7712
        if (value && !angular.isArray(value)) {
7713
          throw new Error("collection-repeat expects an array to repeat over, but instead got '" + typeof value + "'.");
7714
        }
7715
        rerender(value);
7716
      });
7717
 
7718
      // Find every sibling before and after the repeated items, and pass them
7719
      // to the dataSource
7720
      var scrollViewContent = scrollCtrl.scrollView.__content;
7721
      function rerender(value) {
7722
        var beforeSiblings = [];
7723
        var afterSiblings = [];
7724
        var before = true;
7725
 
7726
        forEach(scrollViewContent.children, function(node, i) {
7727
          if ( ionic.DomUtil.elementIsDescendant($element[0], node, scrollViewContent) ) {
7728
            before = false;
7729
          } else {
7730
            if (node.hasAttribute('collection-repeat-ignore')) return;
7731
            var width = node.offsetWidth;
7732
            var height = node.offsetHeight;
7733
            if (width && height) {
7734
              var element = jqLite(node);
7735
              (before ? beforeSiblings : afterSiblings).push({
7736
                width: node.offsetWidth,
7737
                height: node.offsetHeight,
7738
                element: element,
7739
                scope: element.isolateScope() || element.scope(),
7740
                isOutside: true
7741
              });
7742
            }
7743
          }
7744
        });
7745
 
7746
        scrollView.resize();
7747
        dataSource.setData(value, beforeSiblings, afterSiblings);
7748
        collectionRepeatManager.resize();
7749
      }
7750
 
7751
      var requiresRerender;
7752
      function rerenderOnResize() {
7753
        rerender(listExprParsed($scope));
7754
        requiresRerender = (!scrollViewContent.clientWidth && !scrollViewContent.clientHeight);
7755
      }
7756
 
7757
      function viewEnter() {
7758
        if (requiresRerender) {
7759
          rerenderOnResize();
7760
        }
7761
      }
7762
 
7763
      scrollCtrl.$element.on('scroll.resize', rerenderOnResize);
7764
      ionic.on('resize', rerenderOnResize, window);
7765
      var deregisterViewListener;
7766
      if (navViewCtrl) {
7767
        deregisterViewListener = navViewCtrl.scope.$on('$ionicView.afterEnter', viewEnter);
7768
      }
7769
 
7770
      $scope.$on('$destroy', function() {
7771
        collectionRepeatManager.destroy();
7772
        dataSource.destroy();
7773
        ionic.off('resize', rerenderOnResize, window);
7774
        (deregisterViewListener || angular.noop)();
7775
      });
7776
    }
7777
  };
7778
}])
7779
.directive({
7780
  ngSrc: collectionRepeatSrcDirective('ngSrc', 'src'),
7781
  ngSrcset: collectionRepeatSrcDirective('ngSrcset', 'srcset'),
7782
  ngHref: collectionRepeatSrcDirective('ngHref', 'href')
7783
});
7784
 
7785
// Fix for #1674
7786
// Problem: if an ngSrc or ngHref expression evaluates to a falsy value, it will
7787
// not erase the previous truthy value of the href.
7788
// In collectionRepeat, we re-use elements from before. So if the ngHref expression
7789
// evaluates to truthy for item 1 and then falsy for item 2, if an element changes
7790
// from representing item 1 to representing item 2, item 2 will still have
7791
// item 1's href value.
7792
// Solution:  erase the href or src attribute if ngHref/ngSrc are falsy.
7793
function collectionRepeatSrcDirective(ngAttrName, attrName) {
7794
  return [function() {
7795
    return {
7796
      priority: '99', // it needs to run after the attributes are interpolated
7797
      link: function(scope, element, attr) {
7798
        attr.$observe(ngAttrName, function(value) {
7799
          if (!value) {
7800
            element[0].removeAttribute(attrName);
7801
          }
7802
        });
7803
      }
7804
    };
7805
  }];
7806
}
7807
 
7808
/**
7809
 * @ngdoc directive
7810
 * @name ionContent
7811
 * @module ionic
7812
 * @delegate ionic.service:$ionicScrollDelegate
7813
 * @restrict E
7814
 *
7815
 * @description
7816
 * The ionContent directive provides an easy to use content area that can be configured
7817
 * to use Ionic's custom Scroll View, or the built in overflow scrolling of the browser.
7818
 *
7819
 * While we recommend using the custom Scroll features in Ionic in most cases, sometimes
7820
 * (for performance reasons) only the browser's native overflow scrolling will suffice,
7821
 * and so we've made it easy to toggle between the Ionic scroll implementation and
7822
 * overflow scrolling.
7823
 *
7824
 * You can implement pull-to-refresh with the {@link ionic.directive:ionRefresher}
7825
 * directive, and infinite scrolling with the {@link ionic.directive:ionInfiniteScroll}
7826
 * directive.
7827
 *
7828
 * Be aware that this directive gets its own child scope. If you do not understand why this
7829
 * is important, you can read [https://docs.angularjs.org/guide/scope](https://docs.angularjs.org/guide/scope).
7830
 *
7831
 * @param {string=} delegate-handle The handle used to identify this scrollView
7832
 * with {@link ionic.service:$ionicScrollDelegate}.
7833
 * @param {string=} direction Which way to scroll. 'x' or 'y' or 'xy'. Default 'y'.
7834
 * @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.
7835
 * @param {boolean=} padding Whether to add padding to the content.
7836
 * of the content.  Defaults to true on iOS, false on Android.
7837
 * @param {boolean=} scroll Whether to allow scrolling of content.  Defaults to true.
7838
 * @param {boolean=} overflow-scroll Whether to use overflow-scrolling instead of
7839
 * Ionic scroll.
7840
 * @param {boolean=} scrollbar-x Whether to show the horizontal scrollbar. Default true.
7841
 * @param {boolean=} scrollbar-y Whether to show the vertical scrollbar. Default true.
7842
 * @param {string=} start-x Initial horizontal scroll position. Default 0.
7843
 * @param {string=} start-y Initial vertical scroll position. Default 0.
7844
 * @param {expression=} on-scroll Expression to evaluate when the content is scrolled.
7845
 * @param {expression=} on-scroll-complete Expression to evaluate when a scroll action completes.
7846
 * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges
7847
 * of the content.  Defaults to true on iOS, false on Android.
7848
 * @param {number=} scroll-event-interval Number of milliseconds between each firing of the 'on-scroll' expression. Default 10.
7849
 */
7850
IonicModule
7851
.directive('ionContent', [
7852
  '$timeout',
7853
  '$controller',
7854
  '$ionicBind',
7855
function($timeout, $controller, $ionicBind) {
7856
  return {
7857
    restrict: 'E',
7858
    require: '^?ionNavView',
7859
    scope: true,
7860
    priority: 800,
7861
    compile: function(element, attr) {
7862
      var innerElement;
7863
 
7864
      element.addClass('scroll-content ionic-scroll');
7865
 
7866
      if (attr.scroll != 'false') {
7867
        //We cannot use normal transclude here because it breaks element.data()
7868
        //inheritance on compile
7869
        innerElement = jqLite('<div class="scroll"></div>');
7870
        innerElement.append(element.contents());
7871
        element.append(innerElement);
7872
      } else {
7873
        element.addClass('scroll-content-false');
7874
      }
7875
 
7876
      return { pre: prelink };
7877
      function prelink($scope, $element, $attr, navViewCtrl) {
7878
        var parentScope = $scope.$parent;
7879
        $scope.$watch(function() {
7880
          return (parentScope.$hasHeader ? ' has-header' : '')  +
7881
            (parentScope.$hasSubheader ? ' has-subheader' : '') +
7882
            (parentScope.$hasFooter ? ' has-footer' : '') +
7883
            (parentScope.$hasSubfooter ? ' has-subfooter' : '') +
7884
            (parentScope.$hasTabs ? ' has-tabs' : '') +
7885
            (parentScope.$hasTabsTop ? ' has-tabs-top' : '');
7886
        }, function(className, oldClassName) {
7887
          $element.removeClass(oldClassName);
7888
          $element.addClass(className);
7889
        });
7890
 
7891
        //Only this ionContent should use these variables from parent scopes
7892
        $scope.$hasHeader = $scope.$hasSubheader =
7893
          $scope.$hasFooter = $scope.$hasSubfooter =
7894
          $scope.$hasTabs = $scope.$hasTabsTop =
7895
          false;
7896
        $ionicBind($scope, $attr, {
7897
          $onScroll: '&onScroll',
7898
          $onScrollComplete: '&onScrollComplete',
7899
          hasBouncing: '@',
7900
          padding: '@',
7901
          direction: '@',
7902
          scrollbarX: '@',
7903
          scrollbarY: '@',
7904
          startX: '@',
7905
          startY: '@',
7906
          scrollEventInterval: '@'
7907
        });
7908
        $scope.direction = $scope.direction || 'y';
7909
 
7910
        if (angular.isDefined($attr.padding)) {
7911
          $scope.$watch($attr.padding, function(newVal) {
7912
              (innerElement || $element).toggleClass('padding', !!newVal);
7913
          });
7914
        }
7915
 
7916
        if ($attr.scroll === "false") {
7917
          //do nothing
7918
        } else if(attr.overflowScroll === "true") {
7919
          $element.addClass('overflow-scroll');
7920
        } else {
7921
          var scrollViewOptions = {
7922
            el: $element[0],
7923
            delegateHandle: attr.delegateHandle,
7924
            locking: (attr.locking || 'true') === 'true',
7925
            bouncing: $scope.$eval($scope.hasBouncing),
7926
            startX: $scope.$eval($scope.startX) || 0,
7927
            startY: $scope.$eval($scope.startY) || 0,
7928
            scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
7929
            scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
7930
            scrollingX: $scope.direction.indexOf('x') >= 0,
7931
            scrollingY: $scope.direction.indexOf('y') >= 0,
7932
            scrollEventInterval: parseInt($scope.scrollEventInterval, 10) || 10,
7933
            scrollingComplete: function() {
7934
              $scope.$onScrollComplete({
7935
                scrollTop: this.__scrollTop,
7936
                scrollLeft: this.__scrollLeft
7937
              });
7938
            }
7939
          };
7940
          $controller('$ionicScroll', {
7941
            $scope: $scope,
7942
            scrollViewOptions: scrollViewOptions
7943
          });
7944
 
7945
          $scope.$on('$destroy', function() {
7946
            scrollViewOptions.scrollingComplete = angular.noop;
7947
            delete scrollViewOptions.el;
7948
            innerElement = null;
7949
            $element = null;
7950
            attr.$$element = null;
7951
          });
7952
        }
7953
 
7954
      }
7955
    }
7956
  };
7957
}]);
7958
 
7959
/**
7960
 * @ngdoc directive
7961
 * @name exposeAsideWhen
7962
 * @module ionic
7963
 * @restrict A
7964
 * @parent ionic.directive:ionSideMenus
7965
 *
7966
 * @description
7967
 * It is common for a tablet application to hide a menu when in portrait mode, but to show the
7968
 * same menu on the left side when the tablet is in landscape mode. The `exposeAsideWhen` attribute
7969
 * directive can be used to accomplish a similar interface.
7970
 *
7971
 * By default, side menus are hidden underneath its side menu content, and can be opened by either
7972
 * swiping the content left or right, or toggling a button to show the side menu. However, by adding the
7973
 * `exposeAsideWhen` attribute directive to an {@link ionic.directive:ionSideMenu} element directive,
7974
 * a side menu can be given instructions on "when" the menu should be exposed (always viewable). For
7975
 * example, the `expose-aside-when="large"` attribute will keep the side menu hidden when the viewport's
7976
 * width is less than `768px`, but when the viewport's width is `768px` or greater, the menu will then
7977
 * always be shown and can no longer be opened or closed like it could when it was hidden for smaller
7978
 * viewports.
7979
 *
7980
 * Using `large` as the attribute's value is a shortcut value to `(min-width:768px)` since it is
7981
 * the most common use-case. However, for added flexibility, any valid media query could be added
7982
 * as the value, such as `(min-width:600px)` or even multiple queries such as
7983
 * `(min-width:750px) and (max-width:1200px)`.
7984
 
7985
 * @usage
7986
 * ```html
7987
 * <ion-side-menus>
7988
 *   <!-- Center content -->
7989
 *   <ion-side-menu-content>
7990
 *   </ion-side-menu-content>
7991
 *
7992
 *   <!-- Left menu -->
7993
 *   <ion-side-menu expose-aside-when="large">
7994
 *   </ion-side-menu>
7995
 * </ion-side-menus>
7996
 * ```
7997
 * For a complete side menu example, see the
7998
 * {@link ionic.directive:ionSideMenus} documentation.
7999
 */
8000
IonicModule
8001
.directive('exposeAsideWhen', ['$window', function($window) {
8002
  return {
8003
    restrict: 'A',
8004
    require: '^ionSideMenus',
8005
    link: function($scope, $element, $attr, sideMenuCtrl) {
8006
 
8007
      function checkAsideExpose() {
8008
        var mq = $attr.exposeAsideWhen == 'large' ? '(min-width:768px)' : $attr.exposeAsideWhen;
8009
        sideMenuCtrl.exposeAside( $window.matchMedia(mq).matches );
8010
        sideMenuCtrl.activeAsideResizing(false);
8011
      }
8012
 
8013
      function onResize() {
8014
        sideMenuCtrl.activeAsideResizing(true);
8015
        debouncedCheck();
8016
      }
8017
 
8018
      var debouncedCheck = ionic.debounce(function() {
8019
        $scope.$apply(function(){
8020
          checkAsideExpose();
8021
        });
8022
      }, 300, false);
8023
 
8024
      checkAsideExpose();
8025
 
8026
      ionic.on('resize', onResize, $window);
8027
 
8028
      $scope.$on('$destroy', function(){
8029
        ionic.off('resize', onResize, $window);
8030
      });
8031
 
8032
    }
8033
  };
8034
}]);
8035
 
8036
 
8037
var GESTURE_DIRECTIVES = 'onHold onTap onTouch onRelease onDrag onDragUp onDragRight onDragDown onDragLeft onSwipe onSwipeUp onSwipeRight onSwipeDown onSwipeLeft'.split(' ');
8038
 
8039
GESTURE_DIRECTIVES.forEach(function(name) {
8040
  IonicModule.directive(name, gestureDirective(name));
8041
});
8042
 
8043
 
8044
/**
8045
 * @ngdoc directive
8046
 * @name onHold
8047
 * @module ionic
8048
 * @restrict A
8049
 *
8050
 * @description
8051
 * Touch stays at the same location for 500ms. Similar to long touch events available for AngularJS and jQuery.
8052
 *
8053
 * @usage
8054
 * ```html
8055
 * <button on-hold="onHold()" class="button">Test</button>
8056
 * ```
8057
 */
8058
 
8059
 
8060
/**
8061
 * @ngdoc directive
8062
 * @name onTap
8063
 * @module ionic
8064
 * @restrict A
8065
 *
8066
 * @description
8067
 * Quick touch at a location. If the duration of the touch goes
8068
 * longer than 250ms it is no longer a tap gesture.
8069
 *
8070
 * @usage
8071
 * ```html
8072
 * <button on-tap="onTap()" class="button">Test</button>
8073
 * ```
8074
 */
8075
 
8076
 
8077
/**
8078
 * @ngdoc directive
8079
 * @name onTouch
8080
 * @module ionic
8081
 * @restrict A
8082
 *
8083
 * @description
8084
 * Called immediately when the user first begins a touch. This
8085
 * gesture does not wait for a touchend/mouseup.
8086
 *
8087
 * @usage
8088
 * ```html
8089
 * <button on-touch="onTouch()" class="button">Test</button>
8090
 * ```
8091
 */
8092
 
8093
 
8094
/**
8095
 * @ngdoc directive
8096
 * @name onRelease
8097
 * @module ionic
8098
 * @restrict A
8099
 *
8100
 * @description
8101
 * Called when the user ends a touch.
8102
 *
8103
 * @usage
8104
 * ```html
8105
 * <button on-release="onRelease()" class="button">Test</button>
8106
 * ```
8107
 */
8108
 
8109
 
8110
/**
8111
 * @ngdoc directive
8112
 * @name onDrag
8113
 * @module ionic
8114
 * @restrict A
8115
 *
8116
 * @description
8117
 * Move with one touch around on the page. Blocking the scrolling when
8118
 * moving left and right is a good practice. When all the drag events are
8119
 * blocking you disable scrolling on that area.
8120
 *
8121
 * @usage
8122
 * ```html
8123
 * <button on-drag="onDrag()" class="button">Test</button>
8124
 * ```
8125
 */
8126
 
8127
 
8128
/**
8129
 * @ngdoc directive
8130
 * @name onDragUp
8131
 * @module ionic
8132
 * @restrict A
8133
 *
8134
 * @description
8135
 * Called when the element is dragged up.
8136
 *
8137
 * @usage
8138
 * ```html
8139
 * <button on-drag-up="onDragUp()" class="button">Test</button>
8140
 * ```
8141
 */
8142
 
8143
 
8144
/**
8145
 * @ngdoc directive
8146
 * @name onDragRight
8147
 * @module ionic
8148
 * @restrict A
8149
 *
8150
 * @description
8151
 * Called when the element is dragged to the right.
8152
 *
8153
 * @usage
8154
 * ```html
8155
 * <button on-drag-right="onDragRight()" class="button">Test</button>
8156
 * ```
8157
 */
8158
 
8159
 
8160
/**
8161
 * @ngdoc directive
8162
 * @name onDragDown
8163
 * @module ionic
8164
 * @restrict A
8165
 *
8166
 * @description
8167
 * Called when the element is dragged down.
8168
 *
8169
 * @usage
8170
 * ```html
8171
 * <button on-drag-down="onDragDown()" class="button">Test</button>
8172
 * ```
8173
 */
8174
 
8175
 
8176
/**
8177
 * @ngdoc directive
8178
 * @name onDragLeft
8179
 * @module ionic
8180
 * @restrict A
8181
 *
8182
 * @description
8183
 * Called when the element is dragged to the left.
8184
 *
8185
 * @usage
8186
 * ```html
8187
 * <button on-drag-left="onDragLeft()" class="button">Test</button>
8188
 * ```
8189
 */
8190
 
8191
 
8192
/**
8193
 * @ngdoc directive
8194
 * @name onSwipe
8195
 * @module ionic
8196
 * @restrict A
8197
 *
8198
 * @description
8199
 * Called when a moving touch has a high velocity in any direction.
8200
 *
8201
 * @usage
8202
 * ```html
8203
 * <button on-swipe="onSwipe()" class="button">Test</button>
8204
 * ```
8205
 */
8206
 
8207
 
8208
/**
8209
 * @ngdoc directive
8210
 * @name onSwipeUp
8211
 * @module ionic
8212
 * @restrict A
8213
 *
8214
 * @description
8215
 * Called when a moving touch has a high velocity moving up.
8216
 *
8217
 * @usage
8218
 * ```html
8219
 * <button on-swipe-up="onSwipeUp()" class="button">Test</button>
8220
 * ```
8221
 */
8222
 
8223
 
8224
/**
8225
 * @ngdoc directive
8226
 * @name onSwipeRight
8227
 * @module ionic
8228
 * @restrict A
8229
 *
8230
 * @description
8231
 * Called when a moving touch has a high velocity moving to the right.
8232
 *
8233
 * @usage
8234
 * ```html
8235
 * <button on-swipe-right="onSwipeRight()" class="button">Test</button>
8236
 * ```
8237
 */
8238
 
8239
 
8240
/**
8241
 * @ngdoc directive
8242
 * @name onSwipeDown
8243
 * @module ionic
8244
 * @restrict A
8245
 *
8246
 * @description
8247
 * Called when a moving touch has a high velocity moving down.
8248
 *
8249
 * @usage
8250
 * ```html
8251
 * <button on-swipe-down="onSwipeDown()" class="button">Test</button>
8252
 * ```
8253
 */
8254
 
8255
 
8256
/**
8257
 * @ngdoc directive
8258
 * @name onSwipeLeft
8259
 * @module ionic
8260
 * @restrict A
8261
 *
8262
 * @description
8263
 * Called when a moving touch has a high velocity moving to the left.
8264
 *
8265
 * @usage
8266
 * ```html
8267
 * <button on-swipe-left="onSwipeLeft()" class="button">Test</button>
8268
 * ```
8269
 */
8270
 
8271
 
8272
function gestureDirective(directiveName) {
8273
  return ['$ionicGesture', '$parse', function($ionicGesture, $parse) {
8274
    var eventType = directiveName.substr(2).toLowerCase();
8275
 
8276
    return function(scope, element, attr) {
8277
      var fn = $parse( attr[directiveName] );
8278
 
8279
      var listener = function(ev) {
8280
        scope.$apply(function() {
8281
          fn(scope, {
8282
            $event: ev
8283
          });
8284
        });
8285
      };
8286
 
8287
      var gesture = $ionicGesture.on(eventType, listener, element);
8288
 
8289
      scope.$on('$destroy', function() {
8290
        $ionicGesture.off(gesture, eventType, listener);
8291
      });
8292
    };
8293
  }];
8294
}
8295
 
8296
 
8297
IonicModule
8298
.directive('ionHeaderBar', tapScrollToTopDirective())
8299
 
8300
/**
8301
 * @ngdoc directive
8302
 * @name ionHeaderBar
8303
 * @module ionic
8304
 * @restrict E
8305
 *
8306
 * @description
8307
 * Adds a fixed header bar above some content.
8308
 *
8309
 * Can also be a subheader (lower down) if the 'bar-subheader' class is applied.
8310
 * See [the header CSS docs](/docs/components/#subheader).
8311
 *
8312
 * @param {string=} align-title How to align the title. By default the title
8313
 * will be aligned the same as how the platform aligns its titles (iOS centers
8314
 * titles, Android aligns them left).
8315
 * Available: 'left', 'right', or 'center'.  Defaults to the same as the platform.
8316
 * @param {boolean=} no-tap-scroll By default, the header bar will scroll the
8317
 * content to the top when tapped.  Set no-tap-scroll to true to disable this
8318
 * behavior.
8319
 * Available: true or false.  Defaults to false.
8320
 *
8321
 * @usage
8322
 * ```html
8323
 * <ion-header-bar align-title="left" class="bar-positive">
8324
 *   <div class="buttons">
8325
 *     <button class="button" ng-click="doSomething()">Left Button</button>
8326
 *   </div>
8327
 *   <h1 class="title">Title!</h1>
8328
 *   <div class="buttons">
8329
 *     <button class="button">Right Button</button>
8330
 *   </div>
8331
 * </ion-header-bar>
8332
 * <ion-content>
8333
 *   Some content!
8334
 * </ion-content>
8335
 * ```
8336
 */
8337
.directive('ionHeaderBar', headerFooterBarDirective(true))
8338
 
8339
/**
8340
 * @ngdoc directive
8341
 * @name ionFooterBar
8342
 * @module ionic
8343
 * @restrict E
8344
 *
8345
 * @description
8346
 * Adds a fixed footer bar below some content.
8347
 *
8348
 * Can also be a subfooter (higher up) if the 'bar-subfooter' class is applied.
8349
 * See [the footer CSS docs](/docs/components/#footer).
8350
 *
8351
 * Note: If you use ionFooterBar in combination with ng-if, the surrounding content
8352
 * will not align correctly.  This will be fixed soon.
8353
 *
8354
 * @param {string=} align-title Where to align the title.
8355
 * Available: 'left', 'right', or 'center'.  Defaults to 'center'.
8356
 *
8357
 * @usage
8358
 * ```html
8359
 * <ion-content>
8360
 *   Some content!
8361
 * </ion-content>
8362
 * <ion-footer-bar align-title="left" class="bar-assertive">
8363
 *   <div class="buttons">
8364
 *     <button class="button">Left Button</button>
8365
 *   </div>
8366
 *   <h1 class="title">Title!</h1>
8367
 *   <div class="buttons" ng-click="doSomething()">
8368
 *     <button class="button">Right Button</button>
8369
 *   </div>
8370
 * </ion-footer-bar>
8371
 * ```
8372
 */
8373
.directive('ionFooterBar', headerFooterBarDirective(false));
8374
 
8375
function tapScrollToTopDirective() {
8376
  return ['$ionicScrollDelegate', function($ionicScrollDelegate) {
8377
    return {
8378
      restrict: 'E',
8379
      link: function($scope, $element, $attr) {
8380
        if ($attr.noTapScroll == 'true') {
8381
          return;
8382
        }
8383
        ionic.on('tap', onTap, $element[0]);
8384
        $scope.$on('$destroy', function() {
8385
          ionic.off('tap', onTap, $element[0]);
8386
        });
8387
 
8388
        function onTap(e) {
8389
          var depth = 3;
8390
          var current = e.target;
8391
          //Don't scroll to top in certain cases
8392
          while (depth-- && current) {
8393
            if (current.classList.contains('button') ||
8394
                current.tagName.match(/input|textarea|select/i) ||
8395
                current.isContentEditable) {
8396
              return;
8397
            }
8398
            current = current.parentNode;
8399
          }
8400
          var touch = e.gesture && e.gesture.touches[0] || e.detail.touches[0];
8401
          var bounds = $element[0].getBoundingClientRect();
8402
          if (ionic.DomUtil.rectContains(
8403
            touch.pageX, touch.pageY,
8404
            bounds.left, bounds.top - 20,
8405
            bounds.left + bounds.width, bounds.top + bounds.height
8406
          )) {
8407
            $ionicScrollDelegate.scrollTop(true);
8408
          }
8409
        }
8410
      }
8411
    };
8412
  }];
8413
}
8414
 
8415
function headerFooterBarDirective(isHeader) {
8416
  return ['$document', '$timeout',function($document, $timeout) {
8417
    return {
8418
      restrict: 'E',
8419
      controller: '$ionicHeaderBar',
8420
      compile: function(tElement, $attr) {
8421
        tElement.addClass(isHeader ? 'bar bar-header' : 'bar bar-footer');
8422
        // top style tabs? if so, remove bottom border for seamless display
8423
        $timeout(function() {
8424
          if (isHeader && $document[0].getElementsByClassName('tabs-top').length) tElement.addClass('has-tabs-top');
8425
        });
8426
 
8427
        return { pre: prelink };
8428
        function prelink($scope, $element, $attr, ctrl) {
8429
          if (isHeader) {
8430
            $scope.$watch(function() { return $element[0].className; }, function(value) {
8431
              var isShown = value.indexOf('ng-hide') === -1;
8432
              var isSubheader = value.indexOf('bar-subheader') !== -1;
8433
              $scope.$hasHeader = isShown && !isSubheader;
8434
              $scope.$hasSubheader = isShown && isSubheader;
8435
            });
8436
            $scope.$on('$destroy', function() {
8437
              delete $scope.$hasHeader;
8438
              delete $scope.$hasSubheader;
8439
            });
8440
            ctrl.align();
8441
            $scope.$on('$ionicHeader.align', function() {
8442
              ionic.requestAnimationFrame(ctrl.align);
8443
            });
8444
 
8445
          } else {
8446
            $scope.$watch(function() { return $element[0].className; }, function(value) {
8447
              var isShown = value.indexOf('ng-hide') === -1;
8448
              var isSubfooter = value.indexOf('bar-subfooter') !== -1;
8449
              $scope.$hasFooter = isShown && !isSubfooter;
8450
              $scope.$hasSubfooter = isShown && isSubfooter;
8451
            });
8452
            $scope.$on('$destroy', function() {
8453
              delete $scope.$hasFooter;
8454
              delete $scope.$hasSubfooter;
8455
            });
8456
            $scope.$watch('$hasTabs', function(val) {
8457
              $element.toggleClass('has-tabs', !!val);
8458
            });
8459
          }
8460
        }
8461
      }
8462
    };
8463
  }];
8464
}
8465
 
8466
/**
8467
 * @ngdoc directive
8468
 * @name ionInfiniteScroll
8469
 * @module ionic
8470
 * @parent ionic.directive:ionContent, ionic.directive:ionScroll
8471
 * @restrict E
8472
 *
8473
 * @description
8474
 * The ionInfiniteScroll directive allows you to call a function whenever
8475
 * the user gets to the bottom of the page or near the bottom of the page.
8476
 *
8477
 * The expression you pass in for `on-infinite` is called when the user scrolls
8478
 * greater than `distance` away from the bottom of the content.  Once `on-infinite`
8479
 * is done loading new data, it should broadcast the `scroll.infiniteScrollComplete`
8480
 * event from your controller (see below example).
8481
 *
8482
 * @param {expression} on-infinite What to call when the scroller reaches the
8483
 * bottom.
8484
 * @param {string=} distance The distance from the bottom that the scroll must
8485
 * reach to trigger the on-infinite expression. Default: 1%.
8486
 * @param {string=} icon The icon to show while loading. Default: 'ion-loading-d'.
8487
 *
8488
 * @usage
8489
 * ```html
8490
 * <ion-content ng-controller="MyController">
8491
 *   <ion-list>
8492
 *   ....
8493
 *   ....
8494
 *   </ion-list>
8495
 *
8496
 *   <ion-infinite-scroll
8497
 *     on-infinite="loadMore()"
8498
 *     distance="1%">
8499
 *   </ion-infinite-scroll>
8500
 * </ion-content>
8501
 * ```
8502
 * ```js
8503
 * function MyController($scope, $http) {
8504
 *   $scope.items = [];
8505
 *   $scope.loadMore = function() {
8506
 *     $http.get('/more-items').success(function(items) {
8507
 *       useItems(items);
8508
 *       $scope.$broadcast('scroll.infiniteScrollComplete');
8509
 *     });
8510
 *   };
8511
 *
8512
 *   $scope.$on('$stateChangeSuccess', function() {
8513
 *     $scope.loadMore();
8514
 *   });
8515
 * }
8516
 * ```
8517
 *
8518
 * An easy to way to stop infinite scroll once there is no more data to load
8519
 * is to use angular's `ng-if` directive:
8520
 *
8521
 * ```html
8522
 * <ion-infinite-scroll
8523
 *   ng-if="moreDataCanBeLoaded()"
8524
 *   icon="ion-loading-c"
8525
 *   on-infinite="loadMoreData()">
8526
 * </ion-infinite-scroll>
8527
 * ```
8528
 */
8529
IonicModule
8530
.directive('ionInfiniteScroll', ['$timeout', function($timeout) {
8531
  function calculateMaxValue(distance, maximum, isPercent) {
8532
    return isPercent ?
8533
      maximum * (1 - parseFloat(distance,10) / 100) :
8534
      maximum - parseFloat(distance, 10);
8535
  }
8536
  return {
8537
    restrict: 'E',
8538
    require: ['^$ionicScroll', 'ionInfiniteScroll'],
8539
    template: '<i class="icon {{icon()}} icon-refreshing"></i>',
8540
    scope: {
8541
      load: '&onInfinite'
8542
    },
8543
    controller: ['$scope', '$attrs', function($scope, $attrs) {
8544
      this.isLoading = false;
8545
      this.scrollView = null; //given by link function
8546
      this.getMaxScroll = function() {
8547
        var distance = ($attrs.distance || '2.5%').trim();
8548
        var isPercent = distance.indexOf('%') !== -1;
8549
        var maxValues = this.scrollView.getScrollMax();
8550
        return {
8551
          left: this.scrollView.options.scrollingX ?
8552
            calculateMaxValue(distance, maxValues.left, isPercent) :
8553
            -1,
8554
          top: this.scrollView.options.scrollingY ?
8555
            calculateMaxValue(distance, maxValues.top, isPercent) :
8556
            -1
8557
        };
8558
      };
8559
    }],
8560
    link: function($scope, $element, $attrs, ctrls) {
8561
      var scrollCtrl = ctrls[0];
8562
      var infiniteScrollCtrl = ctrls[1];
8563
      var scrollView = infiniteScrollCtrl.scrollView = scrollCtrl.scrollView;
8564
 
8565
      $scope.icon = function() {
8566
        return angular.isDefined($attrs.icon) ? $attrs.icon : 'ion-loading-d';
8567
      };
8568
 
8569
      var onInfinite = function() {
8570
        $element[0].classList.add('active');
8571
        infiniteScrollCtrl.isLoading = true;
8572
        $scope.load();
8573
      };
8574
 
8575
      var finishInfiniteScroll = function() {
8576
        $element[0].classList.remove('active');
8577
        $timeout(function() {
8578
          scrollView.resize();
8579
          checkBounds();
8580
        }, 0, false);
8581
        infiniteScrollCtrl.isLoading = false;
8582
      };
8583
 
8584
      $scope.$on('scroll.infiniteScrollComplete', function() {
8585
        finishInfiniteScroll();
8586
      });
8587
 
8588
      $scope.$on('$destroy', function() {
8589
        if(scrollCtrl && scrollCtrl.$element)scrollCtrl.$element.off('scroll', checkBounds);
8590
      });
8591
 
8592
      var checkBounds = ionic.animationFrameThrottle(checkInfiniteBounds);
8593
 
8594
      //Check bounds on start, after scrollView is fully rendered
8595
      $timeout(checkBounds, 0, false);
8596
      scrollCtrl.$element.on('scroll', checkBounds);
8597
 
8598
      function checkInfiniteBounds() {
8599
        if (infiniteScrollCtrl.isLoading) return;
8600
 
8601
        var scrollValues = scrollView.getValues();
8602
        var maxScroll = infiniteScrollCtrl.getMaxScroll();
8603
 
8604
        if ((maxScroll.left !== -1 && scrollValues.left >= maxScroll.left) ||
8605
            (maxScroll.top !== -1 && scrollValues.top >= maxScroll.top)) {
8606
          onInfinite();
8607
        }
8608
      }
8609
    }
8610
  };
8611
}]);
8612
 
8613
var ITEM_TPL_CONTENT_ANCHOR =
8614
  '<a class="item-content" ng-href="{{$href()}}" target="{{$target()}}"></a>';
8615
var ITEM_TPL_CONTENT =
8616
  '<div class="item-content"></div>';
8617
/**
8618
* @ngdoc directive
8619
* @name ionItem
8620
* @parent ionic.directive:ionList
8621
* @module ionic
8622
* @restrict E
8623
* Creates a list-item that can easily be swiped,
8624
* deleted, reordered, edited, and more.
8625
*
8626
* See {@link ionic.directive:ionList} for a complete example & explanation.
8627
*
8628
* Can be assigned any item class name. See the
8629
* [list CSS documentation](/docs/components/#list).
8630
*
8631
* @usage
8632
*
8633
* ```html
8634
* <ion-list>
8635
*   <ion-item>Hello!</ion-item>
8636
*   <ion-item href="#/detail">
8637
*     Link to detail page
8638
*   </ion-item>
8639
* </ion-list>
8640
* ```
8641
*/
8642
IonicModule
8643
.directive('ionItem', function() {
8644
  return {
8645
    restrict: 'E',
8646
    controller: ['$scope', '$element', function($scope, $element) {
8647
      this.$scope = $scope;
8648
      this.$element = $element;
8649
    }],
8650
    scope: true,
8651
    compile: function($element, $attrs) {
8652
      var isAnchor = angular.isDefined($attrs.href) ||
8653
                     angular.isDefined($attrs.ngHref) ||
8654
                     angular.isDefined($attrs.uiSref);
8655
      var isComplexItem = isAnchor ||
8656
        //Lame way of testing, but we have to know at compile what to do with the element
8657
        /ion-(delete|option|reorder)-button/i.test($element.html());
8658
 
8659
        if (isComplexItem) {
8660
          var innerElement = jqLite(isAnchor ? ITEM_TPL_CONTENT_ANCHOR : ITEM_TPL_CONTENT);
8661
          innerElement.append($element.contents());
8662
 
8663
          $element.append(innerElement);
8664
          $element.addClass('item item-complex');
8665
        } else {
8666
          $element.addClass('item');
8667
        }
8668
 
8669
        return function link($scope, $element, $attrs) {
8670
          $scope.$href = function() {
8671
            return $attrs.href || $attrs.ngHref;
8672
          };
8673
          $scope.$target = function() {
8674
            return $attrs.target || '_self';
8675
          };
8676
        };
8677
    }
8678
  };
8679
});
8680
 
8681
var ITEM_TPL_DELETE_BUTTON =
8682
  '<div class="item-left-edit item-delete enable-pointer-events">' +
8683
  '</div>';
8684
/**
8685
* @ngdoc directive
8686
* @name ionDeleteButton
8687
* @parent ionic.directive:ionItem
8688
* @module ionic
8689
* @restrict E
8690
* Creates a delete button inside a list item, that is visible when the
8691
* {@link ionic.directive:ionList ionList parent's} `show-delete` evaluates to true or
8692
* `$ionicListDelegate.showDelete(true)` is called.
8693
*
8694
* Takes any ionicon as a class.
8695
*
8696
* See {@link ionic.directive:ionList} for a complete example & explanation.
8697
*
8698
* @usage
8699
*
8700
* ```html
8701
* <ion-list show-delete="shouldShowDelete">
8702
*   <ion-item>
8703
*     <ion-delete-button class="ion-minus-circled"></ion-delete-button>
8704
*     Hello, list item!
8705
*   </ion-item>
8706
* </ion-list>
8707
* <ion-toggle ng-model="shouldShowDelete">
8708
*   Show Delete?
8709
* </ion-toggle>
8710
* ```
8711
*/
8712
IonicModule
8713
.directive('ionDeleteButton', function() {
8714
  return {
8715
    restrict: 'E',
8716
    require: ['^ionItem', '^?ionList'],
8717
    //Run before anything else, so we can move it before other directives process
8718
    //its location (eg ngIf relies on the location of the directive in the dom)
8719
    priority: Number.MAX_VALUE,
8720
    compile: function($element, $attr) {
8721
      //Add the classes we need during the compile phase, so that they stay
8722
      //even if something else like ngIf removes the element and re-addss it
8723
      $attr.$set('class', ($attr['class'] || '') + ' button icon button-icon', true);
8724
      return function($scope, $element, $attr, ctrls) {
8725
        var itemCtrl = ctrls[0];
8726
        var listCtrl = ctrls[1];
8727
        var container = jqLite(ITEM_TPL_DELETE_BUTTON);
8728
        container.append($element);
8729
        itemCtrl.$element.append(container).addClass('item-left-editable');
8730
 
8731
        if (listCtrl && listCtrl.showDelete()) {
8732
          container.addClass('visible active');
8733
        }
8734
      };
8735
    }
8736
  };
8737
});
8738
 
8739
 
8740
IonicModule
8741
.directive('itemFloatingLabel', function() {
8742
  return {
8743
    restrict: 'C',
8744
    link: function(scope, element) {
8745
      var el = element[0];
8746
      var input = el.querySelector('input, textarea');
8747
      var inputLabel = el.querySelector('.input-label');
8748
 
8749
      if ( !input || !inputLabel ) return;
8750
 
8751
      var onInput = function() {
8752
        if ( input.value ) {
8753
          inputLabel.classList.add('has-input');
8754
        } else {
8755
          inputLabel.classList.remove('has-input');
8756
        }
8757
      };
8758
 
8759
      input.addEventListener('input', onInput);
8760
 
8761
      var ngModelCtrl = angular.element(input).controller('ngModel');
8762
      if ( ngModelCtrl ) {
8763
        ngModelCtrl.$render = function() {
8764
          input.value = ngModelCtrl.$viewValue || '';
8765
          onInput();
8766
        };
8767
      }
8768
 
8769
      scope.$on('$destroy', function() {
8770
        input.removeEventListener('input', onInput);
8771
      });
8772
    }
8773
  };
8774
});
8775
 
8776
var ITEM_TPL_OPTION_BUTTONS =
8777
  '<div class="item-options invisible">' +
8778
  '</div>';
8779
/**
8780
* @ngdoc directive
8781
* @name ionOptionButton
8782
* @parent ionic.directive:ionItem
8783
* @module ionic
8784
* @restrict E
8785
* Creates an option button inside a list item, that is visible when the item is swiped
8786
* to the left by the user.  Swiped open option buttons can be hidden with
8787
* {@link ionic.service:$ionicListDelegate#closeOptionButtons $ionicListDelegate#closeOptionButtons}.
8788
*
8789
* Can be assigned any button class.
8790
*
8791
* See {@link ionic.directive:ionList} for a complete example & explanation.
8792
*
8793
* @usage
8794
*
8795
* ```html
8796
* <ion-list>
8797
*   <ion-item>
8798
*     I love kittens!
8799
*     <ion-option-button class="button-positive">Share</ion-option-button>
8800
*     <ion-option-button class="button-assertive">Edit</ion-option-button>
8801
*   </ion-item>
8802
* </ion-list>
8803
* ```
8804
*/
8805
IonicModule
8806
.directive('ionOptionButton', ['$compile', function($compile) {
8807
  function stopPropagation(e) {
8808
    e.stopPropagation();
8809
  }
8810
  return {
8811
    restrict: 'E',
8812
    require: '^ionItem',
8813
    priority: Number.MAX_VALUE,
8814
    compile: function($element, $attr) {
8815
      $attr.$set('class', ($attr['class'] || '') + ' button', true);
8816
      return function($scope, $element, $attr, itemCtrl) {
8817
        if (!itemCtrl.optionsContainer) {
8818
          itemCtrl.optionsContainer = jqLite(ITEM_TPL_OPTION_BUTTONS);
8819
          itemCtrl.$element.append(itemCtrl.optionsContainer);
8820
        }
8821
        itemCtrl.optionsContainer.append($element);
8822
 
8823
        itemCtrl.$element.addClass('item-right-editable');
8824
 
8825
        //Don't bubble click up to main .item
8826
        $element.on('click', stopPropagation);
8827
      };
8828
    }
8829
  };
8830
}]);
8831
 
8832
var ITEM_TPL_REORDER_BUTTON =
8833
  '<div data-prevent-scroll="true" class="item-right-edit item-reorder enable-pointer-events">' +
8834
  '</div>';
8835
 
8836
/**
8837
* @ngdoc directive
8838
* @name ionReorderButton
8839
* @parent ionic.directive:ionItem
8840
* @module ionic
8841
* @restrict E
8842
* Creates a reorder button inside a list item, that is visible when the
8843
* {@link ionic.directive:ionList ionList parent's} `show-reorder` evaluates to true or
8844
* `$ionicListDelegate.showReorder(true)` is called.
8845
*
8846
* Can be dragged to reorder items in the list. Takes any ionicon class.
8847
*
8848
* 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.
8849
*
8850
* 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.
8851
*
8852
* Look at {@link ionic.directive:ionList} for more examples.
8853
*
8854
* @usage
8855
*
8856
* ```html
8857
* <ion-list ng-controller="MyCtrl" show-reorder="true">
8858
*   <ion-item ng-repeat="item in items">
8859
*     Item {{item}}
8860
*     <ion-reorder-button class="ion-navicon"
8861
*                         on-reorder="moveItem(item, $fromIndex, $toIndex)">
8862
*     </ion-reorder-button>
8863
*   </ion-item>
8864
* </ion-list>
8865
* ```
8866
* ```js
8867
* function MyCtrl($scope) {
8868
*   $scope.items = [1, 2, 3, 4];
8869
*   $scope.moveItem = function(item, fromIndex, toIndex) {
8870
*     //Move the item in the array
8871
*     $scope.items.splice(fromIndex, 1);
8872
*     $scope.items.splice(toIndex, 0, item);
8873
*   };
8874
* }
8875
* ```
8876
*
8877
* @param {expression=} on-reorder Expression to call when an item is reordered.
8878
* Parameters given: $fromIndex, $toIndex.
8879
*/
8880
IonicModule
8881
.directive('ionReorderButton', ['$parse', function($parse) {
8882
  return {
8883
    restrict: 'E',
8884
    require: ['^ionItem', '^?ionList'],
8885
    priority: Number.MAX_VALUE,
8886
    compile: function($element, $attr) {
8887
      $attr.$set('class', ($attr['class'] || '') + ' button icon button-icon', true);
8888
      $element[0].setAttribute('data-prevent-scroll', true);
8889
      return function($scope, $element, $attr, ctrls) {
8890
        var itemCtrl = ctrls[0];
8891
        var listCtrl = ctrls[1];
8892
        var onReorderFn = $parse($attr.onReorder);
8893
 
8894
        $scope.$onReorder = function(oldIndex, newIndex) {
8895
          onReorderFn($scope, {
8896
            $fromIndex: oldIndex,
8897
            $toIndex: newIndex
8898
          });
8899
        };
8900
 
8901
        // prevent clicks from bubbling up to the item
8902
        if(!$attr.ngClick && !$attr.onClick && !$attr.onclick){
8903
          $element[0].onclick = function(e){e.stopPropagation(); return false;};
8904
        }
8905
 
8906
        var container = jqLite(ITEM_TPL_REORDER_BUTTON);
8907
        container.append($element);
8908
        itemCtrl.$element.append(container).addClass('item-right-editable');
8909
 
8910
        if (listCtrl && listCtrl.showReorder()) {
8911
          container.addClass('visible active');
8912
        }
8913
      };
8914
    }
8915
  };
8916
}]);
8917
 
8918
/**
8919
 * @ngdoc directive
8920
 * @name keyboardAttach
8921
 * @module ionic
8922
 * @restrict A
8923
 *
8924
 * @description
8925
 * keyboard-attach is an attribute directive which will cause an element to float above
8926
 * the keyboard when the keyboard shows. Currently only supports the
8927
 * [ion-footer-bar]({{ page.versionHref }}/api/directive/ionFooterBar/) directive.
8928
 *
8929
 * ### Notes
8930
 * - This directive requires the
8931
 * [Ionic Keyboard Plugin](https://github.com/driftyco/ionic-plugins-keyboard).
8932
 * - On Android not in fullscreen mode, i.e. you have
8933
 *   `<preference name="Fullscreen" value="false" />` or no preference in your `config.xml` file,
8934
 *   this directive is unnecessary since it is the default behavior.
8935
 * - On iOS, if there is an input in your footer, you will need to set
8936
 *   `cordova.plugins.Keyboard.disableScroll(true)`.
8937
 *
8938
 * @usage
8939
 *
8940
 * ```html
8941
 *  <ion-footer-bar align-title="left" keyboard-attach class="bar-assertive">
8942
 *    <h1 class="title">Title!</h1>
8943
 *  </ion-footer-bar>
8944
 * ```
8945
 */
8946
 
8947
IonicModule
8948
.directive('keyboardAttach', function() {
8949
  return function(scope, element, attrs) {
8950
    ionic.on('native.keyboardshow', onShow, window);
8951
    ionic.on('native.keyboardhide', onHide, window);
8952
 
8953
    //deprecated
8954
    ionic.on('native.showkeyboard', onShow, window);
8955
    ionic.on('native.hidekeyboard', onHide, window);
8956
 
8957
 
8958
    var scrollCtrl;
8959
 
8960
    function onShow(e) {
8961
      if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {
8962
        return;
8963
      }
8964
 
8965
      //for testing
8966
      var keyboardHeight = e.keyboardHeight || e.detail.keyboardHeight;
8967
      element.css('bottom', keyboardHeight + "px");
8968
      scrollCtrl = element.controller('$ionicScroll');
8969
      if ( scrollCtrl ) {
8970
        scrollCtrl.scrollView.__container.style.bottom = keyboardHeight + keyboardAttachGetClientHeight(element[0]) + "px";
8971
      }
8972
    }
8973
 
8974
    function onHide() {
8975
      if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {
8976
        return;
8977
      }
8978
 
8979
      element.css('bottom', '');
8980
      if ( scrollCtrl ) {
8981
        scrollCtrl.scrollView.__container.style.bottom = '';
8982
      }
8983
    }
8984
 
8985
    scope.$on('$destroy', function() {
8986
      ionic.off('native.keyboardshow', onShow, window);
8987
      ionic.off('native.keyboardhide', onHide, window);
8988
 
8989
      //deprecated
8990
      ionic.off('native.showkeyboard', onShow, window);
8991
      ionic.off('native.hidekeyboard', onHide, window);
8992
    });
8993
  };
8994
});
8995
 
8996
function keyboardAttachGetClientHeight(element) {
8997
  return element.clientHeight;
8998
}
8999
 
9000
/**
9001
* @ngdoc directive
9002
* @name ionList
9003
* @module ionic
9004
* @delegate ionic.service:$ionicListDelegate
9005
* @codepen JsHjf
9006
* @restrict E
9007
* @description
9008
* The List is a widely used interface element in almost any mobile app, and can include
9009
* content ranging from basic text all the way to buttons, toggles, icons, and thumbnails.
9010
*
9011
* Both the list, which contains items, and the list items themselves can be any HTML
9012
* element. The containing element requires the `list` class and each list item requires
9013
* the `item` class.
9014
*
9015
* However, using the ionList and ionItem directives make it easy to support various
9016
* interaction modes such as swipe to edit, drag to reorder, and removing items.
9017
*
9018
* Related: {@link ionic.directive:ionItem}, {@link ionic.directive:ionOptionButton}
9019
* {@link ionic.directive:ionReorderButton}, {@link ionic.directive:ionDeleteButton}, [`list CSS documentation`](/docs/components/#list).
9020
*
9021
* @usage
9022
*
9023
* Basic Usage:
9024
*
9025
* ```html
9026
* <ion-list>
9027
*   <ion-item ng-repeat="item in items">
9028
*     {% raw %}Hello, {{item}}!{% endraw %}
9029
*   </ion-item>
9030
* </ion-list>
9031
* ```
9032
*
9033
* Advanced Usage: Thumbnails, Delete buttons, Reordering, Swiping
9034
*
9035
* ```html
9036
* <ion-list ng-controller="MyCtrl"
9037
*           show-delete="shouldShowDelete"
9038
*           show-reorder="shouldShowReorder"
9039
*           can-swipe="listCanSwipe">
9040
*   <ion-item ng-repeat="item in items"
9041
*             class="item-thumbnail-left">
9042
*
9043
*     {% raw %}<img ng-src="{{item.img}}">
9044
*     <h2>{{item.title}}</h2>
9045
*     <p>{{item.description}}</p>{% endraw %}
9046
*     <ion-option-button class="button-positive"
9047
*                        ng-click="share(item)">
9048
*       Share
9049
*     </ion-option-button>
9050
*     <ion-option-button class="button-info"
9051
*                        ng-click="edit(item)">
9052
*       Edit
9053
*     </ion-option-button>
9054
*     <ion-delete-button class="ion-minus-circled"
9055
*                        ng-click="items.splice($index, 1)">
9056
*     </ion-delete-button>
9057
*     <ion-reorder-button class="ion-navicon"
9058
*                         on-reorder="reorderItem(item, $fromIndex, $toIndex)">
9059
*     </ion-reorder-button>
9060
*
9061
*   </ion-item>
9062
* </ion-list>
9063
* ```
9064
*
9065
* @param {string=} delegate-handle The handle used to identify this list with
9066
* {@link ionic.service:$ionicListDelegate}.
9067
* @param type {string=} The type of list to use (list-inset or card)
9068
* @param show-delete {boolean=} Whether the delete buttons for the items in the list are
9069
* currently shown or hidden.
9070
* @param show-reorder {boolean=} Whether the reorder buttons for the items in the list are
9071
* currently shown or hidden.
9072
* @param can-swipe {boolean=} Whether the items in the list are allowed to be swiped to reveal
9073
* option buttons. Default: true.
9074
*/
9075
IonicModule
9076
.directive('ionList', [
9077
  '$timeout',
9078
function($timeout) {
9079
  return {
9080
    restrict: 'E',
9081
    require: ['ionList', '^?$ionicScroll'],
9082
    controller: '$ionicList',
9083
    compile: function($element, $attr) {
9084
      var listEl = jqLite('<div class="list">')
9085
      .append( $element.contents() )
9086
      .addClass($attr.type);
9087
      $element.append(listEl);
9088
 
9089
      return function($scope, $element, $attrs, ctrls) {
9090
        var listCtrl = ctrls[0];
9091
        var scrollCtrl = ctrls[1];
9092
 
9093
        //Wait for child elements to render...
9094
        $timeout(init);
9095
 
9096
        function init() {
9097
          var listView = listCtrl.listView = new ionic.views.ListView({
9098
            el: $element[0],
9099
            listEl: $element.children()[0],
9100
            scrollEl: scrollCtrl && scrollCtrl.element,
9101
            scrollView: scrollCtrl && scrollCtrl.scrollView,
9102
            onReorder: function(el, oldIndex, newIndex) {
9103
              var itemScope = jqLite(el).scope();
9104
              if (itemScope && itemScope.$onReorder) {
9105
                //Make sure onReorder is called in apply cycle,
9106
                //but also make sure it has no conflicts by doing
9107
                //$evalAsync
9108
                $timeout(function() {
9109
                  itemScope.$onReorder(oldIndex, newIndex);
9110
                });
9111
              }
9112
            },
9113
            canSwipe: function() {
9114
              return listCtrl.canSwipeItems();
9115
            }
9116
          });
9117
 
9118
          $scope.$on('$destroy', function() {
9119
            if(listView) {
9120
              listView.deregister && listView.deregister();
9121
              listView = null;
9122
            }
9123
          });
9124
 
9125
          if (isDefined($attr.canSwipe)) {
9126
            $scope.$watch('!!(' + $attr.canSwipe + ')', function(value) {
9127
              listCtrl.canSwipeItems(value);
9128
            });
9129
          }
9130
          if (isDefined($attr.showDelete)) {
9131
            $scope.$watch('!!(' + $attr.showDelete + ')', function(value) {
9132
              listCtrl.showDelete(value);
9133
            });
9134
          }
9135
          if (isDefined($attr.showReorder)) {
9136
            $scope.$watch('!!(' + $attr.showReorder + ')', function(value) {
9137
              listCtrl.showReorder(value);
9138
            });
9139
          }
9140
 
9141
          $scope.$watch(function() {
9142
            return listCtrl.showDelete();
9143
          }, function(isShown, wasShown) {
9144
            //Only use isShown=false if it was already shown
9145
            if (!isShown && !wasShown) { return; }
9146
 
9147
            if (isShown) listCtrl.closeOptionButtons();
9148
            listCtrl.canSwipeItems(!isShown);
9149
 
9150
            $element.children().toggleClass('list-left-editing', isShown);
9151
            $element.toggleClass('disable-pointer-events', isShown);
9152
 
9153
            var deleteButton = jqLite($element[0].getElementsByClassName('item-delete'));
9154
            setButtonShown(deleteButton, listCtrl.showDelete);
9155
          });
9156
 
9157
          $scope.$watch(function() {
9158
            return listCtrl.showReorder();
9159
          }, function(isShown, wasShown) {
9160
            //Only use isShown=false if it was already shown
9161
            if (!isShown && !wasShown) { return; }
9162
 
9163
            if (isShown) listCtrl.closeOptionButtons();
9164
            listCtrl.canSwipeItems(!isShown);
9165
 
9166
            $element.children().toggleClass('list-right-editing', isShown);
9167
            $element.toggleClass('disable-pointer-events', isShown);
9168
 
9169
            var reorderButton = jqLite($element[0].getElementsByClassName('item-reorder'));
9170
            setButtonShown(reorderButton, listCtrl.showReorder);
9171
          });
9172
 
9173
          function setButtonShown(el, shown) {
9174
            shown() && el.addClass('visible') || el.removeClass('active');
9175
            ionic.requestAnimationFrame(function() {
9176
              shown() && el.addClass('active') || el.removeClass('visible');
9177
            });
9178
          }
9179
        }
9180
 
9181
      };
9182
    }
9183
  };
9184
}]);
9185
 
9186
/**
9187
 * @ngdoc directive
9188
 * @name menuClose
9189
 * @module ionic
9190
 * @restrict AC
9191
 *
9192
 * @description
9193
 * `menu-close` is an attribute directive that closes a currently opened side menu.
9194
 * Note that by default, navigation transitions will not animate between views when
9195
 * the menu is open. Additionally, this directive will reset the entering view's
9196
 * history stack, making the new page the root of the history stack. This is done
9197
 * to replicate the user experience seen in most side menu implementations, which is
9198
 * to not show the back button at the root of the stack and show only the
9199
 * menu button. We recommend that you also use the `enable-menu-with-back-views="false"`
9200
 * {@link ionic.directive:ionSideMenus} attribute when using the menuClose directive.
9201
 *
9202
 * @usage
9203
 * Below is an example of a link within a side menu. Tapping this link would
9204
 * automatically close the currently opened menu.
9205
 *
9206
 * ```html
9207
 * <a menu-close href="#/home" class="item">Home</a>
9208
 * ```
9209
 */
9210
IonicModule
9211
.directive('menuClose', ['$ionicHistory', function($ionicHistory) {
9212
  return {
9213
    restrict: 'AC',
9214
    link: function($scope, $element) {
9215
      $element.bind('click', function() {
9216
        var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
9217
        if (sideMenuCtrl) {
9218
          $ionicHistory.nextViewOptions({
9219
            historyRoot: true,
9220
            disableAnimate: true,
9221
            expire: 300
9222
          });
9223
          sideMenuCtrl.close();
9224
        }
9225
      });
9226
    }
9227
  };
9228
}]);
9229
 
9230
/**
9231
 * @ngdoc directive
9232
 * @name menuToggle
9233
 * @module ionic
9234
 * @restrict AC
9235
 *
9236
 * @description
9237
 * Toggle a side menu on the given side.
9238
 *
9239
 * @usage
9240
 * Below is an example of a link within a nav bar. Tapping this button
9241
 * would open the given side menu, and tapping it again would close it.
9242
 *
9243
 * ```html
9244
 * <ion-nav-bar>
9245
 *   <ion-nav-buttons side="left">
9246
 *    <button menu-toggle="left" class="button button-icon icon ion-navicon"></button>
9247
 *   </ion-nav-buttons>
9248
 * </ion-nav-bar>
9249
 * ```
9250
 */
9251
IonicModule
9252
.directive('menuToggle', function() {
9253
  return {
9254
    restrict: 'AC',
9255
    link: function($scope, $element, $attr) {
9256
      $scope.$on('$ionicView.beforeEnter', function(ev, viewData) {
9257
        if (viewData.enableBack) {
9258
          var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
9259
          if (!sideMenuCtrl.enableMenuWithBackViews()) {
9260
            $element.addClass('hide');
9261
          }
9262
        } else {
9263
          $element.removeClass('hide');
9264
        }
9265
      });
9266
 
9267
      $element.bind('click', function() {
9268
        var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
9269
        sideMenuCtrl && sideMenuCtrl.toggle($attr.menuToggle);
9270
      });
9271
    }
9272
  };
9273
});
9274
 
9275
/*
9276
 * We don't document the ionModal directive, we instead document
9277
 * the $ionicModal service
9278
 */
9279
IonicModule
9280
.directive('ionModal', [function() {
9281
  return {
9282
    restrict: 'E',
9283
    transclude: true,
9284
    replace: true,
9285
    controller: [function(){}],
9286
    template: '<div class="modal-backdrop">' +
9287
                '<div class="modal-wrapper" ng-transclude></div>' +
9288
                '</div>'
9289
  };
9290
}]);
9291
 
9292
IonicModule
9293
.directive('ionModalView', function() {
9294
  return {
9295
    restrict: 'E',
9296
    compile: function(element, attr) {
9297
      element.addClass('modal');
9298
    }
9299
  };
9300
});
9301
 
9302
/**
9303
 * @ngdoc directive
9304
 * @name ionNavBackButton
9305
 * @module ionic
9306
 * @restrict E
9307
 * @parent ionNavBar
9308
 * @description
9309
 * Creates a back button inside an {@link ionic.directive:ionNavBar}.
9310
 *
9311
 * The back button will appear when the user is able to go back in the current navigation stack. By
9312
 * default, the markup of the back button is automatically built using platform-appropriate defaults
9313
 * (iOS back button icon on iOS and Android icon on Android).
9314
 *
9315
 * Additionally, the button is automatically set to `$ionicGoBack()` on click/tap. By default, the
9316
 * app will navigate back one view when the back button is clicked.  More advanced behavior is also
9317
 * possible, as outlined below.
9318
 *
9319
 * @usage
9320
 *
9321
 * Recommended markup for default settings:
9322
 *
9323
 * ```html
9324
 * <ion-nav-bar>
9325
 *   <ion-nav-back-button>
9326
 *   </ion-nav-back-button>
9327
 * </ion-nav-bar>
9328
 * ```
9329
 *
9330
 * With custom inner markup, and automatically adds a default click action:
9331
 *
9332
 * ```html
9333
 * <ion-nav-bar>
9334
 *   <ion-nav-back-button class="button-clear">
9335
 *     <i class="ion-arrow-left-c"></i> Back
9336
 *   </ion-nav-back-button>
9337
 * </ion-nav-bar>
9338
 * ```
9339
 *
9340
 * With custom inner markup and custom click action, using {@link ionic.service:$ionicNavBarDelegate}:
9341
 *
9342
 * ```html
9343
 * <ion-nav-bar ng-controller="MyCtrl">
9344
 *   <ion-nav-back-button class="button-clear"
9345
 *     ng-click="myGoBack()">
9346
 *     <i class="ion-arrow-left-c"></i> Back
9347
 *   </ion-nav-back-button>
9348
 * </ion-nav-bar>
9349
 * ```
9350
 * ```js
9351
 * function MyCtrl($scope, $ionicNavBarDelegate) {
9352
 *   $scope.myGoBack = function() {
9353
 *     $ionicNavBarDelegate.back();
9354
 *   };
9355
 * }
9356
 * ```
9357
 */
9358
IonicModule
9359
.directive('ionNavBackButton', ['$ionicConfig', '$document', function($ionicConfig, $document) {
9360
  return {
9361
    restrict: 'E',
9362
    require: '^ionNavBar',
9363
    compile: function(tElement, tAttrs) {
9364
 
9365
      // clone the back button, but as a <div>
9366
      var buttonEle = $document[0].createElement('button');
9367
      for (var n in tAttrs.$attr) {
9368
        buttonEle.setAttribute(tAttrs.$attr[n], tAttrs[n]);
9369
      }
9370
 
9371
      if (!tAttrs.ngClick) {
9372
        buttonEle.setAttribute('ng-click', '$ionicGoBack($event)');
9373
      }
9374
 
9375
      buttonEle.className = 'button back-button hide buttons ' + (tElement.attr('class') || '');
9376
      buttonEle.innerHTML = tElement.html() || '';
9377
 
9378
      var childNode;
9379
      var hasIcon = hasIconClass(tElement[0]);
9380
      var hasInnerText;
9381
      var hasButtonText;
9382
      var hasPreviousTitle;
9383
 
9384
      for (var x = 0; x < tElement[0].childNodes.length; x++) {
9385
        childNode = tElement[0].childNodes[x];
9386
        if (childNode.nodeType === 1) {
9387
          if (hasIconClass(childNode)) {
9388
            hasIcon = true;
9389
          } else if (childNode.classList.contains('default-title')) {
9390
            hasButtonText = true;
9391
          } else if (childNode.classList.contains('previous-title')) {
9392
            hasPreviousTitle = true;
9393
          }
9394
        } else if (!hasInnerText && childNode.nodeType === 3) {
9395
          hasInnerText = !!childNode.nodeValue.trim();
9396
        }
9397
      }
9398
 
9399
      function hasIconClass(ele) {
9400
        return /ion-|icon/.test(ele.className);
9401
      }
9402
 
9403
      var defaultIcon = $ionicConfig.backButton.icon();
9404
      if (!hasIcon && defaultIcon && defaultIcon !== 'none') {
9405
        buttonEle.innerHTML = '<i class="icon ' + defaultIcon + '"></i> ' + buttonEle.innerHTML;
9406
        buttonEle.className += ' button-clear';
9407
      }
9408
 
9409
      if (!hasInnerText) {
9410
        var buttonTextEle = $document[0].createElement('span');
9411
        buttonTextEle.className = 'back-text';
9412
 
9413
        if (!hasButtonText && $ionicConfig.backButton.text()) {
9414
          buttonTextEle.innerHTML += '<span class="default-title">' + $ionicConfig.backButton.text() + '</span>';
9415
        }
9416
        if (!hasPreviousTitle && $ionicConfig.backButton.previousTitleText()) {
9417
          buttonTextEle.innerHTML += '<span class="previous-title"></span>';
9418
        }
9419
        buttonEle.appendChild(buttonTextEle);
9420
 
9421
      }
9422
 
9423
      tElement.attr('class', 'hide');
9424
      tElement.empty();
9425
 
9426
      return {
9427
        pre: function($scope, $element, $attr, navBarCtrl) {
9428
          // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
9429
          navBarCtrl.navElement('backButton', buttonEle.outerHTML);
9430
          buttonEle = null;
9431
        }
9432
      };
9433
    }
9434
  };
9435
}]);
9436
 
9437
 
9438
/**
9439
 * @ngdoc directive
9440
 * @name ionNavBar
9441
 * @module ionic
9442
 * @delegate ionic.service:$ionicNavBarDelegate
9443
 * @restrict E
9444
 *
9445
 * @description
9446
 * If we have an {@link ionic.directive:ionNavView} directive, we can also create an
9447
 * `<ion-nav-bar>`, which will create a topbar that updates as the application state changes.
9448
 *
9449
 * We can add a back button by putting an {@link ionic.directive:ionNavBackButton} inside.
9450
 *
9451
 * We can add buttons depending on the currently visible view using
9452
 * {@link ionic.directive:ionNavButtons}.
9453
 *
9454
 * Note that the ion-nav-bar element will only work correctly if your content has an
9455
 * ionView around it.
9456
 *
9457
 * @usage
9458
 *
9459
 * ```html
9460
 * <body ng-app="starter">
9461
 *   <!-- The nav bar that will be updated as we navigate -->
9462
 *   <ion-nav-bar class="bar-positive">
9463
 *   </ion-nav-bar>
9464
 *
9465
 *   <!-- where the initial view template will be rendered -->
9466
 *   <ion-nav-view>
9467
 *     <ion-view>
9468
 *       <ion-content>Hello!</ion-content>
9469
 *     </ion-view>
9470
 *   </ion-nav-view>
9471
 * </body>
9472
 * ```
9473
 *
9474
 * @param {string=} delegate-handle The handle used to identify this navBar
9475
 * with {@link ionic.service:$ionicNavBarDelegate}.
9476
 * @param align-title {string=} Where to align the title of the navbar.
9477
 * Available: 'left', 'right', 'center'. Defaults to 'center'.
9478
 * @param {boolean=} no-tap-scroll By default, the navbar will scroll the content
9479
 * to the top when tapped.  Set no-tap-scroll to true to disable this behavior.
9480
 *
9481
 * </table><br/>
9482
 *
9483
 * ### Alternative Usage
9484
 *
9485
 * Alternatively, you may put ion-nav-bar inside of each individual view's ion-view element.
9486
 * This will allow you to have the whole navbar, not just its contents, transition every view change.
9487
 *
9488
 * This is similar to using a header bar inside your ion-view, except it will have all the power of a navbar.
9489
 *
9490
 * If you do this, simply put nav buttons inside the navbar itself; do not use `<ion-nav-buttons>`.
9491
 *
9492
 *
9493
 * ```html
9494
 * <ion-view view-title="myTitle">
9495
 *   <ion-nav-bar class="bar-positive">
9496
 *     <ion-nav-back-button>
9497
 *     </ion-nav-back-button>
9498
 *     <div class="buttons primary-buttons">
9499
 *       <button class="button">
9500
            Button
9501
 *       </button>
9502
 *     </div>
9503
 *   </ion-nav-bar>
9504
 * </ion-view>
9505
 * ```
9506
 */
9507
IonicModule
9508
.directive('ionNavBar', function() {
9509
  return {
9510
    restrict: 'E',
9511
    controller: '$ionicNavBar',
9512
    scope: true,
9513
    link: function($scope, $element, $attr, ctrl) {
9514
      ctrl.init();
9515
    }
9516
  };
9517
});
9518
 
9519
 
9520
/**
9521
 * @ngdoc directive
9522
 * @name ionNavButtons
9523
 * @module ionic
9524
 * @restrict E
9525
 * @parent ionNavView
9526
 *
9527
 * @description
9528
 * Use nav buttons to set the buttons on your {@link ionic.directive:ionNavBar}
9529
 * from within an {@link ionic.directive:ionView}. This gives each
9530
 * view template the ability to specify which buttons should show in the nav bar,
9531
 * overriding any default buttons already placed in the nav bar.
9532
 *
9533
 * Any buttons you declare will be positioned on the navbar's corresponding side. Primary
9534
 * buttons generally map to the left side of the header, and secondary buttons are
9535
 * generally on the right side. However, their exact locations are platform-specific.
9536
 * For example, in iOS, the primary buttons are on the far left of the header, and
9537
 * secondary buttons are on the far right, with the header title centered between them.
9538
 * For Android, however, both groups of buttons are on the far right of the header,
9539
 * with the header title aligned left.
9540
 *
9541
 * We recommend always using `primary` and `secondary`, so the buttons correctly map
9542
 * to the side familiar to users of each platform. However, in cases where buttons should
9543
 * always be on an exact side, both `left` and `right` sides are still available. For
9544
 * example, a toggle button for a left side menu should be on the left side; in this case,
9545
 * we'd recommend using `side="left"`, so it's always on the left, no matter the platform.
9546
 *
9547
 * Note that `ion-nav-buttons` must be immediate descendants of the `ion-view` or
9548
 * `ion-nav-bar` element (basically, don't wrap it in another div).
9549
 *
9550
 * @usage
9551
 * ```html
9552
 * <ion-nav-bar>
9553
 * </ion-nav-bar>
9554
 * <ion-nav-view>
9555
 *   <ion-view>
9556
 *     <ion-nav-buttons side="primary">
9557
 *       <button class="button" ng-click="doSomething()">
9558
 *         I'm a button on the primary of the navbar!
9559
 *       </button>
9560
 *     </ion-nav-buttons>
9561
 *     <ion-content>
9562
 *       Some super content here!
9563
 *     </ion-content>
9564
 *   </ion-view>
9565
 * </ion-nav-view>
9566
 * ```
9567
 *
9568
 * @param {string} side The side to place the buttons in the
9569
 * {@link ionic.directive:ionNavBar}. Available sides: `primary`, `secondary`, `left`, and `right`.
9570
 */
9571
IonicModule
9572
.directive('ionNavButtons', ['$document', function($document) {
9573
  return {
9574
    require: '^ionNavBar',
9575
    restrict: 'E',
9576
    compile: function(tElement, tAttrs) {
9577
      var side = 'left';
9578
 
9579
      if (/^primary|secondary|right$/i.test(tAttrs.side || '')) {
9580
        side = tAttrs.side.toLowerCase();
9581
      }
9582
 
9583
      var spanEle = $document[0].createElement('span');
9584
      spanEle.className = side + '-buttons';
9585
      spanEle.innerHTML = tElement.html();
9586
 
9587
      var navElementType = side + 'Buttons';
9588
 
9589
      tElement.attr('class', 'hide');
9590
      tElement.empty();
9591
 
9592
      return {
9593
        pre: function($scope, $element, $attrs, navBarCtrl) {
9594
          // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
9595
 
9596
          var parentViewCtrl = $element.parent().data('$ionViewController');
9597
          if (parentViewCtrl) {
9598
            // if the parent is an ion-view, then these are ion-nav-buttons for JUST this ion-view
9599
            parentViewCtrl.navElement(navElementType, spanEle.outerHTML);
9600
 
9601
          } else {
9602
            // these are buttons for all views that do not have their own ion-nav-buttons
9603
            navBarCtrl.navElement(navElementType, spanEle.outerHTML);
9604
          }
9605
 
9606
          spanEle = null;
9607
        }
9608
      };
9609
    }
9610
  };
9611
}]);
9612
 
9613
/**
9614
 * @ngdoc directive
9615
 * @name navDirection
9616
 * @module ionic
9617
 * @restrict A
9618
 *
9619
 * @description
9620
 * The direction which the nav view transition should animate. Available options
9621
 * are: `forward`, `back`, `enter`, `exit`, `swap`.
9622
 *
9623
 * @usage
9624
 *
9625
 * ```html
9626
 * <a nav-direction="forward" href="#/home">Home</a>
9627
 * ```
9628
 */
9629
IonicModule
9630
.directive('navDirection', ['$ionicViewSwitcher', function($ionicViewSwitcher) {
9631
  return {
9632
    restrict: 'A',
9633
    priority: 1000,
9634
    link: function($scope, $element, $attr) {
9635
      $element.bind('click', function() {
9636
        $ionicViewSwitcher.nextDirection($attr.navDirection);
9637
      });
9638
    }
9639
  };
9640
}]);
9641
 
9642
/**
9643
 * @ngdoc directive
9644
 * @name ionNavTitle
9645
 * @module ionic
9646
 * @restrict E
9647
 * @parent ionNavView
9648
 *
9649
 * @description
9650
 *
9651
 * The nav title directive replaces an {@link ionic.directive:ionNavBar} title text with
9652
 * custom HTML from within an {@link ionic.directive:ionView} template. This gives each
9653
 * view the ability to specify its own custom title element, such as an image or any HTML,
9654
 * rather than being text-only. Alternatively, text-only titles can be updated using the
9655
 * `view-title` {@link ionic.directive:ionView} attribute.
9656
 *
9657
 * Note that `ion-nav-title` must be an immediate descendant of the `ion-view` or
9658
 * `ion-nav-bar` element (basically don't wrap it in another div).
9659
 *
9660
 * @usage
9661
 * ```html
9662
 * <ion-nav-bar>
9663
 * </ion-nav-bar>
9664
 * <ion-nav-view>
9665
 *   <ion-view>
9666
 *     <ion-nav-title>
9667
 *       <img src="logo.svg">
9668
 *     </ion-nav-title>
9669
 *     <ion-content>
9670
 *       Some super content here!
9671
 *     </ion-content>
9672
 *   </ion-view>
9673
 * </ion-nav-view>
9674
 * ```
9675
 *
9676
 */
9677
IonicModule
9678
.directive('ionNavTitle', ['$document', function($document) {
9679
  return {
9680
    require: '^ionNavBar',
9681
    restrict: 'E',
9682
    compile: function(tElement, tAttrs) {
9683
      var navElementType = 'title';
9684
      var spanEle = $document[0].createElement('span');
9685
      for (var n in tAttrs.$attr) {
9686
        spanEle.setAttribute(tAttrs.$attr[n], tAttrs[n]);
9687
      }
9688
      spanEle.classList.add('nav-bar-title');
9689
      spanEle.innerHTML = tElement.html();
9690
 
9691
      tElement.attr('class', 'hide');
9692
      tElement.empty();
9693
 
9694
      return {
9695
        pre: function($scope, $element, $attrs, navBarCtrl) {
9696
          // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
9697
 
9698
          var parentViewCtrl = $element.parent().data('$ionViewController');
9699
          if (parentViewCtrl) {
9700
            // if the parent is an ion-view, then these are ion-nav-buttons for JUST this ion-view
9701
            parentViewCtrl.navElement(navElementType, spanEle.outerHTML);
9702
 
9703
          } else {
9704
            // these are buttons for all views that do not have their own ion-nav-buttons
9705
            navBarCtrl.navElement(navElementType, spanEle.outerHTML);
9706
          }
9707
 
9708
          spanEle = null;
9709
        }
9710
      };
9711
    }
9712
  };
9713
}]);
9714
 
9715
/**
9716
 * @ngdoc directive
9717
 * @name navTransition
9718
 * @module ionic
9719
 * @restrict A
9720
 *
9721
 * @description
9722
 * The transition type which the nav view transition should use when it animates.
9723
 * Current, options are `ios`, `android`, and `none`. More options coming soon.
9724
 *
9725
 * @usage
9726
 *
9727
 * ```html
9728
 * <a nav-transition="none" href="#/home">Home</a>
9729
 * ```
9730
 */
9731
IonicModule
9732
.directive('navTransition', ['$ionicViewSwitcher', function($ionicViewSwitcher) {
9733
  return {
9734
    restrict: 'A',
9735
    priority: 1000,
9736
    link: function($scope, $element, $attr) {
9737
      $element.bind('click', function() {
9738
        $ionicViewSwitcher.nextTransition($attr.navTransition);
9739
      });
9740
    }
9741
  };
9742
}]);
9743
 
9744
/**
9745
 * @ngdoc directive
9746
 * @name ionNavView
9747
 * @module ionic
9748
 * @restrict E
9749
 * @codepen odqCz
9750
 *
9751
 * @description
9752
 * As a user navigates throughout your app, Ionic is able to keep track of their
9753
 * navigation history. By knowing their history, transitions between views
9754
 * correctly enter and exit using the platform's transition style. An additional
9755
 * benefit to Ionic's navigation system is its ability to manage multiple
9756
 * histories. For example, each tab can have it's own navigation history stack.
9757
 *
9758
 * Ionic uses the AngularUI Router module so app interfaces can be organized
9759
 * into various "states". Like Angular's core $route service, URLs can be used
9760
 * to control the views. However, the AngularUI Router provides a more powerful
9761
 * state manager in that states are bound to named, nested, and parallel views,
9762
 * allowing more than one template to be rendered on the same page.
9763
 * Additionally, each state is not required to be bound to a URL, and data can
9764
 * be pushed to each state which allows much flexibility.
9765
 *
9766
 * The ionNavView directive is used to render templates in your application. Each template
9767
 * is part of a state. States are usually mapped to a url, and are defined programatically
9768
 * using angular-ui-router (see [their docs](https://github.com/angular-ui/ui-router/wiki),
9769
 * and remember to replace ui-view with ion-nav-view in examples).
9770
 *
9771
 * @usage
9772
 * In this example, we will create a navigation view that contains our different states for the app.
9773
 *
9774
 * To do this, in our markup we use ionNavView top level directive. To display a header bar we use
9775
 * the {@link ionic.directive:ionNavBar} directive that updates as we navigate through the
9776
 * navigation stack.
9777
 *
9778
 * Next, we need to setup our states that will be rendered.
9779
 *
9780
 * ```js
9781
 * var app = angular.module('myApp', ['ionic']);
9782
 * app.config(function($stateProvider) {
9783
 *   $stateProvider
9784
 *   .state('index', {
9785
 *     url: '/',
9786
 *     templateUrl: 'home.html'
9787
 *   })
9788
 *   .state('music', {
9789
 *     url: '/music',
9790
 *     templateUrl: 'music.html'
9791
 *   });
9792
 * });
9793
 * ```
9794
 * Then on app start, $stateProvider will look at the url, see it matches the index state,
9795
 * and then try to load home.html into the `<ion-nav-view>`.
9796
 *
9797
 * Pages are loaded by the URLs given. One simple way to create templates in Angular is to put
9798
 * them directly into your HTML file and use the `<script type="text/ng-template">` syntax.
9799
 * So here is one way to put home.html into our app:
9800
 *
9801
 * ```html
9802
 * <script id="home" type="text/ng-template">
9803
 *   <!-- The title of the ion-view will be shown on the navbar -->
9804
 *   <ion-view view-title="Home">
9805
 *     <ion-content ng-controller="HomeCtrl">
9806
 *       <!-- The content of the page -->
9807
 *       <a href="#/music">Go to music page!</a>
9808
 *     </ion-content>
9809
 *   </ion-view>
9810
 * </script>
9811
 * ```
9812
 *
9813
 * This is good to do because the template will be cached for very fast loading, instead of
9814
 * having to fetch them from the network.
9815
 *
9816
 ## Caching
9817
 *
9818
 * By default, views are cached to improve performance. When a view is navigated away from, its
9819
 * element is left in the DOM, and its scope is disconnected from the `$watch` cycle. When
9820
 * navigating to a view that is already cached, its scope is then reconnected, and the existing
9821
 * element that was left in the DOM becomes the active view. This also allows for the scroll
9822
 * position of previous views to be maintained.
9823
 *
9824
 * Caching can be disabled and enabled in multiple ways. By default, Ionic will cache a maximum of
9825
 * 10 views, and not only can this be configured, but apps can also explicitly state which views
9826
 * should and should not be cached.
9827
 *
9828
 * Note that because we are caching these views, *we aren’t destroying scopes*. Instead, scopes
9829
 * are being disconnected from the watch cycle. Because scopes are not being destroyed and
9830
 * recreated, controllers are not loading again on a subsequent viewing. If the app/controller
9831
 * needs to know when a view has entered or has left, then view events emitted from the
9832
 * {@link ionic.directive:ionView} scope, such as `$ionicView.enter`, may be useful
9833
 *
9834
 * #### Disable cache globally
9835
 *
9836
 * The {@link ionic.provider:$ionicConfigProvider} can be used to set the maximum allowable views
9837
 * which can be cached, but this can also be use to disable all caching by setting it to 0.
9838
 *
9839
 * ```js
9840
 * $ionicConfigProvider.views.maxCache(0);
9841
 * ```
9842
 *
9843
 * #### Disable cache within state provider
9844
 *
9845
 * ```js
9846
 * $stateProvider.state('myState', {
9847
 *    cache: false,
9848
 *    url : '/myUrl',
9849
 *    templateUrl : 'my-template.html'
9850
 * })
9851
 * ```
9852
 *
9853
 * #### Disable cache with an attribute
9854
 *
9855
 * ```html
9856
 * <ion-view cache-view="false" view-title="My Title!">
9857
 *   ...
9858
 * </ion-view>
9859
 * ```
9860
 *
9861
 *
9862
 * ## AngularUI Router
9863
 *
9864
 * Please visit [AngularUI Router's docs](https://github.com/angular-ui/ui-router/wiki) for
9865
 * more info. Below is a great video by the AngularUI Router team that may help to explain
9866
 * how it all works:
9867
 *
9868
 * <iframe width="560" height="315" src="//www.youtube.com/embed/dqJRoh8MnBo"
9869
 * frameborder="0" allowfullscreen></iframe>
9870
 *
9871
 * @param {string=} name A view name. The name should be unique amongst the other views in the
9872
 * same state. You can have views of the same name that live in different states. For more
9873
 * information, see ui-router's
9874
 * [ui-view documentation](http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.directive:ui-view).
9875
 */
9876
IonicModule
9877
.directive('ionNavView', [
9878
  '$state',
9879
  '$ionicConfig',
9880
function($state, $ionicConfig) {
9881
  // IONIC's fork of Angular UI Router, v0.2.10
9882
  // the navView handles registering views in the history and how to transition between them
9883
  return {
9884
    restrict: 'E',
9885
    terminal: true,
9886
    priority: 2000,
9887
    transclude: true,
9888
    controller: '$ionicNavView',
9889
    compile: function(tElement, tAttrs, transclude) {
9890
 
9891
      // a nav view element is a container for numerous views
9892
      tElement.addClass('view-container');
9893
      ionic.DomUtil.cachedAttr(tElement, 'nav-view-transition', $ionicConfig.views.transition());
9894
 
9895
      return function($scope, $element, $attr, navViewCtrl) {
9896
        var latestLocals;
9897
 
9898
        // Put in the compiled initial view
9899
        transclude($scope, function(clone) {
9900
          $element.append(clone);
9901
        });
9902
 
9903
        var viewData = navViewCtrl.init();
9904
 
9905
        // listen for $stateChangeSuccess
9906
        $scope.$on('$stateChangeSuccess', function() {
9907
          updateView(false);
9908
        });
9909
        $scope.$on('$viewContentLoading', function() {
9910
          updateView(false);
9911
        });
9912
 
9913
        // initial load, ready go
9914
        updateView(true);
9915
 
9916
 
9917
        function updateView(firstTime) {
9918
          // get the current local according to the $state
9919
          var viewLocals = $state.$current && $state.$current.locals[viewData.name];
9920
 
9921
          // do not update THIS nav-view if its is not the container for the given state
9922
          // if the viewLocals are the same as THIS latestLocals, then nothing to do
9923
          if (!viewLocals || (!firstTime && viewLocals === latestLocals)) return;
9924
 
9925
          // update the latestLocals
9926
          latestLocals = viewLocals;
9927
          viewData.state = viewLocals.$$state;
9928
 
9929
          // register, update and transition to the new view
9930
          navViewCtrl.register(viewLocals);
9931
        }
9932
 
9933
      };
9934
    }
9935
  };
9936
}]);
9937
 
9938
IonicModule
9939
 
9940
.config(['$provide', function($provide) {
9941
  $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
9942
    // drop the default ngClick directive
9943
    $delegate.shift();
9944
    return $delegate;
9945
  }]);
9946
}])
9947
 
9948
/**
9949
 * @private
9950
 */
9951
.factory('$ionicNgClick', ['$parse', function($parse) {
9952
  return function(scope, element, clickExpr) {
9953
    var clickHandler = angular.isFunction(clickExpr) ?
9954
      clickExpr :
9955
      $parse(clickExpr);
9956
 
9957
    element.on('click', function(event) {
9958
      scope.$apply(function() {
9959
        clickHandler(scope, {$event: (event)});
9960
      });
9961
    });
9962
 
9963
    // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
9964
    // something else nearby.
9965
    element.onclick = function(event) { };
9966
  };
9967
}])
9968
 
9969
.directive('ngClick', ['$ionicNgClick', function($ionicNgClick) {
9970
  return function(scope, element, attr) {
9971
    $ionicNgClick(scope, element, attr.ngClick);
9972
  };
9973
}])
9974
 
9975
.directive('ionStopEvent', function() {
9976
  return {
9977
    restrict: 'A',
9978
    link: function(scope, element, attr) {
9979
      element.bind(attr.ionStopEvent, eventStopPropagation);
9980
    }
9981
  };
9982
});
9983
function eventStopPropagation(e) {
9984
  e.stopPropagation();
9985
}
9986
 
9987
 
9988
/**
9989
 * @ngdoc directive
9990
 * @name ionPane
9991
 * @module ionic
9992
 * @restrict E
9993
 *
9994
 * @description A simple container that fits content, with no side effects.  Adds the 'pane' class to the element.
9995
 */
9996
IonicModule
9997
.directive('ionPane', function() {
9998
  return {
9999
    restrict: 'E',
10000
    link: function(scope, element, attr) {
10001
      element.addClass('pane');
10002
    }
10003
  };
10004
});
10005
 
10006
/*
10007
 * We don't document the ionPopover directive, we instead document
10008
 * the $ionicPopover service
10009
 */
10010
IonicModule
10011
.directive('ionPopover', [function() {
10012
  return {
10013
    restrict: 'E',
10014
    transclude: true,
10015
    replace: true,
10016
    controller: [function(){}],
10017
    template: '<div class="popover-backdrop">' +
10018
                '<div class="popover-wrapper" ng-transclude></div>' +
10019
              '</div>'
10020
  };
10021
}]);
10022
 
10023
IonicModule
10024
.directive('ionPopoverView', function() {
10025
  return {
10026
    restrict: 'E',
10027
    compile: function(element) {
10028
      element.append( angular.element('<div class="popover-arrow"></div>') );
10029
      element.addClass('popover');
10030
    }
10031
  };
10032
});
10033
 
10034
/**
10035
 * @ngdoc directive
10036
 * @name ionRadio
10037
 * @module ionic
10038
 * @restrict E
10039
 * @codepen saoBG
10040
 * @description
10041
 * The radio directive is no different than the HTML radio input, except it's styled differently.
10042
 *
10043
 * Radio behaves like any [AngularJS radio](http://docs.angularjs.org/api/ng/input/input[radio]).
10044
 *
10045
 * @usage
10046
 * ```html
10047
 * <ion-radio ng-model="choice" ng-value="'A'">Choose A</ion-radio>
10048
 * <ion-radio ng-model="choice" ng-value="'B'">Choose B</ion-radio>
10049
 * <ion-radio ng-model="choice" ng-value="'C'">Choose C</ion-radio>
10050
 * ```
10051
 * 
10052
 * @param {string=} name The name of the radio input.
10053
 * @param {expression=} value The value of the radio input.
10054
 * @param {boolean=} disabled The state of the radio input.
10055
 * @param {string=} icon The icon to use when the radio input is selected.
10056
 * @param {expression=} ng-value Angular equivalent of the value attribute.
10057
 * @param {expression=} ng-model The angular model for the radio input.
10058
 * @param {boolean=} ng-disabled Angular equivalent of the disabled attribute.
10059
 * @param {expression=} ng-change Triggers given expression when radio input's model changes
10060
 */
10061
IonicModule
10062
.directive('ionRadio', function() {
10063
  return {
10064
    restrict: 'E',
10065
    replace: true,
10066
    require: '?ngModel',
10067
    transclude: true,
10068
    template:
10069
      '<label class="item item-radio">' +
10070
        '<input type="radio" name="radio-group">' +
10071
        '<div class="item-content disable-pointer-events" ng-transclude></div>' +
10072
        '<i class="radio-icon disable-pointer-events icon ion-checkmark"></i>' +
10073
      '</label>',
10074
 
10075
    compile: function(element, attr) {
10076
      if(attr.icon) element.children().eq(2).removeClass('ion-checkmark').addClass(attr.icon);
10077
      var input = element.find('input');
10078
      forEach({
10079
          'name': attr.name,
10080
          'value': attr.value,
10081
          'disabled': attr.disabled,
10082
          'ng-value': attr.ngValue,
10083
          'ng-model': attr.ngModel,
10084
          'ng-disabled': attr.ngDisabled,
10085
          'ng-change': attr.ngChange
10086
      }, function(value, name) {
10087
        if (isDefined(value)) {
10088
            input.attr(name, value);
10089
          }
10090
      });
10091
 
10092
      return function(scope, element, attr) {
10093
        scope.getValue = function() {
10094
          return scope.ngValue || attr.value;
10095
        };
10096
      };
10097
    }
10098
  };
10099
});
10100
 
10101
 
10102
/**
10103
 * @ngdoc directive
10104
 * @name ionRefresher
10105
 * @module ionic
10106
 * @restrict E
10107
 * @parent ionic.directive:ionContent, ionic.directive:ionScroll
10108
 * @description
10109
 * Allows you to add pull-to-refresh to a scrollView.
10110
 *
10111
 * Place it as the first child of your {@link ionic.directive:ionContent} or
10112
 * {@link ionic.directive:ionScroll} element.
10113
 *
10114
 * When refreshing is complete, $broadcast the 'scroll.refreshComplete' event
10115
 * from your controller.
10116
 *
10117
 * @usage
10118
 *
10119
 * ```html
10120
 * <ion-content ng-controller="MyController">
10121
 *   <ion-refresher
10122
 *     pulling-text="Pull to refresh..."
10123
 *     on-refresh="doRefresh()">
10124
 *   </ion-refresher>
10125
 *   <ion-list>
10126
 *     <ion-item ng-repeat="item in items"></ion-item>
10127
 *   </ion-list>
10128
 * </ion-content>
10129
 * ```
10130
 * ```js
10131
 * angular.module('testApp', ['ionic'])
10132
 * .controller('MyController', function($scope, $http) {
10133
 *   $scope.items = [1,2,3];
10134
 *   $scope.doRefresh = function() {
10135
 *     $http.get('/new-items')
10136
 *      .success(function(newItems) {
10137
 *        $scope.items = newItems;
10138
 *      })
10139
 *      .finally(function() {
10140
 *        // Stop the ion-refresher from spinning
10141
 *        $scope.$broadcast('scroll.refreshComplete');
10142
 *      });
10143
 *   };
10144
 * });
10145
 * ```
10146
 *
10147
 * @param {expression=} on-refresh Called when the user pulls down enough and lets go
10148
 * of the refresher.
10149
 * @param {expression=} on-pulling Called when the user starts to pull down
10150
 * on the refresher.
10151
 * @param {string=} pulling-icon The icon to display while the user is pulling down.
10152
 * Default: 'ion-arrow-down-c'.
10153
 * @param {string=} pulling-text The text to display while the user is pulling down.
10154
 * @param {string=} refreshing-icon The icon to display after user lets go of the
10155
 * refresher.
10156
 * @param {string=} refreshing-text The text to display after the user lets go of
10157
 * the refresher.
10158
 * @param {boolean=} disable-pulling-rotation Disables the rotation animation of the pulling
10159
 * icon when it reaches its activated threshold. To be used with a custom `pulling-icon`.
10160
 *
10161
 */
10162
IonicModule
10163
.directive('ionRefresher', ['$ionicBind', function($ionicBind) {
10164
  return {
10165
    restrict: 'E',
10166
    replace: true,
10167
    require: '^$ionicScroll',
10168
    template:
10169
    '<div class="scroll-refresher" collection-repeat-ignore>' +
10170
      '<div class="ionic-refresher-content" ' +
10171
      'ng-class="{\'ionic-refresher-with-text\': pullingText || refreshingText}">' +
10172
        '<div class="icon-pulling" ng-class="{\'pulling-rotation-disabled\':disablePullingRotation}">' +
10173
          '<i class="icon {{pullingIcon}}"></i>' +
10174
        '</div>' +
10175
        '<div class="text-pulling" ng-bind-html="pullingText"></div>' +
10176
        '<div class="icon-refreshing"><i class="icon {{refreshingIcon}}"></i></div>' +
10177
        '<div class="text-refreshing" ng-bind-html="refreshingText"></div>' +
10178
      '</div>' +
10179
    '</div>',
10180
    compile: function($element, $attrs) {
10181
      if (angular.isUndefined($attrs.pullingIcon)) {
10182
        $attrs.$set('pullingIcon', 'ion-ios7-arrow-down');
10183
      }
10184
      if (angular.isUndefined($attrs.refreshingIcon)) {
10185
        $attrs.$set('refreshingIcon', 'ion-loading-d');
10186
      }
10187
      return function($scope, $element, $attrs, scrollCtrl) {
10188
        $ionicBind($scope, $attrs, {
10189
          pullingIcon: '@',
10190
          pullingText: '@',
10191
          refreshingIcon: '@',
10192
          refreshingText: '@',
10193
          disablePullingRotation: '@',
10194
          $onRefresh: '&onRefresh',
10195
          $onPulling: '&onPulling'
10196
        });
10197
 
10198
        scrollCtrl._setRefresher($scope, $element[0]);
10199
        $scope.$on('scroll.refreshComplete', function() {
10200
          $scope.$evalAsync(function() {
10201
            scrollCtrl.scrollView.finishPullToRefresh();
10202
          });
10203
        });
10204
      };
10205
    }
10206
  };
10207
}]);
10208
 
10209
/**
10210
 * @ngdoc directive
10211
 * @name ionScroll
10212
 * @module ionic
10213
 * @delegate ionic.service:$ionicScrollDelegate
10214
 * @codepen mwFuh
10215
 * @restrict E
10216
 *
10217
 * @description
10218
 * Creates a scrollable container for all content inside.
10219
 *
10220
 * @usage
10221
 *
10222
 * Basic usage:
10223
 *
10224
 * ```html
10225
 * <ion-scroll zooming="true" direction="xy" style="width: 500px; height: 500px">
10226
 *   <div style="width: 5000px; height: 5000px; background: url('https://upload.wikimedia.org/wikipedia/commons/a/ad/Europe_geological_map-en.jpg') repeat"></div>
10227
 *  </ion-scroll>
10228
 * ```
10229
 *
10230
 * Note that it's important to set the height of the scroll box as well as the height of the inner
10231
 * content to enable scrolling. This makes it possible to have full control over scrollable areas.
10232
 *
10233
 * If you'd just like to have a center content scrolling area, use {@link ionic.directive:ionContent} instead.
10234
 *
10235
 * @param {string=} delegate-handle The handle used to identify this scrollView
10236
 * with {@link ionic.service:$ionicScrollDelegate}.
10237
 * @param {string=} direction Which way to scroll. 'x' or 'y' or 'xy'. Default 'y'.
10238
 * @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.
10239
 * @param {boolean=} paging Whether to scroll with paging.
10240
 * @param {expression=} on-refresh Called on pull-to-refresh, triggered by an {@link ionic.directive:ionRefresher}.
10241
 * @param {expression=} on-scroll Called whenever the user scrolls.
10242
 * @param {boolean=} scrollbar-x Whether to show the horizontal scrollbar. Default true.
10243
 * @param {boolean=} scrollbar-y Whether to show the vertical scrollbar. Default true.
10244
 * @param {boolean=} zooming Whether to support pinch-to-zoom
10245
 * @param {integer=} min-zoom The smallest zoom amount allowed (default is 0.5)
10246
 * @param {integer=} max-zoom The largest zoom amount allowed (default is 3)
10247
 * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges
10248
 * of the content.  Defaults to true on iOS, false on Android.
10249
 */
10250
IonicModule
10251
.directive('ionScroll', [
10252
  '$timeout',
10253
  '$controller',
10254
  '$ionicBind',
10255
function($timeout, $controller, $ionicBind) {
10256
  return {
10257
    restrict: 'E',
10258
    scope: true,
10259
    controller: function() {},
10260
    compile: function(element, attr) {
10261
      element.addClass('scroll-view ionic-scroll');
10262
 
10263
      //We cannot transclude here because it breaks element.data() inheritance on compile
10264
      var innerElement = jqLite('<div class="scroll"></div>');
10265
      innerElement.append(element.contents());
10266
      element.append(innerElement);
10267
 
10268
      return { pre: prelink };
10269
      function prelink($scope, $element, $attr) {
10270
        var scrollView, scrollCtrl;
10271
 
10272
        $ionicBind($scope, $attr, {
10273
          direction: '@',
10274
          paging: '@',
10275
          $onScroll: '&onScroll',
10276
          scroll: '@',
10277
          scrollbarX: '@',
10278
          scrollbarY: '@',
10279
          zooming: '@',
10280
          minZoom: '@',
10281
          maxZoom: '@'
10282
        });
10283
        $scope.direction = $scope.direction || 'y';
10284
 
10285
        if (angular.isDefined($attr.padding)) {
10286
          $scope.$watch($attr.padding, function(newVal) {
10287
            innerElement.toggleClass('padding', !!newVal);
10288
          });
10289
        }
10290
        if($scope.$eval($scope.paging) === true) {
10291
          innerElement.addClass('scroll-paging');
10292
        }
10293
 
10294
        if(!$scope.direction) { $scope.direction = 'y'; }
10295
        var isPaging = $scope.$eval($scope.paging) === true;
10296
 
10297
        var scrollViewOptions= {
10298
          el: $element[0],
10299
          delegateHandle: $attr.delegateHandle,
10300
          locking: ($attr.locking || 'true') === 'true',
10301
          bouncing: $scope.$eval($attr.hasBouncing),
10302
          paging: isPaging,
10303
          scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
10304
          scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
10305
          scrollingX: $scope.direction.indexOf('x') >= 0,
10306
          scrollingY: $scope.direction.indexOf('y') >= 0,
10307
          zooming: $scope.$eval($scope.zooming) === true,
10308
          maxZoom: $scope.$eval($scope.maxZoom) || 3,
10309
          minZoom: $scope.$eval($scope.minZoom) || 0.5,
10310
          preventDefault: true
10311
        };
10312
        if (isPaging) {
10313
          scrollViewOptions.speedMultiplier = 0.8;
10314
          scrollViewOptions.bouncing = false;
10315
        }
10316
 
10317
        scrollCtrl = $controller('$ionicScroll', {
10318
          $scope: $scope,
10319
          scrollViewOptions: scrollViewOptions
10320
        });
10321
        scrollView = $scope.$parent.scrollView = scrollCtrl.scrollView;
10322
      }
10323
    }
10324
  };
10325
}]);
10326
 
10327
/**
10328
 * @ngdoc directive
10329
 * @name ionSideMenu
10330
 * @module ionic
10331
 * @restrict E
10332
 * @parent ionic.directive:ionSideMenus
10333
 *
10334
 * @description
10335
 * A container for a side menu, sibling to an {@link ionic.directive:ionSideMenuContent} directive.
10336
 *
10337
 * @usage
10338
 * ```html
10339
 * <ion-side-menu
10340
 *   side="left"
10341
 *   width="myWidthValue + 20"
10342
 *   is-enabled="shouldLeftSideMenuBeEnabled()">
10343
 * </ion-side-menu>
10344
 * ```
10345
 * For a complete side menu example, see the
10346
 * {@link ionic.directive:ionSideMenus} documentation.
10347
 *
10348
 * @param {string} side Which side the side menu is currently on.  Allowed values: 'left' or 'right'.
10349
 * @param {boolean=} is-enabled Whether this side menu is enabled.
10350
 * @param {number=} width How many pixels wide the side menu should be.  Defaults to 275.
10351
 */
10352
IonicModule
10353
.directive('ionSideMenu', function() {
10354
  return {
10355
    restrict: 'E',
10356
    require: '^ionSideMenus',
10357
    scope: true,
10358
    compile: function(element, attr) {
10359
      angular.isUndefined(attr.isEnabled) && attr.$set('isEnabled', 'true');
10360
      angular.isUndefined(attr.width) && attr.$set('width', '275');
10361
 
10362
      element.addClass('menu menu-' + attr.side);
10363
 
10364
      return function($scope, $element, $attr, sideMenuCtrl) {
10365
        $scope.side = $attr.side || 'left';
10366
 
10367
        var sideMenu = sideMenuCtrl[$scope.side] = new ionic.views.SideMenu({
10368
          width: attr.width,
10369
          el: $element[0],
10370
          isEnabled: true
10371
        });
10372
 
10373
        $scope.$watch($attr.width, function(val) {
10374
          var numberVal = +val;
10375
          if (numberVal && numberVal == val) {
10376
            sideMenu.setWidth(+val);
10377
          }
10378
        });
10379
        $scope.$watch($attr.isEnabled, function(val) {
10380
          sideMenu.setIsEnabled(!!val);
10381
        });
10382
      };
10383
    }
10384
  };
10385
});
10386
 
10387
 
10388
/**
10389
 * @ngdoc directive
10390
 * @name ionSideMenuContent
10391
 * @module ionic
10392
 * @restrict E
10393
 * @parent ionic.directive:ionSideMenus
10394
 *
10395
 * @description
10396
 * A container for the main visible content, sibling to one or more
10397
 * {@link ionic.directive:ionSideMenu} directives.
10398
 *
10399
 * @usage
10400
 * ```html
10401
 * <ion-side-menu-content
10402
 *   edge-drag-threshold="true"
10403
 *   drag-content="true">
10404
 * </ion-side-menu-content>
10405
 * ```
10406
 * For a complete side menu example, see the
10407
 * {@link ionic.directive:ionSideMenus} documentation.
10408
 *
10409
 * @param {boolean=} drag-content Whether the content can be dragged. Default true.
10410
 * @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:
10411
   *  - 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.
10412
   *  - If true is given, the default number of pixels (25) is used as the maximum allowed distance.
10413
   *  - If false or 0 is given, the edge drag threshold is disabled, and dragging from anywhere on the content is allowed.
10414
 *
10415
 */
10416
IonicModule
10417
.directive('ionSideMenuContent', [
10418
  '$timeout',
10419
  '$ionicGesture',
10420
  '$window',
10421
function($timeout, $ionicGesture, $window) {
10422
 
10423
  return {
10424
    restrict: 'EA', //DEPRECATED 'A'
10425
    require: '^ionSideMenus',
10426
    scope: true,
10427
    compile: function(element, attr) {
10428
      element.addClass('menu-content pane');
10429
 
10430
      return { pre: prelink };
10431
      function prelink($scope, $element, $attr, sideMenuCtrl) {
10432
        var startCoord = null;
10433
        var primaryScrollAxis = null;
10434
 
10435
        if (isDefined(attr.dragContent)) {
10436
          $scope.$watch(attr.dragContent, function(value) {
10437
            sideMenuCtrl.canDragContent(value);
10438
          });
10439
        } else {
10440
          sideMenuCtrl.canDragContent(true);
10441
        }
10442
 
10443
        if (isDefined(attr.edgeDragThreshold)) {
10444
          $scope.$watch(attr.edgeDragThreshold, function(value) {
10445
            sideMenuCtrl.edgeDragThreshold(value);
10446
          });
10447
        }
10448
 
10449
        // Listen for taps on the content to close the menu
10450
        function onContentTap(gestureEvt) {
10451
          if (sideMenuCtrl.getOpenAmount() !== 0) {
10452
            sideMenuCtrl.close();
10453
            gestureEvt.gesture.srcEvent.preventDefault();
10454
            startCoord = null;
10455
            primaryScrollAxis = null;
10456
          } else if (!startCoord) {
10457
            startCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
10458
          }
10459
        }
10460
 
10461
        function onDragX(e) {
10462
          if (!sideMenuCtrl.isDraggableTarget(e)) return;
10463
 
10464
          if (getPrimaryScrollAxis(e) == 'x') {
10465
            sideMenuCtrl._handleDrag(e);
10466
            e.gesture.srcEvent.preventDefault();
10467
          }
10468
        }
10469
 
10470
        function onDragY(e) {
10471
          if (getPrimaryScrollAxis(e) == 'x') {
10472
            e.gesture.srcEvent.preventDefault();
10473
          }
10474
        }
10475
 
10476
        function onDragRelease(e) {
10477
          sideMenuCtrl._endDrag(e);
10478
          startCoord = null;
10479
          primaryScrollAxis = null;
10480
        }
10481
 
10482
        function getPrimaryScrollAxis(gestureEvt) {
10483
          // gets whether the user is primarily scrolling on the X or Y
10484
          // If a majority of the drag has been on the Y since the start of
10485
          // the drag, but the X has moved a little bit, it's still a Y drag
10486
 
10487
          if (primaryScrollAxis) {
10488
            // we already figured out which way they're scrolling
10489
            return primaryScrollAxis;
10490
          }
10491
 
10492
          if (gestureEvt && gestureEvt.gesture) {
10493
 
10494
            if (!startCoord) {
10495
              // get the starting point
10496
              startCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
10497
 
10498
            } else {
10499
              // we already have a starting point, figure out which direction they're going
10500
              var endCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
10501
 
10502
              var xDistance = Math.abs(endCoord.x - startCoord.x);
10503
              var yDistance = Math.abs(endCoord.y - startCoord.y);
10504
 
10505
              var scrollAxis = (xDistance < yDistance ? 'y' : 'x');
10506
 
10507
              if (Math.max(xDistance, yDistance) > 30) {
10508
                // ok, we pretty much know which way they're going
10509
                // let's lock it in
10510
                primaryScrollAxis = scrollAxis;
10511
              }
10512
 
10513
              return scrollAxis;
10514
            }
10515
          }
10516
          return 'y';
10517
        }
10518
 
10519
        var content = {
10520
          element: element[0],
10521
          onDrag: function(e) {},
10522
          endDrag: function(e) {},
10523
          getTranslateX: function() {
10524
            return $scope.sideMenuContentTranslateX || 0;
10525
          },
10526
          setTranslateX: ionic.animationFrameThrottle(function(amount) {
10527
            var xTransform = content.offsetX + amount;
10528
            $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + xTransform + 'px,0,0)';
10529
            $timeout(function() {
10530
              $scope.sideMenuContentTranslateX = amount;
10531
            });
10532
          }),
10533
          setMarginLeft: ionic.animationFrameThrottle(function(amount) {
10534
            if (amount) {
10535
              amount = parseInt(amount, 10);
10536
              $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amount + 'px,0,0)';
10537
              $element[0].style.width = ($window.innerWidth - amount) + 'px';
10538
              content.offsetX = amount;
10539
            } else {
10540
              $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
10541
              $element[0].style.width = '';
10542
              content.offsetX = 0;
10543
            }
10544
          }),
10545
          setMarginRight: ionic.animationFrameThrottle(function(amount) {
10546
            if (amount) {
10547
              amount = parseInt(amount, 10);
10548
              $element[0].style.width = ($window.innerWidth - amount) + 'px';
10549
              content.offsetX = amount;
10550
            } else {
10551
              $element[0].style.width = '';
10552
              content.offsetX = 0;
10553
            }
10554
            // reset incase left gets grabby
10555
            $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
10556
          }),
10557
          enableAnimation: function() {
10558
            $scope.animationEnabled = true;
10559
            $element[0].classList.add('menu-animated');
10560
          },
10561
          disableAnimation: function() {
10562
            $scope.animationEnabled = false;
10563
            $element[0].classList.remove('menu-animated');
10564
          },
10565
          offsetX: 0
10566
        };
10567
 
10568
        sideMenuCtrl.setContent(content);
10569
 
10570
        // add gesture handlers
10571
        var gestureOpts = { stop_browser_behavior: false };
10572
        var contentTapGesture = $ionicGesture.on('tap', onContentTap, $element, gestureOpts);
10573
        var dragRightGesture = $ionicGesture.on('dragright', onDragX, $element, gestureOpts);
10574
        var dragLeftGesture = $ionicGesture.on('dragleft', onDragX, $element, gestureOpts);
10575
        var dragUpGesture = $ionicGesture.on('dragup', onDragY, $element, gestureOpts);
10576
        var dragDownGesture = $ionicGesture.on('dragdown', onDragY, $element, gestureOpts);
10577
        var releaseGesture = $ionicGesture.on('release', onDragRelease, $element, gestureOpts);
10578
 
10579
        // Cleanup
10580
        $scope.$on('$destroy', function() {
10581
          if (content) {
10582
            content.element = null;
10583
            content = null;
10584
          }
10585
          $ionicGesture.off(dragLeftGesture, 'dragleft', onDragX);
10586
          $ionicGesture.off(dragRightGesture, 'dragright', onDragX);
10587
          $ionicGesture.off(dragUpGesture, 'dragup', onDragY);
10588
          $ionicGesture.off(dragDownGesture, 'dragdown', onDragY);
10589
          $ionicGesture.off(releaseGesture, 'release', onDragRelease);
10590
          $ionicGesture.off(contentTapGesture, 'tap', onContentTap);
10591
        });
10592
      }
10593
    }
10594
  };
10595
}]);
10596
 
10597
IonicModule
10598
 
10599
/**
10600
 * @ngdoc directive
10601
 * @name ionSideMenus
10602
 * @module ionic
10603
 * @delegate ionic.service:$ionicSideMenuDelegate
10604
 * @restrict E
10605
 *
10606
 * @description
10607
 * A container element for side menu(s) and the main content. Allows the left and/or right side menu
10608
 * to be toggled by dragging the main content area side to side.
10609
 *
10610
 * To automatically close an opened menu, you can add the {@link ionic.directive:menuClose} attribute
10611
 * directive. The `menu-close` attribute is usually added to links and buttons within
10612
 * `ion-side-menu-content`, so that when the element is clicked, the opened side menu will
10613
 * automatically close.
10614
 *
10615
 * By default, side menus are hidden underneath their side menu content and can be opened by swiping
10616
 * the content left or right or by toggling a button to show the side menu. Additionally, by adding the
10617
 * {@link ionic.directive:exposeAsideWhen} attribute directive to an
10618
 * {@link ionic.directive:ionSideMenu} element directive, a side menu can be given instructions about
10619
 * "when" the menu should be exposed (always viewable).
10620
 *
10621
 * ![Side Menu](http://ionicframework.com.s3.amazonaws.com/docs/controllers/sidemenu.gif)
10622
 *
10623
 * For more information on side menus, check out:
10624
 *
10625
 * - {@link ionic.directive:ionSideMenuContent}
10626
 * - {@link ionic.directive:ionSideMenu}
10627
 * - {@link ionic.directive:menuToggle}
10628
 * - {@link ionic.directive:menuClose}
10629
 * - {@link ionic.directive:exposeAsideWhen}
10630
 *
10631
 * @usage
10632
 * To use side menus, add an `<ion-side-menus>` parent element,
10633
 * an `<ion-side-menu-content>` for the center content,
10634
 * and one or more `<ion-side-menu>` directives.
10635
 *
10636
 * ```html
10637
 * <ion-side-menus>
10638
 *   <!-- Center content -->
10639
 *   <ion-side-menu-content ng-controller="ContentController">
10640
 *   </ion-side-menu-content>
10641
 *
10642
 *   <!-- Left menu -->
10643
 *   <ion-side-menu side="left">
10644
 *   </ion-side-menu>
10645
 *
10646
 *   <!-- Right menu -->
10647
 *   <ion-side-menu side="right">
10648
 *   </ion-side-menu>
10649
 * </ion-side-menus>
10650
 * ```
10651
 * ```js
10652
 * function ContentController($scope, $ionicSideMenuDelegate) {
10653
 *   $scope.toggleLeft = function() {
10654
 *     $ionicSideMenuDelegate.toggleLeft();
10655
 *   };
10656
 * }
10657
 * ```
10658
 *
10659
 * @param {bool=} enable-menu-with-back-views Determines whether the side menu is enabled when the
10660
 * back button is showing. When set to `false`, any {@link ionic.directive:menuToggle} will be hidden,
10661
 * and the user cannot swipe to open the menu. When going back to the root page of the side menu (the
10662
 * page without a back button visible), then any menuToggle buttons will show again, and menus will be
10663
 * enabled again.
10664
 * @param {string=} delegate-handle The handle used to identify this side menu
10665
 * with {@link ionic.service:$ionicSideMenuDelegate}.
10666
 *
10667
 */
10668
.directive('ionSideMenus', ['$ionicBody', function($ionicBody) {
10669
  return {
10670
    restrict: 'ECA',
10671
    controller: '$ionicSideMenus',
10672
    compile: function(element, attr) {
10673
      attr.$set('class', (attr['class'] || '') + ' view');
10674
 
10675
      return { pre: prelink };
10676
      function prelink($scope, $element, $attrs, ctrl) {
10677
 
10678
        ctrl.enableMenuWithBackViews($scope.$eval($attrs.enableMenuWithBackViews));
10679
 
10680
        $scope.$on('$ionicExposeAside', function(evt, isAsideExposed) {
10681
          if (!$scope.$exposeAside) $scope.$exposeAside = {};
10682
          $scope.$exposeAside.active = isAsideExposed;
10683
          $ionicBody.enableClass(isAsideExposed, 'aside-open');
10684
        });
10685
 
10686
        $scope.$on('$ionicView.beforeEnter', function(ev, d){
10687
          if (d.historyId) {
10688
            $scope.$activeHistoryId = d.historyId;
10689
          }
10690
        });
10691
 
10692
        $scope.$on('$destroy', function() {
10693
          $ionicBody.removeClass('menu-open', 'aside-open');
10694
        });
10695
 
10696
      }
10697
    }
10698
  };
10699
}]);
10700
 
10701
 
10702
/**
10703
 * @ngdoc directive
10704
 * @name ionSlideBox
10705
 * @module ionic
10706
 * @delegate ionic.service:$ionicSlideBoxDelegate
10707
 * @restrict E
10708
 * @description
10709
 * The Slide Box is a multi-page container where each page can be swiped or dragged between:
10710
 *
10711
 * ![SlideBox](http://ionicframework.com.s3.amazonaws.com/docs/controllers/slideBox.gif)
10712
 *
10713
 * @usage
10714
 * ```html
10715
 * <ion-slide-box on-slide-changed="slideHasChanged($index)">
10716
 *   <ion-slide>
10717
 *     <div class="box blue"><h1>BLUE</h1></div>
10718
 *   </ion-slide>
10719
 *   <ion-slide>
10720
 *     <div class="box yellow"><h1>YELLOW</h1></div>
10721
 *   </ion-slide>
10722
 *   <ion-slide>
10723
 *     <div class="box pink"><h1>PINK</h1></div>
10724
 *   </ion-slide>
10725
 * </ion-slide-box>
10726
 * ```
10727
 *
10728
 * @param {string=} delegate-handle The handle used to identify this slideBox
10729
 * with {@link ionic.service:$ionicSlideBoxDelegate}.
10730
 * @param {boolean=} does-continue Whether the slide box should loop.
10731
 * @param {boolean=} auto-play Whether the slide box should automatically slide. Default true if does-continue is true.
10732
 * @param {number=} slide-interval How many milliseconds to wait to change slides (if does-continue is true). Defaults to 4000.
10733
 * @param {boolean=} show-pager Whether a pager should be shown for this slide box.
10734
 * @param {expression=} pager-click Expression to call when a pager is clicked (if show-pager is true). Is passed the 'index' variable.
10735
 * @param {expression=} on-slide-changed Expression called whenever the slide is changed.  Is passed an '$index' variable.
10736
 * @param {expression=} active-slide Model to bind the current slide to.
10737
 */
10738
IonicModule
10739
.directive('ionSlideBox', [
10740
  '$timeout',
10741
  '$compile',
10742
  '$ionicSlideBoxDelegate',
10743
  '$ionicHistory',
10744
function($timeout, $compile, $ionicSlideBoxDelegate, $ionicHistory) {
10745
  return {
10746
    restrict: 'E',
10747
    replace: true,
10748
    transclude: true,
10749
    scope: {
10750
      autoPlay: '=',
10751
      doesContinue: '@',
10752
      slideInterval: '@',
10753
      showPager: '@',
10754
      pagerClick: '&',
10755
      disableScroll: '@',
10756
      onSlideChanged: '&',
10757
      activeSlide: '=?'
10758
    },
10759
    controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
10760
      var _this = this;
10761
 
10762
      var continuous = $scope.$eval($scope.doesContinue) === true;
10763
      var shouldAutoPlay = isDefined($attrs.autoPlay) ? !!$scope.autoPlay : false;
10764
      var slideInterval = shouldAutoPlay ? $scope.$eval($scope.slideInterval) || 4000 : 0;
10765
 
10766
      var slider = new ionic.views.Slider({
10767
        el: $element[0],
10768
        auto: slideInterval,
10769
        continuous: continuous,
10770
        startSlide: $scope.activeSlide,
10771
        slidesChanged: function() {
10772
          $scope.currentSlide = slider.currentIndex();
10773
 
10774
          // Try to trigger a digest
10775
          $timeout(function() {});
10776
        },
10777
        callback: function(slideIndex) {
10778
          $scope.currentSlide = slideIndex;
10779
          $scope.onSlideChanged({ index: $scope.currentSlide, $index: $scope.currentSlide});
10780
          $scope.$parent.$broadcast('slideBox.slideChanged', slideIndex);
10781
          $scope.activeSlide = slideIndex;
10782
          // Try to trigger a digest
10783
          $timeout(function() {});
10784
        }
10785
      });
10786
 
10787
      slider.enableSlide($scope.$eval($attrs.disableScroll) !== true);
10788
 
10789
      $scope.$watch('activeSlide', function(nv) {
10790
        if(angular.isDefined(nv)){
10791
          slider.slide(nv);
10792
        }
10793
      });
10794
 
10795
      $scope.$on('slideBox.nextSlide', function() {
10796
        slider.next();
10797
      });
10798
 
10799
      $scope.$on('slideBox.prevSlide', function() {
10800
        slider.prev();
10801
      });
10802
 
10803
      $scope.$on('slideBox.setSlide', function(e, index) {
10804
        slider.slide(index);
10805
      });
10806
 
10807
      //Exposed for testing
10808
      this.__slider = slider;
10809
 
10810
      var deregisterInstance = $ionicSlideBoxDelegate._registerInstance(
10811
        slider, $attrs.delegateHandle, function() {
10812
          return $ionicHistory.isActiveScope($scope);
10813
        }
10814
      );
10815
      $scope.$on('$destroy', deregisterInstance);
10816
 
10817
      this.slidesCount = function() {
10818
        return slider.slidesCount();
10819
      };
10820
 
10821
      this.onPagerClick = function(index) {
10822
        void 0;
10823
        $scope.pagerClick({index: index});
10824
      };
10825
 
10826
      $timeout(function() {
10827
        slider.load();
10828
      });
10829
    }],
10830
    template: '<div class="slider">' +
10831
      '<div class="slider-slides" ng-transclude>' +
10832
      '</div>' +
10833
    '</div>',
10834
 
10835
    link: function($scope, $element, $attr, slideBoxCtrl) {
10836
      // If the pager should show, append it to the slide box
10837
      if($scope.$eval($scope.showPager) !== false) {
10838
        var childScope = $scope.$new();
10839
        var pager = jqLite('<ion-pager></ion-pager>');
10840
        $element.append(pager);
10841
        $compile(pager)(childScope);
10842
      }
10843
    }
10844
  };
10845
}])
10846
.directive('ionSlide', function() {
10847
  return {
10848
    restrict: 'E',
10849
    require: '^ionSlideBox',
10850
    compile: function(element, attr) {
10851
      element.addClass('slider-slide');
10852
      return function($scope, $element, $attr) {
10853
      };
10854
    },
10855
  };
10856
})
10857
 
10858
.directive('ionPager', function() {
10859
  return {
10860
    restrict: 'E',
10861
    replace: true,
10862
    require: '^ionSlideBox',
10863
    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>',
10864
    link: function($scope, $element, $attr, slideBox) {
10865
      var selectPage = function(index) {
10866
        var children = $element[0].children;
10867
        var length = children.length;
10868
        for(var i = 0; i < length; i++) {
10869
          if(i == index) {
10870
            children[i].classList.add('active');
10871
          } else {
10872
            children[i].classList.remove('active');
10873
          }
10874
        }
10875
      };
10876
 
10877
      $scope.pagerClick = function(index) {
10878
        slideBox.onPagerClick(index);
10879
      };
10880
 
10881
      $scope.numSlides = function() {
10882
        return new Array(slideBox.slidesCount());
10883
      };
10884
 
10885
      $scope.$watch('currentSlide', function(v) {
10886
        selectPage(v);
10887
      });
10888
    }
10889
  };
10890
 
10891
});
10892
 
10893
/**
10894
 * @ngdoc directive
10895
 * @name ionTab
10896
 * @module ionic
10897
 * @restrict E
10898
 * @parent ionic.directive:ionTabs
10899
 *
10900
 * @description
10901
 * Contains a tab's content.  The content only exists while the given tab is selected.
10902
 *
10903
 * Each ionTab has its own view history.
10904
 *
10905
 * @usage
10906
 * ```html
10907
 * <ion-tab
10908
 *   title="Tab!"
10909
 *   icon="my-icon"
10910
 *   href="#/tab/tab-link"
10911
 *   on-select="onTabSelected()"
10912
 *   on-deselect="onTabDeselected()">
10913
 * </ion-tab>
10914
 * ```
10915
 * For a complete, working tab bar example, see the {@link ionic.directive:ionTabs} documentation.
10916
 *
10917
 * @param {string} title The title of the tab.
10918
 * @param {string=} href The link that this tab will navigate to when tapped.
10919
 * @param {string=} icon The icon of the tab. If given, this will become the default for icon-on and icon-off.
10920
 * @param {string=} icon-on The icon of the tab while it is selected.
10921
 * @param {string=} icon-off The icon of the tab while it is not selected.
10922
 * @param {expression=} badge The badge to put on this tab (usually a number).
10923
 * @param {expression=} badge-style The style of badge to put on this tab (eg: badge-positive).
10924
 * @param {expression=} on-select Called when this tab is selected.
10925
 * @param {expression=} on-deselect Called when this tab is deselected.
10926
 * @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()}.
10927
 */
10928
IonicModule
10929
.directive('ionTab', [
10930
  '$compile',
10931
  '$ionicConfig',
10932
  '$ionicBind',
10933
  '$ionicViewSwitcher',
10934
function($compile, $ionicConfig, $ionicBind, $ionicViewSwitcher) {
10935
 
10936
  //Returns ' key="value"' if value exists
10937
  function attrStr(k, v) {
10938
    return angular.isDefined(v) ? ' ' + k + '="' + v + '"' : '';
10939
  }
10940
  return {
10941
    restrict: 'E',
10942
    require: ['^ionTabs', 'ionTab'],
10943
    controller: '$ionicTab',
10944
    scope: true,
10945
    compile: function(element, attr) {
10946
 
10947
      //We create the tabNavTemplate in the compile phase so that the
10948
      //attributes we pass down won't be interpolated yet - we want
10949
      //to pass down the 'raw' versions of the attributes
10950
      var tabNavTemplate = '<ion-tab-nav' +
10951
        attrStr('ng-click', attr.ngClick) +
10952
        attrStr('title', attr.title) +
10953
        attrStr('icon', attr.icon) +
10954
        attrStr('icon-on', attr.iconOn) +
10955
        attrStr('icon-off', attr.iconOff) +
10956
        attrStr('badge', attr.badge) +
10957
        attrStr('badge-style', attr.badgeStyle) +
10958
        attrStr('hidden', attr.hidden) +
10959
        attrStr('class', attr['class']) +
10960
        '></ion-tab-nav>';
10961
 
10962
      //Remove the contents of the element so we can compile them later, if tab is selected
10963
      var tabContentEle = document.createElement('div');
10964
      for (var x = 0; x < element[0].children.length; x++) {
10965
        tabContentEle.appendChild(element[0].children[x].cloneNode(true));
10966
      }
10967
      var childElementCount = tabContentEle.childElementCount;
10968
      element.empty();
10969
 
10970
      var navViewName, isNavView;
10971
      if (childElementCount) {
10972
        if (tabContentEle.children[0].tagName === 'ION-NAV-VIEW') {
10973
          // get the name if it's a nav-view
10974
          navViewName = tabContentEle.children[0].getAttribute('name');
10975
          tabContentEle.children[0].classList.add('view-container');
10976
          isNavView = true;
10977
        }
10978
        if (childElementCount === 1) {
10979
          // make the 1 child element the primary tab content container
10980
          tabContentEle = tabContentEle.children[0];
10981
        }
10982
        if (!isNavView) tabContentEle.classList.add('pane');
10983
        tabContentEle.classList.add('tab-content');
10984
      }
10985
 
10986
      return function link($scope, $element, $attr, ctrls) {
10987
        var childScope;
10988
        var childElement;
10989
        var tabsCtrl = ctrls[0];
10990
        var tabCtrl = ctrls[1];
10991
        var isTabContentAttached = false;
10992
 
10993
        $ionicBind($scope, $attr, {
10994
          onSelect: '&',
10995
          onDeselect: '&',
10996
          title: '@',
10997
          uiSref: '@',
10998
          href: '@'
10999
        });
11000
 
11001
        tabsCtrl.add($scope);
11002
        $scope.$on('$destroy', function() {
11003
          if (!$scope.$tabsDestroy) {
11004
            // if the containing ionTabs directive is being destroyed
11005
            // then don't bother going through the controllers remove
11006
            // method, since remove will reset the active tab as each tab
11007
            // is being destroyed, causing unnecessary view loads and transitions
11008
            tabsCtrl.remove($scope);
11009
          }
11010
          tabNavElement.isolateScope().$destroy();
11011
          tabNavElement.remove();
11012
          tabNavElement = tabContentEle = childElement = null;
11013
        });
11014
 
11015
        //Remove title attribute so browser-tooltip does not apear
11016
        $element[0].removeAttribute('title');
11017
 
11018
        if (navViewName) {
11019
          tabCtrl.navViewName = $scope.navViewName = navViewName;
11020
        }
11021
        $scope.$on('$stateChangeSuccess', selectIfMatchesState);
11022
        selectIfMatchesState();
11023
        function selectIfMatchesState() {
11024
          if (tabCtrl.tabMatchesState()) {
11025
            tabsCtrl.select($scope, false);
11026
          }
11027
        }
11028
 
11029
        var tabNavElement = jqLite(tabNavTemplate);
11030
        tabNavElement.data('$ionTabsController', tabsCtrl);
11031
        tabNavElement.data('$ionTabController', tabCtrl);
11032
        tabsCtrl.$tabsElement.append($compile(tabNavElement)($scope));
11033
 
11034
 
11035
        function tabSelected(isSelected) {
11036
          if (isSelected && childElementCount) {
11037
            // this tab is being selected
11038
 
11039
            // check if the tab is already in the DOM
11040
            // only do this if the tab has child elements
11041
            if (!isTabContentAttached) {
11042
              // tab should be selected and is NOT in the DOM
11043
              // create a new scope and append it
11044
              childScope = $scope.$new();
11045
              childElement = jqLite(tabContentEle);
11046
              $ionicViewSwitcher.viewEleIsActive(childElement, true);
11047
              tabsCtrl.$element.append(childElement);
11048
              $compile(childElement)(childScope);
11049
              isTabContentAttached = true;
11050
            }
11051
 
11052
            // remove the hide class so the tabs content shows up
11053
            $ionicViewSwitcher.viewEleIsActive(childElement, true);
11054
 
11055
          } else if (isTabContentAttached && childElement) {
11056
            // this tab should NOT be selected, and it is already in the DOM
11057
 
11058
            if ($ionicConfig.views.maxCache() > 0) {
11059
              // keep the tabs in the DOM, only css hide it
11060
              $ionicViewSwitcher.viewEleIsActive(childElement, false);
11061
 
11062
            } else {
11063
              // do not keep tabs in the DOM
11064
              destroyTab();
11065
            }
11066
 
11067
          }
11068
        }
11069
 
11070
        function destroyTab() {
11071
          childScope && childScope.$destroy();
11072
          isTabContentAttached && childElement && childElement.remove();
11073
          isTabContentAttached = childScope = childElement = null;
11074
        }
11075
 
11076
        $scope.$watch('$tabSelected', tabSelected);
11077
 
11078
        $scope.$on('$ionicView.afterEnter', function() {
11079
          $ionicViewSwitcher.viewEleIsActive(childElement, $scope.$tabSelected);
11080
        });
11081
 
11082
        $scope.$on('$ionicView.clearCache', function() {
11083
          if (!$scope.$tabSelected) {
11084
            destroyTab();
11085
          }
11086
        });
11087
 
11088
      };
11089
    }
11090
  };
11091
}]);
11092
 
11093
IonicModule
11094
.directive('ionTabNav', [function() {
11095
  return {
11096
    restrict: 'E',
11097
    replace: true,
11098
    require: ['^ionTabs', '^ionTab'],
11099
    template:
11100
    '<a ng-class="{\'tab-item-active\': isTabActive(), \'has-badge\':badge, \'tab-hidden\':isHidden()}" ' +
11101
      ' class="tab-item">' +
11102
      '<span class="badge {{badgeStyle}}" ng-if="badge">{{badge}}</span>' +
11103
      '<i class="icon {{getIconOn()}}" ng-if="getIconOn() && isTabActive()"></i>' +
11104
      '<i class="icon {{getIconOff()}}" ng-if="getIconOff() && !isTabActive()"></i>' +
11105
      '<span class="tab-title" ng-bind-html="title"></span>' +
11106
    '</a>',
11107
    scope: {
11108
      title: '@',
11109
      icon: '@',
11110
      iconOn: '@',
11111
      iconOff: '@',
11112
      badge: '=',
11113
      hidden: '@',
11114
      badgeStyle: '@',
11115
      'class': '@'
11116
    },
11117
    compile: function(element, attr, transclude) {
11118
      return function link($scope, $element, $attrs, ctrls) {
11119
        var tabsCtrl = ctrls[0],
11120
          tabCtrl = ctrls[1];
11121
 
11122
        //Remove title attribute so browser-tooltip does not apear
11123
        $element[0].removeAttribute('title');
11124
 
11125
        $scope.selectTab = function(e) {
11126
          e.preventDefault();
11127
          tabsCtrl.select(tabCtrl.$scope, true);
11128
        };
11129
        if (!$attrs.ngClick) {
11130
          $element.on('click', function(event) {
11131
            $scope.$apply(function() {
11132
              $scope.selectTab(event);
11133
            });
11134
          });
11135
        }
11136
 
11137
        $scope.isHidden = function() {
11138
          if ($attrs.hidden === 'true' || $attrs.hidden === true) return true;
11139
          return false;
11140
        };
11141
 
11142
        $scope.getIconOn = function() {
11143
          return $scope.iconOn || $scope.icon;
11144
        };
11145
        $scope.getIconOff = function() {
11146
          return $scope.iconOff || $scope.icon;
11147
        };
11148
 
11149
        $scope.isTabActive = function() {
11150
          return tabsCtrl.selectedTab() === tabCtrl.$scope;
11151
        };
11152
      };
11153
    }
11154
  };
11155
}]);
11156
 
11157
/**
11158
 * @ngdoc directive
11159
 * @name ionTabs
11160
 * @module ionic
11161
 * @delegate ionic.service:$ionicTabsDelegate
11162
 * @restrict E
11163
 * @codepen KbrzJ
11164
 *
11165
 * @description
11166
 * Powers a multi-tabbed interface with a Tab Bar and a set of "pages" that can be tabbed
11167
 * through.
11168
 *
11169
 * Assign any [tabs class](/docs/components#tabs) or
11170
 * [animation class](/docs/components#animation) to the element to define
11171
 * its look and feel.
11172
 *
11173
 * See the {@link ionic.directive:ionTab} directive's documentation for more details on
11174
 * individual tabs.
11175
 *
11176
 * Note: do not place ion-tabs inside of an ion-content element; it has been known to cause a
11177
 * certain CSS bug.
11178
 *
11179
 * @usage
11180
 * ```html
11181
 * <ion-tabs class="tabs-positive tabs-icon-only">
11182
 *
11183
 *   <ion-tab title="Home" icon-on="ion-ios7-filing" icon-off="ion-ios7-filing-outline">
11184
 *     <!-- Tab 1 content -->
11185
 *   </ion-tab>
11186
 *
11187
 *   <ion-tab title="About" icon-on="ion-ios7-clock" icon-off="ion-ios7-clock-outline">
11188
 *     <!-- Tab 2 content -->
11189
 *   </ion-tab>
11190
 *
11191
 *   <ion-tab title="Settings" icon-on="ion-ios7-gear" icon-off="ion-ios7-gear-outline">
11192
 *     <!-- Tab 3 content -->
11193
 *   </ion-tab>
11194
 *
11195
 * </ion-tabs>
11196
 * ```
11197
 *
11198
 * @param {string=} delegate-handle The handle used to identify these tabs
11199
 * with {@link ionic.service:$ionicTabsDelegate}.
11200
 */
11201
 
11202
IonicModule
11203
.directive('ionTabs', [
11204
  '$ionicTabsDelegate',
11205
  '$ionicConfig',
11206
  '$ionicHistory',
11207
function($ionicTabsDelegate, $ionicConfig, $ionicHistory) {
11208
  return {
11209
    restrict: 'E',
11210
    scope: true,
11211
    controller: '$ionicTabs',
11212
    compile: function(tElement) {
11213
      //We cannot use regular transclude here because it breaks element.data()
11214
      //inheritance on compile
11215
      var innerElement = jqLite('<div class="tab-nav tabs">');
11216
      innerElement.append(tElement.contents());
11217
 
11218
      tElement.append(innerElement)
11219
              .addClass('tabs-' + $ionicConfig.tabs.position() + ' tabs-' + $ionicConfig.tabs.style());
11220
 
11221
      return { pre: prelink, post: postLink };
11222
      function prelink($scope, $element, $attr, tabsCtrl) {
11223
        var deregisterInstance = $ionicTabsDelegate._registerInstance(
11224
          tabsCtrl, $attr.delegateHandle, tabsCtrl.hasActiveScope
11225
        );
11226
 
11227
        tabsCtrl.$scope = $scope;
11228
        tabsCtrl.$element = $element;
11229
        tabsCtrl.$tabsElement = jqLite($element[0].querySelector('.tabs'));
11230
 
11231
        $scope.$watch(function() { return $element[0].className; }, function(value) {
11232
          var isTabsTop = value.indexOf('tabs-top') !== -1;
11233
          var isHidden = value.indexOf('tabs-item-hide') !== -1;
11234
          $scope.$hasTabs = !isTabsTop && !isHidden;
11235
          $scope.$hasTabsTop = isTabsTop && !isHidden;
11236
        });
11237
 
11238
        $scope.$on('$destroy', function() {
11239
          // variable to inform child tabs that they're all being blown away
11240
          // used so that while destorying an individual tab, each one
11241
          // doesn't select the next tab as the active one, which causes unnecessary
11242
          // loading of tab views when each will eventually all go away anyway
11243
          $scope.$tabsDestroy = true;
11244
          deregisterInstance();
11245
          tabsCtrl.$tabsElement = tabsCtrl.$element = tabsCtrl.$scope = innerElement = null;
11246
          delete $scope.$hasTabs;
11247
          delete $scope.$hasTabsTop;
11248
        });
11249
      }
11250
 
11251
      function postLink($scope, $element, $attr, tabsCtrl) {
11252
        if (!tabsCtrl.selectedTab()) {
11253
          // all the tabs have been added
11254
          // but one hasn't been selected yet
11255
          tabsCtrl.select(0);
11256
        }
11257
      }
11258
    }
11259
  };
11260
}]);
11261
 
11262
/**
11263
 * @ngdoc directive
11264
 * @name ionToggle
11265
 * @module ionic
11266
 * @codepen tfAzj
11267
 * @restrict E
11268
 *
11269
 * @description
11270
 * A toggle is an animated switch which binds a given model to a boolean.
11271
 *
11272
 * Allows dragging of the switch's nub.
11273
 *
11274
 * The toggle behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]) otherwise.
11275
 *
11276
 * @param toggle-class {string=} Sets the CSS class on the inner `label.toggle` element created by the directive.
11277
 *
11278
 * @usage
11279
 * Below is an example of a toggle directive which is wired up to the `airplaneMode` model
11280
 * and has the `toggle-calm` CSS class assigned to the inner element.
11281
 *
11282
 * ```html
11283
 * <ion-toggle ng-model="airplaneMode" toggle-class="toggle-calm">Airplane Mode</ion-toggle>
11284
 * ```
11285
 */
11286
IonicModule
11287
.directive('ionToggle', [
11288
  '$ionicGesture',
11289
  '$timeout',
11290
function($ionicGesture, $timeout) {
11291
 
11292
  return {
11293
    restrict: 'E',
11294
    replace: true,
11295
    require: '?ngModel',
11296
    transclude: true,
11297
    template:
11298
      '<div class="item item-toggle">' +
11299
        '<div ng-transclude></div>' +
11300
        '<label class="toggle">' +
11301
          '<input type="checkbox">' +
11302
          '<div class="track">' +
11303
            '<div class="handle"></div>' +
11304
          '</div>' +
11305
        '</label>' +
11306
      '</div>',
11307
 
11308
    compile: function(element, attr) {
11309
      var input = element.find('input');
11310
      forEach({
11311
        'name': attr.name,
11312
        'ng-value': attr.ngValue,
11313
        'ng-model': attr.ngModel,
11314
        'ng-checked': attr.ngChecked,
11315
        'ng-disabled': attr.ngDisabled,
11316
        'ng-true-value': attr.ngTrueValue,
11317
        'ng-false-value': attr.ngFalseValue,
11318
        'ng-change': attr.ngChange
11319
      }, function(value, name) {
11320
        if (isDefined(value)) {
11321
          input.attr(name, value);
11322
        }
11323
      });
11324
 
11325
      if(attr.toggleClass) {
11326
        element[0].getElementsByTagName('label')[0].classList.add(attr.toggleClass);
11327
      }
11328
 
11329
      return function($scope, $element, $attr) {
11330
         var el, checkbox, track, handle;
11331
 
11332
         el = $element[0].getElementsByTagName('label')[0];
11333
         checkbox = el.children[0];
11334
         track = el.children[1];
11335
         handle = track.children[0];
11336
 
11337
         var ngModelController = jqLite(checkbox).controller('ngModel');
11338
 
11339
         $scope.toggle = new ionic.views.Toggle({
11340
           el: el,
11341
           track: track,
11342
           checkbox: checkbox,
11343
           handle: handle,
11344
           onChange: function() {
11345
             if(checkbox.checked) {
11346
               ngModelController.$setViewValue(true);
11347
             } else {
11348
               ngModelController.$setViewValue(false);
11349
             }
11350
             $scope.$apply();
11351
           }
11352
         });
11353
 
11354
         $scope.$on('$destroy', function() {
11355
           $scope.toggle.destroy();
11356
         });
11357
      };
11358
    }
11359
 
11360
  };
11361
}]);
11362
 
11363
/**
11364
 * @ngdoc directive
11365
 * @name ionView
11366
 * @module ionic
11367
 * @restrict E
11368
 * @parent ionNavView
11369
 *
11370
 * @description
11371
 * A container for view content and any navigational and header bar information. When a view
11372
 * enters and exists its parent {@link ionic.directive:ionNavView}, the view also emits view
11373
 * information, such as its title, whether the back button should show or not, whether the
11374
 * corresponding {@link ionic.directive:ionNavBar} should show or not, which transition the view
11375
 * should use to animate, and which direction to animate.
11376
 *
11377
 * *Views are cached to improve performance.* When a view is navigated away from, its element is
11378
 * left in the DOM, and its scope is disconnected from the `$watch` cycle. When navigating to a
11379
 * view that is already cached, its scope is reconnected, and the existing element, which was
11380
 * left in the DOM, becomes active again. This can be disabled, or the maximum number of cached
11381
 * views changed in {@link ionic.directive:ionicConfig}, in the view's `$state` configuration, or
11382
 * as an attribute on the view itself (see below).
11383
 *
11384
 * @usage
11385
 * Below is an example where our page will load with a {@link ionic.directive:ionNavBar} containing
11386
 * "My Page" as the title.
11387
 *
11388
 * ```html
11389
 * <ion-nav-bar></ion-nav-bar>
11390
 * <ion-nav-view>
11391
 *   <ion-view view-title="My Page">
11392
 *     <ion-content>
11393
 *       Hello!
11394
 *     </ion-content>
11395
 *   </ion-view>
11396
 * </ion-nav-view>
11397
 * ```
11398
 *
11399
 * ## View LifeCycle and Events
11400
 *
11401
 * Views can be cached, which means *controllers normally only load once*, which may
11402
 * affect your controller logic. To know when a view has entered or left, events
11403
 * have been added that are emitted from the view's scope. These events also
11404
 * contain data about the view, such as the title and whether the back button should
11405
 * show. Also contained is transition data, such as the transition type and
11406
 * direction that will be or was used.
11407
 *
11408
 * <table class="table">
11409
 *  <tr>
11410
 *   <td><code>$ionicView.loaded</code></td>
11411
 *   <td>The view has loaded. This event only happens once per
11412
 * view being created and added to the DOM. If a view leaves but is cached,
11413
 * then this event will not fire again on a subsequent viewing. The loaded event
11414
 * is good place to put your setup code for the view; however, it is not the
11415
 * recommended event to listen to when a view becomes active.</td>
11416
 *  </tr>
11417
 *  <tr>
11418
 *   <td><code>$ionicView.enter</code></td>
11419
 *   <td>The view has fully entered and is now the active view.
11420
 * This event will fire, whether it was the first load or a cached view.</td>
11421
 *  </tr>
11422
 *  <tr>
11423
 *   <td><code>$ionicView.leave</code></td>
11424
 *   <td>The view has finished leaving and is no longer the
11425
 * active view. This event will fire, whether it is cached or destroyed.</td>
11426
 *  </tr>
11427
 *  <tr>
11428
 *   <td><code>$ionicView.beforeEnter</code></td>
11429
 *   <td>The view is about to enter and become the active view.</td>
11430
 *  </tr>
11431
 *  <tr>
11432
 *   <td><code>$ionicView.beforeLeave</code></td>
11433
 *   <td>The view is about to leave and no longer be the active view.</td>
11434
 *  </tr>
11435
 *  <tr>
11436
 *   <td><code>$ionicView.afterEnter</code></td>
11437
 *   <td>The view has fully entered and is now the active view.</td>
11438
 *  </tr>
11439
 *  <tr>
11440
 *   <td><code>$ionicView.afterLeave</code></td>
11441
 *   <td>The view has finished leaving and is no longer the active view.</td>
11442
 *  </tr>
11443
 *  <tr>
11444
 *   <td><code>$ionicView.unloaded</code></td>
11445
 *   <td>The view's controller has been destroyed and its element has been
11446
 * removed from the DOM.</td>
11447
 *  </tr>
11448
 * </table>
11449
 *
11450
 * ## Caching
11451
 *
11452
 * Caching can be disabled and enabled in multiple ways. By default, Ionic will
11453
 * cache a maximum of 10 views. You can optionally choose to disable caching at
11454
 * either an individual view basis, or by global configuration. Please see the
11455
 * _Caching_ section in {@link ionic.directive:ionNavView} for more info.
11456
 *
11457
 * @param {string=} view-title A text-only title to display on the parent {@link ionic.directive:ionNavBar}.
11458
 * For an HTML title, such as an image, see {@link ionic.directive:ionNavTitle} instead.
11459
 * @param {boolean=} cache-view If this view should be allowed to be cached or not.
11460
 * Please see the _Caching_ section in {@link ionic.directive:ionNavView} for
11461
 * more info. Default `true`
11462
 * @param {boolean=} hide-back-button Whether to hide the back button on the parent
11463
 * {@link ionic.directive:ionNavBar} by default.
11464
 * @param {boolean=} hide-nav-bar Whether to hide the parent
11465
 * {@link ionic.directive:ionNavBar} by default.
11466
 */
11467
IonicModule
11468
.directive('ionView', function() {
11469
  return {
11470
    restrict: 'EA',
11471
    priority: 1000,
11472
    controller: '$ionicView',
11473
    compile: function(tElement) {
11474
      tElement.addClass('pane');
11475
      tElement[0].removeAttribute('title');
11476
      return function link($scope, $element, $attrs, viewCtrl) {
11477
        viewCtrl.init();
11478
      };
11479
    }
11480
  };
11481
});
11482
 
11483
})();