Merge pull request #4805 from tsiegleauq/more-listview-patches

Adds custom filter predicates for native object data
This commit is contained in:
Sean 2019-06-24 17:04:57 +02:00 committed by GitHub
commit 19a389cae6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 103 additions and 134 deletions

View File

@ -148,6 +148,9 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
@Input() @Input()
public columns: PblColumnDefinition[] = []; public columns: PblColumnDefinition[] = [];
@Input()
public filterProps: string[];
/** /**
* Key to restore scroll position after navigating * Key to restore scroll position after navigating
*/ */
@ -299,12 +302,13 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
}) })
.create(); .create();
const filterPredicate = (item: any): boolean => { const filterPredicate = (item: V): boolean => {
if (!this.inputValue) { if (!this.inputValue) {
return true; return true;
} }
if (this.inputValue) { if (this.inputValue) {
// filter by ID
const trimmedInput = this.inputValue.trim().toLowerCase(); const trimmedInput = this.inputValue.trim().toLowerCase();
const idString = '' + item.id; const idString = '' + item.id;
const foundId = const foundId =
@ -316,20 +320,21 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
return true; return true;
} }
for (const column of this.columns) { // custom filter predicates
const col = this.dataSource.hostGrid.columnApi.findColumn(column.prop); if (this.filterProps && this.filterProps.length) {
const value = col.getValue(item); for (const prop of this.filterProps) {
const propertyAsString = '' + item[prop];
if (!!value) { if (!!propertyAsString) {
const valueAsString = '' + value; const foundProp =
const foundValue = propertyAsString
valueAsString .trim()
.trim() .toLowerCase()
.toLocaleLowerCase() .indexOf(trimmedInput) !== -1;
.indexOf(trimmedInput) !== -1;
if (foundValue) { if (foundProp) {
return true; return true;
}
} }
} }
} }

View File

@ -18,7 +18,9 @@
[filterService]="filterService" [filterService]="filterService"
[columns]="tableColumnDefinition" [columns]="tableColumnDefinition"
[multiSelect]="isMultiSelect" [multiSelect]="isMultiSelect"
[restricted]="restrictedColumns"
[hiddenInMobile]="['info']" [hiddenInMobile]="['info']"
[filterProps]="filterProps"
scrollKey="agenda" scrollKey="agenda"
[(selectedRows)]="selectedRows" [(selectedRows)]="selectedRows"
(dataSourceChange)="onDataSourceChange($event)" (dataSourceChange)="onDataSourceChange($event)"

View File

@ -6,10 +6,12 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { PblColumnDefinition } from '@pebula/ngrid'; import { PblColumnDefinition } from '@pebula/ngrid';
import { _ } from 'app/core/translate/translation-marker';
import { AgendaCsvExportService } from '../../services/agenda-csv-export.service'; import { AgendaCsvExportService } from '../../services/agenda-csv-export.service';
import { AgendaFilterListService } from '../../services/agenda-filter-list.service'; import { AgendaFilterListService } from '../../services/agenda-filter-list.service';
import { AgendaPdfService } from '../../services/agenda-pdf.service'; import { AgendaPdfService } from '../../services/agenda-pdf.service';
import { ConfigService } from 'app/core/ui-services/config.service'; import { ConfigService } from 'app/core/ui-services/config.service';
import { ColumnRestriction } from 'app/shared/components/list-view-table/list-view-table.component';
import { DurationService } from 'app/core/ui-services/duration.service'; import { DurationService } from 'app/core/ui-services/duration.service';
import { ItemInfoDialogComponent } from '../item-info-dialog/item-info-dialog.component'; import { ItemInfoDialogComponent } from '../item-info-dialog/item-info-dialog.component';
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service'; import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
@ -20,11 +22,10 @@ import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { PromptService } from 'app/core/ui-services/prompt.service'; import { PromptService } from 'app/core/ui-services/prompt.service';
import { PdfDocumentService } from 'app/core/ui-services/pdf-document.service'; import { PdfDocumentService } from 'app/core/ui-services/pdf-document.service';
import { StorageService } from 'app/core/core-services/storage.service'; import { StorageService } from 'app/core/core-services/storage.service';
import { TopicRepositoryService } from 'app/core/repositories/topics/topic-repository.service';
import { ViewportService } from 'app/core/ui-services/viewport.service'; import { ViewportService } from 'app/core/ui-services/viewport.service';
import { ViewItem } from '../../models/view-item'; import { ViewItem } from '../../models/view-item';
import { ViewListOfSpeakers } from '../../models/view-list-of-speakers'; import { ViewListOfSpeakers } from '../../models/view-list-of-speakers';
import { _ } from 'app/core/translate/translation-marker';
import { TopicRepositoryService } from 'app/core/repositories/topics/topic-repository.service';
import { ViewTopic } from '../../models/view-topic'; import { ViewTopic } from '../../models/view-topic';
/** /**
@ -80,7 +81,7 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
}, },
{ {
prop: 'speaker', prop: 'speaker',
width: this.singleButtonWidth width: this.badgeButtonWidth
}, },
{ {
prop: 'menu', prop: 'menu',
@ -88,6 +89,22 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
} }
]; ];
public restrictedColumns: ColumnRestriction[] = [
{
columnName: 'menu',
permission: 'agenda.can_manage'
},
{
columnName: 'speaker',
permission: 'agenda.can_see_list_of_speakers'
}
];
/**
* Define extra filter properties
*/
public filterProps = ['itemNumber', 'comment'];
/** /**
* The usual constructor for components * The usual constructor for components
* @param titleService Setting the browser tab title * @param titleService Setting the browser tab title
@ -322,26 +339,4 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
return result; return result;
} }
} }
/**
* Overwrites the dataSource's string filter with a case-insensitive search
* in the item number and title
*
* TODO: Filter predicates will be missed :(
*/
// private setFulltextFilter(): void {
// this.dataSource.filterPredicate = (data, filter) => {
// if (!data) {
// return false;
// }
// filter = filter ? filter.toLowerCase() : '';
// return (
// data.itemNumber.toLowerCase().includes(filter) ||
// data
// .getListTitle()
// .toLowerCase()
// .includes(filter)
// );
// };
// }
} }

