UNPKG

ionic-angular

Version:

[![Circle CI](https://circleci.com/gh/driftyco/ionic.svg?style=svg)](https://circleci.com/gh/driftyco/ionic)

512 lines (389 loc) 16.3 kB
IonicModule .controller('$ionicNavView', [ '$scope', '$element', '$attrs', '$compile', '$controller', '$ionicNavBarDelegate', '$ionicNavViewDelegate', '$ionicHistory', '$ionicViewSwitcher', '$ionicConfig', '$ionicScrollDelegate', '$ionicSideMenuDelegate', function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, $ionicNavViewDelegate, $ionicHistory, $ionicViewSwitcher, $ionicConfig, $ionicScrollDelegate, $ionicSideMenuDelegate) { var DATA_ELE_IDENTIFIER = '$eleId'; var DATA_DESTROY_ELE = '$destroyEle'; var DATA_NO_CACHE = '$noCache'; var VIEW_STATUS_ACTIVE = 'active'; var VIEW_STATUS_CACHED = 'cached'; var self = this; var direction; var isPrimary = false; var navBarDelegate; var activeEleId; var navViewAttr = $ionicViewSwitcher.navViewAttr; var disableRenderStartViewId, disableAnimation; self.scope = $scope; self.element = $element; self.init = function() { var navViewName = $attrs.name || ''; // Find the details of the parent view directive (if any) and use it // to derive our own qualified view name, then hang our own details // off the DOM so child directives can find it. var parent = $element.parent().inheritedData('$uiView'); var parentViewName = ((parent && parent.state) ? parent.state.name : ''); if (navViewName.indexOf('@') < 0) navViewName = navViewName + '@' + parentViewName; var viewData = { name: navViewName, state: null }; $element.data('$uiView', viewData); var deregisterInstance = $ionicNavViewDelegate._registerInstance(self, $attrs.delegateHandle); $scope.$on('$destroy', function() { deregisterInstance(); // ensure no scrolls have been left frozen if (self.isSwipeFreeze) { $ionicScrollDelegate.freezeAllScrolls(false); } }); $scope.$on('$ionicHistory.deselect', self.cacheCleanup); $scope.$on('$ionicTabs.top', onTabsTop); $scope.$on('$ionicSubheader', onBarSubheader); $scope.$on('$ionicTabs.beforeLeave', onTabsLeave); $scope.$on('$ionicTabs.afterLeave', onTabsLeave); $scope.$on('$ionicTabs.leave', onTabsLeave); ionic.Platform.ready(function() { if ( ionic.Platform.isWebView() && ionic.Platform.isIOS() ) { self.initSwipeBack(); } }); return viewData; }; self.register = function(viewLocals) { var leavingView = extend({}, $ionicHistory.currentView()); // register that a view is coming in and get info on how it should transition var registerData = $ionicHistory.register($scope, viewLocals); // update which direction self.update(registerData); // begin rendering and transitioning var enteringView = $ionicHistory.getViewById(registerData.viewId) || {}; var renderStart = (disableRenderStartViewId !== registerData.viewId); self.render(registerData, viewLocals, enteringView, leavingView, renderStart, true); }; self.update = function(registerData) { // always reset that this is the primary navView isPrimary = true; // remember what direction this navView should use // this may get updated later by a child navView direction = registerData.direction; var parentNavViewCtrl = $element.parent().inheritedData('$ionNavViewController'); if (parentNavViewCtrl) { // this navView is nested inside another one // update the parent to use this direction and not // the other it originally was set to // inform the parent navView that it is not the primary navView parentNavViewCtrl.isPrimary(false); if (direction === 'enter' || direction === 'exit') { // they're entering/exiting a history // find parent navViewController parentNavViewCtrl.direction(direction); if (direction === 'enter') { // reset the direction so this navView doesn't animate // because it's parent will direction = 'none'; } } } }; self.render = function(registerData, viewLocals, enteringView, leavingView, renderStart, renderEnd) { // register the view and figure out where it lives in the various // histories and nav stacks, along with how views should enter/leave var switcher = $ionicViewSwitcher.create(self, viewLocals, enteringView, leavingView, renderStart, renderEnd); // init the rendering of views for this navView directive switcher.init(registerData, function() { // the view is now compiled, in the dom and linked, now lets transition the views. // this uses a callback incase THIS nav-view has a nested nav-view, and after the NESTED // nav-view links, the NESTED nav-view would update which direction THIS nav-view should use // kick off the transition of views switcher.transition(self.direction(), registerData.enableBack, !disableAnimation); // reset private vars for next time disableRenderStartViewId = disableAnimation = null; }); }; self.beforeEnter = function(transitionData) { if (isPrimary) { // only update this nav-view's nav-bar if this is the primary nav-view navBarDelegate = transitionData.navBarDelegate; var associatedNavBarCtrl = getAssociatedNavBarCtrl(); associatedNavBarCtrl && associatedNavBarCtrl.update(transitionData); navSwipeAttr(''); } }; self.activeEleId = function(eleId) { if (arguments.length) { activeEleId = eleId; } return activeEleId; }; self.transitionEnd = function() { var viewElements = $element.children(); var x, l, viewElement; for (x = 0, l = viewElements.length; x < l; x++) { viewElement = viewElements.eq(x); if (viewElement.data(DATA_ELE_IDENTIFIER) === activeEleId) { // this is the active element navViewAttr(viewElement, VIEW_STATUS_ACTIVE); } else if (navViewAttr(viewElement) === 'leaving' || navViewAttr(viewElement) === VIEW_STATUS_ACTIVE || navViewAttr(viewElement) === VIEW_STATUS_CACHED) { // this is a leaving element or was the former active element, or is an cached element if (viewElement.data(DATA_DESTROY_ELE) || viewElement.data(DATA_NO_CACHE)) { // this element shouldn't stay cached $ionicViewSwitcher.destroyViewEle(viewElement); } else { // keep in the DOM, mark as cached navViewAttr(viewElement, VIEW_STATUS_CACHED); // disconnect the leaving scope ionic.Utils.disconnectScope(viewElement.scope()); } } } navSwipeAttr(''); // ensure no scrolls have been left frozen if (self.isSwipeFreeze) { $ionicScrollDelegate.freezeAllScrolls(false); } }; function onTabsLeave(ev, data) { var viewElements = $element.children(); var viewElement, viewScope; for (var x = 0, l = viewElements.length; x < l; x++) { viewElement = viewElements.eq(x); if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) { viewScope = viewElement.scope(); viewScope && viewScope.$emit(ev.name.replace('Tabs', 'View'), data); viewScope && viewScope.$broadcast(ev.name.replace('Tabs', 'ParentView'), data); break; } } } self.cacheCleanup = function() { var viewElements = $element.children(); for (var x = 0, l = viewElements.length; x < l; x++) { if (viewElements.eq(x).data(DATA_DESTROY_ELE)) { $ionicViewSwitcher.destroyViewEle(viewElements.eq(x)); } } }; self.clearCache = function(stateIds) { var viewElements = $element.children(); var viewElement, viewScope, x, l, y, eleIdentifier; for (x = 0, l = viewElements.length; x < l; x++) { viewElement = viewElements.eq(x); if (stateIds) { eleIdentifier = viewElement.data(DATA_ELE_IDENTIFIER); for (y = 0; y < stateIds.length; y++) { if (eleIdentifier === stateIds[y]) { $ionicViewSwitcher.destroyViewEle(viewElement); } } continue; } if (navViewAttr(viewElement) == VIEW_STATUS_CACHED) { $ionicViewSwitcher.destroyViewEle(viewElement); } else if (navViewAttr(viewElement) == VIEW_STATUS_ACTIVE) { viewScope = viewElement.scope(); viewScope && viewScope.$broadcast('$ionicView.clearCache'); } } }; self.getViewElements = function() { return $element.children(); }; self.appendViewElement = function(viewEle, viewLocals) { // compile the entering element and get the link function var linkFn = $compile(viewEle); $element.append(viewEle); var viewScope = $scope.$new(); if (viewLocals && viewLocals.$$controller) { viewLocals.$scope = viewScope; var controller = $controller(viewLocals.$$controller, viewLocals); if (viewLocals.$$controllerAs) { viewScope[viewLocals.$$controllerAs] = controller; } $element.children().data('$ngControllerController', controller); } linkFn(viewScope); return viewScope; }; self.title = function(val) { var associatedNavBarCtrl = getAssociatedNavBarCtrl(); associatedNavBarCtrl && associatedNavBarCtrl.title(val); }; /** * @ngdoc method * @name $ionicNavView#enableBackButton * @description Enable/disable if the back button can be shown or not. For * example, the very first view in the navigation stack would not have a * back view, so the back button would be disabled. */ self.enableBackButton = function(shouldEnable) { var associatedNavBarCtrl = getAssociatedNavBarCtrl(); associatedNavBarCtrl && associatedNavBarCtrl.enableBackButton(shouldEnable); }; /** * @ngdoc method * @name $ionicNavView#showBackButton * @description Show/hide the nav bar active back button. If the back button * is not possible this will not force the back button to show. The * `enableBackButton()` method handles if a back button is even possible or not. */ self.showBackButton = function(shouldShow) { var associatedNavBarCtrl = getAssociatedNavBarCtrl(); if (associatedNavBarCtrl) { if (arguments.length) { return associatedNavBarCtrl.showActiveBackButton(shouldShow); } return associatedNavBarCtrl.showActiveBackButton(); } return true; }; self.showBar = function(val) { var associatedNavBarCtrl = getAssociatedNavBarCtrl(); if (associatedNavBarCtrl) { if (arguments.length) { return associatedNavBarCtrl.showBar(val); } return associatedNavBarCtrl.showBar(); } return true; }; self.isPrimary = function(val) { if (arguments.length) { isPrimary = val; } return isPrimary; }; self.direction = function(val) { if (arguments.length) { direction = val; } return direction; }; self.initSwipeBack = function() { var swipeBackHitWidth = $ionicConfig.views.swipeBackHitWidth(); var viewTransition, associatedNavBarCtrl, backView; var deregDragStart, deregDrag, deregRelease; var windowWidth, startDragX, dragPoints; var cancelData = {}; function onDragStart(ev) { if (!isPrimary || !$ionicConfig.views.swipeBackEnabled() || $ionicSideMenuDelegate.isOpenRight() ) return; startDragX = getDragX(ev); if (startDragX > swipeBackHitWidth) return; backView = $ionicHistory.backView(); var currentView = $ionicHistory.currentView(); if (!backView || backView.historyId !== currentView.historyId || currentView.canSwipeBack === false) return; if (!windowWidth) windowWidth = window.innerWidth; self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(true); var registerData = { direction: 'back' }; dragPoints = []; cancelData = { showBar: self.showBar(), showBackButton: self.showBackButton() }; var switcher = $ionicViewSwitcher.create(self, registerData, backView, currentView, true, false); switcher.loadViewElements(registerData); switcher.render(registerData); viewTransition = switcher.transition('back', $ionicHistory.enabledBack(backView), true); associatedNavBarCtrl = getAssociatedNavBarCtrl(); deregDrag = ionic.onGesture('drag', onDrag, $element[0]); deregRelease = ionic.onGesture('release', onRelease, $element[0]); } function onDrag(ev) { if (isPrimary && viewTransition) { var dragX = getDragX(ev); dragPoints.push({ t: Date.now(), x: dragX }); if (dragX >= windowWidth - 15) { onRelease(ev); } else { var step = Math.min(Math.max(getSwipeCompletion(dragX), 0), 1); viewTransition.run(step); associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.run(step); } } } function onRelease(ev) { if (isPrimary && viewTransition && dragPoints && dragPoints.length > 1) { var now = Date.now(); var releaseX = getDragX(ev); var startDrag = dragPoints[dragPoints.length - 1]; for (var x = dragPoints.length - 2; x >= 0; x--) { if (now - startDrag.t > 200) { break; } startDrag = dragPoints[x]; } var isSwipingRight = (releaseX >= dragPoints[dragPoints.length - 2].x); var releaseSwipeCompletion = getSwipeCompletion(releaseX); var velocity = Math.abs(startDrag.x - releaseX) / (now - startDrag.t); // private variables because ui-router has no way to pass custom data using $state.go disableRenderStartViewId = backView.viewId; disableAnimation = (releaseSwipeCompletion < 0.03 || releaseSwipeCompletion > 0.97); if (isSwipingRight && (releaseSwipeCompletion > 0.5 || velocity > 0.1)) { // complete view transition on release var speed = (velocity > 0.5 || velocity < 0.05 || releaseX > windowWidth - 45) ? 'fast' : 'slow'; navSwipeAttr(disableAnimation ? '' : speed); backView.go(); associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.complete(!disableAnimation, speed); } else { // cancel view transition on release navSwipeAttr(disableAnimation ? '' : 'fast'); disableRenderStartViewId = null; viewTransition.cancel(!disableAnimation); associatedNavBarCtrl && associatedNavBarCtrl.activeTransition && associatedNavBarCtrl.activeTransition.cancel(!disableAnimation, 'fast', cancelData); disableAnimation = null; } } ionic.offGesture(deregDrag, 'drag', onDrag); ionic.offGesture(deregRelease, 'release', onRelease); windowWidth = viewTransition = dragPoints = null; self.isSwipeFreeze = $ionicScrollDelegate.freezeAllScrolls(false); } function getDragX(ev) { return ionic.tap.pointerCoord(ev.gesture.srcEvent).x; } function getSwipeCompletion(dragX) { return (dragX - startDragX) / windowWidth; } deregDragStart = ionic.onGesture('dragstart', onDragStart, $element[0]); $scope.$on('$destroy', function() { ionic.offGesture(deregDragStart, 'dragstart', onDragStart); ionic.offGesture(deregDrag, 'drag', onDrag); ionic.offGesture(deregRelease, 'release', onRelease); self.element = viewTransition = associatedNavBarCtrl = null; }); }; function navSwipeAttr(val) { ionic.DomUtil.cachedAttr($element, 'nav-swipe', val); } function onTabsTop(ev, isTabsTop) { var associatedNavBarCtrl = getAssociatedNavBarCtrl(); associatedNavBarCtrl && associatedNavBarCtrl.hasTabsTop(isTabsTop); } function onBarSubheader(ev, isBarSubheader) { var associatedNavBarCtrl = getAssociatedNavBarCtrl(); associatedNavBarCtrl && associatedNavBarCtrl.hasBarSubheader(isBarSubheader); } function getAssociatedNavBarCtrl() { if (navBarDelegate) { for (var x = 0; x < $ionicNavBarDelegate._instances.length; x++) { if ($ionicNavBarDelegate._instances[x].$$delegateHandle == navBarDelegate) { return $ionicNavBarDelegate._instances[x]; } } } return $element.inheritedData('$ionNavBarController'); } }]);