@ticatec/uniface-flexi-module 0.0.2

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.
Files changed (136) hide show
  1. package/FLEXICRITERIASET_GUIDE.md +1559 -0
  2. package/FLEXICRITERIASET_GUIDE_CN.md +1133 -0
  3. package/FLEXIDATATABLE_GUIDE.md +1650 -0
  4. package/FLEXIDATATABLE_GUIDE_CN.md +1650 -0
  5. package/FLEXIFORM_GUIDE.md +1068 -0
  6. package/FLEXIFORM_GUIDE_CN.md +1068 -0
  7. package/FLEXI_CONTEXT_GUIDE_CN.md +172 -0
  8. package/MODULE_LOADER_CN.md +228 -0
  9. package/README.md +307 -0
  10. package/README_CN.md +51 -0
  11. package/SANDBOX_CN.md +201 -0
  12. package/dist/FlexiContext.d.ts +28 -0
  13. package/dist/FlexiContext.js +45 -0
  14. package/dist/ModuleLoader.d.ts +41 -0
  15. package/dist/ModuleLoader.js +55 -0
  16. package/dist/Sandbox.d.ts +33 -0
  17. package/dist/Sandbox.js +101 -0
  18. package/dist/criteria-panel/CriteriaFieldsPanel.svelte +26 -0
  19. package/dist/criteria-panel/CriteriaFieldsPanel.svelte.d.ts +22 -0
  20. package/dist/criteria-panel/components/CascadeSelectSearchField.svelte +10 -0
  21. package/dist/criteria-panel/components/CascadeSelectSearchField.svelte.d.ts +25 -0
  22. package/dist/criteria-panel/components/DateRangeField.svelte +11 -0
  23. package/dist/criteria-panel/components/DateRangeField.svelte.d.ts +25 -0
  24. package/dist/criteria-panel/components/DateSearchField.svelte +10 -0
  25. package/dist/criteria-panel/components/DateSearchField.svelte.d.ts +24 -0
  26. package/dist/criteria-panel/components/DateTimeSearchField.svelte +10 -0
  27. package/dist/criteria-panel/components/DateTimeSearchField.svelte.d.ts +24 -0
  28. package/dist/criteria-panel/components/InputOptionSelectSearchField.svelte +9 -0
  29. package/dist/criteria-panel/components/InputOptionSelectSearchField.svelte.d.ts +24 -0
  30. package/dist/criteria-panel/components/NumberRangeField.svelte +11 -0
  31. package/dist/criteria-panel/components/NumberRangeField.svelte.d.ts +25 -0
  32. package/dist/criteria-panel/components/NumberSearchField.svelte +9 -0
  33. package/dist/criteria-panel/components/NumberSearchField.svelte.d.ts +24 -0
  34. package/dist/criteria-panel/components/OptionMultiSelectSearchField.svelte +9 -0
  35. package/dist/criteria-panel/components/OptionMultiSelectSearchField.svelte.d.ts +24 -0
  36. package/dist/criteria-panel/components/OptionSelectSearchField.svelte +9 -0
  37. package/dist/criteria-panel/components/OptionSelectSearchField.svelte.d.ts +24 -0
  38. package/dist/criteria-panel/components/SearchField.svelte +14 -0
  39. package/dist/criteria-panel/components/SearchField.svelte.d.ts +33 -0
  40. package/dist/criteria-panel/components/TextSearchField.svelte +9 -0
  41. package/dist/criteria-panel/components/TextSearchField.svelte.d.ts +24 -0
  42. package/dist/criteria-panel/components/UnknownCriteriaField.svelte +9 -0
  43. package/dist/criteria-panel/components/UnknownCriteriaField.svelte.d.ts +24 -0
  44. package/dist/criteria-panel/index.d.ts +6 -0
  45. package/dist/criteria-panel/index.js +6 -0
  46. package/dist/criteria-panel/lib/CriteriaComponentBuilder.d.ts +19 -0
  47. package/dist/criteria-panel/lib/CriteriaComponentBuilder.js +31 -0
  48. package/dist/criteria-panel/lib/CriteriaFieldBuilder.d.ts +1 -0
  49. package/dist/criteria-panel/lib/CriteriaFieldBuilder.js +127 -0
  50. package/dist/criteria-panel/lib/FlexiCriteriaField.d.ts +38 -0
  51. package/dist/criteria-panel/lib/FlexiCriteriaField.js +31 -0
  52. package/dist/criteria-panel/lib/FlexiCriteriaSet.d.ts +24 -0
  53. package/dist/criteria-panel/lib/FlexiCriteriaSet.js +48 -0
  54. package/dist/flexi-datatable/FlexiDataTable.d.ts +111 -0
  55. package/dist/flexi-datatable/FlexiDataTable.js +90 -0
  56. package/dist/flexi-datatable/index.d.ts +2 -0
  57. package/dist/flexi-datatable/index.js +2 -0
  58. package/dist/flexi-form/FlexiCompound.d.ts +34 -0
  59. package/dist/flexi-form/FlexiCompound.js +84 -0
  60. package/dist/flexi-form/FlexiFormDialog.svelte +24 -0
  61. package/dist/flexi-form/FlexiFormDialog.svelte.d.ts +21 -0
  62. package/dist/flexi-form/FlexiFormPage.svelte +26 -0
  63. package/dist/flexi-form/FlexiFormPage.svelte.d.ts +25 -0
  64. package/dist/flexi-form/Schema.d.ts +6 -0
  65. package/dist/flexi-form/Schema.js +1 -0
  66. package/dist/flexi-form/components/BreakLine.svelte +1 -0
  67. package/dist/flexi-form/components/BreakLine.svelte.d.ts +26 -0
  68. package/dist/flexi-form/components/CardTitleBar.svelte +18 -0
  69. package/dist/flexi-form/components/CardTitleBar.svelte.d.ts +22 -0
  70. package/dist/flexi-form/components/CascadeOptionSelectField.svelte +13 -0
  71. package/dist/flexi-form/components/CascadeOptionSelectField.svelte.d.ts +24 -0
  72. package/dist/flexi-form/components/CellFieldBuilder.d.ts +1 -0
  73. package/dist/flexi-form/components/CellFieldBuilder.js +178 -0
  74. package/dist/flexi-form/components/DateField.svelte +12 -0
  75. package/dist/flexi-form/components/DateField.svelte.d.ts +24 -0
  76. package/dist/flexi-form/components/DateTimeField.svelte +13 -0
  77. package/dist/flexi-form/components/DateTimeField.svelte.d.ts +24 -0
  78. package/dist/flexi-form/components/InputOptionSelectField.svelte +13 -0
  79. package/dist/flexi-form/components/InputOptionSelectField.svelte.d.ts +24 -0
  80. package/dist/flexi-form/components/MemoField.svelte +12 -0
  81. package/dist/flexi-form/components/MemoField.svelte.d.ts +24 -0
  82. package/dist/flexi-form/components/NumberField.svelte +12 -0
  83. package/dist/flexi-form/components/NumberField.svelte.d.ts +24 -0
  84. package/dist/flexi-form/components/OptionsMultiSelectField.svelte +13 -0
  85. package/dist/flexi-form/components/OptionsMultiSelectField.svelte.d.ts +24 -0
  86. package/dist/flexi-form/components/OptionsSelectField.svelte +13 -0
  87. package/dist/flexi-form/components/OptionsSelectField.svelte.d.ts +24 -0
  88. package/dist/flexi-form/components/TextField.svelte +12 -0
  89. package/dist/flexi-form/components/TextField.svelte.d.ts +24 -0
  90. package/dist/flexi-form/components/UnitNumberField.svelte +12 -0
  91. package/dist/flexi-form/components/UnitNumberField.svelte.d.ts +24 -0
  92. package/dist/flexi-form/components/UnknownTypeField.svelte +5 -0
  93. package/dist/flexi-form/components/UnknownTypeField.svelte.d.ts +18 -0
  94. package/dist/flexi-form/containers/FlexiPanel.svelte +13 -0
  95. package/dist/flexi-form/containers/FlexiPanel.svelte.d.ts +33 -0
  96. package/dist/flexi-form/flexi_card/FlexiCard.d.ts +64 -0
  97. package/dist/flexi-form/flexi_card/FlexiCard.js +66 -0
  98. package/dist/flexi-form/flexi_card/FlexiCardPanel.svelte +57 -0
  99. package/dist/flexi-form/flexi_card/FlexiCardPanel.svelte.d.ts +22 -0
  100. package/dist/flexi-form/flexi_composite/FlexiComposite.d.ts +50 -0
  101. package/dist/flexi-form/flexi_composite/FlexiComposite.js +26 -0
  102. package/dist/flexi-form/flexi_composite/FlexiCompositePanel.svelte +42 -0
  103. package/dist/flexi-form/flexi_composite/FlexiCompositePanel.svelte.d.ts +25 -0
  104. package/dist/flexi-form/flexi_composite/README.md +50 -0
  105. package/dist/flexi-form/flexi_datasheet/FlexiDataSheet.d.ts +4 -0
  106. package/dist/flexi-form/flexi_datasheet/FlexiDataSheet.js +2 -0
  107. package/dist/flexi-form/flexi_field/FlexiField.d.ts +76 -0
  108. package/dist/flexi-form/flexi_field/FlexiField.js +128 -0
  109. package/dist/flexi-form/flexi_field/FlexiFieldCell.svelte +35 -0
  110. package/dist/flexi-form/flexi_field/FlexiFieldCell.svelte.d.ts +25 -0
  111. package/dist/flexi-form/flexi_field/UnknownField.d.ts +3 -0
  112. package/dist/flexi-form/flexi_field/UnknownField.js +3 -0
  113. package/dist/flexi-form/flexi_form/FlexiForm.d.ts +127 -0
  114. package/dist/flexi-form/flexi_form/FlexiForm.js +160 -0
  115. package/dist/flexi-form/flexi_form/FlexiFormPanel.svelte +57 -0
  116. package/dist/flexi-form/flexi_form/FlexiFormPanel.svelte.d.ts +25 -0
  117. package/dist/flexi-form/index.d.ts +11 -0
  118. package/dist/flexi-form/index.js +11 -0
  119. package/dist/flexi-form/lib/ComponentBuilder.d.ts +15 -0
  120. package/dist/flexi-form/lib/ComponentBuilder.js +31 -0
  121. package/dist/flexi-form/lib/index.d.ts +5 -0
  122. package/dist/flexi-form/lib/index.js +2 -0
  123. package/dist/flexi-form/lib/types.d.ts +7 -0
  124. package/dist/flexi-form/lib/types.js +6 -0
  125. package/dist/flexi-form/lib/utils.d.ts +10 -0
  126. package/dist/flexi-form/lib/utils.js +48 -0
  127. package/dist/i18n-res/i18nRes.d.ts +2 -0
  128. package/dist/i18n-res/i18nRes.js +8 -0
  129. package/dist/i18n-res/index.d.ts +2 -0
  130. package/dist/i18n-res/index.js +2 -0
  131. package/dist/index.d.ts +5 -0
  132. package/dist/index.js +5 -0
  133. package/dist/uniface-flexi-module.css +46 -0
  134. package/dist/utils.d.ts +4 -0
  135. package/dist/utils.js +8 -0
  136. package/package.json +135 -0
