@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.
Files changed (169) hide show
  1. package/AppSkin/AppSkin.css +1268 -0
  2. package/AppSkin/AppSkin.js +2748 -0
  3. package/AppSkin/Themes/Default.css +241 -0
  4. package/Base/Base.js +3407 -0
  5. package/Base/ComponentIcons.css +66 -0
  6. package/Base/Font/ComponentIcons.woff +0 -0
  7. package/Base/Font/UI.woff +0 -0
  8. package/Base/Library.js +8432 -0
  9. package/Base/NavigationManager.js +462 -0
  10. package/Base/ResponsiveTemplates.js +371 -0
  11. package/Base/Sanitizer.js +288 -0
  12. package/Base/Themes/Default.css +255 -0
  13. package/Base/UI.css +618 -0
  14. package/Base/UIResourcePath.js +6 -0
  15. package/Bindary/Bindary.js +2011 -0
  16. package/Bindary/Modules/Connection.js +216 -0
  17. package/Bindary/Modules/Core.js +854 -0
  18. package/Bindary/Modules/DataUpdater.js +351 -0
  19. package/Bindary/Modules/ExpressionEngine.js +347 -0
  20. package/Bindary/Modules/ProxyManager.js +482 -0
  21. package/Bindary/Modules/Renderer.js +605 -0
  22. package/Bindary/Modules/Router.js +291 -0
  23. package/Bindary/Modules/Scroller.js +89 -0
  24. package/Bindary/Modules/Template.js +1273 -0
  25. package/BookmarkSpy/BookmarkSpy.js +430 -0
  26. package/Box/Box.css +36 -0
  27. package/Box/Box.js +1685 -0
  28. package/Button/Button.css +470 -0
  29. package/Button/Button.js +1398 -0
  30. package/Button/Themes/Default.css +200 -0
  31. package/Carousel/Carousel.css +273 -0
  32. package/Carousel/Carousel.js +607 -0
  33. package/ColorButton/ColorButton.css +39 -0
  34. package/ColorButton/ColorButton.js +336 -0
  35. package/ColorPicker/ColorPicker.css +359 -0
  36. package/ColorPicker/ColorPicker.js +972 -0
  37. package/ComboBox/ComboBox.css +357 -0
  38. package/ComboBox/ComboBox.js +2342 -0
  39. package/ComboBox/Themes/Default.css +23 -0
  40. package/DataPager/DataPager.css +72 -0
  41. package/DataPager/DataPager.js +674 -0
  42. package/DatePicker/DatePicker.css +227 -0
  43. package/DatePicker/DatePicker.js +1531 -0
  44. package/DatePicker/Themes/Default.css +26 -0
  45. package/Dialog/Dialog.css +106 -0
  46. package/Dialog/Dialog.js +572 -0
  47. package/Dialog/Themes/Default.css +10 -0
  48. package/Editor/Data/Emoji.json +8285 -0
  49. package/Editor/Data/Special.json +1042 -0
  50. package/Editor/Editor.css +1562 -0
  51. package/Editor/Editor.js +4058 -0
  52. package/Editor/Font/Editor.woff +0 -0
  53. package/Editor/Modules/ComponentFactory.js +565 -0
  54. package/Editor/Modules/ContentManager.js +748 -0
  55. package/Editor/Modules/DialogManager.js +1234 -0
  56. package/Editor/Modules/EventManager.js +458 -0
  57. package/Editor/Modules/Format.js +950 -0
  58. package/Editor/Modules/History.js +191 -0
  59. package/Editor/Modules/LayoutState.js +379 -0
  60. package/Editor/Modules/ListManager.js +296 -0
  61. package/Editor/Modules/MenuManager.js +388 -0
  62. package/Editor/Modules/NodeManager.js +681 -0
  63. package/Editor/Modules/ParagraphButtons.js +122 -0
  64. package/Editor/Modules/Resizer.js +176 -0
  65. package/Editor/Modules/SelectionRange.js +564 -0
  66. package/Editor/Modules/SourceViewBuilder.js +212 -0
  67. package/Editor/Modules/SymbolPicker.js +208 -0
  68. package/Editor/Modules/Table.js +2327 -0
  69. package/Editor/Modules/TableDialog.js +757 -0
  70. package/Editor/Modules/Toolbar.js +375 -0
  71. package/Editor/Modules/TooltipMenu.js +125 -0
  72. package/Editor/Modules/Utility.js +131 -0
  73. package/Editor/Themes/Default.css +204 -0
  74. package/FileUpload/FileUpload.css +156 -0
  75. package/FileUpload/FileUpload.js +898 -0
  76. package/FileUpload/Themes/Default.css +17 -0
  77. package/Form/Form.css +1934 -0
  78. package/Form/Form.js +4943 -0
  79. package/Form/Modules/ActionManager.js +368 -0
  80. package/Form/Modules/BuildPanelManager.js +319 -0
  81. package/Form/Modules/ComponentFactory.js +468 -0
  82. package/Form/Modules/ComponentPanel.js +265 -0
  83. package/Form/Modules/ConfigPanelManager.js +685 -0
  84. package/Form/Modules/DataBinder.js +602 -0
  85. package/Form/Modules/DataObserver.js +717 -0
  86. package/Form/Modules/Draggable.js +756 -0
  87. package/Form/Modules/OptionsPanel.js +342 -0
  88. package/Form/Modules/Renderer.js +1889 -0
  89. package/Form/Modules/RuleEngine.js +585 -0
  90. package/Form/Modules/RulesPanel.js +1158 -0
  91. package/Form/Modules/Types.js +1709 -0
  92. package/Form/Modules/ValidationManager.js +194 -0
  93. package/Form/Themes/Default.css +216 -0
  94. package/FormField/FormField.css +468 -0
  95. package/FormField/FormField.js +576 -0
  96. package/FormField/Themes/Default.css +99 -0
  97. package/Grid/Grid.css +544 -0
  98. package/Grid/Grid.js +3761 -0
  99. package/Grid/Themes/Default.css +43 -0
  100. package/LICENSE +126 -0
  101. package/MaskedTextBox/MaskedTextBox.css +28 -0
  102. package/MaskedTextBox/MaskedTextBox.js +642 -0
  103. package/MaskedTextBox/Themes/Default.css +6 -0
  104. package/Menu/Menu.css +328 -0
  105. package/Menu/Menu.js +2386 -0
  106. package/Menu/Themes/Default.css +24 -0
  107. package/NumericBox/NumericBox.css +120 -0
  108. package/NumericBox/NumericBox.js +984 -0
  109. package/PanelBar/PanelBar.css +123 -0
  110. package/PanelBar/PanelBar.js +505 -0
  111. package/PanelBar/Themes/Default.css +51 -0
  112. package/PieChart/PieChart.css +95 -0
  113. package/PieChart/PieChart.js +820 -0
  114. package/PieChart/Themes/Default.css +9 -0
  115. package/README.md +178 -3
  116. package/Slider/Slider.css +348 -0
  117. package/Slider/Slider.js +1306 -0
  118. package/Slider/Themes/Default.css +44 -0
  119. package/TabStrip/TabStrip.css +52 -0
  120. package/TabStrip/TabStrip.js +362 -0
  121. package/TabStrip/Themes/Default.css +35 -0
  122. package/TimePicker/Themes/Default.css +30 -0
  123. package/TimePicker/TimePicker.css +253 -0
  124. package/TimePicker/TimePicker.js +1485 -0
  125. package/TooltipManager/Themes/Default.css +21 -0
  126. package/TooltipManager/TooltipManager.css +227 -0
  127. package/TooltipManager/TooltipManager.js +791 -0
  128. package/Validator/Validator.css +54 -0
  129. package/Validator/Validator.js +2508 -0
  130. package/componyx.html-data.json +619 -0
  131. package/index.js +27 -0
  132. package/package.json +43 -4
  133. package/types/AppSkin.d.ts +842 -0
  134. package/types/Base.d.ts +1034 -0
  135. package/types/Bindary.d.ts +1196 -0
  136. package/types/BookmarkSpy.d.ts +112 -0
  137. package/types/Box.d.ts +374 -0
  138. package/types/Button.d.ts +455 -0
  139. package/types/Carousel.d.ts +136 -0
  140. package/types/ColorButton.d.ts +120 -0
  141. package/types/ColorPicker.d.ts +225 -0
  142. package/types/ComboBox.d.ts +507 -0
  143. package/types/DataPager.d.ts +152 -0
  144. package/types/DatePicker.d.ts +354 -0
  145. package/types/Dialog.d.ts +160 -0
  146. package/types/Editor.d.ts +1289 -0
  147. package/types/FileUpload.d.ts +206 -0
  148. package/types/Form.d.ts +3078 -0
  149. package/types/FormField.d.ts +204 -0
  150. package/types/Grid.d.ts +911 -0
  151. package/types/Library.d.ts +1899 -0
  152. package/types/MaskedTextBox.d.ts +91 -0
  153. package/types/Menu.d.ts +577 -0
  154. package/types/NumericBox.d.ts +310 -0
  155. package/types/PanelBar.d.ts +223 -0
  156. package/types/PieChart.d.ts +224 -0
  157. package/types/ResponsiveTemplates.d.ts +108 -0
  158. package/types/Slider.d.ts +227 -0
  159. package/types/TabStrip.d.ts +185 -0
  160. package/types/TimePicker.d.ts +265 -0
  161. package/types/TooltipManager.d.ts +178 -0
  162. package/types/UI.d.ts +29 -0
  163. package/types/Validator.d.ts +653 -0
  164. package/types/index.d.ts +59 -0
  165. package/ui.css +11783 -0
  166. package/ui.js +79477 -0
  167. package/ui.min.css +138 -0
  168. package/ui.min.js +264 -0
  169. package/ui.min.js.map +1 -0
