import {forwardRef, Directive, Host, View, EventEmitter, ElementRef} from 'angular2/angular2'; import {Ion} from '../ion'; import {IonicApp} from '../app/app'; import {IonicConfig} from '../../config/config'; import {IonicComponent} from '../../config/annotations'; import * as gestures from './menu-gestures'; /** * Menu is a side-menu navigation that can be dragged out or toggled to show. * Menu supports two display styles currently: overlay, and reveal. Overlay * is the tradtional Android drawer style, and Reveal is the traditional iOS * style. By default, Menu will adjust to the correct style for the platform. */ @IonicComponent({ selector: 'ion-menu', properties: [ 'content', 'dragThreshold', 'id' ], defaultProperties: { 'side': 'left', 'type': 'reveal' }, host: { 'role': 'navigation' }, events: ['opening'] }) @View({ template: '', directives: [forwardRef(() => MenuBackdrop)] }) export class Menu extends Ion { constructor(app: IonicApp, elementRef: ElementRef, config: IonicConfig) { super(elementRef, config); this.app = app; this.opening = new EventEmitter('opening'); this.isOpen = false; this._disableTime = 0; } onInit() { super.onInit(); this.contentElement = (this.content instanceof Node) ? this.content : this.content.getNativeElement(); if (!this.contentElement) { return console.error('Menu: must have a [content] element to listen for drag events on. Example:\n\n\n\n'); } if (!this.id) { // Auto register this.id = 'menu'; this.app.register(this.id, this); } this._initGesture(); this._initType(this.type); this.contentElement.classList.add('menu-content'); this.contentElement.classList.add('menu-content-' + this.type); let self = this; this.onContentClick = function(ev) { ev.preventDefault(); ev.stopPropagation(); self.close(); }; } _initGesture() { switch(this.side) { case 'right': this._gesture = new gestures.RightMenuGesture(this); break; case 'left': this._gesture = new gestures.LeftMenuGesture(this); break; } } _initType(type) { type = type && type.trim().toLowerCase() || FALLBACK_MENU_TYPE; let menuTypeCls = menuTypes[type]; if (!menuTypeCls) { type = FALLBACK_MENU_TYPE; menuTypeCls = menuTypes[type]; } this._type = new menuTypeCls(this); this.type = type; } /** * Sets the state of the Menu to open or not. * @param {boolean} isOpen If the Menu is open or not. * @return {Promise} TODO */ setOpen(shouldOpen) { // _isDisabled is used to prevent unwanted opening/closing after swiping open/close // or swiping open the menu while pressing down on the menu-toggle button if (shouldOpen === this.isOpen || this._isDisabled()) { return Promise.resolve(); } this._before(); return this._type.setOpen(shouldOpen).then(() => { this._after(shouldOpen); }); } setProgressStart() { // user started swiping the menu open/close if (this._isDisabled()) return; this._before(); this._type.setProgressStart(this.isOpen); } setProgess(value) { // user actively dragging the menu this._disable(); this.app.setEnabled(false, 4000); this._type.setProgess(value); } setProgressFinish(shouldComplete) { // user has finished dragging the menu this._disable(); this.app.setEnabled(false); this._type.setProgressFinish(shouldComplete).then(isOpen => { this._after(isOpen); }); } _before() { // this places the menu into the correct location before it animates in // this css class doesn't actually kick off any animations this.getNativeElement().classList.add('show-menu'); this.getBackdropElement().classList.add('show-backdrop'); this._disable(); this.app.setEnabled(false); } _after(isOpen) { // keep opening/closing the menu disabled for a touch more yet this._disable(); // but the app itself can be used again this.app.setEnabled(true); this.isOpen = isOpen; this.contentElement.classList[isOpen ? 'add' : 'remove']('menu-content-open'); this.contentElement.removeEventListener('click', this.onContentClick); if (isOpen) { this.contentElement.addEventListener('click', this.onContentClick); } else { this.getNativeElement().classList.remove('show-menu'); this.getBackdropElement().classList.remove('show-backdrop'); } } _disable() { // used to prevent unwanted opening/closing after swiping open/close // or swiping open the menu while pressing down on the menu-toggle this._disableTime = Date.now() + 300; } _isDisabled() { return this._disableTime > Date.now(); } /** * TODO * @return {TODO} TODO */ open() { return this.setOpen(true); } /** * TODO * @return {TODO} TODO */ close() { return this.setOpen(false); } /** * TODO * @return {TODO} TODO */ toggle() { return this.setOpen(!this.isOpen); } /** * TODO * @return {Element} The Menu element. */ getMenuElement() { return this.getNativeElement(); } /** * TODO * @return {Element} The Menu's associated content element. */ getContentElement() { return this.contentElement; } /** * TODO * @return {Element} The Menu's backdrop element. */ getBackdropElement() { return this.backdrop.elementRef.nativeElement; } static register(name, cls) { menuTypes[name] = cls; } onDestroy() { this.app.unregister(this.id); this._gesture && this._gesture.destroy(); this._type && this._type.onDestroy(); this.contentElement = null; } } let menuTypes = {}; const FALLBACK_MENU_TYPE = 'reveal'; /** * TODO */ @Directive({ selector: 'backdrop', host: { '(click)': 'clicked($event)' } }) class MenuBackdrop { /** * TODO * @param {Menu} menu TODO */ constructor(@Host() menu: Menu, elementRef: ElementRef) { this.menu = menu; this.elementRef = elementRef; menu.backdrop = this; } /** * TODO * @param {TODO} event TODO */ clicked(ev) { ev.preventDefault(); ev.stopPropagation(); this.menu.close(); } }