@@ -43,6 +43,8 @@ import { ManageUsersListComponent } from './modules/user/users/manage-users-list | |||
import { ManageUsersAddComponent } from './modules/user/users/manage-users-add/manage-users-add.component'; | |||
import { NgbPaginationModule, NgbTooltipModule} from "@ng-bootstrap/ng-bootstrap"; | |||
import { PaginatedEntitiesComponent } from './modules/general/paginated-entities/paginated-entities.component'; | |||
import { SortedTableHeaderComponent } from './modules/general/sorted-table-header/sorted-table-header.component'; | |||
import { SortedTableHeaderRowComponent } from './modules/general/sorted-table-header-row/sorted-table-header-row.component'; | |||
@NgModule({ | |||
@@ -65,6 +67,8 @@ import { PaginatedEntitiesComponent } from './modules/general/paginated-entities | |||
ManageUsersListComponent, | |||
ManageUsersAddComponent, | |||
PaginatedEntitiesComponent, | |||
SortedTableHeaderComponent, | |||
SortedTableHeaderRowComponent, | |||
], | |||
imports: [ | |||
BrowserModule, |
@@ -24,5 +24,5 @@ import {Observable} from "rxjs"; | |||
* @typeparam T The type of the entity that is returned from the service | |||
*/ | |||
export interface EntityService<T> { | |||
(searchTerm:string,offset:number,limit:number,orderBy:string,order:string):Observable<PagedResult<T>> | |||
(searchTerm:string,offset:number,limit:number,orderBy:string[],order:string):Observable<PagedResult<T>> | |||
} |
@@ -0,0 +1,21 @@ | |||
/* | |||
* Licensed to the Apache Software Foundation (ASF) under one | |||
* or more contributor license agreements. See the NOTICE file | |||
* distributed with this work for additional information | |||
* regarding copyright ownership. The ASF licenses this file | |||
* to you under the Apache License, Version 2.0 (the | |||
* "License"); you may not use this file except in compliance | |||
* with the License. You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* Unless required by applicable law or agreed to in writing, | |||
* software distributed under the License is distributed on an | |||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |||
* KIND, either express or implied. See the License for the | |||
* specific language governing permissions and limitations | |||
* under the License. | |||
*/ | |||
export interface FieldToggle { | |||
toggleField(fieldName: string[]); | |||
} |
@@ -16,12 +16,11 @@ | |||
* under the License. | |||
*/ | |||
import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core'; | |||
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; | |||
import {merge, Observable, Subject} from "rxjs"; | |||
import {UserInfo} from "../../../model/user-info"; | |||
import {TranslateService} from "@ngx-translate/core"; | |||
import {debounceTime, distinctUntilChanged, map, mergeMap, pluck, share, startWith} from "rxjs/operators"; | |||
import {EntityService} from "../../../model/entity-service"; | |||
import {FieldToggle} from "../../../model/field-toggle"; | |||
/** | |||
@@ -44,95 +43,154 @@ import {EntityService} from "../../../model/entity-service"; | |||
* @typeparam T The type of the retrieved entity elements. | |||
*/ | |||
@Component({ | |||
selector: 'app-paginated-entities', | |||
templateUrl: './paginated-entities.component.html', | |||
styleUrls: ['./paginated-entities.component.scss'] | |||
selector: 'app-paginated-entities', | |||
templateUrl: './paginated-entities.component.html', | |||
styleUrls: ['./paginated-entities.component.scss'] | |||
}) | |||
export class PaginatedEntitiesComponent<T> implements OnInit { | |||
/** | |||
* This must be set, if you use the component. This service retrieves the entity data. | |||
*/ | |||
@Input() service : EntityService<T>; | |||
/** | |||
* The number of elements per page retrieved | |||
*/ | |||
@Input() pageSize = 10; | |||
/** | |||
* Pagination controls | |||
*/ | |||
@Input() pagination = { | |||
maxSize:5, | |||
rotate:true, | |||
boundaryLinks:true, | |||
ellipses:false | |||
} | |||
/** | |||
* The current page that is selected | |||
*/ | |||
page = 1; | |||
/** | |||
* The current search term entered in the search field | |||
*/ | |||
searchTerm: string; | |||
/** | |||
* Event thrown, if the page value changes | |||
*/ | |||
@Output() pageEvent : EventEmitter<number> = new EventEmitter<number>(); | |||
/** | |||
* Event thrown, if the search term changes | |||
*/ | |||
@Output() searchTermEvent: EventEmitter<string> = new EventEmitter<string>(); | |||
/** | |||
* The total number of elements available for the given search term | |||
*/ | |||
total$: Observable<number>; | |||
/** | |||
* The entity items retrieved from the service | |||
*/ | |||
items$: Observable<T[]>; | |||
private pageStream: Subject<number> = new Subject<number>(); | |||
private searchTermStream: Subject<string> = new Subject<string>(); | |||
constructor() { } | |||
ngOnInit(): void { | |||
// We combine the sources for the page and the search input field to a observable 'source' | |||
const pageSource = this.pageStream.pipe(map(pageNumber => { | |||
return {search: this.searchTerm, page: pageNumber} | |||
})); | |||
const searchSource = this.searchTermStream.pipe( | |||
debounceTime(1000), | |||
distinctUntilChanged(), | |||
map(searchTerm => { | |||
this.searchTerm = searchTerm; | |||
return {search: searchTerm, page: 1} | |||
export class PaginatedEntitiesComponent<T> implements OnInit, FieldToggle { | |||
/** | |||
* This must be set, if you use the component. This service retrieves the entity data. | |||
*/ | |||
@Input() service: EntityService<T>; | |||
/** | |||
* The number of elements per page retrieved | |||
*/ | |||
@Input() pageSize = 10; | |||
/** | |||
* Two-Way-Binding attribute for sorting field | |||
*/ | |||
@Input() sortField = []; | |||
/** | |||
* Two-Way Binding attribute for sort order | |||
*/ | |||
@Input() sortOrder = "asc"; | |||
/** | |||
* Pagination controls | |||
*/ | |||
@Input() pagination = { | |||
maxSize: 5, | |||
rotate: true, | |||
boundaryLinks: true, | |||
ellipses: false | |||
} | |||
/** | |||
* The current page that is selected | |||
*/ | |||
page = 1; | |||
/** | |||
* The current search term entered in the search field | |||
*/ | |||
searchTerm: string; | |||
/** | |||
* Event thrown, if the page value changes | |||
*/ | |||
@Output() pageChange: EventEmitter<number> = new EventEmitter<number>(); | |||
/** | |||
* Event thrown, if the search term changes | |||
*/ | |||
@Output() searchTermChange: EventEmitter<string> = new EventEmitter<string>(); | |||
@Output() sortFieldChange: EventEmitter<string[]> = new EventEmitter<string[]>(); | |||
@Output() sortOrderChange: EventEmitter<string> = new EventEmitter<string>(); | |||
/** | |||
* The total number of elements available for the given search term | |||
*/ | |||
total$: Observable<number>; | |||
/** | |||
* The entity items retrieved from the service | |||
*/ | |||
items$: Observable<T[]>; | |||
private pageStream: Subject<number> = new Subject<number>(); | |||
private searchTermStream: Subject<string> = new Subject<string>(); | |||
constructor() { | |||
} | |||
ngOnInit(): void { | |||
// We combine the sources for the page and the search input field to a observable 'source' | |||
const pageSource = this.pageStream.pipe(map(pageNumber => { | |||
return {search: this.searchTerm, page: pageNumber} | |||
})); | |||
const source = merge(pageSource, searchSource).pipe( | |||
startWith({search: this.searchTerm, page: this.page}), | |||
mergeMap((params: { search: string, page: number }) => { | |||
return this.service(params.search, (params.page - 1) * this.pageSize, this.pageSize, "", "asc"); | |||
}),share()); | |||
this.total$ = source.pipe(pluck('pagination','totalCount')); | |||
this.items$ = source.pipe(pluck('data')); | |||
} | |||
search(terms: string) { | |||
// console.log("Keystroke " + terms); | |||
this.searchTermEvent.emit(terms); | |||
this.searchTermStream.next(terms) | |||
} | |||
changePage(pageNumber : number) { | |||
// console.log("Page change " +pageNumber); | |||
this.pageEvent.emit(pageNumber); | |||
this.pageStream.next(pageNumber); | |||
} | |||
const searchSource = this.searchTermStream.pipe( | |||
debounceTime(1000), | |||
distinctUntilChanged(), | |||
map(searchTerm => { | |||
this.searchTerm = searchTerm; | |||
return {search: searchTerm, page: 1} | |||
})); | |||
const source = merge(pageSource, searchSource).pipe( | |||
startWith({search: this.searchTerm, page: this.page}), | |||
mergeMap((params: { search: string, page: number }) => { | |||
return this.service(params.search, (params.page - 1) * this.pageSize, this.pageSize, this.sortField, this.sortOrder); | |||
}), share()); | |||
this.total$ = source.pipe(pluck('pagination', 'totalCount')); | |||
this.items$ = source.pipe(pluck('data')); | |||
} | |||
search(terms: string) { | |||
// console.log("Keystroke " + terms); | |||
this.searchTermChange.emit(terms); | |||
this.searchTermStream.next(terms) | |||
} | |||
changePage(pageNumber: number) { | |||
// console.log("Page change " +pageNumber); | |||
this.pageChange.emit(pageNumber); | |||
this.pageStream.next(pageNumber); | |||
} | |||
private compareArrays(a1: string[], a2: string[]) { | |||
let i = a1.length; | |||
while (i--) { | |||
if (a1[i] !== a2[i]) return false; | |||
} | |||
return true | |||
} | |||
toggleSortField(fieldName: string) { | |||
this.toggleField([fieldName]); | |||
} | |||
toggleField(fieldArray: string[]) { | |||
console.log("Changing sort field " + fieldArray); | |||
let sortOrderChanged: boolean = false; | |||
let sortFieldChanged: boolean = false; | |||
if (!this.compareArrays(this.sortField, fieldArray)) { | |||
console.log("Fields differ: " + this.sortField + " - " + fieldArray); | |||
this.sortField = fieldArray; | |||
if (this.sortOrder != 'asc') { | |||
this.sortOrder = 'asc'; | |||
sortOrderChanged = true; | |||
} | |||
sortFieldChanged = true; | |||
} else { | |||
if (this.sortOrder == "asc") { | |||
this.sortOrder = "desc"; | |||
} else { | |||
this.sortOrder = "asc"; | |||
} | |||
console.log("Toggled sort order: " + this.sortOrder); | |||
sortOrderChanged = true; | |||
} | |||
if (sortOrderChanged) { | |||
console.log("Sort order changed: "+this.sortOrder) | |||
this.sortOrderChange.emit(this.sortOrder); | |||
} | |||
if (sortFieldChanged) { | |||
this.sortFieldChange.emit(this.sortField); | |||
} | |||
if (sortFieldChanged || sortOrderChanged) { | |||
this.changePage(1); | |||
} | |||
} | |||
} |
@@ -0,0 +1,19 @@ | |||
<!-- | |||
~ Licensed to the Apache Software Foundation (ASF) under one | |||
~ or more contributor license agreements. See the NOTICE file | |||
~ distributed with this work for additional information | |||
~ regarding copyright ownership. The ASF licenses this file | |||
~ to you under the Apache License, Version 2.0 (the | |||
~ "License"); you may not use this file except in compliance | |||
~ with the License. You may obtain a copy of the License at | |||
~ | |||
~ http://www.apache.org/licenses/LICENSE-2.0 | |||
~ Unless required by applicable law or agreed to in writing, | |||
~ software distributed under the License is distributed on an | |||
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |||
~ KIND, either express or implied. See the License for the | |||
~ specific language governing permissions and limitations | |||
~ under the License. | |||
--> | |||
<ng-content ></ng-content> |
@@ -0,0 +1,18 @@ | |||
/*! | |||
* Licensed to the Apache Software Foundation (ASF) under one | |||
* or more contributor license agreements. See the NOTICE file | |||
* distributed with this work for additional information | |||
* regarding copyright ownership. The ASF licenses this file | |||
* to you under the Apache License, Version 2.0 (the | |||
* "License"); you may not use this file except in compliance | |||
* with the License. You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* Unless required by applicable law or agreed to in writing, | |||
* software distributed under the License is distributed on an | |||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |||
* KIND, either express or implied. See the License for the | |||
* specific language governing permissions and limitations | |||
* under the License. | |||
*/ | |||
@@ -0,0 +1,43 @@ | |||
/* | |||
* Licensed to the Apache Software Foundation (ASF) under one | |||
* or more contributor license agreements. See the NOTICE file | |||
* distributed with this work for additional information | |||
* regarding copyright ownership. The ASF licenses this file | |||
* to you under the Apache License, Version 2.0 (the | |||
* "License"); you may not use this file except in compliance | |||
* with the License. You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* Unless required by applicable law or agreed to in writing, | |||
* software distributed under the License is distributed on an | |||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |||
* KIND, either express or implied. See the License for the | |||
* specific language governing permissions and limitations | |||
* under the License. | |||
*/ | |||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||
import { SortedTableHeaderRowComponent } from './sorted-table-header-row.component'; | |||
describe('SortedTableHeaderRowComponent', () => { | |||
let component: SortedTableHeaderRowComponent; | |||
let fixture: ComponentFixture<SortedTableHeaderRowComponent>; | |||
beforeEach(async () => { | |||
await TestBed.configureTestingModule({ | |||
declarations: [ SortedTableHeaderRowComponent ] | |||
}) | |||
.compileComponents(); | |||
}); | |||
beforeEach(() => { | |||
fixture = TestBed.createComponent(SortedTableHeaderRowComponent); | |||
component = fixture.componentInstance; | |||
fixture.detectChanges(); | |||
}); | |||
it('should create', () => { | |||
expect(component).toBeTruthy(); | |||
}); | |||
}); |
@@ -0,0 +1,79 @@ | |||
/* | |||
* Licensed to the Apache Software Foundation (ASF) under one | |||
* or more contributor license agreements. See the NOTICE file | |||
* distributed with this work for additional information | |||
* regarding copyright ownership. The ASF licenses this file | |||
* to you under the Apache License, Version 2.0 (the | |||
* "License"); you may not use this file except in compliance | |||
* with the License. You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* Unless required by applicable law or agreed to in writing, | |||
* software distributed under the License is distributed on an | |||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |||
* KIND, either express or implied. See the License for the | |||
* specific language governing permissions and limitations | |||
* under the License. | |||
*/ | |||
import { | |||
AfterViewChecked, AfterViewInit, | |||
Component, | |||
Input, | |||
OnInit, | |||
QueryList, | |||
TemplateRef, | |||
ViewChild, | |||
ViewChildren, | |||
ViewContainerRef, | |||
ContentChildren, AfterContentInit, AfterContentChecked, ChangeDetectorRef, Output, EventEmitter | |||
} from '@angular/core'; | |||
import {FieldToggle} from "../../../model/field-toggle"; | |||
import {SortedTableHeaderComponent} from "../sorted-table-header/sorted-table-header.component"; | |||
import { delay, startWith } from 'rxjs/operators'; | |||
@Component({ | |||
selector: 'tr[sorted]', | |||
templateUrl: './sorted-table-header-row.component.html', | |||
styleUrls: ['./sorted-table-header-row.component.scss'] | |||
}) | |||
export class SortedTableHeaderRowComponent implements OnInit, AfterViewInit, AfterContentInit, AfterContentChecked { | |||
@Input() sortFieldEmitter: EventEmitter<string[]>; | |||
@Input() sortOrderEmitter: EventEmitter<string>; | |||
@Input() sortFields: string[]; | |||
@Input() sortOrder: string; | |||
@Input() toggleObserver: FieldToggle; | |||
@ContentChildren(SortedTableHeaderComponent, { descendants: true }) contentChilds: QueryList<SortedTableHeaderComponent>; | |||
constructor(private readonly viewContainer: ViewContainerRef) { | |||
} | |||
ngAfterContentChecked(): void { | |||
} | |||
ngAfterContentInit(): void { | |||
this.contentChilds.changes.pipe(startWith(this.contentChilds), delay(0)).subscribe(() => { | |||
this.contentChilds.forEach((colComponent, index) => { | |||
console.log("Children " + colComponent); | |||
colComponent.registerSortFieldEmitter(this.sortFieldEmitter); | |||
colComponent.registerSortOrderEmitter(this.sortOrderEmitter); | |||
colComponent.sortOrder = this.sortOrder; | |||
colComponent.currentFieldArray = this.sortFields; | |||
colComponent.toggleObserver = this.toggleObserver; | |||
}); | |||
}); | |||
} | |||
ngOnInit(): void { | |||
} | |||
ngAfterViewInit(): void { | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
<!-- | |||
~ Licensed to the Apache Software Foundation (ASF) under one | |||
~ or more contributor license agreements. See the NOTICE file | |||
~ distributed with this work for additional information | |||
~ regarding copyright ownership. The ASF licenses this file | |||
~ to you under the Apache License, Version 2.0 (the | |||
~ "License"); you may not use this file except in compliance | |||
~ with the License. You may obtain a copy of the License at | |||
~ | |||
~ http://www.apache.org/licenses/LICENSE-2.0 | |||
~ Unless required by applicable law or agreed to in writing, | |||
~ software distributed under the License is distributed on an | |||
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |||
~ KIND, either express or implied. See the License for the | |||
~ specific language governing permissions and limitations | |||
~ under the License. | |||
--> | |||
<ng-template #content> | |||
<th scope="col" (click)="toggleSortField()"> | |||
<ng-container *ngIf="contentText!=null && contentText!=''" >{{contentText | translate}}</ng-container> | |||
<ng-content></ng-content> | |||
<span *ngIf="sortCheck()" class="fas" [ngClass]="isAscending()?'fa-sort-alpha-up':'fa-sort-alpha-down'"></span></th> | |||
</ng-template> |
@@ -0,0 +1,18 @@ | |||
/*! | |||
* Licensed to the Apache Software Foundation (ASF) under one | |||
* or more contributor license agreements. See the NOTICE file | |||
* distributed with this work for additional information | |||
* regarding copyright ownership. The ASF licenses this file | |||
* to you under the Apache License, Version 2.0 (the | |||
* "License"); you may not use this file except in compliance | |||
* with the License. You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* Unless required by applicable law or agreed to in writing, | |||
* software distributed under the License is distributed on an | |||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |||
* KIND, either express or implied. See the License for the | |||
* specific language governing permissions and limitations | |||
* under the License. | |||
*/ | |||
@@ -0,0 +1,43 @@ | |||
/* | |||
* Licensed to the Apache Software Foundation (ASF) under one | |||
* or more contributor license agreements. See the NOTICE file | |||
* distributed with this work for additional information | |||
* regarding copyright ownership. The ASF licenses this file | |||
* to you under the Apache License, Version 2.0 (the | |||
* "License"); you may not use this file except in compliance | |||
* with the License. You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* Unless required by applicable law or agreed to in writing, | |||
* software distributed under the License is distributed on an | |||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |||
* KIND, either express or implied. See the License for the | |||
* specific language governing permissions and limitations | |||
* under the License. | |||
*/ | |||
import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||
import { SortedTableHeaderComponent } from './sorted-table-header.component'; | |||
describe('SortedTableHeaderComponent', () => { | |||
let component: SortedTableHeaderComponent; | |||
let fixture: ComponentFixture<SortedTableHeaderComponent>; | |||
beforeEach(async () => { | |||
await TestBed.configureTestingModule({ | |||
declarations: [ SortedTableHeaderComponent ] | |||
}) | |||
.compileComponents(); | |||
}); | |||
beforeEach(() => { | |||
fixture = TestBed.createComponent(SortedTableHeaderComponent); | |||
component = fixture.componentInstance; | |||
fixture.detectChanges(); | |||
}); | |||
it('should create', () => { | |||
expect(component).toBeTruthy(); | |||
}); | |||
}); |
@@ -0,0 +1,90 @@ | |||
/* | |||
* Licensed to the Apache Software Foundation (ASF) under one | |||
* or more contributor license agreements. See the NOTICE file | |||
* distributed with this work for additional information | |||
* regarding copyright ownership. The ASF licenses this file | |||
* to you under the Apache License, Version 2.0 (the | |||
* "License"); you may not use this file except in compliance | |||
* with the License. You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* Unless required by applicable law or agreed to in writing, | |||
* software distributed under the License is distributed on an | |||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |||
* KIND, either express or implied. See the License for the | |||
* specific language governing permissions and limitations | |||
* under the License. | |||
*/ | |||
import { | |||
Component, | |||
OnInit, | |||
Input, | |||
ViewContainerRef, | |||
ViewChild, | |||
TemplateRef, | |||
ChangeDetectorRef, | |||
AfterViewChecked, EventEmitter, Output | |||
} from '@angular/core'; | |||
import {FieldToggle} from "../../../model/field-toggle"; | |||
import { ChangeDetectionStrategy } from '@angular/core'; | |||
@Component({ | |||
host: { style: 'display:none' }, | |||
selector: 'app-th-sorted', | |||
templateUrl: './sorted-table-header.component.html', | |||
styleUrls: ['./sorted-table-header.component.scss'] | |||
}) | |||
export class SortedTableHeaderComponent implements OnInit, AfterViewChecked { | |||
@Input() fieldArray: string[]; | |||
currentFieldArray: string[]; | |||
sortOrder: string; | |||
toggleObserver: FieldToggle; | |||
@Input() contentText:string; | |||
@ViewChild('content', { static: true }) content: TemplateRef<{}>; | |||
constructor(private readonly viewContainer: ViewContainerRef) { } | |||
ngOnInit(): void { | |||
this.viewContainer.createEmbeddedView(this.content); | |||
} | |||
ngAfterViewChecked() { | |||
} | |||
toggleSortField() { | |||
console.log("Toggling sort field " + this.fieldArray); | |||
this.toggleObserver.toggleField(this.fieldArray); | |||
} | |||
private compareArrays(a1: string[], a2: string[]) { | |||
if (a1==null || a2==null) { | |||
return false; | |||
} | |||
let i = a1.length; | |||
while (i--) { | |||
if (a1[i] !== a2[i]) return false; | |||
} | |||
return true | |||
} | |||
sortCheck() { | |||
return this.compareArrays(this.fieldArray, this.currentFieldArray); | |||
} | |||
isAscending() :boolean { | |||
console.log("Is ascending: " + this.sortOrder); | |||
return this.sortOrder == 'asc'; | |||
} | |||
registerSortOrderEmitter(emitter : EventEmitter<string>) { | |||
emitter.subscribe((field) => this.sortOrder = field); | |||
} | |||
registerSortFieldEmitter(emitter : EventEmitter<string[]>) { | |||
emitter.subscribe((field) => this.currentFieldArray = field); | |||
} | |||
} |
@@ -17,24 +17,28 @@ | |||
~ under the License. | |||
--> | |||
<app-paginated-entities [service]="service" pageSize="5"We #parent> | |||
<app-paginated-entities [service]="service" pageSize="5" [(sortField)]="sortField" [(sortOrder)]="sortOrder" | |||
#parent> | |||
<table class="table table-striped table-bordered"> | |||
<thead class="thead-light"> | |||
<tr> | |||
<th scope="col">{{'users.list.table.head.user_id' | translate}}</th> | |||
<th scope="col">{{'users.list.table.head.fullName' | translate}}</th> | |||
<th scope="col">{{'users.list.table.head.email' | translate}}</th> | |||
<th scope="col"><span class="fas fa-check" placement="top" | |||
[ngbTooltip]="heads.validated" [attr.aria-label]="heads.validated"></span> | |||
</th> | |||
<tr sorted [sortFieldEmitter]="parent.sortFieldChange" [sortOrder]="sortOrder" [sortFields]="sortField" | |||
[sortOrderEmitter]="parent.sortOrderChange" [toggleObserver]="parent" > | |||
<app-th-sorted [fieldArray]="['user_id']" contentText="users.list.table.head.user_id"></app-th-sorted> | |||
<app-th-sorted contentText="users.list.table.head.fullName" [fieldArray]="['fullName']" ></app-th-sorted> | |||
<app-th-sorted contentText="users.list.table.head.email" [fieldArray]="['email']"></app-th-sorted> | |||
<app-th-sorted [fieldArray]="['validated','user_id']"> | |||
<span class="fas fa-check" placement="top" | |||
[ngbTooltip]="heads.validated" [attr.aria-label]="heads.validated"> | |||
</span> | |||
</app-th-sorted> | |||
<th scope="col"><span class="fas fa-lock" placement="top" | |||
[ngbTooltip]="heads.locked" [attr.aria-label]="heads.locked"></span></th> | |||
<th scope="col"><span class="fa fa-chevron-circle-right" placement="top" | |||
[ngbTooltip]="heads.pwchange" [attr.aria-label]="heads.pwchange"></span> | |||
</th> | |||
<th scope="col">{{'users.list.table.head.lastLogin' | translate}}</th> | |||
<th scope="col">{{'users.list.table.head.created' | translate}}</th> | |||
<app-th-sorted contentText="users.list.table.head.created" [fieldArray]="['created']" ></app-th-sorted> | |||
<th scope="col">{{'users.list.table.head.lastPwChange' | translate}}</th> | |||
</tr> | |||
</thead> |
@@ -25,7 +25,6 @@ import {EntityService} from "../../../../model/entity-service"; | |||
import {Observable, of} from "rxjs"; | |||
import {PagedResult} from "../../../../model/paged-result"; | |||
@Component({ | |||
selector: 'app-manage-users-list', | |||
templateUrl: './manage-users-list.component.html', | |||
@@ -35,10 +34,13 @@ export class ManageUsersListComponent implements OnInit { | |||
@Input() heads: any; | |||
service : EntityService<UserInfo>; | |||
sortField = ["user_id"]; | |||
sortOrder = "asc"; | |||
constructor(private translator: TranslateService, private userService : UserService) { | |||
this.service = function (searchTerm: string, offset: number, limit: number, orderBy: string, order: string) : Observable<PagedResult<UserInfo>> { | |||
this.service = function (searchTerm: string, offset: number, limit: number, orderBy: string[], order: string) : Observable<PagedResult<UserInfo>> { | |||
console.log("Retrieving data " + searchTerm + "," + offset + "," + limit + "," + orderBy + "," + order); | |||
return userService.query(searchTerm, offset, limit, orderBy, order); | |||
} | |||
@@ -53,9 +55,29 @@ export class ManageUsersListComponent implements OnInit { | |||
this.heads[suffix] = this.translator.instant('users.list.table.head.' + suffix); | |||
} | |||
}); | |||
} | |||
changeSortOrder(order:string) { | |||
if (this.sortOrder!=order) { | |||
this.sortOrder = order; | |||
} | |||
} | |||
private compareArrays(a1: string[], a2: string[]) { | |||
let i = a1.length; | |||
while (i--) { | |||
if (a1[i] !== a2[i]) return false; | |||
} | |||
return true | |||
} | |||
sortCheck(fieldArray:string[]) { | |||
return this.compareArrays(this.sortField, fieldArray); | |||
} | |||
isAscending() : boolean { | |||
return this.sortOrder == "asc"; | |||
} | |||
@@ -258,12 +258,15 @@ export class UserService implements OnInit, OnDestroy { | |||
this.authenticated = false; | |||
} | |||
public query(searchTerm : string, offset : number = 0, limit : number = 10, orderBy : string = 'user_id', order: string = 'asc') : Observable<PagedResult<UserInfo>> { | |||
public query(searchTerm : string, offset : number = 0, limit : number = 10, orderBy : string[] = ['user_id'], order: string = 'asc') : Observable<PagedResult<UserInfo>> { | |||
console.log("getUserList " + searchTerm + "," + offset + "," + limit + "," + orderBy + "," + order); | |||
if (searchTerm==null) { | |||
searchTerm="" | |||
} | |||
return this.rest.executeRestCall<PagedResult<UserInfo>>("get", "redback", "users", {'q':searchTerm, 'offset':offset,'limit':limit}); | |||
if (orderBy==null || orderBy.length==0) { | |||
orderBy = ['user_id']; | |||
} | |||
return this.rest.executeRestCall<PagedResult<UserInfo>>("get", "redback", "users", {'q':searchTerm, 'offset':offset,'limit':limit,'orderBy':orderBy,'order':order}); | |||
} | |||
} |