diff --git a/config/docs/templates/api_menu_version.template.html b/config/docs/templates/api_menu_version.template.html
index d1fbb06b75..47aa7c8258 100644
--- a/config/docs/templates/api_menu_version.template.html
+++ b/config/docs/templates/api_menu_version.template.html
@@ -469,6 +469,12 @@
+
+
+ expose-aside-when
+
+
+
menu-toggle
diff --git a/js/angular/controller/sideMenuController.js b/js/angular/controller/sideMenuController.js
index 01b58b17f4..4c628b020f 100644
--- a/js/angular/controller/sideMenuController.js
+++ b/js/angular/controller/sideMenuController.js
@@ -4,10 +4,11 @@ IonicModule
'$attrs',
'$ionicSideMenuDelegate',
'$ionicPlatform',
-function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform) {
+ '$document',
+function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform, $document) {
var self = this;
var rightShowing, leftShowing, isDragging;
- var startX, lastX, offsetX;
+ var startX, lastX, offsetX, isAsideExposed;
self.$scope = $scope;
@@ -132,9 +133,9 @@ function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform) {
}
if(percentage !== 0) {
- document.body.classList.add('menu-open');
+ $document[0].body.classList.add('menu-open');
} else {
- document.body.classList.remove('menu-open');
+ $document[0].body.classList.remove('menu-open');
}
};
@@ -253,8 +254,32 @@ function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform) {
}
};
+ 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) {
+ if(isResizing) {
+ $document[0].body.classList.add('aside-resizing');
+ } else {
+ $document[0].body.classList.remove('aside-resizing');
+ }
+ };
+
// End a drag with the given event
self._endDrag = function(e) {
+ if(isAsideExposed) return;
+
if(isDragging) {
self.snapToRest(e);
}
@@ -265,6 +290,7 @@ function($scope, $attrs, $ionicSideMenuDelegate, $ionicPlatform) {
// Handle a drag event
self._handleDrag = function(e) {
+ if(isAsideExposed) return;
// If we don't have start coords, grab and store them
if(!startX) {
diff --git a/js/angular/directive/exposeAsideWhen.js b/js/angular/directive/exposeAsideWhen.js
new file mode 100644
index 0000000000..eef74b11c7
--- /dev/null
+++ b/js/angular/directive/exposeAsideWhen.js
@@ -0,0 +1,77 @@
+/**
+ * @ngdoc directive
+ * @name exposeAsideWhen
+ * @module ionic
+ * @restrict A
+ * @parent ionic.directive:ionSideMenus
+ *
+ * @description
+ * It is common for a tablet application to hide a menu when in portrait mode, but to show the
+ * same menu on the left side when the tablet is in landscape mode. The `exposeAsideWhen` attribute
+ * directive can be used to accomplish a similar interface.
+ *
+ * By default, side menus are hidden underneath its side menu content, and can be opened by either
+ * swiping the content left or right, or toggling a button to show the side menu. However, by adding the
+ * `exposeAsideWhen` attribute directive to an {@link ionic.directive:ionSideMenu} element directive,
+ * a side menu can be given instructions on "when" the menu should be exposed (always viewable). For
+ * example, the `expose-aside-when="large"` attribute will keep the side menu hidden when the viewport's
+ * width is less than `768px`, but when the viewport's width is `768px` or greater, the menu will then
+ * always be shown and can no longer be opened or closed like it could when it was hidden for smaller
+ * viewports.
+ *
+ * Using `large` as the attribute's value is a shortcut value to `(min-width:768px)` since it is
+ * the most common use-case. However, for added flexibility, any valid media query could be added
+ * as the value, such as `(min-width:600px)` or even multiple queries such as
+ * `(min-width:750px) and (max-width:1200px)`.
+
+ * @usage
+ * ```html
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * ```
+ * For a complete side menu example, see the
+ * {@link ionic.directive:ionSideMenus} documentation.
+ */
+IonicModule
+.directive('exposeAsideWhen', ['$window', function($window) {
+ return {
+ restrict: 'A',
+ require: '^ionSideMenus',
+ link: function($scope, $element, $attr, sideMenuCtrl) {
+
+ function checkAsideExpose() {
+ var mq = $attr.exposeAsideWhen == 'large' ? '(min-width:768px)' : $attr.exposeAsideWhen;
+ sideMenuCtrl.exposeAside( $window.matchMedia(mq).matches );
+ sideMenuCtrl.activeAsideResizing(false);
+ }
+
+ function onResize() {
+ sideMenuCtrl.activeAsideResizing(true);
+ debouncedCheck();
+ }
+
+ var debouncedCheck = ionic.debounce(function() {
+ $scope.$apply(function(){
+ checkAsideExpose();
+ });
+ }, 300, false);
+
+ checkAsideExpose();
+
+ ionic.on('resize', onResize, $window);
+
+ $scope.$on('$destroy', function(){
+ ionic.off('resize', onResize, $window);
+ });
+
+ }
+ };
+}]);
+
diff --git a/js/angular/directive/sideMenuContent.js b/js/angular/directive/sideMenuContent.js
index f7ce69f2b0..6810881f6d 100644
--- a/js/angular/directive/sideMenuContent.js
+++ b/js/angular/directive/sideMenuContent.js
@@ -30,7 +30,8 @@ IonicModule
.directive('ionSideMenuContent', [
'$timeout',
'$ionicGesture',
-function($timeout, $ionicGesture) {
+ '$window',
+function($timeout, $ionicGesture, $window) {
return {
restrict: 'EA', //DEPRECATED 'A'
@@ -126,7 +127,7 @@ function($timeout, $ionicGesture) {
}
}
- sideMenuCtrl.setContent({
+ var content = {
element: element[0],
onDrag: function(e) {},
endDrag: function(e) {},
@@ -134,11 +135,23 @@ function($timeout, $ionicGesture) {
return $scope.sideMenuContentTranslateX || 0;
},
setTranslateX: ionic.animationFrameThrottle(function(amount) {
- $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amount + 'px, 0, 0)';
+ var xTransform = content.offsetX + amount;
+ $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + xTransform + 'px,0,0)';
$timeout(function() {
$scope.sideMenuContentTranslateX = amount;
});
}),
+ setMarginLeft: ionic.animationFrameThrottle(function(amount) {
+ if(amount) {
+ $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amount + 'px,0,0)';
+ $element[0].style.width = ($window.innerWidth - amount) + 'px';
+ content.offsetX = amount;
+ } else {
+ $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(0,0,0)';
+ $element[0].style.width = '';
+ content.offsetX = 0;
+ }
+ }),
enableAnimation: function() {
$scope.animationEnabled = true;
$element[0].classList.add('menu-animated');
@@ -146,8 +159,11 @@ function($timeout, $ionicGesture) {
disableAnimation: function() {
$scope.animationEnabled = false;
$element[0].classList.remove('menu-animated');
- }
- });
+ },
+ offsetX: 0
+ };
+
+ sideMenuCtrl.setContent(content);
// add gesture handlers
var dragRightGesture = $ionicGesture.on('dragright', onDragX, $element);
diff --git a/js/angular/directive/sideMenus.js b/js/angular/directive/sideMenus.js
index 94819be2ac..d008b35e71 100644
--- a/js/angular/directive/sideMenus.js
+++ b/js/angular/directive/sideMenus.js
@@ -17,6 +17,12 @@ IonicModule
* links and buttons within `ion-side-menu` content, so that when the element is
* clicked then the opened side menu will automatically close.
*
+ * By default, side menus are hidden underneath its side menu content, and can be opened by
+ * either swiping the content left or right, or toggling a button to show the side menu. However,
+ * by adding the {@link ionic.directive:exposeAsideWhen} attribute directive to an
+ * {@link ionic.directive:ionSideMenu} element directive, a side menu can be given instructions
+ * on "when" the menu should be exposed (always viewable).
+ *
* 
*
* For more information on side menus, check out:
@@ -24,6 +30,7 @@ IonicModule
* - {@link ionic.directive:ionSideMenuContent}
* - {@link ionic.directive:ionSideMenu}
* - {@link ionic.directive:menuClose}
+ * - {@link ionic.directive:exposeAsideWhen}
*
* @usage
* To use side menus, add an `` parent element,
@@ -58,18 +65,35 @@ IonicModule
*
*/
.directive('ionSideMenus', ['$document', function($document) {
+
+ var ASIDE_OPEN_CSS = 'aside-open';
+
return {
restrict: 'ECA',
controller: '$ionicSideMenus',
compile: function(element, attr) {
attr.$set('class', (attr['class'] || '') + ' view');
- return function($scope) {
- $scope.$on('$destroy', function(){
- $document[0].body.classList.remove('menu-open');
+ return { pre: prelink };
+ function prelink($scope) {
+ var bodyClassList = $document[0].body.classList;
+
+ $scope.$on('$ionicExposeAside', function(evt, isAsideExposed){
+ if(!$scope.$exposeAside) $scope.$exposeAside = {};
+ $scope.$exposeAside.active = isAsideExposed;
+ if(isAsideExposed) {
+ bodyClassList.add(ASIDE_OPEN_CSS);
+ } else {
+ bodyClassList.remove(ASIDE_OPEN_CSS);
+ }
});
- };
+ $scope.$on('$destroy', function(){
+ bodyClassList.remove('menu-open');
+ bodyClassList.remove(ASIDE_OPEN_CSS);
+ });
+
+ }
}
};
}]);
diff --git a/scss/_menu.scss b/scss/_menu.scss
index 3788727cd4..c2bf286a69 100644
--- a/scss/_menu.scss
+++ b/scss/_menu.scss
@@ -55,6 +55,10 @@
right: 0;
}
+.aside-open.aside-resizing .menu-right {
+ display: none;
+}
+
.menu-animated {
@include transition-transform($menu-animation-speed ease);
}
diff --git a/scss/_split-pane.scss b/scss/_split-pane.scss
deleted file mode 100644
index ea84d65b17..0000000000
--- a/scss/_split-pane.scss
+++ /dev/null
@@ -1,29 +0,0 @@
-
-/**
- * Split Pane
- * --------------------------------------------------
- */
-
-.split-pane {
- @include display-flex();
- @include align-items(stretch);
- width: 100%;
- height: 100%;
-}
-
-.split-pane-menu {
- @include flex(0, 0, $split-pane-menu-width);
-
- overflow-y: auto;
- width: $split-pane-menu-width;
- height: 100%;
- border-right: 1px solid $split-pane-menu-border-color;
-
- @media all and (max-width: 568px) {
- border-right: none;
- }
-}
-
-.split-pane-content {
- @include flex(1, 0, auto);
-}
diff --git a/scss/_variables.scss b/scss/_variables.scss
index dfa41196e9..361dea9297 100644
--- a/scss/_variables.scss
+++ b/scss/_variables.scss
@@ -543,9 +543,6 @@ $menu-animation-speed: 200ms !default;
$menu-side-shadow: -1px 0px 2px rgba(0, 0, 0, 0.2), 1px 0px 2px rgba(0,0,0,0.2) !default;
-$split-pane-menu-width: 320px !default;
-$split-pane-menu-border-color: #eee !default;
-
// Modals
// -------------------------------
diff --git a/scss/ionic.scss b/scss/ionic.scss
index 5a426a4ecb..a659f0ca8e 100644
--- a/scss/ionic.scss
+++ b/scss/ionic.scss
@@ -27,7 +27,6 @@
"list",
"badge",
"slide-box",
- "split-pane",
// Forms
"form",
diff --git a/test/html/sideMenu.html b/test/html/sideMenu.html
index b9983de104..5145f47ee8 100644
--- a/test/html/sideMenu.html
+++ b/test/html/sideMenu.html
@@ -15,7 +15,7 @@
@@ -32,7 +32,7 @@
-
+
diff --git a/test/unit/angular/directive/sideMenu.unit.js b/test/unit/angular/directive/sideMenu.unit.js
index 2916cfefe9..0892f90bf1 100644
--- a/test/unit/angular/directive/sideMenu.unit.js
+++ b/test/unit/angular/directive/sideMenu.unit.js
@@ -24,6 +24,55 @@ describe('Ionic Angular Side Menu', function() {
expect(deregisterSpy).toHaveBeenCalled();
}));
+ it('should set $exposeAside.active', inject(function($compile, $rootScope) {
+ var el = $compile('>')($rootScope.$new());
+ $rootScope.$apply();
+ var sideMenuController = el.controller('ionSideMenus');
+ expect(sideMenuController.isAsideExposed()).toBe(false);
+ expect(el.scope().$exposeAside).toBeUndefined();
+
+ sideMenuController.exposeAside(true);
+ expect(el.scope().$exposeAside.active).toEqual(true);
+ expect(sideMenuController.isAsideExposed()).toBe(true);
+
+ sideMenuController.exposeAside(false);
+ expect(el.scope().$exposeAside.active).toEqual(false);
+ expect(sideMenuController.isAsideExposed()).toBe(false);
+ }));
+
+ it('should emit $ionicexposeAside', inject(function($compile, $rootScope) {
+ var el = $compile('>')($rootScope.$new());
+ $rootScope.$apply();
+ var sideMenuController = el.controller('ionSideMenus');
+
+ spyOn(el.scope(), "$emit")
+ sideMenuController.exposeAside(true);
+ expect(el.scope().$emit).toHaveBeenCalledWith("$ionicExposeAside", true);
+
+ sideMenuController.exposeAside(false);
+ expect(el.scope().$emit).toHaveBeenCalledWith("$ionicExposeAside", false);
+ }));
+
+ it('should set exposed menu', inject(function($compile, $rootScope) {
+ var el = $compile('>')($rootScope.$new());
+ $rootScope.$apply();
+ var sideMenuController = el.controller('ionSideMenus');
+ var content = sideMenuController.content;
+ expect(content.offsetX).toEqual(0);
+ expect(content.getTranslateX()).toEqual(0);
+ expect(content.element.style.width).toEqual('');
+ sideMenuController.exposeAside(true);
+ expect(content.offsetX).toEqual(275);
+ expect(content.getTranslateX()).toEqual(0);
+ expect(content.element.getAttribute('style').indexOf('translate3d(275px, 0px, 0px)') > -1).toEqual(true);
+ expect(content.element.style.width).toNotEqual('');
+ sideMenuController.exposeAside(false);
+ expect(content.element.getAttribute('style').indexOf('translate3d(0px, 0px, 0px)') > -1).toEqual(true);
+ expect(content.getTranslateX()).toEqual(0);
+ expect(content.offsetX).toEqual(0);
+ expect(content.element.style.width).toEqual('');
+ }));
+
it('should canDragContent', inject(function($compile, $rootScope) {
var el = $compile('')($rootScope.$new());
$rootScope.$apply();