View File

@ -24,6 +24,7 @@
[filterService]="filterService" [filterService]="filterService"
[sortService]="sortService" [sortService]="sortService"
[columns]="tableColumnDefinition" [columns]="tableColumnDefinition"
[filterProps]="filterProps"
[multiSelect]="isMultiSelect" [multiSelect]="isMultiSelect"
scrollKey="assignments" scrollKey="assignments"
[(selectedRows)]="selectedRows" [(selectedRows)]="selectedRows"

View File

@ -49,6 +49,11 @@ export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignmen
} }
]; ];
/**
* Define extra filter properties
*/
public filterProps = ['title', 'candidates', 'assignmentRelatedUsers', 'tags', 'candidateAmount'];
/** /**
* Constructor. * Constructor.
* *

View File

@ -43,6 +43,11 @@ export abstract class ListViewBaseComponent<V extends BaseViewModel> extends Bas
*/ */
public singleButtonWidth = '40px'; public singleButtonWidth = '40px';
/**
* NGrid column width for single buttons with badge
*/
public badgeButtonWidth = '45px';
/** /**
* @param titleService the title service * @param titleService the title service
* @param translate the translate service * @param translate the translate service

View File

@ -24,6 +24,8 @@
[sortService]="sortService" [sortService]="sortService"
[columns]="tableColumnDefinition" [columns]="tableColumnDefinition"
[multiSelect]="isMultiSelect" [multiSelect]="isMultiSelect"
[restricted]="restrictedColumns"
[filterProps]="filterProps"
scrollKey="user" scrollKey="user"
[(selectedRows)]="selectedRows" [(selectedRows)]="selectedRows"
(dataSourceChange)="onDataSourceChange($event)" (dataSourceChange)="onDataSourceChange($event)"

View File

@ -1,22 +1,23 @@
import { Component, OnInit, ViewChild, TemplateRef } from '@angular/core'; import { Component, OnInit, ViewChild, TemplateRef } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormGroup, Validators, FormBuilder } from '@angular/forms'; import { FormGroup, Validators, FormBuilder } from '@angular/forms';
import { Title } from '@angular/platform-browser';
import { MatSnackBar, MatDialog } from '@angular/material'; import { MatSnackBar, MatDialog } from '@angular/material';
import { Router, ActivatedRoute } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { PblColumnDefinition } from '@pebula/ngrid'; import { PblColumnDefinition } from '@pebula/ngrid';
import { ListViewBaseComponent } from '../../../base/list-view-base'; import { ColumnRestriction } from 'app/shared/components/list-view-table/list-view-table.component';
import { ViewMediafile } from '../../models/view-mediafile'; import { ListViewBaseComponent } from 'app/site/base/list-view-base';
import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service'; import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service';
import { MediaManageService } from 'app/core/ui-services/media-manage.service'; import { MediaManageService } from 'app/core/ui-services/media-manage.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { MediafileFilterListService } from '../../services/mediafile-filter.service'; import { MediafileFilterListService } from '../../services/mediafile-filter.service';
import { MediafilesSortListService } from '../../services/mediafiles-sort-list.service'; import { MediafilesSortListService } from '../../services/mediafiles-sort-list.service';
import { ViewportService } from 'app/core/ui-services/viewport.service';
import { OperatorService } from 'app/core/core-services/operator.service'; import { OperatorService } from 'app/core/core-services/operator.service';
import { PromptService } from 'app/core/ui-services/prompt.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 { ViewMediafile } from '../../models/view-mediafile';
/** /**
* Lists all the uploaded files. * Lists all the uploaded files.
@ -95,6 +96,25 @@ export class MediafileListComponent extends ListViewBaseComponent<ViewMediafile>
} }
]; ];
/**
* Restricted Columns
*/
public restrictedColumns: ColumnRestriction[] = [
{
columnName: 'indicator',
permission: 'mediafiles.can_manage'
},
{
columnName: 'menu',
permission: 'mediafiles.can_manage'
}
];
/**
* Define extra filter properties
*/
public filterProps = ['title', 'type'];
/** /**
* Constructs the component * Constructs the component
* *
@ -301,20 +321,4 @@ export class MediafileListComponent extends ListViewBaseComponent<ViewMediafile>
this.editFile = false; this.editFile = false;
} }
} }
/**
* Overwrites the dataSource's string filter with a case-insensitive search
* in the file name property
*
* TODO: Filter predicates will be missed :(
*/
// private setFulltextFilter(): void {
// this.dataSource.filterPredicate = (data, filter) => {
// if (!data || !data.title) {
// return false;
// }
// filter = filter ? filter.toLowerCase() : '';
// return data.title.toLowerCase().indexOf(filter) >= 0;
// };
// }
} }

