import { ChangeDetectionStrategy , Component , ContentChild , ElementRef , EventEmitter , Input , OnDestroy , OnInit , Output , Renderer , ViewChild , ViewEncapsulation , forwardRef } from '@angular/core' ; import { App } from '../app/app' ; import { Backdrop } from '../backdrop/backdrop' ; import { Config } from '../../config/config' ; import { Content } from '../content/content' ; import { DomController } from '../../platform/dom-controller' ; import { BlockerDelegate , GESTURE_GO_BACK_SWIPE , GestureController , } from '../../gestures/gesture-controller' ; import { Side , assert , isRightSide , isTrueProperty } from '../../util/util' ; import { Keyboard } from '../../platform/keyboard' ; import { MenuContentGesture } from './menu-gestures' ; import { Menu as MenuInterface } from '../app/menu-interface' ; import { MenuController } from '../app/menu-controller' ; import { MenuType } from './menu-types' ; import { Nav } from '../nav/nav' ; import { Platform } from '../../platform/platform' ; import { UIEventManager } from '../../gestures/ui-event-manager' ; import { RootNode } from '../split-pane/split-pane' ; name description usage Component demo see {@link /docs/components#menus Menu Component Docs} see {@link ../../app/MenuController MenuController API Docs} see {@link ../../nav/Nav Nav API Docs} see {@link ../../nav/NavController NavController API Docs} @ Component ( { selector : 'ion-menu' , template : '<div class="menu-inner"><ng-content></ng-content></div>' + '<ion-backdrop></ion-backdrop>' , host : { 'role' : 'navigation' } , changeDetection : ChangeDetectionStrategy . OnPush , encapsulation : ViewEncapsulation . None , providers : [ { provide : RootNode , useExisting : forwardRef ( ( ) => Menu ) } ] } ) export class Menu implements RootNode , MenuInterface , OnInit , OnDestroy { private _cntEle : HTMLElement ; private _gesture : MenuContentGesture ; private _type : MenuType ; private _isEnabled : boolean ; private _isSwipeEnabled : boolean = true ; private _isAnimating : boolean = false ; private _isPersistent : boolean = false ; private _init : boolean = false ; private _events : UIEventManager ; private _gestureBlocker : BlockerDelegate ; private _isPane : boolean = false ; private _side : Side ; hidden isOpen : boolean = false ; hidden isRightSide : boolean = false ; hidden @ ViewChild ( Backdrop ) backdrop : Backdrop ; hidden @ ContentChild ( Content ) menuContent : Content ; hidden @ ContentChild ( Nav ) menuNav : Nav ; input @ Input ( ) content : any ; input @ Input ( ) id : string ; input @ Input ( ) type : string ; input @ Input ( ) get enabled ( ) : boolean { return this . _isEnabled ; } set enabled ( val ) { const isEnabled = isTrueProperty ( val ) ; this . enable ( isEnabled ) ; } input @ Input ( ) get side ( ) : Side { return this . _side ; } set side ( val ) { this . isRightSide = isRightSide ( val , this . _plt . isRTL ) ; if ( this . isRightSide ) { this . _side = 'right' ; } else { this . _side = 'left' ; } } input @ Input ( ) get swipeEnabled ( ) : boolean { return this . _isSwipeEnabled ; } set swipeEnabled ( val ) { const isEnabled = isTrueProperty ( val ) ; this . swipeEnable ( isEnabled ) ; } input @ Input ( ) get persistent ( ) : boolean { return this . _isPersistent ; } set persistent ( val ) { this . _isPersistent = isTrueProperty ( val ) ; } hidden @ Input ( ) maxEdgeStart : number ; output @ Output ( ) ionDrag : EventEmitter < number > = new EventEmitter < number > ( ) ; output @ Output ( ) ionOpen : EventEmitter < boolean > = new EventEmitter < boolean > ( ) ; output @ Output ( ) ionClose : EventEmitter < boolean > = new EventEmitter < boolean > ( ) ; constructor ( public _menuCtrl , private _elementRef , private _config , private _plt , private _renderer , private _keyboard , private _gestureCtrl , private _domCtrl , private _app ) { this . _events = new UIEventManager ( _plt ) ; this . _gestureBlocker = _gestureCtrl . createBlocker ( { disable : [ GESTURE_GO_BACK_SWIPE ] } ) ; this . side = 'start' ; } hidden ngOnInit ( ) { this . _init = true ; let content = this . content ; this . _cntEle = ( content instanceof Node ) ? content : content && content . getNativeElement && content . getNativeElement ( ) ; if ( ! this . _cntEle ) { return console . error ( 'Menu: must have a [content] element to listen for drag events on. Example:\n\n<ion-menu [content]="content"></ion-menu>\n\n<ion-nav #content></ion-nav>' ) ; } this . setElementAttribute ( 'side' , this . _side ) ; if ( ! this . type ) { this . type = this . _config . get ( 'menuType' ) ; } this . setElementAttribute ( 'type' , this . type ) ; this . _gesture = new MenuContentGesture ( this . _plt , this , this . _gestureCtrl , this . _domCtrl ) ; this . _cntEle . classList . add ( 'menu-content' ) ; this . _cntEle . classList . add ( 'menu-content-' + this . type ) ; let isEnabled = this . _isEnabled ; if ( isEnabled === true || typeof isEnabled === 'undefined' ) { isEnabled = ! this . _menuCtrl . getMenus ( ) . some ( m => { return m . side === this . side && m . enabled ; } ) ; } this . _menuCtrl . _register ( this ) ; this . enable ( isEnabled ) ; } hidden onBackdropClick ( ev ) { ev . preventDefault ( ) ; ev . stopPropagation ( ) ; this . _menuCtrl . close ( ) ; } hidden private _getType ( ) { if ( ! this . _type ) { this . _type = MenuController . create ( this . type , this , this . _plt ) ; if ( this . _config . get ( 'animate' ) === false ) { this . _type . ani . duration ( 0 ) ; } } return this . _type ; } hidden setOpen ( shouldOpen , animated = true ) { if ( ( shouldOpen === this . isOpen ) || ! this . _canOpen ( ) || this . _isAnimating ) { return Promise . resolve ( this . isOpen ) ; } return new Promise ( resolve => { this . _before ( ) ; this . _getType ( ) . setOpen ( shouldOpen , animated , ( ) => { this . _after ( shouldOpen ) ; resolve ( this . isOpen ) ; } ) ; } ) ; } _forceClosing ( ) { assert ( this . isOpen , 'menu cannot be closed' ) ; this . _isAnimating = true ; this . _getType ( ) . setOpen ( false , false , ( ) => { this . _after ( false ) ; } ) ; } hidden canSwipe ( ) { return this . _isSwipeEnabled && ! this . _isAnimating && this . _canOpen ( ) && this . _app . isEnabled ( ) ; } hidden isAnimating ( ) { return this . _isAnimating ; } _swipeBeforeStart ( ) { if ( ! this . canSwipe ( ) ) { assert ( false , 'canSwipe() has to be true' ) ; return ; } this . _before ( ) ; } _swipeStart ( ) { if ( ! this . _isAnimating ) { assert ( false , '_isAnimating has to be true' ) ; return ; } this . _getType ( ) . setProgressStart ( this . isOpen ) ; } _swipeProgress ( stepValue ) { if ( ! this . _isAnimating ) { assert ( false , '_isAnimating has to be true' ) ; return ; } this . _getType ( ) . setProgessStep ( stepValue ) ; const ionDrag = this . ionDrag ; if ( ionDrag . observers . length > 0 ) { ionDrag . emit ( stepValue ) ; } } _swipeEnd ( shouldCompleteLeft , shouldCompleteRight , stepValue , velocity ) { if ( ! this . _isAnimating ) { assert ( false , '_isAnimating has to be true' ) ; return ; } const isRightSide = this . isRightSide ; const isRTL = this . _plt . isRTL ; const opening = ! this . isOpen ; const shouldComplete = ( opening ) ? ( isRightSide !== isRTL ) ? shouldCompleteLeft : shouldCompleteRight : ( isRightSide !== isRTL ) ? shouldCompleteRight : shouldCompleteLeft ; this . _getType ( ) . setProgressEnd ( shouldComplete , stepValue , velocity , ( isOpen ) => { console . debug ( 'menu, swipeEnd' , this . side ) ; this . _after ( isOpen ) ; } ) ; } private _before ( ) { assert ( ! this . _isAnimating , '_before() should not be called while animating' ) ; this . setElementClass ( 'show-menu' , true ) ; this . backdrop . setElementClass ( 'show-backdrop' , true ) ; this . resize ( ) ; this . _keyboard . close ( ) ; this . _isAnimating = true ; } private _after ( isOpen ) { assert ( this . _isAnimating , '_before() should be called while animating' ) ; this . _app . setEnabled ( false , 100 ) ; this . isOpen = isOpen ; this . _isAnimating = false ; this . _events . unlistenAll ( ) ; if ( isOpen ) { this . _gestureBlocker . block ( ) ; this . _cntEle . classList . add ( 'menu-content-open' ) ; let callback = this . onBackdropClick . bind ( this ) ; this . _events . listen ( this . _cntEle , 'click' , callback , { capture : true } ) ; this . _events . listen ( this . backdrop . getNativeElement ( ) , 'click' , callback , { capture : true } ) ; this . ionOpen . emit ( true ) ; } else { this . _gestureBlocker . unblock ( ) ; this . _cntEle . classList . remove ( 'menu-content-open' ) ; this . setElementClass ( 'show-menu' , false ) ; this . backdrop . setElementClass ( 'show-menu' , false ) ; this . ionClose . emit ( true ) ; } } hidden open ( ) { return this . setOpen ( true ) ; } hidden close ( ) { return this . setOpen ( false ) ; } hidden resize ( ) { const content = this . menuContent ? this . menuContent : this . menuNav ; content && content . resize ( ) ; } hidden toggle ( ) { return this . setOpen ( ! this . isOpen ) ; } _canOpen ( ) { return this . _isEnabled && ! this . _isPane ; } hidden _updateState ( ) { const canOpen = this . _canOpen ( ) ; if ( ! canOpen && this . isOpen ) { assert ( this . _init , 'menu must be initialized' ) ; this . _forceClosing ( ) ; } if ( this . _isEnabled && this . _menuCtrl ) { this . _menuCtrl . _setActiveMenu ( this ) ; } if ( ! this . _init ) { return ; } const gesture = this . _gesture ; if ( canOpen && this . _isSwipeEnabled && ! gesture . isListening ) { console . debug ( 'menu, gesture listen' , this . side ) ; gesture . listen ( ) ; } else if ( gesture . isListening && ( ! canOpen || ! this . _isSwipeEnabled ) ) { console . debug ( 'menu, gesture unlisten' , this . side ) ; gesture . unlisten ( ) ; } if ( this . isOpen || ( this . _isPane && this . _isEnabled ) ) { this . resize ( ) ; } assert ( ! this . _isAnimating , 'can not be animating' ) ; } hidden enable ( shouldEnable ) { this . _isEnabled = shouldEnable ; this . setElementClass ( 'menu-enabled' , shouldEnable ) ; this . _updateState ( ) ; return this ; } internal initPane ( ) { return false ; } internal paneChanged ( isPane ) { this . _isPane = isPane ; this . _updateState ( ) ; } hidden swipeEnable ( shouldEnable ) { this . _isSwipeEnabled = shouldEnable ; this . _updateState ( ) ; return this ; } hidden getNativeElement ( ) { return this . _elementRef . nativeElement ; } hidden getMenuElement ( ) { return < HTMLElement > this . getNativeElement ( ) . querySelector ( '.menu-inner' ) ; } hidden getContentElement ( ) { return this . _cntEle ; } hidden getBackdropElement ( ) { return this . backdrop . getNativeElement ( ) ; } hidden width ( ) { return this . getMenuElement ( ) . offsetWidth ; } hidden getMenuController ( ) { return this . _menuCtrl ; } hidden setElementClass ( className , add ) { this . _renderer . setElementClass ( this . _elementRef . nativeElement , className , add ) ; } hidden setElementAttribute ( attributeName , value ) { this . _renderer . setElementAttribute ( this . _elementRef . nativeElement , attributeName , value ) ; } hidden getElementRef ( ) { return this . _elementRef ; } hidden ngOnDestroy ( ) { this . _menuCtrl . _unregister ( this ) ; this . _events . destroy ( ) ; this . _gesture && this . _gesture . destroy ( ) ; this . _type && this . _type . destroy ( ) ; this . _gesture = null ; this . _type = null ; this . _cntEle = null ; } }