@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,1559 @@
1
+ # FlexiCriteriaSet Complete Usage Guide
2
+
3
+ A comprehensive guide to using FlexiCriteriaSet for building dynamic search and filtering panels 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. [Criteria Field Types](#criteria-field-types)
11
+ 5. [Advanced Search Patterns](#advanced-search-patterns)
12
+ 6. [Dynamic Criteria Generation](#dynamic-criteria-generation)
13
+ 7. [Events and Interactions](#events-and-interactions)
14
+ 8. [Extending FlexiCriteriaSet](#extending-flexicriteriaset)
15
+ 9. [Integration Patterns](#integration-patterns)
16
+ 10. [Best Practices](#best-practices)
17
+
18
+ ## Overview
19
+
20
+ FlexiCriteriaSet is a powerful search criteria builder that allows users to construct complex search queries through an intuitive interface. It supports various field types, operators, and provides extensible architecture for custom search components.
21
+
22
+ ### Key Features
23
+
24
+ - **Flexible Search Fields**: Support for text, number, date, select, and range searches
25
+ - **Dynamic Operators**: Context-aware operators for different field types
26
+ - **Schema-Based Configuration**: JSON-driven criteria panel setup
27
+ - **Extensible Architecture**: Custom field types and search logic
28
+ - **Type Safety**: Full TypeScript support
29
+ - **Real-time Updates**: Immediate search criteria generation
30
+
31
+ ## Basic Usage
32
+
33
+ ### 1. Installation and Setup
34
+
35
+ ```typescript
36
+ import CriteriaPanel, { FlexiCriteriaSet } from '@ticatec/uniface-flexi-form/criteria-panel';
37
+ import '@ticatec/uniface-flexi-form/uniface-flexi-form.css';
38
+ ```
39
+
40
+ ### 2. Simple Criteria Panel
41
+
42
+ ```svelte
43
+ <!-- SimpleCriteriaPanel.svelte -->
44
+ <script lang="ts">
45
+ import CriteriaPanel from '@ticatec/uniface-flexi-form/criteria-panel';
46
+
47
+ // Search criteria data
48
+ let searchCriteria = {};
49
+
50
+ // Criteria panel schema
51
+ const userSearchSchema = {
52
+ arrangement: 'vertical',
53
+ variant: 'outlined',
54
+ fields: [
55
+ {
56
+ type: 'text-search',
57
+ name: 'userName',
58
+ label: 'User Name',
59
+ keys: { field: 'userName' },
60
+ size: 'x25',
61
+ props: {
62
+ placeholder: 'Enter user name'
63
+ }
64
+ },
65
+ {
66
+ type: 'option-select-search',
67
+ name: 'status',
68
+ label: 'Status',
69
+ keys: { field: 'status' },
70
+ dictName: 'user-status',
71
+ size: 'x20',
72
+ props: {
73
+ placeholder: 'Select status'
74
+ }
75
+ },
76
+ {
77
+ type: 'date-range',
78
+ name: 'registrationDate',
79
+ label: 'Registration Date',
80
+ keys: {
81
+ fromField: 'registeredFrom',
82
+ toField: 'registeredTo'
83
+ },
84
+ size: 'x30'
85
+ },
86
+ {
87
+ type: 'number-range',
88
+ name: 'ageRange',
89
+ label: 'Age Range',
90
+ keys: {
91
+ minField: 'ageMin',
92
+ maxField: 'ageMax'
93
+ },
94
+ size: 'x25'
95
+ }
96
+ ]
97
+ };
98
+
99
+ function handleCriteriaChange(event) {
100
+ searchCriteria = event.detail.criteria;
101
+ console.log('Search criteria updated:', searchCriteria);
102
+ performSearch(searchCriteria);
103
+ }
104
+
105
+ function handleSearch(event) {
106
+ const criteria = event.detail.criteria;
107
+ console.log('Search triggered with criteria:', criteria);
108
+ executeSearch(criteria);
109
+ }
110
+
111
+ function handleReset() {
112
+ searchCriteria = {};
113
+ console.log('Search criteria reset');
114
+ }
115
+
116
+ async function performSearch(criteria) {
117
+ // Implement your search logic
118
+ try {
119
+ const results = await searchAPI(criteria);
120
+ console.log('Search results:', results);
121
+ } catch (error) {
122
+ console.error('Search failed:', error);
123
+ }
124
+ }
125
+ </script>
126
+
127
+ <div class="search-panel">
128
+ <h3>User Search</h3>
129
+ <CriteriaPanel
130
+ schema={userSearchSchema}
131
+ bind:criteria={searchCriteria}
132
+ on:change={handleCriteriaChange}
133
+ on:search={handleSearch}
134
+ on:reset={handleReset}
135
+ />
136
+ </div>
137
+
138
+ <style>
139
+ .search-panel {
140
+ padding: 16px;
141
+ border: 1px solid #ddd;
142
+ border-radius: 8px;
143
+ }
144
+ </style>
145
+ ```
146
+
147
+ ## Schema Structure
148
+
149
+ ### FlexiCriteriaSetSchema
150
+
151
+ ```typescript
152
+ interface FlexiCriteriaSetSchema {
153
+ class?: string; // CSS class for styling
154
+ variant?: 'outlined' | 'filled'; // Field variant
155
+ arrangement?: 'vertical' | 'horizontal'; // Layout direction
156
+ label$style?: string; // Label styling
157
+ fields: Array<FlexiCriteriaFieldSchema>; // Search fields
158
+ }
159
+ ```
160
+
161
+ ### FlexiCriteriaFieldSchema
162
+
163
+ ```typescript
164
+ interface FlexiCriteriaFieldSchema {
165
+ type: string; // Field type identifier
166
+ name?: string; // Field name
167
+ label: string; // Display label
168
+ keys: { [key: string]: string }; // Data binding keys
169
+ dictName?: string; // Dictionary for options
170
+ visible?: boolean; // Field visibility
171
+ size?: 'x15' | 'x20' | 'x25' | 'x30' | 'x35' | 'x40'; // Field width
172
+ events?: Record<string, string>; // Event handlers
173
+ props?: any; // Field-specific properties
174
+ }
175
+ ```
176
+
177
+ ## Criteria Field Types
178
+
179
+ ### Built-in Field Types
180
+
181
+ | Type | Description | Keys Required | Common Props |
182
+ |------|-------------|---------------|--------------|
183
+ | `text-search` | Text search field | `field` | `placeholder`, `operators` |
184
+ | `number-search` | Numeric search field | `field` | `min`, `max`, `operators` |
185
+ | `number-range` | Number range search | `minField`, `maxField` | `min`, `max`, `step` |
186
+ | `date-search` | Date search field | `field` | `min`, `max`, `format` |
187
+ | `date-range` | Date range search | `fromField`, `toField` | `min`, `max`, `format` |
188
+ | `datetime-search` | DateTime search | `field` | `min`, `max`, `format` |
189
+ | `option-select-search` | Single select search | `field` | `dictName`, `placeholder` |
190
+ | `option-multi-select-search` | Multi-select search | `field` | `dictName`, `maxSelections` |
191
+ | `input-option-select-search` | Searchable select | `field` | `getOptions`, `minLength` |
192
+ | `cascade-select-search` | Cascade select search | `field` | `dictName`, `levels` |
193
+
194
+ ### Field Examples
195
+
196
+ ```typescript
197
+ // Text search with operators
198
+ {
199
+ type: 'text-search',
200
+ name: 'productName',
201
+ label: 'Product Name',
202
+ keys: { field: 'name' },
203
+ size: 'x30',
204
+ props: {
205
+ placeholder: 'Enter product name',
206
+ operators: ['contains', 'startsWith', 'endsWith', 'equals']
207
+ }
208
+ }
209
+
210
+ // Number range search
211
+ {
212
+ type: 'number-range',
213
+ name: 'priceRange',
214
+ label: 'Price Range',
215
+ keys: {
216
+ minField: 'priceMin',
217
+ maxField: 'priceMax'
218
+ },
219
+ size: 'x25',
220
+ props: {
221
+ min: 0,
222
+ max: 10000,
223
+ step: 10,
224
+ currency: 'USD'
225
+ }
226
+ }
227
+
228
+ // Date range with constraints
229
+ {
230
+ type: 'date-range',
231
+ name: 'orderDate',
232
+ label: 'Order Date',
233
+ keys: {
234
+ fromField: 'orderDateFrom',
235
+ toField: 'orderDateTo'
236
+ },
237
+ size: 'x30',
238
+ props: {
239
+ min: '2020-01-01',
240
+ max: new Date().toISOString().split('T')[0],
241
+ format: 'YYYY-MM-DD'
242
+ }
243
+ }
244
+
245
+ // Multi-select with dictionary
246
+ {
247
+ type: 'option-multi-select-search',
248
+ name: 'categories',
249
+ label: 'Categories',
250
+ keys: { field: 'categoryIds' },
251
+ dictName: 'product-categories',
252
+ size: 'x25',
253
+ props: {
254
+ maxSelections: 5,
255
+ placeholder: 'Select categories'
256
+ }
257
+ }
258
+
259
+ // Dynamic searchable select
260
+ {
261
+ type: 'input-option-select-search',
262
+ name: 'assignee',
263
+ label: 'Assigned To',
264
+ keys: { field: 'assigneeId' },
265
+ size: 'x25',
266
+ props: {
267
+ getOptions: 'loadUserSuggestions',
268
+ minLength: 2,
269
+ debounce: 300,
270
+ placeholder: 'Type to search users'
271
+ }
272
+ }
273
+ ```
274
+
275
+ ## Advanced Search Patterns
276
+
277
+ ### Complex Search Builder
278
+
279
+ ```typescript
280
+ class AdvancedSearchBuilder {
281
+ private schema: FlexiCriteriaSetSchema;
282
+
283
+ constructor() {
284
+ this.schema = {
285
+ arrangement: 'vertical',
286
+ variant: 'outlined',
287
+ fields: []
288
+ };
289
+ }
290
+
291
+ addTextField(name: string, label: string, options: any = {}): this {
292
+ this.schema.fields.push({
293
+ type: 'text-search',
294
+ name,
295
+ label,
296
+ keys: { field: options.field || name },
297
+ size: options.size || 'x25',
298
+ props: {
299
+ placeholder: options.placeholder || `Enter ${label.toLowerCase()}`,
300
+ operators: options.operators || ['contains', 'equals']
301
+ }
302
+ });
303
+ return this;
304
+ }
305
+
306
+ addSelectField(name: string, label: string, dictName: string, options: any = {}): this {
307
+ this.schema.fields.push({
308
+ type: options.multi ? 'option-multi-select-search' : 'option-select-search',
309
+ name,
310
+ label,
311
+ keys: { field: options.field || name },
312
+ dictName,
313
+ size: options.size || 'x20',
314
+ props: {
315
+ placeholder: options.placeholder || `Select ${label.toLowerCase()}`,
316
+ maxSelections: options.maxSelections
317
+ }
318
+ });
319
+ return this;
320
+ }
321
+
322
+ addDateRangeField(name: string, label: string, options: any = {}): this {
323
+ this.schema.fields.push({
324
+ type: 'date-range',
325
+ name,
326
+ label,
327
+ keys: {
328
+ fromField: options.fromField || `${name}From`,
329
+ toField: options.toField || `${name}To`
330
+ },
331
+ size: options.size || 'x30',
332
+ props: {
333
+ min: options.min,
334
+ max: options.max,
335
+ format: options.format || 'YYYY-MM-DD'
336
+ }
337
+ });
338
+ return this;
339
+ }
340
+
341
+ addNumberRangeField(name: string, label: string, options: any = {}): this {
342
+ this.schema.fields.push({
343
+ type: 'number-range',
344
+ name,
345
+ label,
346
+ keys: {
347
+ minField: options.minField || `${name}Min`,
348
+ maxField: options.maxField || `${name}Max`
349
+ },
350
+ size: options.size || 'x25',
351
+ props: {
352
+ min: options.min,
353
+ max: options.max,
354
+ step: options.step,
355
+ precision: options.precision
356
+ }
357
+ });
358
+ return this;
359
+ }
360
+
361
+ build(): FlexiCriteriaSetSchema {
362
+ return this.schema;
363
+ }
364
+ }
365
+
366
+ // Usage
367
+ const productSearchSchema = new AdvancedSearchBuilder()
368
+ .addTextField('name', 'Product Name', {
369
+ operators: ['contains', 'startsWith']
370
+ })
371
+ .addTextField('sku', 'SKU', {
372
+ operators: ['equals', 'startsWith']
373
+ })
374
+ .addSelectField('category', 'Category', 'product-categories')
375
+ .addSelectField('tags', 'Tags', 'product-tags', {
376
+ multi: true,
377
+ maxSelections: 3
378
+ })
379
+ .addNumberRangeField('price', 'Price Range', {
380
+ min: 0,
381
+ step: 0.01
382
+ })
383
+ .addDateRangeField('createdDate', 'Created Date')
384
+ .build();
385
+ ```
386
+
387
+ ### Conditional Criteria Fields
388
+
389
+ ```typescript
390
+ class ConditionalCriteriaSet extends FlexiCriteriaSet {
391
+ private conditionalFields = new Map<string, string[]>();
392
+
393
+ constructor(schema: FlexiCriteriaSetSchema) {
394
+ super(schema);
395
+ this.setupConditionalLogic();
396
+ }
397
+
398
+ private setupConditionalLogic(): void {
399
+ // Define conditional relationships
400
+ this.conditionalFields.set('searchType', ['specificFields']);
401
+
402
+ // Setup change handlers
403
+ this.setupFieldChangeHandlers();
404
+ }
405
+
406
+ private setupFieldChangeHandlers(): void {
407
+ const searchTypeField = this.field['searchType'];
408
+ if (searchTypeField) {
409
+ searchTypeField.events.change = (value: string) => {
410
+ this.updateConditionalFields(value);
411
+ };
412
+ }
413
+ }
414
+
415
+ private updateConditionalFields(searchType: string): void {
416
+ const showAdvanced = searchType === 'advanced';
417
+
418
+ // Show/hide fields based on search type
419
+ this.fields.forEach(field => {
420
+ if (field.name?.startsWith('advanced_')) {
421
+ field.visible = showAdvanced;
422
+ }
423
+ });
424
+
425
+ this.invalidate();
426
+ }
427
+ }
428
+
429
+ // Usage
430
+ const conditionalSchema = {
431
+ arrangement: 'vertical',
432
+ fields: [
433
+ {
434
+ type: 'option-select-search',
435
+ name: 'searchType',
436
+ label: 'Search Type',
437
+ keys: { field: 'type' },
438
+ props: {
439
+ options: [
440
+ { value: 'basic', label: 'Basic Search' },
441
+ { value: 'advanced', label: 'Advanced Search' }
442
+ ]
443
+ }
444
+ },
445
+ {
446
+ type: 'text-search',
447
+ name: 'basicQuery',
448
+ label: 'Search Query',
449
+ keys: { field: 'query' },
450
+ visible: true
451
+ },
452
+ {
453
+ type: 'text-search',
454
+ name: 'advanced_title',
455
+ label: 'Title',
456
+ keys: { field: 'title' },
457
+ visible: false
458
+ },
459
+ {
460
+ type: 'text-search',
461
+ name: 'advanced_content',
462
+ label: 'Content',
463
+ keys: { field: 'content' },
464
+ visible: false
465
+ }
466
+ ]
467
+ };
468
+ ```
469
+
470
+ ## Dynamic Criteria Generation
471
+
472
+ ### Loading Criteria from API
473
+
474
+ ```typescript
475
+ class DynamicCriteriaLoader {
476
+ private cache = new Map<string, FlexiCriteriaSetSchema>();
477
+
478
+ async loadCriteriaSchema(entityType: string): Promise<FlexiCriteriaSetSchema> {
479
+ if (this.cache.has(entityType)) {
480
+ return this.cache.get(entityType)!;
481
+ }
482
+
483
+ try {
484
+ const response = await fetch(`/api/search/criteria/${entityType}`);
485
+ const schema = await response.json();
486
+ this.cache.set(entityType, schema);
487
+ return schema;
488
+ } catch (error) {
489
+ console.error('Failed to load criteria schema:', error);
490
+ return this.getDefaultSchema();
491
+ }
492
+ }
493
+
494
+ async loadDictionaries(dictNames: string[]): Promise<Record<string, any[]>> {
495
+ const dictionaries: Record<string, any[]> = {};
496
+
497
+ for (const dictName of dictNames) {
498
+ try {
499
+ const response = await fetch(`/api/dictionaries/${dictName}`);
500
+ dictionaries[dictName] = await response.json();
501
+ } catch (error) {
502
+ console.error(`Failed to load dictionary ${dictName}:`, error);
503
+ dictionaries[dictName] = [];
504
+ }
505
+ }
506
+
507
+ return dictionaries;
508
+ }
509
+
510
+ private getDefaultSchema(): FlexiCriteriaSetSchema {
511
+ return {
512
+ arrangement: 'vertical',
513
+ fields: [
514
+ {
515
+ type: 'text-search',
516
+ name: 'query',
517
+ label: 'Search',
518
+ keys: { field: 'q' },
519
+ size: 'x40'
520
+ }
521
+ ]
522
+ };
523
+ }
524
+ }
525
+
526
+ // Usage in Svelte component
527
+ let criteriaSchema = null;
528
+ let searchCriteria = {};
529
+
530
+ onMount(async () => {
531
+ const loader = new DynamicCriteriaLoader();
532
+ criteriaSchema = await loader.loadCriteriaSchema('products');
533
+
534
+ // Load required dictionaries
535
+ const dictNames = extractDictionaryNames(criteriaSchema);
536
+ const dictionaries = await loader.loadDictionaries(dictNames);
537
+
538
+ // Setup criteria set with dictionaries
539
+ setupCriteriaSet(criteriaSchema, dictionaries);
540
+ });
541
+ ```
542
+
543
+ ### Saved Search Patterns
544
+
545
+ ```typescript
546
+ class SavedSearchManager {
547
+ private storageKey = 'saved-searches';
548
+
549
+ saveSearch(name: string, criteria: any, schema: FlexiCriteriaSetSchema): void {
550
+ const savedSearches = this.getSavedSearches();
551
+ savedSearches[name] = {
552
+ criteria,
553
+ schema,
554
+ createdAt: new Date().toISOString()
555
+ };
556
+ localStorage.setItem(this.storageKey, JSON.stringify(savedSearches));
557
+ }
558
+
559
+ loadSearch(name: string): { criteria: any; schema: FlexiCriteriaSetSchema } | null {
560
+ const savedSearches = this.getSavedSearches();
561
+ return savedSearches[name] || null;
562
+ }
563
+
564
+ listSavedSearches(): string[] {
565
+ const savedSearches = this.getSavedSearches();
566
+ return Object.keys(savedSearches);
567
+ }
568
+
569
+ deleteSearch(name: string): void {
570
+ const savedSearches = this.getSavedSearches();
571
+ delete savedSearches[name];
572
+ localStorage.setItem(this.storageKey, JSON.stringify(savedSearches));
573
+ }
574
+
575
+ private getSavedSearches(): Record<string, any> {
576
+ try {
577
+ return JSON.parse(localStorage.getItem(this.storageKey) || '{}');
578
+ } catch {
579
+ return {};
580
+ }
581
+ }
582
+ }
583
+
584
+ // Usage
585
+ const searchManager = new SavedSearchManager();
586
+
587
+ function saveCurrentSearch() {
588
+ const name = prompt('Enter search name:');
589
+ if (name) {
590
+ searchManager.saveSearch(name, searchCriteria, criteriaSchema);
591
+ }
592
+ }
593
+
594
+ function loadSavedSearch(name: string) {
595
+ const saved = searchManager.loadSearch(name);
596
+ if (saved) {
597
+ searchCriteria = saved.criteria;
598
+ criteriaSchema = saved.schema;
599
+ }
600
+ }
601
+ ```
602
+
603
+ ## Events and Interactions
604
+
605
+ ### Criteria Change Events
606
+
607
+ ```svelte
608
+ <script>
609
+ function handleCriteriaChange(event) {
610
+ const { criteria, field, value } = event.detail;
611
+ console.log(`Field ${field} changed to:`, value);
612
+ console.log('Full criteria:', criteria);
613
+
614
+ // Trigger real-time search
615
+ debouncedSearch(criteria);
616
+ }
617
+
618
+ function handleFieldFocus(event) {
619
+ const { field } = event.detail;
620
+ console.log(`Field ${field} focused`);
621
+
622
+ // Load suggestions for this field
623
+ loadFieldSuggestions(field);
624
+ }
625
+
626
+ function handleOperatorChange(event) {
627
+ const { field, operator } = event.detail;
628
+ console.log(`Field ${field} operator changed to:`, operator);
629
+ }
630
+
631
+ // Debounced search function
632
+ const debouncedSearch = debounce((criteria) => {
633
+ performSearch(criteria);
634
+ }, 300);
635
+
636
+ function debounce(func, wait) {
637
+ let timeout;
638
+ return function executedFunction(...args) {
639
+ const later = () => {
640
+ clearTimeout(timeout);
641
+ func(...args);
642
+ };
643
+ clearTimeout(timeout);
644
+ timeout = setTimeout(later, wait);
645
+ };
646
+ }
647
+ </script>
648
+
649
+ <CriteriaPanel
650
+ {schema}
651
+ bind:criteria={searchCriteria}
652
+ on:change={handleCriteriaChange}
653
+ on:fieldFocus={handleFieldFocus}
654
+ on:operatorChange={handleOperatorChange}
655
+ on:search={handleSearch}
656
+ on:reset={handleReset}
657
+ />
658
+ ```
659
+
660
+ ### Custom Event Handlers
661
+
662
+ ```typescript
663
+ class CustomCriteriaEventHandler {
664
+ private criteriaSet: FlexiCriteriaSet;
665
+
666
+ constructor(criteriaSet: FlexiCriteriaSet) {
667
+ this.criteriaSet = criteriaSet;
668
+ this.setupEventHandlers();
669
+ }
670
+
671
+ private setupEventHandlers(): void {
672
+ // Setup field-specific event handlers
673
+ this.setupCategoryFieldHandler();
674
+ this.setupLocationFieldHandler();
675
+ this.setupDateRangeHandler();
676
+ }
677
+
678
+ private setupCategoryFieldHandler(): void {
679
+ const categoryField = this.criteriaSet.field['category'];
680
+ if (categoryField) {
681
+ categoryField.events.change = async (value: string) => {
682
+ console.log('Category changed:', value);
683
+
684
+ // Load subcategories based on selected category
685
+ await this.loadSubcategories(value);
686
+
687
+ // Update related fields
688
+ this.updateRelatedFields('category', value);
689
+ };
690
+ }
691
+ }
692
+
693
+ private setupLocationFieldHandler(): void {
694
+ const countryField = this.criteriaSet.field['country'];
695
+ const stateField = this.criteriaSet.field['state'];
696
+
697
+ if (countryField && stateField) {
698
+ countryField.events.change = async (country: string) => {
699
+ // Clear state field
700
+ stateField.data = null;
701
+
702
+ // Load states for selected country
703
+ const states = await this.loadStates(country);
704
+ stateField.props.options = states;
705
+
706
+ this.criteriaSet.invalidate();
707
+ };
708
+ }
709
+ }
710
+
711
+ private setupDateRangeHandler(): void {
712
+ const fromDateField = this.criteriaSet.field['fromDate'];
713
+ const toDateField = this.criteriaSet.field['toDate'];
714
+
715
+ if (fromDateField && toDateField) {
716
+ fromDateField.events.change = (fromDate: string) => {
717
+ // Update minimum date for toDate field
718
+ toDateField.props.min = fromDate;
719
+ this.criteriaSet.invalidate();
720
+ };
721
+
722
+ toDateField.events.change = (toDate: string) => {
723
+ // Update maximum date for fromDate field
724
+ fromDateField.props.max = toDate;
725
+ this.criteriaSet.invalidate();
726
+ };
727
+ }
728
+ }
729
+
730
+ private async loadSubcategories(categoryId: string): Promise<void> {
731
+ try {
732
+ const response = await fetch(`/api/categories/${categoryId}/subcategories`);
733
+ const subcategories = await response.json();
734
+
735
+ const subcategoryField = this.criteriaSet.field['subcategory'];
736
+ if (subcategoryField) {
737
+ subcategoryField.props.options = subcategories;
738
+ this.criteriaSet.invalidate();
739
+ }
740
+ } catch (error) {
741
+ console.error('Failed to load subcategories:', error);
742
+ }
743
+ }
744
+
745
+ private async loadStates(countryCode: string): Promise<any[]> {
746
+ try {
747
+ const response = await fetch(`/api/countries/${countryCode}/states`);
748
+ return await response.json();
749
+ } catch (error) {
750
+ console.error('Failed to load states:', error);
751
+ return [];
752
+ }
753
+ }
754
+
755
+ private updateRelatedFields(changedField: string, value: any): void {
756
+ // Implement business logic for updating related fields
757
+ console.log(`Updating fields related to ${changedField}:`, value);
758
+ }
759
+ }
760
+ ```
761
+
762
+ ## Extending FlexiCriteriaSet
763
+
764
+ ### Creating Custom Criteria Field Types
765
+
766
+ #### 1. Custom Range Slider Field
767
+
768
+ ```svelte
769
+ <!-- CustomRangeSliderField.svelte -->
770
+ <script lang="ts">
771
+ import type FlexiCriteriaField from "$lib/criteria-panel/lib/FlexiCriteriaField";
772
+
773
+ export let field: FlexiCriteriaField;
774
+ export let readonly: boolean = false;
775
+ export let disabled: boolean = false;
776
+
777
+ $: props = field.props;
778
+ $: keys = field.keys;
779
+
780
+ let minValue = props.min || 0;
781
+ let maxValue = props.max || 100;
782
+ let currentMin = minValue;
783
+ let currentMax = maxValue;
784
+
785
+ // Initialize from data
786
+ if (field.data) {
787
+ currentMin = field.data[keys.minField] || minValue;
788
+ currentMax = field.data[keys.maxField] || maxValue;
789
+ }
790
+
791
+ function updateRange() {
792
+ if (!field.data) field.data = {};
793
+ field.data[keys.minField] = currentMin;
794
+ field.data[keys.maxField] = currentMax;
795
+
796
+ // Trigger change event
797
+ field.events.change?.({ min: currentMin, max: currentMax });
798
+ }
799
+
800
+ $: if (currentMin !== undefined && currentMax !== undefined) {
801
+ updateRange();
802
+ }
803
+ </script>
804
+
805
+ <div class="range-slider-field" class:readonly class:disabled>
806
+ <label>{field.label}</label>
807
+
808
+ <div class="range-inputs">
809
+ <input
810
+ type="number"
811
+ bind:value={currentMin}
812
+ min={minValue}
813
+ max={currentMax}
814
+ {disabled}
815
+ {readonly}
816
+ class="range-input"
817
+ />
818
+ <span class="range-separator">to</span>
819
+ <input
820
+ type="number"
821
+ bind:value={currentMax}
822
+ min={currentMin}
823
+ max={maxValue}
824
+ {disabled}
825
+ {readonly}
826
+ class="range-input"
827
+ />
828
+ </div>
829
+
830
+ <div class="slider-container">
831
+ <input
832
+ type="range"
833
+ bind:value={currentMin}
834
+ min={minValue}
835
+ max={maxValue}
836
+ step={props.step || 1}
837
+ {disabled}
838
+ {readonly}
839
+ class="slider slider-min"
840
+ />
841
+ <input
842
+ type="range"
843
+ bind:value={currentMax}
844
+ min={minValue}
845
+ max={maxValue}
846
+ step={props.step || 1}
847
+ {disabled}
848
+ {readonly}
849
+ class="slider slider-max"
850
+ />
851
+ </div>
852
+
853
+ <div class="range-display">
854
+ {props.prefix || ''}{currentMin} - {currentMax}{props.suffix || ''}
855
+ </div>
856
+ </div>
857
+
858
+ <style>
859
+ .range-slider-field {
860
+ display: flex;
861
+ flex-direction: column;
862
+ gap: 8px;
863
+ }
864
+
865
+ .range-inputs {
866
+ display: flex;
867
+ align-items: center;
868
+ gap: 8px;
869
+ }
870
+
871
+ .range-input {
872
+ width: 80px;
873
+ padding: 4px 8px;
874
+ border: 1px solid #ddd;
875
+ border-radius: 4px;
876
+ }
877
+
878
+ .range-separator {
879
+ color: #666;
880
+ }
881
+
882
+ .slider-container {
883
+ position: relative;
884
+ height: 20px;
885
+ }
886
+
887
+ .slider {
888
+ position: absolute;
889
+ width: 100%;
890
+ height: 20px;
891
+ -webkit-appearance: none;
892
+ background: transparent;
893
+ outline: none;
894
+ }
895
+
896
+ .slider::-webkit-slider-track {
897
+ height: 4px;
898
+ background: #ddd;
899
+ border-radius: 2px;
900
+ }
901
+
902
+ .slider::-webkit-slider-thumb {
903
+ -webkit-appearance: none;
904
+ width: 16px;
905
+ height: 16px;
906
+ background: #007bff;
907
+ border-radius: 50%;
908
+ cursor: pointer;
909
+ }
910
+
911
+ .range-display {
912
+ text-align: center;
913
+ font-weight: bold;
914
+ color: #333;
915
+ }
916
+
917
+ .readonly, .disabled {
918
+ opacity: 0.6;
919
+ pointer-events: none;
920
+ }
921
+ </style>
922
+ ```
923
+
924
+ #### 2. Register Custom Field Type
925
+
926
+ ```typescript
927
+ import CriteriaComponentBuilder from '@ticatec/uniface-flexi-form/criteria-panel/lib/CriteriaComponentBuilder';
928
+ import CustomRangeSliderField from './CustomRangeSliderField.svelte';
929
+
930
+ // Create builder function
931
+ const buildRangeSliderField = (schema: FlexiCriteriaFieldSchema, dictLoader: DictionaryLoader) => {
932
+ return {
933
+ component: CustomRangeSliderField,
934
+ props: schema.props,
935
+ keyFields: schema.keys
936
+ };
937
+ };
938
+
939
+ // Register the custom field type
940
+ const componentBuilder = CriteriaComponentBuilder.getInstance();
941
+ componentBuilder.register('range-slider', buildRangeSliderField);
942
+
943
+ // Use in schema
944
+ const schemaWithRangeSlider = {
945
+ arrangement: 'vertical',
946
+ fields: [
947
+ {
948
+ type: 'range-slider',
949
+ name: 'priceRange',
950
+ label: 'Price Range',
951
+ keys: {
952
+ minField: 'priceMin',
953
+ maxField: 'priceMax'
954
+ },
955
+ size: 'x30',
956
+ props: {
957
+ min: 0,
958
+ max: 1000,
959
+ step: 10,
960
+ prefix: '$',
961
+ suffix: ' USD'
962
+ }
963
+ }
964
+ ]
965
+ };
966
+ ```
967
+
968
+ ### Creating Custom Criteria Sets
969
+
970
+ ```typescript
971
+ class AdvancedProductCriteriaSet extends FlexiCriteriaSet {
972
+ private productCategories: any[] = [];
973
+ private priceHistory: any[] = [];
974
+
975
+ constructor(schema: FlexiCriteriaSetSchema) {
976
+ super(schema);
977
+ this.setupAdvancedFeatures();
978
+ }
979
+
980
+ private async setupAdvancedFeatures(): Promise<void> {
981
+ await this.loadProductCategories();
982
+ await this.loadPriceHistory();
983
+ this.setupSmartDefaults();
984
+ this.setupAdvancedValidation();
985
+ }
986
+
987
+ private async loadProductCategories(): Promise<void> {
988
+ try {
989
+ const response = await fetch('/api/products/categories/tree');
990
+ this.productCategories = await response.json();
991
+ } catch (error) {
992
+ console.error('Failed to load product categories:', error);
993
+ }
994
+ }
995
+
996
+ private async loadPriceHistory(): Promise<void> {
997
+ try {
998
+ const response = await fetch('/api/products/price-history');
999
+ this.priceHistory = await response.json();
1000
+ } catch (error) {
1001
+ console.error('Failed to load price history:', error);
1002
+ }
1003
+ }
1004
+
1005
+ private setupSmartDefaults(): void {
1006
+ // Set intelligent defaults based on user history or popular searches
1007
+ const categoryField = this.field['category'];
1008
+ const priceField = this.field['priceRange'];
1009
+
1010
+ if (categoryField && this.productCategories.length > 0) {
1011
+ // Set most popular category as default
1012
+ const popularCategory = this.getMostPopularCategory();
1013
+ categoryField.data = popularCategory?.id;
1014
+ }
1015
+
1016
+ if (priceField && this.priceHistory.length > 0) {
1017
+ // Set common price range as default
1018
+ const commonRange = this.getCommonPriceRange();
1019
+ priceField.data = commonRange;
1020
+ }
1021
+ }
1022
+
1023
+ private setupAdvancedValidation(): void {
1024
+ // Add cross-field validation
1025
+ this.addValidator('priceRange', (value: any) => {
1026
+ if (value.min > value.max) {
1027
+ return 'Minimum price cannot be greater than maximum price';
1028
+ }
1029
+ return null;
1030
+ });
1031
+
1032
+ this.addValidator('dateRange', (value: any) => {
1033
+ if (new Date(value.from) > new Date(value.to)) {
1034
+ return 'Start date cannot be after end date';
1035
+ }
1036
+ return null;
1037
+ });
1038
+ }
1039
+
1040
+ private getMostPopularCategory(): any {
1041
+ // Implement logic to determine most popular category
1042
+ return this.productCategories[0];
1043
+ }
1044
+
1045
+ private getCommonPriceRange(): any {
1046
+ // Implement logic to determine common price range
1047
+ return { min: 10, max: 100 };
1048
+ }
1049
+
1050
+ private addValidator(fieldName: string, validator: (value: any) => string | null): void {
1051
+ const field = this.field[fieldName];
1052
+ if (field) {
1053
+ const originalChange = field.events.change;
1054
+ field.events.change = (value: any) => {
1055
+ const error = validator(value);
1056
+ if (error) {
1057
+ console.error(`Validation error for ${fieldName}:`, error);
1058
+ // Handle validation error
1059
+ } else {
1060
+ originalChange?.(value);
1061
+ }
1062
+ };
1063
+ }
1064
+ }
1065
+
1066
+ // Public API methods
1067
+ getCriteriaQuery(): any {
1068
+ const criteria = {};
1069
+
1070
+ for (const field of this.fields) {
1071
+ if (field.data !== null && field.data !== undefined) {
1072
+ // Convert field data to query format
1073
+ const queryPart = this.convertFieldToQuery(field);
1074
+ Object.assign(criteria, queryPart);
1075
+ }
1076
+ }
1077
+
1078
+ return criteria;
1079
+ }
1080
+
1081
+ private convertFieldToQuery(field: FlexiCriteriaField): any {
1082
+ const query = {};
1083
+
1084
+ switch (field.component.name) {
1085
+ case 'TextSearchField':
1086
+ query[field.keys.field] = {
1087
+ operator: field.props.selectedOperator || 'contains',
1088
+ value: field.data
1089
+ };
1090
+ break;
1091
+
1092
+ case 'RangeSliderField':
1093
+ if (field.data.min !== undefined) {
1094
+ query[field.keys.minField] = {
1095
+ operator: 'gte',
1096
+ value: field.data.min
1097
+ };
1098
+ }
1099
+ if (field.data.max !== undefined) {
1100
+ query[field.keys.maxField] = {
1101
+ operator: 'lte',
1102
+ value: field.data.max
1103
+ };
1104
+ }
1105
+ break;
1106
+
1107
+ // Handle other field types...
1108
+ }
1109
+
1110
+ return query;
1111
+ }
1112
+
1113
+ exportCriteria(): string {
1114
+ return JSON.stringify(this.getCriteriaQuery(), null, 2);
1115
+ }
1116
+
1117
+ importCriteria(jsonString: string): void {
1118
+ try {
1119
+ const criteria = JSON.parse(jsonString);
1120
+ this.applyCriteria(criteria);
1121
+ } catch (error) {
1122
+ console.error('Failed to import criteria:', error);
1123
+ }
1124
+ }
1125
+
1126
+ private applyCriteria(criteria: any): void {
1127
+ // Apply imported criteria to fields
1128
+ for (const [key, value] of Object.entries(criteria)) {
1129
+ const field = this.findFieldByKey(key);
1130
+ if (field) {
1131
+ field.data = value;
1132
+ }
1133
+ }
1134
+ this.invalidate();
1135
+ }
1136
+
1137
+ private findFieldByKey(key: string): FlexiCriteriaField | null {
1138
+ for (const field of this.fields) {
1139
+ if (Object.values(field.keys).includes(key)) {
1140
+ return field;
1141
+ }
1142
+ }
1143
+ return null;
1144
+ }
1145
+ }
1146
+ ```
1147
+
1148
+ ## Integration Patterns
1149
+
1150
+ ### Integration with Data Tables
1151
+
1152
+ ```svelte
1153
+ <!-- ProductSearchAndTable.svelte -->
1154
+ <script lang="ts">
1155
+ import CriteriaPanel from '@ticatec/uniface-flexi-form/criteria-panel';
1156
+ import DataTable from '@ticatec/uniface-element/DataTable';
1157
+
1158
+ let searchCriteria = {};
1159
+ let tableData = [];
1160
+ let loading = false;
1161
+ let totalRecords = 0;
1162
+ let currentPage = 1;
1163
+ let pageSize = 20;
1164
+
1165
+ const criteriaSchema = {
1166
+ arrangement: 'horizontal',
1167
+ fields: [
1168
+ {
1169
+ type: 'text-search',
1170
+ name: 'name',
1171
+ label: 'Product Name',
1172
+ keys: { field: 'name' },
1173
+ size: 'x25'
1174
+ },
1175
+ {
1176
+ type: 'option-select-search',
1177
+ name: 'category',
1178
+ label: 'Category',
1179
+ keys: { field: 'categoryId' },
1180
+ dictName: 'categories',
1181
+ size: 'x20'
1182
+ },
1183
+ {
1184
+ type: 'number-range',
1185
+ name: 'price',
1186
+ label: 'Price Range',
1187
+ keys: { minField: 'priceMin', maxField: 'priceMax' },
1188
+ size: 'x25'
1189
+ }
1190
+ ]
1191
+ };
1192
+
1193
+ const tableColumns = [
1194
+ { field: 'name', text: 'Product Name', width: 200 },
1195
+ { field: 'category', text: 'Category', width: 150 },
1196
+ { field: 'price', text: 'Price', width: 100, formatter: 'currency' },
1197
+ { field: 'stock', text: 'Stock', width: 100 },
1198
+ { field: 'status', text: 'Status', width: 120 }
1199
+ ];
1200
+
1201
+ // Reactive search when criteria changes
1202
+ $: if (searchCriteria) {
1203
+ performSearch();
1204
+ }
1205
+
1206
+ async function performSearch() {
1207
+ loading = true;
1208
+ try {
1209
+ const queryParams = new URLSearchParams({
1210
+ page: currentPage.toString(),
1211
+ limit: pageSize.toString(),
1212
+ ...flattenCriteria(searchCriteria)
1213
+ });
1214
+
1215
+ const response = await fetch(`/api/products/search?${queryParams}`);
1216
+ const result = await response.json();
1217
+
1218
+ tableData = result.data;
1219
+ totalRecords = result.total;
1220
+ } catch (error) {
1221
+ console.error('Search failed:', error);
1222
+ tableData = [];
1223
+ } finally {
1224
+ loading = false;
1225
+ }
1226
+ }
1227
+
1228
+ function flattenCriteria(criteria) {
1229
+ const flattened = {};
1230
+ for (const [key, value] of Object.entries(criteria)) {
1231
+ if (value !== null && value !== undefined && value !== '') {
1232
+ flattened[key] = value;
1233
+ }
1234
+ }
1235
+ return flattened;
1236
+ }
1237
+
1238
+ function handlePageChange(event) {
1239
+ currentPage = event.detail.page;
1240
+ performSearch();
1241
+ }
1242
+
1243
+ function handleCriteriaChange(event) {
1244
+ searchCriteria = event.detail.criteria;
1245
+ currentPage = 1; // Reset to first page on new search
1246
+ }
1247
+
1248
+ function handleReset() {
1249
+ searchCriteria = {};
1250
+ currentPage = 1;
1251
+ }
1252
+ </script>
1253
+
1254
+ <div class="search-and-table">
1255
+ <div class="search-panel">
1256
+ <h3>Product Search</h3>
1257
+ <CriteriaPanel
1258
+ schema={criteriaSchema}
1259
+ bind:criteria={searchCriteria}
1260
+ on:change={handleCriteriaChange}
1261
+ on:reset={handleReset}
1262
+ />
1263
+ </div>
1264
+
1265
+ <div class="results-panel">
1266
+ <div class="results-header">
1267
+ <h3>Search Results ({totalRecords} items)</h3>
1268
+ {#if loading}
1269
+ <div class="loading">Searching...</div>
1270
+ {/if}
1271
+ </div>
1272
+
1273
+ <DataTable
1274
+ columns={tableColumns}
1275
+ data={tableData}
1276
+ {totalRecords}
1277
+ {currentPage}
1278
+ {pageSize}
1279
+ on:pageChange={handlePageChange}
1280
+ />
1281
+ </div>
1282
+ </div>
1283
+
1284
+ <style>
1285
+ .search-and-table {
1286
+ display: flex;
1287
+ flex-direction: column;
1288
+ gap: 20px;
1289
+ }
1290
+
1291
+ .search-panel {
1292
+ background: #f8f9fa;
1293
+ padding: 16px;
1294
+ border-radius: 8px;
1295
+ border: 1px solid #dee2e6;
1296
+ }
1297
+
1298
+ .results-panel {
1299
+ flex: 1;
1300
+ }
1301
+
1302
+ .results-header {
1303
+ display: flex;
1304
+ justify-content: space-between;
1305
+ align-items: center;
1306
+ margin-bottom: 16px;
1307
+ }
1308
+
1309
+ .loading {
1310
+ color: #6c757d;
1311
+ font-style: italic;
1312
+ }
1313
+ </style>
1314
+ ```
1315
+
1316
+ ### URL State Management
1317
+
1318
+ ```typescript
1319
+ class CriteriaURLManager {
1320
+ private criteria: any = {};
1321
+ private schema: FlexiCriteriaSetSchema;
1322
+
1323
+ constructor(schema: FlexiCriteriaSetSchema) {
1324
+ this.schema = schema;
1325
+ this.loadFromURL();
1326
+ }
1327
+
1328
+ loadFromURL(): any {
1329
+ const urlParams = new URLSearchParams(window.location.search);
1330
+ const criteria = {};
1331
+
1332
+ for (const field of this.schema.fields) {
1333
+ const keys = field.keys || {};
1334
+
1335
+ for (const [keyName, paramName] of Object.entries(keys)) {
1336
+ const value = urlParams.get(paramName);
1337
+ if (value !== null) {
1338
+ if (!criteria[field.name!]) {
1339
+ criteria[field.name!] = {};
1340
+ }
1341
+ criteria[field.name!][keyName] = this.parseURLValue(value, field.type);
1342
+ }
1343
+ }
1344
+ }
1345
+
1346
+ this.criteria = criteria;
1347
+ return criteria;
1348
+ }
1349
+
1350
+ updateURL(criteria: any): void {
1351
+ const urlParams = new URLSearchParams();
1352
+
1353
+ for (const field of this.schema.fields) {
1354
+ const fieldCriteria = criteria[field.name!];
1355
+ if (fieldCriteria) {
1356
+ const keys = field.keys || {};
1357
+
1358
+ for (const [keyName, paramName] of Object.entries(keys)) {
1359
+ const value = fieldCriteria[keyName];
1360
+ if (value !== null && value !== undefined && value !== '') {
1361
+ urlParams.set(paramName, this.serializeURLValue(value));
1362
+ }
1363
+ }
1364
+ }
1365
+ }
1366
+
1367
+ const newURL = `${window.location.pathname}?${urlParams.toString()}`;
1368
+ window.history.replaceState({}, '', newURL);
1369
+ }
1370
+
1371
+ private parseURLValue(value: string, fieldType: string): any {
1372
+ switch (fieldType) {
1373
+ case 'number-search':
1374
+ case 'number-range':
1375
+ return parseFloat(value);
1376
+ case 'date-search':
1377
+ case 'date-range':
1378
+ return value; // Keep as string for date inputs
1379
+ case 'option-multi-select-search':
1380
+ return value.split(',');
1381
+ default:
1382
+ return value;
1383
+ }
1384
+ }
1385
+
1386
+ private serializeURLValue(value: any): string {
1387
+ if (Array.isArray(value)) {
1388
+ return value.join(',');
1389
+ }
1390
+ return String(value);
1391
+ }
1392
+
1393
+ getCriteria(): any {
1394
+ return this.criteria;
1395
+ }
1396
+ }
1397
+
1398
+ // Usage in component
1399
+ let criteriaURLManager: CriteriaURLManager;
1400
+ let searchCriteria = {};
1401
+
1402
+ onMount(() => {
1403
+ criteriaURLManager = new CriteriaURLManager(criteriaSchema);
1404
+ searchCriteria = criteriaURLManager.loadFromURL();
1405
+ });
1406
+
1407
+ function handleCriteriaChange(event) {
1408
+ searchCriteria = event.detail.criteria;
1409
+ criteriaURLManager.updateURL(searchCriteria);
1410
+ }
1411
+ ```
1412
+
1413
+ ## Best Practices
1414
+
1415
+ ### 1. Schema Organization
1416
+
1417
+ ```typescript
1418
+ // Good: Organized schema with constants
1419
+ const FIELD_SIZES = {
1420
+ SMALL: 'x15',
1421
+ MEDIUM: 'x25',
1422
+ LARGE: 'x35',
1423
+ FULL: 'x40'
1424
+ } as const;
1425
+
1426
+ const COMMON_FIELDS = {
1427
+ NAME_SEARCH: {
1428
+ type: 'text-search',
1429
+ keys: { field: 'name' },
1430
+ size: FIELD_SIZES.MEDIUM,
1431
+ props: {
1432
+ placeholder: 'Enter name',
1433
+ operators: ['contains', 'startsWith', 'equals']
1434
+ }
1435
+ },
1436
+
1437
+ STATUS_SELECT: {
1438
+ type: 'option-select-search',
1439
+ keys: { field: 'status' },
1440
+ dictName: 'status-options',
1441
+ size: FIELD_SIZES.SMALL
1442
+ }
1443
+ } as const;
1444
+
1445
+ const PRODUCT_SEARCH_SCHEMA = {
1446
+ arrangement: 'horizontal' as const,
1447
+ variant: 'outlined' as const,
1448
+ fields: [
1449
+ {
1450
+ ...COMMON_FIELDS.NAME_SEARCH,
1451
+ name: 'productName',
1452
+ label: 'Product Name'
1453
+ },
1454
+ {
1455
+ ...COMMON_FIELDS.STATUS_SELECT,
1456
+ name: 'status',
1457
+ label: 'Status'
1458
+ }
1459
+ ]
1460
+ };
1461
+ ```
1462
+
1463
+ ### 2. Performance Optimization
1464
+
1465
+ ```typescript
1466
+ // Debounced search for real-time filtering
1467
+ class OptimizedCriteriaHandler {
1468
+ private searchDebounceTime = 300;
1469
+ private dictionaryCache = new Map();
1470
+
1471
+ createDebouncedSearch(searchFunction: Function) {
1472
+ let timeoutId: number;
1473
+
1474
+ return (criteria: any) => {
1475
+ clearTimeout(timeoutId);
1476
+ timeoutId = setTimeout(() => {
1477
+ searchFunction(criteria);
1478
+ }, this.searchDebounceTime);
1479
+ };
1480
+ }
1481
+
1482
+ async getCachedDictionary(dictName: string): Promise<any[]> {
1483
+ if (this.dictionaryCache.has(dictName)) {
1484
+ return this.dictionaryCache.get(dictName);
1485
+ }
1486
+
1487
+ const dictionary = await this.loadDictionary(dictName);
1488
+ this.dictionaryCache.set(dictName, dictionary);
1489
+ return dictionary;
1490
+ }
1491
+
1492
+ private async loadDictionary(dictName: string): Promise<any[]> {
1493
+ const response = await fetch(`/api/dictionaries/${dictName}`);
1494
+ return await response.json();
1495
+ }
1496
+ }
1497
+ ```
1498
+
1499
+ ### 3. Error Handling
1500
+
1501
+ ```typescript
1502
+ class CriteriaErrorHandler {
1503
+ static handleCriteriaError(error: any, criteriaSet: FlexiCriteriaSet): void {
1504
+ if (error.fieldErrors) {
1505
+ this.displayFieldErrors(error.fieldErrors, criteriaSet);
1506
+ } else if (error.networkError) {
1507
+ this.displayNetworkError();
1508
+ } else {
1509
+ this.displayGenericError(error);
1510
+ }
1511
+ }
1512
+
1513
+ private static displayFieldErrors(errors: Record<string, string>, criteriaSet: FlexiCriteriaSet): void {
1514
+ for (const [fieldName, errorMessage] of Object.entries(errors)) {
1515
+ const field = criteriaSet.field[fieldName];
1516
+ if (field) {
1517
+ // Display error near field
1518
+ console.error(`Field ${fieldName}: ${errorMessage}`);
1519
+ }
1520
+ }
1521
+ }
1522
+
1523
+ private static displayNetworkError(): void {
1524
+ console.error('Network error occurred while searching');
1525
+ }
1526
+
1527
+ private static displayGenericError(error: any): void {
1528
+ console.error('Search error:', error);
1529
+ }
1530
+ }
1531
+ ```
1532
+
1533
+ ### 4. Accessibility
1534
+
1535
+ ```typescript
1536
+ const accessibleCriteriaSchema = {
1537
+ arrangement: 'vertical',
1538
+ fields: [
1539
+ {
1540
+ type: 'text-search',
1541
+ name: 'search',
1542
+ label: 'Search Products',
1543
+ keys: { field: 'query' },
1544
+ props: {
1545
+ placeholder: 'Enter search terms',
1546
+ 'aria-label': 'Search products by name or description',
1547
+ 'aria-describedby': 'search-help'
1548
+ }
1549
+ }
1550
+ ]
1551
+ };
1552
+
1553
+ // Add help text and ARIA attributes
1554
+ const helpText = {
1555
+ 'search-help': 'Use keywords to search product names and descriptions'
1556
+ };
1557
+ ```
1558
+
1559
+ This comprehensive guide provides everything needed to effectively use and extend FlexiCriteriaSet for building sophisticated search interfaces. The examples progress from basic usage to advanced patterns, showing how to create powerful search experiences with FlexiCriteriaSet's flexible architecture.