View File

@ -48,6 +48,7 @@
[columns]="tableColumnDefinition" [columns]="tableColumnDefinition"
[multiSelect]="isMultiSelect" [multiSelect]="isMultiSelect"
[restricted]="restrictedColumns" [restricted]="restrictedColumns"
[filterProps]="filterProps"
[hiddenInMobile]="['state']" [hiddenInMobile]="['state']"
scrollKey="motion" scrollKey="motion"
[(selectedRows)]="selectedRows" [(selectedRows)]="selectedRows"

View File

@ -107,7 +107,8 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
minWidth: 160 minWidth: 160
}, },
{ {
prop: 'speaker' prop: 'speaker',
width: this.badgeButtonWidth
} }
]; ];
@ -129,13 +130,23 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
public categories: ViewCategory[] = []; public categories: ViewCategory[] = [];
public motionBlocks: ViewMotionBlock[] = []; public motionBlocks: ViewMotionBlock[] = [];
/**
* Columns that demand certain permissions
*/
public restrictedColumns: ColumnRestriction[] = [ public restrictedColumns: ColumnRestriction[] = [
{ {
columnName: 'speaker', columnName: 'speaker',
permission: 'agenda.can_see' permission: 'agenda.can_see_list_of_speakers'
} }
]; ];
/**
* Define extra filter properties
*
* TODO: repo.getExtendedStateLabel(), repo.getExtendedRecommendationLabel()
*/
public filterProps = ['submitters', 'motion_block', 'title', 'identifier'];
/** /**
* List of `TileCategoryInformation`. * List of `TileCategoryInformation`.
* Necessary to not iterate over the values of the map below. * Necessary to not iterate over the values of the map below.
@ -394,58 +405,6 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
); );
} }
/**
* Overwrites the dataSource's string filter with a case-insensitive search
* in the identifier, title, state, recommendations, submitters, motion blocks and id
*
* TODO: Does currently not work with virtual scrolling tables. Filter predicates will be missed :(
*/
// private setFulltextFilter(): void {
// this.dataSource.filterPredicate = (data, filter) => {
// if (!data) {
// return false;
// }
// filter = filter ? filter.toLowerCase() : '';
// if (data.submitters.length && data.submitters.find(user => user.full_name.toLowerCase().includes(filter))) {
// return true;
// }
// if (data.motion_block && data.motion_block.title.toLowerCase().includes(filter)) {
// return true;
// }
// if (data.title.toLowerCase().includes(filter)) {
// return true;
// }
// if (data.identifier && data.identifier.toLowerCase().includes(filter)) {
// return true;
// }
// if (
// this.getStateLabel(data) &&
// this.getStateLabel(data)
// .toLocaleLowerCase()
// .includes(filter)
// ) {
// return true;
// }
// if (
// this.getRecommendationLabel(data) &&
// this.getRecommendationLabel(data)
// .toLocaleLowerCase()
// .includes(filter)
// ) {
// return true;
// }
// const dataid = '' + data.id;
// if (dataid.includes(filter)) {
// return true;
// }
// return false;
// };
// }
/** /**
* This function saves the selected view by changes. * This function saves the selected view by changes.
* *
@ -478,7 +437,7 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
* @param ev a MouseEvent. * @param ev a MouseEvent.
*/ */
public async openEditInfo(motion: ViewMotion): Promise<void> { public async openEditInfo(motion: ViewMotion): Promise<void> {
if (!this.isMultiSelect) { if (!this.isMultiSelect && this.perms.isAllowed('change_metadata')) {
// The interface holding the current information from motion. // The interface holding the current information from motion.
this.infoDialog = { this.infoDialog = {
title: motion.title, title: motion.title,

View File

@ -19,6 +19,7 @@
[filterService]="filterService" [filterService]="filterService"
[sortService]="sortService" [sortService]="sortService"
[columns]="tableColumnDefinition" [columns]="tableColumnDefinition"
[filterProps]="filterProps"
[multiSelect]="isMultiSelect" [multiSelect]="isMultiSelect"
[hiddenInMobile]="['group']" [hiddenInMobile]="['group']"
scrollKey="user" scrollKey="user"
@ -35,11 +36,11 @@
matTooltip="{{ getUserTooltip(user) | translate }}" matTooltip="{{ getUserTooltip(user) | translate }}"
></a> ></a>
<div> <div>
<span *ngIf="user.is_active"> <span *ngIf="user.is_active !== false">
{{ name }} {{ name }}
</span> </span>
<span *ngIf="!user.is_active"> <span *ngIf="user.is_active === false">
<os-icon-container icon="block" swap="true"> <os-icon-container icon="block" swap="true">
{{ name }} {{ name }}
</os-icon-container> </os-icon-container>
@ -96,7 +97,7 @@
class="checkbox-ripple-padding" class="checkbox-ripple-padding"
(change)="setPresent(user)" (change)="setPresent(user)"
[checked]="user.is_present" [checked]="user.is_present"
[disabled]="isMultiSelect" [disabled]="isMultiSelect || !this.operator.hasPerms('users.can_manage')"
> >
<span translate>Present</span> <span translate>Present</span>
</mat-checkbox> </mat-checkbox>

View File

@ -126,6 +126,11 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
} }
]; ];
/**
* Define extra filter properties
*/
public filterProps = ['full_name', 'groups', 'structure_level', 'number'];
/** /**
* The usual constructor for components * The usual constructor for components
* @param titleService Serivce for setting the title * @param titleService Serivce for setting the title
@ -413,20 +418,4 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
viewUser.user.is_present = !viewUser.user.is_present; viewUser.user.is_present = !viewUser.user.is_present;
await this.repo.update(viewUser.user, viewUser); await this.repo.update(viewUser.user, viewUser);
} }
/**
* Overwrites the dataSource's string filter with a case-insensitive search
* in the full_name property
*
* TODO: Filter predicates will be missed :(
*/
// private setFulltextFilter(): void {
// this.dataSource.filterPredicate = (data, filter) => {
// if (!data || !data.full_name) {
// return false;
// }
// filter = filter ? filter.toLowerCase() : '';
// return data.full_name.toLowerCase().indexOf(filter) >= 0;
// };
// }
} }