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:
parent
450819c035
commit
2372408e4c
@ -226,27 +226,29 @@ 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 => {
|
||||||
for (const newDef of newDefinitions) {
|
if (!!storedFilter) {
|
||||||
let count = 0;
|
for (const newDef of newDefinitions) {
|
||||||
const matchingExistingFilter = storedFilter.find(oldDef => oldDef.property === newDef.property);
|
let count = 0;
|
||||||
for (const option of newDef.options) {
|
const matchingExistingFilter = storedFilter.find(oldDef => oldDef.property === newDef.property);
|
||||||
if (typeof option === 'object') {
|
for (const option of newDef.options) {
|
||||||
if (matchingExistingFilter && matchingExistingFilter.options) {
|
if (typeof option === 'object') {
|
||||||
const existingOption = matchingExistingFilter.options.find(
|
if (matchingExistingFilter && matchingExistingFilter.options) {
|
||||||
o =>
|
const existingOption = matchingExistingFilter.options.find(
|
||||||
typeof o !== 'string' &&
|
o =>
|
||||||
JSON.stringify(o.condition) === JSON.stringify(option.condition)
|
typeof o !== 'string' &&
|
||||||
) as OsFilterOption;
|
JSON.stringify(o.condition) === JSON.stringify(option.condition)
|
||||||
if (existingOption) {
|
) as OsFilterOption;
|
||||||
option.isActive = existingOption.isActive;
|
if (existingOption) {
|
||||||
}
|
option.isActive = existingOption.isActive;
|
||||||
if (option.isActive) {
|
}
|
||||||
count++;
|
if (option.isActive) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
newDef.count = count;
|
||||||
}
|
}
|
||||||
newDef.count = count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.filterDefinitions = newDefinitions;
|
this.filterDefinitions = newDefinitions;
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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`.
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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%;
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
/**
|
/**
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user