@@ -0,0 +1,1650 @@
1
+ # FlexiDataTable Complete Usage Guide
2
+
3
+ A comprehensive guide to using FlexiDataTable for building powerful, flexible data tables in Svelte applications.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Overview](#overview)
8
+ 2. [Basic Usage](#basic-usage)
9
+ 3. [Schema Structure](#schema-structure)
10
+ 4. [Column Types and Features](#column-types-and-features)
11
+ 5. [Advanced Table Features](#advanced-table-features)
12
+ 6. [Dynamic Data Handling](#dynamic-data-handling)
13
+ 7. [Events and Interactions](#events-and-interactions)
14
+ 8. [Extending FlexiDataTable](#extending-flexidatatable)
15
+ 9. [Integration Patterns](#integration-patterns)
16
+ 10. [Best Practices](#best-practices)
17
+
18
+ ## Overview
19
+
20
+ FlexiDataTable is a powerful data table component that provides flexible column configuration, sorting, filtering, pagination, and extensive customization options. It's built on top of the uniface-element DataTable with enhanced schema-driven configuration.
21
+
22
+ ### Key Features
23
+
24
+ - **Schema-Driven Configuration**: Define tables through JSON schemas
25
+ - **Flexible Column Types**: Data columns, indicator columns, and action columns
26
+ - **Custom Formatters**: Built-in and custom cell formatters
27
+ - **Sorting and Filtering**: Advanced data manipulation
28
+ - **Row Actions**: Configurable row-level actions
29
+ - **Responsive Design**: Adaptive layouts for different screen sizes
30
+ - **Extensible Architecture**: Custom renderers and formatters
31
+
32
+ ## Basic Usage
33
+
34
+ ### 1. Installation and Setup
35
+
36
+ ```typescript
37
+ import FlexiDataTable from '@ticatec/uniface-flexi-form/flexi-datatable';
38
+ import DataTable from '@ticatec/uniface-element/DataTable';
39
+ import '@ticatec/uniface-flexi-form/uniface-flexi-form.css';
40
+ ```
41
+
42
+ ### 2. Simple Data Table
43
+
44
+ ```svelte
45
+ <!-- SimpleDataTable.svelte -->
46
+ <script lang="ts">
47
+ import DataTable from '@ticatec/uniface-element/DataTable';
48
+ import FlexiDataTable from '@ticatec/uniface-flexi-form/flexi-datatable';
49
+
50
+ // Sample data
51
+ let tableData = [
52
+ { id: 1, name: 'John Doe', email: 'john@example.com', age: 30, status: 'active' },
53
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com', age: 25, status: 'active' },
54
+ { id: 3, name: 'Bob Johnson', email: 'bob@example.com', age: 35, status: 'inactive' }
55
+ ];
56
+
57
+ // Table schema
58
+ const tableSchema = {
59
+ round: true,
60
+ indicatorColumn: {
61
+ width: 60,
62
+ displayNo: true,
63
+ selectable: true
64
+ },
65
+ actionsColumn: {
66
+ width: 120,
67
+ align: 'center',
68
+ getActions: 'getRowActions'
69
+ },
70
+ columns: [
71
+ {
72
+ text: 'Name',
73
+ field: 'name',
74
+ width: 150,
75
+ align: 'left',
76
+ resizable: true
77
+ },
78
+ {
79
+ text: 'Email',
80
+ field: 'email',
81
+ width: 200,
82
+ align: 'left',
83
+ resizable: true
84
+ },
85
+ {
86
+ text: 'Age',
87
+ field: 'age',
88
+ width: 80,
89
+ align: 'center',
90
+ formatter: 'formatAge'
91
+ },
92
+ {
93
+ text: 'Status',
94
+ field: 'status',
95
+ width: 100,
96
+ align: 'center',
97
+ formatter: 'formatStatus'
98
+ }
99
+ ]
100
+ };
101
+
102
+ // Create FlexiDataTable instance
103
+ class UserDataTable extends FlexiDataTable {
104
+ constructor(schema) {
105
+ super(schema);
106
+ }
107
+
108
+ formatAge(value, row) {
109
+ return `${value} years`;
110
+ }
111
+
112
+ formatStatus(value, row) {
113
+ const statusClass = value === 'active' ? 'status-active' : 'status-inactive';
114
+ return `<span class="${statusClass}">${value.toUpperCase()}</span>`;
115
+ }
116
+
117
+ getRowActions(row) {
118
+ return [
119
+ {
120
+ text: 'Edit',
121
+ icon: 'edit',
122
+ handler: () => this.editUser(row)
123
+ },
124
+ {
125
+ text: 'Delete',
126
+ icon: 'delete',
127
+ handler: () => this.deleteUser(row),
128
+ confirm: 'Are you sure you want to delete this user?'
129
+ }
130
+ ];
131
+ }
132
+
133
+ editUser(row) {
134
+ console.log('Editing user:', row);
135
+ }
136
+
137
+ deleteUser(row) {
138
+ console.log('Deleting user:', row);
139
+ }
140
+ }
141
+
142
+ let dataTable = new UserDataTable(tableSchema);
143
+
144
+ onMount(async () => {
145
+ await dataTable.initialize();
146
+ });
147
+
148
+ function handleRowSelect(event) {
149
+ console.log('Selected rows:', event.detail.selectedRows);
150
+ }
151
+
152
+ function handleSort(event) {
153
+ console.log('Sort changed:', event.detail);
154
+ }
155
+ </script>
156
+
157
+ <div class="data-table-container">
158
+ <DataTable
159
+ columns={dataTable.columns}
160
+ data={tableData}
161
+ indicatorColumn={dataTable.indicatorColumn}
162
+ actionsColumn={dataTable.actionsColumn}
163
+ round={dataTable.round}
164
+ on:rowSelect={handleRowSelect}
165
+ on:sort={handleSort}
166
+ />
167
+ </div>
168
+
169
+ <style>
170
+ .data-table-container {
171
+ padding: 16px;
172
+ }
173
+
174
+ :global(.status-active) {
175
+ color: #28a745;
176
+ font-weight: bold;
177
+ }
178
+
179
+ :global(.status-inactive) {
180
+ color: #dc3545;
181
+ font-weight: bold;
182
+ }
183
+ </style>
184
+ ```
185
+
186
+ ## Schema Structure
187
+
188
+ ### FlexiDataTableSchema
189
+
190
+ ```typescript
191
+ interface FlexiDataTableSchema {
192
+ round?: boolean; // Rounded corners
193
+ indicatorColumn: IndicatorColumnSchema; // Row numbers/selection
194
+ actionsColumn?: ActionsColumnSchema; // Row actions
195
+ columns: Array<FlexiDataTableColumnSchema>; // Data columns
196
+ }
197
+ ```
198
+
199
+ ### FlexiDataTableColumnSchema
200
+
201
+ ```typescript
202
+ interface FlexiDataTableColumnSchema {
203
+ text: string; // Column header text
204
+ field?: string; // Data field name
205
+ frozen?: boolean; // Freeze column
206
+ align?: 'left' | 'center' | 'right'; // Text alignment
207
+ width: number; // Column width
208
+ minWidth?: number; // Minimum width
209
+ warp?: boolean; // Text wrapping
210
+ formatter?: string; // Formatter function name
211
+ escapeHTML?: boolean; // HTML escaping
212
+ href?: string; // Link function name
213
+ hint?: string; // Tooltip function name
214
+ render?: string; // Custom renderer name
215
+ visible?: boolean; // Column visibility
216
+ resizable?: boolean; // Column resizing
217
+ compareFunction?: string; // Custom sort function name
218
+ }
219
+ ```
220
+
221
+ ### IndicatorColumnSchema
222
+
223
+ ```typescript
224
+ interface IndicatorColumnSchema {
225
+ width: number; // Column width
226
+ displayNo?: boolean; // Show row numbers
227
+ selectable?: boolean; // Row selection
228
+ }
229
+ ```
230
+
231
+ ### ActionsColumnSchema
232
+
233
+ ```typescript
234
+ interface ActionsColumnSchema {
235
+ width: number; // Column width
236
+ align?: 'left' | 'center'; // Alignment
237
+ getActions: string; // Action provider function name
238
+ }
239
+ ```
240
+
241
+ ## Column Types and Features
242
+
243
+ ### Data Columns
244
+
245
+ ```typescript
246
+ // Basic data column
247
+ {
248
+ text: 'Product Name',
249
+ field: 'name',
250
+ width: 200,
251
+ align: 'left',
252
+ resizable: true
253
+ }
254
+
255
+ // Formatted column
256
+ {
257
+ text: 'Price',
258
+ field: 'price',
259
+ width: 100,
260
+ align: 'right',
261
+ formatter: 'formatCurrency'
262
+ }
263
+
264
+ // Linked column
265
+ {
266
+ text: 'Website',
267
+ field: 'website',
268
+ width: 150,
269
+ href: 'buildWebsiteLink'
270
+ }
271
+
272
+ // Custom rendered column
273
+ {
274
+ text: 'Status',
275
+ field: 'status',
276
+ width: 120,
277
+ render: 'renderStatusBadge'
278
+ }
279
+ ```
280
+
281
+ ### Column Examples with Implementations
282
+
283
+ ```typescript
284
+ class ProductDataTable extends FlexiDataTable {
285
+ constructor(schema: FlexiDataTableSchema) {
286
+ super(schema);
287
+ }
288
+
289
+ // Currency formatter
290
+ formatCurrency(value: number, row: any): string {
291
+ return new Intl.NumberFormat('en-US', {
292
+ style: 'currency',
293
+ currency: 'USD'
294
+ }).format(value);
295
+ }
296
+
297
+ // Date formatter
298
+ formatDate(value: string, row: any): string {
299
+ return new Date(value).toLocaleDateString();
300
+ }
301
+
302
+ // Percentage formatter
303
+ formatPercentage(value: number, row: any): string {
304
+ return `${(value * 100).toFixed(1)}%`;
305
+ }
306
+
307
+ // File size formatter
308
+ formatFileSize(bytes: number, row: any): string {
309
+ const sizes = ['B', 'KB', 'MB', 'GB'];
310
+ if (bytes === 0) return '0 B';
311
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
312
+ return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
313
+ }
314
+
315
+ // Link builder
316
+ buildWebsiteLink(value: string, row: any): string {
317
+ return value.startsWith('http') ? value : `https://${value}`;
318
+ }
319
+
320
+ // Product link builder
321
+ buildProductLink(value: string, row: any): string {
322
+ return `/products/${row.id}`;
323
+ }
324
+
325
+ // Tooltip provider
326
+ getProductHint(value: string, row: any): string {
327
+ return `Product ID: ${row.id}\\nCategory: ${row.category}\\nStock: ${row.stock}`;
328
+ }
329
+
330
+ // Status badge renderer
331
+ renderStatusBadge(value: string, row: any): any {
332
+ // Return Svelte component configuration
333
+ return {
334
+ component: StatusBadge,
335
+ props: {
336
+ status: value,
337
+ size: 'small'
338
+ }
339
+ };
340
+ }
341
+
342
+ // Image renderer
343
+ renderProductImage(value: string, row: any): any {
344
+ return {
345
+ component: ProductImage,
346
+ props: {
347
+ src: value,
348
+ alt: row.name,
349
+ size: 'thumbnail'
350
+ }
351
+ };
352
+ }
353
+
354
+ // Custom sort function
355
+ compareProductNames(a: any, b: any): number {
356
+ // Custom sorting logic for product names
357
+ const nameA = a.name.toLowerCase();
358
+ const nameB = b.name.toLowerCase();
359
+ return this.compareStrings(nameA, nameB, { ignoreCase: true });
360
+ }
361
+
362
+ // Numeric sort with null handling
363
+ compareStock(a: any, b: any): number {
364
+ const stockA = a.stock || 0;
365
+ const stockB = b.stock || 0;
366
+ return stockA - stockB;
367
+ }
368
+
369
+ // Row actions
370
+ getRowActions(row: any): any[] {
371
+ const actions = [
372
+ {
373
+ text: 'View',
374
+ icon: 'eye',
375
+ handler: () => this.viewProduct(row)
376
+ }
377
+ ];
378
+
379
+ if (row.status === 'active') {
380
+ actions.push({
381
+ text: 'Edit',
382
+ icon: 'edit',
383
+ handler: () => this.editProduct(row)
384
+ });
385
+ }
386
+
387
+ if (row.stock === 0) {
388
+ actions.push({
389
+ text: 'Restock',
390
+ icon: 'plus',
391
+ handler: () => this.restockProduct(row)
392
+ });
393
+ }
394
+
395
+ actions.push({
396
+ text: 'Delete',
397
+ icon: 'trash',
398
+ handler: () => this.deleteProduct(row),
399
+ confirm: 'Are you sure you want to delete this product?',
400
+ style: 'danger'
401
+ });
402
+
403
+ return actions;
404
+ }
405
+
406
+ viewProduct(row: any): void {
407
+ window.open(`/products/${row.id}`, '_blank');
408
+ }
409
+
410
+ editProduct(row: any): void {
411
+ // Navigate to edit page or open modal
412
+ console.log('Editing product:', row);
413
+ }
414
+
415
+ restockProduct(row: any): void {
416
+ // Open restock dialog
417
+ console.log('Restocking product:', row);
418
+ }
419
+
420
+ deleteProduct(row: any): void {
421
+ // Perform delete operation
422
+ console.log('Deleting product:', row);
423
+ }
424
+ }
425
+ ```
426
+
427
+ ## Advanced Table Features
428
+
429
+ ### Frozen Columns
430
+
431
+ ```typescript
432
+ const tableSchemaWithFrozenColumns = {
433
+ round: true,
434
+ indicatorColumn: {
435
+ width: 60,
436
+ displayNo: true,
437
+ selectable: true
438
+ },
439
+ columns: [
440
+ {
441
+ text: 'ID',
442
+ field: 'id',
443
+ width: 80,
444
+ frozen: true, // This column stays fixed
445
+ align: 'center'
446
+ },
447
+ {
448
+ text: 'Name',
449
+ field: 'name',
450
+ width: 150,
451
+ frozen: true, // This column also stays fixed
452
+ align: 'left'
453
+ },
454
+ {
455
+ text: 'Description',
456
+ field: 'description',
457
+ width: 300,
458
+ align: 'left',
459
+ warp: true // Allow text wrapping
460
+ },
461
+ {
462
+ text: 'Price',
463
+ field: 'price',
464
+ width: 100,
465
+ align: 'right',
466
+ formatter: 'formatCurrency'
467
+ }
468
+ ]
469
+ };
470
+ ```
471
+
472
+ ### Conditional Column Visibility
473
+
474
+ ```typescript
475
+ class ConditionalColumnsTable extends FlexiDataTable {
476
+ private userRole: string;
477
+
478
+ constructor(schema: FlexiDataTableSchema, userRole: string) {
479
+ super(schema);
480
+ this.userRole = userRole;
481
+ }
482
+
483
+ async initialize(): Promise<void> {
484
+ // Modify schema based on user role before initialization
485
+ this.applyRoleBasedVisibility();
486
+ await super.initialize();
487
+ }
488
+
489
+ private applyRoleBasedVisibility(): void {
490
+ this.schema.columns.forEach(column => {
491
+ switch (column.field) {
492
+ case 'salary':
493
+ column.visible = this.userRole === 'admin' || this.userRole === 'hr';
494
+ break;
495
+ case 'ssn':
496
+ column.visible = this.userRole === 'admin';
497
+ break;
498
+ case 'internalNotes':
499
+ column.visible = this.userRole !== 'guest';
500
+ break;
501
+ default:
502
+ column.visible = column.visible !== false;
503
+ }
504
+ });
505
+ }
506
+ }
507
+ ```
508
+
509
+ ### Dynamic Column Configuration
510
+
511
+ ```typescript
512
+ class DynamicColumnsTable extends FlexiDataTable {
513
+ constructor(schema: FlexiDataTableSchema) {
514
+ super(schema);
515
+ }
516
+
517
+ updateColumnConfiguration(newConfig: any): void {
518
+ // Update column widths
519
+ if (newConfig.columnWidths) {
520
+ this.columns.forEach((column, index) => {
521
+ if (newConfig.columnWidths[index]) {
522
+ column.width = newConfig.columnWidths[index];
523
+ }
524
+ });
525
+ }
526
+
527
+ // Update column visibility
528
+ if (newConfig.visibleColumns) {
529
+ this.columns.forEach((column, index) => {
530
+ column.visible = newConfig.visibleColumns.includes(index);
531
+ });
532
+ }
533
+
534
+ // Update column order
535
+ if (newConfig.columnOrder) {
536
+ const reorderedColumns = newConfig.columnOrder.map(index => this.columns[index]);
537
+ this.#columns = reorderedColumns;
538
+ }
539
+ }
540
+
541
+ getColumnConfiguration(): any {
542
+ return {
543
+ columnWidths: this.columns.map(col => col.width),
544
+ visibleColumns: this.columns
545
+ .map((col, index) => col.visible !== false ? index : null)
546
+ .filter(index => index !== null),
547
+ columnOrder: this.columns.map((_, index) => index)
548
+ };
549
+ }
550
+
551
+ saveColumnConfiguration(): void {
552
+ const config = this.getColumnConfiguration();
553
+ localStorage.setItem('table-config', JSON.stringify(config));
554
+ }
555
+
556
+ loadColumnConfiguration(): void {
557
+ try {
558
+ const config = JSON.parse(localStorage.getItem('table-config') || '{}');
559
+ this.updateColumnConfiguration(config);
560
+ } catch (error) {
561
+ console.error('Failed to load column configuration:', error);
562
+ }
563
+ }
564
+ }
565
+ ```
566
+
567
+ ## Dynamic Data Handling
568
+
569
+ ### Paginated Data Loading
570
+
571
+ ```typescript
572
+ class PaginatedDataTable extends FlexiDataTable {
573
+ private currentPage = 1;
574
+ private pageSize = 20;
575
+ private totalRecords = 0;
576
+ private loading = false;
577
+ private searchCriteria: any = {};
578
+
579
+ constructor(schema: FlexiDataTableSchema) {
580
+ super(schema);
581
+ }
582
+
583
+ async loadData(page: number = 1, criteria: any = {}): Promise<any[]> {
584
+ this.loading = true;
585
+ this.currentPage = page;
586
+ this.searchCriteria = criteria;
587
+
588
+ try {
589
+ const queryParams = new URLSearchParams({
590
+ page: page.toString(),
591
+ limit: this.pageSize.toString(),
592
+ ...this.flattenCriteria(criteria)
593
+ });
594
+
595
+ const response = await fetch(`/api/data?${queryParams}`);
596
+ const result = await response.json();
597
+
598
+ this.totalRecords = result.total;
599
+ return result.data;
600
+ } catch (error) {
601
+ console.error('Failed to load data:', error);
602
+ return [];
603
+ } finally {
604
+ this.loading = false;
605
+ }
606
+ }
607
+
608
+ async refreshData(): Promise<any[]> {
609
+ return this.loadData(this.currentPage, this.searchCriteria);
610
+ }
611
+
612
+ async nextPage(): Promise<any[]> {
613
+ if (this.hasNextPage()) {
614
+ return this.loadData(this.currentPage + 1, this.searchCriteria);
615
+ }
616
+ return [];
617
+ }
618
+
619
+ async previousPage(): Promise<any[]> {
620
+ if (this.hasPreviousPage()) {
621
+ return this.loadData(this.currentPage - 1, this.searchCriteria);
622
+ }
623
+ return [];
624
+ }
625
+
626
+ hasNextPage(): boolean {
627
+ return this.currentPage * this.pageSize < this.totalRecords;
628
+ }
629
+
630
+ hasPreviousPage(): boolean {
631
+ return this.currentPage > 1;
632
+ }
633
+
634
+ getCurrentPageInfo(): any {
635
+ const startRecord = (this.currentPage - 1) * this.pageSize + 1;
636
+ const endRecord = Math.min(this.currentPage * this.pageSize, this.totalRecords);
637
+
638
+ return {
639
+ currentPage: this.currentPage,
640
+ pageSize: this.pageSize,
641
+ totalRecords: this.totalRecords,
642
+ totalPages: Math.ceil(this.totalRecords / this.pageSize),
643
+ startRecord,
644
+ endRecord,
645
+ loading: this.loading
646
+ };
647
+ }
648
+
649
+ private flattenCriteria(criteria: any): Record<string, string> {
650
+ const flattened: Record<string, string> = {};
651
+ for (const [key, value] of Object.entries(criteria)) {
652
+ if (value !== null && value !== undefined && value !== '') {
653
+ flattened[key] = String(value);
654
+ }
655
+ }
656
+ return flattened;
657
+ }
658
+ }
659
+ ```
660
+
661
+ ### Real-time Data Updates
662
+
663
+ ```typescript
664
+ class RealtimeDataTable extends FlexiDataTable {
665
+ private websocket: WebSocket | null = null;
666
+ private updateCallback: ((data: any[]) => void) | null = null;
667
+
668
+ constructor(schema: FlexiDataTableSchema) {
669
+ super(schema);
670
+ }
671
+
672
+ connectRealtime(wsUrl: string, onUpdate: (data: any[]) => void): void {
673
+ this.updateCallback = onUpdate;
674
+ this.websocket = new WebSocket(wsUrl);
675
+
676
+ this.websocket.onopen = () => {
677
+ console.log('WebSocket connected');
678
+ };
679
+
680
+ this.websocket.onmessage = (event) => {
681
+ try {
682
+ const message = JSON.parse(event.data);
683
+ this.handleRealtimeUpdate(message);
684
+ } catch (error) {
685
+ console.error('Failed to parse WebSocket message:', error);
686
+ }
687
+ };
688
+
689
+ this.websocket.onclose = () => {
690
+ console.log('WebSocket disconnected');
691
+ // Attempt to reconnect
692
+ setTimeout(() => this.connectRealtime(wsUrl, onUpdate), 5000);
693
+ };
694
+
695
+ this.websocket.onerror = (error) => {
696
+ console.error('WebSocket error:', error);
697
+ };
698
+ }
699
+
700
+ private handleRealtimeUpdate(message: any): void {
701
+ switch (message.type) {
702
+ case 'data_updated':
703
+ this.updateCallback?.(message.data);
704
+ break;
705
+ case 'record_added':
706
+ this.handleRecordAdded(message.record);
707
+ break;
708
+ case 'record_updated':
709
+ this.handleRecordUpdated(message.record);
710
+ break;
711
+ case 'record_deleted':
712
+ this.handleRecordDeleted(message.recordId);
713
+ break;
714
+ }
715
+ }
716
+
717
+ private handleRecordAdded(record: any): void {
718
+ // Add record to current data and notify
719
+ console.log('New record added:', record);
720
+ }
721
+
722
+ private handleRecordUpdated(record: any): void {
723
+ // Update existing record and notify
724
+ console.log('Record updated:', record);
725
+ }
726
+
727
+ private handleRecordDeleted(recordId: string): void {
728
+ // Remove record from current data and notify
729
+ console.log('Record deleted:', recordId);
730
+ }
731
+
732
+ disconnect(): void {
733
+ if (this.websocket) {
734
+ this.websocket.close();
735
+ this.websocket = null;
736
+ }
737
+ }
738
+ }
739
+ ```
740
+
741
+ ## Events and Interactions
742
+
743
+ ### Table Events
744
+
745
+ ```svelte
746
+ <script>
747
+ function handleRowSelect(event) {
748
+ const { selectedRows, isAllSelected } = event.detail;
749
+ console.log('Selected rows:', selectedRows);
750
+ console.log('All selected:', isAllSelected);
751
+ }
752
+
753
+ function handleRowClick(event) {
754
+ const { row, rowIndex } = event.detail;
755
+ console.log('Row clicked:', row, 'at index:', rowIndex);
756
+ }
757
+
758
+ function handleRowDoubleClick(event) {
759
+ const { row, rowIndex } = event.detail;
760
+ console.log('Row double-clicked:', row);
761
+ // Open edit dialog or navigate to detail view
762
+ openEditDialog(row);
763
+ }
764
+
765
+ function handleSort(event) {
766
+ const { column, direction } = event.detail;
767
+ console.log('Sort changed:', column.field, direction);
768
+
769
+ // Perform sorting
770
+ if (direction === 'asc') {
771
+ tableData.sort((a, b) => a[column.field] > b[column.field] ? 1 : -1);
772
+ } else if (direction === 'desc') {
773
+ tableData.sort((a, b) => a[column.field] < b[column.field] ? 1 : -1);
774
+ }
775
+
776
+ // Trigger reactivity
777
+ tableData = [...tableData];
778
+ }
779
+
780
+ function handleColumnResize(event) {
781
+ const { column, newWidth } = event.detail;
782
+ console.log('Column resized:', column.field, 'new width:', newWidth);
783
+
784
+ // Save column configuration
785
+ dataTable.saveColumnConfiguration();
786
+ }
787
+
788
+ function handlePageChange(event) {
789
+ const { page, pageSize } = event.detail;
790
+ console.log('Page changed:', page, 'size:', pageSize);
791
+
792
+ // Load new page data
793
+ loadPageData(page, pageSize);
794
+ }
795
+ </script>
796
+
797
+ <DataTable
798
+ columns={dataTable.columns}
799
+ data={tableData}
800
+ indicatorColumn={dataTable.indicatorColumn}
801
+ actionsColumn={dataTable.actionsColumn}
802
+ on:rowSelect={handleRowSelect}
803
+ on:rowClick={handleRowClick}
804
+ on:rowDoubleClick={handleRowDoubleClick}
805
+ on:sort={handleSort}
806
+ on:columnResize={handleColumnResize}
807
+ on:pageChange={handlePageChange}
808
+ />
809
+ ```
810
+
811
+ ### Bulk Operations
812
+
813
+ ```typescript
814
+ class BulkOperationsTable extends FlexiDataTable {
815
+ private selectedRows: Set<any> = new Set();
816
+
817
+ constructor(schema: FlexiDataTableSchema) {
818
+ super(schema);
819
+ }
820
+
821
+ handleRowSelection(selectedRows: any[]): void {
822
+ this.selectedRows = new Set(selectedRows);
823
+ }
824
+
825
+ getBulkActions(): any[] {
826
+ if (this.selectedRows.size === 0) {
827
+ return [];
828
+ }
829
+
830
+ return [
831
+ {
832
+ text: `Delete ${this.selectedRows.size} items`,
833
+ icon: 'trash',
834
+ handler: () => this.bulkDelete(),
835
+ confirm: `Are you sure you want to delete ${this.selectedRows.size} items?`,
836
+ style: 'danger'
837
+ },
838
+ {
839
+ text: `Export ${this.selectedRows.size} items`,
840
+ icon: 'download',
841
+ handler: () => this.bulkExport()
842
+ },
843
+ {
844
+ text: 'Update Status',
845
+ icon: 'edit',
846
+ handler: () => this.bulkUpdateStatus()
847
+ }
848
+ ];
849
+ }
850
+
851
+ async bulkDelete(): Promise<void> {
852
+ try {
853
+ const ids = Array.from(this.selectedRows).map(row => row.id);
854
+ await fetch('/api/bulk/delete', {
855
+ method: 'POST',
856
+ headers: { 'Content-Type': 'application/json' },
857
+ body: JSON.stringify({ ids })
858
+ });
859
+
860
+ console.log('Bulk delete successful');
861
+ this.selectedRows.clear();
862
+ } catch (error) {
863
+ console.error('Bulk delete failed:', error);
864
+ }
865
+ }
866
+
867
+ async bulkExport(): Promise<void> {
868
+ try {
869
+ const data = Array.from(this.selectedRows);
870
+ const csv = this.convertToCSV(data);
871
+ this.downloadCSV(csv, 'export.csv');
872
+ } catch (error) {
873
+ console.error('Bulk export failed:', error);
874
+ }
875
+ }
876
+
877
+ async bulkUpdateStatus(): Promise<void> {
878
+ const newStatus = prompt('Enter new status:');
879
+ if (!newStatus) return;
880
+
881
+ try {
882
+ const ids = Array.from(this.selectedRows).map(row => row.id);
883
+ await fetch('/api/bulk/update-status', {
884
+ method: 'POST',
885
+ headers: { 'Content-Type': 'application/json' },
886
+ body: JSON.stringify({ ids, status: newStatus })
887
+ });
888
+
889
+ console.log('Bulk status update successful');
890
+ } catch (error) {
891
+ console.error('Bulk status update failed:', error);
892
+ }
893
+ }
894
+
895
+ private convertToCSV(data: any[]): string {
896
+ if (data.length === 0) return '';
897
+
898
+ const headers = Object.keys(data[0]);
899
+ const csvContent = [
900
+ headers.join(','),
901
+ ...data.map(row => headers.map(header => `"${row[header]}"`).join(','))
902
+ ].join('\\n');
903
+
904
+ return csvContent;
905
+ }
906
+
907
+ private downloadCSV(csv: string, filename: string): void {
908
+ const blob = new Blob([csv], { type: 'text/csv' });
909
+ const url = window.URL.createObjectURL(blob);
910
+ const link = document.createElement('a');
911
+ link.href = url;
912
+ link.download = filename;
913
+ link.click();
914
+ window.URL.revokeObjectURL(url);
915
+ }
916
+ }
917
+ ```
918
+
919
+ ## Extending FlexiDataTable
920
+
921
+ ### Custom Cell Renderers
922
+
923
+ ```svelte
924
+ <!-- StatusBadge.svelte -->
925
+ <script lang="ts">
926
+ export let status: string;
927
+ export let size: 'small' | 'medium' | 'large' = 'medium';
928
+
929
+ $: statusConfig = getStatusConfig(status);
930
+
931
+ function getStatusConfig(status: string) {
932
+ const configs = {
933
+ active: { color: '#28a745', background: '#d4edda', text: 'Active' },
934
+ inactive: { color: '#6c757d', background: '#e2e3e5', text: 'Inactive' },
935
+ pending: { color: '#ffc107', background: '#fff3cd', text: 'Pending' },
936
+ error: { color: '#dc3545', background: '#f8d7da', text: 'Error' }
937
+ };
938
+ return configs[status] || configs.inactive;
939
+ }
940
+ </script>
941
+
942
+ <span
943
+ class="status-badge {size}"
944
+ style="color: {statusConfig.color}; background-color: {statusConfig.background};"
945
+ >
946
+ {statusConfig.text}
947
+ </span>
948
+
949
+ <style>
950
+ .status-badge {
951
+ display: inline-block;
952
+ padding: 4px 8px;
953
+ border-radius: 12px;
954
+ font-weight: 600;
955
+ text-align: center;
956
+ white-space: nowrap;
957
+ }
958
+
959
+ .status-badge.small {
960
+ font-size: 0.75rem;
961
+ padding: 2px 6px;
962
+ }
963
+
964
+ .status-badge.medium {
965
+ font-size: 0.875rem;
966
+ padding: 4px 8px;
967
+ }
968
+
969
+ .status-badge.large {
970
+ font-size: 1rem;
971
+ padding: 6px 12px;
972
+ }
973
+ </style>
974
+ ```
975
+
976
+ ```svelte
977
+ <!-- ProgressBar.svelte -->
978
+ <script lang="ts">
979
+ export let value: number;
980
+ export let max: number = 100;
981
+ export let color: string = '#007bff';
982
+ export let showText: boolean = true;
983
+
984
+ $: percentage = Math.round((value / max) * 100);
985
+ </script>
986
+
987
+ <div class="progress-container">
988
+ <div class="progress-bar">
989
+ <div
990
+ class="progress-fill"
991
+ style="width: {percentage}%; background-color: {color};"
992
+ ></div>
993
+ </div>
994
+ {#if showText}
995
+ <span class="progress-text">{percentage}%</span>
996
+ {/if}
997
+ </div>
998
+
999
+ <style>
1000
+ .progress-container {
1001
+ display: flex;
1002
+ align-items: center;
1003
+ gap: 8px;
1004
+ width: 100%;
1005
+ }
1006
+
1007
+ .progress-bar {
1008
+ flex: 1;
1009
+ height: 8px;
1010
+ background-color: #e9ecef;
1011
+ border-radius: 4px;
1012
+ overflow: hidden;
1013
+ }
1014
+
1015
+ .progress-fill {
1016
+ height: 100%;
1017
+ transition: width 0.3s ease;
1018
+ }
1019
+
1020
+ .progress-text {
1021
+ font-size: 0.75rem;
1022
+ color: #6c757d;
1023
+ min-width: 35px;
1024
+ }
1025
+ </style>
1026
+ ```
1027
+
1028
+ ### Custom Table Classes
1029
+
1030
+ ```typescript
1031
+ class AdvancedProductTable extends FlexiDataTable {
1032
+ private productCategories: any[] = [];
1033
+ private userPermissions: string[] = [];
1034
+
1035
+ constructor(schema: FlexiDataTableSchema, userPermissions: string[]) {
1036
+ super(schema);
1037
+ this.userPermissions = userPermissions;
1038
+ }
1039
+
1040
+ async initialize(): Promise<void> {
1041
+ await this.loadProductCategories();
1042
+ this.setupPermissionBasedActions();
1043
+ await super.initialize();
1044
+ }
1045
+
1046
+ private async loadProductCategories(): Promise<void> {
1047
+ try {
1048
+ const response = await fetch('/api/product-categories');
1049
+ this.productCategories = await response.json();
1050
+ } catch (error) {
1051
+ console.error('Failed to load product categories:', error);
1052
+ }
1053
+ }
1054
+
1055
+ private setupPermissionBasedActions(): void {
1056
+ // Modify schema based on user permissions
1057
+ if (!this.userPermissions.includes('edit_products')) {
1058
+ // Remove edit-related columns or actions
1059
+ this.schema.columns = this.schema.columns.filter(col =>
1060
+ col.field !== 'editActions'
1061
+ );
1062
+ }
1063
+ }
1064
+
1065
+ // Advanced formatters
1066
+ formatProductCategory(categoryId: string, row: any): string {
1067
+ const category = this.productCategories.find(cat => cat.id === categoryId);
1068
+ return category ? category.name : 'Unknown Category';
1069
+ }
1070
+
1071
+ formatInventoryStatus(stock: number, row: any): string {
1072
+ if (stock === 0) {
1073
+ return '<span class="text-danger">Out of Stock</span>';
1074
+ } else if (stock < row.minimumStock) {
1075
+ return '<span class="text-warning">Low Stock</span>';
1076
+ } else {
1077
+ return '<span class="text-success">In Stock</span>';
1078
+ }
1079
+ }
1080
+
1081
+ formatPriceWithDiscount(price: number, row: any): string {
1082
+ if (row.discount > 0) {
1083
+ const discountedPrice = price * (1 - row.discount);
1084
+ return `
1085
+ <span class="original-price">$${price.toFixed(2)}</span>
1086
+ <span class="discounted-price">$${discountedPrice.toFixed(2)}</span>
1087
+ `;
1088
+ }
1089
+ return `$${price.toFixed(2)}`;
1090
+ }
1091
+
1092
+ // Advanced renderers
1093
+ renderProductImage(imageUrl: string, row: any): any {
1094
+ return {
1095
+ component: ProductImage,
1096
+ props: {
1097
+ src: imageUrl,
1098
+ alt: row.name,
1099
+ size: 'thumbnail',
1100
+ lazy: true,
1101
+ fallback: '/images/product-placeholder.png'
1102
+ }
1103
+ };
1104
+ }
1105
+
1106
+ renderStockLevel(stock: number, row: any): any {
1107
+ const percentage = Math.min((stock / row.maximumStock) * 100, 100);
1108
+ let color = '#28a745'; // Green
1109
+
1110
+ if (percentage < 20) {
1111
+ color = '#dc3545'; // Red
1112
+ } else if (percentage < 50) {
1113
+ color = '#ffc107'; // Yellow
1114
+ }
1115
+
1116
+ return {
1117
+ component: ProgressBar,
1118
+ props: {
1119
+ value: stock,
1120
+ max: row.maximumStock,
1121
+ color,
1122
+ showText: true
1123
+ }
1124
+ };
1125
+ }
1126
+
1127
+ renderRating(rating: number, row: any): any {
1128
+ return {
1129
+ component: StarRating,
1130
+ props: {
1131
+ rating,
1132
+ maxRating: 5,
1133
+ readonly: true,
1134
+ size: 'small'
1135
+ }
1136
+ };
1137
+ }
1138
+
1139
+ // Advanced actions
1140
+ getRowActions(row: any): any[] {
1141
+ const actions = [];
1142
+
1143
+ // Always available actions
1144
+ actions.push({
1145
+ text: 'View Details',
1146
+ icon: 'eye',
1147
+ handler: () => this.viewProductDetails(row)
1148
+ });
1149
+
1150
+ // Permission-based actions
1151
+ if (this.userPermissions.includes('edit_products')) {
1152
+ actions.push({
1153
+ text: 'Edit',
1154
+ icon: 'edit',
1155
+ handler: () => this.editProduct(row)
1156
+ });
1157
+ }
1158
+
1159
+ if (this.userPermissions.includes('manage_inventory')) {
1160
+ if (row.stock === 0) {
1161
+ actions.push({
1162
+ text: 'Restock',
1163
+ icon: 'plus-circle',
1164
+ handler: () => this.restockProduct(row)
1165
+ });
1166
+ }
1167
+
1168
+ actions.push({
1169
+ text: 'Adjust Stock',
1170
+ icon: 'warehouse',
1171
+ handler: () => this.adjustStock(row)
1172
+ });
1173
+ }
1174
+
1175
+ if (this.userPermissions.includes('delete_products')) {
1176
+ actions.push({
1177
+ text: 'Delete',
1178
+ icon: 'trash',
1179
+ handler: () => this.deleteProduct(row),
1180
+ confirm: 'Are you sure you want to delete this product?',
1181
+ style: 'danger'
1182
+ });
1183
+ }
1184
+
1185
+ return actions;
1186
+ }
1187
+
1188
+ // Action implementations
1189
+ viewProductDetails(row: any): void {
1190
+ window.open(`/products/${row.id}`, '_blank');
1191
+ }
1192
+
1193
+ editProduct(row: any): void {
1194
+ // Open edit modal or navigate to edit page
1195
+ const editEvent = new CustomEvent('edit-product', { detail: row });
1196
+ document.dispatchEvent(editEvent);
1197
+ }
1198
+
1199
+ restockProduct(row: any): void {
1200
+ // Open restock dialog
1201
+ const restockEvent = new CustomEvent('restock-product', { detail: row });
1202
+ document.dispatchEvent(restockEvent);
1203
+ }
1204
+
1205
+ adjustStock(row: any): void {
1206
+ // Open stock adjustment dialog
1207
+ const adjustEvent = new CustomEvent('adjust-stock', { detail: row });
1208
+ document.dispatchEvent(adjustEvent);
1209
+ }
1210
+
1211
+ deleteProduct(row: any): void {
1212
+ // Perform delete operation
1213
+ fetch(`/api/products/${row.id}`, { method: 'DELETE' })
1214
+ .then(() => {
1215
+ const deleteEvent = new CustomEvent('product-deleted', { detail: row });
1216
+ document.dispatchEvent(deleteEvent);
1217
+ })
1218
+ .catch(error => {
1219
+ console.error('Delete failed:', error);
1220
+ });
1221
+ }
1222
+
1223
+ // Advanced sorting
1224
+ compareProductNames(a: any, b: any): number {
1225
+ return this.compareStrings(a.name, b.name, { ignoreCase: true });
1226
+ }
1227
+
1228
+ compareByCategory(a: any, b: any): number {
1229
+ const catA = this.formatProductCategory(a.categoryId, a);
1230
+ const catB = this.formatProductCategory(b.categoryId, b);
1231
+ return this.compareStrings(catA, catB);
1232
+ }
1233
+
1234
+ compareByStockLevel(a: any, b: any): number {
1235
+ const levelA = a.stock / a.maximumStock;
1236
+ const levelB = b.stock / b.maximumStock;
1237
+ return levelA - levelB;
1238
+ }
1239
+ }
1240
+ ```
1241
+
1242
+ ## Integration Patterns
1243
+
1244
+ ### Table with Search Integration
1245
+
1246
+ ```svelte
1247
+ <!-- ProductManagement.svelte -->
1248
+ <script lang="ts">
1249
+ import CriteriaPanel from '@ticatec/uniface-flexi-form/criteria-panel';
1250
+ import DataTable from '@ticatec/uniface-element/DataTable';
1251
+ import { AdvancedProductTable } from './AdvancedProductTable';
1252
+
1253
+ let searchCriteria = {};
1254
+ let tableData = [];
1255
+ let totalRecords = 0;
1256
+ let currentPage = 1;
1257
+ let pageSize = 20;
1258
+ let loading = false;
1259
+
1260
+ const searchSchema = {
1261
+ arrangement: 'horizontal',
1262
+ fields: [
1263
+ {
1264
+ type: 'text-search',
1265
+ name: 'name',
1266
+ label: 'Product Name',
1267
+ keys: { field: 'name' },
1268
+ size: 'x25'
1269
+ },
1270
+ {
1271
+ type: 'option-select-search',
1272
+ name: 'category',
1273
+ label: 'Category',
1274
+ keys: { field: 'categoryId' },
1275
+ dictName: 'categories',
1276
+ size: 'x20'
1277
+ },
1278
+ {
1279
+ type: 'number-range',
1280
+ name: 'price',
1281
+ label: 'Price Range',
1282
+ keys: { minField: 'priceMin', maxField: 'priceMax' },
1283
+ size: 'x25'
1284
+ },
1285
+ {
1286
+ type: 'option-select-search',
1287
+ name: 'status',
1288
+ label: 'Status',
1289
+ keys: { field: 'status' },
1290
+ dictName: 'product-status',
1291
+ size: 'x15'
1292
+ }
1293
+ ]
1294
+ };
1295
+
1296
+ const tableSchema = {
1297
+ round: true,
1298
+ indicatorColumn: {
1299
+ width: 60,
1300
+ displayNo: true,
1301
+ selectable: true
1302
+ },
1303
+ actionsColumn: {
1304
+ width: 150,
1305
+ align: 'center',
1306
+ getActions: 'getRowActions'
1307
+ },
1308
+ columns: [
1309
+ {
1310
+ text: 'Image',
1311
+ field: 'imageUrl',
1312
+ width: 80,
1313
+ align: 'center',
1314
+ render: 'renderProductImage'
1315
+ },
1316
+ {
1317
+ text: 'Name',
1318
+ field: 'name',
1319
+ width: 200,
1320
+ align: 'left',
1321
+ resizable: true,
1322
+ compareFunction: 'compareProductNames'
1323
+ },
1324
+ {
1325
+ text: 'Category',
1326
+ field: 'categoryId',
1327
+ width: 150,
1328
+ formatter: 'formatProductCategory',
1329
+ compareFunction: 'compareByCategory'
1330
+ },
1331
+ {
1332
+ text: 'Price',
1333
+ field: 'price',
1334
+ width: 120,
1335
+ align: 'right',
1336
+ formatter: 'formatPriceWithDiscount'
1337
+ },
1338
+ {
1339
+ text: 'Stock',
1340
+ field: 'stock',
1341
+ width: 100,
1342
+ align: 'center',
1343
+ render: 'renderStockLevel',
1344
+ compareFunction: 'compareByStockLevel'
1345
+ },
1346
+ {
1347
+ text: 'Rating',
1348
+ field: 'rating',
1349
+ width: 120,
1350
+ align: 'center',
1351
+ render: 'renderRating'
1352
+ },
1353
+ {
1354
+ text: 'Status',
1355
+ field: 'status',
1356
+ width: 100,
1357
+ align: 'center',
1358
+ formatter: 'formatInventoryStatus'
1359
+ }
1360
+ ]
1361
+ };
1362
+
1363
+ let productTable = new AdvancedProductTable(tableSchema, ['edit_products', 'manage_inventory']);
1364
+
1365
+ onMount(async () => {
1366
+ await productTable.initialize();
1367
+ await loadData();
1368
+ });
1369
+
1370
+ async function loadData() {
1371
+ loading = true;
1372
+ try {
1373
+ const queryParams = new URLSearchParams({
1374
+ page: currentPage.toString(),
1375
+ limit: pageSize.toString(),
1376
+ ...flattenCriteria(searchCriteria)
1377
+ });
1378
+
1379
+ const response = await fetch(`/api/products?${queryParams}`);
1380
+ const result = await response.json();
1381
+
1382
+ tableData = result.data;
1383
+ totalRecords = result.total;
1384
+ } catch (error) {
1385
+ console.error('Failed to load products:', error);
1386
+ } finally {
1387
+ loading = false;
1388
+ }
1389
+ }
1390
+
1391
+ function flattenCriteria(criteria) {
1392
+ const flattened = {};
1393
+ for (const [key, value] of Object.entries(criteria)) {
1394
+ if (value !== null && value !== undefined && value !== '') {
1395
+ flattened[key] = value;
1396
+ }
1397
+ }
1398
+ return flattened;
1399
+ }
1400
+
1401
+ function handleCriteriaChange(event) {
1402
+ searchCriteria = event.detail.criteria;
1403
+ currentPage = 1;
1404
+ loadData();
1405
+ }
1406
+
1407
+ function handlePageChange(event) {
1408
+ currentPage = event.detail.page;
1409
+ pageSize = event.detail.pageSize;
1410
+ loadData();
1411
+ }
1412
+
1413
+ function handleSort(event) {
1414
+ console.log('Sort:', event.detail);
1415
+ loadData();
1416
+ }
1417
+
1418
+ // Listen for custom events from table actions
1419
+ onMount(() => {
1420
+ document.addEventListener('edit-product', handleEditProduct);
1421
+ document.addEventListener('restock-product', handleRestockProduct);
1422
+ document.addEventListener('product-deleted', handleProductDeleted);
1423
+
1424
+ return () => {
1425
+ document.removeEventListener('edit-product', handleEditProduct);
1426
+ document.removeEventListener('restock-product', handleRestockProduct);
1427
+ document.removeEventListener('product-deleted', handleProductDeleted);
1428
+ };
1429
+ });
1430
+
1431
+ function handleEditProduct(event) {
1432
+ console.log('Edit product:', event.detail);
1433
+ // Open edit modal or navigate to edit page
1434
+ }
1435
+
1436
+ function handleRestockProduct(event) {
1437
+ console.log('Restock product:', event.detail);
1438
+ // Open restock modal
1439
+ }
1440
+
1441
+ function handleProductDeleted(event) {
1442
+ console.log('Product deleted:', event.detail);
1443
+ // Refresh table data
1444
+ loadData();
1445
+ }
1446
+ </script>
1447
+
1448
+ <div class="product-management">
1449
+ <div class="search-section">
1450
+ <h3>Product Search</h3>
1451
+ <CriteriaPanel
1452
+ schema={searchSchema}
1453
+ bind:criteria={searchCriteria}
1454
+ on:change={handleCriteriaChange}
1455
+ />
1456
+ </div>
1457
+
1458
+ <div class="table-section">
1459
+ <div class="table-header">
1460
+ <h3>Products ({totalRecords} items)</h3>
1461
+ {#if loading}
1462
+ <div class="loading">Loading...</div>
1463
+ {/if}
1464
+ </div>
1465
+
1466
+ <DataTable
1467
+ columns={productTable.columns}
1468
+ data={tableData}
1469
+ indicatorColumn={productTable.indicatorColumn}
1470
+ actionsColumn={productTable.actionsColumn}
1471
+ round={productTable.round}
1472
+ {totalRecords}
1473
+ {currentPage}
1474
+ {pageSize}
1475
+ on:pageChange={handlePageChange}
1476
+ on:sort={handleSort}
1477
+ />
1478
+ </div>
1479
+ </div>
1480
+
1481
+ <style>
1482
+ .product-management {
1483
+ display: flex;
1484
+ flex-direction: column;
1485
+ gap: 24px;
1486
+ padding: 16px;
1487
+ }
1488
+
1489
+ .search-section {
1490
+ background: #f8f9fa;
1491
+ padding: 16px;
1492
+ border-radius: 8px;
1493
+ border: 1px solid #dee2e6;
1494
+ }
1495
+
1496
+ .table-section {
1497
+ flex: 1;
1498
+ }
1499
+
1500
+ .table-header {
1501
+ display: flex;
1502
+ justify-content: space-between;
1503
+ align-items: center;
1504
+ margin-bottom: 16px;
1505
+ }
1506
+
1507
+ .loading {
1508
+ color: #6c757d;
1509
+ font-style: italic;
1510
+ }
1511
+ </style>
1512
+ ```
1513
+
1514
+ ## Best Practices
1515
+
1516
+ ### 1. Performance Optimization
1517
+
1518
+ ```typescript
1519
+ // Optimize large datasets with virtual scrolling
1520
+ class VirtualizedTable extends FlexiDataTable {
1521
+ private virtualItemHeight = 40;
1522
+ private visibleItemCount = 20;
1523
+
1524
+ getVirtualizedProps() {
1525
+ return {
1526
+ itemHeight: this.virtualItemHeight,
1527
+ visibleCount: this.visibleItemCount,
1528
+ overscan: 5
1529
+ };
1530
+ }
1531
+ }
1532
+
1533
+ // Debounce search and filter operations
1534
+ class OptimizedTable extends FlexiDataTable {
1535
+ private searchDebounce = 300;
1536
+
1537
+ createDebouncedSearch(searchFn: Function) {
1538
+ let timeoutId: number;
1539
+ return (...args: any[]) => {
1540
+ clearTimeout(timeoutId);
1541
+ timeoutId = setTimeout(() => searchFn(...args), this.searchDebounce);
1542
+ };
1543
+ }
1544
+ }
1545
+ ```
1546
+
1547
+ ### 2. Accessibility
1548
+
1549
+ ```typescript
1550
+ const accessibleTableSchema = {
1551
+ round: true,
1552
+ indicatorColumn: {
1553
+ width: 60,
1554
+ displayNo: true,
1555
+ selectable: true
1556
+ },
1557
+ columns: [
1558
+ {
1559
+ text: 'Product Name',
1560
+ field: 'name',
1561
+ width: 200,
1562
+ // Add ARIA attributes for screen readers
1563
+ 'aria-label': 'Product name, sortable column',
1564
+ 'aria-sort': 'none'
1565
+ }
1566
+ ]
1567
+ };
1568
+ ```
1569
+
1570
+ ### 3. Error Handling
1571
+
1572
+ ```typescript
1573
+ class RobustDataTable extends FlexiDataTable {
1574
+ handleError(error: any, context: string): void {
1575
+ console.error(`Table error in ${context}:`, error);
1576
+
1577
+ // Show user-friendly error message
1578
+ this.showErrorMessage(`Failed to ${context}. Please try again.`);
1579
+
1580
+ // Log error for monitoring
1581
+ this.logError(error, context);
1582
+ }
1583
+
1584
+ private showErrorMessage(message: string): void {
1585
+ // Implement user notification
1586
+ console.log('User message:', message);
1587
+ }
1588
+
1589
+ private logError(error: any, context: string): void {
1590
+ // Send error to monitoring service
1591
+ fetch('/api/errors', {
1592
+ method: 'POST',
1593
+ headers: { 'Content-Type': 'application/json' },
1594
+ body: JSON.stringify({
1595
+ error: error.message,
1596
+ context,
1597
+ timestamp: new Date().toISOString()
1598
+ })
1599
+ });
1600
+ }
1601
+ }
1602
+ ```
1603
+
1604
+ ### 4. State Management
1605
+
1606
+ ```typescript
1607
+ class StatefulDataTable extends FlexiDataTable {
1608
+ private state = {
1609
+ sortColumn: null,
1610
+ sortDirection: 'asc',
1611
+ selectedRows: [],
1612
+ columnWidths: {},
1613
+ hiddenColumns: []
1614
+ };
1615
+
1616
+ saveState(): void {
1617
+ localStorage.setItem('table-state', JSON.stringify(this.state));
1618
+ }
1619
+
1620
+ loadState(): void {
1621
+ try {
1622
+ const saved = localStorage.getItem('table-state');
1623
+ if (saved) {
1624
+ this.state = { ...this.state, ...JSON.parse(saved) };
1625
+ this.applyState();
1626
+ }
1627
+ } catch (error) {
1628
+ console.error('Failed to load table state:', error);
1629
+ }
1630
+ }
1631
+
1632
+ private applyState(): void {
1633
+ // Apply saved column widths
1634
+ this.columns.forEach((column, index) => {
1635
+ if (this.state.columnWidths[index]) {
1636
+ column.width = this.state.columnWidths[index];
1637
+ }
1638
+ });
1639
+
1640
+ // Apply column visibility
1641
+ this.state.hiddenColumns.forEach(index => {
1642
+ if (this.columns[index]) {
1643
+ this.columns[index].visible = false;
1644
+ }
1645
+ });
1646
+ }
1647
+ }
1648
+ ```
1649
+
1650
+ This comprehensive guide provides everything needed to effectively use and extend FlexiDataTable for building sophisticated data management interfaces. The examples show how to create powerful, interactive tables with advanced features like real-time updates, custom rendering, and complex data operations.