Skip to content

Commit 946e8b0

Browse files
committed
feat(module:table): add bodyInnerHTML property
close #233
1 parent 41586db commit 946e8b0

16 files changed

+297
-4
lines changed

lib/ng-nest/ui/core/config/config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ export interface XTableConfig {
570570
loading?: boolean;
571571
showHeader?: boolean;
572572
headerPosition?: 'top' | 'bottom' | 'top-bottom';
573+
bodyInnerHTML?: boolean;
573574
virtualScroll?: boolean;
574575
rowHeight?: number;
575576
itemSize?: number;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<x-table [columns]="columns" [data]="data" loading bodyInnerHTML> </x-table>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Component } from '@angular/core';
2+
import { InnerHTMLService } from './innerHTML.service';
3+
import { XQuery } from '@ng-nest/ui/core';
4+
import { XTableColumn, XTableComponent } from '@ng-nest/ui/table';
5+
import { delay } from 'rxjs/operators';
6+
7+
@Component({
8+
selector: 'ex-innerHTML',
9+
imports: [XTableComponent],
10+
templateUrl: './innerHTML.component.html',
11+
providers: [InnerHTMLService]
12+
})
13+
export class ExInnerHTMLComponent {
14+
data = (index: number, size: number, query: XQuery) => this.service.getList(index, size, query).pipe(delay(1000));
15+
columns: XTableColumn[] = [
16+
{ id: 'index', label: 'serial', flex: 0.5, left: 0, type: 'index' },
17+
{ id: 'name', label: 'user', flex: 1.5, sort: true },
18+
{ id: 'position', label: 'position', flex: 0.5, sort: true },
19+
{ id: 'email', label: 'mailbox', flex: 1, innerHTML: false },
20+
{ id: 'phone', label: 'phone', flex: 1 },
21+
{ id: 'organization', label: 'organization', flex: 1, sort: true }
22+
];
23+
24+
constructor(private service: InnerHTMLService) {}
25+
26+
ngOnInit() {}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { Injectable } from '@angular/core';
2+
import {
3+
XRepositoryAbstract,
4+
XQuery,
5+
XResultList,
6+
XGroupItem,
7+
XFilter,
8+
XChunk,
9+
XGroupBy,
10+
XSort,
11+
XId,
12+
XOrderBy
13+
} from '@ng-nest/ui/core';
14+
import { Observable } from 'rxjs';
15+
16+
@Injectable()
17+
export class InnerHTMLService extends XRepositoryAbstract {
18+
organizations = ['Manufacturing Center', 'R&D Center', 'Finance Center', 'Marketing Center', 'Administrative Center'];
19+
positions = ['Technician', 'Sales', 'Manager', 'Director', 'Production'];
20+
users: User[] = Array.from({ length: 123456 }).map((_x, i) => {
21+
i++;
22+
return {
23+
id: i,
24+
name: `<span style="color: red">name${i}</span>`,
25+
position: this.positions[Math.floor(Math.random() * 10 + 1) % 5],
26+
email: `<span>email${i}</span>`,
27+
phone: 'phone' + i,
28+
organization: this.organizations[Math.floor(Math.random() * 10 + 1) % 5]
29+
};
30+
});
31+
32+
getList(index: number, size: number, query?: XQuery): Observable<XResultList<User | XGroupItem>> {
33+
return new Observable((x) => {
34+
let data: User[] | XGroupItem[] = [];
35+
data = this.setFilter(this.users, query?.filter as XFilter[]);
36+
if (query?.group) {
37+
data = this.setGroup(data, query.group);
38+
}
39+
if (query?.sort) {
40+
data = this.setSort(data, query.sort);
41+
}
42+
let chunks = XChunk(data, size);
43+
if ((index as number) <= chunks.length) {
44+
x.next({ total: data.length, list: chunks[index - 1] });
45+
} else {
46+
x.next({ total: data.length, list: [] });
47+
}
48+
x.complete();
49+
});
50+
}
51+
get(_id: number | string): Observable<User> {
52+
return new Observable();
53+
}
54+
post(_entity: User): Observable<User> {
55+
return new Observable();
56+
}
57+
put(_entity: User): Observable<User> {
58+
return new Observable();
59+
}
60+
delete(_id: number | string): Observable<boolean> {
61+
return new Observable();
62+
}
63+
64+
private setFilter(data: User[], filters: XFilter[]): User[] {
65+
let result = data;
66+
if (filters && filters.length > 0) {
67+
filters.forEach((x) => {
68+
result = result.filter((y) => y[x.field!].indexOf(x.value) >= 0);
69+
});
70+
}
71+
return result;
72+
}
73+
74+
private setGroup(data: User[], group: string): XGroupItem[] {
75+
return XGroupBy(data, group).map((value, key) => {
76+
let groupItem: XGroupItem = { id: key, count: value.length };
77+
groupItem[group] = key;
78+
return groupItem;
79+
});
80+
}
81+
82+
private setSort(data: User[] | XGroupItem[], sort: XSort[]): User[] | XGroupItem[] {
83+
return XOrderBy(
84+
data,
85+
sort.map((x) => x.field!),
86+
sort.map((x) => x.value) as ('desc' | 'asc')[]
87+
) as User[] | XGroupItem[];
88+
}
89+
}
90+
91+
export interface User extends XId {
92+
name?: string;
93+
account?: string;
94+
password?: string;
95+
email?: string;
96+
phone?: string;
97+
organization?: string;
98+
[property: string]: any;
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
order: 19
3+
label: 'Render HTML'
4+
---
5+
6+
- The `bodyInnerHTML` property can be used to set all column contents to support rendering `HTML` tags.
7+
- The `innerHTML` property in the `XTableColumn` column configuration can be used to set the current column to support rendering `HTML` tags with a priority higher than `bodyInnerHTML`.

lib/ng-nest/ui/table/examples/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export * from './zh_CN/default/scroll/scroll.component';
1717
export * from './zh_CN/default/search/search.component';
1818
export * from './zh_CN/default/head-template/head-template.component';
1919
export * from './zh_CN/default/array-data/array-data.component';
20+
export * from './zh_CN/default/innerHTML/innerHTML.component';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<x-table [columns]="columns" [data]="data" loading bodyInnerHTML> </x-table>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Component } from '@angular/core';
2+
import { InnerHTMLService } from './innerHTML.service';
3+
import { XQuery } from '@ng-nest/ui/core';
4+
import { XTableColumn, XTableComponent } from '@ng-nest/ui/table';
5+
import { delay } from 'rxjs/operators';
6+
7+
@Component({
8+
selector: 'ex-innerHTML',
9+
imports: [XTableComponent],
10+
templateUrl: './innerHTML.component.html',
11+
providers: [InnerHTMLService]
12+
})
13+
export class ExInnerHTMLComponent {
14+
data = (index: number, size: number, query: XQuery) => this.service.getList(index, size, query).pipe(delay(1000));
15+
columns: XTableColumn[] = [
16+
{ id: 'index', label: '序号', flex: 0.5, left: 0, type: 'index' },
17+
{ id: 'name', label: '用户', flex: 1.5, sort: true },
18+
{ id: 'position', label: '职位', flex: 0.5, sort: true },
19+
{ id: 'email', label: '邮箱', flex: 1, innerHTML: false },
20+
{ id: 'phone', label: '电话', flex: 1 },
21+
{ id: 'organization', label: '组织机构', flex: 1, sort: true }
22+
];
23+
24+
constructor(private service: InnerHTMLService) {}
25+
26+
ngOnInit() {}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { Injectable } from '@angular/core';
2+
import {
3+
XRepositoryAbstract,
4+
XQuery,
5+
XResultList,
6+
XGroupItem,
7+
XFilter,
8+
XChunk,
9+
XGroupBy,
10+
XSort,
11+
XId,
12+
XOrderBy
13+
} from '@ng-nest/ui/core';
14+
import { Observable } from 'rxjs';
15+
16+
@Injectable()
17+
export class InnerHTMLService extends XRepositoryAbstract {
18+
organizations = ['制造中心', '研发中心', '财务中心', '营销中心', '行政中心'];
19+
positions = ['技术员', '销售', '经理', '总监', '生产员'];
20+
users: User[] = Array.from({ length: 123456 }).map((_x, i) => {
21+
i++;
22+
return {
23+
id: i,
24+
name: `<span style="color: red">姓名${i}</span>`,
25+
position: this.positions[Math.floor(Math.random() * 10 + 1) % 5],
26+
email: `<span>邮箱${i}</span>`,
27+
phone: '手机' + i,
28+
organization: this.organizations[Math.floor(Math.random() * 10 + 1) % 5]
29+
};
30+
});
31+
32+
getList(index: number, size: number, query?: XQuery): Observable<XResultList<User | XGroupItem>> {
33+
return new Observable((x) => {
34+
let data: User[] | XGroupItem[] = [];
35+
data = this.setFilter(this.users, query?.filter as XFilter[]);
36+
if (query?.group) {
37+
data = this.setGroup(data, query.group);
38+
}
39+
if (query?.sort) {
40+
data = this.setSort(data, query.sort);
41+
}
42+
let chunks = XChunk(data, size);
43+
if ((index as number) <= chunks.length) {
44+
x.next({ total: data.length, list: chunks[index - 1] });
45+
} else {
46+
x.next({ total: data.length, list: [] });
47+
}
48+
x.complete();
49+
});
50+
}
51+
get(_id: number | string): Observable<User> {
52+
return new Observable();
53+
}
54+
post(_entity: User): Observable<User> {
55+
return new Observable();
56+
}
57+
put(_entity: User): Observable<User> {
58+
return new Observable();
59+
}
60+
delete(_id: number | string): Observable<boolean> {
61+
return new Observable();
62+
}
63+
64+
private setFilter(data: User[], filters: XFilter[]): User[] {
65+
let result = data;
66+
if (filters && filters.length > 0) {
67+
filters.forEach((x) => {
68+
result = result.filter((y) => y[x.field!].indexOf(x.value) >= 0);
69+
});
70+
}
71+
return result;
72+
}
73+
74+
private setGroup(data: User[], group: string): XGroupItem[] {
75+
return XGroupBy(data, group).map((value, key) => {
76+
let groupItem: XGroupItem = { id: key, count: value.length };
77+
groupItem[group] = key;
78+
return groupItem;
79+
});
80+
}
81+
82+
private setSort(data: User[] | XGroupItem[], sort: XSort[]): User[] | XGroupItem[] {
83+
return XOrderBy(
84+
data,
85+
sort.map((x) => x.field!),
86+
sort.map((x) => x.value) as ('desc' | 'asc')[]
87+
) as User[] | XGroupItem[];
88+
}
89+
}
90+
91+
export interface User extends XId {
92+
name?: string;
93+
account?: string;
94+
password?: string;
95+
email?: string;
96+
phone?: string;
97+
organization?: string;
98+
[property: string]: any;
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
order: 19
3+
label: '渲染HTML'
4+
---
5+
6+
- 使用 `bodyInnerHTML` 属性可以设置所有列内容支持渲染 `HTML` 标签。
7+
- 使用 `XTableColumn` 列配置中的 `innerHTML` 属性可以设置当前列支持渲染 `HTML` 标签,优先级大于 `bodyInnerHTML`

lib/ng-nest/ui/table/table-body.component.html

+5-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,11 @@
137137
*xOutlet="columnTpl()[column.id]; context: { $column: column, $row: row, $index: table.getIndex(i) }"
138138
>
139139
<ng-container *xOutlet="table.bodyTdTpl(); context: { $column: column, $row: row, $index: table.getIndex(i) }">
140-
<div [innerHTML]="row[column.id]" [style.text-align]="column.textAlign"></div>
140+
@if ((table.bodyInnerHTML() && column.innerHTML !== false) || column.innerHTML) {
141+
<div [innerHTML]="setDomSanitizer(row[column.id])" [style.text-align]="column.textAlign"></div>
142+
} @else {
143+
<div [style.text-align]="column.textAlign">{{ row[column.id] }}</div>
144+
}
141145
{{ table.rowExpand() && table.rowExpand()!.id === column.id ? 'x-table-body-level-' + row.level : '' }}
142146
</ng-container>
143147
</ng-container>

lib/ng-nest/ui/table/table-body.component.ts

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { XCheckboxComponent } from '@ng-nest/ui/checkbox';
2626
import { FormsModule } from '@angular/forms';
2727
import { XButtonComponent } from '@ng-nest/ui/button';
2828
import { XTableComponent } from './table.component';
29+
import { DomSanitizer } from '@angular/platform-browser';
2930

3031
@Component({
3132
selector: `${XTableBodyPrefix}`,
@@ -53,6 +54,7 @@ export class XTableBodyComponent extends XTableBodyProperty implements OnInit, A
5354
private doc = inject(DOCUMENT);
5455
private unSubject = new Subject<void>();
5556
private resizeObserver!: XResizeObserver;
57+
private domSanitizer = inject(DomSanitizer);
5658
tbodyStyle = signal<{ [property: string]: any }>({});
5759

5860
isEmpty = computed(() => this.data().length === 0);
@@ -230,6 +232,10 @@ export class XTableBodyComponent extends XTableBodyProperty implements OnInit, A
230232
});
231233
}
232234

235+
setDomSanitizer(str: string) {
236+
return this.domSanitizer.bypassSecurityTrustHtml(str);
237+
}
238+
233239
getIndex(index: number, item: XTableRow) {
234240
if (!isNaN(index)) return index;
235241
return this.data().indexOf(item);

lib/ng-nest/ui/table/table.property.ts

+10
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ export class XTableProperty extends XPropertyFunction(X_TABLE_CONFIG_NAME) {
7171
* @en_US Whether to display the column headers
7272
*/
7373
readonly headerPosition = input<XTableHeaderPosition>(this.config?.headerPosition ?? 'top');
74+
/**
75+
* @zh_CN 列内容支持 innerHTML 渲染 html 标签
76+
* @en_US Column content supports innerHTML rendering of HTML tags
77+
*/
78+
readonly bodyInnerHTML = input<boolean, XBoolean>(this.config?.bodyInnerHTML ?? false, { transform: XToBoolean });
7479
/**
7580
* @zh_CN 当前选中行数据
7681
* @en_US Currently selected row data
@@ -556,6 +561,11 @@ export interface XTableColumn extends XIdentityProperty {
556561
* @en_US Head shows expand
557562
*/
558563
headExpand?: boolean;
564+
/**
565+
* @zh_CN 列内容支持 innerHTML 渲染 html 标签
566+
* @en_US Column content supports innerHTML rendering of HTML tags
567+
*/
568+
innerHTML?: boolean;
559569
/**
560570
* @zh_CN 自定义属性
561571
* @en_US Custom attributes
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export const environment = {
22
layout: 'test',
33
version: '19.0.0',
4-
defaultPage: 'dialog',
4+
defaultPage: 'table',
55
static: 'https://ngnest.com/static'
66
};

src/main/test/table/table.component.html

+1
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@
1616
<ex-scroll></ex-scroll>
1717
<ex-search></ex-search>
1818
<ex-head-tempalte></ex-head-tempalte>
19+
<ex-innerHTML></ex-innerHTML>

src/main/test/table/table.component.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import {
1717
ExRowclassComponent,
1818
ExScrollComponent,
1919
ExSearchComponent,
20-
ExHeadTemplateComponent
20+
ExHeadTemplateComponent,
21+
ExInnerHTMLComponent
2122
} from '@ng-nest/ui/table/examples';
2223

2324
@Component({
@@ -40,7 +41,8 @@ import {
4041
ExRowclassComponent,
4142
ExScrollComponent,
4243
ExSearchComponent,
43-
ExHeadTemplateComponent
44+
ExHeadTemplateComponent,
45+
ExInnerHTMLComponent
4446
],
4547
templateUrl: './table.component.html'
4648
})

0 commit comments

Comments
 (0)