@libs-ui/components-list 0.2.356-9 → 0.2.357-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +605 -216
- package/esm2022/defines/list.define.mjs +2 -2
- package/esm2022/highlight-key-search/highlight-key-search.directive.mjs +1 -1
- package/esm2022/interfaces/config-item.interface.mjs +1 -1
- package/esm2022/list.component.mjs +5 -5
- package/esm2022/templates/checkbox/checkbox.component.mjs +6 -6
- package/esm2022/templates/group/group.component.mjs +31 -36
- package/esm2022/templates/group/item/item.component.mjs +5 -5
- package/esm2022/templates/radio/radio.component.mjs +4 -4
- package/esm2022/templates/rows/rows.component.mjs +3 -3
- package/esm2022/templates/tag/tag.component.mjs +5 -5
- package/esm2022/templates/templates.component.abstract.mjs +30 -20
- package/esm2022/templates/text/text.component.mjs +8 -6
- package/fesm2022/libs-ui-components-list.mjs +90 -83
- package/fesm2022/libs-ui-components-list.mjs.map +1 -1
- package/highlight-key-search/highlight-key-search.directive.d.ts +1 -1
- package/interfaces/config-item.interface.d.ts +3 -3
- package/list.component.d.ts +13 -13
- package/package.json +26 -23
- package/templates/group/group.component.d.ts +3 -3
package/README.md
CHANGED
|
@@ -1,58 +1,61 @@
|
|
|
1
1
|
# @libs-ui/components-list
|
|
2
2
|
|
|
3
|
-
> Component
|
|
4
|
-
|
|
5
|
-
**Version:** `0.2.356-3` | **Package:** `@libs-ui/components-list`
|
|
3
|
+
> Component danh sách đa năng hỗ trợ 5 kiểu template (text, radio, checkbox, tag, group/tree) với tìm kiếm, lazy-loading HTTP và Angular Signals.
|
|
6
4
|
|
|
7
5
|
## Giới thiệu
|
|
8
6
|
|
|
9
|
-
`LibsUiComponentsListComponent` là một standalone Angular component được thiết kế để xử lý các bài toán hiển thị danh sách phức tạp. Component
|
|
7
|
+
`LibsUiComponentsListComponent` là một standalone Angular component được thiết kế để xử lý các bài toán hiển thị danh sách phức tạp. Component tích hợp sẵn tìm kiếm online/offline, lazy-loading qua HTTP, virtual scroll và quản lý trạng thái chọn item (single/multi). Mỗi kiểu hiển thị (text, radio, checkbox, tag, group/tree) được khởi tạo động thông qua `LibsUiDynamicComponentService`, giúp bundle size tối ưu và cấu hình linh hoạt.
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
## Tính năng
|
|
12
10
|
|
|
13
|
-
- ✅ **Đa dạng
|
|
14
|
-
- ✅ **Cấu trúc
|
|
15
|
-
- ✅ **Tìm kiếm
|
|
11
|
+
- ✅ **Đa dạng template**: Hỗ trợ 5 loại hiển thị: `text`, `radio`, `checkbox`, `tag`, `group/tree`.
|
|
12
|
+
- ✅ **Cấu trúc cây (Group/Tree)**: Phân cấp không giới hạn với logic quan hệ cha-con tích hợp sẵn qua `buildListGroupConfig`.
|
|
13
|
+
- ✅ **Tìm kiếm thông minh**: Hỗ trợ cả tìm kiếm online (server-side) và offline (client-side).
|
|
16
14
|
- ✅ **Tích hợp HTTP**: Tự động load dữ liệu thông qua cấu hình `httpRequestData`.
|
|
17
15
|
- ✅ **Virtual Scroll**: Tối ưu render danh sách lớn.
|
|
18
|
-
- ✅ **OnPush Change Detection**: Tối ưu hiệu năng render.
|
|
19
|
-
- ✅ **Standalone Component**: Dễ dàng import và sử dụng.
|
|
20
|
-
- ✅ **Angular Signals**: State management hiện đại và reactive.
|
|
21
16
|
- ✅ **Custom Row/Column**: Tùy biến layout từng item qua cấu hình `rows`/`cols`.
|
|
17
|
+
- ✅ **FunctionControl API**: Điều khiển component từ bên ngoài (refresh, resetKeySelected, removeItems, updateData).
|
|
18
|
+
- ✅ **Validation**: Hỗ trợ bắt buộc chọn item qua `validRequired`.
|
|
19
|
+
- ✅ **OnPush + Standalone**: Hiệu năng cao, dễ import.
|
|
22
20
|
|
|
23
21
|
## Khi nào sử dụng
|
|
24
22
|
|
|
25
|
-
- Khi cần
|
|
26
|
-
- Khi xây dựng
|
|
27
|
-
- Khi danh sách có kích thước lớn
|
|
28
|
-
- Khi
|
|
29
|
-
|
|
30
|
-
## ⚠️ Important Notes
|
|
31
|
-
|
|
32
|
-
- **Config Type**: Phải luôn cung cấp `type` và signal config tương ứng (ví dụ: `configTemplateText` cho type `text`).
|
|
33
|
-
- **Performance**: Virtual scroll được bật mặc định cho danh sách lớn. Tắt bằng cách set `notUseVirtualScroll: true` trong config.
|
|
34
|
-
- **Signals**: Các cấu hình bên trong `config` (như `httpRequestData`) phải là `WritableSignal` để component có thể reactive.
|
|
35
|
-
- **keysDisableItem + configCheckboxCheckAll**: Không dùng `keysDisableItem` kết hợp với template checkbox có `configCheckboxCheckAll`.
|
|
23
|
+
- Khi cần danh sách tùy chọn đơn (single-select) hoặc đa (multi-select).
|
|
24
|
+
- Khi xây dựng bộ lọc (filter) phân cấp dạng cây (tree structure).
|
|
25
|
+
- Khi danh sách có kích thước lớn, cần lazy-loading hoặc tìm kiếm online từ API.
|
|
26
|
+
- Khi cần hiển thị dữ liệu danh sách với layout item phức tạp (nhiều dòng, nhiều cột).
|
|
27
|
+
- Khi xây dựng dropdown, menu chọn nhanh với icon và hình ảnh.
|
|
36
28
|
|
|
37
29
|
## Cài đặt
|
|
38
30
|
|
|
39
31
|
```bash
|
|
40
|
-
# npm
|
|
41
32
|
npm install @libs-ui/components-list
|
|
42
|
-
|
|
43
|
-
# yarn
|
|
44
|
-
yarn add @libs-ui/components-list
|
|
45
33
|
```
|
|
46
34
|
|
|
47
35
|
## Import
|
|
48
36
|
|
|
49
37
|
```typescript
|
|
50
|
-
// Component chính
|
|
51
38
|
import { LibsUiComponentsListComponent } from '@libs-ui/components-list';
|
|
52
|
-
|
|
53
|
-
// Sub-component No-Data (khi cần dùng riêng)
|
|
54
39
|
import { LibsUiComponentsListTemplatesNoDataComponent } from '@libs-ui/components-list';
|
|
55
40
|
|
|
41
|
+
// Types & Interfaces
|
|
42
|
+
import {
|
|
43
|
+
IListConfigItem,
|
|
44
|
+
IListConfigItemText,
|
|
45
|
+
IListConfigItemRadio,
|
|
46
|
+
IListConfigItemCheckbox,
|
|
47
|
+
IListConfigItemGroup,
|
|
48
|
+
IListConfigItemTag,
|
|
49
|
+
IListFunctionControlEvent,
|
|
50
|
+
IListDataEmitKey,
|
|
51
|
+
IListDataEmitMultiKey,
|
|
52
|
+
IDataUpdateToStore,
|
|
53
|
+
TYPE_TEMPLATE,
|
|
54
|
+
} from '@libs-ui/components-list';
|
|
55
|
+
|
|
56
|
+
// Helper function để tạo cấu hình group/tree
|
|
57
|
+
import { buildListGroupConfig } from '@libs-ui/components-list';
|
|
58
|
+
|
|
56
59
|
@Component({
|
|
57
60
|
standalone: true,
|
|
58
61
|
imports: [LibsUiComponentsListComponent],
|
|
@@ -61,234 +64,600 @@ import { LibsUiComponentsListTemplatesNoDataComponent } from '@libs-ui/component
|
|
|
61
64
|
export class YourComponent {}
|
|
62
65
|
```
|
|
63
66
|
|
|
64
|
-
## Ví dụ
|
|
67
|
+
## Ví dụ sử dụng
|
|
65
68
|
|
|
66
|
-
###
|
|
69
|
+
### Ví dụ 1 — Text (Single Select)
|
|
67
70
|
|
|
68
71
|
```html
|
|
69
72
|
<libs_ui-components-list
|
|
70
73
|
[config]="configText()"
|
|
71
74
|
[keySelected]="keySelected()"
|
|
72
|
-
(outSelectKey)="
|
|
73
|
-
(outFunctionsControl)="
|
|
75
|
+
(outSelectKey)="handlerSelectKey($event)"
|
|
76
|
+
(outFunctionsControl)="handlerFunctionsControl($event)">
|
|
77
|
+
</libs_ui-components-list>
|
|
74
78
|
```
|
|
75
79
|
|
|
76
80
|
```typescript
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
import { signal } from '@angular/core';
|
|
82
|
+
import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitKey, IListFunctionControlEvent } from '@libs-ui/components-list';
|
|
83
|
+
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
|
|
84
|
+
|
|
85
|
+
// Dữ liệu mẫu
|
|
86
|
+
const sampleData = [
|
|
87
|
+
{ id: '1', name: 'Nguyễn Văn An', avatar: '' },
|
|
88
|
+
{ id: '2', name: 'Trần Thị Bình', avatar: '' },
|
|
89
|
+
{ id: '3', name: 'Lê Quốc Cường', avatar: '' },
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
export class YourComponent {
|
|
93
|
+
protected keySelected = signal<string | undefined>('1');
|
|
94
|
+
private listFunctionControl: IListFunctionControlEvent | undefined;
|
|
95
|
+
|
|
96
|
+
protected configText = signal<IListConfigItem>({
|
|
97
|
+
type: 'text',
|
|
98
|
+
httpRequestData: signal<IHttpRequestConfig>({
|
|
99
|
+
objectInstance: returnListObject(sampleData),
|
|
100
|
+
argumentsValue: [],
|
|
101
|
+
functionName: 'list',
|
|
102
|
+
}),
|
|
103
|
+
configTemplateText: signal({
|
|
104
|
+
fieldKey: 'id',
|
|
105
|
+
getValue: (item) => item.name,
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
protected handlerSelectKey(event?: IListDataEmitKey) {
|
|
110
|
+
event?.event?.stopPropagation?.();
|
|
111
|
+
this.keySelected.set(event?.key as string);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
protected handlerFunctionsControl(event: IListFunctionControlEvent) {
|
|
115
|
+
this.listFunctionControl = event;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
89
118
|
```
|
|
90
119
|
|
|
91
|
-
### Checkbox (Multi
|
|
120
|
+
### Ví dụ 2 — Checkbox (Multi Select)
|
|
92
121
|
|
|
93
122
|
```html
|
|
94
123
|
<libs_ui-components-list
|
|
95
124
|
[config]="configCheckbox()"
|
|
96
125
|
[multiKeySelected]="multiKeySelected()"
|
|
97
|
-
|
|
126
|
+
[searchConfig]="{ placeholder: 'Tìm kiếm...' }"
|
|
127
|
+
(outSelectMultiKey)="handlerSelectMultiKey($event)"
|
|
128
|
+
(outFunctionsControl)="handlerFunctionsControl($event)">
|
|
129
|
+
</libs_ui-components-list>
|
|
98
130
|
```
|
|
99
131
|
|
|
100
132
|
```typescript
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
}
|
|
133
|
+
import { signal } from '@angular/core';
|
|
134
|
+
import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitMultiKey, IListFunctionControlEvent } from '@libs-ui/components-list';
|
|
135
|
+
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
|
|
136
|
+
|
|
137
|
+
const sampleData = [
|
|
138
|
+
{ id: '1', name: 'Báo cáo doanh thu' },
|
|
139
|
+
{ id: '2', name: 'Báo cáo chi phí' },
|
|
140
|
+
{ id: '3', name: 'Báo cáo lợi nhuận' },
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
export class YourComponent {
|
|
144
|
+
protected multiKeySelected = signal<string[]>(['1', '2']);
|
|
145
|
+
private listFunctionControl: IListFunctionControlEvent | undefined;
|
|
146
|
+
|
|
147
|
+
protected configCheckbox = signal<IListConfigItem>({
|
|
148
|
+
type: 'checkbox',
|
|
149
|
+
httpRequestData: signal<IHttpRequestConfig>({
|
|
150
|
+
objectInstance: returnListObject(sampleData),
|
|
151
|
+
argumentsValue: [],
|
|
152
|
+
functionName: 'list',
|
|
153
|
+
}),
|
|
154
|
+
configTemplateCheckbox: signal({
|
|
155
|
+
fieldKey: 'id',
|
|
156
|
+
getValue: (item) => item.name,
|
|
157
|
+
}),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
protected handlerSelectMultiKey(event?: IListDataEmitMultiKey) {
|
|
161
|
+
this.multiKeySelected.set(event?.keys as string[]);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
protected handlerFunctionsControl(event: IListFunctionControlEvent) {
|
|
165
|
+
this.listFunctionControl = event;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Ví dụ 3 — Group/Tree với `buildListGroupConfig`
|
|
171
|
+
|
|
172
|
+
```html
|
|
173
|
+
<libs_ui-components-list
|
|
174
|
+
[config]="configGroup()"
|
|
175
|
+
[multiKeySelected]="multiKeySelected()"
|
|
176
|
+
(outSelectMultiKey)="handlerSelectMultiKey($event)">
|
|
177
|
+
</libs_ui-components-list>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { signal } from '@angular/core';
|
|
182
|
+
import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitMultiKey, buildListGroupConfig } from '@libs-ui/components-list';
|
|
183
|
+
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
|
|
184
|
+
|
|
185
|
+
const groupData = [
|
|
186
|
+
{
|
|
187
|
+
id: 'g1',
|
|
188
|
+
name: 'Nhóm A',
|
|
189
|
+
items: [
|
|
190
|
+
{ id: 'a1', name: 'Item A1' },
|
|
191
|
+
{ id: 'a2', name: 'Item A2' },
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: 'g2',
|
|
196
|
+
name: 'Nhóm B',
|
|
197
|
+
items: [
|
|
198
|
+
{ id: 'b1', name: 'Item B1' },
|
|
199
|
+
{ id: 'b2', name: 'Item B2' },
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
export class YourComponent {
|
|
205
|
+
protected multiKeySelected = signal<string[]>([]);
|
|
206
|
+
|
|
207
|
+
// buildListGroupConfig tạo sẵn cấu hình cho các pattern phổ biến
|
|
208
|
+
protected configGroup = signal<IListConfigItem>({
|
|
209
|
+
type: 'group',
|
|
210
|
+
httpRequestData: signal<IHttpRequestConfig>({
|
|
211
|
+
objectInstance: returnListObject(groupData),
|
|
212
|
+
argumentsValue: [],
|
|
213
|
+
functionName: 'list',
|
|
214
|
+
}),
|
|
215
|
+
configTemplateGroup: buildListGroupConfig('group_tree_checkbox_1'),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
protected handlerSelectMultiKey(event?: IListDataEmitMultiKey) {
|
|
219
|
+
this.multiKeySelected.set(event?.keys as string[]);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
108
222
|
```
|
|
109
223
|
|
|
110
|
-
###
|
|
224
|
+
### Ví dụ 4 — Radio
|
|
225
|
+
|
|
226
|
+
```html
|
|
227
|
+
<libs_ui-components-list
|
|
228
|
+
[config]="configRadio()"
|
|
229
|
+
[keySelected]="keySelected()"
|
|
230
|
+
(outSelectKey)="handlerSelectKey($event)">
|
|
231
|
+
</libs_ui-components-list>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { signal } from '@angular/core';
|
|
236
|
+
import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitKey } from '@libs-ui/components-list';
|
|
237
|
+
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
|
|
238
|
+
|
|
239
|
+
const sampleData = [
|
|
240
|
+
{ id: 'all', name: 'Tất cả' },
|
|
241
|
+
{ id: 'active', name: 'Đang hoạt động' },
|
|
242
|
+
{ id: 'inactive', name: 'Ngừng hoạt động' },
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
export class YourComponent {
|
|
246
|
+
protected keySelected = signal<string | undefined>('all');
|
|
247
|
+
|
|
248
|
+
protected configRadio = signal<IListConfigItem>({
|
|
249
|
+
type: 'radio',
|
|
250
|
+
httpRequestData: signal<IHttpRequestConfig>({
|
|
251
|
+
objectInstance: returnListObject(sampleData),
|
|
252
|
+
argumentsValue: [],
|
|
253
|
+
functionName: 'list',
|
|
254
|
+
}),
|
|
255
|
+
configTemplateRadio: signal({
|
|
256
|
+
fieldKey: 'id',
|
|
257
|
+
getValue: (item) => item.name,
|
|
258
|
+
}),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
protected handlerSelectKey(event?: IListDataEmitKey) {
|
|
262
|
+
this.keySelected.set(event?.key as string);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Ví dụ 5 — Tag
|
|
111
268
|
|
|
112
269
|
```html
|
|
113
270
|
<libs_ui-components-list
|
|
114
271
|
[config]="configTag()"
|
|
115
|
-
[searchConfig]="{ placeholder: 'Tìm theo tên...' }"
|
|
272
|
+
[searchConfig]="{ placeholder: 'Tìm theo tên...' }"
|
|
273
|
+
[multiKeySelected]="multiKeySelected()"
|
|
274
|
+
(outSelectMultiKey)="handlerSelectMultiKey($event)">
|
|
275
|
+
</libs_ui-components-list>
|
|
116
276
|
```
|
|
117
277
|
|
|
118
278
|
```typescript
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
279
|
+
import { signal } from '@angular/core';
|
|
280
|
+
import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitMultiKey } from '@libs-ui/components-list';
|
|
281
|
+
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
|
|
282
|
+
|
|
283
|
+
const tagData = [
|
|
284
|
+
{ id: '1', name: 'Angular' },
|
|
285
|
+
{ id: '2', name: 'TypeScript' },
|
|
286
|
+
{ id: '3', name: 'RxJS' },
|
|
287
|
+
{ id: '4', name: 'Signals' },
|
|
288
|
+
];
|
|
289
|
+
|
|
290
|
+
export class YourComponent {
|
|
291
|
+
protected multiKeySelected = signal<string[]>([]);
|
|
292
|
+
|
|
293
|
+
protected configTag = signal<IListConfigItem>({
|
|
294
|
+
type: 'tag',
|
|
295
|
+
httpRequestData: signal<IHttpRequestConfig>({
|
|
296
|
+
objectInstance: returnListObject(tagData),
|
|
297
|
+
argumentsValue: [],
|
|
298
|
+
functionName: 'list',
|
|
299
|
+
}),
|
|
300
|
+
configTemplateTag: signal({
|
|
301
|
+
fieldKey: 'id',
|
|
302
|
+
getValue: (item) => item.name,
|
|
303
|
+
}),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
protected handlerSelectMultiKey(event?: IListDataEmitMultiKey) {
|
|
307
|
+
this.multiKeySelected.set(event?.keys as string[]);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
123
310
|
```
|
|
124
311
|
|
|
125
|
-
###
|
|
312
|
+
### Ví dụ 6 — Custom Row & Column
|
|
313
|
+
|
|
314
|
+
```html
|
|
315
|
+
<libs_ui-components-list [config]="configCustomRow()">
|
|
316
|
+
</libs_ui-components-list>
|
|
317
|
+
```
|
|
126
318
|
|
|
127
319
|
```typescript
|
|
128
|
-
import {
|
|
320
|
+
import { signal } from '@angular/core';
|
|
321
|
+
import { of } from 'rxjs';
|
|
322
|
+
import { LibsUiComponentsListComponent, IListConfigItem } from '@libs-ui/components-list';
|
|
323
|
+
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
|
|
324
|
+
import { get } from '@libs-ui/utils';
|
|
325
|
+
|
|
326
|
+
const richData = [
|
|
327
|
+
{ id: '1', name: 'Nguyễn Văn An', subTitle: 'Trưởng phòng', desc: 'Kinh nghiệm 5 năm' },
|
|
328
|
+
{ id: '2', name: 'Trần Thị Bình', subTitle: 'Nhân viên', desc: 'Kinh nghiệm 2 năm' },
|
|
329
|
+
];
|
|
330
|
+
|
|
331
|
+
export class YourComponent {
|
|
332
|
+
protected configCustomRow = signal<IListConfigItem>({
|
|
333
|
+
type: 'text',
|
|
334
|
+
httpRequestData: signal<IHttpRequestConfig>({
|
|
335
|
+
objectInstance: returnListObject(richData),
|
|
336
|
+
argumentsValue: [],
|
|
337
|
+
functionName: 'list',
|
|
338
|
+
}),
|
|
339
|
+
configTemplateText: signal({
|
|
340
|
+
fieldKey: 'id',
|
|
341
|
+
rows: signal([
|
|
342
|
+
signal({
|
|
343
|
+
classRow: 'flex flex-col mb-1',
|
|
344
|
+
cols: signal([
|
|
345
|
+
signal({ getValue: (data) => of(get(data, 'item.name') as string) }),
|
|
346
|
+
signal({ getValue: (data) => of(get(data, 'item.subTitle') as string) }),
|
|
347
|
+
]),
|
|
348
|
+
}),
|
|
349
|
+
signal({
|
|
350
|
+
classRow: 'text-sm text-gray-500 mt-1',
|
|
351
|
+
getValue: (data) => of(get(data, 'item.desc') as string),
|
|
352
|
+
}),
|
|
353
|
+
]),
|
|
354
|
+
}),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
```
|
|
129
358
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
359
|
+
### Ví dụ 7 — FunctionControl: refresh, resetKeySelected, removeItems
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
import { signal } from '@angular/core';
|
|
363
|
+
import { LibsUiComponentsListComponent, IListFunctionControlEvent } from '@libs-ui/components-list';
|
|
364
|
+
|
|
365
|
+
export class YourComponent {
|
|
366
|
+
private listFunctionControl: IListFunctionControlEvent | undefined;
|
|
367
|
+
|
|
368
|
+
protected handlerFunctionsControl(event: IListFunctionControlEvent) {
|
|
369
|
+
this.listFunctionControl = event;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
protected handlerRefreshList() {
|
|
373
|
+
this.listFunctionControl?.refresh();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
protected handlerResetSelection() {
|
|
377
|
+
this.listFunctionControl?.resetKeySelected();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
protected handlerRemoveItems(keys: string[]) {
|
|
381
|
+
this.listFunctionControl?.removeItems(keys);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
protected async handlerValidate(): Promise<boolean> {
|
|
385
|
+
return this.listFunctionControl?.checkIsValid() ?? true;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
134
388
|
```
|
|
135
389
|
|
|
136
|
-
###
|
|
390
|
+
### Ví dụ 8 — Saved Views (Group flat với single-select item)
|
|
391
|
+
|
|
392
|
+
```html
|
|
393
|
+
<libs_ui-components-list
|
|
394
|
+
[config]="configSavedViews()"
|
|
395
|
+
[keySelected]="savedViewKeySelected()"
|
|
396
|
+
(outSelectKey)="handlerSelectedSavedView($event)">
|
|
397
|
+
</libs_ui-components-list>
|
|
398
|
+
```
|
|
137
399
|
|
|
138
400
|
```typescript
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
401
|
+
import { signal } from '@angular/core';
|
|
402
|
+
import { LibsUiComponentsListComponent, IListConfigItem, IListDataEmitKey } from '@libs-ui/components-list';
|
|
403
|
+
import { IHttpRequestConfig, returnListObject } from '@libs-ui/services-http-request';
|
|
404
|
+
|
|
405
|
+
const savedViewGroups = [
|
|
406
|
+
{
|
|
407
|
+
key: 'my_views',
|
|
408
|
+
label: 'Views của tôi',
|
|
409
|
+
views: [
|
|
410
|
+
{ key: 'view_high_value', label: 'Khách hàng giá trị cao' },
|
|
411
|
+
{ key: 'view_new', label: 'Khách hàng mới' },
|
|
412
|
+
],
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
key: 'shared_views',
|
|
416
|
+
label: 'Views chia sẻ',
|
|
417
|
+
views: [
|
|
418
|
+
{ key: 'view_vip', label: 'VIP' },
|
|
419
|
+
],
|
|
420
|
+
},
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
export class YourComponent {
|
|
424
|
+
protected savedViewKeySelected = signal<string[] | undefined>(['view_high_value']);
|
|
425
|
+
|
|
426
|
+
protected configSavedViews = signal<IListConfigItem>({
|
|
427
|
+
type: 'group',
|
|
428
|
+
httpRequestData: signal<IHttpRequestConfig>({
|
|
429
|
+
objectInstance: returnListObject(savedViewGroups),
|
|
430
|
+
argumentsValue: [],
|
|
431
|
+
functionName: 'list',
|
|
432
|
+
}),
|
|
433
|
+
configTemplateGroup: signal({
|
|
434
|
+
fieldKey: 'key',
|
|
435
|
+
fieldGetItems: 'views',
|
|
436
|
+
getLabelGroup: (group) => `${group.label} (${group.views.length})`,
|
|
437
|
+
getMaxLevelGroup: () => 2,
|
|
438
|
+
getLabelItem: (item) => item.label,
|
|
439
|
+
singleSelectItem: true,
|
|
440
|
+
hasIconCheckSingleSelectItem: true,
|
|
441
|
+
hasBackgroundColorWhenItemSelected: true,
|
|
442
|
+
colorBlueWhenItemSelected: true,
|
|
443
|
+
iconExpand: 'right',
|
|
444
|
+
ignoreCheckboxGroup: true,
|
|
445
|
+
ignoreCheckboxItem: true,
|
|
446
|
+
getExpandGroup: () => true,
|
|
447
|
+
}),
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
protected handlerSelectedSavedView(event?: IListDataEmitKey) {
|
|
451
|
+
this.savedViewKeySelected.set(event ? [event.key as string] : undefined);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
152
454
|
```
|
|
153
455
|
|
|
154
|
-
|
|
456
|
+
## @Input()
|
|
457
|
+
|
|
458
|
+
### `libs_ui-components-list`
|
|
459
|
+
|
|
460
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
461
|
+
|---|---|---|---|---|
|
|
462
|
+
| `autoSelectedFirstItemCallOutsideBefore` | `boolean` | `false` | Tự động chọn item đầu tiên nếu chưa có item nào được chọn khi component khởi tạo. | `[autoSelectedFirstItemCallOutsideBefore]="true"` |
|
|
463
|
+
| `backgroundListCustom` | `string` | `'bg-[#ffffff]'` | Class màu nền tùy chỉnh cho container danh sách. | `[backgroundListCustom]="'bg-gray-50'"` |
|
|
464
|
+
| `buttonsOther` | `IButton[] \| undefined` | `undefined` | Danh sách các nút bấm bổ sung hiển thị phía trên danh sách. | `[buttonsOther]="extraButtons"` |
|
|
465
|
+
| `clickExactly` | `boolean` | `undefined` | Chỉ kích hoạt sự kiện chọn khi click trực tiếp vào vùng item, không kích hoạt khi click vào popover hoặc vùng ngoài. | `[clickExactly]="true"` |
|
|
466
|
+
| `config` | `IListConfigItem` | **BẮT BUỘC** | Cấu hình chính cho component: loại template, nguồn dữ liệu, config hiển thị item. | `[config]="configText()"` |
|
|
467
|
+
| `disable` | `boolean` | `undefined` | Vô hiệu hóa toàn bộ component, không cho phép chọn item. | `[disable]="isDisabled()"` |
|
|
468
|
+
| `disableLabel` | `boolean` | `undefined` | Vô hiệu hóa phần label tiêu đề của danh sách. | `[disableLabel]="true"` |
|
|
469
|
+
| `dividerClassInclude` | `string` | `undefined` | Class CSS bổ sung cho đường kẻ phân cách giữa các phần. | `[dividerClassInclude]="'border-blue-200'"` |
|
|
470
|
+
| `dropdownTabKeyActive` | `string` | `undefined` | Key của tab đang active trong dropdown. Thay đổi giá trị này sẽ trigger refresh danh sách. | `[dropdownTabKeyActive]="activeTab()"` |
|
|
471
|
+
| `focusInputSearch` | `boolean` | `undefined` | Tự động focus vào ô tìm kiếm sau khi dữ liệu load xong. | `[focusInputSearch]="true"` |
|
|
472
|
+
| `functionGetItemsAutoAddList` | `(() => Array<any>) \| undefined` | `undefined` | Hàm trả về danh sách item được thêm tự động vào đầu danh sách (ví dụ: option "Tất cả"). | `[functionGetItemsAutoAddList]="getExtraItems"` |
|
|
473
|
+
| `hasButtonUnSelectOption` | `boolean` | `undefined` | Hiển thị nút "Bỏ chọn" để xóa toàn bộ lựa chọn hiện tại. | `[hasButtonUnSelectOption]="true"` |
|
|
474
|
+
| `hasDivider` | `boolean` | `true` | Hiển thị đường kẻ phân cách giữa các phần (search bar và danh sách). | `[hasDivider]="false"` |
|
|
475
|
+
| `hiddenInputSearch` | `boolean` | `false` | Ẩn thanh tìm kiếm. Mặc định hiển thị. | `[hiddenInputSearch]="true"` |
|
|
476
|
+
| `ignoreClassDisableDefaultWhenUseKeysDisableItem` | `boolean` | `undefined` | Bỏ class CSS disable mặc định khi dùng `keysDisableItem`, cho phép tự xử lý UI disabled từng phần trong rows. | `[ignoreClassDisableDefaultWhenUseKeysDisableItem]="true"` |
|
|
477
|
+
| `isSearchOnline` | `boolean` | `undefined` | Bật chế độ tìm kiếm từ server (call API với keyword). Mặc định là tìm kiếm client-side. | `[isSearchOnline]="true"` |
|
|
478
|
+
| `keySearch` | `string` | `undefined` | Giá trị keyword tìm kiếm được truyền vào từ bên ngoài. | `[keySearch]="searchKeyword()"` |
|
|
479
|
+
| `keySelected` | `unknown` | `undefined` | Key của item đang được chọn (single select). Dùng cho type `text` và `radio`. | `[keySelected]="selectedId()"` |
|
|
480
|
+
| `keysDisableItem` | `Array<unknown> \| undefined` | `undefined` | Danh sách các key bị vô hiệu hóa, không thể chọn. Không dùng kết hợp với `configCheckboxCheckAll`. | `[keysDisableItem]="disabledIds()"` |
|
|
481
|
+
| `keysHiddenItem` | `Array<unknown> \| undefined` | `undefined` | Danh sách các key bị ẩn khỏi danh sách. Không dùng kết hợp với `configCheckboxCheckAll`. | `[keysHiddenItem]="hiddenIds()"` |
|
|
482
|
+
| `labelConfig` | `ILabel` | `undefined` | Cấu hình label tiêu đề hiển thị phía trên danh sách. | `[labelConfig]="{ label: 'Chọn danh mục' }"` |
|
|
483
|
+
| `loadingIconSize` | `'large' \| 'medium' \| 'small' \| 'smaller' \| undefined` | `undefined` | Kích thước icon loading khi đang tải dữ liệu. | `[loadingIconSize]="'small'"` |
|
|
484
|
+
| `maxItemShow` | `number` | `undefined` | Số lượng item tối đa hiển thị trong viewport trước khi bắt đầu scroll. | `[maxItemShow]="5"` |
|
|
485
|
+
| `multiKeySelected` | `Array<unknown> \| undefined` | `undefined` | Danh sách các key đang được chọn (multi select). Dùng cho type `checkbox` và `group`. | `[multiKeySelected]="selectedIds()"` |
|
|
486
|
+
| `paddingLeftItem` | `boolean` | `undefined` | Thêm padding bên trái cho các item trong danh sách. | `[paddingLeftItem]="true"` |
|
|
487
|
+
| `resetKeyWhenSelectAllKeyDropdown` | `boolean` | `undefined` | Reset key đã chọn khi người dùng chọn option "Tất cả" trong dropdown. | `[resetKeyWhenSelectAllKeyDropdown]="true"` |
|
|
488
|
+
| `searchConfig` | `IInputSearchConfig` | `{}` | Cấu hình chi tiết cho ô tìm kiếm (placeholder, debounce, v.v.). | `[searchConfig]="{ placeholder: 'Tìm kiếm...' }"` |
|
|
489
|
+
| `searchPadding` | `boolean` | `undefined` | Thêm padding cho container thanh tìm kiếm. | `[searchPadding]="true"` |
|
|
490
|
+
| `showValidateBottom` | `boolean` | `undefined` | Hiển thị thông báo lỗi validation ở phía dưới danh sách thay vì phía trên. | `[showValidateBottom]="true"` |
|
|
491
|
+
| `skipFocusInputWhenKeySearchStoreUndefined` | `boolean` | `undefined` | Bỏ qua auto-focus vào ô tìm kiếm khi `keySearchStore` đang là `undefined`. | `[skipFocusInputWhenKeySearchStoreUndefined]="true"` |
|
|
492
|
+
| `templateRefNotSearchNoData` | `TemplateRef \| undefined` | `undefined` | Template tùy chỉnh hiển thị khi chưa có từ khóa tìm kiếm và danh sách rỗng. | `[templateRefNotSearchNoData]="tplNoData"` |
|
|
493
|
+
| `templateRefSearchNoData` | `TemplateRef \| undefined` | `undefined` | Template tùy chỉnh hiển thị khi tìm kiếm không có kết quả. | `[templateRefSearchNoData]="tplNoResult"` |
|
|
494
|
+
| `validRequired` | `IValidRequired \| undefined` | `undefined` | Cấu hình bắt buộc phải chọn ít nhất một item. Tích hợp với form validation. | `[validRequired]="{ message: 'Vui lòng chọn ít nhất 1 mục' }"` |
|
|
495
|
+
| `zIndex` | `number` | `undefined` | Z-index CSS cho container danh sách, dùng khi cần quản lý lớp hiển thị. | `[zIndex]="100"` |
|
|
496
|
+
|
|
497
|
+
## @Output()
|
|
498
|
+
|
|
499
|
+
### `libs_ui-components-list`
|
|
500
|
+
|
|
501
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
502
|
+
|---|---|---|---|---|
|
|
503
|
+
| `(outChangeView)` | `Array<any>` | Phát ra mảng item đang hiển thị sau mỗi lần danh sách thay đổi (load xong, filter, search). | `handlerChangeView(items: any[]): void { items.stopPropagation?.(); ... }` | `(outChangeView)="handlerChangeView($event)"` |
|
|
504
|
+
| `(outChangStageFlagMousePopover)` | `IFlagMouse` | Phát ra trạng thái hover chuột vào/ra khỏi popover trong danh sách. | `handlerFlagMousePopover(flag: IFlagMouse): void { flag.stopPropagation?.(); ... }` | `(outChangStageFlagMousePopover)="handlerFlagMousePopover($event)"` |
|
|
505
|
+
| `(outClickButtonOther)` | `IButton` | Phát ra khi người dùng click vào một trong các nút bổ sung (`buttonsOther`). | `handlerClickButtonOther(btn: IButton): void { btn.stopPropagation?.(); ... }` | `(outClickButtonOther)="handlerClickButtonOther($event)"` |
|
|
506
|
+
| `(outFieldKey)` | `string \| undefined` | Phát ra tên trường `fieldKey` đang sử dụng trong template hiện tại. | `handlerFieldKey(key: string \| undefined): void { ... }` | `(outFieldKey)="handlerFieldKey($event)"` |
|
|
507
|
+
| `(outFunctionsControl)` | `IListFunctionControlEvent` | Phát ra object chứa các method điều khiển component từ bên ngoài. Nên lưu lại để gọi sau. | `handlerFunctionsControl(event: IListFunctionControlEvent): void { this.listControl = event; }` | `(outFunctionsControl)="handlerFunctionsControl($event)"` |
|
|
508
|
+
| `(outKeySearch)` | `string` | Phát ra keyword mỗi khi người dùng thay đổi nội dung ô tìm kiếm. | `handlerKeySearch(keyword: string): void { this.keyword.set(keyword); }` | `(outKeySearch)="handlerKeySearch($event)"` |
|
|
509
|
+
| `(outLoadItemsComplete)` | `{ items: Array<any>; paging?: IPaging }` | Phát ra sau khi HTTP request tải dữ liệu hoàn tất. | `handlerLoadComplete(event: { items: any[]; paging?: IPaging }): void { this.total.set(event.paging?.total ?? 0); }` | `(outLoadItemsComplete)="handlerLoadComplete($event)"` |
|
|
510
|
+
| `(outLoading)` | `boolean` | Phát ra trạng thái loading: `true` khi đang tải, `false` khi xong. | `handlerLoading(loading: boolean): void { this.isLoading.set(loading); }` | `(outLoading)="handlerLoading($event)"` |
|
|
511
|
+
| `(outSelectKey)` | `IListDataEmitKey \| undefined` | Phát ra khi người dùng chọn 1 item (single select, type `text`/`radio`). `undefined` khi bỏ chọn. | `handlerSelectKey(event?: IListDataEmitKey): void { event?.event?.stopPropagation?.(); this.selected.set(event?.key as string); }` | `(outSelectKey)="handlerSelectKey($event)"` |
|
|
512
|
+
| `(outSelectMultiKey)` | `IListDataEmitMultiKey \| undefined` | Phát ra khi người dùng chọn/bỏ chọn item (multi select, type `checkbox`/`group`). | `handlerSelectMultiKey(event?: IListDataEmitMultiKey): void { this.selectedIds.set(event?.keys as string[]); }` | `(outSelectMultiKey)="handlerSelectMultiKey($event)"` |
|
|
513
|
+
| `(outUnSelectMultiKey)` | `Array<unknown>` | Phát ra mảng keys bị bỏ chọn trong multi select. | `handlerUnSelectMultiKey(keys: unknown[]): void { ... }` | `(outUnSelectMultiKey)="handlerUnSelectMultiKey($event)"` |
|
|
514
|
+
|
|
515
|
+
## FunctionControl (via `outFunctionsControl`)
|
|
516
|
+
|
|
517
|
+
Nhận `IListFunctionControlEvent` qua `(outFunctionsControl)` để điều khiển component từ bên ngoài:
|
|
518
|
+
|
|
519
|
+
| Method | Signature | Mô tả |
|
|
520
|
+
|---|---|---|
|
|
521
|
+
| `checkIsValid` | `() => Promise<boolean>` | Kiểm tra tính hợp lệ của lựa chọn khi có cấu hình `validRequired`. Trả về `true` nếu hợp lệ. |
|
|
522
|
+
| `getRectListView` | `() => Promise<IBoundingClientRect>` | Lấy kích thước và vị trí DOM của container danh sách. |
|
|
523
|
+
| `refresh` | `() => Promise<void>` | Làm mới danh sách: clone lại config và tải lại dữ liệu từ đầu. |
|
|
524
|
+
| `removeItems` | `(keys: string[]) => Promise<void>` | Xóa các item khỏi store hiển thị theo danh sách keys. |
|
|
525
|
+
| `resetKeySelected` | `() => Promise<void>` | Xóa toàn bộ item đang được chọn (cả single lẫn multi select). |
|
|
526
|
+
| `updateData` | `(data: IDataUpdateToStore) => Promise<void>` | Cập nhật/merge dữ liệu vào store hiện tại mà không cần reload từ API. |
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
// Sử dụng FunctionControl
|
|
530
|
+
export class YourComponent {
|
|
531
|
+
private listControl: IListFunctionControlEvent | undefined;
|
|
532
|
+
|
|
533
|
+
protected handlerFunctionsControl(event: IListFunctionControlEvent) {
|
|
534
|
+
this.listControl = event;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
protected handlerRefresh() {
|
|
538
|
+
this.listControl?.refresh();
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
protected async handlerSubmit(): Promise<void> {
|
|
542
|
+
const isValid = await this.listControl?.checkIsValid();
|
|
543
|
+
if (!isValid) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
// proceed...
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
## Sub-component: `libs_ui-components-list-templates-no_data`
|
|
552
|
+
|
|
553
|
+
Component hiển thị trạng thái no-data, có thể dùng độc lập hoặc truyền qua `[templateRefSearchNoData]` / `[templateRefNotSearchNoData]`.
|
|
155
554
|
|
|
156
555
|
```html
|
|
556
|
+
<!-- No data cơ bản -->
|
|
157
557
|
<libs_ui-components-list-templates-no_data
|
|
158
|
-
[config]="
|
|
558
|
+
[config]="configText()"
|
|
159
559
|
[keySearch]="''"
|
|
160
560
|
[loading]="false"
|
|
161
|
-
[enableNoData]="true"
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
### No-Data (Custom trước khi search)
|
|
561
|
+
[enableNoData]="true">
|
|
562
|
+
</libs_ui-components-list-templates-no_data>
|
|
165
563
|
|
|
166
|
-
|
|
167
|
-
<ng-template #
|
|
564
|
+
<!-- Custom template khi chưa search -->
|
|
565
|
+
<ng-template #tplNotSearch>
|
|
168
566
|
<div class="flex flex-col items-center p-4">
|
|
169
567
|
<span>Vui lòng nhập thông tin để tìm kiếm.</span>
|
|
170
568
|
</div>
|
|
171
569
|
</ng-template>
|
|
172
570
|
|
|
173
571
|
<libs_ui-components-list-templates-no_data
|
|
174
|
-
[config]="
|
|
572
|
+
[config]="configText()"
|
|
175
573
|
[keySearch]="''"
|
|
176
574
|
[loading]="false"
|
|
177
575
|
[enableNoData]="true"
|
|
178
|
-
[templateRefNotSearchNoData]="
|
|
179
|
-
|
|
576
|
+
[templateRefNotSearchNoData]="tplNotSearch">
|
|
577
|
+
</libs_ui-components-list-templates-no_data>
|
|
180
578
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
<
|
|
185
|
-
#tplNoResult
|
|
186
|
-
let-keySearch="keySearch">
|
|
187
|
-
<div>
|
|
188
|
-
Không có kết quả cho:
|
|
189
|
-
<b>{{ keySearch }}</b>
|
|
579
|
+
<!-- Custom template khi search không có kết quả -->
|
|
580
|
+
<ng-template #tplNoResult let-keySearch="keySearch">
|
|
581
|
+
<div class="p-4 text-center text-gray-500">
|
|
582
|
+
Không tìm thấy kết quả cho: <b>{{ keySearch }}</b>
|
|
190
583
|
</div>
|
|
191
584
|
</ng-template>
|
|
192
585
|
|
|
193
586
|
<libs_ui-components-list-templates-no_data
|
|
194
|
-
[config]="
|
|
195
|
-
[keySearch]="
|
|
587
|
+
[config]="configText()"
|
|
588
|
+
[keySearch]="'từ khóa tìm kiếm'"
|
|
196
589
|
[loading]="false"
|
|
197
590
|
[enableNoData]="true"
|
|
198
|
-
[templateRefSearchNoData]="tplNoResult"
|
|
591
|
+
[templateRefSearchNoData]="tplNoResult">
|
|
592
|
+
</libs_ui-components-list-templates-no_data>
|
|
199
593
|
```
|
|
200
594
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
### libs_ui-components-list
|
|
204
|
-
|
|
205
|
-
#### Inputs
|
|
206
|
-
|
|
207
|
-
| Property | Type | Default | Description |
|
|
208
|
-
| ------------------------------------------------- | -------------------------------- | -------------- | -------------------------------------------------------------------------- |
|
|
209
|
-
| `autoSelectedFirstItemCallOutsideBefore` | `boolean` | `false` | Tự động chọn item đầu tiên nếu chưa có item nào được chọn. |
|
|
210
|
-
| `backgroundListCustom` | `string` | `bg-[#ffffff]` | Màu nền tùy chỉnh cho danh sách. |
|
|
211
|
-
| `buttonsOther` | `IButton[]` | `undefined` | Danh sách các nút bấm khác hiển thị cuối danh sách. |
|
|
212
|
-
| `clickExactly` | `boolean` | `undefined` | Chỉ kích hoạt chọn khi click chính xác vào item. |
|
|
213
|
-
| `config` | `IListConfigItem` | **REQUIRED** | Cấu hình chính (loại template, logic lấy dữ liệu...). |
|
|
214
|
-
| `disable` | `boolean` | `false` | Vô hiệu hóa toàn bộ component. |
|
|
215
|
-
| `disableLabel` | `boolean` | `false` | Vô hiệu hóa nhãn (label). |
|
|
216
|
-
| `dividerClassInclude` | `string` | `undefined` | Class CSS bổ sung cho đường kẻ phân cách. |
|
|
217
|
-
| `dropdownTabKeyActive` | `string` | `undefined` | Key của tab đang active trong dropdown. |
|
|
218
|
-
| `focusInputSearch` | `boolean` | `undefined` | Tự động focus vào ô tìm kiếm khi dữ liệu load xong. |
|
|
219
|
-
| `functionGetItemsAutoAddList` | `() => Array<any>` | `undefined` | Hàm lấy danh sách item tự động thêm vào list. |
|
|
220
|
-
| `hasButtonUnSelectOption` | `boolean` | `undefined` | Hiển thị nút bỏ chọn tất cả. |
|
|
221
|
-
| `hasDivider` | `boolean` | `true` | Hiển thị gạch phân cách giữa các phần. |
|
|
222
|
-
| `hiddenInputSearch` | `boolean` | `false` | Ẩn/hiện thanh tìm kiếm. |
|
|
223
|
-
| `ignoreClassDisableDefaultWhenUseKeysDisableItem` | `boolean` | `undefined` | Bỏ class disable mặc định khi dùng `keysDisableItem`. |
|
|
224
|
-
| `isSearchOnline` | `boolean` | `false` | Bật tìm kiếm từ Server (call API). |
|
|
225
|
-
| `keySearch` | `string` | `undefined` | Giá trị tìm kiếm khởi tạo. |
|
|
226
|
-
| `keySelected` | `any` | `undefined` | Key đang chọn (single select). |
|
|
227
|
-
| `keysDisableItem` | `Array<any>` | `undefined` | Danh sách các keys bị vô hiệu hóa. |
|
|
228
|
-
| `keysHiddenItem` | `Array<any>` | `undefined` | Danh sách các keys bị ẩn đi. |
|
|
229
|
-
| `labelConfig` | `ILabel` | `undefined` | Cấu hình label tiêu đề cho danh sách. |
|
|
230
|
-
| `loadingIconSize` | `'large' \| 'medium' \| 'small'` | `undefined` | Kích thước icon loading. |
|
|
231
|
-
| `maxItemShow` | `number` | `undefined` | Số lượng item tối đa hiển thị trước khi scroll. |
|
|
232
|
-
| `multiKeySelected` | `Array<any>` | `[]` | Danh sách keys đang chọn (multi select). |
|
|
233
|
-
| `paddingLeftItem` | `boolean` | `undefined` | Thêm padding bên trái cho item. |
|
|
234
|
-
| `resetKeyWhenSelectAllKeyDropdown` | `boolean` | `undefined` | Reset key khi chọn tất cả trong dropdown. |
|
|
235
|
-
| `searchConfig` | `IInputSearchConfig` | `{}` | Cấu hình chi tiết cho ô tìm kiếm. |
|
|
236
|
-
| `searchPadding` | `boolean` | `undefined` | Thêm padding cho thanh tìm kiếm. |
|
|
237
|
-
| `showValidateBottom` | `boolean` | `undefined` | Hiển thị thông báo validate ở phía dưới. |
|
|
238
|
-
| `skipFocusInputWhenKeySearchStoreUndefined` | `boolean` | `undefined` | Bỏ qua focus input nếu `keySearchStore` là undefined. |
|
|
239
|
-
| `templateRefSearchNoData` | `TemplateRef` | `undefined` | Template hiển thị khi tìm kiếm không có kết quả. |
|
|
240
|
-
| `templateRefNotSearchNoData` | `TemplateRef` | `undefined` | Template hiển thị khi không trong trạng thái tìm kiếm và không có dữ liệu. |
|
|
241
|
-
| `validRequired` | `IValidRequired` | `undefined` | Cấu hình bắt buộc phải chọn item. |
|
|
242
|
-
| `zIndex` | `number` | `undefined` | Z-index cho danh sách. |
|
|
243
|
-
|
|
244
|
-
#### Outputs
|
|
245
|
-
|
|
246
|
-
| Property | Type | Description |
|
|
247
|
-
| --------------------------------- | ----------------------------------------- | -------------------------------------------------------- |
|
|
248
|
-
| `(outChangeView)` | `Array<any>` | Phát ra khi danh sách hiển thị thay đổi (ví dụ sau lọc). |
|
|
249
|
-
| `(outChangStageFlagMousePopover)` | `IFlagMouse` | Phát ra trạng thái hover chuột cho popover. |
|
|
250
|
-
| `(outClickButtonOther)` | `IButton` | Phát ra khi click vào các nút bấm bổ sung. |
|
|
251
|
-
| `(outFieldKey)` | `string` | Phát ra key của trường dữ liệu được chọn. |
|
|
252
|
-
| `(outFunctionsControl)` | `IListFunctionControlEvent` | Cung cấp các method điều khiển từ bên ngoài. |
|
|
253
|
-
| `(outKeySearch)` | `string` | Phát ra khi keyword tìm kiếm thay đổi. |
|
|
254
|
-
| `(outLoadItemsComplete)` | `{ items: Array<any>, paging?: IPaging }` | Phát ra khi việc tải dữ liệu hoàn tất. |
|
|
255
|
-
| `(outLoading)` | `boolean` | Trạng thái đang tải dữ liệu. |
|
|
256
|
-
| `(outSelectKey)` | `IListDataEmitKey` | Phát ra khi chọn 1 item (Single Select). |
|
|
257
|
-
| `(outSelectMultiKey)` | `IListDataEmitMultiKey` | Phát ra khi chọn nhiều item (Multi Select). |
|
|
258
|
-
| `(outUnSelectMultiKey)` | `Array<unknown>` | Phát ra danh sách các keys bị bỏ chọn. |
|
|
259
|
-
|
|
260
|
-
#### Methods (via `outFunctionsControl`)
|
|
261
|
-
|
|
262
|
-
| Method | Parameters | Description |
|
|
263
|
-
| -------------------- | -------------------------- | ----------------------------------------------------------- |
|
|
264
|
-
| `checkIsValid()` | `-` | Kiểm tra tính hợp lệ của lựa chọn (nếu có `validRequired`). |
|
|
265
|
-
| `getRectListView()` | `-` | Lấy kích thước và vị trí của list view container. |
|
|
266
|
-
| `refresh()` | `-` | Làm mới danh sách và tải lại dữ liệu từ đầu. |
|
|
267
|
-
| `removeItems()` | `keys: Array<any>` | Xóa các item cụ thể khỏi danh sách theo keys. |
|
|
268
|
-
| `resetKeySelected()` | `-` | Xóa sạch toàn bộ các item đang được chọn. |
|
|
269
|
-
| `updateData()` | `data: IDataUpdateToStore` | Cập nhật dữ liệu cụ thể vào store của list. |
|
|
270
|
-
|
|
271
|
-
### libs_ui-components-list-templates-no_data
|
|
272
|
-
|
|
273
|
-
Sub-component hiển thị giao diện trạng thái No-Data, có thể dùng độc lập hoặc tích hợp qua `[templateRefSearchNoData]` / `[templateRefNotSearchNoData]`.
|
|
274
|
-
|
|
275
|
-
#### Inputs
|
|
276
|
-
|
|
277
|
-
| Property | Type | Default | Description |
|
|
278
|
-
| ---------------------------- | ----------------- | ------------ | ------------------------------------------------------------------------------ |
|
|
279
|
-
| `config` | `IListConfigItem` | **REQUIRED** | Cấu hình list (dùng để lấy `textNoData`). |
|
|
280
|
-
| `keySearch` | `string` | `''` | Keyword đang search. Nếu rỗng → hiển thị no-data; nếu có → hiển thị no-result. |
|
|
281
|
-
| `loading` | `boolean` | `false` | Đang tải dữ liệu — khi `true` sẽ không hiển thị no-data. |
|
|
282
|
-
| `enableNoData` | `boolean` | `false` | Bật/tắt hiển thị trạng thái no-data. |
|
|
283
|
-
| `templateRefNotSearchNoData` | `TemplateRef` | `undefined` | Template tùy chỉnh khi chưa tìm kiếm (keySearch rỗng). |
|
|
284
|
-
| `templateRefSearchNoData` | `TemplateRef` | `undefined` | Template tùy chỉnh khi tìm kiếm không có kết quả. |
|
|
595
|
+
### @Input() của `libs_ui-components-list-templates-no_data`
|
|
285
596
|
|
|
286
|
-
|
|
597
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
598
|
+
|---|---|---|---|---|
|
|
599
|
+
| `config` | `IListConfigItem \| undefined` | `undefined` | Cấu hình list để lấy text no-data tùy chỉnh (`textNoData`). | `[config]="configText()"` |
|
|
600
|
+
| `enableNoData` | `boolean` | **BẮT BUỘC** | Bật/tắt hiển thị trạng thái no-data. Phải là `true` để component hiển thị. | `[enableNoData]="true"` |
|
|
601
|
+
| `keySearch` | `string` | **BẮT BUỘC** | Keyword tìm kiếm hiện tại. Rỗng → hiển thị no-data; có giá trị → hiển thị no-result. | `[keySearch]="keyword()"` |
|
|
602
|
+
| `loading` | `boolean` | **BẮT BUỘC** | Trạng thái đang tải. Khi `true` không hiển thị no-data. | `[loading]="isLoading()"` |
|
|
603
|
+
| `templateRefNotSearchNoData` | `TemplateRef \| undefined` | `undefined` | Template tùy chỉnh khi chưa có từ khóa tìm kiếm (keySearch rỗng). | `[templateRefNotSearchNoData]="tplNotSearch"` |
|
|
604
|
+
| `templateRefSearchNoData` | `TemplateRef \| undefined` | `undefined` | Template tùy chỉnh khi tìm kiếm không có kết quả. Context: `{ keySearch: string }`. | `[templateRefSearchNoData]="tplNoResult"` |
|
|
605
|
+
|
|
606
|
+
## Helper: `buildListGroupConfig`
|
|
607
|
+
|
|
608
|
+
Hàm tiện ích tạo sẵn cấu hình `IListConfigItemGroup` theo các pattern phổ biến:
|
|
287
609
|
|
|
288
610
|
```typescript
|
|
289
|
-
|
|
611
|
+
import { buildListGroupConfig } from '@libs-ui/components-list';
|
|
612
|
+
|
|
613
|
+
// Tham số 1: template name
|
|
614
|
+
// Tham số 2: overrides (tùy chọn) — ghi đè fieldKey, fieldGetItems, getLabelGroup, getLabelItem, getMaxLevelGroup
|
|
615
|
+
const groupConfig = buildListGroupConfig('group_tree_checkbox_1', {
|
|
616
|
+
fieldKey: 'code',
|
|
617
|
+
fieldGetItems: 'children',
|
|
618
|
+
getLabelGroup: (g) => g.categoryName,
|
|
619
|
+
getLabelItem: (item) => item.productName,
|
|
620
|
+
});
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
| Template | Mô tả |
|
|
624
|
+
|---|---|
|
|
625
|
+
| `group_tree_checkbox_1` | Tree nhiều cấp, checkbox, logic Bank: chọn con → chọn cha; bỏ cha → bỏ hết con. |
|
|
626
|
+
| `group_tree_checkbox_2` | Tree nhiều cấp, checkbox, logic Retail: chọn cha → chọn hết con; bỏ cha không bỏ con. |
|
|
627
|
+
| `group_tree_radio_1` | Tree nhiều cấp, radio, single-select theo logic child triggers parent. |
|
|
628
|
+
| `group_tree_radio_2` | Tree nhiều cấp, radio, single-select (biến thể emit all item checked). |
|
|
629
|
+
| `group_tree_text_1` | Tree nhiều cấp, text, single-select theo logic Bank. |
|
|
630
|
+
| `group_tree_text_2` | Tree nhiều cấp, text, single-select theo logic Retail. |
|
|
631
|
+
| `group_label_checkbox_1` | Group dạng label phân nhóm, item checkbox multi-select. |
|
|
632
|
+
| `group_label_text_1` | Group dạng label phân nhóm, item text single-select với icon check. |
|
|
290
633
|
|
|
291
|
-
|
|
634
|
+
## Types & Interfaces
|
|
635
|
+
|
|
636
|
+
```typescript
|
|
637
|
+
import {
|
|
638
|
+
TYPE_TEMPLATE,
|
|
639
|
+
IListConfigItem,
|
|
640
|
+
IListConfigItemText,
|
|
641
|
+
IListConfigItemRadio,
|
|
642
|
+
IListConfigItemCheckbox,
|
|
643
|
+
IListConfigItemGroup,
|
|
644
|
+
IListConfigItemTag,
|
|
645
|
+
IListConfigItemTextRow,
|
|
646
|
+
IListConfigItemTextCol,
|
|
647
|
+
IListDataEmitKey,
|
|
648
|
+
IListDataEmitMultiKey,
|
|
649
|
+
IListFunctionControlEvent,
|
|
650
|
+
IDataUpdateToStore,
|
|
651
|
+
IDataFunctionCallInConfig,
|
|
652
|
+
IConfigDescriptionGroup,
|
|
653
|
+
CONFIG_LIST_GROUP_TYPE,
|
|
654
|
+
} from '@libs-ui/components-list';
|
|
655
|
+
|
|
656
|
+
// Kiểu template
|
|
657
|
+
type TYPE_TEMPLATE = 'checkbox' | 'group' | 'radio' | 'text' | 'tag';
|
|
658
|
+
|
|
659
|
+
// Cấu hình chính
|
|
660
|
+
interface IListConfigItem {
|
|
292
661
|
type: TYPE_TEMPLATE;
|
|
293
662
|
httpRequestData?: WritableSignal<IHttpRequestConfig>;
|
|
294
663
|
configTemplateText?: WritableSignal<IListConfigItemText>;
|
|
@@ -296,53 +665,73 @@ export interface IListConfigItem {
|
|
|
296
665
|
configTemplateCheckbox?: WritableSignal<IListConfigItemCheckbox>;
|
|
297
666
|
configTemplateGroup?: WritableSignal<IListConfigItemGroup>;
|
|
298
667
|
configTemplateTag?: WritableSignal<IListConfigItemTag>;
|
|
299
|
-
textNoData?: string;
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
//
|
|
668
|
+
textNoData?: string; // Text hiển thị khi không có dữ liệu
|
|
669
|
+
textNoDataClassInclude?: string; // Class CSS cho text no-data
|
|
670
|
+
textSearchNoData?: string; // Text khi tìm kiếm không có kết quả
|
|
671
|
+
autoSelectFirstItem?: boolean; // Tự động chọn item đầu tiên khi có dữ liệu
|
|
672
|
+
sort?: (items: Array<any>) => void; // Hàm sort tùy chỉnh
|
|
673
|
+
hasIconNoData?: boolean;
|
|
674
|
+
ignoreShowDataWhenNotSearch?: boolean; // Ẩn danh sách khi chưa search
|
|
675
|
+
highlightTextSearchInResult?: boolean; // Highlight keyword trong kết quả
|
|
676
|
+
heightItem?: number;
|
|
677
|
+
minHeightItem?: number;
|
|
303
678
|
}
|
|
304
679
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
680
|
+
// Event khi chọn single item
|
|
681
|
+
interface IListDataEmitKey {
|
|
682
|
+
key: unknown; // Key của item được chọn
|
|
683
|
+
item: any; // Toàn bộ object item
|
|
684
|
+
isClickManual: boolean; // true nếu do user click, false nếu programmatic
|
|
309
685
|
}
|
|
310
686
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
687
|
+
// Event khi chọn nhiều item
|
|
688
|
+
interface IListDataEmitMultiKey {
|
|
689
|
+
keys: Array<unknown>; // Mảng keys được chọn
|
|
690
|
+
mapKeys: Array<IListDataEmitKey>; // Chi tiết từng item được chọn
|
|
314
691
|
isClickManual: boolean;
|
|
315
692
|
}
|
|
316
693
|
|
|
317
|
-
|
|
694
|
+
// FunctionControl API
|
|
695
|
+
interface IListFunctionControlEvent {
|
|
318
696
|
checkIsValid: () => Promise<boolean>;
|
|
319
697
|
refresh: () => Promise<void>;
|
|
320
698
|
resetKeySelected: () => Promise<void>;
|
|
321
699
|
getRectListView: () => Promise<IBoundingClientRect>;
|
|
322
700
|
removeItems: (keys: Array<string>) => Promise<void>;
|
|
323
701
|
updateData: (data: IDataUpdateToStore) => Promise<void>;
|
|
702
|
+
setMessageError?: (message: string) => Promise<void>;
|
|
324
703
|
}
|
|
325
704
|
|
|
326
|
-
|
|
705
|
+
// Dùng cho updateData
|
|
706
|
+
interface IDataUpdateToStore {
|
|
327
707
|
newData: WritableSignal<Array<WritableSignal<any>>>;
|
|
328
|
-
functionCustomAddDataToStore: (
|
|
708
|
+
functionCustomAddDataToStore: (
|
|
709
|
+
newData: WritableSignal<Array<WritableSignal<any>>>,
|
|
710
|
+
store: WritableSignal<Array<WritableSignal<any>>>
|
|
711
|
+
) => void;
|
|
329
712
|
}
|
|
330
713
|
```
|
|
331
714
|
|
|
332
|
-
##
|
|
715
|
+
## Lưu ý quan trọng
|
|
716
|
+
|
|
717
|
+
⚠️ **Config bắt buộc**: Phải luôn cung cấp `type` và signal config tương ứng. Ví dụ: type `text` cần có `configTemplateText: signal({...})`, type `group` cần có `configTemplateGroup: signal({...})` hoặc dùng `buildListGroupConfig(...)`.
|
|
718
|
+
|
|
719
|
+
⚠️ **WritableSignal cho config con**: Tất cả các config con (`configTemplateText`, `configTemplateCheckbox`, `httpRequestData`, v.v.) phải là `WritableSignal` để component có thể reactive và refresh đúng cách khi gọi `FunctionControl.refresh()`.
|
|
720
|
+
|
|
721
|
+
⚠️ **keysDisableItem không tương thích với configCheckboxCheckAll**: Không dùng `[keysDisableItem]` kết hợp với template checkbox có cấu hình `configCheckboxCheckAll` vì sẽ gây hành vi không xác định.
|
|
333
722
|
|
|
334
|
-
|
|
723
|
+
⚠️ **keysHiddenItem không tương thích với configCheckboxCheckAll**: Tương tự như trên, không dùng `[keysHiddenItem]` kết hợp với `configCheckboxCheckAll`.
|
|
335
724
|
|
|
336
|
-
|
|
725
|
+
⚠️ **Virtual Scroll mặc định**: Virtual scroll được bật mặc định cho danh sách lớn. Nếu cần tắt (ví dụ: dùng trong config badge của table), set `notUseVirtualScroll: true` trong `configTemplateText`.
|
|
337
726
|
|
|
338
|
-
|
|
339
|
-
| ----------------- | ----------------------------- |
|
|
340
|
-
| Angular 18+ | Framework chính |
|
|
341
|
-
| Angular Signals | Quản lý state reactive |
|
|
342
|
-
| TailwindCSS | Styling hệ thống |
|
|
343
|
-
| Dynamic Component | Khởi tạo template linh hoạt |
|
|
344
|
-
| Virtual Scroll | Render danh sách lớn hiệu quả |
|
|
727
|
+
⚠️ **outFunctionsControl**: Luôn lưu lại `IListFunctionControlEvent` khi nhận từ `(outFunctionsControl)` để dùng sau. Chỉ gọi các method sau khi component đã mount (ngOnInit xong).
|
|
345
728
|
|
|
346
|
-
|
|
729
|
+
⚠️ **dropdownTabKeyActive và refresh**: Thay đổi giá trị `dropdownTabKeyActive` sẽ tự động trigger `refresh()` bên trong component. Sử dụng cơ chế này khi cần reload danh sách khi đổi tab trong dropdown.
|
|
730
|
+
|
|
731
|
+
## Demo
|
|
732
|
+
|
|
733
|
+
```bash
|
|
734
|
+
npx nx serve core-ui
|
|
735
|
+
```
|
|
347
736
|
|
|
348
|
-
|
|
737
|
+
Truy cập: http://localhost:4500/components/list
|