UNPKG

generator-ionicgenerator

Version:

An Ionic app, custom with gulp and sass

1,538 lines (1,369 loc) 454 kB
/*! * Copyright 2015 Drifty Co. * http://drifty.com/ * * Ionic, v1.3.0 * A powerful HTML5 mobile app framework. * http://ionicframework.com/ * * By @maxlynch, @benjsperry, @adamdbradley <3 * * Licensed under the MIT license. Please see LICENSE for more information. * */ (function() { /* eslint no-unused-vars:0 */ var IonicModule = angular.module('ionic', ['ngAnimate', 'ngSanitize', 'ui.router', 'ngIOS9UIWebViewPatch']), extend = angular.extend, forEach = angular.forEach, isDefined = angular.isDefined, isNumber = angular.isNumber, isString = angular.isString, jqLite = angular.element, noop = angular.noop; /** * @ngdoc service * @name $ionicActionSheet * @module ionic * @description * The Action Sheet is a slide-up pane that lets the user choose from a set of options. * Dangerous options are highlighted in red and made obvious. * * There are easy ways to cancel out of the action sheet, such as tapping the backdrop or even * hitting escape on the keyboard for desktop testing. * * ![Action Sheet](http://ionicframework.com.s3.amazonaws.com/docs/controllers/actionSheet.gif) * * @usage * To trigger an Action Sheet in your code, use the $ionicActionSheet service in your angular controllers: * * ```js * angular.module('mySuperApp', ['ionic']) * .controller(function($scope, $ionicActionSheet, $timeout) { * * // Triggered on a button click, or some other target * $scope.show = function() { * * // Show the action sheet * var hideSheet = $ionicActionSheet.show({ * buttons: [ * { text: '<b>Share</b> This' }, * { text: 'Move' } * ], * destructiveText: 'Delete', * titleText: 'Modify your album', * cancelText: 'Cancel', * cancel: function() { // add cancel code.. }, * buttonClicked: function(index) { * return true; * } * }); * * // For example's sake, hide the sheet after two seconds * $timeout(function() { * hideSheet(); * }, 2000); * * }; * }); * ``` * */ IonicModule .factory('$ionicActionSheet', [ '$rootScope', '$compile', '$animate', '$timeout', '$ionicTemplateLoader', '$ionicPlatform', '$ionicBody', 'IONIC_BACK_PRIORITY', function($rootScope, $compile, $animate, $timeout, $ionicTemplateLoader, $ionicPlatform, $ionicBody, IONIC_BACK_PRIORITY) { return { show: actionSheet }; /** * @ngdoc method * @name $ionicActionSheet#show * @description * Load and return a new action sheet. * * A new isolated scope will be created for the * action sheet and the new element will be appended into the body. * * @param {object} options The options for this ActionSheet. Properties: * * - `[Object]` `buttons` Which buttons to show. Each button is an object with a `text` field. * - `{string}` `titleText` The title to show on the action sheet. * - `{string=}` `cancelText` the text for a 'cancel' button on the action sheet. * - `{string=}` `destructiveText` The text for a 'danger' on the action sheet. * - `{function=}` `cancel` Called if the cancel button is pressed, the backdrop is tapped or * the hardware back button is pressed. * - `{function=}` `buttonClicked` Called when one of the non-destructive buttons is clicked, * with the index of the button that was clicked and the button object. Return true to close * the action sheet, or false to keep it opened. * - `{function=}` `destructiveButtonClicked` Called when the destructive button is clicked. * Return true to close the action sheet, or false to keep it opened. * - `{boolean=}` `cancelOnStateChange` Whether to cancel the actionSheet when navigating * to a new state. Default true. * - `{string}` `cssClass` The custom CSS class name. * * @returns {function} `hideSheet` A function which, when called, hides & cancels the action sheet. */ function actionSheet(opts) { var scope = $rootScope.$new(true); extend(scope, { cancel: noop, destructiveButtonClicked: noop, buttonClicked: noop, $deregisterBackButton: noop, buttons: [], cancelOnStateChange: true }, opts || {}); function textForIcon(text) { if (text && /icon/.test(text)) { scope.$actionSheetHasIcon = true; } } for (var x = 0; x < scope.buttons.length; x++) { textForIcon(scope.buttons[x].text); } textForIcon(scope.cancelText); textForIcon(scope.destructiveText); // Compile the template var element = scope.element = $compile('<ion-action-sheet ng-class="cssClass" buttons="buttons"></ion-action-sheet>')(scope); // Grab the sheet element for animation var sheetEl = jqLite(element[0].querySelector('.action-sheet-wrapper')); var stateChangeListenDone = scope.cancelOnStateChange ? $rootScope.$on('$stateChangeSuccess', function() { scope.cancel(); }) : noop; // removes the actionSheet from the screen scope.removeSheet = function(done) { if (scope.removed) return; scope.removed = true; sheetEl.removeClass('action-sheet-up'); $timeout(function() { // wait to remove this due to a 300ms delay native // click which would trigging whatever was underneath this $ionicBody.removeClass('action-sheet-open'); }, 400); scope.$deregisterBackButton(); stateChangeListenDone(); $animate.removeClass(element, 'active').then(function() { scope.$destroy(); element.remove(); // scope.cancel.$scope is defined near the bottom scope.cancel.$scope = sheetEl = null; (done || noop)(opts.buttons); }); }; scope.showSheet = function(done) { if (scope.removed) return; $ionicBody.append(element) .addClass('action-sheet-open'); $animate.addClass(element, 'active').then(function() { if (scope.removed) return; (done || noop)(); }); $timeout(function() { if (scope.removed) return; sheetEl.addClass('action-sheet-up'); }, 20, false); }; // registerBackButtonAction returns a callback to deregister the action scope.$deregisterBackButton = $ionicPlatform.registerBackButtonAction( function() { $timeout(scope.cancel); }, IONIC_BACK_PRIORITY.actionSheet ); // called when the user presses the cancel button scope.cancel = function() { // after the animation is out, call the cancel callback scope.removeSheet(opts.cancel); }; scope.buttonClicked = function(index) { // Check if the button click event returned true, which means // we can close the action sheet if (opts.buttonClicked(index, opts.buttons[index]) === true) { scope.removeSheet(); } }; scope.destructiveButtonClicked = function() { // Check if the destructive button click event returned true, which means // we can close the action sheet if (opts.destructiveButtonClicked() === true) { scope.removeSheet(); } }; scope.showSheet(); // Expose the scope on $ionicActionSheet's return value for the sake // of testing it. scope.cancel.$scope = scope; return scope.cancel; } }]); jqLite.prototype.addClass = function(cssClasses) { var x, y, cssClass, el, splitClasses, existingClasses; if (cssClasses && cssClasses != 'ng-scope' && cssClasses != 'ng-isolate-scope') { for (x = 0; x < this.length; x++) { el = this[x]; if (el.setAttribute) { if (cssClasses.indexOf(' ') < 0 && el.classList.add) { el.classList.add(cssClasses); } else { existingClasses = (' ' + (el.getAttribute('class') || '') + ' ') .replace(/[\n\t]/g, " "); splitClasses = cssClasses.split(' '); for (y = 0; y < splitClasses.length; y++) { cssClass = splitClasses[y].trim(); if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { existingClasses += cssClass + ' '; } } el.setAttribute('class', existingClasses.trim()); } } } } return this; }; jqLite.prototype.removeClass = function(cssClasses) { var x, y, splitClasses, cssClass, el; if (cssClasses) { for (x = 0; x < this.length; x++) { el = this[x]; if (el.getAttribute) { if (cssClasses.indexOf(' ') < 0 && el.classList.remove) { el.classList.remove(cssClasses); } else { splitClasses = cssClasses.split(' '); for (y = 0; y < splitClasses.length; y++) { cssClass = splitClasses[y]; el.setAttribute('class', ( (" " + (el.getAttribute('class') || '') + " ") .replace(/[\n\t]/g, " ") .replace(" " + cssClass.trim() + " ", " ")).trim() ); } } } } } return this; }; /** * @ngdoc service * @name $ionicBackdrop * @module ionic * @description * Shows and hides a backdrop over the UI. Appears behind popups, loading, * and other overlays. * * Often, multiple UI components require a backdrop, but only one backdrop is * ever needed in the DOM at a time. * * Therefore, each component that requires the backdrop to be shown calls * `$ionicBackdrop.retain()` when it wants the backdrop, then `$ionicBackdrop.release()` * when it is done with the backdrop. * * For each time `retain` is called, the backdrop will be shown until `release` is called. * * For example, if `retain` is called three times, the backdrop will be shown until `release` * is called three times. * * **Notes:** * - The backdrop service will broadcast 'backdrop.shown' and 'backdrop.hidden' events from the root scope, * this is useful for alerting native components not in html. * * @usage * * ```js * function MyController($scope, $ionicBackdrop, $timeout, $rootScope) { * //Show a backdrop for one second * $scope.action = function() { * $ionicBackdrop.retain(); * $timeout(function() { * $ionicBackdrop.release(); * }, 1000); * }; * * // Execute action on backdrop disappearing * $scope.$on('backdrop.hidden', function() { * // Execute action * }); * * // Execute action on backdrop appearing * $scope.$on('backdrop.shown', function() { * // Execute action * }); * * } * ``` */ IonicModule .factory('$ionicBackdrop', [ '$document', '$timeout', '$$rAF', '$rootScope', function($document, $timeout, $$rAF, $rootScope) { var el = jqLite('<div class="backdrop">'); var backdropHolds = 0; $document[0].body.appendChild(el[0]); return { /** * @ngdoc method * @name $ionicBackdrop#retain * @description Retains the backdrop. */ retain: retain, /** * @ngdoc method * @name $ionicBackdrop#release * @description * Releases the backdrop. */ release: release, getElement: getElement, // exposed for testing _element: el }; function retain() { backdropHolds++; if (backdropHolds === 1) { el.addClass('visible'); $rootScope.$broadcast('backdrop.shown'); $$rAF(function() { // If we're still at >0 backdropHolds after async... if (backdropHolds >= 1) el.addClass('active'); }); } } function release() { if (backdropHolds === 1) { el.removeClass('active'); $rootScope.$broadcast('backdrop.hidden'); $timeout(function() { // If we're still at 0 backdropHolds after async... if (backdropHolds === 0) el.removeClass('visible'); }, 400, false); } backdropHolds = Math.max(0, backdropHolds - 1); } function getElement() { return el; } }]); /** * @private */ IonicModule .factory('$ionicBind', ['$parse', '$interpolate', function($parse, $interpolate) { var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; return function(scope, attrs, bindDefinition) { forEach(bindDefinition || {}, function(definition, scopeName) { //Adapted from angular.js $compile var match = definition.match(LOCAL_REGEXP) || [], attrName = match[3] || scopeName, mode = match[1], // @, =, or & parentGet, unwatch; switch (mode) { case '@': if (!attrs[attrName]) { return; } attrs.$observe(attrName, function(value) { scope[scopeName] = value; }); // we trigger an interpolation to ensure // the value is there for use immediately if (attrs[attrName]) { scope[scopeName] = $interpolate(attrs[attrName])(scope); } break; case '=': if (!attrs[attrName]) { return; } unwatch = scope.$watch(attrs[attrName], function(value) { scope[scopeName] = value; }); //Destroy parent scope watcher when this scope is destroyed scope.$on('$destroy', unwatch); break; case '&': /* jshint -W044 */ if (attrs[attrName] && attrs[attrName].match(RegExp(scopeName + '\(.*?\)'))) { throw new Error('& expression binding "' + scopeName + '" looks like it will recursively call "' + attrs[attrName] + '" and cause a stack overflow! Please choose a different scopeName.'); } parentGet = $parse(attrs[attrName]); scope[scopeName] = function(locals) { return parentGet(scope, locals); }; break; } }); }; }]); /** * @ngdoc service * @name $ionicBody * @module ionic * @description An angular utility service to easily and efficiently * add and remove CSS classes from the document's body element. */ IonicModule .factory('$ionicBody', ['$document', function($document) { return { /** * @ngdoc method * @name $ionicBody#addClass * @description Add a class to the document's body element. * @param {string} class Each argument will be added to the body element. * @returns {$ionicBody} The $ionicBody service so methods can be chained. */ addClass: function() { for (var x = 0; x < arguments.length; x++) { $document[0].body.classList.add(arguments[x]); } return this; }, /** * @ngdoc method * @name $ionicBody#removeClass * @description Remove a class from the document's body element. * @param {string} class Each argument will be removed from the body element. * @returns {$ionicBody} The $ionicBody service so methods can be chained. */ removeClass: function() { for (var x = 0; x < arguments.length; x++) { $document[0].body.classList.remove(arguments[x]); } return this; }, /** * @ngdoc method * @name $ionicBody#enableClass * @description Similar to the `add` method, except the first parameter accepts a boolean * value determining if the class should be added or removed. Rather than writing user code, * such as "if true then add the class, else then remove the class", this method can be * given a true or false value which reduces redundant code. * @param {boolean} shouldEnableClass A true/false value if the class should be added or removed. * @param {string} class Each remaining argument would be added or removed depending on * the first argument. * @returns {$ionicBody} The $ionicBody service so methods can be chained. */ enableClass: function(shouldEnableClass) { var args = Array.prototype.slice.call(arguments).slice(1); if (shouldEnableClass) { this.addClass.apply(this, args); } else { this.removeClass.apply(this, args); } return this; }, /** * @ngdoc method * @name $ionicBody#append * @description Append a child to the document's body. * @param {element} element The element to be appended to the body. The passed in element * can be either a jqLite element, or a DOM element. * @returns {$ionicBody} The $ionicBody service so methods can be chained. */ append: function(ele) { $document[0].body.appendChild(ele.length ? ele[0] : ele); return this; }, /** * @ngdoc method * @name $ionicBody#get * @description Get the document's body element. * @returns {element} Returns the document's body element. */ get: function() { return $document[0].body; } }; }]); IonicModule .factory('$ionicClickBlock', [ '$document', '$ionicBody', '$timeout', function($document, $ionicBody, $timeout) { var CSS_HIDE = 'click-block-hide'; var cbEle, fallbackTimer, pendingShow; function preventClick(ev) { ev.preventDefault(); ev.stopPropagation(); } function addClickBlock() { if (pendingShow) { if (cbEle) { cbEle.classList.remove(CSS_HIDE); } else { cbEle = $document[0].createElement('div'); cbEle.className = 'click-block'; $ionicBody.append(cbEle); cbEle.addEventListener('touchstart', preventClick); cbEle.addEventListener('mousedown', preventClick); } pendingShow = false; } } function removeClickBlock() { cbEle && cbEle.classList.add(CSS_HIDE); } return { show: function(autoExpire) { pendingShow = true; $timeout.cancel(fallbackTimer); fallbackTimer = $timeout(this.hide, autoExpire || 310, false); addClickBlock(); }, hide: function() { pendingShow = false; $timeout.cancel(fallbackTimer); removeClickBlock(); } }; }]); /** * @ngdoc service * @name $ionicGesture * @module ionic * @description An angular service exposing ionic * {@link ionic.utility:ionic.EventController}'s gestures. */ IonicModule .factory('$ionicGesture', [function() { return { /** * @ngdoc method * @name $ionicGesture#on * @description Add an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#onGesture}. * @param {string} eventType The gesture event to listen for. * @param {function(e)} callback The function to call when the gesture * happens. * @param {element} $element The angular element to listen for the event on. * @param {object} options object. * @returns {ionic.Gesture} The gesture object (use this to remove the gesture later on). */ on: function(eventType, cb, $element, options) { return window.ionic.onGesture(eventType, cb, $element[0], options); }, /** * @ngdoc method * @name $ionicGesture#off * @description Remove an event listener for a gesture on an element. See {@link ionic.utility:ionic.EventController#offGesture}. * @param {ionic.Gesture} gesture The gesture that should be removed. * @param {string} eventType The gesture event to remove the listener for. * @param {function(e)} callback The listener to remove. */ off: function(gesture, eventType, cb) { return window.ionic.offGesture(gesture, eventType, cb); } }; }]); /** * @ngdoc service * @name $ionicHistory * @module ionic * @description * $ionicHistory keeps track of views as the user navigates through an app. Similar to the way a * browser behaves, an Ionic app is able to keep track of the previous view, the current view, and * the forward view (if there is one). However, a typical web browser only keeps track of one * history stack in a linear fashion. * * Unlike a traditional browser environment, apps and webapps have parallel independent histories, * such as with tabs. Should a user navigate few pages deep on one tab, and then switch to a new * tab and back, the back button relates not to the previous tab, but to the previous pages * visited within _that_ tab. * * `$ionicHistory` facilitates this parallel history architecture. */ IonicModule .factory('$ionicHistory', [ '$rootScope', '$state', '$location', '$window', '$timeout', '$ionicViewSwitcher', '$ionicNavViewDelegate', function($rootScope, $state, $location, $window, $timeout, $ionicViewSwitcher, $ionicNavViewDelegate) { // history actions while navigating views var ACTION_INITIAL_VIEW = 'initialView'; var ACTION_NEW_VIEW = 'newView'; var ACTION_MOVE_BACK = 'moveBack'; var ACTION_MOVE_FORWARD = 'moveForward'; // direction of navigation var DIRECTION_BACK = 'back'; var DIRECTION_FORWARD = 'forward'; var DIRECTION_ENTER = 'enter'; var DIRECTION_EXIT = 'exit'; var DIRECTION_SWAP = 'swap'; var DIRECTION_NONE = 'none'; var stateChangeCounter = 0; var lastStateId, nextViewOptions, deregisterStateChangeListener, nextViewExpireTimer, forcedNav; var viewHistory = { histories: { root: { historyId: 'root', parentHistoryId: null, stack: [], cursor: -1 } }, views: {}, backView: null, forwardView: null, currentView: null }; var View = function() {}; View.prototype.initialize = function(data) { if (data) { for (var name in data) this[name] = data[name]; return this; } return null; }; View.prototype.go = function() { if (this.stateName) { return $state.go(this.stateName, this.stateParams); } if (this.url && this.url !== $location.url()) { if (viewHistory.backView === this) { return $window.history.go(-1); } else if (viewHistory.forwardView === this) { return $window.history.go(1); } $location.url(this.url); } return null; }; View.prototype.destroy = function() { if (this.scope) { this.scope.$destroy && this.scope.$destroy(); this.scope = null; } }; function getViewById(viewId) { return (viewId ? viewHistory.views[ viewId ] : null); } function getBackView(view) { return (view ? getViewById(view.backViewId) : null); } function getForwardView(view) { return (view ? getViewById(view.forwardViewId) : null); } function getHistoryById(historyId) { return (historyId ? viewHistory.histories[ historyId ] : null); } function getHistory(scope) { var histObj = getParentHistoryObj(scope); if (!viewHistory.histories[ histObj.historyId ]) { // this history object exists in parent scope, but doesn't // exist in the history data yet viewHistory.histories[ histObj.historyId ] = { historyId: histObj.historyId, parentHistoryId: getParentHistoryObj(histObj.scope.$parent).historyId, stack: [], cursor: -1 }; } return getHistoryById(histObj.historyId); } function getParentHistoryObj(scope) { var parentScope = scope; while (parentScope) { if (parentScope.hasOwnProperty('$historyId')) { // this parent scope has a historyId return { historyId: parentScope.$historyId, scope: parentScope }; } // nothing found keep climbing up parentScope = parentScope.$parent; } // no history for the parent, use the root return { historyId: 'root', scope: $rootScope }; } function setNavViews(viewId) { viewHistory.currentView = getViewById(viewId); viewHistory.backView = getBackView(viewHistory.currentView); viewHistory.forwardView = getForwardView(viewHistory.currentView); } function getCurrentStateId() { var id; if ($state && $state.current && $state.current.name) { id = $state.current.name; if ($state.params) { for (var key in $state.params) { if ($state.params.hasOwnProperty(key) && $state.params[key]) { id += "_" + key + "=" + $state.params[key]; } } } return id; } // if something goes wrong make sure its got a unique stateId return ionic.Utils.nextUid(); } function getCurrentStateParams() { var rtn; if ($state && $state.params) { for (var key in $state.params) { if ($state.params.hasOwnProperty(key)) { rtn = rtn || {}; rtn[key] = $state.params[key]; } } } return rtn; } return { register: function(parentScope, viewLocals) { var currentStateId = getCurrentStateId(), hist = getHistory(parentScope), currentView = viewHistory.currentView, backView = viewHistory.backView, forwardView = viewHistory.forwardView, viewId = null, action = null, direction = DIRECTION_NONE, historyId = hist.historyId, url = $location.url(), tmp, x, ele; if (lastStateId !== currentStateId) { lastStateId = currentStateId; stateChangeCounter++; } if (forcedNav) { // we've previously set exactly what to do viewId = forcedNav.viewId; action = forcedNav.action; direction = forcedNav.direction; forcedNav = null; } else if (backView && backView.stateId === currentStateId) { // they went back one, set the old current view as a forward view viewId = backView.viewId; historyId = backView.historyId; action = ACTION_MOVE_BACK; if (backView.historyId === currentView.historyId) { // went back in the same history direction = DIRECTION_BACK; } else if (currentView) { direction = DIRECTION_EXIT; tmp = getHistoryById(backView.historyId); if (tmp && tmp.parentHistoryId === currentView.historyId) { direction = DIRECTION_ENTER; } else { tmp = getHistoryById(currentView.historyId); if (tmp && tmp.parentHistoryId === hist.parentHistoryId) { direction = DIRECTION_SWAP; } } } } else if (forwardView && forwardView.stateId === currentStateId) { // they went to the forward one, set the forward view to no longer a forward view viewId = forwardView.viewId; historyId = forwardView.historyId; action = ACTION_MOVE_FORWARD; if (forwardView.historyId === currentView.historyId) { direction = DIRECTION_FORWARD; } else if (currentView) { direction = DIRECTION_EXIT; if (currentView.historyId === hist.parentHistoryId) { direction = DIRECTION_ENTER; } else { tmp = getHistoryById(currentView.historyId); if (tmp && tmp.parentHistoryId === hist.parentHistoryId) { direction = DIRECTION_SWAP; } } } tmp = getParentHistoryObj(parentScope); if (forwardView.historyId && tmp.scope) { // if a history has already been created by the forward view then make sure it stays the same tmp.scope.$historyId = forwardView.historyId; historyId = forwardView.historyId; } } else if (currentView && currentView.historyId !== historyId && hist.cursor > -1 && hist.stack.length > 0 && hist.cursor < hist.stack.length && hist.stack[hist.cursor].stateId === currentStateId) { // they just changed to a different history and the history already has views in it var switchToView = hist.stack[hist.cursor]; viewId = switchToView.viewId; historyId = switchToView.historyId; action = ACTION_MOVE_BACK; direction = DIRECTION_SWAP; tmp = getHistoryById(currentView.historyId); if (tmp && tmp.parentHistoryId === historyId) { direction = DIRECTION_EXIT; } else { tmp = getHistoryById(historyId); if (tmp && tmp.parentHistoryId === currentView.historyId) { direction = DIRECTION_ENTER; } } // if switching to a different history, and the history of the view we're switching // to has an existing back view from a different history than itself, then // it's back view would be better represented using the current view as its back view tmp = getViewById(switchToView.backViewId); if (tmp && switchToView.historyId !== tmp.historyId) { hist.stack[hist.cursor].backViewId = currentView.viewId; } } else { // create an element from the viewLocals template ele = $ionicViewSwitcher.createViewEle(viewLocals); if (this.isAbstractEle(ele, viewLocals)) { void 0; return { action: 'abstractView', direction: DIRECTION_NONE, ele: ele }; } // set a new unique viewId viewId = ionic.Utils.nextUid(); if (currentView) { // set the forward view if there is a current view (ie: if its not the first view) currentView.forwardViewId = viewId; action = ACTION_NEW_VIEW; // check if there is a new forward view within the same history if (forwardView && currentView.stateId !== forwardView.stateId && currentView.historyId === forwardView.historyId) { // they navigated to a new view but the stack already has a forward view // since its a new view remove any forwards that existed tmp = getHistoryById(forwardView.historyId); if (tmp) { // the forward has a history for (x = tmp.stack.length - 1; x >= forwardView.index; x--) { // starting from the end destroy all forwards in this history from this point var stackItem = tmp.stack[x]; stackItem && stackItem.destroy && stackItem.destroy(); tmp.stack.splice(x); } historyId = forwardView.historyId; } } // its only moving forward if its in the same history if (hist.historyId === currentView.historyId) { direction = DIRECTION_FORWARD; } else if (currentView.historyId !== hist.historyId) { // DB: this is a new view in a different tab direction = DIRECTION_ENTER; tmp = getHistoryById(currentView.historyId); if (tmp && tmp.parentHistoryId === hist.parentHistoryId) { direction = DIRECTION_SWAP; } else { tmp = getHistoryById(tmp.parentHistoryId); if (tmp && tmp.historyId === hist.historyId) { direction = DIRECTION_EXIT; } } } } else { // there's no current view, so this must be the initial view action = ACTION_INITIAL_VIEW; } if (stateChangeCounter < 2) { // views that were spun up on the first load should not animate direction = DIRECTION_NONE; } // add the new view viewHistory.views[viewId] = this.createView({ viewId: viewId, index: hist.stack.length, historyId: hist.historyId, backViewId: (currentView && currentView.viewId && (currentView.historyId === hist.historyId || currentView.historyId === hist.parentHistoryId) ? currentView.viewId : null), forwardViewId: null, stateId: currentStateId, stateName: this.currentStateName(), stateParams: getCurrentStateParams(), url: url, canSwipeBack: canSwipeBack(ele, viewLocals) }); // add the new view to this history's stack hist.stack.push(viewHistory.views[viewId]); } deregisterStateChangeListener && deregisterStateChangeListener(); $timeout.cancel(nextViewExpireTimer); if (nextViewOptions) { if (nextViewOptions.disableAnimate) direction = DIRECTION_NONE; if (nextViewOptions.disableBack) viewHistory.views[viewId].backViewId = null; if (nextViewOptions.historyRoot) { for (x = 0; x < hist.stack.length; x++) { if (hist.stack[x].viewId === viewId) { hist.stack[x].index = 0; hist.stack[x].backViewId = hist.stack[x].forwardViewId = null; } else { delete viewHistory.views[hist.stack[x].viewId]; } } hist.stack = [viewHistory.views[viewId]]; } nextViewOptions = null; } setNavViews(viewId); if (viewHistory.backView && historyId == viewHistory.backView.historyId && currentStateId == viewHistory.backView.stateId && url == viewHistory.backView.url) { for (x = 0; x < hist.stack.length; x++) { if (hist.stack[x].viewId == viewId) { action = 'dupNav'; direction = DIRECTION_NONE; if (x > 0) { hist.stack[x - 1].forwardViewId = null; } viewHistory.forwardView = null; viewHistory.currentView.index = viewHistory.backView.index; viewHistory.currentView.backViewId = viewHistory.backView.backViewId; viewHistory.backView = getBackView(viewHistory.backView); hist.stack.splice(x, 1); break; } } } void 0; hist.cursor = viewHistory.currentView.index; return { viewId: viewId, action: action, direction: direction, historyId: historyId, enableBack: this.enabledBack(viewHistory.currentView), isHistoryRoot: (viewHistory.currentView.index === 0), ele: ele }; }, registerHistory: function(scope) { scope.$historyId = ionic.Utils.nextUid(); }, createView: function(data) { var newView = new View(); return newView.initialize(data); }, getViewById: getViewById, /** * @ngdoc method * @name $ionicHistory#viewHistory * @description The app's view history data, such as all the views and histories, along * with how they are ordered and linked together within the navigation stack. * @returns {object} Returns an object containing the apps view history data. */ viewHistory: function() { return viewHistory; }, /** * @ngdoc method * @name $ionicHistory#currentView * @description The app's current view. * @returns {object} Returns the current view. */ currentView: function(view) { if (arguments.length) { viewHistory.currentView = view; } return viewHistory.currentView; }, /** * @ngdoc method * @name $ionicHistory#currentHistoryId * @description The ID of the history stack which is the parent container of the current view. * @returns {string} Returns the current history ID. */ currentHistoryId: function() { return viewHistory.currentView ? viewHistory.currentView.historyId : null; }, /** * @ngdoc method * @name $ionicHistory#currentTitle * @description Gets and sets the current view's title. * @param {string=} val The title to update the current view with. * @returns {string} Returns the current view's title. */ currentTitle: function(val) { if (viewHistory.currentView) { if (arguments.length) { viewHistory.currentView.title = val; } return viewHistory.currentView.title; } }, /** * @ngdoc method * @name $ionicHistory#backView * @description Returns the view that was before the current view in the history stack. * If the user navigated from View A to View B, then View A would be the back view, and * View B would be the current view. * @returns {object} Returns the back view. */ backView: function(view) { if (arguments.length) { viewHistory.backView = view; } return viewHistory.backView; }, /** * @ngdoc method * @name $ionicHistory#backTitle * @description Gets the back view's title. * @returns {string} Returns the back view's title. */ backTitle: function(view) { var backView = (view && getViewById(view.backViewId)) || viewHistory.backView; return backView && backView.title; }, /** * @ngdoc method * @name $ionicHistory#forwardView * @description Returns the view that was in front of the current view in the history stack. * A forward view would exist if the user navigated from View A to View B, then * navigated back to View A. At this point then View B would be the forward view, and View * A would be the current view. * @returns {object} Returns the forward view. */ forwardView: function(view) { if (arguments.length) { viewHistory.forwardView = view; } return viewHistory.forwardView; }, /** * @ngdoc method * @name $ionicHistory#currentStateName * @description Returns the current state name. * @returns {string} */ currentStateName: function() { return ($state && $state.current ? $state.current.name : null); }, isCurrentStateNavView: function(navView) { return !!($state && $state.current && $state.current.views && $state.current.views[navView]); }, goToHistoryRoot: function(historyId) { if (historyId) { var hist = getHistoryById(historyId); if (hist && hist.stack.length) { if (viewHistory.currentView && viewHistory.currentView.viewId === hist.stack[0].viewId) { return; } forcedNav = { viewId: hist.stack[0].viewId, action: ACTION_MOVE_BACK, direction: DIRECTION_BACK }; hist.stack[0].go(); } } }, /** * @ngdoc method * @name $ionicHistory#goBack * @param {number=} backCount Optional negative integer setting how many views to go * back. By default it'll go back one view by using the value `-1`. To go back two * views you would use `-2`. If the number goes farther back than the number of views * in the current history's stack then it'll go to the first view in the current history's * stack. If the number is zero or greater then it'll do nothing. It also does not * cross history stacks, meaning it can only go as far back as the current history. * @description Navigates the app to the back view, if a back view exists. */ goBack: function(backCount) { if (isDefined(backCount) && backCount !== -1) { if (backCount > -1) return; var currentHistory = viewHistory.histories[this.currentHistoryId()]; var newCursor = currentHistory.cursor + backCount + 1; if (newCursor < 1) { newCursor = 1; } currentHistory.cursor = newCursor; setNavViews(currentHistory.stack[newCursor].viewId); var cursor = newCursor - 1; var clearStateIds = []; var fwdView = getViewById(currentHistory.stack[cursor].forwardViewId); while (fwdView) { clearStateIds.push(fwdView.stateId || fwdView.viewId); cursor++; if (cursor >= currentHistory.stack.length) break; fwdView = getViewById(currentHistory.stack[cursor].forwardViewId); } var self = this; if (clearStateIds.length) { $timeout(function() { self.clearCache(clearStateIds); }, 300); } } viewHistory.backView && viewHistory.backView.go(); }, /** * @ngdoc method * @name $ionicHistory#removeBackView * @description Remove the previous view from the history completely, including the * cached element and scope (if they exist). */ removeBackView: function() { var self = this; var currentHistory = viewHistory.histories[this.currentHistoryId()]; var currentCursor = currentHistory.cursor; var currentView = currentHistory.stack[currentCursor]; var backView = currentHistory.stack[currentCursor - 1]; var replacementView = currentHistory.stack[currentCursor - 2]; // fail if we dont have enough views in the history if (!backView || !replacementView) { return; } // remove the old backView and the cached element/scope currentHistory.stack.splice(currentCursor - 1, 1); self.clearCache([backView.viewId]); // make the replacementView and currentView point to each other (bypass the old backView) currentView.backViewId = replacementView.viewId; currentView.index = currentView.index - 1; replacementView.forwardViewId = currentView.viewId; // update the cursor and set new backView viewHistory.backView = replacementView; currentHistory.currentCursor += -1; }, enabledBack: function(view) { var backView = getBackView(view); return !!(backView && backView.historyId === view.historyId); }, /** * @ngdoc method * @name $ionicHistory#clearHistory * @description Clears out the app's entire history, except for the current view. */ clearHistory: function() { var histories = viewHistory.histories, currentView = viewHistory.currentView; if (histories) { for (var historyId in histories) { if (histories[historyId].stack) { histories[historyId].stack = []; histories[historyId].cursor = -1; } if (currentView && currentView.historyId === historyId) { currentView.backViewId = currentView.forwardViewId = null; histories[historyId].stack.push(currentView); } else if (histories[historyId].destroy) { histories[historyId].destroy(); } } } for (var viewId in viewHistory.views) { if (viewId !== currentView.viewId) { delete viewHistory.views[viewId]; } } if (currentView) { setNavViews(currentView.viewId); } }, /** * @ngdoc method * @name $ionicHistory#clearCache * @return promise * @description Removes all cached views within every {@link ionic.directive:ionNavView}. * This both removes the view element from the DOM, and destroy it's scope. */ clearCache: function(stateIds) { return $timeout(function() { $ionicNavViewDelegate._instances.forEach(function(instance) { instance.clearCache(stateIds); }); }); }, /** * @ngdoc method * @name $ionicHistory#nextViewOptions * @description Sets options for the next view. This method can be useful to override * certain view/transition defaults right before a view transition happens. For example, * the {@link ionic.directive:menuClose} directive uses this method internally to ensure * an animated view transition does not happen when a side menu is open, and also sets * the next view as the root of its history stack. After the transition these options * are set back to null. * * Available options: * * * `disableAnimate`: Do not animate the next transition. * * `disableBack`: The next view should forget its back view, and set it to null. * * `historyRoot`: The next view should become the root view in its history stack. * * ```js * $ionicHistory.nextViewOptions({ * disableAnimate: true, * disableBack: true * }); * ``` */ nextViewOptions: function(opts) { deregisterStateChangeListener && deregisterStateChangeListener(); if (arguments.length) { $timeout.cancel(nextViewExpireTimer); if (opts === null) { nextViewOptions = opts; } else { nextViewOptions = nextViewOptions || {}; extend(nextViewOptions, opts); if (nextViewOptions.expire) { deregisterStateChangeListener = $rootScope.$on('$stateChangeSuccess', function() { nextViewExpireTimer = $timeout(function() { nextViewOptions = null; }, nextViewOptions.expire); }); } } } return nextViewOptions; }, isAbstractEle: function(ele, viewLocals) { if (viewLocals && viewLocals.$$state && viewLocals.$$state.self['abstract']) { return true; } return !!(ele && (isAbstractTag(ele) || isAbstractTag(ele.children()))); }, isActiveScope: function(scope) { if (!scope) return false; var climbScope = scope; var currentHistoryId = this.currentHistoryId(); var foundHistoryId; while (climbScope) { if (climbScope.$$disconnected) { return false; } if (!foundHistoryId && climbScope.hasOwnProperty('$historyId')) { foundHistoryId = true; } if (currentHistoryId) { if (climbScope.hasOwnProperty('$historyId') && currentHistoryId == climbScope.$historyId) { return true; } if (climbScope.hasOwnProperty('$activeHistoryId')) { if (currentHistoryId == climbScope.$activeHistoryId) { if (climbScope.hasOwnProperty('$historyId')) { return true; } if (!foundHistoryId) { return true; } } } } if (foundHistoryId && climbScope.hasOwnProperty('$activeHistoryId')) { foundHistoryId = false; } climbScope = climbScope.$parent; } return currentHistoryId ? currentHistoryId == 'root' : true; } }; function isAbstractTag(ele) { return ele && ele.length && /ion-side-menus|ion-tabs/i.test(ele[0].tagName); } function canSwipeBack(ele, viewLocals) { if (viewLocals && viewLocals.$$state && viewLocals.$$state.self.canSwipeBack === false) { return false; } if (ele && ele.attr('can-swipe-back') === 'false') { return false; } var eleChild = ele.find('ion-view'); if (eleChild && eleChild.attr('can-swipe-back') === 'false') { return false; } return true; } }]) .run([ '$rootScope', '$state', '$location', '$document', '$ionicPlatform', '$ionicHistory', 'IONIC_BACK_PRIORITY', function($rootScope, $state, $location, $document, $ionicPlatform, $ionicHistory, IONIC_BACK_PRIORITY) { // always reset the keyboard state when change stage $rootScope.$on('$ionicView.beforeEnter', function() { ionic.keyboard && ionic.keyboard.hide && ionic.keyboard.hide(); }); $rootScope.$on('$ionicHistory.change', function(e, data) { if (!data) return null; var viewHistory = $ionicHistory.viewHistory(); var hist = (data.historyId ? viewHistory.histories[ data.historyId ] : null); if (hist && hist.cursor > -1 && hist.cursor < hist.stack.length) { // the history they're going to already exists // go to it's last view in its stack var view = hist.stack[ hist.cursor ]; return view.go(data); } // this history does not have a URL, but it does have a uiSref // figure out its URL from the uiSref if (!data.url && data.uiSref) { data.url = $state.href(data.uiSref); } if (data.url) { // don't let it start with a #, messes with $location.url() if (data.url.indexOf('#') === 0) { data.url = data.url.replace('#', ''); } if (data.url !== $location.url()) { // we've got a good URL, ready GO! $location.url(data.url); } } }); $rootScope.$ionicGoBack = function(backCount) { $ionicHistory.goBack(backCount); }; // Set the document title when a new view is shown $rootScope.$on('$ionicView.afterEnter', function(ev, data) { if (data && data.title) { $document[0].title = data.title; } }); // Triggered when devices with a hardware back button (Android) is clicked by the user // This is a Cordova/Phonegap platform specifc method function onHardwareBackButton(e) { var backView = $ionicHistory.backView(); if (backView) { // there is a back view, go to it backView.go(); } else { // there is no back view, so close the app instead ionic.Platform.exitApp(); } e.preventDefault(); return false; } $ionicPlatform.registerBackButtonAction( onHardwareBackButton, IONIC_BACK_PRIORITY.view ); }]); /** * @ngdoc provider * @name $ionicConfigProvider * @module ionic * @description * Ionic automatically takes platform configurations into account to adjust things like what * transition style to use and whether tab icons should show on the top or bottom. For example, * iOS will move forward by transitioning the entering view from right to center and the leaving * view from center to left. However, Android will transition with the entering view going from * bottom to center, covering the previous view, which remains stationary. It should be noted * that when a platform is not iOS or Android, then it'll default to iOS. So if you are * developing on a desktop browser, it's going to take on iOS default configs. * * These configs can be changed using the `$ionicConfigProvider` during the configuration phase * of your app. Additionally, `$ionicConfig` can also set and get config values during the run * phase and within the app itself. * * By default, all base config variables are set to `'platform'`, which means it'll take on the * default config of the platform on which it's running. Config variables can be set at this * level so all platforms follow the same setting, rather than its platform config. * The following code would set the same config variable for all platforms: * * ```js * $ionicConfigProvider.views.maxCache(10); * ``` * * Additionally, each platform can have it's own config within the `$ionicConfigProvider.platform` * property. The config below would only apply to Android devices. * * ```js * $ionicConfigProvider.platform.android.views.maxCache(