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). + * * ![Side Menu](http://ionicframework.com.s3.amazonaws.com/docs/controllers/sidemenu.gif) * * 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 @@
    - +

    Slide me

    @@ -32,7 +32,7 @@
    - +

    Left

    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();