mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Fixed navbar `showBackButton()` and `showBar()` methods to not set values to false if they were not passed an argument. Also, when swipe to go back is canceled, then correctly reset bar and back button values prior to the swipe to go back setting. Closes
491 lines
15 KiB
JavaScript
491 lines
15 KiB
JavaScript
IonicModule
|
|
.controller('$ionicNavView', [
|
|
'$scope',
|
|
'$element',
|
|
'$attrs',
|
|
'$compile',
|
|
'$controller',
|
|
'$ionicNavBarDelegate',
|
|
'$ionicNavViewDelegate',
|
|
'$ionicHistory',
|
|
'$ionicViewSwitcher',
|
|
'$ionicConfig',
|
|
'$ionicScrollDelegate',
|
|
function($scope, $element, $attrs, $compile, $controller, $ionicNavBarDelegate, $ionicNavViewDelegate, $ionicHistory, $ionicViewSwitcher, $ionicConfig, $ionicScrollDelegate) {
|
|
|
|
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;
|
|
var transitionDuration, transitionTiming;
|
|
|
|
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() && $ionicConfig.views.swipeBackEnabled()) {
|
|
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);
|
|
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() {
|
|
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_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);
|
|
$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) return;
|
|
|
|
startDragX = getDragX(ev);
|
|
if (startDragX > swipeBackHitWidth) return;
|
|
|
|
backView = $ionicHistory.backView();
|
|
|
|
if (!backView || backView.historyId !== $ionicHistory.currentView().historyId) 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, $ionicHistory.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');
|
|
}
|
|
|
|
}]);
|