From acf12cdd150e25b23f06fe68bdaeb478e1627b4f Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Thu, 4 Feb 2016 16:47:02 -0600 Subject: [PATCH] refactor(menu): inject MenuController to control app menus Menu has been improved to make it easier to open, close, toggle and enable menus. Instead of injecting `IonicApp` to find the menu component, you now inject `MenuController`. Was: ``` constructor(app: IonicApp) { this.app = app; } openMenu() { this.app.getComponent('leftMenu').close(); } ``` Now: To programmatically interact with any menu, you can inject the `MenuController` provider into any component or directive. This makes it easy get ahold of and control the correct menu instance. By default Ionic will find the app's menu without requiring a menu ID. An id attribute on an `` is only required if there are multiple menus on the same side. If there are multiple menus, but on different sides, you can use the name of the side to get the correct menu If there's only one menu: ``` constructor(menu: MenuController) { this.menu = menu; } openMenu() { this.menu.close(); } ``` If there is a menu on the left and right side: ``` toggleMenu() { this.menu.toggle('left'); } ``` If there are multiple menus on the same side: ``` ... ... closeMenu() { this.menu.close('myMenuId'); } ``` --- ionic/components.ts | 1 + ionic/components/app/app.ts | 19 +- ionic/components/menu/menu-close.ts | 12 +- ionic/components/menu/menu-controller.ts | 203 ++++++++++++++++++ ionic/components/menu/menu-gestures.ts | 33 ++- ionic/components/menu/menu-toggle.ts | 15 +- ionic/components/menu/menu-types.ts | 8 +- ionic/components/menu/menu.ts | 165 ++------------ ionic/components/menu/test/basic/index.ts | 6 +- ionic/components/menu/test/basic/main.html | 54 ++--- ionic/components/menu/test/basic/page1.html | 8 +- ionic/components/menu/test/basic/page2.html | 4 +- ionic/components/menu/test/basic/page3.html | 4 +- .../menu/test/disable-swipe/index.ts | 10 +- .../menu/test/disable-swipe/main.html | 4 +- .../menu/test/disable-swipe/page1.html | 4 +- ionic/components/menu/test/multiple/index.ts | 17 +- ionic/components/menu/test/overlay/main.html | 4 +- ionic/components/menu/test/overlay/page1.html | 8 +- ionic/components/menu/test/push/main.html | 4 +- ionic/components/menu/test/push/page1.html | 8 +- ionic/components/menu/test/reveal/main.html | 4 +- ionic/components/menu/test/reveal/page1.html | 8 +- ionic/components/nav/test/nested/index.ts | 10 +- ionic/config/bootstrap.ts | 22 +- 25 files changed, 365 insertions(+), 270 deletions(-) create mode 100644 ionic/components/menu/menu-controller.ts diff --git a/ionic/components.ts b/ionic/components.ts index acfe79c892..0c315e0b33 100644 --- a/ionic/components.ts +++ b/ionic/components.ts @@ -10,6 +10,7 @@ export * from './components/icon/icon' export * from './components/input/input' export * from './components/item/item' export * from './components/item/item-sliding' +export * from './components/menu/menu-controller' export * from './components/menu/menu' export * from './components/menu/menu-types' export * from './components/menu/menu-toggle' diff --git a/ionic/components/app/app.ts b/ionic/components/app/app.ts index ec16214ada..cf8864e743 100644 --- a/ionic/components/app/app.ts +++ b/ionic/components/app/app.ts @@ -97,9 +97,6 @@ export class IonicApp { * @param {Object} component The component to register */ register(id: string, component: any) { - if (this.components[id] && this.components[id] !== component) { - //console.error('Component id "' + id + '" already registered.'); - } this.components[id] = component; } @@ -134,6 +131,22 @@ export class IonicApp { * @return {Object} TODO */ getComponent(id: string): any { + // deprecated warning + if (/menu/i.test(id)) { + console.warn('Using app.getComponent(menuId) to control menus has been deprecated as of alpha55.\n' + + 'Instead inject MenuController, for example:\n\n' + + 'constructor(menu: MenuController) {\n' + + ' this.menu = menu;\n' + + '}\n' + + 'toggleMenu() {\n' + + ' this.menu.toggle();\n' + + '}\n' + + 'openRightMenu() {\n' + + ' this.menu.open("right");\n' + + '}' + ); + } + return this.components[id]; } diff --git a/ionic/components/menu/menu-close.ts b/ionic/components/menu/menu-close.ts index 3fda50f11b..04a24ad663 100644 --- a/ionic/components/menu/menu-close.ts +++ b/ionic/components/menu/menu-close.ts @@ -1,18 +1,16 @@ import {Directive, Input, HostListener} from 'angular2/core'; -import {IonicApp} from '../app/app'; -import {Menu} from './menu'; +import {MenuController} from './menu-controller'; /** * @name MenuClose * @description -* Place `menuClose` on a button to automatically close an open menu. Note that the menu's id must be either -* `leftMenu` or `rightMenu` +* Place `menuClose` on a button to automatically close an open menu. * * @usage * ```html - * + * * * * Close the menu @@ -36,14 +34,14 @@ export class MenuClose { */ @Input() menuClose; - constructor(private _app: IonicApp) {} + constructor(private _menu: MenuController) {} /** * @private */ @HostListener('click') close() { - let menu = Menu.getById(this._app, this.menuClose); + let menu = this._menu.get(this.menuClose); menu && menu.close(); } diff --git a/ionic/components/menu/menu-controller.ts b/ionic/components/menu/menu-controller.ts new file mode 100644 index 0000000000..f8a6a056fd --- /dev/null +++ b/ionic/components/menu/menu-controller.ts @@ -0,0 +1,203 @@ +import {Menu} from './menu'; +import {MenuType} from './menu-types'; + + +/** + * @name Menu + * @description + * _For basic Menu usage, see the [Menu section](../../../../components/#menus) + * of the Component docs._ + * + * Menu is a side-menu interface that can be dragged out or toggled to open or closed. + * An Ionic app can have numerous menus, all of which can be controlled within + * template HTML, or programmatically. + * + * @usage + * In order to use Menu, you must specify a [reference](https://angular.io/docs/ts/latest/guide/user-input.html#local-variables) + * to the content element that Menu should listen on for drag events, using the `content` property. + * This is telling the menu which content the menu is attached to, so it knows which element to + * move over, and to respond to drag events. Note that a **menu is a sibling to its content**. + * + * ```html + * + * + * + * ... + * + * + * + * + * + * ``` + * + * By default, Menus are on the left, but this can be overridden with the `side` + * property: + * + * ```html + * ... + * ``` + * + * + * ### Programmatic Interaction + * + * To programmatically interact with any menu, you can inject the `MenuController` + * provider into any component or directive. This makes it easy get ahold of and + * control the correct menu instance. By default Ionic will find the app's menu + * without requiring a menu ID. + * + * ```ts + * @Page({...}) + * export class MyPage { + * constructor(menu: MenuController) { + * this.menu = menu; + * } + * + * openMenu() { + * this.menu.open(); + * } + * + * } + * ``` + * + * Note that if you want to easily toggle or close a menu just from a page's + * template, you can use `menuToggle` and/or `menuClose` to accomplish the same + * tasks as above. + * + * + * ### Apps With Left And Right Menus + * + * For apps with a left and right menu, you can control the desired + * menu by passing in the side of the menu. + * + * ```html + * ... + * ... + * + * ``` + * + * ```ts + * openLeftMenu() { + * this.menu.open('left'); + * } + * + * closeRightMenu() { + * this.menu.close('right'); + * } + * ``` + * + * + * ### Apps With Multiple, Same Side Menus + * + * Since more than one menu on a the same side is possible, and you wouldn't want + * both to be open at the same time, an app can decide which menu should be enabled. + * For apps with multiple menus on the same side, it's required to give each menu a + * unique ID. In the example below, we're saying that the left menu with the + * `authenticated` id should be enabled, and the left menu with the `unauthenticated` + * id be disabled. + * + * ```html + * ... + * ... + * + * ``` + * + * ```ts + * enableAuthenticatedMenu() { + * this.menu.enable(true, 'authenticated'); + * this.menu.enable(false, 'unauthenticated'); + * } + * ``` + * + * Note that if an app only had one menu, there is no reason to pass a menu id. + * + * + * ### Menu Types + * + * Menu supports two display types: `overlay`, `reveal` and `push`. Overlay + * is the traditional Material Design drawer type, and Reveal is the traditional + * iOS type. By default, menus will use to the correct type for the platform, + * but this can be overriden using the `type` property: + * + * ```html + * + * ``` + * + * @demo /docs/v2/demos/menu/ + * + * @see {@link /docs/v2/components#menus Menu Component Docs} + * @see {@link /docs/v2/components#navigation Navigation Component Docs} + * @see {@link ../../nav/Nav Nav API Docs} + * + */ +export class MenuController { + private _menus: Array = []; + + open(menuId?: string) { + let menu = this.get(menuId); + menu && menu.open(); + } + + close(menuId?: string) { + let menu = this.get(menuId); + menu && menu.close(); + } + + enable(shouldEnable: boolean, menuId?: string) { + let menu = this.get(menuId); + menu && menu.enable(shouldEnable); + } + + swipeEnable(shouldEnable: boolean, menuId?: string) { + let menu = this.get(menuId); + menu && menu.swipeEnable(shouldEnable); + } + + get(menuId?: string): Menu { + if (menuId) { + // first try by "id" + let menu = this._menus.find(m => m.id === menuId); + if (menu) return menu; + + // not found by "id", next try by "side" + menu = this._menus.find(m => m.side === menuId); + if (menu) return menu; + } + + // get the first menu in the array, if one exists + return (this._menus.length ? this._menus[0] : null); + } + + /** + * @private + */ + register(menu: Menu) { + this._menus.push(menu); + } + + /** + * @private + */ + unregister(menu: Menu) { + let index = this._menus.indexOf(menu); + if (index > -1) { + this._menus.splice(index, 1); + } + } + + /** + * @private + */ + static registerType(name: string, cls: new(...args: any[]) => MenuType) { + menuTypes[name] = cls; + } + + /** + * @private + */ + static create(type, menuCmp) { + return new menuTypes[type](menuCmp); + } + +} + +let menuTypes:{ [name: string]: new(...args: any[]) => MenuType } = {}; diff --git a/ionic/components/menu/menu-gestures.ts b/ionic/components/menu/menu-gestures.ts index 87dc517c70..7c40ca5ce6 100644 --- a/ionic/components/menu/menu-gestures.ts +++ b/ionic/components/menu/menu-gestures.ts @@ -20,29 +20,44 @@ export class MenuContentGesture extends SlideEdgeGesture { canStart(ev) { let menu = this.menu; - console.debug('menu canStart, id', menu.id, 'angle', ev.angle, 'distance', ev.distance); + console.debug('menu canStart,', menu.side, 'isOpen', menu.isOpen, 'angle', ev.angle, 'distance', ev.distance); if (!menu.isEnabled || !menu.isSwipeEnabled) { - console.debug('menu canStart, isEnabled', menu.isEnabled, 'isSwipeEnabled', menu.isSwipeEnabled, 'id', menu.id); + console.debug('menu canStart, isEnabled', menu.isEnabled, 'isSwipeEnabled', menu.isSwipeEnabled, 'side', menu.side); return false; } if (ev.distance > 50) { // the distance is longer than you'd expect a side menu swipe to be - console.debug('menu canStart, distance too far', ev.distance, 'id', menu.id); + console.debug('menu canStart, distance too far', ev.distance, 'side', menu.side); return false; } + if (menu.side === 'left') { - // left side menu - if (ev.angle > -40 && ev.angle < 40) { - return super.canStart(ev); + // left side + if (menu.isOpen) { + // left side, opened + return true; + + } else { + // left side, closed + if (ev.angle > -40 && ev.angle < 40) { + return super.canStart(ev); + } } } else if (menu.side === 'right') { - // right side menu - if ((ev.angle > 140 && ev.angle <= 180) || (ev.angle > -140 && ev.angle <= -180)) { - return super.canStart(ev); + // right side + if (menu.isOpen) { + // right side, opened + return true; + + } else { + // right side, closed + if ((ev.angle > 140 && ev.angle <= 180) || (ev.angle > -140 && ev.angle <= -180)) { + return super.canStart(ev); + } } } diff --git a/ionic/components/menu/menu-toggle.ts b/ionic/components/menu/menu-toggle.ts index edfda4a0e8..0061321cd0 100644 --- a/ionic/components/menu/menu-toggle.ts +++ b/ionic/components/menu/menu-toggle.ts @@ -1,9 +1,8 @@ import {Directive, ElementRef, Optional, Input, HostListener} from 'angular2/core'; -import {IonicApp} from '../app/app'; import {ViewController} from '../nav/view-controller'; import {Navbar} from '../navbar/navbar'; -import {Menu} from './menu'; +import {MenuController} from './menu-controller'; /** @@ -41,18 +40,18 @@ export class MenuToggle { /** * @private */ - withinNavbar: boolean; + private _inNavbar: boolean; constructor( - private _app: IonicApp, + private _menu: MenuController, elementRef: ElementRef, @Optional() private _viewCtrl: ViewController, @Optional() private _navbar: Navbar ) { - this.withinNavbar = !!_navbar; + this._inNavbar = !!_navbar; // Deprecation warning - if (this.withinNavbar && elementRef.nativeElement.tagName === 'A') { + if (this._inNavbar && elementRef.nativeElement.tagName === 'A') { console.warn('Menu toggles within a navbar should use - - - - - - - - - - - - @@ -66,7 +66,7 @@ - + Right Menu @@ -80,55 +80,55 @@ {{p.title}} - - - - - - - - - - - - - diff --git a/ionic/components/menu/test/basic/page1.html b/ionic/components/menu/test/basic/page1.html index 0d1b51c54b..6cea0dd7e5 100644 --- a/ionic/components/menu/test/basic/page1.html +++ b/ionic/components/menu/test/basic/page1.html @@ -1,7 +1,7 @@ - @@ -21,7 +21,7 @@ - @@ -33,11 +33,11 @@

Page 1

- +

- +

diff --git a/ionic/components/menu/test/basic/page2.html b/ionic/components/menu/test/basic/page2.html index 542adc9b2f..6229f3150f 100644 --- a/ionic/components/menu/test/basic/page2.html +++ b/ionic/components/menu/test/basic/page2.html @@ -1,7 +1,7 @@ - @@ -16,7 +16,7 @@

Page 2

- +

diff --git a/ionic/components/menu/test/basic/page3.html b/ionic/components/menu/test/basic/page3.html index 519d0cfc93..1b631ce4a1 100644 --- a/ionic/components/menu/test/basic/page3.html +++ b/ionic/components/menu/test/basic/page3.html @@ -5,7 +5,7 @@ Menu - @@ -17,7 +17,7 @@

Page 3

- +

diff --git a/ionic/components/menu/test/disable-swipe/index.ts b/ionic/components/menu/test/disable-swipe/index.ts index ce49ee2598..c255978493 100644 --- a/ionic/components/menu/test/disable-swipe/index.ts +++ b/ionic/components/menu/test/disable-swipe/index.ts @@ -1,4 +1,4 @@ -import {App, IonicApp, Page, NavController} from 'ionic/ionic'; +import {App, Page, NavController, MenuController} from 'ionic/ionic'; @Page({ templateUrl: 'page1.html' @@ -7,20 +7,20 @@ class Page1 { leftMenuSwipeEnabled: boolean = true; rightMenuSwipeEnabled: boolean = false; - constructor(app: IonicApp) { - this.app = app; + constructor(menu: MenuController) { + this.menu = menu; } toggleLeftMenuSwipeable() { this.leftMenuSwipeEnabled = !this.leftMenuSwipeEnabled; - this.app.getComponent('leftMenu').swipeEnable(this.leftMenuSwipeEnabled); + this.menu.swipeEnable(this.leftMenuSwipeEnabled, 'leftMenu'); } toggleRightMenuSwipeable() { this.rightMenuSwipeEnabled = !this.rightMenuSwipeEnabled; - this.app.getComponent('rightMenu').swipeEnable(this.rightMenuSwipeEnabled); + this.menu.swipeEnable(this.leftMenuSwipeEnabled, 'rightMenu'); } } diff --git a/ionic/components/menu/test/disable-swipe/main.html b/ionic/components/menu/test/disable-swipe/main.html index d43eb3fed4..b687ca825e 100644 --- a/ionic/components/menu/test/disable-swipe/main.html +++ b/ionic/components/menu/test/disable-swipe/main.html @@ -1,4 +1,4 @@ - + Left Menu @@ -18,7 +18,7 @@ - + Right Menu diff --git a/ionic/components/menu/test/disable-swipe/page1.html b/ionic/components/menu/test/disable-swipe/page1.html index 3439f434db..f6eb53faea 100644 --- a/ionic/components/menu/test/disable-swipe/page1.html +++ b/ionic/components/menu/test/disable-swipe/page1.html @@ -1,6 +1,6 @@ - @@ -8,7 +8,7 @@ Main - diff --git a/ionic/components/menu/test/multiple/index.ts b/ionic/components/menu/test/multiple/index.ts index bf7e01458c..46e9d35e60 100644 --- a/ionic/components/menu/test/multiple/index.ts +++ b/ionic/components/menu/test/multiple/index.ts @@ -1,23 +1,23 @@ -import {App, IonicApp, Page, NavController} from 'ionic/ionic'; +import {App, Page, MenuController} from 'ionic/ionic'; @Page({ templateUrl: 'page1.html' }) class Page1 { - constructor(app: IonicApp) { - this.app = app; + constructor(menu: MenuController) { + this.menu = menu; this.menu1Active(); } menu1Active() { this.activeMenu = 'menu1'; - this.app.getComponent('menu1').enable(true); - this.app.getComponent('menu2').enable(false); + this.menu.enable(true, 'menu1'); + this.menu.enable(false, 'menu2'); } menu2Active() { this.activeMenu = 'menu2'; - this.app.getComponent('menu1').enable(false); - this.app.getComponent('menu2').enable(true); + this.menu.enable(false, 'menu1'); + this.menu.enable(true, 'menu2'); } } @@ -26,8 +26,7 @@ class Page1 { templateUrl: 'main.html' }) class E2EApp { - constructor(app: IonicApp) { - this.app = app; + constructor() { this.rootPage = Page1; } } diff --git a/ionic/components/menu/test/overlay/main.html b/ionic/components/menu/test/overlay/main.html index 5c25565b6b..a1202898c9 100644 --- a/ionic/components/menu/test/overlay/main.html +++ b/ionic/components/menu/test/overlay/main.html @@ -1,4 +1,4 @@ - + Left Menu @@ -8,7 +8,7 @@ - diff --git a/ionic/components/menu/test/overlay/page1.html b/ionic/components/menu/test/overlay/page1.html index 727f99b717..08c8263765 100644 --- a/ionic/components/menu/test/overlay/page1.html +++ b/ionic/components/menu/test/overlay/page1.html @@ -1,7 +1,7 @@ - @@ -17,11 +17,7 @@

Content

- -

- -

- +

diff --git a/ionic/components/menu/test/push/main.html b/ionic/components/menu/test/push/main.html index 82f96ae76e..d1ea781d8c 100644 --- a/ionic/components/menu/test/push/main.html +++ b/ionic/components/menu/test/push/main.html @@ -1,4 +1,4 @@ - + Left Menu @@ -8,7 +8,7 @@ - diff --git a/ionic/components/menu/test/push/page1.html b/ionic/components/menu/test/push/page1.html index 11883cb781..efe910927b 100644 --- a/ionic/components/menu/test/push/page1.html +++ b/ionic/components/menu/test/push/page1.html @@ -1,7 +1,7 @@ - @@ -17,11 +17,7 @@

Content

- -

- -

- +

diff --git a/ionic/components/menu/test/reveal/main.html b/ionic/components/menu/test/reveal/main.html index bb6faf5e81..2dc4023f73 100644 --- a/ionic/components/menu/test/reveal/main.html +++ b/ionic/components/menu/test/reveal/main.html @@ -1,4 +1,4 @@ - + Left Menu @@ -8,7 +8,7 @@ - diff --git a/ionic/components/menu/test/reveal/page1.html b/ionic/components/menu/test/reveal/page1.html index a014cad793..6ca4afcacc 100644 --- a/ionic/components/menu/test/reveal/page1.html +++ b/ionic/components/menu/test/reveal/page1.html @@ -1,7 +1,7 @@ - @@ -17,11 +17,7 @@

Content

- -

- -

- +

diff --git a/ionic/components/nav/test/nested/index.ts b/ionic/components/nav/test/nested/index.ts index 64db097b61..68f6f4ff45 100644 --- a/ionic/components/nav/test/nested/index.ts +++ b/ionic/components/nav/test/nested/index.ts @@ -1,6 +1,5 @@ -import {App, NavController} from 'ionic/ionic'; +import {App, NavParams, NavController, ViewController, MenuController} from 'ionic/ionic'; import {Page, Config, IonicApp} from 'ionic/ionic'; -import {NavParams, NavController, ViewController} from 'ionic/ionic'; @Page({ @@ -49,20 +48,21 @@ export class Login { ` }) export class Account { - constructor(app: IonicApp) { + constructor(app: IonicApp, menu: MenuController) { this.app = app; + this.menu = menu; this.rootPage = Dashboard; } goToProfile() { this.app.getComponent('account-nav').setRoot(Profile).then(() => { - this.app.getComponent('menu').close(); + this.menu.close(); }); } goToDashboard() { this.app.getComponent('account-nav').setRoot(Dashboard).then(() => { - this.app.getComponent('menu').close(); + this.menu.close(); }); } diff --git a/ionic/config/bootstrap.ts b/ionic/config/bootstrap.ts index 2770881bc0..b527b04143 100644 --- a/ionic/config/bootstrap.ts +++ b/ionic/config/bootstrap.ts @@ -2,19 +2,20 @@ import {provide, Provider} from 'angular2/core'; import {ROUTER_PROVIDERS, LocationStrategy, HashLocationStrategy} from 'angular2/router'; import {HTTP_PROVIDERS} from 'angular2/http'; -import {IonicApp} from '../components/app/app'; -import {Config} from './config'; -import {Platform} from '../platform/platform'; -import {Form} from '../util/form'; -import {Keyboard} from '../util/keyboard'; -import {ScrollTo} from '../animations/scroll-to'; -import {Events} from '../util/events'; -import {NavRegistry} from '../components/nav/nav-registry'; -import {Translate} from '../translation/translate'; import {ClickBlock} from '../util/click-block'; +import {Config} from './config'; +import {Events} from '../util/events'; import {FeatureDetect} from '../util/feature-detect'; -import {TapClick} from '../components/tap-click/tap-click'; +import {Form} from '../util/form'; +import {IonicApp} from '../components/app/app'; +import {Keyboard} from '../util/keyboard'; +import {MenuController} from '../components/menu/menu-controller'; +import {NavRegistry} from '../components/nav/nav-registry'; +import {Platform} from '../platform/platform'; import {ready, closest} from '../util/dom'; +import {ScrollTo} from '../animations/scroll-to'; +import {TapClick} from '../components/tap-click/tap-click'; +import {Translate} from '../translation/translate'; /** * @private @@ -57,6 +58,7 @@ export function ionicProviders(args: any={}) { TapClick, Form, Keyboard, + MenuController, Translate, ROUTER_PROVIDERS, provide(LocationStrategy, {useClass: HashLocationStrategy}),