@componyx/ui 0.0.0-stage → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AppSkin/AppSkin.css +1268 -0
- package/AppSkin/AppSkin.js +2748 -0
- package/AppSkin/Themes/Default.css +241 -0
- package/Base/Base.js +3407 -0
- package/Base/ComponentIcons.css +66 -0
- package/Base/Font/ComponentIcons.woff +0 -0
- package/Base/Font/UI.woff +0 -0
- package/Base/Library.js +8432 -0
- package/Base/NavigationManager.js +462 -0
- package/Base/ResponsiveTemplates.js +371 -0
- package/Base/Sanitizer.js +288 -0
- package/Base/Themes/Default.css +255 -0
- package/Base/UI.css +618 -0
- package/Base/UIResourcePath.js +6 -0
- package/Bindary/Bindary.js +2011 -0
- package/Bindary/Modules/Connection.js +216 -0
- package/Bindary/Modules/Core.js +854 -0
- package/Bindary/Modules/DataUpdater.js +351 -0
- package/Bindary/Modules/ExpressionEngine.js +347 -0
- package/Bindary/Modules/ProxyManager.js +482 -0
- package/Bindary/Modules/Renderer.js +605 -0
- package/Bindary/Modules/Router.js +291 -0
- package/Bindary/Modules/Scroller.js +89 -0
- package/Bindary/Modules/Template.js +1273 -0
- package/BookmarkSpy/BookmarkSpy.js +430 -0
- package/Box/Box.css +36 -0
- package/Box/Box.js +1685 -0
- package/Button/Button.css +470 -0
- package/Button/Button.js +1398 -0
- package/Button/Themes/Default.css +200 -0
- package/Carousel/Carousel.css +273 -0
- package/Carousel/Carousel.js +607 -0
- package/ColorButton/ColorButton.css +39 -0
- package/ColorButton/ColorButton.js +336 -0
- package/ColorPicker/ColorPicker.css +359 -0
- package/ColorPicker/ColorPicker.js +972 -0
- package/ComboBox/ComboBox.css +357 -0
- package/ComboBox/ComboBox.js +2342 -0
- package/ComboBox/Themes/Default.css +23 -0
- package/DataPager/DataPager.css +72 -0
- package/DataPager/DataPager.js +674 -0
- package/DatePicker/DatePicker.css +227 -0
- package/DatePicker/DatePicker.js +1531 -0
- package/DatePicker/Themes/Default.css +26 -0
- package/Dialog/Dialog.css +106 -0
- package/Dialog/Dialog.js +572 -0
- package/Dialog/Themes/Default.css +10 -0
- package/Editor/Data/Emoji.json +8285 -0
- package/Editor/Data/Special.json +1042 -0
- package/Editor/Editor.css +1562 -0
- package/Editor/Editor.js +4058 -0
- package/Editor/Font/Editor.woff +0 -0
- package/Editor/Modules/ComponentFactory.js +565 -0
- package/Editor/Modules/ContentManager.js +748 -0
- package/Editor/Modules/DialogManager.js +1234 -0
- package/Editor/Modules/EventManager.js +458 -0
- package/Editor/Modules/Format.js +950 -0
- package/Editor/Modules/History.js +191 -0
- package/Editor/Modules/LayoutState.js +379 -0
- package/Editor/Modules/ListManager.js +296 -0
- package/Editor/Modules/MenuManager.js +388 -0
- package/Editor/Modules/NodeManager.js +681 -0
- package/Editor/Modules/ParagraphButtons.js +122 -0
- package/Editor/Modules/Resizer.js +176 -0
- package/Editor/Modules/SelectionRange.js +564 -0
- package/Editor/Modules/SourceViewBuilder.js +212 -0
- package/Editor/Modules/SymbolPicker.js +208 -0
- package/Editor/Modules/Table.js +2327 -0
- package/Editor/Modules/TableDialog.js +757 -0
- package/Editor/Modules/Toolbar.js +375 -0
- package/Editor/Modules/TooltipMenu.js +125 -0
- package/Editor/Modules/Utility.js +131 -0
- package/Editor/Themes/Default.css +204 -0
- package/FileUpload/FileUpload.css +156 -0
- package/FileUpload/FileUpload.js +898 -0
- package/FileUpload/Themes/Default.css +17 -0
- package/Form/Form.css +1934 -0
- package/Form/Form.js +4943 -0
- package/Form/Modules/ActionManager.js +368 -0
- package/Form/Modules/BuildPanelManager.js +319 -0
- package/Form/Modules/ComponentFactory.js +468 -0
- package/Form/Modules/ComponentPanel.js +265 -0
- package/Form/Modules/ConfigPanelManager.js +685 -0
- package/Form/Modules/DataBinder.js +602 -0
- package/Form/Modules/DataObserver.js +717 -0
- package/Form/Modules/Draggable.js +756 -0
- package/Form/Modules/OptionsPanel.js +342 -0
- package/Form/Modules/Renderer.js +1889 -0
- package/Form/Modules/RuleEngine.js +585 -0
- package/Form/Modules/RulesPanel.js +1158 -0
- package/Form/Modules/Types.js +1709 -0
- package/Form/Modules/ValidationManager.js +194 -0
- package/Form/Themes/Default.css +216 -0
- package/FormField/FormField.css +468 -0
- package/FormField/FormField.js +576 -0
- package/FormField/Themes/Default.css +99 -0
- package/Grid/Grid.css +544 -0
- package/Grid/Grid.js +3761 -0
- package/Grid/Themes/Default.css +43 -0
- package/LICENSE +126 -0
- package/MaskedTextBox/MaskedTextBox.css +28 -0
- package/MaskedTextBox/MaskedTextBox.js +642 -0
- package/MaskedTextBox/Themes/Default.css +6 -0
- package/Menu/Menu.css +328 -0
- package/Menu/Menu.js +2386 -0
- package/Menu/Themes/Default.css +24 -0
- package/NumericBox/NumericBox.css +120 -0
- package/NumericBox/NumericBox.js +984 -0
- package/PanelBar/PanelBar.css +123 -0
- package/PanelBar/PanelBar.js +505 -0
- package/PanelBar/Themes/Default.css +51 -0
- package/PieChart/PieChart.css +95 -0
- package/PieChart/PieChart.js +820 -0
- package/PieChart/Themes/Default.css +9 -0
- package/README.md +178 -3
- package/Slider/Slider.css +348 -0
- package/Slider/Slider.js +1306 -0
- package/Slider/Themes/Default.css +44 -0
- package/TabStrip/TabStrip.css +52 -0
- package/TabStrip/TabStrip.js +362 -0
- package/TabStrip/Themes/Default.css +35 -0
- package/TimePicker/Themes/Default.css +30 -0
- package/TimePicker/TimePicker.css +253 -0
- package/TimePicker/TimePicker.js +1485 -0
- package/TooltipManager/Themes/Default.css +21 -0
- package/TooltipManager/TooltipManager.css +227 -0
- package/TooltipManager/TooltipManager.js +791 -0
- package/Validator/Validator.css +54 -0
- package/Validator/Validator.js +2508 -0
- package/componyx.html-data.json +619 -0
- package/index.js +27 -0
- package/package.json +43 -4
- package/types/AppSkin.d.ts +842 -0
- package/types/Base.d.ts +1034 -0
- package/types/Bindary.d.ts +1196 -0
- package/types/BookmarkSpy.d.ts +112 -0
- package/types/Box.d.ts +374 -0
- package/types/Button.d.ts +455 -0
- package/types/Carousel.d.ts +136 -0
- package/types/ColorButton.d.ts +120 -0
- package/types/ColorPicker.d.ts +225 -0
- package/types/ComboBox.d.ts +507 -0
- package/types/DataPager.d.ts +152 -0
- package/types/DatePicker.d.ts +354 -0
- package/types/Dialog.d.ts +160 -0
- package/types/Editor.d.ts +1289 -0
- package/types/FileUpload.d.ts +206 -0
- package/types/Form.d.ts +3078 -0
- package/types/FormField.d.ts +204 -0
- package/types/Grid.d.ts +911 -0
- package/types/Library.d.ts +1899 -0
- package/types/MaskedTextBox.d.ts +91 -0
- package/types/Menu.d.ts +577 -0
- package/types/NumericBox.d.ts +310 -0
- package/types/PanelBar.d.ts +223 -0
- package/types/PieChart.d.ts +224 -0
- package/types/ResponsiveTemplates.d.ts +108 -0
- package/types/Slider.d.ts +227 -0
- package/types/TabStrip.d.ts +185 -0
- package/types/TimePicker.d.ts +265 -0
- package/types/TooltipManager.d.ts +178 -0
- package/types/UI.d.ts +29 -0
- package/types/Validator.d.ts +653 -0
- package/types/index.d.ts +59 -0
- package/ui.css +11783 -0
- package/ui.js +79477 -0
- package/ui.min.css +138 -0
- package/ui.min.js +264 -0
- package/ui.min.js.map +1 -0
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);
|