@componyx/ui 0.0.0-stage → 1.0.3

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/DataObserver.js +717 -0
  85. package/Form/Modules/Databinder.js +602 -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
package/Base/Base.js ADDED
@@ -0,0 +1,3407 @@
1
+ /*! Componyx.UI | (c) E.H. Daanen / Componyx | BUSL-1.1 | componyx.com/license */
2
+ /**
3
+ * componyx.UI ($UI) Contains global helper methods for UI components.
4
+ * @namespace UI
5
+ * @memberof componyx
6
+ */
7
+ componyx.UI = {};
8
+
9
+ /**
10
+ * @ignore
11
+ */
12
+ globalThis.$UI = componyx.UI;
13
+
14
+ /**
15
+ * @namespace base
16
+ * @memberof componyx.UI
17
+ */
18
+ componyx.UI.base = {};
19
+
20
+ (function (window)
21
+ {
22
+ var _initComponent = {},
23
+ _renderCount = 0, _isDetectAwaiting, _isBusy, _readyTimerId,
24
+ _dataAttr = 'data-ui-', _dataTemplate = _dataAttr + 'template', _dataContentFrom = _dataAttr + 'contentfrom',
25
+ _dataSettings = _dataAttr + 'settings', _dataSettingsId = _dataAttr + 'settingsid', _dataCloneable = _dataAttr + 'cloneable', _dataInterpolate = _dataAttr + 'interpolate', _dataType = _dataAttr + 'type',
26
+ _dataAction = _dataAttr + 'action', _dataGroup = _dataAttr + 'group',
27
+ _baseEventProto = Object.getPrototypeOf($lib.createEvent('')),
28
+ _eventPrototype = Object.create(_baseEventProto),
29
+ _namespace = 'componyx.UI.',
30
+ _customTagPrefix = 'cui-',
31
+ _cssClassHidden = 'hidden',
32
+ _detected = Symbol('detected'),
33
+ _settings = {},
34
+ _addVersion = function (src)
35
+ {
36
+ if (!$UI.version || src.indexOf('?v=') > -1 || src.indexOf('&v=') > -1)
37
+ return src;
38
+
39
+ var version = 'v=' + $UI.version;
40
+ return (src.indexOf('?') > -1) ? src + '&' + version : src + '?' + version;
41
+ },
42
+ _getObj = function (namespace)
43
+ {
44
+ var obj = window;
45
+ for (var index = 0; index < namespace.length; ++index)
46
+ {
47
+ obj = obj[namespace[index]];
48
+ }
49
+
50
+ return obj;
51
+ },
52
+ _postRenderEvent = (function (eventName)
53
+ {
54
+ var proto = Object.create(_baseEventProto);
55
+
56
+ proto.fire = function ()
57
+ {
58
+ if (_renderCount !== 0)
59
+ return;
60
+
61
+ if (_isDetectAwaiting) // reschedule
62
+ {
63
+ _readyTimerId = setTimeout(this.fire.bind(this));
64
+ return;
65
+ }
66
+
67
+ _isBusy = false;
68
+ return _baseEventProto.fire.call(this, null, []);
69
+ };
70
+
71
+ function Fn()
72
+ {
73
+ this.eventName = eventName;
74
+ }
75
+
76
+ Fn.prototype = proto;
77
+ return new Fn();
78
+ })('onPostRender'),
79
+
80
+ _getMethod = function (name)
81
+ {
82
+ if ($lib.isEmpty(name) || typeof name !== 'string')
83
+ return name;
84
+
85
+ name = name.trim();
86
+
87
+ const match = name.match(/^([\w$.]+)\((.*)\);?$/);
88
+
89
+ let argString = null;
90
+ if (match)
91
+ {
92
+ name = match[1];
93
+ argString = match[2].trim();
94
+ }
95
+
96
+ // Resolve function reference
97
+ let obj = window;
98
+ for (const part of name.split('.'))
99
+ {
100
+ obj = obj?.[part];
101
+ if (!obj) return null;
102
+ }
103
+
104
+ if (typeof obj !== 'function')
105
+ return obj;
106
+
107
+ if (!match || !argString)
108
+ return obj;
109
+
110
+ // Split arguments by comma, naive (does not handle nested commas)
111
+ let paramList = [];
112
+ if (argString)
113
+ paramList = argString.split(',').map(s => s.trim());
114
+
115
+ // Return a wrapper that maps static and runtime args
116
+ return (...runtimeArgs) =>
117
+ {
118
+ const finalArgs = paramList.map((p, i) =>
119
+ {
120
+ try
121
+ {
122
+ // Single-quoted string literal: 'text' normalize to valid JSON before parsing
123
+ if (p.length >= 2 && p[0] === "'" && p[p.length - 1] === "'")
124
+ {
125
+ const inner = p.slice(1, -1)
126
+ .replace(/\\'/g, "'") // unescape \' back to '
127
+ .replace(/"/g, '\\"'); // escape any literal " so JSON.parse doesn't choke
128
+ p = '"' + inner + '"';
129
+ }
130
+
131
+ return JSON.parse(p); // static JSON literal
132
+ }
133
+ catch
134
+ {
135
+ return runtimeArgs[i]; // reference to runtime argument
136
+ }
137
+ });
138
+ return obj(...finalArgs);
139
+ };
140
+ },
141
+
142
+ /**
143
+ * creates a new Base Event
144
+ * @function _createEvent
145
+ * @param {String} eventName
146
+ * @returns {componyx.UI.base.Event}
147
+ * @ignore
148
+ */
149
+ _createEvent = function (eventName)
150
+ {
151
+ var fn = function ()
152
+ {
153
+ this.eventName = eventName;
154
+ };
155
+
156
+ fn.prototype = _eventPrototype;
157
+ return new fn();
158
+ },
159
+
160
+ /**
161
+ * Represents a UI event used throughout the componyx.UI.base framework.
162
+ * This event type is based on and compatible with {@link componyx.library.Event}, but may have an internal prototype chain or additional behaviors specific to the UI framework.
163
+ * Event handlers receive the component instance (sender) as first argument and the event arguments as second argument.
164
+ * The event arguments are described by the type in the @see tag of the event, if any.
165
+ * @typedef {componyx.library.Event} componyx.UI.base.Event
166
+ * @memberof componyx.UI.base
167
+ * @see {@link componyx.library.Event}
168
+ */
169
+
170
+ /**
171
+ * @typedef {Object} componyx.UI.base.EventArgs
172
+ * @property {componyx.UI.base.Component|componyx.UI.base.WebComponent} component - The component instance.
173
+ */
174
+
175
+ /**
176
+ * @typedef {Object} componyx.UI.base.PreRenderEventArgs
177
+ * @property {componyx.UI.base.Component|componyx.UI.base.WebComponent} component - The component instance.
178
+ * @property {Object} eventArgs - The event object containing more detailed information about the event.
179
+ * @property {Boolean} eventArgs.cancel - Set to true to cancel the render.
180
+ */
181
+
182
+ /**
183
+ * @typedef {Object} componyx.UI.base.TemplateEventArgs
184
+ * @property {componyx.UI.base.Component|componyx.UI.base.WebComponent} component - The component instance.
185
+ * @property {Object} eventArgs - The event object containing more detailed information about the event.
186
+ * @property {String} eventArgs.id - The template id.
187
+ * @property {HTMLElement} eventArgs.template - The template element.
188
+ * @property {HTMLElement} eventArgs.element - The element to which the template content is added.
189
+ * @property {Object|Object[]} eventArgs.values - The template replacement values.
190
+ * @property {Function} [eventArgs.matchHandler] - The callback method for the template replacement values.
191
+ */
192
+
193
+ /**
194
+ * @typedef {Object} componyx.UI.base.AjaxEventArgs
195
+ * @property {componyx.UI.base.Component|componyx.UI.base.WebComponent} component - The component instance.
196
+ * @property {Object} eventArgs - The XHR arguments as passed to the componyx.library.XhrCallback. Any callArgs passed to ajaxCall follow as additional handler parameters.
197
+ */
198
+
199
+ /**
200
+ * @typedef {Object} componyx.UI.base.ClickEventArgs
201
+ * @property {componyx.UI.base.Component|componyx.UI.base.WebComponent} component - The component instance.
202
+ * @property {MouseEvent} eventArgs - The original click event.
203
+ */
204
+
205
+ /**
206
+ * Base component events.
207
+ * @class Events
208
+ * @memberof componyx.UI.base
209
+ * @property {Function} dispose - Removes all event handlers.
210
+ * @property {componyx.UI.base.Event} onPreRender - Event which fires before the component is rendered. @see {@link componyx.UI.base.PreRenderEventArgs}
211
+ * @property {componyx.UI.base.Event} onPostRender - Event which fires after the component is rendered. @see {@link componyx.UI.base.EventArgs}
212
+ * @property {componyx.UI.base.Event} onShow - Event which fires when the component is shown. @see {@link componyx.UI.base.EventArgs}
213
+ * @property {componyx.UI.base.Event} onHide - Event which fires when the component is hidden. @see {@link componyx.UI.base.EventArgs}
214
+ * @property {componyx.UI.base.Event} onDestroy - Event which fires when the component is destroyed. @see {@link componyx.UI.base.EventArgs}
215
+ * @property {componyx.UI.base.Event} onPreApplyTemplate - Event which fires before a template is applied. @see {@link componyx.UI.base.TemplateEventArgs}
216
+ * @property {componyx.UI.base.Event} onPostApplyTemplate - Event which fires after a template is applied. @see {@link componyx.UI.base.TemplateEventArgs}
217
+ * @property {componyx.UI.base.Event} onAjaxStart - Event which fires before an ajax method call. @see {@link componyx.UI.base.AjaxEventArgs}
218
+ * @property {componyx.UI.base.Event} onAjaxComplete - Event which fires after an ajax method call. @see {@link componyx.UI.base.AjaxEventArgs}
219
+ * @property {componyx.UI.base.Event} onAjaxSuccess - Event which fires when an ajax method response has httpstatus >= 200 & < 300 or 304. @see {@link componyx.UI.base.AjaxEventArgs}
220
+ * @property {componyx.UI.base.Event} onAjaxError - Event which fires when an ajax method response does not have an http success status code. @see {@link componyx.UI.base.AjaxEventArgs}
221
+ * @property {componyx.UI.base.Event} onAjaxProgress - Event which fires periodically while downloading data. @see {@link componyx.UI.base.AjaxEventArgs}
222
+ * @property {componyx.UI.base.Event} onAjaxUploadProgress - Event which fires periodically while uploading data. @see {@link componyx.UI.base.AjaxEventArgs}
223
+ * @property {componyx.UI.base.Event} onAjaxUploadError - Event which fires when an upload failed. @see {@link componyx.UI.base.AjaxEventArgs}
224
+ * @property {componyx.UI.base.Event} onAjaxAbort - Event which fires when a running ajax call is aborted. @see {@link componyx.UI.base.AjaxEventArgs}
225
+ * @property {componyx.UI.base.Event} onClick - Event which fires when the component is clicked. @see {@link componyx.UI.base.ClickEventArgs}
226
+ */
227
+ _baseEvents = function ()
228
+ {
229
+ this.dispose = function (eventPriority)
230
+ {
231
+ for (var name in this)
232
+ {
233
+ if (this[name].priorityRemove)
234
+ this[name].priorityRemove(eventPriority);
235
+ }
236
+ };
237
+ this.onPreRender = $base.static.createEvent('onPreRender');
238
+ this.onPostRender = $base.static.createEvent('onPostRender');
239
+ this.onShow = $base.static.createEvent('onShow');
240
+ this.onHide = $base.static.createEvent('onHide');
241
+ this.onDestroy = $base.static.createEvent('onDestroy');
242
+ this.onPreApplyTemplate = $base.static.createEvent('onPreApplyTemplate');
243
+ this.onPostApplyTemplate = $base.static.createEvent('onPostApplyTemplate');
244
+ this.onAjaxStart = $base.static.createEvent('onAjaxStart');
245
+ this.onAjaxComplete = $base.static.createEvent('onAjaxComplete');
246
+ this.onAjaxSuccess = $base.static.createEvent('onAjaxSuccess');
247
+ this.onAjaxError = $base.static.createEvent('onAjaxError');
248
+ this.onAjaxProgress = $base.static.createEvent('onAjaxProgress');
249
+ this.onAjaxUploadProgress = $base.static.createEvent('onAjaxUploadProgress');
250
+ this.onAjaxUploadError = $base.static.createEvent('onAjaxUploadError');
251
+ this.onAjaxAbort = $base.static.createEvent('onAjaxAbort');
252
+ this.onClick = $base.static.createEvent('onClick');
253
+ },
254
+
255
+ _baseProperties = function ()
256
+ {
257
+ let instance = this;
258
+ this.beforeElement = null;
259
+ this.name = null;
260
+ this.theme = null;
261
+ this.themeName = null;
262
+ this.cssClass = '';
263
+ this.cssClassTouch = 'touch';
264
+ this.autoTouchConfig = true;
265
+ this.cloneInput = false;
266
+ this.keepInputId = true;
267
+ this.keepInputEventHandlers = false;
268
+ this.disableDefaultCssClass = false;
269
+ this.renderState = $base.static.RenderState.NONE;
270
+ this.element = null;
271
+ this.showing = false;
272
+ this.allowPostRender = true;
273
+ this.renderWhenConnected = false;
274
+ this.ignoreConnectionCallbacks = false;
275
+ this.canRender = true;
276
+ this.templates = new Map();
277
+ this.store = [];
278
+ this.observing = false;
279
+ this.attributeObserver = null;
280
+ this.removalObserver = null;
281
+ this.__suppressNextChangeEvent = false;
282
+ this.__hidden = true;
283
+ this.__ajax = {};
284
+ this.__createdTemplates = {};
285
+ this.__focus = false;
286
+
287
+ this.ajax =
288
+ {
289
+ url: '',
290
+ absoluteURL: true,
291
+ headers: null,
292
+ jsonResponseDataWrapper: 'd',
293
+ jsonDeserializer: null,
294
+ load: new $base.static.AjaxMethod(instance),
295
+
296
+ addMethod: function (name)
297
+ {
298
+ if (this[name] == null)
299
+ this[name] = new $base.static.AjaxMethod(instance);
300
+ else
301
+ {
302
+ var settings = this[name];
303
+ this[name] = new $base.static.AjaxMethod(instance);
304
+ $lib.clone(this[name], settings, true, true, true);
305
+ }
306
+ }
307
+ };
308
+ },
309
+
310
+ _getGroupTagSettings = function (el)
311
+ {
312
+ let settings = _getAttribute(el, _dataSettings);
313
+
314
+ if (!$lib.isEmpty(settings))
315
+ {
316
+ _removeAttribute(el, _dataSettings);
317
+
318
+ if (settings.indexOf('{') != 0)
319
+ settings = '{' + settings + '}';
320
+
321
+ return window.JSON.parse(settings);
322
+ }
323
+
324
+ return null;
325
+ },
326
+
327
+ _getComponentTagSettings = function (el)
328
+ {
329
+ if (el[_detected])
330
+ return;
331
+
332
+ let settings = _getAttribute(el, _dataSettings),
333
+ settingsId = _getAttribute(el, _dataSettingsId),
334
+ obj = (settingsId) ? _settings[settingsId] : null;
335
+
336
+ if (!$lib.isEmpty(settings))
337
+ {
338
+ _removeAttribute(el, _dataSettings); // we remove JSON attribute to free up memory, but settings can still be re-applied via the settingsId reference or individual ui- attributes
339
+
340
+ if (settings.indexOf('{') != 0)
341
+ settings = '{' + settings + '}';
342
+
343
+ var settingsObj = window.JSON.parse(settings);
344
+
345
+ if (obj)
346
+ obj = $lib.clone(settingsObj, obj, true, false);
347
+ else
348
+ obj = settingsObj;
349
+ }
350
+
351
+ return obj;
352
+ },
353
+ _getAttribute = (el, name) => el.getAttribute(name) || el.getAttribute(name.slice(5)),
354
+ _hasAttribute = (el, name) => el.hasAttribute(name) || el.hasAttribute(name.slice(5)),
355
+ _removeAttribute = (el, name) => { el.removeAttribute(name); el.removeAttribute(name.slice(5)); },
356
+ _toCamelCase = (str) => { return str.replace(/-([a-z0-9])/gi, (match, char) => char.toUpperCase()); },
357
+ _getPropertyName = (attrName) =>
358
+ {
359
+ let name = attrName.startsWith(_dataAttr) ? attrName.split('-').slice(2).join('-') : attrName.startsWith(_dataAttr.slice(5)) ? attrName.split('-').slice(1).join('-') : attrName; // remove prefix data-ui- or ui-
360
+ return _toCamelCase(name);
361
+ },
362
+ _getDataAttributes = (el) =>
363
+ {
364
+ return Array.from(el.attributes)
365
+ .filter(a =>
366
+ {
367
+ const name = a.name.toLowerCase();
368
+ return name.startsWith(_dataAttr) || name.startsWith(_dataAttr.slice(5)); // data-ui- or ui-
369
+ }).map(a => ({ name: a.name.toLowerCase(), value: a.value }));
370
+ },
371
+ _initDataAttributes = function (id, el)
372
+ {
373
+ const init = _initComponent[id] = _initComponent[id] || {},
374
+ attrs = _getDataAttributes(el);
375
+
376
+ init.attrs = init.attrs || {};
377
+
378
+ // remove attributes that no longer exist on the element
379
+ for (const name in init.attrs)
380
+ {
381
+ if (!attrs.some(a => a.name === name))
382
+ {
383
+ _setPropertyFromAttribute.call(this, el, name, "");
384
+ delete init.attrs[name];
385
+ }
386
+ }
387
+
388
+ attrs.forEach(attr =>
389
+ {
390
+ _setPropertyFromAttribute.call(this, el, attr.name, attr.value);
391
+ init.attrs[attr.name] = attr.value;
392
+ });
393
+ },
394
+ _setPropertyFromAttribute = function (element, name, value, remove = false)
395
+ {
396
+ if (value === null || element === null)
397
+ return;
398
+
399
+ const isCustomAttr = name.startsWith(_dataAttr) || name.startsWith(_dataAttr.slice(5));
400
+ if (!isCustomAttr)
401
+ return;
402
+
403
+ let propName = _getPropertyName(name),
404
+ prop = this[propName],
405
+ restoreObserver = false,
406
+ changed = false,
407
+ newValue = {};
408
+
409
+ if (prop === undefined)
410
+ return;
411
+
412
+ if (remove)
413
+ element.removeAttribute(name);
414
+
415
+ if (propName === 'settingsid')
416
+ value = _settings[value];
417
+
418
+ if (this.observing && this.attributeObserver)
419
+ {
420
+ restoreObserver = true;
421
+ this.observing = false;
422
+ }
423
+
424
+ if (value === '')
425
+ {
426
+ if (Array.isArray(prop))
427
+ {
428
+ newValue[propName] = [];
429
+ }
430
+ else if (prop && typeof prop === 'object')
431
+ {
432
+ newValue[propName] = {};
433
+ }
434
+ else if (typeof prop === 'string')
435
+ {
436
+ newValue[propName] = '';
437
+ }
438
+ else
439
+ {
440
+ newValue[propName] = null;
441
+ }
442
+ }
443
+ else
444
+ {
445
+ switch (typeof prop)
446
+ {
447
+ case 'boolean':
448
+ newValue[propName] = (value === 'true');
449
+ break;
450
+
451
+ case 'number':
452
+ newValue[propName] = parseFloat(value);
453
+ break;
454
+
455
+ case 'object':
456
+ try
457
+ {
458
+ const trimmed = value.trim();
459
+ let parsed, isJson = true;
460
+
461
+ try
462
+ {
463
+ parsed = JSON.parse(trimmed);
464
+ }
465
+ catch (parseError)
466
+ {
467
+ isJson = false;
468
+ }
469
+
470
+ if (prop === null && !isJson)
471
+ {
472
+ newValue[propName] = value;
473
+ break;
474
+ }
475
+
476
+ if (propName === 'settings')
477
+ {
478
+ const settings = isJson ? parsed : JSON.parse('{' + trimmed + '}');
479
+
480
+ for (let key in settings)
481
+ {
482
+ newValue[key] = settings[key];
483
+ }
484
+ }
485
+ else if (isJson)
486
+ {
487
+ newValue[propName] = parsed;
488
+ }
489
+ }
490
+ catch (e)
491
+ {
492
+ console.error(`Failed to parse JSON for ${name}:`, e);
493
+ throw e;
494
+ }
495
+ break;
496
+
497
+ default:
498
+ newValue[propName] = value;
499
+ break;
500
+ }
501
+ }
502
+
503
+ // Apply new values
504
+ if (Object.keys(newValue).length > 0)
505
+ changed = this.cloneProperties(newValue);
506
+
507
+ if (restoreObserver)
508
+ setTimeout(() => { this.observing = true; }, 0);
509
+
510
+ return changed;
511
+ },
512
+ _createGroup = function (groupId)
513
+ {
514
+ if (!$UI.groupStore[groupId])
515
+ $UI.groupStore[groupId] = { items: [], exclude: [] };
516
+ },
517
+ _getComponentType = function (el)
518
+ {
519
+ if (el.__uiDetected)
520
+ return null;
521
+
522
+ let tag = el.tagName.toLowerCase(), type;
523
+
524
+ if (tag.startsWith(_customTagPrefix.toLowerCase()))
525
+ {
526
+ type = tag.substr(_customTagPrefix.length);
527
+ type = type
528
+ .split(/[\-]+/)
529
+ .map(x => x.charAt(0).toUpperCase() + x.slice(1)) // capitalize each first letter (masked-text-box to MaskedTextBox)
530
+ .join('');
531
+ }
532
+ else if (_hasAttribute(el, _dataType))
533
+ {
534
+ type = _getAttribute(el, _dataType);
535
+ }
536
+ else
537
+ return null;
538
+
539
+ if (type.split('.').length == 1)
540
+ type = _namespace + type;
541
+
542
+ el.__uiDetected = true; // set temporary flag to prevent retecting the component while already being processed
543
+
544
+ return type;
545
+ },
546
+ _monkeyPatch = function (target, src, fn)
547
+ {
548
+ if (src[fn])
549
+ target[fn] = function (fn, org, args) { org[fn](args); src[fn](args); }.bind(this, fn, target[fn]);
550
+ },
551
+ _detect = function (templateId)
552
+ {
553
+ var instance = this;
554
+
555
+ if (!instance.__createdTemplates[templateId])
556
+ return;
557
+
558
+ const containers = instance.__createdTemplates[templateId].containers;
559
+ delete instance.__createdTemplates[templateId];
560
+
561
+ if (!instance.element)
562
+ return;
563
+
564
+ containers.forEach(container =>
565
+ {
566
+ if (container?.isConnected)
567
+ $UI.detect(container);
568
+ });
569
+ };
570
+
571
+ /**
572
+ * A list of supported UI component HTML data-attributes.
573
+ * @typedef {Object} HTML_Attributes
574
+ * @memberof componyx.UI.base
575
+ * @property {String} ["data-ui-template"] - Denotes an element as UI data template with the attribute value as template identifier (e.g. 'Header'). A component can have static templates, like a Header or Footer, and/or dynamic (item) templates.
576
+ * @property {String} ["data-ui-cloneable"] - Denotes a UI data template as cloneable (true) or non-cloneable (false). A cloneable template is for multiple use and a non-cloneable template is for single use, e.g. a template containing an element with a unique identifier.
577
+ * @property {String} ["data-ui-interpolate"] - Defines if a search and replace action on the inner HTML of the template will be performed to set the corresponding value for a interpolation tag (e.g. {value}).
578
+ * @property {String} ["data-ui-contentfrom"] - Sets the id of the HTML element whose content to use.
579
+ * @property {String} ["data-ui-type"] - The full name of the component type to render, e.g. Componyx.UI.Button.
580
+ * @property {String} ["data-ui-action"] - Defines if the component is rendered only (render) or rendered and displayed (show).
581
+ * @property {String} ["data-ui-settings"] - Initializes the component with the settings specified via the JSON formatted attribute value.
582
+ * @property {String} ["data-ui-group"] - Denotes an element as UI component group with the attribute value as group identifier.
583
+ */
584
+
585
+
586
+ /** Fires the event. This method should not be invoked by client code directly but only from inside component code.
587
+ * @param {Object} instance The component instance on which initiates the event.
588
+ * @param {Object[]} arguments A list of arguments to pass to the event handlers.
589
+ * @param {Boolean} withInstance A value indicating if the component instance is passed as first argument to the event handler.
590
+ * @name fire
591
+ * @memberof componyx.UI.base.Event#
592
+ * @function
593
+ * @protected
594
+ */
595
+ _eventPrototype.fire = function (instance, args, withInstance)
596
+ {
597
+ if (args == null)
598
+ args = [];
599
+ else if (!$lib.isArray(args))
600
+ args = [args];
601
+
602
+ if (withInstance !== false)
603
+ args.unshift(instance);
604
+
605
+ return _baseEventProto.fire.call(this, instance, args);
606
+ };
607
+
608
+ componyx.UI.__sourceElements = {};
609
+
610
+ /**
611
+ * When specified, the version is appended to the URL for locating UI CSS & Script resources.
612
+ * @type {String|null}
613
+ * @memberof componyx.UI
614
+ */
615
+ componyx.UI.version = null;
616
+
617
+ /**
618
+ * A value indicating whether UI components are automatically detected when the page is ready.
619
+ * @type {Boolean}
620
+ * @memberof componyx.UI
621
+ */
622
+ componyx.UI.autoDetect = true;
623
+
624
+ /**
625
+ * Holds UI component references by unique id and HTMLElement.
626
+ * @type {Object}
627
+ * @memberof componyx.UI
628
+ */
629
+ componyx.UI.store = {};
630
+
631
+ /**
632
+ * componyx.UI.elementStore Holds UI component references by HTMLElement.
633
+ * @type {Object}
634
+ * @memberof componyx.UI
635
+ */
636
+ componyx.UI.elementStore = new Map();
637
+
638
+ /**
639
+ * componyx.UI.groupStore Holds UI component id's for each unique group.
640
+ * @type {Object}
641
+ * @memberof componyx.UI
642
+ */
643
+ componyx.UI.groupStore = {};
644
+
645
+ /**
646
+ * componyx.UI.scriptResources Contains UI script resources for dynamic loading.
647
+ * @type {Object}
648
+ * @memberof componyx.UI
649
+ */
650
+ componyx.UI.scriptResources = { global: '' };
651
+
652
+ /**
653
+ * componyx.UI.cssResources Contains UI css resources for dynamic loading.
654
+ * @type {Object}
655
+ * @memberof componyx.UI
656
+ */
657
+ componyx.UI.cssResources = { global: [] };
658
+
659
+ /**
660
+ * When specified, component resources will be added before this resource element.
661
+ * @type {HTMLElement}
662
+ * @memberof componyx.UI
663
+ */
664
+ componyx.UI.resourcesBefore = null;
665
+
666
+ /**
667
+ * A value indicating if the UI is busy rendering components.
668
+ * @type {Boolean}
669
+ * @readonly
670
+ * @memberof componyx.UI
671
+ */
672
+ Object.defineProperty(componyx.UI, 'busy',
673
+ {
674
+ get: function () { return _isBusy; }
675
+ });
676
+
677
+ /**
678
+ * A value indicating if UI components use custom element names.
679
+ * @type {Boolean}
680
+ * @memberof componyx.UI
681
+ */
682
+ componyx.UI.useCustomElementNames = false;
683
+
684
+ /**
685
+ * The CSS theme name which is set on the component's root element. Can be overridden when defined on a component-group or component.
686
+ * @type {String}
687
+ * @memberof componyx.UI
688
+ */
689
+ componyx.UI.themeName = null;
690
+
691
+ /**
692
+ * Gets or sets a value indicating if component resources (CSS and scripts) are loaded on demand. Disable when all resources are already included, e.g. with the bundled ui.js and ui.css.
693
+ * @type {Boolean}
694
+ * @memberof componyx.UI
695
+ */
696
+ componyx.UI.onDemandResourceLoading = true;
697
+
698
+ /**
699
+ * Gets or sets the custom tag prefix for UI components. Defaults to 'cui-'.
700
+ * @type {String}
701
+ * @memberof componyx.UI
702
+ */
703
+ Object.defineProperty(componyx.UI, 'tagPrefix', {
704
+ get: () => _customTagPrefix,
705
+ set: value => _customTagPrefix = value
706
+ });
707
+
708
+ /**
709
+ * Sets the relative or absolute path for on-demand loading of component script and css resources.
710
+ * @memberof componyx.UI
711
+ * @param {String} scriptPath The path (relative or absolute root URL) to locate script resources. Use {0} as placeholder for the component name. (e.g. 'scripts/{0}.min.js' or 'http://path/src?d={0}.min.js')
712
+ * @param {String} [cssPath] The path (relative or absolute root URL) to locate css resources. Use {0} as placeholder for the component name. (e.g. 'css/(0).min.css' or 'http://path/src?d={0}.min.css')
713
+ * @param {componyx.UI.base.static.ThemeOption} [theme] The css theme to activate. Is required when a default component theme is used (e.g. ThemeOption.DEFAULT).
714
+ * @param {String} [cssThemePath] The path (relative or absolute root URL) to locate css theme resources. Use {0} as placeholder for the component name. (e.g. 'css/{0}.Themes.Default.css' or 'http://path/src?d={0}.Themes.Default.css')
715
+ */
716
+ componyx.UI.setResourcePath = function (scriptPath, cssPath, theme, cssThemePath)
717
+ {
718
+ var script = $UI.scriptResources,
719
+ css = $UI.cssResources;
720
+
721
+ if (scriptPath)
722
+ script.global = scriptPath;
723
+
724
+ if (cssPath)
725
+ {
726
+ css.global[0] = cssPath;
727
+
728
+ if (theme > $base.static.ThemeOption.BASE)
729
+ css.global[theme] = cssThemePath;
730
+ }
731
+ };
732
+
733
+ /**
734
+ * Gets the resource path for on-demand loading of component scripts.
735
+ * @memberof componyx.UI
736
+ * @param {String} name The component name.
737
+ * @returns {string} The script resource path
738
+ */
739
+ componyx.UI.getScriptResourcePath = function (name)
740
+ {
741
+ return _addVersion($UI.scriptResources[name] || $lib.format($UI.scriptResources.global, name));
742
+ };
743
+
744
+ /**
745
+ * Gets the resource path for on-demand loading of component CSS.
746
+ * @memberof componyx.UI
747
+ * @param {String} name The component name.
748
+ * @param {componyx.UI.base.static.ThemeOption} [theme=0] The css theme (e.g. ThemeOption.DEFAULT).
749
+ * @returns {string} The css resource path
750
+ */
751
+ componyx.UI.getCssResourcePath = function (name, theme = 0)
752
+ {
753
+ return _addVersion(($UI.cssResources[name] || [])[theme] || $lib.format($UI.cssResources.global[theme], name));
754
+ };
755
+
756
+ /**
757
+ * Creates a new component instance.
758
+ * @memberof componyx.UI
759
+ * @param {Function|Constructor|String} componentClass The component class (or name when options.loadScript is set to true).
760
+ * @param {object} properties The properties to initialize the component. Must include the id property.
761
+ * @param {HTMLElement} [element] The element that is the actual component instance (standard web components) or the placeholder element to replace with thew component instance.
762
+ * @param {object} [options] Additional options.
763
+ * @param {boolean} [options.loadScript=false] Indicates whether to load the component script if it is not already loaded. Be aware, this call is awaited!
764
+ * @param {string|null} [options.namespace=null] The namespace to use when resolving the component script. Defaults to `componyx.UI.` if not provided.
765
+ * @returns {object|Promise<object>} A new component instance or a Promise resolving to the new component instance in case of loadScript:true.
766
+ */
767
+ componyx.UI.createComponent = function (componentClass, properties, element, options)
768
+ {
769
+ const { loadScript = false, namespace = _namespace } = options ?? {};
770
+
771
+ // If we need to load the script, return a Promise
772
+ if (loadScript)
773
+ {
774
+ return (async () =>
775
+ {
776
+ componentClass = await $UI.loadComponentScript(namespace + componentClass);
777
+ return $UI.createComponent(componentClass, properties, element, { ...options, loadScript: false });
778
+ })();
779
+ }
780
+
781
+ let component = $UI.store[properties.id],
782
+ create = (componentClass, properties) =>
783
+ {
784
+ const customTagName = `${_customTagPrefix}${componentClass.name.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '')}`;
785
+ component = document.createElement(customTagName);
786
+ component.id = properties.id;
787
+ component.init(properties);
788
+ return component;
789
+ };
790
+
791
+ if (component && component.renderState != $base.static.RenderState.NONE)
792
+ component.destroy();
793
+
794
+ if (componentClass.prototype instanceof window.HTMLElement)
795
+ {
796
+ if ($lib.isEmpty(properties.renderWhenConnected))
797
+ properties.renderWhenConnected = false;
798
+
799
+ if (element)
800
+ {
801
+ if (!element.nodeName.toLowerCase().startsWith(_customTagPrefix))
802
+ {
803
+ let templates = [];
804
+ $lib.each($lib.children(element), function (el)
805
+ {
806
+ if (_hasAttribute(el, _dataTemplate))
807
+ templates.push(el);
808
+ });
809
+
810
+ component = create(componentClass, properties);
811
+
812
+ $lib.each(templates, function (el)
813
+ {
814
+ component.collectTemplate(el);
815
+ });
816
+
817
+ element.replaceWith(component);
818
+ }
819
+ else
820
+ {
821
+ component = element;
822
+
823
+ if (properties)
824
+ component.cloneProperties(properties);
825
+ }
826
+
827
+ if (properties?.containerElement && properties.containerElement !== component.parentElement)
828
+ properties.containerElement.insertBefore(component, properties.beforeElement);
829
+ }
830
+ else
831
+ {
832
+ component = create(componentClass, properties);
833
+
834
+ if (properties?.containerElement)
835
+ properties.containerElement.insertBefore(component, properties.beforeElement);
836
+ }
837
+ }
838
+ else
839
+ {
840
+ component = new componentClass(properties.id, properties);
841
+ }
842
+
843
+ return component;
844
+ };
845
+
846
+ /**
847
+ * Loads the component script asynchronously by its full type name (including namespace).
848
+ * This method dynamically imports the script file, waits for any module initialization, and returns the component class once it is loaded and available.
849
+ * @param typeName The full name of the component type, including the namespace (e.g. 'componyx.UI.ComboBox').
850
+ * @memberof componyx.UI
851
+ * @returns {Promise<Function>} A Promise resolving to the loaded component class.
852
+ */
853
+ componyx.UI.loadComponentScript = async function (typeName)
854
+ {
855
+ const namespace = typeName.split('.'),
856
+ name = namespace[namespace.length - 1];
857
+
858
+ const existing = _getObj(namespace);
859
+ if (existing)
860
+ return existing;
861
+
862
+ let src = $UI.getScriptResourcePath(name);
863
+ if (!src.startsWith('/'))
864
+ src = '/' + src;
865
+
866
+ // load script file and avoid UI post-render event from firing
867
+ _renderCount++;
868
+ await import(src);
869
+
870
+ const moduleContainer = `${name.charAt(0).toLowerCase() + name.slice(1)}_modules`,
871
+ modules = _getObj(namespace.slice(0, -1).concat([moduleContainer]));
872
+
873
+ if (modules)
874
+ await modules.loaded;
875
+
876
+ _renderCount--;
877
+
878
+ return _getObj(namespace);
879
+ };
880
+
881
+ /**
882
+ * Detects and registers on the page defined components and component-groups.
883
+ * @param {HTMLElement} [container] A container element in which the UI components must be detected.
884
+ * @param {Boolean} [overwrite=true] A value indicating to overwrite (recreate) the component element if it already exists.
885
+ * @memberof componyx.UI
886
+ */
887
+ componyx.UI.detect = async function (container, overwrite)
888
+ {
889
+ let track = new Map(), groups = [], removeList = [], createList = [], inputList = [],
890
+ addToInputList = function (el)
891
+ {
892
+ var c = $UI.elementStore.get(el);
893
+
894
+ if (c && (c.inputId || c.hiddenInputId) && overwrite)
895
+ inputList.push(c);
896
+ };
897
+
898
+ if ($lib.isEmpty(overwrite))
899
+ overwrite = true;
900
+
901
+ // collect component group elements
902
+ $lib(function (el)
903
+ {
904
+ if (_hasAttribute(el, _dataGroup) && !$lib.isEmpty(el.id))
905
+ {
906
+ groups.push(el);
907
+ _createGroup(el.id);
908
+ _removeAttribute(el, _dataGroup);
909
+ }
910
+
911
+ }, container, _customTagPrefix + 'group');
912
+
913
+ $lib.each(groups, function (groupEl)
914
+ {
915
+ var action = (_getAttribute(groupEl, _dataAction) || '').toLowerCase(),
916
+ exclude = (_getAttribute(groupEl, _dataGroup) || '').replace(/\s/, '').split(','),
917
+ properties = _getGroupTagSettings(groupEl),
918
+ theme, themeName;
919
+
920
+ if ($lib.isEmpty(action) && _hasAttribute(groupEl, _dataAction))
921
+ action = "none";
922
+
923
+ if (!$lib.isEmpty(properties))
924
+ {
925
+ theme = properties.theme;
926
+ themeName = properties.themeName;
927
+ }
928
+
929
+ $lib(function (el, stop)
930
+ {
931
+ if (_hasAttribute(el, _dataGroup))
932
+ stop();
933
+
934
+ let type;
935
+ track.set(el, ''); // track elements
936
+
937
+ if ((type = _getComponentType(el)))
938
+ {
939
+ var id = el.id || (type.toLowerCase() + '_' + $lib.guid()),
940
+ mustExclude = $lib.indexOf(exclude, el.id) > -1;
941
+
942
+ el.id = id; // set id if it was not already defined
943
+
944
+ if (mustExclude)
945
+ createList.push([el, type]); // create instance only
946
+ else
947
+ {
948
+ createList.push([el, type, action, theme, themeName]);
949
+ addToInputList(el);
950
+ }
951
+
952
+ $UI.addToGroup(groupEl.id, el.id);
953
+
954
+ if ((_getAttribute(el, _dataAction) || '').toLowerCase() === 'clone' || mustExclude)
955
+ $UI.groupStore[groupEl.id].exclude.push(id); // exclude from render (included when group is destroyed)
956
+ }
957
+ }, groupEl);
958
+
959
+ // groups are virtual elements, only keep content
960
+ $lib.unsurround(groupEl);
961
+ });
962
+
963
+ // component elements
964
+ $lib(function (el)
965
+ {
966
+ var type;
967
+
968
+ if (_hasAttribute(el, _dataTemplate) && _getAttribute(el, _dataCloneable) != 'false')
969
+ $lib(function (child) { track.set(child, ''); }, el); // never render elements that are inside a cloneable template
970
+
971
+ if (!track.has(el) && (type = _getComponentType(el)))
972
+ {
973
+ createList.push([el, type]);
974
+ addToInputList(el);
975
+ }
976
+
977
+ }, container);
978
+
979
+ $lib.each(inputList, function (c) { c.getSourceElement(c.inputId || c.hiddenInputId); }); // this is a precaution to re-cache the input when the id could potentially have changed
980
+
981
+ for (const item of createList)
982
+ {
983
+ if ($lib.contains(container || document, item[0]))
984
+ await create(...item);
985
+ }
986
+
987
+ $lib.each(removeList, function (el) { $lib.remove(el); }); // remove elements for instantiated components intented for cloning
988
+
989
+ return createList;
990
+
991
+ async function create(el, type, groupAction, theme, themeName)
992
+ {
993
+ let obj, templates,
994
+ action = (_getAttribute(el, _dataAction) || '').toLowerCase(),
995
+ id = el.id || $lib.guid();
996
+
997
+ el.id = id; // set id if it was not already defined
998
+ themeName = themeName || $UI.themeName;
999
+
1000
+ if (action != "clone")
1001
+ action = action || groupAction;
1002
+
1003
+ if ($lib.isEmpty(action) && !_hasAttribute(el, _dataAction))
1004
+ action = 'show';
1005
+
1006
+ obj = $UI.store[id];
1007
+
1008
+ if (obj)
1009
+ {
1010
+ templates = obj.templates;
1011
+
1012
+ if ($lib.isElement(obj))
1013
+ {
1014
+ if (obj.renderWhenConnected && obj.renderState) // standard webcomponent type
1015
+ {
1016
+ delete el.__uiDetected;
1017
+ return;
1018
+ }
1019
+
1020
+ _initDataAttributes.bind(obj)(id, obj);
1021
+ }
1022
+ else
1023
+ {
1024
+ if (obj.element === el && !overwrite)
1025
+ {
1026
+ delete el.__uiDetected;
1027
+ return;
1028
+ }
1029
+
1030
+ if (action != "clone" && obj.element === el)
1031
+ {
1032
+ var parent = el.parentElement,
1033
+ sibling = el.nextSibling;
1034
+
1035
+ obj.destroy();
1036
+ parent.insertBefore(el, sibling);
1037
+ }
1038
+ else if (obj.element === el)
1039
+ obj.destroy();
1040
+ }
1041
+ }
1042
+
1043
+ let namespace = type.split('.');
1044
+ let componentClass = _getObj(namespace);
1045
+
1046
+ _isDetectAwaiting = true; // make sure post-render event is not fired too early
1047
+
1048
+ if (componentClass === undefined)
1049
+ {
1050
+ el[_detected] = true; // mark element as detected so we do not remove settings on the tag if a connectedCallback is triggered
1051
+ componentClass = await $UI.loadComponentScript(type);
1052
+
1053
+ if ($UI.store[id] && $lib.isElement($UI.store[id]) && $UI.store[id].renderWhenConnected) // component is directly rendered via connectedCallback
1054
+ {
1055
+ _isDetectAwaiting = false;
1056
+ delete el[_detected];
1057
+ delete el.__uiDetected;
1058
+ return;
1059
+ }
1060
+ }
1061
+
1062
+ await $UI.onPreLoadSettings.fire(el);
1063
+ _isDetectAwaiting = false; // ready for post-render event again
1064
+ _removeAttribute(el, _dataType); // clear type attribute so component wont be detected again
1065
+ el.__uiType = type; // store type on element for later reference (when component is inside template and parent component gets re-rendered)
1066
+ delete el[_detected];
1067
+ delete el.__uiDetected;
1068
+
1069
+ const properties = _getComponentTagSettings(el) || {};
1070
+
1071
+ if (templates)
1072
+ properties.templates = templates;
1073
+
1074
+ if (!$lib.isEmpty(theme))
1075
+ properties.theme = theme;
1076
+
1077
+ if (!$lib.isEmpty(themeName))
1078
+ properties.themeName = themeName;
1079
+
1080
+ properties.id = id;
1081
+ obj = $UI.createComponent(componentClass, properties, el);
1082
+
1083
+ if (!$lib.isElement(obj))
1084
+ _initDataAttributes.bind(obj)(id, $lib('#' + id));
1085
+
1086
+ if (componentClass.prototype instanceof window.HTMLElement && obj.renderWhenConnected)
1087
+ return;
1088
+
1089
+ if (action == 'clone')
1090
+ removeList.push(el); // component element is only intended for cloning
1091
+ else if (action == 'render')
1092
+ obj.render();
1093
+ else if (action == 'show')
1094
+ obj.show();
1095
+ }
1096
+ };
1097
+
1098
+ /**
1099
+ * Adds base settings for a specific component(s).
1100
+ * @memberof componyx.UI
1101
+ * @param {String} settingsId Identifier of the settings object.
1102
+ * @param {Object} settings The settings used to initialize the component.
1103
+ */
1104
+ componyx.UI.addSettings = function (settingsId, settings)
1105
+ {
1106
+ _settings[settingsId] = settings;
1107
+ };
1108
+
1109
+ /**
1110
+ * Adds a component to a unique group.
1111
+ * @memberof componyx.UI
1112
+ * @param {String} groupId Unique identifier of the component group.
1113
+ * @param {String} componentId Unique identifier of the component.
1114
+ */
1115
+ componyx.UI.addToGroup = function (groupId, componentId)
1116
+ {
1117
+ _createGroup(groupId);
1118
+ $UI.groupStore[groupId].items.push(componentId);
1119
+ };
1120
+
1121
+ /**
1122
+ * Renders all components in the specified group.
1123
+ * @memberof componyx.UI
1124
+ * @param {String} groupId Unique identifier of the component group.
1125
+ * @param {Boolean} [show] A value indicating if the components should be displayed (true by default).
1126
+ * @param {String[]} [exclude] A list of component id's inside the group to exclude from rendering.
1127
+ * @param {componyx.UI.base.static.ThemeOption} [theme] The active styling theme of the component.
1128
+ * @param {String} [themeName] The theme name is appended to the CSS class of the component's root HTMLElement.
1129
+ */
1130
+ componyx.UI.renderGroup = function (groupId, show, exclude, theme, themeName)
1131
+ {
1132
+ var group = $UI.groupStore[groupId];
1133
+
1134
+ exclude = exclude || [];
1135
+
1136
+ $lib.each(group.items, function (id)
1137
+ {
1138
+ var ex = exclude.slice().concat(group.exclude);
1139
+
1140
+ if ($UI.store[id] && (!ex.length || $lib.indexOf(ex, id) == -1))
1141
+ {
1142
+ var c = $UI.store[id];
1143
+ c.theme = ($lib.isEmpty(theme)) ? c.theme : theme;
1144
+ c.themeName = ($lib.isEmpty(themeName)) ? c.themeName : themeName;
1145
+
1146
+ if (show == false)
1147
+ {
1148
+ if (!c.renderState)
1149
+ c.render();
1150
+ }
1151
+ else
1152
+ c.show();
1153
+ }
1154
+ });
1155
+ };
1156
+
1157
+ /**
1158
+ * Destroys all components in the specified group.
1159
+ * @memberof componyx.UI
1160
+ * @param {String} groupId Unique identifier of the component group.
1161
+ * @param {Boolean} [removeGroup=false] A value indicating if the group should be removed.
1162
+ */
1163
+ componyx.UI.destroyGroup = function (groupId, removeGroup)
1164
+ {
1165
+ $lib.each($UI.groupStore[groupId].items, function (id)
1166
+ {
1167
+ if ($UI.store[id])
1168
+ $UI.store[id].destroy();
1169
+ });
1170
+
1171
+ if (removeGroup)
1172
+ $UI.removeGroup(groupId);
1173
+ };
1174
+
1175
+ /**
1176
+ * Removes the specified group.
1177
+ * @memberof componyx.UI
1178
+ * @param {String} groupId Unique identifier of the component group.
1179
+ */
1180
+ componyx.UI.removeGroup = function (groupId)
1181
+ {
1182
+ delete $UI.groupStore[groupId];
1183
+ };
1184
+
1185
+ /**
1186
+ * Destroys all components within the specified container element.
1187
+ * @param {HTMLElement} container A container element in which the UI components must be destoyed.
1188
+ * @memberof componyx.UI
1189
+ */
1190
+ componyx.UI.destroy = function (container)
1191
+ {
1192
+ var remove = [];
1193
+
1194
+ $UI.elementStore.forEach(function (instance, el)
1195
+ {
1196
+ if ($lib.contains(container, el)) // first detect then remove, otherwise components with child-components could be removed first and therefor they are no longer within container.
1197
+ remove.push(instance);
1198
+ });
1199
+
1200
+ $.each(remove, function (o)
1201
+ {
1202
+ o.destroy.bind(o)();
1203
+ });
1204
+ };
1205
+
1206
+ /**
1207
+ * Event which fires before a UI component loads the component tag settings (JSON settings from the attribute data-ui-settings or data-ui-settingsid). Incoming argument is the HTMLElement.
1208
+ * @type {componyx.UI.base.Event}
1209
+ * @see {@link componyx.UI.base.Event}
1210
+ * @memberof componyx.UI
1211
+ */
1212
+ componyx.UI.onPreLoadSettings = _createEvent('onPreLoadSettings');
1213
+
1214
+ /**
1215
+ * Event which fires before a UI component is rendered. Incoming argument is the component instance.
1216
+ * @type {componyx.UI.base.Event}
1217
+ * @see {@link componyx.UI.base.Event}
1218
+ * @memberof componyx.UI
1219
+ */
1220
+ componyx.UI.onPreRender = _createEvent('onPreRender');
1221
+
1222
+ /**
1223
+ * Event which fires when the UI component chain is rendered.
1224
+ * @type {componyx.UI.base.Event}
1225
+ * @see {@link componyx.UI.base.Event}
1226
+ * @memberof componyx.UI
1227
+ */
1228
+ componyx.UI.onPostRender = _postRenderEvent;
1229
+
1230
+ /**
1231
+ * Represents a configurable AJAX method associated with a component or context instance.
1232
+ * Used to generate dynamic endpoint URLs and manage request payloads and headers.
1233
+ * @typedef {Object} AjaxMethod
1234
+ * @memberof componyx.UI.base
1235
+ * @property {String} url - The method-specific URL to use. If not set, falls back to `ajax.url`.
1236
+ * @property {Boolean} absoluteURL=true - If `false`, the URL will be resolved as relative to `location.origin`.
1237
+ * @property {String|null} jsonResponseDataWrapper=null - Optional key to extract response data from (e.g., for .NET-style responses). Falls back to `ajax.jsonResponseDataWrapper`.
1238
+ * @property {Function|null} jsonDeserializer=null - Optional function to deserialize the JSON response.
1239
+ * @property {String} endPointMethod - The server-side method name to be appended to the endpoint URL.
1240
+ * @property {String|Object} customParameters - Additional query parameters or payload to be merged with the request data.
1241
+ * @property {Object.<string, string>|null} headers=null - HTTP headers specific to this method. Overrides global headers in `ajax.headers`.
1242
+ */
1243
+
1244
+ /**
1245
+ * @typedef {function(string): void} AddAjaxMethod
1246
+ * @memberof componyx.UI.base
1247
+ * @param {String} name - The method name to create or override.
1248
+ */
1249
+
1250
+ /**
1251
+ * UI component Base Component class.
1252
+ * @class
1253
+ * @memberof componyx.UI
1254
+ * @property {String} id - The unique identifier of the component.
1255
+ * @property {HTMLElement} containerElement - The parent element of the component's root element.
1256
+ * @property {HTMLElement} beforeElement - The component's root element will be placed before this node when specified.
1257
+ * @property {String} name - The name of the component's input field when applicable.
1258
+ * @property {componyx.UI.base.static.ThemeOption} theme - The active styling theme of the component.
1259
+ * @property {String} themeName - The CSS theme name which is set on the component's root element.
1260
+ * @property {String} cssClass - The CSS class of the component's root HTMLElement.
1261
+ * @property {String} cssClassTouch - The CSS class applied to the component's root HTMLElement when dealing with a touch device.
1262
+ * @property {String} style - The CSS style text applied to the root HTMLElement of the component.
1263
+ * @property {String} title - The title of the root element.
1264
+ * @property {String} tabIndex - The tabindex of the component's root HTMLElement.
1265
+ * @property {Boolean} autoTouchConfig=true - A value indicating if the component automatically uses optimal touch device settings when rendered on a touch device.
1266
+ * @property {Boolean} cloneInput=false - A value indicating if the source input is cloned when a (hidden)input is used as base element.
1267
+ * @property {Boolean} keepInputId=true - A value indicating if the source input id is kept when a (hidden)input is cloned and used as base element. Should be false when cloning a single input for use in multiple components.
1268
+ * @property {Boolean} keepInputEventHandlers=false - A value indicating if the source input event handlers are kept when a (hidden)input is cloned and used as base element.
1269
+ * @property {Boolean} disableDefaultCssClass=false - A value indicating if the default component css class is set on the component element.
1270
+ * @property {componyx.UI.base.static.RenderState} renderState - The current render state of the component.
1271
+ * @property {HTMLElement} element - The root HTMLElement of the component.
1272
+ * @property {Boolean} showing=false - A value indicating if the component is visible. This active state might differ from property hidden when async resources are loaded, i.e. when a component.show() is called this property value is set to true while the hidden property will also remain true until the resources are loaded and the component is rendered.
1273
+ * @property {Boolean} allowPostRender=true - A value indicating if the post render event is allowed.
1274
+ * @property {Boolean} renderWhenConnected=false - A value indicating if the component is rendered when the connectedCallback method is invoked. This property only applies to components that are build as standard Web Component with a custom element definition, see documentation for applicable components.
1275
+ * @property {Boolean} ignoreConnectionCallbacks=false - A value indicating if the execution of the connectedCallback and disconnectedCallback are prevented during DOM modifications (e.g. replaceChild, appendChild, insertBefore, replaceWith). By default, this is `false`. If needed, you can set it to `true` before modifying the DOM. This property only applies to components built as standard Web Components with a custom element definition; see documentation for applicable components.
1276
+ * @property {Boolean} canRender=true - A value indicating if the component is allowed to render. Useful for delaying rendering until data binding (using any prefered framework) is complete when attributes are data-bound.
1277
+ * @property {Object} templates - Initialized Templates of the component.
1278
+ * @property {Object} store - Internal storage of UI child components.
1279
+ * @property {componyx.UI.base.Events} events - The base component events.
1280
+ * @property {Object} ajax - Contains AJAX configuration and dynamically created AJAX methods.
1281
+ * @property {String} ajax.url - The default base URL used for AJAX calls.
1282
+ * @property {Boolean} ajax.absoluteURL=true - Indicates whether the `url` is treated as absolute (`true`) or relative to the current location (`false`).
1283
+ * @property {Object.<string, string>} [ajax.headers=null] - Optional HTTP request headers to include with each AJAX call.
1284
+ * @property {String} ajax.jsonResponseDataWrapper='d' - The JSON wrapper key used to extract data from the server response (e.g., for ASP.NET JSON responses).
1285
+ * @property {Function|null} ajax.jsonDeserializer=null - Optional function to deserialize JSON data from the server.
1286
+ * @property {componyx.UI.base.AjaxMethod} ajax.load - Default AJAX method instance used for basic data loading.
1287
+ * @property {componyx.UI.base.AddAjaxMethod} ajax.addMethod - INTERNAL: Adds or overrides a named AJAX method.
1288
+ */
1289
+ componyx.UI.base.Component = function (id, properties, baseObject)
1290
+ {
1291
+ var instance = this, isElement = $lib.isElement(properties);
1292
+
1293
+ this.id = id;
1294
+ this.containerElement = (isElement) ? properties : (properties) ? properties.containerElement : null;
1295
+ this.style = '';
1296
+ this.tabIndex = null;
1297
+ this.events = this.events || {};
1298
+
1299
+ _baseProperties.apply(this);
1300
+ _baseEvents.apply(this.events);
1301
+
1302
+ if (properties && !isElement)
1303
+ {
1304
+ delete properties.containerElement;
1305
+ this.cloneProperties(properties);
1306
+ }
1307
+
1308
+ if (baseObject)
1309
+ {
1310
+ if (baseObject.constructor.toString().indexOf('Array') == -1)
1311
+ baseObject = [baseObject];
1312
+
1313
+ $lib.each(baseObject, function (baseObj)
1314
+ {
1315
+ if (typeof (baseObj) == 'function')
1316
+ baseObj = new baseObj();
1317
+
1318
+ $lib.clone({
1319
+ target: instance,
1320
+ source: baseObj,
1321
+ deep: true,
1322
+ overwrite: false,
1323
+ extend: true,
1324
+ excludeEmpty: false,
1325
+ excludeFunctions: false
1326
+ });
1327
+ });
1328
+ }
1329
+
1330
+ // Store component instance in shared variable for communication between components
1331
+ if (instance.id)
1332
+ {
1333
+ $UI.store[instance.id] = instance;
1334
+
1335
+ let placeHolder = $lib('#' + instance.id);
1336
+
1337
+ isElement = $lib.isElement(instance);
1338
+
1339
+ if (!placeHolder && (instance.containerElement && instance.containerElement.isConnected))
1340
+ {
1341
+ if (isElement)
1342
+ placeHolder = instance.containerElement.insertBefore(instance, instance.beforeElement);
1343
+ else
1344
+ placeHolder = instance.containerElement.insertBefore(document.createElement('div'), instance.beforeElement);
1345
+ }
1346
+
1347
+ if (placeHolder)
1348
+ {
1349
+ placeHolder.id = instance.id;
1350
+ placeHolder.style.display = 'none';
1351
+ instance.element = placeHolder;
1352
+ $UI.elementStore.set(placeHolder, instance);
1353
+ }
1354
+ }
1355
+ };
1356
+
1357
+ window.$base = componyx.UI.base;
1358
+
1359
+ /**
1360
+ * Base static utility methods.
1361
+ * @namespace static
1362
+ * @memberof componyx.UI.base
1363
+ * @static
1364
+ */
1365
+ componyx.UI.base.static =
1366
+ {
1367
+ /**
1368
+ * Theme options.
1369
+ * @readonly
1370
+ * @enum {number}
1371
+ */
1372
+ ThemeOption:
1373
+ {
1374
+ NONE: -1,
1375
+ BASE: 0,
1376
+ DEFAULT: 1,
1377
+
1378
+ getName: function (value)
1379
+ {
1380
+ return $base.static.getKeyByValue(this, value).toLowerCase();
1381
+ }
1382
+ },
1383
+ /**
1384
+ * Render states.
1385
+ * @readonly
1386
+ * @enum {number}
1387
+ */
1388
+ RenderState:
1389
+ {
1390
+ NONE: 0,
1391
+ RENDERING: 1,
1392
+ RENDERED: 2
1393
+ },
1394
+
1395
+ /**
1396
+ * Animation easing options.
1397
+ * @readonly
1398
+ * @enum {number}
1399
+ */
1400
+ AnimationEasingOption:
1401
+ {
1402
+ linear: 0,
1403
+ easeInQuad: 1,
1404
+ easeOutQuad: 2,
1405
+ easeInOutQuad: 3,
1406
+ easeInCubic: 4,
1407
+ easeOutCubic: 5,
1408
+ easeInOutCubic: 6,
1409
+ easeInQuart: 7,
1410
+ easeOutQuart: 8,
1411
+ easeInOutQuart: 9,
1412
+ easeInQuint: 10,
1413
+ easeOutQuint: 11,
1414
+ easeInOutQuint: 12,
1415
+ easeInSine: 13,
1416
+ easeOutSine: 14,
1417
+ easeInOutSine: 15,
1418
+ easeInExpo: 16,
1419
+ easeOutExpo: 17,
1420
+ easeInOutExpo: 18,
1421
+ easeInCirc: 19,
1422
+ easeOutCirc: 20,
1423
+ easeInOutCirc: 21,
1424
+ easeInElastic: 22,
1425
+ easeOutElastic: 23,
1426
+ easeInOutElastic: 24,
1427
+ easeInBack: 25,
1428
+ easeOutBack: 26,
1429
+ easeInOutBack: 27,
1430
+ easeInBounce: 28,
1431
+ easeOutBounce: 29,
1432
+ easeInOutBounce: 30,
1433
+
1434
+ getName: function (value) { return $base.static.getKeyByValue(this, value); }
1435
+ },
1436
+
1437
+ getMethod: function (name)
1438
+ {
1439
+ return _getMethod(name);
1440
+ },
1441
+
1442
+ getKeyByValue: function (obj, value)
1443
+ {
1444
+ for (var key in obj)
1445
+ {
1446
+ if (obj[key] == value)
1447
+ return key.toString();
1448
+ }
1449
+
1450
+ return null;
1451
+ },
1452
+
1453
+ initDragSettings: function (settings, srcSettings)
1454
+ {
1455
+ $lib.each(settings.dropZones, function (id, index)
1456
+ {
1457
+ settings.dropZones[index] = (typeof (id) == 'string') ? $lib('#' + id) : id;
1458
+ });
1459
+
1460
+ settings.dragHandle = (typeof (settings.dragHandle) == 'string') ? $lib('#' + settings.dragHandle) : settings.dragHandle;
1461
+ settings.dragGhost = (typeof (settings.dragGhost) == 'string') ? $lib('#' + settings.dragGhost) : settings.dragGhost;
1462
+ settings.boundaryZone = (typeof (settings.boundaryZone) == 'string') ? $lib('#' + settings.boundaryZone) : settings.boundaryZone;
1463
+
1464
+ if (srcSettings)
1465
+ $lib.clone(settings, srcSettings, false, 2, true);
1466
+
1467
+ $lib.each(settings, function (item, key)
1468
+ {
1469
+ if (item && typeof item == 'function')
1470
+ _monkeyPatch(settings, srcSettings, key);
1471
+ });
1472
+
1473
+ return settings;
1474
+ },
1475
+
1476
+ initResizeSettings: function (settings, srcSettings)
1477
+ {
1478
+ $lib.each(settings.resizeHandles, function (value, key)
1479
+ {
1480
+ settings.resizeHandles[key] = (typeof (value) == 'string') ? $lib('#' + value) : value;
1481
+ });
1482
+
1483
+ settings.resizeGhost = (typeof (settings.resizeGhost) == 'string') ? $lib('#' + settings.resizeGhost) : settings.resizeGhost;
1484
+ settings.boundaryZone = (typeof (settings.boundaryZone) == 'string') ? $lib('#' + settings.boundaryZone) : settings.boundaryZone;
1485
+
1486
+ if (srcSettings)
1487
+ $lib.clone(settings, srcSettings, false, 2, true);
1488
+
1489
+ $lib.each(settings, function (item, key)
1490
+ {
1491
+ if (item && typeof item == 'function')
1492
+ _monkeyPatch(settings, srcSettings, key);
1493
+ });
1494
+
1495
+ return settings;
1496
+ },
1497
+
1498
+ initSelectSettings: function (settings, srcSettings)
1499
+ {
1500
+ $lib.each(settings.include, function (id, index)
1501
+ {
1502
+ settings.include[index] = (typeof (id) == 'string') ? $lib('#' + id) : id;
1503
+ });
1504
+
1505
+ $lib.each(settings.exclude, function (id, index)
1506
+ {
1507
+ settings.exclude[index] = (typeof (id) == 'string') ? $lib('#' + id) : id;
1508
+ });
1509
+
1510
+ settings.dragZone = (typeof (settings.dragZone) == 'string') ? $lib('#' + settings.dragZone) : settings.dragZone;
1511
+ settings.selectZone = (typeof (settings.selectZone) == 'string') ? $lib('#' + settings.selectZone) : settings.selectZone;
1512
+
1513
+ if (srcSettings)
1514
+ $lib.clone(settings, srcSettings, false, 2, true);
1515
+
1516
+ $lib.each(settings, function (item, key)
1517
+ {
1518
+ if (item && typeof item == 'function')
1519
+ _monkeyPatch(settings, srcSettings, key);
1520
+ });
1521
+
1522
+ return settings;
1523
+ },
1524
+
1525
+ AjaxMethod: function (instance)
1526
+ {
1527
+ this.url = '';
1528
+ this.absoluteURL = true;
1529
+ this.jsonResponseDataWrapper = null;
1530
+ this.jsonDeserializer = null;
1531
+ this.endPointMethod = '';
1532
+ this.customParameters = '';
1533
+ this.headers = null;
1534
+
1535
+ this.isDefined = function ()
1536
+ {
1537
+ return (!$lib.isEmpty(instance.ajax.url) || !$lib.isEmpty(this.url));
1538
+ };
1539
+
1540
+ this.invoke = function (data, onStart, onComplete, onSuccess, onError, onProgress, onUploadProgress, onUploadError, onAbort)
1541
+ {
1542
+ let url = (this.url || instance.ajax.url),
1543
+ absoluteURL = (this.absoluteURL || instance.ajax.absoluteURL),
1544
+ headers = this.headers || instance.ajax.headers,
1545
+ parameters = this.customParameters,
1546
+ wrapper = '';
1547
+
1548
+ if (!absoluteURL)
1549
+ url = url.startsWith('/') ? `${location.origin}${url}` : `${location.origin}/${url}`;
1550
+
1551
+ if (this.endPointMethod)
1552
+ url += '/' + this.endPointMethod;
1553
+
1554
+ if (!(data instanceof FormData) && (typeof data != "string" || $lib.startsWith(data, '{')))
1555
+ {
1556
+ if (typeof data != "string")
1557
+ data = window.JSON.stringify(data);
1558
+
1559
+ if (typeof parameters != "string")
1560
+ parameters = window.JSON.stringify(parameters);
1561
+
1562
+ if (parameters && parameters.match(/^\{/))
1563
+ parameters = parameters.substring(1, parameters.length - 1);
1564
+
1565
+ if (data && data.match(/^\{/))
1566
+ data = data.substring(1, data.length - 1);
1567
+
1568
+ if (data && parameters)
1569
+ data += ', ' + parameters;
1570
+ else if (parameters)
1571
+ data = parameters;
1572
+
1573
+ if (data)
1574
+ data = '{' + data + '}';
1575
+
1576
+ if (this.jsonResponseDataWrapper != null)
1577
+ wrapper = this.jsonResponseDataWrapper;
1578
+ else if (instance.ajax.jsonResponseDataWrapper != null)
1579
+ wrapper = instance.ajax.jsonResponseDataWrapper;
1580
+ }
1581
+ else
1582
+ {
1583
+ if (parameters && (parameters.match(/^\?/) || parameters.match(/^\&/)))
1584
+ parameters = parameters.substring(1);
1585
+
1586
+ if (data && parameters)
1587
+ data += '&' + parameters;
1588
+ else if (parameters)
1589
+ data = parameters;
1590
+ }
1591
+
1592
+ return $lib.xhr({
1593
+ url,
1594
+ data,
1595
+ headers,
1596
+ jsonResponseDataWrapper: wrapper,
1597
+ jsonDeserializer: this.jsonDeserializer || instance.ajax.jsonDeserializer,
1598
+ onStart,
1599
+ onComplete,
1600
+ onSuccess,
1601
+ onError,
1602
+ onProgress,
1603
+ onUploadProgress,
1604
+ onUploadError,
1605
+ onAbort
1606
+ });
1607
+ };
1608
+ },
1609
+
1610
+ /**
1611
+ * ItemList Base class.
1612
+ * @class
1613
+ * @memberof componyx.UI.base.static
1614
+ */
1615
+ ItemList: function ()
1616
+ {
1617
+ var _list = this;
1618
+
1619
+ this.get = function (id)
1620
+ {
1621
+ return _list[getIndex(id)];
1622
+ };
1623
+
1624
+ this.getByValue = function (value)
1625
+ {
1626
+ return _list[getIndexByValue(value)];
1627
+ };
1628
+
1629
+ this.getByText = function (text, caseInsensitive)
1630
+ {
1631
+ return _list[getIndexByText(text, caseInsensitive)];
1632
+ };
1633
+
1634
+ this.previous = function (id)
1635
+ {
1636
+ var index = getIndex(id),
1637
+ item = _list[--index], start = item;
1638
+
1639
+ while (item && item.disabled)
1640
+ {
1641
+ item = _list[--index];
1642
+
1643
+ if (item === start)
1644
+ item = null;
1645
+ }
1646
+
1647
+ return item;
1648
+ };
1649
+
1650
+ this.next = function (id)
1651
+ {
1652
+ var index = getIndex(id),
1653
+ item = _list[++index], start = item;
1654
+
1655
+ while (item && item.disabled)
1656
+ {
1657
+ item = _list[++index];
1658
+
1659
+ if (item === start)
1660
+ item = null;
1661
+ }
1662
+
1663
+ return item;
1664
+ };
1665
+
1666
+ this.find = function (text, id)
1667
+ {
1668
+ var list = _list.filter(text);
1669
+ return (!id) ? list[0] : list[getIndex(id, list) + 1] || list[0];
1670
+ };
1671
+
1672
+ this.filter = function (text)
1673
+ {
1674
+ var list = [];
1675
+ $lib.each(_list, function (item)
1676
+ {
1677
+ if ($lib.startsWith(item.text, text, true))
1678
+ list.push(item);
1679
+ });
1680
+
1681
+ return list;
1682
+ };
1683
+
1684
+ function getIndex(id, list)
1685
+ {
1686
+ return $lib.indexOf(list || _list, function (item) { return (item.id === id); });
1687
+ }
1688
+
1689
+ function getIndexByValue(value, list)
1690
+ {
1691
+ return $lib.indexOf(list || _list, function (item) { return (item.value === value); });
1692
+ }
1693
+
1694
+ function getIndexByText(text, caseInsensitive, list)
1695
+ {
1696
+ return $lib.indexOf(list || _list, function (item)
1697
+ {
1698
+ return caseInsensitive
1699
+ ? item.text.toLowerCase() === text.toLowerCase()
1700
+ : item.text === text;
1701
+ });
1702
+ }
1703
+ },
1704
+
1705
+ /**
1706
+ * Item base class.
1707
+ * @class
1708
+ * @memberof componyx.UI.base.static
1709
+ * @param {Object} properties The properties used to initialize the object.
1710
+ * @property {Object} attributes Gets or sets the attributes of the item.
1711
+ * @property {String} id Gets or sets the id of the item.
1712
+ * @property {String} templateId Gets or sets the id of the item template.
1713
+ * @property {String} cssClass Gets or sets the css class of the item.
1714
+ * @property {String} text Gets or sets the text of the item.
1715
+ * @property {String} value Gets or sets the value of the item.
1716
+ * @property {String} title Gets or sets the title of the item.
1717
+ * @property {Boolean} disabled Gets or sets a value indicating if the item is disabled.
1718
+ * @property {Boolean} readOnly Gets or sets a value indicating if the item is read-only.
1719
+ * @property {Boolean} selected Gets or sets a Value indicating if the item is selected.
1720
+ */
1721
+ Item: function (properties)
1722
+ {
1723
+ this.attributes = {};
1724
+ this.id = '';
1725
+ this.templateId = '';
1726
+ this.cssClass = '';
1727
+ this.text = '';
1728
+ this.value = '';
1729
+ this.title = '';
1730
+ this.disabled = false;
1731
+ this.readOnly = false;
1732
+ this.selected = false;
1733
+ $lib.clone(this, properties, true, true, true, true, false);
1734
+ },
1735
+
1736
+ createEvent: function (eventName)
1737
+ {
1738
+ return _createEvent(eventName);
1739
+ }
1740
+ };
1741
+
1742
+ /**
1743
+ * A base class for UI components extending the HTMLElement.
1744
+ * This class provides the core functionality for UI components including base properties and events.
1745
+ * @class WebComponent
1746
+ * @memberof componyx.UI.base
1747
+ * @augments HTMLElement
1748
+ */
1749
+ componyx.UI.base.WebComponent = class WebComponent extends HTMLElement
1750
+ {
1751
+ constructor()
1752
+ {
1753
+ super();
1754
+
1755
+ /**
1756
+ * Events attached to the component.
1757
+ * @type {componyx.UI.base.Events}
1758
+ */
1759
+ this.events = this.events || {};
1760
+
1761
+ _baseProperties.apply(this);
1762
+ _baseEvents.apply(this.events);
1763
+ }
1764
+
1765
+ /**
1766
+ * Initializes the component with the specified properties.
1767
+ * @function
1768
+ */
1769
+ init(props)
1770
+ {
1771
+ _initComponent[this.id] = { props };
1772
+ }
1773
+
1774
+ /**
1775
+ * Invoked when the element is added to the DOM. Renders the component if not already in the rendering state.
1776
+ * @function
1777
+ */
1778
+ async connectedCallback()
1779
+ {
1780
+ if (this.ignoreConnectionCallbacks)
1781
+ return;
1782
+
1783
+ let id = this.id;
1784
+
1785
+ if ($lib.isEmpty(id))
1786
+ {
1787
+ let type = Object.getPrototypeOf(this).constructor.name;
1788
+ id = (type.toLowerCase() + $lib.guid());
1789
+ this.setAttribute('id', id);
1790
+ }
1791
+
1792
+ if (!$UI.store[id] || $UI.store[id] !== this) // new component, use initialisation props
1793
+ {
1794
+ const init = _initComponent[id] = _initComponent[id] || {},
1795
+ props = init.props;
1796
+
1797
+ if (!$lib.isEmpty(props))
1798
+ this.cloneProperties(props);
1799
+
1800
+ _initDataAttributes.bind(this)(id, this);
1801
+ }
1802
+
1803
+ $UI.store[id] = this;
1804
+
1805
+ if (this.renderWhenConnected && this.renderState == $base.static.RenderState.NONE)
1806
+ {
1807
+ await $UI.onPreLoadSettings.fire(this);
1808
+
1809
+ if (this.renderState == $base.static.RenderState.NONE) // could have changed while awaiting (e.g. destroyed)
1810
+ this.show();
1811
+ }
1812
+ }
1813
+
1814
+ /**
1815
+ * Invoked when the element is removed from the DOM. Cleans up the component.
1816
+ * @function
1817
+ */
1818
+ disconnectedCallback()
1819
+ {
1820
+ if (!this.ignoreConnectionCallbacks && this.element)
1821
+ {
1822
+ this.destroy();
1823
+ }
1824
+ }
1825
+
1826
+ /**
1827
+ * Renders the component.
1828
+ * @param {Function} resourceHandler The callback method for registering script and css resources.
1829
+ * @param {String} defaultName The default name is used as default css class and when $UI.useCustomElementNames is enabled, as the element tag name prefixed by 'com'.
1830
+ * @param {Boolean} hasTheme A value indicating if the component has a CSS theme.
1831
+ * @function
1832
+ */
1833
+ render(resourceHandler, defaultName, hasTheme)
1834
+ {
1835
+ if (this.renderState == $base.static.RenderState.RENDERING || this.canRender === false)
1836
+ return;
1837
+
1838
+ this.destroy(true, false);
1839
+ $UI.store[this.id] = this;
1840
+ this.element = this;
1841
+ this.initRenderPhase(resourceHandler, defaultName, hasTheme);
1842
+ }
1843
+
1844
+ /**
1845
+ * Destroys the component.
1846
+ * @param {Boolean} keepEvents A value indicating if component events should be kept.
1847
+ * @param {Boolean} removeElement=true A value indicating if the corresponding HTML Element should be removed.
1848
+ * @function
1849
+ */
1850
+ destroy(keepEvents, removeElement = true)
1851
+ {
1852
+ $base.methods.destroy.call(this, keepEvents, removeElement);
1853
+ }
1854
+
1855
+ /**
1856
+ * Gets the CSS class name from the component property if it exists, otherwise returns the default CSS class.
1857
+ * @param {Object} classOption Object containing class options.
1858
+ * @param {string} cssClassValue Default CSS class value.
1859
+ * @returns {string} The resolved CSS class name.
1860
+ * @function
1861
+ */
1862
+ getCssClass(classOption, cssClassValue)
1863
+ {
1864
+ return $base.methods.getCssClass.call(this, classOption, cssClassValue);
1865
+ }
1866
+
1867
+ /**
1868
+ * Clones component properties. This method should not be invoked by client code directly but only from inside component code.
1869
+ * @param {Object} properties The properties to clone.
1870
+ * @function
1871
+ * @protected
1872
+ */
1873
+ cloneProperties(properties)
1874
+ {
1875
+ $base.methods.cloneProperties.call(this, properties);
1876
+ }
1877
+
1878
+ /**
1879
+ * Executes the post render procedure. This method should not be invoked by client code directly but only from inside component code.
1880
+ * @function
1881
+ * @protected
1882
+ */
1883
+ postRender()
1884
+ {
1885
+ $base.methods.postRender.call(this);
1886
+ }
1887
+ };
1888
+
1889
+ /**
1890
+ * @interface
1891
+ * @name componyx.UI.base.methods
1892
+ * @memberof componyx.UI.base
1893
+ */
1894
+
1895
+ componyx.UI.base.methods =
1896
+ {
1897
+ /**
1898
+ * Handles attribute mutations, updates corresponding properties, and triggers a single render after all mutations are processed.
1899
+ * @function componyx.UI.base.methods#mutationCallback
1900
+ * @param {MutationRecord[]} mutations - An array of mutation records containing details of the observed attribute changes.
1901
+ * @param {MutationObserver} observer - The MutationObserver instance that is observing changes to the DOM.
1902
+ * @protected
1903
+ */
1904
+ mutationCallback(mutations, observer)
1905
+ {
1906
+ let changes = false;
1907
+
1908
+ if (!this.observing)
1909
+ return;
1910
+
1911
+ clearTimeout(this.mutationRenderTimerId);
1912
+
1913
+ for (const mutation of mutations)
1914
+ {
1915
+ let name = mutation.attributeName.toLowerCase(),
1916
+ value = mutation.target.getAttribute(mutation.attributeName);
1917
+
1918
+ if (_setPropertyFromAttribute.bind(this)(this.element, name, value))
1919
+ {
1920
+ changes = true;
1921
+
1922
+ const init = _initComponent[this.id];
1923
+ if (init?.attrs)
1924
+ init.attrs[name] = value; // keep the diff cache in sync
1925
+ }
1926
+ }
1927
+
1928
+ if (changes)
1929
+ this.mutationRenderTimerId = setTimeout(() => { this.render(); }, 0);
1930
+ },
1931
+
1932
+ /**
1933
+ * Executes and XHR call. This method should not be invoked by client code directly but only from inside component code.
1934
+ * @function componyx.UI.base.methods#ajaxCall
1935
+ * @param {String} method The name of the method.
1936
+ * @param {Object|String} data The data to send.
1937
+ * @param {Object} events An object holding event handlers for various XHR events.
1938
+ * @property {componyx.library.XhrCallback} events.onStart Defines the callback function to call before the request.
1939
+ * @property {componyx.library.XhrCallback} events.onComplete Defines the callback function to call after the request.
1940
+ * @property {componyx.library.XhrCallback} events.onSuccess Defines the callback function to call when the request was successful.
1941
+ * @property {componyx.library.XhrCallback} events.onError the callback function to call when the HTTP request failed. (HTTP status >= 200 and < 300).
1942
+ * @property {componyx.library.XhrCallback} events.onAbort Defines the callback function to call when the request was aborted.
1943
+ * @property {componyx.library.XhrCallback} events.onProgress Defines the callback function to call when there is a change in the download progress.
1944
+ * @property {componyx.library.XhrCallback} events.onUploadProgress Defines the callback function to call when there is a change in the upload progress.
1945
+ * @property {componyx.library.XhrCallback} events.onUploadError Defines the callback function to call when the upload failed.
1946
+ * @param {Object[]} callArgs Arguments to pass to the global event handlers.
1947
+ * @function
1948
+ * @protected
1949
+ */
1950
+ ajaxCall: function (method, data, events, callArgs)
1951
+ {
1952
+ events = events || {};
1953
+
1954
+ let instance = this, guid = $lib.guid(),
1955
+ clear = () =>
1956
+ {
1957
+ if (instance.__ajax && instance.__ajax[method] && instance.__ajax[method][guid]) // clear stored ajax request
1958
+ delete instance.__ajax[method][guid];
1959
+ },
1960
+ invoke = (name, args) =>
1961
+ {
1962
+ if (events[`on${name}`])
1963
+ events[`on${name}`](args);
1964
+
1965
+ instance.events[`onAjax${name}`].fire(instance, [args].concat(callArgs || []));
1966
+ },
1967
+ begin = (args) => { invoke('Start', args); },
1968
+ end = (args) =>
1969
+ {
1970
+ clear();
1971
+ invoke('Complete', args);
1972
+ },
1973
+ success = (args) => { invoke('Success', args); },
1974
+ error = (args) => { invoke('Error', args); },
1975
+ progress = (args) => { invoke('Progress', args); },
1976
+ uploadProgress = (args) => { invoke('UploadProgress', args); },
1977
+ uploadError = (args) => { invoke('UploadError', args); },
1978
+ abort = (args) =>
1979
+ {
1980
+ clear();
1981
+ invoke('Abort', args);
1982
+ };
1983
+
1984
+ if (!instance.__ajax[method])
1985
+ instance.__ajax[method] = {};
1986
+
1987
+ instance.__ajax[method][guid] = instance.ajax[method].invoke(data, begin, end, success, error, progress, uploadProgress, uploadError, abort);
1988
+ return instance.__ajax[method][guid];
1989
+ },
1990
+
1991
+ /**
1992
+ * Gets the css class for the specified class option value: the value of the matching cssClass<Key> property on the component when set, otherwise the default value.
1993
+ * Example: for the key 'BUILD_PANE' the property 'cssClassBuildPane' is checked.
1994
+ * @param {Object} classOption The class option object of the component.
1995
+ * @param {String} cssClassValue The default css class (a value of the class option object).
1996
+ * @returns {String} The resolved css class.
1997
+ * @function componyx.UI.base.methods#getCssClass
1998
+ * @protected
1999
+ */
2000
+ getCssClass: function (classOption, cssClassValue)
2001
+ {
2002
+ const key = Object.keys(classOption).find(key => classOption[key] === cssClassValue);
2003
+
2004
+ if (!key)
2005
+ return cssClassValue;
2006
+
2007
+ const propertyName = key.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(''); // e.g. 'BUILD_PANE' -> 'BuildPane'
2008
+ return this[`cssClass${propertyName}`] || cssClassValue;
2009
+ },
2010
+
2011
+ /**
2012
+ * Suppresses any change events fired by the underlying input element during the execution of the provided action. Use this when programmatically setting a component value to prevent the change event from propagating as a user interaction.
2013
+ * The suppression is automatically lifted after the action completes, even if it throws.
2014
+ * @param {Function} action - The action to execute with change events suppressed.
2015
+ */
2016
+ suppressNextChangeEvent: function (action)
2017
+ {
2018
+ this.__suppressNextChangeEvent = true;
2019
+ try
2020
+ {
2021
+ action();
2022
+ }
2023
+ finally
2024
+ {
2025
+ this.__suppressNextChangeEvent = false;
2026
+ }
2027
+ },
2028
+
2029
+ /**
2030
+ * Hides the component when it's showing, otherwise it shows the component.
2031
+ * @function componyx.UI.base.methods#toggle
2032
+ */
2033
+ toggle: function ()
2034
+ {
2035
+ if (this.showing)
2036
+ this.hide();
2037
+ else
2038
+ this.show();
2039
+ },
2040
+
2041
+ /**
2042
+ * Shows the component.
2043
+ * @function componyx.UI.base.methods#show
2044
+ */
2045
+ show: function (fireEvent)
2046
+ {
2047
+ var instance = this;
2048
+
2049
+ instance.showing = true;
2050
+
2051
+ if (instance.renderState == $base.static.RenderState.NONE)
2052
+ {
2053
+ instance.render();
2054
+ }
2055
+ else if (instance.renderState == $base.static.RenderState.RENDERED && instance.__hidden)
2056
+ {
2057
+ instance.element.classList.remove(_cssClassHidden);
2058
+ instance.__hidden = false;
2059
+
2060
+ if (fireEvent != false)
2061
+ instance.events.onShow.fire(instance);
2062
+ }
2063
+ else
2064
+ instance.containerElement = instance.element.parentNode;
2065
+ },
2066
+
2067
+ /**
2068
+ * Hides the component.
2069
+ * @function componyx.UI.base.methods#hide
2070
+ */
2071
+ hide: function (fireEvent)
2072
+ {
2073
+ var instance = this;
2074
+
2075
+ instance.showing = false;
2076
+
2077
+ if (instance.renderState == $base.static.RenderState.RENDERED && !instance.__hidden)
2078
+ {
2079
+ var el, index,
2080
+ elements = $lib('focus', instance.element);
2081
+
2082
+ for (index = 0; index < elements.length; ++index)
2083
+ {
2084
+ el = elements[index];
2085
+
2086
+ if (el.blur)
2087
+ el.blur();
2088
+
2089
+ $lib.removeClass(el, 'focus');
2090
+ }
2091
+
2092
+ instance.element.classList.add(_cssClassHidden);
2093
+ instance.__hidden = true;
2094
+
2095
+ if (fireEvent != false)
2096
+ instance.events.onHide.fire(instance);
2097
+ }
2098
+ },
2099
+
2100
+ /**
2101
+ * Destroys the component.
2102
+ * @param {Boolean} keepEvents A value indicating if component events should be kept.
2103
+ * @param {Boolean} removeElement=true A value indicating if the corresponding HTML Element should be removed.
2104
+ * @function componyx.UI.base.methods#destroy
2105
+ */
2106
+ destroy: function (keepEvents, removeElement = true)
2107
+ {
2108
+ var instance = this,
2109
+ element = instance.element, method, guid;
2110
+
2111
+ if (instance.renderState != $base.static.RenderState.NONE)
2112
+ {
2113
+ instance.events.onDestroy.fire(instance);
2114
+
2115
+ if (instance.renderState == $base.static.RenderState.RENDERED)
2116
+ {
2117
+ // abort possible pending ajax requests
2118
+ for (method in instance.__ajax)
2119
+ {
2120
+ for (guid in instance.__ajax[method])
2121
+ instance.__ajax[method][guid].abort();
2122
+ }
2123
+
2124
+ // remove child components
2125
+ $lib.each(instance.store, function (id)
2126
+ {
2127
+ if ($UI.store[id])
2128
+ $UI.store[id].destroy();
2129
+ });
2130
+ }
2131
+ else if (instance.renderState == $base.static.RenderState.RENDERING)
2132
+ _renderCount--; // component will never reach postRender event so remove from render stack
2133
+
2134
+ instance.events.dispose((keepEvents) ? 2 : null); // when keep events is true, we only remove priority events otherwise they would be added again on render
2135
+
2136
+ if (element && ($lib.isEmpty(element.id) || element.id === instance.id))
2137
+ {
2138
+ var inputId = instance.inputId || instance.hiddenInputId;
2139
+
2140
+ if (!$lib.isEmpty(inputId))
2141
+ this.getSourceElement(inputId); // this is a precaution to re-cache the input when the id could potentially have changed
2142
+
2143
+ if (removeElement)
2144
+ {
2145
+ instance.element = null;
2146
+
2147
+ if (!this.showing && element.parentNode)
2148
+ $lib.remove(element);
2149
+ else if (this.showing && element.parentNode)
2150
+ $lib.remove(element);
2151
+
2152
+ delete _initComponent[instance.id];
2153
+ }
2154
+ else
2155
+ element.innerHTML = '';
2156
+ }
2157
+ }
2158
+
2159
+ $lib.each(instance.__createdTemplates, function (t) { clearTimeout(t.timerId); });
2160
+
2161
+ instance.renderState = $base.static.RenderState.NONE;
2162
+ instance.element = null;
2163
+ instance.__createdTemplates = {};
2164
+ instance.__ajax = {};
2165
+ instance.store = [];
2166
+ delete $UI.store[instance.id];
2167
+ $UI.elementStore.delete(element);
2168
+
2169
+ if (this.attributeObserver)
2170
+ this.attributeObserver.disconnect();
2171
+
2172
+ if (this.removalObserver)
2173
+ this.removalObserver.disconnect();
2174
+ },
2175
+
2176
+ /**
2177
+ * Renders the component.
2178
+ * @param {Function} resourceHandler The callback method for registering script and css resources.
2179
+ * @param {String} defaultName The default name is used as default css class and when $UI.useCustomElementNames is enabled, as the element tag name prefixed by 'com'.
2180
+ * @param {Boolean} hasTheme A value indicating if the component has a CSS theme.
2181
+ * @param {String} tagName The tag name of the component element.
2182
+ * @param {String} customTagName The tag name of the component element when $UI.useCustomElementNames is enabled and a different tag than the default name is desired.
2183
+ * @function componyx.UI.base.methods#render
2184
+ */
2185
+ render: function (resourceHandler, defaultName, hasTheme, tagName, customTagName)
2186
+ {
2187
+ if (this.renderState == $base.static.RenderState.RENDERING || this.canRender === false)
2188
+ return;
2189
+
2190
+ this.createElement(defaultName, tagName, customTagName);
2191
+ this.initRenderPhase(resourceHandler, defaultName, hasTheme);
2192
+ },
2193
+
2194
+
2195
+ /**
2196
+ * Initializes the render phase for the component.
2197
+ * This method should not be invoked by client code directly but only from inside component code.
2198
+ * @param {Function} resourceHandler The callback method for registering script and css resources.
2199
+ * @param {String} defaultName The default name is used as default css class and when $UI.useCustomElementNames is enabled, as the element tag name prefixed by 'cui'.
2200
+ * @param {Boolean} hasTheme A value indicating if the component has a CSS theme.
2201
+ * @function componyx.UI.base.methods#initRenderPhase
2202
+ * @protected
2203
+ */
2204
+ initRenderPhase: function (resourceHandler, defaultName, hasTheme)
2205
+ {
2206
+ const instance = this;
2207
+
2208
+ // global $UI variables
2209
+ clearTimeout(_readyTimerId);
2210
+ _renderCount++;
2211
+ _isBusy = true;
2212
+
2213
+ instance.renderState = $base.static.RenderState.RENDERING;
2214
+ instance.hasTheme = hasTheme;
2215
+ $UI.elementStore.delete(instance.element);
2216
+ $UI.elementStore.set(instance.element, instance);
2217
+ this.configureElement(defaultName);
2218
+ this.setupMutationObserver();
2219
+
2220
+ if (this.invokePreRender() === true)
2221
+ {
2222
+ _renderCount--;
2223
+ return;
2224
+ }
2225
+
2226
+ this.registerResources(resourceHandler);
2227
+ },
2228
+
2229
+ /**
2230
+ * Creates the component's element and handles updates.
2231
+ * This method should not be invoked by client code directly but only from inside component code.
2232
+ * @param {String} defaultName The default name is used as default css class and when $UI.useCustomElementNames is enabled, as the element tag name prefixed by 'com'.
2233
+ * @param {String} tagName The tag name of the component element.
2234
+ * @param {String} customTagName The tag name of the component element when $UI.useCustomElementNames is enabled and a different tag than the default name is desired.
2235
+ * @function componyx.UI.base.methods#createElement
2236
+ * @protected
2237
+ */
2238
+ createElement: function (defaultName, tagName, customTagName)
2239
+ {
2240
+ if (customTagName || $UI.useCustomElementNames)
2241
+ tagName = (customTagName) ? customTagName : _customTagPrefix + defaultName;
2242
+ else
2243
+ tagName = tagName || 'div';
2244
+
2245
+ let instance = this,
2246
+ element = document.createElement(tagName),
2247
+ containerElement = instance.containerElement,
2248
+ nextSibling, oldElement = (instance.element?.isConnected) ? instance.element : $lib('#' + instance.id);
2249
+
2250
+ if (oldElement)
2251
+ {
2252
+ instance.element = oldElement;
2253
+ instance.collectTemplates();
2254
+ }
2255
+
2256
+ if (instance.renderState == $base.static.RenderState.RENDERED)
2257
+ {
2258
+ // the component has been rendered before so this is an update
2259
+ // the old element could already been removed from the page if we are dealing with a child component and the parent component's destroy was called.
2260
+ // if there is an old element, destroy it and make sure that it is recreated on the same position in the page.
2261
+
2262
+ // Restore ui-type on cached template content so child components
2263
+ // can be re-detected on next applyTemplate
2264
+ instance.templates.forEach(function (template)
2265
+ {
2266
+ if (!template.content)
2267
+ return;
2268
+
2269
+ template.content.forEach(function (node)
2270
+ {
2271
+ if (node.nodeType === Node.ELEMENT_NODE && node.__uiType)
2272
+ node.setAttribute(_dataType, node.__uiType);
2273
+ });
2274
+ });
2275
+
2276
+
2277
+ if (oldElement)
2278
+ {
2279
+ nextSibling = oldElement.nextSibling;
2280
+ containerElement = oldElement.parentNode;
2281
+ }
2282
+
2283
+ instance.destroy(true); // keep events
2284
+
2285
+ if (nextSibling)
2286
+ containerElement.insertBefore(oldElement, nextSibling);
2287
+ else if (oldElement)
2288
+ containerElement.appendChild(oldElement);
2289
+ }
2290
+
2291
+ $UI.store[instance.id] = instance;
2292
+
2293
+ if (oldElement)
2294
+ {
2295
+ if (oldElement.isConnected)
2296
+ oldElement.parentNode.replaceChild(element, oldElement);
2297
+
2298
+ if (instance.templates.size) // keep templates
2299
+ {
2300
+ instance.templates.forEach(function (template)
2301
+ {
2302
+ if (!template.content)
2303
+ element.appendChild(template.element);
2304
+ });
2305
+ }
2306
+
2307
+ if (containerElement && containerElement != element.parentNode)
2308
+ containerElement.appendChild(element);
2309
+
2310
+ $lib.each(oldElement.attributes, (attr) => { element.setAttribute(attr.name, attr.value); });
2311
+ }
2312
+ else
2313
+ {
2314
+ containerElement = containerElement || document.body;
2315
+ containerElement.appendChild(element);
2316
+ }
2317
+
2318
+ instance.element = element;
2319
+ },
2320
+
2321
+ /**
2322
+ * Configures the component's element.
2323
+ * This method should not be invoked by client code directly but only from inside component code.
2324
+ * @param {String} defaultName The default name is used as default css class and when $UI.useCustomElementNames is enabled, as the element tag name prefixed by 'com'.
2325
+ * @function componyx.UI.base.methods#configureElement
2326
+ * @protected
2327
+ */
2328
+ configureElement: function (defaultName)
2329
+ {
2330
+ let instance = this,
2331
+ element = this.element,
2332
+ beforeElement = this.beforeElement,
2333
+ defaultTheme = $base.static.ThemeOption.DEFAULT;
2334
+
2335
+ if (beforeElement && beforeElement != element.nextElementSibling && beforeElement.parentNode)
2336
+ beforeElement.parentNode.insertBefore(element, beforeElement);
2337
+
2338
+ instance.containerElement = element.parentNode;
2339
+
2340
+ element.id = instance.id;
2341
+ element.className = this.disableDefaultCssClass ? instance.cssClass ?? "" : `${defaultName} ${instance.cssClass || ""}`.trim();
2342
+ element.style.display = '';
2343
+
2344
+ if (typeof instance.style === 'string')
2345
+ $lib.setStyle(element, instance.style);
2346
+
2347
+ element.classList.add(_cssClassHidden); // component is invisible until rendered
2348
+ instance.__hidden = true;
2349
+ instance.theme = (instance.theme != null) ? instance.theme : defaultTheme || 0;
2350
+ $.addClass(element, instance.getThemeCSS());
2351
+
2352
+ // add extra class when dealing with a touch device
2353
+ if (instance.cssClassTouch && $lib.touch)
2354
+ $lib.addClass(element, instance.cssClassTouch);
2355
+
2356
+ if (!$lib.isEmpty(instance.title))
2357
+ element.title = instance.title;
2358
+
2359
+ // bind default click event
2360
+ $lib.on(element, 'click', function (e)
2361
+ {
2362
+ instance.events.onClick.fire(instance, e);
2363
+ });
2364
+ },
2365
+
2366
+ /**
2367
+ * Sets up the mutation observer to track attribute changes and DOM element removal.
2368
+ * This method should not be invoked by client code directly but only from inside component code.
2369
+ * @function componyx.UI.base.methods#setupMutationObserver
2370
+ * @protected
2371
+ */
2372
+ setupMutationObserver: function ()
2373
+ {
2374
+ const instance = this,
2375
+ doc = this.element.ownerDocument;
2376
+
2377
+ if (this.attributeObserver)
2378
+ this.attributeObserver.disconnect();
2379
+
2380
+ if (this.removalObserver)
2381
+ this.removalObserver.disconnect();
2382
+
2383
+ if (!this.observing)
2384
+ return;
2385
+
2386
+ this.attributeObserver = new MutationObserver(this.mutationCallback.bind(this));
2387
+ this.attributeObserver.observe(this.element, { attributes: true });
2388
+
2389
+ this.removalObserver = new MutationObserver((mutations) =>
2390
+ {
2391
+ for (const mutation of mutations)
2392
+ {
2393
+ for (const removedNode of mutation.removedNodes)
2394
+ {
2395
+ if (removedNode === instance.element && !(instance instanceof doc.defaultView.HTMLElement))
2396
+ {
2397
+ instance.destroy();
2398
+ return;
2399
+ }
2400
+ }
2401
+ }
2402
+ });
2403
+
2404
+ this.removalObserver.observe(this.element.parentElement, { childList: true });
2405
+ },
2406
+
2407
+ /**
2408
+ * Invokes pre-render actions and fires the `onPreRender` event.
2409
+ * This method should not be invoked by client code directly but only from inside component code.
2410
+ * @function componyx.UI.base.methods#invokePreRender
2411
+ * @returns {boolean} A value indicating if the render should be canceled.
2412
+ * @protected
2413
+ */
2414
+ invokePreRender: function ()
2415
+ {
2416
+ let instance = this;
2417
+ $UI.onPreRender.fire(instance);
2418
+
2419
+ const args = { cancel: false };
2420
+
2421
+ instance.events.onPreRender.fire(instance, args);
2422
+ return args.cancel;
2423
+ },
2424
+
2425
+ /**
2426
+ * Registers required resources (CSS and JS) for the component.
2427
+ * This method should not be invoked by client code directly but only from inside component code.
2428
+ * @param {Function} resourceHandler The callback method for fetching resources.
2429
+ * @param {Function} callback The callback to call after resources are loaded.
2430
+ * @function componyx.UI.base.methods#registerResources
2431
+ * @protected
2432
+ */
2433
+ registerResources: function (resourceHandler, callback)
2434
+ {
2435
+ const instance = this,
2436
+ theme = instance.theme,
2437
+ resources = resourceHandler ? resourceHandler.call(instance) : [],
2438
+ componentName = resources[0],
2439
+ script = $UI.onDemandResourceLoading ? (resources[1] || []).filter((name) => !$UI[name]) : [], // copy, never mutate the caller's array
2440
+ css = [];
2441
+
2442
+ let scriptsLoaded = 0,
2443
+ cssLoaded = 0;
2444
+
2445
+ const handler = function ()
2446
+ {
2447
+ if (scriptsLoaded < script.length || cssLoaded < css.length)
2448
+ return;
2449
+
2450
+ if (instance.renderState != $base.static.RenderState.RENDERING)
2451
+ return; // destroyed while loading resources (destroy already corrected _renderCount)
2452
+
2453
+ if (instance.element && instance.element !== $UI.store[instance.id]?.element) // re-rendered while loading resources
2454
+ {
2455
+ --_renderCount;
2456
+ return;
2457
+ }
2458
+
2459
+ if (callback)
2460
+ callback();
2461
+ else if (instance.render)
2462
+ instance.render(); // finished including script and css, return to the component's render method
2463
+ };
2464
+
2465
+ if ($UI.onDemandResourceLoading && theme > $base.static.ThemeOption.NONE)
2466
+ {
2467
+ let src = $UI.getCssResourcePath(componentName);
2468
+
2469
+ if (src)
2470
+ css.push({ src: src, id: _namespace + componentName + '.css' });
2471
+
2472
+ if (theme != $base.static.ThemeOption.BASE && instance.hasTheme !== false)
2473
+ {
2474
+ const themeName = $base.static.ThemeOption.getName(theme);
2475
+ src = $UI.getCssResourcePath(componentName, theme);
2476
+
2477
+ if (src)
2478
+ css.push({ src: src, id: $lib.format('{0}{1}.{2}.css', _namespace, componentName, themeName) });
2479
+ }
2480
+ }
2481
+
2482
+ if (!script.length && !css.length)
2483
+ {
2484
+ handler(); // nothing to load
2485
+ return;
2486
+ }
2487
+
2488
+ $lib.each(script, function (name)
2489
+ {
2490
+ const src = $UI.getScriptResourcePath(name);
2491
+ $lib.addScriptSource(src, _namespace + name + '.js', { onComplete: function () { scriptsLoaded++; handler(); } }, null, null, $UI.resourcesBefore);
2492
+ });
2493
+
2494
+ $lib.each(css, function (item)
2495
+ {
2496
+ $lib.addCssSource(item.src, item.id, { onComplete: function () { cssLoaded++; handler(); } }, $UI.resourcesBefore);
2497
+ });
2498
+ },
2499
+
2500
+ /**
2501
+ * Returns the CSS class string for the component's theme.
2502
+ * This method should not be invoked by client code directly but only from inside component code.
2503
+ * @function componyx.UI.base.methods#getThemeCSS
2504
+ * @protected
2505
+ */
2506
+ getThemeCSS: function ()
2507
+ {
2508
+ var cssClass = [], theme = this.theme, themeName = this.themeName;
2509
+
2510
+ if (theme > $base.static.ThemeOption.NONE)
2511
+ {
2512
+ cssClass.push('theme');
2513
+
2514
+ if (theme >= $base.static.ThemeOption.DEFAULT) // the class name is always set, even when hasTheme is false. The component can still pick-up styles from the global default css
2515
+ cssClass.push('theme-' + $base.static.ThemeOption.getName(theme));
2516
+ }
2517
+
2518
+ if (themeName)
2519
+ cssClass.push(themeName); // custom theme name
2520
+
2521
+ return (cssClass.length) ? cssClass.join(' ') : '';
2522
+ },
2523
+
2524
+ /**
2525
+ * Moves the component to a new position within the DOM.
2526
+ * @param {HTMLElement} container The new container element.
2527
+ * @param {HTMLElement} [before] The new before element.
2528
+ * @function componyx.UI.base.methods#move
2529
+ */
2530
+ move: function (container, before)
2531
+ {
2532
+ var element = this.element;
2533
+ container.insertBefore(element, before);
2534
+ this.containerElement = container;
2535
+ },
2536
+
2537
+ /**
2538
+ * Calls the render method on all child components. This method should not be invoked by client code directly but only from inside component code.
2539
+ * @function componyx.UI.base.methods#renderChildren
2540
+ * @protected
2541
+ */
2542
+ renderChildren: function ()
2543
+ {
2544
+ var index = -1, ready = true, obj;
2545
+
2546
+ while (++index < this.store.length)
2547
+ {
2548
+ if (obj = $UI.store[this.store[index]])
2549
+ {
2550
+ if (obj.renderState != $base.static.RenderState.RENDERED)
2551
+ ready = false;
2552
+
2553
+ if (!obj.renderState)
2554
+ obj.render();
2555
+ }
2556
+ else
2557
+ ready = false;
2558
+ }
2559
+
2560
+ if (ready)
2561
+ this.postRender();
2562
+ },
2563
+
2564
+ /**
2565
+ * Checks if all child components have been rendered and calls the component's postRender method when this is the case. This method should not be invoked by client code directly but only from inside component code.
2566
+ * This method is used as a postRender callback for child components.
2567
+ * @function componyx.UI.base.methods#childReady
2568
+ * @protected
2569
+ */
2570
+ isReady: function ()
2571
+ {
2572
+ var index = -1, ready = this.allowPostRender,
2573
+ state = $base.static.RenderState.RENDERED;
2574
+
2575
+ while (ready && ++index < this.store.length)
2576
+ {
2577
+ ready = ($UI.store[this.store[index]] && $UI.store[this.store[index]].renderState == state);
2578
+ }
2579
+
2580
+ if (ready)
2581
+ this.postRender();
2582
+ },
2583
+
2584
+ /**
2585
+ * Executes the post render procedure. This method should not be invoked by client code directly but only from inside component code.
2586
+ * @function componyx.UI.base.methods#postRender
2587
+ * @protected
2588
+ */
2589
+ postRender: function ()
2590
+ {
2591
+ var instance = this;
2592
+
2593
+ if (instance.renderState != $base.static.RenderState.RENDERING) // extra safety to never execute a postRender when the component state is incorrect
2594
+ return;
2595
+
2596
+ instance.renderState = $base.static.RenderState.RENDERED;
2597
+ instance.allowPostRender = true;
2598
+
2599
+ if (instance.showing)
2600
+ instance.show();
2601
+
2602
+ _renderCount--;
2603
+ instance.events.onPostRender.fire(instance);
2604
+
2605
+ if (instance.renderState != $base.static.RenderState.RENDERED)
2606
+ return false;
2607
+
2608
+ if (_renderCount == 0)
2609
+ {
2610
+ clearTimeout(_readyTimerId);
2611
+
2612
+ var ready = function (instance)
2613
+ {
2614
+ if (instance.renderState == $base.static.RenderState.RENDERED) // destroy could have happened because of call via setTimeout
2615
+ $UI.onPostRender.fire();
2616
+ };
2617
+
2618
+ _readyTimerId = setTimeout(ready.bind(this, instance), 0); // put as last call on the stack
2619
+ }
2620
+
2621
+ return true;
2622
+ },
2623
+
2624
+ /**
2625
+ * Adds (or removes) a template.
2626
+ * Templates for data-items provide support for property interpolations.
2627
+ * These interpolations are curly bracket formatted markers which are replaced with item property values during template rendering.
2628
+ * Example: {text} will output the value of item.text, while {customProperty} will output the value of item.attributes.customProperty.
2629
+ * @param {String} id Id of the template.
2630
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content HTML string or element node array or element node. Pass null or empty string to remove the existing template.
2631
+ * @param {Boolean} cloneable Defines if the template can be cloned for multiple views. Defaults to true.
2632
+ * @param {Boolean} interpolate Defines if a search and replace action on the inner HTML of the template will be performed to set the corresponding value for a interpolation tag (e.g. {value}).
2633
+ * @function componyx.UI.base.methods#addTemplate
2634
+ */
2635
+ addTemplate: function (id, content, cloneable, interpolate)
2636
+ {
2637
+ var template, instance = this,
2638
+ templates = this.templates;
2639
+
2640
+ if ($lib.isEmpty(content))
2641
+ {
2642
+ if (templates.has(id))
2643
+ templates.delete(id);
2644
+ else
2645
+ {
2646
+ $lib(function (el)
2647
+ {
2648
+ if (_hasAttribute(el, _dataTemplate) && _getAttribute(el, _dataTemplate) === id)
2649
+ {
2650
+ template = el;
2651
+ return false;
2652
+ }
2653
+ }, instance.element, 'div');
2654
+
2655
+ $lib.remove(template);
2656
+ }
2657
+
2658
+ return;
2659
+ }
2660
+
2661
+ template = document.createElement('div');
2662
+ template.setAttribute(_dataTemplate, id);
2663
+ template.setAttribute(_dataCloneable, (cloneable != undefined) ? cloneable : true);
2664
+ template.setAttribute(_dataInterpolate, (interpolate != undefined) ? interpolate : true);
2665
+
2666
+ if ($lib.isArray(content))
2667
+ {
2668
+ for (var index = 0; index < content.length; ++index)
2669
+ {
2670
+ template.appendChild(content[index]);
2671
+ }
2672
+ }
2673
+ else if (typeof content == 'string')
2674
+ template.innerHTML = content;
2675
+ else // HTMLElement or DocumentFragment
2676
+ template.appendChild(content);
2677
+
2678
+ instance.collectTemplate(template);
2679
+ },
2680
+
2681
+ /**
2682
+ * Checks if the template with the specified template id exists.
2683
+ * @param {String} templateId The id of the template.
2684
+ * @returns {Boolean} True if the template is present, otherwise false.
2685
+ * @function componyx.UI.base.methods#hasTemplate
2686
+ */
2687
+ hasTemplate: function (templateId)
2688
+ {
2689
+ var instance = this, templates = this.templates;
2690
+
2691
+ if (!templates.has(templateId))
2692
+ instance.collectTemplates();
2693
+
2694
+ return (templates.has(templateId));
2695
+ },
2696
+
2697
+ /**
2698
+ * Gets all the template HTMLElements for the component and stores them in the component's internal template cache. This method should not be invoked by client code directly but only from inside component code.
2699
+ * @param {String} templateId The id of the template.
2700
+ * @function componyx.UI.base.methods#collectTemplates
2701
+ * @protected
2702
+ * @returns {HTMLElement[]} A list of collected template elements.
2703
+ */
2704
+ collectTemplates: function ()
2705
+ {
2706
+ var instance = this, templates = [],
2707
+ elements = $lib.children(instance.element);
2708
+
2709
+ $lib.each(elements, function (el)
2710
+ {
2711
+ if (_hasAttribute(el, _dataTemplate))
2712
+ templates.push(el);
2713
+ });
2714
+
2715
+ $lib.each(templates, function (el)
2716
+ {
2717
+ instance.collectTemplate(el);
2718
+ });
2719
+
2720
+ return templates;
2721
+ },
2722
+
2723
+ /**
2724
+ * Gets the template HTMLElement and stores it in the component's internal template cache. This method should not be invoked by client code directly but only from inside component code.
2725
+ * @param {HTMLElement} element The template element.
2726
+ * @function componyx.UI.base.methods#collectTemplate
2727
+ * @protected
2728
+ */
2729
+ collectTemplate: function (element)
2730
+ {
2731
+ if (!element)
2732
+ return null;
2733
+
2734
+ var templateId = _getAttribute(element, _dataTemplate),
2735
+ cloneable = _getAttribute(element, _dataCloneable),
2736
+ interpolate = _getAttribute(element, _dataInterpolate);
2737
+
2738
+ if ($lib.isEmpty(templateId)) // auto-generate a stable ID if none was specified
2739
+ {
2740
+ templateId = $lib.guid();
2741
+ element.setAttribute(_dataTemplate, templateId);
2742
+ }
2743
+
2744
+ this.templates.set(templateId, { element: element, content: null, cloneable: ($lib.isEmpty(cloneable) || cloneable === 'true') ? true : false, interpolate: ($lib.isEmpty(interpolate) || interpolate === 'true') ? true : false });
2745
+ element.classList.add(_cssClassHidden);
2746
+
2747
+ return element;
2748
+ },
2749
+
2750
+ /**
2751
+ * Gets the template content for the specified template id. This method should not be invoked by client code directly but only from inside component code.
2752
+ * @returns {DocumentFragment} The template content.
2753
+ * @function componyx.UI.base.methods#getTemplateContent
2754
+ * @protected
2755
+ */
2756
+ getTemplateContent: function (templateId)
2757
+ {
2758
+ let instance = this, element, template,
2759
+ templates = this.templates,
2760
+ templateProps = new Set(['template', 'contentfrom', 'cloneable', 'interpolate']),
2761
+ hasMeaningfulAttributes = (el) =>
2762
+ {
2763
+ return Array.from(el.attributes).some(attr =>
2764
+ {
2765
+ let name = attr.name.toLowerCase();
2766
+
2767
+ // ignore presentation
2768
+ if (name === 'class' || name === 'style')
2769
+ return false;
2770
+
2771
+ if (name.startsWith('data-ui-'))
2772
+ name = name.slice(5);
2773
+
2774
+ if (name.startsWith('ui-'))
2775
+ {
2776
+ let prop = name.slice(3);
2777
+ return !templateProps.has(prop);
2778
+ }
2779
+
2780
+ return true;
2781
+ });
2782
+ },
2783
+ keepRootElement = false;
2784
+
2785
+ if (!templates.has(templateId))
2786
+ instance.collectTemplates();
2787
+
2788
+ if (templates.has(templateId))
2789
+ {
2790
+ template = templates.get(templateId);
2791
+
2792
+ if (!template.content)
2793
+ {
2794
+ element = template.element;
2795
+ var contentFrom = _getAttribute(element, _dataContentFrom);
2796
+
2797
+ if (contentFrom)
2798
+ contentFrom = $lib('#' + contentFrom);
2799
+ else
2800
+ keepRootElement = element.nodeName !== 'TEMPLATE' && hasMeaningfulAttributes(element);
2801
+
2802
+ if (keepRootElement)
2803
+ template.content = [element];
2804
+ else
2805
+ template.content = $lib.extract(contentFrom || element, true); // extract to array
2806
+
2807
+ if (contentFrom)
2808
+ $lib.remove(contentFrom);
2809
+
2810
+ $lib.remove(element); // remove from original position
2811
+ }
2812
+
2813
+ return template.content;
2814
+ }
2815
+ else
2816
+ return null;
2817
+ },
2818
+
2819
+ /**
2820
+ * Applies the template content. This method should not be invoked by client code directly but only from inside component code.
2821
+ * @param {HTMLElement} element The element to which the template content will be added.
2822
+ * @param {String} templateId The id of the template.
2823
+ * @param {String[]} values Template replacement values.
2824
+ * @param {Function} matchHandler Callback method for the template replacement values.
2825
+ * @function componyx.UI.base.methods#applyTemplate
2826
+ * @protected
2827
+ */
2828
+ applyTemplate: function (element, templateId, values, matchHandler)
2829
+ {
2830
+ let content = this.getTemplateContent(templateId);
2831
+
2832
+ if (!content)
2833
+ return;
2834
+
2835
+ let instance = this,
2836
+ templates = instance.templates,
2837
+ isArray = $lib.isArray(values),
2838
+ template = templates.get(templateId),
2839
+ cloneable = template.cloneable,
2840
+ interpolate = template.interpolate,
2841
+ templateEl = template.element,
2842
+ isTemplate = templateEl.nodeName == 'TEMPLATE',
2843
+ keepRootElement = (content.length == 1 && content[0] === templateEl);
2844
+
2845
+ if (keepRootElement) // template root element must be kept
2846
+ {
2847
+ if (cloneable)
2848
+ templateEl = templateEl.cloneNode(true);
2849
+
2850
+ if (templateEl.classList.contains(_cssClassHidden))
2851
+ templateEl.classList.remove(_cssClassHidden);
2852
+ }
2853
+ else
2854
+ {
2855
+ templateEl.innerHTML = '';
2856
+ $lib.each(content, function (node)
2857
+ {
2858
+ node = (cloneable) ? node.cloneNode(true) : node;
2859
+ if (isTemplate)
2860
+ templateEl.content.appendChild(node);
2861
+ else
2862
+ templateEl.appendChild(node);
2863
+ });
2864
+ }
2865
+
2866
+ values = values || {};
2867
+ instance.events.onPreApplyTemplate.fire(instance, { id: templateId, template: templateEl, element: element, values: values, matchHandler: matchHandler });
2868
+
2869
+ if (interpolate && !$lib.isEmpty(values))
2870
+ templateEl = parseTemplate(templateEl);
2871
+
2872
+ if (element.nodeName.toLowerCase() == 'input')
2873
+ element.value = templateEl.textContent;
2874
+ else
2875
+ {
2876
+ if (keepRootElement)
2877
+ {
2878
+ element.appendChild(templateEl);
2879
+ }
2880
+ else
2881
+ {
2882
+ content = (isTemplate) ? templateEl.content : templateEl;
2883
+
2884
+ while (content.firstChild)
2885
+ {
2886
+ element.appendChild(content.firstChild);
2887
+ }
2888
+ }
2889
+
2890
+ let createdTemplate = instance.__createdTemplates[templateId];
2891
+
2892
+ if (!createdTemplate)
2893
+ createdTemplate = instance.__createdTemplates[templateId] = { containers: [], timerId: null };
2894
+
2895
+ if (element && !createdTemplate.containers.includes(element))
2896
+ createdTemplate.containers.push(element);
2897
+
2898
+ clearTimeout(createdTemplate.timerId);
2899
+ createdTemplate.timerId = setTimeout(_detect.bind(instance, templateId));
2900
+ }
2901
+
2902
+ instance.events.onPostApplyTemplate.fire(instance, { id: templateId, template: templateEl, element: element, values: values, matchHandler: matchHandler });
2903
+
2904
+ function parseTemplate(template)
2905
+ {
2906
+ let regex = /\{+([^\{\}\s]+)\}+/gm,
2907
+ nodes = [],
2908
+ attrNodes = [],
2909
+ node,
2910
+ replacementNodes = {},
2911
+ walker = template.ownerDocument.createTreeWalker(template, NodeFilter.SHOW_TEXT, null, false);
2912
+
2913
+ while (node = walker.nextNode())
2914
+ {
2915
+ nodes.push(node); // Store each text node
2916
+ }
2917
+
2918
+ walker = template.ownerDocument.createTreeWalker(template, NodeFilter.SHOW_ELEMENT, null, false);
2919
+ while (node = walker.nextNode())
2920
+ {
2921
+ for (let attr of node.attributes)
2922
+ {
2923
+ if (regex.test(attr.value))
2924
+ {
2925
+ attrNodes.push({ element: node, name: attr.name, value: attr.value }); // Store each attribute
2926
+ }
2927
+ }
2928
+ }
2929
+
2930
+ $lib.each(nodes, function (node)
2931
+ {
2932
+ if (node.nodeValue.match(regex))
2933
+ {
2934
+ let tempElement = template.ownerDocument.createElement('div'); // use temporary element node because the replacement value might include html
2935
+ tempElement.innerHTML = node.nodeValue.replace(regex, replace); // replace the {name}, {name/} and {/name} occurences for the corresponding values
2936
+
2937
+ for (let guid in replacementNodes)
2938
+ {
2939
+ const el = tempElement.querySelector('#' + guid);
2940
+ if (el) el.replaceWith(replacementNodes[guid]);
2941
+ }
2942
+
2943
+ node.parentElement.replaceChild(tempElement, node);
2944
+ $.unsurround(tempElement); // remove the temporary element
2945
+ }
2946
+ });
2947
+
2948
+ $lib.each(attrNodes, function (attr)
2949
+ {
2950
+ attr.element.setAttribute(attr.name, attr.value.replace(regex, replace));
2951
+ });
2952
+
2953
+ function replace(match)
2954
+ {
2955
+ var value = match.toString(),
2956
+ left = (value.match(/\{/g) || []).length,
2957
+ right = (value.match(/\}/g) || []).length;
2958
+
2959
+ if ((left >= 1 && left % 2 != 0) && (right >= 1 && right % 2 != 0))
2960
+ value = value.replace(/\{([^\{\}\s]+)\}/, replace);
2961
+
2962
+ return value.replace('{{', '{').replace('}}', '}'); // escape braces {{ = {
2963
+
2964
+ function replace(match)
2965
+ {
2966
+ var value = '',
2967
+ found = false,
2968
+ index = 0,
2969
+ key = match.substring(1, match.length - 1);
2970
+
2971
+ if (values && isArray)
2972
+ {
2973
+ while (index < values.length && !found)
2974
+ {
2975
+ value = lookup(values[index]);
2976
+ index++;
2977
+ }
2978
+ }
2979
+ else if (values)
2980
+ value = lookup(values);
2981
+
2982
+ if (matchHandler)
2983
+ value = matchHandler(match, value);
2984
+
2985
+ if (typeof value === 'string' || typeof value === 'number')
2986
+ {
2987
+ return value;
2988
+ }
2989
+ else if (value && value instanceof $lib.defaultView(template).Node)
2990
+ {
2991
+ let guid = $lib.guid();
2992
+ replacementNodes[guid] = value;
2993
+ return `<span id="${guid}"></span>`;
2994
+ }
2995
+
2996
+ return value;
2997
+
2998
+ function lookup(keyValuePair)
2999
+ {
3000
+ found = true;
3001
+
3002
+ if (keyValuePair[key] != undefined)
3003
+ return keyValuePair[key];
3004
+ else if (keyValuePair.attributes && keyValuePair.attributes[key])
3005
+ return keyValuePair.attributes[key];
3006
+
3007
+ found = false;
3008
+ return '';
3009
+ }
3010
+ }
3011
+ }
3012
+
3013
+ return template;
3014
+ }
3015
+ },
3016
+
3017
+ /**
3018
+ * Executes a form post-back. This method should not be invoked by client code directly but only from inside component code.
3019
+ * @param {String} arg The postback argument.
3020
+ * @param {String} formId The id of the form.
3021
+ * @function componyx.UI.base.methods#postBack
3022
+ * @protected
3023
+ */
3024
+ postBack: function (arg, formId)
3025
+ {
3026
+ var instance = this, frm = null, result = null, result = null;
3027
+
3028
+ if (formId)
3029
+ {
3030
+ frm = $lib('#' + formId);
3031
+ }
3032
+ else
3033
+ {
3034
+ result = $lib(null, instance.element, 'form', true, true);
3035
+
3036
+ if (result == null)
3037
+ frm = document.getElementsByTagName('form')[0];
3038
+ else
3039
+ frm = result;
3040
+ }
3041
+
3042
+ if (!frm)
3043
+ return false;
3044
+
3045
+ instance.setPostBackArgs(frm, arg);
3046
+ frm.submit();
3047
+ },
3048
+
3049
+ /**
3050
+ * Sets the postback arguments. This method should not be invoked by client code directly but only from inside component code.
3051
+ * @param {HTMLElement} frm The form element.
3052
+ * @param {String} arg The postback argument.
3053
+ * @function componyx.UI.base.methods#setPostBackArgs
3054
+ * @function
3055
+ * @protected
3056
+ */
3057
+ setPostBackArgs: function (frm, arg)
3058
+ {
3059
+ var instance = this;
3060
+ var hidField;
3061
+
3062
+ if (!frm.__EVENTTARGET)
3063
+ {
3064
+ hidField = document.createElement('input');
3065
+ hidField.type = 'hidden';
3066
+ hidField.id = hidField.name = '__EVENTTARGET';
3067
+ frm.appendChild(hidField);
3068
+ }
3069
+
3070
+ if (!frm.__EVENTARGUMENT)
3071
+ {
3072
+ hidField = document.createElement('input');
3073
+ hidField.type = 'hidden';
3074
+ hidField.id = hidField.name = '__EVENTARGUMENT';
3075
+ frm.appendChild(hidField);
3076
+ }
3077
+
3078
+ frm.__EVENTTARGET.value = instance.name;
3079
+ frm.__EVENTARGUMENT.value = arg;
3080
+ },
3081
+
3082
+ /**
3083
+ * Clones the source component. This method should not be invoked by client code directly but only from inside component code.
3084
+ * @param {componyx.UI.base.Component} source The source component.
3085
+ * @param {componyx.UI.base.Component} parent The parent component.
3086
+ * @param {Boolean} [cloneTemplates=true] A value indicating if templates are cloned.
3087
+ * @param {Boolean} [cloneEvents=true] A value indicating if events are cloned.
3088
+ * @param {Object} [omitKeys=null] An object with keys to omit from cloning.
3089
+ * @function componyx.UI.base.methods#clone
3090
+ * @protected
3091
+ */
3092
+ clone: function (source, parent, cloneTemplates, cloneEvents, omitKeys)
3093
+ {
3094
+ let target = this,
3095
+ id = target.id,
3096
+ container = target.containerElement;
3097
+
3098
+ // set theme and render state
3099
+ target.theme = parent.theme;
3100
+ target.themeName = parent.themeName;
3101
+ target.renderState = $base.static.RenderState.NONE;
3102
+
3103
+ // add to internal storage
3104
+ parent.store.push(id);
3105
+
3106
+ if (!source)
3107
+ return;
3108
+
3109
+ if (!omitKeys)
3110
+ omitKeys = {};
3111
+
3112
+ source.collectTemplates(); // make sure that templates are collected
3113
+ let omit = $lib.clone({ element: '', events: '', templates: '', store: '', attributeObserver: '', removalObserver: '', observing: '' }, omitKeys);
3114
+ $lib.clone(target, source, true, true, true, true, true, null, omit);
3115
+
3116
+ // clone events and templates separately
3117
+ if (cloneEvents != false)
3118
+ target.cloneEvents(source.events);
3119
+
3120
+ if (cloneTemplates != false)
3121
+ target.templates = new Map(source.templates);
3122
+
3123
+ target.id = id;
3124
+ target.containerElement = container;
3125
+ target.theme = parent.theme;
3126
+ target.themeName = parent.themeName;
3127
+ target.renderState = $base.static.RenderState.NONE;
3128
+ },
3129
+
3130
+ /**
3131
+ * Clones component properties. This method should not be invoked by client code directly but only from inside component code.
3132
+ * @param {Object} properties The properties to clone.
3133
+ * @function componyx.UI.base.methods#cloneProperties
3134
+ * @protected
3135
+ */
3136
+ cloneProperties: function (properties)
3137
+ {
3138
+ const instance = this;
3139
+ let changed = false;
3140
+
3141
+ if (properties.events)
3142
+ {
3143
+ instance.cloneEvents(properties.events);
3144
+ changed = true;
3145
+ }
3146
+ delete properties.events;
3147
+
3148
+ for (const key in properties)
3149
+ {
3150
+ const value = properties[key];
3151
+ const prev = instance[key];
3152
+
3153
+ if (value === null || typeof value !== 'object')
3154
+ instance[key] = value; // primitive or null assigned directly
3155
+ else if (Array.isArray(value) || $lib.isPlainObject(value, true))
3156
+ {
3157
+ const report = { changed: false };
3158
+ instance[key] = $lib.clone({
3159
+ target: Array.isArray(value) ? instance[key] || [] : instance[key] || {},
3160
+ source: value,
3161
+ deep: true,
3162
+ overwrite: true,
3163
+ extend: true,
3164
+ excludeEmpty: 2,
3165
+ excludeFunctions: false,
3166
+ report: report
3167
+ }); // deep clone arrays/objects
3168
+
3169
+ if (!changed && report.changed)
3170
+ changed = true;
3171
+
3172
+ continue; // report already tells us definitively whether this key changed, skip the reference check below
3173
+ }
3174
+ else
3175
+ instance[key] = value; // assign class instances, natives, etc by reference
3176
+
3177
+ if (!changed && instance[key] !== prev)
3178
+ changed = true;
3179
+ }
3180
+
3181
+ return changed;
3182
+ },
3183
+
3184
+ /**
3185
+ * Clones component events. This method should not be invoked by client code directly but only from inside component code.
3186
+ * @param {componyx.UI.base.Events} source The source events.
3187
+ * @function componyx.UI.base.methods#cloneEvents
3188
+ * @protected
3189
+ */
3190
+ cloneEvents: function (source)
3191
+ {
3192
+ let target = this.events,
3193
+ src,
3194
+ copyHandlers = function (handlers, target, priority)
3195
+ {
3196
+ if (!handlers)
3197
+ return;
3198
+
3199
+ handlers.forEach(function (handler, guid)
3200
+ {
3201
+ $lib.each(handler, function (h)
3202
+ {
3203
+ if (priority)
3204
+ target.priorityAdd(h.handler, h.args, h.fireOnce);
3205
+ else
3206
+ target.add(h.handler, h.args, h.fireOnce);
3207
+ });
3208
+ });
3209
+ };
3210
+
3211
+ for (let name in source)
3212
+ {
3213
+ if (!target[name])
3214
+ target[name] = $base.static.createEvent(name);
3215
+
3216
+ if (target[name].add)
3217
+ {
3218
+ src = source[name];
3219
+
3220
+ if (typeof (src) === "string") // html registered events
3221
+ {
3222
+ src = _getMethod(src);
3223
+
3224
+ if (src)
3225
+ {
3226
+ src.__guid = source[name]; // deterministic guid, tied to the raw expression
3227
+
3228
+ if (!target[name].has(src)) // check if the handler already exists
3229
+ target[name].add(src);
3230
+ }
3231
+ }
3232
+ else if (typeof (src) === "function") // instance initialization with function
3233
+ target[name].add(src);
3234
+ else if (src && src.isBound && src.isBound())
3235
+ {
3236
+ copyHandlers(src.priorityHandlers(), target[name], true);
3237
+ copyHandlers(src.handlers(), target[name]);
3238
+ }
3239
+ }
3240
+ }
3241
+ },
3242
+
3243
+ /**
3244
+ * Checks the focus state of the component and fires the focus event accordingly. This method should not be invoked by client code directly but only from inside component code.
3245
+ * @param {componyx.UI.base.Component} [instance] A component for which to check the focus state.
3246
+ * @function componyx.UI.base.methods#onFocus
3247
+ * @protected
3248
+ */
3249
+ onFocus: function (instance)
3250
+ {
3251
+ instance = instance || this;
3252
+
3253
+ if (instance.__focus)
3254
+ return;
3255
+
3256
+ instance.__focus = true;
3257
+ instance.events.onFocus.fire(instance);
3258
+ },
3259
+
3260
+ /**
3261
+ * Checks the blur state of the component and fires the blur event accordingly. This method should not be invoked by client code directly but only from inside component code.
3262
+ * @param {componyx.UI.base.Component} [instance] A component for which to check the blur state.
3263
+ * @function componyx.UI.base.methods#onBlur
3264
+ * @protected
3265
+ */
3266
+ onBlur: function (instance)
3267
+ {
3268
+ instance = instance || this;
3269
+
3270
+ if ($lib.event && $lib.event.relatedTarget && instance.element.contains($lib.event.relatedTarget))
3271
+ return;
3272
+
3273
+ instance.__focus = false;
3274
+ instance.events.onBlur.fire(instance);
3275
+ },
3276
+
3277
+ /**
3278
+ * Delegates focus events on the current instance to the specified instance. This method should not be invoked by client code directly but only from inside component code.
3279
+ * @param {componyx.UI.base.Component} instance The component instance to which the focus / blur events are delegated.
3280
+ * @function componyx.UI.base.methods#delegateFocusEvents
3281
+ * @protected
3282
+ */
3283
+ delegateFocusEvents: function (instance)
3284
+ {
3285
+ this.events.onFocus.priorityAdd(instance.onFocus, instance);
3286
+ this.events.onBlur.priorityAdd(instance.onBlur, instance);
3287
+ },
3288
+
3289
+ /**
3290
+ * Creates a (hidden) input field to store the input value and syncs it with the component. This method should not be invoked by client code directly but only from inside component code.
3291
+ * @param {String} sourceElementId The identifier of the source element.
3292
+ * @param {Function} onChange The handler for the event which fires when the hidden input value changes.
3293
+ * @param {bool} isHidden A value indicating if it is a hidden input.
3294
+ * @function componyx.UI.base.methods#createHiddenInput
3295
+ * @protected
3296
+ */
3297
+ createSyncedInput: function (sourceElementId, onChange, isHidden = true)
3298
+ {
3299
+ let input = document.createElement('input'),
3300
+ classInstance = this;
3301
+
3302
+ if (sourceElementId)
3303
+ {
3304
+ input = this.getSourceElement(sourceElementId);
3305
+
3306
+ if (input.disabled)
3307
+ this.disabled = true;
3308
+ if (input.readOnly)
3309
+ this.readOnly = true;
3310
+ }
3311
+
3312
+ if (isHidden)
3313
+ input.type = 'hidden';
3314
+
3315
+ // store synced value
3316
+ input.__value = input.value;
3317
+
3318
+ if (!$lib.isEmpty(this.name))
3319
+ input.setAttribute('name', this.name);
3320
+
3321
+ // Save native descriptor
3322
+ const nativeValueDesc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
3323
+
3324
+ // Override value property
3325
+ Object.defineProperty(input, "value", {
3326
+ get()
3327
+ {
3328
+ return nativeValueDesc.get.call(this); // Return native value to reflect user input
3329
+ },
3330
+ set(value)
3331
+ {
3332
+ if (this.__value === value)
3333
+ return;
3334
+
3335
+ nativeValueDesc.set.call(this, value); // Update native value so UI stays in sync
3336
+ this.__value = value; // Keep internal __value synced
3337
+
3338
+ if (onChange)
3339
+ onChange.apply(input, [classInstance]);
3340
+ },
3341
+ configurable: true
3342
+ });
3343
+
3344
+ input.__setValue = function (value)
3345
+ {
3346
+ if (value == this.__value)
3347
+ {
3348
+ return;
3349
+ }
3350
+
3351
+ // Update both native and internal value
3352
+ this.__value = value;
3353
+ nativeValueDesc.set.call(this, value);
3354
+
3355
+ if (classInstance?.__suppressNextChangeEvent)
3356
+ {
3357
+ return;
3358
+ }
3359
+
3360
+ $lib.fireEvent(this, 'change'); // Fire HTML change
3361
+ }.bind(input);
3362
+
3363
+ return this.element.appendChild(input);
3364
+ },
3365
+
3366
+ /**
3367
+ * Gets the source element. This method should not be invoked by client code directly but only from inside component code.
3368
+ * @param {String} elementId The identifier of the source element.
3369
+ * @function componyx.UI.base.methods#getSourceElement
3370
+ * @protected
3371
+ */
3372
+ getSourceElement: function (elementId)
3373
+ {
3374
+ var el = $lib('#' + elementId);
3375
+
3376
+ if (el)
3377
+ {
3378
+ if (!this.keepInputId)
3379
+ el.removeAttribute('id');
3380
+
3381
+ $UI.__sourceElements[elementId] = el;
3382
+
3383
+ let change = el.onchange;
3384
+ el.onchange = null; // we do not wan't to fire the onchange when the element is removed from DOM
3385
+ el.remove();
3386
+ el.onchange = change;
3387
+ }
3388
+ else
3389
+ el = $UI.__sourceElements[elementId];
3390
+
3391
+ if (!this.cloneInput)
3392
+ return el;
3393
+ else
3394
+ {
3395
+ var clone = el.cloneNode(true); // clone attributes
3396
+ return (this.keepInputEventHandlers) ? $lib.copyEvents(clone, el) : clone;
3397
+ }
3398
+ }
3399
+ };
3400
+
3401
+ $lib.ready(function ()
3402
+ {
3403
+ if ($UI.autoDetect !== false)
3404
+ $UI.detect.bind(window, null)(); // register component detection on DOM ready event
3405
+ });
3406
+
3407
+ })(window);