feat: add server-side pagination, sort and search to groups component (#507)

- Add server-side pagination, sort and search to groups component
- Refactor code to conform to current guidelines

See: #234
This commit is contained in:
punwai
2020-05-07 03:08:41 +05:30
committed by GitHub
parent 3136cd3b30
commit 4e95d57081
7 changed files with 281 additions and 123 deletions
+8 -1
View File
@@ -1,3 +1,4 @@
/** Angular Imports */
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
@@ -7,11 +8,12 @@ import { Route } from '../core/route/route.service';
/** Translation Imports */ /** Translation Imports */
import { extract } from '../core/i18n/i18n.service'; import { extract } from '../core/i18n/i18n.service';
/** Custom Imports */
import { GroupsComponent } from './groups.component'; import { GroupsComponent } from './groups.component';
/** Groups Routes */
const routes: Routes = [ const routes: Routes = [
Route.withShell([ Route.withShell([
{ {
path: 'groups', path: 'groups',
component: GroupsComponent, component: GroupsComponent,
@@ -20,6 +22,11 @@ const routes: Routes = [
]) ])
]; ];
/**
* Groups Routing Module
*
* Configures the groups routes.
*/
@NgModule({ @NgModule({
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule], exports: [RouterModule],
+54 -50
View File
@@ -1,51 +1,55 @@
<div class="container m-t-40 m-b-40"> <mat-card class="container">
<div fxLayout="row" fxLayoutAlign="start center">
<div class="search-box">
<mat-form-field>
<input matInput placeholder="Search by Name" [formControl]="name">
</mat-form-field>
<mat-checkbox #showClosedGroups labelPosition="after" (change)="changeShowClosedGroups()">Show Closed Groups</mat-checkbox>
</div>
<div class="action-button" fxLayoutGap="25px">
<button mat-raised-button color="primary">
<fa-icon icon="plus"></fa-icon>&nbsp;&nbsp;Create Group
</button>
</div>
</div>
<mat-form-field class="filter"> <table mat-table [dataSource]="dataSource" matSort>
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</mat-form-field> <ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th>
<div class="mat-elevation-z8"> <td mat-cell *matCellDef="let group"> {{ group.name }} </td>
<table mat-table [dataSource]="dataSource" matSort> </ng-container>
<!-- Name Column --> <ng-container matColumnDef="accountNo">
<ng-container matColumnDef="name"> <th mat-header-cell *matHeaderCellDef> Account # </th>
<th mat-header-cell *matHeaderCellDef mat-sort-header='name'> Name </th> <td mat-cell *matCellDef="let group"> {{ group.accountNo }} </td>
<td mat-cell *matCellDef="let row"> {{row?.name}} </td> </ng-container>
</ng-container>
<ng-container matColumnDef="externalId">
<!-- Name Column --> <th mat-header-cell *matHeaderCellDef> ExternalID </th>
<ng-container matColumnDef="accountno"> <td mat-cell *matCellDef="let group"> {{ group.externalId }} </td>
<th mat-header-cell *matHeaderCellDef mat-sort-header='accountno'> Account # </th> </ng-container>
<td mat-cell *matCellDef="let row"> {{row?.accountNo}} </td>
</ng-container> <ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef> Status </th>
<!-- Account no Column --> <td mat-cell *matCellDef="let row">
<ng-container matColumnDef="externalid"> <div [ngClass]="row.active|statusLookup">
<th mat-header-cell *matHeaderCellDef mat-sort-header='externalid'> ExternalID </th> <fa-icon icon="stop"></fa-icon>
<td mat-cell *matCellDef="let row"> {{row?.externalId}} </td> </div>
</ng-container> </td>
</ng-container>
<!-- External ID Column -->
<ng-container matColumnDef="status"> <ng-container matColumnDef="officeName">
<th mat-header-cell *matHeaderCellDef mat-sort-header='status'> Status </th> <th mat-header-cell *matHeaderCellDef mat-sort-header> Office Name </th>
<td mat-cell *matCellDef="let row"> <div [className]="row.active === true ? 'active' : 'inactive'"> <td mat-cell *matCellDef="let group"> {{ group.officeName }} </td>
<i class="fa fa-circle fa-2x"> </i> </ng-container>
</div> </td>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
</ng-container> <tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="[ ]"></tr>
<!-- Status Column --> </table>
<ng-container matColumnDef="office">
<th mat-header-cell *matHeaderCellDef mat-sort-header='office'> Office </th> <mat-paginator [length]="dataSource?.records$ | async" [pageSize]="10" [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
<td mat-cell *matCellDef="let row"> {{row?.officeName}} </td>
</ng-container> </mat-card>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [routerLink]="[ ]">
</tr>
</table>
<mat-paginator [pageSizeOptions]="[10, 25, 100]"></mat-paginator>
</div>
+8 -25
View File
@@ -1,27 +1,10 @@
.container {
.action-button{
margin-left: auto;
}
table { .search-button{
width: 100%; min-width: 30px;
margin: 4px;
} }
}
.mat-form-field {
font-size: 14px;
width: 100%;
}
td, th {
width: 15%;
}
.filter{
width: 50%;
}
.inactive {
color: red;
}
.active {
color: green;
}
+94 -25
View File
@@ -1,44 +1,113 @@
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; /** Angular Imports */
import {MatPaginator, MatSort, MatTableDataSource} from '@angular/material'; import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
import { MatPaginator, MatSort, MatCheckbox } from '@angular/material';
import { FormControl } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
/** rxjs Imports */
import { merge } from 'rxjs';
import { tap, startWith, map, distinctUntilChanged, debounceTime} from 'rxjs/operators';
/** Custom Services */
import { GroupsService } from './groups.service'; import { GroupsService } from './groups.service';
import {DataSource} from '@angular/cdk/collections';
import { Observable } from 'rxjs'; /** Custom Data Source */
import { GroupsDataSource } from './groups.datasource';
/**
* Groups component.
*/
@Component({ @Component({
selector: 'mifosx-app-groups', selector: 'mifosx-app-groups',
templateUrl: './groups.component.html', templateUrl: './groups.component.html',
styleUrls: ['./groups.component.scss'] styleUrls: ['./groups.component.scss']
}) })
export class GroupsComponent implements OnInit { export class GroupsComponent implements OnInit, AfterViewInit {
@ViewChild('showClosedGroups') showClosedGroups: MatCheckbox;
private ELEMENT_DATA: any = undefined; /** Name form control. */
displayedColumns = ['name', 'accountno', 'externalid', 'status', 'office']; name = new FormControl();
dataSource = new MatTableDataSource(); /** Columns to be displayed in groups table. */
displayedColumns = ['name', 'accountNo', 'externalId', 'status', 'officeName'];
/** Data source for groups table. */
dataSource: GroupsDataSource;
/** Groups filter. */
filterGroupsBy = [
{
type: 'name',
value: ''
}
];
/** Paginator for groups table. */
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatPaginator) paginator: MatPaginator;
/** Sorter for groups table. */
@ViewChild(MatSort) sort: MatSort; @ViewChild(MatSort) sort: MatSort;
constructor(private groupService: GroupsService) { }
constructor(private groupsService: GroupsService) { }
ngOnInit() { ngOnInit() {
this.groupService.getGroups() this.getGroups();
.subscribe( }
(res => {
console.log(res); /**
// console.log(res.active); * Subscribes to all search filters:
res.active = !!res.active; * Name
this.dataSource = new MatTableDataSource(res); * sort change and page change.
this.dataSource.paginator = this.paginator; */
this.dataSource.sort = this.sort; ngAfterViewInit() {
this.name.valueChanges
.pipe(
debounceTime(500),
distinctUntilChanged(),
tap((filterValue) => {
this.applyFilter(filterValue, 'name');
}) })
); )
.subscribe();
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
merge(this.sort.sortChange, this.paginator.page)
.pipe(
tap(() => this.loadGroupsPage())
)
.subscribe();
} }
changeShowClosedGroups() {
this.loadGroupsPage();
}
applyFilter(filterValue: string) { /**
filterValue = filterValue.trim(); // Remove whitespace * Loads a page of groups.
filterValue = filterValue.toLowerCase(); // Datasource defaults to lowercase matches */
this.dataSource.filter = filterValue; loadGroupsPage() {
if (this.dataSource.paginator) { if (!this.sort.direction) {
this.dataSource.paginator.firstPage(); delete this.sort.active;
} }
this.dataSource.getGroups(this.filterGroupsBy, this.sort.active, this.sort.direction, this.paginator.pageIndex, this.paginator.pageSize, !this.showClosedGroups.checked);
} }
/**
* Filters data in groups table based on passed value and poperty.
* @param {string} filterValue Value to filter data.
* @param {string} property Property to filter data by.
*/
applyFilter(filterValue: string, property: string) {
this.paginator.pageIndex = 0;
const findIndex = this.filterGroupsBy.findIndex(filter => filter.type === property);
this.filterGroupsBy[findIndex].value = filterValue;
this.loadGroupsPage();
}
/**
* Initializes the data source for groups table and loads the first page.
*/
getGroups() {
this.dataSource = new GroupsDataSource(this.groupsService);
this.dataSource.getGroups(this.filterGroupsBy, this.sort.active, this.sort.direction, this.paginator.pageIndex, this.paginator.pageSize);
}
} }
+61
View File
@@ -0,0 +1,61 @@
/** Angular Imports */
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
/** rxjs Imports */
import { Observable, BehaviorSubject } from 'rxjs';
/** Custom Services */
import { GroupsService } from './groups.service';
/**
* Groups custom data source to implement server side filtering, pagination and sorting.
*/
export class GroupsDataSource implements DataSource<any> {
/** groups behavior subject to represent loaded groups page. */
private groupsSubject = new BehaviorSubject<any[]>([]);
/** Records subject to represent total number of filtered groups records. */
private recordsSubject = new BehaviorSubject<number>(0);
/** Records observable which can be subscribed to get the value of total number of filtered groups records. */
public records$ = this.recordsSubject.asObservable();
/**
* @param {GroupsService} groupsService Groups Service
*/
constructor(private groupsService: GroupsService) { }
/**
* Gets groups on the basis of provided parameters and emits the value.
* @param {any} filterBy Properties by which entries should be filtered.
* @param {string} orderBy Property by which entries should be sorted.
* @param {string} sortOrder Sort order: ascending or descending.
* @param {number} pageIndex Page number.
* @param {number} limit Number of entries within the page.
* @param {boolean} groupActive Specify whether to only filter active groups.
*/
getGroups(filterBy: any, orderBy: string = '', sortOrder: string = '', pageIndex: number = 0, limit: number = 10, groupActive: boolean = true) {
this.groupsSubject.next([]);
this.groupsService.getGroups(filterBy, orderBy, sortOrder, pageIndex * limit, limit)
.subscribe((groups: any) => {
groups.pageItems = (groupActive) ? (groups.pageItems.filter((group: any) => group.active)) : groups.pageItems;
this.recordsSubject.next(groups.totalFilteredRecords);
this.groupsSubject.next(groups.pageItems);
});
}
/**
* @param {CollectionViewer} collectionViewer
*/
connect(collectionViewer: CollectionViewer): Observable<any[]> {
return this.groupsSubject.asObservable();
}
/**
* @param {CollectionViewer} collectionViewer
*/
disconnect(collectionViewer: CollectionViewer): void {
this.groupsSubject.complete();
this.recordsSubject.complete();
}
}
+21 -15
View File
@@ -1,23 +1,29 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; /** Angular Imports */
import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core';
import { CoreModule } from '../core/core.module'; import { DatePipe } from '@angular/common';
import { MatTableModule } from '@angular/material/table';
import { FlexLayoutModule } from '@angular/flex-layout';
/** Custom Modules */
import { GroupsRoutingModule } from './groups-routing.module';
import { SharedModule } from 'app/shared/shared.module';
import { PipesModule } from '../pipes/pipes.module';
/** Custom Components */
import { GroupsComponent } from './groups.component'; import { GroupsComponent } from './groups.component';
import { GroupsRoutingModule } from 'app/groups/groups-routing.module';
import { GroupsService } from './groups.service'; /**
* Groups Module
*
* All components related to Groups should be declared here.
*/
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule, SharedModule,
GroupsRoutingModule, GroupsRoutingModule,
CoreModule, PipesModule
MatTableModule,
FlexLayoutModule
], ],
declarations: [GroupsComponent], declarations: [
schemas: [ GroupsComponent
CUSTOM_ELEMENTS_SCHEMA ],
] providers: [DatePipe]
}) })
export class GroupsModule { } export class GroupsModule { }
+35 -7
View File
@@ -1,17 +1,45 @@
/** Angular Imports */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http'; import { HttpClient, HttpParams } from '@angular/common/http';
/** rxjs Imports */
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
/**
* Groups service.
*/
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class GroupsService { export class GroupsService {
constructor(private http: HttpClient) { } /**
* @param {HttpClient} http Http Client to send requests.
*/
constructor(private http: HttpClient) { }
getGroups(): Observable<any> { /**
return this.http.get('/groups'); * @param {any} filterBy Properties by which entries should be filtered.
} * @param {string} orderBy Property by which entries should be sorted.
* @param {string} sortOrder Sort order: ascending or descending.
* @param {number} offset Page offset.
* @param {number} limit Number of entries within the page.
* @returns {Observable<any>} Groups.
*/
getGroups(filterBy: any, orderBy: string, sortOrder: string, offset: number, limit: number): Observable<any> {
let httpParams = new HttpParams()
.set('offset', offset.toString())
.set('limit', limit.toString())
.set('sortOrder', sortOrder)
.set('orderBy', orderBy)
.set('paged', 'true');
// filterBy: name
filterBy.forEach(function (filter: any) {
if (filter.value) {
console.log(filter.value);
httpParams = httpParams.set(filter.type, filter.value);
}
});
return this.http.get('/groups', { params: httpParams });
}
} }