Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(platform): vhd custom filter controls and column renderer #11083

Merged
merged 1 commit into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libs/core/src/lib/combobox/combobox.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ export class ComboboxComponent

/** @hidden */
private _defaultDisplay(str: any): string {
return str;
return `${str}`;
}

/** @hidden */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<button
fd-button
aria-label="Open value help dialog"
title="Open value help dialog"
glyph="value-help"
(click)="vhd.open()"
label="Open value help dialog"
></button>
<br />
<div *ngIf="actualItems.length">
<fd-tokenizer fdCompact>
<fd-token *ngFor="let token of actualItems" [readOnly]="true">{{ token }}</fd-token>
</fd-tokenizer>
</div>
<fdp-value-help-dialog
#vhd
fdCompact
dialogTitle="Simple value help dialog"
uniqueKey="id"
tokenViewField="name"
[formatToken]="formatTokenFn"
[dataSource]="dataSource"
(valueChange)="valueChange($event)"
headerId="fdp-vhd-header-1"
>
<ng-container *fdpValueHelpColumnDef="let row; column: 'verified'; let colName = key; let value = value">
{{ value ? 'Yes' : 'No' }}
</ng-container>
<ng-container *ngFor="let filter of filters; let i = index">
<fdp-value-help-dialog-filter
*ngIf="filter.key === 'verified'"
[main]="true"
[key]="filter.key"
[label]="filter.label"
[advanced]="true"
>
<fd-select
*fdpValueHelpFilterDef="let filterModel"
[(ngModel)]="filterModel.value"
fd-form-control
class="vhd-custom-select"
>
<li *ngFor="let option of booleanDropdownValues" fd-option [value]="option.value">
<span fd-list-title>{{ option.displayValue }}</span>
</li>
</fd-select>
</fdp-value-help-dialog-filter>
<fdp-value-help-dialog-filter
*ngIf="filter.key !== 'verified'"
[main]="i < 2"
[key]="filter.key"
[label]="filter.label"
[advanced]="i !== 0"
></fdp-value-help-dialog-filter>
</ng-container>
</fdp-value-help-dialog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { Component, ViewEncapsulation } from '@angular/core';
import {
ValueHelpDialogDataSource,
VhdDataProvider,
VhdDefineExcludeStrategy,
VhdDefineIncludeStrategy,
VhdExcludedEntity,
VhdIncludedEntity,
VhdValue,
VhdValueChangeEvent
} from '@fundamental-ngx/platform/value-help-dialog';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';

interface ExampleTestModel {
id: number;
name: string;
code: string;
city: string;
zipcode: string;
address: string;
nickname: string;
verified: boolean;
}

interface FilterData {
key: string;
name: string;
label: string;
advanced: boolean;
}

const exampleDataSource = (): { dataSource: ExampleTestModel[]; filters: FilterData[] } => {
const dataSource = Array(137)
.fill(null)
.map((_value, index) => ({
id: index + 1,
name: `Name ${index + 1}`,
code: `${Math.floor(Math.random() * 99999)}`,
city: `City ${Math.floor(Math.random() * index)}`,
zipcode: `zipcode ${Math.floor(Math.random() * index)}`,
address: `Address ${Math.floor(Math.random() * index)}`,
nickname: `Nickname ${Math.floor(Math.random() * index)}`,
verified: Math.random() < 0.5
}));
return {
dataSource,
filters: Object.keys(dataSource[0]).map((value, index) => ({
key: value,
name: `${value}`,
label: `Product ${value}`,
advanced: index > 0
}))
};
};

const data = exampleDataSource();

@Component({
selector: 'fdp-vhd-column-template-example',
styles: [
`
.vhd-custom-select {
display: block !important;
}
`
],
templateUrl: './platform-vhd-column-template-example.component.html',
encapsulation: ViewEncapsulation.None
})
export class PlatformVhdColumnTemplateExampleComponent {
filters = data.filters;
dataSource = new ValueHelpDialogDataSource(new DelayedVhdDataProvider(data.dataSource));

actualValue: Partial<VhdValue<ExampleTestModel>> = {};

booleanDropdownValues = [
{ value: true, displayValue: 'Yes' },
{ value: false, displayValue: 'No' }
];

actualItems: string[] = [];
formatTokenFn = (value: VhdValueChangeEvent<ExampleTestModel>): void => {
this.actualItems = [
...(value.selected || []).map((item) => item.name),
...(value.conditions || []).map((item) => this.conditionDisplayFn(item))
].filter((v): v is string => !!v);
};
conditionDisplayFn = (item: VhdIncludedEntity | VhdExcludedEntity): string | null => {
let value = (() => {
switch (item.strategy) {
case VhdDefineIncludeStrategy.empty:
case VhdDefineExcludeStrategy.not_empty:
return null;
case VhdDefineIncludeStrategy.between:
return `${item.value}...${item.valueTo}`;
case VhdDefineIncludeStrategy.contains:
return `*${item.value}*`;
case VhdDefineIncludeStrategy.equalTo:
return `=${item.value}`;
case VhdDefineIncludeStrategy.startsWith:
return `${item.value}*`;
case VhdDefineIncludeStrategy.endsWith:
return `*${item.value}`;
case VhdDefineIncludeStrategy.greaterThan:
return `>${item.value}`;
case VhdDefineIncludeStrategy.greaterThanEqual:
return `>=${item.value}`;
case VhdDefineIncludeStrategy.lessThan:
return `<${item.value}`;
case VhdDefineIncludeStrategy.lessThanEqual:
return `<=${item.value}`;
case VhdDefineExcludeStrategy.not_equalTo:
return `!(=${item.value})`;
}
})();
if (value && item.type === 'exclude') {
value = `!(${value})`;
}

return value;
};

valueChange($event: VhdValueChangeEvent<ExampleTestModel>): void {
this.actualValue = { ...$event };
}
}

// Simulating real http request by adding 300ms delay to the DataProvider's "fetch" method
class DelayedVhdDataProvider<R extends object> extends VhdDataProvider<R> {
// Override default fetch method to be able to deal with booleans.
// Developers should implement own logic of filtering the data. E.g. sending http request to the backend.
fetch(params: Map<string, string>): Observable<R[]> {
let data = this.values;
const arrayParams = Array.from(params);
const filterFn = (row: R): boolean => {
const rowEntries = Object.entries(row) as string[][];
return arrayParams.every(([key, value]) => {
if (key === '*') {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
return rowEntries.some(([_rowEntryKey, rowEntryValue]) => this._search(rowEntryValue, value));
} else {
return this._search(row[key], value);
}
});
};
if (params.size) {
data = this.values.filter(filterFn);
}
return of(data).pipe(delay(300));
}

private _search(rowEntryValue: any, value: any): boolean {
if (typeof value === 'boolean') {
return rowEntryValue === value;
} else {
return String(rowEntryValue).toLowerCase().includes(value.toLowerCase());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,28 @@
(valueChange)="valueChange($event)"
headerId="fdp-vhd-header-1"
>
<fdp-value-help-dialog-filter
*ngFor="let filter of filters; index as i"
[main]="i < 2"
[key]="filter.key"
[label]="filter.label"
[advanced]="i !== 0"
></fdp-value-help-dialog-filter>
<ng-container *ngFor="let filter of filters; let i = index">
<fdp-value-help-dialog-filter
*ngIf="filter.key === 'verified'"
[main]="true"
[key]="filter.key"
[label]="filter.label"
[advanced]="true"
>
<fd-combobox
*fdpValueHelpFilterDef="let filterModel"
[(ngModel)]="filterModel.value"
[dropdownValues]="booleanDropdownValues"
[placeholder]="filterModel.label"
fd-form-control
></fd-combobox>
</fdp-value-help-dialog-filter>
<fdp-value-help-dialog-filter
*ngIf="filter.key !== 'verified'"
[main]="i < 2"
[key]="filter.key"
[label]="filter.label"
[advanced]="i !== 0"
></fdp-value-help-dialog-filter>
</ng-container>
</fdp-value-help-dialog>
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface ExampleTestModel {
zipcode: string;
address: string;
nickname: string;
verified: string;
}

interface FilterData {
Expand All @@ -40,7 +41,8 @@ const exampleDataSource = (): { dataSource: ExampleTestModel[]; filters: FilterD
city: `City ${Math.floor(Math.random() * index)}`,
zipcode: `zipcode ${Math.floor(Math.random() * index)}`,
address: `Address ${Math.floor(Math.random() * index)}`,
nickname: `Nickname ${Math.floor(Math.random() * index)}`
nickname: `Nickname ${Math.floor(Math.random() * index)}`,
verified: Math.random() < 0.5 ? 'Yes' : 'No'
}));
return {
dataSource,
Expand All @@ -65,6 +67,8 @@ export class PlatformVhdBasicExampleComponent {

actualValue: Partial<VhdValue<ExampleTestModel>> = {};

booleanDropdownValues = ['Yes', 'No'];

actualItems: string[] = [];
formatTokenFn = (value: VhdValueChangeEvent<ExampleTestModel>): void => {
this.actualItems = [
Expand Down
10 changes: 8 additions & 2 deletions libs/docs/platform/vhd/platform-vhd-docs.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import {
import { PlatformVhdLoadingExampleComponent } from './examples/platform-vhd-loading-example.component';
import { platformContentDensityModuleDeprecationsProvider } from '@fundamental-ngx/platform/shared';
import { PlatformVhdInitialLoadingExampleComponent } from './examples/initial-loading/platform-vhd-initial-loading-example.component';
import { PlatformVhdColumnTemplateExampleComponent } from './examples/column-template/platform-vhd-column-template-example.component';
import { ComboboxModule } from '@fundamental-ngx/core/combobox';
import { SelectModule } from '@fundamental-ngx/core/select';

const routes: Routes = [
{
Expand All @@ -55,7 +58,9 @@ const routes: Routes = [
TokenModule,
ToolbarModule,
PlatformValueHelpDialogModule,
CheckboxModule
CheckboxModule,
ComboboxModule,
SelectModule
],
exports: [RouterModule],
declarations: [
Expand All @@ -68,7 +73,8 @@ const routes: Routes = [
PlatformVhdInputExampleComponent,
PlatformVhdMobileExampleComponent,
PlatformVhdStrategyLabelExampleComponent,
PlatformVhdInitialLoadingExampleComponent
PlatformVhdInitialLoadingExampleComponent,
PlatformVhdColumnTemplateExampleComponent
],
providers: [
platformContentDensityModuleDeprecationsProvider('fdp-value-help-dialog'),
Expand Down
16 changes: 16 additions & 0 deletions libs/docs/platform/vhd/platform-vhd.docs.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,19 @@
<code-example [exampleFiles]="initialLoadingValueHelpDialog"></code-example>

<separator></separator>

<fd-docs-section-title id="custom-column" componentName="value-help-dialog">Custom columns</fd-docs-section-title>
<description>
<p>
Developers can use structural directive <code>*fdpValueHelpColumnDef</code> to define custom template to render
the column.
</p>
<p>
This example shows how to transform boolean column value into a string representation (Yes/No) with the ability
to filter by boolean values.
</p>
</description>
<component-example>
<fdp-vhd-column-template-example></fdp-vhd-column-template-example>
</component-example>
<code-example [exampleFiles]="customColumnValueHelpDialog"></code-example>
17 changes: 17 additions & 0 deletions libs/docs/platform/vhd/platform-vhd.docs.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ const loadingVhdTs = 'platform-vhd-loading-example.component.ts';
const initialLoadingVhdHtml = 'initial-loading/platform-vhd-initial-loading-example.component.html';
const initialLoadingVhdTs = 'initial-loading/platform-vhd-initial-loading-example.component.ts';

const customColumnVhdHtml = 'column-template/platform-vhd-column-template-example.component.html';
const customColumnVhdTs = 'column-template/platform-vhd-column-template-example.component.ts';

@Component({
selector: 'app-platform-vhd',
templateUrl: './platform-vhd.docs.component.html'
Expand Down Expand Up @@ -141,4 +144,18 @@ export class PlatformVhdDocsComponent {
fileName: 'platform-vhd-initial-loading-example'
}
];

customColumnValueHelpDialog: ExampleFile[] = [
{
language: 'html',
code: getAssetFromModuleAssets(customColumnVhdHtml),
fileName: 'platform-vhd-column-template-example'
},
{
language: 'typescript',
component: 'PlatformVhdColumnTemplateExampleComponent',
code: getAssetFromModuleAssets(customColumnVhdTs),
fileName: 'platform-vhd-column-template-example'
}
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,19 @@ <h5 fd-title role="heading" aria-level="5">
*ngFor="let filter of _tableFilters.main; let rowIndex = index"
[attr.aria-rowindex]="rowIndex"
>
{{ row[filter.key] || '' }}
<ng-container *ngIf="_columnDefMap.get(filter.key) as columnDef; else defaultColumnRenderer">
<ng-template
[ngTemplateOutlet]="columnDef.templateRef"
[ngTemplateOutletContext]="{
$implicit: row,
key: filter.key,
value: row[filter.key] || ''
}"
></ng-template>
</ng-container>
<ng-template #defaultColumnRenderer>
{{ row[filter.key] || '' }}
</ng-template>
</td>
</tr>

Expand Down
Loading
Loading