@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,2748 @@
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 '../Button/Button.js';
5
+ import '../FormField/FormField.js';
6
+ import '../Box/Box.js';
7
+ import '../Menu/Menu.js';
8
+
9
+ (function (window)
10
+ {
11
+ /**
12
+ * AppSkin class.
13
+ * @class
14
+ * @memberof componyx.UI
15
+ * @augments componyx.UI.base.Component
16
+ * @mixes componyx.UI.base.methods
17
+ * @param {String} id The id of the component.
18
+ * @param {Object|HTMLElement} properties The properties used to initialize the component or the container element.
19
+ * @property {componyx.UI.base.AjaxMethod} ajax.authenticate - AJAX method used to authenticate.
20
+ * @property {componyx.UI.base.AjaxMethod} ajax.search - AJAX method used to load search results.
21
+ * @returns {componyx.UI.AppSkin} An instance of the component.
22
+ */
23
+ componyx.UI.AppSkin = function AppSkin(id, properties)
24
+ {
25
+ window.$appskin = this;
26
+
27
+ // define private properties
28
+ let _instance = this,
29
+ _screenOption = componyx.UI.AppSkin.ScreenOption,
30
+ _menuTypeOption = componyx.UI.AppSkin.MenuTypeOption,
31
+ _footerDisplayOption = componyx.UI.AppSkin.FooterDisplayOption,
32
+ _menuSectionOption = componyx.UI.AppSkin.MenuSectionOption,
33
+ _routePathMatchingOption = componyx.UI.AppSkin.RoutePathMatchingOption,
34
+ _failureReasonOption = componyx.UI.AppSkin.FailureReasonOption,
35
+ _tileSizeOption = componyx.UI.AppSkin.TileSizeOption,
36
+ _menuButton, _login, _loginVerification, _topbar, _page, _header, _footer, _sideMenuBox, _mainMenu, _headerMenu, _footerMenu, _quickLaunchMenu, _searchBox, _searchResult,
37
+ _user, _pass, _remember, _loginFailedMessage, _verificationFailedMessage, _verificationCode, _activeScreen, _view, _error, _overlay, _allInHeader = true, _errorData,
38
+ _preloader, _launch = true, _resizeTimerId, _headerOverflowTimerId, _searchTimerId, _windowWidth, _classOption;
39
+
40
+ /**
41
+ * Internal CSS class name constants.
42
+ * You can override any of these classes on the Component instance by defining a property named
43
+ * `cssClass<Key>` where <Key> is the PascalCase key from this object.
44
+ * Example:
45
+ * 'SEARCH_RESULT' -> 'cssClassSearchResult'
46
+ * Be cautious: overriding these classes without including the default names may break styling and functionality.
47
+ * @constant
48
+ * @type {Readonly<Object<string, string>>}
49
+ */
50
+ this.classOption = Object.freeze(
51
+ {
52
+ FULL: 'full',
53
+ ERROR: 'error',
54
+ TOPBAR: 'topbar',
55
+ FOOTER: 'footer',
56
+ REQUIRED: 'required',
57
+ MENU_LEFT: 'menu-left',
58
+ AUTHENTICATED: 'authenticated',
59
+ SEARCH_ACTIVE: 'search-active',
60
+ PORTAL_MENU: 'portal-menu',
61
+ SIDE_MENU: 'side-menu',
62
+ LOGIN: 'login',
63
+ VERIFICATION: 'verification',
64
+ HEADER_ALL: 'header-all',
65
+ HEADER_MENU_HIDDEN: 'header-menu-hidden',
66
+ HEADER_OVERFLOW: 'header-overflow',
67
+ BODY: 'body',
68
+ PAGE: 'page',
69
+ VIEW: 'view',
70
+ OVERLAY: 'overlay',
71
+ SIDEBAR: 'sidebar',
72
+ APP_CONTENT: 'app-content',
73
+ SEARCH: 'search',
74
+ SEARCH_RESULT: 'search-result',
75
+ SPINNER: 'spinner',
76
+ BOX: 'box',
77
+ PRELOADER: 'preloader',
78
+ MENU: 'menu',
79
+ MAIN: 'main',
80
+ SIDE: 'side',
81
+ PORTAL: 'portal',
82
+ HEADER_MENU: 'header-menu',
83
+ QUICK_LAUNCH: 'quick-launch',
84
+ FOOTER_MENU: 'footer-menu',
85
+ BUTTON: 'button',
86
+ MENU_BUTTON: 'menu-button',
87
+ ACCOUNT: 'account',
88
+ ACCOUNT_AVATAR: 'account-avatar',
89
+ NO_ACTION: 'no-action',
90
+ SUB_GROUP: 'sub-group',
91
+ ROW_START: 'row-start',
92
+ SHADE: 'shade',
93
+ LOGIN_SCREEN: 'login-screen',
94
+ LOGIN_VERIFICATION: 'login-verification',
95
+ CONTENT: 'content',
96
+ USER: 'user',
97
+ PASS: 'pass',
98
+ REMEMBER: 'remember',
99
+ CODE: 'code',
100
+ MESSAGE: 'message',
101
+ ICON_MENU: 'ico-menu ico-m',
102
+ ICON_USER: 'ico-user',
103
+ ICON_LOCK: 'ico-lock',
104
+ ICON_KEY: 'ico-key',
105
+ ICON_EYE: 'ico-eye',
106
+ ICON_EYE_BLOCKED: 'ico-eye-blocked'
107
+ });
108
+
109
+ /**
110
+ * Gets the css class if it exists and otherwise the default css class.
111
+ * @param {String} cssClassValue The default css class (a value of the classOption object).
112
+ * @returns {String} The css class.
113
+ */
114
+ this.getCssClass = function (cssClassValue)
115
+ {
116
+ return $base.methods.getCssClass.call(_instance, _instance.classOption, cssClassValue);
117
+ };
118
+
119
+ // define public properties
120
+ /**
121
+ * Gets or sets a value indicating if authentication is required.
122
+ * @type {Boolean}
123
+ */
124
+ this.authenticationRequired = true;
125
+
126
+ /**
127
+ * Gets a value indicating if the user is authenticated.
128
+ * @type {Boolean}
129
+ */
130
+ this.authenticated = false;
131
+
132
+ /**
133
+ * Gets a value indicating if the menu is displayed on app launch.
134
+ * @type {Boolean}
135
+ */
136
+ this.launchMenu = true;
137
+
138
+ /**
139
+ * Gets a value indicating if the menu button is displayed when the app-skin login page is active.
140
+ * @type { Boolean }
141
+ */
142
+ this.hideMenuButtonOnLogin = false;
143
+
144
+ /**
145
+ * Gets or sets a value indicating if the contents of the app skin will utilize the full-screen width.
146
+ * @type {Boolean}
147
+ */
148
+ this.fullscreen = false;
149
+
150
+ /**
151
+ * Gets or sets a value indicating if the page content utilizes the full-screen width when not in full-screen mode.
152
+ * @type {Boolean}
153
+ */
154
+ this.fullpage = false;
155
+
156
+ /**
157
+ * Gets or sets a value indicating if the rememeber me option is available for manual login.
158
+ * @type {Boolean}
159
+ */
160
+ this.showRememberMe = false;
161
+
162
+ /**
163
+ * Gets or sets a value indicating if the top-bar in the header is always visible (true) or is hidden when the portal-menu is active (false).
164
+ * @type {Boolean}
165
+ */
166
+ this.persistentTopBar = false;
167
+
168
+ /**
169
+ * Gets or sets a value indicating if the menu button is always visible (true) or is hidden when the all menu-items are visible in the header-menu.
170
+ * @type {Boolean}
171
+ */
172
+ this.persistentMenuButton = false;
173
+
174
+ /**
175
+ * Gets or sets a value indicating if the side-menu displays at the right side (false) or the left side (true) of the screen.
176
+ * @type {Boolean}
177
+ */
178
+ this.menuLeft = false;
179
+
180
+ /**
181
+ * Gets or sets a value indicating if the account button is displayed as avatar when the user is authenticated.
182
+ * @type {Boolean}
183
+ */
184
+ this.accountButtonAvatar = false;
185
+
186
+ /**
187
+ * Gets or sets a value indicating if the login template is created when rendering the app skin.
188
+ * @type {Boolean}
189
+ */
190
+ this.useLoginTemplate = true;
191
+
192
+ /**
193
+ * Gets or sets a value indicating if the Bindary framework is used for loading routes.
194
+ * @type {Boolean}
195
+ */
196
+ this.useBindary = true;
197
+
198
+ /**
199
+ * Gets or sets the menu navigation type (portal-menu or side-menu).
200
+ * @type {componyx.UI.AppSkin.MenuTypeOption}
201
+ */
202
+ this.menuType = _menuTypeOption.PORTALMENU;
203
+
204
+ /**
205
+ * Gets or sets a value indicating if menu-items are automatically selected if the item's route-path (partially) matches the current URL route-path.
206
+ * @type {componyx.UI.AppSkin.RoutePathMatchingOption}
207
+ */
208
+ this.menuRoutePathMatching = _routePathMatchingOption.PARTIAL;
209
+
210
+ /**
211
+ * Gets or sets a value indicating when the footer is displayed.
212
+ * @type {componyx.UI.AppSkin.FooterDisplayOption}
213
+ */
214
+ this.footerDisplay = _footerDisplayOption.MENU;
215
+
216
+ /**
217
+ * Gets or sets the delay in milliseconds before the typed letter(s) in the search-box are sent to the server.
218
+ * @type {Number}
219
+ */
220
+ this.searchDelay = 100;
221
+
222
+ /**
223
+ * Gets or sets the search-box placeholder label.
224
+ * @type {String}
225
+ */
226
+ this.searchLabel = 'Search';
227
+
228
+ /**
229
+ * Gets or sets the label when no search results are found.
230
+ * @type {String}
231
+ */
232
+ this.searchNoResultLabel = 'No results found.';
233
+
234
+ /**
235
+ * Gets or sets the category labels for the search-results. Use empty string key for root label.
236
+ * @type {Object}
237
+ */
238
+ this.searchCategoryLabels = {};
239
+
240
+ /**
241
+ * Gets or sets the action of the account button.
242
+ * @type {Function|String}
243
+ */
244
+ this.accountAction = null;
245
+
246
+ /**
247
+ * Gets or sets the account button avatar initials.
248
+ * @type {String}
249
+ */
250
+ this.avatarInitials = '';
251
+
252
+ /**
253
+ * Gets or sets the account button avatar image path.
254
+ * @type {String}
255
+ */
256
+ this.avatarImagePath = '';
257
+
258
+ /**
259
+ * Gets or sets the Loading text label.
260
+ * @type {String}
261
+ */
262
+ this.loadingLabel = 'Loading...';
263
+
264
+ /**
265
+ * Gets or sets the text label of the main category.
266
+ * @type {String}
267
+ */
268
+ this.mainCategoryLabel = '';
269
+
270
+ /**
271
+ * Gets or sets the username of the authenticated user.
272
+ * @type {String}
273
+ */
274
+ this.username = 'Not logged in';
275
+
276
+ /**
277
+ * Gets or sets the Username text label.
278
+ * @type {String}
279
+ */
280
+ this.usernameLabel = 'USERNAME';
281
+
282
+ /**
283
+ * Gets or sets the Password text label.
284
+ * @type {String}
285
+ */
286
+ this.passwordLabel = 'PASSWORD';
287
+
288
+ /**
289
+ * Gets or sets the RememberMe text label.
290
+ * @type {String}
291
+ */
292
+ this.rememberMeLabel = 'Remember me';
293
+
294
+ /**
295
+ * Gets or sets the text label of the login button.
296
+ * @type {String}
297
+ */
298
+ this.loginButtonLabel = 'LOGIN';
299
+
300
+ /**
301
+ * Gets or sets the default label for an authentication failure.
302
+ * @type {String}
303
+ */
304
+ this.authenticationFailureLabel = 'Authentication failed';
305
+
306
+ /**
307
+ * Gets or sets the labels for the authentication failure reasons.
308
+ * @type {String[]}
309
+ */
310
+ this.authenticationFailureReasonLabels = [
311
+ 'CRSF-Token mismatch.',
312
+ 'Maximum sign-in attempts exceeded.',
313
+ 'Username and/or parssword is incorrect.',
314
+ 'Verification code is incorrect.',
315
+ 'The verification code has expired.',
316
+ 'Maximum verification attempts exceeded.',
317
+ 'Authentication failed.'
318
+ ];
319
+
320
+ /**
321
+ * Gets or sets the text label of the login-verification button.
322
+ * @type {String}
323
+ */
324
+ this.verificationButtonLabel = 'VERIFY';
325
+
326
+ /**
327
+ * Gets or sets the text label of the login verification code.
328
+ * @type {String}
329
+ */
330
+ this.verificationCodeLabel = 'Verification Code';
331
+
332
+ /**
333
+ * Gets or sets the input field length of the login verification code.
334
+ * @type {Number}
335
+ */
336
+ this.verificationCodeLength = 6;
337
+
338
+ /**
339
+ * Gets or sets the delay in milliseconds before the app is reloaded when the login verification failed.
340
+ * @type {Number}
341
+ */
342
+ this.verificationFailedReloadDelay = 5000;
343
+
344
+ /**
345
+ * Gets or sets the CRSF Token to send with autorisation POST actions.
346
+ * @type {String|null}
347
+ */
348
+ this.crsfToken = null;
349
+
350
+ /**
351
+ * Gets or sets the list of menu-items.
352
+ * @type {componyx.UI.AppSkin.MenuItem[]}
353
+ */
354
+ this.menu = [];
355
+
356
+ /**
357
+ * Gets or sets the id of the menu component used as base for the main menu.
358
+ * @type {String|null}
359
+ */
360
+ this.mainMenuId = null;
361
+
362
+ /**
363
+ * Gets or sets the id of the menu component used as base for the header menu.
364
+ * @type {String|null}
365
+ */
366
+ this.headerMenuId = null;
367
+
368
+ /**
369
+ * Gets or sets the id of the menu component used as base for the footer menu.
370
+ * @type {String|null}
371
+ */
372
+ this.footerMenuId = null;
373
+
374
+ /**
375
+ * Gets or sets the id of the menu component used as base for the quick launch menu.
376
+ * @type {String|null}
377
+ */
378
+ this.quickLaunchMenuId = null;
379
+
380
+ /**
381
+ * Gets or sets the id of the button component used as base for the menu button.
382
+ * @type {String|null}
383
+ */
384
+ this.menuButtonId = null;
385
+
386
+ /**
387
+ * Gets or sets the id of the button component used as base for the login button.
388
+ * @type {String|null}
389
+ */
390
+ this.loginButtonId = null;
391
+
392
+ /**
393
+ * Gets or sets the id of the button component used as base for the login-verification button.
394
+ * @type {String|null}
395
+ */
396
+ this.verificationButtonId = null;
397
+
398
+ /**
399
+ * Gets or sets the id of the button component used as base for the account button.
400
+ * @type {String|null}
401
+ */
402
+ this.accountButtonId = null;
403
+
404
+ /**
405
+ * Gets or sets the id of the form-field component used as base for the username form field.
406
+ * @type {String|null}
407
+ */
408
+ this.usernameFormFieldId = null;
409
+
410
+ /**
411
+ * Gets or sets the id of the form-field component used as base for the password form field.
412
+ * @type {String|null}
413
+ */
414
+ this.passwordFormFieldId = null;
415
+
416
+ /**
417
+ * Gets or sets the id of the form-field component used as base for the remember-me form field.
418
+ * @type {String|null}
419
+ */
420
+ this.rememberMeFormFieldId = null;
421
+
422
+ /**
423
+ * Gets or sets the id of the form-field component used as base for the verification code form field.
424
+ * @type {String|null}
425
+ */
426
+ this.verificationCodeFormFieldId = null;
427
+
428
+ /**
429
+ * @typedef {Object} AuthenticationResponseEventArgs
430
+ * @memberof componyx.UI.AppSkin
431
+ * @property {AuthenticationStateOption} authenticationState - The authentication state.
432
+ * @property {FailureReasonOption} failureReason - The reason why the authentication attempt failed.
433
+ * @property {String} failureMessage - The failure message.
434
+ * @property {Boolean} [cancel=false] - A value indicating if the default action of the AppSkin component should be canceled.
435
+ */
436
+
437
+ /**
438
+ * @typedef {Object} ResizeActionEventArgs
439
+ * @memberof componyx.UI.AppSkin
440
+ * @property {Boolean} headerOverflow - A value indicating if the content in the header caused an overflow.
441
+ * @property {Boolean} [cancel=false] - A value indicating if the default action of the AppSkin component should be canceled.
442
+ */
443
+
444
+ /**
445
+ * @typedef {Object} SearchResultsEventArgs
446
+ * @memberof componyx.UI.AppSkin
447
+ * @property {Array<Object>} data - The search results.
448
+ * @property {HTMLElement} container - The search results container element.
449
+ * @property {Boolean} [cancel=false] - A value indicating if the default action of the AppSkin component should be canceled.
450
+ */
451
+
452
+ /**
453
+ * @typedef {Object} SearchResultEventArgs
454
+ * @memberof componyx.UI.AppSkin
455
+ * @property {Array<Object>} data - The search results.
456
+ * @property {HTMLElement} container - The search results container element.
457
+ * @property {HTMLElement} [anchor] - The seach result element.
458
+ * @property {Object} [item] - The seach result item.
459
+ */
460
+
461
+ /**
462
+ * @class
463
+ * @augments componyx.UI.base.Events
464
+ * @memberof componyx.UI.AppSkin
465
+ * @property {componyx.UI.base.Event} onShowLogin - Event which fires when the login-screen is shown.
466
+ * @property {componyx.UI.base.Event} onShowLoginVerification - Event which fires when the login-verification-screen is shown.
467
+ * @property {componyx.UI.base.Event} onShowPage - Event which fires when the page-screen is shown.
468
+ * @property {componyx.UI.base.Event} onShowError - Event which fires when the error-screen is shown.
469
+ * @property {componyx.UI.base.Event} onShowMenu - Event which fires when the menu is shown.
470
+ * @property {componyx.UI.base.Event} onHideMenu - Event which fires when the menu is hidden.
471
+ * @property {componyx.UI.base.Event} onLoginAttempt - Event which fires when a login attempt is made. @see {@link componyx.UI.AppSkin.AuthenticationResponseEventArgs}
472
+ * @property {componyx.UI.base.Event} onLoginVerificationAttempt - Event which fires when a login verification attempt is made. @see {@link componyx.UI.AppSkin.AuthenticationResponseEventArgs}
473
+ * @property {componyx.UI.base.Event} onLogout - Event which fires when the user logged out. @see {@link componyx.UI.AppSkin.AuthenticationResponseEventArgs}
474
+ * @property {componyx.UI.base.Event} onPreSearchResults - Event which fires before the search results are rendered. @see {@link componyx.UI.AppSkin.SearchResultsEventArgs}
475
+ * @property {componyx.UI.base.Event} onRenderSearchResult - Event which fires when a search result link is rendered. @see {@link componyx.UI.AppSkin.SearchResultEventArgs}
476
+ * @property {componyx.UI.base.Event} onPostSearchResults - Event which fires after the search results are rendered. @see {@link componyx.UI.AppSkin.SearchResultsEventArgs}
477
+ * @property {componyx.UI.base.Event} onResize - Event which fires when the window is resized. @see {@link componyx.UI.AppSkin.ResizeActionEventArgs}
478
+ * @see {@link componyx.UI.base.Events}
479
+ */
480
+ function AppSkinEvents(events)
481
+ {
482
+ Object.assign(this, events);
483
+
484
+ this.onShowLogin = $base.static.createEvent('onShowLogin');
485
+ this.onShowLoginVerification = $base.static.createEvent('onShowLoginVerification');
486
+ this.onShowPage = $base.static.createEvent('onShowPage');
487
+ this.onShowError = $base.static.createEvent('onShowError');
488
+ this.onShowMenu = $base.static.createEvent('onShowMenu');
489
+ this.onHideMenu = $base.static.createEvent('onHideMenu');
490
+ this.onLoginAttempt = $base.static.createEvent('onLoginAttempt');
491
+ this.onLoginVerificationAttempt = $base.static.createEvent('onLoginVerificationAttempt');
492
+ this.onLogout = $base.static.createEvent('onLogout');
493
+ this.onPreSearchResults = $base.static.createEvent('onPreSearchResults');
494
+ this.onRenderSearchResult = $base.static.createEvent('onRenderSearchResult');
495
+ this.onPostSearchResults = $base.static.createEvent('onPostSearchResults');
496
+ this.onResize = $base.static.createEvent('onResize');
497
+ }
498
+ /**
499
+ * AppSkin events
500
+ * @type {componyx.UI.AppSkin.AppSkinEvents}
501
+ */
502
+ this.events = new AppSkinEvents(this.events);
503
+
504
+ // inherit base instance members
505
+ $base.Component.apply(this, [id, properties]);
506
+ _classOption = this.classOption;
507
+
508
+ // define ajax methods
509
+ this.ajax.addMethod('authenticate');
510
+ this.ajax.addMethod('search');
511
+
512
+ /**
513
+ * Sets the header template. This template supports the below listed interpolations. Default value: {logo}{quickLaunchMenu}{menuButton}{topbar}{menu}{/topbar}
514
+ * - {topbar} This value will be replaced with the opening topbar element tag.
515
+ * - {/topbar} This value will be replaced with the closing topbar element tag.
516
+ * - {logo} This value will be replaced with the figure element.
517
+ * - {menuButton} This value will be replaced with the menu button.
518
+ * - {accountButton} When authenticated this value will be replaced with the account button otherwise it will open the login screen.
519
+ * - {menu} This value will be replaced with the header menu, containing all menu-items with menuSection set to either MAIN_HEADER or HEADER.
520
+ * - {quickLaunchMenu} This value will be replaced with the quick-launch menu, containing all menu-items with menuSection set to QUICKLAUNCH
521
+ * - {username} This value will be replaced with the username of the logged in user.
522
+ * - {searchBox} This value will be replaced with the search-box.
523
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The HTML template.
524
+ */
525
+ this.setHeaderTemplate = function (content)
526
+ {
527
+ _instance.addTemplate('Header', content, false);
528
+ };
529
+
530
+ /**
531
+ * Sets the footer template. This template supports the below listed interpolations. Default value: {menu}{username}{loginButton}
532
+ * - {logo} This value will be replaced with the figure element.
533
+ * - {accountButton} When authenticated this value will be replaced with the account button otherwise it will open the login screen.
534
+ * - {menu} This value will be replaced with the footer menu, containing all menu-items with menuSection set to FOOTER.
535
+ * - {quickLaunchMenu} This value will be replaced with the quick-launch menu, containing all menu-items with menuSection set to QUICKLAUNCH
536
+ * - {username} This value will be replaced with the username of the logged in user.
537
+ * - {searchBox} This value will be replaced with the search-box.
538
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The HTML template.
539
+ */
540
+ this.setFooterTemplate = function (content)
541
+ {
542
+ _instance.addTemplate('Footer', content, false);
543
+ };
544
+
545
+ /**
546
+ * Sets the sidebar template. This template supports the below listed interpolations.
547
+ * - {logo} This value will be replaced with the figure element.
548
+ * - {menuButton} This value will be replaced with the menu button.
549
+ * - {accountButton} When authenticated this value will be replaced with the account button otherwise it will open the login screen.
550
+ * - {quickLaunchMenu} This value will be replaced with the quick-launch menu, containing all menu-items with menuSection set to QUICKLAUNCH
551
+ * - {username} This value will be replaced with the username of the logged in user.
552
+ * - {searchBox} This value will be replaced with the search-box.
553
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The HTML template.
554
+ */
555
+ this.setSidebarTemplate = function (content)
556
+ {
557
+ _instance.addTemplate('SidebarLeft', content, false);
558
+ };
559
+
560
+ /**
561
+ * Sets the login template. This template supports the below listed interpolations. Default value: {logo}{username}{password}{rememberMe}{loginButton}{failedMessage}
562
+ * - {logo} This value will be replaced with the figure element.
563
+ * - {username} This value will be replaced with the username input field.
564
+ * - {password} This value will be replaced with the password input field.
565
+ * - {rememberMe} This value will be replaced with the remember-me checkbox.
566
+ * - {loginButton} This value will be replaced with the login button.
567
+ * - {failedMessage} This value will be replaced with the message displayed when a login attempt failed.
568
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The HTML template.
569
+ */
570
+ this.setLoginTemplate = function (content)
571
+ {
572
+ _instance.addTemplate('Login', content, false);
573
+ };
574
+
575
+ /**
576
+ * Sets the login verification template. This template supports the below listed interpolations. Default value: {logo}{verificationCode}{verificationButton}{failedMessage}
577
+ * - {logo} This value will be replaced with the figure element.
578
+ * - {verificationCode} This value will be replaced with the verification-code input field.
579
+ * - {verificationButton} This value will be replaced with the verification button.
580
+ * - {failedMessage} This value will be replaced with the message displayed when a verification attempt failed.
581
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The HTML template.
582
+ */
583
+ this.setLoginVerificationTemplate = function (content)
584
+ {
585
+ _instance.addTemplate('LoginVerification', content, false);
586
+ };
587
+
588
+ /**
589
+ * Sets the error template. The error template supports the below listed interpolations.
590
+ * - {message} This value will be replaced with the error message.
591
+ * - {timestamp} This value will be replaced with the error timestamp (optional).
592
+ * - {id} This value will be replaced with the error identifier (optional).
593
+ * - {stackTrace} This value will be replaced with the error stack trace (optional).
594
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The HTML template.
595
+ */
596
+ this.setErrorTemplate = function (content)
597
+ {
598
+ _instance.addTemplate('Error', content, false);
599
+ };
600
+
601
+ /**
602
+ * Sets the not found template, shown when the requested page does not exist: no route matches the URL, or the page request returns 404 or 410. The not found template supports the below listed interpolations.
603
+ * - {status} This value will be replaced with the HTTP status of the page request, when there was one (optional).
604
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The HTML template.
605
+ */
606
+ this.setNotFoundTemplate = function (content)
607
+ {
608
+ _instance.addTemplate('NotFound', content, false);
609
+ };
610
+
611
+ /**
612
+ * Sets the page header template.
613
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The HTML template.
614
+ */
615
+ this.setPageHeaderTemplate = function (content)
616
+ {
617
+ _instance.addTemplate('PageHeader', content, false);
618
+ };
619
+
620
+ /**
621
+ * Sets the page footer template.
622
+ * @param {HTMLElement|HTMLElement[]|DocumentFragment|String} content The HTML template.
623
+ */
624
+ this.setPageFooterTemplate = function (content)
625
+ {
626
+ _instance.addTemplate('PageFooter', content, false);
627
+ };
628
+
629
+ /**
630
+ * Sets a value indicating if the app skin utilizes the full width of the screen.
631
+ * @param {Boolean} value A value indicating if the appskin utilizes the full width of the screen.
632
+ */
633
+ this.setFullscreen = function (value)
634
+ {
635
+ if (value)
636
+ $lib.addClass(_instance.element, _instance.getCssClass(_classOption.FULL));
637
+ else
638
+ $lib.removeClass(_instance.element, _instance.getCssClass(_classOption.FULL));
639
+ };
640
+
641
+ /**
642
+ * Sets a value indicating if the page content utilizes the full-screen width when not in full-screen mode.
643
+ * @param {Boolean} value A value indicating if the page content container utilizes the full width of the screen.
644
+ */
645
+ this.setFullpage = function (value)
646
+ {
647
+ if (value)
648
+ $lib.addClass(_view.firstChild, _instance.getCssClass(_classOption.FULL));
649
+ else
650
+ $lib.removeClass(_view.firstChild, _instance.getCssClass(_classOption.FULL));
651
+ };
652
+
653
+ /**
654
+ * Shows the intial page based upon the defined properties.
655
+ */
656
+ this.launch = function ()
657
+ {
658
+ launch();
659
+ };
660
+
661
+ /**
662
+ * Updates the menu state to the active route path. The corresponding menu items are selected / expanded based on the menuRoutePathMatching setting.
663
+ * @param {String} routePath The active route path. The route path may end with a page anchor.
664
+ * @param {String} [hashtagRoutes] A value indicating if hashtag routes are used, in other words, the route-path follows the first hashtag.
665
+ */
666
+ this.updateMenuState = function (routePath, hashtagRoutes)
667
+ {
668
+ updateMenuState(routePath, hashtagRoutes);
669
+ };
670
+
671
+ /**
672
+ * Gets the active screen.
673
+ * @returns {componyx.UI.AppSkin.ScreenOption} The active screen.
674
+ */
675
+ this.activeScreen = function ()
676
+ {
677
+ return _activeScreen;
678
+ };
679
+
680
+ /**
681
+ * Gets the main menu.
682
+ * @returns {componyx.UI.Menu} The main menu component.
683
+ */
684
+ this.getMainMenu = function ()
685
+ {
686
+ return _mainMenu;
687
+ };
688
+
689
+ /**
690
+ * Gets the header menu.
691
+ * @returns {componyx.UI.Menu} The header menu component.
692
+ */
693
+ this.getHeaderMenu = function ()
694
+ {
695
+ return _headerMenu;
696
+ };
697
+
698
+ /**
699
+ * Gets the footer menu.
700
+ * @returns {componyx.UI.Menu} The footer menu component.
701
+ */
702
+ this.getFooterMenu = function ()
703
+ {
704
+ return _footerMenu;
705
+ };
706
+
707
+ /**
708
+ * Gets the quick-launch menu.
709
+ * @returns {componyx.UI.Menu} The quick-launch menu component.
710
+ */
711
+ this.getQuickLaunchMenu = function ()
712
+ {
713
+ return _quickLaunchMenu;
714
+ };
715
+
716
+ /**
717
+ * Gets the preloader.
718
+ * @returns {componyx.UI.Box} The preloader box component.
719
+ */
720
+ this.preloader = function ()
721
+ {
722
+ return _preloader;
723
+ };
724
+
725
+ /**
726
+ * Shows the login page.
727
+ */
728
+ this.showLogin = function ()
729
+ {
730
+ showLogin();
731
+ };
732
+
733
+ /**
734
+ * Shows the login verification page.
735
+ */
736
+ this.showLoginVerification = function ()
737
+ {
738
+ showLoginVerification();
739
+ };
740
+
741
+ /**
742
+ * Toggles the display of the main menu.
743
+ */
744
+ this.toggleMenu = function ()
745
+ {
746
+ toggleMenu();
747
+ };
748
+
749
+ /**
750
+ * Shows the main menu.
751
+ * @param {Boolean} [instant] A value indicating if the menu should be shown instantly without animation. Only applicable for side menu.
752
+ */
753
+ this.showMenu = function (instant)
754
+ {
755
+ showMenu(instant);
756
+ };
757
+
758
+ /**
759
+ * Hides the main menu.
760
+ * @param {Boolean} [instant] A value indicating if the menu should be shown instantly without animation. Only applicable for side menu.
761
+ */
762
+ this.hideMenu = function (instant)
763
+ {
764
+ hideMenu(instant);
765
+ };
766
+
767
+ /**
768
+ * Processes the header overflow tasks.
769
+ */
770
+ this.checkHeaderOverflow = function ()
771
+ {
772
+ checkHeaderOverflow();
773
+ };
774
+
775
+ /**
776
+ * Shows the page.
777
+ * @param {Boolean} hideError Hides the error screen if active.
778
+ */
779
+ this.showPage = function (hideError)
780
+ {
781
+ if (_launch && _instance.launchMenu && _instance.menuType == _menuTypeOption.PORTALMENU)
782
+ return;
783
+
784
+ let errorShowing = _error.style.display != 'none';
785
+ hideAll();
786
+ updateMenuButtonVisibility();
787
+ _page.style.display = '';
788
+
789
+ if (!errorShowing || hideError)
790
+ {
791
+ $lib.removeClass(_instance.element, _instance.getCssClass(_classOption.ERROR));
792
+ _error.style.display = 'none';
793
+ _view.style.display = '';
794
+ }
795
+
796
+ document.documentElement.style.overflow = "";
797
+
798
+ if (_topbar)
799
+ {
800
+ _topbar.style.display = '';
801
+ $lib.addClass(_instance.element, _instance.getCssClass(_classOption.TOPBAR));
802
+ }
803
+
804
+ _instance.events.onShowPage.fire(_instance);
805
+ };
806
+
807
+ /**
808
+ * Checks if the error screen is active.
809
+ * @returns {Boolean} A value indicating if an error occurred.
810
+ */
811
+ this.hasError = function ()
812
+ {
813
+ return (_error.style.display != 'none');
814
+ };
815
+
816
+ /**
817
+ * Initiates a search request.
818
+ * @param {String} terms The search terms.
819
+ */
820
+ this.search = function (terms)
821
+ {
822
+ search(terms);
823
+ };
824
+
825
+ /**
826
+ * Log's the user in.
827
+ * @param {Object} [settings] The login settings to send to the authentication handler. By default input values (string username, string password, boolean persistent) from the login-screen are sent to the authentication handler.
828
+ */
829
+ this.login = function (settings)
830
+ {
831
+ login(settings);
832
+ };
833
+
834
+ /**
835
+ * Verifies the login verification code.
836
+ * @param {Object} [settings] The login settings to send to the authentication handler. By default the input value (string verificationCode) from the login-verification-screen is sent to the authentication handler.
837
+ */
838
+ this.verifyCode = function (settings)
839
+ {
840
+ verifyCode(settings);
841
+ };
842
+
843
+ /**
844
+ * Log's the user out.
845
+ * @param {Object} [settings] The logout settings to send to the authentication handler. By default boolean logout:true is sent to the authentication handler.
846
+ */
847
+ this.logout = function (settings)
848
+ {
849
+ logout(settings);
850
+ };
851
+
852
+ /**
853
+ * Renders the component
854
+ */
855
+ this.render = async function ()
856
+ {
857
+ if (_instance.renderState != $base.static.RenderState.RENDERING)
858
+ {
859
+ // call base render and return
860
+ $base.methods.render.call(_instance, preRender, 'app-skin');
861
+ return;
862
+ }
863
+
864
+ if (componyx.UI.menu_modules)
865
+ await componyx.UI.menu_modules.loaded; // we wait before the menu and it's modules are loaded
866
+
867
+ // render logic after loading resources
868
+ if (!_instance.hasTemplate('Header'))
869
+ _instance.addTemplate('Header', '{logo}{quickLaunchMenu}{searchBox}{menuButton}{topbar}{menu}{/topbar}', false);
870
+
871
+ if (!_instance.hasTemplate('Footer'))
872
+ _instance.addTemplate('Footer', '{menu}{username}{accountButton}', false);
873
+
874
+ if (!_instance.hasTemplate('Login'))
875
+ _instance.addTemplate('Login', '{logo}{username}{password}{rememberMe}{loginButton}{failedMessage}', false);
876
+
877
+ if (!_instance.hasTemplate('LoginVerification'))
878
+ _instance.addTemplate('LoginVerification', '{logo}{verificationCode}{verificationButton}{failedMessage}', false);
879
+
880
+ if (!_instance.hasTemplate('Error'))
881
+ _instance.addTemplate('Error', '<p><b>Description:</b> {message}</p><p><b>Status:</b> {status}</p><p><b>Timestamp:</b> {timestamp}</p><p><b>Log Id:</b> {id}</p>', false);
882
+
883
+ if (!_instance.hasTemplate('NotFound'))
884
+ _instance.addTemplate('NotFound', '<p>The page you are looking for does not exist.</p>', false);
885
+
886
+ draw();
887
+ };
888
+
889
+ /**
890
+ * Handles the post render procedure.
891
+ */
892
+ this.postRender = function ()
893
+ {
894
+ if (_instance.renderState != $base.static.RenderState.RENDERING) // extra safety to never execute a postRender when the component state is incorrect
895
+ return;
896
+
897
+ postRender();
898
+ };
899
+
900
+ /**
901
+ * Destroys the component.
902
+ * @see {@link componyx.UI.base.methods#destroy}
903
+ */
904
+ this.destroy = function (...args)
905
+ {
906
+ dispose();
907
+ $base.methods.destroy.call(this, ...args);
908
+ };
909
+
910
+ function preRender()
911
+ {
912
+ // initialize script and css
913
+ return ['AppSkin', ['Button', 'FormField', 'Box', 'Menu']];
914
+ }
915
+
916
+ async function postRender()
917
+ {
918
+ if (_instance.useBindary)
919
+ await handleRoutes();
920
+
921
+ if (!_instance.ajax.authenticate.isDefined())
922
+ _instance.ajax.authenticate.url = 'authenticate';
923
+
924
+ if (!_instance.ajax.search.isDefined())
925
+ _instance.ajax.search.url = 'search';
926
+
927
+ _login.style.display = 'none';
928
+
929
+ launch();
930
+ $base.methods.postRender.call(_instance);
931
+ }
932
+
933
+ async function handleRoutes()
934
+ {
935
+ await componyx.bindary_modules.loaded; // make sure Bindary is loaded
936
+
937
+ if ($bindary.routes && $bindary.routes.length) // routes are handled by the Bindary framework
938
+ {
939
+ $bindary.onAnchorScroll.add(anchorScroll);
940
+ $bindary.onLoad.add(loadRoute);
941
+
942
+ if ($bindary.linkDispatching && !_instance.events.onRenderSearchResult.has(dispatchLink))
943
+ _instance.events.onRenderSearchResult.priorityAdd(dispatchLink);
944
+ }
945
+
946
+ _view.firstChild.setAttribute($bindary.attributePrefix + 'view', '');
947
+ }
948
+
949
+ function loadRoute()
950
+ {
951
+ let routePath = $bindary.routePath,
952
+ anchor = $bindary.getAnchor();
953
+
954
+ if (anchor)
955
+ routePath += '#' + anchor;
956
+
957
+ if ($bindary.prettyURL)
958
+ {
959
+ let baseHref = $bindary.getBaseHref();
960
+
961
+ if (!$lib.isEmpty(baseHref) && $lib.startsWith(routePath, baseHref))
962
+ routePath = routePath.replace(baseHref, '');
963
+ }
964
+
965
+ updateMenuState(routePath, !$bindary.prettyURL);
966
+ _launch = false;
967
+ }
968
+
969
+ function dispatchLink(instance, args)
970
+ {
971
+ $bindary.dispatchLink(args.anchor);
972
+ }
973
+
974
+ function anchorScroll(el)
975
+ {
976
+ if (!_header)
977
+ return;
978
+
979
+ let height = _header.offsetHeight;
980
+
981
+ if (_topbar)
982
+ height += _topbar.offsetHeight;
983
+
984
+ $lib.scrollTo(el);
985
+ document.documentElement.scrollTop -= height;
986
+
987
+ return false;
988
+ }
989
+
990
+ function updateMenuState(routePath, hashtagRoutes)
991
+ {
992
+ let routePathMatching = _instance.menuRoutePathMatching;
993
+
994
+ if (routePathMatching == _routePathMatchingOption.DISABLED)
995
+ return;
996
+
997
+ routePath = routePath.toLowerCase();
998
+
999
+ let anchorlessItem,
1000
+ hasAnchor = routePath.indexOf('#') > -1,
1001
+ anchorlessRoutePath = (hasAnchor) ? routePath.split('#')[0] : null;
1002
+
1003
+ let path = $lib.path(_instance.menu, 'itemList', function (item)
1004
+ {
1005
+ let href = (item.href || '').toLowerCase();
1006
+
1007
+ if ($lib.isEmpty(href))
1008
+ return false;
1009
+
1010
+ // a category whose own href equals a direct child's href should defer to that child, so the more specific item is selected, not the category
1011
+ if (item.itemList && item.itemList.some(child => (child.href || '').toLowerCase() === href))
1012
+ return false;
1013
+
1014
+ if (hashtagRoutes && href.indexOf('#') == 0)
1015
+ {
1016
+ href = href.substr(1);
1017
+
1018
+ if (href.indexOf('!') == 0) // hash-bang
1019
+ href = href.substr(1);
1020
+
1021
+ if ($lib.isEmpty(href))
1022
+ return false;
1023
+ }
1024
+
1025
+ if (hasAnchor && anchorlessRoutePath === href.toLowerCase())
1026
+ anchorlessItem = item;
1027
+
1028
+ if (routePathMatching == _routePathMatchingOption.PARTIAL)
1029
+ return (routePath === href || (routePath.indexOf(href) == 0 && '?/'.indexOf(routePath.split(href)[1][0]) > -1));
1030
+ else
1031
+ return (routePath === href);
1032
+
1033
+ }, true);
1034
+
1035
+ if (!path && anchorlessItem)
1036
+ path = $lib.path(_instance.menu, 'itemList', function (item) { return (item === anchorlessItem) }, true); // item was found without anchor
1037
+
1038
+ if (path && !path.item.selected)
1039
+ {
1040
+ let root = path.create().root(), headerPath;
1041
+
1042
+ if (root.item.menuSection == _menuSectionOption.HEADER)
1043
+ {
1044
+ headerPath = (root.item == path.item) ? path.create() : getLastHeaderItem(path.create());
1045
+
1046
+ if (headerPath && !headerPath.item.selected)
1047
+ {
1048
+ let headerItems = buildPath(headerPath.create());
1049
+ if (headerItems?.length)
1050
+ _headerMenu.expand(headerItems, null, false, true);
1051
+
1052
+ _headerMenu.collapseAll(true); // collapse all dynamic positioned item-groups
1053
+ selectItem(_headerMenu, headerPath.item);
1054
+ }
1055
+ }
1056
+
1057
+ if (!path.item.selected)
1058
+ {
1059
+ if (isSideMenu())
1060
+ {
1061
+ let mainItems = buildPath(path.create(), true);
1062
+ if (mainItems?.length)
1063
+ _mainMenu.expand(mainItems, null, false, true);
1064
+ }
1065
+
1066
+ selectItem(_mainMenu, path.item, isSideMenu() && (!$lib.event || $lib.event.type != 'click' || !isMainMenuTarget($lib.event.target)));
1067
+ }
1068
+ }
1069
+ else if (!path)
1070
+ {
1071
+ deselectItem(_mainMenu);
1072
+ deselectItem(_headerMenu);
1073
+ deselectItem(_footerMenu);
1074
+ deselectItem(_quickLaunchMenu);
1075
+
1076
+ if (_mainMenu)
1077
+ _mainMenu.showCategory(null, false, true); // main category
1078
+
1079
+ checkHeaderOverflow();
1080
+ }
1081
+ }
1082
+
1083
+ function deselectItem(menu)
1084
+ {
1085
+ if (menu)
1086
+ menu.deselectItem();
1087
+ }
1088
+
1089
+ function isMainMenuTarget(target)
1090
+ {
1091
+ return ($lib.contains(_mainMenu.element, target));
1092
+ }
1093
+
1094
+ function getLastHeaderItem(path)
1095
+ {
1096
+ let item;
1097
+
1098
+ while (path)
1099
+ {
1100
+ item = path.item;
1101
+
1102
+ if (item.isCategory && !item.categoryInHeader && _headerMenu.loadItem(item.id))
1103
+ return path;
1104
+
1105
+ path = (path.hasParent()) ? path.parent() : null;
1106
+ }
1107
+
1108
+ return null;
1109
+ }
1110
+
1111
+ function buildPath(path, includeCurrent)
1112
+ {
1113
+ let items = [];
1114
+
1115
+ if (includeCurrent && !$lib.isEmpty(path.children))
1116
+ items.push(path.item.id);
1117
+
1118
+ while (path.hasParent())
1119
+ {
1120
+ items.unshift(path.parent().item.id);
1121
+ }
1122
+
1123
+ return items;
1124
+ }
1125
+
1126
+ function launch()
1127
+ {
1128
+ _launch = true;
1129
+ hideAll();
1130
+
1131
+ if (_instance.authenticationRequired && !_instance.authenticated)
1132
+ {
1133
+ $lib.addClass(_instance.element, _instance.getCssClass(_classOption.REQUIRED));
1134
+ _instance.showLogin();
1135
+ }
1136
+ else if (_instance.launchMenu)
1137
+ showMenu();
1138
+ else
1139
+ _instance.showPage();
1140
+
1141
+ checkHeaderOverflow();
1142
+ }
1143
+
1144
+ function draw()
1145
+ {
1146
+ let el = _instance.element, htmlTag = $lib(null, '', 'html', true);
1147
+
1148
+ _instance.element.classList.forEach(c =>
1149
+ {
1150
+ if (c !== 'app-skin' && c !== 'hidden')
1151
+ htmlTag.classList.add(c);
1152
+ });
1153
+
1154
+ if (_instance.menuLeft)
1155
+ $lib.addClass(el, _instance.getCssClass(_classOption.MENU_LEFT));
1156
+
1157
+ createLayout(el);
1158
+
1159
+ _instance.setFullscreen(_instance.fullscreen);
1160
+ _instance.setFullpage(_instance.fullpage);
1161
+
1162
+ if (_instance.footerDisplay == _footerDisplayOption.PERSISTENT)
1163
+ showFooter();
1164
+
1165
+ bindEvents();
1166
+ bindErrorEvent();
1167
+ _instance.renderChildren();
1168
+ }
1169
+
1170
+ function createPreloader(container)
1171
+ {
1172
+ let div = document.createElement('div'),
1173
+ i = div.appendChild(document.createElement('i')),
1174
+ id = _instance.id + '_preloader', b;
1175
+
1176
+ b = div.appendChild(document.createElement('b'));
1177
+ b.appendChild(document.createTextNode(_instance.loadingLabel));
1178
+ i.className = _instance.getCssClass(_classOption.SPINNER);
1179
+ _preloader = $UI.createComponent(componyx.UI.Box, { id: id, containerElement: container });
1180
+ _instance.store.push(id);
1181
+
1182
+ _preloader.setContentTemplate(div);
1183
+ _preloader.cssClass = `${_instance.getCssClass(_classOption.BOX)} ${_instance.getCssClass(_classOption.PRELOADER)}`;
1184
+ _preloader.showing = false;
1185
+ _preloader.modal = true;
1186
+ _preloader.theme = $base.static.ThemeOption.NONE;
1187
+ _preloader.animation.showType = _preloader.animation.hideType = componyx.UI.Box.AnimationTypeOption.NONE;
1188
+ _preloader.autoPosition = componyx.UI.Box.AutoPositionOption.CENTER;
1189
+ _preloader.events.onPostRender.priorityAdd(function () { _instance.isReady.apply(_instance); }, null);
1190
+ }
1191
+
1192
+ function bindEvents()
1193
+ {
1194
+ $lib.on(window, 'resize', resize);
1195
+ $lib.on(document, 'click', hideSearchResult);
1196
+
1197
+ if (isSideMenu())
1198
+ $lib.on(_overlay, 'click', detectMenuToggle);
1199
+ }
1200
+
1201
+ function bindErrorEvent()
1202
+ {
1203
+ $lib.httpRequestHandlers.onSuccess.add(function (args)
1204
+ {
1205
+ let contentType = args.xhr.getResponseHeader('content-type'),
1206
+ data = args.data,
1207
+ parse = function (d) { return (typeof d == 'string') ? window.JSON.parse(d) : d };
1208
+
1209
+ if (contentType && contentType.toLowerCase().indexOf('application/json') > -1 && data)
1210
+ {
1211
+ data = parse(data);
1212
+
1213
+ if (data && data.d)
1214
+ data = parse(data.d);
1215
+
1216
+ return error(data);
1217
+ }
1218
+ });
1219
+
1220
+ if (!_instance.useBindary)
1221
+ return;
1222
+
1223
+ componyx.bindary_modules?.loaded.then(() =>
1224
+ {
1225
+ $bindary.onDataError.add(function (data)
1226
+ {
1227
+ return error(data);
1228
+ });
1229
+
1230
+ $bindary.onRouteError.add(function (args)
1231
+ {
1232
+ const notFound = (args.type === 'route' || args.status === 404 || args.status === 410);
1233
+
1234
+ if (notFound && !document.head.querySelector('meta[data-appskin-noindex]')) // avoid soft 404s: tell search engines not to index this result
1235
+ document.head.insertAdjacentHTML('beforeend', '<meta name="robots" content="noindex" data-appskin-noindex>');
1236
+
1237
+ showError({ status: args.status }, notFound ? 'NotFound' : 'Error');
1238
+ });
1239
+
1240
+ $bindary.onPreLoad.add(function () // a new route load starts, so hide a previous error again
1241
+ {
1242
+ _errorData = null;
1243
+ document.head.querySelector('meta[data-appskin-noindex]')?.remove();
1244
+ _instance.showPage(true);
1245
+ });
1246
+ }).catch(console.error);
1247
+ }
1248
+
1249
+ function error(data)
1250
+ {
1251
+ if (!data || !data.isError)
1252
+ {
1253
+ _instance.showPage(true);
1254
+ return;
1255
+ }
1256
+
1257
+ let errorData = (data.data && data.data.appData) ? data.data.appData : data;
1258
+
1259
+ if (_errorData != errorData)
1260
+ {
1261
+ _errorData = errorData;
1262
+ return showError(errorData);
1263
+ }
1264
+ }
1265
+
1266
+ function showError(error, templateName = 'Error')
1267
+ {
1268
+ let divMessage,
1269
+ timestamp = error.timestamp;
1270
+
1271
+ if (timestamp && !(timestamp instanceof Date))
1272
+ timestamp = new Date(timestamp);
1273
+
1274
+ _instance.preloader().hide();
1275
+ _instance.showPage();
1276
+ $lib.addClass(_instance.element, _instance.getCssClass(_classOption.ERROR));
1277
+ _view.style.display = 'none'; // hide view container
1278
+
1279
+ let el = _error.firstChild;
1280
+ el.innerHTML = '';
1281
+ _error.style.display = '';
1282
+
1283
+ divMessage = createElement(el, 'div');
1284
+
1285
+ _instance.applyTemplate(divMessage, templateName, {
1286
+ timestamp: (timestamp && !isNaN(timestamp)) ? $lib.formatDate(timestamp, 'dd-MM-yyyy HH:mm:ss') : '-',
1287
+ message: error.message || '-',
1288
+ stackTrace: error.stackTrace || '-',
1289
+ id: error.id || '-',
1290
+ status: error.status || '-'
1291
+ });
1292
+
1293
+ _instance.events.onShowError.fire(_instance);
1294
+
1295
+ return false;
1296
+ }
1297
+
1298
+ function createLayout(container)
1299
+ {
1300
+ let div = createElement(container, 'div', _instance.getCssClass(_classOption.BODY));
1301
+
1302
+ createMainMenu(container);
1303
+ createHeader(container);
1304
+ createSidebar(container);
1305
+
1306
+ _page = createElement(container, '', _instance.getCssClass(_classOption.PAGE));
1307
+
1308
+ if (_instance.hasTemplate('PageHeader'))
1309
+ {
1310
+ div = createElement(_page, 'header');
1311
+ _instance.applyTemplate(div, 'PageHeader');
1312
+ }
1313
+
1314
+ _error = createElement(_page, '', _instance.getCssClass(_classOption.ERROR), createContent());
1315
+ _error.style.display = 'none';
1316
+
1317
+ _view = createElement(_page, '', _instance.getCssClass(_classOption.VIEW), createContent());
1318
+
1319
+ if (_instance.hasTemplate('PageFooter'))
1320
+ {
1321
+ div = createElement(_page, 'footer');
1322
+ _instance.applyTemplate(div, 'PageFooter');
1323
+ }
1324
+
1325
+ if (_instance.useLoginTemplate)
1326
+ {
1327
+ createLoginScreen(container);
1328
+ createLoginVerificationScreen(container);
1329
+ }
1330
+
1331
+ if (_instance.footerDisplay > _footerDisplayOption.DISABLED)
1332
+ createFooter(container);
1333
+
1334
+ _overlay = createElement(container, 'div', _instance.getCssClass(_classOption.OVERLAY));
1335
+
1336
+ if (_instance.authenticated)
1337
+ $lib.addClass(_instance.element, _instance.getCssClass(_classOption.AUTHENTICATED));
1338
+
1339
+ createPreloader(container);
1340
+ }
1341
+
1342
+ function createSidebar(container)
1343
+ {
1344
+ let name = 'Sidebar';
1345
+
1346
+ if (!_instance.hasTemplate(name))
1347
+ return;
1348
+
1349
+ let id = $lib.format('{0}_{1}_', _instance.id, name),
1350
+ bar = createElement(container, '', _instance.getCssClass(_classOption.SIDEBAR), content);
1351
+
1352
+ _instance.applyTemplate(bar, name, getinterpolations(id));
1353
+ replacePlaceHolders(id);
1354
+ }
1355
+
1356
+ function createHeader(container)
1357
+ {
1358
+ let id = _instance.id + '_Header_', el;
1359
+
1360
+ _header = createElement(container, 'header'); // create header
1361
+ _instance.applyTemplate(_header, 'Header', getinterpolations(id));
1362
+ _header.appendChild(createContent($lib.extract(_header)));
1363
+
1364
+ _topbar = $lib('#' + id + 'TopBar');
1365
+
1366
+ if (_topbar)
1367
+ {
1368
+ _topbar.appendChild(createContent($lib.extract(_topbar)));
1369
+ $lib.addClass(_instance.element, _instance.getCssClass(_classOption.TOPBAR));
1370
+ }
1371
+
1372
+ if ((el = $lib('#' + id + 'Menu')) && hasMenu(_menuSectionOption.HEADER))
1373
+ _headerMenu = createMenu(el.parentNode, el.id, _instance.headerMenuId, _menuSectionOption.HEADER, getHeaderProps());
1374
+
1375
+ replacePlaceHolders(id);
1376
+ }
1377
+
1378
+ function createFooter(container)
1379
+ {
1380
+ let id = _instance.id + '_Footer_', el;
1381
+
1382
+ _footer = createElement(container, 'footer'); // create footer
1383
+ _instance.applyTemplate(_footer, 'Footer', getinterpolations(id));
1384
+ _footer.appendChild(createContent($lib.extract(_footer)));
1385
+
1386
+ if ((el = $lib('#' + id + 'Menu')) && hasMenu(_menuSectionOption.FOOTER))
1387
+ _footerMenu = createMenu(el.parentNode, el.id, _instance.headerMenuId, _menuSectionOption.FOOTER, getFooterProps());
1388
+
1389
+ replacePlaceHolders(id);
1390
+ }
1391
+
1392
+ function replacePlaceHolders(id)
1393
+ {
1394
+ let el;
1395
+
1396
+ if ((el = $lib('#' + id + 'QuickLaunchMenu')) && hasMenu(_menuSectionOption.QUICKLAUNCH))
1397
+ _quickLaunchMenu = createMenu(el.parentNode, el.id, _instance.quickLaunchMenuId, _menuSectionOption.QUICKLAUNCH, getQuickLaunchProps());
1398
+
1399
+ if (el = $lib('#' + id + 'SearchBox'))
1400
+ _searchBox = createSearchBox(el);
1401
+
1402
+ if (el = $lib('#' + id + 'Username'))
1403
+ el.innerHTML = _instance.username;
1404
+
1405
+ createMenuButton($lib('#' + id + 'MenuButton'));
1406
+ createAccountButton($lib('#' + id + 'AccountButton'));
1407
+ }
1408
+
1409
+ function hasMenu(menuSection)
1410
+ {
1411
+ let hasMenu = false;
1412
+
1413
+ $lib.each(_instance.menu, function (item)
1414
+ {
1415
+ if (item.menuSection === menuSection)
1416
+ return !(hasMenu = true); // stop iteration
1417
+ });
1418
+
1419
+ return hasMenu;
1420
+ }
1421
+
1422
+ function createContent(content)
1423
+ {
1424
+ return createElement(null, '', _instance.getCssClass(_classOption.APP_CONTENT), content);
1425
+ }
1426
+
1427
+ function joinCssClasses(...classes)
1428
+ {
1429
+ return classes.filter(c => !$lib.isEmpty(c)).join(' ').replace(/\s+/g, ' ').trim();
1430
+ }
1431
+
1432
+ function createElement(container, tagName, cssClass, content)
1433
+ {
1434
+ return $lib.element(container, '', tagName, content, (cssClass) ? { "class": cssClass } : null);
1435
+ }
1436
+
1437
+ function createSearchBox(placeHolder)
1438
+ {
1439
+ let div = createElement(null, 'div', _instance.getCssClass(_classOption.SEARCH)),
1440
+ input = createElement(div, 'input');
1441
+ placeHolder.parentNode.replaceChild(div, placeHolder);
1442
+
1443
+ div.setAttribute('tabindex', '0');
1444
+ input.placeholder = _instance.searchLabel;
1445
+
1446
+ $lib.on(div, 'focus', function ()
1447
+ {
1448
+ $lib.addClass(_instance.element, _instance.getCssClass(_classOption.SEARCH_ACTIVE));
1449
+ input.focus();
1450
+ });
1451
+ $lib.on(input, 'blur', function ()
1452
+ {
1453
+ $lib.removeClass(_instance.element, _instance.getCssClass(_classOption.SEARCH_ACTIVE));
1454
+ });
1455
+
1456
+ $lib.on(input, 'keyup paste', function ()
1457
+ {
1458
+ clearTimeout(_searchTimerId);
1459
+ _searchTimerId = setTimeout(search, _instance.searchDelay);
1460
+ });
1461
+ $lib.on(input, 'focus', function ()
1462
+ {
1463
+ $lib.addClass(_instance.element, _instance.getCssClass(_classOption.SEARCH_ACTIVE));
1464
+
1465
+ if (_searchResult && !$lib.isEmpty(_searchBox.value))
1466
+ _searchResult.style.display = '';
1467
+ });
1468
+
1469
+ return input;
1470
+ }
1471
+
1472
+ function createMenuButton(placeHolder)
1473
+ {
1474
+ if (!placeHolder)
1475
+ return;
1476
+
1477
+ _menuButton = createButton(placeHolder.parentNode, placeHolder.id, _instance.menuButtonId, { cssClass: _instance.getCssClass(_classOption.MENU_BUTTON), cssClassIcon: _instance.getCssClass(_classOption.ICON_MENU) }, function (button, args)
1478
+ {
1479
+ toggleMenu();
1480
+ });
1481
+ }
1482
+
1483
+ function createAccountButton(placeHolder)
1484
+ {
1485
+ if (!placeHolder)
1486
+ return;
1487
+
1488
+ let authenticated = _instance.authenticated,
1489
+ action = $base.static.getMethod(_instance.accountAction),
1490
+ item = {};
1491
+
1492
+ if (!action)
1493
+ action = (authenticated) ? _instance.showLogin : _instance.logout;
1494
+
1495
+ item.cssClass = _instance.getCssClass(_classOption.ACCOUNT);
1496
+
1497
+ if (authenticated && _instance.accountButtonAvatar)
1498
+ {
1499
+ item.cssClass = _instance.getCssClass(_classOption.ACCOUNT_AVATAR);
1500
+ _instance.element.style.setProperty("--avatar-initials", `'${_instance.avatarInitials}'`);
1501
+ }
1502
+ else if (authenticated)
1503
+ item.cssClassIcon = _instance.getCssClass(_classOption.ICON_USER);
1504
+ else
1505
+ {
1506
+ item.cssClassIcon = _instance.getCssClass(_classOption.ICON_LOCK);
1507
+ action = _instance.showLogin;
1508
+ }
1509
+
1510
+ createButton(placeHolder.parentNode, placeHolder.id, _instance.accountButtonId, item, action);
1511
+
1512
+ if (_instance.avatarImagePath)
1513
+ _instance.element.style.setProperty("--avatar-image", `url('${_instance.avatarImagePath}')`);
1514
+ }
1515
+
1516
+ function createMainMenu(container)
1517
+ {
1518
+ let id = _instance.id + '_MainMenu';
1519
+
1520
+ if (isSideMenu())
1521
+ container = createSideMenuBox(container);
1522
+
1523
+ _mainMenu = createMenu(container, id, _instance.mainMenuId, _menuSectionOption.MAIN, getMainMenuProps());
1524
+ }
1525
+
1526
+ function createMenu(container, id, cloneId, section, properties)
1527
+ {
1528
+ if (!container || !_instance.menu.length)
1529
+ return;
1530
+
1531
+ if (!properties)
1532
+ properties = {};
1533
+
1534
+ properties.id = id;
1535
+ properties.containerElement = container;
1536
+
1537
+ let menu = $UI.createComponent(componyx.UI.Menu, properties),
1538
+ groupSettings;
1539
+
1540
+ menu.clone($UI.store[cloneId], _instance);
1541
+ menu.templates = getMenuItemTemplates();
1542
+ menu.mainCategoryLabel = _instance.mainCategoryLabel;
1543
+ menu.events.onPostRender.priorityAdd(function () { _instance.isReady.apply(_instance); }, null);
1544
+ menu.showing = ((section == _menuSectionOption.MAIN && isSideMenu()) || section == _menuSectionOption.HEADER || section >= _menuSectionOption.FOOTER);
1545
+
1546
+ if (section == _menuSectionOption.HEADER)
1547
+ menu.itemGroupBoxId = createHeaderItemGroupBox(); // default root item-group box for the header menu
1548
+
1549
+ if (section == _menuSectionOption.MAIN)
1550
+ menu.events.onHide.priorityAdd(function () { $lib.removeClass(_instance.element, _instance.getCssClass(_classOption.PORTAL_MENU)); });
1551
+
1552
+ $lib.each(_instance.menu, function (item, index)
1553
+ {
1554
+ if (item.menuSection == section || (section == _menuSectionOption.MAIN && item.menuSection == _menuSectionOption.HEADER))
1555
+ {
1556
+ item.id = item.id || $lib.guid();
1557
+
1558
+ if (section == _menuSectionOption.HEADER)
1559
+ item = copyItem(item); // copy header menu-item
1560
+
1561
+ if (section == _menuSectionOption.HEADER
1562
+ || (section == _menuSectionOption.MAIN && item.menuSection == _menuSectionOption.HEADER)
1563
+ || (section == _menuSectionOption.MAIN && _instance.menuType == _menuTypeOption.PORTALMENU))
1564
+ {
1565
+ if (!item.selectable && !item.command && !item.href)
1566
+ item.cssClass = joinCssClasses(item.cssClass, _instance.getCssClass(_classOption.NO_ACTION));
1567
+
1568
+ // menu-item is in both header and main navigation
1569
+ if (item.__command === undefined)
1570
+ {
1571
+ item.__command = item.command || null;
1572
+ item.command = defaultCommand;
1573
+ }
1574
+ else
1575
+ item.command = defaultCommand;
1576
+
1577
+ if (item.itemList)
1578
+ setCommands(item.itemList);
1579
+ }
1580
+
1581
+ if (section == _menuSectionOption.MAIN && item.menuSection == _menuSectionOption.MAIN)
1582
+ _allInHeader = false;
1583
+
1584
+ if (section == _menuSectionOption.MAIN && _instance.menuType == _menuTypeOption.PORTALMENU)
1585
+ {
1586
+ groupSettings = createPortalItem(item, index, groupSettings);
1587
+
1588
+ if (item.isCategory)
1589
+ createPortalItems(item.itemList);
1590
+ }
1591
+
1592
+ menu.itemList.push(item);
1593
+ }
1594
+ });
1595
+
1596
+ return menu;
1597
+ }
1598
+
1599
+ function copyItem(cloneItem)
1600
+ {
1601
+ let item = new componyx.UI.AppSkin.MenuItem(cloneItem);
1602
+
1603
+ item.selectable = true;
1604
+ item.command = item.__command;
1605
+ item.subGroupId = null;
1606
+
1607
+ if (item.__cssClass != undefined)
1608
+ item.cssClass = item.__cssClass;
1609
+
1610
+ if (item.isCategory && !item.categoryInHeader)
1611
+ {
1612
+ item.isCategory = false;
1613
+ item.itemList = [];
1614
+ return item;
1615
+ }
1616
+
1617
+ $lib.each(cloneItem.itemList, function (childItem, index)
1618
+ {
1619
+ item.itemList[index] = copyItem(childItem);
1620
+ });
1621
+
1622
+ return item;
1623
+ }
1624
+
1625
+ function createPortalItems(itemList)
1626
+ {
1627
+ let groupSettings;
1628
+
1629
+ $lib.each(itemList, function (item, index)
1630
+ {
1631
+ groupSettings = createPortalItem(item, index, groupSettings);
1632
+
1633
+ if (item.isCategory)
1634
+ createPortalItem(item.itemList);
1635
+ });
1636
+ }
1637
+
1638
+ function createPortalItem(item, index, groupSettings)
1639
+ {
1640
+ let cssStyles = ['1', '2', '3', '4'], cssStyle;
1641
+
1642
+ item.cssClass = item.cssClass || '';
1643
+ item.__cssClass = item.cssClass;
1644
+
1645
+ if (!item.groupTileSize)
1646
+ item.groupTileSize = null;
1647
+
1648
+ if (!groupSettings || (!$lib.isEmpty(item.groupTileSize) && item.groupTileSize != groupSettings.groupTileSize))
1649
+ {
1650
+ groupSettings = {};
1651
+ groupSettings.tileSize = 0;
1652
+ groupSettings.id = $lib.guid();
1653
+ groupSettings.groupTileSize = (item.groupTileSize) ? item.groupTileSize : (item.buttonTileSize <= _tileSizeOption.MEDIUM) ? _tileSizeOption.MEDIUM : _tileSizeOption.LARGE;
1654
+ item.cssClassSubGroup = joinCssClasses(_instance.getCssClass(_classOption.SUB_GROUP), _tileSizeOption.getName(groupSettings.groupTileSize).replace('_', '-'));
1655
+ }
1656
+
1657
+ item.subGroupId = groupSettings.id; // item will be appended to the group container element
1658
+ item.buttonTileSize = item.buttonTileSize || _tileSizeOption.MEDIUM;
1659
+ item.cssClass = joinCssClasses(item.cssClass, _tileSizeOption.getName(item.buttonTileSize).replace('_', '-'));
1660
+
1661
+ cssStyle = cssStyles[(index % 4)];
1662
+
1663
+ if (cssStyle)
1664
+ item.cssClass = joinCssClasses(item.cssClass, _instance.getCssClass(_classOption.SHADE) + '-' + cssStyle);
1665
+
1666
+ if ((index % 4) == 3)
1667
+ cssStyles.reverse();
1668
+
1669
+ if (groupSettings.groupTileSize == _tileSizeOption.LARGE && item.buttonTileSize <= _tileSizeOption.SMALL_WIDE && (groupSettings.tileSize == 2 || groupSettings.tileSize == 10))
1670
+ item.cssClass = joinCssClasses(item.cssClass, _instance.getCssClass(_classOption.ROW_START));
1671
+
1672
+ groupSettings.tileSize += item.buttonTileSize;
1673
+
1674
+ if (groupSettings.tileSize >= groupSettings.groupTileSize)
1675
+ groupSettings = null; // group is filled
1676
+
1677
+ return groupSettings;
1678
+ }
1679
+
1680
+ function getMenuItemTemplates()
1681
+ {
1682
+ let templates = new Map(),
1683
+ reserved = 'Header Footer Login Error PageHeader PageFooter';
1684
+
1685
+ if (_instance.templates.size)
1686
+ {
1687
+ _instance.templates.forEach(function (template, id)
1688
+ {
1689
+ if (reserved.indexOf(id) == -1)
1690
+ templates.set(id, template);
1691
+ });
1692
+ }
1693
+
1694
+ return templates;
1695
+ }
1696
+
1697
+ function setCommands(itemList)
1698
+ {
1699
+ $lib.each(itemList, function (item, index)
1700
+ {
1701
+ item.id = item.id || $lib.guid();
1702
+
1703
+ if (!item.selectable && !item.command && !item.href)
1704
+ item.cssClass = joinCssClasses(item.cssClass, _instance.getCssClass(_classOption.NO_ACTION));
1705
+
1706
+ if (item.__command === undefined)
1707
+ {
1708
+ item.__command = item.command || null;
1709
+ item.command = defaultCommand;
1710
+ }
1711
+ else
1712
+ item.command = defaultCommand;
1713
+
1714
+ if (item.itemList)
1715
+ setCommands(item.itemList);
1716
+ });
1717
+ }
1718
+
1719
+ function defaultCommand(sender, args)
1720
+ {
1721
+ let isMain = (sender === _mainMenu),
1722
+ menu = (isMain) ? _headerMenu : _mainMenu;
1723
+
1724
+ if (isMain && args && args.event && args.event.type == 'click' && !args.item.isCategory && args.item.selectable && $lib.styleValue(_overlay, 'display', true) != 'none') // menu is in overlay-mode (small template)
1725
+ hideMenu();
1726
+
1727
+ if (!menu) // no header menu, handle portal-type case directly
1728
+ {
1729
+ if (_instance.menuType === _menuTypeOption.PORTALMENU)
1730
+ {
1731
+ let portalItemPath = _mainMenu.loadItem(args.item.id),
1732
+ portalItem = portalItemPath?.item;
1733
+
1734
+ if (!portalItem)
1735
+ return;
1736
+
1737
+ executeCommand(_mainMenu, portalItem);
1738
+ }
1739
+
1740
+ return;
1741
+ }
1742
+
1743
+ let itemPath = menu.loadItem(args.item.id),
1744
+ item = itemPath?.item;
1745
+
1746
+ if (!item)
1747
+ {
1748
+ // item only exists in the main menu (e.g. child of a category not shown in the header): fire its original command
1749
+ if (isMain && args.item.__command)
1750
+ {
1751
+ let fn = $base.static.getMethod(args.item.__command);
1752
+
1753
+ if (fn)
1754
+ fn(sender, args);
1755
+ }
1756
+
1757
+ return;
1758
+ }
1759
+
1760
+ executeCommand(menu, item);
1761
+
1762
+ if (!isMain)
1763
+ {
1764
+ if (item.isCategory) // header opens category in main-menu
1765
+ {
1766
+ _mainMenu.showCategory(item.id, false, !menuIsShowing());
1767
+
1768
+ if (!menuIsShowing())
1769
+ showMenu();
1770
+
1771
+ checkHeaderOverflow();
1772
+ }
1773
+ else if (menuIsShowing())
1774
+ {
1775
+ hideMenu(false, showMainCategory);
1776
+ }
1777
+ else
1778
+ {
1779
+ showMainCategory();
1780
+ }
1781
+ }
1782
+ }
1783
+
1784
+ function executeCommand(menu, item)
1785
+ {
1786
+ item.command = item.__command; // original command
1787
+ selectItem(menu, item); // fire original command and set selected state
1788
+ item.command = defaultCommand; // reset command back to default
1789
+
1790
+ if (item.href && (_instance.menuType == _menuTypeOption.PORTALMENU || _error.style.display != 'none'))
1791
+ _instance.showPage(true);
1792
+ }
1793
+
1794
+ function showMainCategory()
1795
+ {
1796
+ _mainMenu.showCategory(null, false, true);
1797
+ checkHeaderOverflow();
1798
+ }
1799
+
1800
+ function setHideMenuCallback(fn)
1801
+ {
1802
+ if (isSideMenu())
1803
+ _sideMenuBox.events.onHideComplete.priorityAdd(fn, null, true);
1804
+ else
1805
+ _mainMenu.events.onHide.priorityAdd(fn, null, true);
1806
+ }
1807
+
1808
+ function selectItem(menu, item, scroll)
1809
+ {
1810
+ let href;
1811
+
1812
+ href = item.href || null;
1813
+ item.href = null; // item was matched on current routePath, there is no reason to re-set URL and re-setting can cause issues when there is a routePath and an anchor in the current URL
1814
+
1815
+ menu.selectItem(item.id);
1816
+
1817
+ if (href != undefined)
1818
+ item.href = href;
1819
+
1820
+ if (scroll)
1821
+ setTimeout($lib.scrollTo.bind(this, $UI.store[menu.id + '_' + item.id].element, _sideMenuBox.element), 0);
1822
+ }
1823
+
1824
+ function createButton(container, id, cloneId, item, command)
1825
+ {
1826
+ let button = $UI.createComponent(componyx.UI.Button, { id: id, containerElement: container }), b;
1827
+
1828
+ button.clone($UI.store[cloneId], _instance);
1829
+ button.renderId = false;
1830
+ button.transparent = ($lib.isEmpty(item.transparent)) ? true : item.transparent;
1831
+ button.cssClass = joinCssClasses(_instance.getCssClass(_classOption.BUTTON), item.cssClass);
1832
+ button.cssClassIcon = ($lib.isEmpty(item.cssClassIcon)) ? '' : item.cssClassIcon;
1833
+ button.hasIcon = !$lib.isEmpty(item.cssClassIcon);
1834
+ button.command = (button.command) ? button.command : command;
1835
+ button.events.onPostRender.priorityAdd(function () { _instance.isReady.apply(_instance); }, null);
1836
+ button.showing = true;
1837
+
1838
+ if (!($lib.isEmpty(item.text)))
1839
+ {
1840
+ b = document.createElement('b');
1841
+ $lib.setText(b, item.text);
1842
+ button.text = b.outerHTML;
1843
+ }
1844
+
1845
+ return button;
1846
+ }
1847
+
1848
+ function createSideMenuBox(container)
1849
+ {
1850
+ let id = _instance.id + '_SideMenuBox',
1851
+ cloneId = _instance.sideMenuBoxId,
1852
+ box = $UI.createComponent(componyx.UI.Box, { id: id, containerElement: container });
1853
+
1854
+ box.clone($UI.store[cloneId], _instance);
1855
+ box.theme = $base.static.ThemeOption.NONE;
1856
+ box.cssClass = `${_instance.getCssClass(_classOption.BOX)} ${_instance.getCssClass(_classOption.SIDE_MENU)}`;
1857
+ box.animation.showType = box.animation.hideType = 3;
1858
+ box.autoPosition = 0;
1859
+ box.autoFit = false;
1860
+ box.events.onHideComplete.priorityAdd(function () { $lib.removeClass(_instance.element, _instance.getCssClass(_classOption.SIDE_MENU)); });
1861
+ box.render(); // instant because there are no dependencies
1862
+
1863
+ _sideMenuBox = box;
1864
+
1865
+ return box.contentElement;
1866
+ }
1867
+
1868
+ function createHeaderItemGroupBox()
1869
+ {
1870
+ let id = _instance.id + '_HeaderMenuBox',
1871
+ box = $UI.createComponent(componyx.UI.Box, { id: id, containerElement: null });
1872
+ box.alignX = componyx.UI.Box.AlignXOption.CENTER;
1873
+ return box.id;
1874
+ }
1875
+
1876
+ function getHeaderProps()
1877
+ {
1878
+ let props = {};
1879
+
1880
+ props.cssClass = `${_instance.getCssClass(_classOption.MENU)} ${_instance.getCssClass(_classOption.HEADER_MENU)}`;
1881
+ return props;
1882
+ }
1883
+
1884
+ function getQuickLaunchProps()
1885
+ {
1886
+ let props = {};
1887
+
1888
+ props.cssClass = `${_instance.getCssClass(_classOption.MENU)} ${_instance.getCssClass(_classOption.QUICK_LAUNCH)}`;
1889
+ return props;
1890
+ }
1891
+
1892
+ function getFooterProps()
1893
+ {
1894
+ let props = {};
1895
+
1896
+ props.cssClass = `${_instance.getCssClass(_classOption.MENU)} ${_instance.getCssClass(_classOption.FOOTER_MENU)}`;
1897
+ return props;
1898
+ }
1899
+
1900
+ function getMainMenuProps()
1901
+ {
1902
+ let props = {};
1903
+
1904
+ props.cssClass = joinCssClasses(_instance.getCssClass(_classOption.MENU), _instance.getCssClass(_classOption.MAIN), (isSideMenu()) ? _instance.getCssClass(_classOption.SIDE) : _instance.getCssClass(_classOption.PORTAL));
1905
+ props.horizontalRoot = !isSideMenu();
1906
+
1907
+ if (isSideMenu())
1908
+ {
1909
+ let defaultButton = $UI.createComponent(componyx.UI.Button, { id: _instance.id + '_DefaultButton', decoration: 1 });
1910
+ props.buttonId = defaultButton.id;
1911
+ }
1912
+
1913
+ return props;
1914
+ }
1915
+
1916
+ function getinterpolations(id)
1917
+ {
1918
+ // replacement values for template
1919
+ let values = {};
1920
+ values.topbar = '<div id="' + id + 'TopBar" class="topbar">';
1921
+ values['/topbar'] = '</div>';
1922
+ values.logo = '<figure id="' + id + 'Logo" class="logo"></figure>';
1923
+ values.menuButton = '<a id="' + id + 'MenuButton"></a>';
1924
+ values.accountButton = '<a id="' + id + 'AccountButton"></a>';
1925
+ values.loginButton = '<a id="' + id + 'LoginButton"></a>';
1926
+ values.menu = '<b id="' + id + 'Menu"></b>';
1927
+ values.quickLaunchMenu = '<b id="' + id + 'QuickLaunchMenu"></b>';
1928
+ values.searchBox = '<div id="' + id + 'SearchBox"></div>';
1929
+ values.username = '<b id="' + id + 'Username" class="username"></b>';
1930
+ values.password = '<b id="' + id + 'Password"></b>';
1931
+ values.rememberMe = '<b id="' + id + 'RememberMe"></b>';
1932
+ values.failedMessage = '<b id="' + id + 'FailedMessage"></b>';
1933
+ values.verificationCode = '<b id="' + id + 'VerificationCode"></b>';
1934
+ values.verificationButton = '<a id="' + id + 'VerificationButton"></a>';
1935
+ return values;
1936
+ }
1937
+
1938
+ function createLoginScreen(container)
1939
+ {
1940
+ let id = _instance.id + '_Login_', el, i, div;
1941
+
1942
+ _login = createElement(container, '', _instance.getCssClass(_classOption.LOGIN_SCREEN));
1943
+ _instance.applyTemplate(_login, 'Login', getinterpolations(id));
1944
+ createElement(_login, '', _instance.getCssClass(_classOption.CONTENT), $lib.extract(_login));
1945
+
1946
+ if (el = $lib('#' + id + 'Username'))
1947
+ {
1948
+ div = createElement(null, '', _instance.getCssClass(_classOption.USER));
1949
+ el.parentNode.replaceChild(div, el);
1950
+
1951
+ _user = createInput('username');
1952
+ createFormField(div, el.id, _instance.usernameFormFieldId, _instance.usernameLabel, _user);
1953
+
1954
+ i = createElement(div, 'i', _instance.getCssClass(_classOption.ICON_USER));
1955
+ }
1956
+
1957
+ if (el = $lib('#' + id + 'Password'))
1958
+ {
1959
+ div = createElement(null, '', _instance.getCssClass(_classOption.PASS));
1960
+ el.parentNode.replaceChild(div, el);
1961
+
1962
+ _pass = createInput('password');
1963
+ createFormField(div, el.id, _instance.passwordFormFieldId, _instance.passwordLabel, _pass);
1964
+
1965
+ i = createElement(div, 'i', _instance.getCssClass(_classOption.ICON_KEY));
1966
+ i = createElement(div, 'i', _instance.getCssClass(_classOption.ICON_EYE));
1967
+ bindInputEvents(_pass);
1968
+ $lib.on(i, 'click', togglePasswordDisplay);
1969
+ }
1970
+
1971
+ if (_instance.showRememberMe && (el = $lib('#' + id + 'RememberMe')))
1972
+ {
1973
+ div = createElement(null, '', _instance.getCssClass(_classOption.REMEMBER));
1974
+ el.parentNode.replaceChild(div, el);
1975
+
1976
+ _remember = createElement(null, 'input');
1977
+ _remember.type = 'checkbox';
1978
+ _remember.checked = true;
1979
+ createFormField(div, el.id, _instance.rememberMeFormFieldId, _instance.rememberMeLabel, _remember);
1980
+ }
1981
+
1982
+ if (el = $lib('#' + id + 'LoginButton'))
1983
+ {
1984
+ div = createElement();
1985
+ el.parentNode.replaceChild(div, el);
1986
+ createButton(div, el.id, _instance.loginButtonId, { cssClass: _instance.getCssClass(_classOption.LOGIN), text: _instance.loginButtonLabel, transparent: false }, function () { login(); });
1987
+ }
1988
+
1989
+ if (el = $lib('#' + id + 'FailedMessage'))
1990
+ {
1991
+ _loginFailedMessage = createElement(null, '', _instance.getCssClass(_classOption.MESSAGE));
1992
+ el.parentNode.replaceChild(_loginFailedMessage, el);
1993
+ }
1994
+ }
1995
+
1996
+ function createLoginVerificationScreen(container)
1997
+ {
1998
+ let id = _instance.id + '_LoginVerification_', el, div;
1999
+
2000
+ _loginVerification = createElement(container, '', `${_instance.getCssClass(_classOption.LOGIN_SCREEN)} ${_instance.getCssClass(_classOption.LOGIN_VERIFICATION)}`);
2001
+ _loginVerification.style.display = 'none';
2002
+ _instance.applyTemplate(_loginVerification, 'LoginVerification', getinterpolations(id));
2003
+ createElement(_loginVerification, '', _instance.getCssClass(_classOption.CONTENT), $lib.extract(_loginVerification));
2004
+
2005
+ if (el = $lib('#' + id + 'VerificationCode'))
2006
+ {
2007
+ div = createElement(null, '', _instance.getCssClass(_classOption.CODE));
2008
+ el.parentNode.replaceChild(div, el);
2009
+
2010
+ _verificationCode = createInput('code');
2011
+ _verificationCode.maxLength = _instance.verificationCodeLength;
2012
+ createFormField(div, el.id, _instance.verificationCodeFormFieldId, _instance.verificationCodeLabel, _verificationCode);
2013
+ }
2014
+
2015
+ if (el = $lib('#' + id + 'VerificationButton'))
2016
+ {
2017
+ div = createElement();
2018
+ el.parentNode.replaceChild(div, el);
2019
+ createButton(div, el.id, _instance.verificationButtonId, { cssClass: _instance.getCssClass(_classOption.VERIFICATION), text: _instance.verificationButtonLabel, transparent: false }, function () { verifyCode(); });
2020
+ }
2021
+
2022
+ if (el = $lib('#' + id + 'FailedMessage'))
2023
+ {
2024
+ _verificationFailedMessage = createElement(null, '', _instance.getCssClass(_classOption.MESSAGE));
2025
+ el.parentNode.replaceChild(_verificationFailedMessage, el);
2026
+ }
2027
+ }
2028
+
2029
+ function createFormField(container, id, cloneId, label, input)
2030
+ {
2031
+ let field = $UI.createComponent(componyx.UI.FormField, { id: id, containerElement: container });
2032
+
2033
+ field.clone($UI.store[cloneId], _instance);
2034
+
2035
+ field.setFieldTemplate(input);
2036
+ field.label = label;
2037
+ field.labelDisplay = (input.type == 'checkbox') ? 0 : componyx.UI.FormField.LabelDisplayOption.INSIDE;
2038
+ field.events.onPostRender.priorityAdd(function () { _instance.isReady.apply(_instance); }, null);
2039
+ field.showing = true;
2040
+ field.render();
2041
+ }
2042
+
2043
+ function createInput(name)
2044
+ {
2045
+ return $lib.element('', '', 'input', null, { type: 'text', name: name, autocomplete: 'off', spellcheck: 'false' });
2046
+ }
2047
+
2048
+ function bindInputEvents(input)
2049
+ {
2050
+ if (input.name == 'pass')
2051
+ {
2052
+ $lib.on(input, 'keydown', function (e)
2053
+ {
2054
+ if (e.key == 'Enter')
2055
+ login();
2056
+ });
2057
+ }
2058
+ }
2059
+
2060
+ function togglePasswordDisplay(e)
2061
+ {
2062
+ let el = this,
2063
+ showing = $lib.hasClass(el, _instance.getCssClass(_classOption.ICON_EYE_BLOCKED));
2064
+
2065
+ _pass.type = (showing) ? 'password' : 'text';
2066
+ el.className = (showing) ? _instance.getCssClass(_classOption.ICON_EYE) : _instance.getCssClass(_classOption.ICON_EYE_BLOCKED);
2067
+ }
2068
+
2069
+ function search(terms)
2070
+ {
2071
+ terms = (terms != undefined) ? terms : _searchBox.value;
2072
+ let settings = { terms: terms };
2073
+
2074
+ if ($lib.isEmpty(terms))
2075
+ {
2076
+ if (_searchResult)
2077
+ _searchResult.style.display = 'none';
2078
+
2079
+ return;
2080
+ }
2081
+
2082
+ _instance.ajaxCall('search', settings,
2083
+ {
2084
+ onSuccess: function (args)
2085
+ {
2086
+ showSearchResult(args.data);
2087
+ },
2088
+ onError: function (args)
2089
+ {
2090
+ createSearchResult(_instance.searchNoResultLabel);
2091
+ }
2092
+ });
2093
+ }
2094
+
2095
+ function showSearchResult(data)
2096
+ {
2097
+ let eventArgs = { data: data, container: _searchResult, cancel: false },
2098
+ categories = {};
2099
+
2100
+ createSearchResult('');
2101
+ _instance.events.onPreSearchResults.fire(_instance, eventArgs);
2102
+
2103
+ if (eventArgs.cancel === true)
2104
+ return;
2105
+
2106
+ if (data && data.length > 0)
2107
+ {
2108
+ $lib.each(data, function (item)
2109
+ {
2110
+ let route = item.route.replace(/\\/, '/').toLowerCase(),
2111
+ title = item.title,
2112
+ cat = getSearchCategory(route);
2113
+
2114
+ if (!categories[cat])
2115
+ {
2116
+ categories[cat] = createElement(_searchResult, 'nav');
2117
+ createElement(categories[cat], 'h2', '', cat);
2118
+ }
2119
+
2120
+ let a = $lib.element(categories[cat], '', 'a', title, { href: route }),
2121
+ itemEventArgs = { ...eventArgs, item: item, anchor: a };
2122
+
2123
+ _instance.events.onRenderSearchResult.fire(_instance, itemEventArgs);
2124
+ });
2125
+ }
2126
+ else
2127
+ _searchResult.innerHTML = _instance.searchNoResultLabel;
2128
+
2129
+ $lib.each(categories, function (nav)
2130
+ {
2131
+ nav.firstElementChild.textContent += ' (' + (nav.childNodes.length - 1) + ')';
2132
+ });
2133
+
2134
+ _searchResult.style.display = '';
2135
+ _instance.events.onPostSearchResults.fire(_instance, eventArgs);
2136
+ }
2137
+
2138
+ function createSearchResult(text)
2139
+ {
2140
+ if (!_searchResult)
2141
+ _searchResult = createElement(_instance.element, 'div', _instance.getCssClass(_classOption.SEARCH_RESULT));
2142
+
2143
+ _searchResult.innerHTML = text;
2144
+ }
2145
+
2146
+ function getSearchCategory(route)
2147
+ {
2148
+ if (route.indexOf('/') == -1)
2149
+ return _instance.searchCategoryLabels[''] || '';
2150
+
2151
+ let cat = route.substr(0, route.lastIndexOf('/'));
2152
+ return _instance.searchCategoryLabels[cat] || cat;
2153
+ }
2154
+
2155
+ function hideAll()
2156
+ {
2157
+ hide([_login, _page, _view, _error]);
2158
+ $lib.removeClass(_instance.element, `${_instance.getCssClass(_classOption.LOGIN)} ${_instance.getCssClass(_classOption.ERROR)}`);
2159
+ _activeScreen = _screenOption.PAGE;
2160
+
2161
+ if (!_launch && _instance.menuType != _menuTypeOption.SIDEMENU)
2162
+ {
2163
+ _mainMenu?.hide();
2164
+ _menuButton?.deselect();
2165
+ }
2166
+
2167
+ if (_topbar && !_instance.persistentTopBar)
2168
+ {
2169
+ _topbar.style.display = 'none';
2170
+ $lib.removeClass(_instance.element, _instance.getCssClass(_classOption.TOPBAR));
2171
+ }
2172
+
2173
+ if (_instance.footerDisplay != _footerDisplayOption.PERSISTENT && _footer)
2174
+ hideFooter();
2175
+ }
2176
+
2177
+ function hide(elements)
2178
+ {
2179
+ for (let index = 0; index < elements.length; ++index)
2180
+ {
2181
+ if (elements[index])
2182
+ elements[index].style.display = 'none';
2183
+ }
2184
+ }
2185
+
2186
+ function toggleMenu()
2187
+ {
2188
+ if (_activeScreen == _screenOption.PORTAL && _launch)
2189
+ return; // only switch back to page when the menu was clicked
2190
+
2191
+ if (menuIsShowing())
2192
+ hideMenu();
2193
+ else
2194
+ showMenu();
2195
+ }
2196
+
2197
+ function showMenu(instant)
2198
+ {
2199
+ if (!instant && menuIsShowing())
2200
+ return;
2201
+
2202
+ if (_instance.menuType == _menuTypeOption.PORTALMENU)
2203
+ {
2204
+ hideAll();
2205
+ _activeScreen = _screenOption.PORTAL; // page is not visible with portal-menu
2206
+ document.documentElement.style.overflow = "hidden"; // temporary disable scrollbar otherwise browser calculates available space for portal-menu with vertical-scrollbar
2207
+ _mainMenu.show(); // portal-menu display is always instant
2208
+ resize();
2209
+ }
2210
+ else
2211
+ {
2212
+ _instance.showPage(); // page is visible with side-menu
2213
+ _sideMenuBox.show(instant || _launch); // without animation when launching
2214
+ }
2215
+
2216
+ if (_instance.footerDisplay == _footerDisplayOption.MENU)
2217
+ showFooter();
2218
+
2219
+ $lib.addClass(_instance.element, (_instance.menuType == _menuTypeOption.PORTALMENU) ? _instance.getCssClass(_classOption.PORTAL_MENU) : _instance.getCssClass(_classOption.SIDE_MENU));
2220
+
2221
+ if (_menuButton)
2222
+ {
2223
+ if (!_menuButton.showing)
2224
+ _menuButton.show();
2225
+
2226
+ _menuButton.select();
2227
+ }
2228
+
2229
+ _instance.events.onShowMenu.fire(_instance);
2230
+ }
2231
+
2232
+ function hideMenu(instant, hideFn)
2233
+ {
2234
+ if (!instant && !menuIsShowing() || (_launch && _instance.launchMenu))
2235
+ return;
2236
+
2237
+ if (hideFn)
2238
+ setHideMenuCallback(function () { showMainCategory(); });
2239
+
2240
+ _instance.events.onHideMenu.fire(_instance);
2241
+ _instance.showPage(); // hides the portal-menu
2242
+
2243
+ if (isSideMenu()) // side-menu is not automatically hidden
2244
+ {
2245
+ _menuButton.deselect();
2246
+ _sideMenuBox.hide(instant);
2247
+ }
2248
+ }
2249
+
2250
+ function menuIsShowing()
2251
+ {
2252
+ return ((isSideMenu() && _sideMenuBox.showing) || (!isSideMenu() && _mainMenu.showing));
2253
+ }
2254
+
2255
+ function isSideMenu()
2256
+ {
2257
+ return (_instance.menuType === _menuTypeOption.SIDEMENU);
2258
+ }
2259
+
2260
+ function showFooter()
2261
+ {
2262
+ _footer.style.display = '';
2263
+ $lib.addClass(_instance.element, _instance.getCssClass(_classOption.FOOTER));
2264
+ }
2265
+
2266
+ function hideFooter()
2267
+ {
2268
+ _footer.style.display = 'none';
2269
+ $lib.removeClass(_instance.element, _instance.getCssClass(_classOption.FOOTER));
2270
+ }
2271
+
2272
+ function showLogin()
2273
+ {
2274
+ if (isSideMenu())
2275
+ hideMenu();
2276
+
2277
+ hideAll();
2278
+
2279
+ if (_instance.hideMenuButtonOnLogin)
2280
+ _menuButton?.hide();
2281
+
2282
+ _login.style.display = '';
2283
+ _activeScreen = _screenOption.LOGIN;
2284
+ $lib.addClass(_instance.element, _instance.getCssClass(_classOption.LOGIN));
2285
+ _user.focus();
2286
+ _pass.type = 'password';
2287
+
2288
+ _instance.events.onShowLogin.fire(_instance);
2289
+ }
2290
+
2291
+ function showLoginVerification()
2292
+ {
2293
+ if (isSideMenu())
2294
+ hideMenu();
2295
+
2296
+ hideAll();
2297
+ _loginVerification.style.display = '';
2298
+ _activeScreen = _screenOption.LOGIN_VERIFICATION;
2299
+ $lib.addClass(_instance.element, `${_instance.getCssClass(_classOption.LOGIN)} ${_instance.getCssClass(_classOption.VERIFICATION)}`);
2300
+ _verificationCode.focus();
2301
+
2302
+ _instance.events.onShowLoginVerification.fire(_instance);
2303
+ }
2304
+
2305
+ function login(settings)
2306
+ {
2307
+ if (!settings)
2308
+ settings = { crsfToken: _instance.crsfToken, username: _user.value, password: _pass.value, persistent: (_remember) ? _remember.checked : false };
2309
+
2310
+ invokeAuthenticationMiddleware(settings, function (args)
2311
+ {
2312
+ args.data.cancel = false;
2313
+ _instance.events.onLoginAttempt.fire(_instance, args.data);
2314
+
2315
+ if (args.data.cancel === true) // we don't have to handle this further (custom handeling of args.data.redirectPath)
2316
+ return;
2317
+
2318
+ if (!args.data.authenticationState)
2319
+ {
2320
+ authenticationFailed([_pass], args.data, _loginFailedMessage);
2321
+ }
2322
+ else if (args.data.authenticationState == componyx.UI.AppSkin.AuthenticationStateOption.CREDENTIALS_VALIDATED)
2323
+ {
2324
+ if (args.data.redirectPath) // not handled in login attempt event
2325
+ {
2326
+ if (_instance.useBindary && $bindary && $bindary.routes && $bindary.routes.length) // we only handle default route behaviour
2327
+ $bindary.loadRoute(args.data.redirectPath);
2328
+ }
2329
+ else
2330
+ {
2331
+ _instance.showLoginVerification();
2332
+ }
2333
+ }
2334
+ else
2335
+ {
2336
+ window.location.reload();
2337
+ }
2338
+ });
2339
+ }
2340
+
2341
+ function verifyCode(settings)
2342
+ {
2343
+ if (!settings)
2344
+ settings = { crsfToken: _instance.crsfToken, verificationCode: _verificationCode.value };
2345
+
2346
+ invokeAuthenticationMiddleware(settings, function (args)
2347
+ {
2348
+ args.data.cancel = false;
2349
+ _instance.events.onLoginVerificationAttempt.fire(_instance, args.data);
2350
+
2351
+ if (args.data.cancel === true) // we don't have to handle this further
2352
+ return;
2353
+
2354
+ if (!args.data.authenticationState)
2355
+ {
2356
+ let reason = args.data.failureReason;
2357
+ authenticationFailed([_verificationCode], args.data, _verificationFailedMessage);
2358
+
2359
+ if (reason == _failureReasonOption.VERIFICATION_CODE_EXPIRED || reason == _failureReasonOption.MAX_VERIFY_ATTEMPTS_EXCEEDED)
2360
+ setTimeout(function () { window.location.reload(); }, _instance.verificationFailedReloadDelay);
2361
+
2362
+ }
2363
+ else if (args.data.authenticationState == componyx.UI.AppSkin.AuthenticationStateOption.AUTHENTICATED)
2364
+ {
2365
+ window.location.reload();
2366
+ }
2367
+ });
2368
+ }
2369
+
2370
+ function logout(settings)
2371
+ {
2372
+ if (!settings)
2373
+ settings = { signout: true };
2374
+
2375
+ invokeAuthenticationMiddleware(settings, function (args)
2376
+ {
2377
+ args.data.cancel = false;
2378
+ _instance.events.onLogout.fire(_instance, args.data);
2379
+
2380
+ if (args.data.cancel === true) // we don't have to handle this further
2381
+ return;
2382
+
2383
+ if (args.data && args.data.failureMessage)
2384
+ $lib.log(args.data.failureMessage);
2385
+
2386
+ window.location.reload();
2387
+ });
2388
+ }
2389
+
2390
+ function authenticationFailed(inputs, data, messageElement)
2391
+ {
2392
+ let label = getAuthenticationFailureLabel(data.failureReason);
2393
+
2394
+ if (messageElement)
2395
+ messageElement.innerHTML = data.failureMessage || label || '';
2396
+
2397
+ inputs.forEach((input) =>
2398
+ {
2399
+ input.value = '';
2400
+ });
2401
+
2402
+ inputs[0].focus();
2403
+
2404
+ let logMessage = 'Authentication failed. ';
2405
+
2406
+ if (data.failureReason)
2407
+ logMessage += `Reason: ${_failureReasonOption.getName(data.failureReason)}. `;
2408
+
2409
+ if (data.failureMessage)
2410
+ logMessage += `Message: ${data.failureMessage}.`;
2411
+
2412
+ $lib.log(logMessage);
2413
+ }
2414
+
2415
+ function getAuthenticationFailureLabel(reason)
2416
+ {
2417
+ if ($lib.isEmpty(reason))
2418
+ return _instance.authenticationFailureLabel;
2419
+ else
2420
+ return _instance.authenticationFailureReasonLabels[reason];
2421
+ }
2422
+
2423
+ function invokeAuthenticationMiddleware(settings, onSuccess)
2424
+ {
2425
+ _instance.ajaxCall('authenticate', settings, { onSuccess: onSuccess });
2426
+ }
2427
+
2428
+ function updateMenuButtonVisibility()
2429
+ {
2430
+ if (!_menuButton)
2431
+ return;
2432
+
2433
+ let shouldShow = _instance.persistentMenuButton
2434
+ || !_allInHeader
2435
+ || $lib.hasClass(_instance.element, _instance.getCssClass(_classOption.HEADER_MENU_HIDDEN))
2436
+ || !$lib.isEmpty(_mainMenu?.getActiveCategoryId());
2437
+
2438
+ if (shouldShow && !_menuButton.showing)
2439
+ _menuButton.show();
2440
+ else if (!shouldShow && _menuButton.showing)
2441
+ _menuButton.hide();
2442
+ }
2443
+
2444
+
2445
+ function resize(e)
2446
+ {
2447
+ clearTimeout(_resizeTimerId);
2448
+ _resizeTimerId = setTimeout(function ()
2449
+ {
2450
+ let docStyle = document.documentElement.style;
2451
+
2452
+ checkHeaderOverflow();
2453
+ docStyle.overflow = "";
2454
+ }, 0);
2455
+ }
2456
+
2457
+ function checkHeaderOverflow()
2458
+ {
2459
+ if (!_headerMenu || !_headerMenu.itemList.length)
2460
+ {
2461
+ headerOverflowCheckComplete();
2462
+ return;
2463
+ }
2464
+
2465
+ clearTimeout(_headerOverflowTimerId);
2466
+
2467
+ if (_allInHeader)
2468
+ $lib.addClass(_instance.element, _instance.getCssClass(_classOption.HEADER_ALL));
2469
+
2470
+ _headerOverflowTimerId = setTimeout(function ()
2471
+ {
2472
+ _headerMenu.show();
2473
+
2474
+ let nav = $lib(null, _headerMenu.element, 'nav', true), topbarDisplay = null;
2475
+
2476
+ if (!nav)
2477
+ return;
2478
+
2479
+ if (_topbar && $lib.contains(_topbar, _headerMenu.element)) // header menu in topbar
2480
+ {
2481
+ topbarDisplay = _topbar.style.display || '';
2482
+ _topbar.style.display = ''; // display topbar for overflow calculation
2483
+ }
2484
+
2485
+ let fits = (nav.scrollWidth == nav.clientWidth),
2486
+ cssClass = _instance.getCssClass(_classOption.HEADER_OVERFLOW), el = _instance.element, retry,
2487
+ hasClass = $lib.hasClass(el, cssClass),
2488
+ obj = { headerOverflow: !fits, cancel: false };
2489
+
2490
+ _instance.events.onResize.fire(_instance, obj);
2491
+
2492
+ if (obj.cancel !== true)
2493
+ {
2494
+ if (fits)
2495
+ {
2496
+ let hasRoutes = _instance.useBindary && $bindary && $bindary.routes && $bindary.routes.length > 0;
2497
+ $lib.removeClass(el, _instance.getCssClass(_classOption.HEADER_MENU_HIDDEN));
2498
+
2499
+ if (_instance.menuType == _menuTypeOption.SIDEMENU && _allInHeader && (!_launch || !hasRoutes) && $lib.isEmpty(_mainMenu.getActiveCategoryId()))
2500
+ {
2501
+ updateMenuButtonVisibility();
2502
+ hideMenu(true);
2503
+ }
2504
+ }
2505
+ else
2506
+ _headerMenu.hide();
2507
+
2508
+ if (!fits && hasClass)
2509
+ {
2510
+ $lib.addClass(el, _instance.getCssClass(_classOption.HEADER_MENU_HIDDEN));
2511
+
2512
+ if (_menuButton && !_menuButton.showing)
2513
+ _menuButton.show();
2514
+ }
2515
+ else if (!fits)
2516
+ {
2517
+ $lib.addClass(el, cssClass);
2518
+ _windowWidth = $lib.getWindowSize().width; // store window width to detect turning point
2519
+ retry = true; // check if the header overflows when the overflow class is applied
2520
+ }
2521
+ else if (fits && hasClass && $lib.getWindowSize().width > _windowWidth) // change in window size (turning point)
2522
+ {
2523
+ $lib.removeClass(el, cssClass);
2524
+
2525
+ if (nav.scrollWidth != nav.clientWidth)
2526
+ $lib.addClass(el, cssClass);
2527
+ else
2528
+ retry = true; // check if the header overflows when the overflow class is removed
2529
+ }
2530
+ }
2531
+
2532
+ if (topbarDisplay != null)
2533
+ _topbar.style.display = topbarDisplay;
2534
+
2535
+ if (retry)
2536
+ checkHeaderOverflow();
2537
+ else
2538
+ headerOverflowCheckComplete();
2539
+ }, 0);
2540
+ }
2541
+
2542
+ function headerOverflowCheckComplete()
2543
+ {
2544
+ if (_instance.useBindary && $bindary && $bindary.routes && $bindary.routes.length > 0)
2545
+ return; // Bindary's own route load will clear _launch instead
2546
+
2547
+ _launch = false;
2548
+ }
2549
+
2550
+ function detectMenuToggle(e)
2551
+ {
2552
+ let src = $lib.eventSource(e);
2553
+
2554
+ if (src == _menuButton.element || $lib.contains(_menuButton.element, src) || src == _mainMenu.element || $lib.contains(_mainMenu.element, src))
2555
+ return;
2556
+
2557
+ if (_sideMenuBox.showing)
2558
+ hideMenu();
2559
+ }
2560
+
2561
+ function hideSearchResult(e)
2562
+ {
2563
+ let target = e.target;
2564
+
2565
+ if (!_searchResult)
2566
+ return;
2567
+
2568
+ if (target.nodeName.toLowerCase() == 'a' || (target != _searchBox && target != _searchBox.parentNode && target != _searchResult && !$lib.contains(_searchResult, target)))
2569
+ _searchResult.style.display = 'none';
2570
+ }
2571
+
2572
+ function dispose()
2573
+ {
2574
+ $lib.off(window, 'resize', resize);
2575
+ $lib.off(_instance.element, 'click', detectMenuToggle);
2576
+ $lib.off(document, 'click', hideSearchResult);
2577
+ }
2578
+ };
2579
+
2580
+ /**
2581
+ * @see {@link componyx.UI.base.methods}
2582
+ */
2583
+ componyx.UI.AppSkin.prototype = Object.create($base.methods);
2584
+ componyx.UI.AppSkin.prototype.constructor = componyx.UI.AppSkin;
2585
+
2586
+ /**
2587
+ * ScreenOption
2588
+ * @readonly
2589
+ * @enum {number}
2590
+ */
2591
+ componyx.UI.AppSkin.ScreenOption =
2592
+ {
2593
+ /** the login screen is visible */
2594
+ LOGIN: 0,
2595
+ /** the login verification screen is visible */
2596
+ LOGIN_VERIFICATION: 1,
2597
+ /** the portal navigation screen is visible */
2598
+ PORTAL: 2,
2599
+ /** the page is visible */
2600
+ PAGE: 3
2601
+ };
2602
+
2603
+ /**
2604
+ * MenuTypeOption
2605
+ * @readonly
2606
+ * @enum {number}
2607
+ */
2608
+ componyx.UI.AppSkin.MenuTypeOption =
2609
+ {
2610
+ /** the main menu is displayed as portal navigation */
2611
+ PORTALMENU: 0,
2612
+ /** the main menu is displayed as side navigation */
2613
+ SIDEMENU: 1
2614
+ };
2615
+
2616
+ /**
2617
+ * MenuSectionOption
2618
+ * @readonly
2619
+ * @enum {number}
2620
+ */
2621
+ componyx.UI.AppSkin.MenuSectionOption =
2622
+ {
2623
+ /** the menu-item will be rendered in the header and main menu-sections */
2624
+ HEADER: 0,
2625
+ /** the menu-item will be rendered in the main menu-section */
2626
+ MAIN: 1,
2627
+ /** the menu-item will be rendered in the footer menu-section */
2628
+ FOOTER: 2,
2629
+ /** the menu-item will be rendered in the quick-launch menu-section */
2630
+ QUICKLAUNCH: 3
2631
+ };
2632
+
2633
+ /**
2634
+ * ButtonTileSizeOption
2635
+ * @readonly
2636
+ * @enum {number}
2637
+ */
2638
+ componyx.UI.AppSkin.TileSizeOption =
2639
+ {
2640
+ /** renders the menu-item button as small tile in the portal-menu */
2641
+ SMALL: 1,
2642
+ /** renders the menu-item button as small-wide tile in the portal-menu */
2643
+ SMALL_WIDE: 2,
2644
+ /** renders the menu-item button as medium tile in the portal-menu */
2645
+ MEDIUM: 4,
2646
+ /** renders the menu-item button as medium-wide tile in the portal-menu */
2647
+ MEDIUM_WIDE: 8,
2648
+ /** renders the menu-item button as large tile in the portal-menu */
2649
+ LARGE: 16,
2650
+
2651
+ getName: function (value) { return $base.static.getKeyByValue(this, value).toLowerCase(); }
2652
+ };
2653
+
2654
+ /**
2655
+ * RoutePathMatchingOption
2656
+ * @readonly
2657
+ * @enum {number}
2658
+ */
2659
+ componyx.UI.AppSkin.RoutePathMatchingOption =
2660
+ {
2661
+ /** there is no menu-item route-path matching */
2662
+ DISABLED: 0,
2663
+ /** matches if the item's route-path equals the current URL route-path exactly */
2664
+ EXACT: 1,
2665
+ /** matches if the item's route-path equals the current URL route-path while ignoring possible parameters preceded by a question mark character. */
2666
+ PARTIAL: 2
2667
+ };
2668
+
2669
+ /**
2670
+ * FooterDisplayOption
2671
+ * @readonly
2672
+ * @enum {number}
2673
+ */
2674
+ componyx.UI.AppSkin.FooterDisplayOption =
2675
+ {
2676
+ /** the footer is not visible */
2677
+ DISABLED: 0,
2678
+ /** the footer is visible when the menu is visible */
2679
+ MENU: 1,
2680
+ /** the footer is always visible */
2681
+ PERSISTENT: 2
2682
+ };
2683
+
2684
+ /**
2685
+ * FailureReasonOption
2686
+ * @readonly
2687
+ * @enum {number}
2688
+ */
2689
+ componyx.UI.AppSkin.FailureReasonOption =
2690
+ {
2691
+ /** Provided CRSF Token does not match with token in Session object. */
2692
+ CRSF_TOKEN_MISMATCH: 0,
2693
+ /** Total attempts exceeded configured max sign-in attempts. */
2694
+ MAX_SIGNIN_ATTEMPTS_EXCEEDED: 1,
2695
+ /** Provided username and/or password do not match. */
2696
+ USERNAME_OR_PASSWORD_MISMATCH: 2,
2697
+ /** Provided verificatin code does not match.*/
2698
+ VERIFICATION_CODE_MISMATCH: 3,
2699
+ /** The generated verification-code has expired.*/
2700
+ VERIFICATION_CODE_EXPIRED: 4,
2701
+ /** Total attempts exceeded the configured max verify code attempts.*/
2702
+ MAX_VERIFY_ATTEMPTS_EXCEEDED: 5,
2703
+ /** Other reason */
2704
+ OTHER: 6,
2705
+
2706
+ getName: function (value) { return $base.static.getKeyByValue(this, value).toLowerCase(); }
2707
+ };
2708
+
2709
+ /**
2710
+ * AuthenticationStateOption
2711
+ * @readonly
2712
+ * @enum {number}
2713
+ */
2714
+ componyx.UI.AppSkin.AuthenticationStateOption =
2715
+ {
2716
+ /** The user is not authenticated. */
2717
+ UNAUTHENTICATED: 0,
2718
+ /** The credentials have been successfully validated but the user is not yet authenticated (Step 1 in Two-Factor authentication). */
2719
+ CREDENTIALS_VALIDATED: 1,
2720
+ /** The user is successfully authenticated. */
2721
+ AUTHENTICATED: 2
2722
+ };
2723
+
2724
+ /**
2725
+ * Creates an instance of the AppSkin MenuItem.
2726
+ * @class
2727
+ * @param {Object} properties The properties used to initialize the object.
2728
+ * @property {componyx.UI.AppSkin.MenuSectionOption} menuSection=HEADER Gets or sets a value indicating in which menu-section the item is displayed.
2729
+ * @property {componyx.UI.AppSkin.TileSizeOption} groupTileSize Gets or sets the tile-size of the portal menu group.
2730
+ * @property {componyx.UI.AppSkin.TileSizeOption} buttonTileSize=MEDIUM Gets or sets the tile-size of the portal menu buttons.
2731
+ * @property {Boolean} categoryInHeader Gets or sets a value indicating if the category (isCategory:true) is shown in the header-menu when menuSection:HEADER. By default the category is always displayed in the main-menu.
2732
+ * @augments componyx.UI.Menu.Item
2733
+ * @see {@link componyx.UI.Menu.Item}
2734
+ * @see {@link componyx.UI.base.Item}
2735
+ */
2736
+ componyx.UI.AppSkin.MenuItem = function (properties)
2737
+ {
2738
+ this.menuSection = componyx.UI.AppSkin.MenuSectionOption.HEADER;
2739
+ this.groupTileSize = null;
2740
+ this.buttonTileSize = componyx.UI.AppSkin.TileSizeOption.MEDIUM;
2741
+ this.categoryInHeader = false;
2742
+ componyx.UI.Menu.Item.call(this, properties);
2743
+ };
2744
+ })(window);
2745
+
2746
+ const AppSkin = componyx.UI.AppSkin;
2747
+
2748
+ export { AppSkin };