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