@componyx/ui 0.0.0-stage → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/AppSkin/AppSkin.css +1268 -0
  2. package/AppSkin/AppSkin.js +2748 -0
  3. package/AppSkin/Themes/Default.css +241 -0
  4. package/Base/Base.js +3407 -0
  5. package/Base/ComponentIcons.css +66 -0
  6. package/Base/Font/ComponentIcons.woff +0 -0
  7. package/Base/Font/UI.woff +0 -0
  8. package/Base/Library.js +8432 -0
  9. package/Base/NavigationManager.js +462 -0
  10. package/Base/ResponsiveTemplates.js +371 -0
  11. package/Base/Sanitizer.js +288 -0
  12. package/Base/Themes/Default.css +255 -0
  13. package/Base/UI.css +618 -0
  14. package/Base/UIResourcePath.js +6 -0
  15. package/Bindary/Bindary.js +2011 -0
  16. package/Bindary/Modules/Connection.js +216 -0
  17. package/Bindary/Modules/Core.js +854 -0
  18. package/Bindary/Modules/DataUpdater.js +351 -0
  19. package/Bindary/Modules/ExpressionEngine.js +347 -0
  20. package/Bindary/Modules/ProxyManager.js +482 -0
  21. package/Bindary/Modules/Renderer.js +605 -0
  22. package/Bindary/Modules/Router.js +291 -0
  23. package/Bindary/Modules/Scroller.js +89 -0
  24. package/Bindary/Modules/Template.js +1273 -0
  25. package/BookmarkSpy/BookmarkSpy.js +430 -0
  26. package/Box/Box.css +36 -0
  27. package/Box/Box.js +1685 -0
  28. package/Button/Button.css +470 -0
  29. package/Button/Button.js +1398 -0
  30. package/Button/Themes/Default.css +200 -0
  31. package/Carousel/Carousel.css +273 -0
  32. package/Carousel/Carousel.js +607 -0
  33. package/ColorButton/ColorButton.css +39 -0
  34. package/ColorButton/ColorButton.js +336 -0
  35. package/ColorPicker/ColorPicker.css +359 -0
  36. package/ColorPicker/ColorPicker.js +972 -0
  37. package/ComboBox/ComboBox.css +357 -0
  38. package/ComboBox/ComboBox.js +2342 -0
  39. package/ComboBox/Themes/Default.css +23 -0
  40. package/DataPager/DataPager.css +72 -0
  41. package/DataPager/DataPager.js +674 -0
  42. package/DatePicker/DatePicker.css +227 -0
  43. package/DatePicker/DatePicker.js +1531 -0
  44. package/DatePicker/Themes/Default.css +26 -0
  45. package/Dialog/Dialog.css +106 -0
  46. package/Dialog/Dialog.js +572 -0
  47. package/Dialog/Themes/Default.css +10 -0
  48. package/Editor/Data/Emoji.json +8285 -0
  49. package/Editor/Data/Special.json +1042 -0
  50. package/Editor/Editor.css +1562 -0
  51. package/Editor/Editor.js +4058 -0
  52. package/Editor/Font/Editor.woff +0 -0
  53. package/Editor/Modules/ComponentFactory.js +565 -0
  54. package/Editor/Modules/ContentManager.js +748 -0
  55. package/Editor/Modules/DialogManager.js +1234 -0
  56. package/Editor/Modules/EventManager.js +458 -0
  57. package/Editor/Modules/Format.js +950 -0
  58. package/Editor/Modules/History.js +191 -0
  59. package/Editor/Modules/LayoutState.js +379 -0
  60. package/Editor/Modules/ListManager.js +296 -0
  61. package/Editor/Modules/MenuManager.js +388 -0
  62. package/Editor/Modules/NodeManager.js +681 -0
  63. package/Editor/Modules/ParagraphButtons.js +122 -0
  64. package/Editor/Modules/Resizer.js +176 -0
  65. package/Editor/Modules/SelectionRange.js +564 -0
  66. package/Editor/Modules/SourceViewBuilder.js +212 -0
  67. package/Editor/Modules/SymbolPicker.js +208 -0
  68. package/Editor/Modules/Table.js +2327 -0
  69. package/Editor/Modules/TableDialog.js +757 -0
  70. package/Editor/Modules/Toolbar.js +375 -0
  71. package/Editor/Modules/TooltipMenu.js +125 -0
  72. package/Editor/Modules/Utility.js +131 -0
  73. package/Editor/Themes/Default.css +204 -0
  74. package/FileUpload/FileUpload.css +156 -0
  75. package/FileUpload/FileUpload.js +898 -0
  76. package/FileUpload/Themes/Default.css +17 -0
  77. package/Form/Form.css +1934 -0
  78. package/Form/Form.js +4943 -0
  79. package/Form/Modules/ActionManager.js +368 -0
  80. package/Form/Modules/BuildPanelManager.js +319 -0
  81. package/Form/Modules/ComponentFactory.js +468 -0
  82. package/Form/Modules/ComponentPanel.js +265 -0
  83. package/Form/Modules/ConfigPanelManager.js +685 -0
  84. package/Form/Modules/DataObserver.js +717 -0
  85. package/Form/Modules/Databinder.js +602 -0
  86. package/Form/Modules/Draggable.js +756 -0
  87. package/Form/Modules/OptionsPanel.js +342 -0
  88. package/Form/Modules/Renderer.js +1889 -0
  89. package/Form/Modules/RuleEngine.js +585 -0
  90. package/Form/Modules/RulesPanel.js +1158 -0
  91. package/Form/Modules/Types.js +1709 -0
  92. package/Form/Modules/ValidationManager.js +194 -0
  93. package/Form/Themes/Default.css +216 -0
  94. package/FormField/FormField.css +468 -0
  95. package/FormField/FormField.js +576 -0
  96. package/FormField/Themes/Default.css +99 -0
  97. package/Grid/Grid.css +544 -0
  98. package/Grid/Grid.js +3761 -0
  99. package/Grid/Themes/Default.css +43 -0
  100. package/LICENSE +126 -0
  101. package/MaskedTextBox/MaskedTextBox.css +28 -0
  102. package/MaskedTextBox/MaskedTextBox.js +642 -0
  103. package/MaskedTextBox/Themes/Default.css +6 -0
  104. package/Menu/Menu.css +328 -0
  105. package/Menu/Menu.js +2386 -0
  106. package/Menu/Themes/Default.css +24 -0
  107. package/NumericBox/NumericBox.css +120 -0
  108. package/NumericBox/NumericBox.js +984 -0
  109. package/PanelBar/PanelBar.css +123 -0
  110. package/PanelBar/PanelBar.js +505 -0
  111. package/PanelBar/Themes/Default.css +51 -0
  112. package/PieChart/PieChart.css +95 -0
  113. package/PieChart/PieChart.js +820 -0
  114. package/PieChart/Themes/Default.css +9 -0
  115. package/README.md +178 -3
  116. package/Slider/Slider.css +348 -0
  117. package/Slider/Slider.js +1306 -0
  118. package/Slider/Themes/Default.css +44 -0
  119. package/TabStrip/TabStrip.css +52 -0
  120. package/TabStrip/TabStrip.js +362 -0
  121. package/TabStrip/Themes/Default.css +35 -0
  122. package/TimePicker/Themes/Default.css +30 -0
  123. package/TimePicker/TimePicker.css +253 -0
  124. package/TimePicker/TimePicker.js +1485 -0
  125. package/TooltipManager/Themes/Default.css +21 -0
  126. package/TooltipManager/TooltipManager.css +227 -0
  127. package/TooltipManager/TooltipManager.js +791 -0
  128. package/Validator/Validator.css +54 -0
  129. package/Validator/Validator.js +2508 -0
  130. package/componyx.html-data.json +619 -0
  131. package/index.js +27 -0
  132. package/package.json +43 -4
  133. package/types/AppSkin.d.ts +842 -0
  134. package/types/Base.d.ts +1034 -0
  135. package/types/Bindary.d.ts +1196 -0
  136. package/types/BookmarkSpy.d.ts +112 -0
  137. package/types/Box.d.ts +374 -0
  138. package/types/Button.d.ts +455 -0
  139. package/types/Carousel.d.ts +136 -0
  140. package/types/ColorButton.d.ts +120 -0
  141. package/types/ColorPicker.d.ts +225 -0
  142. package/types/ComboBox.d.ts +507 -0
  143. package/types/DataPager.d.ts +152 -0
  144. package/types/DatePicker.d.ts +354 -0
  145. package/types/Dialog.d.ts +160 -0
  146. package/types/Editor.d.ts +1289 -0
  147. package/types/FileUpload.d.ts +206 -0
  148. package/types/Form.d.ts +3078 -0
  149. package/types/FormField.d.ts +204 -0
  150. package/types/Grid.d.ts +911 -0
  151. package/types/Library.d.ts +1899 -0
  152. package/types/MaskedTextBox.d.ts +91 -0
  153. package/types/Menu.d.ts +577 -0
  154. package/types/NumericBox.d.ts +310 -0
  155. package/types/PanelBar.d.ts +223 -0
  156. package/types/PieChart.d.ts +224 -0
  157. package/types/ResponsiveTemplates.d.ts +108 -0
  158. package/types/Slider.d.ts +227 -0
  159. package/types/TabStrip.d.ts +185 -0
  160. package/types/TimePicker.d.ts +265 -0
  161. package/types/TooltipManager.d.ts +178 -0
  162. package/types/UI.d.ts +29 -0
  163. package/types/Validator.d.ts +653 -0
  164. package/types/index.d.ts +59 -0
  165. package/ui.css +11783 -0
  166. package/ui.js +79477 -0
  167. package/ui.min.css +138 -0
  168. package/ui.min.js +264 -0
  169. package/ui.min.js.map +1 -0
