commonroutesServer/app/www/lib/ionic/js/ionic-angular.js
app/www/lib/ionic/js/ionic-angular.jsRaw
1/*!
2 * Copyright 2015 Drifty Co.
3 * http://drifty.com/
4 *
5 * Ionic, v1.3.1
6 * A powerful HTML5 mobile app framework.
7 * http://ionicframework.com/
8 *
9 * By @maxlynch, @benjsperry, @adamdbradley <3
10 *
11 * Licensed under the MIT license. Please see LICENSE for more information.
12 *
13 */
14
15(function() {
16/* eslint no-unused-vars:0 */
17var IonicModule = angular.module('ionic', ['ngAnimate', 'ngSanitize', 'ui.router', 'ngIOS9UIWebViewPatch']),
18 extend = angular.extend,
19 forEach = angular.forEach,
20 isDefined = angular.isDefined,
21 isNumber = angular.isNumber,
22 isString = angular.isString,
23 jqLite = angular.element,
24 noop = angular.noop;
25
26/**
27 * @ngdoc service
28 * @name $ionicActionSheet
29 * @module ionic
30 * @description
31 * The Action Sheet is a slide-up pane that lets the user choose from a set of options.
32 * Dangerous options are highlighted in red and made obvious.
33 *
34 * There are easy ways to cancel out of the action sheet, such as tapping the backdrop or even
35 * hitting escape on the keyboard for desktop testing.
36 *
37 * 
38 *
39 * @usage
40 * To trigger an Action Sheet in your code, use the $ionicActionSheet service in your angular controllers:
41 *
42 * ```js
43 * angular.module('mySuperApp', ['ionic'])
44 * .controller(function($scope, $ionicActionSheet, $timeout) {
45 *
46 * // Triggered on a button click, or some other target
47 * $scope.show = function() {
48 *
49 * // Show the action sheet
50 * var hideSheet = $ionicActionSheet.show({
51 * buttons: [
52 * { text: '<b>Share</b> This' },
53 * { text: 'Move' }
54 * ],
55 * destructiveText: 'Delete',
56 * titleText: 'Modify your album',
57 * cancelText: 'Cancel',
58 * cancel: function() {
59 // add cancel code..
60 },
61 * buttonClicked: function(index) {
62 * return true;
63 * }
64 * });
65 *
66 * // For example's sake, hide the sheet after two seconds
67 * $timeout(function() {
68 * hideSheet();
69 * }, 2000);
70 *
71 * };
72 * });
73 * ```
74 *
75 */
76IonicModule
77.factory('$ionicActionSheet', [
78 '$rootScope',
79 '$compile',
80 '$animate',
81 '$timeout',
82 '$ionicTemplateLoader',
83 '$ionicPlatform',
84 '$ionicBody',
85 'IONIC_BACK_PRIORITY',
86function($rootScope, $compile, $animate, $timeout, $ionicTemplateLoader, $ionicPlatform, $ionicBody, IONIC_BACK_PRIORITY) {
87
88 return {
89 show: actionSheet
90 };
91
92 /**
93 * @ngdoc method
94 * @name $ionicActionSheet#show
95 * @description
96 * Load and return a new action sheet.
97 *
98 * A new isolated scope will be created for the
99 * action sheet and the new element will be appended into the body.
100 *
101 * @param {object} options The options for this ActionSheet. Properties:
102 *
103 * - `[Object]` `buttons` Which buttons to show. Each button is an object with a `text` field.
104 * - `{string}` `titleText` The title to show on the action sheet.
105 * - `{string=}` `cancelText` the text for a 'cancel' button on the action sheet.
106 * - `{string=}` `destructiveText` The text for a 'danger' on the action sheet.
107 * - `{function=}` `cancel` Called if the cancel button is pressed, the backdrop is tapped or
108 * the hardware back button is pressed.
109 * - `{function=}` `buttonClicked` Called when one of the non-destructive buttons is clicked,
110 * with the index of the button that was clicked and the button object. Return true to close
111 * the action sheet, or false to keep it opened.
112 * - `{function=}` `destructiveButtonClicked` Called when the destructive button is clicked.
113 * Return true to close the action sheet, or false to keep it opened.
114 * - `{boolean=}` `cancelOnStateChange` Whether to cancel the actionSheet when navigating
115 * to a new state. Default true.
116 * - `{string}` `cssClass` The custom CSS class name.
117 *
118 * @returns {function} `hideSheet` A function which, when called, hides & cancels the action sheet.
119 */
120 function actionSheet(opts) {
121 var scope = $rootScope.$new(true);
122
123 extend(scope, {
124 cancel: noop,
125 destructiveButtonClicked: noop,
126 buttonClicked: noop,
127 $deregisterBackButton: noop,
128 buttons: [],
129 cancelOnStateChange: true
130 }, opts || {});
131
132 function textForIcon(text) {
133 if (text && /icon/.test(text)) {
134 scope.$actionSheetHasIcon = true;
135 }
136 }
137
138 for (var x = 0; x < scope.buttons.length; x++) {
139 textForIcon(scope.buttons[x].text);
140 }
141 textForIcon(scope.cancelText);
142 textForIcon(scope.destructiveText);
143
144 // Compile the template
145 var element = scope.element = $compile('<ion-action-sheet ng-class="cssClass" buttons="buttons"></ion-action-sheet>')(scope);
146
147 // Grab the sheet element for animation
148 var sheetEl = jqLite(element[0].querySelector('.action-sheet-wrapper'));
149
150 var stateChangeListenDone = scope.cancelOnStateChange ?
151 $rootScope.$on('$stateChangeSuccess', function() { scope.cancel(); }) :
152 noop;
153
154 // removes the actionSheet from the screen
155 scope.removeSheet = function(done) {
156 if (scope.removed) return;
157
158 scope.removed = true;
159 sheetEl.removeClass('action-sheet-up');
160 $timeout(function() {
161 // wait to remove this due to a 300ms delay native
162 // click which would trigging whatever was underneath this
163 $ionicBody.removeClass('action-sheet-open');
164 }, 400);
165 scope.$deregisterBackButton();
166 stateChangeListenDone();
167
168 $animate.removeClass(element, 'active').then(function() {
169 scope.$destroy();
170 element.remove();
171 // scope.cancel.$scope is defined near the bottom
172 scope.cancel.$scope = sheetEl = null;
173 (done || noop)(opts.buttons);
174 });
175 };
176
177 scope.showSheet = function(done) {
178 if (scope.removed) return;
179
180 $ionicBody.append(element)
181 .addClass('action-sheet-open');
182
183 $animate.addClass(element, 'active').then(function() {
184 if (scope.removed) return;
185 (done || noop)();
186 });
187 $timeout(function() {
188 if (scope.removed) return;
189 sheetEl.addClass('action-sheet-up');
190 }, 20, false);
191 };
192
193 // registerBackButtonAction returns a callback to deregister the action
194 scope.$deregisterBackButton = $ionicPlatform.registerBackButtonAction(
195 function() {
196 $timeout(scope.cancel);
197 },
198 IONIC_BACK_PRIORITY.actionSheet
199 );
200
201 // called when the user presses the cancel button
202 scope.cancel = function() {
203 // after the animation is out, call the cancel callback
204 scope.removeSheet(opts.cancel);
205 };
206
207 scope.buttonClicked = function(index) {
208 // Check if the button click event returned true, which means
209 // we can close the action sheet
210 if (opts.buttonClicked(index, opts.buttons[index]) === true) {
211 scope.removeSheet();
212 }
213 };
214
215 scope.destructiveButtonClicked = function() {
216 // Check if the destructive button click event returned true, which means
217 // we can close the action sheet
218 if (opts.destructiveButtonClicked() === true) {
219 scope.removeSheet();
220 }
221 };
222
223 scope.showSheet();
224
225 // Expose the scope on $ionicActionSheet's return value for the sake
226 // of testing it.
227 scope.cancel.$scope = scope;
228
229 return scope.cancel;
230 }
231}]);
232
233
234jqLite.prototype.addClass = function(cssClasses) {
235 var x, y, cssClass, el, splitClasses, existingClasses;
236 if (cssClasses && cssClasses != 'ng-scope' && cssClasses != 'ng-isolate-scope') {
237 for (x = 0; x < this.length; x++) {
238 el = this[x];
239 if (el.setAttribute) {
240
241 if (cssClasses.indexOf(' ') < 0 && el.classList.add) {
242 el.classList.add(cssClasses);
243 } else {
244 existingClasses = (' ' + (el.getAttribute('class') || '') + ' ')
245 .replace(/[\n\t]/g, " ");
246 splitClasses = cssClasses.split(' ');
247
248 for (y = 0; y < splitClasses.length; y++) {
249 cssClass = splitClasses[y].trim();
250 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
251 existingClasses += cssClass + ' ';
252 }
253 }
254 el.setAttribute('class', existingClasses.trim());
255 }
256 }
257 }
258 }
259 return this;
260};
261
262jqLite.prototype.removeClass = function(cssClasses) {
263 var x, y, splitClasses, cssClass, el;
264 if (cssClasses) {
265 for (x = 0; x < this.length; x++) {
266 el = this[x];
267 if (el.getAttribute) {
268 if (cssClasses.indexOf(' ') < 0 && el.classList.remove) {
269 el.classList.remove(cssClasses);
270 } else {
271 splitClasses = cssClasses.split(' ');
272
273 for (y = 0; y < splitClasses.length; y++) {
274 cssClass = splitClasses[y];
275 el.setAttribute('class', (
276 (" " + (el.getAttribute('class') || '') + " ")
277 .replace(/[\n\t]/g, " ")
278 .replace(" " + cssClass.trim() + " ", " ")).trim()
279 );
280 }
281 }
282 }
283 }
284 }
285 return this;
286};
287
288/**
289 * @ngdoc service
290 * @name $ionicBackdrop
291 * @module ionic
292 * @description
293 * Shows and hides a backdrop over the UI. Appears behind popups, loading,
294 * and other overlays.
295 *
296 * Often, multiple UI components require a backdrop, but only one backdrop is
297 * ever needed in the DOM at a time.
298 *
299 * Therefore, each component that requires the backdrop to be shown calls
300 * `$ionicBackdrop.retain()` when it wants the backdrop, then `$ionicBackdrop.release()`
301 * when it is done with the backdrop.
302 *
303 * For each time `retain` is called, the backdrop will be shown until `release` is called.
304 *
305 * For example, if `retain` is called three times, the backdrop will be shown until `release`
306 * is called three times.
307 *
308 * **Notes:**
309 * - The backdrop service will broadcast 'backdrop.shown' and 'backdrop.hidden' events from the root scope,
310 * this is useful for alerting native components not in html.
311 *
312 * @usage
313 *
314 * ```js
315 * function MyController($scope, $ionicBackdrop, $timeout, $rootScope) {
316 * //Show a backdrop for one second
317 * $scope.action = function() {
318 * $ionicBackdrop.retain();
319 * $timeout(function() {
320 * $ionicBackdrop.release();
321 * }, 1000);
322 * };
323 *
324 * // Execute action on backdrop disappearing
325 * $scope.$on('backdrop.hidden', function() {
326 * // Execute action
327 * });
328 *
329 * // Execute action on backdrop appearing
330 * $scope.$on('backdrop.shown', function() {
331 * // Execute action
332 * });
333 *
334 * }
335 * ```
336 */
337IonicModule
338.factory('$ionicBackdrop', [
339 '$document', '$timeout', '$$rAF', '$rootScope',
340function($document, $timeout, $$rAF, $rootScope) {
341
342 var el = jqLite('<div class="backdrop">');
343 var backdropHolds = 0;
344
345 $document[0].body.appendChild(el[0]);
346
347 return {
348 /**
349 * @ngdoc method
350 * @name $ionicBackdrop#retain
351 * @description Retains the backdrop.
352 */
353 retain: retain,
354 /**
355 * @ngdoc method
356 * @name $ionicBackdrop#release
357 * @description
358 * Releases the backdrop.
359 */
360 release: release,
361
362 getElement: getElement,
363
364 // exposed for testing
365 _element: el
366 };
367
368 function retain() {
369 backdropHolds++;
370 if (backdropHolds === 1) {
371 el.addClass('visible');
372 $rootScope.$broadcast('backdrop.shown');
373 $$rAF(function() {
374 // If we're still at >0 backdropHolds after async...
375 if (backdropHolds >= 1) el.addClass('active');
376 });
377 }
378 }
379 function release() {
380 if (backdropHolds === 1) {
381 el.removeClass('active');
382 $rootScope.$broadcast('backdrop.hidden');
383 $timeout(function() {
384 // If we're still at 0 backdropHolds after async...
385 if (backdropHolds === 0) el.removeClass('visible');
386 }, 400, false);
387 }
388 backdropHolds = Math.max(0, backdropHolds - 1);
389 }
390
391 function getElement() {
392 return el;
393 }
394
395}]);
396
397/**
398 * @private
399 */
400IonicModule
401.factory('$ionicBind', ['$parse', '$interpolate', function($parse, $interpolate) {
402 var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
403 return function(scope, attrs, bindDefinition) {
404 forEach(bindDefinition || {}, function(definition, scopeName) {
405 //Adapted from angular.js $compile
406 var match = definition.match(LOCAL_REGEXP) || [],
407 attrName = match[3] || scopeName,
408 mode = match[1], // @, =, or &
409 parentGet,
410 unwatch;
411
412 switch (mode) {
413 case '@':
414 if (!attrs[attrName]) {
415 return;
416 }
417 attrs.$observe(attrName, function(value) {
418 scope[scopeName] = value;
419 });
420 // we trigger an interpolation to ensure
421 // the value is there for use immediately
422 if (attrs[attrName]) {
423 scope[scopeName] = $interpolate(attrs[attrName])(scope);
424 }
425 break;
426
427 case '=':
428 if (!attrs[attrName]) {
429 return;
430 }
431 unwatch = scope.$watch(attrs[attrName], function(value) {
432 scope[scopeName] = value;
433 });
434 //Destroy parent scope watcher when this scope is destroyed
435 scope.$on('$destroy', unwatch);
436 break;
437
438 case '&':
439 /* jshint -W044 */
440 if (attrs[attrName] && attrs[attrName].match(RegExp(scopeName + '\(.*?\)'))) {
441 throw new Error('& expression binding "' + scopeName + '" looks like it will recursively call "' +
442 attrs[attrName] + '" and cause a stack overflow! Please choose a different scopeName.');
443 }
444 parentGet = $parse(attrs[attrName]);
445 scope[scopeName] = function(locals) {
446 return parentGet(scope, locals);
447 };
448 break;
449 }
450 });
451 };
452}]);
453
454/**
455 * @ngdoc service
456 * @name $ionicBody
457 * @module ionic
458 * @description An angular utility service to easily and efficiently
459 * add and remove CSS classes from the document's body element.
460 */
461IonicModule
462.factory('$ionicBody', ['$document', function($document) {
463 return {
464 /**
465 * @ngdoc method
466 * @name $ionicBody#addClass
467 * @description Add a class to the document's body element.
468 * @param {string} class Each argument will be added to the body element.
469 * @returns {$ionicBody} The $ionicBody service so methods can be chained.
470 */
471 addClass: function() {
472 for (var x = 0; x < arguments.length; x++) {
473 $document[0].body.classList.add(arguments[x]);
474 }
475 return this;
476 },
477 /**
478 * @ngdoc method
479 * @name $ionicBody#removeClass
480 * @description Remove a class from the document's body element.
481 * @param {string} class Each argument will be removed from the body element.
482 * @returns {$ionicBody} The $ionicBody service so methods can be chained.
483 */
484 removeClass: function() {
485 for (var x = 0; x < arguments.length; x++) {
486 $document[0].body.classList.remove(arguments[x]);
487 }
488 return this;
489 },
490 /**
491 * @ngdoc method
492 * @name $ionicBody#enableClass
493 * @description Similar to the `add` method, except the first parameter accepts a boolean
494 * value determining if the class should be added or removed. Rather than writing user code,
495 * such as "if true then add the class, else then remove the class", this method can be
496 * given a true or false value which reduces redundant code.
497 * @param {boolean} shouldEnableClass A true/false value if the class should be added or removed.
498 * @param {string} class Each remaining argument would be added or removed depending on
499 * the first argument.
500 * @returns {$ionicBody} The $ionicBody service so methods can be chained.
501 */
502 enableClass: function(shouldEnableClass) {
503 var args = Array.prototype.slice.call(arguments).slice(1);
504 if (shouldEnableClass) {
505 this.addClass.apply(this, args);
506 } else {
507 this.removeClass.apply(this, args);
508 }
509 return this;
510 },
511 /**
512 * @ngdoc method
513 * @name $ionicBody#append
514 * @description Append a child to the document's body.
515 * @param {element} element The element to be appended to the body. The passed in element
516 * can be either a jqLite element, or a DOM element.
517 * @returns {$ionicBody} The $ionicBody service so methods can be chained.
518 */
519 append: function(ele) {
520 $document[0].body.appendChild(ele.length ? ele[0] : ele);
521 return this;
522 },
523 /**
524 * @ngdoc method
525 * @name $ionicBody#get
526 * @description Get the document's body element.
527 * @returns {element} Returns the document's body element.
528 */
529 get: function() {
530 return $document[0].body;
531 }
532 };
533}]);
534
535IonicModule
536.factory('$ionicClickBlock', [
537 '$document',
538 '$ionicBody',
539 '$timeout',
540function($document, $ionicBody, $timeout) {
541 var CSS_HIDE = 'click-block-hide';
542 var cbEle, fallbackTimer, pendingShow;
543
544 function preventClick(ev) {
545 ev.preventDefault();
546 ev.stopPropagation();
547 }
548
549 function addClickBlock() {
550 if (pendingShow) {
551 if (cbEle) {
552 cbEle.classList.remove(CSS_HIDE);
553 } else {
554 cbEle = $document[0].createElement('div');
555 cbEle.className = 'click-block';
556 $ionicBody.append(cbEle);
557 cbEle.addEventListener('touchstart', preventClick);
558 cbEle.addEventListener('mousedown', preventClick);
559 }
560 pendingShow = false;
561 }
562 }
563
564 function removeClickBlock() {
565 cbEle && cbEle.classList.add(CSS_HIDE);
566 }
567
568 return {
569 show: function(autoExpire) {
570 pendingShow = true;
571 $timeout.cancel(fallbackTimer);
572 fallbackTimer = $timeout(this.hide, autoExpire || 310, false);
573 addClickBlock();
574 },
575 hide: function() {
576 pendingShow = false;
577 $timeout.cancel(fallbackTimer);
578 removeClickBlock();
579 }
580 };
581}]);
582
583/**
584 * @ngdoc service
585 * @name $ionicGesture
586 * @module ionic
587 * @description An angular service exposing ionic
588 * {@link ionic.utility:ionic.EventController}'s gestures.
589 */
590IonicModule
591.factory('$ionicGesture', [function() {
592 return {
593 /**
594 * @ngdoc method
595 * @name $ionicGesture#on
596 * @description Add an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#onGesture}.
597 * @param {string} eventType The gesture event to listen for.
598 * @param {function(e)} callback The function to call when the gesture
599 * happens.
600 * @param {element} $element The angular element to listen for the event on.
601 * @param {object} options object.
602 * @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on).
603 */
604 on: function(eventType, cb, $element, options) {
605 return window.ionic.onGesture(eventType, cb, $element[0], options);
606 },
607 /**
608 * @ngdoc method
609 * @name $ionicGesture#off
610 * @description Remove an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#offGesture}.
611 * @param {ionic.Gesture} gesture The gesture that should be removed.
612 * @param {string} eventType The gesture event to remove the listener for.
613 * @param {function(e)} callback The listener to remove.
614 */
615 off: function(gesture, eventType, cb) {
616 return window.ionic.offGesture(gesture, eventType, cb);
617 }
618 };
619}]);
620
621/**
622 * @ngdoc service
623 * @name $ionicHistory
624 * @module ionic
625 * @description
626 * $ionicHistory keeps track of views as the user navigates through an app. Similar to the way a
627 * browser behaves, an Ionic app is able to keep track of the previous view, the current view, and
628 * the forward view (if there is one). However, a typical web browser only keeps track of one
629 * history stack in a linear fashion.
630 *
631 * Unlike a traditional browser environment, apps and webapps have parallel independent histories,
632 * such as with tabs. Should a user navigate few pages deep on one tab, and then switch to a new
633 * tab and back, the back button relates not to the previous tab, but to the previous pages
634 * visited within _that_ tab.
635 *
636 * `$ionicHistory` facilitates this parallel history architecture.
637 */
638
639IonicModule
640.factory('$ionicHistory', [
641 '$rootScope',
642 '$state',
643 '$location',
644 '$window',
645 '$timeout',
646 '$ionicViewSwitcher',
647 '$ionicNavViewDelegate',
648function($rootScope, $state, $location, $window, $timeout, $ionicViewSwitcher, $ionicNavViewDelegate) {
649
650 // history actions while navigating views
651 var ACTION_INITIAL_VIEW = 'initialView';
652 var ACTION_NEW_VIEW = 'newView';
653 var ACTION_MOVE_BACK = 'moveBack';
654 var ACTION_MOVE_FORWARD = 'moveForward';
655
656 // direction of navigation
657 var DIRECTION_BACK = 'back';
658 var DIRECTION_FORWARD = 'forward';
659 var DIRECTION_ENTER = 'enter';
660 var DIRECTION_EXIT = 'exit';
661 var DIRECTION_SWAP = 'swap';
662 var DIRECTION_NONE = 'none';
663
664 var stateChangeCounter = 0;
665 var lastStateId, nextViewOptions, deregisterStateChangeListener, nextViewExpireTimer, forcedNav;
666
667 var viewHistory = {
668 histories: { root: { historyId: 'root', parentHistoryId: null, stack: [], cursor: -1 } },
669 views: {},
670 backView: null,
671 forwardView: null,
672 currentView: null
673 };
674
675 var View = function() {};
676 View.prototype.initialize = function(data) {
677 if (data) {
678 for (var name in data) this[name] = data[name];
679 return this;
680 }
681 return null;
682 };
683 View.prototype.go = function() {
684
685 if (this.stateName) {
686 return $state.go(this.stateName, this.stateParams);
687 }
688
689 if (this.url && this.url !== $location.url()) {
690
691 if (viewHistory.backView === this) {
692 return $window.history.go(-1);
693 } else if (viewHistory.forwardView === this) {
694 return $window.history.go(1);
695 }
696
697 $location.url(this.url);
698 }
699
700 return null;
701 };
702 View.prototype.destroy = function() {
703 if (this.scope) {
704 this.scope.$destroy && this.scope.$destroy();
705 this.scope = null;
706 }
707 };
708
709
710 function getViewById(viewId) {
711 return (viewId ? viewHistory.views[ viewId ] : null);
712 }
713
714 function getBackView(view) {
715 return (view ? getViewById(view.backViewId) : null);
716 }
717
718 function getForwardView(view) {
719 return (view ? getViewById(view.forwardViewId) : null);
720 }
721
722 function getHistoryById(historyId) {
723 return (historyId ? viewHistory.histories[ historyId ] : null);
724 }
725
726 function getHistory(scope) {
727 var histObj = getParentHistoryObj(scope);
728
729 if (!viewHistory.histories[ histObj.historyId ]) {
730 // this history object exists in parent scope, but doesn't
731 // exist in the history data yet
732 viewHistory.histories[ histObj.historyId ] = {
733 historyId: histObj.historyId,
734 parentHistoryId: getParentHistoryObj(histObj.scope.$parent).historyId,
735 stack: [],
736 cursor: -1
737 };
738 }
739 return getHistoryById(histObj.historyId);
740 }
741
742 function getParentHistoryObj(scope) {
743 var parentScope = scope;
744 while (parentScope) {
745 if (parentScope.hasOwnProperty('$historyId')) {
746 // this parent scope has a historyId
747 return { historyId: parentScope.$historyId, scope: parentScope };
748 }
749 // nothing found keep climbing up
750 parentScope = parentScope.$parent;
751 }
752 // no history for the parent, use the root
753 return { historyId: 'root', scope: $rootScope };
754 }
755
756 function setNavViews(viewId) {
757 viewHistory.currentView = getViewById(viewId);
758 viewHistory.backView = getBackView(viewHistory.currentView);
759 viewHistory.forwardView = getForwardView(viewHistory.currentView);
760 }
761
762 function getCurrentStateId() {
763 var id;
764 if ($state && $state.current && $state.current.name) {
765 id = $state.current.name;
766 if ($state.params) {
767 for (var key in $state.params) {
768 if ($state.params.hasOwnProperty(key) && $state.params[key]) {
769 id += "_" + key + "=" + $state.params[key];
770 }
771 }
772 }
773 return id;
774 }
775 // if something goes wrong make sure its got a unique stateId
776 return ionic.Utils.nextUid();
777 }
778
779 function getCurrentStateParams() {
780 var rtn;
781 if ($state && $state.params) {
782 for (var key in $state.params) {
783 if ($state.params.hasOwnProperty(key)) {
784 rtn = rtn || {};
785 rtn[key] = $state.params[key];
786 }
787 }
788 }
789 return rtn;
790 }
791
792
793 return {
794
795 register: function(parentScope, viewLocals) {
796
797 var currentStateId = getCurrentStateId(),
798 hist = getHistory(parentScope),
799 currentView = viewHistory.currentView,
800 backView = viewHistory.backView,
801 forwardView = viewHistory.forwardView,
802 viewId = null,
803 action = null,
804 direction = DIRECTION_NONE,
805 historyId = hist.historyId,
806 url = $location.url(),
807 tmp, x, ele;
808
809 if (lastStateId !== currentStateId) {
810 lastStateId = currentStateId;
811 stateChangeCounter++;
812 }
813
814 if (forcedNav) {
815 // we've previously set exactly what to do
816 viewId = forcedNav.viewId;
817 action = forcedNav.action;
818 direction = forcedNav.direction;
819 forcedNav = null;
820
821 } else if (backView && backView.stateId === currentStateId) {
822 // they went back one, set the old current view as a forward view
823 viewId = backView.viewId;
824 historyId = backView.historyId;
825 action = ACTION_MOVE_BACK;
826 if (backView.historyId === currentView.historyId) {
827 // went back in the same history
828 direction = DIRECTION_BACK;
829
830 } else if (currentView) {
831 direction = DIRECTION_EXIT;
832
833 tmp = getHistoryById(backView.historyId);
834 if (tmp && tmp.parentHistoryId === currentView.historyId) {
835 direction = DIRECTION_ENTER;
836
837 } else {
838 tmp = getHistoryById(currentView.historyId);
839 if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
840 direction = DIRECTION_SWAP;
841 }
842 }
843 }
844
845 } else if (forwardView && forwardView.stateId === currentStateId) {
846 // they went to the forward one, set the forward view to no longer a forward view
847 viewId = forwardView.viewId;
848 historyId = forwardView.historyId;
849 action = ACTION_MOVE_FORWARD;
850 if (forwardView.historyId === currentView.historyId) {
851 direction = DIRECTION_FORWARD;
852
853 } else if (currentView) {
854 direction = DIRECTION_EXIT;
855
856 if (currentView.historyId === hist.parentHistoryId) {
857 direction = DIRECTION_ENTER;
858
859 } else {
860 tmp = getHistoryById(currentView.historyId);
861 if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
862 direction = DIRECTION_SWAP;
863 }
864 }
865 }
866
867 tmp = getParentHistoryObj(parentScope);
868 if (forwardView.historyId && tmp.scope) {
869 // if a history has already been created by the forward view then make sure it stays the same
870 tmp.scope.$historyId = forwardView.historyId;
871 historyId = forwardView.historyId;
872 }
873
874 } else if (currentView && currentView.historyId !== historyId &&
875 hist.cursor > -1 && hist.stack.length > 0 && hist.cursor < hist.stack.length &&
876 hist.stack[hist.cursor].stateId === currentStateId) {
877 // they just changed to a different history and the history already has views in it
878 var switchToView = hist.stack[hist.cursor];
879 viewId = switchToView.viewId;
880 historyId = switchToView.historyId;
881 action = ACTION_MOVE_BACK;
882 direction = DIRECTION_SWAP;
883
884 tmp = getHistoryById(currentView.historyId);
885 if (tmp && tmp.parentHistoryId === historyId) {
886 direction = DIRECTION_EXIT;
887
888 } else {
889 tmp = getHistoryById(historyId);
890 if (tmp && tmp.parentHistoryId === currentView.historyId) {
891 direction = DIRECTION_ENTER;
892 }
893 }
894
895 // if switching to a different history, and the history of the view we're switching
896 // to has an existing back view from a different history than itself, then
897 // it's back view would be better represented using the current view as its back view
898 tmp = getViewById(switchToView.backViewId);
899 if (tmp && switchToView.historyId !== tmp.historyId) {
900 // the new view is being removed from it's old position in the history and being placed at the top,
901 // so we need to update any views that reference it as a backview, otherwise there will be infinitely loops
902 var viewIds = Object.keys(viewHistory.views);
903 viewIds.forEach(function(viewId) {
904 var view = viewHistory.views[viewId];
905 if ( view.backViewId === switchToView.viewId ) {
906 view.backViewId = null;
907 }
908 });
909
910 hist.stack[hist.cursor].backViewId = currentView.viewId;
911 }
912
913 } else {
914
915 // create an element from the viewLocals template
916 ele = $ionicViewSwitcher.createViewEle(viewLocals);
917 if (this.isAbstractEle(ele, viewLocals)) {
918 return {
919 action: 'abstractView',
920 direction: DIRECTION_NONE,
921 ele: ele
922 };
923 }
924
925 // set a new unique viewId
926 viewId = ionic.Utils.nextUid();
927
928 if (currentView) {
929 // set the forward view if there is a current view (ie: if its not the first view)
930 currentView.forwardViewId = viewId;
931
932 action = ACTION_NEW_VIEW;
933
934 // check if there is a new forward view within the same history
935 if (forwardView && currentView.stateId !== forwardView.stateId &&
936 currentView.historyId === forwardView.historyId) {
937 // they navigated to a new view but the stack already has a forward view
938 // since its a new view remove any forwards that existed
939 tmp = getHistoryById(forwardView.historyId);
940 if (tmp) {
941 // the forward has a history
942 for (x = tmp.stack.length - 1; x >= forwardView.index; x--) {
943 // starting from the end destroy all forwards in this history from this point
944 var stackItem = tmp.stack[x];
945 stackItem && stackItem.destroy && stackItem.destroy();
946 tmp.stack.splice(x);
947 }
948 historyId = forwardView.historyId;
949 }
950 }
951
952 // its only moving forward if its in the same history
953 if (hist.historyId === currentView.historyId) {
954 direction = DIRECTION_FORWARD;
955
956 } else if (currentView.historyId !== hist.historyId) {
957 // DB: this is a new view in a different tab
958 direction = DIRECTION_ENTER;
959
960 tmp = getHistoryById(currentView.historyId);
961 if (tmp && tmp.parentHistoryId === hist.parentHistoryId) {
962 direction = DIRECTION_SWAP;
963
964 } else {
965 tmp = getHistoryById(tmp.parentHistoryId);
966 if (tmp && tmp.historyId === hist.historyId) {
967 direction = DIRECTION_EXIT;
968 }
969 }
970 }
971
972 } else {
973 // there's no current view, so this must be the initial view
974 action = ACTION_INITIAL_VIEW;
975 }
976
977 if (stateChangeCounter < 2) {
978 // views that were spun up on the first load should not animate
979 direction = DIRECTION_NONE;
980 }
981
982 // add the new view
983 viewHistory.views[viewId] = this.createView({
984 viewId: viewId,
985 index: hist.stack.length,
986 historyId: hist.historyId,
987 backViewId: (currentView && currentView.viewId ? currentView.viewId : null),
988 forwardViewId: null,
989 stateId: currentStateId,
990 stateName: this.currentStateName(),
991 stateParams: getCurrentStateParams(),
992 url: url,
993 canSwipeBack: canSwipeBack(ele, viewLocals)
994 });
995
996 // add the new view to this history's stack
997 hist.stack.push(viewHistory.views[viewId]);
998 }
999
1000 deregisterStateChangeListener && deregisterStateChangeListener();
1001 $timeout.cancel(nextViewExpireTimer);
1002 if (nextViewOptions) {
1003 if (nextViewOptions.disableAnimate) direction = DIRECTION_NONE;
1004 if (nextViewOptions.disableBack) viewHistory.views[viewId].backViewId = null;
1005 if (nextViewOptions.historyRoot) {
1006 for (x = 0; x < hist.stack.length; x++) {
1007 if (hist.stack[x].viewId === viewId) {
1008 hist.stack[x].index = 0;
1009 hist.stack[x].backViewId = hist.stack[x].forwardViewId = null;
1010 } else {
1011 delete viewHistory.views[hist.stack[x].viewId];
1012 }
1013 }
1014 hist.stack = [viewHistory.views[viewId]];
1015 }
1016 nextViewOptions = null;
1017 }
1018
1019 setNavViews(viewId);
1020
1021 if (viewHistory.backView && historyId == viewHistory.backView.historyId && currentStateId == viewHistory.backView.stateId && url == viewHistory.backView.url) {
1022 for (x = 0; x < hist.stack.length; x++) {
1023 if (hist.stack[x].viewId == viewId) {
1024 action = 'dupNav';
1025 direction = DIRECTION_NONE;
1026 if (x > 0) {
1027 hist.stack[x - 1].forwardViewId = null;
1028 }
1029 viewHistory.forwardView = null;
1030 viewHistory.currentView.index = viewHistory.backView.index;
1031 viewHistory.currentView.backViewId = viewHistory.backView.backViewId;
1032 viewHistory.backView = getBackView(viewHistory.backView);
1033 hist.stack.splice(x, 1);
1034 break;
1035 }
1036 }
1037 }
1038
1039 hist.cursor = viewHistory.currentView.index;
1040
1041 return {
1042 viewId: viewId,
1043 action: action,
1044 direction: direction,
1045 historyId: historyId,
1046 enableBack: this.enabledBack(viewHistory.currentView),
1047 isHistoryRoot: (viewHistory.currentView.index === 0),
1048 ele: ele
1049 };
1050 },
1051
1052 registerHistory: function(scope) {
1053 scope.$historyId = ionic.Utils.nextUid();
1054 },
1055
1056 createView: function(data) {
1057 var newView = new View();
1058 return newView.initialize(data);
1059 },
1060
1061 getViewById: getViewById,
1062
1063 /**
1064 * @ngdoc method
1065 * @name $ionicHistory#viewHistory
1066 * @description The app's view history data, such as all the views and histories, along
1067 * with how they are ordered and linked together within the navigation stack.
1068 * @returns {object} Returns an object containing the apps view history data.
1069 */
1070 viewHistory: function() {
1071 return viewHistory;
1072 },
1073
1074 /**
1075 * @ngdoc method
1076 * @name $ionicHistory#currentView
1077 * @description The app's current view.
1078 * @returns {object} Returns the current view.
1079 */
1080 currentView: function(view) {
1081 if (arguments.length) {
1082 viewHistory.currentView = view;
1083 }
1084 return viewHistory.currentView;
1085 },
1086
1087 /**
1088 * @ngdoc method
1089 * @name $ionicHistory#currentHistoryId
1090 * @description The ID of the history stack which is the parent container of the current view.
1091 * @returns {string} Returns the current history ID.
1092 */
1093 currentHistoryId: function() {
1094 return viewHistory.currentView ? viewHistory.currentView.historyId : null;
1095 },
1096
1097 /**
1098 * @ngdoc method
1099 * @name $ionicHistory#currentTitle
1100 * @description Gets and sets the current view's title.
1101 * @param {string=} val The title to update the current view with.
1102 * @returns {string} Returns the current view's title.
1103 */
1104 currentTitle: function(val) {
1105 if (viewHistory.currentView) {
1106 if (arguments.length) {
1107 viewHistory.currentView.title = val;
1108 }
1109 return viewHistory.currentView.title;
1110 }
1111 },
1112
1113 /**
1114 * @ngdoc method
1115 * @name $ionicHistory#backView
1116 * @description Returns the view that was before the current view in the history stack.
1117 * If the user navigated from View A to View B, then View A would be the back view, and
1118 * View B would be the current view.
1119 * @returns {object} Returns the back view.
1120 */
1121 backView: function(view) {
1122 if (arguments.length) {
1123 viewHistory.backView = view;
1124 }
1125 return viewHistory.backView;
1126 },
1127
1128 /**
1129 * @ngdoc method
1130 * @name $ionicHistory#backTitle
1131 * @description Gets the back view's title.
1132 * @returns {string} Returns the back view's title.
1133 */
1134 backTitle: function(view) {
1135 var backView = (view && getViewById(view.backViewId)) || viewHistory.backView;
1136 return backView && backView.title;
1137 },
1138
1139 /**
1140 * @ngdoc method
1141 * @name $ionicHistory#forwardView
1142 * @description Returns the view that was in front of the current view in the history stack.
1143 * A forward view would exist if the user navigated from View A to View B, then
1144 * navigated back to View A. At this point then View B would be the forward view, and View
1145 * A would be the current view.
1146 * @returns {object} Returns the forward view.
1147 */
1148 forwardView: function(view) {
1149 if (arguments.length) {
1150 viewHistory.forwardView = view;
1151 }
1152 return viewHistory.forwardView;
1153 },
1154
1155 /**
1156 * @ngdoc method
1157 * @name $ionicHistory#currentStateName
1158 * @description Returns the current state name.
1159 * @returns {string}
1160 */
1161 currentStateName: function() {
1162 return ($state && $state.current ? $state.current.name : null);
1163 },
1164
1165 isCurrentStateNavView: function(navView) {
1166 return !!($state && $state.current && $state.current.views && $state.current.views[navView]);
1167 },
1168
1169 goToHistoryRoot: function(historyId) {
1170 if (historyId) {
1171 var hist = getHistoryById(historyId);
1172 if (hist && hist.stack.length) {
1173 if (viewHistory.currentView && viewHistory.currentView.viewId === hist.stack[0].viewId) {
1174 return;
1175 }
1176 forcedNav = {
1177 viewId: hist.stack[0].viewId,
1178 action: ACTION_MOVE_BACK,
1179 direction: DIRECTION_BACK
1180 };
1181 hist.stack[0].go();
1182 }
1183 }
1184 },
1185
1186 /**
1187 * @ngdoc method
1188 * @name $ionicHistory#goBack
1189 * @param {number=} backCount Optional negative integer setting how many views to go
1190 * back. By default it'll go back one view by using the value `-1`. To go back two
1191 * views you would use `-2`. If the number goes farther back than the number of views
1192 * in the current history's stack then it'll go to the first view in the current history's
1193 * stack. If the number is zero or greater then it'll do nothing. It also does not
1194 * cross history stacks, meaning it can only go as far back as the current history.
1195 * @description Navigates the app to the back view, if a back view exists.
1196 */
1197 goBack: function(backCount) {
1198 if (isDefined(backCount) && backCount !== -1) {
1199 if (backCount > -1) return;
1200
1201 var currentHistory = viewHistory.histories[this.currentHistoryId()];
1202 var newCursor = currentHistory.cursor + backCount + 1;
1203 if (newCursor < 1) {
1204 newCursor = 1;
1205 }
1206
1207 currentHistory.cursor = newCursor;
1208 setNavViews(currentHistory.stack[newCursor].viewId);
1209
1210 var cursor = newCursor - 1;
1211 var clearStateIds = [];
1212 var fwdView = getViewById(currentHistory.stack[cursor].forwardViewId);
1213 while (fwdView) {
1214 clearStateIds.push(fwdView.stateId || fwdView.viewId);
1215 cursor++;
1216 if (cursor >= currentHistory.stack.length) break;
1217 fwdView = getViewById(currentHistory.stack[cursor].forwardViewId);
1218 }
1219
1220 var self = this;
1221 if (clearStateIds.length) {
1222 $timeout(function() {
1223 self.clearCache(clearStateIds);
1224 }, 300);
1225 }
1226 }
1227
1228 viewHistory.backView && viewHistory.backView.go();
1229 },
1230
1231 /**
1232 * @ngdoc method
1233 * @name $ionicHistory#removeBackView
1234 * @description Remove the previous view from the history completely, including the
1235 * cached element and scope (if they exist).
1236 */
1237 removeBackView: function() {
1238 var self = this;
1239 var currentHistory = viewHistory.histories[this.currentHistoryId()];
1240 var currentCursor = currentHistory.cursor;
1241
1242 var currentView = currentHistory.stack[currentCursor];
1243 var backView = currentHistory.stack[currentCursor - 1];
1244 var replacementView = currentHistory.stack[currentCursor - 2];
1245
1246 // fail if we dont have enough views in the history
1247 if (!backView || !replacementView) {
1248 return;
1249 }
1250
1251 // remove the old backView and the cached element/scope
1252 currentHistory.stack.splice(currentCursor - 1, 1);
1253 self.clearCache([backView.viewId]);
1254 // make the replacementView and currentView point to each other (bypass the old backView)
1255 currentView.backViewId = replacementView.viewId;
1256 currentView.index = currentView.index - 1;
1257 replacementView.forwardViewId = currentView.viewId;
1258 // update the cursor and set new backView
1259 viewHistory.backView = replacementView;
1260 currentHistory.currentCursor += -1;
1261 },
1262
1263 enabledBack: function(view) {
1264 var backView = getBackView(view);
1265 return !!(backView && backView.historyId === view.historyId);
1266 },
1267
1268 /**
1269 * @ngdoc method
1270 * @name $ionicHistory#clearHistory
1271 * @description Clears out the app's entire history, except for the current view.
1272 */
1273 clearHistory: function() {
1274 var
1275 histories = viewHistory.histories,
1276 currentView = viewHistory.currentView;
1277
1278 if (histories) {
1279 for (var historyId in histories) {
1280
1281 if (histories[historyId].stack) {
1282 histories[historyId].stack = [];
1283 histories[historyId].cursor = -1;
1284 }
1285
1286 if (currentView && currentView.historyId === historyId) {
1287 currentView.backViewId = currentView.forwardViewId = null;
1288 histories[historyId].stack.push(currentView);
1289 } else if (histories[historyId].destroy) {
1290 histories[historyId].destroy();
1291 }
1292
1293 }
1294 }
1295
1296 for (var viewId in viewHistory.views) {
1297 if (viewId !== currentView.viewId) {
1298 delete viewHistory.views[viewId];
1299 }
1300 }
1301
1302 if (currentView) {
1303 setNavViews(currentView.viewId);
1304 }
1305 },
1306
1307 /**
1308 * @ngdoc method
1309 * @name $ionicHistory#clearCache
1310 * @return promise
1311 * @description Removes all cached views within every {@link ionic.directive:ionNavView}.
1312 * This both removes the view element from the DOM, and destroy it's scope.
1313 */
1314 clearCache: function(stateIds) {
1315 return $timeout(function() {
1316 $ionicNavViewDelegate._instances.forEach(function(instance) {
1317 instance.clearCache(stateIds);
1318 });
1319 });
1320 },
1321
1322 /**
1323 * @ngdoc method
1324 * @name $ionicHistory#nextViewOptions
1325 * @description Sets options for the next view. This method can be useful to override
1326 * certain view/transition defaults right before a view transition happens. For example,
1327 * the {@link ionic.directive:menuClose} directive uses this method internally to ensure
1328 * an animated view transition does not happen when a side menu is open, and also sets
1329 * the next view as the root of its history stack. After the transition these options
1330 * are set back to null.
1331 *
1332 * Available options:
1333 *
1334 * * `disableAnimate`: Do not animate the next transition.
1335 * * `disableBack`: The next view should forget its back view, and set it to null.
1336 * * `historyRoot`: The next view should become the root view in its history stack.
1337 *
1338 * ```js
1339 * $ionicHistory.nextViewOptions({
1340 * disableAnimate: true,
1341 * disableBack: true
1342 * });
1343 * ```
1344 */
1345 nextViewOptions: function(opts) {
1346 deregisterStateChangeListener && deregisterStateChangeListener();
1347 if (arguments.length) {
1348 $timeout.cancel(nextViewExpireTimer);
1349 if (opts === null) {
1350 nextViewOptions = opts;
1351 } else {
1352 nextViewOptions = nextViewOptions || {};
1353 extend(nextViewOptions, opts);
1354 if (nextViewOptions.expire) {
1355 deregisterStateChangeListener = $rootScope.$on('$stateChangeSuccess', function() {
1356 nextViewExpireTimer = $timeout(function() {
1357 nextViewOptions = null;
1358 }, nextViewOptions.expire);
1359 });
1360 }
1361 }
1362 }
1363 return nextViewOptions;
1364 },
1365
1366 isAbstractEle: function(ele, viewLocals) {
1367 if (viewLocals && viewLocals.$$state && viewLocals.$$state.self['abstract']) {
1368 return true;
1369 }
1370 return !!(ele && (isAbstractTag(ele) || isAbstractTag(ele.children())));
1371 },
1372
1373 isActiveScope: function(scope) {
1374 if (!scope) return false;
1375
1376 var climbScope = scope;
1377 var currentHistoryId = this.currentHistoryId();
1378 var foundHistoryId;
1379
1380 while (climbScope) {
1381 if (climbScope.$$disconnected) {
1382 return false;
1383 }
1384
1385 if (!foundHistoryId && climbScope.hasOwnProperty('$historyId')) {
1386 foundHistoryId = true;
1387 }
1388
1389 if (currentHistoryId) {
1390 if (climbScope.hasOwnProperty('$historyId') && currentHistoryId == climbScope.$historyId) {
1391 return true;
1392 }
1393 if (climbScope.hasOwnProperty('$activeHistoryId')) {
1394 if (currentHistoryId == climbScope.$activeHistoryId) {
1395 if (climbScope.hasOwnProperty('$historyId')) {
1396 return true;
1397 }
1398 if (!foundHistoryId) {
1399 return true;
1400 }
1401 }
1402 }
1403 }
1404
1405 if (foundHistoryId && climbScope.hasOwnProperty('$activeHistoryId')) {
1406 foundHistoryId = false;
1407 }
1408
1409 climbScope = climbScope.$parent;
1410 }
1411
1412 return currentHistoryId ? currentHistoryId == 'root' : true;
1413 }
1414
1415 };
1416
1417 function isAbstractTag(ele) {
1418 return ele && ele.length && /ion-side-menus|ion-tabs/i.test(ele[0].tagName);
1419 }
1420
1421 function canSwipeBack(ele, viewLocals) {
1422 if (viewLocals && viewLocals.$$state && viewLocals.$$state.self.canSwipeBack === false) {
1423 return false;
1424 }
1425 if (ele && ele.attr('can-swipe-back') === 'false') {
1426 return false;
1427 }
1428 var eleChild = ele.find('ion-view');
1429 if (eleChild && eleChild.attr('can-swipe-back') === 'false') {
1430 return false;
1431 }
1432 return true;
1433 }
1434
1435}])
1436
1437.run([
1438 '$rootScope',
1439 '$state',
1440 '$location',
1441 '$document',
1442 '$ionicPlatform',
1443 '$ionicHistory',
1444 'IONIC_BACK_PRIORITY',
1445function($rootScope, $state, $location, $document, $ionicPlatform, $ionicHistory, IONIC_BACK_PRIORITY) {
1446
1447 // always reset the keyboard state when change stage
1448 $rootScope.$on('$ionicView.beforeEnter', function() {
1449 ionic.keyboard && ionic.keyboard.hide && ionic.keyboard.hide();
1450 });
1451
1452 $rootScope.$on('$ionicHistory.change', function(e, data) {
1453 if (!data) return null;
1454
1455 var viewHistory = $ionicHistory.viewHistory();
1456
1457 var hist = (data.historyId ? viewHistory.histories[ data.historyId ] : null);
1458 if (hist && hist.cursor > -1 && hist.cursor < hist.stack.length) {
1459 // the history they're going to already exists
1460 // go to it's last view in its stack
1461 var view = hist.stack[ hist.cursor ];
1462 return view.go(data);
1463 }
1464
1465 // this history does not have a URL, but it does have a uiSref
1466 // figure out its URL from the uiSref
1467 if (!data.url && data.uiSref) {
1468 data.url = $state.href(data.uiSref);
1469 }
1470
1471 if (data.url) {
1472 // don't let it start with a #, messes with $location.url()
1473 if (data.url.indexOf('#') === 0) {
1474 data.url = data.url.replace('#', '');
1475 }
1476 if (data.url !== $location.url()) {
1477 // we've got a good URL, ready GO!
1478 $location.url(data.url);
1479 }
1480 }
1481 });
1482
1483 $rootScope.$ionicGoBack = function(backCount) {
1484 $ionicHistory.goBack(backCount);
1485 };
1486
1487 // Set the document title when a new view is shown
1488 $rootScope.$on('$ionicView.afterEnter', function(ev, data) {
1489 if (data && data.title) {
1490 $document[0].title = data.title;
1491 }
1492 });
1493
1494 // Triggered when devices with a hardware back button (Android) is clicked by the user
1495 // This is a Cordova/Phonegap platform specifc method
1496 function onHardwareBackButton(e) {
1497 var backView = $ionicHistory.backView();
1498 if (backView) {
1499 // there is a back view, go to it
1500 backView.go();
1501 } else {
1502 // there is no back view, so close the app instead
1503 ionic.Platform.exitApp();
1504 }
1505 e.preventDefault();
1506 return false;
1507 }
1508 $ionicPlatform.registerBackButtonAction(
1509 onHardwareBackButton,
1510 IONIC_BACK_PRIORITY.view
1511 );
1512
1513}]);
1514
1515/**
1516 * @ngdoc provider
1517 * @name $ionicConfigProvider
1518 * @module ionic
1519 * @description
1520 * Ionic automatically takes platform configurations into account to adjust things like what
1521 * transition style to use and whether tab icons should show on the top or bottom. For example,
1522 * iOS will move forward by transitioning the entering view from right to center and the leaving
1523 * view from center to left. However, Android will transition with the entering view going from
1524 * bottom to center, covering the previous view, which remains stationary. It should be noted
1525 * that when a platform is not iOS or Android, then it'll default to iOS. So if you are
1526 * developing on a desktop browser, it's going to take on iOS default configs.
1527 *
1528 * These configs can be changed using the `$ionicConfigProvider` during the configuration phase
1529 * of your app. Additionally, `$ionicConfig` can also set and get config values during the run
1530 * phase and within the app itself.
1531 *
1532 * By default, all base config variables are set to `'platform'`, which means it'll take on the
1533 * default config of the platform on which it's running. Config variables can be set at this
1534 * level so all platforms follow the same setting, rather than its platform config.
1535 * The following code would set the same config variable for all platforms:
1536 *
1537 * ```js
1538 * $ionicConfigProvider.views.maxCache(10);
1539 * ```
1540 *
1541 * Additionally, each platform can have it's own config within the `$ionicConfigProvider.platform`
1542 * property. The config below would only apply to Android devices.
1543 *
1544 * ```js
1545 * $ionicConfigProvider.platform.android.views.maxCache(5);
1546 * ```
1547 *
1548 * @usage
1549 * ```js
1550 * var myApp = angular.module('reallyCoolApp', ['ionic']);
1551 *
1552 * myApp.config(function($ionicConfigProvider) {
1553 * $ionicConfigProvider.views.maxCache(5);
1554 *
1555 * // note that you can also chain configs
1556 * $ionicConfigProvider.backButton.text('Go Back').icon('ion-chevron-left');
1557 * });
1558 * ```
1559 */
1560
1561/**
1562 * @ngdoc method
1563 * @name $ionicConfigProvider#views.transition
1564 * @description Animation style when transitioning between views. Default `platform`.
1565 *
1566 * @param {string} transition Which style of view transitioning to use.
1567 *
1568 * * `platform`: Dynamically choose the correct transition style depending on the platform
1569 * the app is running from. If the platform is not `ios` or `android` then it will default
1570 * to `ios`.
1571 * * `ios`: iOS style transition.
1572 * * `android`: Android style transition.
1573 * * `none`: Do not perform animated transitions.
1574 *
1575 * @returns {string} value
1576 */
1577
1578/**
1579 * @ngdoc method
1580 * @name $ionicConfigProvider#views.maxCache
1581 * @description Maximum number of view elements to cache in the DOM. When the max number is
1582 * exceeded, the view with the longest time period since it was accessed is removed. Views that
1583 * stay in the DOM cache the view's scope, current state, and scroll position. The scope is
1584 * disconnected from the `$watch` cycle when it is cached and reconnected when it enters again.
1585 * When the maximum cache is `0`, the leaving view's element will be removed from the DOM after
1586 * each view transition, and the next time the same view is shown, it will have to re-compile,
1587 * attach to the DOM, and link the element again. This disables caching, in effect.
1588 * @param {number} maxNumber Maximum number of views to retain. Default `10`.
1589 * @returns {number} How many views Ionic will hold onto until the a view is removed.
1590 */
1591
1592/**
1593 * @ngdoc method
1594 * @name $ionicConfigProvider#views.forwardCache
1595 * @description By default, when navigating, views that were recently visited are cached, and
1596 * the same instance data and DOM elements are referenced when navigating back. However, when
1597 * navigating back in the history, the "forward" views are removed from the cache. If you
1598 * navigate forward to the same view again, it'll create a new DOM element and controller
1599 * instance. Basically, any forward views are reset each time. Set this config to `true` to have
1600 * forward views cached and not reset on each load.
1601 * @param {boolean} value
1602 * @returns {boolean}
1603 */
1604
1605 /**
1606 * @ngdoc method
1607 * @name $ionicConfigProvider#views.swipeBackEnabled
1608 * @description By default on iOS devices, swipe to go back functionality is enabled by default.
1609 * This method can be used to disable it globally, or on a per-view basis.
1610 * Note: This functionality is only supported on iOS.
1611 * @param {boolean} value
1612 * @returns {boolean}
1613 */
1614
1615/**
1616 * @ngdoc method
1617 * @name $ionicConfigProvider#scrolling.jsScrolling
1618 * @description Whether to use JS or Native scrolling. Defaults to native scrolling. Setting this to
1619 * `true` has the same effect as setting each `ion-content` to have `overflow-scroll='false'`.
1620 * @param {boolean} value Defaults to `false` as of Ionic 1.2
1621 * @returns {boolean}
1622 */
1623
1624/**
1625 * @ngdoc method
1626 * @name $ionicConfigProvider#backButton.icon
1627 * @description Back button icon.
1628 * @param {string} value
1629 * @returns {string}
1630 */
1631
1632/**
1633 * @ngdoc method
1634 * @name $ionicConfigProvider#backButton.text
1635 * @description Back button text.
1636 * @param {string} value Defaults to `Back`.
1637 * @returns {string}
1638 */
1639
1640/**
1641 * @ngdoc method
1642 * @name $ionicConfigProvider#backButton.previousTitleText
1643 * @description If the previous title text should become the back button text. This
1644 * is the default for iOS.
1645 * @param {boolean} value
1646 * @returns {boolean}
1647 */
1648
1649/**
1650 * @ngdoc method
1651 * @name $ionicConfigProvider#form.checkbox
1652 * @description Checkbox style. Android defaults to `square` and iOS defaults to `circle`.
1653 * @param {string} value
1654 * @returns {string}
1655 */
1656
1657/**
1658 * @ngdoc method
1659 * @name $ionicConfigProvider#form.toggle
1660 * @description Toggle item style. Android defaults to `small` and iOS defaults to `large`.
1661 * @param {string} value
1662 * @returns {string}
1663 */
1664
1665/**
1666 * @ngdoc method
1667 * @name $ionicConfigProvider#spinner.icon
1668 * @description Default spinner icon to use.
1669 * @param {string} value Can be: `android`, `ios`, `ios-small`, `bubbles`, `circles`, `crescent`,
1670 * `dots`, `lines`, `ripple`, or `spiral`.
1671 * @returns {string}
1672 */
1673
1674/**
1675 * @ngdoc method
1676 * @name $ionicConfigProvider#tabs.style
1677 * @description Tab style. Android defaults to `striped` and iOS defaults to `standard`.
1678 * @param {string} value Available values include `striped` and `standard`.
1679 * @returns {string}
1680 */
1681
1682/**
1683 * @ngdoc method
1684 * @name $ionicConfigProvider#tabs.position
1685 * @description Tab position. Android defaults to `top` and iOS defaults to `bottom`.
1686 * @param {string} value Available values include `top` and `bottom`.
1687 * @returns {string}
1688 */
1689
1690/**
1691 * @ngdoc method
1692 * @name $ionicConfigProvider#templates.maxPrefetch
1693 * @description Sets the maximum number of templates to prefetch from the templateUrls defined in
1694 * $stateProvider.state. If set to `0`, the user will have to wait
1695 * for a template to be fetched the first time when navigating to a new page. Default `30`.
1696 * @param {integer} value Max number of template to prefetch from the templateUrls defined in
1697 * `$stateProvider.state()`.
1698 * @returns {integer}
1699 */
1700
1701/**
1702 * @ngdoc method
1703 * @name $ionicConfigProvider#navBar.alignTitle
1704 * @description Which side of the navBar to align the title. Default `center`.
1705 *
1706 * @param {string} value side of the navBar to align the title.
1707 *
1708 * * `platform`: Dynamically choose the correct title style depending on the platform
1709 * the app is running from. If the platform is `ios`, it will default to `center`.
1710 * If the platform is `android`, it will default to `left`. If the platform is not
1711 * `ios` or `android`, it will default to `center`.
1712 *
1713 * * `left`: Left align the title in the navBar
1714 * * `center`: Center align the title in the navBar
1715 * * `right`: Right align the title in the navBar.
1716 *
1717 * @returns {string} value
1718 */
1719
1720/**
1721 * @ngdoc method
1722 * @name $ionicConfigProvider#navBar.positionPrimaryButtons
1723 * @description Which side of the navBar to align the primary navBar buttons. Default `left`.
1724 *
1725 * @param {string} value side of the navBar to align the primary navBar buttons.
1726 *
1727 * * `platform`: Dynamically choose the correct title style depending on the platform
1728 * the app is running from. If the platform is `ios`, it will default to `left`.
1729 * If the platform is `android`, it will default to `right`. If the platform is not
1730 * `ios` or `android`, it will default to `left`.
1731 *
1732 * * `left`: Left align the primary navBar buttons in the navBar
1733 * * `right`: Right align the primary navBar buttons in the navBar.
1734 *
1735 * @returns {string} value
1736 */
1737
1738/**
1739 * @ngdoc method
1740 * @name $ionicConfigProvider#navBar.positionSecondaryButtons
1741 * @description Which side of the navBar to align the secondary navBar buttons. Default `right`.
1742 *
1743 * @param {string} value side of the navBar to align the secondary navBar buttons.
1744 *
1745 * * `platform`: Dynamically choose the correct title style depending on the platform
1746 * the app is running from. If the platform is `ios`, it will default to `right`.
1747 * If the platform is `android`, it will default to `right`. If the platform is not
1748 * `ios` or `android`, it will default to `right`.
1749 *
1750 * * `left`: Left align the secondary navBar buttons in the navBar
1751 * * `right`: Right align the secondary navBar buttons in the navBar.
1752 *
1753 * @returns {string} value
1754 */
1755
1756IonicModule
1757.provider('$ionicConfig', function() {
1758
1759 var provider = this;
1760 provider.platform = {};
1761 var PLATFORM = 'platform';
1762
1763 var configProperties = {
1764 views: {
1765 maxCache: PLATFORM,
1766 forwardCache: PLATFORM,
1767 transition: PLATFORM,
1768 swipeBackEnabled: PLATFORM,
1769 swipeBackHitWidth: PLATFORM
1770 },
1771 navBar: {
1772 alignTitle: PLATFORM,
1773 positionPrimaryButtons: PLATFORM,
1774 positionSecondaryButtons: PLATFORM,
1775 transition: PLATFORM
1776 },
1777 backButton: {
1778 icon: PLATFORM,
1779 text: PLATFORM,
1780 previousTitleText: PLATFORM
1781 },
1782 form: {
1783 checkbox: PLATFORM,
1784 toggle: PLATFORM
1785 },
1786 scrolling: {
1787 jsScrolling: PLATFORM
1788 },
1789 spinner: {
1790 icon: PLATFORM
1791 },
1792 tabs: {
1793 style: PLATFORM,
1794 position: PLATFORM
1795 },
1796 templates: {
1797 maxPrefetch: PLATFORM
1798 },
1799 platform: {}
1800 };
1801 createConfig(configProperties, provider, '');
1802
1803
1804
1805 // Default
1806 // -------------------------
1807 setPlatformConfig('default', {
1808
1809 views: {
1810 maxCache: 10,
1811 forwardCache: false,
1812 transition: 'ios',
1813 swipeBackEnabled: true,
1814 swipeBackHitWidth: 45
1815 },
1816
1817 navBar: {
1818 alignTitle: 'center',
1819 positionPrimaryButtons: 'left',
1820 positionSecondaryButtons: 'right',
1821 transition: 'view'
1822 },
1823
1824 backButton: {
1825 icon: 'ion-ios-arrow-back',
1826 text: 'Back',
1827 previousTitleText: true
1828 },
1829
1830 form: {
1831 checkbox: 'circle',
1832 toggle: 'large'
1833 },
1834
1835 scrolling: {
1836 jsScrolling: true
1837 },
1838
1839 spinner: {
1840 icon: 'ios'
1841 },
1842
1843 tabs: {
1844 style: 'standard',
1845 position: 'bottom'
1846 },
1847
1848 templates: {
1849 maxPrefetch: 30
1850 }
1851
1852 });
1853
1854
1855
1856 // iOS (it is the default already)
1857 // -------------------------
1858 setPlatformConfig('ios', {});
1859
1860
1861
1862 // Android
1863 // -------------------------
1864 setPlatformConfig('android', {
1865
1866 views: {
1867 transition: 'android',
1868 swipeBackEnabled: false
1869 },
1870
1871 navBar: {
1872 alignTitle: 'left',
1873 positionPrimaryButtons: 'right',
1874 positionSecondaryButtons: 'right'
1875 },
1876
1877 backButton: {
1878 icon: 'ion-android-arrow-back',
1879 text: false,
1880 previousTitleText: false
1881 },
1882
1883 form: {
1884 checkbox: 'square',
1885 toggle: 'small'
1886 },
1887
1888 spinner: {
1889 icon: 'android'
1890 },
1891
1892 tabs: {
1893 style: 'striped',
1894 position: 'top'
1895 },
1896
1897 scrolling: {
1898 jsScrolling: false
1899 }
1900 });
1901
1902 // Windows Phone
1903 // -------------------------
1904 setPlatformConfig('windowsphone', {
1905 //scrolling: {
1906 // jsScrolling: false
1907 //}
1908 spinner: {
1909 icon: 'android'
1910 }
1911 });
1912
1913
1914 provider.transitions = {
1915 views: {},
1916 navBar: {}
1917 };
1918
1919
1920 // iOS Transitions
1921 // -----------------------
1922 provider.transitions.views.ios = function(enteringEle, leavingEle, direction, shouldAnimate) {
1923
1924 function setStyles(ele, opacity, x, boxShadowOpacity) {
1925 var css = {};
1926 css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : 0;
1927 css.opacity = opacity;
1928 if (boxShadowOpacity > -1) {
1929 css.boxShadow = '0 0 10px rgba(0,0,0,' + (d.shouldAnimate ? boxShadowOpacity * 0.45 : 0.3) + ')';
1930 }
1931 css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';
1932 ionic.DomUtil.cachedStyles(ele, css);
1933 }
1934
1935 var d = {
1936 run: function(step) {
1937 if (direction == 'forward') {
1938 setStyles(enteringEle, 1, (1 - step) * 99, 1 - step); // starting at 98% prevents a flicker
1939 setStyles(leavingEle, (1 - 0.1 * step), step * -33, -1);
1940
1941 } else if (direction == 'back') {
1942 setStyles(enteringEle, (1 - 0.1 * (1 - step)), (1 - step) * -33, -1);
1943 setStyles(leavingEle, 1, step * 100, 1 - step);
1944
1945 } else {
1946 // swap, enter, exit
1947 setStyles(enteringEle, 1, 0, -1);
1948 setStyles(leavingEle, 0, 0, -1);
1949 }
1950 },
1951 shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
1952 };
1953
1954 return d;
1955 };
1956
1957 provider.transitions.navBar.ios = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {
1958
1959 function setStyles(ctrl, opacity, titleX, backTextX) {
1960 var css = {};
1961 css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : '0ms';
1962 css.opacity = opacity === 1 ? '' : opacity;
1963
1964 ctrl.setCss('buttons-left', css);
1965 ctrl.setCss('buttons-right', css);
1966 ctrl.setCss('back-button', css);
1967
1968 css[ionic.CSS.TRANSFORM] = 'translate3d(' + backTextX + 'px,0,0)';
1969 ctrl.setCss('back-text', css);
1970
1971 css[ionic.CSS.TRANSFORM] = 'translate3d(' + titleX + 'px,0,0)';
1972 ctrl.setCss('title', css);
1973 }
1974
1975 function enter(ctrlA, ctrlB, step) {
1976 if (!ctrlA || !ctrlB) return;
1977 var titleX = (ctrlA.titleTextX() + ctrlA.titleWidth()) * (1 - step);
1978 var backTextX = (ctrlB && (ctrlB.titleTextX() - ctrlA.backButtonTextLeft()) * (1 - step)) || 0;
1979 setStyles(ctrlA, step, titleX, backTextX);
1980 }
1981
1982 function leave(ctrlA, ctrlB, step) {
1983 if (!ctrlA || !ctrlB) return;
1984 var titleX = (-(ctrlA.titleTextX() - ctrlB.backButtonTextLeft()) - (ctrlA.titleLeftRight())) * step;
1985 setStyles(ctrlA, 1 - step, titleX, 0);
1986 }
1987
1988 var d = {
1989 run: function(step) {
1990 var enteringHeaderCtrl = enteringHeaderBar.controller();
1991 var leavingHeaderCtrl = leavingHeaderBar && leavingHeaderBar.controller();
1992 if (d.direction == 'back') {
1993 leave(enteringHeaderCtrl, leavingHeaderCtrl, 1 - step);
1994 enter(leavingHeaderCtrl, enteringHeaderCtrl, 1 - step);
1995 } else {
1996 enter(enteringHeaderCtrl, leavingHeaderCtrl, step);
1997 leave(leavingHeaderCtrl, enteringHeaderCtrl, step);
1998 }
1999 },
2000 direction: direction,
2001 shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
2002 };
2003
2004 return d;
2005 };
2006
2007
2008 // Android Transitions
2009 // -----------------------
2010
2011 provider.transitions.views.android = function(enteringEle, leavingEle, direction, shouldAnimate) {
2012 shouldAnimate = shouldAnimate && (direction == 'forward' || direction == 'back');
2013
2014 function setStyles(ele, x, opacity) {
2015 var css = {};
2016 css[ionic.CSS.TRANSITION_DURATION] = d.shouldAnimate ? '' : 0;
2017 css[ionic.CSS.TRANSFORM] = 'translate3d(' + x + '%,0,0)';
2018 css.opacity = opacity;
2019 ionic.DomUtil.cachedStyles(ele, css);
2020 }
2021
2022 var d = {
2023 run: function(step) {
2024 if (direction == 'forward') {
2025 setStyles(enteringEle, (1 - step) * 99, 1); // starting at 98% prevents a flicker
2026 setStyles(leavingEle, step * -100, 1);
2027
2028 } else if (direction == 'back') {
2029 setStyles(enteringEle, (1 - step) * -100, 1);
2030 setStyles(leavingEle, step * 100, 1);
2031
2032 } else {
2033 // swap, enter, exit
2034 setStyles(enteringEle, 0, 1);
2035 setStyles(leavingEle, 0, 0);
2036 }
2037 },
2038 shouldAnimate: shouldAnimate
2039 };
2040
2041 return d;
2042 };
2043
2044 provider.transitions.navBar.android = function(enteringHeaderBar, leavingHeaderBar, direction, shouldAnimate) {
2045
2046 function setStyles(ctrl, opacity) {
2047 if (!ctrl) return;
2048 var css = {};
2049 css.opacity = opacity === 1 ? '' : opacity;
2050
2051 ctrl.setCss('buttons-left', css);
2052 ctrl.setCss('buttons-right', css);
2053 ctrl.setCss('back-button', css);
2054 ctrl.setCss('back-text', css);
2055 ctrl.setCss('title', css);
2056 }
2057
2058 return {
2059 run: function(step) {
2060 setStyles(enteringHeaderBar.controller(), step);
2061 setStyles(leavingHeaderBar && leavingHeaderBar.controller(), 1 - step);
2062 },
2063 shouldAnimate: shouldAnimate && (direction == 'forward' || direction == 'back')
2064 };
2065 };
2066
2067
2068 // No Transition
2069 // -----------------------
2070
2071 provider.transitions.views.none = function(enteringEle, leavingEle) {
2072 return {
2073 run: function(step) {
2074 provider.transitions.views.android(enteringEle, leavingEle, false, false).run(step);
2075 },
2076 shouldAnimate: false
2077 };
2078 };
2079
2080 provider.transitions.navBar.none = function(enteringHeaderBar, leavingHeaderBar) {
2081 return {
2082 run: function(step) {
2083 provider.transitions.navBar.ios(enteringHeaderBar, leavingHeaderBar, false, false).run(step);
2084 provider.transitions.navBar.android(enteringHeaderBar, leavingHeaderBar, false, false).run(step);
2085 },
2086 shouldAnimate: false
2087 };
2088 };
2089
2090
2091 // private: used to set platform configs
2092 function setPlatformConfig(platformName, platformConfigs) {
2093 configProperties.platform[platformName] = platformConfigs;
2094 provider.platform[platformName] = {};
2095
2096 addConfig(configProperties, configProperties.platform[platformName]);
2097
2098 createConfig(configProperties.platform[platformName], provider.platform[platformName], '');
2099 }
2100
2101
2102 // private: used to recursively add new platform configs
2103 function addConfig(configObj, platformObj) {
2104 for (var n in configObj) {
2105 if (n != PLATFORM && configObj.hasOwnProperty(n)) {
2106 if (angular.isObject(configObj[n])) {
2107 if (!isDefined(platformObj[n])) {
2108 platformObj[n] = {};
2109 }
2110 addConfig(configObj[n], platformObj[n]);
2111
2112 } else if (!isDefined(platformObj[n])) {
2113 platformObj[n] = null;
2114 }
2115 }
2116 }
2117 }
2118
2119
2120 // private: create methods for each config to get/set
2121 function createConfig(configObj, providerObj, platformPath) {
2122 forEach(configObj, function(value, namespace) {
2123
2124 if (angular.isObject(configObj[namespace])) {
2125 // recursively drill down the config object so we can create a method for each one
2126 providerObj[namespace] = {};
2127 createConfig(configObj[namespace], providerObj[namespace], platformPath + '.' + namespace);
2128
2129 } else {
2130 // create a method for the provider/config methods that will be exposed
2131 providerObj[namespace] = function(newValue) {
2132 if (arguments.length) {
2133 configObj[namespace] = newValue;
2134 return providerObj;
2135 }
2136 if (configObj[namespace] == PLATFORM) {
2137 // if the config is set to 'platform', then get this config's platform value
2138 var platformConfig = stringObj(configProperties.platform, ionic.Platform.platform() + platformPath + '.' + namespace);
2139 if (platformConfig || platformConfig === false) {
2140 return platformConfig;
2141 }
2142 // didnt find a specific platform config, now try the default
2143 return stringObj(configProperties.platform, 'default' + platformPath + '.' + namespace);
2144 }
2145 return configObj[namespace];
2146 };
2147 }
2148
2149 });
2150 }
2151
2152 function stringObj(obj, str) {
2153 str = str.split(".");
2154 for (var i = 0; i < str.length; i++) {
2155 if (obj && isDefined(obj[str[i]])) {
2156 obj = obj[str[i]];
2157 } else {
2158 return null;
2159 }
2160 }
2161 return obj;
2162 }
2163
2164 provider.setPlatformConfig = setPlatformConfig;
2165
2166
2167 // private: Service definition for internal Ionic use
2168 /**
2169 * @ngdoc service
2170 * @name $ionicConfig
2171 * @module ionic
2172 * @private
2173 */
2174 provider.$get = function() {
2175 return provider;
2176 };
2177})
2178// Fix for URLs in Cordova apps on Windows Phone
2179// http://blogs.msdn.com/b/msdn_answers/archive/2015/02/10/
2180// running-cordova-apps-on-windows-and-windows-phone-8-1-using-ionic-angularjs-and-other-frameworks.aspx
2181.config(['$compileProvider', function($compileProvider) {
2182 $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|sms|tel|geo|ftp|mailto|file|ghttps?|ms-appx-web|ms-appx|x-wmapp0):/);
2183 $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|content|blob|ms-appx|ms-appx-web|x-wmapp0):|data:image\//);
2184}]);
2185
2186
2187var LOADING_TPL =
2188 '<div class="loading-container">' +
2189 '<div class="loading">' +
2190 '</div>' +
2191 '</div>';
2192
2193/**
2194 * @ngdoc service
2195 * @name $ionicLoading
2196 * @module ionic
2197 * @description
2198 * An overlay that can be used to indicate activity while blocking user
2199 * interaction.
2200 *
2201 * @usage
2202 * ```js
2203 * angular.module('LoadingApp', ['ionic'])
2204 * .controller('LoadingCtrl', function($scope, $ionicLoading) {
2205 * $scope.show = function() {
2206 * $ionicLoading.show({
2207 * template: 'Loading...'
2208 * }).then(function(){
2209 * console.log("The loading indicator is now displayed");
2210 * });
2211 * };
2212 * $scope.hide = function(){
2213 * $ionicLoading.hide().then(function(){
2214 * console.log("The loading indicator is now hidden");
2215 * });
2216 * };
2217 * });
2218 * ```
2219 */
2220/**
2221 * @ngdoc object
2222 * @name $ionicLoadingConfig
2223 * @module ionic
2224 * @description
2225 * Set the default options to be passed to the {@link ionic.service:$ionicLoading} service.
2226 *
2227 * @usage
2228 * ```js
2229 * var app = angular.module('myApp', ['ionic'])
2230 * app.constant('$ionicLoadingConfig', {
2231 * template: 'Default Loading Template...'
2232 * });
2233 * app.controller('AppCtrl', function($scope, $ionicLoading) {
2234 * $scope.showLoading = function() {
2235 * //options default to values in $ionicLoadingConfig
2236 * $ionicLoading.show().then(function(){
2237 * console.log("The loading indicator is now displayed");
2238 * });
2239 * };
2240 * });
2241 * ```
2242 */
2243IonicModule
2244.constant('$ionicLoadingConfig', {
2245 template: '<ion-spinner></ion-spinner>'
2246})
2247.factory('$ionicLoading', [
2248 '$ionicLoadingConfig',
2249 '$ionicBody',
2250 '$ionicTemplateLoader',
2251 '$ionicBackdrop',
2252 '$timeout',
2253 '$q',
2254 '$log',
2255 '$compile',
2256 '$ionicPlatform',
2257 '$rootScope',
2258 'IONIC_BACK_PRIORITY',
2259function($ionicLoadingConfig, $ionicBody, $ionicTemplateLoader, $ionicBackdrop, $timeout, $q, $log, $compile, $ionicPlatform, $rootScope, IONIC_BACK_PRIORITY) {
2260
2261 var loaderInstance;
2262 //default values
2263 var deregisterBackAction = noop;
2264 var deregisterStateListener1 = noop;
2265 var deregisterStateListener2 = noop;
2266 var loadingShowDelay = $q.when();
2267
2268 return {
2269 /**
2270 * @ngdoc method
2271 * @name $ionicLoading#show
2272 * @description Shows a loading indicator. If the indicator is already shown,
2273 * it will set the options given and keep the indicator shown.
2274 * @returns {promise} A promise which is resolved when the loading indicator is presented.
2275 * @param {object} opts The options for the loading indicator. Available properties:
2276 * - `{string=}` `template` The html content of the indicator.
2277 * - `{string=}` `templateUrl` The url of an html template to load as the content of the indicator.
2278 * - `{object=}` `scope` The scope to be a child of. Default: creates a child of $rootScope.
2279 * - `{boolean=}` `noBackdrop` Whether to hide the backdrop. By default it will be shown.
2280 * - `{boolean=}` `hideOnStateChange` Whether to hide the loading spinner when navigating
2281 * to a new state. Default false.
2282 * - `{number=}` `delay` How many milliseconds to delay showing the indicator. By default there is no delay.
2283 * - `{number=}` `duration` How many milliseconds to wait until automatically
2284 * hiding the indicator. By default, the indicator will be shown until `.hide()` is called.
2285 */
2286 show: showLoader,
2287 /**
2288 * @ngdoc method
2289 * @name $ionicLoading#hide
2290 * @description Hides the loading indicator, if shown.
2291 * @returns {promise} A promise which is resolved when the loading indicator is hidden.
2292 */
2293 hide: hideLoader,
2294 /**
2295 * @private for testing
2296 */
2297 _getLoader: getLoader
2298 };
2299
2300 function getLoader() {
2301 if (!loaderInstance) {
2302 loaderInstance = $ionicTemplateLoader.compile({
2303 template: LOADING_TPL,
2304 appendTo: $ionicBody.get()
2305 })
2306 .then(function(self) {
2307 self.show = function(options) {
2308 var templatePromise = options.templateUrl ?
2309 $ionicTemplateLoader.load(options.templateUrl) :
2310 //options.content: deprecated
2311 $q.when(options.template || options.content || '');
2312
2313 self.scope = options.scope || self.scope;
2314
2315 if (!self.isShown) {
2316 //options.showBackdrop: deprecated
2317 self.hasBackdrop = !options.noBackdrop && options.showBackdrop !== false;
2318 if (self.hasBackdrop) {
2319 $ionicBackdrop.retain();
2320 $ionicBackdrop.getElement().addClass('backdrop-loading');
2321 }
2322 }
2323
2324 if (options.duration) {
2325 $timeout.cancel(self.durationTimeout);
2326 self.durationTimeout = $timeout(
2327 angular.bind(self, self.hide),
2328 +options.duration
2329 );
2330 }
2331
2332 deregisterBackAction();
2333 //Disable hardware back button while loading
2334 deregisterBackAction = $ionicPlatform.registerBackButtonAction(
2335 noop,
2336 IONIC_BACK_PRIORITY.loading
2337 );
2338
2339 templatePromise.then(function(html) {
2340 if (html) {
2341 var loading = self.element.children();
2342 loading.html(html);
2343 $compile(loading.contents())(self.scope);
2344 }
2345
2346 //Don't show until template changes
2347 if (self.isShown) {
2348 self.element.addClass('visible');
2349 ionic.requestAnimationFrame(function() {
2350 if (self.isShown) {
2351 self.element.addClass('active');
2352 $ionicBody.addClass('loading-active');
2353 }
2354 });
2355 }
2356 });
2357
2358 self.isShown = true;
2359 };
2360 self.hide = function() {
2361
2362 deregisterBackAction();
2363 if (self.isShown) {
2364 if (self.hasBackdrop) {
2365 $ionicBackdrop.release();
2366 $ionicBackdrop.getElement().removeClass('backdrop-loading');
2367 }
2368 self.element.removeClass('active');
2369 $ionicBody.removeClass('loading-active');
2370 self.element.removeClass('visible');
2371 ionic.requestAnimationFrame(function() {
2372 !self.isShown && self.element.removeClass('visible');
2373 });
2374 }
2375 $timeout.cancel(self.durationTimeout);
2376 self.isShown = false;
2377 var loading = self.element.children();
2378 loading.html("");
2379 };
2380
2381 return self;
2382 });
2383 }
2384 return loaderInstance;
2385 }
2386
2387 function showLoader(options) {
2388 options = extend({}, $ionicLoadingConfig || {}, options || {});
2389 // use a default delay of 100 to avoid some issues reported on github
2390 // https://github.com/driftyco/ionic/issues/3717
2391 var delay = options.delay || options.showDelay || 0;
2392
2393 deregisterStateListener1();
2394 deregisterStateListener2();
2395 if (options.hideOnStateChange) {
2396 deregisterStateListener1 = $rootScope.$on('$stateChangeSuccess', hideLoader);
2397 deregisterStateListener2 = $rootScope.$on('$stateChangeError', hideLoader);
2398 }
2399
2400 //If loading.show() was called previously, cancel it and show with our new options
2401 $timeout.cancel(loadingShowDelay);
2402 loadingShowDelay = $timeout(noop, delay);
2403 return loadingShowDelay.then(getLoader).then(function(loader) {
2404 return loader.show(options);
2405 });
2406 }
2407
2408 function hideLoader() {
2409 deregisterStateListener1();
2410 deregisterStateListener2();
2411 $timeout.cancel(loadingShowDelay);
2412 return getLoader().then(function(loader) {
2413 return loader.hide();
2414 });
2415 }
2416}]);
2417
2418/**
2419 * @ngdoc service
2420 * @name $ionicModal
2421 * @module ionic
2422 * @codepen gblny
2423 * @description
2424 *
2425 * Related: {@link ionic.controller:ionicModal ionicModal controller}.
2426 *
2427 * The Modal is a content pane that can go over the user's main view
2428 * temporarily. Usually used for making a choice or editing an item.
2429 *
2430 * Put the content of the modal inside of an `<ion-modal-view>` element.
2431 *
2432 * **Notes:**
2433 * - A modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating
2434 * scope, passing in itself as an event argument. Both the modal.removed and modal.hidden events are
2435 * called when the modal is removed.
2436 *
2437 * - This example assumes your modal is in your main index file or another template file. If it is in its own
2438 * template file, remove the script tags and call it by file name.
2439 *
2440 * @usage
2441 * ```html
2442 * <script id="my-modal.html" type="text/ng-template">
2443 * <ion-modal-view>
2444 * <ion-header-bar>
2445 * <h1 class="title">My Modal title</h1>
2446 * </ion-header-bar>
2447 * <ion-content>
2448 * Hello!
2449 * </ion-content>
2450 * </ion-modal-view>
2451 * </script>
2452 * ```
2453 * ```js
2454 * angular.module('testApp', ['ionic'])
2455 * .controller('MyController', function($scope, $ionicModal) {
2456 * $ionicModal.fromTemplateUrl('my-modal.html', {
2457 * scope: $scope,
2458 * animation: 'slide-in-up'
2459 * }).then(function(modal) {
2460 * $scope.modal = modal;
2461 * });
2462 * $scope.openModal = function() {
2463 * $scope.modal.show();
2464 * };
2465 * $scope.closeModal = function() {
2466 * $scope.modal.hide();
2467 * };
2468 * // Cleanup the modal when we're done with it!
2469 * $scope.$on('$destroy', function() {
2470 * $scope.modal.remove();
2471 * });
2472 * // Execute action on hide modal
2473 * $scope.$on('modal.hidden', function() {
2474 * // Execute action
2475 * });
2476 * // Execute action on remove modal
2477 * $scope.$on('modal.removed', function() {
2478 * // Execute action
2479 * });
2480 * });
2481 * ```
2482 */
2483IonicModule
2484.factory('$ionicModal', [
2485 '$rootScope',
2486 '$ionicBody',
2487 '$compile',
2488 '$timeout',
2489 '$ionicPlatform',
2490 '$ionicTemplateLoader',
2491 '$$q',
2492 '$log',
2493 '$ionicClickBlock',
2494 '$window',
2495 'IONIC_BACK_PRIORITY',
2496function($rootScope, $ionicBody, $compile, $timeout, $ionicPlatform, $ionicTemplateLoader, $$q, $log, $ionicClickBlock, $window, IONIC_BACK_PRIORITY) {
2497
2498 /**
2499 * @ngdoc controller
2500 * @name ionicModal
2501 * @module ionic
2502 * @description
2503 * Instantiated by the {@link ionic.service:$ionicModal} service.
2504 *
2505 * Be sure to call [remove()](#remove) when you are done with each modal
2506 * to clean it up and avoid memory leaks.
2507 *
2508 * Note: a modal will broadcast 'modal.shown', 'modal.hidden', and 'modal.removed' events from its originating
2509 * scope, passing in itself as an event argument. Note: both modal.removed and modal.hidden are
2510 * called when the modal is removed.
2511 */
2512 var ModalView = ionic.views.Modal.inherit({
2513 /**
2514 * @ngdoc method
2515 * @name ionicModal#initialize
2516 * @description Creates a new modal controller instance.
2517 * @param {object} options An options object with the following properties:
2518 * - `{object=}` `scope` The scope to be a child of.
2519 * Default: creates a child of $rootScope.
2520 * - `{string=}` `animation` The animation to show & hide with.
2521 * Default: 'slide-in-up'
2522 * - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of
2523 * the modal when shown. Will only show the keyboard on iOS, to force the keyboard to show
2524 * on Android, please use the [Ionic keyboard plugin](https://github.com/driftyco/ionic-plugin-keyboard#keyboardshow).
2525 * Default: false.
2526 * - `{boolean=}` `backdropClickToClose` Whether to close the modal on clicking the backdrop.
2527 * Default: true.
2528 * - `{boolean=}` `hardwareBackButtonClose` Whether the modal can be closed using the hardware
2529 * back button on Android and similar devices. Default: true.
2530 */
2531 initialize: function(opts) {
2532 ionic.views.Modal.prototype.initialize.call(this, opts);
2533 this.animation = opts.animation || 'slide-in-up';
2534 },
2535
2536 /**
2537 * @ngdoc method
2538 * @name ionicModal#show
2539 * @description Show this modal instance.
2540 * @returns {promise} A promise which is resolved when the modal is finished animating in.
2541 */
2542 show: function(target) {
2543 var self = this;
2544
2545 if (self.scope.$$destroyed) {
2546 $log.error('Cannot call ' + self.viewType + '.show() after remove(). Please create a new ' + self.viewType + ' instance.');
2547 return $$q.when();
2548 }
2549
2550 // on iOS, clicks will sometimes bleed through/ghost click on underlying
2551 // elements
2552 $ionicClickBlock.show(600);
2553 stack.add(self);
2554
2555 var modalEl = jqLite(self.modalEl);
2556
2557 self.el.classList.remove('hide');
2558 $timeout(function() {
2559 if (!self._isShown) return;
2560 $ionicBody.addClass(self.viewType + '-open');
2561 }, 400, false);
2562
2563 if (!self.el.parentElement) {
2564 modalEl.addClass(self.animation);
2565 $ionicBody.append(self.el);
2566 }
2567
2568 // if modal was closed while the keyboard was up, reset scroll view on
2569 // next show since we can only resize it once it's visible
2570 var scrollCtrl = modalEl.data('$$ionicScrollController');
2571 scrollCtrl && scrollCtrl.resize();
2572
2573 if (target && self.positionView) {
2574 self.positionView(target, modalEl);
2575 // set up a listener for in case the window size changes
2576
2577 self._onWindowResize = function() {
2578 if (self._isShown) self.positionView(target, modalEl);
2579 };
2580 ionic.on('resize', self._onWindowResize, window);
2581 }
2582
2583 modalEl.addClass('ng-enter active')
2584 .removeClass('ng-leave ng-leave-active');
2585
2586 self._isShown = true;
2587 self._deregisterBackButton = $ionicPlatform.registerBackButtonAction(
2588 self.hardwareBackButtonClose ? angular.bind(self, self.hide) : noop,
2589 IONIC_BACK_PRIORITY.modal
2590 );
2591
2592 ionic.views.Modal.prototype.show.call(self);
2593
2594 $timeout(function() {
2595 if (!self._isShown) return;
2596 modalEl.addClass('ng-enter-active');
2597 ionic.trigger('resize');
2598 self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.shown', self);
2599 self.el.classList.add('active');
2600 self.scope.$broadcast('$ionicHeader.align');
2601 self.scope.$broadcast('$ionicFooter.align');
2602 self.scope.$broadcast('$ionic.modalPresented');
2603 }, 20);
2604
2605 return $timeout(function() {
2606 if (!self._isShown) return;
2607 self.$el.on('touchmove', function(e) {
2608 //Don't allow scrolling while open by dragging on backdrop
2609 var isInScroll = ionic.DomUtil.getParentOrSelfWithClass(e.target, 'scroll');
2610 if (!isInScroll) {
2611 e.preventDefault();
2612 }
2613 });
2614 //After animating in, allow hide on backdrop click
2615 self.$el.on('click', function(e) {
2616 if (self.backdropClickToClose && e.target === self.el && stack.isHighest(self)) {
2617 self.hide();
2618 }
2619 });
2620 }, 400);
2621 },
2622
2623 /**
2624 * @ngdoc method
2625 * @name ionicModal#hide
2626 * @description Hide this modal instance.
2627 * @returns {promise} A promise which is resolved when the modal is finished animating out.
2628 */
2629 hide: function() {
2630 var self = this;
2631 var modalEl = jqLite(self.modalEl);
2632
2633 // on iOS, clicks will sometimes bleed through/ghost click on underlying
2634 // elements
2635 $ionicClickBlock.show(600);
2636 stack.remove(self);
2637
2638 self.el.classList.remove('active');
2639 modalEl.addClass('ng-leave');
2640
2641 $timeout(function() {
2642 if (self._isShown) return;
2643 modalEl.addClass('ng-leave-active')
2644 .removeClass('ng-enter ng-enter-active active');
2645
2646 self.scope.$broadcast('$ionic.modalRemoved');
2647 }, 20, false);
2648
2649 self.$el.off('click');
2650 self._isShown = false;
2651 self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.hidden', self);
2652 self._deregisterBackButton && self._deregisterBackButton();
2653
2654 ionic.views.Modal.prototype.hide.call(self);
2655
2656 // clean up event listeners
2657 if (self.positionView) {
2658 ionic.off('resize', self._onWindowResize, window);
2659 }
2660
2661 return $timeout(function() {
2662 $ionicBody.removeClass(self.viewType + '-open');
2663 self.el.classList.add('hide');
2664 }, self.hideDelay || 320);
2665 },
2666
2667 /**
2668 * @ngdoc method
2669 * @name ionicModal#remove
2670 * @description Remove this modal instance from the DOM and clean up.
2671 * @returns {promise} A promise which is resolved when the modal is finished animating out.
2672 */
2673 remove: function() {
2674 var self = this,
2675 deferred, promise;
2676 self.scope.$parent && self.scope.$parent.$broadcast(self.viewType + '.removed', self);
2677
2678 // Only hide modal, when it is actually shown!
2679 // The hide function shows a click-block-div for a split second, because on iOS,
2680 // clicks will sometimes bleed through/ghost click on underlying elements.
2681 // However, this will make the app unresponsive for short amount of time.
2682 // We don't want that, if the modal window is already hidden.
2683 if (self._isShown) {
2684 promise = self.hide();
2685 } else {
2686 deferred = $$q.defer();
2687 deferred.resolve();
2688 promise = deferred.promise;
2689 }
2690
2691 return promise.then(function() {
2692 self.scope.$destroy();
2693 self.$el.remove();
2694 });
2695 },
2696
2697 /**
2698 * @ngdoc method
2699 * @name ionicModal#isShown
2700 * @returns boolean Whether this modal is currently shown.
2701 */
2702 isShown: function() {
2703 return !!this._isShown;
2704 }
2705 });
2706
2707 var createModal = function(templateString, options) {
2708 // Create a new scope for the modal
2709 var scope = options.scope && options.scope.$new() || $rootScope.$new(true);
2710
2711 options.viewType = options.viewType || 'modal';
2712
2713 extend(scope, {
2714 $hasHeader: false,
2715 $hasSubheader: false,
2716 $hasFooter: false,
2717 $hasSubfooter: false,
2718 $hasTabs: false,
2719 $hasTabsTop: false
2720 });
2721
2722 // Compile the template
2723 var element = $compile('<ion-' + options.viewType + '>' + templateString + '</ion-' + options.viewType + '>')(scope);
2724
2725 options.$el = element;
2726 options.el = element[0];
2727 options.modalEl = options.el.querySelector('.' + options.viewType);
2728 var modal = new ModalView(options);
2729
2730 modal.scope = scope;
2731
2732 // If this wasn't a defined scope, we can assign the viewType to the isolated scope
2733 // we created
2734 if (!options.scope) {
2735 scope[ options.viewType ] = modal;
2736 }
2737
2738 return modal;
2739 };
2740
2741 var modalStack = [];
2742 var stack = {
2743 add: function(modal) {
2744 modalStack.push(modal);
2745 },
2746 remove: function(modal) {
2747 var index = modalStack.indexOf(modal);
2748 if (index > -1 && index < modalStack.length) {
2749 modalStack.splice(index, 1);
2750 }
2751 },
2752 isHighest: function(modal) {
2753 var index = modalStack.indexOf(modal);
2754 return (index > -1 && index === modalStack.length - 1);
2755 }
2756 };
2757
2758 return {
2759 /**
2760 * @ngdoc method
2761 * @name $ionicModal#fromTemplate
2762 * @param {string} templateString The template string to use as the modal's
2763 * content.
2764 * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
2765 * @returns {object} An instance of an {@link ionic.controller:ionicModal}
2766 * controller.
2767 */
2768 fromTemplate: function(templateString, options) {
2769 var modal = createModal(templateString, options || {});
2770 return modal;
2771 },
2772 /**
2773 * @ngdoc method
2774 * @name $ionicModal#fromTemplateUrl
2775 * @param {string} templateUrl The url to load the template from.
2776 * @param {object} options Options to be passed {@link ionic.controller:ionicModal#initialize ionicModal#initialize} method.
2777 * options object.
2778 * @returns {promise} A promise that will be resolved with an instance of
2779 * an {@link ionic.controller:ionicModal} controller.
2780 */
2781 fromTemplateUrl: function(url, options, _) {
2782 var cb;
2783 //Deprecated: allow a callback as second parameter. Now we return a promise.
2784 if (angular.isFunction(options)) {
2785 cb = options;
2786 options = _;
2787 }
2788 return $ionicTemplateLoader.load(url).then(function(templateString) {
2789 var modal = createModal(templateString, options || {});
2790 cb && cb(modal);
2791 return modal;
2792 });
2793 },
2794
2795 stack: stack
2796 };
2797}]);
2798
2799
2800/**
2801 * @ngdoc service
2802 * @name $ionicNavBarDelegate
2803 * @module ionic
2804 * @description
2805 * Delegate for controlling the {@link ionic.directive:ionNavBar} directive.
2806 *
2807 * @usage
2808 *
2809 * ```html
2810 * <body ng-controller="MyCtrl">
2811 * <ion-nav-bar>
2812 * <button ng-click="setNavTitle('banana')">
2813 * Set title to banana!
2814 * </button>
2815 * </ion-nav-bar>
2816 * </body>
2817 * ```
2818 * ```js
2819 * function MyCtrl($scope, $ionicNavBarDelegate) {
2820 * $scope.setNavTitle = function(title) {
2821 * $ionicNavBarDelegate.title(title);
2822 * }
2823 * }
2824 * ```
2825 */
2826IonicModule
2827.service('$ionicNavBarDelegate', ionic.DelegateService([
2828 /**
2829 * @ngdoc method
2830 * @name $ionicNavBarDelegate#align
2831 * @description Aligns the title with the buttons in a given direction.
2832 * @param {string=} direction The direction to the align the title text towards.
2833 * Available: 'left', 'right', 'center'. Default: 'center'.
2834 */
2835 'align',
2836 /**
2837 * @ngdoc method
2838 * @name $ionicNavBarDelegate#showBackButton
2839 * @description
2840 * Set/get whether the {@link ionic.directive:ionNavBackButton} is shown
2841 * (if it exists and there is a previous view that can be navigated to).
2842 * @param {boolean=} show Whether to show the back button.
2843 * @returns {boolean} Whether the back button is shown.
2844 */
2845 'showBackButton',
2846 /**
2847 * @ngdoc method
2848 * @name $ionicNavBarDelegate#showBar
2849 * @description
2850 * Set/get whether the {@link ionic.directive:ionNavBar} is shown.
2851 * @param {boolean} show Whether to show the bar.
2852 * @returns {boolean} Whether the bar is shown.
2853 */
2854 'showBar',
2855 /**
2856 * @ngdoc method
2857 * @name $ionicNavBarDelegate#title
2858 * @description
2859 * Set the title for the {@link ionic.directive:ionNavBar}.
2860 * @param {string} title The new title to show.
2861 */
2862 'title',
2863
2864 // DEPRECATED, as of v1.0.0-beta14 -------
2865 'changeTitle',
2866 'setTitle',
2867 'getTitle',
2868 'back',
2869 'getPreviousTitle'
2870 // END DEPRECATED -------
2871]));
2872
2873
2874IonicModule
2875.service('$ionicNavViewDelegate', ionic.DelegateService([
2876 'clearCache'
2877]));
2878
2879
2880
2881/**
2882 * @ngdoc service
2883 * @name $ionicPlatform
2884 * @module ionic
2885 * @description
2886 * An angular abstraction of {@link ionic.utility:ionic.Platform}.
2887 *
2888 * Used to detect the current platform, as well as do things like override the
2889 * Android back button in PhoneGap/Cordova.
2890 */
2891IonicModule
2892.constant('IONIC_BACK_PRIORITY', {
2893 view: 100,
2894 sideMenu: 150,
2895 modal: 200,
2896 actionSheet: 300,
2897 popup: 400,
2898 loading: 500
2899})
2900.provider('$ionicPlatform', function() {
2901 return {
2902 $get: ['$q', '$ionicScrollDelegate', function($q, $ionicScrollDelegate) {
2903 var self = {
2904
2905 /**
2906 * @ngdoc method
2907 * @name $ionicPlatform#onHardwareBackButton
2908 * @description
2909 * Some platforms have a hardware back button, so this is one way to
2910 * bind to it.
2911 * @param {function} callback the callback to trigger when this event occurs
2912 */
2913 onHardwareBackButton: function(cb) {
2914 ionic.Platform.ready(function() {
2915 document.addEventListener('backbutton', cb, false);
2916 });
2917 },
2918
2919 /**
2920 * @ngdoc method
2921 * @name $ionicPlatform#offHardwareBackButton
2922 * @description
2923 * Remove an event listener for the backbutton.
2924 * @param {function} callback The listener function that was
2925 * originally bound.
2926 */
2927 offHardwareBackButton: function(fn) {
2928 ionic.Platform.ready(function() {
2929 document.removeEventListener('backbutton', fn);
2930 });
2931 },
2932
2933 /**
2934 * @ngdoc method
2935 * @name $ionicPlatform#registerBackButtonAction
2936 * @description
2937 * Register a hardware back button action. Only one action will execute
2938 * when the back button is clicked, so this method decides which of
2939 * the registered back button actions has the highest priority.
2940 *
2941 * For example, if an actionsheet is showing, the back button should
2942 * close the actionsheet, but it should not also go back a page view
2943 * or close a modal which may be open.
2944 *
2945 * The priorities for the existing back button hooks are as follows:
2946 * Return to previous view = 100
2947 * Close side menu = 150
2948 * Dismiss modal = 200
2949 * Close action sheet = 300
2950 * Dismiss popup = 400
2951 * Dismiss loading overlay = 500
2952 *
2953 * Your back button action will override each of the above actions
2954 * whose priority is less than the priority you provide. For example,
2955 * an action assigned a priority of 101 will override the 'return to
2956 * previous view' action, but not any of the other actions.
2957 *
2958 * @param {function} callback Called when the back button is pressed,
2959 * if this listener is the highest priority.
2960 * @param {number} priority Only the highest priority will execute.
2961 * @param {*=} actionId The id to assign this action. Default: a
2962 * random unique id.
2963 * @returns {function} A function that, when called, will deregister
2964 * this backButtonAction.
2965 */
2966 $backButtonActions: {},
2967 registerBackButtonAction: function(fn, priority, actionId) {
2968
2969 if (!self._hasBackButtonHandler) {
2970 // add a back button listener if one hasn't been setup yet
2971 self.$backButtonActions = {};
2972 self.onHardwareBackButton(self.hardwareBackButtonClick);
2973 self._hasBackButtonHandler = true;
2974 }
2975
2976 var action = {
2977 id: (actionId ? actionId : ionic.Utils.nextUid()),
2978 priority: (priority ? priority : 0),
2979 fn: fn
2980 };
2981 self.$backButtonActions[action.id] = action;
2982
2983 // return a function to de-register this back button action
2984 return function() {
2985 delete self.$backButtonActions[action.id];
2986 };
2987 },
2988
2989 /**
2990 * @private
2991 */
2992 hardwareBackButtonClick: function(e) {
2993 // loop through all the registered back button actions
2994 // and only run the last one of the highest priority
2995 var priorityAction, actionId;
2996 for (actionId in self.$backButtonActions) {
2997 if (!priorityAction || self.$backButtonActions[actionId].priority >= priorityAction.priority) {
2998 priorityAction = self.$backButtonActions[actionId];
2999 }
3000 }
3001 if (priorityAction) {
3002 priorityAction.fn(e);
3003 return priorityAction;
3004 }
3005 },
3006
3007 is: function(type) {
3008 return ionic.Platform.is(type);
3009 },
3010
3011 /**
3012 * @ngdoc method
3013 * @name $ionicPlatform#on
3014 * @description
3015 * Add Cordova event listeners, such as `pause`, `resume`, `volumedownbutton`, `batterylow`,
3016 * `offline`, etc. More information about available event types can be found in
3017 * [Cordova's event documentation](https://cordova.apache.org/docs/en/edge/cordova_events_events.md.html#Events).
3018 * @param {string} type Cordova [event type](https://cordova.apache.org/docs/en/edge/cordova_events_events.md.html#Events).
3019 * @param {function} callback Called when the Cordova event is fired.
3020 * @returns {function} Returns a deregistration function to remove the event listener.
3021 */
3022 on: function(type, cb) {
3023 ionic.Platform.ready(function() {
3024 document.addEventListener(type, cb, false);
3025 });
3026 return function() {
3027 ionic.Platform.ready(function() {
3028 document.removeEventListener(type, cb);
3029 });
3030 };
3031 },
3032
3033 /**
3034 * @ngdoc method
3035 * @name $ionicPlatform#ready
3036 * @description
3037 * Trigger a callback once the device is ready,
3038 * or immediately if the device is already ready.
3039 * @param {function=} callback The function to call.
3040 * @returns {promise} A promise which is resolved when the device is ready.
3041 */
3042 ready: function(cb) {
3043 var q = $q.defer();
3044
3045 ionic.Platform.ready(function() {
3046
3047 window.addEventListener('statusTap', function() {
3048 $ionicScrollDelegate.scrollTop(true);
3049 });
3050
3051 q.resolve();
3052 cb && cb();
3053 });
3054
3055 return q.promise;
3056 }
3057 };
3058
3059 return self;
3060 }]
3061 };
3062
3063});
3064
3065/**
3066 * @ngdoc service
3067 * @name $ionicPopover
3068 * @module ionic
3069 * @description
3070 *
3071 * Related: {@link ionic.controller:ionicPopover ionicPopover controller}.
3072 *
3073 * The Popover is a view that floats above an app’s content. Popovers provide an
3074 * easy way to present or gather information from the user and are
3075 * commonly used in the following situations:
3076 *
3077 * - Show more info about the current view
3078 * - Select a commonly used tool or configuration
3079 * - Present a list of actions to perform inside one of your views
3080 *
3081 * Put the content of the popover inside of an `<ion-popover-view>` element.
3082 *
3083 * @usage
3084 * ```html
3085 * <p>
3086 * <button ng-click="openPopover($event)">Open Popover</button>
3087 * </p>
3088 *
3089 * <script id="my-popover.html" type="text/ng-template">
3090 * <ion-popover-view>
3091 * <ion-header-bar>
3092 * <h1 class="title">My Popover Title</h1>
3093 * </ion-header-bar>
3094 * <ion-content>
3095 * Hello!
3096 * </ion-content>
3097 * </ion-popover-view>
3098 * </script>
3099 * ```
3100 * ```js
3101 * angular.module('testApp', ['ionic'])
3102 * .controller('MyController', function($scope, $ionicPopover) {
3103 *
3104 * // .fromTemplate() method
3105 * var template = '<ion-popover-view><ion-header-bar> <h1 class="title">My Popover Title</h1> </ion-header-bar> <ion-content> Hello! </ion-content></ion-popover-view>';
3106 *
3107 * $scope.popover = $ionicPopover.fromTemplate(template, {
3108 * scope: $scope
3109 * });
3110 *
3111 * // .fromTemplateUrl() method
3112 * $ionicPopover.fromTemplateUrl('my-popover.html', {
3113 * scope: $scope
3114 * }).then(function(popover) {
3115 * $scope.popover = popover;
3116 * });
3117 *
3118 *
3119 * $scope.openPopover = function($event) {
3120 * $scope.popover.show($event);
3121 * };
3122 * $scope.closePopover = function() {
3123 * $scope.popover.hide();
3124 * };
3125 * //Cleanup the popover when we're done with it!
3126 * $scope.$on('$destroy', function() {
3127 * $scope.popover.remove();
3128 * });
3129 * // Execute action on hide popover
3130 * $scope.$on('popover.hidden', function() {
3131 * // Execute action
3132 * });
3133 * // Execute action on remove popover
3134 * $scope.$on('popover.removed', function() {
3135 * // Execute action
3136 * });
3137 * });
3138 * ```
3139 */
3140
3141
3142IonicModule
3143.factory('$ionicPopover', ['$ionicModal', '$ionicPosition', '$document', '$window',
3144function($ionicModal, $ionicPosition, $document, $window) {
3145
3146 var POPOVER_BODY_PADDING = 6;
3147
3148 var POPOVER_OPTIONS = {
3149 viewType: 'popover',
3150 hideDelay: 1,
3151 animation: 'none',
3152 positionView: positionView
3153 };
3154
3155 function positionView(target, popoverEle) {
3156 var targetEle = jqLite(target.target || target);
3157 var buttonOffset = $ionicPosition.offset(targetEle);
3158 var popoverWidth = popoverEle.prop('offsetWidth');
3159 var popoverHeight = popoverEle.prop('offsetHeight');
3160 // Use innerWidth and innerHeight, because clientWidth and clientHeight
3161 // doesn't work consistently for body on all platforms
3162 var bodyWidth = $window.innerWidth;
3163 var bodyHeight = $window.innerHeight;
3164
3165 var popoverCSS = {
3166 left: buttonOffset.left + buttonOffset.width / 2 - popoverWidth / 2
3167 };
3168 var arrowEle = jqLite(popoverEle[0].querySelector('.popover-arrow'));
3169
3170 if (popoverCSS.left < POPOVER_BODY_PADDING) {
3171 popoverCSS.left = POPOVER_BODY_PADDING;
3172 } else if (popoverCSS.left + popoverWidth + POPOVER_BODY_PADDING > bodyWidth) {
3173 popoverCSS.left = bodyWidth - popoverWidth - POPOVER_BODY_PADDING;
3174 }
3175
3176 // If the popover when popped down stretches past bottom of screen,
3177 // make it pop up if there's room above
3178 if (buttonOffset.top + buttonOffset.height + popoverHeight > bodyHeight &&
3179 buttonOffset.top - popoverHeight > 0) {
3180 popoverCSS.top = buttonOffset.top - popoverHeight;
3181 popoverEle.addClass('popover-bottom');
3182 } else {
3183 popoverCSS.top = buttonOffset.top + buttonOffset.height;
3184 popoverEle.removeClass('popover-bottom');
3185 }
3186
3187 arrowEle.css({
3188 left: buttonOffset.left + buttonOffset.width / 2 -
3189 arrowEle.prop('offsetWidth') / 2 - popoverCSS.left + 'px'
3190 });
3191
3192 popoverEle.css({
3193 top: popoverCSS.top + 'px',
3194 left: popoverCSS.left + 'px',
3195 marginLeft: '0',
3196 opacity: '1'
3197 });
3198
3199 }
3200
3201 /**
3202 * @ngdoc controller
3203 * @name ionicPopover
3204 * @module ionic
3205 * @description
3206 * Instantiated by the {@link ionic.service:$ionicPopover} service.
3207 *
3208 * Be sure to call [remove()](#remove) when you are done with each popover
3209 * to clean it up and avoid memory leaks.
3210 *
3211 * Note: a popover will broadcast 'popover.shown', 'popover.hidden', and 'popover.removed' events from its originating
3212 * scope, passing in itself as an event argument. Both the popover.removed and popover.hidden events are
3213 * called when the popover is removed.
3214 */
3215
3216 /**
3217 * @ngdoc method
3218 * @name ionicPopover#initialize
3219 * @description Creates a new popover controller instance.
3220 * @param {object} options An options object with the following properties:
3221 * - `{object=}` `scope` The scope to be a child of.
3222 * Default: creates a child of $rootScope.
3223 * - `{boolean=}` `focusFirstInput` Whether to autofocus the first input of
3224 * the popover when shown. Default: false.
3225 * - `{boolean=}` `backdropClickToClose` Whether to close the popover on clicking the backdrop.
3226 * Default: true.
3227 * - `{boolean=}` `hardwareBackButtonClose` Whether the popover can be closed using the hardware
3228 * back button on Android and similar devices. Default: true.
3229 */
3230
3231 /**
3232 * @ngdoc method
3233 * @name ionicPopover#show
3234 * @description Show this popover instance.
3235 * @param {$event} $event The $event or target element which the popover should align
3236 * itself next to.
3237 * @returns {promise} A promise which is resolved when the popover is finished animating in.
3238 */
3239
3240 /**
3241 * @ngdoc method
3242 * @name ionicPopover#hide
3243 * @description Hide this popover instance.
3244 * @returns {promise} A promise which is resolved when the popover is finished animating out.
3245 */
3246
3247 /**
3248 * @ngdoc method
3249 * @name ionicPopover#remove
3250 * @description Remove this popover instance from the DOM and clean up.
3251 * @returns {promise} A promise which is resolved when the popover is finished animating out.
3252 */
3253
3254 /**
3255 * @ngdoc method
3256 * @name ionicPopover#isShown
3257 * @returns boolean Whether this popover is currently shown.
3258 */
3259
3260 return {
3261 /**
3262 * @ngdoc method
3263 * @name $ionicPopover#fromTemplate
3264 * @param {string} templateString The template string to use as the popovers's
3265 * content.
3266 * @param {object} options Options to be passed to the initialize method.
3267 * @returns {object} An instance of an {@link ionic.controller:ionicPopover}
3268 * controller (ionicPopover is built on top of $ionicPopover).
3269 */
3270 fromTemplate: function(templateString, options) {
3271 return $ionicModal.fromTemplate(templateString, ionic.Utils.extend({}, POPOVER_OPTIONS, options));
3272 },
3273 /**
3274 * @ngdoc method
3275 * @name $ionicPopover#fromTemplateUrl
3276 * @param {string} templateUrl The url to load the template from.
3277 * @param {object} options Options to be passed to the initialize method.
3278 * @returns {promise} A promise that will be resolved with an instance of
3279 * an {@link ionic.controller:ionicPopover} controller (ionicPopover is built on top of $ionicPopover).
3280 */
3281 fromTemplateUrl: function(url, options) {
3282 return $ionicModal.fromTemplateUrl(url, ionic.Utils.extend({}, POPOVER_OPTIONS, options));
3283 }
3284 };
3285
3286}]);
3287
3288
3289var POPUP_TPL =
3290 '<div class="popup-container" ng-class="cssClass">' +
3291 '<div class="popup">' +
3292 '<div class="popup-head">' +
3293 '<h3 class="popup-title" ng-bind-html="title"></h3>' +
3294 '<h5 class="popup-sub-title" ng-bind-html="subTitle" ng-if="subTitle"></h5>' +
3295 '</div>' +
3296 '<div class="popup-body">' +
3297 '</div>' +
3298 '<div class="popup-buttons" ng-show="buttons.length">' +
3299 '<button ng-repeat="button in buttons" ng-click="$buttonTapped(button, $event)" class="button" ng-class="button.type || \'button-default\'" ng-bind-html="button.text"></button>' +
3300 '</div>' +
3301 '</div>' +
3302 '</div>';
3303
3304/**
3305 * @ngdoc service
3306 * @name $ionicPopup
3307 * @module ionic
3308 * @restrict E
3309 * @codepen zkmhJ
3310 * @description
3311 *
3312 * The Ionic Popup service allows programmatically creating and showing popup
3313 * windows that require the user to respond in order to continue.
3314 *
3315 * The popup system has support for more flexible versions of the built in `alert()`, `prompt()`,
3316 * and `confirm()` functions that users are used to, in addition to allowing popups with completely
3317 * custom content and look.
3318 *
3319 * An input can be given an `autofocus` attribute so it automatically receives focus when
3320 * the popup first shows. However, depending on certain use-cases this can cause issues with
3321 * the tap/click system, which is why Ionic prefers using the `autofocus` attribute as
3322 * an opt-in feature and not the default.
3323 *
3324 * @usage
3325 * A few basic examples, see below for details about all of the options available.
3326 *
3327 * ```js
3328 *angular.module('mySuperApp', ['ionic'])
3329 *.controller('PopupCtrl',function($scope, $ionicPopup, $timeout) {
3330 *
3331 * // Triggered on a button click, or some other target
3332 * $scope.showPopup = function() {
3333 * $scope.data = {};
3334 *
3335 * // An elaborate, custom popup
3336 * var myPopup = $ionicPopup.show({
3337 * template: '<input type="password" ng-model="data.wifi">',
3338 * title: 'Enter Wi-Fi Password',
3339 * subTitle: 'Please use normal things',
3340 * scope: $scope,
3341 * buttons: [
3342 * { text: 'Cancel' },
3343 * {
3344 * text: '<b>Save</b>',
3345 * type: 'button-positive',
3346 * onTap: function(e) {
3347 * if (!$scope.data.wifi) {
3348 * //don't allow the user to close unless he enters wifi password
3349 * e.preventDefault();
3350 * } else {
3351 * return $scope.data.wifi;
3352 * }
3353 * }
3354 * }
3355 * ]
3356 * });
3357 *
3358 * myPopup.then(function(res) {
3359 * console.log('Tapped!', res);
3360 * });
3361 *
3362 * $timeout(function() {
3363 * myPopup.close(); //close the popup after 3 seconds for some reason
3364 * }, 3000);
3365 * };
3366 *
3367 * // A confirm dialog
3368 * $scope.showConfirm = function() {
3369 * var confirmPopup = $ionicPopup.confirm({
3370 * title: 'Consume Ice Cream',
3371 * template: 'Are you sure you want to eat this ice cream?'
3372 * });
3373 *
3374 * confirmPopup.then(function(res) {
3375 * if(res) {
3376 * console.log('You are sure');
3377 * } else {
3378 * console.log('You are not sure');
3379 * }
3380 * });
3381 * };
3382 *
3383 * // An alert dialog
3384 * $scope.showAlert = function() {
3385 * var alertPopup = $ionicPopup.alert({
3386 * title: 'Don\'t eat that!',
3387 * template: 'It might taste good'
3388 * });
3389 *
3390 * alertPopup.then(function(res) {
3391 * console.log('Thank you for not eating my delicious ice cream cone');
3392 * });
3393 * };
3394 *});
3395 *```
3396 */
3397
3398IonicModule
3399.factory('$ionicPopup', [
3400 '$ionicTemplateLoader',
3401 '$ionicBackdrop',
3402 '$q',
3403 '$timeout',
3404 '$rootScope',
3405 '$ionicBody',
3406 '$compile',
3407 '$ionicPlatform',
3408 '$ionicModal',
3409 'IONIC_BACK_PRIORITY',
3410function($ionicTemplateLoader, $ionicBackdrop, $q, $timeout, $rootScope, $ionicBody, $compile, $ionicPlatform, $ionicModal, IONIC_BACK_PRIORITY) {
3411 //TODO allow this to be configured
3412 var config = {
3413 stackPushDelay: 75
3414 };
3415 var popupStack = [];
3416
3417 var $ionicPopup = {
3418 /**
3419 * @ngdoc method
3420 * @description
3421 * Show a complex popup. This is the master show function for all popups.
3422 *
3423 * A complex popup has a `buttons` array, with each button having a `text` and `type`
3424 * field, in addition to an `onTap` function. The `onTap` function, called when
3425 * the corresponding button on the popup is tapped, will by default close the popup
3426 * and resolve the popup promise with its return value. If you wish to prevent the
3427 * default and keep the popup open on button tap, call `event.preventDefault()` on the
3428 * passed in tap event. Details below.
3429 *
3430 * @name $ionicPopup#show
3431 * @param {object} options The options for the new popup, of the form:
3432 *
3433 * ```
3434 * {
3435 * title: '', // String. The title of the popup.
3436 * cssClass: '', // String, The custom CSS class name
3437 * subTitle: '', // String (optional). The sub-title of the popup.
3438 * template: '', // String (optional). The html template to place in the popup body.
3439 * templateUrl: '', // String (optional). The URL of an html template to place in the popup body.
3440 * scope: null, // Scope (optional). A scope to link to the popup content.
3441 * buttons: [{ // Array[Object] (optional). Buttons to place in the popup footer.
3442 * text: 'Cancel',
3443 * type: 'button-default',
3444 * onTap: function(e) {
3445 * // e.preventDefault() will stop the popup from closing when tapped.
3446 * e.preventDefault();
3447 * }
3448 * }, {
3449 * text: 'OK',
3450 * type: 'button-positive',
3451 * onTap: function(e) {
3452 * // Returning a value will cause the promise to resolve with the given value.
3453 * return scope.data.response;
3454 * }
3455 * }]
3456 * }
3457 * ```
3458 *
3459 * @returns {object} A promise which is resolved when the popup is closed. Has an additional
3460 * `close` function, which can be used to programmatically close the popup.
3461 */
3462 show: showPopup,
3463
3464 /**
3465 * @ngdoc method
3466 * @name $ionicPopup#alert
3467 * @description Show a simple alert popup with a message and one button that the user can
3468 * tap to close the popup.
3469 *
3470 * @param {object} options The options for showing the alert, of the form:
3471 *
3472 * ```
3473 * {
3474 * title: '', // String. The title of the popup.
3475 * cssClass: '', // String, The custom CSS class name
3476 * subTitle: '', // String (optional). The sub-title of the popup.
3477 * template: '', // String (optional). The html template to place in the popup body.
3478 * templateUrl: '', // String (optional). The URL of an html template to place in the popup body.
3479 * okText: '', // String (default: 'OK'). The text of the OK button.
3480 * okType: '', // String (default: 'button-positive'). The type of the OK button.
3481 * }
3482 * ```
3483 *
3484 * @returns {object} A promise which is resolved when the popup is closed. Has one additional
3485 * function `close`, which can be called with any value to programmatically close the popup
3486 * with the given value.
3487 */
3488 alert: showAlert,
3489
3490 /**
3491 * @ngdoc method
3492 * @name $ionicPopup#confirm
3493 * @description
3494 * Show a simple confirm popup with a Cancel and OK button.
3495 *
3496 * Resolves the promise with true if the user presses the OK button, and false if the
3497 * user presses the Cancel button.
3498 *
3499 * @param {object} options The options for showing the confirm popup, of the form:
3500 *
3501 * ```
3502 * {
3503 * title: '', // String. The title of the popup.
3504 * cssClass: '', // String, The custom CSS class name
3505 * subTitle: '', // String (optional). The sub-title of the popup.
3506 * template: '', // String (optional). The html template to place in the popup body.
3507 * templateUrl: '', // String (optional). The URL of an html template to place in the popup body.
3508 * cancelText: '', // String (default: 'Cancel'). The text of the Cancel button.
3509 * cancelType: '', // String (default: 'button-default'). The type of the Cancel button.
3510 * okText: '', // String (default: 'OK'). The text of the OK button.
3511 * okType: '', // String (default: 'button-positive'). The type of the OK button.
3512 * }
3513 * ```
3514 *
3515 * @returns {object} A promise which is resolved when the popup is closed. Has one additional
3516 * function `close`, which can be called with any value to programmatically close the popup
3517 * with the given value.
3518 */
3519 confirm: showConfirm,
3520
3521 /**
3522 * @ngdoc method
3523 * @name $ionicPopup#prompt
3524 * @description Show a simple prompt popup, which has an input, OK button, and Cancel button.
3525 * Resolves the promise with the value of the input if the user presses OK, and with undefined
3526 * if the user presses Cancel.
3527 *
3528 * ```javascript
3529 * $ionicPopup.prompt({
3530 * title: 'Password Check',
3531 * template: 'Enter your secret password',
3532 * inputType: 'password',
3533 * inputPlaceholder: 'Your password'
3534 * }).then(function(res) {
3535 * console.log('Your password is', res);
3536 * });
3537 * ```
3538 * @param {object} options The options for showing the prompt popup, of the form:
3539 *
3540 * ```
3541 * {
3542 * title: '', // String. The title of the popup.
3543 * cssClass: '', // String, The custom CSS class name
3544 * subTitle: '', // String (optional). The sub-title of the popup.
3545 * template: '', // String (optional). The html template to place in the popup body.
3546 * templateUrl: '', // String (optional). The URL of an html template to place in the popup body.
3547 * inputType: // String (default: 'text'). The type of input to use
3548 * defaultText: // String (default: ''). The initial value placed into the input.
3549 * maxLength: // Integer (default: null). Specify a maxlength attribute for the input.
3550 * inputPlaceholder: // String (default: ''). A placeholder to use for the input.
3551 * cancelText: // String (default: 'Cancel'. The text of the Cancel button.
3552 * cancelType: // String (default: 'button-default'). The type of the Cancel button.
3553 * okText: // String (default: 'OK'). The text of the OK button.
3554 * okType: // String (default: 'button-positive'). The type of the OK button.
3555 * }
3556 * ```
3557 *
3558 * @returns {object} A promise which is resolved when the popup is closed. Has one additional
3559 * function `close`, which can be called with any value to programmatically close the popup
3560 * with the given value.
3561 */
3562 prompt: showPrompt,
3563 /**
3564 * @private for testing
3565 */
3566 _createPopup: createPopup,
3567 _popupStack: popupStack
3568 };
3569
3570 return $ionicPopup;
3571
3572 function createPopup(options) {
3573 options = extend({
3574 scope: null,
3575 title: '',
3576 buttons: []
3577 }, options || {});
3578
3579 var self = {};
3580 self.scope = (options.scope || $rootScope).$new();
3581 self.element = jqLite(POPUP_TPL);
3582 self.responseDeferred = $q.defer();
3583
3584 $ionicBody.get().appendChild(self.element[0]);
3585 $compile(self.element)(self.scope);
3586
3587 extend(self.scope, {
3588 title: options.title,
3589 buttons: options.buttons,
3590 subTitle: options.subTitle,
3591 cssClass: options.cssClass,
3592 $buttonTapped: function(button, event) {
3593 var result = (button.onTap || noop).apply(self, [event]);
3594 event = event.originalEvent || event; //jquery events
3595
3596 if (!event.defaultPrevented) {
3597 self.responseDeferred.resolve(result);
3598 }
3599 }
3600 });
3601
3602 $q.when(
3603 options.templateUrl ?
3604 $ionicTemplateLoader.load(options.templateUrl) :
3605 (options.template || options.content || '')
3606 ).then(function(template) {
3607 var popupBody = jqLite(self.element[0].querySelector('.popup-body'));
3608 if (template) {
3609 popupBody.html(template);
3610 $compile(popupBody.contents())(self.scope);
3611 } else {
3612 popupBody.remove();
3613 }
3614 });
3615
3616 self.show = function() {
3617 if (self.isShown || self.removed) return;
3618
3619 $ionicModal.stack.add(self);
3620 self.isShown = true;
3621 ionic.requestAnimationFrame(function() {
3622 //if hidden while waiting for raf, don't show
3623 if (!self.isShown) return;
3624
3625 self.element.removeClass('popup-hidden');
3626 self.element.addClass('popup-showing active');
3627 focusInput(self.element);
3628 });
3629 };
3630
3631 self.hide = function(callback) {
3632 callback = callback || noop;
3633 if (!self.isShown) return callback();
3634
3635 $ionicModal.stack.remove(self);
3636 self.isShown = false;
3637 self.element.removeClass('active');
3638 self.element.addClass('popup-hidden');
3639 $timeout(callback, 250, false);
3640 };
3641
3642 self.remove = function() {
3643 if (self.removed) return;
3644
3645 self.hide(function() {
3646 self.element.remove();
3647 self.scope.$destroy();
3648 });
3649
3650 self.removed = true;
3651 };
3652
3653 return self;
3654 }
3655
3656 function onHardwareBackButton() {
3657 var last = popupStack[popupStack.length - 1];
3658 last && last.responseDeferred.resolve();
3659 }
3660
3661 function showPopup(options) {
3662 var popup = $ionicPopup._createPopup(options);
3663 var showDelay = 0;
3664
3665 if (popupStack.length > 0) {
3666 showDelay = config.stackPushDelay;
3667 $timeout(popupStack[popupStack.length - 1].hide, showDelay, false);
3668 } else {
3669 //Add popup-open & backdrop if this is first popup
3670 $ionicBody.addClass('popup-open');
3671 $ionicBackdrop.retain();
3672 //only show the backdrop on the first popup
3673 $ionicPopup._backButtonActionDone = $ionicPlatform.registerBackButtonAction(
3674 onHardwareBackButton,
3675 IONIC_BACK_PRIORITY.popup
3676 );
3677 }
3678
3679 // Expose a 'close' method on the returned promise
3680 popup.responseDeferred.promise.close = function popupClose(result) {
3681 if (!popup.removed) popup.responseDeferred.resolve(result);
3682 };
3683 //DEPRECATED: notify the promise with an object with a close method
3684 popup.responseDeferred.notify({ close: popup.responseDeferred.close });
3685
3686 doShow();
3687
3688 return popup.responseDeferred.promise;
3689
3690 function doShow() {
3691 popupStack.push(popup);
3692 $timeout(popup.show, showDelay, false);
3693
3694 popup.responseDeferred.promise.then(function(result) {
3695 var index = popupStack.indexOf(popup);
3696 if (index !== -1) {
3697 popupStack.splice(index, 1);
3698 }
3699
3700 popup.remove();
3701
3702 if (popupStack.length > 0) {
3703 popupStack[popupStack.length - 1].show();
3704 } else {
3705 $ionicBackdrop.release();
3706 //Remove popup-open & backdrop if this is last popup
3707 $timeout(function() {
3708 // wait to remove this due to a 300ms delay native
3709 // click which would trigging whatever was underneath this
3710 if (!popupStack.length) {
3711 $ionicBody.removeClass('popup-open');
3712 }
3713 }, 400, false);
3714 ($ionicPopup._backButtonActionDone || noop)();
3715 }
3716
3717
3718 return result;
3719 });
3720
3721 }
3722
3723 }
3724
3725 function focusInput(element) {
3726 var focusOn = element[0].querySelector('[autofocus]');
3727 if (focusOn) {
3728 focusOn.focus();
3729 }
3730 }
3731
3732 function showAlert(opts) {
3733 return showPopup(extend({
3734 buttons: [{
3735 text: opts.okText || 'OK',
3736 type: opts.okType || 'button-positive',
3737 onTap: function() {
3738 return true;
3739 }
3740 }]
3741 }, opts || {}));
3742 }
3743
3744 function showConfirm(opts) {
3745 return showPopup(extend({
3746 buttons: [{
3747 text: opts.cancelText || 'Cancel',
3748 type: opts.cancelType || 'button-default',
3749 onTap: function() { return false; }
3750 }, {
3751 text: opts.okText || 'OK',
3752 type: opts.okType || 'button-positive',
3753 onTap: function() { return true; }
3754 }]
3755 }, opts || {}));
3756 }
3757
3758 function showPrompt(opts) {
3759 var scope = $rootScope.$new(true);
3760 scope.data = {};
3761 scope.data.fieldtype = opts.inputType ? opts.inputType : 'text';
3762 scope.data.response = opts.defaultText ? opts.defaultText : '';
3763 scope.data.placeholder = opts.inputPlaceholder ? opts.inputPlaceholder : '';
3764 scope.data.maxlength = opts.maxLength ? parseInt(opts.maxLength) : '';
3765 var text = '';
3766 if (opts.template && /<[a-z][\s\S]*>/i.test(opts.template) === false) {
3767 text = '<span>' + opts.template + '</span>';
3768 delete opts.template;
3769 }
3770 return showPopup(extend({
3771 template: text + '<input ng-model="data.response" '
3772 + 'type="{{ data.fieldtype }}"'
3773 + 'maxlength="{{ data.maxlength }}"'
3774 + 'placeholder="{{ data.placeholder }}"'
3775 + '>',
3776 scope: scope,
3777 buttons: [{
3778 text: opts.cancelText || 'Cancel',
3779 type: opts.cancelType || 'button-default',
3780 onTap: function() {}
3781 }, {
3782 text: opts.okText || 'OK',
3783 type: opts.okType || 'button-positive',
3784 onTap: function() {
3785 return scope.data.response || '';
3786 }
3787 }]
3788 }, opts || {}));
3789 }
3790}]);
3791
3792/**
3793 * @ngdoc service
3794 * @name $ionicPosition
3795 * @module ionic
3796 * @description
3797 * A set of utility methods that can be use to retrieve position of DOM elements.
3798 * It is meant to be used where we need to absolute-position DOM elements in
3799 * relation to other, existing elements (this is the case for tooltips, popovers, etc.).
3800 *
3801 * Adapted from [AngularUI Bootstrap](https://github.com/angular-ui/bootstrap/blob/master/src/position/position.js),
3802 * ([license](https://github.com/angular-ui/bootstrap/blob/master/LICENSE))
3803 */
3804IonicModule
3805.factory('$ionicPosition', ['$document', '$window', function($document, $window) {
3806
3807 function getStyle(el, cssprop) {
3808 if (el.currentStyle) { //IE
3809 return el.currentStyle[cssprop];
3810 } else if ($window.getComputedStyle) {
3811 return $window.getComputedStyle(el)[cssprop];
3812 }
3813 // finally try and get inline style
3814 return el.style[cssprop];
3815 }
3816
3817 /**
3818 * Checks if a given element is statically positioned
3819 * @param element - raw DOM element
3820 */
3821 function isStaticPositioned(element) {
3822 return (getStyle(element, 'position') || 'static') === 'static';
3823 }
3824
3825 /**
3826 * returns the closest, non-statically positioned parentOffset of a given element
3827 * @param element
3828 */
3829 var parentOffsetEl = function(element) {
3830 var docDomEl = $document[0];
3831 var offsetParent = element.offsetParent || docDomEl;
3832 while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent)) {
3833 offsetParent = offsetParent.offsetParent;
3834 }
3835 return offsetParent || docDomEl;
3836 };
3837
3838 return {
3839 /**
3840 * @ngdoc method
3841 * @name $ionicPosition#position
3842 * @description Get the current coordinates of the element, relative to the offset parent.
3843 * Read-only equivalent of [jQuery's position function](http://api.jquery.com/position/).
3844 * @param {element} element The element to get the position of.
3845 * @returns {object} Returns an object containing the properties top, left, width and height.
3846 */
3847 position: function(element) {
3848 var elBCR = this.offset(element);
3849 var offsetParentBCR = { top: 0, left: 0 };
3850 var offsetParentEl = parentOffsetEl(element[0]);
3851 if (offsetParentEl != $document[0]) {
3852 offsetParentBCR = this.offset(jqLite(offsetParentEl));
3853 offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
3854 offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
3855 }
3856
3857 var boundingClientRect = element[0].getBoundingClientRect();
3858 return {
3859 width: boundingClientRect.width || element.prop('offsetWidth'),
3860 height: boundingClientRect.height || element.prop('offsetHeight'),
3861 top: elBCR.top - offsetParentBCR.top,
3862 left: elBCR.left - offsetParentBCR.left
3863 };
3864 },
3865
3866 /**
3867 * @ngdoc method
3868 * @name $ionicPosition#offset
3869 * @description Get the current coordinates of the element, relative to the document.
3870 * Read-only equivalent of [jQuery's offset function](http://api.jquery.com/offset/).
3871 * @param {element} element The element to get the offset of.
3872 * @returns {object} Returns an object containing the properties top, left, width and height.
3873 */
3874 offset: function(element) {
3875 var boundingClientRect = element[0].getBoundingClientRect();
3876 return {
3877 width: boundingClientRect.width || element.prop('offsetWidth'),
3878 height: boundingClientRect.height || element.prop('offsetHeight'),
3879 top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
3880 left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
3881 };
3882 }
3883
3884 };
3885}]);
3886
3887
3888/**
3889 * @ngdoc service
3890 * @name $ionicScrollDelegate
3891 * @module ionic
3892 * @description
3893 * Delegate for controlling scrollViews (created by
3894 * {@link ionic.directive:ionContent} and
3895 * {@link ionic.directive:ionScroll} directives).
3896 *
3897 * Methods called directly on the $ionicScrollDelegate service will control all scroll
3898 * views. Use the {@link ionic.service:$ionicScrollDelegate#$getByHandle $getByHandle}
3899 * method to control specific scrollViews.
3900 *
3901 * @usage
3902 *
3903 * ```html
3904 * <body ng-controller="MainCtrl">
3905 * <ion-content>
3906 * <button ng-click="scrollTop()">Scroll to Top!</button>
3907 * </ion-content>
3908 * </body>
3909 * ```
3910 * ```js
3911 * function MainCtrl($scope, $ionicScrollDelegate) {
3912 * $scope.scrollTop = function() {
3913 * $ionicScrollDelegate.scrollTop();
3914 * };
3915 * }
3916 * ```
3917 *
3918 * Example of advanced usage, with two scroll areas using `delegate-handle`
3919 * for fine control.
3920 *
3921 * ```html
3922 * <body ng-controller="MainCtrl">
3923 * <ion-content delegate-handle="mainScroll">
3924 * <button ng-click="scrollMainToTop()">
3925 * Scroll content to top!
3926 * </button>
3927 * <ion-scroll delegate-handle="small" style="height: 100px;">
3928 * <button ng-click="scrollSmallToTop()">
3929 * Scroll small area to top!
3930 * </button>
3931 * </ion-scroll>
3932 * </ion-content>
3933 * </body>
3934 * ```
3935 * ```js
3936 * function MainCtrl($scope, $ionicScrollDelegate) {
3937 * $scope.scrollMainToTop = function() {
3938 * $ionicScrollDelegate.$getByHandle('mainScroll').scrollTop();
3939 * };
3940 * $scope.scrollSmallToTop = function() {
3941 * $ionicScrollDelegate.$getByHandle('small').scrollTop();
3942 * };
3943 * }
3944 * ```
3945 */
3946IonicModule
3947.service('$ionicScrollDelegate', ionic.DelegateService([
3948 /**
3949 * @ngdoc method
3950 * @name $ionicScrollDelegate#resize
3951 * @description Tell the scrollView to recalculate the size of its container.
3952 */
3953 'resize',
3954 /**
3955 * @ngdoc method
3956 * @name $ionicScrollDelegate#scrollTop
3957 * @param {boolean=} shouldAnimate Whether the scroll should animate.
3958 */
3959 'scrollTop',
3960 /**
3961 * @ngdoc method
3962 * @name $ionicScrollDelegate#scrollBottom
3963 * @param {boolean=} shouldAnimate Whether the scroll should animate.
3964 */
3965 'scrollBottom',
3966 /**
3967 * @ngdoc method
3968 * @name $ionicScrollDelegate#scrollTo
3969 * @param {number} left The x-value to scroll to.
3970 * @param {number} top The y-value to scroll to.
3971 * @param {boolean=} shouldAnimate Whether the scroll should animate.
3972 */
3973 'scrollTo',
3974 /**
3975 * @ngdoc method
3976 * @name $ionicScrollDelegate#scrollBy
3977 * @param {number} left The x-offset to scroll by.
3978 * @param {number} top The y-offset to scroll by.
3979 * @param {boolean=} shouldAnimate Whether the scroll should animate.
3980 */
3981 'scrollBy',
3982 /**
3983 * @ngdoc method
3984 * @name $ionicScrollDelegate#zoomTo
3985 * @param {number} level Level to zoom to.
3986 * @param {boolean=} animate Whether to animate the zoom.
3987 * @param {number=} originLeft Zoom in at given left coordinate.
3988 * @param {number=} originTop Zoom in at given top coordinate.
3989 */
3990 'zoomTo',
3991 /**
3992 * @ngdoc method
3993 * @name $ionicScrollDelegate#zoomBy
3994 * @param {number} factor The factor to zoom by.
3995 * @param {boolean=} animate Whether to animate the zoom.
3996 * @param {number=} originLeft Zoom in at given left coordinate.
3997 * @param {number=} originTop Zoom in at given top coordinate.
3998 */
3999 'zoomBy',
4000 /**
4001 * @ngdoc method
4002 * @name $ionicScrollDelegate#getScrollPosition
4003 * @returns {object} The scroll position of this view, with the following properties:
4004 * - `{number}` `left` The distance the user has scrolled from the left (starts at 0).
4005 * - `{number}` `top` The distance the user has scrolled from the top (starts at 0).
4006 * - `{number}` `zoom` The current zoom level.
4007 */
4008 'getScrollPosition',
4009 /**
4010 * @ngdoc method
4011 * @name $ionicScrollDelegate#anchorScroll
4012 * @description Tell the scrollView to scroll to the element with an id
4013 * matching window.location.hash.
4014 *
4015 * If no matching element is found, it will scroll to top.
4016 *
4017 * @param {boolean=} shouldAnimate Whether the scroll should animate.
4018 */
4019 'anchorScroll',
4020 /**
4021 * @ngdoc method
4022 * @name $ionicScrollDelegate#freezeScroll
4023 * @description Does not allow this scroll view to scroll either x or y.
4024 * @param {boolean=} shouldFreeze Should this scroll view be prevented from scrolling or not.
4025 * @returns {boolean} If the scroll view is being prevented from scrolling or not.
4026 */
4027 'freezeScroll',
4028 /**
4029 * @ngdoc method
4030 * @name $ionicScrollDelegate#freezeAllScrolls
4031 * @description Does not allow any of the app's scroll views to scroll either x or y.
4032 * @param {boolean=} shouldFreeze Should all app scrolls be prevented from scrolling or not.
4033 */
4034 'freezeAllScrolls',
4035 /**
4036 * @ngdoc method
4037 * @name $ionicScrollDelegate#getScrollView
4038 * @returns {object} The scrollView associated with this delegate.
4039 */
4040 'getScrollView'
4041 /**
4042 * @ngdoc method
4043 * @name $ionicScrollDelegate#$getByHandle
4044 * @param {string} handle
4045 * @returns `delegateInstance` A delegate instance that controls only the
4046 * scrollViews with `delegate-handle` matching the given handle.
4047 *
4048 * Example: `$ionicScrollDelegate.$getByHandle('my-handle').scrollTop();`
4049 */
4050]));
4051
4052
4053/**
4054 * @ngdoc service
4055 * @name $ionicSideMenuDelegate
4056 * @module ionic
4057 *
4058 * @description
4059 * Delegate for controlling the {@link ionic.directive:ionSideMenus} directive.
4060 *
4061 * Methods called directly on the $ionicSideMenuDelegate service will control all side
4062 * menus. Use the {@link ionic.service:$ionicSideMenuDelegate#$getByHandle $getByHandle}
4063 * method to control specific ionSideMenus instances.
4064 *
4065 * @usage
4066 *
4067 * ```html
4068 * <body ng-controller="MainCtrl">
4069 * <ion-side-menus>
4070 * <ion-side-menu-content>
4071 * Content!
4072 * <button ng-click="toggleLeftSideMenu()">
4073 * Toggle Left Side Menu
4074 * </button>
4075 * </ion-side-menu-content>
4076 * <ion-side-menu side="left">
4077 * Left Menu!
4078 * <ion-side-menu>
4079 * </ion-side-menus>
4080 * </body>
4081 * ```
4082 * ```js
4083 * function MainCtrl($scope, $ionicSideMenuDelegate) {
4084 * $scope.toggleLeftSideMenu = function() {
4085 * $ionicSideMenuDelegate.toggleLeft();
4086 * };
4087 * }
4088 * ```
4089 */
4090IonicModule
4091.service('$ionicSideMenuDelegate', ionic.DelegateService([
4092 /**
4093 * @ngdoc method
4094 * @name $ionicSideMenuDelegate#toggleLeft
4095 * @description Toggle the left side menu (if it exists).
4096 * @param {boolean=} isOpen Whether to open or close the menu.
4097 * Default: Toggles the menu.
4098 */
4099 'toggleLeft',
4100 /**
4101 * @ngdoc method
4102 * @name $ionicSideMenuDelegate#toggleRight
4103 * @description Toggle the right side menu (if it exists).
4104 * @param {boolean=} isOpen Whether to open or close the menu.
4105 * Default: Toggles the menu.
4106 */
4107 'toggleRight',
4108 /**
4109 * @ngdoc method
4110 * @name $ionicSideMenuDelegate#getOpenRatio
4111 * @description Gets the ratio of open amount over menu width. For example, a
4112 * menu of width 100 that is opened by 50 pixels is 50% opened, and would return
4113 * a ratio of 0.5.
4114 *
4115 * @returns {float} 0 if nothing is open, between 0 and 1 if left menu is
4116 * opened/opening, and between 0 and -1 if right menu is opened/opening.
4117 */
4118 'getOpenRatio',
4119 /**
4120 * @ngdoc method
4121 * @name $ionicSideMenuDelegate#isOpen
4122 * @returns {boolean} Whether either the left or right menu is currently opened.
4123 */
4124 'isOpen',
4125 /**
4126 * @ngdoc method
4127 * @name $ionicSideMenuDelegate#isOpenLeft
4128 * @returns {boolean} Whether the left menu is currently opened.
4129 */
4130 'isOpenLeft',
4131 /**
4132 * @ngdoc method
4133 * @name $ionicSideMenuDelegate#isOpenRight
4134 * @returns {boolean} Whether the right menu is currently opened.
4135 */
4136 'isOpenRight',
4137 /**
4138 * @ngdoc method
4139 * @name $ionicSideMenuDelegate#canDragContent
4140 * @param {boolean=} canDrag Set whether the content can or cannot be dragged to open
4141 * side menus.
4142 * @returns {boolean} Whether the content can be dragged to open side menus.
4143 */
4144 'canDragContent',
4145 /**
4146 * @ngdoc method
4147 * @name $ionicSideMenuDelegate#edgeDragThreshold
4148 * @param {boolean|number=} value Set whether the content drag can only start if it is below a certain threshold distance from the edge of the screen. Accepts three different values:
4149 * - If a non-zero number is given, that many pixels is used as the maximum allowed distance from the edge that starts dragging the side menu.
4150 * - If true is given, the default number of pixels (25) is used as the maximum allowed distance.
4151 * - If false or 0 is given, the edge drag threshold is disabled, and dragging from anywhere on the content is allowed.
4152 * @returns {boolean} Whether the drag can start only from within the edge of screen threshold.
4153 */
4154 'edgeDragThreshold'
4155 /**
4156 * @ngdoc method
4157 * @name $ionicSideMenuDelegate#$getByHandle
4158 * @param {string} handle
4159 * @returns `delegateInstance` A delegate instance that controls only the
4160 * {@link ionic.directive:ionSideMenus} directives with `delegate-handle` matching
4161 * the given handle.
4162 *
4163 * Example: `$ionicSideMenuDelegate.$getByHandle('my-handle').toggleLeft();`
4164 */
4165]));
4166
4167
4168/**
4169 * @ngdoc service
4170 * @name $ionicSlideBoxDelegate
4171 * @module ionic
4172 * @description
4173 * Delegate that controls the {@link ionic.directive:ionSlideBox} directive.
4174 *
4175 * Methods called directly on the $ionicSlideBoxDelegate service will control all slide boxes. Use the {@link ionic.service:$ionicSlideBoxDelegate#$getByHandle $getByHandle}
4176 * method to control specific slide box instances.
4177 *
4178 * @usage
4179 *
4180 * ```html
4181 * <ion-view>
4182 * <ion-slide-box>
4183 * <ion-slide>
4184 * <div class="box blue">
4185 * <button ng-click="nextSlide()">Next slide!</button>
4186 * </div>
4187 * </ion-slide>
4188 * <ion-slide>
4189 * <div class="box red">
4190 * Slide 2!
4191 * </div>
4192 * </ion-slide>
4193 * </ion-slide-box>
4194 * </ion-view>
4195 * ```
4196 * ```js
4197 * function MyCtrl($scope, $ionicSlideBoxDelegate) {
4198 * $scope.nextSlide = function() {
4199 * $ionicSlideBoxDelegate.next();
4200 * }
4201 * }
4202 * ```
4203 */
4204IonicModule
4205.service('$ionicSlideBoxDelegate', ionic.DelegateService([
4206 /**
4207 * @ngdoc method
4208 * @name $ionicSlideBoxDelegate#update
4209 * @description
4210 * Update the slidebox (for example if using Angular with ng-repeat,
4211 * resize it for the elements inside).
4212 */
4213 'update',
4214 /**
4215 * @ngdoc method
4216 * @name $ionicSlideBoxDelegate#slide
4217 * @param {number} to The index to slide to.
4218 * @param {number=} speed The number of milliseconds the change should take.
4219 */
4220 'slide',
4221 'select',
4222 /**
4223 * @ngdoc method
4224 * @name $ionicSlideBoxDelegate#enableSlide
4225 * @param {boolean=} shouldEnable Whether to enable sliding the slidebox.
4226 * @returns {boolean} Whether sliding is enabled.
4227 */
4228 'enableSlide',
4229 /**
4230 * @ngdoc method
4231 * @name $ionicSlideBoxDelegate#previous
4232 * @param {number=} speed The number of milliseconds the change should take.
4233 * @description Go to the previous slide. Wraps around if at the beginning.
4234 */
4235 'previous',
4236 /**
4237 * @ngdoc method
4238 * @name $ionicSlideBoxDelegate#next
4239 * @param {number=} speed The number of milliseconds the change should take.
4240 * @description Go to the next slide. Wraps around if at the end.
4241 */
4242 'next',
4243 /**
4244 * @ngdoc method
4245 * @name $ionicSlideBoxDelegate#stop
4246 * @description Stop sliding. The slideBox will not move again until
4247 * explicitly told to do so.
4248 */
4249 'stop',
4250 'autoPlay',
4251 /**
4252 * @ngdoc method
4253 * @name $ionicSlideBoxDelegate#start
4254 * @description Start sliding again if the slideBox was stopped.
4255 */
4256 'start',
4257 /**
4258 * @ngdoc method
4259 * @name $ionicSlideBoxDelegate#currentIndex
4260 * @returns number The index of the current slide.
4261 */
4262 'currentIndex',
4263 'selected',
4264 /**
4265 * @ngdoc method
4266 * @name $ionicSlideBoxDelegate#slidesCount
4267 * @returns number The number of slides there are currently.
4268 */
4269 'slidesCount',
4270 'count',
4271 'loop'
4272 /**
4273 * @ngdoc method
4274 * @name $ionicSlideBoxDelegate#$getByHandle
4275 * @param {string} handle
4276 * @returns `delegateInstance` A delegate instance that controls only the
4277 * {@link ionic.directive:ionSlideBox} directives with `delegate-handle` matching
4278 * the given handle.
4279 *
4280 * Example: `$ionicSlideBoxDelegate.$getByHandle('my-handle').stop();`
4281 */
4282]));
4283
4284
4285/**
4286 * @ngdoc service
4287 * @name $ionicTabsDelegate
4288 * @module ionic
4289 *
4290 * @description
4291 * Delegate for controlling the {@link ionic.directive:ionTabs} directive.
4292 *
4293 * Methods called directly on the $ionicTabsDelegate service will control all ionTabs
4294 * directives. Use the {@link ionic.service:$ionicTabsDelegate#$getByHandle $getByHandle}
4295 * method to control specific ionTabs instances.
4296 *
4297 * @usage
4298 *
4299 * ```html
4300 * <body ng-controller="MyCtrl">
4301 * <ion-tabs>
4302 *
4303 * <ion-tab title="Tab 1">
4304 * Hello tab 1!
4305 * <button ng-click="selectTabWithIndex(1)">Select tab 2!</button>
4306 * </ion-tab>
4307 * <ion-tab title="Tab 2">Hello tab 2!</ion-tab>
4308 *
4309 * </ion-tabs>
4310 * </body>
4311 * ```
4312 * ```js
4313 * function MyCtrl($scope, $ionicTabsDelegate) {
4314 * $scope.selectTabWithIndex = function(index) {
4315 * $ionicTabsDelegate.select(index);
4316 * }
4317 * }
4318 * ```
4319 */
4320IonicModule
4321.service('$ionicTabsDelegate', ionic.DelegateService([
4322 /**
4323 * @ngdoc method
4324 * @name $ionicTabsDelegate#select
4325 * @description Select the tab matching the given index.
4326 *
4327 * @param {number} index Index of the tab to select.
4328 */
4329 'select',
4330 /**
4331 * @ngdoc method
4332 * @name $ionicTabsDelegate#selectedIndex
4333 * @returns `number` The index of the selected tab, or -1.
4334 */
4335 'selectedIndex',
4336 /**
4337 * @ngdoc method
4338 * @name $ionicTabsDelegate#showBar
4339 * @description
4340 * Set/get whether the {@link ionic.directive:ionTabs} is shown
4341 * @param {boolean} show Whether to show the bar.
4342 * @returns {boolean} Whether the bar is shown.
4343 */
4344 'showBar'
4345 /**
4346 * @ngdoc method
4347 * @name $ionicTabsDelegate#$getByHandle
4348 * @param {string} handle
4349 * @returns `delegateInstance` A delegate instance that controls only the
4350 * {@link ionic.directive:ionTabs} directives with `delegate-handle` matching
4351 * the given handle.
4352 *
4353 * Example: `$ionicTabsDelegate.$getByHandle('my-handle').select(0);`
4354 */
4355]));
4356
4357// closure to keep things neat
4358(function() {
4359 var templatesToCache = [];
4360
4361/**
4362 * @ngdoc service
4363 * @name $ionicTemplateCache
4364 * @module ionic
4365 * @description A service that preemptively caches template files to eliminate transition flicker and boost performance.
4366 * @usage
4367 * State templates are cached automatically, but you can optionally cache other templates.
4368 *
4369 * ```js
4370 * $ionicTemplateCache('myNgIncludeTemplate.html');
4371 * ```
4372 *
4373 * Optionally disable all preemptive caching with the `$ionicConfigProvider` or individual states by setting `prefetchTemplate`
4374 * in the `$state` definition
4375 *
4376 * ```js
4377 * angular.module('myApp', ['ionic'])
4378 * .config(function($stateProvider, $ionicConfigProvider) {
4379 *
4380 * // disable preemptive template caching globally
4381 * $ionicConfigProvider.templates.prefetch(false);
4382 *
4383 * // disable individual states
4384 * $stateProvider
4385 * .state('tabs', {
4386 * url: "/tab",
4387 * abstract: true,
4388 * prefetchTemplate: false,
4389 * templateUrl: "tabs-templates/tabs.html"
4390 * })
4391 * .state('tabs.home', {
4392 * url: "/home",
4393 * views: {
4394 * 'home-tab': {
4395 * prefetchTemplate: false,
4396 * templateUrl: "tabs-templates/home.html",
4397 * controller: 'HomeTabCtrl'
4398 * }
4399 * }
4400 * });
4401 * });
4402 * ```
4403 */
4404IonicModule
4405.factory('$ionicTemplateCache', [
4406'$http',
4407'$templateCache',
4408'$timeout',
4409function($http, $templateCache, $timeout) {
4410 var toCache = templatesToCache,
4411 hasRun;
4412
4413 function $ionicTemplateCache(templates) {
4414 if (typeof templates === 'undefined') {
4415 return run();
4416 }
4417 if (isString(templates)) {
4418 templates = [templates];
4419 }
4420 forEach(templates, function(template) {
4421 toCache.push(template);
4422 });
4423 if (hasRun) {
4424 run();
4425 }
4426 }
4427
4428 // run through methods - internal method
4429 function run() {
4430 var template;
4431 $ionicTemplateCache._runCount++;
4432
4433 hasRun = true;
4434 // ignore if race condition already zeroed out array
4435 if (toCache.length === 0) return;
4436
4437 var i = 0;
4438 while (i < 4 && (template = toCache.pop())) {
4439 // note that inline templates are ignored by this request
4440 if (isString(template)) $http.get(template, { cache: $templateCache });
4441 i++;
4442 }
4443 // only preload 3 templates a second
4444 if (toCache.length) {
4445 $timeout(run, 1000);
4446 }
4447 }
4448
4449 // exposing for testing
4450 $ionicTemplateCache._runCount = 0;
4451 // default method
4452 return $ionicTemplateCache;
4453}])
4454
4455// Intercepts the $stateprovider.state() command to look for templateUrls that can be cached
4456.config([
4457'$stateProvider',
4458'$ionicConfigProvider',
4459function($stateProvider, $ionicConfigProvider) {
4460 var stateProviderState = $stateProvider.state;
4461 $stateProvider.state = function(stateName, definition) {
4462 // don't even bother if it's disabled. note, another config may run after this, so it's not a catch-all
4463 if (typeof definition === 'object') {
4464 var enabled = definition.prefetchTemplate !== false && templatesToCache.length < $ionicConfigProvider.templates.maxPrefetch();
4465 if (enabled && isString(definition.templateUrl)) templatesToCache.push(definition.templateUrl);
4466 if (angular.isObject(definition.views)) {
4467 for (var key in definition.views) {
4468 enabled = definition.views[key].prefetchTemplate !== false && templatesToCache.length < $ionicConfigProvider.templates.maxPrefetch();
4469 if (enabled && isString(definition.views[key].templateUrl)) templatesToCache.push(definition.views[key].templateUrl);
4470 }
4471 }
4472 }
4473 return stateProviderState.call($stateProvider, stateName, definition);
4474 };
4475}])
4476
4477// process the templateUrls collected by the $stateProvider, adding them to the cache
4478.run(['$ionicTemplateCache', function($ionicTemplateCache) {
4479 $ionicTemplateCache();
4480}]);
4481
4482})();
4483
4484IonicModule
4485.factory('$ionicTemplateLoader', [
4486 '$compile',
4487 '$controller',
4488 '$http',
4489 '$q',
4490 '$rootScope',
4491 '$templateCache',
4492function($compile, $controller, $http, $q, $rootScope, $templateCache) {
4493
4494 return {
4495 load: fetchTemplate,
4496 compile: loadAndCompile
4497 };
4498
4499 function fetchTemplate(url) {
4500 return $http.get(url, {cache: $templateCache})
4501 .then(function(response) {
4502 return response.data && response.data.trim();
4503 });
4504 }
4505
4506 function loadAndCompile(options) {
4507 options = extend({
4508 template: '',
4509 templateUrl: '',
4510 scope: null,
4511 controller: null,
4512 locals: {},
4513 appendTo: null
4514 }, options || {});
4515
4516 var templatePromise = options.templateUrl ?
4517 this.load(options.templateUrl) :
4518 $q.when(options.template);
4519
4520 return templatePromise.then(function(template) {
4521 var controller;
4522 var scope = options.scope || $rootScope.$new();
4523
4524 //Incase template doesn't have just one root element, do this
4525 var element = jqLite('<div>').html(template).contents();
4526
4527 if (options.controller) {
4528 controller = $controller(
4529 options.controller,
4530 extend(options.locals, {
4531 $scope: scope
4532 })
4533 );
4534 element.children().data('$ngControllerController', controller);
4535 }
4536 if (options.appendTo) {
4537 jqLite(options.appendTo).append(element);
4538 }
4539
4540 $compile(element)(scope);
4541
4542 return {
4543 element: element,
4544 scope: scope
4545 };
4546 });
4547 }
4548
4549}]);
4550
4551/**
4552 * @private
4553 * DEPRECATED, as of v1.0.0-beta14 -------
4554 */
4555IonicModule
4556.factory('$ionicViewService', ['$ionicHistory', '$log', function($ionicHistory, $log) {
4557
4558 function warn(oldMethod, newMethod) {
4559 $log.warn('$ionicViewService' + oldMethod + ' is deprecated, please use $ionicHistory' + newMethod + ' instead: http://ionicframework.com/docs/nightly/api/service/$ionicHistory/');
4560 }
4561
4562 warn('', '');
4563
4564 var methodsMap = {
4565 getCurrentView: 'currentView',
4566 getBackView: 'backView',
4567 getForwardView: 'forwardView',
4568 getCurrentStateName: 'currentStateName',
4569 nextViewOptions: 'nextViewOptions',
4570 clearHistory: 'clearHistory'
4571 };
4572
4573 forEach(methodsMap, function(newMethod, oldMethod) {
4574 methodsMap[oldMethod] = function() {
4575 warn('.' + oldMethod, '.' + newMethod);
4576 return $ionicHistory[newMethod].apply(this, arguments);
4577 };
4578 });
4579
4580 return methodsMap;
4581
4582}]);
4583
4584/**
4585 * @private
4586 * TODO document
4587 */
4588
4589IonicModule.factory('$ionicViewSwitcher', [
4590 '$timeout',
4591 '$document',
4592 '$q',
4593 '$ionicClickBlock',
4594 '$ionicConfig',
4595 '$ionicNavBarDelegate',
4596function($timeout, $document, $q, $ionicClickBlock, $ionicConfig, $ionicNavBarDelegate) {
4597
4598 var TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
4599 var DATA_NO_CACHE = '$noCache';
4600 var DATA_DESTROY_ELE = '$destroyEle';
4601 var DATA_ELE_IDENTIFIER = '$eleId';
4602 var DATA_VIEW_ACCESSED = '$accessed';
4603 var DATA_FALLBACK_TIMER = '$fallbackTimer';
4604 var DATA_VIEW = '$viewData';
4605 var NAV_VIEW_ATTR = 'nav-view';
4606 var VIEW_STATUS_ACTIVE = 'active';
4607 var VIEW_STATUS_CACHED = 'cached';
4608 var VIEW_STATUS_STAGED = 'stage';
4609
4610 var transitionCounter = 0;
4611 var nextTransition, nextDirection;
4612 ionic.transition = ionic.transition || {};
4613 ionic.transition.isActive = false;
4614 var isActiveTimer;
4615 var cachedAttr = ionic.DomUtil.cachedAttr;
4616 var transitionPromises = [];
4617 var defaultTimeout = 1100;
4618
4619 var ionicViewSwitcher = {
4620
4621 create: function(navViewCtrl, viewLocals, enteringView, leavingView, renderStart, renderEnd) {
4622 // get a reference to an entering/leaving element if they exist
4623 // loop through to see if the view is already in the navViewElement
4624 var enteringEle, leavingEle;
4625 var transitionId = ++transitionCounter;
4626 var alreadyInDom;
4627
4628 var switcher = {
4629
4630 init: function(registerData, callback) {
4631 ionicViewSwitcher.isTransitioning(true);
4632
4633 switcher.loadViewElements(registerData);
4634
4635 switcher.render(registerData, function() {
4636 callback && callback();
4637 });
4638 },
4639
4640 loadViewElements: function(registerData) {
4641 var x, l, viewEle;
4642 var viewElements = navViewCtrl.getViewElements();
4643 var enteringEleIdentifier = getViewElementIdentifier(viewLocals, enteringView);
4644 var navViewActiveEleId = navViewCtrl.activeEleId();
4645
4646 for (x = 0, l = viewElements.length; x < l; x++) {
4647 viewEle = viewElements.eq(x);
4648
4649 if (viewEle.data(DATA_ELE_IDENTIFIER) === enteringEleIdentifier) {
4650 // we found an existing element in the DOM that should be entering the view
4651 if (viewEle.data(DATA_NO_CACHE)) {
4652 // the existing element should not be cached, don't use it
4653 viewEle.data(DATA_ELE_IDENTIFIER, enteringEleIdentifier + ionic.Utils.nextUid());
4654 viewEle.data(DATA_DESTROY_ELE, true);
4655
4656 } else {
4657 enteringEle = viewEle;
4658 }
4659
4660 } else if (isDefined(navViewActiveEleId) && viewEle.data(DATA_ELE_IDENTIFIER) === navViewActiveEleId) {
4661 leavingEle = viewEle;
4662 }
4663
4664 if (enteringEle && leavingEle) break;
4665 }
4666
4667 alreadyInDom = !!enteringEle;
4668
4669 if (!alreadyInDom) {
4670 // still no existing element to use
4671 // create it using existing template/scope/locals
4672 enteringEle = registerData.ele || ionicViewSwitcher.createViewEle(viewLocals);
4673
4674 // existing elements in the DOM are looked up by their state name and state id
4675 enteringEle.data(DATA_ELE_IDENTIFIER, enteringEleIdentifier);
4676 }
4677
4678 if (renderEnd) {
4679 navViewCtrl.activeEleId(enteringEleIdentifier);
4680 }
4681
4682 registerData.ele = null;
4683 },
4684
4685 render: function(registerData, callback) {
4686 if (alreadyInDom) {
4687 // it was already found in the DOM, just reconnect the scope
4688 ionic.Utils.reconnectScope(enteringEle.scope());
4689
4690 } else {
4691 // the entering element is not already in the DOM
4692 // set that the entering element should be "staged" and its
4693 // styles of where this element will go before it hits the DOM
4694 navViewAttr(enteringEle, VIEW_STATUS_STAGED);
4695
4696 var enteringData = getTransitionData(viewLocals, enteringEle, registerData.direction, enteringView);
4697 var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none;
4698 transitionFn(enteringEle, null, enteringData.direction, true).run(0);
4699
4700 enteringEle.data(DATA_VIEW, {
4701 viewId: enteringData.viewId,
4702 historyId: enteringData.historyId,
4703 stateName: enteringData.stateName,
4704 stateParams: enteringData.stateParams
4705 });
4706
4707 // if the current state has cache:false
4708 // or the element has cache-view="false" attribute
4709 if (viewState(viewLocals).cache === false || viewState(viewLocals).cache === 'false' ||
4710 enteringEle.attr('cache-view') == 'false' || $ionicConfig.views.maxCache() === 0) {
4711 enteringEle.data(DATA_NO_CACHE, true);
4712 }
4713
4714 // append the entering element to the DOM, create a new scope and run link
4715 var viewScope = navViewCtrl.appendViewElement(enteringEle, viewLocals);
4716
4717 delete enteringData.direction;
4718 delete enteringData.transition;
4719 viewScope.$emit('$ionicView.loaded', enteringData);
4720 }
4721
4722 // update that this view was just accessed
4723 enteringEle.data(DATA_VIEW_ACCESSED, Date.now());
4724
4725 callback && callback();
4726 },
4727
4728 transition: function(direction, enableBack, allowAnimate) {
4729 var deferred;
4730 var enteringData = getTransitionData(viewLocals, enteringEle, direction, enteringView);
4731 var leavingData = extend(extend({}, enteringData), getViewData(leavingView));
4732 enteringData.transitionId = leavingData.transitionId = transitionId;
4733 enteringData.fromCache = !!alreadyInDom;
4734 enteringData.enableBack = !!enableBack;
4735 enteringData.renderStart = renderStart;
4736 enteringData.renderEnd = renderEnd;
4737
4738 cachedAttr(enteringEle.parent(), 'nav-view-transition', enteringData.transition);
4739 cachedAttr(enteringEle.parent(), 'nav-view-direction', enteringData.direction);
4740
4741 // cancel any previous transition complete fallbacks
4742 $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
4743
4744 // get the transition ready and see if it'll animate
4745 var transitionFn = $ionicConfig.transitions.views[enteringData.transition] || $ionicConfig.transitions.views.none;
4746 var viewTransition = transitionFn(enteringEle, leavingEle, enteringData.direction,
4747 enteringData.shouldAnimate && allowAnimate && renderEnd);
4748
4749 if (viewTransition.shouldAnimate) {
4750 // attach transitionend events (and fallback timer)
4751 enteringEle.on(TRANSITIONEND_EVENT, completeOnTransitionEnd);
4752 enteringEle.data(DATA_FALLBACK_TIMER, $timeout(transitionComplete, defaultTimeout));
4753 $ionicClickBlock.show(defaultTimeout);
4754 }
4755
4756 if (renderStart) {
4757 // notify the views "before" the transition starts
4758 switcher.emit('before', enteringData, leavingData);
4759
4760 // stage entering element, opacity 0, no transition duration
4761 navViewAttr(enteringEle, VIEW_STATUS_STAGED);
4762
4763 // render the elements in the correct location for their starting point
4764 viewTransition.run(0);
4765 }
4766
4767 if (renderEnd) {
4768 // create a promise so we can keep track of when all transitions finish
4769 // only required if this transition should complete
4770 deferred = $q.defer();
4771 transitionPromises.push(deferred.promise);
4772 }
4773
4774 if (renderStart && renderEnd) {
4775 // CSS "auto" transitioned, not manually transitioned
4776 // wait a frame so the styles apply before auto transitioning
4777 $timeout(function() {
4778 ionic.requestAnimationFrame(onReflow);
4779 });
4780 } else if (!renderEnd) {
4781 // just the start of a manual transition
4782 // but it will not render the end of the transition
4783 navViewAttr(enteringEle, 'entering');
4784 navViewAttr(leavingEle, 'leaving');
4785
4786 // return the transition run method so each step can be ran manually
4787 return {
4788 run: viewTransition.run,
4789 cancel: function(shouldAnimate) {
4790 if (shouldAnimate) {
4791 enteringEle.on(TRANSITIONEND_EVENT, cancelOnTransitionEnd);
4792 enteringEle.data(DATA_FALLBACK_TIMER, $timeout(cancelTransition, defaultTimeout));
4793 $ionicClickBlock.show(defaultTimeout);
4794 } else {
4795 cancelTransition();
4796 }
4797 viewTransition.shouldAnimate = shouldAnimate;
4798 viewTransition.run(0);
4799 viewTransition = null;
4800 }
4801 };
4802
4803 } else if (renderEnd) {
4804 // just the end of a manual transition
4805 // happens after the manual transition has completed
4806 // and a full history change has happened
4807 onReflow();
4808 }
4809
4810
4811 function onReflow() {
4812 // remove that we're staging the entering element so it can auto transition
4813 navViewAttr(enteringEle, viewTransition.shouldAnimate ? 'entering' : VIEW_STATUS_ACTIVE);
4814 navViewAttr(leavingEle, viewTransition.shouldAnimate ? 'leaving' : VIEW_STATUS_CACHED);
4815
4816 // start the auto transition and let the CSS take over
4817 viewTransition.run(1);
4818
4819 // trigger auto transitions on the associated nav bars
4820 $ionicNavBarDelegate._instances.forEach(function(instance) {
4821 instance.triggerTransitionStart(transitionId);
4822 });
4823
4824 if (!viewTransition.shouldAnimate) {
4825 // no animated auto transition
4826 transitionComplete();
4827 }
4828 }
4829
4830 // Make sure that transitionend events bubbling up from children won't fire
4831 // transitionComplete. Will only go forward if ev.target == the element listening.
4832 function completeOnTransitionEnd(ev) {
4833 if (ev.target !== this) return;
4834 transitionComplete();
4835 }
4836 function transitionComplete() {
4837 if (transitionComplete.x) return;
4838 transitionComplete.x = true;
4839
4840 enteringEle.off(TRANSITIONEND_EVENT, completeOnTransitionEnd);
4841 $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
4842 leavingEle && $timeout.cancel(leavingEle.data(DATA_FALLBACK_TIMER));
4843
4844 // resolve that this one transition (there could be many w/ nested views)
4845 deferred && deferred.resolve(navViewCtrl);
4846
4847 // the most recent transition added has completed and all the active
4848 // transition promises should be added to the services array of promises
4849 if (transitionId === transitionCounter) {
4850 $q.all(transitionPromises).then(ionicViewSwitcher.transitionEnd);
4851
4852 // emit that the views have finished transitioning
4853 // each parent nav-view will update which views are active and cached
4854 switcher.emit('after', enteringData, leavingData);
4855 switcher.cleanup(enteringData);
4856 }
4857
4858 // tell the nav bars that the transition has ended
4859 $ionicNavBarDelegate._instances.forEach(function(instance) {
4860 instance.triggerTransitionEnd();
4861 });
4862
4863
4864 // remove any references that could cause memory issues
4865 nextTransition = nextDirection = enteringView = leavingView = enteringEle = leavingEle = null;
4866 }
4867
4868 // Make sure that transitionend events bubbling up from children won't fire
4869 // transitionComplete. Will only go forward if ev.target == the element listening.
4870 function cancelOnTransitionEnd(ev) {
4871 if (ev.target !== this) return;
4872 cancelTransition();
4873 }
4874 function cancelTransition() {
4875 navViewAttr(enteringEle, VIEW_STATUS_CACHED);
4876 navViewAttr(leavingEle, VIEW_STATUS_ACTIVE);
4877 enteringEle.off(TRANSITIONEND_EVENT, cancelOnTransitionEnd);
4878 $timeout.cancel(enteringEle.data(DATA_FALLBACK_TIMER));
4879 ionicViewSwitcher.transitionEnd([navViewCtrl]);
4880 }
4881
4882 },
4883
4884 emit: function(step, enteringData, leavingData) {
4885 var enteringScope = getScopeForElement(enteringEle, enteringData);
4886 var leavingScope = getScopeForElement(leavingEle, leavingData);
4887
4888 var prefixesAreEqual;
4889
4890 if ( !enteringData.viewId || enteringData.abstractView ) {
4891 // it's an abstract view, so treat it accordingly
4892
4893 // we only get access to the leaving scope once in the transition,
4894 // so dispatch all events right away if it exists
4895 if ( leavingScope ) {
4896 leavingScope.$emit('$ionicView.beforeLeave', leavingData);
4897 leavingScope.$emit('$ionicView.leave', leavingData);
4898 leavingScope.$emit('$ionicView.afterLeave', leavingData);
4899 leavingScope.$broadcast('$ionicParentView.beforeLeave', leavingData);
4900 leavingScope.$broadcast('$ionicParentView.leave', leavingData);
4901 leavingScope.$broadcast('$ionicParentView.afterLeave', leavingData);
4902 }
4903 }
4904 else {
4905 // it's a regular view, so do the normal process
4906 if (step == 'after') {
4907 if (enteringScope) {
4908 enteringScope.$emit('$ionicView.enter', enteringData);
4909 enteringScope.$broadcast('$ionicParentView.enter', enteringData);
4910 }
4911
4912 if (leavingScope) {
4913 leavingScope.$emit('$ionicView.leave', leavingData);
4914 leavingScope.$broadcast('$ionicParentView.leave', leavingData);
4915 }
4916 else if (enteringScope && leavingData && leavingData.viewId && enteringData.stateName !== leavingData.stateName) {
4917 // we only want to dispatch this when we are doing a single-tier
4918 // state change such as changing a tab, so compare the state
4919 // for the same state-prefix but different suffix
4920 prefixesAreEqual = compareStatePrefixes(enteringData.stateName, leavingData.stateName);
4921 if ( prefixesAreEqual ) {
4922 enteringScope.$emit('$ionicNavView.leave', leavingData);
4923 }
4924 }
4925 }
4926
4927 if (enteringScope) {
4928 enteringScope.$emit('$ionicView.' + step + 'Enter', enteringData);
4929 enteringScope.$broadcast('$ionicParentView.' + step + 'Enter', enteringData);
4930 }
4931
4932 if (leavingScope) {
4933 leavingScope.$emit('$ionicView.' + step + 'Leave', leavingData);
4934 leavingScope.$broadcast('$ionicParentView.' + step + 'Leave', leavingData);
4935
4936 } else if (enteringScope && leavingData && leavingData.viewId && enteringData.stateName !== leavingData.stateName) {
4937 // we only want to dispatch this when we are doing a single-tier
4938 // state change such as changing a tab, so compare the state
4939 // for the same state-prefix but different suffix
4940 prefixesAreEqual = compareStatePrefixes(enteringData.stateName, leavingData.stateName);
4941 if ( prefixesAreEqual ) {
4942 enteringScope.$emit('$ionicNavView.' + step + 'Leave', leavingData);
4943 }
4944 }
4945 }
4946 },
4947
4948 cleanup: function(transData) {
4949 // check if any views should be removed
4950 if (leavingEle && transData.direction == 'back' && !$ionicConfig.views.forwardCache()) {
4951 // if they just navigated back we can destroy the forward view
4952 // do not remove forward views if cacheForwardViews config is true
4953 destroyViewEle(leavingEle);
4954 }
4955
4956 var viewElements = navViewCtrl.getViewElements();
4957 var viewElementsLength = viewElements.length;
4958 var x, viewElement;
4959 var removeOldestAccess = (viewElementsLength - 1) > $ionicConfig.views.maxCache();
4960 var removableEle;
4961 var oldestAccess = Date.now();
4962
4963 for (x = 0; x < viewElementsLength; x++) {
4964 viewElement = viewElements.eq(x);
4965
4966 if (removeOldestAccess && viewElement.data(DATA_VIEW_ACCESSED) < oldestAccess) {
4967 // remember what was the oldest element to be accessed so it can be destroyed
4968 oldestAccess = viewElement.data(DATA_VIEW_ACCESSED);
4969 removableEle = viewElements.eq(x);
4970
4971 } else if (viewElement.data(DATA_DESTROY_ELE) && navViewAttr(viewElement) != VIEW_STATUS_ACTIVE) {
4972 destroyViewEle(viewElement);
4973 }
4974 }
4975
4976 destroyViewEle(removableEle);
4977
4978 if (enteringEle.data(DATA_NO_CACHE)) {
4979 enteringEle.data(DATA_DESTROY_ELE, true);
4980 }
4981 },
4982
4983 enteringEle: function() { return enteringEle; },
4984 leavingEle: function() { return leavingEle; }
4985
4986 };
4987
4988 return switcher;
4989 },
4990
4991 transitionEnd: function(navViewCtrls) {
4992 forEach(navViewCtrls, function(navViewCtrl) {
4993 navViewCtrl.transitionEnd();
4994 });
4995
4996 ionicViewSwitcher.isTransitioning(false);
4997 $ionicClickBlock.hide();
4998 transitionPromises = [];
4999 },
5000
5001 nextTransition: function(val) {
5002 nextTransition = val;
5003 },
5004
5005 nextDirection: function(val) {
5006 nextDirection = val;
5007 },
5008
5009 isTransitioning: function(val) {
5010 if (arguments.length) {
5011 ionic.transition.isActive = !!val;
5012 $timeout.cancel(isActiveTimer);
5013 if (val) {
5014 isActiveTimer = $timeout(function() {
5015 ionicViewSwitcher.isTransitioning(false);
5016 }, 999);
5017 }
5018 }
5019 return ionic.transition.isActive;
5020 },
5021
5022 createViewEle: function(viewLocals) {
5023 var containerEle = $document[0].createElement('div');
5024 if (viewLocals && viewLocals.$template) {
5025 containerEle.innerHTML = viewLocals.$template;
5026 if (containerEle.children.length === 1) {
5027 containerEle.children[0].classList.add('pane');
5028 if ( viewLocals.$$state && viewLocals.$$state.self && viewLocals.$$state.self['abstract'] ) {
5029 angular.element(containerEle.children[0]).attr("abstract", "true");
5030 }
5031 else {
5032 if ( viewLocals.$$state && viewLocals.$$state.self ) {
5033 angular.element(containerEle.children[0]).attr("state", viewLocals.$$state.self.name);
5034 }
5035
5036 }
5037 return jqLite(containerEle.children[0]);
5038 }
5039 }
5040 containerEle.className = "pane";
5041 return jqLite(containerEle);
5042 },
5043
5044 viewEleIsActive: function(viewEle, isActiveAttr) {
5045 navViewAttr(viewEle, isActiveAttr ? VIEW_STATUS_ACTIVE : VIEW_STATUS_CACHED);
5046 },
5047
5048 getTransitionData: getTransitionData,
5049 navViewAttr: navViewAttr,
5050 destroyViewEle: destroyViewEle
5051
5052 };
5053
5054 return ionicViewSwitcher;
5055
5056
5057 function getViewElementIdentifier(locals, view) {
5058 if (viewState(locals)['abstract']) return viewState(locals).name;
5059 if (view) return view.stateId || view.viewId;
5060 return ionic.Utils.nextUid();
5061 }
5062
5063 function viewState(locals) {
5064 return locals && locals.$$state && locals.$$state.self || {};
5065 }
5066
5067 function getTransitionData(viewLocals, enteringEle, direction, view) {
5068 // Priority
5069 // 1) attribute directive on the button/link to this view
5070 // 2) entering element's attribute
5071 // 3) entering view's $state config property
5072 // 4) view registration data
5073 // 5) global config
5074 // 6) fallback value
5075
5076 var state = viewState(viewLocals);
5077 var viewTransition = nextTransition || cachedAttr(enteringEle, 'view-transition') || state.viewTransition || $ionicConfig.views.transition() || 'ios';
5078 var navBarTransition = $ionicConfig.navBar.transition();
5079 direction = nextDirection || cachedAttr(enteringEle, 'view-direction') || state.viewDirection || direction || 'none';
5080
5081 return extend(getViewData(view), {
5082 transition: viewTransition,
5083 navBarTransition: navBarTransition === 'view' ? viewTransition : navBarTransition,
5084 direction: direction,
5085 shouldAnimate: (viewTransition !== 'none' && direction !== 'none')
5086 });
5087 }
5088
5089 function getViewData(view) {
5090 view = view || {};
5091 return {
5092 viewId: view.viewId,
5093 historyId: view.historyId,
5094 stateId: view.stateId,
5095 stateName: view.stateName,
5096 stateParams: view.stateParams
5097 };
5098 }
5099
5100 function navViewAttr(ele, value) {
5101 if (arguments.length > 1) {
5102 cachedAttr(ele, NAV_VIEW_ATTR, value);
5103 } else {
5104 return cachedAttr(ele, NAV_VIEW_ATTR);
5105 }
5106 }
5107
5108 function destroyViewEle(ele) {
5109 // we found an element that should be removed
5110 // destroy its scope, then remove the element
5111 if (ele && ele.length) {
5112 var viewScope = ele.scope();
5113 if (viewScope) {
5114 viewScope.$emit('$ionicView.unloaded', ele.data(DATA_VIEW));
5115 viewScope.$destroy();
5116 }
5117 ele.remove();
5118 }
5119 }
5120
5121 function compareStatePrefixes(enteringStateName, exitingStateName) {
5122 var enteringStateSuffixIndex = enteringStateName.lastIndexOf('.');
5123 var exitingStateSuffixIndex = exitingStateName.lastIndexOf('.');
5124
5125 // if either of the prefixes are empty, just return false
5126 if ( enteringStateSuffixIndex < 0 || exitingStateSuffixIndex < 0 ) {
5127 return false;
5128 }
5129
5130 var enteringPrefix = enteringStateName.substring(0, enteringStateSuffixIndex);
5131 var exitingPrefix = exitingStateName.substring(0, exitingStateSuffixIndex);
5132
5133 return enteringPrefix === exitingPrefix;
5134 }
5135
5136 function getScopeForElement(element, stateData) {
5137 if ( !element ) {
5138 return null;
5139 }
5140 // check if it's abstract
5141 var attributeValue = angular.element(element).attr("abstract");
5142 var stateValue = angular.element(element).attr("state");
5143
5144 if ( attributeValue !== "true" ) {
5145 // it's not an abstract view, so make sure the element
5146 // matches the state. Due to abstract view weirdness,
5147 // sometimes it doesn't. If it doesn't, don't dispatch events
5148 // so leave the scope undefined
5149 if ( stateValue === stateData.stateName ) {
5150 return angular.element(element).scope();
5151 }
5152 return null;
5153 }
5154 else {
5155 // it is an abstract element, so look for element with the "state" attributeValue
5156 // set to the name of the stateData state
5157 var elements = aggregateNavViewChildren(element);
5158 for ( var i = 0; i < elements.length; i++ ) {
5159 var state = angular.element(elements[i]).attr("state");
5160 if ( state === stateData.stateName ) {
5161 stateData.abstractView = true;
5162 return angular.element(elements[i]).scope();
5163 }
5164 }
5165 // we didn't find a match, so return null
5166 return null;
5167 }
5168 }
5169
5170 function aggregateNavViewChildren(element) {
5171 var aggregate = [];
5172 var navViews = angular.element(element).find("ion-nav-view");
5173 for ( var i = 0; i < navViews.length; i++ ) {
5174 var children = angular.element(navViews[i]).children();
5175 var childrenAggregated = [];
5176 for ( var j = 0; j < children.length; j++ ) {
5177 childrenAggregated = childrenAggregated.concat(children[j]);
5178 }
5179 aggregate = aggregate.concat(childrenAggregated);
5180 }
5181 return aggregate;
5182 }
5183
5184}]);
5185
5186/**
5187 * ================== angular-ios9-uiwebview.patch.js v1.1.1 ==================
5188 *
5189 * This patch works around iOS9 UIWebView regression that causes infinite digest
5190 * errors in Angular.
5191 *
5192 * The patch can be applied to Angular 1.2.0 – 1.4.5. Newer versions of Angular
5193 * have the workaround baked in.
5194 *
5195 * To apply this patch load/bundle this file with your application and add a
5196 * dependency on the "ngIOS9UIWebViewPatch" module to your main app module.
5197 *
5198 * For example:
5199 *
5200 * ```
5201 * angular.module('myApp', ['ngRoute'])`
5202 * ```
5203 *
5204 * becomes
5205 *
5206 * ```
5207 * angular.module('myApp', ['ngRoute', 'ngIOS9UIWebViewPatch'])
5208 * ```
5209 *
5210 *
5211 * More info:
5212 * - https://openradar.appspot.com/22186109
5213 * - https://github.com/angular/angular.js/issues/12241
5214 * - https://github.com/driftyco/ionic/issues/4082
5215 *
5216 *
5217 * @license AngularJS
5218 * (c) 2010-2015 Google, Inc. http://angularjs.org
5219 * License: MIT
5220 */
5221
5222angular.module('ngIOS9UIWebViewPatch', ['ng']).config(['$provide', function($provide) {
5223 'use strict';
5224
5225 $provide.decorator('$browser', ['$delegate', '$window', function($delegate, $window) {
5226
5227 if (isIOS9UIWebView($window.navigator.userAgent)) {
5228 return applyIOS9Shim($delegate);
5229 }
5230
5231 return $delegate;
5232
5233 function isIOS9UIWebView(userAgent) {
5234 return /(iPhone|iPad|iPod).* OS 9_\d/.test(userAgent) && !/Version\/9\./.test(userAgent);
5235 }
5236
5237 function applyIOS9Shim(browser) {
5238 var pendingLocationUrl = null;
5239 var originalUrlFn = browser.url;
5240
5241 browser.url = function() {
5242 if (arguments.length) {
5243 pendingLocationUrl = arguments[0];
5244 return originalUrlFn.apply(browser, arguments);
5245 }
5246
5247 return pendingLocationUrl || originalUrlFn.apply(browser, arguments);
5248 };
5249
5250 window.addEventListener('popstate', clearPendingLocationUrl, false);
5251 window.addEventListener('hashchange', clearPendingLocationUrl, false);
5252
5253 function clearPendingLocationUrl() {
5254 pendingLocationUrl = null;
5255 }
5256
5257 return browser;
5258 }
5259 }]);
5260}]);
5261
5262/**
5263 * @private
5264 * Parts of Ionic requires that $scope data is attached to the element.
5265 * We do not want to disable adding $scope data to the $element when
5266 * $compileProvider.debugInfoEnabled(false) is used.
5267 */
5268IonicModule.config(['$provide', function($provide) {
5269 $provide.decorator('$compile', ['$delegate', function($compile) {
5270 $compile.$$addScopeInfo = function $$addScopeInfo($element, scope, isolated, noTemplate) {
5271 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
5272 $element.data(dataName, scope);
5273 };
5274 return $compile;
5275 }]);
5276}]);
5277
5278/**
5279 * @private
5280 */
5281IonicModule.config([
5282 '$provide',
5283function($provide) {
5284 function $LocationDecorator($location, $timeout) {
5285
5286 $location.__hash = $location.hash;
5287 //Fix: when window.location.hash is set, the scrollable area
5288 //found nearest to body's scrollTop is set to scroll to an element
5289 //with that ID.
5290 $location.hash = function(value) {
5291 if (isDefined(value) && value.length > 0) {
5292 $timeout(function() {
5293 var scroll = document.querySelector('.scroll-content');
5294 if (scroll) {
5295 scroll.scrollTop = 0;
5296 }
5297 }, 0, false);
5298 }
5299 return $location.__hash(value);
5300 };
5301
5302 return $location;
5303 }
5304
5305 $provide.decorator('$location', ['$delegate', '$timeout', $LocationDecorator]);
5306}]);
5307
5308IonicModule
5309
5310.controller('$ionicHeaderBar', [
5311 '$scope',
5312 '$element',
5313 '$attrs',
5314 '$q',
5315 '$ionicConfig',
5316 '$ionicHistory',
5317function($scope, $element, $attrs, $q, $ionicConfig, $ionicHistory) {
5318 var TITLE = 'title';
5319 var BACK_TEXT = 'back-text';
5320 var BACK_BUTTON = 'back-button';
5321 var DEFAULT_TITLE = 'default-title';
5322 var PREVIOUS_TITLE = 'previous-title';
5323 var HIDE = 'hide';
5324
5325 var self = this;
5326 var titleText = '';
5327 var previousTitleText = '';
5328 var titleLeft = 0;
5329 var titleRight = 0;
5330 var titleCss = '';
5331 var isBackEnabled = false;
5332 var isBackShown = true;
5333 var isNavBackShown = true;
5334 var isBackElementShown = false;
5335 var titleTextWidth = 0;
5336
5337
5338 self.beforeEnter = function(viewData) {
5339 $scope.$broadcast('$ionicView.beforeEnter', viewData);
5340 };
5341
5342
5343 self.title = function(newTitleText) {
5344 if (arguments.length && newTitleText !== titleText) {
5345 getEle(TITLE).innerHTML = newTitleText;
5346 titleText = newTitleText;
5347 titleTextWidth = 0;
5348 }
5349 return titleText;
5350 };
5351
5352
5353 self.enableBack = function(shouldEnable, disableReset) {
5354 // whether or not the back button show be visible, according
5355 // to the navigation and history
5356 if (arguments.length) {
5357 isBackEnabled = shouldEnable;
5358 if (!disableReset) self.updateBackButton();
5359 }
5360 return isBackEnabled;
5361 };
5362
5363
5364 self.showBack = function(shouldShow, disableReset) {
5365 // different from enableBack() because this will always have the back
5366 // visually hidden if false, even if the history says it should show
5367 if (arguments.length) {
5368 isBackShown = shouldShow;
5369 if (!disableReset) self.updateBackButton();
5370 }
5371 return isBackShown;
5372 };
5373
5374
5375 self.showNavBack = function(shouldShow) {
5376 // different from showBack() because this is for the entire nav bar's
5377 // setting for all of it's child headers. For internal use.
5378 isNavBackShown = shouldShow;
5379 self.updateBackButton();
5380 };
5381
5382
5383 self.updateBackButton = function() {
5384 var ele;
5385 if ((isBackShown && isNavBackShown && isBackEnabled) !== isBackElementShown) {
5386 isBackElementShown = isBackShown && isNavBackShown && isBackEnabled;
5387 ele = getEle(BACK_BUTTON);
5388 ele && ele.classList[ isBackElementShown ? 'remove' : 'add' ](HIDE);
5389 }
5390
5391 if (isBackEnabled) {
5392 ele = ele || getEle(BACK_BUTTON);
5393 if (ele) {
5394 if (self.backButtonIcon !== $ionicConfig.backButton.icon()) {
5395 ele = getEle(BACK_BUTTON + ' .icon');
5396 if (ele) {
5397 self.backButtonIcon = $ionicConfig.backButton.icon();
5398 ele.className = 'icon ' + self.backButtonIcon;
5399 }
5400 }
5401
5402 if (self.backButtonText !== $ionicConfig.backButton.text()) {
5403 ele = getEle(BACK_BUTTON + ' .back-text');
5404 if (ele) {
5405 ele.textContent = self.backButtonText = $ionicConfig.backButton.text();
5406 }
5407 }
5408 }
5409 }
5410 };
5411
5412
5413 self.titleTextWidth = function() {
5414 var element = getEle(TITLE);
5415 if ( element ) {
5416 // If the element has a nav-bar-title, use that instead
5417 // to calculate the width of the title
5418 var children = angular.element(element).children();
5419 for ( var i = 0; i < children.length; i++ ) {
5420 if ( angular.element(children[i]).hasClass('nav-bar-title') ) {
5421 element = children[i];
5422 break;
5423 }
5424 }
5425 }
5426 var bounds = ionic.DomUtil.getTextBounds(element);
5427 titleTextWidth = Math.min(bounds && bounds.width || 30);
5428 return titleTextWidth;
5429 };
5430
5431
5432 self.titleWidth = function() {
5433 var titleWidth = self.titleTextWidth();
5434 var offsetWidth = getEle(TITLE).offsetWidth;
5435 if (offsetWidth < titleWidth) {
5436 titleWidth = offsetWidth + (titleLeft - titleRight - 5);
5437 }
5438 return titleWidth;
5439 };
5440
5441
5442 self.titleTextX = function() {
5443 return ($element[0].offsetWidth / 2) - (self.titleWidth() / 2);
5444 };
5445
5446
5447 self.titleLeftRight = function() {
5448 return titleLeft - titleRight;
5449 };
5450
5451
5452 self.backButtonTextLeft = function() {
5453 var offsetLeft = 0;
5454 var ele = getEle(BACK_TEXT);
5455 while (ele) {
5456 offsetLeft += ele.offsetLeft;
5457 ele = ele.parentElement;
5458 }
5459 return offsetLeft;
5460 };
5461
5462
5463 self.resetBackButton = function(viewData) {
5464 if ($ionicConfig.backButton.previousTitleText()) {
5465 var previousTitleEle = getEle(PREVIOUS_TITLE);
5466 if (previousTitleEle) {
5467 previousTitleEle.classList.remove(HIDE);
5468
5469 var view = (viewData && $ionicHistory.getViewById(viewData.viewId));
5470 var newPreviousTitleText = $ionicHistory.backTitle(view);
5471
5472 if (newPreviousTitleText !== previousTitleText) {
5473 previousTitleText = previousTitleEle.innerHTML = newPreviousTitleText;
5474 }
5475 }
5476 var defaultTitleEle = getEle(DEFAULT_TITLE);
5477 if (defaultTitleEle) {
5478 defaultTitleEle.classList.remove(HIDE);
5479 }
5480 }
5481 };
5482
5483
5484 self.align = function(textAlign) {
5485 var titleEle = getEle(TITLE);
5486
5487 textAlign = textAlign || $attrs.alignTitle || $ionicConfig.navBar.alignTitle();
5488
5489 var widths = self.calcWidths(textAlign, false);
5490
5491 if (isBackShown && previousTitleText && $ionicConfig.backButton.previousTitleText()) {
5492 var previousTitleWidths = self.calcWidths(textAlign, true);
5493
5494 var availableTitleWidth = $element[0].offsetWidth - previousTitleWidths.titleLeft - previousTitleWidths.titleRight;
5495
5496 if (self.titleTextWidth() <= availableTitleWidth) {
5497 widths = previousTitleWidths;
5498 }
5499 }
5500
5501 return self.updatePositions(titleEle, widths.titleLeft, widths.titleRight, widths.buttonsLeft, widths.buttonsRight, widths.css, widths.showPrevTitle);
5502 };
5503
5504
5505 self.calcWidths = function(textAlign, isPreviousTitle) {
5506 var titleEle = getEle(TITLE);
5507 var backBtnEle = getEle(BACK_BUTTON);
5508 var x, y, z, b, c, d, childSize, bounds;
5509 var childNodes = $element[0].childNodes;
5510 var buttonsLeft = 0;
5511 var buttonsRight = 0;
5512 var isCountRightOfTitle;
5513 var updateTitleLeft = 0;
5514 var updateTitleRight = 0;
5515 var updateCss = '';
5516 var backButtonWidth = 0;
5517
5518 // Compute how wide the left children are
5519 // Skip all titles (there may still be two titles, one leaving the dom)
5520 // Once we encounter a titleEle, realize we are now counting the right-buttons, not left
5521 for (x = 0; x < childNodes.length; x++) {
5522 c = childNodes[x];
5523
5524 childSize = 0;
5525 if (c.nodeType == 1) {
5526 // element node
5527 if (c === titleEle) {
5528 isCountRightOfTitle = true;
5529 continue;
5530 }
5531
5532 if (c.classList.contains(HIDE)) {
5533 continue;
5534 }
5535
5536 if (isBackShown && c === backBtnEle) {
5537
5538 for (y = 0; y < c.childNodes.length; y++) {
5539 b = c.childNodes[y];
5540
5541 if (b.nodeType == 1) {
5542
5543 if (b.classList.contains(BACK_TEXT)) {
5544 for (z = 0; z < b.children.length; z++) {
5545 d = b.children[z];
5546
5547 if (isPreviousTitle) {
5548 if (d.classList.contains(DEFAULT_TITLE)) continue;
5549 backButtonWidth += d.offsetWidth;
5550 } else {
5551 if (d.classList.contains(PREVIOUS_TITLE)) continue;
5552 backButtonWidth += d.offsetWidth;
5553 }
5554 }
5555
5556 } else {
5557 backButtonWidth += b.offsetWidth;
5558 }
5559
5560 } else if (b.nodeType == 3 && b.nodeValue.trim()) {
5561 bounds = ionic.DomUtil.getTextBounds(b);
5562 backButtonWidth += bounds && bounds.width || 0;
5563 }
5564
5565 }
5566 childSize = backButtonWidth || c.offsetWidth;
5567
5568 } else {
5569 // not the title, not the back button, not a hidden element
5570 childSize = c.offsetWidth;
5571 }
5572
5573 } else if (c.nodeType == 3 && c.nodeValue.trim()) {
5574 // text node
5575 bounds = ionic.DomUtil.getTextBounds(c);
5576 childSize = bounds && bounds.width || 0;
5577 }
5578
5579 if (isCountRightOfTitle) {
5580 buttonsRight += childSize;
5581 } else {
5582 buttonsLeft += childSize;
5583 }
5584 }
5585
5586 // Size and align the header titleEle based on the sizes of the left and
5587 // right children, and the desired alignment mode
5588 if (textAlign == 'left') {
5589 updateCss = 'title-left';
5590 if (buttonsLeft) {
5591 updateTitleLeft = buttonsLeft + 15;
5592 }
5593 if (buttonsRight) {
5594 updateTitleRight = buttonsRight + 15;
5595 }
5596
5597 } else if (textAlign == 'right') {
5598 updateCss = 'title-right';
5599 if (buttonsLeft) {
5600 updateTitleLeft = buttonsLeft + 15;
5601 }
5602 if (buttonsRight) {
5603 updateTitleRight = buttonsRight + 15;
5604 }
5605
5606 } else {
5607 // center the default
5608 var margin = Math.max(buttonsLeft, buttonsRight) + 10;
5609 if (margin > 10) {
5610 updateTitleLeft = updateTitleRight = margin;
5611 }
5612 }
5613
5614 return {
5615 backButtonWidth: backButtonWidth,
5616 buttonsLeft: buttonsLeft,
5617 buttonsRight: buttonsRight,
5618 titleLeft: updateTitleLeft,
5619 titleRight: updateTitleRight,
5620 showPrevTitle: isPreviousTitle,
5621 css: updateCss
5622 };
5623 };
5624
5625
5626 self.updatePositions = function(titleEle, updateTitleLeft, updateTitleRight, buttonsLeft, buttonsRight, updateCss, showPreviousTitle) {
5627 var deferred = $q.defer();
5628
5629 // only make DOM updates when there are actual changes
5630 if (titleEle) {
5631 if (updateTitleLeft !== titleLeft) {
5632 titleEle.style.left = updateTitleLeft ? updateTitleLeft + 'px' : '';
5633 titleLeft = updateTitleLeft;
5634 }
5635 if (updateTitleRight !== titleRight) {
5636 titleEle.style.right = updateTitleRight ? updateTitleRight + 'px' : '';
5637 titleRight = updateTitleRight;
5638 }
5639
5640 if (updateCss !== titleCss) {
5641 updateCss && titleEle.classList.add(updateCss);
5642 titleCss && titleEle.classList.remove(titleCss);
5643 titleCss = updateCss;
5644 }
5645 }
5646
5647 if ($ionicConfig.backButton.previousTitleText()) {
5648 var prevTitle = getEle(PREVIOUS_TITLE);
5649 var defaultTitle = getEle(DEFAULT_TITLE);
5650
5651 prevTitle && prevTitle.classList[ showPreviousTitle ? 'remove' : 'add'](HIDE);
5652 defaultTitle && defaultTitle.classList[ showPreviousTitle ? 'add' : 'remove'](HIDE);
5653 }
5654
5655 ionic.requestAnimationFrame(function() {
5656 if (titleEle && titleEle.offsetWidth + 10 < titleEle.scrollWidth) {
5657 var minRight = buttonsRight + 5;
5658 var testRight = $element[0].offsetWidth - titleLeft - self.titleTextWidth() - 20;
5659 updateTitleRight = testRight < minRight ? minRight : testRight;
5660 if (updateTitleRight !== titleRight) {
5661 titleEle.style.right = updateTitleRight + 'px';
5662 titleRight = updateTitleRight;
5663 }
5664 }
5665 deferred.resolve();
5666 });
5667
5668 return deferred.promise;
5669 };
5670
5671
5672 self.setCss = function(elementClassname, css) {
5673 ionic.DomUtil.cachedStyles(getEle(elementClassname), css);
5674 };
5675
5676
5677 var eleCache = {};
5678 function getEle(className) {
5679 if (!eleCache[className]) {
5680 eleCache[className] = $element[0].querySelector('.' + className);
5681 }
5682 return eleCache[className];
5683 }
5684
5685
5686 $scope.$on('$destroy', function() {
5687 for (var n in eleCache) eleCache[n] = null;
5688 });
5689
5690}]);
5691
5692IonicModule
5693.controller('$ionInfiniteScroll', [
5694 '$scope',
5695 '$attrs',
5696 '$element',
5697 '$timeout',
5698function($scope, $attrs, $element, $timeout) {
5699 var self = this;
5700 self.isLoading = false;
5701
5702 $scope.icon = function() {
5703 return isDefined($attrs.icon) ? $attrs.icon : 'ion-load-d';
5704 };
5705
5706 $scope.spinner = function() {
5707 return isDefined($attrs.spinner) ? $attrs.spinner : '';
5708 };
5709
5710 $scope.$on('scroll.infiniteScrollComplete', function() {
5711 finishInfiniteScroll();
5712 });
5713
5714 $scope.$on('$destroy', function() {
5715 if (self.scrollCtrl && self.scrollCtrl.$element) self.scrollCtrl.$element.off('scroll', self.checkBounds);
5716 if (self.scrollEl && self.scrollEl.removeEventListener) {
5717 self.scrollEl.removeEventListener('scroll', self.checkBounds);
5718 }
5719 });
5720
5721 // debounce checking infinite scroll events
5722 self.checkBounds = ionic.Utils.throttle(checkInfiniteBounds, 300);
5723
5724 function onInfinite() {
5725 ionic.requestAnimationFrame(function() {
5726 $element[0].classList.add('active');
5727 });
5728 self.isLoading = true;
5729 $scope.$parent && $scope.$parent.$apply($attrs.onInfinite || '');
5730 }
5731
5732 function finishInfiniteScroll() {
5733 ionic.requestAnimationFrame(function() {
5734 $element[0].classList.remove('active');
5735 });
5736 $timeout(function() {
5737 if (self.jsScrolling) self.scrollView.resize();
5738 // only check bounds again immediately if the page isn't cached (scroll el has height)
5739 if ((self.jsScrolling && self.scrollView.__container && self.scrollView.__container.offsetHeight > 0) ||
5740 !self.jsScrolling) {
5741 self.checkBounds();
5742 }
5743 }, 30, false);
5744 self.isLoading = false;
5745 }
5746
5747 // check if we've scrolled far enough to trigger an infinite scroll
5748 function checkInfiniteBounds() {
5749 if (self.isLoading) return;
5750 var maxScroll = {};
5751
5752 if (self.jsScrolling) {
5753 maxScroll = self.getJSMaxScroll();
5754 var scrollValues = self.scrollView.getValues();
5755 if ((maxScroll.left !== -1 && scrollValues.left >= maxScroll.left) ||
5756 (maxScroll.top !== -1 && scrollValues.top >= maxScroll.top)) {
5757 onInfinite();
5758 }
5759 } else {
5760 maxScroll = self.getNativeMaxScroll();
5761 if ((
5762 maxScroll.left !== -1 &&
5763 self.scrollEl.scrollLeft >= maxScroll.left - self.scrollEl.clientWidth
5764 ) || (
5765 maxScroll.top !== -1 &&
5766 self.scrollEl.scrollTop >= maxScroll.top - self.scrollEl.clientHeight
5767 )) {
5768 onInfinite();
5769 }
5770 }
5771 }
5772
5773 // determine the threshold at which we should fire an infinite scroll
5774 // note: this gets processed every scroll event, can it be cached?
5775 self.getJSMaxScroll = function() {
5776 var maxValues = self.scrollView.getScrollMax();
5777 return {
5778 left: self.scrollView.options.scrollingX ?
5779 calculateMaxValue(maxValues.left) :
5780 -1,
5781 top: self.scrollView.options.scrollingY ?
5782 calculateMaxValue(maxValues.top) :
5783 -1
5784 };
5785 };
5786
5787 self.getNativeMaxScroll = function() {
5788 var maxValues = {
5789 left: self.scrollEl.scrollWidth,
5790 top: self.scrollEl.scrollHeight
5791 };
5792 var computedStyle = window.getComputedStyle(self.scrollEl) || {};
5793 return {
5794 left: maxValues.left &&
5795 (computedStyle.overflowX === 'scroll' ||
5796 computedStyle.overflowX === 'auto' ||
5797 self.scrollEl.style['overflow-x'] === 'scroll') ?
5798 calculateMaxValue(maxValues.left) : -1,
5799 top: maxValues.top &&
5800 (computedStyle.overflowY === 'scroll' ||
5801 computedStyle.overflowY === 'auto' ||
5802 self.scrollEl.style['overflow-y'] === 'scroll' ) ?
5803 calculateMaxValue(maxValues.top) : -1
5804 };
5805 };
5806
5807 // determine pixel refresh distance based on % or value
5808 function calculateMaxValue(maximum) {
5809 var distance = ($attrs.distance || '2.5%').trim();
5810 var isPercent = distance.indexOf('%') !== -1;
5811 return isPercent ?
5812 maximum * (1 - parseFloat(distance) / 100) :
5813 maximum - parseFloat(distance);
5814 }
5815
5816 //for testing
5817 self.__finishInfiniteScroll = finishInfiniteScroll;
5818
5819}]);
5820
5821/**
5822 * @ngdoc service
5823 * @name $ionicListDelegate
5824 * @module ionic
5825 *
5826 * @description
5827 * Delegate for controlling the {@link ionic.directive:ionList} directive.
5828 *
5829 * Methods called directly on the $ionicListDelegate service will control all lists.
5830 * Use the {@link ionic.service:$ionicListDelegate#$getByHandle $getByHandle}
5831 * method to control specific ionList instances.
5832 *
5833 * @usage
5834 * ```html
5835 * {% raw %}
5836 * <ion-content ng-controller="MyCtrl">
5837 * <button class="button" ng-click="showDeleteButtons()"></button>
5838 * <ion-list>
5839 * <ion-item ng-repeat="i in items">
5840 * Hello, {{i}}!
5841 * <ion-delete-button class="ion-minus-circled"></ion-delete-button>
5842 * </ion-item>
5843 * </ion-list>
5844 * </ion-content>
5845 * {% endraw %}
5846 * ```
5847
5848 * ```js
5849 * function MyCtrl($scope, $ionicListDelegate) {
5850 * $scope.showDeleteButtons = function() {
5851 * $ionicListDelegate.showDelete(true);
5852 * };
5853 * }
5854 * ```
5855 */
5856IonicModule.service('$ionicListDelegate', ionic.DelegateService([
5857 /**
5858 * @ngdoc method
5859 * @name $ionicListDelegate#showReorder
5860 * @param {boolean=} showReorder Set whether or not this list is showing its reorder buttons.
5861 * @returns {boolean} Whether the reorder buttons are shown.
5862 */
5863 'showReorder',
5864 /**
5865 * @ngdoc method
5866 * @name $ionicListDelegate#showDelete
5867 * @param {boolean=} showDelete Set whether or not this list is showing its delete buttons.
5868 * @returns {boolean} Whether the delete buttons are shown.
5869 */
5870 'showDelete',
5871 /**
5872 * @ngdoc method
5873 * @name $ionicListDelegate#canSwipeItems
5874 * @param {boolean=} canSwipeItems Set whether or not this list is able to swipe to show
5875 * option buttons.
5876 * @returns {boolean} Whether the list is able to swipe to show option buttons.
5877 */
5878 'canSwipeItems',
5879 /**
5880 * @ngdoc method
5881 * @name $ionicListDelegate#closeOptionButtons
5882 * @description Closes any option buttons on the list that are swiped open.
5883 */
5884 'closeOptionButtons'
5885 /**
5886 * @ngdoc method
5887 * @name $ionicListDelegate#$getByHandle
5888 * @param {string} handle
5889 * @returns `delegateInstance` A delegate instance that controls only the
5890 * {@link ionic.directive:ionList} directives with `delegate-handle` matching
5891 * the given handle.
5892 *
5893 * Example: `$ionicListDelegate.$getByHandle('my-handle').showReorder(true);`
5894 */
5895]))
5896
5897.controller('$ionicList', [
5898 '$scope',
5899 '$attrs',
5900 '$ionicListDelegate',
5901 '$ionicHistory',
5902function($scope, $attrs, $ionicListDelegate, $ionicHistory) {
5903 var self = this;
5904 var isSwipeable = true;
5905 var isReorderShown = false;
5906 var isDeleteShown = false;
5907
5908 var deregisterInstance = $ionicListDelegate._registerInstance(
5909 self, $attrs.delegateHandle, function() {
5910 return $ionicHistory.isActiveScope($scope);
5911 }
5912 );
5913 $scope.$on('$destroy', deregisterInstance);
5914
5915 self.showReorder = function(show) {
5916 if (arguments.length) {
5917 isReorderShown = !!show;
5918 }
5919 return isReorderShown;
5920 };
5921
5922 self.showDelete = function(show) {
5923 if (arguments.length) {
5924 isDeleteShown = !!show;
5925 }
5926 return isDeleteShown;
5927 };
5928
5929 self.canSwipeItems = function(can) {
5930 if (arguments.length) {
5931 isSwipeable = !!can;
5932 }
5933 return isSwipeable;
5934 };
5935
5936 self.closeOptionButtons = function() {
5937 self.listView && self.listView.clearDragEffects();
5938 };
5939}]);
5940
5941IonicModule
5942
5943.controller('$ionicNavBar', [
5944 '$scope',
5945 '$element',
5946 '$attrs',
5947 '$compile',
5948 '$timeout',
5949 '$ionicNavBarDelegate',
5950 '$ionicConfig',
5951 '$ionicHistory',
5952function($scope, $element, $attrs, $compile, $timeout, $ionicNavBarDelegate, $ionicConfig, $ionicHistory) {
5953
5954 var CSS_HIDE = 'hide';
5955 var DATA_NAV_BAR_CTRL = '$ionNavBarController';
5956 var PRIMARY_BUTTONS = 'primaryButtons';
5957 var SECONDARY_BUTTONS = 'secondaryButtons';
5958 var BACK_BUTTON = 'backButton';
5959 var ITEM_TYPES = 'primaryButtons secondaryButtons leftButtons rightButtons title'.split(' ');
5960
5961 var self = this;
5962 var headerBars = [];
5963 var navElementHtml = {};
5964 var isVisible = true;
5965 var queuedTransitionStart, queuedTransitionEnd, latestTransitionId;
5966
5967 $element.parent().data(DATA_NAV_BAR_CTRL, self);
5968
5969 var delegateHandle = $attrs.delegateHandle || 'navBar' + ionic.Utils.nextUid();
5970
5971 var deregisterInstance = $ionicNavBarDelegate._registerInstance(self, delegateHandle);
5972
5973
5974 self.init = function() {
5975 $element.addClass('nav-bar-container');
5976 ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', $ionicConfig.views.transition());
5977
5978 // create two nav bar blocks which will trade out which one is shown
5979 self.createHeaderBar(false);
5980 self.createHeaderBar(true);
5981
5982 $scope.$emit('ionNavBar.init', delegateHandle);
5983 };
5984
5985
5986 self.createHeaderBar = function(isActive) {
5987 var containerEle = jqLite('<div class="nav-bar-block">');
5988 ionic.DomUtil.cachedAttr(containerEle, 'nav-bar', isActive ? 'active' : 'cached');
5989
5990 var alignTitle = $attrs.alignTitle || $ionicConfig.navBar.alignTitle();
5991 var headerBarEle = jqLite('<ion-header-bar>').addClass($attrs['class']).attr('align-title', alignTitle);
5992 if (isDefined($attrs.noTapScroll)) headerBarEle.attr('no-tap-scroll', $attrs.noTapScroll);
5993 var titleEle = jqLite('<div class="title title-' + alignTitle + '">');
5994 var navEle = {};
5995 var lastViewItemEle = {};
5996 var leftButtonsEle, rightButtonsEle;
5997
5998 navEle[BACK_BUTTON] = createNavElement(BACK_BUTTON);
5999 navEle[BACK_BUTTON] && headerBarEle.append(navEle[BACK_BUTTON]);
6000
6001 // append title in the header, this is the rock to where buttons append
6002 headerBarEle.append(titleEle);
6003
6004 forEach(ITEM_TYPES, function(itemType) {
6005 // create default button elements
6006 navEle[itemType] = createNavElement(itemType);
6007 // append and position buttons
6008 positionItem(navEle[itemType], itemType);
6009 });
6010
6011 // add header-item to the root children
6012 for (var x = 0; x < headerBarEle[0].children.length; x++) {
6013 headerBarEle[0].children[x].classList.add('header-item');
6014 }
6015
6016 // compile header and append to the DOM
6017 containerEle.append(headerBarEle);
6018 $element.append($compile(containerEle)($scope.$new()));
6019
6020 var headerBarCtrl = headerBarEle.data('$ionHeaderBarController');
6021 headerBarCtrl.backButtonIcon = $ionicConfig.backButton.icon();
6022 headerBarCtrl.backButtonText = $ionicConfig.backButton.text();
6023
6024 var headerBarInstance = {
6025 isActive: isActive,
6026 title: function(newTitleText) {
6027 headerBarCtrl.title(newTitleText);
6028 },
6029 setItem: function(navBarItemEle, itemType) {
6030 // first make sure any exiting nav bar item has been removed
6031 headerBarInstance.removeItem(itemType);
6032
6033 if (navBarItemEle) {
6034 if (itemType === 'title') {
6035 // clear out the text based title
6036 headerBarInstance.title("");
6037 }
6038
6039 // there's a custom nav bar item
6040 positionItem(navBarItemEle, itemType);
6041
6042 if (navEle[itemType]) {
6043 // make sure the default on this itemType is hidden
6044 navEle[itemType].addClass(CSS_HIDE);
6045 }
6046 lastViewItemEle[itemType] = navBarItemEle;
6047
6048 } else if (navEle[itemType]) {
6049 // there's a default button for this side and no view button
6050 navEle[itemType].removeClass(CSS_HIDE);
6051 }
6052 },
6053 removeItem: function(itemType) {
6054 if (lastViewItemEle[itemType]) {
6055 lastViewItemEle[itemType].scope().$destroy();
6056 lastViewItemEle[itemType].remove();
6057 lastViewItemEle[itemType] = null;
6058 }
6059 },
6060 containerEle: function() {
6061 return containerEle;
6062 },
6063 headerBarEle: function() {
6064 return headerBarEle;
6065 },
6066 afterLeave: function() {
6067 forEach(ITEM_TYPES, function(itemType) {
6068 headerBarInstance.removeItem(itemType);
6069 });
6070 headerBarCtrl.resetBackButton();
6071 },
6072 controller: function() {
6073 return headerBarCtrl;
6074 },
6075 destroy: function() {
6076 forEach(ITEM_TYPES, function(itemType) {
6077 headerBarInstance.removeItem(itemType);
6078 });
6079 containerEle.scope().$destroy();
6080 for (var n in navEle) {
6081 if (navEle[n]) {
6082 navEle[n].removeData();
6083 navEle[n] = null;
6084 }
6085 }
6086 leftButtonsEle && leftButtonsEle.removeData();
6087 rightButtonsEle && rightButtonsEle.removeData();
6088 titleEle.removeData();
6089 headerBarEle.removeData();
6090 containerEle.remove();
6091 containerEle = headerBarEle = titleEle = leftButtonsEle = rightButtonsEle = null;
6092 }
6093 };
6094
6095 function positionItem(ele, itemType) {
6096 if (!ele) return;
6097
6098 if (itemType === 'title') {
6099 // title element
6100 titleEle.append(ele);
6101
6102 } else if (itemType == 'rightButtons' ||
6103 (itemType == SECONDARY_BUTTONS && $ionicConfig.navBar.positionSecondaryButtons() != 'left') ||
6104 (itemType == PRIMARY_BUTTONS && $ionicConfig.navBar.positionPrimaryButtons() == 'right')) {
6105 // right side
6106 if (!rightButtonsEle) {
6107 rightButtonsEle = jqLite('<div class="buttons buttons-right">');
6108 headerBarEle.append(rightButtonsEle);
6109 }
6110 if (itemType == SECONDARY_BUTTONS) {
6111 rightButtonsEle.append(ele);
6112 } else {
6113 rightButtonsEle.prepend(ele);
6114 }
6115
6116 } else {
6117 // left side
6118 if (!leftButtonsEle) {
6119 leftButtonsEle = jqLite('<div class="buttons buttons-left">');
6120 if (navEle[BACK_BUTTON]) {
6121 navEle[BACK_BUTTON].after(leftButtonsEle);
6122 } else {
6123 headerBarEle.prepend(leftButtonsEle);
6124 }
6125 }
6126 if (itemType == SECONDARY_BUTTONS) {
6127 leftButtonsEle.append(ele);
6128 } else {
6129 leftButtonsEle.prepend(ele);
6130 }
6131 }
6132
6133 }
6134
6135 headerBars.push(headerBarInstance);
6136
6137 return headerBarInstance;
6138 };
6139
6140
6141 self.navElement = function(type, html) {
6142 if (isDefined(html)) {
6143 navElementHtml[type] = html;
6144 }
6145 return navElementHtml[type];
6146 };
6147
6148
6149 self.update = function(viewData) {
6150 var showNavBar = !viewData.hasHeaderBar && viewData.showNavBar;
6151 viewData.transition = $ionicConfig.views.transition();
6152
6153 if (!showNavBar) {
6154 viewData.direction = 'none';
6155 }
6156
6157 self.enable(showNavBar);
6158 var enteringHeaderBar = self.isInitialized ? getOffScreenHeaderBar() : getOnScreenHeaderBar();
6159 var leavingHeaderBar = self.isInitialized ? getOnScreenHeaderBar() : null;
6160 var enteringHeaderCtrl = enteringHeaderBar.controller();
6161
6162 // update if the entering header should show the back button or not
6163 enteringHeaderCtrl.enableBack(viewData.enableBack, true);
6164 enteringHeaderCtrl.showBack(viewData.showBack, true);
6165 enteringHeaderCtrl.updateBackButton();
6166
6167 // update the entering header bar's title
6168 self.title(viewData.title, enteringHeaderBar);
6169
6170 self.showBar(showNavBar);
6171
6172 // update the nav bar items, depending if the view has their own or not
6173 if (viewData.navBarItems) {
6174 forEach(ITEM_TYPES, function(itemType) {
6175 enteringHeaderBar.setItem(viewData.navBarItems[itemType], itemType);
6176 });
6177 }
6178
6179 // begin transition of entering and leaving header bars
6180 self.transition(enteringHeaderBar, leavingHeaderBar, viewData);
6181
6182 self.isInitialized = true;
6183 navSwipeAttr('');
6184 };
6185
6186
6187 self.transition = function(enteringHeaderBar, leavingHeaderBar, viewData) {
6188 var enteringHeaderBarCtrl = enteringHeaderBar.controller();
6189 var transitionFn = $ionicConfig.transitions.navBar[viewData.navBarTransition] || $ionicConfig.transitions.navBar.none;
6190 var transitionId = viewData.transitionId;
6191
6192 enteringHeaderBarCtrl.beforeEnter(viewData);
6193
6194 var navBarTransition = transitionFn(enteringHeaderBar, leavingHeaderBar, viewData.direction, viewData.shouldAnimate && self.isInitialized);
6195
6196 ionic.DomUtil.cachedAttr($element, 'nav-bar-transition', viewData.navBarTransition);
6197 ionic.DomUtil.cachedAttr($element, 'nav-bar-direction', viewData.direction);
6198
6199 if (navBarTransition.shouldAnimate && viewData.renderEnd) {
6200 navBarAttr(enteringHeaderBar, 'stage');
6201 } else {
6202 navBarAttr(enteringHeaderBar, 'entering');
6203 navBarAttr(leavingHeaderBar, 'leaving');
6204 }
6205
6206 enteringHeaderBarCtrl.resetBackButton(viewData);
6207
6208 navBarTransition.run(0);
6209
6210 self.activeTransition = {
6211 run: function(step) {
6212 navBarTransition.shouldAnimate = false;
6213 navBarTransition.direction = 'back';
6214 navBarTransition.run(step);
6215 },
6216 cancel: function(shouldAnimate, speed, cancelData) {
6217 navSwipeAttr(speed);
6218 navBarAttr(leavingHeaderBar, 'active');
6219 navBarAttr(enteringHeaderBar, 'cached');
6220 navBarTransition.shouldAnimate = shouldAnimate;
6221 navBarTransition.run(0);
6222 self.activeTransition = navBarTransition = null;
6223
6224 var runApply;
6225 if (cancelData.showBar !== self.showBar()) {
6226 self.showBar(cancelData.showBar);
6227 }
6228 if (cancelData.showBackButton !== self.showBackButton()) {
6229 self.showBackButton(cancelData.showBackButton);
6230 }
6231 if (runApply) {
6232 $scope.$apply();
6233 }
6234 },
6235 complete: function(shouldAnimate, speed) {
6236 navSwipeAttr(speed);
6237 navBarTransition.shouldAnimate = shouldAnimate;
6238 navBarTransition.run(1);
6239 queuedTransitionEnd = transitionEnd;
6240 }
6241 };
6242
6243 $timeout(enteringHeaderBarCtrl.align, 16);
6244
6245 queuedTransitionStart = function() {
6246 if (latestTransitionId !== transitionId) return;
6247
6248 navBarAttr(enteringHeaderBar, 'entering');
6249 navBarAttr(leavingHeaderBar, 'leaving');
6250
6251 navBarTransition.run(1);
6252
6253 queuedTransitionEnd = function() {
6254 if (latestTransitionId == transitionId || !navBarTransition.shouldAnimate) {
6255 transitionEnd();
6256 }
6257 };
6258
6259 queuedTransitionStart = null;
6260 };
6261
6262 function transitionEnd() {
6263 for (var x = 0; x < headerBars.length; x++) {
6264 headerBars[x].isActive = false;
6265 }
6266 enteringHeaderBar.isActive = true;
6267
6268 navBarAttr(enteringHeaderBar, 'active');
6269 navBarAttr(leavingHeaderBar, 'cached');
6270
6271 self.activeTransition = navBarTransition = queuedTransitionEnd = null;
6272 }
6273
6274 queuedTransitionStart();
6275 };
6276
6277
6278 self.triggerTransitionStart = function(triggerTransitionId) {
6279 latestTransitionId = triggerTransitionId;
6280 queuedTransitionStart && queuedTransitionStart();
6281 };
6282
6283
6284 self.triggerTransitionEnd = function() {
6285 queuedTransitionEnd && queuedTransitionEnd();
6286 };
6287
6288
6289 self.showBar = function(shouldShow) {
6290 if (arguments.length) {
6291 self.visibleBar(shouldShow);
6292 $scope.$parent.$hasHeader = !!shouldShow;
6293 }
6294 return !!$scope.$parent.$hasHeader;
6295 };
6296
6297
6298 self.visibleBar = function(shouldShow) {
6299 if (shouldShow && !isVisible) {
6300 $element.removeClass(CSS_HIDE);
6301 self.align();
6302 } else if (!shouldShow && isVisible) {
6303 $element.addClass(CSS_HIDE);
6304 }
6305 isVisible = shouldShow;
6306 };
6307
6308
6309 self.enable = function(val) {
6310 // set primary to show first
6311 self.visibleBar(val);
6312
6313 // set non primary to hide second
6314 for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {
6315 if ($ionicNavBarDelegate._instances[x] !== self) $ionicNavBarDelegate._instances[x].visibleBar(false);
6316 }
6317 };
6318
6319
6320 /**
6321 * @ngdoc method
6322 * @name $ionicNavBar#showBackButton
6323 * @description Show/hide the nav bar back button when there is a
6324 * back view. If the back button is not possible, for example, the
6325 * first view in the stack, then this will not force the back button
6326 * to show.
6327 */
6328 self.showBackButton = function(shouldShow) {
6329 if (arguments.length) {
6330 for (var x = 0; x < headerBars.length; x++) {
6331 headerBars[x].controller().showNavBack(!!shouldShow);
6332 }
6333 $scope.$isBackButtonShown = !!shouldShow;
6334 }
6335 return $scope.$isBackButtonShown;
6336 };
6337
6338
6339 /**
6340 * @ngdoc method
6341 * @name $ionicNavBar#showActiveBackButton
6342 * @description Show/hide only the active header bar's back button.
6343 */
6344 self.showActiveBackButton = function(shouldShow) {
6345 var headerBar = getOnScreenHeaderBar();
6346 if (headerBar) {
6347 if (arguments.length) {
6348 return headerBar.controller().showBack(shouldShow);
6349 }
6350 return headerBar.controller().showBack();
6351 }
6352 };
6353
6354
6355 self.title = function(newTitleText, headerBar) {
6356 if (isDefined(newTitleText)) {
6357 newTitleText = newTitleText || '';
6358 headerBar = headerBar || getOnScreenHeaderBar();
6359 headerBar && headerBar.title(newTitleText);
6360 $scope.$title = newTitleText;
6361 $ionicHistory.currentTitle(newTitleText);
6362 }
6363 return $scope.$title;
6364 };
6365
6366
6367 self.align = function(val, headerBar) {
6368 headerBar = headerBar || getOnScreenHeaderBar();
6369 headerBar && headerBar.controller().align(val);
6370 };
6371
6372
6373 self.hasTabsTop = function(isTabsTop) {
6374 $element[isTabsTop ? 'addClass' : 'removeClass']('nav-bar-tabs-top');
6375 };
6376
6377 self.hasBarSubheader = function(isBarSubheader) {
6378 $element[isBarSubheader ? 'addClass' : 'removeClass']('nav-bar-has-subheader');
6379 };
6380
6381 // DEPRECATED, as of v1.0.0-beta14 -------
6382 self.changeTitle = function(val) {
6383 deprecatedWarning('changeTitle(val)', 'title(val)');
6384 self.title(val);
6385 };
6386 self.setTitle = function(val) {
6387 deprecatedWarning('setTitle(val)', 'title(val)');
6388 self.title(val);
6389 };
6390 self.getTitle = function() {
6391 deprecatedWarning('getTitle()', 'title()');
6392 return self.title();
6393 };
6394 self.back = function() {
6395 deprecatedWarning('back()', '$ionicHistory.goBack()');
6396 $ionicHistory.goBack();
6397 };
6398 self.getPreviousTitle = function() {
6399 deprecatedWarning('getPreviousTitle()', '$ionicHistory.backTitle()');
6400 $ionicHistory.goBack();
6401 };
6402 function deprecatedWarning(oldMethod, newMethod) {
6403 var warn = console.warn || console.log;
6404 warn && warn.call(console, 'navBarController.' + oldMethod + ' is deprecated, please use ' + newMethod + ' instead');
6405 }
6406 // END DEPRECATED -------
6407
6408
6409 function createNavElement(type) {
6410 if (navElementHtml[type]) {
6411 return jqLite(navElementHtml[type]);
6412 }
6413 }
6414
6415
6416 function getOnScreenHeaderBar() {
6417 for (var x = 0; x < headerBars.length; x++) {
6418 if (headerBars[x].isActive) return headerBars[x];
6419 }
6420 }
6421
6422
6423 function getOffScreenHeaderBar() {
6424 for (var x = 0; x < headerBars.length; x++) {
6425 if (!headerBars[x].isActive) return headerBars[x];
6426 }
6427 }
6428
6429
6430 function navBarAttr(ctrl, val) {
6431 ctrl && ionic.DomUtil.cachedAttr(ctrl.containerEle(), 'nav-bar', val);
6432 }
6433
6434 function navSwipeAttr(val) {
6435 ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);
6436 }
6437
6438
6439 $scope.$on('$destroy', function() {
6440 $scope.$parent.$hasHeader = false;
6441 $element.parent().removeData(DATA_NAV_BAR_CTRL);
6442 for (var x = 0; x < headerBars.length; x++) {
6443 headerBars[x].destroy();
6444 }
6445 $element.remove();
6446 $element = headerBars = null;
6447 deregisterInstance();
6448 });
6449
6450}]);
6451
6452IonicModule
6453.controller('$ionicNavView', [
6454 '$scope',
6455 '$element',
6456 '$attrs',
6457 '$compile',
6458 '$controller',
6459 '$ionicNavBarDelegate',
6460 '$ionicNavViewDelegate',
6461 '$ionicHistory',
6462 '$ionicViewSwitcher',
6463 '$ionicConfig',
6464 '$ionicScrollDelegate',
6465 '$ionicSideMenuDelegate',
6466function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, $ionicNavViewDelegate, $ionicHistory, $ionicViewSwitcher, $ionicConfig, $ionicScrollDelegate, $ionicSideMenuDelegate) {
6467
6468 var DATA_ELE_IDENTIFIER = '$eleId';
6469 var DATA_DESTROY_ELE = '$destroyEle';
6470 var DATA_NO_CACHE = '$noCache';
6471 var VIEW_STATUS_ACTIVE = 'active';
6472 var VIEW_STATUS_CACHED = 'cached';
6473
6474 var self = this;
6475 var direction;
6476 var isPrimary = false;
6477 var navBarDelegate;
6478 var activeEleId;
6479 var navViewAttr = $ionicViewSwitcher.navViewAttr;
6480 var disableRenderStartViewId, disableAnimation;
6481
6482 self.scope = $scope;
6483 self.element = $element;
6484
6485 self.init = function() {
6486 var navViewName = $attrs.name || '';
6487
6488 // Find the details of the parent view directive (if any) and use it
6489 // to derive our own qualified view name, then hang our own details
6490 // off the DOM so child directives can find it.
6491 var parent = $element.parent().inheritedData('$uiView');
6492 var parentViewName = ((parent && parent.state) ? parent.state.name : '');
6493 if (navViewName.indexOf('@') < 0) navViewName = navViewName + '@' + parentViewName;
6494
6495 var viewData = { name: navViewName, state: null };
6496 $element.data('$uiView', viewData);
6497
6498 var deregisterInstance = $ionicNavViewDelegate._registerInstance(self, $attrs.delegateHandle);
6499 $scope.$on('$destroy', function() {
6500 deregisterInstance();
6501
6502 // ensure no scrolls have been left frozen
6503 if (self.isSwipeFreeze) {
6504 $ionicScrollDelegate.freezeAllScrolls(false);
6505 }
6506 });
6507
6508 $scope.$on('$ionicHistory.deselect', self.cacheCleanup);
6509 $scope.$on('$ionicTabs.top', onTabsTop);
6510 $scope.$on('$ionicSubheader', onBarSubheader);
6511
6512 $scope.$on('$ionicTabs.beforeLeave', onTabsLeave);
6513 $scope.$on('$ionicTabs.afterLeave', onTabsLeave);
6514 $scope.$on('$ionicTabs.leave', onTabsLeave);
6515
6516 ionic.Platform.ready(function() {
6517 if ( ionic.Platform.isWebView() && ionic.Platform.isIOS() ) {
6518 self.initSwipeBack();
6519 }
6520 });
6521
6522 return viewData;
6523 };
6524
6525
6526 self.register = function(viewLocals) {
6527 var leavingView = extend({}, $ionicHistory.currentView());
6528
6529 // register that a view is coming in and get info on how it should transition
6530 var registerData = $ionicHistory.register($scope, viewLocals);
6531
6532 // update which direction
6533 self.update(registerData);
6534
6535 // begin rendering and transitioning
6536 var enteringView = $ionicHistory.getViewById(registerData.viewId) || {};
6537
6538 var renderStart = (disableRenderStartViewId !== registerData.viewId);
6539 self.render(registerData, viewLocals, enteringView, leavingView, renderStart, true);
6540 };
6541
6542
6543 self.update = function(registerData) {
6544 // always reset that this is the primary navView
6545 isPrimary = true;
6546
6547 // remember what direction this navView should use
6548 // this may get updated later by a child navView
6549 direction = registerData.direction;
6550
6551 var parentNavViewCtrl = $element.parent().inheritedData('$ionNavViewController');
6552 if (parentNavViewCtrl) {
6553 // this navView is nested inside another one
6554 // update the parent to use this direction and not
6555 // the other it originally was set to
6556
6557 // inform the parent navView that it is not the primary navView
6558 parentNavViewCtrl.isPrimary(false);
6559
6560 if (direction === 'enter' || direction === 'exit') {
6561 // they're entering/exiting a history
6562 // find parent navViewController
6563 parentNavViewCtrl.direction(direction);
6564
6565 if (direction === 'enter') {
6566 // reset the direction so this navView doesn't animate
6567 // because it's parent will
6568 direction = 'none';
6569 }
6570 }
6571 }
6572 };
6573
6574
6575 self.render = function(registerData, viewLocals, enteringView, leavingView, renderStart, renderEnd) {
6576 // register the view and figure out where it lives in the various
6577 // histories and nav stacks, along with how views should enter/leave
6578 var switcher = $ionicViewSwitcher.create(self, viewLocals, enteringView, leavingView, renderStart, renderEnd);
6579
6580 // init the rendering of views for this navView directive
6581 switcher.init(registerData, function() {
6582 // the view is now compiled, in the dom and linked, now lets transition the views.
6583 // this uses a callback incase THIS nav-view has a nested nav-view, and after the NESTED
6584 // nav-view links, the NESTED nav-view would update which direction THIS nav-view should use
6585
6586 // kick off the transition of views
6587 switcher.transition(self.direction(), registerData.enableBack, !disableAnimation);
6588
6589 // reset private vars for next time
6590 disableRenderStartViewId = disableAnimation = null;
6591 });
6592
6593 };
6594
6595
6596 self.beforeEnter = function(transitionData) {
6597 if (isPrimary) {
6598 // only update this nav-view's nav-bar if this is the primary nav-view
6599 navBarDelegate = transitionData.navBarDelegate;
6600 var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6601 associatedNavBarCtrl && associatedNavBarCtrl.update(transitionData);
6602 navSwipeAttr('');
6603 }
6604 };
6605
6606
6607 self.activeEleId = function(eleId) {
6608 if (arguments.length) {
6609 activeEleId = eleId;
6610 }
6611 return activeEleId;
6612 };
6613
6614
6615 self.transitionEnd = function() {
6616 var viewElements = $element.children();
6617 var x, l, viewElement;
6618
6619 for (x = 0, l = viewElements.length; x < l; x++) {
6620 viewElement = viewElements.eq(x);
6621
6622 if (viewElement.data(DATA_ELE_IDENTIFIER) === activeEleId) {
6623 // this is the active element
6624 navViewAttr(viewElement, VIEW_STATUS_ACTIVE);
6625
6626 } else if (navViewAttr(viewElement) === 'leaving' || navViewAttr(viewElement) === VIEW_STATUS_ACTIVE || navViewAttr(viewElement) === VIEW_STATUS_CACHED) {
6627 // this is a leaving element or was the former active element, or is an cached element
6628 if (viewElement.data(DATA_DESTROY_ELE) || viewElement.data(DATA_NO_CACHE)) {
6629 // this element shouldn't stay cached
6630 $ionicViewSwitcher.destroyViewEle(viewElement);
6631
6632 } else {
6633 // keep in the DOM, mark as cached
6634 navViewAttr(viewElement, VIEW_STATUS_CACHED);
6635
6636 // disconnect the leaving scope
6637 ionic.Utils.disconnectScope(viewElement.scope());
6638 }
6639 }
6640 }
6641
6642 navSwipeAttr('');
6643
6644 // ensure no scrolls have been left frozen
6645 if (self.isSwipeFreeze) {
6646 $ionicScrollDelegate.freezeAllScrolls(false);
6647 }
6648 };
6649
6650
6651 function onTabsLeave(ev, data) {
6652 var viewElements = $element.children();
6653 var viewElement, viewScope;
6654
6655 for (var x = 0, l = viewElements.length; x < l; x++) {
6656 viewElement = viewElements.eq(x);
6657 if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
6658 viewScope = viewElement.scope();
6659 viewScope && viewScope.$emit(ev.name.replace('Tabs', 'View'), data);
6660 viewScope && viewScope.$broadcast(ev.name.replace('Tabs', 'ParentView'), data);
6661 break;
6662 }
6663 }
6664 }
6665
6666
6667 self.cacheCleanup = function() {
6668 var viewElements = $element.children();
6669 for (var x = 0, l = viewElements.length; x < l; x++) {
6670 if (viewElements.eq(x).data(DATA_DESTROY_ELE)) {
6671 $ionicViewSwitcher.destroyViewEle(viewElements.eq(x));
6672 }
6673 }
6674 };
6675
6676
6677 self.clearCache = function(stateIds) {
6678 var viewElements = $element.children();
6679 var viewElement, viewScope, x, l, y, eleIdentifier;
6680
6681 for (x = 0, l = viewElements.length; x < l; x++) {
6682 viewElement = viewElements.eq(x);
6683
6684 if (stateIds) {
6685 eleIdentifier = viewElement.data(DATA_ELE_IDENTIFIER);
6686
6687 for (y = 0; y < stateIds.length; y++) {
6688 if (eleIdentifier === stateIds[y]) {
6689 $ionicViewSwitcher.destroyViewEle(viewElement);
6690 }
6691 }
6692 continue;
6693 }
6694
6695 if (navViewAttr(viewElement) == VIEW_STATUS_CACHED) {
6696 $ionicViewSwitcher.destroyViewEle(viewElement);
6697
6698 } else if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) {
6699 viewScope = viewElement.scope();
6700 viewScope && viewScope.$broadcast('$ionicView.clearCache');
6701 }
6702
6703 }
6704 };
6705
6706
6707 self.getViewElements = function() {
6708 return $element.children();
6709 };
6710
6711
6712 self.appendViewElement = function(viewEle, viewLocals) {
6713 // compile the entering element and get the link function
6714 var linkFn = $compile(viewEle);
6715
6716 $element.append(viewEle);
6717
6718 var viewScope = $scope.$new();
6719
6720 if (viewLocals && viewLocals.$$controller) {
6721 viewLocals.$scope = viewScope;
6722 var controller = $controller(viewLocals.$$controller, viewLocals);
6723 if (viewLocals.$$controllerAs) {
6724 viewScope[viewLocals.$$controllerAs] = controller;
6725 }
6726 $element.children().data('$ngControllerController', controller);
6727 }
6728
6729 linkFn(viewScope);
6730
6731 return viewScope;
6732 };
6733
6734
6735 self.title = function(val) {
6736 var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6737 associatedNavBarCtrl && associatedNavBarCtrl.title(val);
6738 };
6739
6740
6741 /**
6742 * @ngdoc method
6743 * @name $ionicNavView#enableBackButton
6744 * @description Enable/disable if the back button can be shown or not. For
6745 * example, the very first view in the navigation stack would not have a
6746 * back view, so the back button would be disabled.
6747 */
6748 self.enableBackButton = function(shouldEnable) {
6749 var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6750 associatedNavBarCtrl && associatedNavBarCtrl.enableBackButton(shouldEnable);
6751 };
6752
6753
6754 /**
6755 * @ngdoc method
6756 * @name $ionicNavView#showBackButton
6757 * @description Show/hide the nav bar active back button. If the back button
6758 * is not possible this will not force the back button to show. The
6759 * `enableBackButton()` method handles if a back button is even possible or not.
6760 */
6761 self.showBackButton = function(shouldShow) {
6762 var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6763 if (associatedNavBarCtrl) {
6764 if (arguments.length) {
6765 return associatedNavBarCtrl.showActiveBackButton(shouldShow);
6766 }
6767 return associatedNavBarCtrl.showActiveBackButton();
6768 }
6769 return true;
6770 };
6771
6772
6773 self.showBar = function(val) {
6774 var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6775 if (associatedNavBarCtrl) {
6776 if (arguments.length) {
6777 return associatedNavBarCtrl.showBar(val);
6778 }
6779 return associatedNavBarCtrl.showBar();
6780 }
6781 return true;
6782 };
6783
6784
6785 self.isPrimary = function(val) {
6786 if (arguments.length) {
6787 isPrimary = val;
6788 }
6789 return isPrimary;
6790 };
6791
6792
6793 self.direction = function(val) {
6794 if (arguments.length) {
6795 direction = val;
6796 }
6797 return direction;
6798 };
6799
6800
6801 self.initSwipeBack = function() {
6802 var swipeBackHitWidth = $ionicConfig.views.swipeBackHitWidth();
6803 var viewTransition, associatedNavBarCtrl, backView;
6804 var deregDragStart, deregDrag, deregRelease;
6805 var windowWidth, startDragX, dragPoints;
6806 var cancelData = {};
6807
6808 function onDragStart(ev) {
6809 if (!isPrimary || !$ionicConfig.views.swipeBackEnabled() || $ionicSideMenuDelegate.isOpenRight() ) return;
6810
6811
6812 startDragX = getDragX(ev);
6813 if (startDragX > swipeBackHitWidth) return;
6814
6815 backView = $ionicHistory.backView();
6816
6817 var currentView = $ionicHistory.currentView();
6818
6819 if (!backView || backView.historyId !== currentView.historyId || currentView.canSwipeBack === false) return;
6820
6821 if (!windowWidth) windowWidth = window.innerWidth;
6822
6823 self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(true);
6824
6825 var registerData = {
6826 direction: 'back'
6827 };
6828
6829 dragPoints = [];
6830
6831 cancelData = {
6832 showBar: self.showBar(),
6833 showBackButton: self.showBackButton()
6834 };
6835
6836 var switcher = $ionicViewSwitcher.create(self, registerData, backView, currentView, true, false);
6837 switcher.loadViewElements(registerData);
6838 switcher.render(registerData);
6839
6840 viewTransition = switcher.transition('back', $ionicHistory.enabledBack(backView), true);
6841
6842 associatedNavBarCtrl = getAssociatedNavBarCtrl();
6843
6844 deregDrag = ionic.onGesture('drag', onDrag, $element[0]);
6845 deregRelease = ionic.onGesture('release', onRelease, $element[0]);
6846 }
6847
6848 function onDrag(ev) {
6849 if (isPrimary && viewTransition) {
6850 var dragX = getDragX(ev);
6851
6852 dragPoints.push({
6853 t: Date.now(),
6854 x: dragX
6855 });
6856
6857 if (dragX >= windowWidth - 15) {
6858 onRelease(ev);
6859
6860 } else {
6861 var step = Math.min(Math.max(getSwipeCompletion(dragX), 0), 1);
6862 viewTransition.run(step);
6863 associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.run(step);
6864 }
6865
6866 }
6867 }
6868
6869 function onRelease(ev) {
6870 if (isPrimary && viewTransition && dragPoints && dragPoints.length > 1) {
6871
6872 var now = Date.now();
6873 var releaseX = getDragX(ev);
6874 var startDrag = dragPoints[dragPoints.length - 1];
6875
6876 for (var x = dragPoints.length - 2; x >= 0; x--) {
6877 if (now - startDrag.t > 200) {
6878 break;
6879 }
6880 startDrag = dragPoints[x];
6881 }
6882
6883 var isSwipingRight = (releaseX >= dragPoints[dragPoints.length - 2].x);
6884 var releaseSwipeCompletion = getSwipeCompletion(releaseX);
6885 var velocity = Math.abs(startDrag.x - releaseX) / (now - startDrag.t);
6886
6887 // private variables because ui-router has no way to pass custom data using $state.go
6888 disableRenderStartViewId = backView.viewId;
6889 disableAnimation = (releaseSwipeCompletion < 0.03 || releaseSwipeCompletion > 0.97);
6890
6891 if (isSwipingRight && (releaseSwipeCompletion > 0.5 || velocity > 0.1)) {
6892 // complete view transition on release
6893 var speed = (velocity > 0.5 || velocity < 0.05 || releaseX > windowWidth - 45) ? 'fast' : 'slow';
6894 navSwipeAttr(disableAnimation ? '' : speed);
6895 backView.go();
6896 associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.complete(!disableAnimation, speed);
6897
6898 } else {
6899 // cancel view transition on release
6900 navSwipeAttr(disableAnimation ? '' : 'fast');
6901 disableRenderStartViewId = null;
6902 viewTransition.cancel(!disableAnimation);
6903 associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.cancel(!disableAnimation, 'fast', cancelData);
6904 disableAnimation = null;
6905 }
6906
6907 }
6908
6909 ionic.offGesture(deregDrag, 'drag', onDrag);
6910 ionic.offGesture(deregRelease, 'release', onRelease);
6911
6912 windowWidth = viewTransition = dragPoints = null;
6913
6914 self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(false);
6915 }
6916
6917 function getDragX(ev) {
6918 return ionic.tap.pointerCoord(ev.gesture.srcEvent).x;
6919 }
6920
6921 function getSwipeCompletion(dragX) {
6922 return (dragX - startDragX) / windowWidth;
6923 }
6924
6925 deregDragStart = ionic.onGesture('dragstart', onDragStart, $element[0]);
6926
6927 $scope.$on('$destroy', function() {
6928 ionic.offGesture(deregDragStart, 'dragstart', onDragStart);
6929 ionic.offGesture(deregDrag, 'drag', onDrag);
6930 ionic.offGesture(deregRelease, 'release', onRelease);
6931 self.element = viewTransition = associatedNavBarCtrl = null;
6932 });
6933 };
6934
6935
6936 function navSwipeAttr(val) {
6937 ionic.DomUtil.cachedAttr($element, 'nav-swipe', val);
6938 }
6939
6940
6941 function onTabsTop(ev, isTabsTop) {
6942 var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6943 associatedNavBarCtrl && associatedNavBarCtrl.hasTabsTop(isTabsTop);
6944 }
6945
6946 function onBarSubheader(ev, isBarSubheader) {
6947 var associatedNavBarCtrl = getAssociatedNavBarCtrl();
6948 associatedNavBarCtrl && associatedNavBarCtrl.hasBarSubheader(isBarSubheader);
6949 }
6950
6951 function getAssociatedNavBarCtrl() {
6952 if (navBarDelegate) {
6953 for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) {
6954 if ($ionicNavBarDelegate._instances[x].$$delegateHandle == navBarDelegate) {
6955 return $ionicNavBarDelegate._instances[x];
6956 }
6957 }
6958 }
6959 return $element.inheritedData('$ionNavBarController');
6960 }
6961
6962}]);
6963
6964IonicModule
6965.controller('$ionicRefresher', [
6966 '$scope',
6967 '$attrs',
6968 '$element',
6969 '$ionicBind',
6970 '$timeout',
6971 function($scope, $attrs, $element, $ionicBind, $timeout) {
6972 var self = this,
6973 isDragging = false,
6974 isOverscrolling = false,
6975 dragOffset = 0,
6976 lastOverscroll = 0,
6977 ptrThreshold = 60,
6978 activated = false,
6979 scrollTime = 500,
6980 startY = null,
6981 deltaY = null,
6982 canOverscroll = true,
6983 scrollParent,
6984 scrollChild;
6985
6986 if (!isDefined($attrs.pullingIcon)) {
6987 $attrs.$set('pullingIcon', 'ion-android-arrow-down');
6988 }
6989
6990 $scope.showSpinner = !isDefined($attrs.refreshingIcon) && $attrs.spinner != 'none';
6991
6992 $scope.showIcon = isDefined($attrs.refreshingIcon);
6993
6994 $ionicBind($scope, $attrs, {
6995 pullingIcon: '@',
6996 pullingText: '@',
6997 refreshingIcon: '@',
6998 refreshingText: '@',
6999 spinner: '@',
7000 disablePullingRotation: '@',
7001 $onRefresh: '&onRefresh',
7002 $onPulling: '&onPulling'
7003 });
7004
7005 function handleMousedown(e) {
7006 e.touches = e.touches || [{
7007 screenX: e.screenX,
7008 screenY: e.screenY
7009 }];
7010 // Mouse needs this
7011 startY = Math.floor(e.touches[0].screenY);
7012 }
7013
7014 function handleTouchstart(e) {
7015 e.touches = e.touches || [{
7016 screenX: e.screenX,
7017 screenY: e.screenY
7018 }];
7019
7020 startY = e.touches[0].screenY;
7021 }
7022
7023 function handleTouchend() {
7024 // reset Y
7025 startY = null;
7026 // if this wasn't an overscroll, get out immediately
7027 if (!canOverscroll && !isDragging) {
7028 return;
7029 }
7030 // the user has overscrolled but went back to native scrolling
7031 if (!isDragging) {
7032 dragOffset = 0;
7033 isOverscrolling = false;
7034 setScrollLock(false);
7035 } else {
7036 isDragging = false;
7037 dragOffset = 0;
7038
7039 // the user has scroll far enough to trigger a refresh
7040 if (lastOverscroll > ptrThreshold) {
7041 start();
7042 scrollTo(ptrThreshold, scrollTime);
7043
7044 // the user has overscrolled but not far enough to trigger a refresh
7045 } else {
7046 scrollTo(0, scrollTime, deactivate);
7047 isOverscrolling = false;
7048 }
7049 }
7050 }
7051
7052 function handleTouchmove(e) {
7053 e.touches = e.touches || [{
7054 screenX: e.screenX,
7055 screenY: e.screenY
7056 }];
7057
7058 // Force mouse events to have had a down event first
7059 if (!startY && e.type == 'mousemove') {
7060 return;
7061 }
7062
7063 // if multitouch or regular scroll event, get out immediately
7064 if (!canOverscroll || e.touches.length > 1) {
7065 return;
7066 }
7067 //if this is a new drag, keep track of where we start
7068 if (startY === null) {
7069 startY = e.touches[0].screenY;
7070 }
7071
7072 deltaY = e.touches[0].screenY - startY;
7073
7074 // how far have we dragged so far?
7075 // kitkat fix for touchcancel events http://updates.html5rocks.com/2014/05/A-More-Compatible-Smoother-Touch
7076 // Only do this if we're not on crosswalk
7077 if (ionic.Platform.isAndroid() && ionic.Platform.version() === 4.4 && !ionic.Platform.isCrosswalk() && scrollParent.scrollTop === 0 && deltaY > 0) {
7078 isDragging = true;
7079 e.preventDefault();
7080 }
7081
7082
7083 // if we've dragged up and back down in to native scroll territory
7084 if (deltaY - dragOffset <= 0 || scrollParent.scrollTop !== 0) {
7085
7086 if (isOverscrolling) {
7087 isOverscrolling = false;
7088 setScrollLock(false);
7089 }
7090
7091 if (isDragging) {
7092 nativescroll(scrollParent, deltaY - dragOffset * -1);
7093 }
7094
7095 // if we're not at overscroll 0 yet, 0 out
7096 if (lastOverscroll !== 0) {
7097 overscroll(0);
7098 }
7099 return;
7100
7101 } else if (deltaY > 0 && scrollParent.scrollTop === 0 && !isOverscrolling) {
7102 // starting overscroll, but drag started below scrollTop 0, so we need to offset the position
7103 dragOffset = deltaY;
7104 }
7105
7106 // prevent native scroll events while overscrolling
7107 e.preventDefault();
7108
7109 // if not overscrolling yet, initiate overscrolling
7110 if (!isOverscrolling) {
7111 isOverscrolling = true;
7112 setScrollLock(true);
7113 }
7114
7115 isDragging = true;
7116 // overscroll according to the user's drag so far
7117 overscroll((deltaY - dragOffset) / 3);
7118
7119 // update the icon accordingly
7120 if (!activated && lastOverscroll > ptrThreshold) {
7121 activated = true;
7122 ionic.requestAnimationFrame(activate);
7123
7124 } else if (activated && lastOverscroll < ptrThreshold) {
7125 activated = false;
7126 ionic.requestAnimationFrame(deactivate);
7127 }
7128 }
7129
7130 function handleScroll(e) {
7131 // canOverscrol is used to greatly simplify the drag handler during normal scrolling
7132 canOverscroll = (e.target.scrollTop === 0) || isDragging;
7133 }
7134
7135 function overscroll(val) {
7136 scrollChild.style[ionic.CSS.TRANSFORM] = 'translate3d(0px, ' + val + 'px, 0px)';
7137 lastOverscroll = val;
7138 }
7139
7140 function nativescroll(target, newScrollTop) {
7141 // creates a scroll event that bubbles, can be cancelled, and with its view
7142 // and detail property initialized to window and 1, respectively
7143 target.scrollTop = newScrollTop;
7144 var e = document.createEvent("UIEvents");
7145 e.initUIEvent("scroll", true, true, window, 1);
7146 target.dispatchEvent(e);
7147 }
7148
7149 function setScrollLock(enabled) {
7150 // set the scrollbar to be position:fixed in preparation to overscroll
7151 // or remove it so the app can be natively scrolled
7152 if (enabled) {
7153 ionic.requestAnimationFrame(function() {
7154 scrollChild.classList.add('overscroll');
7155 show();
7156 });
7157
7158 } else {
7159 ionic.requestAnimationFrame(function() {
7160 scrollChild.classList.remove('overscroll');
7161 hide();
7162 deactivate();
7163 });
7164 }
7165 }
7166
7167 $scope.$on('scroll.refreshComplete', function() {
7168 // prevent the complete from firing before the scroll has started
7169 $timeout(function() {
7170
7171 ionic.requestAnimationFrame(tail);
7172
7173 // scroll back to home during tail animation
7174 scrollTo(0, scrollTime, deactivate);
7175
7176 // return to native scrolling after tail animation has time to finish
7177 $timeout(function() {
7178
7179 if (isOverscrolling) {
7180 isOverscrolling = false;
7181 setScrollLock(false);
7182 }
7183
7184 }, scrollTime);
7185
7186 }, scrollTime);
7187 });
7188
7189 function scrollTo(Y, duration, callback) {
7190 // scroll animation loop w/ easing
7191 // credit https://gist.github.com/dezinezync/5487119
7192 var start = Date.now(),
7193 from = lastOverscroll;
7194
7195 if (from === Y) {
7196 callback();
7197 return; /* Prevent scrolling to the Y point if already there */
7198 }
7199
7200 // decelerating to zero velocity
7201 function easeOutCubic(t) {
7202 return (--t) * t * t + 1;
7203 }
7204
7205 // scroll loop
7206 function scroll() {
7207 var currentTime = Date.now(),
7208 time = Math.min(1, ((currentTime - start) / duration)),
7209 // where .5 would be 50% of time on a linear scale easedT gives a
7210 // fraction based on the easing method
7211 easedT = easeOutCubic(time);
7212
7213 overscroll(Math.floor((easedT * (Y - from)) + from));
7214
7215 if (time < 1) {
7216 ionic.requestAnimationFrame(scroll);
7217
7218 } else {
7219
7220 if (Y < 5 && Y > -5) {
7221 isOverscrolling = false;
7222 setScrollLock(false);
7223 }
7224
7225 callback && callback();
7226 }
7227 }
7228
7229 // start scroll loop
7230 ionic.requestAnimationFrame(scroll);
7231 }
7232
7233
7234 var touchStartEvent, touchMoveEvent, touchEndEvent;
7235 if (window.navigator.pointerEnabled) {
7236 touchStartEvent = 'pointerdown';
7237 touchMoveEvent = 'pointermove';
7238 touchEndEvent = 'pointerup';
7239 } else if (window.navigator.msPointerEnabled) {
7240 touchStartEvent = 'MSPointerDown';
7241 touchMoveEvent = 'MSPointerMove';
7242 touchEndEvent = 'MSPointerUp';
7243 } else {
7244 touchStartEvent = 'touchstart';
7245 touchMoveEvent = 'touchmove';
7246 touchEndEvent = 'touchend';
7247 }
7248
7249 self.init = function() {
7250 scrollParent = $element.parent().parent()[0];
7251 scrollChild = $element.parent()[0];
7252
7253 if (!scrollParent || !scrollParent.classList.contains('ionic-scroll') ||
7254 !scrollChild || !scrollChild.classList.contains('scroll')) {
7255 throw new Error('Refresher must be immediate child of ion-content or ion-scroll');
7256 }
7257
7258
7259 ionic.on(touchStartEvent, handleTouchstart, scrollChild);
7260 ionic.on(touchMoveEvent, handleTouchmove, scrollChild);
7261 ionic.on(touchEndEvent, handleTouchend, scrollChild);
7262 ionic.on('mousedown', handleMousedown, scrollChild);
7263 ionic.on('mousemove', handleTouchmove, scrollChild);
7264 ionic.on('mouseup', handleTouchend, scrollChild);
7265 ionic.on('scroll', handleScroll, scrollParent);
7266
7267 // cleanup when done
7268 $scope.$on('$destroy', destroy);
7269 };
7270
7271 function destroy() {
7272 if ( scrollChild ) {
7273 ionic.off(touchStartEvent, handleTouchstart, scrollChild);
7274 ionic.off(touchMoveEvent, handleTouchmove, scrollChild);
7275 ionic.off(touchEndEvent, handleTouchend, scrollChild);
7276 ionic.off('mousedown', handleMousedown, scrollChild);
7277 ionic.off('mousemove', handleTouchmove, scrollChild);
7278 ionic.off('mouseup', handleTouchend, scrollChild);
7279 }
7280 if ( scrollParent ) {
7281 ionic.off('scroll', handleScroll, scrollParent);
7282 }
7283 scrollParent = null;
7284 scrollChild = null;
7285 }
7286
7287 // DOM manipulation and broadcast methods shared by JS and Native Scrolling
7288 // getter used by JS Scrolling
7289 self.getRefresherDomMethods = function() {
7290 return {
7291 activate: activate,
7292 deactivate: deactivate,
7293 start: start,
7294 show: show,
7295 hide: hide,
7296 tail: tail
7297 };
7298 };
7299
7300 function activate() {
7301 $element[0].classList.add('active');
7302 $scope.$onPulling();
7303 }
7304
7305 function deactivate() {
7306 // give tail 150ms to finish
7307 $timeout(function() {
7308 // deactivateCallback
7309 $element.removeClass('active refreshing refreshing-tail');
7310 if (activated) activated = false;
7311 }, 150);
7312 }
7313
7314 function start() {
7315 // startCallback
7316 $element[0].classList.add('refreshing');
7317 var q = $scope.$onRefresh();
7318
7319 if (q && q.then) {
7320 q['finally'](function() {
7321 $scope.$broadcast('scroll.refreshComplete');
7322 });
7323 }
7324 }
7325
7326 function show() {
7327 // showCallback
7328 $element[0].classList.remove('invisible');
7329 }
7330
7331 function hide() {
7332 // showCallback
7333 $element[0].classList.add('invisible');
7334 }
7335
7336 function tail() {
7337 // tailCallback
7338 $element[0].classList.add('refreshing-tail');
7339 }
7340
7341 // for testing
7342 self.__handleTouchmove = handleTouchmove;
7343 self.__getScrollChild = function() { return scrollChild; };
7344 self.__getScrollParent = function() { return scrollParent; };
7345 }
7346]);
7347
7348/**
7349 * @private
7350 */
7351IonicModule
7352
7353.controller('$ionicScroll', [
7354 '$scope',
7355 'scrollViewOptions',
7356 '$timeout',
7357 '$window',
7358 '$location',
7359 '$document',
7360 '$ionicScrollDelegate',
7361 '$ionicHistory',
7362function($scope,
7363 scrollViewOptions,
7364 $timeout,
7365 $window,
7366 $location,
7367 $document,
7368 $ionicScrollDelegate,
7369 $ionicHistory) {
7370
7371 var self = this;
7372 // for testing
7373 self.__timeout = $timeout;
7374
7375 self._scrollViewOptions = scrollViewOptions; //for testing
7376 self.isNative = function() {
7377 return !!scrollViewOptions.nativeScrolling;
7378 };
7379
7380 var element = self.element = scrollViewOptions.el;
7381 var $element = self.$element = jqLite(element);
7382 var scrollView;
7383 if (self.isNative()) {
7384 scrollView = self.scrollView = new ionic.views.ScrollNative(scrollViewOptions);
7385 } else {
7386 scrollView = self.scrollView = new ionic.views.Scroll(scrollViewOptions);
7387 }
7388
7389
7390 //Attach self to element as a controller so other directives can require this controller
7391 //through `require: '$ionicScroll'
7392 //Also attach to parent so that sibling elements can require this
7393 ($element.parent().length ? $element.parent() : $element)
7394 .data('$$ionicScrollController', self);
7395
7396 var deregisterInstance = $ionicScrollDelegate._registerInstance(
7397 self, scrollViewOptions.delegateHandle, function() {
7398 return $ionicHistory.isActiveScope($scope);
7399 }
7400 );
7401
7402 if (!isDefined(scrollViewOptions.bouncing)) {
7403 ionic.Platform.ready(function() {
7404 if (scrollView && scrollView.options) {
7405 scrollView.options.bouncing = true;
7406 if (ionic.Platform.isAndroid()) {
7407 // No bouncing by default on Android
7408 scrollView.options.bouncing = false;
7409 // Faster scroll decel
7410 scrollView.options.deceleration = 0.95;
7411 }
7412 }
7413 });
7414 }
7415
7416 var resize = angular.bind(scrollView, scrollView.resize);
7417 angular.element($window).on('resize', resize);
7418
7419 var scrollFunc = function(e) {
7420 var detail = (e.originalEvent || e).detail || {};
7421 $scope.$onScroll && $scope.$onScroll({
7422 event: e,
7423 scrollTop: detail.scrollTop || 0,
7424 scrollLeft: detail.scrollLeft || 0
7425 });
7426 };
7427
7428 $element.on('scroll', scrollFunc);
7429
7430 $scope.$on('$destroy', function() {
7431 deregisterInstance();
7432 scrollView && scrollView.__cleanup && scrollView.__cleanup();
7433 angular.element($window).off('resize', resize);
7434 if ( $element ) {
7435 $element.off('scroll', scrollFunc);
7436 }
7437 if ( self._scrollViewOptions ) {
7438 self._scrollViewOptions.el = null;
7439 }
7440 if ( scrollViewOptions ) {
7441 scrollViewOptions.el = null;
7442 }
7443
7444 scrollView = self.scrollView = scrollViewOptions = self._scrollViewOptions = element = self.$element = $element = null;
7445 });
7446
7447 $timeout(function() {
7448 scrollView && scrollView.run && scrollView.run();
7449 });
7450
7451 self.getScrollView = function() {
7452 return scrollView;
7453 };
7454
7455 self.getScrollPosition = function() {
7456 return scrollView.getValues();
7457 };
7458
7459 self.resize = function() {
7460 return $timeout(resize, 0, false).then(function() {
7461 $element && $element.triggerHandler('scroll-resize');
7462 });
7463 };
7464
7465 self.scrollTop = function(shouldAnimate) {
7466 self.resize().then(function() {
7467 if (!scrollView) {
7468 return;
7469 }
7470 scrollView.scrollTo(0, 0, !!shouldAnimate);
7471 });
7472 };
7473
7474 self.scrollBottom = function(shouldAnimate) {
7475 self.resize().then(function() {
7476 if (!scrollView) {
7477 return;
7478 }
7479 var max = scrollView.getScrollMax();
7480 scrollView.scrollTo(max.left, max.top, !!shouldAnimate);
7481 });
7482 };
7483
7484 self.scrollTo = function(left, top, shouldAnimate) {
7485 self.resize().then(function() {
7486 if (!scrollView) {
7487 return;
7488 }
7489 scrollView.scrollTo(left, top, !!shouldAnimate);
7490 });
7491 };
7492
7493 self.zoomTo = function(zoom, shouldAnimate, originLeft, originTop) {
7494 self.resize().then(function() {
7495 if (!scrollView) {
7496 return;
7497 }
7498 scrollView.zoomTo(zoom, !!shouldAnimate, originLeft, originTop);
7499 });
7500 };
7501
7502 self.zoomBy = function(zoom, shouldAnimate, originLeft, originTop) {
7503 self.resize().then(function() {
7504 if (!scrollView) {
7505 return;
7506 }
7507 scrollView.zoomBy(zoom, !!shouldAnimate, originLeft, originTop);
7508 });
7509 };
7510
7511 self.scrollBy = function(left, top, shouldAnimate) {
7512 self.resize().then(function() {
7513 if (!scrollView) {
7514 return;
7515 }
7516 scrollView.scrollBy(left, top, !!shouldAnimate);
7517 });
7518 };
7519
7520 self.anchorScroll = function(shouldAnimate) {
7521 self.resize().then(function() {
7522 if (!scrollView) {
7523 return;
7524 }
7525 var hash = $location.hash();
7526 var elm = hash && $document[0].getElementById(hash);
7527 if (!(hash && elm)) {
7528 scrollView.scrollTo(0, 0, !!shouldAnimate);
7529 return;
7530 }
7531 var curElm = elm;
7532 var scrollLeft = 0, scrollTop = 0;
7533 do {
7534 if (curElm !== null) scrollLeft += curElm.offsetLeft;
7535 if (curElm !== null) scrollTop += curElm.offsetTop;
7536 curElm = curElm.offsetParent;
7537 } while (curElm.attributes != self.element.attributes && curElm.offsetParent);
7538 scrollView.scrollTo(scrollLeft, scrollTop, !!shouldAnimate);
7539 });
7540 };
7541
7542 self.freezeScroll = scrollView.freeze;
7543 self.freezeScrollShut = scrollView.freezeShut;
7544
7545 self.freezeAllScrolls = function(shouldFreeze) {
7546 for (var i = 0; i < $ionicScrollDelegate._instances.length; i++) {
7547 $ionicScrollDelegate._instances[i].freezeScroll(shouldFreeze);
7548 }
7549 };
7550
7551
7552 /**
7553 * @private
7554 */
7555 self._setRefresher = function(refresherScope, refresherElement, refresherMethods) {
7556 self.refresher = refresherElement;
7557 var refresherHeight = self.refresher.clientHeight || 60;
7558 scrollView.activatePullToRefresh(
7559 refresherHeight,
7560 refresherMethods
7561 );
7562 };
7563
7564}]);
7565
7566IonicModule
7567.controller('$ionicSideMenus', [
7568 '$scope',
7569 '$attrs',
7570 '$ionicSideMenuDelegate',
7571 '$ionicPlatform',
7572 '$ionicBody',
7573 '$ionicHistory',
7574 '$ionicScrollDelegate',
7575 'IONIC_BACK_PRIORITY',
7576 '$rootScope',
7577function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform, $ionicBody, $ionicHistory, $ionicScrollDelegate, IONIC_BACK_PRIORITY, $rootScope) {
7578 var self = this;
7579 var rightShowing, leftShowing, isDragging;
7580 var startX, lastX, offsetX, isAsideExposed;
7581 var enableMenuWithBackViews = true;
7582
7583 self.$scope = $scope;
7584
7585 self.initialize = function(options) {
7586 self.left = options.left;
7587 self.right = options.right;
7588 self.setContent(options.content);
7589 self.dragThresholdX = options.dragThresholdX || 10;
7590 $ionicHistory.registerHistory(self.$scope);
7591 };
7592
7593 /**
7594 * Set the content view controller if not passed in the constructor options.
7595 *
7596 * @param {object} content
7597 */
7598 self.setContent = function(content) {
7599 if (content) {
7600 self.content = content;
7601
7602 self.content.onDrag = function(e) {
7603 self._handleDrag(e);
7604 };
7605
7606 self.content.endDrag = function(e) {
7607 self._endDrag(e);
7608 };
7609 }
7610 };
7611
7612 self.isOpenLeft = function() {
7613 return self.getOpenAmount() > 0;
7614 };
7615
7616 self.isOpenRight = function() {
7617 return self.getOpenAmount() < 0;
7618 };
7619
7620 /**
7621 * Toggle the left menu to open 100%
7622 */
7623 self.toggleLeft = function(shouldOpen) {
7624 if (isAsideExposed || !self.left.isEnabled) return;
7625 var openAmount = self.getOpenAmount();
7626 if (arguments.length === 0) {
7627 shouldOpen = openAmount <= 0;
7628 }
7629 self.content.enableAnimation();
7630 if (!shouldOpen) {
7631 self.openPercentage(0);
7632 $rootScope.$emit('$ionicSideMenuClose', 'left');
7633 } else {
7634 self.openPercentage(100);
7635 $rootScope.$emit('$ionicSideMenuOpen', 'left');
7636 }
7637 };
7638
7639 /**
7640 * Toggle the right menu to open 100%
7641 */
7642 self.toggleRight = function(shouldOpen) {
7643 if (isAsideExposed || !self.right.isEnabled) return;
7644 var openAmount = self.getOpenAmount();
7645 if (arguments.length === 0) {
7646 shouldOpen = openAmount >= 0;
7647 }
7648 self.content.enableAnimation();
7649 if (!shouldOpen) {
7650 self.openPercentage(0);
7651 $rootScope.$emit('$ionicSideMenuClose', 'right');
7652 } else {
7653 self.openPercentage(-100);
7654 $rootScope.$emit('$ionicSideMenuOpen', 'right');
7655 }
7656 };
7657
7658 self.toggle = function(side) {
7659 if (side == 'right') {
7660 self.toggleRight();
7661 } else {
7662 self.toggleLeft();
7663 }
7664 };
7665
7666 /**
7667 * Close all menus.
7668 */
7669 self.close = function() {
7670 self.openPercentage(0);
7671 $rootScope.$emit('$ionicSideMenuClose', 'left');
7672 $rootScope.$emit('$ionicSideMenuClose', 'right');
7673 };
7674
7675 /**
7676 * @return {float} The amount the side menu is open, either positive or negative for left (positive), or right (negative)
7677 */
7678 self.getOpenAmount = function() {
7679 return self.content && self.content.getTranslateX() || 0;
7680 };
7681
7682 /**
7683 * @return {float} The ratio of open amount over menu width. For example, a
7684 * menu of width 100 open 50 pixels would be open 50% or a ratio of 0.5. Value is negative
7685 * for right menu.
7686 */
7687 self.getOpenRatio = function() {
7688 var amount = self.getOpenAmount();
7689 if (amount >= 0) {
7690 return amount / self.left.width;
7691 }
7692 return amount / self.right.width;
7693 };
7694
7695 self.isOpen = function() {
7696 return self.getOpenAmount() !== 0;
7697 };
7698
7699 /**
7700 * @return {float} The percentage of open amount over menu width. For example, a
7701 * menu of width 100 open 50 pixels would be open 50%. Value is negative
7702 * for right menu.
7703 */
7704 self.getOpenPercentage = function() {
7705 return self.getOpenRatio() * 100;
7706 };
7707
7708 /**
7709 * Open the menu with a given percentage amount.
7710 * @param {float} percentage The percentage (positive or negative for left/right) to open the menu.
7711 */
7712 self.openPercentage = function(percentage) {
7713 var p = percentage / 100;
7714
7715 if (self.left && percentage >= 0) {
7716 self.openAmount(self.left.width * p);
7717 } else if (self.right && percentage < 0) {
7718 self.openAmount(self.right.width * p);
7719 }
7720
7721 // add the CSS class "menu-open" if the percentage does not
7722 // equal 0, otherwise remove the class from the body element
7723 $ionicBody.enableClass((percentage !== 0), 'menu-open');
7724
7725 self.content.setCanScroll(percentage == 0);
7726 };
7727
7728 /*
7729 function freezeAllScrolls(shouldFreeze) {
7730 if (shouldFreeze && !self.isScrollFreeze) {
7731 $ionicScrollDelegate.freezeAllScrolls(shouldFreeze);
7732
7733 } else if (!shouldFreeze && self.isScrollFreeze) {
7734 $ionicScrollDelegate.freezeAllScrolls(false);
7735 }
7736 self.isScrollFreeze = shouldFreeze;
7737 }
7738 */
7739
7740 /**
7741 * Open the menu the given pixel amount.
7742 * @param {float} amount the pixel amount to open the menu. Positive value for left menu,
7743 * negative value for right menu (only one menu will be visible at a time).
7744 */
7745 self.openAmount = function(amount) {
7746 var maxLeft = self.left && self.left.width || 0;
7747 var maxRight = self.right && self.right.width || 0;
7748
7749 // Check if we can move to that side, depending if the left/right panel is enabled
7750 if (!(self.left && self.left.isEnabled) && amount > 0) {
7751 self.content.setTranslateX(0);
7752 return;
7753 }
7754
7755 if (!(self.right && self.right.isEnabled) && amount < 0) {
7756 self.content.setTranslateX(0);
7757 return;
7758 }
7759
7760 if (leftShowing && amount > maxLeft) {
7761 self.content.setTranslateX(maxLeft);
7762 return;
7763 }
7764
7765 if (rightShowing && amount < -maxRight) {
7766 self.content.setTranslateX(-maxRight);
7767 return;
7768 }
7769
7770 self.content.setTranslateX(amount);
7771
7772 if (amount >= 0) {
7773 leftShowing = true;
7774 rightShowing = false;
7775
7776 if (amount > 0) {
7777 // Push the z-index of the right menu down
7778 self.right && self.right.pushDown && self.right.pushDown();
7779 // Bring the z-index of the left menu up
7780 self.left && self.left.bringUp && self.left.bringUp();
7781 }
7782 } else {
7783 rightShowing = true;
7784 leftShowing = false;
7785
7786 // Bring the z-index of the right menu up
7787 self.right && self.right.bringUp && self.right.bringUp();
7788 // Push the z-index of the left menu down
7789 self.left && self.left.pushDown && self.left.pushDown();
7790 }
7791 };
7792
7793 /**
7794 * Given an event object, find the final resting position of this side
7795 * menu. For example, if the user "throws" the content to the right and
7796 * releases the touch, the left menu should snap open (animated, of course).
7797 *
7798 * @param {Event} e the gesture event to use for snapping
7799 */
7800 self.snapToRest = function(e) {
7801 // We want to animate at the end of this
7802 self.content.enableAnimation();
7803 isDragging = false;
7804
7805 // Check how much the panel is open after the drag, and
7806 // what the drag velocity is
7807 var ratio = self.getOpenRatio();
7808
7809 if (ratio === 0) {
7810 // Just to be safe
7811 self.openPercentage(0);
7812 return;
7813 }
7814
7815 var velocityThreshold = 0.3;
7816 var velocityX = e.gesture.velocityX;
7817 var direction = e.gesture.direction;
7818
7819 // Going right, less than half, too slow (snap back)
7820 if (ratio > 0 && ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {
7821 self.openPercentage(0);
7822 }
7823
7824 // Going left, more than half, too slow (snap back)
7825 else if (ratio > 0.5 && direction == 'left' && velocityX < velocityThreshold) {
7826 self.openPercentage(100);
7827 }
7828
7829 // Going left, less than half, too slow (snap back)
7830 else if (ratio < 0 && ratio > -0.5 && direction == 'left' && velocityX < velocityThreshold) {
7831 self.openPercentage(0);
7832 }
7833
7834 // Going right, more than half, too slow (snap back)
7835 else if (ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {
7836 self.openPercentage(-100);
7837 }
7838
7839 // Going right, more than half, or quickly (snap open)
7840 else if (direction == 'right' && ratio >= 0 && (ratio >= 0.5 || velocityX > velocityThreshold)) {
7841 self.openPercentage(100);
7842 }
7843
7844 // Going left, more than half, or quickly (span open)
7845 else if (direction == 'left' && ratio <= 0 && (ratio <= -0.5 || velocityX > velocityThreshold)) {
7846 self.openPercentage(-100);
7847 }
7848
7849 // Snap back for safety
7850 else {
7851 self.openPercentage(0);
7852 }
7853 };
7854
7855 self.enableMenuWithBackViews = function(val) {
7856 if (arguments.length) {
7857 enableMenuWithBackViews = !!val;
7858 }
7859 return enableMenuWithBackViews;
7860 };
7861
7862 self.isAsideExposed = function() {
7863 return !!isAsideExposed;
7864 };
7865
7866 self.exposeAside = function(shouldExposeAside) {
7867 if (!(self.left && self.left.isEnabled) && !(self.right && self.right.isEnabled)) return;
7868 self.close();
7869
7870 isAsideExposed = shouldExposeAside;
7871 if ((self.left && self.left.isEnabled) && (self.right && self.right.isEnabled)) {
7872 self.content.setMarginLeftAndRight(isAsideExposed ? self.left.width : 0, isAsideExposed ? self.right.width : 0);
7873 } else if (self.left && self.left.isEnabled) {
7874 // set the left marget width if it should be exposed
7875 // otherwise set false so there's no left margin
7876 self.content.setMarginLeft(isAsideExposed ? self.left.width : 0);
7877 } else if (self.right && self.right.isEnabled) {
7878 self.content.setMarginRight(isAsideExposed ? self.right.width : 0);
7879 }
7880 self.$scope.$emit('$ionicExposeAside', isAsideExposed);
7881 };
7882
7883 self.activeAsideResizing = function(isResizing) {
7884 $ionicBody.enableClass(isResizing, 'aside-resizing');
7885 };
7886
7887 // End a drag with the given event
7888 self._endDrag = function(e) {
7889 if (isAsideExposed) return;
7890
7891 if (isDragging) {
7892 self.snapToRest(e);
7893 }
7894 startX = null;
7895 lastX = null;
7896 offsetX = null;
7897 };
7898
7899 // Handle a drag event
7900 self._handleDrag = function(e) {
7901 if (isAsideExposed || !$scope.dragContent) return;
7902
7903 // If we don't have start coords, grab and store them
7904 if (!startX) {
7905 startX = e.gesture.touches[0].pageX;
7906 lastX = startX;
7907 } else {
7908 // Grab the current tap coords
7909 lastX = e.gesture.touches[0].pageX;
7910 }
7911
7912 // Calculate difference from the tap points
7913 if (!isDragging && Math.abs(lastX - startX) > self.dragThresholdX) {
7914 // if the difference is greater than threshold, start dragging using the current
7915 // point as the starting point
7916 startX = lastX;
7917
7918 isDragging = true;
7919 // Initialize dragging
7920 self.content.disableAnimation();
7921 offsetX = self.getOpenAmount();
7922 }
7923
7924 if (isDragging) {
7925 self.openAmount(offsetX + (lastX - startX));
7926 //self.content.setCanScroll(false);
7927 }
7928 };
7929
7930 self.canDragContent = function(canDrag) {
7931 if (arguments.length) {
7932 $scope.dragContent = !!canDrag;
7933 }
7934 return $scope.dragContent;
7935 };
7936
7937 self.edgeThreshold = 25;
7938 self.edgeThresholdEnabled = false;
7939 self.edgeDragThreshold = function(value) {
7940 if (arguments.length) {
7941 if (isNumber(value) && value > 0) {
7942 self.edgeThreshold = value;
7943 self.edgeThresholdEnabled = true;
7944 } else {
7945 self.edgeThresholdEnabled = !!value;
7946 }
7947 }
7948 return self.edgeThresholdEnabled;
7949 };
7950
7951 self.isDraggableTarget = function(e) {
7952 //Only restrict edge when sidemenu is closed and restriction is enabled
7953 var shouldOnlyAllowEdgeDrag = self.edgeThresholdEnabled && !self.isOpen();
7954 var startX = e.gesture.startEvent && e.gesture.startEvent.center &&
7955 e.gesture.startEvent.center.pageX;
7956
7957 var dragIsWithinBounds = !shouldOnlyAllowEdgeDrag ||
7958 startX <= self.edgeThreshold ||
7959 startX >= self.content.element.offsetWidth - self.edgeThreshold;
7960
7961 var backView = $ionicHistory.backView();
7962 var menuEnabled = enableMenuWithBackViews ? true : !backView;
7963 if (!menuEnabled) {
7964 var currentView = $ionicHistory.currentView() || {};
7965 return (dragIsWithinBounds && (backView.historyId !== currentView.historyId));
7966 }
7967
7968 return ($scope.dragContent || self.isOpen()) &&
7969 dragIsWithinBounds &&
7970 !e.gesture.srcEvent.defaultPrevented &&
7971 menuEnabled &&
7972 !e.target.tagName.match(/input|textarea|select|object|embed/i) &&
7973 !e.target.isContentEditable &&
7974 !(e.target.dataset ? e.target.dataset.preventScroll : e.target.getAttribute('data-prevent-scroll') == 'true');
7975 };
7976
7977 $scope.sideMenuContentTranslateX = 0;
7978
7979 var deregisterBackButtonAction = noop;
7980 var closeSideMenu = angular.bind(self, self.close);
7981
7982 $scope.$watch(function() {
7983 return self.getOpenAmount() !== 0;
7984 }, function(isOpen) {
7985 deregisterBackButtonAction();
7986 if (isOpen) {
7987 deregisterBackButtonAction = $ionicPlatform.registerBackButtonAction(
7988 closeSideMenu,
7989 IONIC_BACK_PRIORITY.sideMenu
7990 );
7991 }
7992 });
7993
7994 var deregisterInstance = $ionicSideMenuDelegate._registerInstance(
7995 self, $attrs.delegateHandle, function() {
7996 return $ionicHistory.isActiveScope($scope);
7997 }
7998 );
7999
8000 $scope.$on('$destroy', function() {
8001 deregisterInstance();
8002 deregisterBackButtonAction();
8003 self.$scope = null;
8004 if (self.content) {
8005 self.content.setCanScroll(true);
8006 self.content.element = null;
8007 self.content = null;
8008 }
8009 });
8010
8011 self.initialize({
8012 left: {
8013 width: 275
8014 },
8015 right: {
8016 width: 275
8017 }
8018 });
8019
8020}]);
8021
8022(function(ionic) {
8023
8024 var TRANSLATE32 = 'translate(32,32)';
8025 var STROKE_OPACITY = 'stroke-opacity';
8026 var ROUND = 'round';
8027 var INDEFINITE = 'indefinite';
8028 var DURATION = '750ms';
8029 var NONE = 'none';
8030 var SHORTCUTS = {
8031 a: 'animate',
8032 an: 'attributeName',
8033 at: 'animateTransform',
8034 c: 'circle',
8035 da: 'stroke-dasharray',
8036 os: 'stroke-dashoffset',
8037 f: 'fill',
8038 lc: 'stroke-linecap',
8039 rc: 'repeatCount',
8040 sw: 'stroke-width',
8041 t: 'transform',
8042 v: 'values'
8043 };
8044
8045 var SPIN_ANIMATION = {
8046 v: '0,32,32;360,32,32',
8047 an: 'transform',
8048 type: 'rotate',
8049 rc: INDEFINITE,
8050 dur: DURATION
8051 };
8052
8053 function createSvgElement(tagName, data, parent, spinnerName) {
8054 var ele = document.createElement(SHORTCUTS[tagName] || tagName);
8055 var k, x, y;
8056
8057 for (k in data) {
8058
8059 if (angular.isArray(data[k])) {
8060 for (x = 0; x < data[k].length; x++) {
8061 if (data[k][x].fn) {
8062 for (y = 0; y < data[k][x].t; y++) {
8063 createSvgElement(k, data[k][x].fn(y, spinnerName), ele, spinnerName);
8064 }
8065 } else {
8066 createSvgElement(k, data[k][x], ele, spinnerName);
8067 }
8068 }
8069
8070 } else {
8071 setSvgAttribute(ele, k, data[k]);
8072 }
8073 }
8074
8075 parent.appendChild(ele);
8076 }
8077
8078 function setSvgAttribute(ele, k, v) {
8079 ele.setAttribute(SHORTCUTS[k] || k, v);
8080 }
8081
8082 function animationValues(strValues, i) {
8083 var values = strValues.split(';');
8084 var back = values.slice(i);
8085 var front = values.slice(0, values.length - back.length);
8086 values = back.concat(front).reverse();
8087 return values.join(';') + ';' + values[0];
8088 }
8089
8090 var IOS_SPINNER = {
8091 sw: 4,
8092 lc: ROUND,
8093 line: [{
8094 fn: function(i, spinnerName) {
8095 return {
8096 y1: spinnerName == 'ios' ? 17 : 12,
8097 y2: spinnerName == 'ios' ? 29 : 20,
8098 t: TRANSLATE32 + ' rotate(' + (30 * i + (i < 6 ? 180 : -180)) + ')',
8099 a: [{
8100 fn: function() {
8101 return {
8102 an: STROKE_OPACITY,
8103 dur: DURATION,
8104 v: animationValues('0;.1;.15;.25;.35;.45;.55;.65;.7;.85;1', i),
8105 rc: INDEFINITE
8106 };
8107 },
8108 t: 1
8109 }]
8110 };
8111 },
8112 t: 12
8113 }]
8114 };
8115
8116 var spinners = {
8117
8118 android: {
8119 c: [{
8120 sw: 6,
8121 da: 128,
8122 os: 82,
8123 r: 26,
8124 cx: 32,
8125 cy: 32,
8126 f: NONE
8127 }]
8128 },
8129
8130 ios: IOS_SPINNER,
8131
8132 'ios-small': IOS_SPINNER,
8133
8134 bubbles: {
8135 sw: 0,
8136 c: [{
8137 fn: function(i) {
8138 return {
8139 cx: 24 * Math.cos(2 * Math.PI * i / 8),
8140 cy: 24 * Math.sin(2 * Math.PI * i / 8),
8141 t: TRANSLATE32,
8142 a: [{
8143 fn: function() {
8144 return {
8145 an: 'r',
8146 dur: DURATION,
8147 v: animationValues('1;2;3;4;5;6;7;8', i),
8148 rc: INDEFINITE
8149 };
8150 },
8151 t: 1
8152 }]
8153 };
8154 },
8155 t: 8
8156 }]
8157 },
8158
8159 circles: {
8160
8161 c: [{
8162 fn: function(i) {
8163 return {
8164 r: 5,
8165 cx: 24 * Math.cos(2 * Math.PI * i / 8),
8166 cy: 24 * Math.sin(2 * Math.PI * i / 8),
8167 t: TRANSLATE32,
8168 sw: 0,
8169 a: [{
8170 fn: function() {
8171 return {
8172 an: 'fill-opacity',
8173 dur: DURATION,
8174 v: animationValues('.3;.3;.3;.4;.7;.85;.9;1', i),
8175 rc: INDEFINITE
8176 };
8177 },
8178 t: 1
8179 }]
8180 };
8181 },
8182 t: 8
8183 }]
8184 },
8185
8186 crescent: {
8187 c: [{
8188 sw: 4,
8189 da: 128,
8190 os: 82,
8191 r: 26,
8192 cx: 32,
8193 cy: 32,
8194 f: NONE,
8195 at: [SPIN_ANIMATION]
8196 }]
8197 },
8198
8199 dots: {
8200
8201 c: [{
8202 fn: function(i) {
8203 return {
8204 cx: 16 + (16 * i),
8205 cy: 32,
8206 sw: 0,
8207 a: [{
8208 fn: function() {
8209 return {
8210 an: 'fill-opacity',
8211 dur: DURATION,
8212 v: animationValues('.5;.6;.8;1;.8;.6;.5', i),
8213 rc: INDEFINITE
8214 };
8215 },
8216 t: 1
8217 }, {
8218 fn: function() {
8219 return {
8220 an: 'r',
8221 dur: DURATION,
8222 v: animationValues('4;5;6;5;4;3;3', i),
8223 rc: INDEFINITE
8224 };
8225 },
8226 t: 1
8227 }]
8228 };
8229 },
8230 t: 3
8231 }]
8232 },
8233
8234 lines: {
8235 sw: 7,
8236 lc: ROUND,
8237 line: [{
8238 fn: function(i) {
8239 return {
8240 x1: 10 + (i * 14),
8241 x2: 10 + (i * 14),
8242 a: [{
8243 fn: function() {
8244 return {
8245 an: 'y1',
8246 dur: DURATION,
8247 v: animationValues('16;18;28;18;16', i),
8248 rc: INDEFINITE
8249 };
8250 },
8251 t: 1
8252 }, {
8253 fn: function() {
8254 return {
8255 an: 'y2',
8256 dur: DURATION,
8257 v: animationValues('48;44;36;46;48', i),
8258 rc: INDEFINITE
8259 };
8260 },
8261 t: 1
8262 }, {
8263 fn: function() {
8264 return {
8265 an: STROKE_OPACITY,
8266 dur: DURATION,
8267 v: animationValues('1;.8;.5;.4;1', i),
8268 rc: INDEFINITE
8269 };
8270 },
8271 t: 1
8272 }]
8273 };
8274 },
8275 t: 4
8276 }]
8277 },
8278
8279 ripple: {
8280 f: NONE,
8281 'fill-rule': 'evenodd',
8282 sw: 3,
8283 circle: [{
8284 fn: function(i) {
8285 return {
8286 cx: 32,
8287 cy: 32,
8288 a: [{
8289 fn: function() {
8290 return {
8291 an: 'r',
8292 begin: (i * -1) + 's',
8293 dur: '2s',
8294 v: '0;24',
8295 keyTimes: '0;1',
8296 keySplines: '0.1,0.2,0.3,1',
8297 calcMode: 'spline',
8298 rc: INDEFINITE
8299 };
8300 },
8301 t: 1
8302 }, {
8303 fn: function() {
8304 return {
8305 an: STROKE_OPACITY,
8306 begin: (i * -1) + 's',
8307 dur: '2s',
8308 v: '.2;1;.2;0',
8309 rc: INDEFINITE
8310 };
8311 },
8312 t: 1
8313 }]
8314 };
8315 },
8316 t: 2
8317 }]
8318 },
8319
8320 spiral: {
8321 defs: [{
8322 linearGradient: [{
8323 id: 'sGD',
8324 gradientUnits: 'userSpaceOnUse',
8325 x1: 55, y1: 46, x2: 2, y2: 46,
8326 stop: [{
8327 offset: 0.1,
8328 class: 'stop1'
8329 }, {
8330 offset: 1,
8331 class: 'stop2'
8332 }]
8333 }]
8334 }],
8335 g: [{
8336 sw: 4,
8337 lc: ROUND,
8338 f: NONE,
8339 path: [{
8340 stroke: 'url(#sGD)',
8341 d: 'M4,32 c0,15,12,28,28,28c8,0,16-4,21-9'
8342 }, {
8343 d: 'M60,32 C60,16,47.464,4,32,4S4,16,4,32'
8344 }],
8345 at: [SPIN_ANIMATION]
8346 }]
8347 }
8348
8349 };
8350
8351 var animations = {
8352
8353 android: function(ele) {
8354 // Note that this is called as a function, not a constructor.
8355 var self = {};
8356
8357 this.stop = false;
8358
8359 var rIndex = 0;
8360 var rotateCircle = 0;
8361 var startTime;
8362 var svgEle = ele.querySelector('g');
8363 var circleEle = ele.querySelector('circle');
8364
8365 function run() {
8366 if (self.stop) return;
8367
8368 var v = easeInOutCubic(Date.now() - startTime, 650);
8369 var scaleX = 1;
8370 var translateX = 0;
8371 var dasharray = (188 - (58 * v));
8372 var dashoffset = (182 - (182 * v));
8373
8374 if (rIndex % 2) {
8375 scaleX = -1;
8376 translateX = -64;
8377 dasharray = (128 - (-58 * v));
8378 dashoffset = (182 * v);
8379 }
8380
8381 var rotateLine = [0, -101, -90, -11, -180, 79, -270, -191][rIndex];
8382
8383 setSvgAttribute(circleEle, 'da', Math.max(Math.min(dasharray, 188), 128));
8384 setSvgAttribute(circleEle, 'os', Math.max(Math.min(dashoffset, 182), 0));
8385 setSvgAttribute(circleEle, 't', 'scale(' + scaleX + ',1) translate(' + translateX + ',0) rotate(' + rotateLine + ',32,32)');
8386
8387 rotateCircle += 4.1;
8388 if (rotateCircle > 359) rotateCircle = 0;
8389 setSvgAttribute(svgEle, 't', 'rotate(' + rotateCircle + ',32,32)');
8390
8391 if (v >= 1) {
8392 rIndex++;
8393 if (rIndex > 7) rIndex = 0;
8394 startTime = Date.now();
8395 }
8396
8397 ionic.requestAnimationFrame(run);
8398 }
8399
8400 return function() {
8401 startTime = Date.now();
8402 run();
8403 return self;
8404 };
8405
8406 }
8407
8408 };
8409
8410 function easeInOutCubic(t, c) {
8411 t /= c / 2;
8412 if (t < 1) return 1 / 2 * t * t * t;
8413 t -= 2;
8414 return 1 / 2 * (t * t * t + 2);
8415 }
8416
8417
8418 IonicModule
8419 .controller('$ionicSpinner', [
8420 '$element',
8421 '$attrs',
8422 '$ionicConfig',
8423 function($element, $attrs, $ionicConfig) {
8424 var spinnerName, anim;
8425
8426 this.init = function() {
8427 spinnerName = $attrs.icon || $ionicConfig.spinner.icon();
8428
8429 var container = document.createElement('div');
8430 createSvgElement('svg', {
8431 viewBox: '0 0 64 64',
8432 g: [spinners[spinnerName]]
8433 }, container, spinnerName);
8434
8435 // Specifically for animations to work,
8436 // Android 4.3 and below requires the element to be
8437 // added as an html string, rather than dynmically
8438 // building up the svg element and appending it.
8439 $element.html(container.innerHTML);
8440
8441 this.start();
8442
8443 return spinnerName;
8444 };
8445
8446 this.start = function() {
8447 animations[spinnerName] && (anim = animations[spinnerName]($element[0])());
8448 };
8449
8450 this.stop = function() {
8451 animations[spinnerName] && (anim.stop = true);
8452 };
8453
8454 }]);
8455
8456})(ionic);
8457
8458IonicModule
8459.controller('$ionicTab', [
8460 '$scope',
8461 '$ionicHistory',
8462 '$attrs',
8463 '$location',
8464 '$state',
8465function($scope, $ionicHistory, $attrs, $location, $state) {
8466 this.$scope = $scope;
8467
8468 //All of these exposed for testing
8469 this.hrefMatchesState = function() {
8470 return $attrs.href && $location.path().indexOf(
8471 $attrs.href.replace(/^#/, '').replace(/\/$/, '')
8472 ) === 0;
8473 };
8474 this.srefMatchesState = function() {
8475 return $attrs.uiSref && $state.includes($attrs.uiSref.split('(')[0]);
8476 };
8477 this.navNameMatchesState = function() {
8478 return this.navViewName && $ionicHistory.isCurrentStateNavView(this.navViewName);
8479 };
8480
8481 this.tabMatchesState = function() {
8482 return this.hrefMatchesState() || this.srefMatchesState() || this.navNameMatchesState();
8483 };
8484}]);
8485
8486IonicModule
8487.controller('$ionicTabs', [
8488 '$scope',
8489 '$element',
8490 '$ionicHistory',
8491function($scope, $element, $ionicHistory) {
8492 var self = this;
8493 var selectedTab = null;
8494 var previousSelectedTab = null;
8495 var selectedTabIndex;
8496 var isVisible = true;
8497 self.tabs = [];
8498
8499 self.selectedIndex = function() {
8500 return self.tabs.indexOf(selectedTab);
8501 };
8502 self.selectedTab = function() {
8503 return selectedTab;
8504 };
8505 self.previousSelectedTab = function() {
8506 return previousSelectedTab;
8507 };
8508
8509 self.add = function(tab) {
8510 $ionicHistory.registerHistory(tab);
8511 self.tabs.push(tab);
8512 };
8513
8514 self.remove = function(tab) {
8515 var tabIndex = self.tabs.indexOf(tab);
8516 if (tabIndex === -1) {
8517 return;
8518 }
8519 //Use a field like '$tabSelected' so developers won't accidentally set it in controllers etc
8520 if (tab.$tabSelected) {
8521 self.deselect(tab);
8522 //Try to select a new tab if we're removing a tab
8523 if (self.tabs.length === 1) {
8524 //Do nothing if there are no other tabs to select
8525 } else {
8526 //Select previous tab if it's the last tab, else select next tab
8527 var newTabIndex = tabIndex === self.tabs.length - 1 ? tabIndex - 1 : tabIndex + 1;
8528 self.select(self.tabs[newTabIndex]);
8529 }
8530 }
8531 self.tabs.splice(tabIndex, 1);
8532 };
8533
8534 self.deselect = function(tab) {
8535 if (tab.$tabSelected) {
8536 previousSelectedTab = selectedTab;
8537 selectedTab = selectedTabIndex = null;
8538 tab.$tabSelected = false;
8539 (tab.onDeselect || noop)();
8540 tab.$broadcast && tab.$broadcast('$ionicHistory.deselect');
8541 }
8542 };
8543
8544 self.select = function(tab, shouldEmitEvent) {
8545 var tabIndex;
8546 if (isNumber(tab)) {
8547 tabIndex = tab;
8548 if (tabIndex >= self.tabs.length) return;
8549 tab = self.tabs[tabIndex];
8550 } else {
8551 tabIndex = self.tabs.indexOf(tab);
8552 }
8553
8554 if (arguments.length === 1) {
8555 shouldEmitEvent = !!(tab.navViewName || tab.uiSref);
8556 }
8557
8558 if (selectedTab && selectedTab.$historyId == tab.$historyId) {
8559 if (shouldEmitEvent) {
8560 $ionicHistory.goToHistoryRoot(tab.$historyId);
8561 }
8562
8563 } else if (selectedTabIndex !== tabIndex) {
8564 forEach(self.tabs, function(tab) {
8565 self.deselect(tab);
8566 });
8567
8568 selectedTab = tab;
8569 selectedTabIndex = tabIndex;
8570
8571 if (self.$scope && self.$scope.$parent) {
8572 self.$scope.$parent.$activeHistoryId = tab.$historyId;
8573 }
8574
8575 //Use a funny name like $tabSelected so the developer doesn't overwrite the var in a child scope
8576 tab.$tabSelected = true;
8577 (tab.onSelect || noop)();
8578
8579 if (shouldEmitEvent) {
8580 $scope.$emit('$ionicHistory.change', {
8581 type: 'tab',
8582 tabIndex: tabIndex,
8583 historyId: tab.$historyId,
8584 navViewName: tab.navViewName,
8585 hasNavView: !!tab.navViewName,
8586 title: tab.title,
8587 url: tab.href,
8588 uiSref: tab.uiSref
8589 });
8590 }
8591
8592 $scope.$broadcast("tabSelected", { selectedTab: tab, selectedTabIndex: tabIndex});
8593 }
8594 };
8595
8596 self.hasActiveScope = function() {
8597 for (var x = 0; x < self.tabs.length; x++) {
8598 if ($ionicHistory.isActiveScope(self.tabs[x])) {
8599 return true;
8600 }
8601 }
8602 return false;
8603 };
8604
8605 self.showBar = function(show) {
8606 if (arguments.length) {
8607 if (show) {
8608 $element.removeClass('tabs-item-hide');
8609 } else {
8610 $element.addClass('tabs-item-hide');
8611 }
8612 isVisible = !!show;
8613 }
8614 return isVisible;
8615 };
8616}]);
8617
8618IonicModule
8619.controller('$ionicView', [
8620 '$scope',
8621 '$element',
8622 '$attrs',
8623 '$compile',
8624 '$rootScope',
8625function($scope, $element, $attrs, $compile, $rootScope) {
8626 var self = this;
8627 var navElementHtml = {};
8628 var navViewCtrl;
8629 var navBarDelegateHandle;
8630 var hasViewHeaderBar;
8631 var deregisters = [];
8632 var viewTitle;
8633
8634 var deregIonNavBarInit = $scope.$on('ionNavBar.init', function(ev, delegateHandle) {
8635 // this view has its own ion-nav-bar, remember the navBarDelegateHandle for this view
8636 ev.stopPropagation();
8637 navBarDelegateHandle = delegateHandle;
8638 });
8639
8640
8641 self.init = function() {
8642 deregIonNavBarInit();
8643
8644 var modalCtrl = $element.inheritedData('$ionModalController');
8645 navViewCtrl = $element.inheritedData('$ionNavViewController');
8646
8647 // don't bother if inside a modal or there's no parent navView
8648 if (!navViewCtrl || modalCtrl) return;
8649
8650 // add listeners for when this view changes
8651 $scope.$on('$ionicView.beforeEnter', self.beforeEnter);
8652 $scope.$on('$ionicView.afterEnter', afterEnter);
8653 $scope.$on('$ionicView.beforeLeave', deregisterFns);
8654 };
8655
8656 self.beforeEnter = function(ev, transData) {
8657 // this event was emitted, starting at intial ion-view, then bubbles up
8658 // only the first ion-view should do something with it, parent ion-views should ignore
8659 if (transData && !transData.viewNotified) {
8660 transData.viewNotified = true;
8661
8662 if (!$rootScope.$$phase) $scope.$digest();
8663 viewTitle = isDefined($attrs.viewTitle) ? $attrs.viewTitle : $attrs.title;
8664
8665 var navBarItems = {};
8666 for (var n in navElementHtml) {
8667 navBarItems[n] = generateNavBarItem(navElementHtml[n]);
8668 }
8669
8670 navViewCtrl.beforeEnter(extend(transData, {
8671 title: viewTitle,
8672 showBack: !attrTrue('hideBackButton'),
8673 navBarItems: navBarItems,
8674 navBarDelegate: navBarDelegateHandle || null,
8675 showNavBar: !attrTrue('hideNavBar'),
8676 hasHeaderBar: !!hasViewHeaderBar
8677 }));
8678
8679 // make sure any existing observers are cleaned up
8680 deregisterFns();
8681 }
8682 };
8683
8684
8685 function afterEnter() {
8686 // only listen for title updates after it has entered
8687 // but also deregister the observe before it leaves
8688 var viewTitleAttr = isDefined($attrs.viewTitle) && 'viewTitle' || isDefined($attrs.title) && 'title';
8689 if (viewTitleAttr) {
8690 titleUpdate($attrs[viewTitleAttr]);
8691 deregisters.push($attrs.$observe(viewTitleAttr, titleUpdate));
8692 }
8693
8694 if (isDefined($attrs.hideBackButton)) {
8695 deregisters.push($scope.$watch($attrs.hideBackButton, function(val) {
8696 navViewCtrl.showBackButton(!val);
8697 }));
8698 }
8699
8700 if (isDefined($attrs.hideNavBar)) {
8701 deregisters.push($scope.$watch($attrs.hideNavBar, function(val) {
8702 navViewCtrl.showBar(!val);
8703 }));
8704 }
8705 }
8706
8707
8708 function titleUpdate(newTitle) {
8709 if (isDefined(newTitle) && newTitle !== viewTitle) {
8710 viewTitle = newTitle;
8711 navViewCtrl.title(viewTitle);
8712 }
8713 }
8714
8715
8716 function deregisterFns() {
8717 // remove all existing $attrs.$observe's
8718 for (var x = 0; x < deregisters.length; x++) {
8719 deregisters[x]();
8720 }
8721 deregisters = [];
8722 }
8723
8724
8725 function generateNavBarItem(html) {
8726 if (html) {
8727 // every time a view enters we need to recreate its view buttons if they exist
8728 return $compile(html)($scope.$new());
8729 }
8730 }
8731
8732
8733 function attrTrue(key) {
8734 return !!$scope.$eval($attrs[key]);
8735 }
8736
8737
8738 self.navElement = function(type, html) {
8739 navElementHtml[type] = html;
8740 };
8741
8742}]);
8743
8744/*
8745 * We don't document the ionActionSheet directive, we instead document
8746 * the $ionicActionSheet service
8747 */
8748IonicModule
8749.directive('ionActionSheet', ['$document', function($document) {
8750 return {
8751 restrict: 'E',
8752 scope: true,
8753 replace: true,
8754 link: function($scope, $element) {
8755
8756 var keyUp = function(e) {
8757 if (e.which == 27) {
8758 $scope.cancel();
8759 $scope.$apply();
8760 }
8761 };
8762
8763 var backdropClick = function(e) {
8764 if (e.target == $element[0]) {
8765 $scope.cancel();
8766 $scope.$apply();
8767 }
8768 };
8769 $scope.$on('$destroy', function() {
8770 $element.remove();
8771 $document.unbind('keyup', keyUp);
8772 });
8773
8774 $document.bind('keyup', keyUp);
8775 $element.bind('click', backdropClick);
8776 },
8777 template: '<div class="action-sheet-backdrop">' +
8778 '<div class="action-sheet-wrapper">' +
8779 '<div class="action-sheet" ng-class="{\'action-sheet-has-icons\': $actionSheetHasIcon}">' +
8780 '<div class="action-sheet-group action-sheet-options">' +
8781 '<div class="action-sheet-title" ng-if="titleText" ng-bind-html="titleText"></div>' +
8782 '<button class="button action-sheet-option" ng-click="buttonClicked($index)" ng-class="b.className" ng-repeat="b in buttons" ng-bind-html="b.text"></button>' +
8783 '<button class="button destructive action-sheet-destructive" ng-if="destructiveText" ng-click="destructiveButtonClicked()" ng-bind-html="destructiveText"></button>' +
8784 '</div>' +
8785 '<div class="action-sheet-group action-sheet-cancel" ng-if="cancelText">' +
8786 '<button class="button" ng-click="cancel()" ng-bind-html="cancelText"></button>' +
8787 '</div>' +
8788 '</div>' +
8789 '</div>' +
8790 '</div>'
8791 };
8792}]);
8793
8794
8795/**
8796 * @ngdoc directive
8797 * @name ionCheckbox
8798 * @module ionic
8799 * @restrict E
8800 * @codepen hqcju
8801 * @description
8802 * The checkbox is no different than the HTML checkbox input, except it's styled differently.
8803 *
8804 * The checkbox behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]).
8805 *
8806 * @usage
8807 * ```html
8808 * <ion-checkbox ng-model="isChecked">Checkbox Label</ion-checkbox>
8809 * ```
8810 */
8811
8812IonicModule
8813.directive('ionCheckbox', ['$ionicConfig', function($ionicConfig) {
8814 return {
8815 restrict: 'E',
8816 replace: true,
8817 require: '?ngModel',
8818 transclude: true,
8819 template:
8820 '<label class="item item-checkbox">' +
8821 '<div class="checkbox checkbox-input-hidden disable-pointer-events">' +
8822 '<input type="checkbox">' +
8823 '<i class="checkbox-icon"></i>' +
8824 '</div>' +
8825 '<div class="item-content disable-pointer-events" ng-transclude></div>' +
8826 '</label>',
8827 compile: function(element, attr) {
8828 var input = element.find('input');
8829 forEach({
8830 'name': attr.name,
8831 'ng-value': attr.ngValue,
8832 'ng-model': attr.ngModel,
8833 'ng-checked': attr.ngChecked,
8834 'ng-disabled': attr.ngDisabled,
8835 'ng-true-value': attr.ngTrueValue,
8836 'ng-false-value': attr.ngFalseValue,
8837 'ng-change': attr.ngChange,
8838 'ng-required': attr.ngRequired,
8839 'required': attr.required
8840 }, function(value, name) {
8841 if (isDefined(value)) {
8842 input.attr(name, value);
8843 }
8844 });
8845 var checkboxWrapper = element[0].querySelector('.checkbox');
8846 checkboxWrapper.classList.add('checkbox-' + $ionicConfig.form.checkbox());
8847 }
8848 };
8849}]);
8850
8851
8852/**
8853 * @ngdoc directive
8854 * @restrict A
8855 * @name collectionRepeat
8856 * @module ionic
8857 * @codepen 7ec1ec58f2489ab8f359fa1a0fe89c15
8858 * @description
8859 * `collection-repeat` allows an app to show huge lists of items much more performantly than
8860 * `ng-repeat`.
8861 *
8862 * It renders into the DOM only as many items as are currently visible.
8863 *
8864 * This means that on a phone screen that can fit eight items, only the eight items matching
8865 * the current scroll position will be rendered.
8866 *
8867 * **The Basics**:
8868 *
8869 * - The data given to collection-repeat must be an array.
8870 * - If the `item-height` and `item-width` attributes are not supplied, it will be assumed that
8871 * every item in the list has the same dimensions as the first item.
8872 * - Don't use angular one-time binding (`::`) with collection-repeat. The scope of each item is
8873 * assigned new data and re-digested as you scroll. Bindings need to update, and one-time bindings
8874 * won't.
8875 *
8876 * **Performance Tips**:
8877 *
8878 * - The iOS webview has a performance bottleneck when switching out `<img src>` attributes.
8879 * To increase performance of images on iOS, cache your images in advance and,
8880 * if possible, lower the number of unique images. We're working on [a solution](https://github.com/driftyco/ionic/issues/3194).
8881 *
8882 * @usage
8883 * #### Basic Item List ([codepen](http://codepen.io/ionic/pen/0c2c35a34a8b18ad4d793fef0b081693))
8884 * ```html
8885 * <ion-content>
8886 * <ion-item collection-repeat="item in items">
8887 * {% raw %}{{item}}{% endraw %}
8888 * </ion-item>
8889 * </ion-content>
8890 * ```
8891 *
8892 * #### Grid of Images ([codepen](http://codepen.io/ionic/pen/5515d4efd9d66f780e96787387f41664))
8893 * ```html
8894 * <ion-content>
8895 * <img collection-repeat="photo in photos"
8896 * item-width="33%"
8897 * item-height="200px"
8898 * ng-src="{% raw %}{{photo.url}}{% endraw %}">
8899 * </ion-content>
8900 * ```
8901 *
8902 * #### Horizontal Scroller, Dynamic Item Width ([codepen](http://codepen.io/ionic/pen/67cc56b349124a349acb57a0740e030e))
8903 * ```html
8904 * <ion-content>
8905 * <h2>Available Kittens:</h2>
8906 * <ion-scroll direction="x" class="available-scroller">
8907 * <div class="photo" collection-repeat="photo in main.photos"
8908 * item-height="250" item-width="photo.width + 30">
8909 * <img ng-src="{% raw %}{{photo.src}}{% endraw %}">
8910 * </div>
8911 * </ion-scroll>
8912 * </ion-content>
8913 * ```
8914 *
8915 * @param {expression} collection-repeat The expression indicating how to enumerate a collection,
8916 * of the format `variable in expression` – where variable is the user defined loop variable
8917 * and `expression` is a scope expression giving the collection to enumerate.
8918 * For example: `album in artist.albums` or `album in artist.albums | orderBy:'name'`.
8919 * @param {expression=} item-width The width of the repeated element. The expression must return
8920 * a number (pixels) or a percentage. Defaults to the width of the first item in the list.
8921 * (previously named collection-item-width)
8922 * @param {expression=} item-height The height of the repeated element. The expression must return
8923 * a number (pixels) or a percentage. Defaults to the height of the first item in the list.
8924 * (previously named collection-item-height)
8925 * @param {number=} item-render-buffer The number of items to load before and after the visible
8926 * items in the list. Default 3. Tip: set this higher if you have lots of images to preload, but
8927 * don't set it too high or you'll see performance loss.
8928 * @param {boolean=} force-refresh-images Force images to refresh as you scroll. This fixes a problem
8929 * where, when an element is interchanged as scrolling, its image will still have the old src
8930 * while the new src loads. Setting this to true comes with a small performance loss.
8931 */
8932
8933IonicModule
8934.directive('collectionRepeat', CollectionRepeatDirective)
8935.factory('$ionicCollectionManager', RepeatManagerFactory);
8936
8937var ONE_PX_TRANSPARENT_IMG_SRC = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
8938var WIDTH_HEIGHT_REGEX = /height:.*?px;\s*width:.*?px/;
8939var DEFAULT_RENDER_BUFFER = 3;
8940
8941CollectionRepeatDirective.$inject = ['$ionicCollectionManager', '$parse', '$window', '$$rAF', '$rootScope', '$timeout'];
8942function CollectionRepeatDirective($ionicCollectionManager, $parse, $window, $$rAF, $rootScope, $timeout) {
8943 return {
8944 restrict: 'A',
8945 priority: 1000,
8946 transclude: 'element',
8947 $$tlb: true,
8948 require: '^^$ionicScroll',
8949 link: postLink
8950 };
8951
8952 function postLink(scope, element, attr, scrollCtrl, transclude) {
8953 var scrollView = scrollCtrl.scrollView;
8954 var node = element[0];
8955 var containerNode = angular.element('<div class="collection-repeat-container">')[0];
8956 node.parentNode.replaceChild(containerNode, node);
8957
8958 if (scrollView.options.scrollingX && scrollView.options.scrollingY) {
8959 throw new Error("collection-repeat expected a parent x or y scrollView, not " +
8960 "an xy scrollView.");
8961 }
8962
8963 var repeatExpr = attr.collectionRepeat;
8964 var match = repeatExpr.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
8965 if (!match) {
8966 throw new Error("collection-repeat expected expression in form of '_item_ in " +
8967 "_collection_[ track by _id_]' but got '" + attr.collectionRepeat + "'.");
8968 }
8969 var keyExpr = match[1];
8970 var listExpr = match[2];
8971 var listGetter = $parse(listExpr);
8972 var heightData = {};
8973 var widthData = {};
8974 var computedStyleDimensions = {};
8975 var data = [];
8976 var repeatManager;
8977
8978 // attr.collectionBufferSize is deprecated
8979 var renderBufferExpr = attr.itemRenderBuffer || attr.collectionBufferSize;
8980 var renderBuffer = angular.isDefined(renderBufferExpr) ?
8981 parseInt(renderBufferExpr) :
8982 DEFAULT_RENDER_BUFFER;
8983
8984 // attr.collectionItemHeight is deprecated
8985 var heightExpr = attr.itemHeight || attr.collectionItemHeight;
8986 // attr.collectionItemWidth is deprecated
8987 var widthExpr = attr.itemWidth || attr.collectionItemWidth;
8988
8989 var afterItemsContainer = initAfterItemsContainer();
8990
8991 var changeValidator = makeChangeValidator();
8992 initDimensions();
8993
8994 // Dimensions are refreshed on resize or data change.
8995 scrollCtrl.$element.on('scroll-resize', refreshDimensions);
8996
8997 angular.element($window).on('resize', onResize);
8998 var unlistenToExposeAside = $rootScope.$on('$ionicExposeAside', ionic.animationFrameThrottle(function() {
8999 scrollCtrl.scrollView.resize();
9000 onResize();
9001 }));
9002 $timeout(refreshDimensions, 0, false);
9003
9004 function onResize() {
9005 if (changeValidator.resizeRequiresRefresh(scrollView.__clientWidth, scrollView.__clientHeight)) {
9006 refreshDimensions();
9007 }
9008 }
9009
9010 scope.$watchCollection(listGetter, function(newValue) {
9011 data = newValue || (newValue = []);
9012 if (!angular.isArray(newValue)) {
9013 throw new Error("collection-repeat expected an array for '" + listExpr + "', " +
9014 "but got a " + typeof value);
9015 }
9016 // Wait for this digest to end before refreshing everything.
9017 scope.$$postDigest(function() {
9018 getRepeatManager().setData(data);
9019 if (changeValidator.dataChangeRequiresRefresh(data)) refreshDimensions();
9020 });
9021 });
9022
9023 scope.$on('$destroy', function() {
9024 angular.element($window).off('resize', onResize);
9025 unlistenToExposeAside();
9026 scrollCtrl.$element && scrollCtrl.$element.off('scroll-resize', refreshDimensions);
9027
9028 computedStyleNode && computedStyleNode.parentNode &&
9029 computedStyleNode.parentNode.removeChild(computedStyleNode);
9030 computedStyleScope && computedStyleScope.$destroy();
9031 computedStyleScope = computedStyleNode = null;
9032
9033 repeatManager && repeatManager.destroy();
9034 repeatManager = null;
9035 });
9036
9037 function makeChangeValidator() {
9038 var self;
9039 return (self = {
9040 dataLength: 0,
9041 width: 0,
9042 height: 0,
9043 // A resize triggers a refresh only if we have data, the scrollView has size,
9044 // and the size has changed.
9045 resizeRequiresRefresh: function(newWidth, newHeight) {
9046 var requiresRefresh = self.dataLength && newWidth && newHeight &&
9047 (newWidth !== self.width || newHeight !== self.height);
9048
9049 self.width = newWidth;
9050 self.height = newHeight;
9051
9052 return !!requiresRefresh;
9053 },
9054 // A change in data only triggers a refresh if the data has length, or if the data's
9055 // length is less than before.
9056 dataChangeRequiresRefresh: function(newData) {
9057 var requiresRefresh = newData.length > 0 || newData.length < self.dataLength;
9058
9059 self.dataLength = newData.length;
9060
9061 return !!requiresRefresh;
9062 }
9063 });
9064 }
9065
9066 function getRepeatManager() {
9067 return repeatManager || (repeatManager = new $ionicCollectionManager({
9068 afterItemsNode: afterItemsContainer[0],
9069 containerNode: containerNode,
9070 heightData: heightData,
9071 widthData: widthData,
9072 forceRefreshImages: !!(isDefined(attr.forceRefreshImages) && attr.forceRefreshImages !== 'false'),
9073 keyExpression: keyExpr,
9074 renderBuffer: renderBuffer,
9075 scope: scope,
9076 scrollView: scrollCtrl.scrollView,
9077 transclude: transclude
9078 }));
9079 }
9080
9081 function initAfterItemsContainer() {
9082 var container = angular.element(
9083 scrollView.__content.querySelector('.collection-repeat-after-container')
9084 );
9085 // Put everything in the view after the repeater into a container.
9086 if (!container.length) {
9087 var elementIsAfterRepeater = false;
9088 var afterNodes = [].filter.call(scrollView.__content.childNodes, function(node) {
9089 if (ionic.DomUtil.contains(node, containerNode)) {
9090 elementIsAfterRepeater = true;
9091 return false;
9092 }
9093 return elementIsAfterRepeater;
9094 });
9095 container = angular.element('<span class="collection-repeat-after-container">');
9096 if (scrollView.options.scrollingX) {
9097 container.addClass('horizontal');
9098 }
9099 container.append(afterNodes);
9100 scrollView.__content.appendChild(container[0]);
9101 }
9102 return container;
9103 }
9104
9105 function initDimensions() {
9106 //Height and width have four 'modes':
9107 //1) Computed Mode
9108 // - Nothing is supplied, so we getComputedStyle() on one element in the list and use
9109 // that width and height value for the width and height of every item. This is re-computed
9110 // every resize.
9111 //2) Constant Mode, Static Integer
9112 // - The user provides a constant number for width or height, in pixels. We parse it,
9113 // store it on the `value` field, and it never changes
9114 //3) Constant Mode, Percent
9115 // - The user provides a percent string for width or height. The getter for percent is
9116 // stored on the `getValue()` field, and is re-evaluated once every resize. The result
9117 // is stored on the `value` field.
9118 //4) Dynamic Mode
9119 // - The user provides a dynamic expression for the width or height. This is re-evaluated
9120 // for every item, stored on the `.getValue()` field.
9121 if (heightExpr) {
9122 parseDimensionAttr(heightExpr, heightData);
9123 } else {
9124 heightData.computed = true;
9125 }
9126 if (widthExpr) {
9127 parseDimensionAttr(widthExpr, widthData);
9128 } else {
9129 widthData.computed = true;
9130 }
9131 }
9132
9133 function refreshDimensions() {
9134 var hasData = data.length > 0;
9135
9136 if (hasData && (heightData.computed || widthData.computed)) {
9137 computeStyleDimensions();
9138 }
9139
9140 if (hasData && heightData.computed) {
9141 heightData.value = computedStyleDimensions.height;
9142 if (!heightData.value) {
9143 throw new Error('collection-repeat tried to compute the height of repeated elements "' +
9144 repeatExpr + '", but was unable to. Please provide the "item-height" attribute. ' +
9145 'http://ionicframework.com/docs/api/directive/collectionRepeat/');
9146 }
9147 } else if (!heightData.dynamic && heightData.getValue) {
9148 // If it's a constant with a getter (eg percent), we just refresh .value after resize
9149 heightData.value = heightData.getValue();
9150 }
9151
9152 if (hasData && widthData.computed) {
9153 widthData.value = computedStyleDimensions.width;
9154 if (!widthData.value) {
9155 throw new Error('collection-repeat tried to compute the width of repeated elements "' +
9156 repeatExpr + '", but was unable to. Please provide the "item-width" attribute. ' +
9157 'http://ionicframework.com/docs/api/directive/collectionRepeat/');
9158 }
9159 } else if (!widthData.dynamic && widthData.getValue) {
9160 // If it's a constant with a getter (eg percent), we just refresh .value after resize
9161 widthData.value = widthData.getValue();
9162 }
9163 // Dynamic dimensions aren't updated on resize. Since they're already dynamic anyway,
9164 // .getValue() will be used.
9165
9166 getRepeatManager().refreshLayout();
9167 }
9168
9169 function parseDimensionAttr(attrValue, dimensionData) {
9170 if (!attrValue) return;
9171
9172 var parsedValue;
9173 // Try to just parse the plain attr value
9174 try {
9175 parsedValue = $parse(attrValue);
9176 } catch (e) {
9177 // If the parse fails and the value has `px` or `%` in it, surround the attr in
9178 // quotes, to attempt to let the user provide a simple `attr="100%"` or `attr="100px"`
9179 if (attrValue.trim().match(/\d+(px|%)$/)) {
9180 attrValue = '"' + attrValue + '"';
9181 }
9182 parsedValue = $parse(attrValue);
9183 }
9184
9185 var constantAttrValue = attrValue.replace(/(\'|\"|px|%)/g, '').trim();
9186 var isConstant = constantAttrValue.length && !/([a-zA-Z]|\$|:|\?)/.test(constantAttrValue);
9187 dimensionData.attrValue = attrValue;
9188
9189 // If it's a constant, it's either a percent or just a constant pixel number.
9190 if (isConstant) {
9191 // For percents, store the percent getter on .getValue()
9192 if (attrValue.indexOf('%') > -1) {
9193 var decimalValue = parseFloat(parsedValue()) / 100;
9194 dimensionData.getValue = dimensionData === heightData ?
9195 function() { return Math.floor(decimalValue * scrollView.__clientHeight); } :
9196 function() { return Math.floor(decimalValue * scrollView.__clientWidth); };
9197 } else {
9198 // For static constants, just store the static constant.
9199 dimensionData.value = parseInt(parsedValue());
9200 }
9201
9202 } else {
9203 dimensionData.dynamic = true;
9204 dimensionData.getValue = dimensionData === heightData ?
9205 function heightGetter(scope, locals) {
9206 var result = parsedValue(scope, locals);
9207 if (result.charAt && result.charAt(result.length - 1) === '%') {
9208 return Math.floor(parseFloat(result) / 100 * scrollView.__clientHeight);
9209 }
9210 return parseInt(result);
9211 } :
9212 function widthGetter(scope, locals) {
9213 var result = parsedValue(scope, locals);
9214 if (result.charAt && result.charAt(result.length - 1) === '%') {
9215 return Math.floor(parseFloat(result) / 100 * scrollView.__clientWidth);
9216 }
9217 return parseInt(result);
9218 };
9219 }
9220 }
9221
9222 var computedStyleNode;
9223 var computedStyleScope;
9224 function computeStyleDimensions() {
9225 if (!computedStyleNode) {
9226 transclude(computedStyleScope = scope.$new(), function(clone) {
9227 clone[0].removeAttribute('collection-repeat'); // remove absolute position styling
9228 computedStyleNode = clone[0];
9229 });
9230 }
9231
9232 computedStyleScope[keyExpr] = (listGetter(scope) || [])[0];
9233 if (!$rootScope.$$phase) computedStyleScope.$digest();
9234 containerNode.appendChild(computedStyleNode);
9235
9236 var style = $window.getComputedStyle(computedStyleNode);
9237 computedStyleDimensions.width = parseInt(style.width);
9238 computedStyleDimensions.height = parseInt(style.height);
9239
9240 containerNode.removeChild(computedStyleNode);
9241 }
9242
9243 }
9244
9245}
9246
9247RepeatManagerFactory.$inject = ['$rootScope', '$window', '$$rAF'];
9248function RepeatManagerFactory($rootScope, $window, $$rAF) {
9249 var EMPTY_DIMENSION = { primaryPos: 0, secondaryPos: 0, primarySize: 0, secondarySize: 0, rowPrimarySize: 0 };
9250
9251 return function RepeatController(options) {
9252 var afterItemsNode = options.afterItemsNode;
9253 var containerNode = options.containerNode;
9254 var forceRefreshImages = options.forceRefreshImages;
9255 var heightData = options.heightData;
9256 var widthData = options.widthData;
9257 var keyExpression = options.keyExpression;
9258 var renderBuffer = options.renderBuffer;
9259 var scope = options.scope;
9260 var scrollView = options.scrollView;
9261 var transclude = options.transclude;
9262
9263 var data = [];
9264
9265 var getterLocals = {};
9266 var heightFn = heightData.getValue || function() { return heightData.value; };
9267 var heightGetter = function(index, value) {
9268 getterLocals[keyExpression] = value;
9269 getterLocals.$index = index;
9270 return heightFn(scope, getterLocals);
9271 };
9272
9273 var widthFn = widthData.getValue || function() { return widthData.value; };
9274 var widthGetter = function(index, value) {
9275 getterLocals[keyExpression] = value;
9276 getterLocals.$index = index;
9277 return widthFn(scope, getterLocals);
9278 };
9279
9280 var isVertical = !!scrollView.options.scrollingY;
9281
9282 // We say it's a grid view if we're either dynamic or not 100% width
9283 var isGridView = isVertical ?
9284 (widthData.dynamic || widthData.value !== scrollView.__clientWidth) :
9285 (heightData.dynamic || heightData.value !== scrollView.__clientHeight);
9286
9287 var isStaticView = !heightData.dynamic && !widthData.dynamic;
9288
9289 var PRIMARY = 'PRIMARY';
9290 var SECONDARY = 'SECONDARY';
9291 var TRANSLATE_TEMPLATE_STR = isVertical ?
9292 'translate3d(SECONDARYpx,PRIMARYpx,0)' :
9293 'translate3d(PRIMARYpx,SECONDARYpx,0)';
9294 var WIDTH_HEIGHT_TEMPLATE_STR = isVertical ?
9295 'height: PRIMARYpx; width: SECONDARYpx;' :
9296 'height: SECONDARYpx; width: PRIMARYpx;';
9297
9298 var estimatedHeight;
9299 var estimatedWidth;
9300
9301 var repeaterBeforeSize = 0;
9302 var repeaterAfterSize = 0;
9303
9304 var renderStartIndex = -1;
9305 var renderEndIndex = -1;
9306 var renderAfterBoundary = -1;
9307 var renderBeforeBoundary = -1;
9308
9309 var itemsPool = [];
9310 var itemsLeaving = [];
9311 var itemsEntering = [];
9312 var itemsShownMap = {};
9313 var nextItemId = 0;
9314
9315 var scrollViewSetDimensions = isVertical ?
9316 function() { scrollView.setDimensions(null, null, null, view.getContentSize(), true); } :
9317 function() { scrollView.setDimensions(null, null, view.getContentSize(), null, true); };
9318
9319 // view is a mix of list/grid methods + static/dynamic methods.
9320 // See bottom for implementations. Available methods:
9321 //
9322 // getEstimatedPrimaryPos(i), getEstimatedSecondaryPos(i), getEstimatedIndex(scrollTop),
9323 // calculateDimensions(toIndex), getDimensions(index),
9324 // updateRenderRange(scrollTop, scrollValueEnd), onRefreshLayout(), onRefreshData()
9325 var view = isVertical ? new VerticalViewType() : new HorizontalViewType();
9326 (isGridView ? GridViewType : ListViewType).call(view);
9327 (isStaticView ? StaticViewType : DynamicViewType).call(view);
9328
9329 var contentSizeStr = isVertical ? 'getContentHeight' : 'getContentWidth';
9330 var originalGetContentSize = scrollView.options[contentSizeStr];
9331 scrollView.options[contentSizeStr] = angular.bind(view, view.getContentSize);
9332
9333 scrollView.__$callback = scrollView.__callback;
9334 scrollView.__callback = function(transformLeft, transformTop, zoom, wasResize) {
9335 var scrollValue = view.getScrollValue();
9336 if (renderStartIndex === -1 ||
9337 scrollValue + view.scrollPrimarySize > renderAfterBoundary ||
9338 scrollValue < renderBeforeBoundary) {
9339 render();
9340 }
9341 scrollView.__$callback(transformLeft, transformTop, zoom, wasResize);
9342 };
9343
9344 var isLayoutReady = false;
9345 var isDataReady = false;
9346 this.refreshLayout = function() {
9347 if (data.length) {
9348 estimatedHeight = heightGetter(0, data[0]);
9349 estimatedWidth = widthGetter(0, data[0]);
9350 } else {
9351 // If we don't have any data in our array, just guess.
9352 estimatedHeight = 100;
9353 estimatedWidth = 100;
9354 }
9355
9356 // Get the size of every element AFTER the repeater. We have to get the margin before and
9357 // after the first/last element to fix a browser bug with getComputedStyle() not counting
9358 // the first/last child's margins into height.
9359 var style = getComputedStyle(afterItemsNode) || {};
9360 var firstStyle = afterItemsNode.firstElementChild && getComputedStyle(afterItemsNode.firstElementChild) || {};
9361 var lastStyle = afterItemsNode.lastElementChild && getComputedStyle(afterItemsNode.lastElementChild) || {};
9362 repeaterAfterSize = (parseInt(style[isVertical ? 'height' : 'width']) || 0) +
9363 (firstStyle && parseInt(firstStyle[isVertical ? 'marginTop' : 'marginLeft']) || 0) +
9364 (lastStyle && parseInt(lastStyle[isVertical ? 'marginBottom' : 'marginRight']) || 0);
9365
9366 // Get the offsetTop of the repeater.
9367 repeaterBeforeSize = 0;
9368 var current = containerNode;
9369 do {
9370 repeaterBeforeSize += current[isVertical ? 'offsetTop' : 'offsetLeft'];
9371 } while ( ionic.DomUtil.contains(scrollView.__content, current = current.offsetParent) );
9372
9373 var containerPrevNode = containerNode.previousElementSibling;
9374 var beforeStyle = containerPrevNode ? $window.getComputedStyle(containerPrevNode) : {};
9375 var beforeMargin = parseInt(beforeStyle[isVertical ? 'marginBottom' : 'marginRight'] || 0);
9376
9377 // Because we position the collection container with position: relative, it doesn't take
9378 // into account where to position itself relative to the previous element's marginBottom.
9379 // To compensate, we translate the container up by the previous element's margin.
9380 containerNode.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
9381 .replace(PRIMARY, -beforeMargin)
9382 .replace(SECONDARY, 0);
9383 repeaterBeforeSize -= beforeMargin;
9384
9385 if (!scrollView.__clientHeight || !scrollView.__clientWidth) {
9386 scrollView.__clientWidth = scrollView.__container.clientWidth;
9387 scrollView.__clientHeight = scrollView.__container.clientHeight;
9388 }
9389
9390 (view.onRefreshLayout || angular.noop)();
9391 view.refreshDirection();
9392 scrollViewSetDimensions();
9393
9394 // Create the pool of items for reuse, setting the size to (estimatedItemsOnScreen) * 2,
9395 // plus the size of the renderBuffer.
9396 if (!isLayoutReady) {
9397 var poolSize = Math.max(20, renderBuffer * 3);
9398 for (var i = 0; i < poolSize; i++) {
9399 itemsPool.push(new RepeatItem());
9400 }
9401 }
9402
9403 isLayoutReady = true;
9404 if (isLayoutReady && isDataReady) {
9405 // If the resize or latest data change caused the scrollValue to
9406 // now be out of bounds, resize the scrollView.
9407 if (scrollView.__scrollLeft > scrollView.__maxScrollLeft ||
9408 scrollView.__scrollTop > scrollView.__maxScrollTop) {
9409 scrollView.resize();
9410 }
9411 forceRerender(true);
9412 }
9413 };
9414
9415 this.setData = function(newData) {
9416 data = newData;
9417 (view.onRefreshData || angular.noop)();
9418 isDataReady = true;
9419 };
9420
9421 this.destroy = function() {
9422 render.destroyed = true;
9423
9424 itemsPool.forEach(function(item) {
9425 item.scope.$destroy();
9426 item.scope = item.element = item.node = item.images = null;
9427 });
9428 itemsPool.length = itemsEntering.length = itemsLeaving.length = 0;
9429 itemsShownMap = {};
9430
9431 //Restore the scrollView's normal behavior and resize it to normal size.
9432 scrollView.options[contentSizeStr] = originalGetContentSize;
9433 scrollView.__callback = scrollView.__$callback;
9434 scrollView.resize();
9435
9436 (view.onDestroy || angular.noop)();
9437 };
9438
9439 function forceRerender() {
9440 return render(true);
9441 }
9442 function render(forceRerender) {
9443 if (render.destroyed) return;
9444 var i;
9445 var ii;
9446 var item;
9447 var dim;
9448 var scope;
9449 var scrollValue = view.getScrollValue();
9450 var scrollValueEnd = scrollValue + view.scrollPrimarySize;
9451
9452 view.updateRenderRange(scrollValue, scrollValueEnd);
9453
9454 renderStartIndex = Math.max(0, renderStartIndex - renderBuffer);
9455 renderEndIndex = Math.min(data.length - 1, renderEndIndex + renderBuffer);
9456
9457 for (i in itemsShownMap) {
9458 if (i < renderStartIndex || i > renderEndIndex) {
9459 item = itemsShownMap[i];
9460 delete itemsShownMap[i];
9461 itemsLeaving.push(item);
9462 item.isShown = false;
9463 }
9464 }
9465
9466 // Render indicies that aren't shown yet
9467 //
9468 // NOTE(ajoslin): this may sound crazy, but calling any other functions during this render
9469 // loop will often push the render time over the edge from less than one frame to over
9470 // one frame, causing visible jank.
9471 // DON'T call any other functions inside this loop unless it's vital.
9472 for (i = renderStartIndex; i <= renderEndIndex; i++) {
9473 // We only go forward with render if the index is in data, the item isn't already shown,
9474 // or forceRerender is on.
9475 if (i >= data.length || (itemsShownMap[i] && !forceRerender)) continue;
9476
9477 item = itemsShownMap[i] || (itemsShownMap[i] = itemsLeaving.length ? itemsLeaving.pop() :
9478 itemsPool.length ? itemsPool.shift() :
9479 new RepeatItem());
9480 itemsEntering.push(item);
9481 item.isShown = true;
9482
9483 scope = item.scope;
9484 scope.$index = i;
9485 scope[keyExpression] = data[i];
9486 scope.$first = (i === 0);
9487 scope.$last = (i === (data.length - 1));
9488 scope.$middle = !(scope.$first || scope.$last);
9489 scope.$odd = !(scope.$even = (i & 1) === 0);
9490
9491 if (scope.$$disconnected) ionic.Utils.reconnectScope(item.scope);
9492
9493 dim = view.getDimensions(i);
9494 if (item.secondaryPos !== dim.secondaryPos || item.primaryPos !== dim.primaryPos) {
9495 item.node.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
9496 .replace(PRIMARY, (item.primaryPos = dim.primaryPos))
9497 .replace(SECONDARY, (item.secondaryPos = dim.secondaryPos));
9498 }
9499 if (item.secondarySize !== dim.secondarySize || item.primarySize !== dim.primarySize) {
9500 item.node.style.cssText = item.node.style.cssText
9501 .replace(WIDTH_HEIGHT_REGEX, WIDTH_HEIGHT_TEMPLATE_STR
9502 //TODO fix item.primarySize + 1 hack
9503 .replace(PRIMARY, (item.primarySize = dim.primarySize) + 1)
9504 .replace(SECONDARY, (item.secondarySize = dim.secondarySize))
9505 );
9506 }
9507
9508 }
9509
9510 // If we reach the end of the list, render the afterItemsNode - this contains all the
9511 // elements the developer placed after the collection-repeat
9512 if (renderEndIndex === data.length - 1) {
9513 dim = view.getDimensions(data.length - 1) || EMPTY_DIMENSION;
9514 afterItemsNode.style[ionic.CSS.TRANSFORM] = TRANSLATE_TEMPLATE_STR
9515 .replace(PRIMARY, dim.primaryPos + dim.primarySize)
9516 .replace(SECONDARY, 0);
9517 }
9518
9519 while (itemsLeaving.length) {
9520 item = itemsLeaving.pop();
9521 item.scope.$broadcast('$collectionRepeatLeave');
9522 ionic.Utils.disconnectScope(item.scope);
9523 itemsPool.push(item);
9524 item.node.style[ionic.CSS.TRANSFORM] = 'translate3d(-9999px,-9999px,0)';
9525 item.primaryPos = item.secondaryPos = null;
9526 }
9527
9528 if (forceRefreshImages) {
9529 for (i = 0, ii = itemsEntering.length; i < ii && (item = itemsEntering[i]); i++) {
9530 if (!item.images) continue;
9531 for (var j = 0, jj = item.images.length, img; j < jj && (img = item.images[j]); j++) {
9532 var src = img.src;
9533 img.src = ONE_PX_TRANSPARENT_IMG_SRC;
9534 img.src = src;
9535 }
9536 }
9537 }
9538 if (forceRerender) {
9539 var rootScopePhase = $rootScope.$$phase;
9540 while (itemsEntering.length) {
9541 item = itemsEntering.pop();
9542 if (!rootScopePhase) item.scope.$digest();
9543 }
9544 } else {
9545 digestEnteringItems();
9546 }
9547 }
9548
9549 function digestEnteringItems() {
9550 var item;
9551 if (digestEnteringItems.running) return;
9552 digestEnteringItems.running = true;
9553
9554 $$rAF(function process() {
9555 var rootScopePhase = $rootScope.$$phase;
9556 while (itemsEntering.length) {
9557 item = itemsEntering.pop();
9558 if (item.isShown) {
9559 if (!rootScopePhase) item.scope.$digest();
9560 }
9561 }
9562 digestEnteringItems.running = false;
9563 });
9564 }
9565
9566 function RepeatItem() {
9567 var self = this;
9568 this.scope = scope.$new();
9569 this.id = 'item' + (nextItemId++);
9570 transclude(this.scope, function(clone) {
9571 self.element = clone;
9572 self.element.data('$$collectionRepeatItem', self);
9573 // TODO destroy
9574 self.node = clone[0];
9575 // Batch style setting to lower repaints
9576 self.node.style[ionic.CSS.TRANSFORM] = 'translate3d(-9999px,-9999px,0)';
9577 self.node.style.cssText += ' height: 0px; width: 0px;';
9578 ionic.Utils.disconnectScope(self.scope);
9579 containerNode.appendChild(self.node);
9580 self.images = clone[0].getElementsByTagName('img');
9581 });
9582 }
9583
9584 function VerticalViewType() {
9585 this.getItemPrimarySize = heightGetter;
9586 this.getItemSecondarySize = widthGetter;
9587
9588 this.getScrollValue = function() {
9589 return Math.max(0, Math.min(scrollView.__scrollTop - repeaterBeforeSize,
9590 scrollView.__maxScrollTop - repeaterBeforeSize - repeaterAfterSize));
9591 };
9592
9593 this.refreshDirection = function() {
9594 this.scrollPrimarySize = scrollView.__clientHeight;
9595 this.scrollSecondarySize = scrollView.__clientWidth;
9596
9597 this.estimatedPrimarySize = estimatedHeight;
9598 this.estimatedSecondarySize = estimatedWidth;
9599 this.estimatedItemsAcross = isGridView &&
9600 Math.floor(scrollView.__clientWidth / estimatedWidth) ||
9601 1;
9602 };
9603 }
9604 function HorizontalViewType() {
9605 this.getItemPrimarySize = widthGetter;
9606 this.getItemSecondarySize = heightGetter;
9607
9608 this.getScrollValue = function() {
9609 return Math.max(0, Math.min(scrollView.__scrollLeft - repeaterBeforeSize,
9610 scrollView.__maxScrollLeft - repeaterBeforeSize - repeaterAfterSize));
9611 };
9612
9613 this.refreshDirection = function() {
9614 this.scrollPrimarySize = scrollView.__clientWidth;
9615 this.scrollSecondarySize = scrollView.__clientHeight;
9616
9617 this.estimatedPrimarySize = estimatedWidth;
9618 this.estimatedSecondarySize = estimatedHeight;
9619 this.estimatedItemsAcross = isGridView &&
9620 Math.floor(scrollView.__clientHeight / estimatedHeight) ||
9621 1;
9622 };
9623 }
9624
9625 function GridViewType() {
9626 this.getEstimatedSecondaryPos = function(index) {
9627 return (index % this.estimatedItemsAcross) * this.estimatedSecondarySize;
9628 };
9629 this.getEstimatedPrimaryPos = function(index) {
9630 return Math.floor(index / this.estimatedItemsAcross) * this.estimatedPrimarySize;
9631 };
9632 this.getEstimatedIndex = function(scrollValue) {
9633 return Math.floor(scrollValue / this.estimatedPrimarySize) *
9634 this.estimatedItemsAcross;
9635 };
9636 }
9637
9638 function ListViewType() {
9639 this.getEstimatedSecondaryPos = function() {
9640 return 0;
9641 };
9642 this.getEstimatedPrimaryPos = function(index) {
9643 return index * this.estimatedPrimarySize;
9644 };
9645 this.getEstimatedIndex = function(scrollValue) {
9646 return Math.floor((scrollValue) / this.estimatedPrimarySize);
9647 };
9648 }
9649
9650 function StaticViewType() {
9651 this.getContentSize = function() {
9652 return this.getEstimatedPrimaryPos(data.length - 1) + this.estimatedPrimarySize +
9653 repeaterBeforeSize + repeaterAfterSize;
9654 };
9655 // static view always returns the same object for getDimensions, to avoid memory allocation
9656 // while scrolling. This could be dangerous if this was a public function, but it's not.
9657 // Only we use it.
9658 var dim = {};
9659 this.getDimensions = function(index) {
9660 dim.primaryPos = this.getEstimatedPrimaryPos(index);
9661 dim.secondaryPos = this.getEstimatedSecondaryPos(index);
9662 dim.primarySize = this.estimatedPrimarySize;
9663 dim.secondarySize = this.estimatedSecondarySize;
9664 return dim;
9665 };
9666 this.updateRenderRange = function(scrollValue, scrollValueEnd) {
9667 renderStartIndex = Math.max(0, this.getEstimatedIndex(scrollValue));
9668
9669 // Make sure the renderEndIndex takes into account all the items on the row
9670 renderEndIndex = Math.min(data.length - 1,
9671 this.getEstimatedIndex(scrollValueEnd) + this.estimatedItemsAcross - 1);
9672
9673 renderBeforeBoundary = Math.max(0,
9674 this.getEstimatedPrimaryPos(renderStartIndex));
9675 renderAfterBoundary = this.getEstimatedPrimaryPos(renderEndIndex) +
9676 this.estimatedPrimarySize;
9677 };
9678 }
9679
9680 function DynamicViewType() {
9681 var self = this;
9682 var debouncedScrollViewSetDimensions = ionic.debounce(scrollViewSetDimensions, 25, true);
9683 var calculateDimensions = isGridView ? calculateDimensionsGrid : calculateDimensionsList;
9684 var dimensionsIndex;
9685 var dimensions = [];
9686
9687
9688 // Get the dimensions at index. {width, height, left, top}.
9689 // We start with no dimensions calculated, then any time dimensions are asked for at an
9690 // index we calculate dimensions up to there.
9691 function calculateDimensionsList(toIndex) {
9692 var i, prevDimension, dim;
9693 for (i = Math.max(0, dimensionsIndex); i <= toIndex && (dim = dimensions[i]); i++) {
9694 prevDimension = dimensions[i - 1] || EMPTY_DIMENSION;
9695 dim.primarySize = self.getItemPrimarySize(i, data[i]);
9696 dim.secondarySize = self.scrollSecondarySize;
9697 dim.primaryPos = prevDimension.primaryPos + prevDimension.primarySize;
9698 dim.secondaryPos = 0;
9699 }
9700 }
9701 function calculateDimensionsGrid(toIndex) {
9702 var i, prevDimension, dim;
9703 for (i = Math.max(dimensionsIndex, 0); i <= toIndex && (dim = dimensions[i]); i++) {
9704 prevDimension = dimensions[i - 1] || EMPTY_DIMENSION;
9705 dim.secondarySize = Math.min(
9706 self.getItemSecondarySize(i, data[i]),
9707 self.scrollSecondarySize
9708 );
9709 dim.secondaryPos = prevDimension.secondaryPos + prevDimension.secondarySize;
9710
9711 if (i === 0 || dim.secondaryPos + dim.secondarySize > self.scrollSecondarySize) {
9712 dim.secondaryPos = 0;
9713 dim.primarySize = self.getItemPrimarySize(i, data[i]);
9714 dim.primaryPos = prevDimension.primaryPos + prevDimension.rowPrimarySize;
9715
9716 dim.rowStartIndex = i;
9717 dim.rowPrimarySize = dim.primarySize;
9718 } else {
9719 dim.primarySize = self.getItemPrimarySize(i, data[i]);
9720 dim.primaryPos = prevDimension.primaryPos;
9721 dim.rowStartIndex = prevDimension.rowStartIndex;
9722
9723 dimensions[dim.rowStartIndex].rowPrimarySize = dim.rowPrimarySize = Math.max(
9724 dimensions[dim.rowStartIndex].rowPrimarySize,
9725 dim.primarySize
9726 );
9727 dim.rowPrimarySize = Math.max(dim.primarySize, dim.rowPrimarySize);
9728 }
9729 }
9730 }
9731
9732 this.getContentSize = function() {
9733 var dim = dimensions[dimensionsIndex] || EMPTY_DIMENSION;
9734 return ((dim.primaryPos + dim.primarySize) || 0) +
9735 this.getEstimatedPrimaryPos(data.length - dimensionsIndex - 1) +
9736 repeaterBeforeSize + repeaterAfterSize;
9737 };
9738 this.onDestroy = function() {
9739 dimensions.length = 0;
9740 };
9741
9742 this.onRefreshData = function() {
9743 var i;
9744 var ii;
9745 // Make sure dimensions has as many items as data.length.
9746 // This is to be sure we don't have to allocate objects while scrolling.
9747 for (i = dimensions.length, ii = data.length; i < ii; i++) {
9748 dimensions.push({});
9749 }
9750 dimensionsIndex = -1;
9751 };
9752 this.onRefreshLayout = function() {
9753 dimensionsIndex = -1;
9754 };
9755 this.getDimensions = function(index) {
9756 index = Math.min(index, data.length - 1);
9757
9758 if (dimensionsIndex < index) {
9759 // Once we start asking for dimensions near the end of the list, go ahead and calculate
9760 // everything. This is to make sure when the user gets to the end of the list, the
9761 // scroll height of the list is 100% accurate (not estimated anymore).
9762 if (index > data.length * 0.9) {
9763 calculateDimensions(data.length - 1);
9764 dimensionsIndex = data.length - 1;
9765 scrollViewSetDimensions();
9766 } else {
9767 calculateDimensions(index);
9768 dimensionsIndex = index;
9769 debouncedScrollViewSetDimensions();
9770 }
9771
9772 }
9773 return dimensions[index];
9774 };
9775
9776 var oldRenderStartIndex = -1;
9777 var oldScrollValue = -1;
9778 this.updateRenderRange = function(scrollValue, scrollValueEnd) {
9779 var i;
9780 var len;
9781 var dim;
9782
9783 // Calculate more dimensions than we estimate we'll need, to be sure.
9784 this.getDimensions( this.getEstimatedIndex(scrollValueEnd) * 2 );
9785
9786 // -- Calculate renderStartIndex
9787 // base case: start at 0
9788 if (oldRenderStartIndex === -1 || scrollValue === 0) {
9789 i = 0;
9790 // scrolling down
9791 } else if (scrollValue >= oldScrollValue) {
9792 for (i = oldRenderStartIndex, len = data.length; i < len; i++) {
9793 if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.rowPrimarySize >= scrollValue) {
9794 break;
9795 }
9796 }
9797 // scrolling up
9798 } else {
9799 for (i = oldRenderStartIndex; i >= 0; i--) {
9800 if ((dim = this.getDimensions(i)) && dim.primaryPos <= scrollValue) {
9801 // when grid view, make sure the render starts at the beginning of a row.
9802 i = isGridView ? dim.rowStartIndex : i;
9803 break;
9804 }
9805 }
9806 }
9807
9808 renderStartIndex = Math.min(Math.max(0, i), data.length - 1);
9809 renderBeforeBoundary = renderStartIndex !== -1 ? this.getDimensions(renderStartIndex).primaryPos : -1;
9810
9811 // -- Calculate renderEndIndex
9812 var lastRowDim;
9813 for (i = renderStartIndex + 1, len = data.length; i < len; i++) {
9814 if ((dim = this.getDimensions(i)) && dim.primaryPos + dim.rowPrimarySize > scrollValueEnd) {
9815
9816 // Go all the way to the end of the row if we're in a grid
9817 if (isGridView) {
9818 lastRowDim = dim;
9819 while (i < len - 1 &&
9820 (dim = this.getDimensions(i + 1)).primaryPos === lastRowDim.primaryPos) {
9821 i++;
9822 }
9823 }
9824 break;
9825 }
9826 }
9827
9828 renderEndIndex = Math.min(i, data.length - 1);
9829 renderAfterBoundary = renderEndIndex !== -1 ?
9830 ((dim = this.getDimensions(renderEndIndex)).primaryPos + (dim.rowPrimarySize || dim.primarySize)) :
9831 -1;
9832
9833 oldScrollValue = scrollValue;
9834 oldRenderStartIndex = renderStartIndex;
9835 };
9836 }
9837
9838
9839 };
9840
9841}
9842
9843/**
9844 * @ngdoc directive
9845 * @name ionContent
9846 * @module ionic
9847 * @delegate ionic.service:$ionicScrollDelegate
9848 * @restrict E
9849 *
9850 * @description
9851 * The ionContent directive provides an easy to use content area that can be configured
9852 * to use Ionic's custom Scroll View, or the built in overflow scrolling of the browser.
9853 *
9854 * While we recommend using the custom Scroll features in Ionic in most cases, sometimes
9855 * (for performance reasons) only the browser's native overflow scrolling will suffice,
9856 * and so we've made it easy to toggle between the Ionic scroll implementation and
9857 * overflow scrolling.
9858 *
9859 * You can implement pull-to-refresh with the {@link ionic.directive:ionRefresher}
9860 * directive, and infinite scrolling with the {@link ionic.directive:ionInfiniteScroll}
9861 * directive.
9862 *
9863 * If there is any dynamic content inside the ion-content, be sure to call `.resize()` with {@link ionic.service:$ionicScrollDelegate}
9864 * after the content has been added.
9865 *
9866 * Be aware that this directive gets its own child scope. If you do not understand why this
9867 * is important, you can read [https://docs.angularjs.org/guide/scope](https://docs.angularjs.org/guide/scope).
9868 *
9869 * @param {string=} delegate-handle The handle used to identify this scrollView
9870 * with {@link ionic.service:$ionicScrollDelegate}.
9871 * @param {string=} direction Which way to scroll. 'x' or 'y' or 'xy'. Default 'y'.
9872 * @param {boolean=} locking Whether to lock scrolling in one direction at a time. Useful to set to false when zoomed in or scrolling in two directions. Default true.
9873 * @param {boolean=} padding Whether to add padding to the content.
9874 * Defaults to true on iOS, false on Android.
9875 * @param {boolean=} scroll Whether to allow scrolling of content. Defaults to true.
9876 * @param {boolean=} overflow-scroll Whether to use overflow-scrolling instead of
9877 * Ionic scroll. See {@link ionic.provider:$ionicConfigProvider} to set this as the global default.
9878 * @param {boolean=} scrollbar-x Whether to show the horizontal scrollbar. Default true.
9879 * @param {boolean=} scrollbar-y Whether to show the vertical scrollbar. Default true.
9880 * @param {string=} start-x Initial horizontal scroll position. Default 0.
9881 * @param {string=} start-y Initial vertical scroll position. Default 0.
9882 * @param {expression=} on-scroll Expression to evaluate when the content is scrolled.
9883 * @param {expression=} on-scroll-complete Expression to evaluate when a scroll action completes. Has access to 'scrollLeft' and 'scrollTop' locals.
9884 * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges
9885 * of the content. Defaults to true on iOS, false on Android.
9886 * @param {number=} scroll-event-interval Number of milliseconds between each firing of the 'on-scroll' expression. Default 10.
9887 */
9888IonicModule
9889.directive('ionContent', [
9890 '$timeout',
9891 '$controller',
9892 '$ionicBind',
9893 '$ionicConfig',
9894function($timeout, $controller, $ionicBind, $ionicConfig) {
9895 return {
9896 restrict: 'E',
9897 require: '^?ionNavView',
9898 scope: true,
9899 priority: 800,
9900 compile: function(element, attr) {
9901 var innerElement;
9902 var scrollCtrl;
9903
9904 element.addClass('scroll-content ionic-scroll');
9905
9906 if (attr.scroll != 'false') {
9907 //We cannot use normal transclude here because it breaks element.data()
9908 //inheritance on compile
9909 innerElement = jqLite('<div class="scroll"></div>');
9910 innerElement.append(element.contents());
9911 element.append(innerElement);
9912 } else {
9913 element.addClass('scroll-content-false');
9914 }
9915
9916 var nativeScrolling = attr.overflowScroll !== "false" && (attr.overflowScroll === "true" || !$ionicConfig.scrolling.jsScrolling());
9917
9918 // collection-repeat requires JS scrolling
9919 if (nativeScrolling) {
9920 nativeScrolling = !element[0].querySelector('[collection-repeat]');
9921 }
9922 return { pre: prelink };
9923 function prelink($scope, $element, $attr) {
9924 var parentScope = $scope.$parent;
9925 $scope.$watch(function() {
9926 return (parentScope.$hasHeader ? ' has-header' : '') +
9927 (parentScope.$hasSubheader ? ' has-subheader' : '') +
9928 (parentScope.$hasFooter ? ' has-footer' : '') +
9929 (parentScope.$hasSubfooter ? ' has-subfooter' : '') +
9930 (parentScope.$hasTabs ? ' has-tabs' : '') +
9931 (parentScope.$hasTabsTop ? ' has-tabs-top' : '');
9932 }, function(className, oldClassName) {
9933 $element.removeClass(oldClassName);
9934 $element.addClass(className);
9935 });
9936
9937 //Only this ionContent should use these variables from parent scopes
9938 $scope.$hasHeader = $scope.$hasSubheader =
9939 $scope.$hasFooter = $scope.$hasSubfooter =
9940 $scope.$hasTabs = $scope.$hasTabsTop =
9941 false;
9942 $ionicBind($scope, $attr, {
9943 $onScroll: '&onScroll',
9944 $onScrollComplete: '&onScrollComplete',
9945 hasBouncing: '@',
9946 padding: '@',
9947 direction: '@',
9948 scrollbarX: '@',
9949 scrollbarY: '@',
9950 startX: '@',
9951 startY: '@',
9952 scrollEventInterval: '@'
9953 });
9954 $scope.direction = $scope.direction || 'y';
9955
9956 if (isDefined($attr.padding)) {
9957 $scope.$watch($attr.padding, function(newVal) {
9958 (innerElement || $element).toggleClass('padding', !!newVal);
9959 });
9960 }
9961
9962 if ($attr.scroll === "false") {
9963 //do nothing
9964 } else {
9965 var scrollViewOptions = {};
9966
9967 // determined in compile phase above
9968 if (nativeScrolling) {
9969 // use native scrolling
9970 $element.addClass('overflow-scroll');
9971
9972 scrollViewOptions = {
9973 el: $element[0],
9974 delegateHandle: attr.delegateHandle,
9975 startX: $scope.$eval($scope.startX) || 0,
9976 startY: $scope.$eval($scope.startY) || 0,
9977 nativeScrolling: true
9978 };
9979
9980 } else {
9981 // Use JS scrolling
9982 scrollViewOptions = {
9983 el: $element[0],
9984 delegateHandle: attr.delegateHandle,
9985 locking: (attr.locking || 'true') === 'true',
9986 bouncing: $scope.$eval($scope.hasBouncing),
9987 startX: $scope.$eval($scope.startX) || 0,
9988 startY: $scope.$eval($scope.startY) || 0,
9989 scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
9990 scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
9991 scrollingX: $scope.direction.indexOf('x') >= 0,
9992 scrollingY: $scope.direction.indexOf('y') >= 0,
9993 scrollEventInterval: parseInt($scope.scrollEventInterval, 10) || 10,
9994 scrollingComplete: onScrollComplete
9995 };
9996 }
9997
9998 // init scroll controller with appropriate options
9999 scrollCtrl = $controller('$ionicScroll', {
10000 $scope: $scope,
10001 scrollViewOptions: scrollViewOptions
10002 });
10003
10004 $scope.scrollCtrl = scrollCtrl;
10005
10006 $scope.$on('$destroy', function() {
10007 if (scrollViewOptions) {
10008 scrollViewOptions.scrollingComplete = noop;
10009 delete scrollViewOptions.el;
10010 }
10011 innerElement = null;
10012 $element = null;
10013 attr.$$element = null;
10014 });
10015 }
10016
10017 function onScrollComplete() {
10018 $scope.$onScrollComplete({
10019 scrollTop: scrollCtrl.scrollView.__scrollTop,
10020 scrollLeft: scrollCtrl.scrollView.__scrollLeft
10021 });
10022 }
10023
10024 }
10025 }
10026 };
10027}]);
10028
10029/**
10030 * @ngdoc directive
10031 * @name exposeAsideWhen
10032 * @module ionic
10033 * @restrict A
10034 * @parent ionic.directive:ionSideMenus
10035 *
10036 * @description
10037 * It is common for a tablet application to hide a menu when in portrait mode, but to show the
10038 * same menu on the left side when the tablet is in landscape mode. The `exposeAsideWhen` attribute
10039 * directive can be used to accomplish a similar interface.
10040 *
10041 * By default, side menus are hidden underneath its side menu content, and can be opened by either
10042 * swiping the content left or right, or toggling a button to show the side menu. However, by adding the
10043 * `exposeAsideWhen` attribute directive to an {@link ionic.directive:ionSideMenu} element directive,
10044 * a side menu can be given instructions on "when" the menu should be exposed (always viewable). For
10045 * example, the `expose-aside-when="large"` attribute will keep the side menu hidden when the viewport's
10046 * width is less than `768px`, but when the viewport's width is `768px` or greater, the menu will then
10047 * always be shown and can no longer be opened or closed like it could when it was hidden for smaller
10048 * viewports.
10049 *
10050 * Using `large` as the attribute's value is a shortcut value to `(min-width:768px)` since it is
10051 * the most common use-case. However, for added flexibility, any valid media query could be added
10052 * as the value, such as `(min-width:600px)` or even multiple queries such as
10053 * `(min-width:750px) and (max-width:1200px)`.
10054 * @usage
10055 * ```html
10056 * <ion-side-menus>
10057 * <!-- Center content -->
10058 * <ion-side-menu-content>
10059 * </ion-side-menu-content>
10060 *
10061 * <!-- Left menu -->
10062 * <ion-side-menu expose-aside-when="large">
10063 * </ion-side-menu>
10064 * </ion-side-menus>
10065 * ```
10066 * For a complete side menu example, see the
10067 * {@link ionic.directive:ionSideMenus} documentation.
10068 */
10069
10070IonicModule.directive('exposeAsideWhen', ['$window', function($window) {
10071 return {
10072 restrict: 'A',
10073 require: '^ionSideMenus',
10074 link: function($scope, $element, $attr, sideMenuCtrl) {
10075
10076 var prevInnerWidth = $window.innerWidth;
10077 var prevInnerHeight = $window.innerHeight;
10078
10079 ionic.on('resize', function() {
10080 if (prevInnerWidth === $window.innerWidth && prevInnerHeight === $window.innerHeight) {
10081 return;
10082 }
10083 prevInnerWidth = $window.innerWidth;
10084 prevInnerHeight = $window.innerHeight;
10085 onResize();
10086 }, $window);
10087
10088 function checkAsideExpose() {
10089 var mq = $attr.exposeAsideWhen == 'large' ? '(min-width:768px)' : $attr.exposeAsideWhen;
10090 sideMenuCtrl.exposeAside($window.matchMedia(mq).matches);
10091 sideMenuCtrl.activeAsideResizing(false);
10092 }
10093
10094 function onResize() {
10095 sideMenuCtrl.activeAsideResizing(true);
10096 debouncedCheck();
10097 }
10098
10099 var debouncedCheck = ionic.debounce(function() {
10100 $scope.$apply(checkAsideExpose);
10101 }, 300, false);
10102
10103 $scope.$evalAsync(checkAsideExpose);
10104 }
10105 };
10106}]);
10107
10108var GESTURE_DIRECTIVES = 'onHold onTap onDoubleTap onTouch onRelease onDragStart onDrag onDragEnd onDragUp onDragRight onDragDown onDragLeft onSwipe onSwipeUp onSwipeRight onSwipeDown onSwipeLeft'.split(' ');
10109
10110GESTURE_DIRECTIVES.forEach(function(name) {
10111 IonicModule.directive(name, gestureDirective(name));
10112});
10113
10114
10115/**
10116 * @ngdoc directive
10117 * @name onHold
10118 * @module ionic
10119 * @restrict A
10120 *
10121 * @description
10122 * Touch stays at the same location for 500ms. Similar to long touch events available for AngularJS and jQuery.
10123 *
10124 * @usage
10125 * ```html
10126 * <button on-hold="onHold()" class="button">Test</button>
10127 * ```
10128 */
10129
10130
10131/**
10132 * @ngdoc directive
10133 * @name onTap
10134 * @module ionic
10135 * @restrict A
10136 *
10137 * @description
10138 * Quick touch at a location. If the duration of the touch goes
10139 * longer than 250ms it is no longer a tap gesture.
10140 *
10141 * @usage
10142 * ```html
10143 * <button on-tap="onTap()" class="button">Test</button>
10144 * ```
10145 */
10146
10147
10148/**
10149 * @ngdoc directive
10150 * @name onDoubleTap
10151 * @module ionic
10152 * @restrict A
10153 *
10154 * @description
10155 * Double tap touch at a location.
10156 *
10157 * @usage
10158 * ```html
10159 * <button on-double-tap="onDoubleTap()" class="button">Test</button>
10160 * ```
10161 */
10162
10163
10164/**
10165 * @ngdoc directive
10166 * @name onTouch
10167 * @module ionic
10168 * @restrict A
10169 *
10170 * @description
10171 * Called immediately when the user first begins a touch. This
10172 * gesture does not wait for a touchend/mouseup.
10173 *
10174 * @usage
10175 * ```html
10176 * <button on-touch="onTouch()" class="button">Test</button>
10177 * ```
10178 */
10179
10180
10181/**
10182 * @ngdoc directive
10183 * @name onRelease
10184 * @module ionic
10185 * @restrict A
10186 *
10187 * @description
10188 * Called when the user ends a touch.
10189 *
10190 * @usage
10191 * ```html
10192 * <button on-release="onRelease()" class="button">Test</button>
10193 * ```
10194 */
10195
10196/**
10197 * @ngdoc directive
10198 * @name onDragStart
10199 * @module ionic
10200 * @restrict A
10201 *
10202 * @description
10203 * Called when a drag gesture has started.
10204 *
10205 * @usage
10206 * ```html
10207 * <button on-drag-start="onDragStart()" class="button">Test</button>
10208 * ```
10209 */
10210
10211
10212/**
10213 * @ngdoc directive
10214 * @name onDrag
10215 * @module ionic
10216 * @restrict A
10217 *
10218 * @description
10219 * Move with one touch around on the page. Blocking the scrolling when
10220 * moving left and right is a good practice. When all the drag events are
10221 * blocking you disable scrolling on that area.
10222 *
10223 * @usage
10224 * ```html
10225 * <button on-drag="onDrag()" class="button">Test</button>
10226 * ```
10227 */
10228
10229/**
10230 * @ngdoc directive
10231 * @name onDragEnd
10232 * @module ionic
10233 * @restrict A
10234 *
10235 * @description
10236 * Called when a drag gesture has ended.
10237 *
10238 * @usage
10239 * ```html
10240 * <button on-drag-end="onDragEnd()" class="button">Test</button>
10241 * ```
10242 */
10243
10244/**
10245 * @ngdoc directive
10246 * @name onDragUp
10247 * @module ionic
10248 * @restrict A
10249 *
10250 * @description
10251 * Called when the element is dragged up.
10252 *
10253 * @usage
10254 * ```html
10255 * <button on-drag-up="onDragUp()" class="button">Test</button>
10256 * ```
10257 */
10258
10259
10260/**
10261 * @ngdoc directive
10262 * @name onDragRight
10263 * @module ionic
10264 * @restrict A
10265 *
10266 * @description
10267 * Called when the element is dragged to the right.
10268 *
10269 * @usage
10270 * ```html
10271 * <button on-drag-right="onDragRight()" class="button">Test</button>
10272 * ```
10273 */
10274
10275
10276/**
10277 * @ngdoc directive
10278 * @name onDragDown
10279 * @module ionic
10280 * @restrict A
10281 *
10282 * @description
10283 * Called when the element is dragged down.
10284 *
10285 * @usage
10286 * ```html
10287 * <button on-drag-down="onDragDown()" class="button">Test</button>
10288 * ```
10289 */
10290
10291
10292/**
10293 * @ngdoc directive
10294 * @name onDragLeft
10295 * @module ionic
10296 * @restrict A
10297 *
10298 * @description
10299 * Called when the element is dragged to the left.
10300 *
10301 * @usage
10302 * ```html
10303 * <button on-drag-left="onDragLeft()" class="button">Test</button>
10304 * ```
10305 */
10306
10307
10308/**
10309 * @ngdoc directive
10310 * @name onSwipe
10311 * @module ionic
10312 * @restrict A
10313 *
10314 * @description
10315 * Called when a moving touch has a high velocity in any direction.
10316 *
10317 * @usage
10318 * ```html
10319 * <button on-swipe="onSwipe()" class="button">Test</button>
10320 * ```
10321 */
10322
10323
10324/**
10325 * @ngdoc directive
10326 * @name onSwipeUp
10327 * @module ionic
10328 * @restrict A
10329 *
10330 * @description
10331 * Called when a moving touch has a high velocity moving up.
10332 *
10333 * @usage
10334 * ```html
10335 * <button on-swipe-up="onSwipeUp()" class="button">Test</button>
10336 * ```
10337 */
10338
10339
10340/**
10341 * @ngdoc directive
10342 * @name onSwipeRight
10343 * @module ionic
10344 * @restrict A
10345 *
10346 * @description
10347 * Called when a moving touch has a high velocity moving to the right.
10348 *
10349 * @usage
10350 * ```html
10351 * <button on-swipe-right="onSwipeRight()" class="button">Test</button>
10352 * ```
10353 */
10354
10355
10356/**
10357 * @ngdoc directive
10358 * @name onSwipeDown
10359 * @module ionic
10360 * @restrict A
10361 *
10362 * @description
10363 * Called when a moving touch has a high velocity moving down.
10364 *
10365 * @usage
10366 * ```html
10367 * <button on-swipe-down="onSwipeDown()" class="button">Test</button>
10368 * ```
10369 */
10370
10371
10372/**
10373 * @ngdoc directive
10374 * @name onSwipeLeft
10375 * @module ionic
10376 * @restrict A
10377 *
10378 * @description
10379 * Called when a moving touch has a high velocity moving to the left.
10380 *
10381 * @usage
10382 * ```html
10383 * <button on-swipe-left="onSwipeLeft()" class="button">Test</button>
10384 * ```
10385 */
10386
10387
10388function gestureDirective(directiveName) {
10389 return ['$ionicGesture', '$parse', function($ionicGesture, $parse) {
10390 var eventType = directiveName.substr(2).toLowerCase();
10391
10392 return function(scope, element, attr) {
10393 var fn = $parse( attr[directiveName] );
10394
10395 var listener = function(ev) {
10396 scope.$apply(function() {
10397 fn(scope, {
10398 $event: ev
10399 });
10400 });
10401 };
10402
10403 var gesture = $ionicGesture.on(eventType, listener, element);
10404
10405 scope.$on('$destroy', function() {
10406 $ionicGesture.off(gesture, eventType, listener);
10407 });
10408 };
10409 }];
10410}
10411
10412
10413IonicModule
10414//.directive('ionHeaderBar', tapScrollToTopDirective())
10415
10416/**
10417 * @ngdoc directive
10418 * @name ionHeaderBar
10419 * @module ionic
10420 * @restrict E
10421 *
10422 * @description
10423 * Adds a fixed header bar above some content.
10424 *
10425 * Can also be a subheader (lower down) if the 'bar-subheader' class is applied.
10426 * See [the header CSS docs](/docs/components/#subheader).
10427 *
10428 * @param {string=} align-title How to align the title. By default the title
10429 * will be aligned the same as how the platform aligns its titles (iOS centers
10430 * titles, Android aligns them left).
10431 * Available: 'left', 'right', or 'center'. Defaults to the same as the platform.
10432 * @param {boolean=} no-tap-scroll By default, the header bar will scroll the
10433 * content to the top when tapped. Set no-tap-scroll to true to disable this
10434 * behavior.
10435 * Available: true or false. Defaults to false.
10436 *
10437 * @usage
10438 * ```html
10439 * <ion-header-bar align-title="left" class="bar-positive">
10440 * <div class="buttons">
10441 * <button class="button" ng-click="doSomething()">Left Button</button>
10442 * </div>
10443 * <h1 class="title">Title!</h1>
10444 * <div class="buttons">
10445 * <button class="button">Right Button</button>
10446 * </div>
10447 * </ion-header-bar>
10448 * <ion-content class="has-header">
10449 * Some content!
10450 * </ion-content>
10451 * ```
10452 */
10453.directive('ionHeaderBar', headerFooterBarDirective(true))
10454
10455/**
10456 * @ngdoc directive
10457 * @name ionFooterBar
10458 * @module ionic
10459 * @restrict E
10460 *
10461 * @description
10462 * Adds a fixed footer bar below some content.
10463 *
10464 * Can also be a subfooter (higher up) if the 'bar-subfooter' class is applied.
10465 * See [the footer CSS docs](/docs/components/#footer).
10466 *
10467 * Note: If you use ionFooterBar in combination with ng-if, the surrounding content
10468 * will not align correctly. This will be fixed soon.
10469 *
10470 * @param {string=} align-title Where to align the title.
10471 * Available: 'left', 'right', or 'center'. Defaults to 'center'.
10472 *
10473 * @usage
10474 * ```html
10475 * <ion-content class="has-footer">
10476 * Some content!
10477 * </ion-content>
10478 * <ion-footer-bar align-title="left" class="bar-assertive">
10479 * <div class="buttons">
10480 * <button class="button">Left Button</button>
10481 * </div>
10482 * <h1 class="title">Title!</h1>
10483 * <div class="buttons" ng-click="doSomething()">
10484 * <button class="button">Right Button</button>
10485 * </div>
10486 * </ion-footer-bar>
10487 * ```
10488 */
10489.directive('ionFooterBar', headerFooterBarDirective(false));
10490
10491function tapScrollToTopDirective() { //eslint-disable-line no-unused-vars
10492 return ['$ionicScrollDelegate', function($ionicScrollDelegate) {
10493 return {
10494 restrict: 'E',
10495 link: function($scope, $element, $attr) {
10496 if ($attr.noTapScroll == 'true') {
10497 return;
10498 }
10499 ionic.on('tap', onTap, $element[0]);
10500 $scope.$on('$destroy', function() {
10501 ionic.off('tap', onTap, $element[0]);
10502 });
10503
10504 function onTap(e) {
10505 var depth = 3;
10506 var current = e.target;
10507 //Don't scroll to top in certain cases
10508 while (depth-- && current) {
10509 if (current.classList.contains('button') ||
10510 current.tagName.match(/input|textarea|select/i) ||
10511 current.isContentEditable) {
10512 return;
10513 }
10514 current = current.parentNode;
10515 }
10516 var touch = e.gesture && e.gesture.touches[0] || e.detail.touches[0];
10517 var bounds = $element[0].getBoundingClientRect();
10518 if (ionic.DomUtil.rectContains(
10519 touch.pageX, touch.pageY,
10520 bounds.left, bounds.top - 20,
10521 bounds.left + bounds.width, bounds.top + bounds.height
10522 )) {
10523 $ionicScrollDelegate.scrollTop(true);
10524 }
10525 }
10526 }
10527 };
10528 }];
10529}
10530
10531function headerFooterBarDirective(isHeader) {
10532 return ['$document', '$timeout', function($document, $timeout) {
10533 return {
10534 restrict: 'E',
10535 controller: '$ionicHeaderBar',
10536 compile: function(tElement) {
10537 tElement.addClass(isHeader ? 'bar bar-header' : 'bar bar-footer');
10538 // top style tabs? if so, remove bottom border for seamless display
10539 $timeout(function() {
10540 if (isHeader && $document[0].getElementsByClassName('tabs-top').length) tElement.addClass('has-tabs-top');
10541 });
10542
10543 return { pre: prelink };
10544 function prelink($scope, $element, $attr, ctrl) {
10545 if (isHeader) {
10546 $scope.$watch(function() { return $element[0].className; }, function(value) {
10547 var isShown = value.indexOf('ng-hide') === -1;
10548 var isSubheader = value.indexOf('bar-subheader') !== -1;
10549 $scope.$hasHeader = isShown && !isSubheader;
10550 $scope.$hasSubheader = isShown && isSubheader;
10551 $scope.$emit('$ionicSubheader', $scope.$hasSubheader);
10552 });
10553 $scope.$on('$destroy', function() {
10554 delete $scope.$hasHeader;
10555 delete $scope.$hasSubheader;
10556 });
10557 ctrl.align();
10558 $scope.$on('$ionicHeader.align', function() {
10559 ionic.requestAnimationFrame(function() {
10560 ctrl.align();
10561 });
10562 });
10563
10564 } else {
10565 $scope.$watch(function() { return $element[0].className; }, function(value) {
10566 var isShown = value.indexOf('ng-hide') === -1;
10567 var isSubfooter = value.indexOf('bar-subfooter') !== -1;
10568 $scope.$hasFooter = isShown && !isSubfooter;
10569 $scope.$hasSubfooter = isShown && isSubfooter;
10570 });
10571 $scope.$on('$destroy', function() {
10572 delete $scope.$hasFooter;
10573 delete $scope.$hasSubfooter;
10574 });
10575 $scope.$watch('$hasTabs', function(val) {
10576 $element.toggleClass('has-tabs', !!val);
10577 });
10578 ctrl.align();
10579 $scope.$on('$ionicFooter.align', function() {
10580 ionic.requestAnimationFrame(function() {
10581 ctrl.align();
10582 });
10583 });
10584 }
10585 }
10586 }
10587 };
10588 }];
10589}
10590
10591/**
10592 * @ngdoc directive
10593 * @name ionInfiniteScroll
10594 * @module ionic
10595 * @parent ionic.directive:ionContent, ionic.directive:ionScroll
10596 * @restrict E
10597 *
10598 * @description
10599 * The ionInfiniteScroll directive allows you to call a function whenever
10600 * the user gets to the bottom of the page or near the bottom of the page.
10601 *
10602 * The expression you pass in for `on-infinite` is called when the user scrolls
10603 * greater than `distance` away from the bottom of the content. Once `on-infinite`
10604 * is done loading new data, it should broadcast the `scroll.infiniteScrollComplete`
10605 * event from your controller (see below example).
10606 *
10607 * @param {expression} on-infinite What to call when the scroller reaches the
10608 * bottom.
10609 * @param {string=} distance The distance from the bottom that the scroll must
10610 * reach to trigger the on-infinite expression. Default: 1%.
10611 * @param {string=} spinner The {@link ionic.directive:ionSpinner} to show while loading. The SVG
10612 * {@link ionic.directive:ionSpinner} is now the default, replacing rotating font icons.
10613 * @param {string=} icon The icon to show while loading. Default: 'ion-load-d'. This is depreicated
10614 * in favor of the SVG {@link ionic.directive:ionSpinner}.
10615 * @param {boolean=} immediate-check Whether to check the infinite scroll bounds immediately on load.
10616 *
10617 * @usage
10618 * ```html
10619 * <ion-content ng-controller="MyController">
10620 * <ion-list>
10621 * ....
10622 * ....
10623 * </ion-list>
10624 *
10625 * <ion-infinite-scroll
10626 * on-infinite="loadMore()"
10627 * distance="1%">
10628 * </ion-infinite-scroll>
10629 * </ion-content>
10630 * ```
10631 * ```js
10632 * function MyController($scope, $http) {
10633 * $scope.items = [];
10634 * $scope.loadMore = function() {
10635 * $http.get('/more-items').success(function(items) {
10636 * useItems(items);
10637 * $scope.$broadcast('scroll.infiniteScrollComplete');
10638 * });
10639 * };
10640 *
10641 * $scope.$on('$stateChangeSuccess', function() {
10642 * $scope.loadMore();
10643 * });
10644 * }
10645 * ```
10646 *
10647 * An easy to way to stop infinite scroll once there is no more data to load
10648 * is to use angular's `ng-if` directive:
10649 *
10650 * ```html
10651 * <ion-infinite-scroll
10652 * ng-if="moreDataCanBeLoaded()"
10653 * icon="ion-loading-c"
10654 * on-infinite="loadMoreData()">
10655 * </ion-infinite-scroll>
10656 * ```
10657 */
10658IonicModule
10659.directive('ionInfiniteScroll', ['$timeout', function($timeout) {
10660 return {
10661 restrict: 'E',
10662 require: ['?^$ionicScroll', 'ionInfiniteScroll'],
10663 template: function($element, $attrs) {
10664 if ($attrs.icon) return '<i class="icon {{icon()}} icon-refreshing {{scrollingType}}"></i>';
10665 return '<ion-spinner icon="{{spinner()}}"></ion-spinner>';
10666 },
10667 scope: true,
10668 controller: '$ionInfiniteScroll',
10669 link: function($scope, $element, $attrs, ctrls) {
10670 var infiniteScrollCtrl = ctrls[1];
10671 var scrollCtrl = infiniteScrollCtrl.scrollCtrl = ctrls[0];
10672 var jsScrolling = infiniteScrollCtrl.jsScrolling = !scrollCtrl.isNative();
10673
10674 // if this view is not beneath a scrollCtrl, it can't be injected, proceed w/ native scrolling
10675 if (jsScrolling) {
10676 infiniteScrollCtrl.scrollView = scrollCtrl.scrollView;
10677 $scope.scrollingType = 'js-scrolling';
10678 //bind to JS scroll events
10679 scrollCtrl.$element.on('scroll', infiniteScrollCtrl.checkBounds);
10680 } else {
10681 // grabbing the scrollable element, to determine dimensions, and current scroll pos
10682 var scrollEl = ionic.DomUtil.getParentOrSelfWithClass($element[0].parentNode, 'overflow-scroll');
10683 infiniteScrollCtrl.scrollEl = scrollEl;
10684 // if there's no scroll controller, and no overflow scroll div, infinite scroll wont work
10685 if (!scrollEl) {
10686 throw 'Infinite scroll must be used inside a scrollable div';
10687 }
10688 //bind to native scroll events
10689 infiniteScrollCtrl.scrollEl.addEventListener('scroll', infiniteScrollCtrl.checkBounds);
10690 }
10691
10692 // Optionally check bounds on start after scrollView is fully rendered
10693 var doImmediateCheck = isDefined($attrs.immediateCheck) ? $scope.$eval($attrs.immediateCheck) : true;
10694 if (doImmediateCheck) {
10695 $timeout(function() { infiniteScrollCtrl.checkBounds(); });
10696 }
10697 }
10698 };
10699}]);
10700
10701/**
10702* @ngdoc directive
10703* @name ionInput
10704* @parent ionic.directive:ionList
10705* @module ionic
10706* @restrict E
10707* Creates a text input group that can easily be focused
10708*
10709* @usage
10710*
10711* ```html
10712* <ion-list>
10713* <ion-input>
10714* <input type="text" placeholder="First Name">
10715* </ion-input>
10716*
10717* <ion-input>
10718* <ion-label>Username</ion-label>
10719* <input type="text">
10720* </ion-input>
10721* </ion-list>
10722* ```
10723*/
10724
10725var labelIds = -1;
10726
10727IonicModule
10728.directive('ionInput', [function() {
10729 return {
10730 restrict: 'E',
10731 controller: ['$scope', '$element', function($scope, $element) {
10732 this.$scope = $scope;
10733 this.$element = $element;
10734
10735 this.setInputAriaLabeledBy = function(id) {
10736 var inputs = $element[0].querySelectorAll('input,textarea');
10737 inputs.length && inputs[0].setAttribute('aria-labelledby', id);
10738 };
10739
10740 this.focus = function() {
10741 var inputs = $element[0].querySelectorAll('input,textarea');
10742 inputs.length && inputs[0].focus();
10743 };
10744 }]
10745 };
10746}]);
10747
10748/**
10749* @ngdoc directive
10750* @name ionLabel
10751* @parent ionic.directive:ionList
10752* @module ionic
10753* @restrict E
10754*
10755* New in Ionic 1.2. It is strongly recommended that you use `<ion-label>` in place
10756* of any `<label>` elements for maximum cross-browser support and performance.
10757*
10758* Creates a label for a form input.
10759*
10760* @usage
10761*
10762* ```html
10763* <ion-list>
10764* <ion-input>
10765* <ion-label>Username</ion-label>
10766* <input type="text">
10767* </ion-input>
10768* </ion-list>
10769* ```
10770*/
10771IonicModule
10772.directive('ionLabel', [function() {
10773 return {
10774 restrict: 'E',
10775 require: '?^ionInput',
10776 compile: function() {
10777
10778 return function link($scope, $element, $attrs, ionInputCtrl) {
10779 var element = $element[0];
10780
10781 $element.addClass('input-label');
10782
10783 $element.attr('aria-label', $element.text());
10784 var id = element.id || '_label-' + ++labelIds;
10785
10786 if (!element.id) {
10787 $element.attr('id', id);
10788 }
10789
10790 if (ionInputCtrl) {
10791
10792 ionInputCtrl.setInputAriaLabeledBy(id);
10793
10794 $element.on('click', function() {
10795 ionInputCtrl.focus();
10796 });
10797 }
10798 };
10799 }
10800 };
10801}]);
10802
10803/**
10804 * Input label adds accessibility to <span class="input-label">.
10805 */
10806IonicModule
10807.directive('inputLabel', [function() {
10808 return {
10809 restrict: 'C',
10810 require: '?^ionInput',
10811 compile: function() {
10812
10813 return function link($scope, $element, $attrs, ionInputCtrl) {
10814 var element = $element[0];
10815
10816 $element.attr('aria-label', $element.text());
10817 var id = element.id || '_label-' + ++labelIds;
10818
10819 if (!element.id) {
10820 $element.attr('id', id);
10821 }
10822
10823 if (ionInputCtrl) {
10824 ionInputCtrl.setInputAriaLabeledBy(id);
10825 }
10826
10827 };
10828 }
10829 };
10830}]);
10831
10832/**
10833* @ngdoc directive
10834* @name ionItem
10835* @parent ionic.directive:ionList
10836* @module ionic
10837* @restrict E
10838* Creates a list-item that can easily be swiped,
10839* deleted, reordered, edited, and more.
10840*
10841* See {@link ionic.directive:ionList} for a complete example & explanation.
10842*
10843* Can be assigned any item class name. See the
10844* [list CSS documentation](/docs/components/#list).
10845*
10846* @usage
10847*
10848* ```html
10849* <ion-list>
10850* <ion-item>Hello!</ion-item>
10851* <ion-item href="#/detail">
10852* Link to detail page
10853* </ion-item>
10854* </ion-list>
10855* ```
10856*/
10857IonicModule
10858.directive('ionItem', ['$$rAF', function($$rAF) {
10859 return {
10860 restrict: 'E',
10861 controller: ['$scope', '$element', function($scope, $element) {
10862 this.$scope = $scope;
10863 this.$element = $element;
10864 }],
10865 scope: true,
10866 compile: function($element, $attrs) {
10867 var isAnchor = isDefined($attrs.href) ||
10868 isDefined($attrs.ngHref) ||
10869 isDefined($attrs.uiSref);
10870 var isComplexItem = isAnchor ||
10871 //Lame way of testing, but we have to know at compile what to do with the element
10872 /ion-(delete|option|reorder)-button/i.test($element.html());
10873
10874 if (isComplexItem) {
10875 var innerElement = jqLite(isAnchor ? '<a></a>' : '<div></div>');
10876 innerElement.addClass('item-content');
10877
10878 if (isDefined($attrs.href) || isDefined($attrs.ngHref)) {
10879 innerElement.attr('ng-href', '{{$href()}}');
10880 if (isDefined($attrs.target)) {
10881 innerElement.attr('target', '{{$target()}}');
10882 }
10883 }
10884
10885 innerElement.append($element.contents());
10886
10887 $element.addClass('item item-complex')
10888 .append(innerElement);
10889 } else {
10890 $element.addClass('item');
10891 }
10892
10893 return function link($scope, $element, $attrs) {
10894 $scope.$href = function() {
10895 return $attrs.href || $attrs.ngHref;
10896 };
10897 $scope.$target = function() {
10898 return $attrs.target;
10899 };
10900
10901 var content = $element[0].querySelector('.item-content');
10902 if (content) {
10903 $scope.$on('$collectionRepeatLeave', function() {
10904 if (content && content.$$ionicOptionsOpen) {
10905 content.style[ionic.CSS.TRANSFORM] = '';
10906 content.style[ionic.CSS.TRANSITION] = 'none';
10907 $$rAF(function() {
10908 content.style[ionic.CSS.TRANSITION] = '';
10909 });
10910 content.$$ionicOptionsOpen = false;
10911 }
10912 });
10913 }
10914 };
10915
10916 }
10917 };
10918}]);
10919
10920var ITEM_TPL_DELETE_BUTTON =
10921 '<div class="item-left-edit item-delete enable-pointer-events">' +
10922 '</div>';
10923/**
10924* @ngdoc directive
10925* @name ionDeleteButton
10926* @parent ionic.directive:ionItem
10927* @module ionic
10928* @restrict E
10929* Creates a delete button inside a list item, that is visible when the
10930* {@link ionic.directive:ionList ionList parent's} `show-delete` evaluates to true or
10931* `$ionicListDelegate.showDelete(true)` is called.
10932*
10933* Takes any ionicon as a class.
10934*
10935* See {@link ionic.directive:ionList} for a complete example & explanation.
10936*
10937* @usage
10938*
10939* ```html
10940* <ion-list show-delete="shouldShowDelete">
10941* <ion-item>
10942* <ion-delete-button class="ion-minus-circled"></ion-delete-button>
10943* Hello, list item!
10944* </ion-item>
10945* </ion-list>
10946* <ion-toggle ng-model="shouldShowDelete">
10947* Show Delete?
10948* </ion-toggle>
10949* ```
10950*/
10951IonicModule
10952.directive('ionDeleteButton', function() {
10953
10954 function stopPropagation(ev) {
10955 ev.stopPropagation();
10956 }
10957
10958 return {
10959 restrict: 'E',
10960 require: ['^^ionItem', '^?ionList'],
10961 //Run before anything else, so we can move it before other directives process
10962 //its location (eg ngIf relies on the location of the directive in the dom)
10963 priority: Number.MAX_VALUE,
10964 compile: function($element, $attr) {
10965 //Add the classes we need during the compile phase, so that they stay
10966 //even if something else like ngIf removes the element and re-addss it
10967 $attr.$set('class', ($attr['class'] || '') + ' button icon button-icon', true);
10968 return function($scope, $element, $attr, ctrls) {
10969 var itemCtrl = ctrls[0];
10970 var listCtrl = ctrls[1];
10971 var container = jqLite(ITEM_TPL_DELETE_BUTTON);
10972 container.append($element);
10973 itemCtrl.$element.append(container).addClass('item-left-editable');
10974
10975 //Don't bubble click up to main .item
10976 $element.on('click', stopPropagation);
10977
10978 init();
10979 $scope.$on('$ionic.reconnectScope', init);
10980 function init() {
10981 listCtrl = listCtrl || $element.controller('ionList');
10982 if (listCtrl && listCtrl.showDelete()) {
10983 container.addClass('visible active');
10984 }
10985 }
10986 };
10987 }
10988 };
10989});
10990
10991
10992IonicModule
10993.directive('itemFloatingLabel', function() {
10994 return {
10995 restrict: 'C',
10996 link: function(scope, element) {
10997 var el = element[0];
10998 var input = el.querySelector('input, textarea');
10999 var inputLabel = el.querySelector('.input-label');
11000
11001 if (!input || !inputLabel) return;
11002
11003 var onInput = function() {
11004 if (input.value) {
11005 inputLabel.classList.add('has-input');
11006 } else {
11007 inputLabel.classList.remove('has-input');
11008 }
11009 };
11010
11011 input.addEventListener('input', onInput);
11012
11013 var ngModelCtrl = jqLite(input).controller('ngModel');
11014 if (ngModelCtrl) {
11015 ngModelCtrl.$render = function() {
11016 input.value = ngModelCtrl.$viewValue || '';
11017 onInput();
11018 };
11019 }
11020
11021 scope.$on('$destroy', function() {
11022 input.removeEventListener('input', onInput);
11023 });
11024 }
11025 };
11026});
11027
11028var ITEM_TPL_OPTION_BUTTONS =
11029 '<div class="item-options invisible">' +
11030 '</div>';
11031/**
11032* @ngdoc directive
11033* @name ionOptionButton
11034* @parent ionic.directive:ionItem
11035* @module ionic
11036* @restrict E
11037* @description
11038* Creates an option button inside a list item, that is visible when the item is swiped
11039* to the left by the user. Swiped open option buttons can be hidden with
11040* {@link ionic.service:$ionicListDelegate#closeOptionButtons $ionicListDelegate.closeOptionButtons}.
11041*
11042* Can be assigned any button class.
11043*
11044* See {@link ionic.directive:ionList} for a complete example & explanation.
11045*
11046* @usage
11047*
11048* ```html
11049* <ion-list>
11050* <ion-item>
11051* I love kittens!
11052* <ion-option-button class="button-positive">Share</ion-option-button>
11053* <ion-option-button class="button-assertive">Edit</ion-option-button>
11054* </ion-item>
11055* </ion-list>
11056* ```
11057*/
11058IonicModule.directive('ionOptionButton', [function() {
11059 function stopPropagation(e) {
11060 e.stopPropagation();
11061 }
11062 return {
11063 restrict: 'E',
11064 require: '^ionItem',
11065 priority: Number.MAX_VALUE,
11066 compile: function($element, $attr) {
11067 $attr.$set('class', ($attr['class'] || '') + ' button', true);
11068 return function($scope, $element, $attr, itemCtrl) {
11069 if (!itemCtrl.optionsContainer) {
11070 itemCtrl.optionsContainer = jqLite(ITEM_TPL_OPTION_BUTTONS);
11071 itemCtrl.$element.append(itemCtrl.optionsContainer);
11072 }
11073 itemCtrl.optionsContainer.append($element);
11074
11075 itemCtrl.$element.addClass('item-right-editable');
11076
11077 //Don't bubble click up to main .item
11078 $element.on('click', stopPropagation);
11079 };
11080 }
11081 };
11082}]);
11083
11084var ITEM_TPL_REORDER_BUTTON =
11085 '<div data-prevent-scroll="true" class="item-right-edit item-reorder enable-pointer-events">' +
11086 '</div>';
11087
11088/**
11089* @ngdoc directive
11090* @name ionReorderButton
11091* @parent ionic.directive:ionItem
11092* @module ionic
11093* @restrict E
11094* Creates a reorder button inside a list item, that is visible when the
11095* {@link ionic.directive:ionList ionList parent's} `show-reorder` evaluates to true or
11096* `$ionicListDelegate.showReorder(true)` is called.
11097*
11098* Can be dragged to reorder items in the list. Takes any ionicon class.
11099*
11100* Note: Reordering works best when used with `ng-repeat`. Be sure that all `ion-item` children of an `ion-list` are part of the same `ng-repeat` expression.
11101*
11102* When an item reorder is complete, the expression given in the `on-reorder` attribute is called. The `on-reorder` expression is given two locals that can be used: `$fromIndex` and `$toIndex`. See below for an example.
11103*
11104* Look at {@link ionic.directive:ionList} for more examples.
11105*
11106* @usage
11107*
11108* ```html
11109* <ion-list ng-controller="MyCtrl" show-reorder="true">
11110* <ion-item ng-repeat="item in items">
11111* Item {{item}}
11112* <ion-reorder-button class="ion-navicon"
11113* on-reorder="moveItem(item, $fromIndex, $toIndex)">
11114* </ion-reorder-button>
11115* </ion-item>
11116* </ion-list>
11117* ```
11118* ```js
11119* function MyCtrl($scope) {
11120* $scope.items = [1, 2, 3, 4];
11121* $scope.moveItem = function(item, fromIndex, toIndex) {
11122* //Move the item in the array
11123* $scope.items.splice(fromIndex, 1);
11124* $scope.items.splice(toIndex, 0, item);
11125* };
11126* }
11127* ```
11128*
11129* @param {expression=} on-reorder Expression to call when an item is reordered.
11130* Parameters given: $fromIndex, $toIndex.
11131*/
11132IonicModule
11133.directive('ionReorderButton', ['$parse', function($parse) {
11134 return {
11135 restrict: 'E',
11136 require: ['^ionItem', '^?ionList'],
11137 priority: Number.MAX_VALUE,
11138 compile: function($element, $attr) {
11139 $attr.$set('class', ($attr['class'] || '') + ' button icon button-icon', true);
11140 $element[0].setAttribute('data-prevent-scroll', true);
11141 return function($scope, $element, $attr, ctrls) {
11142 var itemCtrl = ctrls[0];
11143 var listCtrl = ctrls[1];
11144 var onReorderFn = $parse($attr.onReorder);
11145
11146 $scope.$onReorder = function(oldIndex, newIndex) {
11147 onReorderFn($scope, {
11148 $fromIndex: oldIndex,
11149 $toIndex: newIndex
11150 });
11151 };
11152
11153 // prevent clicks from bubbling up to the item
11154 if (!$attr.ngClick && !$attr.onClick && !$attr.onclick) {
11155 $element[0].onclick = function(e) {
11156 e.stopPropagation();
11157 return false;
11158 };
11159 }
11160
11161 var container = jqLite(ITEM_TPL_REORDER_BUTTON);
11162 container.append($element);
11163 itemCtrl.$element.append(container).addClass('item-right-editable');
11164
11165 if (listCtrl && listCtrl.showReorder()) {
11166 container.addClass('visible active');
11167 }
11168 };
11169 }
11170 };
11171}]);
11172
11173/**
11174 * @ngdoc directive
11175 * @name keyboardAttach
11176 * @module ionic
11177 * @restrict A
11178 *
11179 * @description
11180 * keyboard-attach is an attribute directive which will cause an element to float above
11181 * the keyboard when the keyboard shows. Currently only supports the
11182 * [ion-footer-bar]({{ page.versionHref }}/api/directive/ionFooterBar/) directive.
11183 *
11184 * ### Notes
11185 * - This directive requires the
11186 * [Ionic Keyboard Plugin](https://github.com/driftyco/ionic-plugins-keyboard).
11187 * - On Android not in fullscreen mode, i.e. you have
11188 * `<preference name="Fullscreen" value="false" />` or no preference in your `config.xml` file,
11189 * this directive is unnecessary since it is the default behavior.
11190 * - On iOS, if there is an input in your footer, you will need to set
11191 * `cordova.plugins.Keyboard.disableScroll(true)`.
11192 *
11193 * @usage
11194 *
11195 * ```html
11196 * <ion-footer-bar align-title="left" keyboard-attach class="bar-assertive">
11197 * <h1 class="title">Title!</h1>
11198 * </ion-footer-bar>
11199 * ```
11200 */
11201
11202IonicModule
11203.directive('keyboardAttach', function() {
11204 return function(scope, element) {
11205 ionic.on('native.keyboardshow', onShow, window);
11206 ionic.on('native.keyboardhide', onHide, window);
11207
11208 //deprecated
11209 ionic.on('native.showkeyboard', onShow, window);
11210 ionic.on('native.hidekeyboard', onHide, window);
11211
11212
11213 var scrollCtrl;
11214
11215 function onShow(e) {
11216 if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {
11217 return;
11218 }
11219
11220 //for testing
11221 var keyboardHeight = e.keyboardHeight || (e.detail && e.detail.keyboardHeight);
11222 element.css('bottom', keyboardHeight + "px");
11223 scrollCtrl = element.controller('$ionicScroll');
11224 if (scrollCtrl) {
11225 scrollCtrl.scrollView.__container.style.bottom = keyboardHeight + keyboardAttachGetClientHeight(element[0]) + "px";
11226 }
11227 }
11228
11229 function onHide() {
11230 if (ionic.Platform.isAndroid() && !ionic.Platform.isFullScreen) {
11231 return;
11232 }
11233
11234 element.css('bottom', '');
11235 if (scrollCtrl) {
11236 scrollCtrl.scrollView.__container.style.bottom = '';
11237 }
11238 }
11239
11240 scope.$on('$destroy', function() {
11241 ionic.off('native.keyboardshow', onShow, window);
11242 ionic.off('native.keyboardhide', onHide, window);
11243
11244 //deprecated
11245 ionic.off('native.showkeyboard', onShow, window);
11246 ionic.off('native.hidekeyboard', onHide, window);
11247 });
11248 };
11249});
11250
11251function keyboardAttachGetClientHeight(element) {
11252 return element.clientHeight;
11253}
11254
11255/**
11256* @ngdoc directive
11257* @name ionList
11258* @module ionic
11259* @delegate ionic.service:$ionicListDelegate
11260* @codepen JsHjf
11261* @restrict E
11262* @description
11263* The List is a widely used interface element in almost any mobile app, and can include
11264* content ranging from basic text all the way to buttons, toggles, icons, and thumbnails.
11265*
11266* Both the list, which contains items, and the list items themselves can be any HTML
11267* element. The containing element requires the `list` class and each list item requires
11268* the `item` class.
11269*
11270* However, using the ionList and ionItem directives make it easy to support various
11271* interaction modes such as swipe to edit, drag to reorder, and removing items.
11272*
11273* Related: {@link ionic.directive:ionItem}, {@link ionic.directive:ionOptionButton}
11274* {@link ionic.directive:ionReorderButton}, {@link ionic.directive:ionDeleteButton}, [`list CSS documentation`](/docs/components/#list).
11275*
11276* @usage
11277*
11278* Basic Usage:
11279*
11280* ```html
11281* <ion-list>
11282* <ion-item ng-repeat="item in items">
11283* {% raw %}Hello, {{item}}!{% endraw %}
11284* </ion-item>
11285* </ion-list>
11286* ```
11287*
11288* Advanced Usage: Thumbnails, Delete buttons, Reordering, Swiping
11289*
11290* ```html
11291* <ion-list ng-controller="MyCtrl"
11292* show-delete="shouldShowDelete"
11293* show-reorder="shouldShowReorder"
11294* can-swipe="listCanSwipe">
11295* <ion-item ng-repeat="item in items"
11296* class="item-thumbnail-left">
11297*
11298* {% raw %}<img ng-src="{{item.img}}">
11299* <h2>{{item.title}}</h2>
11300* <p>{{item.description}}</p>{% endraw %}
11301* <ion-option-button class="button-positive"
11302* ng-click="share(item)">
11303* Share
11304* </ion-option-button>
11305* <ion-option-button class="button-info"
11306* ng-click="edit(item)">
11307* Edit
11308* </ion-option-button>
11309* <ion-delete-button class="ion-minus-circled"
11310* ng-click="items.splice($index, 1)">
11311* </ion-delete-button>
11312* <ion-reorder-button class="ion-navicon"
11313* on-reorder="reorderItem(item, $fromIndex, $toIndex)">
11314* </ion-reorder-button>
11315*
11316* </ion-item>
11317* </ion-list>
11318* ```
11319*
11320*```javascript
11321* app.controller('MyCtrl', function($scope) {
11322* $scope.shouldShowDelete = false;
11323* $scope.shouldShowReorder = false;
11324* $scope.listCanSwipe = true
11325* });
11326*```
11327*
11328* @param {string=} delegate-handle The handle used to identify this list with
11329* {@link ionic.service:$ionicListDelegate}.
11330* @param type {string=} The type of list to use (list-inset or card)
11331* @param show-delete {boolean=} Whether the delete buttons for the items in the list are
11332* currently shown or hidden.
11333* @param show-reorder {boolean=} Whether the reorder buttons for the items in the list are
11334* currently shown or hidden.
11335* @param can-swipe {boolean=} Whether the items in the list are allowed to be swiped to reveal
11336* option buttons. Default: true.
11337*/
11338IonicModule
11339.directive('ionList', [
11340 '$timeout',
11341function($timeout) {
11342 return {
11343 restrict: 'E',
11344 require: ['ionList', '^?$ionicScroll'],
11345 controller: '$ionicList',
11346 compile: function($element, $attr) {
11347 var listEl = jqLite('<div class="list">')
11348 .append($element.contents())
11349 .addClass($attr.type);
11350
11351 $element.append(listEl);
11352
11353 return function($scope, $element, $attrs, ctrls) {
11354 var listCtrl = ctrls[0];
11355 var scrollCtrl = ctrls[1];
11356
11357 // Wait for child elements to render...
11358 $timeout(init);
11359
11360 function init() {
11361 var listView = listCtrl.listView = new ionic.views.ListView({
11362 el: $element[0],
11363 listEl: $element.children()[0],
11364 scrollEl: scrollCtrl && scrollCtrl.element,
11365 scrollView: scrollCtrl && scrollCtrl.scrollView,
11366 onReorder: function(el, oldIndex, newIndex) {
11367 var itemScope = jqLite(el).scope();
11368 if (itemScope && itemScope.$onReorder) {
11369 // Make sure onReorder is called in apply cycle,
11370 // but also make sure it has no conflicts by doing
11371 // $evalAsync
11372 $timeout(function() {
11373 itemScope.$onReorder(oldIndex, newIndex);
11374 });
11375 }
11376 },
11377 canSwipe: function() {
11378 return listCtrl.canSwipeItems();
11379 }
11380 });
11381
11382 $scope.$on('$destroy', function() {
11383 if (listView) {
11384 listView.deregister && listView.deregister();
11385 listView = null;
11386 }
11387 });
11388
11389 if (isDefined($attr.canSwipe)) {
11390 $scope.$watch('!!(' + $attr.canSwipe + ')', function(value) {
11391 listCtrl.canSwipeItems(value);
11392 });
11393 }
11394 if (isDefined($attr.showDelete)) {
11395 $scope.$watch('!!(' + $attr.showDelete + ')', function(value) {
11396 listCtrl.showDelete(value);
11397 });
11398 }
11399 if (isDefined($attr.showReorder)) {
11400 $scope.$watch('!!(' + $attr.showReorder + ')', function(value) {
11401 listCtrl.showReorder(value);
11402 });
11403 }
11404
11405 $scope.$watch(function() {
11406 return listCtrl.showDelete();
11407 }, function(isShown, wasShown) {
11408 //Only use isShown=false if it was already shown
11409 if (!isShown && !wasShown) { return; }
11410
11411 if (isShown) listCtrl.closeOptionButtons();
11412 listCtrl.canSwipeItems(!isShown);
11413
11414 $element.children().toggleClass('list-left-editing', isShown);
11415 $element.toggleClass('disable-pointer-events', isShown);
11416
11417 var deleteButton = jqLite($element[0].getElementsByClassName('item-delete'));
11418 setButtonShown(deleteButton, listCtrl.showDelete);
11419 });
11420
11421 $scope.$watch(function() {
11422 return listCtrl.showReorder();
11423 }, function(isShown, wasShown) {
11424 //Only use isShown=false if it was already shown
11425 if (!isShown && !wasShown) { return; }
11426
11427 if (isShown) listCtrl.closeOptionButtons();
11428 listCtrl.canSwipeItems(!isShown);
11429
11430 $element.children().toggleClass('list-right-editing', isShown);
11431 $element.toggleClass('disable-pointer-events', isShown);
11432
11433 var reorderButton = jqLite($element[0].getElementsByClassName('item-reorder'));
11434 setButtonShown(reorderButton, listCtrl.showReorder);
11435 });
11436
11437 function setButtonShown(el, shown) {
11438 shown() && el.addClass('visible') || el.removeClass('active');
11439 ionic.requestAnimationFrame(function() {
11440 shown() && el.addClass('active') || el.removeClass('visible');
11441 });
11442 }
11443 }
11444
11445 };
11446 }
11447 };
11448}]);
11449
11450/**
11451 * @ngdoc directive
11452 * @name menuClose
11453 * @module ionic
11454 * @restrict AC
11455 *
11456 * @description
11457 * `menu-close` is an attribute directive that closes a currently opened side menu.
11458 * Note that by default, navigation transitions will not animate between views when
11459 * the menu is open. Additionally, this directive will reset the entering view's
11460 * history stack, making the new page the root of the history stack. This is done
11461 * to replicate the user experience seen in most side menu implementations, which is
11462 * to not show the back button at the root of the stack and show only the
11463 * menu button. We recommend that you also use the `enable-menu-with-back-views="false"`
11464 * {@link ionic.directive:ionSideMenus} attribute when using the menuClose directive.
11465 *
11466 * @usage
11467 * Below is an example of a link within a side menu. Tapping this link would
11468 * automatically close the currently opened menu.
11469 *
11470 * ```html
11471 * <a menu-close href="#/home" class="item">Home</a>
11472 * ```
11473 *
11474 * Note that if your destination state uses a resolve and that resolve asynchronously
11475 * takes longer than a standard transition (300ms), you'll need to set the
11476 * `nextViewOptions` manually as your resolve completes.
11477 *
11478 * ```js
11479 * $ionicHistory.nextViewOptions({
11480 * historyRoot: true,
11481 * disableAnimate: true,
11482 * expire: 300
11483 * });
11484 * ```
11485 */
11486IonicModule
11487.directive('menuClose', ['$ionicHistory', '$timeout', function($ionicHistory, $timeout) {
11488 return {
11489 restrict: 'AC',
11490 link: function($scope, $element) {
11491 $element.bind('click', function() {
11492 var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
11493 if (sideMenuCtrl) {
11494 $ionicHistory.nextViewOptions({
11495 historyRoot: true,
11496 disableAnimate: true,
11497 expire: 300
11498 });
11499 // if no transition in 300ms, reset nextViewOptions
11500 // the expire should take care of it, but will be cancelled in some
11501 // cases. This directive is an exception to the rules of history.js
11502 $timeout( function() {
11503 $ionicHistory.nextViewOptions({
11504 historyRoot: false,
11505 disableAnimate: false
11506 });
11507 }, 300);
11508 sideMenuCtrl.close();
11509 }
11510 });
11511 }
11512 };
11513}]);
11514
11515/**
11516 * @ngdoc directive
11517 * @name menuToggle
11518 * @module ionic
11519 * @restrict AC
11520 *
11521 * @description
11522 * Toggle a side menu on the given side.
11523 *
11524 * @usage
11525 * Below is an example of a link within a nav bar. Tapping this button
11526 * would open the given side menu, and tapping it again would close it.
11527 *
11528 * ```html
11529 * <ion-nav-bar>
11530 * <ion-nav-buttons side="left">
11531 * <!-- Toggle left side menu -->
11532 * <button menu-toggle="left" class="button button-icon icon ion-navicon"></button>
11533 * </ion-nav-buttons>
11534 * <ion-nav-buttons side="right">
11535 * <!-- Toggle right side menu -->
11536 * <button menu-toggle="right" class="button button-icon icon ion-navicon"></button>
11537 * </ion-nav-buttons>
11538 * </ion-nav-bar>
11539 * ```
11540 *
11541 * ### Button Hidden On Child Views
11542 * By default, the menu toggle button will only appear on a root
11543 * level side-menu page. Navigating in to child views will hide the menu-
11544 * toggle button. They can be made visible on child pages by setting the
11545 * enable-menu-with-back-views attribute of the {@link ionic.directive:ionSideMenus}
11546 * directive to true.
11547 *
11548 * ```html
11549 * <ion-side-menus enable-menu-with-back-views="true">
11550 * ```
11551 */
11552IonicModule
11553.directive('menuToggle', function() {
11554 return {
11555 restrict: 'AC',
11556 link: function($scope, $element, $attr) {
11557 $scope.$on('$ionicView.beforeEnter', function(ev, viewData) {
11558 if (viewData.enableBack) {
11559 var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
11560 if (!sideMenuCtrl.enableMenuWithBackViews()) {
11561 $element.addClass('hide');
11562 }
11563 } else {
11564 $element.removeClass('hide');
11565 }
11566 });
11567
11568 $element.bind('click', function() {
11569 var sideMenuCtrl = $element.inheritedData('$ionSideMenusController');
11570 sideMenuCtrl && sideMenuCtrl.toggle($attr.menuToggle);
11571 });
11572 }
11573 };
11574});
11575
11576/*
11577 * We don't document the ionModal directive, we instead document
11578 * the $ionicModal service
11579 */
11580IonicModule
11581.directive('ionModal', [function() {
11582 return {
11583 restrict: 'E',
11584 transclude: true,
11585 replace: true,
11586 controller: [function() {}],
11587 template: '<div class="modal-backdrop">' +
11588 '<div class="modal-backdrop-bg"></div>' +
11589 '<div class="modal-wrapper" ng-transclude></div>' +
11590 '</div>'
11591 };
11592}]);
11593
11594IonicModule
11595.directive('ionModalView', function() {
11596 return {
11597 restrict: 'E',
11598 compile: function(element) {
11599 element.addClass('modal');
11600 }
11601 };
11602});
11603
11604/**
11605 * @ngdoc directive
11606 * @name ionNavBackButton
11607 * @module ionic
11608 * @restrict E
11609 * @parent ionNavBar
11610 * @description
11611 * Creates a back button inside an {@link ionic.directive:ionNavBar}.
11612 *
11613 * The back button will appear when the user is able to go back in the current navigation stack. By
11614 * default, the markup of the back button is automatically built using platform-appropriate defaults
11615 * (iOS back button icon on iOS and Android icon on Android).
11616 *
11617 * Additionally, the button is automatically set to `$ionicGoBack()` on click/tap. By default, the
11618 * app will navigate back one view when the back button is clicked. More advanced behavior is also
11619 * possible, as outlined below.
11620 *
11621 * @usage
11622 *
11623 * Recommended markup for default settings:
11624 *
11625 * ```html
11626 * <ion-nav-bar>
11627 * <ion-nav-back-button>
11628 * </ion-nav-back-button>
11629 * </ion-nav-bar>
11630 * ```
11631 *
11632 * With custom inner markup, and automatically adds a default click action:
11633 *
11634 * ```html
11635 * <ion-nav-bar>
11636 * <ion-nav-back-button class="button-clear">
11637 * <i class="ion-arrow-left-c"></i> Back
11638 * </ion-nav-back-button>
11639 * </ion-nav-bar>
11640 * ```
11641 *
11642 * With custom inner markup and custom click action, using {@link ionic.service:$ionicHistory}:
11643 *
11644 * ```html
11645 * <ion-nav-bar ng-controller="MyCtrl">
11646 * <ion-nav-back-button class="button-clear"
11647 * ng-click="myGoBack()">
11648 * <i class="ion-arrow-left-c"></i> Back
11649 * </ion-nav-back-button>
11650 * </ion-nav-bar>
11651 * ```
11652 * ```js
11653 * function MyCtrl($scope, $ionicHistory) {
11654 * $scope.myGoBack = function() {
11655 * $ionicHistory.goBack();
11656 * };
11657 * }
11658 * ```
11659 */
11660IonicModule
11661.directive('ionNavBackButton', ['$ionicConfig', '$document', function($ionicConfig, $document) {
11662 return {
11663 restrict: 'E',
11664 require: '^ionNavBar',
11665 compile: function(tElement, tAttrs) {
11666
11667 // clone the back button, but as a <div>
11668 var buttonEle = $document[0].createElement('button');
11669 for (var n in tAttrs.$attr) {
11670 buttonEle.setAttribute(tAttrs.$attr[n], tAttrs[n]);
11671 }
11672
11673 if (!tAttrs.ngClick) {
11674 buttonEle.setAttribute('ng-click', '$ionicGoBack()');
11675 }
11676
11677 buttonEle.className = 'button back-button hide buttons ' + (tElement.attr('class') || '');
11678 buttonEle.innerHTML = tElement.html() || '';
11679
11680 var childNode;
11681 var hasIcon = hasIconClass(tElement[0]);
11682 var hasInnerText;
11683 var hasButtonText;
11684 var hasPreviousTitle;
11685
11686 for (var x = 0; x < tElement[0].childNodes.length; x++) {
11687 childNode = tElement[0].childNodes[x];
11688 if (childNode.nodeType === 1) {
11689 if (hasIconClass(childNode)) {
11690 hasIcon = true;
11691 } else if (childNode.classList.contains('default-title')) {
11692 hasButtonText = true;
11693 } else if (childNode.classList.contains('previous-title')) {
11694 hasPreviousTitle = true;
11695 }
11696 } else if (!hasInnerText && childNode.nodeType === 3) {
11697 hasInnerText = !!childNode.nodeValue.trim();
11698 }
11699 }
11700
11701 function hasIconClass(ele) {
11702 return /ion-|icon/.test(ele.className);
11703 }
11704
11705 var defaultIcon = $ionicConfig.backButton.icon();
11706 if (!hasIcon && defaultIcon && defaultIcon !== 'none') {
11707 buttonEle.innerHTML = '<i class="icon ' + defaultIcon + '"></i> ' + buttonEle.innerHTML;
11708 buttonEle.className += ' button-clear';
11709 }
11710
11711 if (!hasInnerText) {
11712 var buttonTextEle = $document[0].createElement('span');
11713 buttonTextEle.className = 'back-text';
11714
11715 if (!hasButtonText && $ionicConfig.backButton.text()) {
11716 buttonTextEle.innerHTML += '<span class="default-title">' + $ionicConfig.backButton.text() + '</span>';
11717 }
11718 if (!hasPreviousTitle && $ionicConfig.backButton.previousTitleText()) {
11719 buttonTextEle.innerHTML += '<span class="previous-title"></span>';
11720 }
11721 buttonEle.appendChild(buttonTextEle);
11722
11723 }
11724
11725 tElement.attr('class', 'hide');
11726 tElement.empty();
11727
11728 return {
11729 pre: function($scope, $element, $attr, navBarCtrl) {
11730 // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
11731 navBarCtrl.navElement('backButton', buttonEle.outerHTML);
11732 buttonEle = null;
11733 }
11734 };
11735 }
11736 };
11737}]);
11738
11739
11740/**
11741 * @ngdoc directive
11742 * @name ionNavBar
11743 * @module ionic
11744 * @delegate ionic.service:$ionicNavBarDelegate
11745 * @restrict E
11746 *
11747 * @description
11748 * If we have an {@link ionic.directive:ionNavView} directive, we can also create an
11749 * `<ion-nav-bar>`, which will create a topbar that updates as the application state changes.
11750 *
11751 * We can add a back button by putting an {@link ionic.directive:ionNavBackButton} inside.
11752 *
11753 * We can add buttons depending on the currently visible view using
11754 * {@link ionic.directive:ionNavButtons}.
11755 *
11756 * Note that the ion-nav-bar element will only work correctly if your content has an
11757 * ionView around it.
11758 *
11759 * @usage
11760 *
11761 * ```html
11762 * <body ng-app="starter">
11763 * <!-- The nav bar that will be updated as we navigate -->
11764 * <ion-nav-bar class="bar-positive">
11765 * </ion-nav-bar>
11766 *
11767 * <!-- where the initial view template will be rendered -->
11768 * <ion-nav-view>
11769 * <ion-view>
11770 * <ion-content>Hello!</ion-content>
11771 * </ion-view>
11772 * </ion-nav-view>
11773 * </body>
11774 * ```
11775 *
11776 * @param {string=} delegate-handle The handle used to identify this navBar
11777 * with {@link ionic.service:$ionicNavBarDelegate}.
11778 * @param align-title {string=} Where to align the title of the navbar.
11779 * Available: 'left', 'right', 'center'. Defaults to 'center'.
11780 * @param {boolean=} no-tap-scroll By default, the navbar will scroll the content
11781 * to the top when tapped. Set no-tap-scroll to true to disable this behavior.
11782 *
11783 * </table><br/>
11784 */
11785IonicModule
11786.directive('ionNavBar', function() {
11787 return {
11788 restrict: 'E',
11789 controller: '$ionicNavBar',
11790 scope: true,
11791 link: function($scope, $element, $attr, ctrl) {
11792 ctrl.init();
11793 }
11794 };
11795});
11796
11797
11798/**
11799 * @ngdoc directive
11800 * @name ionNavButtons
11801 * @module ionic
11802 * @restrict E
11803 * @parent ionNavView
11804 *
11805 * @description
11806 * Use nav buttons to set the buttons on your {@link ionic.directive:ionNavBar}
11807 * from within an {@link ionic.directive:ionView}. This gives each
11808 * view template the ability to specify which buttons should show in the nav bar,
11809 * overriding any default buttons already placed in the nav bar.
11810 *
11811 * Any buttons you declare will be positioned on the navbar's corresponding side. Primary
11812 * buttons generally map to the left side of the header, and secondary buttons are
11813 * generally on the right side. However, their exact locations are platform-specific.
11814 * For example, in iOS, the primary buttons are on the far left of the header, and
11815 * secondary buttons are on the far right, with the header title centered between them.
11816 * For Android, however, both groups of buttons are on the far right of the header,
11817 * with the header title aligned left.
11818 *
11819 * We recommend always using `primary` and `secondary`, so the buttons correctly map
11820 * to the side familiar to users of each platform. However, in cases where buttons should
11821 * always be on an exact side, both `left` and `right` sides are still available. For
11822 * example, a toggle button for a left side menu should be on the left side; in this case,
11823 * we'd recommend using `side="left"`, so it's always on the left, no matter the platform.
11824 *
11825 * ***Note*** that `ion-nav-buttons` must be immediate descendants of the `ion-view` or
11826 * `ion-nav-bar` element (basically, don't wrap it in another div).
11827 *
11828 * @usage
11829 * ```html
11830 * <ion-nav-bar>
11831 * </ion-nav-bar>
11832 * <ion-nav-view>
11833 * <ion-view>
11834 * <ion-nav-buttons side="primary">
11835 * <button class="button" ng-click="doSomething()">
11836 * I'm a button on the primary of the navbar!
11837 * </button>
11838 * </ion-nav-buttons>
11839 * <ion-content>
11840 * Some super content here!
11841 * </ion-content>
11842 * </ion-view>
11843 * </ion-nav-view>
11844 * ```
11845 *
11846 * @param {string} side The side to place the buttons in the
11847 * {@link ionic.directive:ionNavBar}. Available sides: `primary`, `secondary`, `left`, and `right`.
11848 */
11849IonicModule
11850.directive('ionNavButtons', ['$document', function($document) {
11851 return {
11852 require: '^ionNavBar',
11853 restrict: 'E',
11854 compile: function(tElement, tAttrs) {
11855 var side = 'left';
11856
11857 if (/^primary|secondary|right$/i.test(tAttrs.side || '')) {
11858 side = tAttrs.side.toLowerCase();
11859 }
11860
11861 var spanEle = $document[0].createElement('span');
11862 spanEle.className = side + '-buttons';
11863 spanEle.innerHTML = tElement.html();
11864
11865 var navElementType = side + 'Buttons';
11866
11867 tElement.attr('class', 'hide');
11868 tElement.empty();
11869
11870 return {
11871 pre: function($scope, $element, $attrs, navBarCtrl) {
11872 // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
11873
11874 var parentViewCtrl = $element.parent().data('$ionViewController');
11875 if (parentViewCtrl) {
11876 // if the parent is an ion-view, then these are ion-nav-buttons for JUST this ion-view
11877 parentViewCtrl.navElement(navElementType, spanEle.outerHTML);
11878
11879 } else {
11880 // these are buttons for all views that do not have their own ion-nav-buttons
11881 navBarCtrl.navElement(navElementType, spanEle.outerHTML);
11882 }
11883
11884 spanEle = null;
11885 }
11886 };
11887 }
11888 };
11889}]);
11890
11891/**
11892 * @ngdoc directive
11893 * @name navDirection
11894 * @module ionic
11895 * @restrict A
11896 *
11897 * @description
11898 * The direction which the nav view transition should animate. Available options
11899 * are: `forward`, `back`, `enter`, `exit`, `swap`.
11900 *
11901 * @usage
11902 *
11903 * ```html
11904 * <a nav-direction="forward" href="#/home">Home</a>
11905 * ```
11906 */
11907IonicModule
11908.directive('navDirection', ['$ionicViewSwitcher', function($ionicViewSwitcher) {
11909 return {
11910 restrict: 'A',
11911 priority: 1000,
11912 link: function($scope, $element, $attr) {
11913 $element.bind('click', function() {
11914 $ionicViewSwitcher.nextDirection($attr.navDirection);
11915 });
11916 }
11917 };
11918}]);
11919
11920/**
11921 * @ngdoc directive
11922 * @name ionNavTitle
11923 * @module ionic
11924 * @restrict E
11925 * @parent ionNavView
11926 *
11927 * @description
11928 *
11929 * The nav title directive replaces an {@link ionic.directive:ionNavBar} title text with
11930 * custom HTML from within an {@link ionic.directive:ionView} template. This gives each
11931 * view the ability to specify its own custom title element, such as an image or any HTML,
11932 * rather than being text-only. Alternatively, text-only titles can be updated using the
11933 * `view-title` {@link ionic.directive:ionView} attribute.
11934 *
11935 * Note that `ion-nav-title` must be an immediate descendant of the `ion-view` or
11936 * `ion-nav-bar` element (basically don't wrap it in another div).
11937 *
11938 * @usage
11939 * ```html
11940 * <ion-nav-bar>
11941 * </ion-nav-bar>
11942 * <ion-nav-view>
11943 * <ion-view>
11944 * <ion-nav-title>
11945 * <img src="logo.svg">
11946 * </ion-nav-title>
11947 * <ion-content>
11948 * Some super content here!
11949 * </ion-content>
11950 * </ion-view>
11951 * </ion-nav-view>
11952 * ```
11953 *
11954 */
11955IonicModule
11956.directive('ionNavTitle', ['$document', function($document) {
11957 return {
11958 require: '^ionNavBar',
11959 restrict: 'E',
11960 compile: function(tElement, tAttrs) {
11961 var navElementType = 'title';
11962 var spanEle = $document[0].createElement('span');
11963 for (var n in tAttrs.$attr) {
11964 spanEle.setAttribute(tAttrs.$attr[n], tAttrs[n]);
11965 }
11966 spanEle.classList.add('nav-bar-title');
11967 spanEle.innerHTML = tElement.html();
11968
11969 tElement.attr('class', 'hide');
11970 tElement.empty();
11971
11972 return {
11973 pre: function($scope, $element, $attrs, navBarCtrl) {
11974 // only register the plain HTML, the navBarCtrl takes care of scope/compile/link
11975
11976 var parentViewCtrl = $element.parent().data('$ionViewController');
11977 if (parentViewCtrl) {
11978 // if the parent is an ion-view, then these are ion-nav-buttons for JUST this ion-view
11979 parentViewCtrl.navElement(navElementType, spanEle.outerHTML);
11980
11981 } else {
11982 // these are buttons for all views that do not have their own ion-nav-buttons
11983 navBarCtrl.navElement(navElementType, spanEle.outerHTML);
11984 }
11985
11986 spanEle = null;
11987 }
11988 };
11989 }
11990 };
11991}]);
11992
11993/**
11994 * @ngdoc directive
11995 * @name navTransition
11996 * @module ionic
11997 * @restrict A
11998 *
11999 * @description
12000 * The transition type which the nav view transition should use when it animates.
12001 * Current, options are `ios`, `android`, and `none`. More options coming soon.
12002 *
12003 * @usage
12004 *
12005 * ```html
12006 * <a nav-transition="none" href="#/home">Home</a>
12007 * ```
12008 */
12009IonicModule
12010.directive('navTransition', ['$ionicViewSwitcher', function($ionicViewSwitcher) {
12011 return {
12012 restrict: 'A',
12013 priority: 1000,
12014 link: function($scope, $element, $attr) {
12015 $element.bind('click', function() {
12016 $ionicViewSwitcher.nextTransition($attr.navTransition);
12017 });
12018 }
12019 };
12020}]);
12021
12022/**
12023 * @ngdoc directive
12024 * @name ionNavView
12025 * @module ionic
12026 * @restrict E
12027 * @codepen odqCz
12028 *
12029 * @description
12030 * As a user navigates throughout your app, Ionic is able to keep track of their
12031 * navigation history. By knowing their history, transitions between views
12032 * correctly enter and exit using the platform's transition style. An additional
12033 * benefit to Ionic's navigation system is its ability to manage multiple
12034 * histories. For example, each tab can have it's own navigation history stack.
12035 *
12036 * Ionic uses the AngularUI Router module so app interfaces can be organized
12037 * into various "states". Like Angular's core $route service, URLs can be used
12038 * to control the views. However, the AngularUI Router provides a more powerful
12039 * state manager in that states are bound to named, nested, and parallel views,
12040 * allowing more than one template to be rendered on the same page.
12041 * Additionally, each state is not required to be bound to a URL, and data can
12042 * be pushed to each state which allows much flexibility.
12043 *
12044 * The ionNavView directive is used to render templates in your application. Each template
12045 * is part of a state. States are usually mapped to a url, and are defined programatically
12046 * using angular-ui-router (see [their docs](https://github.com/angular-ui/ui-router/wiki),
12047 * and remember to replace ui-view with ion-nav-view in examples).
12048 *
12049 * @usage
12050 * In this example, we will create a navigation view that contains our different states for the app.
12051 *
12052 * To do this, in our markup we use ionNavView top level directive. To display a header bar we use
12053 * the {@link ionic.directive:ionNavBar} directive that updates as we navigate through the
12054 * navigation stack.
12055 *
12056 * Next, we need to setup our states that will be rendered.
12057 *
12058 * ```js
12059 * var app = angular.module('myApp', ['ionic']);
12060 * app.config(function($stateProvider) {
12061 * $stateProvider
12062 * .state('index', {
12063 * url: '/',
12064 * templateUrl: 'home.html'
12065 * })
12066 * .state('music', {
12067 * url: '/music',
12068 * templateUrl: 'music.html'
12069 * });
12070 * });
12071 * ```
12072 * Then on app start, $stateProvider will look at the url, see if it matches the index state,
12073 * and then try to load home.html into the `<ion-nav-view>`.
12074 *
12075 * Pages are loaded by the URLs given. One simple way to create templates in Angular is to put
12076 * them directly into your HTML file and use the `<script type="text/ng-template">` syntax.
12077 * So here is one way to put home.html into our app:
12078 *
12079 * ```html
12080 * <script id="home" type="text/ng-template">
12081 * <!-- The title of the ion-view will be shown on the navbar -->
12082 * <ion-view view-title="Home">
12083 * <ion-content ng-controller="HomeCtrl">
12084 * <!-- The content of the page -->
12085 * <a href="#/music">Go to music page!</a>
12086 * </ion-content>
12087 * </ion-view>
12088 * </script>
12089 * ```
12090 *
12091 * This is good to do because the template will be cached for very fast loading, instead of
12092 * having to fetch them from the network.
12093 *
12094 * ## Caching
12095 *
12096 * By default, views are cached to improve performance. When a view is navigated away from, its
12097 * element is left in the DOM, and its scope is disconnected from the `$watch` cycle. When
12098 * navigating to a view that is already cached, its scope is then reconnected, and the existing
12099 * element that was left in the DOM becomes the active view. This also allows for the scroll
12100 * position of previous views to be maintained.
12101 *
12102 * Caching can be disabled and enabled in multiple ways. By default, Ionic will cache a maximum of
12103 * 10 views, and not only can this be configured, but apps can also explicitly state which views
12104 * should and should not be cached.
12105 *
12106 * Note that because we are caching these views, *we aren’t destroying scopes*. Instead, scopes
12107 * are being disconnected from the watch cycle. Because scopes are not being destroyed and
12108 * recreated, controllers are not loading again on a subsequent viewing. If the app/controller
12109 * needs to know when a view has entered or has left, then view events emitted from the
12110 * {@link ionic.directive:ionView} scope, such as `$ionicView.enter`, may be useful.
12111 *
12112 * By default, when navigating back in the history, the "forward" views are removed from the cache.
12113 * If you navigate forward to the same view again, it'll create a new DOM element and controller
12114 * instance. Basically, any forward views are reset each time. This can be configured using the
12115 * {@link ionic.provider:$ionicConfigProvider}:
12116 *
12117 * ```js
12118 * $ionicConfigProvider.views.forwardCache(true);
12119 * ```
12120 *
12121 * #### Disable cache globally
12122 *
12123 * The {@link ionic.provider:$ionicConfigProvider} can be used to set the maximum allowable views
12124 * which can be cached, but this can also be use to disable all caching by setting it to 0.
12125 *
12126 * ```js
12127 * $ionicConfigProvider.views.maxCache(0);
12128 * ```
12129 *
12130 * #### Disable cache within state provider
12131 *
12132 * ```js
12133 * $stateProvider.state('myState', {
12134 * cache: false,
12135 * url : '/myUrl',
12136 * templateUrl : 'my-template.html'
12137 * })
12138 * ```
12139 *
12140 * #### Disable cache with an attribute
12141 *
12142 * ```html
12143 * <ion-view cache-view="false" view-title="My Title!">
12144 * ...
12145 * </ion-view>
12146 * ```
12147 *
12148 *
12149 * ## AngularUI Router
12150 *
12151 * Please visit [AngularUI Router's docs](https://github.com/angular-ui/ui-router/wiki) for
12152 * more info. Below is a great video by the AngularUI Router team that may help to explain
12153 * how it all works:
12154 *
12155 * <iframe width="560" height="315" src="//www.youtube.com/embed/dqJRoh8MnBo"
12156 * frameborder="0" allowfullscreen></iframe>
12157 *
12158 * Note: We do not recommend using [resolve](https://github.com/angular-ui/ui-router/wiki#resolve)
12159 * of AngularUI Router. The recommended approach is to execute any logic needed before beginning the state transition.
12160 *
12161 * @param {string=} name A view name. The name should be unique amongst the other views in the
12162 * same state. You can have views of the same name that live in different states. For more
12163 * information, see ui-router's
12164 * [ui-view documentation](http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.directive:ui-view).
12165 */
12166IonicModule
12167.directive('ionNavView', [
12168 '$state',
12169 '$ionicConfig',
12170function($state, $ionicConfig) {
12171 // IONIC's fork of Angular UI Router, v0.2.10
12172 // the navView handles registering views in the history and how to transition between them
12173 return {
12174 restrict: 'E',
12175 terminal: true,
12176 priority: 2000,
12177 transclude: true,
12178 controller: '$ionicNavView',
12179 compile: function(tElement, tAttrs, transclude) {
12180
12181 // a nav view element is a container for numerous views
12182 tElement.addClass('view-container');
12183 ionic.DomUtil.cachedAttr(tElement, 'nav-view-transition', $ionicConfig.views.transition());
12184
12185 return function($scope, $element, $attr, navViewCtrl) {
12186 var latestLocals;
12187
12188 // Put in the compiled initial view
12189 transclude($scope, function(clone) {
12190 $element.append(clone);
12191 });
12192
12193 var viewData = navViewCtrl.init();
12194
12195 // listen for $stateChangeSuccess
12196 $scope.$on('$stateChangeSuccess', function() {
12197 updateView(false);
12198 });
12199 $scope.$on('$viewContentLoading', function() {
12200 updateView(false);
12201 });
12202
12203 // initial load, ready go
12204 updateView(true);
12205
12206
12207 function updateView(firstTime) {
12208 // get the current local according to the $state
12209 var viewLocals = $state.$current && $state.$current.locals[viewData.name];
12210
12211 // do not update THIS nav-view if its is not the container for the given state
12212 // if the viewLocals are the same as THIS latestLocals, then nothing to do
12213 if (!viewLocals || (!firstTime && viewLocals === latestLocals)) return;
12214
12215 // update the latestLocals
12216 latestLocals = viewLocals;
12217 viewData.state = viewLocals.$$state;
12218
12219 // register, update and transition to the new view
12220 navViewCtrl.register(viewLocals);
12221 }
12222
12223 };
12224 }
12225 };
12226}]);
12227
12228IonicModule
12229
12230.config(['$provide', function($provide) {
12231 $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
12232 // drop the default ngClick directive
12233 $delegate.shift();
12234 return $delegate;
12235 }]);
12236}])
12237
12238/**
12239 * @private
12240 */
12241.factory('$ionicNgClick', ['$parse', function($parse) {
12242 return function(scope, element, clickExpr) {
12243 var clickHandler = angular.isFunction(clickExpr) ?
12244 clickExpr :
12245 $parse(clickExpr);
12246
12247 element.on('click', function(event) {
12248 scope.$apply(function() {
12249 clickHandler(scope, {$event: (event)});
12250 });
12251 });
12252
12253 // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
12254 // something else nearby.
12255 element.onclick = noop;
12256 };
12257}])
12258
12259.directive('ngClick', ['$ionicNgClick', function($ionicNgClick) {
12260 return function(scope, element, attr) {
12261 $ionicNgClick(scope, element, attr.ngClick);
12262 };
12263}])
12264
12265.directive('ionStopEvent', function() {
12266 return {
12267 restrict: 'A',
12268 link: function(scope, element, attr) {
12269 element.bind(attr.ionStopEvent, eventStopPropagation);
12270 }
12271 };
12272});
12273function eventStopPropagation(e) {
12274 e.stopPropagation();
12275}
12276
12277
12278/**
12279 * @ngdoc directive
12280 * @name ionPane
12281 * @module ionic
12282 * @restrict E
12283 *
12284 * @description A simple container that fits content, with no side effects. Adds the 'pane' class to the element.
12285 */
12286IonicModule
12287.directive('ionPane', function() {
12288 return {
12289 restrict: 'E',
12290 link: function(scope, element) {
12291 element.addClass('pane');
12292 }
12293 };
12294});
12295
12296/*
12297 * We don't document the ionPopover directive, we instead document
12298 * the $ionicPopover service
12299 */
12300IonicModule
12301.directive('ionPopover', [function() {
12302 return {
12303 restrict: 'E',
12304 transclude: true,
12305 replace: true,
12306 controller: [function() {}],
12307 template: '<div class="popover-backdrop">' +
12308 '<div class="popover-wrapper" ng-transclude></div>' +
12309 '</div>'
12310 };
12311}]);
12312
12313IonicModule
12314.directive('ionPopoverView', function() {
12315 return {
12316 restrict: 'E',
12317 compile: function(element) {
12318 element.append(jqLite('<div class="popover-arrow">'));
12319 element.addClass('popover');
12320 }
12321 };
12322});
12323
12324/**
12325 * @ngdoc directive
12326 * @name ionRadio
12327 * @module ionic
12328 * @restrict E
12329 * @codepen saoBG
12330 * @description
12331 * The radio directive is no different than the HTML radio input, except it's styled differently.
12332 *
12333 * Radio behaves like [AngularJS radio](http://docs.angularjs.org/api/ng/input/input[radio]).
12334 *
12335 * @usage
12336 * ```html
12337 * <ion-radio ng-model="choice" ng-value="'A'">Choose A</ion-radio>
12338 * <ion-radio ng-model="choice" ng-value="'B'">Choose B</ion-radio>
12339 * <ion-radio ng-model="choice" ng-value="'C'">Choose C</ion-radio>
12340 * ```
12341 *
12342 * @param {string=} name The name of the radio input.
12343 * @param {expression=} value The value of the radio input.
12344 * @param {boolean=} disabled The state of the radio input.
12345 * @param {string=} icon The icon to use when the radio input is selected.
12346 * @param {expression=} ng-value Angular equivalent of the value attribute.
12347 * @param {expression=} ng-model The angular model for the radio input.
12348 * @param {boolean=} ng-disabled Angular equivalent of the disabled attribute.
12349 * @param {expression=} ng-change Triggers given expression when radio input's model changes
12350 */
12351IonicModule
12352.directive('ionRadio', function() {
12353 return {
12354 restrict: 'E',
12355 replace: true,
12356 require: '?ngModel',
12357 transclude: true,
12358 template:
12359 '<label class="item item-radio">' +
12360 '<input type="radio" name="radio-group">' +
12361 '<div class="radio-content">' +
12362 '<div class="item-content disable-pointer-events" ng-transclude></div>' +
12363 '<i class="radio-icon disable-pointer-events icon ion-checkmark"></i>' +
12364 '</div>' +
12365 '</label>',
12366
12367 compile: function(element, attr) {
12368 if (attr.icon) {
12369 var iconElm = element.find('i');
12370 iconElm.removeClass('ion-checkmark').addClass(attr.icon);
12371 }
12372
12373 var input = element.find('input');
12374 forEach({
12375 'name': attr.name,
12376 'value': attr.value,
12377 'disabled': attr.disabled,
12378 'ng-value': attr.ngValue,
12379 'ng-model': attr.ngModel,
12380 'ng-disabled': attr.ngDisabled,
12381 'ng-change': attr.ngChange,
12382 'ng-required': attr.ngRequired,
12383 'required': attr.required
12384 }, function(value, name) {
12385 if (isDefined(value)) {
12386 input.attr(name, value);
12387 }
12388 });
12389
12390 return function(scope, element, attr) {
12391 scope.getValue = function() {
12392 return scope.ngValue || attr.value;
12393 };
12394 };
12395 }
12396 };
12397});
12398
12399
12400/**
12401 * @ngdoc directive
12402 * @name ionRefresher
12403 * @module ionic
12404 * @restrict E
12405 * @parent ionic.directive:ionContent, ionic.directive:ionScroll
12406 * @description
12407 * Allows you to add pull-to-refresh to a scrollView.
12408 *
12409 * Place it as the first child of your {@link ionic.directive:ionContent} or
12410 * {@link ionic.directive:ionScroll} element.
12411 *
12412 * When refreshing is complete, $broadcast the 'scroll.refreshComplete' event
12413 * from your controller.
12414 *
12415 * @usage
12416 *
12417 * ```html
12418 * <ion-content ng-controller="MyController">
12419 * <ion-refresher
12420 * pulling-text="Pull to refresh..."
12421 * on-refresh="doRefresh()">
12422 * </ion-refresher>
12423 * <ion-list>
12424 * <ion-item ng-repeat="item in items"></ion-item>
12425 * </ion-list>
12426 * </ion-content>
12427 * ```
12428 * ```js
12429 * angular.module('testApp', ['ionic'])
12430 * .controller('MyController', function($scope, $http) {
12431 * $scope.items = [1,2,3];
12432 * $scope.doRefresh = function() {
12433 * $http.get('/new-items')
12434 * .success(function(newItems) {
12435 * $scope.items = newItems;
12436 * })
12437 * .finally(function() {
12438 * // Stop the ion-refresher from spinning
12439 * $scope.$broadcast('scroll.refreshComplete');
12440 * });
12441 * };
12442 * });
12443 * ```
12444 *
12445 * @param {expression=} on-refresh Called when the user pulls down enough and lets go
12446 * of the refresher.
12447 * @param {expression=} on-pulling Called when the user starts to pull down
12448 * on the refresher.
12449 * @param {string=} pulling-text The text to display while the user is pulling down.
12450 * @param {string=} pulling-icon The icon to display while the user is pulling down.
12451 * Default: 'ion-android-arrow-down'.
12452 * @param {string=} spinner The {@link ionic.directive:ionSpinner} icon to display
12453 * after user lets go of the refresher. The SVG {@link ionic.directive:ionSpinner}
12454 * is now the default, replacing rotating font icons. Set to `none` to disable both the
12455 * spinner and the icon.
12456 * @param {string=} refreshing-icon The font icon to display after user lets go of the
12457 * refresher. This is deprecated in favor of the SVG {@link ionic.directive:ionSpinner}.
12458 * @param {boolean=} disable-pulling-rotation Disables the rotation animation of the pulling
12459 * icon when it reaches its activated threshold. To be used with a custom `pulling-icon`.
12460 *
12461 */
12462IonicModule
12463.directive('ionRefresher', [function() {
12464 return {
12465 restrict: 'E',
12466 replace: true,
12467 require: ['?^$ionicScroll', 'ionRefresher'],
12468 controller: '$ionicRefresher',
12469 template:
12470 '<div class="scroll-refresher invisible" collection-repeat-ignore>' +
12471 '<div class="ionic-refresher-content" ' +
12472 'ng-class="{\'ionic-refresher-with-text\': pullingText || refreshingText}">' +
12473 '<div class="icon-pulling" ng-class="{\'pulling-rotation-disabled\':disablePullingRotation}">' +
12474 '<i class="icon {{pullingIcon}}"></i>' +
12475 '</div>' +
12476 '<div class="text-pulling" ng-bind-html="pullingText"></div>' +
12477 '<div class="icon-refreshing">' +
12478 '<ion-spinner ng-if="showSpinner" icon="{{spinner}}"></ion-spinner>' +
12479 '<i ng-if="showIcon" class="icon {{refreshingIcon}}"></i>' +
12480 '</div>' +
12481 '<div class="text-refreshing" ng-bind-html="refreshingText"></div>' +
12482 '</div>' +
12483 '</div>',
12484 link: function($scope, $element, $attrs, ctrls) {
12485
12486 // JS Scrolling uses the scroll controller
12487 var scrollCtrl = ctrls[0],
12488 refresherCtrl = ctrls[1];
12489 if (!scrollCtrl || scrollCtrl.isNative()) {
12490 // Kick off native scrolling
12491 refresherCtrl.init();
12492 } else {
12493 $element[0].classList.add('js-scrolling');
12494 scrollCtrl._setRefresher(
12495 $scope,
12496 $element[0],
12497 refresherCtrl.getRefresherDomMethods()
12498 );
12499
12500 $scope.$on('scroll.refreshComplete', function() {
12501 $scope.$evalAsync(function() {
12502 scrollCtrl.scrollView.finishPullToRefresh();
12503 });
12504 });
12505 }
12506
12507 }
12508 };
12509}]);
12510
12511/**
12512 * @ngdoc directive
12513 * @name ionScroll
12514 * @module ionic
12515 * @delegate ionic.service:$ionicScrollDelegate
12516 * @codepen mwFuh
12517 * @restrict E
12518 *
12519 * @description
12520 * Creates a scrollable container for all content inside.
12521 *
12522 * @usage
12523 *
12524 * Basic usage:
12525 *
12526 * ```html
12527 * <ion-scroll zooming="true" direction="xy" style="width: 500px; height: 500px">
12528 * <div style="width: 5000px; height: 5000px; background: url('https://upload.wikimedia.org/wikipedia/commons/a/ad/Europe_geological_map-en.jpg') repeat"></div>
12529 * </ion-scroll>
12530 * ```
12531 *
12532 * Note that it's important to set the height of the scroll box as well as the height of the inner
12533 * content to enable scrolling. This makes it possible to have full control over scrollable areas.
12534 *
12535 * If you'd just like to have a center content scrolling area, use {@link ionic.directive:ionContent} instead.
12536 *
12537 * @param {string=} delegate-handle The handle used to identify this scrollView
12538 * with {@link ionic.service:$ionicScrollDelegate}.
12539 * @param {string=} direction Which way to scroll. 'x' or 'y' or 'xy'. Default 'y'.
12540 * @param {boolean=} locking Whether to lock scrolling in one direction at a time. Useful to set to false when zoomed in or scrolling in two directions. Default true.
12541 * @param {boolean=} paging Whether to scroll with paging.
12542 * @param {expression=} on-refresh Called on pull-to-refresh, triggered by an {@link ionic.directive:ionRefresher}.
12543 * @param {expression=} on-scroll Called whenever the user scrolls.
12544 * @param {boolean=} scrollbar-x Whether to show the horizontal scrollbar. Default true.
12545 * @param {boolean=} scrollbar-y Whether to show the vertical scrollbar. Default true.
12546 * @param {boolean=} zooming Whether to support pinch-to-zoom
12547 * @param {integer=} min-zoom The smallest zoom amount allowed (default is 0.5)
12548 * @param {integer=} max-zoom The largest zoom amount allowed (default is 3)
12549 * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges
12550 * of the content. Defaults to true on iOS, false on Android.
12551 */
12552IonicModule
12553.directive('ionScroll', [
12554 '$timeout',
12555 '$controller',
12556 '$ionicBind',
12557 '$ionicConfig',
12558function($timeout, $controller, $ionicBind, $ionicConfig) {
12559 return {
12560 restrict: 'E',
12561 scope: true,
12562 controller: function() {},
12563 compile: function(element, attr) {
12564 element.addClass('scroll-view ionic-scroll');
12565
12566 //We cannot transclude here because it breaks element.data() inheritance on compile
12567 var innerElement = jqLite('<div class="scroll"></div>');
12568 innerElement.append(element.contents());
12569 element.append(innerElement);
12570
12571 var nativeScrolling = attr.overflowScroll !== "false" && (attr.overflowScroll === "true" || !$ionicConfig.scrolling.jsScrolling());
12572
12573 return { pre: prelink };
12574 function prelink($scope, $element, $attr) {
12575 $ionicBind($scope, $attr, {
12576 direction: '@',
12577 paging: '@',
12578 $onScroll: '&onScroll',
12579 scroll: '@',
12580 scrollbarX: '@',
12581 scrollbarY: '@',
12582 zooming: '@',
12583 minZoom: '@',
12584 maxZoom: '@'
12585 });
12586 $scope.direction = $scope.direction || 'y';
12587
12588 if (isDefined($attr.padding)) {
12589 $scope.$watch($attr.padding, function(newVal) {
12590 innerElement.toggleClass('padding', !!newVal);
12591 });
12592 }
12593 if ($scope.$eval($scope.paging) === true) {
12594 innerElement.addClass('scroll-paging');
12595 }
12596
12597 if (!$scope.direction) { $scope.direction = 'y'; }
12598 var isPaging = $scope.$eval($scope.paging) === true;
12599
12600 if (nativeScrolling) {
12601 $element.addClass('overflow-scroll');
12602 }
12603
12604 $element.addClass('scroll-' + $scope.direction);
12605
12606 var scrollViewOptions = {
12607 el: $element[0],
12608 delegateHandle: $attr.delegateHandle,
12609 locking: ($attr.locking || 'true') === 'true',
12610 bouncing: $scope.$eval($attr.hasBouncing),
12611 paging: isPaging,
12612 scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
12613 scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
12614 scrollingX: $scope.direction.indexOf('x') >= 0,
12615 scrollingY: $scope.direction.indexOf('y') >= 0,
12616 zooming: $scope.$eval($scope.zooming) === true,
12617 maxZoom: $scope.$eval($scope.maxZoom) || 3,
12618 minZoom: $scope.$eval($scope.minZoom) || 0.5,
12619 preventDefault: true,
12620 nativeScrolling: nativeScrolling
12621 };
12622
12623 if (isPaging) {
12624 scrollViewOptions.speedMultiplier = 0.8;
12625 scrollViewOptions.bouncing = false;
12626 }
12627
12628 $controller('$ionicScroll', {
12629 $scope: $scope,
12630 scrollViewOptions: scrollViewOptions
12631 });
12632 }
12633 }
12634 };
12635}]);
12636
12637/**
12638 * @ngdoc directive
12639 * @name ionSideMenu
12640 * @module ionic
12641 * @restrict E
12642 * @parent ionic.directive:ionSideMenus
12643 *
12644 * @description
12645 * A container for a side menu, sibling to an {@link ionic.directive:ionSideMenuContent} directive.
12646 *
12647 * @usage
12648 * ```html
12649 * <ion-side-menu
12650 * side="left"
12651 * width="myWidthValue + 20"
12652 * is-enabled="shouldLeftSideMenuBeEnabled()">
12653 * </ion-side-menu>
12654 * ```
12655 * For a complete side menu example, see the
12656 * {@link ionic.directive:ionSideMenus} documentation.
12657 *
12658 * @param {string} side Which side the side menu is currently on. Allowed values: 'left' or 'right'.
12659 * @param {boolean=} is-enabled Whether this side menu is enabled.
12660 * @param {number=} width How many pixels wide the side menu should be. Defaults to 275.
12661 */
12662IonicModule
12663.directive('ionSideMenu', function() {
12664 return {
12665 restrict: 'E',
12666 require: '^ionSideMenus',
12667 scope: true,
12668 compile: function(element, attr) {
12669 angular.isUndefined(attr.isEnabled) && attr.$set('isEnabled', 'true');
12670 angular.isUndefined(attr.width) && attr.$set('width', '275');
12671
12672 element.addClass('menu menu-' + attr.side);
12673
12674 return function($scope, $element, $attr, sideMenuCtrl) {
12675 $scope.side = $attr.side || 'left';
12676
12677 var sideMenu = sideMenuCtrl[$scope.side] = new ionic.views.SideMenu({
12678 width: attr.width,
12679 el: $element[0],
12680 isEnabled: true
12681 });
12682
12683 $scope.$watch($attr.width, function(val) {
12684 var numberVal = +val;
12685 if (numberVal && numberVal == val) {
12686 sideMenu.setWidth(+val);
12687 }
12688 });
12689 $scope.$watch($attr.isEnabled, function(val) {
12690 sideMenu.setIsEnabled(!!val);
12691 });
12692 };
12693 }
12694 };
12695});
12696
12697
12698/**
12699 * @ngdoc directive
12700 * @name ionSideMenuContent
12701 * @module ionic
12702 * @restrict E
12703 * @parent ionic.directive:ionSideMenus
12704 *
12705 * @description
12706 * A container for the main visible content, sibling to one or more
12707 * {@link ionic.directive:ionSideMenu} directives.
12708 *
12709 * @usage
12710 * ```html
12711 * <ion-side-menu-content
12712 * edge-drag-threshold="true"
12713 * drag-content="true">
12714 * </ion-side-menu-content>
12715 * ```
12716 * For a complete side menu example, see the
12717 * {@link ionic.directive:ionSideMenus} documentation.
12718 *
12719 * @param {boolean=} drag-content Whether the content can be dragged. Default true.
12720 * @param {boolean|number=} edge-drag-threshold Whether the content drag can only start if it is below a certain threshold distance from the edge of the screen. Default false. Accepts three types of values:
12721 * - If a non-zero number is given, that many pixels is used as the maximum allowed distance from the edge that starts dragging the side menu.
12722 * - If true is given, the default number of pixels (25) is used as the maximum allowed distance.
12723 * - If false or 0 is given, the edge drag threshold is disabled, and dragging from anywhere on the content is allowed.
12724 *
12725 */
12726IonicModule
12727.directive('ionSideMenuContent', [
12728 '$timeout',
12729 '$ionicGesture',
12730 '$window',
12731function($timeout, $ionicGesture, $window) {
12732
12733 return {
12734 restrict: 'EA', //DEPRECATED 'A'
12735 require: '^ionSideMenus',
12736 scope: true,
12737 compile: function(element, attr) {
12738 element.addClass('menu-content pane');
12739
12740 return { pre: prelink };
12741 function prelink($scope, $element, $attr, sideMenuCtrl) {
12742 var startCoord = null;
12743 var primaryScrollAxis = null;
12744
12745 if (isDefined(attr.dragContent)) {
12746 $scope.$watch(attr.dragContent, function(value) {
12747 sideMenuCtrl.canDragContent(value);
12748 });
12749 } else {
12750 sideMenuCtrl.canDragContent(true);
12751 }
12752
12753 if (isDefined(attr.edgeDragThreshold)) {
12754 $scope.$watch(attr.edgeDragThreshold, function(value) {
12755 sideMenuCtrl.edgeDragThreshold(value);
12756 });
12757 }
12758
12759 // Listen for taps on the content to close the menu
12760 function onContentTap(gestureEvt) {
12761 if (sideMenuCtrl.getOpenAmount() !== 0) {
12762 sideMenuCtrl.close();
12763 gestureEvt.gesture.srcEvent.preventDefault();
12764 startCoord = null;
12765 primaryScrollAxis = null;
12766 } else if (!startCoord) {
12767 startCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
12768 }
12769 }
12770
12771 function onDragX(e) {
12772 if (!sideMenuCtrl.isDraggableTarget(e)) return;
12773
12774 if (getPrimaryScrollAxis(e) == 'x') {
12775 sideMenuCtrl._handleDrag(e);
12776 e.gesture.srcEvent.preventDefault();
12777 }
12778 }
12779
12780 function onDragY(e) {
12781 if (getPrimaryScrollAxis(e) == 'x') {
12782 e.gesture.srcEvent.preventDefault();
12783 }
12784 }
12785
12786 function onDragRelease(e) {
12787 sideMenuCtrl._endDrag(e);
12788 startCoord = null;
12789 primaryScrollAxis = null;
12790 }
12791
12792 function getPrimaryScrollAxis(gestureEvt) {
12793 // gets whether the user is primarily scrolling on the X or Y
12794 // If a majority of the drag has been on the Y since the start of
12795 // the drag, but the X has moved a little bit, it's still a Y drag
12796
12797 if (primaryScrollAxis) {
12798 // we already figured out which way they're scrolling
12799 return primaryScrollAxis;
12800 }
12801
12802 if (gestureEvt && gestureEvt.gesture) {
12803
12804 if (!startCoord) {
12805 // get the starting point
12806 startCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
12807
12808 } else {
12809 // we already have a starting point, figure out which direction they're going
12810 var endCoord = ionic.tap.pointerCoord(gestureEvt.gesture.srcEvent);
12811
12812 var xDistance = Math.abs(endCoord.x - startCoord.x);
12813 var yDistance = Math.abs(endCoord.y - startCoord.y);
12814
12815 var scrollAxis = (xDistance < yDistance ? 'y' : 'x');
12816
12817 if (Math.max(xDistance, yDistance) > 30) {
12818 // ok, we pretty much know which way they're going
12819 // let's lock it in
12820 primaryScrollAxis = scrollAxis;
12821 }
12822
12823 return scrollAxis;
12824 }
12825 }
12826 return 'y';
12827 }
12828
12829 var content = {
12830 element: element[0],
12831 onDrag: function() {},
12832 endDrag: function() {},
12833 setCanScroll: function(canScroll) {
12834 var c = $element[0].querySelector('.scroll');
12835
12836 if (!c) {
12837 return;
12838 }
12839
12840 var content = angular.element(c.parentElement);
12841 if (!content) {
12842 return;
12843 }
12844
12845 // freeze our scroll container if we have one
12846 var scrollScope = content.scope();
12847 scrollScope.scrollCtrl && scrollScope.scrollCtrl.freezeScrollShut(!canScroll);
12848 },
12849 getTranslateX: function() {
12850 return $scope.sideMenuContentTranslateX || 0;
12851 },
12852 setTranslateX: ionic.animationFrameThrottle(function(amount) {
12853 var xTransform = content.offsetX + amount;
12854 $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + xTransform + 'px,0,0)';
12855 $timeout(function() {
12856 $scope.sideMenuContentTranslateX = amount;
12857 });
12858 }),
12859 setMarginLeft: ionic.animationFrameThrottle(function(amount) {
12860 if (amount) {
12861 amount = parseInt(amount, 10);
12862 $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amount + 'px,0,0)';
12863 $element[0].style.width = ($window.innerWidth - amount) + 'px';
12864 content.offsetX = amount;
12865 } else {
12866 $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
12867 $element[0].style.width = '';
12868 content.offsetX = 0;
12869 }
12870 }),
12871 setMarginRight: ionic.animationFrameThrottle(function(amount) {
12872 if (amount) {
12873 amount = parseInt(amount, 10);
12874 $element[0].style.width = ($window.innerWidth - amount) + 'px';
12875 content.offsetX = amount;
12876 } else {
12877 $element[0].style.width = '';
12878 content.offsetX = 0;
12879 }
12880 // reset incase left gets grabby
12881 $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
12882 }),
12883 setMarginLeftAndRight: ionic.animationFrameThrottle(function(amountLeft, amountRight) {
12884 amountLeft = amountLeft && parseInt(amountLeft, 10) || 0;
12885 amountRight = amountRight && parseInt(amountRight, 10) || 0;
12886
12887 var amount = amountLeft + amountRight;
12888
12889 if (amount > 0) {
12890 $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amountLeft + 'px,0,0)';
12891 $element[0].style.width = ($window.innerWidth - amount) + 'px';
12892 content.offsetX = amountLeft;
12893 } else {
12894 $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
12895 $element[0].style.width = '';
12896 content.offsetX = 0;
12897 }
12898 // reset incase left gets grabby
12899 //$element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
12900 }),
12901 enableAnimation: function() {
12902 $scope.animationEnabled = true;
12903 $element[0].classList.add('menu-animated');
12904 },
12905 disableAnimation: function() {
12906 $scope.animationEnabled = false;
12907 $element[0].classList.remove('menu-animated');
12908 },
12909 offsetX: 0
12910 };
12911
12912 sideMenuCtrl.setContent(content);
12913
12914 // add gesture handlers
12915 var gestureOpts = { stop_browser_behavior: false };
12916 gestureOpts.prevent_default_directions = ['left', 'right'];
12917 var contentTapGesture = $ionicGesture.on('tap', onContentTap, $element, gestureOpts);
12918 var dragRightGesture = $ionicGesture.on('dragright', onDragX, $element, gestureOpts);
12919 var dragLeftGesture = $ionicGesture.on('dragleft', onDragX, $element, gestureOpts);
12920 var dragUpGesture = $ionicGesture.on('dragup', onDragY, $element, gestureOpts);
12921 var dragDownGesture = $ionicGesture.on('dragdown', onDragY, $element, gestureOpts);
12922 var releaseGesture = $ionicGesture.on('release', onDragRelease, $element, gestureOpts);
12923
12924 // Cleanup
12925 $scope.$on('$destroy', function() {
12926 if (content) {
12927 content.element = null;
12928 content = null;
12929 }
12930 $ionicGesture.off(dragLeftGesture, 'dragleft', onDragX);
12931 $ionicGesture.off(dragRightGesture, 'dragright', onDragX);
12932 $ionicGesture.off(dragUpGesture, 'dragup', onDragY);
12933 $ionicGesture.off(dragDownGesture, 'dragdown', onDragY);
12934 $ionicGesture.off(releaseGesture, 'release', onDragRelease);
12935 $ionicGesture.off(contentTapGesture, 'tap', onContentTap);
12936 });
12937 }
12938 }
12939 };
12940}]);
12941
12942IonicModule
12943
12944/**
12945 * @ngdoc directive
12946 * @name ionSideMenus
12947 * @module ionic
12948 * @delegate ionic.service:$ionicSideMenuDelegate
12949 * @restrict E
12950 *
12951 * @description
12952 * A container element for side menu(s) and the main content. Allows the left and/or right side menu
12953 * to be toggled by dragging the main content area side to side.
12954 *
12955 * To automatically close an opened menu, you can add the {@link ionic.directive:menuClose} attribute
12956 * directive. The `menu-close` attribute is usually added to links and buttons within
12957 * `ion-side-menu-content`, so that when the element is clicked, the opened side menu will
12958 * automatically close.
12959 *
12960 * "Burger Icon" toggles can be added to the header with the {@link ionic.directive:menuToggle}
12961 * attribute directive. Clicking the toggle will open and close the side menu like the `menu-close`
12962 * directive. The side menu will automatically hide on child pages, but can be overridden with the
12963 * enable-menu-with-back-views attribute mentioned below.
12964 *
12965 * By default, side menus are hidden underneath their side menu content and can be opened by swiping
12966 * the content left or right or by toggling a button to show the side menu. Additionally, by adding the
12967 * {@link ionic.directive:exposeAsideWhen} attribute directive to an
12968 * {@link ionic.directive:ionSideMenu} element directive, a side menu can be given instructions about
12969 * "when" the menu should be exposed (always viewable).
12970 *
12971 * 
12972 *
12973 * For more information on side menus, check out:
12974 *
12975 * - {@link ionic.directive:ionSideMenuContent}
12976 * - {@link ionic.directive:ionSideMenu}
12977 * - {@link ionic.directive:menuToggle}
12978 * - {@link ionic.directive:menuClose}
12979 * - {@link ionic.directive:exposeAsideWhen}
12980 *
12981 * @usage
12982 * To use side menus, add an `<ion-side-menus>` parent element. This will encompass all pages that have a
12983 * side menu, and have at least 2 child elements: 1 `<ion-side-menu-content>` for the center content,
12984 * and one or more `<ion-side-menu>` directives for each side menu(left/right) that you wish to place.
12985 *
12986 * ```html
12987 * <ion-side-menus>
12988 * <!-- Left menu -->
12989 * <ion-side-menu side="left">
12990 * </ion-side-menu>
12991 *
12992 * <ion-side-menu-content>
12993 * <!-- Main content, usually <ion-nav-view> -->
12994 * </ion-side-menu-content>
12995 *
12996 * <!-- Right menu -->
12997 * <ion-side-menu side="right">
12998 * </ion-side-menu>
12999 *
13000 * </ion-side-menus>
13001 * ```
13002 * ```js
13003 * function ContentController($scope, $ionicSideMenuDelegate) {
13004 * $scope.toggleLeft = function() {
13005 * $ionicSideMenuDelegate.toggleLeft();
13006 * };
13007 * }
13008 * ```
13009 *
13010 * @param {bool=} enable-menu-with-back-views Determines whether the side menu is enabled when the
13011 * back button is showing. When set to `false`, any {@link ionic.directive:menuToggle} will be hidden,
13012 * and the user cannot swipe to open the menu. When going back to the root page of the side menu (the
13013 * page without a back button visible), then any menuToggle buttons will show again, and menus will be
13014 * enabled again.
13015 * @param {string=} delegate-handle The handle used to identify this side menu
13016 * with {@link ionic.service:$ionicSideMenuDelegate}.
13017 *
13018 */
13019.directive('ionSideMenus', ['$ionicBody', function($ionicBody) {
13020 return {
13021 restrict: 'ECA',
13022 controller: '$ionicSideMenus',
13023 compile: function(element, attr) {
13024 attr.$set('class', (attr['class'] || '') + ' view');
13025
13026 return { pre: prelink };
13027 function prelink($scope, $element, $attrs, ctrl) {
13028
13029 ctrl.enableMenuWithBackViews($scope.$eval($attrs.enableMenuWithBackViews));
13030
13031 $scope.$on('$ionicExposeAside', function(evt, isAsideExposed) {
13032 if (!$scope.$exposeAside) $scope.$exposeAside = {};
13033 $scope.$exposeAside.active = isAsideExposed;
13034 $ionicBody.enableClass(isAsideExposed, 'aside-open');
13035 });
13036
13037 $scope.$on('$ionicView.beforeEnter', function(ev, d) {
13038 if (d.historyId) {
13039 $scope.$activeHistoryId = d.historyId;
13040 }
13041 });
13042
13043 $scope.$on('$destroy', function() {
13044 $ionicBody.removeClass('menu-open', 'aside-open');
13045 });
13046
13047 }
13048 }
13049 };
13050}]);
13051
13052
13053/**
13054 * @ngdoc directive
13055 * @name ionSlideBox
13056 * @module ionic
13057 * @codepen AjgEB
13058 * @deprecated will be removed in the next Ionic release in favor of the new ion-slides component.
13059 * Don't depend on the internal behavior of this widget.
13060 * @delegate ionic.service:$ionicSlideBoxDelegate
13061 * @restrict E
13062 * @description
13063 * The Slide Box is a multi-page container where each page can be swiped or dragged between:
13064 *
13065 *
13066 * @usage
13067 * ```html
13068 * <ion-slide-box on-slide-changed="slideHasChanged($index)">
13069 * <ion-slide>
13070 * <div class="box blue"><h1>BLUE</h1></div>
13071 * </ion-slide>
13072 * <ion-slide>
13073 * <div class="box yellow"><h1>YELLOW</h1></div>
13074 * </ion-slide>
13075 * <ion-slide>
13076 * <div class="box pink"><h1>PINK</h1></div>
13077 * </ion-slide>
13078 * </ion-slide-box>
13079 * ```
13080 *
13081 * @param {string=} delegate-handle The handle used to identify this slideBox
13082 * with {@link ionic.service:$ionicSlideBoxDelegate}.
13083 * @param {boolean=} does-continue Whether the slide box should loop.
13084 * @param {boolean=} auto-play Whether the slide box should automatically slide. Default true if does-continue is true.
13085 * @param {number=} slide-interval How many milliseconds to wait to change slides (if does-continue is true). Defaults to 4000.
13086 * @param {boolean=} show-pager Whether a pager should be shown for this slide box. Accepts expressions via `show-pager="{{shouldShow()}}"`. Defaults to true.
13087 * @param {expression=} pager-click Expression to call when a pager is clicked (if show-pager is true). Is passed the 'index' variable.
13088 * @param {expression=} on-slide-changed Expression called whenever the slide is changed. Is passed an '$index' variable.
13089 * @param {expression=} active-slide Model to bind the current slide index to.
13090 */
13091IonicModule
13092.directive('ionSlideBox', [
13093 '$animate',
13094 '$timeout',
13095 '$compile',
13096 '$ionicSlideBoxDelegate',
13097 '$ionicHistory',
13098 '$ionicScrollDelegate',
13099function($animate, $timeout, $compile, $ionicSlideBoxDelegate, $ionicHistory, $ionicScrollDelegate) {
13100 return {
13101 restrict: 'E',
13102 replace: true,
13103 transclude: true,
13104 scope: {
13105 autoPlay: '=',
13106 doesContinue: '@',
13107 slideInterval: '@',
13108 showPager: '@',
13109 pagerClick: '&',
13110 disableScroll: '@',
13111 onSlideChanged: '&',
13112 activeSlide: '=?',
13113 bounce: '@'
13114 },
13115 controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
13116 var _this = this;
13117
13118 var continuous = $scope.$eval($scope.doesContinue) === true;
13119 var bouncing = ($scope.$eval($scope.bounce) !== false); //Default to true
13120 var shouldAutoPlay = isDefined($attrs.autoPlay) ? !!$scope.autoPlay : false;
13121 var slideInterval = shouldAutoPlay ? $scope.$eval($scope.slideInterval) || 4000 : 0;
13122
13123 var slider = new ionic.views.Slider({
13124 el: $element[0],
13125 auto: slideInterval,
13126 continuous: continuous,
13127 startSlide: $scope.activeSlide,
13128 bouncing: bouncing,
13129 slidesChanged: function() {
13130 $scope.currentSlide = slider.currentIndex();
13131
13132 // Try to trigger a digest
13133 $timeout(function() {});
13134 },
13135 callback: function(slideIndex) {
13136 $scope.currentSlide = slideIndex;
13137 $scope.onSlideChanged({ index: $scope.currentSlide, $index: $scope.currentSlide});
13138 $scope.$parent.$broadcast('slideBox.slideChanged', slideIndex);
13139 $scope.activeSlide = slideIndex;
13140 // Try to trigger a digest
13141 $timeout(function() {});
13142 },
13143 onDrag: function() {
13144 freezeAllScrolls(true);
13145 },
13146 onDragEnd: function() {
13147 freezeAllScrolls(false);
13148 }
13149 });
13150
13151 function freezeAllScrolls(shouldFreeze) {
13152 if (shouldFreeze && !_this.isScrollFreeze) {
13153 $ionicScrollDelegate.freezeAllScrolls(shouldFreeze);
13154
13155 } else if (!shouldFreeze && _this.isScrollFreeze) {
13156 $ionicScrollDelegate.freezeAllScrolls(false);
13157 }
13158 _this.isScrollFreeze = shouldFreeze;
13159 }
13160
13161 slider.enableSlide($scope.$eval($attrs.disableScroll) !== true);
13162
13163 $scope.$watch('activeSlide', function(nv) {
13164 if (isDefined(nv)) {
13165 slider.slide(nv);
13166 }
13167 });
13168
13169 $scope.$on('slideBox.nextSlide', function() {
13170 slider.next();
13171 });
13172
13173 $scope.$on('slideBox.prevSlide', function() {
13174 slider.prev();
13175 });
13176
13177 $scope.$on('slideBox.setSlide', function(e, index) {
13178 slider.slide(index);
13179 });
13180
13181 //Exposed for testing
13182 this.__slider = slider;
13183
13184 var deregisterInstance = $ionicSlideBoxDelegate._registerInstance(
13185 slider, $attrs.delegateHandle, function() {
13186 return $ionicHistory.isActiveScope($scope);
13187 }
13188 );
13189 $scope.$on('$destroy', function() {
13190 deregisterInstance();
13191 slider.kill();
13192 });
13193
13194 this.slidesCount = function() {
13195 return slider.slidesCount();
13196 };
13197
13198 this.onPagerClick = function(index) {
13199 $scope.pagerClick({index: index});
13200 };
13201
13202 $timeout(function() {
13203 slider.load();
13204 });
13205 }],
13206 template: '<div class="slider">' +
13207 '<div class="slider-slides" ng-transclude>' +
13208 '</div>' +
13209 '</div>',
13210
13211 link: function($scope, $element, $attr) {
13212 // Disable ngAnimate for slidebox and its children
13213 $animate.enabled($element, false);
13214
13215 // if showPager is undefined, show the pager
13216 if (!isDefined($attr.showPager)) {
13217 $scope.showPager = true;
13218 getPager().toggleClass('hide', !true);
13219 }
13220
13221 $attr.$observe('showPager', function(show) {
13222 if (show === undefined) return;
13223 show = $scope.$eval(show);
13224 getPager().toggleClass('hide', !show);
13225 });
13226
13227 var pager;
13228 function getPager() {
13229 if (!pager) {
13230 var childScope = $scope.$new();
13231 pager = jqLite('<ion-pager></ion-pager>');
13232 $element.append(pager);
13233 pager = $compile(pager)(childScope);
13234 }
13235 return pager;
13236 }
13237 }
13238 };
13239}])
13240.directive('ionSlide', function() {
13241 return {
13242 restrict: 'E',
13243 require: '?^ionSlideBox',
13244 compile: function(element) {
13245 element.addClass('slider-slide');
13246 }
13247 };
13248})
13249
13250.directive('ionPager', function() {
13251 return {
13252 restrict: 'E',
13253 replace: true,
13254 require: '^ionSlideBox',
13255 template: '<div class="slider-pager"><span class="slider-pager-page" ng-repeat="slide in numSlides() track by $index" ng-class="{active: $index == currentSlide}" ng-click="pagerClick($index)"><i class="icon ion-record"></i></span></div>',
13256 link: function($scope, $element, $attr, slideBox) {
13257 var selectPage = function(index) {
13258 var children = $element[0].children;
13259 var length = children.length;
13260 for (var i = 0; i < length; i++) {
13261 if (i == index) {
13262 children[i].classList.add('active');
13263 } else {
13264 children[i].classList.remove('active');
13265 }
13266 }
13267 };
13268
13269 $scope.pagerClick = function(index) {
13270 slideBox.onPagerClick(index);
13271 };
13272
13273 $scope.numSlides = function() {
13274 return new Array(slideBox.slidesCount());
13275 };
13276
13277 $scope.$watch('currentSlide', function(v) {
13278 selectPage(v);
13279 });
13280 }
13281 };
13282
13283});
13284
13285
13286/**
13287 * @ngdoc directive
13288 * @name ionSlides
13289 * @module ionic
13290 * @delegate ionic.service:$ionicSlideBoxDelegate
13291 * @restrict E
13292 * @description
13293 * The Slides component is a powerful multi-page container where each page can be swiped or dragged between.
13294 *
13295 * Note: this is a new version of the Ionic Slide Box based on the [Swiper](http://www.idangero.us/swiper/#.Vmc1J-ODFBc) widget from
13296 * [idangerous](http://www.idangero.us/).
13297 *
13298 * 
13299 *
13300 * @usage
13301 * ```html
13302 * <ion-content scroll="false">
13303 * <ion-slides options="options" slider="data.slider">
13304 * <ion-slide-page>
13305 * <div class="box blue"><h1>BLUE</h1></div>
13306 * </ion-slide-page>
13307 * <ion-slide-page>
13308 * <div class="box yellow"><h1>YELLOW</h1></div>
13309 * </ion-slide-page>
13310 * <ion-slide-page>
13311 * <div class="box pink"><h1>PINK</h1></div>
13312 * </ion-slide-page>
13313 * </ion-slides>
13314 * </ion-content>
13315 * ```
13316 *
13317 * ```js
13318 * $scope.options = {
13319 * loop: false,
13320 * effect: 'fade',
13321 * speed: 500,
13322 * }
13323 *
13324 * $scope.$on("$ionicSlides.sliderInitialized", function(event, data){
13325 * // data.slider is the instance of Swiper
13326 * $scope.slider = data.slider;
13327 * });
13328 *
13329 * $scope.$on("$ionicSlides.slideChangeStart", function(event, data){
13330 * console.log('Slide change is beginning');
13331 * });
13332 *
13333 * $scope.$on("$ionicSlides.slideChangeEnd", function(event, data){
13334 * // note: the indexes are 0-based
13335 * $scope.activeIndex = data.activeIndex;
13336 * $scope.previousIndex = data.previousIndex;
13337 * });
13338 *
13339 * ```
13340 *
13341 * ## Slide Events
13342 *
13343 * The slides component dispatches events when the active slide changes
13344 *
13345 * <table class="table">
13346 * <tr>
13347 * <td><code>$ionicSlides.slideChangeStart</code></td>
13348 * <td>This event is emitted when a slide change begins</td>
13349 * </tr>
13350 * <tr>
13351 * <td><code>$ionicSlides.slideChangeEnd</code></td>
13352 * <td>This event is emitted when a slide change completes</td>
13353 * </tr>
13354 * <tr>
13355 * <td><code>$ionicSlides.sliderInitialized</code></td>
13356 * <td>This event is emitted when the slider is initialized. It provides access to an instance of the slider.</td>
13357 * </tr>
13358 * </table>
13359 *
13360 *
13361 * ## Updating Slides Dynamically
13362 * When applying data to the slider at runtime, typically everything will work as expected.
13363 *
13364 * In the event that the slides are looped, use the `updateLoop` method on the slider to ensure the slides update correctly.
13365 *
13366 * ```
13367 * $scope.$on("$ionicSlides.sliderInitialized", function(event, data){
13368 * // grab an instance of the slider
13369 * $scope.slider = data.slider;
13370 * });
13371 *
13372 * function dataChangeHandler(){
13373 * // call this function when data changes, such as an HTTP request, etc
13374 * if ( $scope.slider ){
13375 * $scope.slider.updateLoop();
13376 * }
13377 * }
13378 * ```
13379 *
13380 */
13381IonicModule
13382.directive('ionSlides', [
13383 '$animate',
13384 '$timeout',
13385 '$compile',
13386function($animate, $timeout, $compile) {
13387 return {
13388 restrict: 'E',
13389 transclude: true,
13390 scope: {
13391 options: '=',
13392 slider: '='
13393 },
13394 template: '<div class="swiper-container">' +
13395 '<div class="swiper-wrapper" ng-transclude>' +
13396 '</div>' +
13397 '<div ng-hide="!showPager" class="swiper-pagination"></div>' +
13398 '</div>',
13399 controller: ['$scope', '$element', function($scope, $element) {
13400 var _this = this;
13401
13402 this.update = function() {
13403 $timeout(function() {
13404 if (!_this.__slider) {
13405 return;
13406 }
13407
13408 _this.__slider.update();
13409 if (_this._options.loop) {
13410 _this.__slider.createLoop();
13411 }
13412
13413 var slidesLength = _this.__slider.slides.length;
13414
13415 // Don't allow pager to show with > 10 slides
13416 if (slidesLength > 10) {
13417 $scope.showPager = false;
13418 }
13419
13420 // When slide index is greater than total then slide to last index
13421 if (_this.__slider.activeIndex > slidesLength - 1) {
13422 _this.__slider.slideTo(slidesLength - 1);
13423 }
13424 });
13425 };
13426
13427 this.rapidUpdate = ionic.debounce(function() {
13428 _this.update();
13429 }, 50);
13430
13431 this.getSlider = function() {
13432 return _this.__slider;
13433 };
13434
13435 var options = $scope.options || {};
13436
13437 var newOptions = angular.extend({
13438 pagination: $element.children().children()[1],
13439 paginationClickable: true,
13440 lazyLoading: true,
13441 preloadImages: false
13442 }, options);
13443
13444 this._options = newOptions;
13445
13446 $timeout(function() {
13447 var slider = new ionic.views.Swiper($element.children()[0], newOptions, $scope, $compile);
13448
13449 $scope.$emit("$ionicSlides.sliderInitialized", { slider: slider });
13450
13451 _this.__slider = slider;
13452 $scope.slider = _this.__slider;
13453
13454 $scope.$on('$destroy', function() {
13455 slider.destroy();
13456 _this.__slider = null;
13457 });
13458 });
13459
13460 $timeout(function() {
13461 // if it's a loop, render the slides again just incase
13462 _this.rapidUpdate();
13463 }, 200);
13464
13465 }],
13466
13467 link: function($scope) {
13468 $scope.showPager = true;
13469 // Disable ngAnimate for slidebox and its children
13470 //$animate.enabled(false, $element);
13471 }
13472 };
13473}])
13474.directive('ionSlidePage', [function() {
13475 return {
13476 restrict: 'E',
13477 require: '?^ionSlides',
13478 transclude: true,
13479 replace: true,
13480 template: '<div class="swiper-slide" ng-transclude></div>',
13481 link: function($scope, $element, $attr, ionSlidesCtrl) {
13482 ionSlidesCtrl.rapidUpdate();
13483
13484 $scope.$on('$destroy', function() {
13485 ionSlidesCtrl.rapidUpdate();
13486 });
13487 }
13488 };
13489}]);
13490
13491/**
13492* @ngdoc directive
13493* @name ionSpinner
13494* @module ionic
13495* @restrict E
13496 *
13497 * @description
13498 * The `ionSpinner` directive provides a variety of animated spinners.
13499 * Spinners enables you to give your users feedback that the app is
13500 * processing/thinking/waiting/chillin' out, or whatever you'd like it to indicate.
13501 * By default, the {@link ionic.directive:ionRefresher} feature uses this spinner, rather
13502 * than rotating font icons (previously included in [ionicons](http://ionicons.com/)).
13503 * While font icons are great for simple or stationary graphics, they're not suited to
13504 * provide great animations, which is why Ionic uses SVG instead.
13505 *
13506 * Ionic offers ten spinners out of the box, and by default, it will use the appropriate spinner
13507 * for the platform on which it's running. Under the hood, the `ionSpinner` directive dynamically
13508 * builds the required SVG element, which allows Ionic to provide all ten of the animated SVGs
13509 * within 3KB.
13510 *
13511 * <style>
13512 * .spinner-table {
13513 * max-width: 280px;
13514 * }
13515 * .spinner-table tbody > tr > th, .spinner-table tbody > tr > td {
13516 * vertical-align: middle;
13517 * width: 42px;
13518 * height: 42px;
13519 * }
13520 * .spinner {
13521 * stroke: #444;
13522 * fill: #444; }
13523 * .spinner svg {
13524 * width: 28px;
13525 * height: 28px; }
13526 * .spinner.spinner-inverse {
13527 * stroke: #fff;
13528 * fill: #fff; }
13529 *
13530 * .spinner-android {
13531 * stroke: #4b8bf4; }
13532 *
13533 * .spinner-ios, .spinner-ios-small {
13534 * stroke: #69717d; }
13535 *
13536 * .spinner-spiral .stop1 {
13537 * stop-color: #fff;
13538 * stop-opacity: 0; }
13539 * .spinner-spiral.spinner-inverse .stop1 {
13540 * stop-color: #000; }
13541 * .spinner-spiral.spinner-inverse .stop2 {
13542 * stop-color: #fff; }
13543 * </style>
13544 *
13545 * <script src="http://code.ionicframework.com/nightly/js/ionic.bundle.min.js"></script>
13546 * <table class="table spinner-table" ng-app="ionic">
13547 * <tr>
13548 * <th>
13549 * <code>android</code>
13550 * </th>
13551 * <td>
13552 * <ion-spinner icon="android"></ion-spinner>
13553 * </td>
13554 * </tr>
13555 * <tr>
13556 * <th>
13557 * <code>ios</code>
13558 * </th>
13559 * <td>
13560 * <ion-spinner icon="ios"></ion-spinner>
13561 * </td>
13562 * </tr>
13563 * <tr>
13564 * <th>
13565 * <code>ios-small</code>
13566 * </th>
13567 * <td>
13568 * <ion-spinner icon="ios-small"></ion-spinner>
13569 * </td>
13570 * </tr>
13571 * <tr>
13572 * <th>
13573 * <code>bubbles</code>
13574 * </th>
13575 * <td>
13576 * <ion-spinner icon="bubbles"></ion-spinner>
13577 * </td>
13578 * </tr>
13579 * <tr>
13580 * <th>
13581 * <code>circles</code>
13582 * </th>
13583 * <td>
13584 * <ion-spinner icon="circles"></ion-spinner>
13585 * </td>
13586 * </tr>
13587 * <tr>
13588 * <th>
13589 * <code>crescent</code>
13590 * </th>
13591 * <td>
13592 * <ion-spinner icon="crescent"></ion-spinner>
13593 * </td>
13594 * </tr>
13595 * <tr>
13596 * <th>
13597 * <code>dots</code>
13598 * </th>
13599 * <td>
13600 * <ion-spinner icon="dots"></ion-spinner>
13601 * </td>
13602 * </tr>
13603 * <tr>
13604 * <th>
13605 * <code>lines</code>
13606 * </th>
13607 * <td>
13608 * <ion-spinner icon="lines"></ion-spinner>
13609 * </td>
13610 * </tr>
13611 * <tr>
13612 * <th>
13613 * <code>ripple</code>
13614 * </th>
13615 * <td>
13616 * <ion-spinner icon="ripple"></ion-spinner>
13617 * </td>
13618 * </tr>
13619 * <tr>
13620 * <th>
13621 * <code>spiral</code>
13622 * </th>
13623 * <td>
13624 * <ion-spinner icon="spiral"></ion-spinner>
13625 * </td>
13626 * </tr>
13627 * </table>
13628 *
13629 * Each spinner uses SVG with SMIL animations, however, the Android spinner also uses JavaScript
13630 * so it also works on Android 4.0-4.3. Additionally, each spinner can be styled with CSS,
13631 * and scaled to any size.
13632 *
13633 *
13634 * @usage
13635 * The following code would use the default spinner for the platform it's running from. If it's neither
13636 * iOS or Android, it'll default to use `ios`.
13637 *
13638 * ```html
13639 * <ion-spinner></ion-spinner>
13640 * ```
13641 *
13642 * By setting the `icon` attribute, you can specify which spinner to use, no matter what
13643 * the platform is.
13644 *
13645 * ```html
13646 * <ion-spinner icon="spiral"></ion-spinner>
13647 * ```
13648 *
13649 * ## Spinner Colors
13650 * Like with most of Ionic's other components, spinners can also be styled using
13651 * Ionic's standard color naming convention. For example:
13652 *
13653 * ```html
13654 * <ion-spinner class="spinner-energized"></ion-spinner>
13655 * ```
13656 *
13657 *
13658 * ## Styling SVG with CSS
13659 * One cool thing about SVG is its ability to be styled with CSS! Some of the properties
13660 * have different names, for example, SVG uses the term `stroke` instead of `border`, and
13661 * `fill` instead of `background-color`.
13662 *
13663 * ```css
13664 * .spinner svg {
13665 * width: 28px;
13666 * height: 28px;
13667 * stroke: #444;
13668 * fill: #444;
13669 * }
13670 * ```
13671 *
13672*/
13673IonicModule
13674.directive('ionSpinner', function() {
13675 return {
13676 restrict: 'E',
13677 controller: '$ionicSpinner',
13678 link: function($scope, $element, $attrs, ctrl) {
13679 var spinnerName = ctrl.init();
13680 $element.addClass('spinner spinner-' + spinnerName);
13681
13682 $element.on('$destroy', function onDestroy() {
13683 ctrl.stop();
13684 });
13685 }
13686 };
13687});
13688
13689/**
13690 * @ngdoc directive
13691 * @name ionTab
13692 * @module ionic
13693 * @restrict E
13694 * @parent ionic.directive:ionTabs
13695 *
13696 * @description
13697 * Contains a tab's content. The content only exists while the given tab is selected.
13698 *
13699 * Each ionTab has its own view history.
13700 *
13701 * @usage
13702 * ```html
13703 * <ion-tab
13704 * title="Tab!"
13705 * icon="my-icon"
13706 * href="#/tab/tab-link"
13707 * on-select="onTabSelected()"
13708 * on-deselect="onTabDeselected()">
13709 * </ion-tab>
13710 * ```
13711 * For a complete, working tab bar example, see the {@link ionic.directive:ionTabs} documentation.
13712 *
13713 * @param {string} title The title of the tab.
13714 * @param {string=} href The link that this tab will navigate to when tapped.
13715 * @param {string=} icon The icon of the tab. If given, this will become the default for icon-on and icon-off.
13716 * @param {string=} icon-on The icon of the tab while it is selected.
13717 * @param {string=} icon-off The icon of the tab while it is not selected.
13718 * @param {expression=} badge The badge to put on this tab (usually a number).
13719 * @param {expression=} badge-style The style of badge to put on this tab (eg: badge-positive).
13720 * @param {expression=} on-select Called when this tab is selected.
13721 * @param {expression=} on-deselect Called when this tab is deselected.
13722 * @param {expression=} ng-click By default, the tab will be selected on click. If ngClick is set, it will not. You can explicitly switch tabs using {@link ionic.service:$ionicTabsDelegate#select $ionicTabsDelegate.select()}.
13723 * @param {expression=} hidden Whether the tab is to be hidden or not.
13724 * @param {expression=} disabled Whether the tab is to be disabled or not.
13725 */
13726IonicModule
13727.directive('ionTab', [
13728 '$compile',
13729 '$ionicConfig',
13730 '$ionicBind',
13731 '$ionicViewSwitcher',
13732function($compile, $ionicConfig, $ionicBind, $ionicViewSwitcher) {
13733
13734 //Returns ' key="value"' if value exists
13735 function attrStr(k, v) {
13736 return isDefined(v) ? ' ' + k + '="' + v + '"' : '';
13737 }
13738 return {
13739 restrict: 'E',
13740 require: ['^ionTabs', 'ionTab'],
13741 controller: '$ionicTab',
13742 scope: true,
13743 compile: function(element, attr) {
13744
13745 //We create the tabNavTemplate in the compile phase so that the
13746 //attributes we pass down won't be interpolated yet - we want
13747 //to pass down the 'raw' versions of the attributes
13748 var tabNavTemplate = '<ion-tab-nav' +
13749 attrStr('ng-click', attr.ngClick) +
13750 attrStr('title', attr.title) +
13751 attrStr('icon', attr.icon) +
13752 attrStr('icon-on', attr.iconOn) +
13753 attrStr('icon-off', attr.iconOff) +
13754 attrStr('badge', attr.badge) +
13755 attrStr('badge-style', attr.badgeStyle) +
13756 attrStr('hidden', attr.hidden) +
13757 attrStr('disabled', attr.disabled) +
13758 attrStr('class', attr['class']) +
13759 '></ion-tab-nav>';
13760
13761 //Remove the contents of the element so we can compile them later, if tab is selected
13762 var tabContentEle = document.createElement('div');
13763 for (var x = 0; x < element[0].children.length; x++) {
13764 tabContentEle.appendChild(element[0].children[x].cloneNode(true));
13765 }
13766 var childElementCount = tabContentEle.childElementCount;
13767 element.empty();
13768
13769 var navViewName, isNavView;
13770 if (childElementCount) {
13771 if (tabContentEle.children[0].tagName === 'ION-NAV-VIEW') {
13772 // get the name if it's a nav-view
13773 navViewName = tabContentEle.children[0].getAttribute('name');
13774 tabContentEle.children[0].classList.add('view-container');
13775 isNavView = true;
13776 }
13777 if (childElementCount === 1) {
13778 // make the 1 child element the primary tab content container
13779 tabContentEle = tabContentEle.children[0];
13780 }
13781 if (!isNavView) tabContentEle.classList.add('pane');
13782 tabContentEle.classList.add('tab-content');
13783 }
13784
13785 return function link($scope, $element, $attr, ctrls) {
13786 var childScope;
13787 var childElement;
13788 var tabsCtrl = ctrls[0];
13789 var tabCtrl = ctrls[1];
13790 var isTabContentAttached = false;
13791 $scope.$tabSelected = false;
13792
13793 $ionicBind($scope, $attr, {
13794 onSelect: '&',
13795 onDeselect: '&',
13796 title: '@',
13797 uiSref: '@',
13798 href: '@'
13799 });
13800
13801 tabsCtrl.add($scope);
13802 $scope.$on('$destroy', function() {
13803 if (!$scope.$tabsDestroy) {
13804 // if the containing ionTabs directive is being destroyed
13805 // then don't bother going through the controllers remove
13806 // method, since remove will reset the active tab as each tab
13807 // is being destroyed, causing unnecessary view loads and transitions
13808 tabsCtrl.remove($scope);
13809 }
13810 tabNavElement.isolateScope().$destroy();
13811 tabNavElement.remove();
13812 tabNavElement = tabContentEle = childElement = null;
13813 });
13814
13815 //Remove title attribute so browser-tooltip does not apear
13816 $element[0].removeAttribute('title');
13817
13818 if (navViewName) {
13819 tabCtrl.navViewName = $scope.navViewName = navViewName;
13820 }
13821 $scope.$on('$stateChangeSuccess', selectIfMatchesState);
13822 selectIfMatchesState();
13823 function selectIfMatchesState() {
13824 if (tabCtrl.tabMatchesState()) {
13825 tabsCtrl.select($scope, false);
13826 }
13827 }
13828
13829 var tabNavElement = jqLite(tabNavTemplate);
13830 tabNavElement.data('$ionTabsController', tabsCtrl);
13831 tabNavElement.data('$ionTabController', tabCtrl);
13832 tabsCtrl.$tabsElement.append($compile(tabNavElement)($scope));
13833
13834
13835 function tabSelected(isSelected) {
13836 if (isSelected && childElementCount) {
13837 // this tab is being selected
13838
13839 // check if the tab is already in the DOM
13840 // only do this if the tab has child elements
13841 if (!isTabContentAttached) {
13842 // tab should be selected and is NOT in the DOM
13843 // create a new scope and append it
13844 childScope = $scope.$new();
13845 childElement = jqLite(tabContentEle);
13846 $ionicViewSwitcher.viewEleIsActive(childElement, true);
13847 tabsCtrl.$element.append(childElement);
13848 $compile(childElement)(childScope);
13849 isTabContentAttached = true;
13850 }
13851
13852 // remove the hide class so the tabs content shows up
13853 $ionicViewSwitcher.viewEleIsActive(childElement, true);
13854
13855 } else if (isTabContentAttached && childElement) {
13856 // this tab should NOT be selected, and it is already in the DOM
13857
13858 if ($ionicConfig.views.maxCache() > 0) {
13859 // keep the tabs in the DOM, only css hide it
13860 $ionicViewSwitcher.viewEleIsActive(childElement, false);
13861
13862 } else {
13863 // do not keep tabs in the DOM
13864 destroyTab();
13865 }
13866
13867 }
13868 }
13869
13870 function destroyTab() {
13871 childScope && childScope.$destroy();
13872 isTabContentAttached && childElement && childElement.remove();
13873 tabContentEle.innerHTML = '';
13874 isTabContentAttached = childScope = childElement = null;
13875 }
13876
13877 $scope.$watch('$tabSelected', tabSelected);
13878
13879 $scope.$on('$ionicView.afterEnter', function() {
13880 $ionicViewSwitcher.viewEleIsActive(childElement, $scope.$tabSelected);
13881 });
13882
13883 $scope.$on('$ionicView.clearCache', function() {
13884 if (!$scope.$tabSelected) {
13885 destroyTab();
13886 }
13887 });
13888
13889 };
13890 }
13891 };
13892}]);
13893
13894IonicModule
13895.directive('ionTabNav', [function() {
13896 return {
13897 restrict: 'E',
13898 replace: true,
13899 require: ['^ionTabs', '^ionTab'],
13900 template:
13901 '<a ng-class="{\'has-badge\':badge, \'tab-hidden\':isHidden(), \'tab-item-active\': isTabActive()}" ' +
13902 ' ng-disabled="disabled()" class="tab-item">' +
13903 '<span class="badge {{badgeStyle}}" ng-if="badge">{{badge}}</span>' +
13904 '<i class="icon {{getIcon()}}" ng-if="getIcon()"></i>' +
13905 '<span class="tab-title" ng-bind-html="title"></span>' +
13906 '</a>',
13907 scope: {
13908 title: '@',
13909 icon: '@',
13910 iconOn: '@',
13911 iconOff: '@',
13912 badge: '=',
13913 hidden: '@',
13914 disabled: '&',
13915 badgeStyle: '@',
13916 'class': '@'
13917 },
13918 link: function($scope, $element, $attrs, ctrls) {
13919 var tabsCtrl = ctrls[0],
13920 tabCtrl = ctrls[1];
13921
13922 //Remove title attribute so browser-tooltip does not apear
13923 $element[0].removeAttribute('title');
13924
13925 $scope.selectTab = function(e) {
13926 e.preventDefault();
13927 tabsCtrl.select(tabCtrl.$scope, true);
13928 };
13929 if (!$attrs.ngClick) {
13930 $element.on('click', function(event) {
13931 $scope.$apply(function() {
13932 $scope.selectTab(event);
13933 });
13934 });
13935 }
13936
13937 $scope.isHidden = function() {
13938 if ($attrs.hidden === 'true' || $attrs.hidden === true) return true;
13939 return false;
13940 };
13941
13942 $scope.getIconOn = function() {
13943 return $scope.iconOn || $scope.icon;
13944 };
13945 $scope.getIconOff = function() {
13946 return $scope.iconOff || $scope.icon;
13947 };
13948
13949 $scope.isTabActive = function() {
13950 return tabsCtrl.selectedTab() === tabCtrl.$scope;
13951 };
13952
13953 $scope.getIcon = function() {
13954 if ( tabsCtrl.selectedTab() === tabCtrl.$scope ) {
13955 // active
13956 return $scope.iconOn || $scope.icon;
13957 }
13958 else {
13959 // inactive
13960 return $scope.iconOff || $scope.icon;
13961 }
13962 };
13963 }
13964 };
13965}]);
13966
13967/**
13968 * @ngdoc directive
13969 * @name ionTabs
13970 * @module ionic
13971 * @delegate ionic.service:$ionicTabsDelegate
13972 * @restrict E
13973 * @codepen odqCz
13974 *
13975 * @description
13976 * Powers a multi-tabbed interface with a Tab Bar and a set of "pages" that can be tabbed
13977 * through.
13978 *
13979 * Assign any [tabs class](/docs/components#tabs) to the element to define
13980 * its look and feel.
13981 *
13982 * For iOS, tabs will appear at the bottom of the screen. For Android, tabs will be at the top
13983 * of the screen, below the nav-bar. This follows each OS's design specification, but can be
13984 * configured with the {@link ionic.provider:$ionicConfigProvider}.
13985 *
13986 * See the {@link ionic.directive:ionTab} directive's documentation for more details on
13987 * individual tabs.
13988 *
13989 * Note: do not place ion-tabs inside of an ion-content element; it has been known to cause a
13990 * certain CSS bug.
13991 *
13992 * @usage
13993 * ```html
13994 * <ion-tabs class="tabs-positive tabs-icon-top">
13995 *
13996 * <ion-tab title="Home" icon-on="ion-ios-filing" icon-off="ion-ios-filing-outline">
13997 * <!-- Tab 1 content -->
13998 * </ion-tab>
13999 *
14000 * <ion-tab title="About" icon-on="ion-ios-clock" icon-off="ion-ios-clock-outline">
14001 * <!-- Tab 2 content -->
14002 * </ion-tab>
14003 *
14004 * <ion-tab title="Settings" icon-on="ion-ios-gear" icon-off="ion-ios-gear-outline">
14005 * <!-- Tab 3 content -->
14006 * </ion-tab>
14007 *
14008 * </ion-tabs>
14009 * ```
14010 *
14011 * @param {string=} delegate-handle The handle used to identify these tabs
14012 * with {@link ionic.service:$ionicTabsDelegate}.
14013 */
14014
14015IonicModule
14016.directive('ionTabs', [
14017 '$ionicTabsDelegate',
14018 '$ionicConfig',
14019function($ionicTabsDelegate, $ionicConfig) {
14020 return {
14021 restrict: 'E',
14022 scope: true,
14023 controller: '$ionicTabs',
14024 compile: function(tElement) {
14025 //We cannot use regular transclude here because it breaks element.data()
14026 //inheritance on compile
14027 var innerElement = jqLite('<div class="tab-nav tabs">');
14028 innerElement.append(tElement.contents());
14029
14030 tElement.append(innerElement)
14031 .addClass('tabs-' + $ionicConfig.tabs.position() + ' tabs-' + $ionicConfig.tabs.style());
14032
14033 return { pre: prelink, post: postLink };
14034 function prelink($scope, $element, $attr, tabsCtrl) {
14035 var deregisterInstance = $ionicTabsDelegate._registerInstance(
14036 tabsCtrl, $attr.delegateHandle, tabsCtrl.hasActiveScope
14037 );
14038
14039 tabsCtrl.$scope = $scope;
14040 tabsCtrl.$element = $element;
14041 tabsCtrl.$tabsElement = jqLite($element[0].querySelector('.tabs'));
14042
14043 $scope.$watch(function() { return $element[0].className; }, function(value) {
14044 var isTabsTop = value.indexOf('tabs-top') !== -1;
14045 var isHidden = value.indexOf('tabs-item-hide') !== -1;
14046 $scope.$hasTabs = !isTabsTop && !isHidden;
14047 $scope.$hasTabsTop = isTabsTop && !isHidden;
14048 $scope.$emit('$ionicTabs.top', $scope.$hasTabsTop);
14049 });
14050
14051 function emitLifecycleEvent(ev, data) {
14052 ev.stopPropagation();
14053 var previousSelectedTab = tabsCtrl.previousSelectedTab();
14054 if (previousSelectedTab) {
14055 previousSelectedTab.$broadcast(ev.name.replace('NavView', 'Tabs'), data);
14056 }
14057 }
14058
14059 $scope.$on('$ionicNavView.beforeLeave', emitLifecycleEvent);
14060 $scope.$on('$ionicNavView.afterLeave', emitLifecycleEvent);
14061 $scope.$on('$ionicNavView.leave', emitLifecycleEvent);
14062
14063 $scope.$on('$destroy', function() {
14064 // variable to inform child tabs that they're all being blown away
14065 // used so that while destorying an individual tab, each one
14066 // doesn't select the next tab as the active one, which causes unnecessary
14067 // loading of tab views when each will eventually all go away anyway
14068 $scope.$tabsDestroy = true;
14069 deregisterInstance();
14070 tabsCtrl.$tabsElement = tabsCtrl.$element = tabsCtrl.$scope = innerElement = null;
14071 delete $scope.$hasTabs;
14072 delete $scope.$hasTabsTop;
14073 });
14074 }
14075
14076 function postLink($scope, $element, $attr, tabsCtrl) {
14077 if (!tabsCtrl.selectedTab()) {
14078 // all the tabs have been added
14079 // but one hasn't been selected yet
14080 tabsCtrl.select(0);
14081 }
14082 }
14083 }
14084 };
14085}]);
14086
14087/**
14088* @ngdoc directive
14089* @name ionTitle
14090* @module ionic
14091* @restrict E
14092*
14093* Used for titles in header and nav bars. New in 1.2
14094*
14095* Identical to <div class="title"> but with future compatibility for Ionic 2
14096*
14097* @usage
14098*
14099* ```html
14100* <ion-nav-bar>
14101* <ion-title>Hello</ion-title>
14102* <ion-nav-bar>
14103* ```
14104*/
14105IonicModule
14106.directive('ionTitle', [function() {
14107 return {
14108 restrict: 'E',
14109 compile: function(element) {
14110 element.addClass('title');
14111 }
14112 };
14113}]);
14114
14115/**
14116 * @ngdoc directive
14117 * @name ionToggle
14118 * @module ionic
14119 * @codepen tfAzj
14120 * @restrict E
14121 *
14122 * @description
14123 * A toggle is an animated switch which binds a given model to a boolean.
14124 *
14125 * Allows dragging of the switch's nub.
14126 *
14127 * The toggle behaves like any [AngularJS checkbox](http://docs.angularjs.org/api/ng/input/input[checkbox]) otherwise.
14128 *
14129 * @param toggle-class {string=} Sets the CSS class on the inner `label.toggle` element created by the directive.
14130 *
14131 * @usage
14132 * Below is an example of a toggle directive which is wired up to the `airplaneMode` model
14133 * and has the `toggle-calm` CSS class assigned to the inner element.
14134 *
14135 * ```html
14136 * <ion-toggle ng-model="airplaneMode" toggle-class="toggle-calm">Airplane Mode</ion-toggle>
14137 * ```
14138 */
14139IonicModule
14140.directive('ionToggle', [
14141 '$timeout',
14142 '$ionicConfig',
14143function($timeout, $ionicConfig) {
14144
14145 return {
14146 restrict: 'E',
14147 replace: true,
14148 require: '?ngModel',
14149 transclude: true,
14150 template:
14151 '<div class="item item-toggle">' +
14152 '<div ng-transclude></div>' +
14153 '<label class="toggle">' +
14154 '<input type="checkbox">' +
14155 '<div class="track">' +
14156 '<div class="handle"></div>' +
14157 '</div>' +
14158 '</label>' +
14159 '</div>',
14160
14161 compile: function(element, attr) {
14162 var input = element.find('input');
14163 forEach({
14164 'name': attr.name,
14165 'ng-value': attr.ngValue,
14166 'ng-model': attr.ngModel,
14167 'ng-checked': attr.ngChecked,
14168 'ng-disabled': attr.ngDisabled,
14169 'ng-true-value': attr.ngTrueValue,
14170 'ng-false-value': attr.ngFalseValue,
14171 'ng-change': attr.ngChange,
14172 'ng-required': attr.ngRequired,
14173 'required': attr.required
14174 }, function(value, name) {
14175 if (isDefined(value)) {
14176 input.attr(name, value);
14177 }
14178 });
14179
14180 if (attr.toggleClass) {
14181 element[0].getElementsByTagName('label')[0].classList.add(attr.toggleClass);
14182 }
14183
14184 element.addClass('toggle-' + $ionicConfig.form.toggle());
14185
14186 return function($scope, $element) {
14187 var el = $element[0].getElementsByTagName('label')[0];
14188 var checkbox = el.children[0];
14189 var track = el.children[1];
14190 var handle = track.children[0];
14191
14192 var ngModelController = jqLite(checkbox).controller('ngModel');
14193
14194 $scope.toggle = new ionic.views.Toggle({
14195 el: el,
14196 track: track,
14197 checkbox: checkbox,
14198 handle: handle,
14199 onChange: function() {
14200 if (ngModelController) {
14201 ngModelController.$setViewValue(checkbox.checked);
14202 $scope.$apply();
14203 }
14204 }
14205 });
14206
14207 $scope.$on('$destroy', function() {
14208 $scope.toggle.destroy();
14209 });
14210 };
14211 }
14212
14213 };
14214}]);
14215
14216/**
14217 * @ngdoc directive
14218 * @name ionView
14219 * @module ionic
14220 * @restrict E
14221 * @parent ionNavView
14222 *
14223 * @description
14224 * A container for view content and any navigational and header bar information. When a view
14225 * enters and exits its parent {@link ionic.directive:ionNavView}, the view also emits view
14226 * information, such as its title, whether the back button should be displayed or not, whether the
14227 * corresponding {@link ionic.directive:ionNavBar} should be displayed or not, which transition the view
14228 * should use to animate, and which direction to animate.
14229 *
14230 * *Views are cached to improve performance.* When a view is navigated away from, its element is
14231 * left in the DOM, and its scope is disconnected from the `$watch` cycle. When navigating to a
14232 * view that is already cached, its scope is reconnected, and the existing element, which was
14233 * left in the DOM, becomes active again. This can be disabled, or the maximum number of cached
14234 * views changed in {@link ionic.provider:$ionicConfigProvider}, in the view's `$state` configuration, or
14235 * as an attribute on the view itself (see below).
14236 *
14237 * @usage
14238 * Below is an example where our page will load with a {@link ionic.directive:ionNavBar} containing
14239 * "My Page" as the title.
14240 *
14241 * ```html
14242 * <ion-nav-bar></ion-nav-bar>
14243 * <ion-nav-view>
14244 * <ion-view view-title="My Page">
14245 * <ion-content>
14246 * Hello!
14247 * </ion-content>
14248 * </ion-view>
14249 * </ion-nav-view>
14250 * ```
14251 *
14252 * ## View LifeCycle and Events
14253 *
14254 * Views can be cached, which means ***controllers normally only load once***, which may
14255 * affect your controller logic. To know when a view has entered or left, events
14256 * have been added that are emitted from the view's scope. These events also
14257 * contain data about the view, such as the title and whether the back button should
14258 * show. Also contained is transition data, such as the transition type and
14259 * direction that will be or was used.
14260 *
14261 * Life cycle events are emitted upwards from the transitioning view's scope. In some cases, it is
14262 * desirable for a child/nested view to be notified of the event.
14263 * For this use case, `$ionicParentView` life cycle events are broadcast downwards.
14264 *
14265 * <table class="table">
14266 * <tr>
14267 * <td><code>$ionicView.loaded</code></td>
14268 * <td>The view has loaded. This event only happens once per
14269 * view being created and added to the DOM. If a view leaves but is cached,
14270 * then this event will not fire again on a subsequent viewing. The loaded event
14271 * is good place to put your setup code for the view; however, it is not the
14272 * recommended event to listen to when a view becomes active.</td>
14273 * </tr>
14274 * <tr>
14275 * <td><code>$ionicView.enter</code></td>
14276 * <td>The view has fully entered and is now the active view.
14277 * This event will fire, whether it was the first load or a cached view.</td>
14278 * </tr>
14279 * <tr>
14280 * <td><code>$ionicView.leave</code></td>
14281 * <td>The view has finished leaving and is no longer the
14282 * active view. This event will fire, whether it is cached or destroyed.</td>
14283 * </tr>
14284 * <tr>
14285 * <td><code>$ionicView.beforeEnter</code></td>
14286 * <td>The view is about to enter and become the active view.</td>
14287 * </tr>
14288 * <tr>
14289 * <td><code>$ionicView.beforeLeave</code></td>
14290 * <td>The view is about to leave and no longer be the active view.</td>
14291 * </tr>
14292 * <tr>
14293 * <td><code>$ionicView.afterEnter</code></td>
14294 * <td>The view has fully entered and is now the active view.</td>
14295 * </tr>
14296 * <tr>
14297 * <td><code>$ionicView.afterLeave</code></td>
14298 * <td>The view has finished leaving and is no longer the active view.</td>
14299 * </tr>
14300 * <tr>
14301 * <td><code>$ionicView.unloaded</code></td>
14302 * <td>The view's controller has been destroyed and its element has been
14303 * removed from the DOM.</td>
14304 * </tr>
14305 * <tr>
14306 * <td><code>$ionicParentView.enter</code></td>
14307 * <td>The parent view has fully entered and is now the active view.
14308 * This event will fire, whether it was the first load or a cached view.</td>
14309 * </tr>
14310 * <tr>
14311 * <td><code>$ionicParentView.leave</code></td>
14312 * <td>The parent view has finished leaving and is no longer the
14313 * active view. This event will fire, whether it is cached or destroyed.</td>
14314 * </tr>
14315 * <tr>
14316 * <td><code>$ionicParentView.beforeEnter</code></td>
14317 * <td>The parent view is about to enter and become the active view.</td>
14318 * </tr>
14319 * <tr>
14320 * <td><code>$ionicParentView.beforeLeave</code></td>
14321 * <td>The parent view is about to leave and no longer be the active view.</td>
14322 * </tr>
14323 * <tr>
14324 * <td><code>$ionicParentView.afterEnter</code></td>
14325 * <td>The parent view has fully entered and is now the active view.</td>
14326 * </tr>
14327 * <tr>
14328 * <td><code>$ionicParentView.afterLeave</code></td>
14329 * <td>The parent view has finished leaving and is no longer the active view.</td>
14330 * </tr>
14331 * </table>
14332 *
14333 * ## LifeCycle Event Usage
14334 *
14335 * Below is an example of how to listen to life cycle events and
14336 * access state parameter data
14337 *
14338 * ```js
14339 * $scope.$on("$ionicView.beforeEnter", function(event, data){
14340 * // handle event
14341 * console.log("State Params: ", data.stateParams);
14342 * });
14343 *
14344 * $scope.$on("$ionicView.enter", function(event, data){
14345 * // handle event
14346 * console.log("State Params: ", data.stateParams);
14347 * });
14348 *
14349 * $scope.$on("$ionicView.afterEnter", function(event, data){
14350 * // handle event
14351 * console.log("State Params: ", data.stateParams);
14352 * });
14353 * ```
14354 *
14355 * ## Caching
14356 *
14357 * Caching can be disabled and enabled in multiple ways. By default, Ionic will
14358 * cache a maximum of 10 views. You can optionally choose to disable caching at
14359 * either an individual view basis, or by global configuration. Please see the
14360 * _Caching_ section in {@link ionic.directive:ionNavView} for more info.
14361 *
14362 * @param {string=} view-title A text-only title to display on the parent {@link ionic.directive:ionNavBar}.
14363 * For an HTML title, such as an image, see {@link ionic.directive:ionNavTitle} instead.
14364 * @param {boolean=} cache-view If this view should be allowed to be cached or not.
14365 * Please see the _Caching_ section in {@link ionic.directive:ionNavView} for
14366 * more info. Default `true`
14367 * @param {boolean=} can-swipe-back If this view should be allowed to use the swipe to go back gesture or not.
14368 * This does not enable the swipe to go back feature if it is not available for the platform it's running
14369 * from, or there isn't a previous view. Default `true`
14370 * @param {boolean=} hide-back-button Whether to hide the back button on the parent
14371 * {@link ionic.directive:ionNavBar} by default.
14372 * @param {boolean=} hide-nav-bar Whether to hide the parent
14373 * {@link ionic.directive:ionNavBar} by default.
14374 */
14375IonicModule
14376.directive('ionView', function() {
14377 return {
14378 restrict: 'EA',
14379 priority: 1000,
14380 controller: '$ionicView',
14381 compile: function(tElement) {
14382 tElement.addClass('pane');
14383 tElement[0].removeAttribute('title');
14384 return function link($scope, $element, $attrs, viewCtrl) {
14385 viewCtrl.init();
14386 };
14387 }
14388 };
14389});
14390
14391})();