TSK-178 add master and detail component

This commit is contained in:
Martin Rojas Miguel Angel 2018-02-01 14:20:20 +01:00 committed by Holger Hagen
parent 0c5ed430c8
commit 4044b8f8b3
15 changed files with 276 additions and 61 deletions

View File

@ -5,7 +5,7 @@
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
"build": " npm run", "build": " ng build --env=dev",
"build:prod": "ng build --env=prod", "build:prod": "ng build --env=prod",
"test": "ng test", "test": "ng test",
"lint": "ng lint", "lint": "ng lint",

View File

@ -1,27 +1,42 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { WorkbasketListComponent } from './workbasket/list/workbasket-list.component';
import { WorkbasketadministrationComponent } from './workbasketadministration/workbasketadministration.component'; import { WorkbasketadministrationComponent } from './workbasketadministration/workbasketadministration.component';
import { CategoriesadministrationComponent } from './categoriesadministration/categoriesadministration.component'; import { CategoriesadministrationComponent } from './categoriesadministration/categoriesadministration.component';
import { MasterAndDetailComponent } from './shared/masterAndDetail/master-and-detail.component';
const appRoutes: Routes = [ const appRoutes: Routes = [
{ path: 'workbaskets',
component: MasterAndDetailComponent,
children: [
{ {
path: 'workbaskets', path: '',
component: WorkbasketadministrationComponent component: WorkbasketListComponent,
outlet: 'master'
}, },
{ {
path: 'workbaskets/:id', path: ':id',
component: WorkbasketadministrationComponent component: WorkbasketadministrationComponent,
outlet: 'detail'
}
]
}, },
{ path: 'clasifications',
component: MasterAndDetailComponent,
children: [
{ {
path: 'categories', path: '',
component: CategoriesadministrationComponent component: CategoriesadministrationComponent,
outlet: 'detail'
}
]
}, },
{ {
path: '', path: '',
redirectTo: 'workbaskets', redirectTo: 'workbaskets',
pathMatch: 'full' pathMatch: 'full'
}, }
]; ];
@NgModule({ @NgModule({
imports: [ imports: [

View File

@ -10,7 +10,7 @@
<div class="col-xs-9 col-md-7 logo-container"> <div class="col-xs-9 col-md-7 logo-container">
<ul class="nav nav-tabs no-border-bottom" id="myTabs" role="tablist"> <ul class="nav nav-tabs no-border-bottom" id="myTabs" role="tablist">
<li role="presentation" class="{{workbasketsRoute? 'active' : 'inactive'}}" role="tab" data-toggle="tab" ><a routerLink="/workbaskets" aria-controls="Work baskets" routerLinkActive="active">Workbaskets</a></li> <li role="presentation" class="{{workbasketsRoute? 'active' : 'inactive'}}" role="tab" data-toggle="tab" ><a routerLink="/workbaskets" aria-controls="Work baskets" routerLinkActive="active">Workbaskets</a></li>
<li role="presentation" class="{{workbasketsRoute? 'inactive' : 'active'}}" role="tab" data-toggle="tab" ><a routerLink="/categories" aria-controls="Clasifications" routerLinkActive="active">Clasifications</a></li> <li role="presentation" class="{{workbasketsRoute? 'inactive' : 'active'}}" role="tab" data-toggle="tab" ><a routerLink="/clasifications" aria-controls="Clasifications" routerLinkActive="active">Clasifications</a></li>
</ul> </ul>
</div> </div>
<div class="col-xs-1"> <div class="col-xs-1">
@ -31,5 +31,3 @@
</nav> </nav>
<router-outlet></router-outlet> <router-outlet></router-outlet>

View File

@ -14,7 +14,6 @@ describe('AppComponent', () => {
{ path: 'categories', component: AppComponent } { path: 'categories', component: AppComponent }
]; ];
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ declarations: [
@ -24,7 +23,6 @@ describe('AppComponent', () => {
AngularSvgIconModule, AngularSvgIconModule,
RouterTestingModule.withRoutes(routes), RouterTestingModule.withRoutes(routes),
HttpClientModule HttpClientModule
] ]
}).compileComponents(); }).compileComponents();

View File

@ -1,12 +1,22 @@
/**
* Modules
*/
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { NgModule, } from '@angular/core'; import { NgModule, } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { HttpModule, JsonpModule } from '@angular/http'; import { HttpModule, JsonpModule } from '@angular/http';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module';
import { AlertModule } from 'ngx-bootstrap';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { TabsModule } from 'ngx-bootstrap/tabs'; import { TabsModule } from 'ngx-bootstrap/tabs';
import { TreeModule } from 'angular-tree-component'; import { TreeModule } from 'angular-tree-component';
import { WorkbasketlistComponent } from './workbasketlist/workbasketlist.component';
/**
* Components
*/
import { AppComponent } from './app.component';
import { WorkbasketListComponent } from './workbasket/list/workbasket-list.component';
import { WorkbasketeditorComponent } from './workbasketeditor/workbasketeditor.component'; import { WorkbasketeditorComponent } from './workbasketeditor/workbasketeditor.component';
import { CategorieslistComponent } from './categorieslist/categorieslist.component'; import { CategorieslistComponent } from './categorieslist/categorieslist.component';
import { CategoriestreeComponent } from './categoriestree/categoriestree.component'; import { CategoriestreeComponent } from './categoriestree/categoriestree.component';
@ -16,25 +26,15 @@ import { WorkbasketadministrationComponent } from './workbasketadministration/wo
import { WorkbasketAuthorizationComponent } from './workbasket-authorization/workbasket-authorization.component'; import { WorkbasketAuthorizationComponent } from './workbasket-authorization/workbasket-authorization.component';
import { WorkbasketDetailsComponent } from './workbasket-details/workbasket-details.component'; import { WorkbasketDetailsComponent } from './workbasket-details/workbasket-details.component';
import { WorkbasketDistributiontargetsComponent } from './workbasket-distributiontargets/workbasket-distributiontargets.component'; import { WorkbasketDistributiontargetsComponent } from './workbasket-distributiontargets/workbasket-distributiontargets.component';
import { AppRoutingModule } from './app-routing.module'; //Shared
import { AlertModule } from 'ngx-bootstrap'; import { MasterAndDetailComponent} from './shared/masterAndDetail/master-and-detail.component';
import { AngularSvgIconModule } from 'angular-svg-icon';
@NgModule({
declarations: [ /**
AppComponent, * Services
WorkbasketlistComponent, */
WorkbasketeditorComponent,
CategorieslistComponent, const MODULES = [
CategoriestreeComponent,
CategoryeditorComponent,
CategoriesadministrationComponent,
WorkbasketadministrationComponent,
WorkbasketAuthorizationComponent,
WorkbasketDetailsComponent,
WorkbasketDistributiontargetsComponent
],
imports: [
BrowserModule, BrowserModule,
FormsModule, FormsModule,
HttpModule, HttpModule,
@ -45,7 +45,25 @@ import { AngularSvgIconModule } from 'angular-svg-icon';
AlertModule.forRoot(), AlertModule.forRoot(),
AngularSvgIconModule, AngularSvgIconModule,
HttpClientModule HttpClientModule
], ];
const COMPONENTS = [
AppComponent,
WorkbasketListComponent,
WorkbasketeditorComponent,
CategorieslistComponent,
CategoriestreeComponent,
CategoryeditorComponent,
CategoriesadministrationComponent,
WorkbasketadministrationComponent,
WorkbasketAuthorizationComponent,
WorkbasketDetailsComponent,
WorkbasketDistributiontargetsComponent,
MasterAndDetailComponent
];
@NgModule({
declarations: COMPONENTS,
imports: MODULES,
providers: [HttpClientModule], providers: [HttpClientModule],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -0,0 +1,15 @@
<div class="{{showDetail? 'col-md-4 hidden-xs hidden-sm':'col-xs-12 col-md-4'}} vertical-right-divider full-height" >
<router-outlet name="master"></router-outlet>
</div>
<div class="{{showDetail? 'col-xs-12 col-md-8': 'hidden'}} container-scrollable" >
<router-outlet name="detail">
<a class="{{showDetail? 'hidden visible-xs visible-sm': 'hidden'}}" (click) = "backClicked()"> < Back</a>
</router-outlet>
</div>
<div class="{{showDetail? 'hidden': 'col-md-8 container-no-detail' }}">
<div class = "center-block no-detail" >
<h3>Select a worbasket</h3>
<svg-icon class="img-responsive no-detail-icon" src="./assets/icons/no-shopping-basket.svg"></svg-icon>
</div>
</div>

View File

@ -0,0 +1,17 @@
.container-no-detail .no-detail-icon {
display: block;
width: 150px;
height: 150px;
fill: grey;
margin: 0 auto;
}
.container-no-detail{
top:30vh;
}
.center-block.no-detail {
width: 250px;
text-align: center;
}

View File

@ -0,0 +1,105 @@
import {Location} from "@angular/common";
import { Component, OnInit, OnDestroy } from '@angular/core';
import { TestBed, async, inject, fakeAsync } from '@angular/core/testing';
import { MasterAndDetailComponent } from './master-and-detail.component';
import { RouterTestingModule } from '@angular/router/testing';
import { Router, Routes, ActivatedRoute, NavigationStart, RouterEvent } from '@angular/router';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
@Component({
selector: 'dummy-master',
template: 'dummymaster'
})
export class DummyMasterComponent {
}
@Component({
selector: 'dummy-detail',
template: 'dummydetail'
})
export class DummyDetailComponent {
}
describe('MasterAndDetailComponent ', () => {
var component, fixture, debugElement, location, router;
const routes: Routes = [
{ path: 'workbaskets',
component: MasterAndDetailComponent,
children: [
{
path: '',
component: DummyMasterComponent,
outlet: 'master'
},
{
path: ':id',
component: DummyDetailComponent,
outlet: 'detail'
}
]
}
];
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MasterAndDetailComponent, DummyMasterComponent, DummyDetailComponent ],
imports:[
RouterTestingModule.withRoutes(routes),
AngularSvgIconModule,
HttpClientModule
]
})
.compileComponents();
fixture = TestBed.createComponent(MasterAndDetailComponent);
component = fixture.debugElement.componentInstance;
debugElement = fixture.debugElement.nativeElement;
location = TestBed.get(Location);
router = TestBed.get(Router);
router.initialNavigation();
}));
afterEach(async(()=>{
document.body.removeChild(debugElement);
}));
it('should be created', () => {
expect(component).toBeTruthy();
});
it('should call Router.navigateByUrl("/wokbaskets") and showDetail property should be false', async ( () => {
expect(component.showDetail).toBe(false);
fixture.detectChanges();
router.navigateByUrl('/workbaskets');
expect(component.showDetail).toBe(false);
}));
it('should call Router.navigateByUrl("/wokbaskets/(detail:Id)") and showDetail property should be true', async ( () => {
expect(component.showDetail).toBe(false);
fixture.detectChanges();
router.navigateByUrl('/workbaskets/(detail:2)');
expect(component.showDetail).toBe(true);
}));
it('should navigate to parent state when backIsClicked', async( () => {
const spy = spyOn(router, 'navigateByUrl');
router.navigateByUrl('/workbaskets/(detail:2)');
fixture.detectChanges();
expect(spy.calls.first().args[0]).toBe('/workbaskets/(detail:2)');
component.backClicked();
expect(spy.calls.mostRecent().args.length).toBe(2);
}));
});

View File

@ -0,0 +1,46 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, Routes, ActivatedRoute, NavigationStart, RouterEvent } from '@angular/router';
@Component({
selector: 'master-and-detail',
templateUrl: './master-and-detail.component.html',
styleUrls: ['./master-and-detail.component.scss'],
})
export class MasterAndDetailComponent implements OnInit{
private detailRoutes: Array<string> = ['/workbaskets/(detail', 'clasifications'];
private sub: any;
showDetail: Boolean = false;
constructor(private route: ActivatedRoute, private router: Router){
}
ngOnInit(): void {
this.showDetail = this.showDetails();
this.router.events.subscribe(event => {
if(event instanceof NavigationStart) {
this.showDetail = this.showDetails(event);
}
});
}
backClicked(): void {
this.router.navigate(['../'], { relativeTo: this.route });
}
private showDetails(event? : RouterEvent): Boolean {
if(event === undefined) {
return this.checkUrl(this.router.url);
}
return this.checkUrl(event.url)
}
private checkUrl(url: string): Boolean {
for(let routeDetail of this.detailRoutes){
if(url.indexOf(routeDetail) !== -1){
return true;
}
}
return false;
}
}