@@ -0,0 +1,1273 @@
1
+ /*! Componyx.UI | (c) E.H. Daanen / Componyx | BUSL-1.1 | componyx.com/license */
2
+ await (async function ()
3
+ {
4
+ await import(`${$UI.getScriptResourcePath('Base.Bindary.Core')}`);
5
+ await import(`${$UI.getScriptResourcePath('Base.Bindary.DataUpdater')}`);
6
+ const core = componyx.bindary_modules.core;
7
+ const dataUpdater = componyx.bindary_modules.dataUpdater;
8
+
9
+ /**
10
+ * The base template class.
11
+ * @class
12
+ * @memberof componyx.bindary
13
+ */
14
+ class TemplateBase
15
+ {
16
+ resolveLivePath(path)
17
+ {
18
+ let orgPath = path,
19
+ me = this;
20
+
21
+ if (!path) return path;
22
+
23
+ if (me.livePath[path]) return me.livePath[path];
24
+
25
+ let parent = me.parent,
26
+ parentRepItem = me.getParentRepeater(),
27
+ useItemContext = ((me.contextDataKey && path != me.contextDataKeyPath) || (me.repeatDataKeyPath && path != me.repeatDataKeyPath)),
28
+ contextItem = useItemContext ? me : parent,
29
+ livePath, contextPath;
30
+
31
+ if (path.endsWith(core.dksc + core.valueKey) || path.endsWith(core.dksc + core.key))
32
+ {
33
+ path = path.replace(new RegExp('\\' + core.dksc.split('').join('\\') + '(\\$value|\\$key)$'), '');
34
+ }
35
+
36
+ // Adds the collection key to the live path
37
+ if (parentRepItem)
38
+ {
39
+ path = path.replace(parentRepItem.repeatDataKeyPath, parentRepItem.repeatDataKeyPath + core.dksc + parentRepItem.key);
40
+ }
41
+
42
+ if (contextItem)
43
+ {
44
+ contextPath = contextItem.contextDataKeyPath;
45
+ livePath = contextItem.livePath ? contextItem.livePath[contextPath] : null;
46
+
47
+ if (livePath && livePath != contextPath && !path.startsWith(livePath) && path.startsWith(contextPath))
48
+ {
49
+ path = path.replace(contextPath, livePath); // Fast convert when parent's cached path is available
50
+ }
51
+ }
52
+
53
+ if (path.indexOf(core.ipc) > -1)
54
+ { // Path contains pointer
55
+ let parts = path.split(core.dksc),
56
+ pointerPaths = [];
57
+
58
+ $lib.each(parts, (p, index) =>
59
+ {
60
+ if (p.indexOf(core.ipc) == 0)
61
+ {
62
+ let key = me.indexKey[p.substr(core.ipc.length)];
63
+
64
+ if (typeof key != 'number')
65
+ {
66
+ pointerPaths.push(key.join(core.dksc).substr(core.dksc.length + '$'.length)); // Add data-key to pointer path
67
+ key = me.getDataValue(key, contextItem); // Dynamic index/key pointer
68
+ }
69
+
70
+ parts[index] = key;
71
+ }
72
+ });
73
+
74
+ me.pointerPaths = pointerPaths;
75
+ path = parts.join(core.dksc);
76
+ }
77
+
78
+ return (this.livePath[orgPath] = path); // Cache calculated path
79
+ }
80
+
81
+ getParentRepeater()
82
+ {
83
+ let item = this;
84
+
85
+ if (!item.inRepeat) return null;
86
+
87
+ while ((item = item.parent))
88
+ {
89
+ if (item.repeatDataKeyPath && !$lib.isEmpty(item.key))
90
+ {
91
+ return item;
92
+ }
93
+ }
94
+
95
+ return null;
96
+ }
97
+
98
+ getDataChange()
99
+ {
100
+ let me = this,
101
+ c = core.dataChanges,
102
+ m = core.modelDataChanges,
103
+ rules = c['#' + me.updateId] || ((m.size > 0) ? m.get(me.context) || m.get(me.repeatValue) : null);
104
+
105
+ if (!rules)
106
+ {
107
+ if (me.repeatDataKey)
108
+ {
109
+ rules = c[me.livePath[me.repeatDataKeyPath]];
110
+
111
+ if (!me.rewriteArray(rules))
112
+ {
113
+ rules = me.getChangedIndexes(rules);
114
+ }
115
+ }
116
+
117
+ if (!rules && me.hasParentChange())
118
+ {
119
+ rules = [];
120
+ }
121
+ }
122
+
123
+ if (!rules)
124
+ {
125
+ let paths = me.resolveLivePaths();
126
+
127
+ if (!$lib.isEmpty(me.pointerPaths))
128
+ {
129
+ paths = paths.concat(me.pointerPaths); // Also check pointer data-keys
130
+ }
131
+
132
+ $lib.each(paths, (path) =>
133
+ {
134
+ rules = c[path] || null; // Exact match check
135
+
136
+ if (!rules && path)
137
+ {
138
+ $lib.each(c, (r, key) =>
139
+ {
140
+ if (core.dataKeyMatch(path, key))
141
+ { // Partial match check
142
+ rules = [];
143
+ }
144
+
145
+ return (rules == null);
146
+ });
147
+ }
148
+
149
+ return (rules == null);
150
+ });
151
+ }
152
+
153
+ me.dataChange = rules || null;
154
+ }
155
+
156
+ rewriteArray(rules)
157
+ {
158
+ let rewrite = false;
159
+ $lib.each(rules, (r) =>
160
+ {
161
+ rewrite = $lib.isEmpty(r); // Empty object as rules means rewrite
162
+ return !rewrite;
163
+ });
164
+
165
+ return rewrite;
166
+ }
167
+
168
+ hasParentChange()
169
+ {
170
+ let me = this, parent = me.parent;
171
+
172
+ if (!parent || !parent.dataChange) return false;
173
+
174
+ let updateId = parent.updateId;
175
+
176
+ if (updateId && core.dataChanges['#' + updateId])
177
+ { // Always update child items when change by update-id
178
+ me.updateId = updateId;
179
+ return me.updateIdFromParent = true;
180
+ }
181
+
182
+ if (me.repeatDataKey || parent.repeatDataKey)
183
+ { // Repeater can only be updated by parent through the updateId (is rerender)
184
+ return false;
185
+ }
186
+
187
+ // Check if item context belongs to the parent-item context
188
+ return (me.paths.findIndex((p) =>
189
+ {
190
+ let found = false;
191
+ $lib.each(parent.paths, (pp) =>
192
+ {
193
+ return !(found = p.startsWith(pp));
194
+ });
195
+
196
+ return found;
197
+ }) > -1);
198
+ }
199
+
200
+ createDataKeyPath(key, parentKey)
201
+ {
202
+ let me = this;
203
+
204
+ if (key == null)
205
+ return key;
206
+
207
+ let k = key.join(core.dksc), merge = true, parent;
208
+
209
+ if (key[0] === '$')
210
+ {
211
+ k = k.substr('$'.length + core.dksc.length);
212
+ merge = false;
213
+ }
214
+
215
+ if (core.isKey(key[0], core.parentKey))
216
+ {
217
+ while (core.isKey(key[0], core.parentKey)) // routine for multiple $parent navigators in data-key
218
+ {
219
+ parent = me.parent;
220
+ key = key.slice(1); // remove $parent from key
221
+ }
222
+
223
+ parentKey = parent.contextDataKeyPath;
224
+ }
225
+
226
+ if (merge && parentKey)
227
+ return parentKey + core.dksc + k;
228
+ else
229
+ return k;
230
+ }
231
+
232
+ createDataKey(dataKey, isRoot)
233
+ {
234
+ let me = this;
235
+
236
+ if (!dataKey || dataKey == 'null')
237
+ return dataKey;
238
+
239
+ dataKey = dataKey.replace(/\[+([\w.$]+)\]+/g, function (match, capture)
240
+ {
241
+ let number = parseInt(capture, 10);
242
+
243
+ if (isNaN(number))
244
+ me.indexKey.push(core.addDataContainer(capture.split(core.dksc), false)); // dynamic index/key pointer
245
+ else
246
+ me.indexKey.push(number); // static index pointer
247
+
248
+ return core.dksc + core.ipc + (me.indexKey.length - 1);
249
+ });
250
+
251
+ if ($lib.startsWith(dataKey, core.dksc + core.ipc))
252
+ dataKey = dataKey.substr(core.dksc.length);
253
+
254
+ return core.addDataContainer(dataKey.split(core.dksc), isRoot);
255
+ }
256
+
257
+ getDataValue(dataKey, dc)
258
+ {
259
+ let me = this;
260
+
261
+ if (!dataKey)
262
+ return null;
263
+
264
+ if (core.isKey(dataKey[0], core.valueKey))
265
+ return (dc || '').toString();
266
+
267
+ let index = 0, current, curItem, arrIndex, root = dc;
268
+
269
+ if (dataKey[0] === '$') // set context to root
270
+ {
271
+ dc = $bindary;
272
+ dataKey = dataKey.slice(1); // remove $ from key
273
+
274
+ if ($lib.indexOf(core.dataLabel, dataKey[0]) == -1)
275
+ dc = window;
276
+ }
277
+ else if (core.isKey(dataKey[0], core.parentKey)) // set context to parent
278
+ {
279
+ curItem = me.parent;
280
+ current = curItem.context;
281
+
282
+ while (core.isKey(dataKey[0], core.parentKey)) // routine for multiple $parent navigators in data-key
283
+ {
284
+ while (dc === current && curItem) // context must be different
285
+ {
286
+ curItem = curItem.parent;
287
+ current = (curItem) ? curItem.context : null;
288
+ }
289
+
290
+ dataKey = dataKey.slice(1); // remove $parent from key
291
+ dc = current;
292
+ }
293
+ }
294
+
295
+ core.escapeDataKey(dataKey);
296
+
297
+ if (core.isKey(dataKey, core.key))
298
+ return me.key;
299
+
300
+ while (dc && index < dataKey.length)
301
+ {
302
+ if (dataKey[index].indexOf(core.ipc) == 0) // position index within array
303
+ {
304
+ arrIndex = me.indexKey[dataKey[index].substr(core.ipc.length)];
305
+
306
+ if (typeof arrIndex != 'number')
307
+ arrIndex = me.getDataValue(arrIndex, root); // dynamic index/key pointer
308
+
309
+ dc = dc[arrIndex];
310
+ index++;
311
+ }
312
+ else
313
+ dc = dc[dataKey[index++]];
314
+ }
315
+
316
+ return dc;
317
+ }
318
+ }
319
+
320
+ /**
321
+ * TemplateItem class.
322
+ * @class
323
+ * @property {componyx.bindary.TemplateItem} root The root item or itself if this is the root of the item tree.
324
+ * @property {componyx.bindary.TemplateItem} parent The parent item.
325
+ * @property {TemplateItem[]} children The child items of the current item.
326
+ * @property {String} id The unique identifier of the item.
327
+ * @property {HTMLElement} placeHolder A place holder element for an item that is not rendered.
328
+ * @property {HTMLElement} repeatPlaceHolder A place holder element for a repeating item.
329
+ * @property {Boolean} trusted A value indicating if the HTML value is trusted.
330
+ * @property {Boolean} includable A value indicating if the item is an includable item.
331
+ * @property {Boolean} includableRoot A value indicating if the item is the root of the includable.
332
+ * @property {Boolean} included A value indicating if an includable is included.
333
+ * @property {String} includeFile The include file data-key or path.
334
+ * @property {String} include The include data-key or identifier.
335
+ * @property {Boolean} isComponent A value indicating if the item is a component.
336
+ * @property {Boolean} loaded A value indicating if the included data or component is loaded.
337
+ * @property {HTMLElement} element The rendered element.
338
+ * @property {HTMLElement} sourceElement The original collected element. This element can differ from the rendered element when it is included or within a repeater.
339
+ * @property {String} display The original display style of the element.
340
+ * @property {elementInfo} elementInfo Information about the element.
341
+ * @property {Object} context The object context.
342
+ * @property {Object} repeatValue The repeat value context.
343
+ * @property {Object} repeatItemIdValue The value of the repeat item id.
344
+ * @property {Object} itemId The item is rendered if the item id matches the value of the repeat item id.
345
+ * @property {Number} index The current index within the array repeater.
346
+ * @property {String} key The current key within the object repeater.
347
+ * @property {Object} value The data value.
348
+ * @property {Boolean} isRendered A value indicating if the element is rendered.
349
+ * @property {Object[]} dataChange Holds the active data change rules for the item.
350
+ * @property {String[]|Number[]} indexKey The index key holds the dynamic (data-key) or static index pointer.
351
+ * @property {HTMLElement[]} repeatElements The collection of elements within the repeater.
352
+ * @property {String} dataType The type of the data value.
353
+ * @property {String} inputBind The data bind value.
354
+ * @property {Boolean|Number} liveBind A value indicating if live binding is enabled (true) or disabled (false) or the debounce delay in milliseconds.
355
+ * @property {String} updateId The update identifier.
356
+ * @property {Boolean} updateIdFromParent A value indicating that the updateId was copied from the parent item in the update loop.
357
+ * @property {Boolean} isHTML A value indicating if the data value is HTML.
358
+ * @property {String} if The value of the `if` attribute, representing either a method name or an expression string used to evaluate the conditional statement.
359
+ * @property {function} ifExpr The expression function that evaluates the conditional statement.
360
+ * @property {String} elseIf The value of the `elseif` attribute, representing either a method name or an expression string used to evaluate the conditional statement.
361
+ * @property {function} elseIfExpr The expression function that evaluates the conditional statement.
362
+ * @property {Boolean} else A flag indicating that this element should be rendered when none of the preceding conditional (if/elseif) statements evaluate to true.
363
+ * @property {Boolean} conditionMet A value indicating the state of the conditional statement.
364
+ * @property {String} dataFormatter The function invoked when the value is set on the data object.
365
+ * @property {String} viewFormatter The function invoked when the value is set on the element.
366
+ * @property {String} preRender The function invoked before the element is rendered.
367
+ * @property {String} postRender The function invoked after the element is rendered.
368
+ * @property {String} load The function invoked when an includable is included.
369
+ * @property {String} filter The function invoked to filter a collection.
370
+ * @property {Object} events The element events.
371
+ * @property {string[]} repeatDataKey - Hierarchical data key as an array of parts (e.g. ['myObj', 'value']).
372
+ * @property {string[]} repeatItemIdDataKey - Hierarchical data key array for the repeat item ID.
373
+ * @property {string[]} contextDataKey - Hierarchical data key array for the context value.
374
+ * @property {string[]} hasValue - Hierarchical data key array for value existence checks.
375
+ * @property {string[]} dataKey - Hierarchical data key array for the main data value.
376
+ * @property {string} expressionValue - The raw expression value if the data-key contains expressions denoted with brackets {{}}
377
+ * @property {function} expression - The expression function if the data-key contains expressions.
378
+ * @property {Object} attributes The data bound attributes.
379
+ * @property {function[]} attributeExpressions Expression functions for the attributes if the attribute values contain expressions.
380
+ * @property {string} repeatDataKeyPath - Full path for the repeat value. (e.g. 'appData.container.myArr').
381
+ * @property {string} contextDataKeyPath - Path for the context value.
382
+ * @property {string} hasValueDataKeyPath - Full path for value existence check.
383
+ * @property {string} dataKeyPath - Full path for the data value (value/html/bind) if its a single data-key.
384
+ * @property {string} dataKeyPaths - Full paths for all value data keys when using a value expression.
385
+ * @property {string[]} attributeDataKeyPaths - Full paths for all attribute data keys.
386
+ * @property {string[]} conditionalDataKeyPaths - Full paths for all the data keys used in conditional expressions.
387
+ * @property {Object[]} treeState Internal. Snapshot of this item's context and its ancestors', used to restore tree state later.
388
+ * @property {Number} treeStatePass Internal. Tracks which render pass last captured treeState, to avoid stale ancestor data.
389
+ * @memberof componyx.bindary
390
+ */
391
+ class TemplateItem extends TemplateBase
392
+ {
393
+ /**
394
+ * Creates an instance of TemplateItem.
395
+ *
396
+ * @param {HTMLElement} el - The DOM element associated with the template item.
397
+ * @param {Object} events - The events related to the template item.
398
+ * @param {Object} attributes - The attributes for the template item.
399
+ * @param {componyx.bindary.TemplateItem} parent - The parent TemplateItem if this item is a child.
400
+ * @param {boolean} appendToRoot - Determines whether to append to the root.
401
+ */
402
+ constructor(el, events, attributes, parent, appendToRoot)
403
+ {
404
+ super();
405
+ let me = this;
406
+
407
+ me.root = (parent) ? (parent.root) : me;
408
+
409
+ if (el)
410
+ {
411
+ me.id = el.id = el.id || core.getId(); // keep original id or generate id
412
+ el.setAttribute(core.attr.id, me.id);
413
+ }
414
+
415
+ me.parent = parent;
416
+ me.children = [];
417
+ me.repeatPlaceHolder = null;
418
+ me.trusted = null;
419
+ me.includable = null;
420
+ me.includableRoot = false;
421
+ me.included = false;
422
+ me.includeFile = null;
423
+ me.include = null;
424
+ me.isComponent = false;
425
+ me.loaded = false;
426
+ me.element = null;
427
+ me.sourceElement = null;
428
+ me.display = null;
429
+ me.elementInfo = null;
430
+ me.context = null;
431
+ me.repeatValue = null;
432
+ me.repeatItemIdValue = null;
433
+ me.itemId = null;
434
+ me.index = null;
435
+ me.key = null;
436
+ me.value = null;
437
+ me.isRendered = false;
438
+ me.dataChange = null;
439
+ me.indexKey = [];
440
+ me.repeatElements = {};
441
+ me.dataType = null;
442
+ me.inputBind = null;
443
+ me.liveBind = null;
444
+ me.updateId = null;
445
+ me.updateIdFromParent = null;
446
+ me.isHTML = false;
447
+ me.if = null;
448
+ me.ifExpr = null;
449
+ me.elseIf = null;
450
+ me.elseIfExpr = null;
451
+ me.else = null;
452
+ me.conditionMet = null;
453
+ me.dataFormatter = null;
454
+ me.viewFormatter = null;
455
+ me.preRender = null;
456
+ me.postRender = null;
457
+ me.load = null;
458
+ me.filter = null;
459
+ me.events = null;
460
+ me.hasValue = null;
461
+ me.dataKey = null;
462
+ me.expressionValue = null;
463
+ me.expression = null;
464
+ me.attributes = null;
465
+ me.attributeExpressions = null;
466
+ me.repeatDataKey = null;
467
+ me.repeatItemIdDataKey = null;
468
+ me.contextDataKey = null;
469
+ me.hasValueDataKeyPath = null;
470
+ me.repeatDataKeyPath = null;
471
+ me.contextDataKeyPath = null;
472
+ me.dataKeyPath = null;
473
+ me.dataKeyPaths = null;
474
+ me.attributeDataKeyPaths = null;
475
+ me.conditionalDataKeyPaths = null;
476
+ me.treeState = null;
477
+ me.treeStatePass = null;
478
+
479
+ if (parent)
480
+ parent.children.push(me);
481
+ else if (appendToRoot !== false)
482
+ core.viewTree.push(me); // root item
483
+
484
+ core.templateList.push(me);
485
+
486
+ if (el)
487
+ me.parseTemplateElement(el, events, attributes, parent);
488
+ }
489
+
490
+ parseTemplateElement(el, events, colAttributes, parent)
491
+ {
492
+ // data-order: repeat, context, value
493
+ let me = this,
494
+ { keep, live } = core.getTemplateFlags(el, ['keep', 'live']),
495
+ liveBind = live,
496
+ keepAttr = keep === null ? $bindary.keepAttributes : keep,
497
+ contextKeyPath = (parent) ? parent.contextDataKeyPath : null,
498
+ hasParentContext = (contextKeyPath != null),
499
+ hasParentInclude = (parent) ? parent.includable : false,
500
+ include = el.getAttribute(core.attr.include),
501
+ isRoot = !(el.hasAttribute(core.attr.repeat) || el.hasAttribute(core.attr.context) || hasParentContext || hasParentInclude),
502
+ text = el.getAttribute(core.attr.value),
503
+ html = el.getAttribute(core.attr.html),
504
+ inputBind = el.hasAttribute(core.attr.bind),
505
+ dataKey = el.getAttribute(core.attr.bind) || text || html || '',
506
+ attributes = el.getAttribute(core.attr.attributes),
507
+ hasValue = el.getAttribute(core.attr.hasValue),
508
+ updateValue = (key, value) =>
509
+ {
510
+ if (keepAttr || me[key] == undefined || !$lib.isEmpty(value))
511
+ me[key] = (value != 'null') ? value : null; // string value 'null' means clear previously stored value
512
+ };
513
+
514
+ if (keepAttr) // with keepAttributes enabled values are always set (removing the attribute will clear the stored value)
515
+ {
516
+ me.hasValue = null;
517
+ me.isHTML = false;
518
+ }
519
+
520
+ // these are always re-evaluated
521
+ me.expression = null;
522
+ me.ifExpr = null;
523
+ me.elseIfExpr = null;
524
+ me.dataKeyPaths = [];
525
+ me.conditionalDataKeyPaths = [];
526
+
527
+ if (html && $lib.startsWith(html, 'trust:'))
528
+ {
529
+ html = html.substr(6);
530
+ me.trusted = true;
531
+ }
532
+
533
+ me.includable = hasParentInclude;
534
+
535
+ if (el.hasAttribute(core.attr.includable))
536
+ {
537
+ me.includableRoot = (el.getAttribute(core.attr.includable) != 'null');
538
+ me.includable = (el.getAttribute(core.attr.includable) == 'null') ? hasParentInclude : true; // includable cannot be cleared if parent has includable specified
539
+ }
540
+
541
+ me.isComponent = (parent) ? parent.isComponent : false;
542
+ updateValue('component', el.getAttribute(core.attr.component));
543
+ me.isComponent = (me.component) ? true : me.isComponent;
544
+
545
+ me.parseInclude(include, hasParentContext);
546
+
547
+ if (me.isComponent)
548
+ isRoot = false;
549
+
550
+ const hasContentExpr = core.hasContentExpression(el);
551
+
552
+ if (!inputBind && (hasContentExpr || dataKey.indexOf('{{') != -1))
553
+ {
554
+ me.expressionValue = (hasContentExpr) ? el.textContent : dataKey;
555
+ dataKey = null;
556
+
557
+ if (hasContentExpr)
558
+ el.textContent = '';
559
+ }
560
+ else
561
+ dataKey = (!$lib.isEmpty(dataKey)) ? me.createDataKey(dataKey, isRoot) : null;
562
+
563
+
564
+ me.isRendered = false;
565
+ me.display = el.style.display;
566
+ me.sourceElement = me.tempElement = el; // tempElement is for backwards compatibility
567
+ me.elementInfo = me.getElementInfo(el);
568
+
569
+ updateValue('dataType', (el.getAttribute(core.attr.type) || '').toLowerCase());
570
+ updateValue('dataKey', dataKey);
571
+ updateValue('inputBind', inputBind);
572
+ updateValue('liveBind', liveBind);
573
+ updateValue('updateId', el.getAttribute(core.attr.updateId));
574
+ updateValue('itemId', el.getAttribute(core.attr.itemId));
575
+ updateValue('attributes', (attributes && attributes != 'null') ? window.JSON.parse('{' + core.createJSON(attributes) + '}') : attributes);
576
+
577
+ if (colAttributes)
578
+ {
579
+ me.attributes = me.attributes || {};
580
+ $lib.each(colAttributes, (value, key) => { me.attributes[key] = value; });
581
+ }
582
+ else
583
+ this.collectAttributes(el);
584
+
585
+ if (!me.dataKey)
586
+ me.expression = null;
587
+
588
+ if (hasValue && hasValue == 'null')
589
+ me.hasValue = null;
590
+ else if (el.hasAttribute(core.attr.hasValue))
591
+ me.hasValue = ($lib.isEmpty(hasValue)) ? hasValue.split(core.dksc) : me.createDataKey(hasValue, isRoot);
592
+
593
+ if (dataKey)
594
+ me.isHTML = !$lib.isEmpty(html);
595
+
596
+ isRoot = !(hasParentContext || me.includable || me.isComponent); // defines whether repeat/context is root
597
+
598
+ if (el.hasAttribute(core.attr.repeat))
599
+ {
600
+ updateValue('repeatDataKey', me.createDataKey(el.getAttribute(core.attr.repeat), isRoot));
601
+ updateValue('repeatItemIdDataKey', me.createDataKey(el.getAttribute(core.attr.repeatItemId), false));
602
+
603
+ if (!me.includable)
604
+ {
605
+ updateValue('repeatDataKeyPath', me.createDataKeyPath(me.repeatDataKey, contextKeyPath));
606
+ contextKeyPath = me.repeatDataKeyPath || contextKeyPath;
607
+ }
608
+ }
609
+
610
+ if (el.hasAttribute(core.attr.context))
611
+ {
612
+ updateValue('contextDataKey', me.createDataKey(el.getAttribute(core.attr.context), !el.hasAttribute(core.attr.repeat) && isRoot));
613
+
614
+ if (!me.includable)
615
+ {
616
+
617
+ updateValue('contextDataKeyPath', me.createDataKeyPath(me.contextDataKey, contextKeyPath));
618
+ contextKeyPath = me.contextDataKeyPath || contextKeyPath;
619
+ }
620
+ }
621
+
622
+ if (el.hasAttribute(core.attr.observe))
623
+ {
624
+
625
+ updateValue('observeDataKeyPath', el.getAttribute(core.attr.observe));
626
+ }
627
+
628
+ if (!me.includable)
629
+ {
630
+ if (me.dataKey)
631
+ me.dataKeyPath = me.createDataKeyPath(me.dataKey, contextKeyPath);
632
+
633
+ if (me.hasValue && me.hasValue[0].length > 0)
634
+ me.hasValueDataKeyPath = me.createDataKeyPath(me.hasValue, contextKeyPath);
635
+ }
636
+
637
+ // store current context key-path
638
+ me.contextDataKeyPath = contextKeyPath;
639
+
640
+ // controller methods
641
+ updateValue('dataFormatter', el.getAttribute(core.attr.dataFormatter));
642
+ updateValue('viewFormatter', el.getAttribute(core.attr.viewFormatter));
643
+ updateValue('preRender', el.getAttribute(core.attr.preRender));
644
+ updateValue('if', el.getAttribute(core.attr.if));
645
+ updateValue('elseIf', el.getAttribute(core.attr.elseIf));
646
+ me.else = (el.hasAttribute(core.attr.else) && el.getAttribute(core.attr.else) != 'null');
647
+ updateValue('postRender', el.getAttribute(core.attr.postRender));
648
+ updateValue('load', el.getAttribute(core.attr.load));
649
+ updateValue('filter', el.getAttribute(core.attr.filter));
650
+
651
+ me.evaluateExpressions();
652
+
653
+ // events
654
+ me.events = $lib.clone(me.events || {}, events, true, true);
655
+
656
+ for (let event in me.events)
657
+ {
658
+ if (events[event] == 'null')
659
+ {
660
+ $lib.off(el, event, core.customEventHandler);
661
+ delete me.events[event];
662
+ }
663
+ }
664
+ }
665
+
666
+ evaluateExpressions()
667
+ {
668
+ const me = this,
669
+ contextKeyPath = me.contextDataKeyPath,
670
+ seen = {},
671
+ tokenHandler = function (seen, arr, token)
672
+ {
673
+ if (token.endsWith('()')) // controller method
674
+ return;
675
+
676
+ let dataKey = me.createDataKeyPath(me.createDataKey(token), contextKeyPath);
677
+
678
+ if (!seen[dataKey])
679
+ arr.push(dataKey);
680
+
681
+ seen[dataKey] = true;
682
+ };
683
+
684
+ me.dataKeyPaths = [];
685
+ me.conditionalDataKeyPaths = [];
686
+
687
+ if (me.if && me.if.indexOf('{{') != -1)
688
+ me.ifExpr = core.expressionEngine.evaluate(me.if, tokenHandler.bind(this, seen, me.conditionalDataKeyPaths));
689
+
690
+ if (me.elseIf && me.elseIf.indexOf('{{') != -1)
691
+ me.elseIfExpr = core.expressionEngine.evaluate(me.elseIf, tokenHandler.bind(this, seen, me.conditionalDataKeyPaths));
692
+
693
+ if (me.expressionValue)
694
+ me.expression = core.expressionEngine.evaluate(me.expressionValue, tokenHandler.bind(this, {}, me.dataKeyPaths));
695
+
696
+ me.createAttributeExpressions();
697
+ }
698
+
699
+ parseInclude(include, hasParentContext)
700
+ {
701
+ if ($lib.isEmpty(include))
702
+ return;
703
+
704
+ let me = this,
705
+ isRoot = !(hasParentContext || me.includable || me.isComponent);
706
+
707
+ me.includeFile = ($lib.startsWith(include, 'file:')) ? include.substr(5) : null;
708
+
709
+ if (me.includeFile)
710
+ me.includeFile = ($lib.startsWith(me.includeFile, '/')) ? me.includeFile : me.createDataKey(me.includeFile, isRoot);
711
+
712
+ if (include == 'null' || me.includeFile)
713
+ me.include = null;
714
+ else
715
+ me.include = ($lib.startsWith(include, '#')) ? include : me.createDataKey(include, isRoot);
716
+ }
717
+
718
+ getElementInfo(el)
719
+ {
720
+ let info = {};
721
+
722
+ info.name = el.nodeName.toLowerCase();
723
+ info.isInput = (info.name == 'input');
724
+ info.isTextArea = (info.name == 'textarea');
725
+ info.isSelect = (info.name == 'select');
726
+ info.isCheck = (info.isInput && el.type == 'checkbox');
727
+ info.isRadio = (info.isInput && el.type == 'radio');
728
+ return info;
729
+ }
730
+
731
+ createAttributeExpressions()
732
+ {
733
+ const me = this,
734
+ contextDataKeyPath = me.contextDataKeyPath,
735
+ seen = {},
736
+ addKey = (value) =>
737
+ {
738
+ if (value.endsWith('()')) // controller method
739
+ return;
740
+
741
+ let dataKey = me.createDataKeyPath(me.createDataKey(value), contextDataKeyPath);
742
+
743
+ if (!seen[dataKey])
744
+ me.attributeDataKeyPaths.push(dataKey);
745
+
746
+ seen[dataKey] = true;
747
+ };
748
+
749
+ me.attributeExpressions = {};
750
+ me.attributeDataKeyPaths = [];
751
+
752
+ $lib.each(me.attributes, (value, name) =>
753
+ {
754
+ if (value.match(core.singleWord)) // $key or $value
755
+ {
756
+ addKey(value);
757
+ }
758
+ else // simple data-key or expression
759
+ {
760
+ const exprFn = core.expressionEngine.evaluate(value, (token) => { addKey(token); });
761
+ me.attributeExpressions[name] = exprFn;
762
+ }
763
+ });
764
+ }
765
+
766
+ collectAttributes(el)
767
+ {
768
+ let me = this,
769
+ key = 'attributes',
770
+ attrObj = me[key] || {};
771
+
772
+ $lib.each(el.attributes, (attr) =>
773
+ {
774
+ if (attr.name.indexOf(core.prefix) != 0 && attr.value.match(core.hasBindingRegEx) != null)
775
+ attrObj[attr.name] = attr.value;
776
+ });
777
+
778
+ if (!$lib.isEmpty(attrObj))
779
+ me[key] = attrObj;
780
+ }
781
+
782
+ updateKeyPaths()
783
+ {
784
+ let me = this,
785
+ oldDataKey = me.contextDataKeyPath,
786
+ contextKeyPath = me.parent.contextDataKeyPath;
787
+
788
+ if (me.repeatDataKey)
789
+ contextKeyPath = me.repeatDataKeyPath = me.createDataKeyPath(me.repeatDataKey, contextKeyPath);
790
+
791
+ if (me.contextDataKey)
792
+ contextKeyPath = me.createDataKeyPath(me.contextDataKey, contextKeyPath);
793
+
794
+ if (me.dataKey)
795
+ me.dataKeyPath = me.createDataKeyPath(me.dataKey, contextKeyPath);
796
+
797
+ if (me.hasValue)
798
+ me.hasValueDataKeyPath = me.createDataKeyPath(me.hasValue, contextKeyPath);
799
+
800
+ me.contextDataKeyPath = contextKeyPath;
801
+
802
+ const replaceContext = (value, index, obj) =>
803
+ {
804
+ if (value.startsWith(oldDataKey))
805
+ obj[index] = contextKeyPath + value.slice(oldDataKey.length);
806
+ };
807
+
808
+ $lib.each(me.dataKeyPaths, replaceContext);
809
+ $lib.each(me.attributeDataKeyPaths, replaceContext);
810
+ $lib.each(me.conditionalDataKeyPaths, replaceContext);
811
+ }
812
+
813
+ resolveLivePaths(fromCache)
814
+ {
815
+ let me = this;
816
+
817
+ if (fromCache !== false && me.paths)
818
+ return me.paths;
819
+
820
+ let paths = Array.from(new Set([me.repeatDataKeyPath, me.contextDataKeyPath, me.dataKeyPath, me.hasValueDataKeyPath, me.observeDataKeyPath]
821
+ .concat(me.dataKeyPaths || [])
822
+ .concat(me.attributeDataKeyPaths || [])
823
+ .concat(me.conditionalDataKeyPaths || [])))
824
+ .filter((el) => { return el != null; });
825
+
826
+ $lib.each(paths, (path, index) =>
827
+ {
828
+ paths[index] = (path) ? me.resolveLivePath(path) : null;
829
+ });
830
+
831
+ return (me.paths = paths);
832
+ }
833
+
834
+ getChangedIndexes(orgRules)
835
+ {
836
+
837
+ let me = this,
838
+ found,
839
+ path = me.resolveLivePath(me.repeatDataKeyPath),
840
+ length = path.split(core.dksc).length;
841
+
842
+ let rules = dataUpdater.initUpdateRules({}, orgRules),
843
+ r = rules[rules.length - 1];
844
+
845
+ r.keys = [];
846
+
847
+ $lib.each(core.dataChanges, (item, dataKey) =>
848
+ {
849
+
850
+ if ($lib.startsWith(dataKey, path))
851
+ {
852
+
853
+ let dk = dataKey.split(core.dksc),
854
+ key = dk[length]; // collection index or key
855
+
856
+ if (key !== undefined && r.keys.indexOf(key) == -1)
857
+ {
858
+
859
+ r.keys.push(key);
860
+ found = true;
861
+ }
862
+ }
863
+ });
864
+
865
+ return (found) ? rules : orgRules;
866
+ }
867
+
868
+ initRepeater(element, parentElement, beforeElement, remove)
869
+ {
870
+ let me = this;
871
+
872
+ if (!me.repeatElements)
873
+ me.repeatElements = {};
874
+
875
+ if (remove)
876
+ {
877
+ me.clearRepeater(element);
878
+
879
+ if (!beforeElement?.isConnected)
880
+ beforeElement = null;
881
+ }
882
+
883
+ let placeHolder = me.repeatPlaceHolder,
884
+ exists = placeHolder?.isConnected;
885
+
886
+ if (!exists)
887
+ {
888
+ me.repeatPlaceHolder = core.createPlaceHolder(me, parentElement, beforeElement);
889
+ me.repeatPlaceHolder.removeAttribute('id');
890
+
891
+ if (element)
892
+ $lib.remove(element);
893
+
894
+ }
895
+ else if (beforeElement?.isConnected)
896
+ {
897
+ beforeElement.parentNode.insertBefore(placeHolder, beforeElement);
898
+ }
899
+ }
900
+
901
+ clearRepeater(element)
902
+ {
903
+ let me = this,
904
+ rootKey = me.resolveLivePath(me.repeatDataKeyPath) + core.dksc;
905
+
906
+ // cleanup internal clone registry
907
+ $lib.each(core.repItemClones, (clone, key) =>
908
+ {
909
+ if ($lib.startsWith(key, rootKey))
910
+ delete core.repItemClones[key];
911
+ });
912
+
913
+ // remove tracked elements
914
+ $lib.each(me.repeatElements, (el) =>
915
+ {
916
+ $lib.remove(el);
917
+ });
918
+
919
+ // remove orphaned DOM elements
920
+ let parent = me.parent?.element || document;
921
+ let duplicates = parent.querySelectorAll(`[${core.attr.id}="${me.id}"]`);
922
+
923
+ duplicates.forEach((el) =>
924
+ {
925
+ if (el !== element && el !== me.repeatPlaceHolder)
926
+ {
927
+ el.remove();
928
+ }
929
+ });
930
+
931
+ me.repeatElements = {};
932
+ }
933
+
934
+ isActiveRepeatItem()
935
+ {
936
+
937
+ let me = this,
938
+ itemId = me.itemId, item = me;
939
+
940
+ if ($lib.isEmpty(itemId))
941
+ return true;
942
+
943
+ while (item && $lib.isEmpty(item.repeatItemIdValue))
944
+ {
945
+
946
+ item = item.parent;
947
+ }
948
+
949
+ if (!item || item.repeatItemIdValue === itemId)
950
+ return true;
951
+
952
+ return false;
953
+ }
954
+
955
+ getIncludeItem()
956
+ {
957
+
958
+ let me = this,
959
+ idReference = $lib.startsWith(me.include, '#'),
960
+ id = (idReference) ? me.include.substr(1) : me.getDataValue(me.include, (!me.component && me.parent) ? me.parent.context : me.context);
961
+
962
+ return me.getTemplateItemById(id);
963
+ }
964
+
965
+ getTemplateItemById(id)
966
+ {
967
+ if ($lib.isEmpty(id))
968
+ return null;
969
+
970
+ let index = $lib.indexOf(core.templateList, function (item) { return (item.id == id); });
971
+
972
+ if (index > -1)
973
+ return core.templateList[index];
974
+ else
975
+ return null;
976
+ }
977
+
978
+ dataBind(live)
979
+ {
980
+ let me = this;
981
+
982
+ if (!me)
983
+ return;
984
+
985
+ let el = me.element,
986
+ value = core.getElementValue(me),
987
+ info = me.elementInfo,
988
+ keyPath = me.dataKey.slice(0),
989
+ lastKey = me.dataKey[me.dataKey.length - 1],
990
+ isSwitch = info.isRadio || info.isCheck,
991
+ isPlainToggle = isSwitch && ($lib.isEmpty(value) || (info.isCheck && value == 'on')),
992
+ type = me.dataType,
993
+ obj;
994
+
995
+ keyPath.pop();
996
+ el._bindaryLastState = (info.isRadio || info.isCheck) ? `${el.checked}:${value}` : value;
997
+
998
+ if (me.dataFormatter)
999
+ value = core.call(me.dataFormatter, [me, el, value]);
1000
+ else if (isSwitch)
1001
+ {
1002
+ if (isPlainToggle)
1003
+ value = el.checked;
1004
+ else
1005
+ {
1006
+ value = me.castValue(value, type);
1007
+
1008
+ if (!el.checked && type == 'boolean')
1009
+ value = !value; // reverse
1010
+ }
1011
+ }
1012
+ else if (type) // cast if explicit type is defined (e.g. for number/date inputs or select with "true" and "false" values)
1013
+ value = me.castValue(value, type);
1014
+
1015
+ core.observed = false; // value changes to true if object is observed
1016
+
1017
+ if (me.repeatValue)
1018
+ {
1019
+ if (core.isKey(me.dataKey, core.valueKey))
1020
+ {
1021
+ if ((obj = me.repeatValue))
1022
+ obj[me.key] = value;
1023
+ }
1024
+ else
1025
+ {
1026
+ if ((obj = me.getDataValue(keyPath, me.repeatValue[me.key])))
1027
+ obj[lastKey] = value;
1028
+ }
1029
+ }
1030
+ else
1031
+ {
1032
+ if (me.dataKey.length == 1)
1033
+ obj = me.context;
1034
+ else
1035
+ obj = me.getDataValue(keyPath, me.context);
1036
+
1037
+ if (Array.isArray(obj[lastKey]) && info.isCheck && type !== 'boolean') // handle checkboxes to array binding
1038
+ {
1039
+ const arr = obj[lastKey],
1040
+ val = !$lib.isEmpty(value) && value !== 'on' ? value : el.name;
1041
+
1042
+ if (el.checked)
1043
+ {
1044
+ if (!arr.includes(val))
1045
+ arr.push(val);
1046
+ }
1047
+ else
1048
+ {
1049
+ const index = arr.indexOf(val);
1050
+ if (index > -1)
1051
+ arr.splice(index, 1);
1052
+ }
1053
+ }
1054
+ else if (Array.isArray(obj[lastKey]) && typeof value === 'string') // CSV array for normal inputs
1055
+ {
1056
+ if (!$lib.isEmpty(value))
1057
+ {
1058
+ const parts = value.split(',')
1059
+ .map(v => v.trim())
1060
+ .filter(v => v.length > 0);
1061
+
1062
+ obj[lastKey] = (type) ? parts.map(v => me.castValue(v, type)) : parts;
1063
+ }
1064
+ else
1065
+ {
1066
+ obj[lastKey] = [];
1067
+ }
1068
+ }
1069
+ else if (obj)
1070
+ {
1071
+ if (info.isCheck && !isPlainToggle && !el.checked && type !== 'boolean') // a checkbox with string value that is unchecked
1072
+ value = '';
1073
+
1074
+ obj[lastKey] = value;
1075
+ }
1076
+ }
1077
+
1078
+ core.dataBindLive = false; // if this data-bind came from oninput event but there is no matching object, set to false
1079
+
1080
+ if (obj)
1081
+ {
1082
+ core.dataBindLive = live;
1083
+ $bindary.dataBindTrigger = el; // keep track of update trigger
1084
+ }
1085
+
1086
+ const modelValue = obj
1087
+ ? (me.repeatValue && core.isKey(me.dataKey, core.valueKey) ? obj[me.key] : obj[lastKey])
1088
+ : value; // fallback to raw value if obj was never resolved
1089
+
1090
+ core.fireEvent('postDataBind', [me, el, obj, modelValue]);
1091
+
1092
+ if (obj && !core.observed)
1093
+ {
1094
+ $bindary.dataChange(me.resolveLivePath(me.dataKeyPath), null, null, false); // register data-change when object is not observed
1095
+ $bindary.updateViewDelayed();
1096
+ }
1097
+ }
1098
+
1099
+ castValue(value, type)
1100
+ {
1101
+ if (!type)
1102
+ return value;
1103
+
1104
+ if (type == 'boolean')
1105
+ return core.toBoolean(value);
1106
+
1107
+ if ($lib.isEmpty(value) && (type == 'date' || type == 'datetime' || type == 'number'))
1108
+ return null;
1109
+
1110
+ if (type == 'date')
1111
+ return $lib.parseDate(value, $bindary.dateFormat.replace(core.timeRegEx, ''));
1112
+
1113
+ if (type == 'datetime')
1114
+ return $lib.parseDate(value, $bindary.dateFormat);
1115
+
1116
+ if (type.startsWith('number'))
1117
+ return parseFloat(value.replace($bindary.decimalSeparator, '.'));
1118
+
1119
+ return value;
1120
+ }
1121
+
1122
+ clone(parent)
1123
+ {
1124
+ let cloner = (item, parent) =>
1125
+ {
1126
+ let target = new componyx.bindary_modules.TemplateItem(null, null, null, null, false);
1127
+
1128
+ $lib.each(item.children, (child) =>
1129
+ {
1130
+ target.children.push(cloner(child, target));
1131
+ });
1132
+
1133
+ $lib.each(item, (value, key) =>
1134
+ {
1135
+ if (core.omit[key] === undefined && typeof (value) !== 'function' && !$lib.isEmpty(value))
1136
+ target[key] = value;
1137
+ });
1138
+
1139
+ target.evaluateExpressions();
1140
+ target.livePath = {};
1141
+ target.parent = parent;
1142
+ target.root = item.root;
1143
+ return target;
1144
+ };
1145
+
1146
+ return cloner(this, parent || this.parent);
1147
+ }
1148
+
1149
+ getClone()
1150
+ {
1151
+ let me = this,
1152
+ key = me.resolveLivePath(me.repeatDataKeyPath),
1153
+ clone = core.repItemClones[key];
1154
+
1155
+ if (!clone)
1156
+ return (core.repItemClones[key] = me.clone());
1157
+ else
1158
+ return clone;
1159
+ }
1160
+
1161
+ defineContext(context)
1162
+ {
1163
+ let me = this,
1164
+ rootIndex;
1165
+
1166
+ me.context = context;
1167
+
1168
+ if (me.contextDataKey)
1169
+ me.context = me.getDataValue(me.contextDataKey, context); // define context/scope
1170
+ else if (me.dataKey && me.dataKey[0] == '$')
1171
+ {
1172
+
1173
+ rootIndex = $lib.indexOf(core.dataLabel, me.dataKey[1]);
1174
+
1175
+ if (rootIndex > -1)
1176
+ me.context = $bindary[core.dataLabel[rootIndex]];
1177
+ }
1178
+ }
1179
+
1180
+ setContextValue()
1181
+ {
1182
+ let me = this, context = me.context;
1183
+
1184
+ if ($lib.isEmpty(me.dataKey) && !me.expression)
1185
+ return;
1186
+
1187
+ if (me.expression)
1188
+ {
1189
+ let resolvedValue = me.expression(me.expressionTokenResolver.bind(me));
1190
+ me.value = (resolvedValue == null) ? '' : resolvedValue;
1191
+ }
1192
+ else
1193
+ me.value = me.getDataValue(me.dataKey, context);
1194
+ }
1195
+
1196
+ expressionTokenResolver(token)
1197
+ {
1198
+ let me = this;
1199
+
1200
+ if (token.endsWith('()'))
1201
+ {
1202
+ const methodName = token.slice(0, -2);
1203
+
1204
+ if (me.isComponent)
1205
+ {
1206
+ let item = me;
1207
+
1208
+ while (item && !item.component)
1209
+ item = item.parent;
1210
+
1211
+ if (item?.context?.[methodName])
1212
+ return item.context[methodName](me);
1213
+ }
1214
+
1215
+ // not a component, or the component didn't have this method: fall back to the controller
1216
+ return ($bindary.controller) ? $bindary.controller[methodName](me) : '';
1217
+ }
1218
+ else
1219
+ return me.getDataValue(token.split('.'), me.context); // data-key
1220
+ }
1221
+
1222
+ captureTreeState()
1223
+ {
1224
+ let item = this,
1225
+ tree = (item.parent?.treeStatePass == core.renderPass) ? item.parent.treeState.slice() : item.parent?.captureTreeState().slice() || []; // clone always, cached or not, so no two items ever share the same treeState array reference
1226
+
1227
+ tree.push({
1228
+ key: item.key,
1229
+ index: item.index,
1230
+ inRepeat: item.inRepeat,
1231
+ repeatItemIdValue: item.repeatItemIdValue,
1232
+ context: item.context,
1233
+ value: item.value,
1234
+ element: item.element,
1235
+ livePath: $lib.clone({}, item.livePath)
1236
+ });
1237
+
1238
+ item.treeState = tree;
1239
+ item.treeStatePass = core.renderPass; // valid only for this pass
1240
+ return tree;
1241
+ }
1242
+
1243
+ restoreContext(treeState)
1244
+ {
1245
+ let item = this, index = treeState.length, obj;
1246
+
1247
+ item.treeState = treeState;
1248
+
1249
+ while (--index > -1)
1250
+ {
1251
+ obj = treeState[index];
1252
+ item.key = obj.key;
1253
+ item.index = obj.index;
1254
+ item.inRepeat = obj.inRepeat;
1255
+ item.repeatItemIdValue = obj.repeatItemIdValue;
1256
+ item.context = obj.context;
1257
+ item.value = obj.value;
1258
+ item.element = obj.element;
1259
+ item.livePath = obj.livePath;
1260
+ item = item.parent;
1261
+ }
1262
+ }
1263
+
1264
+ }
1265
+
1266
+ componyx.bindary_modules.TemplateItem = TemplateItem;
1267
+ })();
1268
+
1269
+ var Template = {
1270
+ TemplateItem: componyx.bindary_modules.TemplateItem
1271
+ };
1272
+
1273
+ export { Template as default };