Skip to content

Commit fb66b41

Browse files
committed
sidebar menu done - now it's dynamically rendered
1 parent 1c3ecc6 commit fb66b41

File tree

6 files changed

+292
-18
lines changed

6 files changed

+292
-18
lines changed

angular/src/app/app.module.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { HttpClientModule } from '@angular/common/http';
66

77
import { ModalModule } from 'ngx-bootstrap/modal';
88
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
9+
import { CollapseModule } from 'ngx-bootstrap/collapse';
910
import { NgxPaginationModule } from 'ngx-pagination';
1011

1112
import { AppRoutingModule } from './app-routing.module';
@@ -44,6 +45,7 @@ import { FooterComponent } from './layout/footer.component';
4445
import { SidebarComponent } from './layout/sidebar.component';
4546
import { SidebarLogoComponent } from './layout/sidebar-logo.component';
4647
import { SidebarUserPanelComponent } from './layout/sidebar-user-panel.component';
48+
import { SidebarMenuComponent } from './layout/sidebar-menu.component';
4749

4850
@NgModule({
4951
declarations: [
@@ -75,7 +77,8 @@ import { SidebarUserPanelComponent } from './layout/sidebar-user-panel.component
7577
FooterComponent,
7678
SidebarComponent,
7779
SidebarLogoComponent,
78-
SidebarUserPanelComponent
80+
SidebarUserPanelComponent,
81+
SidebarMenuComponent
7982
],
8083
imports: [
8184
CommonModule,
@@ -85,6 +88,7 @@ import { SidebarUserPanelComponent } from './layout/sidebar-user-panel.component
8588
HttpClientJsonpModule,
8689
ModalModule.forChild(),
8790
BsDropdownModule,
91+
CollapseModule,
8892
AbpModule,
8993
AppRoutingModule,
9094
ServiceProxyModule,
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<nav class="mt-2">
2+
<ul
3+
class="nav nav-pills nav-sidebar flex-column nav-flat"
4+
data-widget="treeview"
5+
role="menu"
6+
data-accordion="false"
7+
>
8+
<ng-container *ngFor="let item of menuItems">
9+
<ng-container
10+
*ngTemplateOutlet="sidebarInner; context: { item: item }"
11+
></ng-container>
12+
</ng-container>
13+
</ul>
14+
</nav>
15+
16+
<ng-template #sidebarInner let-item="item">
17+
<li
18+
*ngIf="isMenuItemVisible(item)"
19+
class="nav-item"
20+
[class.menu-open]="!item.isCollapsed"
21+
[class.has-treeview]="item.children"
22+
>
23+
<a
24+
*ngIf="item.route && item.route.indexOf('http') != 0"
25+
class="nav-link"
26+
[routerLink]="item.route"
27+
[class.active]="item.isActive"
28+
>
29+
<i class="nav-icon {{ item.icon }}"></i>
30+
<p>
31+
{{ item.label }}
32+
</p>
33+
</a>
34+
<a
35+
*ngIf="item.route && item.route.indexOf('http') == 0 && !item.children"
36+
class="nav-link"
37+
target="_blank"
38+
[href]="item.route"
39+
>
40+
<i class="nav-icon {{ item.icon }}"></i>
41+
<p>
42+
{{ item.label }}
43+
</p>
44+
</a>
45+
<a
46+
*ngIf="!item.route && item.children"
47+
class="nav-link"
48+
href="javascript:;"
49+
[class.active]="item.isActive"
50+
(click)="item.isCollapsed = !item.isCollapsed"
51+
>
52+
<i class="nav-icon {{ item.icon }}"></i>
53+
<p>
54+
{{ item.label }}
55+
<i class="right fas fa-angle-left"></i>
56+
</p>
57+
</a>
58+
<ul
59+
*ngIf="item.children"
60+
class="nav nav-treeview"
61+
[collapse]="item.isCollapsed"
62+
[isAnimated]="true"
63+
>
64+
<ng-container *ngFor="let item of item.children">
65+
<ng-container
66+
*ngTemplateOutlet="sidebarInner; context: { item: item }"
67+
></ng-container>
68+
</ng-container>
69+
</ul>
70+
</li>
71+
</ng-template>
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { Component, Injector, OnInit } from '@angular/core';
2+
import { AppComponentBase } from '@shared/app-component-base';
3+
import {
4+
Router,
5+
RouterEvent,
6+
NavigationEnd,
7+
PRIMARY_OUTLET
8+
} from '@angular/router';
9+
import { BehaviorSubject } from 'rxjs';
10+
import { filter } from 'rxjs/operators';
11+
import { MenuItem } from '@shared/layout/menu-item';
12+
13+
@Component({
14+
selector: 'sidebar-menu',
15+
templateUrl: './sidebar-menu.component.html'
16+
})
17+
export class SidebarMenuComponent extends AppComponentBase implements OnInit {
18+
menuItems: MenuItem[];
19+
nestedMenuItemsMap: { [key: number]: MenuItem } = {};
20+
activatedMenuItems: MenuItem[] = [];
21+
routerEvents: BehaviorSubject<RouterEvent> = new BehaviorSubject(undefined);
22+
homeRoute = '/app/home';
23+
24+
constructor(injector: Injector, private router: Router) {
25+
super(injector);
26+
this.router.events.subscribe(this.routerEvents);
27+
}
28+
29+
ngOnInit(): void {
30+
this.menuItems = this.getMenuItems();
31+
this.patchMenuItems(this.menuItems);
32+
this.routerEvents
33+
.pipe(filter((event) => event instanceof NavigationEnd))
34+
.subscribe((event) => {
35+
const currentUrl = event.url !== '/' ? event.url : this.homeRoute;
36+
const primaryUrlSegmentGroup = this.router.parseUrl(currentUrl).root
37+
.children[PRIMARY_OUTLET];
38+
if (primaryUrlSegmentGroup) {
39+
this.activeMenuItems('/' + primaryUrlSegmentGroup.toString());
40+
}
41+
});
42+
}
43+
44+
getMenuItems(): MenuItem[] {
45+
return [
46+
new MenuItem(this.l('HomePage'), '/app/home', 'fas fa-home'),
47+
new MenuItem(
48+
this.l('Tenants'),
49+
'/app/tenants',
50+
'fas fa-building',
51+
'Pages.Tenants'
52+
),
53+
new MenuItem(
54+
this.l('Users'),
55+
'/app/users',
56+
'fas fa-users',
57+
'Pages.Users'
58+
),
59+
new MenuItem(
60+
this.l('Roles'),
61+
'/app/roles',
62+
'fas fa-theater-masks',
63+
'Pages.Roles'
64+
),
65+
new MenuItem(this.l('About'), '/app/about', 'fas fa-info-circle'),
66+
new MenuItem(this.l('MultiLevelMenu'), '', 'fas fa-circle', '', [
67+
new MenuItem('ASP.NET Boilerplate', '', 'fas fa-dot-circle', '', [
68+
new MenuItem(
69+
'Home',
70+
'https://aspnetboilerplate.com?ref=abptmpl',
71+
'far fa-circle'
72+
),
73+
new MenuItem(
74+
'Templates',
75+
'https://aspnetboilerplate.com/Templates?ref=abptmpl',
76+
'far fa-circle'
77+
),
78+
new MenuItem(
79+
'Samples',
80+
'https://aspnetboilerplate.com/Samples?ref=abptmpl',
81+
'far fa-circle'
82+
),
83+
new MenuItem(
84+
'Documents',
85+
'https://aspnetboilerplate.com/Pages/Documents?ref=abptmpl',
86+
'far fa-circle'
87+
),
88+
]),
89+
new MenuItem('ASP.NET Zero', '', 'fas fa-dot-circle', '', [
90+
new MenuItem(
91+
'Home',
92+
'https://aspnetzero.com?ref=abptmpl',
93+
'far fa-circle'
94+
),
95+
new MenuItem(
96+
'Description',
97+
'https://aspnetzero.com/?ref=abptmpl#description',
98+
'far fa-circle'
99+
),
100+
new MenuItem(
101+
'Features',
102+
'https://aspnetzero.com/?ref=abptmpl#features',
103+
'far fa-circle'
104+
),
105+
new MenuItem(
106+
'Pricing',
107+
'https://aspnetzero.com/?ref=abptmpl#pricing',
108+
'far fa-circle'
109+
),
110+
new MenuItem(
111+
'Faq',
112+
'https://aspnetzero.com/Faq?ref=abptmpl',
113+
'far fa-circle'
114+
),
115+
new MenuItem(
116+
'Documents',
117+
'https://aspnetzero.com/Documents?ref=abptmpl',
118+
'far fa-circle'
119+
)
120+
])
121+
])
122+
];
123+
}
124+
125+
patchMenuItems(items: MenuItem[], parentId?: number): void {
126+
items.forEach((item: MenuItem, index: number) => {
127+
item.id = parentId ? Number(parentId + '' + (index + 1)) : index + 1;
128+
if (parentId) {
129+
item.parentId = parentId;
130+
}
131+
if (parentId || item.children) {
132+
this.nestedMenuItemsMap[item.id] = item;
133+
}
134+
if (item.children) {
135+
this.patchMenuItems(item.children, item.id);
136+
}
137+
});
138+
}
139+
140+
activeMenuItems(url: string): void {
141+
this.deactiveMenuItems(this.menuItems);
142+
this.activatedMenuItems = [];
143+
const foundedItems = this.findMenuItemsByUrl(url, this.menuItems);
144+
foundedItems.forEach((item) => {
145+
this.activeMenuItem(item);
146+
});
147+
}
148+
149+
deactiveMenuItems(items: MenuItem[]): void {
150+
items.forEach((item: MenuItem) => {
151+
item.isActive = false;
152+
item.isCollapsed = true;
153+
if (item.children) {
154+
this.deactiveMenuItems(item.children);
155+
}
156+
});
157+
}
158+
159+
findMenuItemsByUrl(
160+
url: string,
161+
items: MenuItem[],
162+
foundedItems: MenuItem[] = []
163+
): MenuItem[] {
164+
items.forEach((item: MenuItem) => {
165+
if (item.route === url) {
166+
foundedItems.push(item);
167+
} else if (item.children) {
168+
this.findMenuItemsByUrl(url, item.children, foundedItems);
169+
}
170+
});
171+
return foundedItems;
172+
}
173+
174+
activeMenuItem(item: MenuItem): void {
175+
item.isActive = true;
176+
if (item.children) {
177+
item.isCollapsed = false;
178+
}
179+
this.activatedMenuItems.push(item);
180+
if (item.parentId) {
181+
this.activeMenuItem(this.nestedMenuItemsMap[item.parentId]);
182+
}
183+
}
184+
185+
isMenuItemVisible(item: MenuItem): boolean {
186+
if (!item.permissionName) {
187+
return true;
188+
}
189+
return this.permission.isGranted(item.permissionName);
190+
}
191+
}

angular/src/app/layout/sidebar.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
<sidebar-logo></sidebar-logo>
33
<div class="sidebar">
44
<sidebar-user-panel></sidebar-user-panel>
5+
<sidebar-menu></sidebar-menu>
56
</div>
67
</aside>

angular/src/root.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { RootComponent } from './root.component';
1919
import { AppPreBootstrap } from './AppPreBootstrap';
2020
import { ModalModule } from 'ngx-bootstrap/modal';
2121
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
22+
import { CollapseModule } from 'ngx-bootstrap/collapse';
2223
import { HttpClientModule } from '@angular/common/http';
2324

2425
import { GestureConfig } from '@angular/material';
@@ -99,6 +100,7 @@ export function getCurrentLanguage(): string {
99100
SharedModule.forRoot(),
100101
ModalModule.forRoot(),
101102
BsDropdownModule.forRoot(),
103+
CollapseModule.forRoot(),
102104
AbpModule,
103105
ServiceProxyModule,
104106
RootRoutingModule,
Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
export class MenuItem {
2-
name = '';
3-
permissionName = '';
4-
icon = '';
5-
route = '';
6-
items: MenuItem[];
2+
id: number;
3+
parentId: number;
4+
label: string;
5+
route: string;
6+
icon: string;
7+
permissionName: string;
8+
isActive?: boolean;
9+
isCollapsed?: boolean;
10+
children: MenuItem[];
711

8-
constructor(name: string, permissionName: string, icon: string, route: string, childItems: MenuItem[] = null) {
9-
this.name = name;
10-
this.permissionName = permissionName;
11-
this.icon = icon;
12-
this.route = route;
13-
14-
if (childItems) {
15-
this.items = childItems;
16-
} else {
17-
this.items = [];
18-
}
19-
}
12+
constructor(
13+
label: string,
14+
route: string,
15+
icon: string,
16+
permissionName: string = null,
17+
children: MenuItem[] = null
18+
) {
19+
this.label = label;
20+
this.route = route;
21+
this.icon = icon;
22+
this.permissionName = permissionName;
23+
this.children = children;
24+
}
2025
}

0 commit comments

Comments
 (0)