TSK-184 Add pagination feature

This commit is contained in:
Martin Rojas Miguel Angel 2018-03-27 19:21:24 +02:00 committed by Holger Hagen
parent db8de1063e
commit 83f0472bac
40 changed files with 610 additions and 197 deletions

View File

@ -20,6 +20,7 @@ import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { SavingWorkbasketService, SavingInformation } from 'app/services/saving-workbaskets/saving-workbaskets.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { AlertService } from 'app/services/alert/alert.service';
import { SimpleChange } from '@angular/core';
@ -56,7 +57,9 @@ describe('AccessItemsComponent', () => {
spyOn(workbasketService, 'updateWorkBasketAccessItem').and.returnValue(Observable.of(true)),
spyOn(alertService, 'triggerAlert').and.returnValue(Observable.of(true)),
debugElement = fixture.debugElement.nativeElement;
component.ngOnChanges({
active: new SimpleChange(undefined, 'accessItems', true)
});
fixture.detectChanges();
});

View File

@ -1,4 +1,4 @@
import { Component, OnInit, Input, AfterViewInit, OnDestroy } from '@angular/core';
import { Component, OnInit, Input, AfterViewInit, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { Workbasket } from 'app/models/workbasket';
@ -20,12 +20,15 @@ declare var $: any;
templateUrl: './access-items.component.html',
styleUrls: ['./access-items.component.scss']
})
export class AccessItemsComponent implements OnInit, OnDestroy {
export class AccessItemsComponent implements OnChanges, OnDestroy {
@Input()
workbasket: Workbasket;
@Input()
action: string;
@Input()
active: string;
badgeMessage = '';
accessItemsResource: WorkbasketAccessItemsResource;
@ -38,6 +41,7 @@ export class AccessItemsComponent implements OnInit, OnDestroy {
modalErrorMessage: string;
accessItemsubscription: Subscription;
savingAccessItemsSubscription: Subscription;
private initialized = false;
constructor(
@ -46,7 +50,13 @@ export class AccessItemsComponent implements OnInit, OnDestroy {
private errorModalService: ErrorModalService,
private savingWorkbaskets: SavingWorkbasketService) { }
ngOnInit() {
ngOnChanges(changes: SimpleChanges): void {
if (changes.active.currentValue === 'accessItems' && !this.initialized) {
this.init();
}
}
private init() {
this.initialized = true;
if (!this.workbasket._links.accessItems) {
return;
}

View File

@ -1,4 +1,4 @@
import { Input, Component } from '@angular/core';
import { Input, Component, SimpleChange } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Observable } from 'rxjs/Observable';
import { AngularSvgIconModule } from 'angular-svg-icon';
@ -24,13 +24,14 @@ import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/general-message-modal.component';
import { IconTypeComponent } from 'app/shared/type-icon/icon-type.component';
import { SelectWorkBasketPipe } from 'app/pipes/selectedWorkbasket/seleted-workbasket.pipe';
import { LinksWorkbasketSummary } from '../../../../models/links-workbasket-summary';
const workbasketSummaryResource: WorkbasketSummaryResource = new WorkbasketSummaryResource({
'workbaskets': new Array<WorkbasketSummary>(
new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1', '', '', 'PERSONAL', '', '', '', ''),
new WorkbasketSummary('2', 'key2', 'NAME2', 'description 2', 'owner 2', '', '', 'GROUP', '', '', '', ''))
}, new Links({ 'href': 'url' }));
}, new LinksWorkbasketSummary({ 'href': 'url' }));
@Component({
selector: 'taskana-filter',
@ -73,16 +74,18 @@ describe('DistributionTargetsComponent', () => {
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })),
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })),
new WorkbasketSummary('id3', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })))
}, new Links({ 'href': 'someurl' })))
}, new LinksWorkbasketSummary({ 'href': 'someurl' })))
})
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => {
return Observable.of(new WorkbasketDistributionTargetsResource(
{
'distributionTargets': new Array<WorkbasketSummary>(
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })))
}, new Links({ 'href': 'someurl' })))
}, new LinksWorkbasketSummary({ 'href': 'someurl' })))
})
component.ngOnChanges({
active: new SimpleChange(undefined, 'distributionTargets', true)
});
fixture.detectChanges();
});
@ -144,7 +147,7 @@ describe('DistributionTargetsComponent', () => {
'distributionTargets': new Array<WorkbasketSummary>(
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })),
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', new Links({ 'href': 'someurl' })))
}, new Links({ 'href': 'someurl' })))
}, new LinksWorkbasketSummary({ 'href': 'someurl' })))
})
component.onSave();
fixture.detectChanges();

View File

