mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
If an element isContentEditable, do not ignoreScrollStart incase users are using contenteditable elements to scroll. This may have originally been put in because it disabled text selection, and moving the text cursor on touch. But this doesn’t seem to be the case anymore, so it may have been put in for platform versions we no longer support. Also fix the data-prevent-scroll dataset attribute. Closes #2091
386 lines
11 KiB
JavaScript
386 lines
11 KiB
JavaScript
IonicModule
|
|
.controller('$ionicSideMenus', [
|
|
'$scope',
|
|
'$attrs',
|
|
'$ionicSideMenuDelegate',
|
|
'$ionicPlatform',
|
|
'$ionicBody',
|
|
function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform, $ionicBody) {
|
|
var self = this;
|
|
var rightShowing, leftShowing, isDragging;
|
|
var startX, lastX, offsetX, isAsideExposed;
|
|
|
|
self.$scope = $scope;
|
|
|
|
self.initialize = function(options) {
|
|
self.left = options.left;
|
|
self.right = options.right;
|
|
self.setContent(options.content);
|
|
self.dragThresholdX = options.dragThresholdX || 10;
|
|
};
|
|
|
|
/**
|
|
* Set the content view controller if not passed in the constructor options.
|
|
*
|
|
* @param {object} content
|
|
*/
|
|
self.setContent = function(content) {
|
|
if(content) {
|
|
self.content = content;
|
|
|
|
self.content.onDrag = function(e) {
|
|
self._handleDrag(e);
|
|
};
|
|
|
|
self.content.endDrag = function(e) {
|
|
self._endDrag(e);
|
|
};
|
|
}
|
|
};
|
|
|
|
self.isOpenLeft = function() {
|
|
return self.getOpenAmount() > 0;
|
|
};
|
|
|
|
self.isOpenRight = function() {
|
|
return self.getOpenAmount() < 0;
|
|
};
|
|
|
|
/**
|
|
* Toggle the left menu to open 100%
|
|
*/
|
|
self.toggleLeft = function(shouldOpen) {
|
|
var openAmount = self.getOpenAmount();
|
|
if (arguments.length === 0) {
|
|
shouldOpen = openAmount <= 0;
|
|
}
|
|
self.content.enableAnimation();
|
|
if(!shouldOpen) {
|
|
self.openPercentage(0);
|
|
} else {
|
|
self.openPercentage(100);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Toggle the right menu to open 100%
|
|
*/
|
|
self.toggleRight = function(shouldOpen) {
|
|
var openAmount = self.getOpenAmount();
|
|
if (arguments.length === 0) {
|
|
shouldOpen = openAmount >= 0;
|
|
}
|
|
self.content.enableAnimation();
|
|
if(!shouldOpen) {
|
|
self.openPercentage(0);
|
|
} else {
|
|
self.openPercentage(-100);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Close all menus.
|
|
*/
|
|
self.close = function() {
|
|
self.openPercentage(0);
|
|
};
|
|
|
|
/**
|
|
* @return {float} The amount the side menu is open, either positive or negative for left (positive), or right (negative)
|
|
*/
|
|
self.getOpenAmount = function() {
|
|
return self.content && self.content.getTranslateX() || 0;
|
|
};
|
|
|
|
/**
|
|
* @return {float} The ratio of open amount over menu width. For example, a
|
|
* menu of width 100 open 50 pixels would be open 50% or a ratio of 0.5. Value is negative
|
|
* for right menu.
|
|
*/
|
|
self.getOpenRatio = function() {
|
|
var amount = self.getOpenAmount();
|
|
if(amount >= 0) {
|
|
return amount / self.left.width;
|
|
}
|
|
return amount / self.right.width;
|
|
};
|
|
|
|
self.isOpen = function() {
|
|
return self.getOpenAmount() !== 0;
|
|
};
|
|
|
|
/**
|
|
* @return {float} The percentage of open amount over menu width. For example, a
|
|
* menu of width 100 open 50 pixels would be open 50%. Value is negative
|
|
* for right menu.
|
|
*/
|
|
self.getOpenPercentage = function() {
|
|
return self.getOpenRatio() * 100;
|
|
};
|
|
|
|
/**
|
|
* Open the menu with a given percentage amount.
|
|
* @param {float} percentage The percentage (positive or negative for left/right) to open the menu.
|
|
*/
|
|
self.openPercentage = function(percentage) {
|
|
var p = percentage / 100;
|
|
|
|
if(self.left && percentage >= 0) {
|
|
self.openAmount(self.left.width * p);
|
|
} else if(self.right && percentage < 0) {
|
|
var maxRight = self.right.width;
|
|
self.openAmount(self.right.width * p);
|
|
}
|
|
|
|
// add the CSS class "menu-open" if the percentage does not
|
|
// equal 0, otherwise remove the class from the body element
|
|
$ionicBody.enableClass( (percentage !== 0), 'menu-open');
|
|
};
|
|
|
|
/**
|
|
* Open the menu the given pixel amount.
|
|
* @param {float} amount the pixel amount to open the menu. Positive value for left menu,
|
|
* negative value for right menu (only one menu will be visible at a time).
|
|
*/
|
|
self.openAmount = function(amount) {
|
|
var maxLeft = self.left && self.left.width || 0;
|
|
var maxRight = self.right && self.right.width || 0;
|
|
|
|
// Check if we can move to that side, depending if the left/right panel is enabled
|
|
if(!(self.left && self.left.isEnabled) && amount > 0) {
|
|
self.content.setTranslateX(0);
|
|
return;
|
|
}
|
|
|
|
if(!(self.right && self.right.isEnabled) && amount < 0) {
|
|
self.content.setTranslateX(0);
|
|
return;
|
|
}
|
|
|
|
if(leftShowing && amount > maxLeft) {
|
|
self.content.setTranslateX(maxLeft);
|
|
return;
|
|
}
|
|
|
|
if(rightShowing && amount < -maxRight) {
|
|
self.content.setTranslateX(-maxRight);
|
|
return;
|
|
}
|
|
|
|
self.content.setTranslateX(amount);
|
|
|
|
if(amount >= 0) {
|
|
leftShowing = true;
|
|
rightShowing = false;
|
|
|
|
if(amount > 0) {
|
|
// Push the z-index of the right menu down
|
|
self.right && self.right.pushDown && self.right.pushDown();
|
|
// Bring the z-index of the left menu up
|
|
self.left && self.left.bringUp && self.left.bringUp();
|
|
}
|
|
} else {
|
|
rightShowing = true;
|
|
leftShowing = false;
|
|
|
|
// Bring the z-index of the right menu up
|
|
self.right && self.right.bringUp && self.right.bringUp();
|
|
// Push the z-index of the left menu down
|
|
self.left && self.left.pushDown && self.left.pushDown();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Given an event object, find the final resting position of this side
|
|
* menu. For example, if the user "throws" the content to the right and
|
|
* releases the touch, the left menu should snap open (animated, of course).
|
|
*
|
|
* @param {Event} e the gesture event to use for snapping
|
|
*/
|
|
self.snapToRest = function(e) {
|
|
// We want to animate at the end of this
|
|
self.content.enableAnimation();
|
|
isDragging = false;
|
|
|
|
// Check how much the panel is open after the drag, and
|
|
// what the drag velocity is
|
|
var ratio = self.getOpenRatio();
|
|
|
|
if(ratio === 0) {
|
|
// Just to be safe
|
|
self.openPercentage(0);
|
|
return;
|
|
}
|
|
|
|
var velocityThreshold = 0.3;
|
|
var velocityX = e.gesture.velocityX;
|
|
var direction = e.gesture.direction;
|
|
|
|
// Going right, less than half, too slow (snap back)
|
|
if(ratio > 0 && ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {
|
|
self.openPercentage(0);
|
|
}
|
|
|
|
// Going left, more than half, too slow (snap back)
|
|
else if(ratio > 0.5 && direction == 'left' && velocityX < velocityThreshold) {
|
|
self.openPercentage(100);
|
|
}
|
|
|
|
// Going left, less than half, too slow (snap back)
|
|
else if(ratio < 0 && ratio > -0.5 && direction == 'left' && velocityX < velocityThreshold) {
|
|
self.openPercentage(0);
|
|
}
|
|
|
|
// Going right, more than half, too slow (snap back)
|
|
else if(ratio < 0.5 && direction == 'right' && velocityX < velocityThreshold) {
|
|
self.openPercentage(-100);
|
|
}
|
|
|
|
// Going right, more than half, or quickly (snap open)
|
|
else if(direction == 'right' && ratio >= 0 && (ratio >= 0.5 || velocityX > velocityThreshold)) {
|
|
self.openPercentage(100);
|
|
}
|
|
|
|
// Going left, more than half, or quickly (span open)
|
|
else if(direction == 'left' && ratio <= 0 && (ratio <= -0.5 || velocityX > velocityThreshold)) {
|
|
self.openPercentage(-100);
|
|
}
|
|
|
|
// Snap back for safety
|
|
else {
|
|
self.openPercentage(0);
|
|
}
|
|
};
|
|
|
|
self.isAsideExposed = function() {
|
|
return !!isAsideExposed;
|
|
};
|
|
|
|
self.exposeAside = function(shouldExposeAside) {
|
|
isAsideExposed = shouldExposeAside;
|
|
|
|
// set the left marget width if it should be exposed
|
|
// otherwise set false so there's no left margin
|
|
self.content.setMarginLeft( isAsideExposed ? self.left.width : 0 );
|
|
|
|
self.$scope.$emit('$ionicExposeAside', isAsideExposed);
|
|
};
|
|
|
|
self.activeAsideResizing = function(isResizing) {
|
|
$ionicBody.enableClass(isResizing, 'aside-resizing');
|
|
};
|
|
|
|
// End a drag with the given event
|
|
self._endDrag = function(e) {
|
|
if(isAsideExposed) return;
|
|
|
|
if(isDragging) {
|
|
self.snapToRest(e);
|
|
}
|
|
startX = null;
|
|
lastX = null;
|
|
offsetX = null;
|
|
};
|
|
|
|
// Handle a drag event
|
|
self._handleDrag = function(e) {
|
|
if(isAsideExposed) return;
|
|
|
|
// If we don't have start coords, grab and store them
|
|
if(!startX) {
|
|
startX = e.gesture.touches[0].pageX;
|
|
lastX = startX;
|
|
} else {
|
|
// Grab the current tap coords
|
|
lastX = e.gesture.touches[0].pageX;
|
|
}
|
|
|
|
// Calculate difference from the tap points
|
|
if(!isDragging && Math.abs(lastX - startX) > self.dragThresholdX) {
|
|
// if the difference is greater than threshold, start dragging using the current
|
|
// point as the starting point
|
|
startX = lastX;
|
|
|
|
isDragging = true;
|
|
// Initialize dragging
|
|
self.content.disableAnimation();
|
|
offsetX = self.getOpenAmount();
|
|
}
|
|
|
|
if(isDragging) {
|
|
self.openAmount(offsetX + (lastX - startX));
|
|
}
|
|
};
|
|
|
|
self.canDragContent = function(canDrag) {
|
|
if (arguments.length) {
|
|
$scope.dragContent = !!canDrag;
|
|
}
|
|
return $scope.dragContent;
|
|
};
|
|
|
|
self.edgeThreshold = 25;
|
|
self.edgeThresholdEnabled = false;
|
|
self.edgeDragThreshold = function(value) {
|
|
if (arguments.length) {
|
|
if (angular.isNumber(value) && value > 0) {
|
|
self.edgeThreshold = value;
|
|
self.edgeThresholdEnabled = true;
|
|
} else {
|
|
self.edgeThresholdEnabled = !!value;
|
|
}
|
|
}
|
|
return self.edgeThresholdEnabled;
|
|
};
|
|
|
|
self.isDraggableTarget = function(e) {
|
|
//Only restrict edge when sidemenu is closed and restriction is enabled
|
|
var shouldOnlyAllowEdgeDrag = self.edgeThresholdEnabled && !self.isOpen();
|
|
var startX = e.gesture.startEvent && e.gesture.startEvent.center &&
|
|
e.gesture.startEvent.center.pageX;
|
|
|
|
var dragIsWithinBounds = !shouldOnlyAllowEdgeDrag ||
|
|
startX <= self.edgeThreshold ||
|
|
startX >= self.content.offsetWidth - self.edgeThreshold;
|
|
|
|
return ($scope.dragContent || self.isOpen()) &&
|
|
dragIsWithinBounds &&
|
|
!e.gesture.srcEvent.defaultPrevented &&
|
|
!e.target.tagName.match(/input|textarea|select|object|embed/i) &&
|
|
!e.target.isContentEditable &&
|
|
!(e.target.dataset ? e.target.dataset.preventScroll : e.target.getAttribute('data-prevent-scroll') == 'true');
|
|
};
|
|
|
|
$scope.sideMenuContentTranslateX = 0;
|
|
|
|
var deregisterBackButtonAction = angular.noop;
|
|
var closeSideMenu = angular.bind(self, self.close);
|
|
|
|
$scope.$watch(function() {
|
|
return self.getOpenAmount() !== 0;
|
|
}, function(isOpen) {
|
|
deregisterBackButtonAction();
|
|
if (isOpen) {
|
|
deregisterBackButtonAction = $ionicPlatform.registerBackButtonAction(
|
|
closeSideMenu,
|
|
PLATFORM_BACK_BUTTON_PRIORITY_SIDE_MENU
|
|
);
|
|
}
|
|
});
|
|
|
|
var deregisterInstance = $ionicSideMenuDelegate._registerInstance(
|
|
self, $attrs.delegateHandle
|
|
);
|
|
|
|
$scope.$on('$destroy', function() {
|
|
deregisterInstance();
|
|
deregisterBackButtonAction();
|
|
});
|
|
|
|
self.initialize({
|
|
left: { width: 275 },
|
|
right: { width: 275 }
|
|
});
|
|
|
|
}]);
|