@componyx/ui 0.0.0-stage → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AppSkin/AppSkin.css +1268 -0
- package/AppSkin/AppSkin.js +2748 -0
- package/AppSkin/Themes/Default.css +241 -0
- package/Base/Base.js +3407 -0
- package/Base/ComponentIcons.css +66 -0
- package/Base/Font/ComponentIcons.woff +0 -0
- package/Base/Font/UI.woff +0 -0
- package/Base/Library.js +8432 -0
- package/Base/NavigationManager.js +462 -0
- package/Base/ResponsiveTemplates.js +371 -0
- package/Base/Sanitizer.js +288 -0
- package/Base/Themes/Default.css +255 -0
- package/Base/UI.css +618 -0
- package/Base/UIResourcePath.js +6 -0
- package/Bindary/Bindary.js +2011 -0
- package/Bindary/Modules/Connection.js +216 -0
- package/Bindary/Modules/Core.js +854 -0
- package/Bindary/Modules/DataUpdater.js +351 -0
- package/Bindary/Modules/ExpressionEngine.js +347 -0
- package/Bindary/Modules/ProxyManager.js +482 -0
- package/Bindary/Modules/Renderer.js +605 -0
- package/Bindary/Modules/Router.js +291 -0
- package/Bindary/Modules/Scroller.js +89 -0
- package/Bindary/Modules/Template.js +1273 -0
- package/BookmarkSpy/BookmarkSpy.js +430 -0
- package/Box/Box.css +36 -0
- package/Box/Box.js +1685 -0
- package/Button/Button.css +470 -0
- package/Button/Button.js +1398 -0
- package/Button/Themes/Default.css +200 -0
- package/Carousel/Carousel.css +273 -0
- package/Carousel/Carousel.js +607 -0
- package/ColorButton/ColorButton.css +39 -0
- package/ColorButton/ColorButton.js +336 -0
- package/ColorPicker/ColorPicker.css +359 -0
- package/ColorPicker/ColorPicker.js +972 -0
- package/ComboBox/ComboBox.css +357 -0
- package/ComboBox/ComboBox.js +2342 -0
- package/ComboBox/Themes/Default.css +23 -0
- package/DataPager/DataPager.css +72 -0
- package/DataPager/DataPager.js +674 -0
- package/DatePicker/DatePicker.css +227 -0
- package/DatePicker/DatePicker.js +1531 -0
- package/DatePicker/Themes/Default.css +26 -0
- package/Dialog/Dialog.css +106 -0
- package/Dialog/Dialog.js +572 -0
- package/Dialog/Themes/Default.css +10 -0
- package/Editor/Data/Emoji.json +8285 -0
- package/Editor/Data/Special.json +1042 -0
- package/Editor/Editor.css +1562 -0
- package/Editor/Editor.js +4058 -0
- package/Editor/Font/Editor.woff +0 -0
- package/Editor/Modules/ComponentFactory.js +565 -0
- package/Editor/Modules/ContentManager.js +748 -0
- package/Editor/Modules/DialogManager.js +1234 -0
- package/Editor/Modules/EventManager.js +458 -0
- package/Editor/Modules/Format.js +950 -0
- package/Editor/Modules/History.js +191 -0
- package/Editor/Modules/LayoutState.js +379 -0
- package/Editor/Modules/ListManager.js +296 -0
- package/Editor/Modules/MenuManager.js +388 -0
- package/Editor/Modules/NodeManager.js +681 -0
- package/Editor/Modules/ParagraphButtons.js +122 -0
- package/Editor/Modules/Resizer.js +176 -0
- package/Editor/Modules/SelectionRange.js +564 -0
- package/Editor/Modules/SourceViewBuilder.js +212 -0
- package/Editor/Modules/SymbolPicker.js +208 -0
- package/Editor/Modules/Table.js +2327 -0
- package/Editor/Modules/TableDialog.js +757 -0
- package/Editor/Modules/Toolbar.js +375 -0
- package/Editor/Modules/TooltipMenu.js +125 -0
- package/Editor/Modules/Utility.js +131 -0
- package/Editor/Themes/Default.css +204 -0
- package/FileUpload/FileUpload.css +156 -0
- package/FileUpload/FileUpload.js +898 -0
- package/FileUpload/Themes/Default.css +17 -0
- package/Form/Form.css +1934 -0
- package/Form/Form.js +4943 -0
- package/Form/Modules/ActionManager.js +368 -0
- package/Form/Modules/BuildPanelManager.js +319 -0
- package/Form/Modules/ComponentFactory.js +468 -0
- package/Form/Modules/ComponentPanel.js +265 -0
- package/Form/Modules/ConfigPanelManager.js +685 -0
- package/Form/Modules/DataBinder.js +602 -0
- package/Form/Modules/DataObserver.js +717 -0
- package/Form/Modules/Draggable.js +756 -0
- package/Form/Modules/OptionsPanel.js +342 -0
- package/Form/Modules/Renderer.js +1889 -0
- package/Form/Modules/RuleEngine.js +585 -0
- package/Form/Modules/RulesPanel.js +1158 -0
- package/Form/Modules/Types.js +1709 -0
- package/Form/Modules/ValidationManager.js +194 -0
- package/Form/Themes/Default.css +216 -0
- package/FormField/FormField.css +468 -0
- package/FormField/FormField.js +576 -0
- package/FormField/Themes/Default.css +99 -0
- package/Grid/Grid.css +544 -0
- package/Grid/Grid.js +3761 -0
- package/Grid/Themes/Default.css +43 -0
- package/LICENSE +126 -0
- package/MaskedTextBox/MaskedTextBox.css +28 -0
- package/MaskedTextBox/MaskedTextBox.js +642 -0
- package/MaskedTextBox/Themes/Default.css +6 -0
- package/Menu/Menu.css +328 -0
- package/Menu/Menu.js +2386 -0
- package/Menu/Themes/Default.css +24 -0
- package/NumericBox/NumericBox.css +120 -0
- package/NumericBox/NumericBox.js +984 -0
- package/PanelBar/PanelBar.css +123 -0
- package/PanelBar/PanelBar.js +505 -0
- package/PanelBar/Themes/Default.css +51 -0
- package/PieChart/PieChart.css +95 -0
- package/PieChart/PieChart.js +820 -0
- package/PieChart/Themes/Default.css +9 -0
- package/README.md +178 -3
- package/Slider/Slider.css +348 -0
- package/Slider/Slider.js +1306 -0
- package/Slider/Themes/Default.css +44 -0
- package/TabStrip/TabStrip.css +52 -0
- package/TabStrip/TabStrip.js +362 -0
- package/TabStrip/Themes/Default.css +35 -0
- package/TimePicker/Themes/Default.css +30 -0
- package/TimePicker/TimePicker.css +253 -0
- package/TimePicker/TimePicker.js +1485 -0
- package/TooltipManager/Themes/Default.css +21 -0
- package/TooltipManager/TooltipManager.css +227 -0
- package/TooltipManager/TooltipManager.js +791 -0
- package/Validator/Validator.css +54 -0
- package/Validator/Validator.js +2508 -0
- package/componyx.html-data.json +619 -0
- package/index.js +27 -0
- package/package.json +43 -4
- package/types/AppSkin.d.ts +842 -0
- package/types/Base.d.ts +1034 -0
- package/types/Bindary.d.ts +1196 -0
- package/types/BookmarkSpy.d.ts +112 -0
- package/types/Box.d.ts +374 -0
- package/types/Button.d.ts +455 -0
- package/types/Carousel.d.ts +136 -0
- package/types/ColorButton.d.ts +120 -0
- package/types/ColorPicker.d.ts +225 -0
- package/types/ComboBox.d.ts +507 -0
- package/types/DataPager.d.ts +152 -0
- package/types/DatePicker.d.ts +354 -0
- package/types/Dialog.d.ts +160 -0
- package/types/Editor.d.ts +1289 -0
- package/types/FileUpload.d.ts +206 -0
- package/types/Form.d.ts +3078 -0
- package/types/FormField.d.ts +204 -0
- package/types/Grid.d.ts +911 -0
- package/types/Library.d.ts +1899 -0
- package/types/MaskedTextBox.d.ts +91 -0
- package/types/Menu.d.ts +577 -0
- package/types/NumericBox.d.ts +310 -0
- package/types/PanelBar.d.ts +223 -0
- package/types/PieChart.d.ts +224 -0
- package/types/ResponsiveTemplates.d.ts +108 -0
- package/types/Slider.d.ts +227 -0
- package/types/TabStrip.d.ts +185 -0
- package/types/TimePicker.d.ts +265 -0
- package/types/TooltipManager.d.ts +178 -0
- package/types/UI.d.ts +29 -0
- package/types/Validator.d.ts +653 -0
- package/types/index.d.ts +59 -0
- package/ui.css +11783 -0
- package/ui.js +79477 -0
- package/ui.min.css +138 -0
- package/ui.min.js +264 -0
- package/ui.min.js.map +1 -0
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
/*! Componyx.UI | (c) E.H. Daanen / Componyx | BUSL-1.1 | componyx.com/license */
|
|
2
|
+
componyx.UI.form_modules = componyx.UI.form_modules || {};
|
|
3
|
+
|
|
4
|
+
const Operators = {
|
|
5
|
+
equal: (a, b) =>
|
|
6
|
+
{
|
|
7
|
+
if (Array.isArray(a) && Array.isArray(b))
|
|
8
|
+
return a.length === b.length && a.every(x => b.includes(x));
|
|
9
|
+
if (Array.isArray(a))
|
|
10
|
+
return a.includes(b);
|
|
11
|
+
if (Array.isArray(b))
|
|
12
|
+
return b.includes(a);
|
|
13
|
+
return a === b;
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
not_equal: (a, b) =>
|
|
17
|
+
{
|
|
18
|
+
if (Array.isArray(a) && Array.isArray(b))
|
|
19
|
+
return !(a.length === b.length && a.every(x => b.includes(x)));
|
|
20
|
+
if (Array.isArray(a))
|
|
21
|
+
return !a.includes(b);
|
|
22
|
+
if (Array.isArray(b))
|
|
23
|
+
return !b.includes(a);
|
|
24
|
+
return a !== b;
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
greater_than: (a, b) =>
|
|
28
|
+
{
|
|
29
|
+
if (Array.isArray(a)) return a.length > b;
|
|
30
|
+
return a > b;
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
greater_than_or_equal: (a, b) =>
|
|
34
|
+
{
|
|
35
|
+
if (Array.isArray(a)) return a.length >= b;
|
|
36
|
+
return a >= b;
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
less_than: (a, b) =>
|
|
40
|
+
{
|
|
41
|
+
if (Array.isArray(a)) return a.length < b;
|
|
42
|
+
return a < b;
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
less_than_or_equal: (a, b) =>
|
|
46
|
+
{
|
|
47
|
+
if (Array.isArray(a)) return a.length <= b;
|
|
48
|
+
return a <= b;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
contains: (a, b) =>
|
|
52
|
+
{
|
|
53
|
+
if (Array.isArray(a) && Array.isArray(b))
|
|
54
|
+
return b.some(x => a.includes(x)); // any overlap
|
|
55
|
+
if (Array.isArray(a))
|
|
56
|
+
return a.includes(b);
|
|
57
|
+
if (typeof a === 'string')
|
|
58
|
+
return a.indexOf(b) !== -1;
|
|
59
|
+
return false;
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
not_contains: (a, b) =>
|
|
63
|
+
{
|
|
64
|
+
if (Array.isArray(a) && Array.isArray(b))
|
|
65
|
+
return b.every(x => !a.includes(x)); // no overlap
|
|
66
|
+
if (Array.isArray(a))
|
|
67
|
+
return !a.includes(b);
|
|
68
|
+
if (typeof a === 'string')
|
|
69
|
+
return a.indexOf(b) === -1;
|
|
70
|
+
return false;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
starts_with: (a, b) => typeof a === 'string' && a.startsWith(b),
|
|
74
|
+
ends_with: (a, b) => typeof a === 'string' && a.endsWith(b),
|
|
75
|
+
|
|
76
|
+
empty: (a) =>
|
|
77
|
+
{
|
|
78
|
+
if (Array.isArray(a)) return a.length === 0;
|
|
79
|
+
return a == null || a === '';
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
not_empty: (a) =>
|
|
83
|
+
{
|
|
84
|
+
if (Array.isArray(a)) return a.length > 0;
|
|
85
|
+
return !(a == null || a === '');
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
componyx.UI.form_modules.RuleEngine = class RuleEngine
|
|
90
|
+
{
|
|
91
|
+
#fields;
|
|
92
|
+
#isExecuting = false;
|
|
93
|
+
#hiddenFieldCache;
|
|
94
|
+
#dependencyMap;
|
|
95
|
+
#originalFieldStates = new Map();
|
|
96
|
+
#visible = { on: 'show', off: 'hide' };
|
|
97
|
+
#readOnly = { on: 'readOnly', off: 'editable' };
|
|
98
|
+
#disabled = { on: 'disable', off: 'enable' };
|
|
99
|
+
#required = { on: 'required', off: 'optional' };
|
|
100
|
+
|
|
101
|
+
constructor(form)
|
|
102
|
+
{
|
|
103
|
+
this.form = form;
|
|
104
|
+
this.dataBinder = form.dataBinder;
|
|
105
|
+
this.InputTypeOption = form.constructor.InputTypeOption;
|
|
106
|
+
this.TriggerOption = form.constructor.RuleCaseTriggerOption;
|
|
107
|
+
this.RunVisibilityOption = form.constructor.RuleCaseRunVisibilityOption;
|
|
108
|
+
this.ComparisonOperatorOption = form.constructor.RuleComparisonOperatorOption;
|
|
109
|
+
this.ActionOption = form.constructor.RuleActionOption;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
destroy()
|
|
113
|
+
{
|
|
114
|
+
this.#restoreFields();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Evaluates all rules for all fields/fieldsets and executes actions for matching rule cases.
|
|
119
|
+
* @param {componyx.UI.Form.RuleCaseTriggerOption} caseTrigger The trigger causing this evaluation (e.g., INIT, SHOW_SECTION, SUBMIT).
|
|
120
|
+
*/
|
|
121
|
+
initRules(trigger = this.TriggerOption.INIT)
|
|
122
|
+
{
|
|
123
|
+
if (!this.#dependencyMap)
|
|
124
|
+
this.#buildDependencyMap();
|
|
125
|
+
|
|
126
|
+
this.#hiddenFieldCache = {};
|
|
127
|
+
this.form.getFields().forEach(field => this.#applyRulesChain(field, trigger));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Evaluates the field/fieldset rules and executes actions for each rule case if conditions are met.
|
|
132
|
+
* @param {componyx.UI.Form.Field} changedField The field for which the rule actions are applied.
|
|
133
|
+
* @param {componyx.UI.Form.RuleCaseTriggerOption} caseTrigger The trigger that caused this evaluation.
|
|
134
|
+
*/
|
|
135
|
+
applyRules(changedField, trigger = this.TriggerOption.CHANGE)
|
|
136
|
+
{
|
|
137
|
+
this.#applyRulesChain(changedField, trigger);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Updates the temporary rule-driven state of a field.
|
|
142
|
+
* @param {Object} field - The form field whose rule-driven state should be updated.
|
|
143
|
+
* @param {string} key - The property name being updated (e.g., 'readOnly', 'value').
|
|
144
|
+
* @param {*} newValue - The new value to store for this rule-driven property.
|
|
145
|
+
* @param {boolean} manualChange - A value indicating if this value was set manually.
|
|
146
|
+
*/
|
|
147
|
+
updateRuleDrivenState(field, key, newValue, manualChange = false)
|
|
148
|
+
{
|
|
149
|
+
const entry = this.#originalFieldStates.get(field.id);
|
|
150
|
+
if (!entry) return;
|
|
151
|
+
|
|
152
|
+
entry.ruleState ??= {};
|
|
153
|
+
entry.ruleState[key] = newValue;
|
|
154
|
+
entry.manualChange = manualChange;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Build the dependency map where each field holds a set of its dependend fields stored by field id.
|
|
159
|
+
* @private
|
|
160
|
+
*/
|
|
161
|
+
#buildDependencyMap()
|
|
162
|
+
{
|
|
163
|
+
this.#dependencyMap = new Map();
|
|
164
|
+
|
|
165
|
+
this.form.getFields().forEach(field =>
|
|
166
|
+
{
|
|
167
|
+
const targetId = field.id;
|
|
168
|
+
(field.ruleCases || []).forEach(ruleCase =>
|
|
169
|
+
{
|
|
170
|
+
(ruleCase.ruleGroups || []).forEach(group =>
|
|
171
|
+
{
|
|
172
|
+
(group.rules || []).forEach(rule =>
|
|
173
|
+
{
|
|
174
|
+
const sourceId = rule.sourceId;
|
|
175
|
+
if (!this.#dependencyMap.has(sourceId))
|
|
176
|
+
{
|
|
177
|
+
this.#dependencyMap.set(sourceId, new Set());
|
|
178
|
+
}
|
|
179
|
+
this.#dependencyMap.get(sourceId).add(targetId);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#restoreFields()
|
|
187
|
+
{
|
|
188
|
+
const fields = this.form.getFields();
|
|
189
|
+
|
|
190
|
+
for (const [fieldId] of this.#originalFieldStates)
|
|
191
|
+
{
|
|
192
|
+
const field = this.form.getField(fieldId, null, fields);
|
|
193
|
+
if (!field) continue;
|
|
194
|
+
|
|
195
|
+
this.#restoreOriginalState(field, true);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
this.#originalFieldStates = new Map();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Evaluates the field/fieldset rules and executes actions for each rule case if conditions are met.
|
|
203
|
+
* @private
|
|
204
|
+
* @param {Field} changedField - The field or fieldset to evaluate.
|
|
205
|
+
* @param {string} trigger - The current lifecycle trigger ('init' or 'change').
|
|
206
|
+
*/
|
|
207
|
+
async #applyRulesChain(changedField, trigger)
|
|
208
|
+
{
|
|
209
|
+
if (this.#isExecuting)
|
|
210
|
+
return;
|
|
211
|
+
|
|
212
|
+
this.#isExecuting = true;
|
|
213
|
+
|
|
214
|
+
try
|
|
215
|
+
{
|
|
216
|
+
this.#fields = this.form.getFields();
|
|
217
|
+
|
|
218
|
+
const visited = new Set(),
|
|
219
|
+
queue = [...(this.#dependencyMap.get(changedField.id) || []), ...this.#getUnconditionalFields(trigger)];
|
|
220
|
+
|
|
221
|
+
while (queue.length > 0)
|
|
222
|
+
{
|
|
223
|
+
const currentId = queue.shift();
|
|
224
|
+
if (visited.has(currentId)) continue;
|
|
225
|
+
visited.add(currentId);
|
|
226
|
+
|
|
227
|
+
const currentField = this.form.getField(currentId, null, this.#fields);
|
|
228
|
+
if (!currentField) continue;
|
|
229
|
+
|
|
230
|
+
this.#restoreOriginalState(currentField);
|
|
231
|
+
|
|
232
|
+
if (this.form.ended) return; // If form ended, stop executing rules and actions
|
|
233
|
+
|
|
234
|
+
const section = this.form.getParentSection(currentField);
|
|
235
|
+
|
|
236
|
+
// Process each ruleCase sequentially
|
|
237
|
+
const ruleCases = currentField.ruleCases || [];
|
|
238
|
+
for (const ruleCase of ruleCases)
|
|
239
|
+
{
|
|
240
|
+
if (section && !section.visible && ruleCase.runVisibility !== this.RunVisibilityOption.SECTION_HIDDEN)
|
|
241
|
+
continue;
|
|
242
|
+
|
|
243
|
+
if (!ruleCase.trigger || ruleCase.trigger === this.TriggerOption.ALWAYS || ruleCase.trigger === trigger)
|
|
244
|
+
{
|
|
245
|
+
const isHidden = this.#isFieldHidden(currentField);
|
|
246
|
+
if (isHidden && (ruleCase.runVisibility !== this.RunVisibilityOption.HIDDEN && ruleCase.runVisibility !== this.RunVisibilityOption.SECTION_HIDDEN))
|
|
247
|
+
continue;
|
|
248
|
+
|
|
249
|
+
const caseMatched = this.#evaluateCase(ruleCase);
|
|
250
|
+
if (!caseMatched)
|
|
251
|
+
continue;
|
|
252
|
+
|
|
253
|
+
const actions = ruleCase.actions || [];
|
|
254
|
+
for (const action of actions)
|
|
255
|
+
{
|
|
256
|
+
const target = action.targetFieldSetId ? this.form.getFieldSet(action.targetFieldSetId) : currentField,
|
|
257
|
+
runtime = this.form.runtimeActions[action.runtimeActionId];
|
|
258
|
+
|
|
259
|
+
this.#storeFieldState(currentField, action);
|
|
260
|
+
const result = await this.form.executeAction(action, target); // await confirmation if mustAwait
|
|
261
|
+
|
|
262
|
+
if (runtime?.mustAwait && result && result.success === false)
|
|
263
|
+
break; // skip remaining actions in this ruleCase
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Add all dependents of currentField to the queue
|
|
269
|
+
(this.#dependencyMap.get(currentId) || []).forEach(depId =>
|
|
270
|
+
{
|
|
271
|
+
if (!visited.has(depId))
|
|
272
|
+
queue.push(depId);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
finally
|
|
277
|
+
{
|
|
278
|
+
this.#isExecuting = false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
#getUnconditionalFields(trigger)
|
|
283
|
+
{
|
|
284
|
+
const result = [];
|
|
285
|
+
|
|
286
|
+
this.#fields.forEach(field =>
|
|
287
|
+
{
|
|
288
|
+
const cases = field.ruleCases || [];
|
|
289
|
+
cases.forEach(ruleCase =>
|
|
290
|
+
{
|
|
291
|
+
const hasNoRules = !ruleCase.ruleGroups || ruleCase.ruleGroups.length === 0,
|
|
292
|
+
hasActions = ruleCase.actions && ruleCase.actions.length > 0,
|
|
293
|
+
triggerMatches = !ruleCase.trigger || ruleCase.trigger === this.TriggerOption.ALWAYS || ruleCase.trigger === trigger;
|
|
294
|
+
|
|
295
|
+
if (hasNoRules && hasActions && triggerMatches)
|
|
296
|
+
{
|
|
297
|
+
result.push(field.id);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#storeFieldState(field, action)
|
|
306
|
+
{
|
|
307
|
+
const relevantActions = [this.#visible.on, this.#visible.off, this.#readOnly.on, this.#readOnly.off,
|
|
308
|
+
this.#disabled.on, this.#disabled.off, this.#required.on, this.#required.off, 'value', 'expression', 'feedback'];
|
|
309
|
+
|
|
310
|
+
if (!relevantActions.includes(action?.runtimeActionId)) return; // nothing to store
|
|
311
|
+
|
|
312
|
+
const fields = action?.targetFieldSetId
|
|
313
|
+
? this.form.getFields(this.form.getFieldSet(action.targetFieldSetId)) || []
|
|
314
|
+
: [field];
|
|
315
|
+
|
|
316
|
+
const props = {};
|
|
317
|
+
if ([this.#required.on, this.#required.off].includes(action.runtimeActionId)) props.required = true;
|
|
318
|
+
if ([this.#visible.on, this.#visible.off].includes(action.runtimeActionId)) props.visible = true;
|
|
319
|
+
if ([this.#readOnly.on, this.#readOnly.off].includes(action.runtimeActionId)) props.readOnly = true;
|
|
320
|
+
if ([this.#disabled.on, this.#disabled.off].includes(action.runtimeActionId)) props.disabled = true;
|
|
321
|
+
|
|
322
|
+
fields.forEach(f =>
|
|
323
|
+
{
|
|
324
|
+
const entry = this.#originalFieldStates.get(f.id) || { original: {}, affectedByFieldSet: null };
|
|
325
|
+
|
|
326
|
+
Object.keys(props).forEach(prop =>
|
|
327
|
+
{
|
|
328
|
+
if (!(prop in entry.original))
|
|
329
|
+
entry.original[prop] = f[prop];
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
if (action.runtimeActionId == 'feedback' && !('feedback' in entry.original))
|
|
333
|
+
{
|
|
334
|
+
const ruleFeedbackEl = this.#getFeedbackElement(f);
|
|
335
|
+
|
|
336
|
+
if (ruleFeedbackEl)
|
|
337
|
+
entry.original.feedback = ruleFeedbackEl.textContent;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (!('value' in entry.original))
|
|
341
|
+
{
|
|
342
|
+
entry.original.value = this.dataBinder.getModelValue(f);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Store the state of field options (disabled state of options)
|
|
346
|
+
if (f.options && Array.isArray(f.options))
|
|
347
|
+
{
|
|
348
|
+
entry.original.options = f.options.map(opt => ({
|
|
349
|
+
id: opt.id,
|
|
350
|
+
disabled: opt.disabled
|
|
351
|
+
}));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (action.targetFieldSetId)
|
|
355
|
+
entry.affectedByFieldSet = action.targetFieldSetId;
|
|
356
|
+
|
|
357
|
+
this.#originalFieldStates.set(f.id, entry);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
#restoreOriginalState(field, ignoreManualFlag)
|
|
362
|
+
{
|
|
363
|
+
const entry = this.#originalFieldStates.get(field.id);
|
|
364
|
+
if (!entry) return;
|
|
365
|
+
|
|
366
|
+
const restore = f =>
|
|
367
|
+
{
|
|
368
|
+
const entry = this.#originalFieldStates.get(f.id);
|
|
369
|
+
if (!entry) return;
|
|
370
|
+
|
|
371
|
+
const { value, feedback, options, ...switchStates } = entry.original;
|
|
372
|
+
|
|
373
|
+
if ('value' in entry.original && ignoreManualFlag || !entry.manualChange)
|
|
374
|
+
{
|
|
375
|
+
if (Operators.equal(entry.ruleState?.value, this.dataBinder.getModelValue(f)))
|
|
376
|
+
this.form.dataBinder.setModelValue(f, entry.original.value); // only restore if it holds the value set by the action
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (('feedback' in entry.original))
|
|
380
|
+
{
|
|
381
|
+
const ruleFeedbackEl = this.#getFeedbackElement(f);
|
|
382
|
+
|
|
383
|
+
if (ruleFeedbackEl && entry.ruleState?.feedback === ruleFeedbackEl.textContent)
|
|
384
|
+
ruleFeedbackEl.textContent = entry.original.feedback; // only restore if it holds the feedback msg set by the action
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Restore options (disabled state for each option)
|
|
388
|
+
if (options && Array.isArray(options))
|
|
389
|
+
{
|
|
390
|
+
f.options.forEach(opt =>
|
|
391
|
+
{
|
|
392
|
+
const storedOpt = options.find(o => o.id === opt.id);
|
|
393
|
+
if (storedOpt)
|
|
394
|
+
{
|
|
395
|
+
opt.disabled = storedOpt.disabled;
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (!$lib.isEmpty(switchStates))
|
|
401
|
+
{
|
|
402
|
+
Object.assign(f, switchStates);
|
|
403
|
+
this.form.updateField(f);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
if (entry.affectedByFieldSet)
|
|
408
|
+
{
|
|
409
|
+
const siblings = this.form.getFields(this.form.getFieldSet(entry.affectedByFieldSet)) || [];
|
|
410
|
+
siblings.forEach(restore);
|
|
411
|
+
}
|
|
412
|
+
else
|
|
413
|
+
{
|
|
414
|
+
restore(field);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
#getFeedbackElement(field)
|
|
419
|
+
{
|
|
420
|
+
return field.element.querySelector('.' + this.form.getCssClass(this.form.classOption.RULE_FEEDBACK));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
#isFieldHidden(field)
|
|
424
|
+
{
|
|
425
|
+
if (field.id in this.#hiddenFieldCache)
|
|
426
|
+
return this.#hiddenFieldCache[field.id];
|
|
427
|
+
|
|
428
|
+
let current = field,
|
|
429
|
+
hidden = false;
|
|
430
|
+
|
|
431
|
+
while (current)
|
|
432
|
+
{
|
|
433
|
+
if (current.visible === false)
|
|
434
|
+
{
|
|
435
|
+
hidden = true;
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
if (current.type === 'Section') break;
|
|
439
|
+
|
|
440
|
+
current = current.parent;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
this.#hiddenFieldCache[field.id] = hidden;
|
|
444
|
+
return hidden;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Loops through all ruleGroups in a case and evaluates them to see if they match.
|
|
449
|
+
* @private
|
|
450
|
+
* @param {Object} ruleCase
|
|
451
|
+
* @returns {boolean} A value indicating if the rule case matches.
|
|
452
|
+
*/
|
|
453
|
+
#evaluateCase(ruleCase)
|
|
454
|
+
{
|
|
455
|
+
if (!ruleCase.ruleGroups || ruleCase.ruleGroups.length === 0) return true;
|
|
456
|
+
|
|
457
|
+
let result;
|
|
458
|
+
const groups = ruleCase.ruleGroups;
|
|
459
|
+
|
|
460
|
+
for (let index = 0; index < groups.length; index++)
|
|
461
|
+
{
|
|
462
|
+
const matched = this.#evaluateGroup(groups[index]);
|
|
463
|
+
|
|
464
|
+
if (index === 0)
|
|
465
|
+
{
|
|
466
|
+
result = matched;
|
|
467
|
+
}
|
|
468
|
+
else
|
|
469
|
+
{
|
|
470
|
+
const op = groups[index - 1].logicalOperator || 'AND';
|
|
471
|
+
result = (op === 'OR') ? (result || matched) : (result && matched);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return result;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Loops through rules in a group and returns true if the group matches.
|
|
480
|
+
* @private
|
|
481
|
+
* @param {Object} group
|
|
482
|
+
* @returns {boolean} A value indicating if the group matches.
|
|
483
|
+
*/
|
|
484
|
+
#evaluateGroup(group)
|
|
485
|
+
{
|
|
486
|
+
if (!group?.rules || group.rules.length === 0) return false;
|
|
487
|
+
|
|
488
|
+
let result;
|
|
489
|
+
const rules = group.rules;
|
|
490
|
+
|
|
491
|
+
for (let index = 0; index < rules.length; index++)
|
|
492
|
+
{
|
|
493
|
+
const matched = this.#evaluateRule(rules[index]);
|
|
494
|
+
|
|
495
|
+
if (index === 0)
|
|
496
|
+
{
|
|
497
|
+
result = matched;
|
|
498
|
+
}
|
|
499
|
+
else
|
|
500
|
+
{
|
|
501
|
+
// operator that combines rules[i-1] with rules[i]
|
|
502
|
+
const op = rules[index - 1].logicalOperator || 'AND';
|
|
503
|
+
result = (op === 'OR') ? (result || matched) : (result && matched);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return result;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Evaluates a single rule and returns true if the rule matches.
|
|
512
|
+
* @private
|
|
513
|
+
* @param {Object} rule
|
|
514
|
+
* @returns {boolean} A value indicating if the rule matches.
|
|
515
|
+
*/
|
|
516
|
+
#evaluateRule(rule)
|
|
517
|
+
{
|
|
518
|
+
if (!rule?.sourceId || !rule.comparisonOperator)
|
|
519
|
+
return false;
|
|
520
|
+
|
|
521
|
+
const target = this.#getRuleTarget(rule);
|
|
522
|
+
if (!target)
|
|
523
|
+
return false;
|
|
524
|
+
|
|
525
|
+
const isValueOperator = Operators.hasOwnProperty(rule.comparisonOperator);
|
|
526
|
+
let valueToCheck;
|
|
527
|
+
|
|
528
|
+
if (isValueOperator)
|
|
529
|
+
{
|
|
530
|
+
if (target.inputType === this.InputTypeOption.CHECKBOX || target.inputType === this.InputTypeOption.SWITCH)
|
|
531
|
+
{
|
|
532
|
+
const arrVal = this.dataBinder.getModelValue(target);
|
|
533
|
+
|
|
534
|
+
if (Array.isArray(arrVal))
|
|
535
|
+
{
|
|
536
|
+
if (target.componentSettings?.range)
|
|
537
|
+
{
|
|
538
|
+
valueToCheck = rule.isRangeEnd ? arrVal[1] : arrVal[0];
|
|
539
|
+
}
|
|
540
|
+
else
|
|
541
|
+
valueToCheck = arrVal;
|
|
542
|
+
}
|
|
543
|
+
else
|
|
544
|
+
{
|
|
545
|
+
valueToCheck = (target.options || []).filter(opt =>
|
|
546
|
+
{
|
|
547
|
+
return !!this.dataBinder.getModelValue(opt); // make boolean of result
|
|
548
|
+
}).map(opt => (opt.value !== undefined ? opt.value : (opt.name || null))).filter(v => v !== null); // first filter all checked options, then either take the value (if set), otherwise the name
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
else
|
|
552
|
+
{
|
|
553
|
+
valueToCheck = this.dataBinder.getModelValue(target);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const operatorFn = Operators[rule.comparisonOperator];
|
|
557
|
+
return operatorFn ? operatorFn(valueToCheck, rule.value) : false;
|
|
558
|
+
}
|
|
559
|
+
else
|
|
560
|
+
{
|
|
561
|
+
const propMap = {
|
|
562
|
+
[this.ComparisonOperatorOption.VISIBLE]: ['visible', true],
|
|
563
|
+
[this.ComparisonOperatorOption.DISABLED]: ['disabled', true],
|
|
564
|
+
[this.ComparisonOperatorOption.REQUIRED]: ['required', true],
|
|
565
|
+
[this.ComparisonOperatorOption.HIDDEN]: ['visible', false],
|
|
566
|
+
[this.ComparisonOperatorOption.ENABLED]: ['disabled', false],
|
|
567
|
+
[this.ComparisonOperatorOption.OPTIONAL]: ['required', false],
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
const [propertyName, expectedValue] = propMap[rule.comparisonOperator];
|
|
571
|
+
return target[propertyName] === expectedValue;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
#getRuleTarget(rule)
|
|
576
|
+
{
|
|
577
|
+
if (!rule?.sourceId) return null;
|
|
578
|
+
|
|
579
|
+
return this.form.getField(rule.sourceId, null, this.#fields) || null;
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
var RuleEngine = componyx.UI.form_modules.RuleEngine;
|
|
584
|
+
|
|
585
|
+
export { RuleEngine as default };
|