@ -1,4 +1,4 @@
import { Component, OnInit, Input, OnDestroy } from '@angular/core';
import { Component, OnInit, Input, OnDestroy, SimpleChanges, OnChanges } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { Workbasket } from 'app/models/workbasket';
@ -24,12 +24,14 @@ export enum Side {
templateUrl: './distribution-targets.component.html',
styleUrls: ['./distribution-targets.component.scss']
})
export class DistributionTargetsComponent implements OnInit, OnDestroy {
export class DistributionTargetsComponent implements OnChanges, OnDestroy {
@Input()
workbasket: Workbasket;
@Input()
action: string;
@Input()
active: string;
badgeMessage = '';
distributionTargetsSubscription: Subscription;
@ -44,12 +46,12 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
distributionTargetsClone: Array<WorkbasketSummary>;
distributionTargetsSelectedClone: Array<WorkbasketSummary>;
requestInProgress = false;
requestInProgressLeft = false;
requestInProgressRight = false;
modalErrorMessage: string;
side = Side;
private initialized = false;
constructor(
private workbasketService: WorkbasketService,
@ -57,7 +59,14 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
private savingWorkbaskets: SavingWorkbasketService,
private errorModalService: ErrorModalService) { }
ngOnInit() {
ngOnChanges(changes: SimpleChanges): void {
if (changes.active.currentValue === 'distributionTargets' && !this.initialized) {
this.init();
}
}
private init() {
this.initialized = true;
this.onRequest(undefined);
if (!this.workbasket._links.distributionTargets) {
return;
@ -69,7 +78,9 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
this.distributionTargetsSelected = distributionTargetsSelectedResource._embedded ?
distributionTargetsSelectedResource._embedded.distributionTargets : [];
this.distributionTargetsSelectedClone = Object.assign([], this.distributionTargetsSelected);
this.workbasketSubscription = this.workbasketService.getWorkBasketsSummary().subscribe(
this.workbasketSubscription = this.workbasketService.getWorkBasketsSummary(true,
undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
undefined, undefined, undefined, true).subscribe(
(distributionTargetsAvailable: WorkbasketSummaryResource) => {
this.distributionTargetsLeft = Object.assign([], distributionTargetsAvailable._embedded.workbaskets);
this.distributionTargetsRight = Object.assign([], distributionTargetsAvailable._embedded.workbaskets);
@ -143,7 +154,7 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
this.onRequest(dualListFilter.side, false);
this.workbasketFilterSubscription = this.workbasketService.getWorkBasketsSummary(true, undefined, undefined, undefined,
dualListFilter.filterBy.name, dualListFilter.filterBy.description, undefined, dualListFilter.filterBy.owner,
dualListFilter.filterBy.type, undefined, dualListFilter.filterBy.key).subscribe(resultList => {
dualListFilter.filterBy.type, undefined, dualListFilter.filterBy.key, undefined, true).subscribe(resultList => {
(dualListFilter.side === Side.RIGHT) ?
this.distributionTargetsRight = (resultList._embedded ? resultList._embedded.workbaskets : []) :
this.distributionTargetsLeft = (resultList._embedded ? resultList._embedded.workbaskets : []);

View File

@ -9,30 +9,32 @@
<h5>Available distribution targets</h5>
</div>
<div class="pull-right">
<button class="btn btn-default collapsed" type="button" id="collapsedMenufilterWbDta" data-toggle="collapse"
[attr.data-target]="'#wb-dta-filter-bar-' + sideNumber"
aria-expanded="false">
<button class="btn btn-default" type="button" id="collapsedMenufilterWb" aria-expanded="false" (click)="toolbarState=!toolbarState">
<span class="glyphicon glyphicon-filter blue"></span>
</button>
</div>
</div>
<taskana-filter target="wb-dta-filter-bar-{{sideNumber}}" (performFilter)="performAvailableFilter($event)"></taskana-filter>
<div [@toggle]="toolbarState" *ngIf="toolbarState" class="row">
<taskana-filter class="col-xs-12" (performFilter)="performAvailableFilter($event)"></taskana-filter>
</div>
<taskana-spinner [isRunning]="requestInProgress" positionClass="centered-spinner" class="centered-horizontally floating"></taskana-spinner>
<ul class="list-group ">
<div>
<ul class="list-group">
<li class="list-group-item" *ngFor="let distributionTarget of distributionTargets | selectWorkbaskets: distributionTargetsSelected: side"
[class.selected]="distributionTarget.selected" type="text" (click)="distributionTarget.selected = !distributionTarget.selected">
<div class="row">
<dl class="col-xs-1">
<dt>
<taskana-icon-type class="vertical-align" [type]="distributionTarget.type"></taskana-icon-type>
</dt>
</dl>
<dl class="col-xs-10">
<dt>{{distributionTarget.name}} ({{distributionTarget.key}}) </dt>
<dd>{{distributionTarget.description}}</dd>
<dt>{{distributionTarget.name}},
<i>{{distributionTarget.key}} </i>
</dt>
<dd>{{distributionTarget.description}} &nbsp;</dd>
<dd>{{distributionTarget.owner}} &nbsp;</dd>
</dl>
</div>
</li>
</ul>
</div>
</div>

View File

@ -58,3 +58,33 @@ button.no-style{
background: none;
border:none;
}
.row.list-group {
margin-left: 2px;
}
.list-group > li {
border-left: none;
border-right: none;
}
a > label{
height: 2em;
width: 100%;
}
dd, dt {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
dt > i {
font-weight: normal;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
li > div.row > dl {
margin-bottom: 0px;
}

View File

@ -1,5 +1,6 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { WorkbasketSummary } from 'app/models/workbasket-summary';
import { trigger, state, style, transition, animate, keyframes } from '@angular/animations';
import { FilterModel } from 'app/models/filter';
import { filter } from 'rxjs/operators';
import { Side } from '../distribution-targets.component';
@ -7,7 +8,21 @@ import { Side } from '../distribution-targets.component';
@Component({
selector: 'taskana-dual-list',
templateUrl: './dual-list.component.html',
styleUrls: ['./dual-list.component.scss']
styleUrls: ['./dual-list.component.scss'],
animations: [
trigger('toggle', [
state('*', style({ opacity: '1' })),
state('void', style({ opacity: '0' })),
transition('void => *', animate('300ms ease-in', keyframes([
style({ opacity: 0, height: '0px' }),
style({ opacity: 0.5, height: '50px' }),
style({ opacity: 1, height: '*' })]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({ opacity: 1, height: '*' }),
style({ opacity: 0.5, height: '50px' }),
style({ opacity: 0, height: '0px' })])))
]
)],
})
export class DualListComponent implements OnInit {
@ -21,6 +36,7 @@ export class DualListComponent implements OnInit {
sideNumber = 0;
toggleDtl = false;
toolbarState = false;
constructor() { }

View File

@ -7,6 +7,8 @@ import { HttpClientModule } from '@angular/common/http';
import { HttpModule, JsonpModule } from '@angular/http';
import { RouterTestingModule } from '@angular/router/testing';
import { Observable } from 'rxjs/Observable';
import {Component} from '@angular/core';
import {Routes} from '@angular/router';
import { Workbasket } from 'app/models/workbasket';
import { ICONTYPES } from 'app/models/type';
@ -22,8 +24,6 @@ import { RemoveNoneTypePipe } from 'app/pipes/removeNoneType/remove-none-type.pi
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { SavingWorkbasketService, SavingInformation } from 'app/services/saving-workbaskets/saving-workbaskets.service';
import { AlertService } from 'app/services/alert/alert.service';
import {Component} from '@angular/core';
import {Routes} from '@angular/router';
@Component({
selector: 'taskana-dummy-detail',

View File

@ -130,7 +130,6 @@ export class WorkbasketInformationComponent implements OnInit, OnDestroy {
const date = datePipe.transform(Date.now(), dateFormat) + 'Z';
this.workbasket.created = date;
this.workbasket.modified = date;
}
ngOnDestroy() {

View File

@ -7,25 +7,25 @@
<a (click)="backClicked()">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>Back</a>
</li>
<li role="presentation" class="active">
<li role="presentation" class="active" (click)="tabSelected = 'information'" >
<a href="#work-baskets" aria-controls="work baskets" role="tab" data-toggle="tab" aria-expanded="true">Information</a>
</li>
<li role="presentation" [ngClass]="{'disabled': !workbasket._links?.accessItems}">
<li role="presentation" [ngClass]="{'disabled': !workbasket._links?.accessItems}" (click)="tabSelected = 'accessItems'">
<a href="#access-items" aria-controls="Acccess" role="tab" data-toggle="tab" aria-expanded="true">Access</a>
</li>
<li role="presentation" [ngClass]="{'disabled': !workbasket._links?.distributionTargets}">
<li role="presentation" [ngClass]="{'disabled': !workbasket._links?.distributionTargets}" (click)="tabSelected = 'distributionTargets'">
<a href="#distribution-targets" aria-controls="distribution targets" role="tab" data-toggle="tab" aria-expanded="true">Distribution targets</a>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane fade in active" id="work-baskets">
<taskana-workbasket-information [workbasket]="workbasket" [action]="action"></taskana-workbasket-information>
<div role="tabpanel" class="tab-pane fade in active"id="work-baskets">
<taskana-workbasket-information [workbasket]="workbasket" [action]="action" ></taskana-workbasket-information>
</div>
<div role="tabpanel" class="tab-pane fade" id="access-items">
<taskana-workbasket-access-items [workbasket]="workbasket" [action]="action"></taskana-workbasket-access-items>
<taskana-workbasket-access-items [workbasket]="workbasket" [action]="action" [active] ="tabSelected"></taskana-workbasket-access-items>
</div>
<div role="tabpanel" class="tab-pane fade" id="distribution-targets">
<taskana-workbaskets-distribution-targets [workbasket]="workbasket" [action]="action"></taskana-workbaskets-distribution-targets>
<taskana-workbaskets-distribution-targets [workbasket]="workbasket" [action]="action" [active] ="tabSelected"></taskana-workbaskets-distribution-targets>
</div>
</div>
</div>

View File

@ -37,6 +37,7 @@ import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/g
import { MapValuesPipe } from 'app/pipes/mapValues/map-values.pipe';
import { RemoveNoneTypePipe } from 'app/pipes/removeNoneType/remove-none-type.pipe';
import { SelectWorkBasketPipe } from 'app/pipes/selectedWorkbasket/seleted-workbasket.pipe';
import { LinksWorkbasketSummary } from '../../../models/links-workbasket-summary';
@Component({
selector: 'taskana-filter',
@ -53,10 +54,8 @@ export class FilterComponent {
template: 'dummydetail'
})
export class DummyDetailComponent {
}
describe('WorkbasketDetailsComponent', () => {
let component: WorkbasketDetailsComponent;
let fixture: ComponentFixture<WorkbasketDetailsComponent>;
@ -68,7 +67,7 @@ describe('WorkbasketDetailsComponent', () => {
new Links({ 'href': 'someurl' }, { 'href': 'someurl' }, { 'href': 'someurl' }));
const routes: Routes = [
{ path: ':id', component: DummyDetailComponent, outlet: 'detail' }
{ path: '*', component: DummyDetailComponent}
];
beforeEach(async(() => {
@ -100,7 +99,7 @@ describe('WorkbasketDetailsComponent', () => {
'workbaskets': new Array<WorkbasketSummary>(
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '',
new Links({ 'href': 'someurl' })))
}, new Links({ 'href': 'someurl' })))
}, new LinksWorkbasketSummary({ 'href': 'someurl' })))
})
spyOn(workbasketService, 'getWorkBasket').and.callFake(() => { return Observable.of(workbasket) })
@ -110,7 +109,7 @@ describe('WorkbasketDetailsComponent', () => {
})
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => {
return Observable.of(new WorkbasketSummaryResource(
{ 'workbaskets': new Array<WorkbasketSummary>() }, new Links({ 'href': 'url' })))
{ 'workbaskets': new Array<WorkbasketSummary>() }, new LinksWorkbasketSummary({ 'href': 'url' })))
})
});

View File

@ -100,14 +100,14 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
private getWorkbasketInformation(workbasketIdSelected: string, copyId: string = undefined) {
this.requestInProgress = true;
this.service.getWorkBasketsSummary(true).subscribe((workbasketSummary: WorkbasketSummaryResource) => {
this.service.getWorkBasketsSummary().subscribe((workbasketSummary: WorkbasketSummaryResource) => {
if (!workbasketIdSelected && this.action === ACTION.CREATE) { // CREATE
this.workbasket = new Workbasket(undefined);
this.workbasket._links.self = workbasketSummary._links.self;
this.workbasket._links.self = workbasketSummary._links.allWorkbaskets;
this.requestInProgress = false;
} else if (!workbasketIdSelected && this.action === ACTION.COPY) { // COPY
this.workbasket = { ...this.workbasketCopy };
this.workbasket._links.self = workbasketSummary._links.self;
this.workbasket._links.self = workbasketSummary._links.allWorkbaskets;
this.workbasket.workbasketId = undefined;
this.requestInProgress = false;
}

View File

@ -0,0 +1,15 @@
<ul id="wb-pagination" class="pagination vertical-center">
<li>
<a (click)="changeToPage(1)" aria-label="First">
First</a>
</li>
<li *ngFor="let pageNumber of workbasketsResource?.page?.totalPages |
spreadNumber: workbasketsResource?.page?.number: maxPagesAvailable: workbasketsResource?.page?.totalPages">
<a *ngIf="pageNumber + 1 !== workbasketsResource?.page?.number" (click)="changeToPage(pageNumber+1)">{{pageNumber + 1}}</a>
<input *ngIf="pageNumber + 1 === workbasketsResource?.page?.number" [(ngModel)]="pageSelected" (keyup.enter)="changeToPage(pageSelected)"
type="text" class="pagination" (blur)="changeToPage(pageSelected)">
</li>
<li>
<a (click)="changeToPage(workbasketsResource?.page.totalPages)" aria-label="Last">Last</a>
</li>
</ul>

View File

@ -0,0 +1,11 @@
input.pagination{
width: 35px;
height: 35px;
margin: 0 10px;
text-align: center;
background-color: aliceblue;
}
ul.pagination{
overflow: hidden;
}

View File

@ -0,0 +1,84 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { PaginationComponent } from './pagination.component';
import { SpreadNumberPipe } from 'app/pipes/spreadNumber/spread-number';
import { WorkbasketSummaryResource } from '../../../../../models/workbasket-summary-resource';
import { Page } from '../../../../../models/page';
describe('PaginationComponent', () => {
let component: PaginationComponent;
let fixture: ComponentFixture<PaginationComponent>;
let debugElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [PaginationComponent, SpreadNumberPipe],
imports: [FormsModule]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PaginationComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
fixture.detectChanges();
});
afterEach(() => {
fixture.detectChanges()
document.body.removeChild(debugElement);
})
it('should create', () => {
expect(component).toBeTruthy();
expect(debugElement.querySelectorAll('#wb-pagination > li').length).toBe(2);
});
it('should create 3 pages if total pages are 3', () => {
component.workbasketsResource = new WorkbasketSummaryResource(undefined, undefined, new Page(6, 3, 3, 1));
fixture.detectChanges();
expect(debugElement.querySelectorAll('#wb-pagination > li').length).toBe(5);
});
it('should emit change if previous page was different than current one', () => {
component.workbasketsResource = new WorkbasketSummaryResource(undefined, undefined, new Page(6, 3, 3, 1));
component.previousPageSelected = 2;
fixture.detectChanges();
component.changePage.subscribe(value => {
expect(value).toBe(1)
})
component.changeToPage(1);
});
it('should not emit change if previous page was the same than current one', () => {
component.workbasketsResource = new WorkbasketSummaryResource(undefined, undefined, new Page(6, 3, 3, 1));
component.previousPageSelected = 2;
fixture.detectChanges();
component.changePage.subscribe(value => {
expect(false).toBe(true)
})
component.changeToPage(2);
});
it('should emit totalPages if page is more than page.totalPages', () => {
component.workbasketsResource = new WorkbasketSummaryResource(undefined, undefined, new Page(6, 3, 3, 1));
component.previousPageSelected = 2;
fixture.detectChanges();
component.changePage.subscribe(value => {
expect(value).toBe(3)
})
component.changeToPage(100);
});
it('should emit 1 if page is less than 1', () => {
component.workbasketsResource = new WorkbasketSummaryResource(undefined, undefined, new Page(6, 3, 3, 1));
component.previousPageSelected = 2;
fixture.detectChanges();
component.changePage.subscribe(value => {
expect(value).toBe(1)
})
component.changeToPage(0);
});
});

View File

@ -0,0 +1,36 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resource';
@Component({
selector: 'taskana-pagination',
templateUrl: './pagination.component.html',
styleUrls: ['./pagination.component.scss']
})
export class PaginationComponent implements OnInit {
@Input()
workbasketsResource: WorkbasketSummaryResource;
@Output()
workbasketsResourceChange = new EventEmitter<WorkbasketSummaryResource>();
@Output() changePage = new EventEmitter<number>();
previousPageSelected = 1;
pageSelected = 1;
maxPagesAvailable = 8;
constructor() { }
ngOnInit() {
}
changeToPage(page) {
if (page < 1) { page = this.pageSelected = 1; }
if (page > this.workbasketsResource.page.totalPages) {
page = this.workbasketsResource.page.totalPages;
}
if (this.previousPageSelected !== page) {
this.changePage.emit(page);
this.previousPageSelected = page;
this.pageSelected = page;
}
}
}

View File

@ -1,13 +1,9 @@
<li id="wb-action-toolbar" class="list-group-item tab-align">
<div class="row">
<div class="col-xs-9">
<taskana-import-export-component [currentSelection]="'workbaskets'"></taskana-import-export-component>
<button type="button" (click)="addWorkbasket()" data-toggle="tooltip" title="Add" class="btn btn-default">
<span class="glyphicon glyphicon-plus green" aria-hidden="true"></span>
</button>
<button type="button" data-toggle="tooltip" title="Edit" class="btn btn-default">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
</button>
<button *ngIf="workbasketIdSelected" type="button" (click)="copyWorkbasket()" data-toggle="tooltip" title="copy" class="btn btn-default">
<span class="glyphicon glyphicon-copy" aria-hidden="true"></span>
</button>
@ -17,18 +13,18 @@
<button *ngIf="workbasketIdSelected" type="button" (click)="removeWorkbasket()" data-toggle="tooltip" title="Remove" class="btn btn-default remove">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
<taskana-import-export-component [currentSelection]="'workbaskets'"></taskana-import-export-component>
</div>
<div class="pull-right margin-right">
<taskana-sort (performSorting)="sorting($event)"></taskana-sort>
<div class="clearfix btn-group">
<button class="btn btn-default collapsed" type="button" id="collapsedMenufilterWb" data-toggle="collapse" data-target="#wb-filter-bar"
aria-expanded="false">
<button class="btn btn-default collapsed" type="button" id="collapsedMenufilterWb" aria-expanded="false" (click)="toolbarState=!toolbarState">
<span class="glyphicon glyphicon-filter blue"></span>
</button>
</div>
</div>
</div>
<div class="row">
<taskana-filter target="wb-filter-bar" (performFilter)="filtering($event)"></taskana-filter>
<div [@toggle]="toolbarState" *ngIf="toolbarState" class="row no-overflow">
<taskana-filter (performFilter)="filtering($event)"></taskana-filter>
</div>
</li>

View File

@ -1,4 +1,5 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Component, OnInit, Input, Output, EventEmitter, AfterViewChecked } from '@angular/core';
import { trigger, state, style, transition, animate, keyframes } from '@angular/animations';
import { Router, ActivatedRoute } from '@angular/router';
import { SortingModel } from 'app/models/sorting';
@ -11,10 +12,24 @@ import { AlertModel, AlertType } from 'app/models/alert';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { AlertService} from 'app/services/alert/alert.service';
import { AlertService } from 'app/services/alert/alert.service';
@Component({
selector: 'taskana-workbasket-list-toolbar',
animations: [
trigger('toggle', [
state('*', style({ opacity: '1' })),
state('void', style({ opacity: '0' })),
transition('void => *', animate('300ms ease-in', keyframes([
style({ opacity: 0, height: '0px' }),
style({ opacity: 0.5, height: '50px' }),
style({ opacity: 1, height: '*' })]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({ opacity: 1, height: '*' }),
style({ opacity: 0.5, height: '50px' }),
style({ opacity: 0, height: '0px' })])))
]
)],
templateUrl: './workbasket-list-toolbar.component.html',
styleUrls: ['./workbasket-list-toolbar.component.scss']
})
@ -27,6 +42,7 @@ export class WorkbasketListToolbarComponent implements OnInit {
@Output() performSorting = new EventEmitter<SortingModel>();
@Output() performFilter = new EventEmitter<FilterModel>();
workbasketServiceSubscription: Subscription;
toolbarState = false;
constructor(
private workbasketService: WorkbasketService,

View File

@ -1,15 +1,18 @@
<div class="workbasket-list-full-height">
<ul id="wb-list-container" class="list-group footer-space">
<div class="footer-space">
<div #wbToolbar>
<taskana-workbasket-list-toolbar [workbaskets]="workbaskets" (performFilter)="performFilter($event)" (performSorting)="performSorting ($event)"
[(workbasketIdSelected)]="selectedId"></taskana-workbasket-list-toolbar>
</div>
<taskana-spinner [isRunning]="requestInProgress" class="centered-horizontally"></taskana-spinner>
<div>
<ul #wbList id="wb-list-container" class="list-group">
<li class="list-group-item" *ngFor="let workbasket of workbaskets" [class.active]="workbasket.workbasketId == selectedId"
type="text" (click)="selectWorkbasket(workbasket.workbasketId)">
<div class="row">
<dl class="col-xs-1">
<dt>
<taskana-icon-type class="vertical-align" [type]="workbasket.type" [selected]="workbasket.workbasketId === selectedId"></taskana-icon-type>
</dt>
</dl>
<dl class="col-xs-10">
<dt>{{workbasket.name}},
@ -21,31 +24,7 @@
</div>
</li>
</ul>
<ul id="wb-pagination" class="pagination vertical-center">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<li>
<a href="">1</a>
</li>
<li>
<a href="">2</a>
</li>
<li>
<a href="">3</a>
</li>
<li>
<a href="">4</a>
</li>
<li>
<a href="">5</a>
</li>
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</div>
</div>
<taskana-pagination [(workbasketsResource)]="workbasketsResource" (changePage)="changePage($event)"></taskana-pagination>
</div>

View File

@ -15,9 +15,17 @@ a > label{
height: 2em;
width: 100%;
}
dd, dt {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
dt > i {
font-weight: normal;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
li > div.row > dl {

View File

@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
import { Observable } from 'rxjs/Observable';
@ -12,38 +12,50 @@ import { WorkbasketSummary } from 'app/models/workbasket-summary';
import { Links } from 'app/models/links';
import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resource';
import { FilterModel } from 'app/models/filter';
import { LinksWorkbasketSummary } from 'app/models/links-workbasket-summary';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { AlertService } from 'app/services/alert/alert.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { OrientationService } from 'app/services/orientation/orientation.service';
import { WorkbasketListComponent } from './workbasket-list.component';
import { WorkbasketListToolbarComponent } from './workbasket-list-toolbar/workbasket-list-toolbar.component';
import { IconTypeComponent } from 'app/shared/type-icon/icon-type.component';
import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { SortComponent } from 'app/shared/sort/sort.component';
import { ImportExportComponent } from '../../../import-export/import-export.component';
import { RemoveNoneTypePipe } from 'app/pipes/removeNoneType/remove-none-type.pipe';
import { MapValuesPipe } from 'app/pipes/mapValues/map-values.pipe';
import {ClassificationService} from '../../../../services/classification/classification.service';
import {WorkbasketDefinitionService} from '../../../../services/workbasket/workbasketDefinition.service';
import {ImportExportComponent} from '../../../import-export/import-export.component';
import { ClassificationService } from '../../../../services/classification/classification.service';
import { WorkbasketDefinitionService } from '../../../../services/workbasket/workbasketDefinition.service';
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'
})
export class DummyDetailComponent {
class DummyDetailComponent {
}
@Component({
selector: 'taskana-pagination',
template: 'dummydetail'
})
class PaginationComponent {
@Input()
workbasketsResource: any;
@Output()
workbasketsResourceChange = new EventEmitter<any>();
@Output() changePage = new EventEmitter<any>();
}
@Component({
selector: 'taskana-filter',
template: ''
})
export class FilterComponent {
class FilterComponent {
}
@ -51,7 +63,7 @@ const workbasketSummaryResource: WorkbasketSummaryResource = new WorkbasketSumma
'workbaskets': new Array<WorkbasketSummary>(
new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1', '', '', 'PERSONAL', '', '', '', ''),
new WorkbasketSummary('2', 'key2', 'NAME2', 'description 2', 'owner 2', '', '', 'GROUP', '', '', '', ''))
}, new Links({ 'href': 'url' }));
}, new LinksWorkbasketSummary({ 'href': 'url' }));
describe('WorkbasketListComponent', () => {
@ -59,9 +71,11 @@ describe('WorkbasketListComponent', () => {
let fixture: ComponentFixture<WorkbasketListComponent>;
let debugElement: any = undefined;
let workbasketService: WorkbasketService;
let workbasketSummarySpy;
const routes: Routes = [
{ path: ':id', component: DummyDetailComponent, outlet: 'detail' }
{ path: ':id', component: DummyDetailComponent, outlet: 'detail' },
{ path: 'workbaskets', component: DummyDetailComponent }
];
@ -69,7 +83,7 @@ describe('WorkbasketListComponent', () => {
TestBed.configureTestingModule({
declarations: [WorkbasketListComponent, DummyDetailComponent, SpinnerComponent, FilterComponent, WorkbasketListToolbarComponent,
RemoveNoneTypePipe, IconTypeComponent, SortComponent, MapValuesPipe, ImportExportComponent],
RemoveNoneTypePipe, IconTypeComponent, SortComponent, PaginationComponent, ImportExportComponent, MapValuesPipe],
imports: [
AngularSvgIconModule,
HttpModule,
@ -77,7 +91,7 @@ describe('WorkbasketListComponent', () => {
RouterTestingModule.withRoutes(routes)
],
providers: [WorkbasketService, ErrorModalService, RequestInProgressService, AlertService,
ClassificationService, WorkbasketDefinitionService]
ClassificationService, WorkbasketDefinitionService, OrientationService]
})
.compileComponents();
@ -86,13 +100,14 @@ describe('WorkbasketListComponent', () => {
component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
workbasketService = TestBed.get(WorkbasketService);
spyOn(workbasketService, 'getWorkBasketsSummary').and.returnValue(Observable.of(workbasketSummaryResource));
workbasketSummarySpy = spyOn(workbasketService, 'getWorkBasketsSummary').and.returnValue(Observable.of(workbasketSummaryResource));
spyOn(workbasketService, 'getSelectedWorkBasket').and.returnValue(Observable.of('2'));
fixture.detectChanges();
}));
afterEach(() => {
fixture.detectChanges()
document.body.removeChild(debugElement);
})
@ -119,11 +134,16 @@ describe('WorkbasketListComponent', () => {
expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(2);
});
it('should have two workbasketsummary rows created with the second one selected.', () => {
it('should have two workbasketsummary rows created with the second one selected.', fakeAsync(() => {
tick(0);
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(2);
expect(debugElement.querySelectorAll('#wb-list-container > li')[0].getAttribute('class')).toBe('list-group-item');
expect(debugElement.querySelectorAll('#wb-list-container > li')[1].getAttribute('class')).toBe('list-group-item active');
});
})
}));
it('should have two workbasketsummary rows created with two different icons: user and users', () => {
expect(debugElement.querySelectorAll('#wb-list-container > li')[0]
@ -145,8 +165,9 @@ describe('WorkbasketListComponent', () => {
const type = 'PERSONAL', name = 'someName', description = 'someDescription', owner = 'someOwner', key = 'someKey';
const filter = new FilterModel(type, name, description, owner, key);
component.performFilter(filter);
expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalledWith(true, 'key', 'asc', undefined,
name, description, undefined, owner, type, undefined, key);
expect(workbasketSummarySpy.calls.all()[1].args).toEqual([true, 'key', 'asc',
undefined, 'someName', 'someDescription', undefined, 'someOwner', 'PERSONAL', undefined, 'someKey', undefined]);
}));

View File

@ -1,4 +1,7 @@
import { Component, OnInit, EventEmitter, OnDestroy, ChangeDetectorRef } from '@angular/core';
import {
Component, OnInit, EventEmitter, OnDestroy,
HostListener, ViewChild, ElementRef, AfterViewChecked
} from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
@ -6,8 +9,10 @@ import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resourc
import { WorkbasketSummary } from 'app/models/workbasket-summary';
import { FilterModel } from 'app/models/filter'
import { SortingModel } from 'app/models/sorting';
import { Orientation } from 'app/models/orientation';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service'
import { OrientationService } from 'app/services/orientation/orientation.service';
@Component({
selector: 'taskana-workbasket-list',
@ -17,37 +22,39 @@ import { WorkbasketService } from 'app/services/workbasket/workbasket.service'
export class WorkbasketListComponent implements OnInit, OnDestroy {
selectedId = '';
workbasketsResource: WorkbasketSummaryResource;
workbaskets: Array<WorkbasketSummary> = [];
requestInProgress = false;
sort: SortingModel = new SortingModel();
filterBy: FilterModel = new FilterModel();
@ViewChild('wbToolbar')
private toolbarElement: ElementRef;
private workBasketSummarySubscription: Subscription;
private workbasketServiceSubscription: Subscription;
private workbasketServiceSavedSubscription: Subscription;
private orientationSubscription: Subscription;
constructor(
private workbasketService: WorkbasketService,
private router: Router,
private route: ActivatedRoute,
private cdRef: ChangeDetectorRef) { }
private orientationService: OrientationService) { }
ngOnInit() {
this.requestInProgress = true;
this.workBasketSummarySubscription = this.workbasketService.getWorkBasketsSummary().subscribe(resultList => {
this.workbaskets = resultList._embedded ? resultList._embedded.workbaskets : [];
this.requestInProgress = false;
});
this.workbasketServiceSubscription = this.workbasketService.getSelectedWorkBasket().subscribe(workbasketIdSelected => {
this.selectedId = workbasketIdSelected;
this.cdRef.detectChanges();
//TODO should be done in a different way.
setTimeout(() => { this.selectedId = workbasketIdSelected; }, 0);
});
this.workbasketServiceSavedSubscription = this.workbasketService.workbasketSavedTriggered().subscribe(value => {
this.performRequest();
});
this.orientationSubscription = this.orientationService.getOrientation().subscribe((orientation: Orientation) => {
this.refreshWorkbasketList();
})
}
selectWorkbasket(id: string) {
@ -69,17 +76,34 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
this.performRequest();
}
changePage(page) {
this.workbasketService.page = page;
this.performRequest();
}
private refreshWorkbasketList() {
const toolbarSize = this.toolbarElement.nativeElement.offsetHeight;
const cardHeight = 75;
const unusedHeight = 140
const totalHeight = window.innerHeight;
const cards = Math.round((totalHeight - (unusedHeight + toolbarSize)) / cardHeight);
this.workbasketService.pageSize = cards;
this.performRequest();
}
private performRequest(): void {
this.requestInProgress = true;
this.workbaskets = [];
this.workbasketServiceSubscription.add(this.workbasketService.getWorkBasketsSummary(true, this.sort.sortBy,
this.sort.sortDirection, undefined,
this.workbasketServiceSubscription = this.workbasketService.getWorkBasketsSummary(
true, this.sort.sortBy, this.sort.sortDirection, undefined,
this.filterBy.name, this.filterBy.description, undefined, this.filterBy.owner,
this.filterBy.type, undefined, this.filterBy.key).subscribe(resultList => {
this.filterBy.type, undefined, this.filterBy.key, undefined)
.subscribe(resultList => {
this.workbasketsResource = resultList;
this.workbaskets = resultList._embedded ? resultList._embedded.workbaskets : [];
this.requestInProgress = false;
this.unSelectWorkbasket();
}));
});
}
@ -90,10 +114,10 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
}
ngOnDestroy() {
this.workBasketSummarySubscription.unsubscribe();
this.workbasketServiceSubscription.unsubscribe();
this.workbasketServiceSavedSubscription.unsubscribe();
if (this.workBasketSummarySubscription) { this.workBasketSummarySubscription.unsubscribe(); }
if (this.workbasketServiceSubscription) { this.workbasketServiceSubscription.unsubscribe(); }
if (this.workbasketServiceSavedSubscription) { this.workbasketServiceSavedSubscription.unsubscribe(); }
if (this.orientationSubscription) { this.orientationSubscription.unsubscribe(); }
}
}

View File

@ -1,4 +1,4 @@
<nav class="navbar navbar-fixed-top">
<nav class="navbar navbar-fixed-top" (window:resize)="onResize($event)">
<div class="navbar show no-border-radius navbar-inverse no-gutter">
<div class="col-xs-3 col-md-4">
<svg-icon class="logo hidden visible-xs visible-sm" src="./assets/icons/logo.svg"></svg-icon>

View File

@ -8,6 +8,7 @@ import { HttpClientModule } from '@angular/common/http';
import { ErrorModalService } from './services/errorModal/error-modal.service';
import { RequestInProgressService } from './services/requestInProgress/request-in-progress.service';
import { AlertService } from './services/alert/alert.service';
import { OrientationService } from './services/orientation/orientation.service';
import { GeneralMessageModalComponent } from './shared/general-message-modal/general-message-modal.component'
import { SpinnerComponent } from './shared/spinner/spinner.component'
@ -32,7 +33,7 @@ describe('AppComponent', () => {
RouterTestingModule.withRoutes(routes),
HttpClientModule
],
providers: [ErrorModalService, RequestInProgressService, AlertService]
providers: [ErrorModalService, RequestInProgressService, AlertService, OrientationService]
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, HostListener } from '@angular/core';
import { environment } from '../environments/environment';
import { Router, NavigationStart } from '@angular/router';
@ -6,6 +6,7 @@ import { ErrorModel } from './models/modal-error';
import { ErrorModalService } from './services/errorModal/error-modal.service';
import { RequestInProgressService } from './services/requestInProgress/request-in-progress.service';
import { OrientationService } from './services/orientation/orientation.service';
@Component({
selector: 'taskana-root',
@ -25,10 +26,16 @@ export class AppComponent implements OnInit {
requestInProgress = false;
@HostListener('window:resize', ['$event'])
onResize(event) {
this.orientationService.onResize();
}
constructor(
private router: Router,
private errorModalService: ErrorModalService,
private requestInProgressService: RequestInProgressService) {
private requestInProgressService: RequestInProgressService,
private orientationService: OrientationService) {
}
ngOnInit() {

View File

@ -31,6 +31,9 @@ import { IconTypeComponent } from './shared/type-icon/icon-type.component';
import { AlertComponent } from './shared/alert/alert.component';
import { SortComponent } from './shared/sort/sort.component';
import { GeneralMessageModalComponent } from './shared/general-message-modal/general-message-modal.component';
import { PaginationComponent } from './administration/workbasket/master/list/pagination/pagination.component';
import { ClassificationListComponent } from './administration/classification/master/list/classification-list.component';
import { ImportExportComponent } from './administration/import-export/import-export.component';
// Shared
import { MasterAndDetailComponent } from './shared/masterAndDetail/master-and-detail.component';
@ -47,8 +50,9 @@ import { AlertService } from './services/alert/alert.service';
import { ErrorModalService } from './services/errorModal/error-modal.service';
import { RequestInProgressService } from './services/requestInProgress/request-in-progress.service';
import { SavingWorkbasketService } from './services/saving-workbaskets/saving-workbaskets.service';
import { OrientationService } from './services/orientation/orientation.service';
import { ClassificationService } from './services/classification/classification.service';
import { WorkbasketDefinitionService } from './services/workbasket/workbasketDefinition.service';
/**
* Pipes
@ -56,10 +60,7 @@ import { SavingWorkbasketService } from './services/saving-workbaskets/saving-wo
import { MapValuesPipe } from './pipes/mapValues/map-values.pipe';
import { RemoveNoneTypePipe } from './pipes/removeNoneType/remove-none-type.pipe';
import { SelectWorkBasketPipe } from './pipes/selectedWorkbasket/seleted-workbasket.pipe';
import {ClassificationListComponent} from './administration/classification/master/list/classification-list.component';
import {ClassificationService} from './services/classification/classification.service';
import {WorkbasketDefinitionService} from './services/workbasket/workbasketDefinition.service';
import {ImportExportComponent} from './administration/import-export/import-export.component';
import { SpreadNumberPipe } from './pipes/spreadNumber/spread-number';
const MODULES = [
BrowserModule,
@ -91,11 +92,13 @@ const DECLARATIONS = [
DistributionTargetsComponent,
SortComponent,
DualListComponent,
PaginationComponent,
ClassificationListComponent,
ImportExportComponent,
MapValuesPipe,
RemoveNoneTypePipe,
SelectWorkBasketPipe,
ClassificationListComponent,
ImportExportComponent
SpreadNumberPipe
];
@NgModule({
@ -115,7 +118,8 @@ const DECLARATIONS = [
AlertService,
ErrorModalService,
RequestInProgressService,
SavingWorkbasketService
SavingWorkbasketService,
OrientationService
],
bootstrap: [AppComponent]
})

View File

@ -0,0 +1,10 @@
import { Links } from './links';
export class LinksWorkbasketSummary extends Links {
constructor(
self = undefined,
distributionTargets = undefined,
accessItems = undefined,
public allWorkbaskets: { 'href': string } = undefined
) { super(self, distributionTargets, accessItems) }
}

View File

@ -0,0 +1,4 @@
export enum Orientation {
landscape = 'landscape',
portrait = 'portrait'
}

View File

@ -0,0 +1,8 @@
export class Page {
constructor(
public size: number = undefined,
public totalElements: number = undefined,
public totalPages: number = undefined,
public number: number = undefined
) { }
}

View File

@ -1,8 +1,14 @@
import { WorkbasketSummary } from './workbasket-summary';
import { Links } from './links';
import { Page } from 'app/models/page';
import { LinksWorkbasketSummary } from './links-workbasket-summary';
export class WorkbasketSummaryResource {
constructor(public _embedded: { 'workbaskets': Array<WorkbasketSummary> } =
{ 'workbaskets': [] }, public _links: Links = null) {
constructor(
public _embedded: {
'workbaskets': Array<WorkbasketSummary>
} = { 'workbaskets': [] },
public _links: LinksWorkbasketSummary = new LinksWorkbasketSummary(),
public page: Page = new Page()
) {
}
}

View File

@ -1,5 +1,6 @@
import { Links } from './links';
import { ICONTYPES } from './type';
import { Page } from './page';
import { Links } from './links';
export class WorkbasketSummary {
constructor(
@ -15,6 +16,7 @@ export class WorkbasketSummary {
public orgLevel2: string = undefined,
public orgLevel3: string = undefined,
public orgLevel4: string = undefined,
public _links: Links = undefined) {
public _links: Links = undefined,
public page: Page = undefined) {
}
}

View File

@ -0,0 +1,33 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'spreadNumber' })
export class SpreadNumberPipe implements PipeTransform {
transform(value: number, currentIndex: number, maxArrayElements: number, maxPageNumber: number): number[] {
const returnArray = new Array();
if (maxPageNumber <= 5) {
for (let i = 0; i < maxPageNumber; i++) {
returnArray.push(i);
}
return returnArray;
}
let minArrayValue = (currentIndex - maxArrayElements / 2);
let maxArrayValue = (currentIndex + maxArrayElements / 2);
let leftDifference = 0;
let rightDifference = 0;
if (minArrayValue < 0) { leftDifference = Math.abs(minArrayValue); minArrayValue = 0; }
if (maxArrayValue > maxPageNumber) {
rightDifference = maxArrayValue - maxPageNumber;
maxArrayValue = maxPageNumber;
}
const minIndex = (minArrayValue - rightDifference) <= 0 ? 0 : minArrayValue - rightDifference;
const maxIndex = (maxArrayValue + leftDifference) > maxPageNumber ? maxPageNumber : maxArrayValue + leftDifference;
for (let i = minIndex; i < maxIndex; i++) {
returnArray.push(i);
}
return returnArray;
}
}

View File

@ -0,0 +1,14 @@
import { TestBed, inject } from '@angular/core/testing';
import { OrientationService } from './orientation.service';
describe('OrientationService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [OrientationService]
});
});
it('should be created', inject([OrientationService], (service: OrientationService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,43 @@
import { Injectable, HostListener } from '@angular/core';
import { Orientation } from 'app/models/orientation';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class OrientationService {
private lock = false;
private currentOrientation = undefined;
public orientation = new BehaviorSubject<Orientation>(this.currentOrientation);
constructor() { }
onResize() {
const orientation = this.detectOrientation();
if (orientation !== this.currentOrientation) {
this.currentOrientation = orientation;
if (!this.lock) {
this.lock = !this.lock;
setTimeout(() => this.changeOrientation(orientation), 250);
}
}
}
getOrientation(): Observable<Orientation> {
return this.orientation.asObservable();
}
changeOrientation(orientation: Orientation) {
this.lock = !this.lock;
if (this.currentOrientation) {
this.orientation.next(orientation);
}
}
private detectOrientation(): Orientation {
if (window.innerHeight > window.innerWidth) {
return Orientation.portrait;
}
return Orientation.landscape;
}
}

View File

@ -27,27 +27,27 @@ describe('WorkbasketService ', () => {
it('should have a valid query parameter expression sortBy=key, order=asc as default', () => {
workbasketService.getWorkBasketsSummary();
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/?sortBy=key&order=asc',
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/?sortBy=key&order=asc&page=1&pagesize=9',
jasmine.any(Object));
});
it('should have a valid query parameter expression with sortBy=name and order=desc', () => {
workbasketService.getWorkBasketsSummary(undefined, 'name', Direction.DESC);
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/?sortBy=name&order=desc',
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/?sortBy=name&order=desc&page=1&pagesize=9',
jasmine.any(Object));
});
it('should have a valid query parameter expression with sortBy=name and order=desc and descLike=some description ', () => {
workbasketService.getWorkBasketsSummary(undefined, 'name', Direction.DESC, undefined, undefined, 'some description');
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/' +
'?sortBy=name&order=desc&descLike=some description', jasmine.any(Object));
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/?sortBy=name&order=desc' +
'&descLike=some description&page=1&pagesize=9', jasmine.any(Object));
});
it('should have a valid query parameter expression with sortBy=key, order=asc, descLike=some description and type=group ', () => {
workbasketService.getWorkBasketsSummary(undefined, 'name', Direction.DESC,
undefined, undefined, 'some description', undefined, undefined, 'group');
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/' +
'?sortBy=name&order=desc&descLike=some description&type=group', jasmine.any(Object));
'?sortBy=name&order=desc&descLike=some description&type=group&page=1&pagesize=9', jasmine.any(Object));
});
});
});

View File

@ -36,6 +36,10 @@ export class WorkbasketService {
// Access
readonly REQUIREDPERMISSION = 'requiredPermission';
// Pagination
readonly PAGE = 'page';
readonly PAGESIZE = 'pagesize';
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
@ -43,6 +47,9 @@ export class WorkbasketService {
})
};
page = 1;
pageSize = 9;
private workbasketSummaryRef: Observable<WorkbasketSummaryResource>;
constructor(private httpClient: HttpClient) { }
@ -60,13 +67,16 @@ export class WorkbasketService {
type: string = undefined,
key: string = undefined,
keyLike: string = undefined,
requiredPermission: string = undefined): Observable<WorkbasketSummaryResource> {
requiredPermission: string = undefined,
allPages: boolean = false): Observable<WorkbasketSummaryResource> {
if (this.workbasketSummaryRef && !forceRequest) {
return this.workbasketSummaryRef;
}
return this.workbasketSummaryRef = this.httpClient.get<WorkbasketSummaryResource>(
`${environment.taskanaRestUrl}/v1/workbaskets/${this.getWorkbasketSummaryQueryParameters(sortBy, order, name,
nameLike, descLike, owner, ownerLike, type, key, keyLike, requiredPermission)}`, this.httpOptions);
`${environment.taskanaRestUrl}/v1/workbaskets/${this.getWorkbasketSummaryQueryParameters(
sortBy, order, name,
nameLike, descLike, owner, ownerLike, type, key, keyLike, requiredPermission,
!allPages ? this.page : undefined, !allPages ? this.pageSize : undefined)}`, this.httpOptions);
}
// GET
@ -135,6 +145,7 @@ export class WorkbasketService {
workbasketSavedTriggered(): Observable<number> {
return this.workBasketSaved.asObservable();
}
// #endregion
// #region private
@ -148,7 +159,9 @@ export class WorkbasketService {
type: string,
key: string,
keyLike: string,
requiredPermission: string): string {
requiredPermission: string,
page: number,
pageSize: number): string {
let query = '?';
query += sortBy ? `${this.SORTBY}=${sortBy}&` : '';
query += order ? `${this.ORDER}=${order}&` : '';
@ -161,6 +174,8 @@ export class WorkbasketService {
query += key ? `${this.KEY}=${key}&` : '';
query += keyLike ? `${this.KEYLIKE}=${keyLike}&` : '';
query += requiredPermission ? `${this.REQUIREDPERMISSION}=${requiredPermission}&` : '';
query += page ? `${this.PAGE}=${page}&` : '';
query += pageSize ? `${this.PAGESIZE}=${pageSize}&` : '';
if (query.lastIndexOf('&') === query.length - 1) {
query = query.slice(0, query.lastIndexOf('&'))

View File

@ -1,14 +1,14 @@
<div id="{{target}}" data-toogle="collapse" class="list-group-search collapse">
<div id="{{target}}" class="list-group-search">
<div class="row">
<div class="dropdown col-xs-2">
<button class="btn btn-default" (click)="toggleDropDown = !toggleDropDown" type="button" id="dropdownMenufilter" data-toggle="dropdown"
<button class="btn btn-default" data-toggle="dropdown" type="button" id="dropdownMenufilter" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true">
<taskana-icon-type [type]="filter.type" class="vertical-align"></taskana-icon-type>
</button>
<ul class="dropdown-menu dropdown-menu-users" [ngClass]="{'hidden': !toggleDropDown}" role="menu">
<ul class="dropdown-menu dropdown-menu-users" role="menu">
<li *ngFor="let type of allTypes | mapValues">
<button type="button" (click)="selectType(type.key); toggleDropDown = !toggleDropDown; search()" class="btn btn-default btn-users-list" data-toggle="tooltip"
<button type="button" (click)="selectType(type.key); search()" class="btn btn-default btn-users-list" data-toggle="tooltip"
[title]="type.value">
<taskana-icon-type class="vertical-align" [type]='type.key'></taskana-icon-type>
</button>
@ -25,7 +25,7 @@
data-toggle="tooltip" title="Clear">
</button>
</div>
<div class="row">
<div class="row padding">
<div class="col-xs-2">
</div>
<div class="col-xs-8">

View File

@ -10,9 +10,12 @@
}
.list-group-search {
padding: 10px 15px;
padding: 0px 15px;
border-top: 1px solid #ddd;
}
.list-group-search {
border-top: none;
}
row.padding {
padding: 1px 0px;
}

View File

@ -122,8 +122,8 @@
}
.footer-space {
max-height: calc(100vh - 140px);
height: calc(100vh - 140px);
max-height: calc(100vh - 130px);
height: calc(100vh - 130px);
overflow-y: auto;
overflow-x: hidden;
}