diff --git a/src/app/pages/pages.routes.ts b/src/app/pages/pages.routes.ts index b14f7b27..512a2530 100644 --- a/src/app/pages/pages.routes.ts +++ b/src/app/pages/pages.routes.ts @@ -29,93 +29,221 @@ export const PagesRoutes:RouterConfig = [ children: [ { path: 'dashboard', - component: Dashboard + component: Dashboard, + data: { + menu: { + title: 'Dashboard', + icon: 'ion-android-home', + selected: false, + expanded: false, + order: 0 + } + } }, { path: 'editors', component: Editors, + data: { + menu: { + title: 'Editors', + icon: 'ion-edit', + selected: false, + expanded: false, + order: 100, + } + }, children: [ { path: 'ckeditor', - component: Ckeditor + component: Ckeditor, + data: { + menu: { + title: 'CKEditor', + } + } } ] }, { path: 'charts', component: Charts, + data: { + menu: { + title: 'Charts', + icon: 'ion-stats-bars', + selected: false, + expanded: false, + order: 200, + } + }, children: [ { path: 'chartist-js', - component: ChartistJs + component: ChartistJs, + data: { + menu: { + title: 'Chartist.Js', + } + } } ] }, { path: 'ui', component: Ui, + data: { + menu: { + title: 'UI Features', + icon: 'ion-android-laptop', + selected: false, + expanded: false, + order: 300, + } + }, children: [ { path: 'typography', - component: Typography + component: Typography, + data: { + menu: { + title: 'Typography', + } + } }, { path: 'buttons', - component: Buttons + component: Buttons, + data: { + menu: { + title: 'Buttons', + } + } }, { path: 'icons', - component: Icons + component: Icons, + data: { + menu: { + title: 'Icons', + } + } }, { path: 'grid', - component: Grid + component: Grid, + data: { + menu: { + title: 'Grid', + } + } }, ] }, { path: 'forms', component: Forms, + data: { + menu: { + title: 'Form Elements', + icon: 'ion-compose', + selected: false, + expanded: false, + order: 400, + } + }, children: [ { path: 'inputs', - component: Inputs + component: Inputs, + data: { + menu: { + title: 'Form Inputs', + } + } }, { path: 'layouts', - component: Layouts + component: Layouts, + data: { + menu: { + title: 'Form Layouts', + } + } } ] }, { path: 'tables', component: Tables, + data: { + menu: { + title: 'Tables', + icon: 'ion-grid', + selected: false, + expanded: false, + order: 500, + } + }, children: [ { path: 'basictables', - component: BasicTables + component: BasicTables, + data: { + menu: { + title: 'Basic Tables', + } + } } ] }, { path: 'maps', component: Maps, + data: { + menu: { + title: 'Maps', + icon: 'ion-ios-location-outline', + selected: false, + expanded: false, + order: 600, + } + }, children: [ { path: 'googlemaps', - component: GoogleMaps + component: GoogleMaps, + data: { + menu: { + title: 'Google Maps', + } + } }, { path: 'leafletmaps', - component: LeafletMaps + component: LeafletMaps, + data: { + menu: { + title: 'Leaflet Maps', + } + } }, { path: 'bubblemaps', - component: BubbleMaps + component: BubbleMaps, + data: { + menu: { + title: 'Bubble Maps', + } + } }, { path: 'linemaps', - component: LineMaps + component: LineMaps, + data: { + menu: { + title: 'Line Maps', + } + } } ] }, diff --git a/src/app/theme/components/baMenu/baMenu.component.ts b/src/app/theme/components/baMenu/baMenu.component.ts new file mode 100644 index 00000000..c0941e03 --- /dev/null +++ b/src/app/theme/components/baMenu/baMenu.component.ts @@ -0,0 +1,70 @@ +import {Component, ViewEncapsulation, Input, Output, EventEmitter} from '@angular/core'; +import {Router, RouterConfig, NavigationEnd} from '@angular/router'; +import {Subscription} from 'rxjs/Rx'; + +import {BaSlimScroll} from '../../../theme/directives'; +import {BaMenuService} from './baMenu.service'; +import {BaMenuItem} from './components/baMenuItem'; + +@Component({ + selector: 'ba-menu', + encapsulation: ViewEncapsulation.None, + styles: [require('./baMenu.scss')], + template: require('./baMenu.html'), + providers: [BaMenuService], + directives: [BaMenuItem, BaSlimScroll] +}) +export class BaMenu { + + @Input() menuRoutes:RouterConfig = []; + @Input() sidebarCollapsed:boolean = false; + @Input() menuHeight:number; + + @Output() expandMenu = new EventEmitter(); + + public menuItems:any[]; + public showHoverElem:boolean; + public hoverElemHeight:number; + public hoverElemTop:number; + protected _onRouteChange:Subscription; + + constructor(private _router:Router, private _service:BaMenuService) { + + this._onRouteChange = this._router.events.subscribe((event) => { + if (this.menuItems && event instanceof NavigationEnd) { + this.menuItems = this._service.selectMenuItem(this.menuItems); + } + }); + } + + public ngOnInit():void { + this.menuItems = this._service.convertRoutesToMenus(this.menuRoutes); + } + + public ngOnDestroy():void { + this._onRouteChange.unsubscribe(); + } + + public hoverItem($event):void { + this.showHoverElem = true; + this.hoverElemHeight = $event.currentTarget.clientHeight; + // TODO: get rid of magic 66 constant + this.hoverElemTop = $event.currentTarget.getBoundingClientRect().top - 66; + } + + public toggleSubMenu($event):boolean { + var submenu = jQuery($event.currentTarget).next(); + + if (this.sidebarCollapsed) { + this.expandMenu.emit(null); + if (!$event.item.expanded) { + $event.item.expanded = true; + } + } else { + $event.item.expanded = !$event.item.expanded; + submenu.slideToggle(); + } + + return false; + } +} diff --git a/src/app/theme/components/baMenu/baMenu.html b/src/app/theme/components/baMenu/baMenu.html new file mode 100644 index 00000000..ebe22ab7 --- /dev/null +++ b/src/app/theme/components/baMenu/baMenu.html @@ -0,0 +1,11 @@ + diff --git a/src/app/theme/components/baMenu/baMenu.scss b/src/app/theme/components/baMenu/baMenu.scss new file mode 100644 index 00000000..c909ac72 --- /dev/null +++ b/src/app/theme/components/baMenu/baMenu.scss @@ -0,0 +1 @@ +@import '../../sass/conf/conf'; diff --git a/src/app/theme/components/baMenu/baMenu.service.ts b/src/app/theme/components/baMenu/baMenu.service.ts new file mode 100644 index 00000000..1c82fc87 --- /dev/null +++ b/src/app/theme/components/baMenu/baMenu.service.ts @@ -0,0 +1,101 @@ +import {Injectable} from '@angular/core'; +import {Router, UrlTree, RouterConfig} from '@angular/router'; + +@Injectable() +export class BaMenuService { + + constructor(private _router:Router) { + } + + public convertRoutesToMenus(routes:RouterConfig):any[] { + let items = this._convertArrayToItems(routes); + return this._skipEmpty(items); + } + + public selectMenuItem(menuItems:any[]):any[] { + let items = []; + menuItems.forEach((item) => { + this._selectItem(item); + + if (item.children && item.children.length > 0) { + item.children = this.selectMenuItem(item.children); + } + items.push(item); + }); + return items; + } + + protected _skipEmpty(items:any[]):any[] { + let menu = []; + items.forEach((item) => { + let menuItem; + if (item.skip) { + if (item.children && item.children.length > 0) { + menuItem = item.children; + } + } else { + menuItem = item; + } + + if (menuItem) { + menu.push(menuItem); + } + }); + + return [].concat.apply([], menu); + } + + protected _convertArrayToItems(routes:any[], parent?:any):any[] { + let items = []; + routes.forEach((route) => { + items.push(this._convertObjectToItem(route, parent)); + }); + return items; + } + + protected _convertObjectToItem(object, parent?:any):any { + let item:any = {}; + if (object.data && object.data.menu) { + // this is a menu object + item = object.data.menu; + item.route = object; + delete item.route.data.menu; + } else { + item.route = object; + item.skip = true; + } + + // we have to collect all pathes to correctly build the url then + item.route.paths = parent && parent.route && parent.route.paths ? parent.route.paths.slice(0) : []; + item.route.paths.push(item.route.path); + + if (object.children && object.children.length > 0) { + item.children = this._convertArrayToItems(object.children, item); + } + + let prepared = this._prepareItem(item); + + // if current item is selected or expanded - then parent is expanded too + if ((prepared.selected || prepared.expanded) && parent) { + parent.expanded = true; + } + + return prepared; + } + + protected _prepareItem(object:any):any { + if (!object.disabled && !object.skip) { + + let itemUrl = this._router.serializeUrl(this._router.createUrlTree(object.route.paths)); + object.url = object.url ? object.url : '/#' + itemUrl; + return this._selectItem(object); + } + + return object; + } + + protected _selectItem(object:any):any { + object.selected = object.url == ('/#' + this._router.url); + return object; + } +} diff --git a/src/app/theme/components/baMenu/components/baMenuItem/baMenuItem.component.ts b/src/app/theme/components/baMenu/components/baMenuItem/baMenuItem.component.ts new file mode 100644 index 00000000..9cfd1017 --- /dev/null +++ b/src/app/theme/components/baMenu/components/baMenuItem/baMenuItem.component.ts @@ -0,0 +1,33 @@ +import {Component, ElementRef, HostListener, ViewEncapsulation, Input, Output, EventEmitter} from '@angular/core'; + +@Component({ + selector: 'ba-menu-item', + encapsulation: ViewEncapsulation.None, + styles: [require('./baMenuItem.scss')], + template: require('./baMenuItem.html'), + providers: [], + directives: [BaMenuItem] +}) +export class BaMenuItem { + + @Input() menuItem:any; + @Input() child:boolean = false; + + @Output() itemHover = new EventEmitter(); + @Output() toggleSubMenu = new EventEmitter(); + + public outOfArea:number = -200; + + public ngOnInit():void { + } + + public onHoverItem($event):void { + this.itemHover.emit($event); + } + + public onToggleSubMenu($event, item):boolean { + $event.item = item; + this.toggleSubMenu.emit($event); + return false; + } +} diff --git a/src/app/theme/components/baMenu/components/baMenuItem/baMenuItem.html b/src/app/theme/components/baMenu/components/baMenuItem/baMenuItem.html new file mode 100644 index 00000000..f4de6762 --- /dev/null +++ b/src/app/theme/components/baMenu/components/baMenuItem/baMenuItem.html @@ -0,0 +1,17 @@ +
  • + + + + {{ menuItem.title }} + + + + {{ menuItem.title }} + + + +
      + +
    + +
  • diff --git a/src/app/theme/components/baMenu/components/baMenuItem/baMenuItem.scss b/src/app/theme/components/baMenu/components/baMenuItem/baMenuItem.scss new file mode 100644 index 00000000..00bb0106 --- /dev/null +++ b/src/app/theme/components/baMenu/components/baMenuItem/baMenuItem.scss @@ -0,0 +1 @@ +@import '../../../../sass/conf/conf'; diff --git a/src/app/theme/components/baMenu/components/baMenuItem/index.ts b/src/app/theme/components/baMenu/components/baMenuItem/index.ts new file mode 100644 index 00000000..5ee5509a --- /dev/null +++ b/src/app/theme/components/baMenu/components/baMenuItem/index.ts @@ -0,0 +1 @@ +export * from './baMenuItem.component'; diff --git a/src/app/theme/components/baMenu/index.ts b/src/app/theme/components/baMenu/index.ts new file mode 100644 index 00000000..99f5ff1f --- /dev/null +++ b/src/app/theme/components/baMenu/index.ts @@ -0,0 +1 @@ +export * from './baMenu.component.ts'; diff --git a/src/app/theme/components/baSidebar/baSidebar.component.ts b/src/app/theme/components/baSidebar/baSidebar.component.ts index eb4b6805..c8825733 100644 --- a/src/app/theme/components/baSidebar/baSidebar.component.ts +++ b/src/app/theme/components/baSidebar/baSidebar.component.ts @@ -1,60 +1,42 @@ import {Component, ElementRef, HostListener, ViewEncapsulation} from '@angular/core'; -import {Router} from '@angular/router'; import {AppState} from '../../../app.state'; import {layoutSizes} from '../../../theme'; -import {BaSlimScroll} from '../../../theme/directives'; -import {BaSidebarService} from './baSidebar.service'; +import {BaMenu} from '../baMenu'; +import {routes} from '../../../../app/app.routes'; @Component({ selector: 'ba-sidebar', encapsulation: ViewEncapsulation.None, styles: [require('./baSidebar.scss')], template: require('./baSidebar.html'), - providers: [BaSidebarService], - directives: [BaSlimScroll] + providers: [], + directives: [BaMenu] }) export class BaSidebar { - public menuItems:Array; + // here we declare which routes we want to use as a menu in our sidebar + public routes = routes; + public menuHeight:number; public isMenuCollapsed:boolean = false; - - public showHoverElem:boolean; - public hoverElemHeight:number; - public hoverElemTop:number; - - public outOfArea:number = -200; - public isMenuShouldCollapsed:boolean = false; - protected _onRouteChange; - constructor(private _elementRef:ElementRef, - private _router:Router, - private _sidebarService:BaSidebarService, - private _state:AppState) { - this.menuItems = this._sidebarService.getMenuItems(); - this._onRouteChange = this._router.events.subscribe(() => { - this._selectMenuItem(); - }); + constructor(private _elementRef:ElementRef, private _state:AppState) { + this._state.subscribe('menu.isCollapsed', (isCollapsed) => { this.isMenuCollapsed = isCollapsed; }); } - public ngOnInit():void { if (this._shouldMenuCollapse()) { this.menuCollapse(); } } - public ngOnDestroy():void { - this._onRouteChange.unsubscribe(); - } - public ngAfterViewInit():void { - this.updateSidebarHeight(); + setTimeout(() => this.updateSidebarHeight()); } @HostListener('window:resize') @@ -82,45 +64,12 @@ export class BaSidebar { this._state.notifyDataChanged('menu.isCollapsed', this.isMenuCollapsed); } - public hoverItem($event):void { - this.showHoverElem = true; - this.hoverElemHeight = $event.currentTarget.clientHeight; - // TODO: get rid of magic 66 constant - this.hoverElemTop = $event.currentTarget.getBoundingClientRect().top - 66; - } - public updateSidebarHeight():void { // TODO: get rid of magic 84 constant this.menuHeight = this._elementRef.nativeElement.childNodes[0].clientHeight - 84; } - public toggleSubMenu($event, item):boolean { - var submenu = jQuery($event.currentTarget).next(); - - if (this.isMenuCollapsed) { - this.menuExpand(); - if (!item.expanded) { - item.expanded = true; - } - } else { - item.expanded = !item.expanded; - submenu.slideToggle(); - } - - return false; - } - private _shouldMenuCollapse():boolean { return window.innerWidth <= layoutSizes.resWidthCollapseSidebar; } - - private _selectMenuItem():void { - - let currentMenu = this._sidebarService.setRouter(this._router).selectMenuItem(this.menuItems); - this._state.notifyDataChanged('menu.activeLink', currentMenu); - // hide menu after natigation on mobile devises - if (this._shouldMenuCollapse()) { - this.menuCollapse(); - } - } } diff --git a/src/app/theme/components/baSidebar/baSidebar.html b/src/app/theme/components/baSidebar/baSidebar.html index c8956c7e..62e9eab9 100644 --- a/src/app/theme/components/baSidebar/baSidebar.html +++ b/src/app/theme/components/baSidebar/baSidebar.html @@ -1,55 +1,6 @@ diff --git a/src/app/theme/components/baSidebar/baSidebar.scss b/src/app/theme/components/baSidebar/baSidebar.scss index b5de70a3..1f843eee 100644 --- a/src/app/theme/components/baSidebar/baSidebar.scss +++ b/src/app/theme/components/baSidebar/baSidebar.scss @@ -135,7 +135,7 @@ a.al-sidebar-list-link { &.expanded { display: block; } - > li { + > ba-menu-item > li { display: block; float: none; padding: 0; diff --git a/src/app/theme/components/baSidebar/baSidebar.service.ts b/src/app/theme/components/baSidebar/baSidebar.service.ts deleted file mode 100644 index 8b004cf6..00000000 --- a/src/app/theme/components/baSidebar/baSidebar.service.ts +++ /dev/null @@ -1,62 +0,0 @@ -import {Injectable} from '@angular/core'; -import {menuItems} from '../../../app.menu'; -import {Router, UrlTree} from "@angular/router"; - -@Injectable() -export class BaSidebarService { - - private _router:Router; - - public getMenuItems():Array { - return menuItems; - } - - public setRouter(router:Router):BaSidebarService { - this._router = router; - return this; - } - - public selectMenuItem(items:Array) { - let currentMenu; - - let assignCurrent = (menu) => (menu.selected ? currentMenu = menu : null); - - items.forEach((menu:any) => { - - this._selectItem([menu.path], menu); - assignCurrent(menu); - - if (menu.subMenu) { - menu.subMenu.forEach((subMenu) => { - this._selectItem([menu.path, subMenu.path], subMenu, menu); - assignCurrent(subMenu); - }); - } - }); - return currentMenu; - } - - private _selectItem(instructions, item, parentMenu = null) { - let route = this._generateRoute(instructions); - item.selected = !item.disabled && this._isCurrent(route); - if (parentMenu) { - parentMenu.expanded = parentMenu.expanded || item.selected; - } - } - - private _isCurrent(route:UrlTree):boolean { - if (route) { - return this._router.url === this._router.serializeUrl(route); - } - return false; - } - - private _generateRoute(instructions:any[]):UrlTree { - instructions = instructions.filter(item => !!item); - - if (instructions.length != 0) { - return this._router.createUrlTree(instructions); - } - return null; - } -} diff --git a/src/app/theme/components/index.ts b/src/app/theme/components/index.ts index 98c1deea..a96111fc 100644 --- a/src/app/theme/components/index.ts +++ b/src/app/theme/components/index.ts @@ -1,6 +1,7 @@ export * from './baPageTop'; export * from './baMsgCenter'; export * from './baSidebar'; +export * from './baMenu'; export * from './baContentTop'; export * from './baCard'; export * from './baAmChart';