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}),