@@ -0,0 +1,2011 @@
1
+ /*! Componyx.UI | (c) E.H. Daanen / Componyx | BUSL-1.1 | componyx.com/license */
2
+ import '../Base/Base.js';
3
+ import '../Base/Library.js';
4
+ import '../Base/Sanitizer.js';
5
+ import './Modules/Core.js';
6
+ import './Modules/Connection.js';
7
+ import './Modules/DataUpdater.js';
8
+ import './Modules/ExpressionEngine.js';
9
+ import './Modules/ProxyManager.js';
10
+ import './Modules/Renderer.js';
11
+ import './Modules/Router.js';
12
+ import './Modules/Scroller.js';
13
+ import './Modules/Template.js';
14
+
15
+ (async function (window)
16
+ {
17
+ /**
18
+ * Namespace for bindary modules.
19
+ * @namespace componyx.UI.bindary_modules
20
+ */
21
+ componyx.bindary_modules = componyx.bindary_modules || {};
22
+
23
+ /**
24
+ * Promise that resolves when all bindary modules are loaded asynchronously.
25
+ * @type {Promise<void>}
26
+ * @memberof componyx.UI.bindary_modules
27
+ */
28
+ componyx.bindary_modules.loaded = (async () =>
29
+ {
30
+ // these dynamic imports are removed when files are bundled into UI(.min).js
31
+ })();
32
+
33
+ await componyx.bindary_modules.loaded;
34
+ const Sanitizer = componyx.base_modules.Sanitizer;
35
+ const core = componyx.bindary_modules.core;
36
+ const dataUpdater = componyx.bindary_modules.dataUpdater;
37
+ const connection = componyx.bindary_modules.connection;
38
+ const proxyManager = componyx.bindary_modules.proxyManager;
39
+ const Renderer = componyx.bindary_modules.Renderer;
40
+ const router = componyx.bindary_modules.router;
41
+ const scroller = componyx.bindary_modules.scroller;
42
+ const TemplateItem = componyx.bindary_modules.TemplateItem;
43
+ const ExpressionEngine = componyx.bindary_modules.ExpressionEngine;
44
+ core.scroller = scroller;
45
+ core.expressionEngine = new ExpressionEngine();
46
+ core.sanitizer = new Sanitizer();
47
+
48
+ /**
49
+ * Bindary HTML data-attributes.
50
+ * @typedef {Object} HTMLAttributes
51
+ * @memberof componyx.bindary
52
+ * @property {String} ["data-bindary-context"] - This attribute sets the data-context (object scope) for the element and its child-elements through the data-key.
53
+ * @property {String} ["data-bindary-value"] - This attribute sets the inner text value of the element through the data-key.
54
+ * @property {String} ["data-bindary-html"] - This attribute sets the inner HTML value of the element through the data-key. By default the HTML data is sanitized unless specified as trusted data. Use trust:data-key for own (not user defined) HTML data-sources or enable $bindary.trustHTML to trust all data provided through data-bindary-html.
55
+ * @property {String} ["data-bindary-bind"] - This attribute enables two-way data-binding for input elements (input, textarea, select) through the data-key. Two-way data binding means that, apart from the element's value being updated when the data changes (either automatically or by manual registering), the data is updated as well when the element value changes.
56
+ * @property {String} ["data-bindary-live"] - This attribute enables live binding per element. Use "false" to disable live binding, "true" or empty to enable and use the global delay ($bindary.liveBindDelay), or a number (as string) to enable and set a custom delay in ms. Active on the element and its child elements.
57
+ * @property {String} ["data-bindary-type"] - This attribute can be used in conjunction with the bind/value attribute to define the data-type of the corresponding data value when the type is not a string. The supported data-types are boolean, number and date. For number, an optional precision can be specified: number(2) overrides the global decimalPrecision for this binding.
58
+ * @property {String} ["data-bindary-attributes"] - This attribute sets element attributes that require data-binding. Wrap the data-key inside curly brackets when custom text in the HTML attribute is desired.
59
+ * @property {String} ["data-bindary-has-value"] - This attribute without value defines that the element is rendered when the current data context is NOT empty, null or undefined. A data-key value can be specified when a different value than the current context should determine if the element will be rendered.
60
+ * @property {String} ["data-bindary-repeat"] - This attribute enables collection data-binding through the data-key.
61
+ * @property {String} ["data-bindary-repeat-item-id"] - This attribute can be used in conjunction with the repeat attribute. The data-key value specifies which item property of the bound collection serves as the item-template identifier.
62
+ * @property {String} ["data-bindary-item-id"] - This attribute sets the value for matching the item property specified through repeatItemId. The element will be rendered when the property value of the collection item matches the attribute value.
63
+ * @property {String} ["data-bindary-filter"] - This attribute can be used in conjunction with the repeat attribute to bind a (controller) function to filter the collection before rendering.
64
+ * @property {String} ["data-bindary-view-formatter"] - This attribute can be used in conjunction with the value or html attribute to bind a (controller) function to format the data value when the element is rendered.
65
+ * @property {String} ["data-bindary-data-formatter"] - This attribute can be used in conjunction with the bind attribute to bind a (controller) function to format the input value when data binding occurs.
66
+ * @property {String} ["data-bindary-includable"] - This attribute marks an element as includable template-item (no attribute value required).
67
+ * @property {String} ["data-bindary-include"] - This attribute includes the contents of an element or file. Use '#element-id' or 'data.key' (dynamic element-id value) to reference an element marked with the includable attribute by id. To reference a file use 'file:path'.
68
+ * @property {String} ["data-bindary-component"] - This attribute can be used in conjunction with the include attribute (file:path) and marks the included file content as component. The attribute value must contain the name of the component. The file must include JavaScript code to register the Bindary component, through $bindary.addComponent(), providing the component name (matching the component attribute value) and an initiator function.
69
+ * @property {String} ["data-bindary-observe"] - This attribute can be used in conjunction with the component attribute. The data-key value specifies which item property of the component's context will be automatically observed for data changes, triggering view updates when changes occur.
70
+ * @property {String} ["data-bindary-load"] - This attribute can be used in conjunction with the include attribute (file:path) for binding a (controller) function to invoke when the file contents have been loaded.
71
+ * @property {String} ["data-bindary-pre-render"] - This attribute can be used to bind a (controller) function to invoke before the element is rendered. The element is not rendered if the method returns false.
72
+ * @property {String} ["data-bindary-if"] - This attribute can be used to bind a (controller) function to execute a conditional if statement. The element is not rendered if the method returns false.
73
+ * @property {String} ["data-bindary-else-if"] - This attribute can be used to bind a (controller) function to execute a conditional else if statement. The element is not rendered if the method returns false.
74
+ * @property {String} ["data-bindary-else"] - This attribute determines that the element is rendered when the conditional if or elseif statements returned false.
75
+ * @property {String} ["data-bindary-post-render"] - This attribute can be used to bind a (controller) function to invoke after the element is rendered. The element is not visible if the method returns false.
76
+ * @property {String} ["data-bindary-on"] - This attribute can be used to bind a (controller) function to any HTML event.
77
+ * @property {String} ["data-bindary-update-id"] - This attribute sets a unique identifier to use when registering data changes for a specific element.
78
+ * @property {String} ["data-bindary-keep"] - This attribute can be used to define if the data-bindary- attributes are removed (no value) or remain (value "true") on the element after it has been discovered by the framework. Active on the element and its child elements.
79
+ * @property {String} ["data-bindary-view"] - This valueless attribute marks an HTML element as root container for requested views.
80
+ * @property {String} ["data-bindary-ignore"] - This valueless attribute marks an HTML element and its child elements with data-bindary attributes to be ignored. This can be used to activate templates at a desired moment and for such purpose the intended 'include' attribute is not desired.
81
+ */
82
+
83
+ core.initializePrefix(core.shortPrefix);
84
+ core.initCache();
85
+
86
+ /**
87
+ * Bindary ($bindary) is a highly flexible data-binding framework for developing web applications. It utilizes declarative HTML templates based on data-attributes to bind the DOM- to the -JavaScript world.
88
+ * @namespace
89
+ * @memberof componyx
90
+ */
91
+ componyx.bindary = {
92
+ /**
93
+ * A value indicating if the route for the initial route path value is loaded when the DOM is ready.
94
+ * @type {Boolean}
95
+ */
96
+ launchOnLoad: true,
97
+
98
+ /**
99
+ * The connection URL of the server side data-server for retrieving app/view data. Leave this setting empty when custom data retrieval methods are desired.
100
+ * @type {String|null}
101
+ */
102
+ dataServerURL: null,
103
+
104
+ /**
105
+ * A value indicating if a relative data-server URL is used.
106
+ * @type {Boolean}
107
+ */
108
+ relativeDataServerURL: false,
109
+
110
+ /**
111
+ * The relative or absolute root URL where views are located.
112
+ * @type {String}
113
+ */
114
+ viewRootURL: '',
115
+
116
+ /**
117
+ * The relative or absolute root URL where client controllers are located.
118
+ * @type {String}
119
+ */
120
+ controllerRootURL: '',
121
+
122
+ /**
123
+ * The relative or absolute root URL where includables are located.
124
+ * @type {String}
125
+ */
126
+ includeRootURL: '',
127
+
128
+ /**
129
+ * A value indicating if the browser has support for the websocket protocol.
130
+ * @type {Boolean}
131
+ */
132
+ get websocketSupport() { return "WebSocket" in window; },
133
+
134
+ /**
135
+ * A value indicating if websockets should be used for updating view data.
136
+ * @type {Boolean}
137
+ */
138
+ useWebSocket: true,
139
+
140
+ /**
141
+ * A value indicating to use a secure connection (SSL/TLS). Defaults to true if a HTTPS connection is detected.
142
+ * @type {Boolean|null}
143
+ */
144
+ secure: null,
145
+
146
+ /**
147
+ * The interval time in milliseconds between xhr data update checks when websockets are not supported/enabled.
148
+ * @type {Number|null}
149
+ */
150
+ xhrUpdateInterval: null,
151
+
152
+ /**
153
+ * The time in milliseconds before a new attempt is made, when a previous attempt failed, to scroll to the anchor or history scroll position.
154
+ * @type {Number}
155
+ */
156
+ scrollRetryInterval: 50,
157
+
158
+ /**
159
+ * The time in milliseconds before attempting to set the correct scroll position stops.
160
+ * @type {Number}
161
+ */
162
+ scrollRetryTimeout: 2000,
163
+
164
+ /**
165
+ * A value indicating if a data-bound input element performs a live update of the corresponding data model (keyup events) or after a change (change/blur events).
166
+ * @type {Boolean}
167
+ */
168
+ liveBind: true,
169
+
170
+ /**
171
+ * The delay in milliseconds for a live update.
172
+ * @type {Number}
173
+ */
174
+ liveBindDelay: 0,
175
+
176
+ /**
177
+ * Fires when the WebSocket connection is opened.
178
+ * @type {componyx.library.Event}
179
+ * @see {@link componyx.bindary.WebSocketEventArgs}
180
+ */
181
+ onWebSocketOpen: $lib.createEvent('onWebSocketOpen'),
182
+
183
+ /**
184
+ * Fires when the WebSocket connection is closed (server can close connection also).
185
+ * @type {componyx.library.Event}
186
+ * @see {@link componyx.bindary.WebSocketEventArgs}
187
+ */
188
+ onWebSocketClose: $lib.createEvent('onWebSocketClose'),
189
+
190
+ /**
191
+ * Fires before a data-bound input element is updated. Can be used for validation.
192
+ * @type {componyx.library.Event}
193
+ * @see {@link componyx.bindary.PreDataBindEventArgs}
194
+ */
195
+ onPreDataBind: $lib.createEvent('onPreDataBind'),
196
+
197
+ /**
198
+ * Fires after data binding is applied to an input element.
199
+ * @type {componyx.library.Event}
200
+ * @see {@link componyx.bindary.PostDataBindEventArgs}
201
+ */
202
+ onPostDataBind: $lib.createEvent('onPostDataBind'),
203
+
204
+ /**
205
+ * Fires when the returned data object contains an error (`isError: true`).
206
+ * @type {componyx.library.Event}
207
+ * @see {@link componyx.bindary.ErrorEventArgs}
208
+ */
209
+ onDataError: $lib.createEvent('onDataError'),
210
+
211
+ /**
212
+ * Fires when a route cannot be loaded: no route matches the route path, or the route's view or controller fails to load.
213
+ * @type {componyx.library.Event}
214
+ * @see {@link componyx.bindary.RouteErrorEventArgs}
215
+ */
216
+ onRouteError: $lib.createEvent('onRouteError'),
217
+
218
+ /**
219
+ * Fires before loading route data and sources.
220
+ * @type {componyx.library.Event}
221
+ * @see {@link componyx.bindary.PreLoadEventArgs}
222
+ */
223
+ onPreLoad: $lib.createEvent('onPreLoad'),
224
+
225
+ /**
226
+ * Fires when the view data is requested.
227
+ * @type {componyx.library.Event}
228
+ * @see {@link componyx.bindary.DataRequestEventArgs}
229
+ */
230
+ onDataRequest: $lib.createEvent('onDataRequest'),
231
+
232
+ /**
233
+ * Fires after route data and sources are loaded.
234
+ * @type {componyx.library.Event}
235
+ * @see {@link componyx.bindary.LoadEventArgs}
236
+ */
237
+ onLoad: $lib.createEvent('onLoad'),
238
+
239
+ /**
240
+ * Fires when new data is received, before the client data is updated.
241
+ * @type {componyx.library.Event}
242
+ * @see {@link componyx.bindary.PreDataUpdateEventArgs}
243
+ */
244
+ onPreDataUpdate: $lib.createEvent('onPreDataUpdate'),
245
+
246
+ /**
247
+ * Fires after the client data has been updated.
248
+ * @type {componyx.library.Event}
249
+ * @see {@link componyx.bindary.PostDataUpdateEventArgs}
250
+ */
251
+ onPostDataUpdate: $lib.createEvent('onPostDataUpdate'),
252
+
253
+ /**
254
+ * Fires before the view is rendered.
255
+ * @type {componyx.library.Event}
256
+ */
257
+ onPreRender: $lib.createEvent('onPreRender'),
258
+
259
+ /**
260
+ * Fires after the view is rendered.
261
+ * @type {componyx.library.Event}
262
+ */
263
+ onPostRender: $lib.createEvent('onPostRender'),
264
+
265
+ /**
266
+ * Fires after the route has fully loaded.
267
+ * @type {componyx.library.Event}
268
+ * @see {@link componyx.bindary.LoadEventArgs}
269
+ */
270
+ onPostLoad: $lib.createEvent('onPostLoad'),
271
+
272
+ /**
273
+ * Fires when the document tab becomes visible or hidden.
274
+ * @type {componyx.library.Event}
275
+ * @see {@link componyx.bindary.VisibilityChangeEventArgs}
276
+ */
277
+ onVisibilityChange: $lib.createEvent('onVisibilityChange'),
278
+
279
+ /**
280
+ * Fires when the document is scrolled to an anchor.
281
+ * @type {componyx.library.Event}
282
+ * @see {@link componyx.bindary.AnchorScrollEventArgs}
283
+ */
284
+ onAnchorScroll: $lib.createEvent('onAnchorScroll'),
285
+
286
+ /**
287
+ * Fires after the route is unloaded because a new route is requested.
288
+ * @type {componyx.library.Event}
289
+ * @see {@link componyx.bindary.UnloadEventArgs}
290
+ */
291
+ onUnload: $lib.createEvent('onUnload'),
292
+
293
+ /**
294
+ * The id prefix for bindary elements within the view.
295
+ * @type {String}
296
+ */
297
+ idPrefix: 'bindary_',
298
+
299
+ /**
300
+ * The date format used to convert date(time) types for textual display. Defaults to MM/dd/yyyy hh:mm.
301
+ * @type {String}
302
+ */
303
+ dateFormat: 'MM/dd/yyyy hh:mm',
304
+
305
+ /**
306
+ * The decimal separator used to convert numeric types for textual display. Defaults to '.'.
307
+ * @type {String}
308
+ */
309
+ decimalSeparator: '.',
310
+
311
+ /**
312
+ * The decimal precision used to convert numeric types for textual display.
313
+ * @type {String|null}
314
+ */
315
+ decimalPrecision: null,
316
+
317
+ /**
318
+ * A value which is sent with each data request to be able to uniquely identify clients on the server. Only required when the client-id is not stored in a server-side Session.
319
+ * @type {String|null}
320
+ */
321
+ clientId: null,
322
+
323
+ /**
324
+ * Holds the current data request object.
325
+ * @type {dataRequestType|null}
326
+ */
327
+ dataRequest: null,
328
+
329
+ /**
330
+ * Holds the current active route.
331
+ * @type {componyx.bindary.Route|null}
332
+ */
333
+ route: null,
334
+
335
+ /**
336
+ * Holds the current active route index.
337
+ * @type {String|null}
338
+ */
339
+ routeIndex: null,
340
+
341
+ /**
342
+ * Holds the current active route path and can be set as initial launch value.
343
+ * @type {String}
344
+ */
345
+ routePath: '',
346
+
347
+ /**
348
+ * The root path of the application must be specified when "prettyURL" is enabled and the application URL contains a path after the URL origin (protocol//domain:port), so that Bindary can detect the route path within the URL.
349
+ * The rootPath must start and end with a forward slash (/).
350
+ * @type {String}
351
+ */
352
+ rootPath: '/',
353
+
354
+ /**
355
+ * The base title of the application.
356
+ * @type {String}
357
+ */
358
+ baseTitle: '',
359
+
360
+ /**
361
+ * Holds the controller object for the active route.
362
+ * @type {Object}
363
+ */
364
+ controller: {},
365
+
366
+ /**
367
+ * Holds the data object which is bound to the global application context.
368
+ * @type {Object}
369
+ */
370
+ appData: {},
371
+
372
+ /**
373
+ * Holds the data object which is bound to the context of the active route.
374
+ * @type {Object}
375
+ */
376
+ routeData: {},
377
+
378
+ /**
379
+ * Holds the data object which is bound to the context of the active view.
380
+ * @type {Object}
381
+ */
382
+ viewData: {},
383
+
384
+ /**
385
+ * The list of mapped routes.
386
+ * @type {Object[]}
387
+ */
388
+ routes: [],
389
+
390
+ /**
391
+ * A value indicating if Bindary data-bound elements (template-items) are being detected outside of the view container element.
392
+ * Set to false if template elements are only defined inside of the view container for better performance.
393
+ * @type {Boolean}
394
+ */
395
+ detectOutsideView: true,
396
+
397
+ /**
398
+ * A value indicating if the data change detection must be precise (false) or as fast as possible (true).
399
+ * When changed, it will have direct effect on the next view update. With dirty checks enabled: detection of changes during the view update works by checking if the data-key for a registered change is equal or inside an item's context.
400
+ * Items that don't match with a registered data change are skipped and possible child items are not processed. Therefore possible context switching (by root/parent navigators) inside a template item is not considered. Nor are the preRender event actions, which may decide if a template item is rendered.
401
+ * @type {Boolean}
402
+ */
403
+ dirtyChecks: false,
404
+
405
+ /**
406
+ * A value indicating if the route path in the URL starts with a forward slash (pretty) instead of a hash(#) or hash-bang(#!).
407
+ * e.g. /Category/Product instead of #Category/Product.
408
+ * When enabled, server-side routing of the route-path to the application's entrance page is required to deal with initial page loads where a route path is part of the URL (direct link).
409
+ * Bindary will detect the route path in the URL and launch with the relevant route.
410
+ * @type {Boolean}
411
+ */
412
+ prettyURL: true,
413
+
414
+ /**
415
+ * A value indicating if relative hyperlinks are dispatched to (handled by) the Bindary routing.
416
+ * @type {Boolean}
417
+ */
418
+ linkDispatching: true,
419
+
420
+ /**
421
+ * A value indicating if the view HTML is cleared when a route is unloaded.
422
+ * @type {Boolean}
423
+ */
424
+ clearViewOnUnload: false,
425
+
426
+ /**
427
+ * A value indicating if the framework is connected with a data provider.
428
+ * @type {Boolean}
429
+ */
430
+ connected: false,
431
+
432
+ /**
433
+ * A value indicating if data-bindary attributes on the element are retained.
434
+ * When disabled, calling updateTemplateItem() will update the defined attributes and hold previously stored values; clearing can be accomplished by specifying 'null' as attribute value.
435
+ * When enabled, calling updateTemplateItem() will clear undefined attributes.
436
+ * @type {Boolean}
437
+ */
438
+ keepAttributes: false,
439
+
440
+ /**
441
+ * A value indicating if a fragment identifier (anchor tag) specified in the URL through a hashtag is automatically scrolled into view after loading the route.
442
+ * Use a second hashtag in the URL (without a matching route) to jump to a bookmark when the first hashtag contains the route-path.
443
+ * @type {Boolean}
444
+ */
445
+ anchorScroll: true,
446
+
447
+ /**
448
+ * A value indicating whether client-side caching of views, view-css, view-scripts, controllers and data is enabled.
449
+ * @type {Boolean}
450
+ */
451
+ caching: true,
452
+
453
+ /**
454
+ * A value indicating whether internal script and css tags loaded with a specific view are being cached.
455
+ * @type {Boolean}
456
+ */
457
+ cacheInternalTags: false,
458
+
459
+ /**
460
+ * A value indicating whether the routeData container is retained in cache when a route is unloaded.
461
+ * Route data is keyed by route index, so it is shared by every URL matching the same route definition.
462
+ * @type {Boolean}
463
+ */
464
+ cacheRouteData: true,
465
+
466
+ /**
467
+ * A value indicating whether the viewData container is retained in cache when a route is unloaded.
468
+ * View data is keyed by route path, so each distinct URL holds its own.
469
+ * @type {Boolean}
470
+ */
471
+ cacheViewData: false,
472
+
473
+ /**
474
+ * A value indicating if server data overwrites (false) or updates (true) data on the client.
475
+ * @type {Boolean}
476
+ */
477
+ defaultUpdateRule: false,
478
+
479
+ /**
480
+ * A value indicating whether incoming data responses are compared for equality before updating to avoid redundant changes.
481
+ * @type {Boolean}
482
+ */
483
+ equalityCheck: true,
484
+
485
+ /**
486
+ * A value indicating if the HTML element must be recreated when there is already an element for the relevant index/key.
487
+ * @type {Boolean}
488
+ */
489
+ defaultRecreateRule: false,
490
+
491
+ /**
492
+ * A value indicating if changes are being observed for the default appData, routeData and viewData data-containers (ECMAScript 6 required).
493
+ * @type {Boolean}
494
+ */
495
+ observing: true,
496
+
497
+ /**
498
+ * A value indicating if changes are being observed during the view rendering process.
499
+ * @type {Boolean}
500
+ */
501
+ observeDuringRender: false,
502
+
503
+ /**
504
+ * A value indicating if the view is automatically updated when changes are being observed.
505
+ * @type {Boolean}
506
+ */
507
+ autoUpdateView: true,
508
+
509
+ /**
510
+ * A value indicating if all data provided through data-bindary-html attributes are trusted.
511
+ * Enable this option only if there are no (sanitized) user defined HTML data-sources (potential security risks).
512
+ * To trust a specific attribute use data-bindary-html="trust:dataKey".
513
+ * @type {Boolean}
514
+ */
515
+ trustHTML: false,
516
+
517
+ /**
518
+ * A value indicating that the page is reloaded when the WebSocket is closed by the server due to an internal server error.
519
+ * @type {Boolean}
520
+ */
521
+ reloadPageOnWebSocketServerError: true,
522
+
523
+ /**
524
+ * A value indicating that the route is reloaded when the page is restored from the back/forward cache (bfcache) after navigating back to the page.
525
+ * @type {Boolean}
526
+ */
527
+ reloadRouteOnPageRestore: true,
528
+
529
+ /**
530
+ * The element that triggered a data-bind.
531
+ * Used to prevent updating the field value while typing. Cleared after a view update or can be manually cleared to force the element’s value to update on the next view refresh.
532
+ * @type {HTMLElement|null}
533
+ */
534
+ dataBindTrigger: null,
535
+
536
+ /**
537
+ * Gets or sets a custom sanitizer function. Overrides built-in sanitizer.
538
+ * @type {Function|null}
539
+ */
540
+ sanitizer: null,
541
+
542
+ get onDataBind()
543
+ {
544
+ console.warn("'onDataBind' is deprecated, mapped to 'onPreDataBind'.");
545
+ return $bindary.onPreDataBind;
546
+ },
547
+
548
+ /**
549
+ * A value indicating whether to use shorthand ('m-') or default ('data-bindary-') prefixes for attributes.
550
+ * @type {boolean}
551
+ */
552
+ get useShorthand()
553
+ {
554
+ return core.useShorthand;
555
+ },
556
+
557
+ set useShorthand(value)
558
+ {
559
+ core.useShorthand = value;
560
+ core.initializePrefix(value === true ? core.shortPrefix : core.longPrefix);
561
+ },
562
+
563
+ /**
564
+ * A value indicating if attribute names use the hyphenated form (e.g. view-formatter) instead of the fused form (e.g. viewFormatter). This setting exists for backwards compatibility.
565
+ * @type {boolean}
566
+ */
567
+ get hyphenatedAttributes()
568
+ {
569
+ return core.hyphenatedAttributes;
570
+ },
571
+
572
+ set hyphenatedAttributes(value)
573
+ {
574
+ core.initializePrefix(core.prefix, value);
575
+ },
576
+
577
+
578
+ /**
579
+ * Gets the custom prefix.
580
+ * @type {String}
581
+ */
582
+ get attributePrefix()
583
+ {
584
+ return core.prefix;
585
+ },
586
+
587
+ /**
588
+ * Sets and uses a custom prefix to use for the 'data-bindary-' attributes.
589
+ * @param {String} [prefix] The custom attribute prefix to use.
590
+ */
591
+ setCustomPrefix: function (prefix)
592
+ {
593
+ core.initializePrefix(prefix);
594
+ },
595
+
596
+ /**
597
+ * Launches the route for the initial route path value.
598
+ *
599
+ */
600
+ launch: function ()
601
+ {
602
+ launch();
603
+ },
604
+ /**
605
+ * Adds a route with the specified route settings.
606
+ *
607
+ * @param {componyx.bindary.Route} route
608
+ * @returns {number} The array index of the added route within the $bindary.routes array.
609
+ */
610
+ addRoute: function (route)
611
+ {
612
+ route.pathExp = (route.path instanceof RegExp);
613
+ $bindary.routes.push(route);
614
+ return $bindary.routes.length - 1;
615
+ },
616
+
617
+ /**
618
+ * Loads the route based on the current URL or based on the specified route path. Method is only applicable when $bindary.prettyURL is enabled.
619
+ * @param {String} [routePath] The route path to load.
620
+ * @returns {Number} The route index.
621
+ */
622
+ loadRoute: function (routePath)
623
+ {
624
+ if ($lib.isEmpty(routePath))
625
+ routePath = core.getRoutePath();
626
+ else if ($bindary.prettyURL)
627
+ {
628
+ let url = core.getFullPath(routePath);
629
+
630
+ routePath = core.removeAnchor(routePath);
631
+
632
+ if ($bindary.routePath != routePath)
633
+ window.history.pushState({ routePath: routePath }, '', url);
634
+ }
635
+
636
+ return router.load(routePath);
637
+ },
638
+
639
+
640
+ /**
641
+ * Dispatches anchor links so that they are handled by the framework's routing. Method is only applicable when "pretty URL" is enabled.
642
+ * @param {HTMLElement} [container] The container element in which hyperlinks will be updated.
643
+ */
644
+ dispatchLinks: function (container)
645
+ {
646
+ core.dispatchLinks(container);
647
+ },
648
+
649
+ /**
650
+ * Dispatch an anchor link so that it is handled by the framework's routing. Method is only applicable when "pretty URL" is enabled.
651
+ * @param {HTMLElement} anchor The anchor link tag.
652
+ */
653
+ dispatchLink: function (anchor)
654
+ {
655
+ core.dispatchLink(anchor);
656
+ },
657
+
658
+ /**
659
+ * Adds a component.
660
+ * @param {String} name The name of the component.
661
+ * @param {Function} initiator A method which initializes and returns a new instance of the component.
662
+ */
663
+ addComponent: function (name, initiator)
664
+ {
665
+ core.components[name] = initiator;
666
+ },
667
+
668
+ /**
669
+ * Observes the specified object for changes (ECMAScript 6 required). This method is intended to observe objects on the global (window) scope. Observing of the appData, routeData and viewData containers can be enabled through $bindary.observing.
670
+ * @example window.myContainer = $bindary.observe({}, 'myContainer');
671
+ * @param {Object} obj The object to observe.
672
+ * @param {String} dataContainer The name of the root data-container on which the proxy object is defined.
673
+ * @param {Object} settings An object with configuration settings for observing the object.
674
+ * @param {CustomEvent} settings.event The custom event to dispatch when a property is changed. This can be used in combination with Native WebComponents to update your custom HTML component when an object property changes. Return false from this event to cancel data-change and view update.
675
+ * @param {function(): CustomEvent} [settings.createEvent] - A factory function that returns a new CustomEvent for each change. Recommended when changes are batched, deferred, or when change isolation is required.
676
+ * @param {Object} settings.eventPayload The additional data included in the event.detail, providing context for the data change.
677
+ * @param {HTMLElement} settings.element The element on which the custom event is fired. By default, the event is fired on the document object.
678
+ * @param {TemplateItem[]} settings.viewTree The root tree to use when updating the view after a data change.
679
+ * @param {Boolean} [settings.reuseProxy=true] A value indicating whether a single proxy instance is shared across multiple data paths. When disabled, each path gets its own independent proxy, losing cross-path change propagation
680
+ * @returns {Object} A proxy object for template-binding on which modifications are immediately noticed and result in an update of the view.
681
+ */
682
+ observe: function (obj, dataContainer, settings)
683
+ {
684
+ if (settings && settings.viewTree)
685
+ {
686
+ settings.treeState = [];
687
+ $lib.each(settings.viewTree, (item, index) => { settings.treeState[index] = item.captureTreeState(); });
688
+ }
689
+
690
+ return proxyManager.createProxy(obj, dataContainer, settings);
691
+ },
692
+
693
+ /**
694
+ * @typedef {Object} ProxyManager
695
+ * @memberof componyx.bindary
696
+ * @property {function(Object): boolean} isProxy - Returns true if the object is a proxy.
697
+ * @property {function(Object): Object|null} getTarget - Gets the underlying target of the given proxy.
698
+ */
699
+
700
+ /**
701
+ * Gets the proxy manager used to create observable objects.
702
+ * @returns {ProxyManager} The proxy manager object.
703
+ */
704
+ getProxyManager: function ()
705
+ {
706
+ return proxyManager;
707
+ },
708
+
709
+ /**
710
+ * Stops observing for data-changes on observed objects until startObserving() is called.
711
+ */
712
+ stopObserving: function ()
713
+ {
714
+ core.observing = false;
715
+ },
716
+
717
+ /**
718
+ * Restarts observing for data-changes on observed objects.
719
+ */
720
+ startObserving: function ()
721
+ {
722
+ core.observing = true;
723
+ },
724
+
725
+ /**
726
+ * A function that determines if the handler applies to a specific template item or element.
727
+ * @memberof componyx.bindary
728
+ * @callback ValueMatcher
729
+ * @param {componyx.bindary.TemplateItem} item The template item containing the element.
730
+ * @returns {boolean} True if the handler should handle this, otherwise false.
731
+ */
732
+
733
+ /**
734
+ * A function that retrieves the value from an element.
735
+ * @callback ValueGetter
736
+ * @memberof componyx.bindary
737
+ * @param {HTMLElement} element The target element from which the value should be retrieved.
738
+ * @returns {string} The extracted value from the element.
739
+ */
740
+
741
+ /**
742
+ * A function that sets the value on an element.
743
+ * @callback ValueSetter
744
+ * @memberof componyx.bindary
745
+ * @param {HTMLElement} element The target element on which the value should be set.
746
+ * @param {string} value The value to set on the element.
747
+ */
748
+
749
+ /**
750
+ * Registers a value handler for data-bound elements.
751
+ * @param {ValueMatcher} matcher A function that checks if the handler applies for the specified template item.
752
+ * @param {ValueGetter} getValue A function that retrieves the value from the element.
753
+ * @param {ValueSetter} setValue A function that sets the value on the element.
754
+ * @returns {String} The id of the registered handler (needed in case of removal).
755
+ */
756
+ registerValueHandler: function (matcher, getValue, setValue)
757
+ {
758
+ const id = $lib.guid();
759
+ core.valueHandlers.push({ id, matcher, getValue, setValue });
760
+ return id;
761
+ },
762
+
763
+ /**
764
+ * Unregisters a value handler.
765
+ * @param {String} id The id of the handler to remove.
766
+ */
767
+ unregisterValueHandler: function (id)
768
+ {
769
+ const index = core.valueHandlers.findIndex(h => h.id === id);
770
+ if (index !== -1)
771
+ {
772
+ core.valueHandlers.splice(index, 1);
773
+ }
774
+ },
775
+
776
+ /**
777
+ * Places a lock (on the specified dataKey and/or dataContainer) to postpone data updates until unlock is called.
778
+ * Ommit parameters to lock complete data-tree or specify dataContainer only to lock container.
779
+ *
780
+ * @param {String} dataKey The hierarchical path of the object or object value. This string value should consist of object keys separated by a '.' (dot) character (default, change via dataKeySeparatorChar) and without the data container name.
781
+ * @param {String} [dataContainer] One of the following data containers: appData, routeData or viewData. Defaults to viewData when dataKey is specified.
782
+ */
783
+ lock: function (dataKey, dataContainer)
784
+ {
785
+ // keep cached data updates on lock for duplicate dataKey
786
+ if (!dataKey && !dataContainer)
787
+ core.dataLocks['#'] = core.dataLocks['#'] || []; // # for full lock
788
+ else
789
+ {
790
+ let key = $lib.format('{0}{1}', dataContainer || core.dataLabel[2], (dataKey) ? core.dksc + dataKey : '');
791
+ core.dataLocks[key] = core.dataLocks[key] || [];
792
+ }
793
+ },
794
+
795
+ /**
796
+ * Removes the lock (on the specified dataKey and/or dataContainer) to re-enable data updates.
797
+ * Ommit parameters to unlock complete data-tree or specify dataContainer only to unlock container.
798
+ *
799
+ * @param {String} dataKey The hierarchical path of the object or object value. This string value should consist of object keys separated by a '.' (dot) character (default, change via dataKeySeparatorChar) and without the data container name.
800
+ * @param {String} [dataContainer] One of the following data containers: appData, routeData or viewData. Defaults to viewData when dataKey is specified.
801
+ * @param {Boolean} [ignore] A value indicating that previous received data must be ignored.
802
+ * @param {Boolean} [update] The view is updated automatically after the unlock. This default update behaviour can be cancelled by specifying false for this argument.
803
+ */
804
+ unlock: function (dataKey, dataContainer, ignore, update)
805
+ {
806
+ let key;
807
+
808
+ if (!dataKey && !dataContainer)
809
+ key = '#';
810
+ else
811
+ key = $lib.format('{0}{1}', dataContainer || core.dataLabel[2], (dataKey) ? core.dksc + dataKey : '');
812
+
813
+ if (!ignore)
814
+ {
815
+ core.autoUpdateView = false;
816
+ $lib.each(core.dataLocks[key], function (fn)
817
+ {
818
+ fn(); // call stored data-update functions
819
+ });
820
+ core.autoUpdateView = true;
821
+
822
+ core.fireEvent('postDataUpdate');
823
+ }
824
+
825
+ delete core.dataLocks[key];
826
+
827
+ if (!ignore && update != false)
828
+ updateView();
829
+ },
830
+
831
+ /**
832
+ * Updates data-model values for data-bound elements.
833
+ * @param {HTMLElement[]} elements A collection of data-bound elements for which the data-model values must be updated.
834
+ */
835
+ dataBind: function (elements)
836
+ {
837
+ $lib.each(elements, function (el)
838
+ {
839
+ let item = getTemplateItem(el);
840
+
841
+ if (item)
842
+ item.dataBind();
843
+ });
844
+ },
845
+
846
+ /**
847
+ * Registers a data change for the specified model (object reference) so that the next view update will display the new data.
848
+ *
849
+ * @param {Object} model The model of the corresponding data-bound element.
850
+ * @param {Object[]} arrayUpdateRules An array of updateRule objects for the array corresponding with the data-bound repeat element.
851
+ * @param {Number[]} arrayUpdateRules.keys Indexes or keys within the collection where items where updated.
852
+ * @param {Boolean} arrayUpdateRules.recreate A value indicating if the HTML element must be recreated when there is already an element for the relevant index/key.
853
+ * @param {Number} arrayUpdateRules.addIndex Position within the array where the items where added. Do not specify the index (Null/Undefined) when items where added at the end of the array.
854
+ * @param {Number} arrayUpdateRules.addCount Number of items that where added to the array.
855
+ * @param {Number} arrayUpdateRules.removeIndex Position within the array where the items where removed. Do not specify the index (Null/Undefined) when items where removed at the end of the array.
856
+ * @param {Number} arrayUpdateRules.removeCount Number of items that where removed from the array.
857
+ * @param {Boolean} [update] The view is updated automatically after a data change. This default update behaviour can be cancelled by specifying false for this argument.
858
+ */
859
+ dataChangeForModel: function (model, arrayUpdateRules, update)
860
+ {
861
+ core.modelDataChanges.set(model, dataUpdater.initUpdateRules(core.modelDataChanges.get(model), arrayUpdateRules));
862
+
863
+ if (update != false)
864
+ updateView();
865
+ },
866
+
867
+ /**
868
+ * Registers a data change for the element with the specified updateId so that the next view update will display the new data.
869
+ *
870
+ * @param {String} updateId The update id of the corresponding data-bound element.
871
+ * @param {Object[]} arrayUpdateRules An array of updateRule objects for the array corresponding with the data-bound repeat element.
872
+ * @param {Number[]} arrayUpdateRules.keys Indexes or keys within the collection where items where updated.
873
+ * @param {Boolean} arrayUpdateRules.recreate A value indicating if the HTML element must be recreated when there is already an element for the relevant index/key.
874
+ * @param {Number} arrayUpdateRules.addIndex Position within the array where the items where added. Do not specify the index (Null/Undefined) when items where added at the end of the array.
875
+ * @param {Number} arrayUpdateRules.addCount Number of items that where added to the array.
876
+ * @param {Number} arrayUpdateRules.removeIndex Position within the array where the items where removed. Do not specify the index (Null/Undefined) when items where removed at the end of the array.
877
+ * @param {Number} arrayUpdateRules.removeCount Number of items that where removed from the array.
878
+ * @param {Boolean} [update] The view is updated automatically after a data change. This default update behaviour can be cancelled by specifying false for this argument.
879
+ */
880
+ dataChangeForId: function (updateId, arrayUpdateRules, update)
881
+ {
882
+ core.dataChanges['#' + updateId] = dataUpdater.initUpdateRules(arrayUpdateRules, core.dataChanges['#' + updateId]);
883
+
884
+ if (update != false)
885
+ updateView();
886
+ },
887
+
888
+ /**
889
+ * Registers a data change for the specified dataKey so that the next view update will display the new data.
890
+ *
891
+ * @param {String} dataKey The hierarchical path of the changed object or object value. This string value should consist of object keys separated by a '.' (dot) character (default, change via dataKeySeparatorChar) and without the data container name.
892
+ * @param {String|Number} [dataContainer] One of the root data-containers to which the data-key belongs: appData(0), routeData(1) or viewData(2).
893
+ * @param {Object[]} arrayUpdateRules An array of updateRule objects for the array corresponding with the data-bound repeat element.
894
+ * @param {Number[]} arrayUpdateRules.keys Indexes or keys within the collection where items where updated.
895
+ * @param {Boolean} arrayUpdateRules.recreate A value indicating if the HTML element must be recreated when there is already an element for the relevant index/key.
896
+ * @param {Number} arrayUpdateRules.addIndex Position within the array where the items where added. Do not specify the index (Null/Undefined) when items where added at the end of the array.
897
+ * @param {Number} arrayUpdateRules.addCount Number of items that where added to the array.
898
+ * @param {Number} arrayUpdateRules.removeIndex Position within the array where the items where removed. Do not specify the index (Null/Undefined) when items where removed at the end of the array.
899
+ * @param {Number} arrayUpdateRules.removeCount Number of items that where removed from the array.
900
+ * @param {Boolean} [update] The view is updated automatically after a data change. This default update behaviour can be cancelled by specifying false for this argument.
901
+ */
902
+ dataChange: function (dataKey, dataContainer, arrayUpdateRules, update)
903
+ {
904
+ dataKey = $lib.isEmpty(dataContainer) ? dataKey : $lib.format('{0}.{1}', (typeof dataContainer === 'number') ? core.dataLabel[dataContainer] : dataContainer, dataKey);
905
+ core.dataChanges[dataKey] = dataUpdater.initUpdateRules(arrayUpdateRules, core.dataChanges[dataKey]);
906
+
907
+ if (update != false)
908
+ updateView();
909
+ },
910
+
911
+ /**
912
+ * Clears all registered data changes and cancels the delayed view update.
913
+ */
914
+ clearDataChanges: function ()
915
+ {
916
+ core.updatePending = false;
917
+ core.pendingComplete = false;
918
+
919
+ if (!core.busy)
920
+ core.dataChanges = {};
921
+ },
922
+
923
+ /**
924
+ * Sends the specified data to the server through the connected websocket or new xhr connection and returns an awaitable Promise that resolves when the response is received.
925
+ *
926
+ * @param {Object} data The JSON data request.
927
+ * @param {String} [serverMethod] A class type and static method (dot separated) to call on the server when executing the data request. Requires the .NET server-side Bindary framework.
928
+ * @param {String} [clientId] A value which is send with each data request to be able to uniquely identify clients on the server side.
929
+ * @param {Boolean} [updateView] A value indicating if the view is updated after the data response. Defaults to true.
930
+ * @returns {Promise<Object>} Resolves with the server response message.
931
+ */
932
+ dataSendAsync: function (data, serverMethod, clientId, updateView)
933
+ {
934
+ return new Promise(resolve =>
935
+ {
936
+ this.dataSend(data, serverMethod, clientId, resolve, updateView);
937
+ });
938
+ },
939
+
940
+ /**
941
+ * Sends the specified data to the server through the connected websocket or new xhr connection.
942
+ *
943
+ * @param {Object} data The JSON data request.
944
+ * @param {String} [serverMethod] A class type and static method (dot separated) to call on the server when executing the data request. Requires the .NET server-side Bindary framework.
945
+ * @param {String} [clientId] A value which is send with each data request to be able to uniquely identify clients on the server side.
946
+ * @param {Function} [onDataResponse] An event callback method which is invoked on the response for this request. Method parameters: data (Object).
947
+ * @param {Boolean} [updateView] A value indicating if the view is updated after the data response. Defaults to true.
948
+ * @returns {WebSocket|XMLHttpRequest} The WebSocket or XHR object used to transfer the data.
949
+ */
950
+ dataSend: function (data, serverMethod, clientId, onDataResponse, updateView)
951
+ {
952
+ let callbackId = $lib.guid(),
953
+ onResponse = function (onDataResponse, updateView, dataMsg)
954
+ {
955
+ if (updateView)
956
+ {
957
+ core.updatePending = false;
958
+ core.pendingComplete = false;
959
+ }
960
+
961
+ if (onDataResponse)
962
+ onDataResponse(dataMsg);
963
+
964
+ return updateView;
965
+ }.bind(window, onDataResponse, updateView);
966
+
967
+ if (!$lib.isEmpty(clientId))
968
+ $bindary.clientId = clientId;
969
+
970
+ core.callbacks[callbackId] = onResponse;
971
+
972
+ return connection.dataSend(connection.createDataRequestMsg(data, serverMethod, callbackId), true);
973
+ },
974
+
975
+ /*
976
+ * Sends the specified data to the server while attempting to keep the browser responsive to user-input.
977
+ * Use this method to send data only when the window is being unloaded. Check the 3rd argument (boolean) in the controller's unload method to see if the call came from a window-before-unload event.
978
+ * When Websockets are disabled or unsupported an attempt is made to send the data through navigator.sendBeacon, to keep the browser responsive to user-input.
979
+ * However, if sendBeacon is unsupported a fallback to a synchronous XHR call is made.
980
+ *
981
+ * @param {Object} data The JSON data request. The navigator.sendBeacon method has browser-specific data size limitations.
982
+ * @param {String} [serverMethod] A class type and static method (dot separated) to call on the server when executing the data request. Requires the .NET server-side Bindary framework.
983
+ * @param {String} [clientId] A value which is send with each data request to be able to uniquely identify clients on the server side.
984
+ * @returns {Boolean} A value is only returned when the data is sent through navigator.sendBeacon. This value indicates if the data was Successfully sent or if a data limit was reached.</returns>
985
+ */
986
+ unload: function (data, serverMethod, clientId)
987
+ {
988
+ if (clientId != undefined)
989
+ $bindary.clientId = clientId;
990
+
991
+ let result;
992
+ data = connection.createDataRequestMsg(data, serverMethod);
993
+
994
+ if (core.useWS())
995
+ connection.dataSend(data);
996
+ else
997
+ {
998
+ if (navigator.sendBeacon)
999
+ result = navigator.sendBeacon(connection.getDataServerURL($bindary.dataServerURL), data);
1000
+ else
1001
+ $lib.xhr({ url: connection.getDataServerURL($bindary.dataServerURL), data: data, async: false });
1002
+
1003
+ return result;
1004
+ }
1005
+ },
1006
+
1007
+ /**
1008
+ * Creates or updates a data-bound template item. The item will be rendered on the next view update.
1009
+ *
1010
+ * @param {HTMLElement} element An HTML element having attributes to serve as data-bound template item.
1011
+ * @param {Boolean} [deep=false] A value indicating if child elements should be updated.
1012
+ * @param {Boolean} [clear=false] A value indicating if child items must be cleared.
1013
+ */
1014
+ updateTemplateItem: function (element, deep, clear)
1015
+ {
1016
+ return updateTemplateItem(element, deep, clear);
1017
+ },
1018
+
1019
+ /**
1020
+ * Removes a data-bound template item.
1021
+ *
1022
+ * @param {HTMLElement} element The original element.
1023
+ */
1024
+ removeTemplateItem: function (element)
1025
+ {
1026
+ const item = getTemplateItem(element);
1027
+
1028
+ if (!item)
1029
+ return;
1030
+
1031
+ // Step 1: remove all descendants from templateList and clear children arrays
1032
+ removeTemplateItemTree(item);
1033
+
1034
+ // Step 2: remove the item itself from templateList
1035
+ let index = $lib.indexOf(core.templateList, i => i.id === item.id);
1036
+ if (index > -1)
1037
+ core.templateList.splice(index, 1);
1038
+
1039
+ // Step 3: detach from parent or viewTree
1040
+ if (item.parent)
1041
+ {
1042
+ const parentChildren = item.parent.children;
1043
+ index = parentChildren.findIndex(c => c.id === item.id);
1044
+ if (index > -1)
1045
+ parentChildren.splice(index, 1);
1046
+ }
1047
+ else
1048
+ {
1049
+ index = core.viewTree.findIndex(c => c.id === item.id);
1050
+ if (index > -1)
1051
+ core.viewTree.splice(index, 1);
1052
+ }
1053
+
1054
+ item.children = [];
1055
+ },
1056
+
1057
+ /**
1058
+ * (Re)Renders the view with the active data objects.
1059
+ *
1060
+ * @param {Boolean} complete A value indicating if the view must be refreshed completely instead of checking for registered data changes.
1061
+ * @param {Boolean} redetectTemplateElements A value indicating if the template elements should be redetected. Make sure keep attributes is set to true, either globally or on the elements, otherwise these elements are not redetected.
1062
+ * @param {TemplateItem[]} [viewTree] The root template items for the view tree, from which the update will propagate.
1063
+ */
1064
+ updateView: function (complete, redetectTemplateElements, viewTree)
1065
+ {
1066
+ if (redetectTemplateElements)
1067
+ createViewTree();
1068
+
1069
+ updateView(complete, viewTree, true);
1070
+ },
1071
+
1072
+ /**
1073
+ * (Re)Renders the view with the active data objects. The delayed update call is placed at the end of the execution queue through setTimeout()
1074
+ *
1075
+ * @param {Boolean} complete A value indicating if the view must be refreshed completely instead of checking for registered data changes.
1076
+ * @param {Boolean} [redetectTemplateElements] A value indicating if the template elements should be redetected.
1077
+ * @param {TemplateItem[]} [viewTree] The root template items for the view tree, from which the update will propagate.
1078
+ */
1079
+ updateViewDelayed: function (complete, redetectTemplateElements, viewTree)
1080
+ {
1081
+ if (redetectTemplateElements)
1082
+ createViewTree();
1083
+
1084
+ updateViewDelayed(complete, viewTree);
1085
+ },
1086
+
1087
+ /**
1088
+ * Creates the view tree with data-bound template items.
1089
+ *
1090
+ * @param {HTMLElement} [rootEl] The root HTML Element.
1091
+ * @param {componyx.bindary.TemplateItem} [rootItem] The root Template Item.
1092
+ */
1093
+ createViewTree: function (rootEl, rootItem)
1094
+ {
1095
+ createViewTree(rootEl, rootItem);
1096
+ },
1097
+
1098
+ /**
1099
+ * Renders the view tree using data-bound template items.
1100
+ * **Important:** This method should only be called during an active render cycle, otherwise use `updateViewDelayed()` or `updateView()` instead.
1101
+ * Note that unlike `updateView()`, this method does **not** trigger `preRender` or `postRender` events.
1102
+ * @param {Boolean} update A value indicating if this is an update.
1103
+ * @param {TemplateItem[]} viewTree The view tree to create.
1104
+ */
1105
+ renderView: function (update, viewTree)
1106
+ {
1107
+ renderView(update, viewTree);
1108
+ },
1109
+
1110
+ /**
1111
+ * Disconnects the data connection.
1112
+ */
1113
+ disconnect: function ()
1114
+ {
1115
+ connection.disconnect();
1116
+ },
1117
+
1118
+ /**
1119
+ * Gets the template item that belongs to the specified element.
1120
+ * @param {HTMLElement} The element for wich to retrieve the template item.
1121
+ * @returns {componyx.bindary.TemplateItem} The template item.
1122
+ */
1123
+ getTemplateItem: function (element)
1124
+ {
1125
+ return getTemplateItem(element);
1126
+ },
1127
+
1128
+ /**
1129
+ * Gets the view container element.
1130
+ * @returns {HTMLElement} The view container element.
1131
+ */
1132
+ getViewContainer: function ()
1133
+ {
1134
+ return core.viewContainer;
1135
+ },
1136
+
1137
+ /**
1138
+ * Gets the page anchor.
1139
+ * @returns {String} The page anchor.
1140
+ */
1141
+ getAnchor: function ()
1142
+ {
1143
+ return core.getAnchor();
1144
+ },
1145
+
1146
+ /**
1147
+ * Gets the base href which precedes all route paths when prettyURL is enabled.
1148
+ * @returns {String} The base href.
1149
+ */
1150
+ getBaseHref: function ()
1151
+ {
1152
+ return core.getBaseHref();
1153
+ },
1154
+
1155
+ /**
1156
+ * Gets the internal cache object which holds views, includes, controllers, source-ready states and the routeData/viewData containers per route.
1157
+ * The object is returned by reference, so modifications affect the framework directly.
1158
+ * @returns {Object} The cache object.
1159
+ */
1160
+ getCache: function ()
1161
+ {
1162
+ return core.cache;
1163
+ },
1164
+
1165
+ /**
1166
+ * Clears the internal cache object in where views, includes, controllers and data are stored. Loaded script and CSS tags from a view or includable file will also be removed from the page when clearing the cache (unless indicated otherwise through the parameters).
1167
+ * @param {Boolean} [keepControllers] A value indicating if loaded controller script tags must remain on the page.
1168
+ * @param {Boolean} [keepExternalScripts] A value indicating if external script tags from a view or includable file must remain on the page.
1169
+ * @param {Boolean} [keepExternalCss] A value indicating if external css tags from a view or includable file must remain on the page.
1170
+ * @param {Boolean} [keepInternalScripts] A value indicating if internal (also called inline) script tags from a view or includable file must remain on the page.
1171
+ * @param {Boolean} [keepInternalCss] A value indicating if internal (also called inline) css tags from a view or includable file must remain on the page.
1172
+ */
1173
+ clearCache: function (keepControllers, keepExternalScripts, keepExternalCSS, keepInternalScripts, keepInternalCss)
1174
+ {
1175
+ core.clearCache(keepControllers, keepExternalScripts, keepExternalCSS, keepInternalScripts, keepInternalCss);
1176
+ }
1177
+ };
1178
+
1179
+ window.$bindary = componyx.bindary;
1180
+
1181
+ /**
1182
+ * @property {Boolean} [busy] A value indicating if the framework is busy loading a route or rendering the view.
1183
+ * @readonly
1184
+ * @memberof componyx.bindary
1185
+ */
1186
+ Object.defineProperty(window.$bindary, 'busy',
1187
+ {
1188
+ get: function () { return core.busy; }
1189
+ });
1190
+
1191
+ /**
1192
+ * @property {String} [dataKeySeparatorChar] The character(s) used as data-key separator. Defaults to '.' (dot). Only change if a dot is used in object keys.
1193
+ * @memberof componyx.bindary
1194
+ */
1195
+ Object.defineProperty(window.$bindary, 'dataKeySeparatorChar',
1196
+ {
1197
+ get: function () { return core.dksc; },
1198
+ set: function (value)
1199
+ {
1200
+ core.dksc = value;
1201
+ }
1202
+ });
1203
+
1204
+ /**
1205
+ * @property {String} [indexPointerChar] The character(s) used to indicate an index pointer within a data-key. Defaults to '@' (at sign). Only change if an at sign is used in object keys.
1206
+ * @memberof componyx.bindary
1207
+ */
1208
+ Object.defineProperty(window.$bindary, 'indexPointerChar',
1209
+ {
1210
+ get: function () { return core.ipc; },
1211
+ set: function (value)
1212
+ {
1213
+ core.ipc = value;
1214
+ }
1215
+ });
1216
+
1217
+ function launch()
1218
+ {
1219
+ let routePath = core.getRoutePath();
1220
+
1221
+ core.allowHistoryScroll = !$bindary.prettyURL;
1222
+ core.secure = core.getLocationOrigin().match(/^https:\/\//) != null;
1223
+
1224
+ if (!$bindary.prettyURL)
1225
+ {
1226
+ if (!$lib.has(window, 'hashchange', hashChange))
1227
+ $lib.on(window, 'hashchange', hashChange);
1228
+
1229
+ // because there is no way to distinguish between a hash change through a link or through a history action, we listen for document clicks to disallow history scrolling
1230
+ if (!$lib.has(document, 'click', docClick))
1231
+ $lib.on(document, 'click', docClick);
1232
+ }
1233
+
1234
+ if ($bindary.observing)
1235
+ {
1236
+ $bindary.appData = $bindary.observe($bindary.appData, core.dataLabel[0]);
1237
+ $bindary.routeData = $bindary.observe($bindary.routeData, core.dataLabel[1]);
1238
+ $bindary.viewData = $bindary.observe($bindary.viewData, core.dataLabel[2]);
1239
+ }
1240
+
1241
+ createBaseHref();
1242
+
1243
+ if ($bindary.linkDispatching)
1244
+ core.dispatchLinks();
1245
+
1246
+ core.launchRoutePath = $bindary.routePath;
1247
+
1248
+ if ($lib.isEmpty(routePath) && !$lib.isEmpty($bindary.routePath))
1249
+ routePath = core.removeAnchor(($bindary.prettyURL) ? $bindary.routePath : $bindary.routePath.replace(/^[\/#!]+/, ''));
1250
+
1251
+ $bindary.routePath = null; // route-path is compared and set in router.load()
1252
+
1253
+ let state = $lib.clone({}, getHistoryState());
1254
+
1255
+ state.routePath = routePath;
1256
+ window.history.replaceState(state, '', window.location.href); // store the initial routePath in the current state
1257
+
1258
+ if (router.load(routePath) == -1)
1259
+ updateView(true);
1260
+ }
1261
+
1262
+ function createBaseHref()
1263
+ {
1264
+ let head = $lib(null, document, 'head', true),
1265
+ base = $lib(null, head, 'base', true);
1266
+
1267
+ if (base)
1268
+ return;
1269
+
1270
+ $lib.element(head, null, 'base', null, { href: '/' });
1271
+ }
1272
+
1273
+ function getHistoryState()
1274
+ {
1275
+ try
1276
+ {
1277
+ return window.history.state;
1278
+ }
1279
+ catch (ex)
1280
+ {
1281
+ $lib.log(ex.message);
1282
+ return null;
1283
+ }
1284
+ }
1285
+
1286
+ function scroll(e)
1287
+ {
1288
+ clearTimeout(core.scrollTimerId);
1289
+ core.scrollTimerId = setTimeout(saveScrollPosition, 100);
1290
+ }
1291
+
1292
+ function saveScrollPosition()
1293
+ {
1294
+ let path = $bindary.routePath;
1295
+
1296
+ if ($lib.isEmpty(path))
1297
+ return;
1298
+
1299
+ let win = $lib.getWindowSize(),
1300
+ x = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0,
1301
+ y = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
1302
+
1303
+ core.scrollPos[path] =
1304
+ {
1305
+ path: path,
1306
+ x: x,
1307
+ y: y,
1308
+ width: win.width + x,
1309
+ height: win.height + y
1310
+ };
1311
+
1312
+ window.sessionStorage.setItem('scrollPosition', window.JSON.stringify(core.scrollPos[path])); // store last scroll position in session storage
1313
+ }
1314
+
1315
+ function docClick()
1316
+ {
1317
+ core.allowHistoryScroll = false;
1318
+ clearTimeout(core.historyTimerId);
1319
+ core.historyTimerId = setTimeout(function () { core.allowHistoryScroll = true; }, 0);
1320
+ }
1321
+
1322
+ function hashChange(e)
1323
+ {
1324
+ let routePath = core.getHashtag();
1325
+
1326
+ if (core.isBookmarkJump(routePath))
1327
+ scroller.init(true);
1328
+ else
1329
+ {
1330
+ if ($lib.isEmpty(routePath))
1331
+ routePath = core.launchRoutePath;
1332
+
1333
+ router.load(routePath);
1334
+ }
1335
+ }
1336
+
1337
+ function popState(e)
1338
+ {
1339
+ let routePath = (e && e.state && !$lib.isEmpty(e.state.routePath)) ? e.state.routePath : null,
1340
+ anchor = core.getAnchor();
1341
+
1342
+ if (!$bindary.prettyURL && !routePath) // only history actions and not hash changes if prettyURL is disabled
1343
+ return;
1344
+
1345
+ core.allowHistoryScroll = true;
1346
+
1347
+ if (!routePath)
1348
+ routePath = core.getRoutePath();
1349
+
1350
+ if (!$lib.isEmpty(anchor) && core.isBookmarkJump(routePath + '#' + anchor))
1351
+ return;
1352
+
1353
+ router.load(routePath);
1354
+ }
1355
+
1356
+ function visibilityChange()
1357
+ {
1358
+ if (core.fireEvent('onVisibilityChange') !== false)
1359
+ {
1360
+ if (document.visibilityState === "visible" && core.useWS() && $bindary.connected)
1361
+ connection.dataSend(connection.createDataRequestMsg(null, "", null, false, true)); // update client websocket object
1362
+ }
1363
+ }
1364
+
1365
+ function updateViewDelayed(complete, viewTree)
1366
+ {
1367
+ if (!viewTree)
1368
+ {
1369
+ core.pendingFullRender = core.pendingFullRender || complete; // escalate: a full-rebuild request anywhere in this batch always wins
1370
+
1371
+ if (core.pendingUpdate)
1372
+ return; // already queued for this tick; the eventual flush will see the escalated value above
1373
+
1374
+ core.pendingUpdate = true;
1375
+
1376
+ queueMicrotask(function ()
1377
+ {
1378
+ if (!core.pendingUpdate) // flushed early elsewhere (e.g. a direct updateView call) - nothing left to do
1379
+ return;
1380
+
1381
+ updateView(core.clearPendingUpdate());
1382
+ });
1383
+
1384
+ return;
1385
+ }
1386
+
1387
+ if (core.pendingUpdate) // a full update is already queued; it supersedes any per-item batching
1388
+ return;
1389
+
1390
+ // partial (scoped) update: accumulate items across calls in this tick into one batch
1391
+ const itemsToUpdate = viewTree.filter(item => !core.pendingTreeItems.has(item));
1392
+
1393
+ if (itemsToUpdate.length === 0) // all already included in the pending batch
1394
+ return;
1395
+
1396
+ itemsToUpdate.forEach(item => core.pendingTreeItems.add(item));
1397
+ core.pendingTreeBatch = core.pendingTreeBatch.concat(itemsToUpdate);
1398
+ core.pendingPartialFullRender = core.pendingPartialFullRender || complete;
1399
+
1400
+ if (core.pendingPartialUpdate)
1401
+ return;
1402
+
1403
+ core.pendingPartialUpdate = true;
1404
+
1405
+ queueMicrotask(function ()
1406
+ {
1407
+ if (!core.pendingPartialUpdate)
1408
+ return;
1409
+
1410
+ const { batch, complete } = core.clearPendingPartialUpdate();
1411
+
1412
+ updateView(complete, batch, true);
1413
+ });
1414
+ }
1415
+
1416
+ function updateView(complete, viewTree, isRoot)
1417
+ {
1418
+ const triggerElement = $bindary.dataBindTrigger;
1419
+
1420
+ $bindary.dataBindTrigger = null;
1421
+
1422
+ if (!viewTree) // clear global timer id
1423
+ core.clearPendingUpdate();
1424
+
1425
+ if (!core.viewTree && !viewTree)
1426
+ {
1427
+ complete = true;
1428
+ createView();
1429
+ }
1430
+
1431
+ if (!complete && (!core.templateUpdate && $lib.isEmpty(core.dataChanges) && core.modelDataChanges.size == 0))
1432
+ {
1433
+ core.postRender();
1434
+ return; // nothing to update
1435
+ }
1436
+
1437
+ core.templateUpdate = false;
1438
+ core.preRender(triggerElement);
1439
+ renderView(!complete, viewTree, isRoot);
1440
+ }
1441
+
1442
+ function createView()
1443
+ {
1444
+ if (core.viewURL)
1445
+ core.getViewContainer().innerHTML = core.cache.view[core.viewURL].html; // copy html
1446
+
1447
+ createViewTree();
1448
+ }
1449
+
1450
+ function renderView(update, tree, isRoot)
1451
+ {
1452
+ let root = isRoot || !tree,
1453
+ parentContext,
1454
+ setIdAttr = function (element, item)
1455
+ {
1456
+ if (element && (item.inRepeat || item.included))
1457
+ {
1458
+ element.removeAttribute('id'); // remove id for element within repeat container or includable to avoid doubles
1459
+ element.setAttribute(core.attr.id, item.id); // keep id as bindary attribute
1460
+ }
1461
+ };
1462
+
1463
+ if (root)
1464
+ core.renderPass++;
1465
+
1466
+ if (!tree)
1467
+ parentContext = window;
1468
+
1469
+ tree = tree || core.viewTree || [];
1470
+
1471
+ $lib.each(tree, function (item, index, tree, args, next)
1472
+ {
1473
+ let element, repeatValue,
1474
+ parent = item.parent;
1475
+
1476
+ item.livePath = {};
1477
+ item.dataChange = null;
1478
+ element = null;
1479
+
1480
+ if (item.updateIdFromParent)
1481
+ item.updateIdFromParent = item.updateId = null;
1482
+
1483
+ if (parent)
1484
+ {
1485
+ parentContext = parent.context;
1486
+
1487
+ if (parent.isComponent)
1488
+ item.isComponent = true;
1489
+ }
1490
+
1491
+ if (!item.included && parent)
1492
+ item.included = parent.included;
1493
+
1494
+ item.inRepeat = false;
1495
+
1496
+ if (parent && (parent.inRepeat || parent.repeatDataKeyPath))
1497
+ {
1498
+ item.key = parent.key;
1499
+ item.inRepeat = true;
1500
+
1501
+ if (item.repeatDataKey) // repeat within repeat
1502
+ item = item.getClone();
1503
+ }
1504
+
1505
+ element = getElement(item);
1506
+
1507
+ if (item.includable && !item.included)
1508
+ {
1509
+ $lib.remove(element); // do not display includable elements until they are included
1510
+ return next();
1511
+ }
1512
+
1513
+ if (!item.component)
1514
+ item.context = parentContext;
1515
+ else if (item.loaded && item.inRepeat && !item.observeDataKeyPath)
1516
+ {
1517
+ item.context = null;
1518
+ item.element = element;
1519
+ item.context = core.components[item.component](item); // stale context across repeated instances without b-observe: re-derive rather than trust the cached value
1520
+ }
1521
+
1522
+ if (item.includable) // the key-path for includables is known when the includable element is included in the tree
1523
+ item.updateKeyPaths();
1524
+
1525
+ if (skipItem(element, item, tree[index - 1]))
1526
+ return next();
1527
+
1528
+ if (!element) // element not available
1529
+ return next();
1530
+
1531
+ if (!item.component && !item.repeatDataKey)
1532
+ item.defineContext(item.context);
1533
+
1534
+ item.resolveLivePaths(false);
1535
+
1536
+ if (update) // check data changes when updating
1537
+ {
1538
+ item.getDataChange();
1539
+
1540
+ if (item.dataChange == null && item.isRendered)
1541
+ {
1542
+ if ((!$bindary.dirtyChecks || checkRecursive(item)) && (!item.repeatDataKey && item.children && item.children.length))
1543
+ {
1544
+ item.element = element;
1545
+ renderView(update, item.children); // check recursive for data changes
1546
+
1547
+ if (item.component && item.context?.postRender)
1548
+ item.context.postRender(item); // always fire postrender for components if rendered, even when there are no data changes, because the component might need to update itself after its children have been updated.
1549
+ }
1550
+
1551
+ return next();
1552
+ }
1553
+ }
1554
+
1555
+ if (!item.repeatDataKey && element && element._bindaryPlaceholder) // replace place-holder for original element
1556
+ {
1557
+ let clone = item.sourceElement.cloneNode(true);
1558
+ element.parentElement.replaceChild(clone, element);
1559
+ item.element = element = clone;
1560
+ setIdAttr(element, item);
1561
+ }
1562
+ else if (!item.repeatDataKey && element)
1563
+ setIdAttr(element, item);
1564
+
1565
+ let itemRenderer = new Renderer(item, element, update, next);
1566
+
1567
+ if (item.repeatDataKey) // repeat element
1568
+ {
1569
+ item.repeatValue = repeatValue = item.getDataValue(item.repeatDataKey, parentContext); // define repeat value
1570
+
1571
+ let emptyRepeater = $lib.isEmpty(item.repeatElements);
1572
+ let repeatExists = !emptyRepeater && $lib.contains((parent) ? parent.element : document, item.repeatElements[0]);
1573
+
1574
+ if (repeatExists && dataChangeIsUpdate(item.dataChange)) // update
1575
+ {
1576
+ $lib.each(item.dataChange, function (dataChange, index, arr, args, next)
1577
+ {
1578
+ let added = 0,
1579
+ reps = item.repeatElements,
1580
+ isArray = $lib.isArray(repeatValue),
1581
+ repContainer = (function () { for (let x in reps) { return reps[x].parentNode; } })();
1582
+
1583
+ if (dataChange.removeCount > 0 && isArray)
1584
+ removeRepeatElements(item, dataChange);
1585
+
1586
+
1587
+ $lib.each(dataChange.keys || [], function (key, index, arr, args, next)
1588
+ {
1589
+ key = key.toString();
1590
+
1591
+ if ($lib.isEmpty(repeatValue?.[key]))
1592
+ {
1593
+ if (reps[key])
1594
+ {
1595
+ $lib.remove(reps[key]);
1596
+ delete reps[key];
1597
+ }
1598
+
1599
+ next();
1600
+ }
1601
+ else
1602
+ {
1603
+ if (!reps[key])
1604
+ item.initRepeater(null, repContainer);
1605
+
1606
+ itemRenderer.next = next;
1607
+ itemRenderer.dataChange = dataChange;
1608
+ itemRenderer.render(repeatValue[key], key);
1609
+ }
1610
+ }, null, null, true, function () // last method
1611
+ {
1612
+ if (dataChange.addCount > 0 && isArray)
1613
+ {
1614
+ let startIndex = ($lib.isEmpty(dataChange.addIndex)) ? repeatValue.length - dataChange.addCount : dataChange.addIndex;
1615
+ item.initRepeater(null, repContainer, reps[startIndex.toString()]);
1616
+
1617
+ // add elements at correct position
1618
+ $lib.each(repeatValue, function (item, key, arr, args, next)
1619
+ {
1620
+ itemRenderer.next = next;
1621
+ itemRenderer.render(item, key);
1622
+ return (++added != dataChange.addCount);
1623
+ }, null, startIndex, true, next);
1624
+ }
1625
+ else
1626
+ next();
1627
+ });
1628
+
1629
+ }, null, null, true, next);
1630
+ }
1631
+ else // recreate
1632
+ {
1633
+ if (item.filter)
1634
+ repeatValue = core.call(item.filter, [item, repeatValue]); // filter collection before repeat (not possbile with update)
1635
+
1636
+ item.dataChange = [];
1637
+ item.initRepeater(element, element.parentNode, element.nextElementSibling, true);
1638
+
1639
+ // recreate elements
1640
+ $lib.each(repeatValue || [], function (item, key, arr, args, next)
1641
+ {
1642
+ itemRenderer.next = next;
1643
+ itemRenderer.render(item, key);
1644
+ }, null, null, true, next);
1645
+ }
1646
+ }
1647
+ else
1648
+ {
1649
+ item.dataChange = [];
1650
+ itemRenderer.render();
1651
+ }
1652
+
1653
+ }, null, null, true, (root) ? core.postRender : null); // each loop with manual fetching and the root-iteration calls the postRender() after the last item.
1654
+ }
1655
+
1656
+ function dataChangeIsUpdate(dataChange)
1657
+ {
1658
+ let update = true;
1659
+
1660
+ if ($lib.isEmpty(dataChange))
1661
+ return false;
1662
+
1663
+ $lib.each(dataChange, function (c)
1664
+ {
1665
+ return (update = (!$lib.isEmpty(c.keys) || !$lib.isEmpty(c.addCount) || !$lib.isEmpty(c.removeCount)));
1666
+ });
1667
+
1668
+ return update;
1669
+ }
1670
+
1671
+ function getElement(item)
1672
+ {
1673
+ let element;
1674
+
1675
+ element = $lib('#' + item.id);
1676
+
1677
+ if (!element && (!item.includable || item.included))
1678
+ {
1679
+ let parent = (item.parent && item.parent.element) ? item.parent.element : null;
1680
+ element = $lib((el) => { return (el.getAttribute(core.attr.id) === item.id) }, parent, item.sourceElement.nodeName, true);
1681
+
1682
+ if (!element && parent) // element inside <template>?
1683
+ {
1684
+ let templates = parent.querySelectorAll('template');
1685
+
1686
+ $lib.each(templates, (t) =>
1687
+ {
1688
+ element = t.content.getElementById(item.id);
1689
+
1690
+ if (element)
1691
+ return false;
1692
+ });
1693
+ }
1694
+ }
1695
+
1696
+ return element;
1697
+ }
1698
+
1699
+ function skipItem(element, item, prevItem)
1700
+ {
1701
+ let conditional = item.else || !$lib.isEmpty(item.elseIf) || !$lib.isEmpty(item.if),
1702
+ skip = false,
1703
+ result;
1704
+
1705
+ if (!conditional)
1706
+ return false;
1707
+
1708
+ const evalIf = () =>
1709
+ {
1710
+ if (item.ifExpr)
1711
+ return !!item.ifExpr(item.expressionTokenResolver.bind(item));
1712
+
1713
+ return !!core.call(item.if, [item]);
1714
+ };
1715
+
1716
+ const evalElseIf = () =>
1717
+ {
1718
+ if (item.elseIfExpr)
1719
+ return !!item.elseIfExpr(item.expressionTokenResolver.bind(item));
1720
+
1721
+ return !!core.call(item.elseIf, [item]);
1722
+ };
1723
+
1724
+ if (item.if)
1725
+ {
1726
+ result = evalIf();
1727
+ skip = !(item.conditionMet = result);
1728
+ }
1729
+ else if (item.elseIf || item.else)
1730
+ {
1731
+ skip = item.conditionMet = prevItem.conditionMet;
1732
+
1733
+ if (!skip)
1734
+ {
1735
+ result = item.elseIf ? evalElseIf() : true;
1736
+ skip = !(item.conditionMet = result);
1737
+ }
1738
+ }
1739
+
1740
+ if (skip && element && !element._bindaryPlaceholder)
1741
+ core.setItemPlaceHolder(item, element);
1742
+
1743
+ return skip;
1744
+ }
1745
+
1746
+ function checkRecursive(item)
1747
+ {
1748
+ if (core.modelDataChanges.size)
1749
+ return true;
1750
+
1751
+ let mustCheck = false,
1752
+ paths = item.resolveLivePaths();
1753
+
1754
+ $lib.each(paths, function (path)
1755
+ {
1756
+ $lib.each(core.dataChanges, function (r, key)
1757
+ {
1758
+ if (core.dataKeyMatch(key, path))
1759
+ mustCheck = true;
1760
+
1761
+ return !mustCheck;
1762
+ });
1763
+
1764
+ return !mustCheck;
1765
+ });
1766
+
1767
+ return mustCheck;
1768
+ }
1769
+
1770
+ function removeRepeatElements(item, dataChange)
1771
+ {
1772
+ let reps = item.repeatElements;
1773
+
1774
+ for (let index = 0; index < dataChange.removeCount; ++index)
1775
+ {
1776
+ let key = (dataChange.removeIndex + index).toString();
1777
+
1778
+ $lib.remove(reps[key]);
1779
+ delete reps[key];
1780
+ }
1781
+ }
1782
+
1783
+ function updateTemplateItem(el, deep, clear)
1784
+ {
1785
+ let item = getTemplateItem(el), parent, parentItem;
1786
+
1787
+ if (item)
1788
+ item.parseTemplateElement(el, getEvents(el), null, item.parent);
1789
+ else
1790
+ {
1791
+ parent = el.parentNode;
1792
+
1793
+ while (parent)
1794
+ {
1795
+ parentItem = getTemplateItem(parent);
1796
+
1797
+ if (parentItem != null)
1798
+ {
1799
+ item = new TemplateItem(el, getEvents(el), null, parentItem);
1800
+ parent = null;
1801
+ }
1802
+ else
1803
+ parent = parent.parentNode;
1804
+ }
1805
+
1806
+ if (!parentItem)
1807
+ item = new TemplateItem(el, getEvents(el), null);
1808
+ }
1809
+
1810
+ if (clear)
1811
+ {
1812
+ removeTemplateItemTree(item);
1813
+ }
1814
+
1815
+ if (deep)
1816
+ createViewTree(el, item);
1817
+
1818
+ core.templateUpdate = true;
1819
+ return item;
1820
+ }
1821
+
1822
+ function removeTemplateItemTree(item)
1823
+ {
1824
+ if (!item || !Array.isArray(item.children)) return;
1825
+
1826
+ item.children.forEach(child =>
1827
+ {
1828
+ // Recursively remove all descendants
1829
+ removeTemplateItemTree(child);
1830
+
1831
+ const index = $lib.indexOf(core.templateList, i => i.id === child.id); // Remove child from templateList
1832
+
1833
+ if (index > -1)
1834
+ core.templateList.splice(index, 1);
1835
+ });
1836
+
1837
+ item.children = [];
1838
+ }
1839
+
1840
+ function getTemplateItem(node)
1841
+ {
1842
+ let id = (node.getAttribute) ? node.getAttribute(core.attr.id) || node.id : node.id;
1843
+
1844
+ if ($lib.isEmpty(id))
1845
+ return null;
1846
+
1847
+ let index = $lib.indexOf(core.templateList, function (i)
1848
+ {
1849
+ return (i.id === id);
1850
+ });
1851
+
1852
+ if (index > -1)
1853
+ return core.templateList[index];
1854
+ else
1855
+ return null;
1856
+ }
1857
+
1858
+ function createViewTree(root, rootItem)
1859
+ {
1860
+ let el, events, includes = [], index;
1861
+
1862
+ if (!root)
1863
+ {
1864
+ core.templateList = [];
1865
+ core.viewTree = [];
1866
+ }
1867
+
1868
+ iterate((!$bindary.detectOutsideView) ? core.getViewContainer() : root || document.documentElement, rootItem, includes);
1869
+
1870
+ for (index = 0; index < includes.length; ++index)
1871
+ {
1872
+ $lib.remove(includes[index]);
1873
+ }
1874
+
1875
+ for (index = 0; index < core.templateList.length; ++index)
1876
+ {
1877
+ el = core.templateList[index].sourceElement;
1878
+
1879
+ if (!el || core.keepAttributes(el))
1880
+ continue;
1881
+
1882
+ events = getEvents(el);
1883
+
1884
+ // clear stored attributes
1885
+ $lib.each(core.attr, function (item, key)
1886
+ {
1887
+ if (key != 'view' && key != 'id')
1888
+ el.removeAttribute(item);
1889
+ });
1890
+
1891
+ $lib.each(events, function (value, event)
1892
+ {
1893
+ el.removeAttribute(core.attr.on + '-' + event);
1894
+ });
1895
+ }
1896
+ }
1897
+
1898
+ function iterate(parent, parentItem, includes)
1899
+ {
1900
+ let node = (parent.nodeName == 'TEMPLATE' && parent.content) ? parent.content.firstChild : parent.firstChild, events, attributes, item, nodeType, obj;
1901
+
1902
+ while (node)
1903
+ {
1904
+ nodeType = node.nodeType;
1905
+ obj = (nodeType == 1) ? getEventsAndAttributes(node) : null;
1906
+ events = (nodeType == 1) ? obj.events : null;
1907
+ attributes = (nodeType == 1) ? obj.attributes : null;
1908
+ item = parentItem;
1909
+
1910
+ if (nodeType != 1 || !node.hasAttribute(core.attr.ignore))
1911
+ {
1912
+ const hasContentExpr = (nodeType == 1) ? core.resolveContentExpression(node) : false;
1913
+
1914
+ if (nodeType == 1 && (hasTemplateAttribute(node) || !$lib.isEmpty(events) || !$lib.isEmpty(attributes)) || hasContentExpr)
1915
+ {
1916
+ if (item = getTemplateItem(node))
1917
+ item.parseTemplateElement(node, events, attributes, item.parent); // update
1918
+ else
1919
+ item = new TemplateItem(node, events, attributes, parentItem);
1920
+
1921
+ if (item.includableRoot)
1922
+ includes.push(node);
1923
+ }
1924
+
1925
+ if (nodeType == 1)
1926
+ iterate(node, item, includes);
1927
+ }
1928
+
1929
+ node = node.nextSibling;
1930
+ }
1931
+ }
1932
+
1933
+ function getEventsAndAttributes(el)
1934
+ {
1935
+ let attr, events = {}, attributes = {};
1936
+
1937
+ for (let index = 0; index < el.attributes.length; ++index)
1938
+ {
1939
+ attr = el.attributes[index];
1940
+
1941
+ if ($lib.startsWith(attr.nodeName, core.attr.on))
1942
+ events[attr.nodeName.substr(core.attr.on.length + 1)] = attr.value;
1943
+ else if (attr.name.indexOf(core.prefix) != 0 && core.hasExpression(attr.value))
1944
+ attributes[attr.name] = attr.value;
1945
+ }
1946
+
1947
+ return { events: events, attributes: attributes };
1948
+ }
1949
+
1950
+ function getEvents(el)
1951
+ {
1952
+ let attr, events = {};
1953
+
1954
+ for (let index = 0; index < el.attributes.length; ++index)
1955
+ {
1956
+ attr = el.attributes[index];
1957
+
1958
+ if ($lib.startsWith(attr.nodeName, core.attr.on))
1959
+ events[attr.nodeName.substr(core.attr.on.length + 1)] = attr.value;
1960
+ }
1961
+
1962
+ return events;
1963
+ }
1964
+
1965
+ function hasTemplateAttribute(el)
1966
+ {
1967
+ const ignoreKeys = ['view', 'id', 'keep', 'live'];
1968
+
1969
+ for (let key in core.attr)
1970
+ {
1971
+ if (!ignoreKeys.includes(key) && el.hasAttribute(core.attr[key]))
1972
+ {
1973
+ return true;
1974
+ }
1975
+ }
1976
+
1977
+ return false;
1978
+ }
1979
+
1980
+ $lib.on(document, 'visibilitychange', visibilityChange);
1981
+ $lib.on(window, 'scroll', scroll);
1982
+ $lib.on(window, 'popstate', popState);
1983
+ $lib.on(window, 'pageshow', (e) =>
1984
+ {
1985
+ if (e.persisted && $bindary.reloadRouteOnPageRestore) // restored from the back/forward cache
1986
+ router.load(core.getRoutePath());
1987
+ });
1988
+ $lib.on(window, 'pagehide', () =>
1989
+ {
1990
+ if (!$lib.isEmpty($bindary.routeIndex))
1991
+ router.unload($bindary.routeIndex, $bindary.routePath, true);
1992
+ });
1993
+
1994
+ $.ready(async () =>
1995
+ {
1996
+ await componyx.bindary_modules.loaded;
1997
+
1998
+ $.defer(() =>
1999
+ {
2000
+ if ($bindary.launchOnLoad)
2001
+ launch();
2002
+ });
2003
+ });
2004
+
2005
+ componyx.bindary.TemplateItem = componyx.bindary_modules.TemplateItem;
2006
+
2007
+ })(window);
2008
+
2009
+ const bindary = componyx.bindary;
2010
+
2011
+ export { bindary };