View File

@ -28,7 +28,8 @@
</button> </button>
</td> </td>
</tr> </tr>
<tr *ngFor="let workbasket of workbaskets" (click)="onSelect(workbasket)" [class.active]="workbasket.id == selected.id" [routerLink]="['/workbaskets', workbasket.id]">
<tr *ngFor="let workbasket of workbaskets" (click)="onSelect(workbasket)" [class.active]="workbasket.id == selected.id" [routerLink]="[ {outlets: { detail: [workbasket.id] } }]">
<td *ngIf="workbasket.id != editing.id">{{ workbasket.id }}</td> <td *ngIf="workbasket.id != editing.id">{{ workbasket.id }}</td>
<td *ngIf="workbasket.id == editing.id"> <td *ngIf="workbasket.id == editing.id">
<input class="form-control" placeholder="Id" name="editid" [(ngModel)]="editing.id" readonly> <input class="form-control" placeholder="Id" name="editid" [(ngModel)]="editing.id" readonly>

View File

@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { WorkbasketlistComponent } from './workbasketlist.component'; import { WorkbasketListComponent } from './workbasket-list.component';
describe('WorkbasketlistComponent', () => { describe('WorkbasketListComponent', () => {
let component: WorkbasketlistComponent; let component: WorkbasketListComponent;
let fixture: ComponentFixture<WorkbasketlistComponent>; let fixture: ComponentFixture<WorkbasketListComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ WorkbasketlistComponent ] declarations: [ WorkbasketListComponent ]
}) })
.compileComponents(); .compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(WorkbasketlistComponent); fixture = TestBed.createComponent(WorkbasketListComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@ -1,16 +1,16 @@
import { Component, OnInit, EventEmitter } from '@angular/core'; import { Component, OnInit, EventEmitter } from '@angular/core';
import { Workbasket } from '../model/workbasket'; import { Workbasket } from '../../model/workbasket';
import { WorkbasketserviceService } from '../services/workbasketservice.service' import { WorkbasketserviceService } from '../../services/workbasketservice.service'
import { ActivatedRoute, Params } from '@angular/router'; import { ActivatedRoute, Params } from '@angular/router';
@Component({ @Component({
selector: 'app-workbasketlist', selector: 'workbasket-list',
outputs: ['selectedWorkbasket'], outputs: ['selectedWorkbasket'],
templateUrl: './workbasketlist.component.html', templateUrl: './workbasket-list.component.html',
styleUrls: ['./workbasketlist.component.css'], styleUrls: ['./workbasket-list.component.css'],
providers: [WorkbasketserviceService] providers: [WorkbasketserviceService]
}) })
export class WorkbasketlistComponent implements OnInit { export class WorkbasketListComponent implements OnInit {
public selectedWorkbasket: EventEmitter<Workbasket> = new EventEmitter(); public selectedWorkbasket: EventEmitter<Workbasket> = new EventEmitter();
workbasket: Workbasket = this.getEmptyObject(); workbasket: Workbasket = this.getEmptyObject();

View File

@ -1,2 +1,3 @@
<!-- app-workbasketeditor [workbasket]="selectedWorkbasket" (workbasketSaved)="onWorkbasketSaved($event)"></app-workbasketeditor --> <!-- app-workbasketeditor [workbasket]="selectedWorkbasket" (workbasketSaved)="onWorkbasketSaved($event)"></app-workbasketeditor -->
<app-workbasketlist (selectedWorkbasket)="onWorkbasketSelected($event)"></app-workbasketlist> <!-- <workbasket-list (selectedWorkbasket)="onWorkbasketSelected($event)"></workbasket-list> -->
DETAIL COMPONENT

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M30.314 16.935c.856 0 1.544.84 1.544 1.885 0 1.046-.688 1.885-1.545 1.885h-.18l-1.39 9.747c-.132.898-.772 1.56-1.52 1.56h-15.45c-.749 0-1.388-.662-1.521-1.56l-1.388-9.747h-.181c-.857 0-1.545-.84-1.545-1.885 0-1.046.688-1.885 1.545-1.885zm-17.322 11.78c.422-.045.748-.5.712-1.016l-.386-6.126c-.036-.515-.41-.912-.833-.868-.422.044-.748.5-.712 1.016l.386 6.125c.036.486.374.869.772.869zm4.96-.943v-6.125c0-.515-.35-.942-.772-.942-.422 0-.773.427-.773.942v6.125c0 .516.35.943.773.943.423 0 .773-.427.773-.943zm4.636 0v-6.125c0-.515-.35-.942-.773-.942-.422 0-.772.427-.772.942v6.125c0 .516.35.943.772.943.423 0 .773-.427.773-.943zm4.249.074l.386-6.125c.036-.516-.29-.972-.712-1.016-.423-.044-.797.353-.833.868l-.386 6.126c-.037.515.29.971.712 1.016h.06c.399 0 .737-.383.773-.869zM12.883 9.926l-1.122 6.067h-1.594l1.22-6.494c.313-1.722 1.557-2.93 3.005-2.93h2.016c0-.515.35-.942.773-.942h4.634c.423 0 .773.427.773.942h2.016c1.448 0 2.692 1.208 3.006 2.93l1.219 6.494h-1.594l-1.122-6.066c-.17-.87-.785-1.473-1.51-1.473h-2.015c0 .515-.35.942-.773.942h-4.634c-.423 0-.773-.426-.773-.942h-2.016c-.724 0-1.34.604-1.509 1.473zM2.28.11l2.806 3.237L7.878.124a.327.327 0 0 1 .51.002l1.53 1.766c.14.162.141.425 0 .587L7.127 5.7 9.932 8.94c.14.162.141.425.001.587l-1.524 1.758a.327.327 0 0 1-.509-.001L5.094 8.046l-2.793 3.222a.327.327 0 0 1-.509-.001L.262 9.5a.462.462 0 0 1-.002-.587L3.053 5.69.247 2.453a.462.462 0 0 1 0-.587L1.77.108a.327.327 0 0 1 .509.002z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB