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

Support rendering HTML data in table & column filtering input #383

Merged
merged 7 commits into from
May 10, 2024
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
40 changes: 40 additions & 0 deletions projects/composition/src/app/api-data/cps-table.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@
"default": "field",
"description": "A key used to retrieve the field from columns."
},
{
"name": "colFilterTypeName",
"optional": false,
"readonly": false,
"type": "string",
"default": "filterType",
"description": "A key used to retrieve the filter type from columns."
},
{
"name": "colDateFormatName",
"optional": false,
"readonly": false,
"type": "string",
"default": "dateFormat",
"description": "A key used to retrieve the date format from columns."
},
{
"name": "striped",
"optional": false,
Expand Down Expand Up @@ -141,6 +157,22 @@
"default": "false",
"description": "Makes all columns sortable if columns prop is provided."
},
{
"name": "filterableByColumns",
"optional": false,
"readonly": false,
"type": "boolean",
"default": "false",
"description": "Enable filtering on all columns."
},
{
"name": "autoColumnFilterType",
"optional": false,
"readonly": false,
"type": "boolean",
"default": "true",
"description": "If true, automatically detects filter type based on values, otherwise sets 'text' filter type for all columns.\nNote: This setting only takes effect if 'filterableByColumns' is true."
},
{
"name": "sortMode",
"optional": false,
Expand Down Expand Up @@ -509,6 +541,14 @@
"default": "[]",
"description": "Array of initial columns to show in the table. If not provided, all columns are initially visible."
},
{
"name": "renderDataAsHTML",
"optional": false,
"readonly": false,
"type": "boolean",
"default": "false",
"description": "If set to true, row data are rendered using innerHTML."
},
{
"name": "data",
"optional": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

<ng-template let-item #body>
<td>{{ item.a | uppercase }}</td>
<td>{{ item.b | date : 'MM/dd/yyyy' }}</td>
<td>{{ item.b | date: 'MM/dd/yyyy' }}</td>
<td>{{ item.c | percent }}</td>
<td [style.color]="item.d ? 'green' : 'red'">
{{ item.d ? '&#10004;' : '&#10008;' }}
Expand All @@ -47,7 +47,8 @@
[virtualScroll]="true"
[showColumnsToggleBtn]="true"
scrollHeight="500px"
toolbarTitle="Sortable table with virtual scroller, global filter and internal columns toggle">
toolbarTitle="Sortable table with virtual scroller, global filter, internal columns toggle and with column filtering enabled"
[filterableByColumns]="true">
</cps-table>
</cps-tab>
<cps-tab label="Table 3">
Expand Down Expand Up @@ -78,7 +79,7 @@

<ng-template let-item #body>
<td>{{ item.a | uppercase }}</td>
<td>{{ item.b | date : 'MM/dd/yyyy' }}</td>
<td>{{ item.b | date: 'MM/dd/yyyy' }}</td>
<td>{{ item.c | percent }}</td>
<td [style.color]="item.d ? 'green' : 'red'">
{{ item.d ? '&#10004;' : '&#10008;' }}
Expand Down Expand Up @@ -117,7 +118,7 @@
<ng-template let-item #body>
<ng-container *ngFor="let scol of selCols" [ngSwitch]="scol.field">
<td *ngSwitchCase="'a'">{{ item.a | uppercase }}</td>
<td *ngSwitchCase="'b'">{{ item.b | date : 'MM/dd/yyyy' }}</td>
<td *ngSwitchCase="'b'">{{ item.b | date: 'MM/dd/yyyy' }}</td>
<td *ngSwitchCase="'c'">{{ item.c | percent }}</td>
<td *ngSwitchCase="'d'" [style.color]="item.d ? 'green' : 'red'">
{{ item.d ? '&#10004;' : '&#10008;' }}
Expand Down Expand Up @@ -164,7 +165,7 @@
</ng-template>
<ng-template let-item #body>
<td>{{ item.a | uppercase }}</td>
<td>{{ item.b | date : 'MM/dd/yyyy' }}</td>
<td>{{ item.b | date: 'MM/dd/yyyy' }}</td>
<td>{{ item.c | percent }}</td>
<td [style.color]="item.d ? 'green' : 'red'">
{{ item.d ? '&#10004;' : '&#10008;' }}
Expand Down Expand Up @@ -193,7 +194,7 @@

<ng-template let-item #body>
<td>{{ item.a | uppercase }}</td>
<td>{{ item.b | date : 'MM/dd/yyyy' }}</td>
<td>{{ item.b | date: 'MM/dd/yyyy' }}</td>
<td>{{ item.c | percent }}</td>
<td [style.color]="item.d ? 'green' : 'red'">
{{ item.d ? '&#10004;' : '&#10008;' }}
Expand All @@ -219,7 +220,7 @@

<ng-template let-item #body>
<td>{{ item.a | uppercase }}</td>
<td>{{ item.b | date : 'MM/dd/yyyy' }}</td>
<td>{{ item.b | date: 'MM/dd/yyyy' }}</td>
<td>{{ item.c | percent }}</td>
<td [style.color]="item.d ? 'green' : 'red'">
{{ item.d ? '&#10004;' : '&#10008;' }}
Expand Down Expand Up @@ -248,5 +249,19 @@
toolbarTitle="Table in data loading state">
</cps-table>
</cps-tab>
<cps-tab label="Table 10">
<cps-table
[data]="dataWithHTML"
scrollHeight="400px"
[columns]="colsVirtual"
[renderDataAsHTML]="renderAsHTML"
[showActionBtn]="true"
toolbarTitle="Table containing HTML"
actionBtnTitle="renderAsHTML is set to {{
renderAsHTML ? 'true' : 'false'
}}"
(actionBtnClicked)="renderAsHTML = !renderAsHTML">
</cps-table>
</cps-tab>
</cps-tab-group>
</app-component-docs-viewer>
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,21 @@ export class TablePageComponent implements OnInit {

isRemoveBtnVisible = false;

renderAsHTML = true;

dataWithHTML = [
{
a: '<h1>hello</h1>',
b: '<h2>world</2>',
c: '<a href="https://www.github.com">link to github</a>'
},
{
a: 'this is sanitized <script>console.log("pwned")</script>',
b: '<img src="/assets/ui_logo.svg" width="100" />',
c: '<code>null === undefined</code>'
}
];

data = [
{
a: 'a1',
Expand Down Expand Up @@ -332,12 +347,27 @@ export class TablePageComponent implements OnInit {

selCols: { [key: string]: any }[] = [];

dataVirtual: { a: string; b: string; c: number }[] = [];
dataVirtual: {
a: string;
b: string;
c: number;
d: Date;
e: boolean;
f: Date;
}[] = [];

colsVirtual = [
{ field: 'a', header: 'A' },
{ field: 'b', header: 'B' },
{ field: 'c', header: 'C' }
{ field: 'a', header: 'String' },
{ field: 'b', header: 'String (only 5 distinct values)' },
{ field: 'c', header: 'Number' },
{ field: 'd', header: 'Date', dateFormat: 'dd. MM. yyyy' },
{ field: 'e', header: 'Boolean' },
{
field: 'f',
header: 'Date but with category filter',
filterType: 'category',
dateFormat: 'yyyy/MM/dd HH:mm:ss'
}
];

componentData = ComponentData;
Expand All @@ -348,9 +378,20 @@ export class TablePageComponent implements OnInit {
}

private _genVirtualData() {
const sevenRandomDates = Array.from(
{ length: 7 },
() => new Date(Math.round(Math.random() * 1e12))
);
let c = 0.0;
for (let i = 0; i <= 1000; i++) {
this.dataVirtual.push({ a: 'a' + i, b: 'b' + i, c });
this.dataVirtual.push({
a: 'a' + i,
b: 'b' + (i % 5),
c,
d: new Date(new Date().valueOf() - Math.random() * 1e12),
e: Math.random() > 0.5,
f: sevenRandomDates[i % 7]
});

c = parseFloat((c += 0.1).toFixed(1));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,45 @@
</ng-container>
<ng-container *ngIf="!headerTemplate && columns.length > 0">
<ng-container *ngIf="sortable">
<th *ngFor="let col of columns" [cpsTColSortable]="col[colFieldName]">
{{ col[colHeaderName] }}
</th>
@if (filterableByColumns) {
<th
*ngFor="let col of columns"
[cpsTColSortable]="col[colFieldName]"
[cpsTColFilter]="col[colFieldName]"
[filterType]="
col[colFilterTypeName] ??
(autoColumnFilterType
? (data | cpsDetectFilterType: col[colFieldName])
: 'text')
">
{{ col[colHeaderName] }}
</th>
} @else {
<th
*ngFor="let col of columns"
[cpsTColSortable]="col[colFieldName]">
{{ col[colHeaderName] }}
</th>
}
</ng-container>
<ng-container *ngIf="!sortable">
<th *ngFor="let col of columns">
{{ col[colHeaderName] }}
</th>
@if (filterableByColumns) {
<th
*ngFor="let col of columns"
[cpsTColFilter]="col[colFieldName]"
[filterType]="
col[colFilterTypeName] ??
(autoColumnFilterType
? (data | cpsDetectFilterType: col[colFieldName])
: 'text')
">
{{ col[colHeaderName] }}
</th>
} @else {
<th *ngFor="let col of columns">
{{ col[colHeaderName] }}
</th>
}
</ng-container>
</ng-container>
<th
Expand Down Expand Up @@ -262,9 +293,19 @@
</ng-container>
<ng-container *ngIf="!bodyTemplate">
<ng-container *ngIf="columns.length > 0">
<td *ngFor="let col of columns">
{{ rowData[col[colFieldName]] }}
</td>
@if (renderDataAsHTML) {
<td
*ngFor="let col of columns"
[innerHTML]="rowData[col[colFieldName]]"></td>
} @else {
<td *ngFor="let col of columns">
{{
col[colDateFormatName]
? (rowData[col[colFieldName]] | date: col[colDateFormatName])
: rowData[col[colFieldName]]
}}
</td>
}
</ng-container>
</ng-container>
<td
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import { CpsTableColumnSortableDirective } from './directives/cps-table-column-s
import { TableUnsortDirective } from './directives/internal/table-unsort.directive';
import { convertSize } from '../../utils/internal/size-utils';
import { isEqual } from 'lodash-es';
import { CpsTableColumnFilterDirective } from './directives/cps-table-column-filter.directive';
import { CpsDetectFilterTypePipe } from './pipes/cps-detect-filter-type.pipe';

// import jsPDF from 'jspdf';
// import 'jspdf-autotable';
Expand Down Expand Up @@ -81,7 +83,9 @@ export type CpsTableSortMode = 'single' | 'multiple';
CpsMenuComponent,
CpsLoaderComponent,
TableRowMenuComponent,
CpsTableColumnSortableDirective
CpsTableColumnSortableDirective,
CpsTableColumnFilterDirective,
CpsDetectFilterTypePipe
],
templateUrl: './cps-table.component.html',
styleUrls: ['./cps-table.component.scss'],
Expand Down Expand Up @@ -114,6 +118,18 @@ export class CpsTableComponent implements OnInit, AfterViewChecked, OnChanges {
*/
@Input() colFieldName = 'field';

/**
* A key used to retrieve the filter type from columns.
* @group Props
*/
@Input() colFilterTypeName = 'filterType';

/**
* A key used to retrieve the date format from columns.
* @group Props
*/
@Input() colDateFormatName = 'dateFormat';

/**
* Determines whether the table should have alternating stripes.
* @group Props
Expand Down Expand Up @@ -200,6 +216,19 @@ export class CpsTableComponent implements OnInit, AfterViewChecked, OnChanges {
*/
@Input() sortable = false;

/**
* Enable filtering on all columns.
* @group Props
*/
@Input() filterableByColumns = false;

/**
* If true, automatically detects filter type based on values, otherwise sets 'text' filter type for all columns.
* Note: This setting only takes effect if 'filterableByColumns' is true.
* @group Props
*/
@Input() autoColumnFilterType = true;

/**
* Determines whether sorting works on single column or on multiple columns.
* @group Props
Expand Down Expand Up @@ -476,6 +505,12 @@ export class CpsTableComponent implements OnInit, AfterViewChecked, OnChanges {
*/
@Input() initialColumns: { [key: string]: any }[] = [];

/**
* If set to true, row data are rendered using innerHTML.
* @group Props
*/
@Input() renderDataAsHTML = false;

/**
* An array of objects to display.
* @default []
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Pipe, PipeTransform } from '@angular/core';
import { CpsColumnFilterType } from '../cps-column-filter-types';

@Pipe({
name: 'cpsDetectFilterType',
standalone: true
})
export class CpsDetectFilterTypePipe implements PipeTransform {
transform(
data: { [key: string]: unknown }[],
column: string
): CpsColumnFilterType {
if (data.every((item) => typeof item[column] === 'boolean')) {
return 'boolean';
} else if (data.every((item) => typeof item[column] === 'number')) {
return 'number';
} else if (data.every((item) => item[column] instanceof Date)) {
return 'date';
} else if (
data.reduce((acc, item) => acc.add(item[column]), new Set()).size < 6
) {
return 'category';
}
return 'text';
}
}
Loading
Loading