Skip to content

Commit

Permalink
feat(platform): allow developer to pass array for table filter option…
Browse files Browse the repository at this point in the history
…s to allow from select options (#13040)

* feat(platform): add dropdown menu for predefined filter options

* fix: some renaming/docs updates for clarification

* fix: more code cleanup

* fix: FilterableColumnDataType used incorrectly

---------

Co-authored-by: Mohammad Nmeri <mohammad.nmeri@sap.com>
  • Loading branch information
mikerodonnell89 and mohammad-nmeri authored Mar 3, 2025
1 parent 0106aa2 commit e733479
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
key="name"
label="Name"
align="start"
[filterSelectOptions]="selectOptions"
[sortable]="true"
[filterable]="true"
[dataType]="dataTypeEnum.STRING"
>
</fdp-column>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { DatetimeAdapter, FdDate, FdDatetimeModule } from '@fundamental-ngx/core/datetime';
import { FdDate, FdDatetimeModule } from '@fundamental-ngx/core/datetime';
import { PlatformTableModule } from '@fundamental-ngx/platform/table';
import {
ChildTableDataSource,
CollectionBooleanFilter,
CollectionDateFilter,
CollectionNumberFilter,
CollectionStringFilter,
FdpTableDataSource,
Expand Down Expand Up @@ -46,6 +44,7 @@ export class AdvancedScrollingExampleComponent {
childSource: ChildTableDataSource<ExampleItem>;
readonly filterTypeEnum = FilterType;
readonly dataTypeEnum = FilterableColumnDataType;
readonly selectOptions = ['Laptops 1 (Level 1)', 'Laptops 12 (Level 1)'];

constructor() {
this.source = new TableDataSource(new TableDataProviderExample());
Expand Down Expand Up @@ -125,6 +124,23 @@ class ChildTableProviderExample extends TableChildrenDataProvider<ExampleItem> {
return of(itemsMap).pipe(delay(1000));
}

search(items: ExampleItem[], { searchInput, columnKeys }: TableState): ExampleItem[] {
const searchText = searchInput?.text || '';
const keysToSearchBy = columnKeys;

if (searchText.trim() === '' || keysToSearchBy.length === 0) {
return items;
}

return items.filter((item) => {
const valuesForSearch = keysToSearchBy.map((key) => getNestedValue(key, item));
return valuesForSearch
.filter((value) => !!value)
.map((value): string => value.toString())
.some((value) => value.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()));
});
}

private filter(items: ExampleItem[], { filterBy }: TableState): ExampleItem[] {
filterBy
.filter(({ field }) => !!field)
Expand All @@ -147,23 +163,6 @@ class ChildTableProviderExample extends TableChildrenDataProvider<ExampleItem> {
return items;
}

search(items: ExampleItem[], { searchInput, columnKeys }: TableState): ExampleItem[] {
const searchText = searchInput?.text || '';
const keysToSearchBy = columnKeys;

if (searchText.trim() === '' || keysToSearchBy.length === 0) {
return items;
}

return items.filter((item) => {
const valuesForSearch = keysToSearchBy.map((key) => getNestedValue(key, item));
return valuesForSearch
.filter((value) => !!value)
.map((value): string => value.toString())
.some((value) => value.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()));
});
}

private sort(items: ExampleItem[], { sortBy }: TableState): ExampleItem[] {
sortBy = sortBy.filter(({ field }) => !!field);

Expand Down Expand Up @@ -224,6 +223,23 @@ export class TableDataProviderExample extends TableDataProvider<ExampleItem> {
return of(this.items).pipe(delay(1000));
}

search(items: ExampleItem[], { searchInput, columnKeys }: TableState): ExampleItem[] {
const searchText = searchInput?.text || '';
const keysToSearchBy = columnKeys;

if (searchText.trim() === '' || keysToSearchBy.length === 0) {
return items;
}

return items.filter((item) => {
const valuesForSearch = keysToSearchBy.map((key) => getNestedValue(key, item));
return valuesForSearch
.filter((value) => !!value)
.map((value): string => value.toString())
.some((value) => value.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()));
});
}

private filter(items: ExampleItem[], { filterBy }: TableState): ExampleItem[] {
filterBy
.filter(({ field }) => !!field)
Expand All @@ -246,23 +262,6 @@ export class TableDataProviderExample extends TableDataProvider<ExampleItem> {
return items;
}

search(items: ExampleItem[], { searchInput, columnKeys }: TableState): ExampleItem[] {
const searchText = searchInput?.text || '';
const keysToSearchBy = columnKeys;

if (searchText.trim() === '' || keysToSearchBy.length === 0) {
return items;
}

return items.filter((item) => {
const valuesForSearch = keysToSearchBy.map((key) => getNestedValue(key, item));
return valuesForSearch
.filter((value) => !!value)
.map((value): string => value.toString())
.some((value) => value.toLocaleLowerCase().includes(searchText.toLocaleLowerCase()));
});
}

private sort({ sortBy }: TableState): ExampleItem[] {
const items = this.items.slice();

Expand Down Expand Up @@ -382,53 +381,3 @@ const filterByNumber = (item: ExampleItem, filter: CollectionNumberFilter): bool

return filter.exclude ? !result : result;
};

const filterByDate = <D = FdDate>(
item: ExampleItem,
filter: CollectionDateFilter,
adapter: DatetimeAdapter<D>
): boolean => {
const filterValue = filter.value;
const filterValue2 = filter.value2;
const itemValue = getNestedValue(filter.field, item);
const diff = adapter.compareDate(itemValue, filterValue);
let result = false;

switch (filter.strategy) {
case 'after':
result = diff > 0;
break;
case 'onOrAfter':
result = diff >= 0;
break;
case 'before':
result = diff < 0;
break;
case 'beforeOrOn':
result = diff <= 0;
break;
case 'between':
result = adapter.isBetween(itemValue, filterValue, filterValue2);
break;

case 'equalTo':
default:
result = adapter.dateTimesEqual(itemValue, filterValue);
}

return filter.exclude ? !result : result;
};

const filterByBoolean = (item: ExampleItem, filter: CollectionBooleanFilter): boolean => {
const filterValue = filter.value;
const itemValue = getNestedValue(filter.field, item);
let result = false;

switch (filter.strategy) {
case 'equalTo':
default:
result = itemValue === filterValue;
}

return filter.exclude ? !result : result;
};
6 changes: 6 additions & 0 deletions libs/platform/table-helpers/table-column.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ export abstract class TableColumn {
/** Data type the column represents. */
abstract dataType: FilterableColumnDataType;

/**
* Optional array of available filter options.
* Providing values to this input will cause the filter to change from a text-type input to a select-type input.
* */
abstract filterSelectOptions: string[];

/** Width of the column cells. */
abstract width: string;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ export class TableColumnComponent extends TableColumn implements OnInit, OnChang
@Input()
dataType: FilterableColumnDataType = FilterableColumnDataType.STRING;

/**
* Optional array of available filter options.
* Providing values to this input will cause the filter to change from a text-type input to a select-type input.
* */
@Input()
filterSelectOptions: string[] = [];

/** Toggles grouping feature for the column. */
@Input()
groupable = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,19 @@
{{ 'platformTable.P13FilterBooleanOptionFalse' | fdTranslate }}
</li>
</fd-select>
} @else if (rule.filterSelectOptions?.length) {
<!-- Filter options dropdown menu -->
<fd-select
[ngModel]="rule[valueKey]"
(ngModelChange)="rule.setValue($event); _onModelChange()"
[required]="true"
[name]="valueKey"
class="filter-row__select"
>
@for (option of rule.filterSelectOptions; track $index) {
<li fd-option [value]="option">{{ option }}</li>
}
</fd-select>
} @else {
<input
type="text"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface FilterableColumn {
key: string;
dataType: FilterableColumnDataType;
filterable?: boolean;
filterSelectOptions?: string[]; // add optional filterSelectOptions
}

export interface FilterDialogData extends TableDialogCommonData {
Expand Down Expand Up @@ -39,6 +40,12 @@ export class FilterRule<T = any> {
/** Data type */
dataType?: FilterableColumnDataType;

/**
* Optional array of available filter options.
* Providing values to this input will cause the filter to change from a text-type input to a select-type input.
* */
filterSelectOptions: string[] = [];

/** returns whether filter rule has value */
get hasValue(): boolean {
return this.valueExists(this.value) || this.valueExists(this.value2);
Expand All @@ -65,6 +72,9 @@ export class FilterRule<T = any> {
if (this.strategies.length === 0) {
this.setStrategiesByColumnKey(this.columnKey);
}
if (this.filterSelectOptions.length === 0) {
this.setFilterSelectOptionsByColumnKey(this.columnKey);
}
}

/** @hidden */
Expand Down Expand Up @@ -121,21 +131,25 @@ export class FilterRule<T = any> {
// update data type
this.setDataTypeByColumnKey(columnKey);

// update available Filter options
this.setFilterSelectOptionsByColumnKey(columnKey);

// update available strategies list
this.setStrategiesByColumnKey(columnKey);
}

/** @hidden */
setDataTypeByColumnKey(columnKey?: string): void {
const dataType = this.columns.find((column) => column.key === columnKey)?.dataType;

if (dataType === this.dataType) {
return;
}

this.dataType = dataType;
}

/** @hidden */
setFilterSelectOptionsByColumnKey(columnKey?: string): void {
const filterSelectOptions = this.columns.find((column) => column.key === columnKey)?.filterSelectOptions;
this.filterSelectOptions = filterSelectOptions ? filterSelectOptions : [];
}

/** @hidden */
private valueExists(value: any): boolean {
return !!value || value === 0 || typeof value === 'boolean';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,14 @@ export class TableP13DialogComponent implements OnDestroy {
const columns = this._getTableColumns();
const filterBy = state?.filterBy;
const dialogData: FilterDialogData = {
columns: columns.map(({ label, key, dataType, filterable }) => ({ label, key, dataType, filterable })),
// add filterSelectOptions to be sent with the data
columns: columns.map(({ label, key, dataType, filterable, filterSelectOptions }) => ({
label,
key,
dataType,
filterable,
filterSelectOptions
})),
collectionFilter: filterBy
};

Expand Down

0 comments on commit e733479

Please # to comment.