Skip to content

Commit 0ad9ff3

Browse files
calvellidoanamariamv
authored andcommitted
Add sidebar tree navigation (#23)
* Add ESlint rules, Stroustrup brace style * Add navigation tree data JSON files * Available through build time and also one added to assets directory to be able to get it on runtime * Add navigation tree data type and model * Add ambient declarations for NavTreeData to be able to import on compilation time * Also add a model to be able to work with this type of data on runtime dynamically * Add navigation tree component * Add navTreeNode component, implemented recursively * Styled it following the material style * Use this new component on the sidebar replacing the old one * Add navigation tree data retrieval service * Remove duplicated way of getting nav menu data * We were still letting a load JSON file way of getting data as commented code, which now gets removed
1 parent 7f0b27c commit 0ad9ff3

9 files changed

+212
-14
lines changed

src/app/app.component.html

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,12 @@
2424
<md-sidenav-container class="sidenav-container">
2525

2626
<md-sidenav #sidenav class="sidenav" mode="side" opened="true">
27+
2728
<md-nav-list>
28-
<h3 mat-subheader>Microservices</h3>
29-
<md-list-item *ngFor="let link of links" routerLink="/ms/{{ link | number:'2.0-0'}}" routerLinkActive="active">
30-
<span mdLine>Microservice MS{{ link | number:'2.0-0'}}</span>
31-
<button md-icon-button>
32-
<md-icon>grain</md-icon>
33-
</button>
34-
</md-list-item>
29+
<h3 mat-subheader>{{ navTreeObject?.label }}</h3>
30+
<frees-nav-tree-node *ngIf="navTreeObject" [data]="navTreeObject" [showLabel]="false"></frees-nav-tree-node>
3531
</md-nav-list>
32+
3633
</md-sidenav>
3734

3835
<section class="sidenav-content">

src/app/app.component.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
1-
import { Component } from '@angular/core';
1+
import { Component, OnInit } from '@angular/core';
2+
3+
import { NavTreeDataService } from 'app/services/nav-tree-data.service';
4+
5+
import { NavTreeNode } from 'app/shared/nav-tree-node.model';
6+
27

38
@Component({
49
selector: 'frees-root',
510
templateUrl: './app.component.html',
6-
styleUrls: ['./app.component.scss'],
11+
styleUrls: ['./app.component.scss']
712
})
8-
export class AppComponent {
13+
export class AppComponent implements OnInit {
14+
15+
public navTreeObject: NavTreeData;
916

10-
links = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
17+
constructor(private navTreeDataService: NavTreeDataService) { }
1118

12-
constructor() { }
19+
ngOnInit() {
20+
this.navTreeDataService.getJSON()
21+
.subscribe(data => this.navTreeObject = data);
22+
}
1323

1424
}

src/app/app.module.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { MdMenuModule,
1313
MdListModule,
1414
MdProgressBarModule,
1515
MdTabsModule,
16+
MdRippleModule
1617
} from '@angular/material';
1718

1819
import { NgxChartsModule } from '@swimlane/ngx-charts';
@@ -22,13 +23,15 @@ import { AppComponent } from './app.component';
2223

2324
// Services
2425
import { MetricService } from './services/metric.service';
26+
import { NavTreeDataService } from './services/nav-tree-data.service';
2527

2628
// Components
2729
import { MsViewerComponent } from './ms-viewer/ms-viewer.component';
2830
import { NavGridComponent } from './nav-grid/nav-grid.component';
2931
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
3032
import { SettingsComponent } from './settings/settings.component';
3133
import { BreadcrumbComponent } from './breadcrumb/breadcrumb.component';
34+
import { NavTreeNodeComponent } from './nav-tree-node/nav-tree-node.component';
3235

3336
@NgModule({
3437
declarations: [
@@ -37,7 +40,8 @@ import { BreadcrumbComponent } from './breadcrumb/breadcrumb.component';
3740
NavGridComponent,
3841
PageNotFoundComponent,
3942
SettingsComponent,
40-
BreadcrumbComponent
43+
BreadcrumbComponent,
44+
NavTreeNodeComponent,
4145
],
4246
imports: [
4347
BrowserModule,
@@ -53,9 +57,10 @@ import { BreadcrumbComponent } from './breadcrumb/breadcrumb.component';
5357
MdListModule,
5458
MdProgressBarModule,
5559
MdTabsModule,
60+
MdRippleModule,
5661
NgxChartsModule
5762
],
58-
providers: [MetricService],
63+
providers: [MetricService, NavTreeDataService],
5964
bootstrap: [AppComponent]
6065
})
6166
export class AppModule { }
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<ng-container *ngTemplateOutlet="labelTemplate ? labelTemplate : data.link ? leafLabelTemplate : data.subs ? branchLabelTemplate : simpleLabelTemplate; context: {$implicit: this}">
2+
</ng-container>
3+
4+
<ng-container *ngIf="data.subs">
5+
<ul [ngClass]="{'hide': !isExpanded}">
6+
<li *ngFor="let item of data.subs">
7+
<frees-nav-tree-node [data]="item" [isExpanded]="false"></frees-nav-tree-node>
8+
</li>
9+
</ul>
10+
</ng-container>
11+
12+
13+
<ng-template #branchLabelTemplate let-implicit>
14+
<div class="label" #label *ngIf="implicit.showLabel" (click)="implicit.toggle()">
15+
<div class="ripple" md-ripple [mdRippleTrigger]="label"></div>
16+
<span class="label-text">{{ implicit.data.label }}</span>
17+
<button md-icon-button>
18+
<md-icon class="label-icon">keyboard_arrow_down</md-icon>
19+
</button>
20+
</div>
21+
</ng-template>
22+
23+
<ng-template #leafLabelTemplate let-implicit>
24+
<div class="label" #label *ngIf="implicit.showLabel"
25+
routerLink="/ms/{{ implicit.data.link | number:'2.0-0' }}"
26+
routerLinkActive="active">
27+
<div class="ripple" md-ripple [mdRippleTrigger]="label"></div>
28+
<span class="label-text">{{ implicit.data.label }}</span>
29+
<button md-icon-button>
30+
<md-icon class="label-icon">grain</md-icon>
31+
</button>
32+
</div>
33+
</ng-template>
34+
35+
<ng-template #simpleLabelTemplate let-implicit>
36+
<div class="label deactivated" *ngIf="implicit.showLabel">
37+
<div class="ripple" md-ripple [mdRippleTrigger]="label"></div>
38+
<span class="label-text">{{ implicit.data.label }}</span>
39+
</div>
40+
</ng-template>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
ul {
2+
list-style-type: none;
3+
padding: 0;
4+
margin: 0;
5+
&:first-child{
6+
padding-right: 16px;
7+
}
8+
}
9+
10+
li {
11+
font-size: 16px;
12+
padding: 0 0 0 16px;
13+
}
14+
15+
.label {
16+
cursor: pointer;
17+
display: flex;
18+
flex-direction: row;
19+
align-items: center;
20+
box-sizing: border-box;
21+
height: 48px;
22+
outline: none;
23+
position: relative;
24+
25+
&:hover {
26+
background: rgba(0,0,0,.04);
27+
}
28+
29+
&.active {
30+
box-shadow: -4px 0 0 rgba(0,0,0,.4);
31+
}
32+
33+
&.deactivated {
34+
background: none;
35+
color: gray;
36+
cursor: default;
37+
}
38+
39+
.label-text {
40+
width: 100%;
41+
}
42+
}
43+
44+
.ripple {
45+
top: 0;
46+
left: 0;
47+
right: 0;
48+
bottom: 0;
49+
position: absolute;
50+
pointer-events: none;
51+
}
52+
53+
54+
.hide {
55+
display: none;
56+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { NavTreeNodeComponent } from './nav-tree-node.component';
4+
5+
describe('NavTreeNodeComponent', () => {
6+
let component: NavTreeNodeComponent;
7+
let fixture: ComponentFixture<NavTreeNodeComponent>;
8+
9+
beforeEach(async(() => {
10+
TestBed.configureTestingModule({
11+
declarations: [ NavTreeNodeComponent ]
12+
})
13+
.compileComponents();
14+
}));
15+
16+
beforeEach(() => {
17+
fixture = TestBed.createComponent(NavTreeNodeComponent);
18+
component = fixture.componentInstance;
19+
fixture.detectChanges();
20+
});
21+
22+
it('should create', () => {
23+
expect(component).toBeTruthy();
24+
});
25+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Component, Input, TemplateRef, HostListener } from '@angular/core';
2+
3+
import { NavTreeNode } from 'app/shared/nav-tree-node.model';
4+
5+
@Component({
6+
selector: 'frees-nav-tree-node',
7+
templateUrl: './nav-tree-node.component.html',
8+
styleUrls: ['./nav-tree-node.component.scss']
9+
})
10+
export class NavTreeNodeComponent {
11+
12+
@Input() data: NavTreeNode;
13+
@Input() showLabel = true;
14+
@Input() labelTemplate: TemplateRef<any>;
15+
@Input() isExpanded = true;
16+
17+
// This is needed to prevent the click on a specific leaf to
18+
// bubble up to its ancestor nodes, making them to trigger a toggle
19+
@HostListener('click', ['$event']) onClickListener(event: Event) { event.stopPropagation(); }
20+
21+
constructor() { }
22+
23+
/**
24+
* The method that open/closes a tree branch node
25+
*/
26+
toggle() {
27+
this.isExpanded = !this.isExpanded;
28+
}
29+
30+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { TestBed, inject } from '@angular/core/testing';
2+
3+
import { NavTreeDataService } from './nav-tree-data.service';
4+
5+
describe('NavTreeDataService', () => {
6+
beforeEach(() => {
7+
TestBed.configureTestingModule({
8+
providers: [NavTreeDataService]
9+
});
10+
});
11+
12+
it('should be created', inject([NavTreeDataService], (service: NavTreeDataService) => {
13+
expect(service).toBeTruthy();
14+
}));
15+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Injectable } from '@angular/core';
2+
import { Http } from '@angular/http';
3+
import { Observable } from 'rxjs/Observable';
4+
// Operators
5+
import 'rxjs/add/operator/map';
6+
import 'rxjs/add/operator/catch';
7+
8+
9+
@Injectable()
10+
export class NavTreeDataService {
11+
12+
constructor(private http: Http) { }
13+
14+
public getJSON(): Observable<any> {
15+
return this.http.get('assets/nav-tree-data.json')
16+
.map((data: any) => data.json())
17+
.catch(error => error);
18+
}
19+
20+
}

0 commit comments

Comments
 (0)