@brandup/ui-app 1.0.1

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.
@@ -0,0 +1,886 @@
1
+ 'use strict';
2
+
3
+ const ElemAttributeName = "uiElement";
4
+ const ElemPropertyName = "brandupUiElement";
5
+ const CommandAttributeName = "command";
6
+ const CommandExecutingCssClassName = "executing";
7
+ class UIElement {
8
+ __element = null;
9
+ __events = {};
10
+ __commandHandlers = {};
11
+ // Element members
12
+ get element() { return this.__element; }
13
+ setElement(elem) {
14
+ if (!elem)
15
+ throw "Not set value elem.";
16
+ if (this.__element || UIElement.hasElement(elem))
17
+ throw "UIElement already defined";
18
+ this.__element = elem;
19
+ this.__element[ElemPropertyName] = this;
20
+ this.__element.dataset[ElemAttributeName] = this.typeName;
21
+ this.defineEvent("command", { cancelable: false, bubbles: true });
22
+ this._onRenderElement(elem);
23
+ }
24
+ // static members
25
+ static hasElement(elem) {
26
+ return !!elem.dataset[ElemAttributeName];
27
+ }
28
+ // HTMLElement event members
29
+ defineEvent(eventName, eventOptions) {
30
+ this.__events[eventName] = eventOptions ? eventOptions : null;
31
+ }
32
+ raiseEvent(eventName, eventArgs) {
33
+ if (!(eventName in this.__events))
34
+ throw new Error(`Not found event "${eventName}".`);
35
+ const eventOptions = this.__events[eventName];
36
+ const eventInit = {};
37
+ if (eventOptions) {
38
+ if (eventOptions.bubbles)
39
+ eventInit.bubbles = eventOptions.bubbles;
40
+ if (eventOptions.cancelable)
41
+ eventInit.cancelable = eventOptions.cancelable;
42
+ if (eventOptions.composed)
43
+ eventInit.composed = eventOptions.composed;
44
+ }
45
+ eventInit.detail = eventArgs ? eventArgs : {};
46
+ const event = new CustomEvent(eventName, eventInit);
47
+ return this.dispatchEvent(event);
48
+ }
49
+ addEventListener(type, listener, options) {
50
+ this.__element?.addEventListener(type, listener, options);
51
+ }
52
+ removeEventListener(type, listener, options) {
53
+ this.__element?.removeEventListener(type, listener, options);
54
+ }
55
+ dispatchEvent(event) {
56
+ if (!this.__element)
57
+ throw new Error("HTMLElement is not defined.");
58
+ return this.__element.dispatchEvent(event);
59
+ }
60
+ // Command members
61
+ registerCommand(name, execute, canExecute) {
62
+ name = this.verifyCommandName(name);
63
+ this.__commandHandlers[name] = {
64
+ name: name,
65
+ execute,
66
+ canExecute,
67
+ isExecuting: false
68
+ };
69
+ }
70
+ registerAsyncCommand(name, delegate, canExecute) {
71
+ name = this.verifyCommandName(name);
72
+ this.__commandHandlers[name] = {
73
+ name: name,
74
+ delegate,
75
+ canExecute,
76
+ isExecuting: false
77
+ };
78
+ }
79
+ hasCommand(name) {
80
+ return name.toLowerCase() in this.__commandHandlers;
81
+ }
82
+ execCommand(name, elem) {
83
+ if (!this.__element)
84
+ throw new Error("UIElement is not set HTMLElement.");
85
+ const key = name.toLowerCase();
86
+ if (!(key in this.__commandHandlers))
87
+ throw new Error(`Command "${name}" is not registered.`);
88
+ const context = {
89
+ target: elem,
90
+ uiElem: this,
91
+ transparent: false
92
+ };
93
+ const handler = this.__commandHandlers[key];
94
+ if (handler.isExecuting)
95
+ return { result: CommandsExecStatus.AlreadyExecuting, context };
96
+ handler.isExecuting = true;
97
+ if (!this._onCanExecCommand(name, elem)) {
98
+ handler.isExecuting = false;
99
+ return { result: CommandsExecStatus.NotAllow, context };
100
+ }
101
+ if (handler.canExecute && !handler.canExecute(elem, context)) {
102
+ handler.isExecuting = false;
103
+ return { result: CommandsExecStatus.NotAllow, context };
104
+ }
105
+ this.raiseEvent("command", {
106
+ name: handler.name,
107
+ uiElem: this,
108
+ elem: this.__element
109
+ });
110
+ if (handler.execute) {
111
+ // Если команда синхронная.
112
+ try {
113
+ handler.execute(elem, context);
114
+ }
115
+ finally {
116
+ handler.isExecuting = false;
117
+ }
118
+ }
119
+ else if (handler.delegate) {
120
+ // Если команда асинхронная.
121
+ elem.classList.add(CommandExecutingCssClassName);
122
+ let timeoutId = 0;
123
+ const endFunc = () => {
124
+ handler.isExecuting = false;
125
+ elem.classList.remove(CommandExecutingCssClassName);
126
+ };
127
+ const asyncContext = {
128
+ target: elem,
129
+ uiElem: this,
130
+ transparent: context.transparent,
131
+ complate: () => {
132
+ clearTimeout(timeoutId);
133
+ endFunc();
134
+ }
135
+ };
136
+ const handlerResult = handler.delegate(asyncContext);
137
+ if (handlerResult && handlerResult instanceof Promise)
138
+ handlerResult.finally(() => asyncContext.complate());
139
+ if (handler.isExecuting && asyncContext.timeout) {
140
+ timeoutId = window.setTimeout(() => {
141
+ if (asyncContext.timeoutCallback)
142
+ asyncContext.timeoutCallback();
143
+ endFunc();
144
+ }, asyncContext.timeout);
145
+ }
146
+ context.transparent = asyncContext.transparent;
147
+ }
148
+ else
149
+ throw new Error("Not set command execute flow.");
150
+ return { result: CommandsExecStatus.Success, context: context };
151
+ }
152
+ verifyCommandName(name) {
153
+ const key = name.toLowerCase();
154
+ if (key in this.__commandHandlers)
155
+ throw new Error(`Command "${name}" already registered.`);
156
+ return key;
157
+ }
158
+ _onRenderElement(_elem) {
159
+ return;
160
+ }
161
+ _onCanExecCommand(_name, _elem) {
162
+ return true;
163
+ }
164
+ destroy() {
165
+ const elem = this.__element;
166
+ if (!elem)
167
+ return;
168
+ this.__element = null;
169
+ delete elem.dataset[ElemAttributeName];
170
+ delete elem[ElemPropertyName];
171
+ }
172
+ }
173
+ var CommandsExecStatus;
174
+ (function (CommandsExecStatus) {
175
+ CommandsExecStatus[CommandsExecStatus["NotAllow"] = 1] = "NotAllow";
176
+ CommandsExecStatus[CommandsExecStatus["AlreadyExecuting"] = 2] = "AlreadyExecuting";
177
+ CommandsExecStatus[CommandsExecStatus["Success"] = 3] = "Success";
178
+ })(CommandsExecStatus || (CommandsExecStatus = {}));
179
+ const fundUiElementByCommand = (elem, commandName) => {
180
+ while (elem) {
181
+ if (elem.dataset[ElemAttributeName]) {
182
+ const uiElem = elem[ElemPropertyName];
183
+ if (uiElem.hasCommand(commandName))
184
+ return uiElem;
185
+ }
186
+ if (typeof elem.parentElement === "undefined")
187
+ elem = elem.parentNode;
188
+ else if (elem.parentElement)
189
+ elem = elem.parentElement;
190
+ else
191
+ break;
192
+ }
193
+ return null;
194
+ };
195
+ const commandClickHandler = (e) => {
196
+ let commandElem = e.target;
197
+ while (commandElem) {
198
+ if (commandElem.dataset[CommandAttributeName])
199
+ break;
200
+ if (commandElem === e.currentTarget)
201
+ return;
202
+ commandElem = commandElem.parentElement;
203
+ }
204
+ if (!commandElem)
205
+ return;
206
+ const commandName = commandElem.dataset[CommandAttributeName];
207
+ if (!commandName)
208
+ throw new Error("Command data attribute is not have value.");
209
+ const uiElem = fundUiElementByCommand(commandElem, commandName);
210
+ if (uiElem === null) {
211
+ console.warn(`Not find handler for command "${commandName}".`);
212
+ }
213
+ else {
214
+ const commandResult = uiElem.execCommand(commandName, commandElem);
215
+ if (commandResult.context.transparent)
216
+ return;
217
+ }
218
+ e.preventDefault();
219
+ e.stopPropagation();
220
+ e.stopImmediatePropagation();
221
+ };
222
+ window.addEventListener("click", commandClickHandler, false);
223
+
224
+ class MiddlewareInvoker {
225
+ middleware;
226
+ __next = null;
227
+ constructor(middleware) {
228
+ this.middleware = middleware;
229
+ }
230
+ next(middleware) {
231
+ if (this.__next)
232
+ this.__next.next(middleware);
233
+ else
234
+ this.__next = new MiddlewareInvoker(middleware);
235
+ }
236
+ async invoke(method, context) {
237
+ try {
238
+ await this.__invoke(method, context);
239
+ }
240
+ catch (e) {
241
+ console.error(`Error middleware "${method}" execution: ${e}`);
242
+ throw e;
243
+ }
244
+ }
245
+ async __invoke(method, context) {
246
+ const nextFunc = () => { return this.__next ? this.__next.__invoke(method, context) : Promise.resolve(); };
247
+ const methodFunc = this.middleware[method];
248
+ if (typeof methodFunc === "function") {
249
+ const methodResult = methodFunc.call(this.middleware, context, nextFunc);
250
+ if (!methodResult || !(methodResult instanceof Promise))
251
+ throw new Error(`Middleware method "${method}" is not async.`);
252
+ await methodResult;
253
+ }
254
+ else
255
+ await nextFunc();
256
+ }
257
+ }
258
+
259
+ const result = {
260
+ LoadingElementClass: "loading",
261
+ NavUrlClassName: "applink",
262
+ FormClassName: "appform",
263
+ NavUrlAttributeName: "data-nav-url",
264
+ NavUrlReplaceAttributeName: "data-nav-replace",
265
+ NavIgnoreAttributeName: "data-nav-ignore",
266
+ STATE_CLASS: {
267
+ LOADING: "bp-state-loading",
268
+ LOADED: "bp-state-loaded",
269
+ READY: "bp-state-ready"
270
+ }
271
+ };
272
+
273
+ var constants = /*#__PURE__*/Object.freeze({
274
+ __proto__: null,
275
+ default: result
276
+ });
277
+
278
+ const STATE_MIDDLEWARE_NAME = "app-state";
279
+ const StateMiddlewareFactory = () => {
280
+ let counter = 0;
281
+ const begin = (context) => {
282
+ counter++;
283
+ context.app.element?.classList.remove(result.STATE_CLASS.LOADED);
284
+ context.app.element?.classList.add(result.STATE_CLASS.LOADING);
285
+ };
286
+ const end = (context) => {
287
+ counter--;
288
+ if (counter <= 0) {
289
+ counter = 0;
290
+ context.app.element?.classList.remove(result.STATE_CLASS.LOADING);
291
+ context.app.element?.classList.add(result.STATE_CLASS.LOADED);
292
+ }
293
+ };
294
+ return {
295
+ name: STATE_MIDDLEWARE_NAME,
296
+ start: async (context, next) => {
297
+ begin(context);
298
+ try {
299
+ await next();
300
+ }
301
+ catch (reason) {
302
+ end(context);
303
+ throw reason;
304
+ }
305
+ },
306
+ loaded: async (context, next) => {
307
+ try {
308
+ await next();
309
+ context.app.element?.classList.add(result.STATE_CLASS.READY);
310
+ }
311
+ catch (reason) {
312
+ end(context);
313
+ throw reason;
314
+ }
315
+ },
316
+ navigate: async (context, next) => {
317
+ begin(context);
318
+ try {
319
+ await next();
320
+ }
321
+ finally {
322
+ if (context.source == "first")
323
+ end(context);
324
+ end(context);
325
+ }
326
+ },
327
+ submit: async (context, next) => {
328
+ try {
329
+ await next();
330
+ }
331
+ finally {
332
+ end(context);
333
+ }
334
+ },
335
+ stop: async (_context, next) => {
336
+ await next();
337
+ }
338
+ };
339
+ };
340
+
341
+ const win = window;
342
+ const doc = window.document;
343
+ const body = doc.body;
344
+ const loc = window.location;
345
+ var BROWSER = {
346
+ window: win,
347
+ document: doc,
348
+ body,
349
+ location: loc,
350
+ reload: () => loc.reload()
351
+ };
352
+
353
+ var browser = /*#__PURE__*/Object.freeze({
354
+ __proto__: null,
355
+ default: BROWSER
356
+ });
357
+
358
+ const HYPERLINK_MIDDLEWARE_NAME = "app-hyperlink";
359
+ const HyperLinkMiddlewareFactory = () => {
360
+ let __ctrlPressed = false;
361
+ const onKeyDownUp = (e) => {
362
+ __ctrlPressed = e.ctrlKey;
363
+ };
364
+ let onClick;
365
+ return {
366
+ name: HYPERLINK_MIDDLEWARE_NAME,
367
+ start: async (context, next) => {
368
+ await next();
369
+ BROWSER.window.addEventListener("click", onClick = (e) => {
370
+ let elem = e.target;
371
+ let ignore = false;
372
+ while (elem) {
373
+ if (elem instanceof Element) {
374
+ if (elem.hasAttribute(result.NavIgnoreAttributeName)) {
375
+ ignore = true;
376
+ break;
377
+ }
378
+ if (elem.classList && elem.classList.contains(result.NavUrlClassName))
379
+ break;
380
+ }
381
+ if (elem === e.currentTarget)
382
+ return;
383
+ elem = elem.parentElement;
384
+ }
385
+ if (!elem || __ctrlPressed || elem.getAttribute("target") === "_blank")
386
+ return;
387
+ e.preventDefault();
388
+ e.stopPropagation();
389
+ if (ignore)
390
+ return;
391
+ let url;
392
+ if (elem.tagName === "A")
393
+ url = elem.getAttribute("href");
394
+ else if (elem.hasAttribute(result.NavUrlAttributeName))
395
+ url = elem.getAttribute(result.NavUrlAttributeName);
396
+ else
397
+ throw "Not found url for navigation.";
398
+ if (elem.classList.contains(result.LoadingElementClass))
399
+ return;
400
+ elem.classList.add(result.LoadingElementClass);
401
+ context.app
402
+ .nav({ url, replace: elem.hasAttribute(result.NavUrlReplaceAttributeName), data: { clickElem: elem } })
403
+ .finally(() => elem.classList.remove(result.LoadingElementClass));
404
+ }, false);
405
+ BROWSER.window.addEventListener("keydown", onKeyDownUp, false);
406
+ BROWSER.window.addEventListener("keyup", onKeyDownUp, false);
407
+ },
408
+ stop: (_context, next) => {
409
+ BROWSER.window.removeEventListener("click", onClick, false);
410
+ BROWSER.window.removeEventListener("keydown", onKeyDownUp, false);
411
+ BROWSER.window.removeEventListener("keyup", onKeyDownUp, false);
412
+ return next();
413
+ }
414
+ };
415
+ };
416
+
417
+ const FORM_MIDDLEWARE_NAME = "app-form";
418
+ const FormMiddlewareFactory = () => {
419
+ let onWindowSubmit;
420
+ return {
421
+ name: FORM_MIDDLEWARE_NAME,
422
+ start: async (context, next) => {
423
+ await next();
424
+ BROWSER.window.addEventListener("submit", onWindowSubmit = (e) => {
425
+ const form = e.target;
426
+ if (!form.classList.contains(result.FormClassName))
427
+ return;
428
+ e.preventDefault();
429
+ context.app.submit({ form, button: e.submitter instanceof HTMLButtonElement ? e.submitter : null });
430
+ }, false);
431
+ },
432
+ submit: async (context, next) => {
433
+ const { form, button, method } = context;
434
+ if (!form.checkValidity())
435
+ return Promise.reject('Form is invalid.');
436
+ if (form.classList.contains(result.LoadingElementClass))
437
+ return form["_submit_"];
438
+ form.classList.add(result.LoadingElementClass);
439
+ if (button)
440
+ button.classList.add(result.LoadingElementClass);
441
+ context.replace = form.hasAttribute(result.NavUrlReplaceAttributeName);
442
+ if (button && button.hasAttribute(result.NavUrlReplaceAttributeName))
443
+ context.replace = true;
444
+ try {
445
+ if (method === "GET")
446
+ await context.app.nav({ url: context.url, query: new FormData(form), data: context.data });
447
+ else
448
+ await next();
449
+ }
450
+ finally {
451
+ form.classList.remove(result.LoadingElementClass);
452
+ if (button)
453
+ button.classList.remove(result.LoadingElementClass);
454
+ }
455
+ },
456
+ stop: (_context, next) => {
457
+ BROWSER.window.removeEventListener("submit", onWindowSubmit, false);
458
+ return next();
459
+ }
460
+ };
461
+ };
462
+
463
+ const parseUrl = (url) => {
464
+ const loc = BROWSER.location;
465
+ let origin = loc.origin;
466
+ let path;
467
+ let query = null;
468
+ let hash = null;
469
+ let isExternal = false;
470
+ if (!url) {
471
+ path = loc.pathname;
472
+ if (loc.search)
473
+ query = new URLSearchParams(loc.search);
474
+ if (loc.hash)
475
+ hash = loc.hash;
476
+ }
477
+ else {
478
+ if (url.startsWith("#")) {
479
+ path = loc.pathname;
480
+ query = new URLSearchParams(loc.search);
481
+ hash = url;
482
+ }
483
+ else if (url.startsWith("?")) {
484
+ const hastIndex = url.lastIndexOf("#");
485
+ if (hastIndex !== -1) {
486
+ hash = url.substring(hastIndex);
487
+ url = url.substring(0, hastIndex);
488
+ }
489
+ path = loc.pathname;
490
+ query = new URLSearchParams(url);
491
+ }
492
+ else if (url.startsWith("http")) {
493
+ const u = new URL(url);
494
+ if (u.origin != origin) {
495
+ origin = u.origin;
496
+ isExternal = true;
497
+ }
498
+ path = u.pathname;
499
+ query = u.searchParams;
500
+ hash = u.hash || null;
501
+ }
502
+ else {
503
+ const hastIndex = url.lastIndexOf("#");
504
+ if (hastIndex !== -1) {
505
+ hash = url.substring(hastIndex);
506
+ url = url.substring(0, hastIndex);
507
+ }
508
+ const queryIndex = url.lastIndexOf("?");
509
+ if (queryIndex !== -1) {
510
+ query = new URLSearchParams(url.substring(queryIndex));
511
+ url = url.substring(0, queryIndex);
512
+ }
513
+ path = url;
514
+ if (!path.startsWith("/")) {
515
+ let curPath = loc.pathname;
516
+ if (curPath.endsWith("/"))
517
+ curPath = curPath.substring(0, curPath.length - 1);
518
+ path = curPath + "/" + path;
519
+ }
520
+ }
521
+ }
522
+ if (!path)
523
+ path = "/";
524
+ else if (path.length > 1 && path.endsWith("/"))
525
+ path = path.substring(0, path.length - 1);
526
+ if (!query)
527
+ query = new URLSearchParams();
528
+ if (hash === "#")
529
+ hash = null;
530
+ else if (hash)
531
+ hash = hash.substring(1);
532
+ var result = {
533
+ full: "",
534
+ relative: "",
535
+ origin,
536
+ path,
537
+ query,
538
+ hash,
539
+ external: isExternal
540
+ };
541
+ rebuildUrl(result);
542
+ return result;
543
+ };
544
+ /**
545
+ * Add or replace query parameters.
546
+ * @param url Source url for extending query.
547
+ * @param query New or update parameters.
548
+ */
549
+ const extendQuery = (url, query) => {
550
+ if (query instanceof URLSearchParams) {
551
+ query.forEach((v, k) => url.query.delete(k));
552
+ query.forEach((v, k) => url.query.append(k, v));
553
+ }
554
+ else if (query instanceof FormData) {
555
+ query.forEach((v, k) => url.query.delete(k));
556
+ query.forEach((v, k) => url.query.append(k, v.toString()));
557
+ }
558
+ else {
559
+ for (let key in query) {
560
+ const value = query[key];
561
+ if (!Array.isArray(value)) {
562
+ url.query.set(key, value);
563
+ }
564
+ else {
565
+ url.query.delete(key);
566
+ value.forEach(val => url.query.append(key, val));
567
+ }
568
+ }
569
+ }
570
+ rebuildUrl(url);
571
+ };
572
+ const rebuildUrl = (url) => {
573
+ let relativeUrl = url.path;
574
+ if (url.query.size)
575
+ relativeUrl += "?" + url.query.toString();
576
+ if (url.hash)
577
+ relativeUrl += "#" + url.hash;
578
+ url.full = url.origin + relativeUrl;
579
+ url.relative = relativeUrl;
580
+ };
581
+ const buildUrl = (basePath, path, query, hash) => {
582
+ let url = basePath;
583
+ if (path) {
584
+ if (path.startsWith("/"))
585
+ path = path.substring(1);
586
+ url += path;
587
+ }
588
+ if (query) {
589
+ let params;
590
+ if (query instanceof URLSearchParams)
591
+ params = query;
592
+ else if (query instanceof FormData) {
593
+ params = new URLSearchParams();
594
+ query.forEach((value, key) => params.append(key, value.toString()));
595
+ }
596
+ else {
597
+ params = new URLSearchParams();
598
+ for (const key in query) {
599
+ const value = query[key];
600
+ if (value === null || typeof value === "undefined")
601
+ continue;
602
+ if (Array.isArray(value))
603
+ value.forEach(v => params.append(key, v));
604
+ else
605
+ params.append(key, value);
606
+ }
607
+ }
608
+ if (params.size)
609
+ url += "?" + params.toString();
610
+ }
611
+ if (hash) {
612
+ if (!hash.startsWith("#"))
613
+ hash = "#" + hash;
614
+ if (hash != "#")
615
+ url += hash;
616
+ }
617
+ return url;
618
+ };
619
+ var urlHelper = {
620
+ parseUrl,
621
+ extendQuery,
622
+ buildUrl
623
+ };
624
+
625
+ var url = /*#__PURE__*/Object.freeze({
626
+ __proto__: null,
627
+ default: urlHelper
628
+ });
629
+
630
+ /**
631
+ * Base application class.
632
+ */
633
+ class Application extends UIElement {
634
+ env;
635
+ model;
636
+ __invoker;
637
+ __isInitialized = false;
638
+ __isDestroy = false;
639
+ __middlewares = {};
640
+ __lastNav = null;
641
+ constructor(env, model) {
642
+ super();
643
+ this.env = env;
644
+ this.model = model;
645
+ const core = { name: "app-root" };
646
+ this.__invoker = new MiddlewareInvoker(core);
647
+ }
648
+ get typeName() { return "Application"; }
649
+ /** Middleware methods invoker. */
650
+ get invoker() { return this.__invoker; }
651
+ /**
652
+ * @param middlewares Initialize application with middlewares. Using in ApplicationBuilder.
653
+ */
654
+ initialize(middlewares) {
655
+ if (this.__isInitialized)
656
+ throw 'Application already initialized.';
657
+ this.__isInitialized = true;
658
+ this.onInitialize();
659
+ middlewares.forEach(middleware => {
660
+ var name = middleware.name;
661
+ if (this.__middlewares.hasOwnProperty(name))
662
+ throw `Middleware "${name}" already registered.`;
663
+ this.__middlewares[name] = middleware;
664
+ this.__invoker.next(middleware);
665
+ });
666
+ }
667
+ onInitialize() {
668
+ this.__invoker.next(StateMiddlewareFactory());
669
+ this.__invoker.next(HyperLinkMiddlewareFactory());
670
+ this.__invoker.next(FormMiddlewareFactory());
671
+ }
672
+ /**
673
+ * Get middleware by type.
674
+ * @param type Type of middleware.
675
+ * @returns Middleware instance.
676
+ */
677
+ middleware(name) {
678
+ const middleware = this.__middlewares[name];
679
+ if (!middleware)
680
+ throw `Middleware ${name} is not registered.`;
681
+ return middleware;
682
+ }
683
+ /**
684
+ * Run application.
685
+ * @param contextData Run context data.
686
+ * @param element HTMLElement of application. Default is document.body.
687
+ * @returns Promise of runned result.
688
+ */
689
+ async run(contextData, element) {
690
+ if (!contextData)
691
+ contextData = {};
692
+ this.setElement(element || BROWSER.body);
693
+ try {
694
+ const context = {
695
+ app: this,
696
+ data: contextData
697
+ };
698
+ await this.__invoker.invoke("start", context);
699
+ console.info("app start success");
700
+ await this.__invoker.invoke("loaded", context);
701
+ console.info("app load success");
702
+ return await this.nav({ data: context.data });
703
+ }
704
+ catch (reason) {
705
+ console.error(`Unable to run application with reason: ${reason}`);
706
+ throw reason;
707
+ }
708
+ finally {
709
+ console.info("app runned");
710
+ }
711
+ }
712
+ /**
713
+ * Navigate application to url.
714
+ * @param options Navigate options.
715
+ * @returns Promise of navigated result.
716
+ */
717
+ async nav(options) {
718
+ const opt = (!options || options instanceof String) ? { url: options } : options;
719
+ let { url = null, replace = false, data = {}, callback } = opt;
720
+ const navUrl = urlHelper.parseUrl(url);
721
+ if (opt.query)
722
+ urlHelper.extendQuery(navUrl, opt.query);
723
+ const isFirst = !this.__lastNav;
724
+ const context = this.__lastNav = {
725
+ app: this,
726
+ source: isFirst ? "first" : "nav",
727
+ data,
728
+ url: navUrl.full,
729
+ origin: navUrl.origin,
730
+ path: navUrl.path,
731
+ query: navUrl.query,
732
+ hash: navUrl.hash,
733
+ replace,
734
+ external: navUrl.external
735
+ };
736
+ console.info(context);
737
+ try {
738
+ console.info(`app nav begin ${navUrl.full}`);
739
+ await this.__invoker.invoke("navigate", context);
740
+ if (callback)
741
+ callback({ status: "success", context: context });
742
+ console.info(`app nav success ${navUrl.full}`);
743
+ return context;
744
+ }
745
+ catch (reason) {
746
+ if (callback)
747
+ callback({ status: "error", context: context });
748
+ console.error(`app nav error ${navUrl.full}: ${reason}`);
749
+ throw reason;
750
+ }
751
+ }
752
+ /**
753
+ * Submit application form.
754
+ * @param options Submit options.
755
+ * @returns Promise of submitted result.
756
+ */
757
+ async submit(options) {
758
+ const opt = options instanceof HTMLFormElement ? { form: options } : options;
759
+ const { form, button = null, query, data = {}, callback = null } = opt;
760
+ let method = form.method;
761
+ let enctype = form.enctype;
762
+ let url = form.action;
763
+ if (button) {
764
+ // Get button patameters for request
765
+ if (button.hasAttribute("formmethod"))
766
+ method = button.formMethod;
767
+ if (button.hasAttribute("formenctype"))
768
+ enctype = button.formEnctype;
769
+ if (button.hasAttribute("formaction"))
770
+ url = button.formAction;
771
+ }
772
+ method = method.toUpperCase();
773
+ const navUrl = urlHelper.parseUrl(url);
774
+ if (query)
775
+ urlHelper.extendQuery(navUrl, query);
776
+ let submitContext = {
777
+ app: this,
778
+ source: "submit",
779
+ data,
780
+ form,
781
+ button,
782
+ method,
783
+ enctype,
784
+ url: navUrl.full,
785
+ origin: navUrl.origin,
786
+ path: navUrl.path,
787
+ query: navUrl.query,
788
+ hash: navUrl.hash,
789
+ replace: false,
790
+ external: navUrl.external
791
+ };
792
+ try {
793
+ console.info(`submit ${method} begin ${navUrl.full}`);
794
+ await this.__invoker.invoke("submit", submitContext);
795
+ console.info(`submit ${method} success ${navUrl.full}`);
796
+ if (callback)
797
+ callback({ status: "success", context: submitContext });
798
+ }
799
+ catch (reason) {
800
+ console.error(`submit ${method} error ${navUrl.full} reason: ${reason}`);
801
+ if (callback)
802
+ callback({ status: "error", context: submitContext });
803
+ throw reason;
804
+ }
805
+ return submitContext;
806
+ }
807
+ /**
808
+ * Reload page with nav.
809
+ */
810
+ reload() {
811
+ return this.nav({ replace: true });
812
+ }
813
+ /**
814
+ * Global reload page in browser.
815
+ */
816
+ restart() {
817
+ BROWSER.reload();
818
+ }
819
+ async destroy(contextData) {
820
+ if (this.__isDestroy)
821
+ return Promise.reject('Application already destroyed.');
822
+ this.__isDestroy = true;
823
+ console.info("app destroy begin");
824
+ const context = {
825
+ app: this,
826
+ data: contextData || {}
827
+ };
828
+ try {
829
+ await this.__invoker.invoke("stop", context);
830
+ console.info("app destroy success");
831
+ return context;
832
+ }
833
+ catch (reason) {
834
+ console.error(`app destroy error: ${reason}`);
835
+ throw reason;
836
+ }
837
+ }
838
+ /**
839
+ * Generate url of application base url.
840
+ * @param path Add optional path of base url.
841
+ * @param query Add optional query params.
842
+ * @param hash Add optional hash.
843
+ * @returns Relative url with base path.
844
+ */
845
+ buildUrl(path, query, hash) {
846
+ return urlHelper.buildUrl(this.env.basePath, path, query, hash);
847
+ }
848
+ }
849
+
850
+ class ApplicationBuilder {
851
+ __appType = (Application);
852
+ __middlewares = [];
853
+ useApp(appType) {
854
+ this.__appType = appType;
855
+ return this;
856
+ }
857
+ useMiddleware(middleware, ...params) {
858
+ if (!middleware)
859
+ throw `Middleware propery is required.`;
860
+ let midl;
861
+ if (typeof middleware === "function")
862
+ midl = middleware(...params);
863
+ else
864
+ midl = middleware;
865
+ this.__middlewares.push(midl);
866
+ return this;
867
+ }
868
+ build(env, model) {
869
+ if (!env)
870
+ throw new Error("Parameter env is required.");
871
+ if (!model)
872
+ throw new Error("Parameter model is required.");
873
+ if (!env.basePath)
874
+ env.basePath = "/";
875
+ const app = new this.__appType(env, model);
876
+ app.initialize(this.__middlewares);
877
+ return app;
878
+ }
879
+ }
880
+
881
+ exports.Application = Application;
882
+ exports.ApplicationBuilder = ApplicationBuilder;
883
+ exports.BROWSER = browser;
884
+ exports.CONSTANTS = constants;
885
+ exports.UrlHelper = url;
886
+ //# sourceMappingURL=index.js.map