Implement NGrids filter function

- Implements NGrids new "filteredData" function, to restore old filter behavior.
Export a quick-filtered list is now possible.

- More controll about List-View-Table UI

- Use NGrid target-events for better mutli-select behavior

- Changes the behavior of hidden headers to ignore paddings-changes

- filters are more resistant to errors and storage loss
This commit is contained in:
Sean Engelhardt 2019-07-19 14:35:24 +02:00
parent 450819c035
commit 2372408e4c
15 changed files with 96 additions and 53 deletions

View File

@ -226,6 +226,7 @@ export abstract class BaseFilterListService<V extends BaseViewModel> {
const newDefinitions = this.getFilterDefinitions(); const newDefinitions = this.getFilterDefinitions();
this.store.get<OsFilter[]>('filter_' + this.name).then(storedFilter => { this.store.get<OsFilter[]>('filter_' + this.name).then(storedFilter => {
if (!!storedFilter) {
for (const newDef of newDefinitions) { for (const newDef of newDefinitions) {
let count = 0; let count = 0;
const matchingExistingFilter = storedFilter.find(oldDef => oldDef.property === newDef.property); const matchingExistingFilter = storedFilter.find(oldDef => oldDef.property === newDef.property);
@ -248,6 +249,7 @@ export abstract class BaseFilterListService<V extends BaseViewModel> {
} }
newDef.count = count; newDef.count = count;
} }
}
this.filterDefinitions = newDefinitions; this.filterDefinitions = newDefinitions;
this.storeActiveFilters(); this.storeActiveFilters();

View File

@ -11,25 +11,30 @@
<!-- vScrollFixed="110" --> <!-- vScrollFixed="110" -->
<!-- vScrollAuto () --> <!-- vScrollAuto () -->
<pbl-ngrid <pbl-ngrid
[ngClass]="showFilterBar ? 'virtual-scroll-with-head-bar' : 'virtual-scroll-full-page'" [ngClass]="{
'virtual-scroll-with-head-bar ngrid-hide-head': showFilterBar,
'virtual-scroll-full-page': !showFilterBar,
'multiselect': multiSelect
}"
cellTooltip cellTooltip
showHeader="!showFilterBar" [showHeader]="!showFilterBar"
matCheckboxSelection="selection" matCheckboxSelection="selection"
vScrollFixed="110" vScrollFixed="110"
[dataSource]="dataSource" [dataSource]="dataSource"
[columns]="columnSet" [columns]="columnSet"
[hideColumns]="hiddenColumns" [hideColumns]="hiddenColumns"
(rowClick)="onSelectRow($event)"
> >
<!-- "row" has the view model --> <!-- "row" has the view model -->
<!-- "value" has the property, that was defined in the columnDefinition --> <!-- "value" has the property, that was defined in the columnDefinition -->
<!-- "col" has a column reference --> <!-- "col" has a column reference -->
<!-- Projector column --> <!-- Projector column -->
<div *pblNgridCellDef="'projector'; row as viewModel" class="fill"> <div *pblNgridCellDef="'projector'; row as viewModel" class="fill ngrid-lg">
<os-projector-button class="projector-button" [object]="getProjectable(viewModel)"></os-projector-button> <os-projector-button class="projector-button" [object]="getProjectable(viewModel)"></os-projector-button>
</div> </div>
<!-- Slot transclusion for the individual cells --> <!-- Slot transclusion for the individual cells -->
<ng-content select=".cell-slot"></ng-content> <ng-content class="ngrid-lg" select=".cell-slot"></ng-content>
</pbl-ngrid> </pbl-ngrid>
</mat-drawer-container> </mat-drawer-container>

View File

@ -3,3 +3,17 @@
.projector-button { .projector-button {
margin: auto; margin: auto;
} }
.pbl-ngrid-row {
height: 110px;
}
.pbl-ngrid-cell {
height: inherit;
}
.multiselect {
.pbl-ngrid-cell {
cursor: pointer;
}
}

View File

@ -9,17 +9,19 @@ import {
ViewEncapsulation, ViewEncapsulation,
ChangeDetectorRef ChangeDetectorRef
} from '@angular/core'; } from '@angular/core';
import { Observable } from 'rxjs';
import { PblDataSource, columnFactory, PblNgridComponent, createDS } from '@pebula/ngrid';
import { PblColumnDefinition, PblNgridColumnSet } from '@pebula/ngrid/lib/table';
import { PblNgridDataMatrixRow } from '@pebula/ngrid/target-events';
import { BaseViewModel } from 'app/site/base/base-view-model'; import { BaseViewModel } from 'app/site/base/base-view-model';
import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model'; import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model';
import { BaseSortListService } from 'app/core/ui-services/base-sort-list.service'; import { BaseSortListService } from 'app/core/ui-services/base-sort-list.service';
import { BaseViewModelWithContentObject } from 'app/site/base/base-view-model-with-content-object'; import { BaseViewModelWithContentObject } from 'app/site/base/base-view-model-with-content-object';
import { PblDataSource, columnFactory, PblNgridComponent, createDS } from '@pebula/ngrid';
import { BaseFilterListService } from 'app/core/ui-services/base-filter-list.service'; import { BaseFilterListService } from 'app/core/ui-services/base-filter-list.service';
import { Observable } from 'rxjs';
import { BaseRepository } from 'app/core/repositories/base-repository'; import { BaseRepository } from 'app/core/repositories/base-repository';
import { BaseModel } from 'app/shared/models/base/base-model'; import { BaseModel } from 'app/shared/models/base/base-model';
import { PblColumnDefinition, PblNgridColumnSet } from '@pebula/ngrid/lib/table';
import { Permission, OperatorService } from 'app/core/core-services/operator.service'; import { Permission, OperatorService } from 'app/core/core-services/operator.service';
import { StorageService } from 'app/core/core-services/storage.service'; import { StorageService } from 'app/core/core-services/storage.service';
import { ViewportService } from 'app/core/ui-services/viewport.service'; import { ViewportService } from 'app/core/ui-services/viewport.service';
@ -221,7 +223,7 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
* Gets the amount of filtered data * Gets the amount of filtered data
*/ */
public get countFilter(): number { public get countFilter(): number {
return this.dataSource.source.length; return this.dataSource.filteredData.length;
} }
/** /**
@ -358,7 +360,7 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
// Define the columns. Has to be in the OnInit cause "columns" is slower than // Define the columns. Has to be in the OnInit cause "columns" is slower than
// the constructor of this class // the constructor of this class
this.columnSet = columnFactory() this.columnSet = columnFactory()
.default({ width: this.columnMinWidth, css: 'ngrid-lg' }) .default({ width: this.columnMinWidth })
.table(...this.defaultColumns, ...this.columns) .table(...this.defaultColumns, ...this.columns)
.build(); .build();
@ -368,6 +370,22 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
} }
} }
/**
* Generic click handler for rows. Allow so (multi) select anywhere
* @param event the clicked row
*/
public onSelectRow(event: PblNgridDataMatrixRow<V>): void {
if (this.multiSelect) {
const clickedModel: V = event.row;
const alreadySelected = this.dataSource.selection.isSelected(clickedModel);
if (alreadySelected) {
this.dataSource.selection.deselect(clickedModel);
} else {
this.dataSource.selection.select(clickedModel);
}
}
}
/** /**
* Depending on the view, the view model in the row can either be a * Depending on the view, the view model in the row can either be a
* `BaseViewModelWithContentObject` or a `BaseViewModelWithContentObject`. * `BaseViewModelWithContentObject` or a `BaseViewModelWithContentObject`.

View File

@ -58,6 +58,7 @@ import { AutofocusDirective } from './directives/autofocus.directive';
// PblNgrid. Cleanup Required. // PblNgrid. Cleanup Required.
import { PblNgridModule } from '@pebula/ngrid'; import { PblNgridModule } from '@pebula/ngrid';
import { PblNgridMaterialModule } from '@pebula/ngrid-material'; import { PblNgridMaterialModule } from '@pebula/ngrid-material';
import { PblNgridTargetEventsModule } from '@pebula/ngrid/target-events';
// components // components
import { HeadBarComponent } from './components/head-bar/head-bar.component'; import { HeadBarComponent } from './components/head-bar/head-bar.component';
@ -152,7 +153,8 @@ import { AttachmentControlComponent } from './components/attachment-control/atta
CdkTreeModule, CdkTreeModule,
ScrollingModule, ScrollingModule,
PblNgridModule, PblNgridModule,
PblNgridMaterialModule PblNgridMaterialModule,
PblNgridTargetEventsModule
], ],
exports: [ exports: [
FormsModule, FormsModule,
@ -226,6 +228,7 @@ import { AttachmentControlComponent } from './components/attachment-control/atta
SpeakerButtonComponent, SpeakerButtonComponent,
PblNgridModule, PblNgridModule,
PblNgridMaterialModule, PblNgridMaterialModule,
PblNgridTargetEventsModule,
ListViewTableComponent, ListViewTableComponent,
AgendaContentObjectFormComponent, AgendaContentObjectFormComponent,
ExtensionFieldComponent ExtensionFieldComponent

View File

@ -303,7 +303,7 @@ export class AgendaListComponent extends BaseListViewComponent<ViewItem> impleme
* Export all items as CSV * Export all items as CSV
*/ */
public csvExportItemList(): void { public csvExportItemList(): void {
this.csvExport.exportItemList(this.dataSource.source); this.csvExport.exportItemList(this.dataSource.filteredData);
} }
/** /**
@ -312,7 +312,7 @@ export class AgendaListComponent extends BaseListViewComponent<ViewItem> impleme
*/ */
public onDownloadPdf(): void { public onDownloadPdf(): void {
const filename = this.translate.instant('Agenda'); const filename = this.translate.instant('Agenda');
this.pdfService.download(this.agendaPdfService.agendaListToDocDef(this.dataSource.source), filename); this.pdfService.download(this.agendaPdfService.agendaListToDocDef(this.dataSource.filteredData), filename);
} }
/** /**

View File

@ -89,7 +89,7 @@ export abstract class BaseListViewComponent<V extends BaseViewModel> extends Bas
* Select all files in the current data source * Select all files in the current data source
*/ */
public selectAll(): void { public selectAll(): void {
this.dataSource.selection.select(...this.dataSource.source); this.dataSource.selection.select(...this.dataSource.filteredData);
} }
/** /**

View File

@ -66,7 +66,7 @@
</div> </div>
<!-- the actual file manager --> <!-- the actual file manager -->
<pbl-ngrid class="file-manager-table" showHeader="false" vScrollAuto [dataSource]="dataSource" [columns]="columnSet"> <pbl-ngrid class="file-manager-table ngrid-hide-head" showHeader="false" vScrollAuto [dataSource]="dataSource" [columns]="columnSet">
<!-- Icon column --> <!-- Icon column -->
<div *pblNgridCellDef="'icon'; row as mediafile" class="fill clickable"> <div *pblNgridCellDef="'icon'; row as mediafile" class="fill clickable">
<a class="detail-link" target="_blank" [routerLink]="mediafile.url" *ngIf="mediafile.is_file"> </a> <a class="detail-link" target="_blank" [routerLink]="mediafile.url" *ngIf="mediafile.is_file"> </a>

View File

@ -57,7 +57,7 @@
font-size: 90%; font-size: 90%;
} }
height: calc(100vh - 170px); height: calc(100vh - 105px);
.pbl-ngrid-row { .pbl-ngrid-row {
$size: 60px; $size: 60px;
@ -67,11 +67,4 @@
height: $size !important; height: $size !important;
} }
} }
// For some reason, hiding the table header adds an empty meta bar.
.pbl-ngrid-container {
> div {
display: none;
}
}
} }

View File

@ -38,6 +38,7 @@
<mat-card class="os-card"> <mat-card class="os-card">
<os-list-view-table <os-list-view-table
class="block-list"
[repo]="repo" [repo]="repo"
[showFilterBar]="false" [showFilterBar]="false"
[columns]="tableColumnDefinition" [columns]="tableColumnDefinition"

View File

@ -1,5 +1,13 @@
@import '~assets/styles/tables.scss'; @import '~assets/styles/tables.scss';
::ng-deep .mat-form-field { .block-list {
display: block;
.virtual-scroll-full-page {
height: calc(100vh - 150px);
}
}
.mat-form-field {
width: 50%; width: 50%;
} }

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
@ -23,7 +23,8 @@ import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
@Component({ @Component({
selector: 'os-motion-block-list', selector: 'os-motion-block-list',
templateUrl: './motion-block-list.component.html', templateUrl: './motion-block-list.component.html',
styleUrls: ['./motion-block-list.component.scss'] styleUrls: ['./motion-block-list.component.scss'],
encapsulation: ViewEncapsulation.None
}) })
export class MotionBlockListComponent extends BaseListViewComponent<ViewMotionBlock> implements OnInit { export class MotionBlockListComponent extends BaseListViewComponent<ViewMotionBlock> implements OnInit {
/** /**

View File

@ -308,7 +308,7 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
exportDialogRef.afterClosed().subscribe((exportInfo: ExportFormData) => { exportDialogRef.afterClosed().subscribe((exportInfo: ExportFormData) => {
if (exportInfo && exportInfo.format) { if (exportInfo && exportInfo.format) {
const data = this.isMultiSelect ? this.selectedRows : this.dataSource.source; const data = this.isMultiSelect ? this.selectedRows : this.dataSource.filteredData;
if (exportInfo.format === FileFormat.PDF) { if (exportInfo.format === FileFormat.PDF) {
try { try {
this.pdfExport.exportMotionCatalog(data, exportInfo); this.pdfExport.exportMotionCatalog(data, exportInfo);

View File

@ -245,7 +245,7 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
*/ */
public csvExportUserList(): void { public csvExportUserList(): void {
this.csvExport.export( this.csvExport.export(
this.dataSource.source, this.dataSource.filteredData,
[ [
{ property: 'title' }, { property: 'title' },
{ property: 'first_name', label: 'Given name' }, { property: 'first_name', label: 'Given name' },
@ -270,7 +270,7 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
* (access information, including personal information such as initial passwords) * (access information, including personal information such as initial passwords)
*/ */
public onDownloadAccessPdf(): void { public onDownloadAccessPdf(): void {
this.userPdf.exportMultipleUserAccessPDF(this.dataSource.source); this.userPdf.exportMultipleUserAccessPDF(this.dataSource.filteredData);
} }
/** /**
@ -278,7 +278,7 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
* with all users currently matching the filter * with all users currently matching the filter
*/ */
public pdfExportUserList(): void { public pdfExportUserList(): void {
this.userPdf.exportUserList(this.dataSource.source); this.userPdf.exportUserList(this.dataSource.filteredData);
} }
/** /**

View File

@ -191,11 +191,6 @@
} }
} }
.ngrid-lg {
height: 110px;
min-height: 90px;
}
/** Define the general style-rules */ /** Define the general style-rules */
* { * {
font-family: OSFont, Fira Sans, Roboto, Arial, Helvetica, sans-serif; font-family: OSFont, Fira Sans, Roboto, Arial, Helvetica, sans-serif;
@ -629,6 +624,7 @@ button.mat-menu-item.selected {
* Depending in mobile-mode and desktop mode we need to subtract different values * Depending in mobile-mode and desktop mode we need to subtract different values
* from 100vh * from 100vh
*/ */
// no os-sort-filter-bar
.virtual-scroll-full-page { .virtual-scroll-full-page {
height: calc(100vh - 64px); height: calc(100vh - 64px);
} }
@ -638,14 +634,16 @@ button.mat-menu-item.selected {
display: inline-block; display: inline-block;
line-height: 150%; line-height: 150%;
} }
// with os-sort-filter-bar
.virtual-scroll-with-head-bar { .virtual-scroll-with-head-bar {
height: calc(100vh - 125px); height: calc(100vh - 125px);
}
.ngrid-hide-head {
// For some reason, hiding the table header adds an empty meta bar. // For some reason, hiding the table header adds an empty meta bar.
.pbl-ngrid-container { .pbl-ngrid-container {
> div { > div {
display: none; height: 0;
} }
} }
} }