Skip to content

Commit

Permalink
Support rendering HTML data in table & column filtering input (#383)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasmatta authored May 10, 2024
1 parent 8ed3570 commit 2398b05
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 22 deletions.
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

0 comments on commit 2398b05

Please sign in to comment.