haori 0.13.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +1 -1
- package/README.md +1 -1
- package/dist/haori.cjs.js +10 -10
- package/dist/haori.es.js +191 -165
- package/dist/haori.iife.js +10 -10
- package/dist/index.d.ts +1 -1
- package/package.json +11 -3
- package/dist/haori.cjs.js.map +0 -1
- package/dist/haori.es.js.map +0 -1
- package/dist/haori.iife.js.map +0 -1
- package/dist/package.json +0 -70
- package/dist/src/builtins.d.ts +0 -100
- package/dist/src/builtins.d.ts.map +0 -1
- package/dist/src/builtins.js +0 -194
- package/dist/src/builtins.js.map +0 -1
- package/dist/src/core.d.ts +0 -505
- package/dist/src/core.d.ts.map +0 -1
- package/dist/src/core.js +0 -1817
- package/dist/src/core.js.map +0 -1
- package/dist/src/dev.d.ts +0 -35
- package/dist/src/dev.d.ts.map +0 -1
- package/dist/src/dev.js +0 -44
- package/dist/src/dev.js.map +0 -1
- package/dist/src/env.d.ts +0 -43
- package/dist/src/env.d.ts.map +0 -1
- package/dist/src/env.js +0 -108
- package/dist/src/env.js.map +0 -1
- package/dist/src/event.d.ts +0 -171
- package/dist/src/event.d.ts.map +0 -1
- package/dist/src/event.js +0 -235
- package/dist/src/event.js.map +0 -1
- package/dist/src/event_dispatcher.d.ts +0 -66
- package/dist/src/event_dispatcher.d.ts.map +0 -1
- package/dist/src/event_dispatcher.js +0 -167
- package/dist/src/event_dispatcher.js.map +0 -1
- package/dist/src/expression.d.ts +0 -260
- package/dist/src/expression.d.ts.map +0 -1
- package/dist/src/expression.js +0 -975
- package/dist/src/expression.js.map +0 -1
- package/dist/src/form.d.ts +0 -148
- package/dist/src/form.d.ts.map +0 -1
- package/dist/src/form.js +0 -428
- package/dist/src/form.js.map +0 -1
- package/dist/src/fragment.d.ts +0 -621
- package/dist/src/fragment.d.ts.map +0 -1
- package/dist/src/fragment.js +0 -1963
- package/dist/src/fragment.js.map +0 -1
- package/dist/src/haori.d.ts +0 -130
- package/dist/src/haori.d.ts.map +0 -1
- package/dist/src/haori.js +0 -215
- package/dist/src/haori.js.map +0 -1
- package/dist/src/import.d.ts +0 -19
- package/dist/src/import.d.ts.map +0 -1
- package/dist/src/import.js +0 -64
- package/dist/src/import.js.map +0 -1
- package/dist/src/index.d.ts +0 -27
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -30
- package/dist/src/index.js.map +0 -1
- package/dist/src/intersect.d.ts +0 -28
- package/dist/src/intersect.d.ts.map +0 -1
- package/dist/src/intersect.js +0 -201
- package/dist/src/intersect.js.map +0 -1
- package/dist/src/log.d.ts +0 -32
- package/dist/src/log.d.ts.map +0 -1
- package/dist/src/log.js +0 -43
- package/dist/src/log.js.map +0 -1
- package/dist/src/observer.d.ts +0 -24
- package/dist/src/observer.d.ts.map +0 -1
- package/dist/src/observer.js +0 -131
- package/dist/src/observer.js.map +0 -1
- package/dist/src/procedure.d.ts +0 -447
- package/dist/src/procedure.d.ts.map +0 -1
- package/dist/src/procedure.js +0 -1921
- package/dist/src/procedure.js.map +0 -1
- package/dist/src/queue.d.ts +0 -35
- package/dist/src/queue.d.ts.map +0 -1
- package/dist/src/queue.js +0 -202
- package/dist/src/queue.js.map +0 -1
- package/dist/src/url.d.ts +0 -14
- package/dist/src/url.d.ts.map +0 -1
- package/dist/src/url.js +0 -22
- package/dist/src/url.js.map +0 -1
- package/dist/tests/bind-arg-own-binding.test.d.ts +0 -2
- package/dist/tests/bind-arg-own-binding.test.d.ts.map +0 -1
- package/dist/tests/bind-arg-own-binding.test.js +0 -63
- package/dist/tests/bind-arg-own-binding.test.js.map +0 -1
- package/dist/tests/bindcomplete-and-scope.test.d.ts +0 -2
- package/dist/tests/bindcomplete-and-scope.test.d.ts.map +0 -1
- package/dist/tests/bindcomplete-and-scope.test.js +0 -115
- package/dist/tests/bindcomplete-and-scope.test.js.map +0 -1
- package/dist/tests/builtins.test.d.ts +0 -2
- package/dist/tests/builtins.test.d.ts.map +0 -1
- package/dist/tests/builtins.test.js +0 -152
- package/dist/tests/builtins.test.js.map +0 -1
- package/dist/tests/click-attributes.test.d.ts +0 -2
- package/dist/tests/click-attributes.test.d.ts.map +0 -1
- package/dist/tests/click-attributes.test.js +0 -113
- package/dist/tests/click-attributes.test.js.map +0 -1
- package/dist/tests/click-no-disabled.test.d.ts +0 -2
- package/dist/tests/click-no-disabled.test.d.ts.map +0 -1
- package/dist/tests/click-no-disabled.test.js +0 -67
- package/dist/tests/click-no-disabled.test.js.map +0 -1
- package/dist/tests/core.test.d.ts +0 -5
- package/dist/tests/core.test.d.ts.map +0 -1
- package/dist/tests/core.test.js +0 -1056
- package/dist/tests/core.test.js.map +0 -1
- package/dist/tests/data-bind-arg-reeval.test.d.ts +0 -2
- package/dist/tests/data-bind-arg-reeval.test.d.ts.map +0 -1
- package/dist/tests/data-bind-arg-reeval.test.js +0 -119
- package/dist/tests/data-bind-arg-reeval.test.js.map +0 -1
- package/dist/tests/data-bind-merge.test.d.ts +0 -2
- package/dist/tests/data-bind-merge.test.d.ts.map +0 -1
- package/dist/tests/data-bind-merge.test.js +0 -86
- package/dist/tests/data-bind-merge.test.js.map +0 -1
- package/dist/tests/data-click-defer.test.d.ts +0 -2
- package/dist/tests/data-click-defer.test.d.ts.map +0 -1
- package/dist/tests/data-click-defer.test.js +0 -57
- package/dist/tests/data-click-defer.test.js.map +0 -1
- package/dist/tests/data-click-run.test.d.ts +0 -5
- package/dist/tests/data-click-run.test.d.ts.map +0 -1
- package/dist/tests/data-click-run.test.js +0 -109
- package/dist/tests/data-click-run.test.js.map +0 -1
- package/dist/tests/data-derive.test.d.ts +0 -2
- package/dist/tests/data-derive.test.d.ts.map +0 -1
- package/dist/tests/data-derive.test.js +0 -999
- package/dist/tests/data-derive.test.js.map +0 -1
- package/dist/tests/data-each-browserlike.test.d.ts +0 -2
- package/dist/tests/data-each-browserlike.test.d.ts.map +0 -1
- package/dist/tests/data-each-browserlike.test.js +0 -48
- package/dist/tests/data-each-browserlike.test.js.map +0 -1
- package/dist/tests/data-each-fragment-debug.test.d.ts +0 -2
- package/dist/tests/data-each-fragment-debug.test.d.ts.map +0 -1
- package/dist/tests/data-each-fragment-debug.test.js +0 -119
- package/dist/tests/data-each-fragment-debug.test.js.map +0 -1
- package/dist/tests/data-each-table.test.d.ts +0 -2
- package/dist/tests/data-each-table.test.d.ts.map +0 -1
- package/dist/tests/data-each-table.test.js +0 -131
- package/dist/tests/data-each-table.test.js.map +0 -1
- package/dist/tests/data-each-template-recovery.test.d.ts +0 -2
- package/dist/tests/data-each-template-recovery.test.d.ts.map +0 -1
- package/dist/tests/data-each-template-recovery.test.js +0 -87
- package/dist/tests/data-each-template-recovery.test.js.map +0 -1
- package/dist/tests/data-event-prevent.test.d.ts +0 -2
- package/dist/tests/data-event-prevent.test.d.ts.map +0 -1
- package/dist/tests/data-event-prevent.test.js +0 -76
- package/dist/tests/data-event-prevent.test.js.map +0 -1
- package/dist/tests/data-fetch-form-input-binding.test.d.ts +0 -2
- package/dist/tests/data-fetch-form-input-binding.test.d.ts.map +0 -1
- package/dist/tests/data-fetch-form-input-binding.test.js +0 -202
- package/dist/tests/data-fetch-form-input-binding.test.js.map +0 -1
- package/dist/tests/data-fetch-tbody-dom.test.d.ts +0 -2
- package/dist/tests/data-fetch-tbody-dom.test.d.ts.map +0 -1
- package/dist/tests/data-fetch-tbody-dom.test.js +0 -82
- package/dist/tests/data-fetch-tbody-dom.test.js.map +0 -1
- package/dist/tests/data-form-container.test.d.ts +0 -2
- package/dist/tests/data-form-container.test.d.ts.map +0 -1
- package/dist/tests/data-form-container.test.js +0 -88
- package/dist/tests/data-form-container.test.js.map +0 -1
- package/dist/tests/data-if-dev-diagnostics.test.d.ts +0 -2
- package/dist/tests/data-if-dev-diagnostics.test.d.ts.map +0 -1
- package/dist/tests/data-if-dev-diagnostics.test.js +0 -55
- package/dist/tests/data-if-dev-diagnostics.test.js.map +0 -1
- package/dist/tests/data-if-falsy.test.d.ts +0 -2
- package/dist/tests/data-if-falsy.test.d.ts.map +0 -1
- package/dist/tests/data-if-falsy.test.js +0 -73
- package/dist/tests/data-if-falsy.test.js.map +0 -1
- package/dist/tests/data-load-on-show.test.d.ts +0 -2
- package/dist/tests/data-load-on-show.test.d.ts.map +0 -1
- package/dist/tests/data-load-on-show.test.js +0 -98
- package/dist/tests/data-load-on-show.test.js.map +0 -1
- package/dist/tests/derive-each-render-wait.test.d.ts +0 -2
- package/dist/tests/derive-each-render-wait.test.d.ts.map +0 -1
- package/dist/tests/derive-each-render-wait.test.js +0 -90
- package/dist/tests/derive-each-render-wait.test.js.map +0 -1
- package/dist/tests/dev.test.d.ts +0 -2
- package/dist/tests/dev.test.d.ts.map +0 -1
- package/dist/tests/dev.test.js +0 -51
- package/dist/tests/dev.test.js.map +0 -1
- package/dist/tests/each-update-event.test.d.ts +0 -2
- package/dist/tests/each-update-event.test.d.ts.map +0 -1
- package/dist/tests/each-update-event.test.js +0 -83
- package/dist/tests/each-update-event.test.js.map +0 -1
- package/dist/tests/each_arg.test.d.ts +0 -2
- package/dist/tests/each_arg.test.d.ts.map +0 -1
- package/dist/tests/each_arg.test.js +0 -41
- package/dist/tests/each_arg.test.js.map +0 -1
- package/dist/tests/env.test.d.ts +0 -2
- package/dist/tests/env.test.d.ts.map +0 -1
- package/dist/tests/env.test.js +0 -124
- package/dist/tests/env.test.js.map +0 -1
- package/dist/tests/evaluation-profile.test.d.ts +0 -2
- package/dist/tests/evaluation-profile.test.d.ts.map +0 -1
- package/dist/tests/evaluation-profile.test.js +0 -92
- package/dist/tests/evaluation-profile.test.js.map +0 -1
- package/dist/tests/event.test.d.ts +0 -2
- package/dist/tests/event.test.d.ts.map +0 -1
- package/dist/tests/event.test.js +0 -300
- package/dist/tests/event.test.js.map +0 -1
- package/dist/tests/expression-disallowed-keyword-hint.test.d.ts +0 -2
- package/dist/tests/expression-disallowed-keyword-hint.test.d.ts.map +0 -1
- package/dist/tests/expression-disallowed-keyword-hint.test.js +0 -45
- package/dist/tests/expression-disallowed-keyword-hint.test.js.map +0 -1
- package/dist/tests/expression-forbidden-hint.test.d.ts +0 -2
- package/dist/tests/expression-forbidden-hint.test.d.ts.map +0 -1
- package/dist/tests/expression-forbidden-hint.test.js +0 -37
- package/dist/tests/expression-forbidden-hint.test.js.map +0 -1
- package/dist/tests/expression.test.d.ts +0 -2
- package/dist/tests/expression.test.d.ts.map +0 -1
- package/dist/tests/expression.test.js +0 -482
- package/dist/tests/expression.test.js.map +0 -1
- package/dist/tests/fetch-and-procedure-scenarios.test.d.ts +0 -2
- package/dist/tests/fetch-and-procedure-scenarios.test.d.ts.map +0 -1
- package/dist/tests/fetch-and-procedure-scenarios.test.js +0 -195
- package/dist/tests/fetch-and-procedure-scenarios.test.js.map +0 -1
- package/dist/tests/form-number-type.test.d.ts +0 -2
- package/dist/tests/form-number-type.test.d.ts.map +0 -1
- package/dist/tests/form-number-type.test.js +0 -83
- package/dist/tests/form-number-type.test.js.map +0 -1
- package/dist/tests/form.test.d.ts +0 -2
- package/dist/tests/form.test.d.ts.map +0 -1
- package/dist/tests/form.test.js +0 -636
- package/dist/tests/form.test.js.map +0 -1
- package/dist/tests/fragment.test.d.ts +0 -2
- package/dist/tests/fragment.test.d.ts.map +0 -1
- package/dist/tests/fragment.test.js +0 -284
- package/dist/tests/fragment.test.js.map +0 -1
- package/dist/tests/haori.test.d.ts +0 -2
- package/dist/tests/haori.test.d.ts.map +0 -1
- package/dist/tests/haori.test.js +0 -149
- package/dist/tests/haori.test.js.map +0 -1
- package/dist/tests/helpers/async.d.ts +0 -20
- package/dist/tests/helpers/async.d.ts.map +0 -1
- package/dist/tests/helpers/async.js +0 -35
- package/dist/tests/helpers/async.js.map +0 -1
- package/dist/tests/import.test.d.ts +0 -2
- package/dist/tests/import.test.d.ts.map +0 -1
- package/dist/tests/import.test.js +0 -148
- package/dist/tests/import.test.js.map +0 -1
- package/dist/tests/intersect.test.d.ts +0 -2
- package/dist/tests/intersect.test.d.ts.map +0 -1
- package/dist/tests/intersect.test.js +0 -173
- package/dist/tests/intersect.test.js.map +0 -1
- package/dist/tests/log.test.d.ts +0 -2
- package/dist/tests/log.test.d.ts.map +0 -1
- package/dist/tests/log.test.js +0 -58
- package/dist/tests/log.test.js.map +0 -1
- package/dist/tests/observer.test.d.ts +0 -2
- package/dist/tests/observer.test.d.ts.map +0 -1
- package/dist/tests/observer.test.js +0 -64
- package/dist/tests/observer.test.js.map +0 -1
- package/dist/tests/placeholder-resolution.test.d.ts +0 -2
- package/dist/tests/placeholder-resolution.test.d.ts.map +0 -1
- package/dist/tests/placeholder-resolution.test.js +0 -463
- package/dist/tests/placeholder-resolution.test.js.map +0 -1
- package/dist/tests/procedure-action-operations.test.d.ts +0 -2
- package/dist/tests/procedure-action-operations.test.d.ts.map +0 -1
- package/dist/tests/procedure-action-operations.test.js +0 -1140
- package/dist/tests/procedure-action-operations.test.js.map +0 -1
- package/dist/tests/procedure-bind-append.test.d.ts +0 -2
- package/dist/tests/procedure-bind-append.test.d.ts.map +0 -1
- package/dist/tests/procedure-bind-append.test.js +0 -80
- package/dist/tests/procedure-bind-append.test.js.map +0 -1
- package/dist/tests/procedure-fetch-options.test.d.ts +0 -2
- package/dist/tests/procedure-fetch-options.test.d.ts.map +0 -1
- package/dist/tests/procedure-fetch-options.test.js +0 -255
- package/dist/tests/procedure-fetch-options.test.js.map +0 -1
- package/dist/tests/procedure.test.d.ts +0 -2
- package/dist/tests/procedure.test.d.ts.map +0 -1
- package/dist/tests/procedure.test.js +0 -137
- package/dist/tests/procedure.test.js.map +0 -1
- package/dist/tests/procedure_events.test.d.ts +0 -7
- package/dist/tests/procedure_events.test.d.ts.map +0 -1
- package/dist/tests/procedure_events.test.js +0 -261
- package/dist/tests/procedure_events.test.js.map +0 -1
- package/dist/tests/render-wait.test.d.ts +0 -2
- package/dist/tests/render-wait.test.d.ts.map +0 -1
- package/dist/tests/render-wait.test.js +0 -68
- package/dist/tests/render-wait.test.js.map +0 -1
- package/dist/tests/reset_each.test.d.ts +0 -2
- package/dist/tests/reset_each.test.d.ts.map +0 -1
- package/dist/tests/reset_each.test.js +0 -215
- package/dist/tests/reset_each.test.js.map +0 -1
- package/dist/tests/row-move.test.d.ts +0 -2
- package/dist/tests/row-move.test.d.ts.map +0 -1
- package/dist/tests/row-move.test.js +0 -220
- package/dist/tests/row-move.test.js.map +0 -1
- package/dist/tests/row-operations.test.d.ts +0 -2
- package/dist/tests/row-operations.test.d.ts.map +0 -1
- package/dist/tests/row-operations.test.js +0 -259
- package/dist/tests/row-operations.test.js.map +0 -1
- package/dist/tests/scheduled-evaluate-wait.test.d.ts +0 -2
- package/dist/tests/scheduled-evaluate-wait.test.d.ts.map +0 -1
- package/dist/tests/scheduled-evaluate-wait.test.js +0 -68
- package/dist/tests/scheduled-evaluate-wait.test.js.map +0 -1
- package/dist/tests/url.test.d.ts +0 -2
- package/dist/tests/url.test.d.ts.map +0 -1
- package/dist/tests/url.test.js +0 -150
- package/dist/tests/url.test.js.map +0 -1
- package/dist/vite.config.d.ts +0 -3
- package/dist/vite.config.d.ts.map +0 -1
- package/dist/vite.config.js +0 -28
- package/dist/vite.config.js.map +0 -1
- package/dist/vitest.config.d.ts +0 -3
- package/dist/vitest.config.d.ts.map +0 -1
- package/dist/vitest.config.js +0 -19
- package/dist/vitest.config.js.map +0 -1
package/dist/src/procedure.js
DELETED
|
@@ -1,1921 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview 手続き的処理管理機能
|
|
3
|
-
*
|
|
4
|
-
* イベントに基づく手続き的な処理を提供します。
|
|
5
|
-
*/
|
|
6
|
-
import Core from './core';
|
|
7
|
-
import Env from './env';
|
|
8
|
-
import Expression from './expression';
|
|
9
|
-
import Form from './form';
|
|
10
|
-
import Fragment from './fragment';
|
|
11
|
-
import Haori from './haori';
|
|
12
|
-
import Log from './log';
|
|
13
|
-
import HaoriEvent from './event';
|
|
14
|
-
const PROCEDURE_HAORI_METHOD_NAMES = [
|
|
15
|
-
'addErrorMessage',
|
|
16
|
-
'closeDialog',
|
|
17
|
-
'confirm',
|
|
18
|
-
'dialog',
|
|
19
|
-
'openDialog',
|
|
20
|
-
'toast',
|
|
21
|
-
];
|
|
22
|
-
const PROCEDURE_HISTORY_STATE_KEY = '__haoriHistoryState__';
|
|
23
|
-
/** click ロック中であることを示す内部マーカー属性名 */
|
|
24
|
-
const PROCEDURE_CLICK_LOCK_MARKER = 'data-haori-click-lock';
|
|
25
|
-
/**
|
|
26
|
-
* Procedure から利用する Haori API を解決します。
|
|
27
|
-
* window.Haori が差し替えられている場合はそちらを優先します。
|
|
28
|
-
*
|
|
29
|
-
* @returns Procedure が使用する Haori API。
|
|
30
|
-
*/
|
|
31
|
-
function resolveProcedureHaoriApi() {
|
|
32
|
-
const scope = globalThis;
|
|
33
|
-
const candidate = scope.window?.Haori;
|
|
34
|
-
const hasRequiredMethods = PROCEDURE_HAORI_METHOD_NAMES.every(methodName => typeof candidate?.[methodName] === 'function');
|
|
35
|
-
return hasRequiredMethods ? candidate : Haori;
|
|
36
|
-
}
|
|
37
|
-
const QUERY_TRANSPORT_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
38
|
-
/**
|
|
39
|
-
* URL クエリ化の対象メソッドかどうかを判定します。
|
|
40
|
-
*
|
|
41
|
-
* @param method 判定対象の HTTP メソッド。
|
|
42
|
-
* @return クエリ送信対象なら true。
|
|
43
|
-
*/
|
|
44
|
-
function isQueryTransportMethod(method) {
|
|
45
|
-
return QUERY_TRANSPORT_METHODS.has(method.toUpperCase());
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* 送信データを URLSearchParams に追加します。
|
|
49
|
-
*
|
|
50
|
-
* @param params 追加先の URLSearchParams。
|
|
51
|
-
* @param payload 追加対象の送信データ。
|
|
52
|
-
* @return 戻り値はありません。
|
|
53
|
-
*/
|
|
54
|
-
function appendPayloadToSearchParams(params, payload) {
|
|
55
|
-
for (const [key, value] of Object.entries(payload)) {
|
|
56
|
-
if (value === undefined) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (value === null) {
|
|
60
|
-
params.append(key, '');
|
|
61
|
-
}
|
|
62
|
-
else if (Array.isArray(value)) {
|
|
63
|
-
value.forEach(item => {
|
|
64
|
-
params.append(key, String(item));
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
else if (typeof value === 'object' || typeof value === 'function') {
|
|
68
|
-
params.append(key, JSON.stringify(value));
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
params.append(key, String(value));
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* 送信データをクエリ文字列へ付加した URL を返します。
|
|
77
|
-
*
|
|
78
|
-
* @param fetchUrl 元のフェッチ URL。
|
|
79
|
-
* @param payload 追加対象の送信データ。
|
|
80
|
-
* @return クエリ文字列を付加した URL。
|
|
81
|
-
*/
|
|
82
|
-
function appendPayloadToUrl(fetchUrl, payload) {
|
|
83
|
-
const url = new URL(fetchUrl, window.location.href);
|
|
84
|
-
const params = new URLSearchParams(url.search);
|
|
85
|
-
appendPayloadToSearchParams(params, payload);
|
|
86
|
-
url.search = params.toString();
|
|
87
|
-
return url.toString();
|
|
88
|
-
}
|
|
89
|
-
function normalizeRequestBody(body) {
|
|
90
|
-
if (body === undefined || body === null) {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
if (typeof body === 'string') {
|
|
94
|
-
return body;
|
|
95
|
-
}
|
|
96
|
-
if (body instanceof URLSearchParams) {
|
|
97
|
-
return body.toString();
|
|
98
|
-
}
|
|
99
|
-
if (body instanceof FormData) {
|
|
100
|
-
return Array.from(body.entries()).map(([key, value]) => {
|
|
101
|
-
if (value instanceof File) {
|
|
102
|
-
return [
|
|
103
|
-
key,
|
|
104
|
-
{
|
|
105
|
-
type: 'file',
|
|
106
|
-
name: value.name,
|
|
107
|
-
size: value.size,
|
|
108
|
-
mimeType: value.type,
|
|
109
|
-
},
|
|
110
|
-
];
|
|
111
|
-
}
|
|
112
|
-
return [key, String(value)];
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
return String(body);
|
|
116
|
-
}
|
|
117
|
-
function buildFetchSignature(url, options) {
|
|
118
|
-
const headers = new Headers(options.headers || undefined);
|
|
119
|
-
const normalizedHeaders = Array.from(headers.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
120
|
-
return JSON.stringify({
|
|
121
|
-
url,
|
|
122
|
-
method: String(options.method || 'GET').toUpperCase(),
|
|
123
|
-
headers: normalizedHeaders,
|
|
124
|
-
body: normalizeRequestBody(options.body || null),
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* 手続き的処理管理クラスです。
|
|
129
|
-
*/
|
|
130
|
-
class Procedure {
|
|
131
|
-
/**
|
|
132
|
-
* イベント属性名を正しく生成します。
|
|
133
|
-
* 例: ("click", "fetch") => "data-click-fetch"
|
|
134
|
-
* (null, "fetch") => "data-fetch"
|
|
135
|
-
* ("change", "bind-arg") => "data-change-bind-arg"
|
|
136
|
-
* 非イベント変種が "data-fetch-xxx" として存在するものについては、event が null の場合にそちらを返します。
|
|
137
|
-
*/
|
|
138
|
-
static attrName(event, key, hasFetchFallback = false) {
|
|
139
|
-
if (event) {
|
|
140
|
-
return `${Env.prefix}${event}-${key}`;
|
|
141
|
-
}
|
|
142
|
-
return hasFetchFallback
|
|
143
|
-
? `${Env.prefix}fetch-${key}`
|
|
144
|
-
: `${Env.prefix}${key}`;
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* data 属性のテンプレート式評価結果を URLSearchParams 向けに組み立てます。
|
|
148
|
-
*
|
|
149
|
-
* @param rawAttribute 生の属性値
|
|
150
|
-
* @param bindingValues バインディング値
|
|
151
|
-
* @returns パラメータ形式として扱える文字列
|
|
152
|
-
*/
|
|
153
|
-
static resolveDataParamString(rawAttribute, bindingValues) {
|
|
154
|
-
return Procedure.resolveDataParamStringDetailed(rawAttribute, bindingValues)
|
|
155
|
-
.value;
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* data 属性のテンプレート式評価結果を URLSearchParams 向けに組み立てます。
|
|
159
|
-
*
|
|
160
|
-
* @param rawAttribute 生の属性値
|
|
161
|
-
* @param bindingValues バインディング値
|
|
162
|
-
* @returns パラメータ形式として扱える文字列と未解決参照の有無
|
|
163
|
-
*/
|
|
164
|
-
static resolveDataParamStringDetailed(rawAttribute, bindingValues) {
|
|
165
|
-
let hasUnresolvedReference = false;
|
|
166
|
-
const value = rawAttribute.replace(Procedure.DATA_PLACEHOLDER_REGEX, (_matched, rawExpression, expression) => {
|
|
167
|
-
const result = Expression.evaluateDetailed(rawExpression ?? expression ?? '', bindingValues);
|
|
168
|
-
hasUnresolvedReference =
|
|
169
|
-
hasUnresolvedReference || result.unresolvedReference;
|
|
170
|
-
if (result.value === null ||
|
|
171
|
-
result.value === undefined ||
|
|
172
|
-
Number.isNaN(result.value)) {
|
|
173
|
-
return '';
|
|
174
|
-
}
|
|
175
|
-
if (typeof result.value === 'object') {
|
|
176
|
-
return encodeURIComponent(JSON.stringify(result.value));
|
|
177
|
-
}
|
|
178
|
-
return encodeURIComponent(String(result.value));
|
|
179
|
-
});
|
|
180
|
-
return { value, hasUnresolvedReference };
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* JSON 文字列中のテンプレート式かどうかを判定します。
|
|
184
|
-
*
|
|
185
|
-
* @param source 生の属性値
|
|
186
|
-
* @param offset プレースホルダ開始位置
|
|
187
|
-
* @returns JSON 文字列中なら true
|
|
188
|
-
*/
|
|
189
|
-
static isJsonStringContext(source, offset) {
|
|
190
|
-
let inString = false;
|
|
191
|
-
let escaped = false;
|
|
192
|
-
for (let index = 0; index < offset; index += 1) {
|
|
193
|
-
const char = source[index];
|
|
194
|
-
if (escaped) {
|
|
195
|
-
escaped = false;
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
if (char === '\\') {
|
|
199
|
-
escaped = true;
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
if (char === '"') {
|
|
203
|
-
inString = !inString;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return inString;
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* JSON 値コンテキスト向けにテンプレート式の評価結果を直列化します。
|
|
210
|
-
*
|
|
211
|
-
* @param result テンプレート式の評価結果
|
|
212
|
-
* @returns JSON 値として埋め込める文字列
|
|
213
|
-
*/
|
|
214
|
-
static stringifyJsonTemplateValue(result) {
|
|
215
|
-
if (result === undefined || Number.isNaN(result)) {
|
|
216
|
-
return 'null';
|
|
217
|
-
}
|
|
218
|
-
try {
|
|
219
|
-
const serialized = JSON.stringify(result);
|
|
220
|
-
return serialized ?? JSON.stringify(String(result));
|
|
221
|
-
}
|
|
222
|
-
catch {
|
|
223
|
-
return JSON.stringify(String(result));
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* JSON 文字列コンテキスト向けにテンプレート式の評価結果を直列化します。
|
|
228
|
-
*
|
|
229
|
-
* @param result テンプレート式の評価結果
|
|
230
|
-
* @returns JSON 文字列へ安全に埋め込める文字列
|
|
231
|
-
*/
|
|
232
|
-
static stringifyJsonTemplateStringContent(result) {
|
|
233
|
-
if (result === null || result === undefined || Number.isNaN(result)) {
|
|
234
|
-
return '';
|
|
235
|
-
}
|
|
236
|
-
const value = typeof result === 'object'
|
|
237
|
-
? Procedure.stringifyJsonTemplateValue(result)
|
|
238
|
-
: String(result);
|
|
239
|
-
return JSON.stringify(value).slice(1, -1);
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* JSON 形式 data 属性内のテンプレート式を安全に解決します。
|
|
243
|
-
*
|
|
244
|
-
* @param rawAttribute 生の属性値
|
|
245
|
-
* @param bindingValues バインディング値
|
|
246
|
-
* @returns JSON として解釈可能な文字列
|
|
247
|
-
*/
|
|
248
|
-
static resolveDataJsonString(rawAttribute, bindingValues) {
|
|
249
|
-
return Procedure.resolveDataJsonStringDetailed(rawAttribute, bindingValues)
|
|
250
|
-
.value;
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* JSON 形式 data 属性内のテンプレート式を安全に解決します。
|
|
254
|
-
*
|
|
255
|
-
* @param rawAttribute 生の属性値
|
|
256
|
-
* @param bindingValues バインディング値
|
|
257
|
-
* @returns JSON として解釈可能な文字列と未解決参照の有無
|
|
258
|
-
*/
|
|
259
|
-
static resolveDataJsonStringDetailed(rawAttribute, bindingValues) {
|
|
260
|
-
let hasUnresolvedReference = false;
|
|
261
|
-
const value = rawAttribute.replace(Procedure.DATA_PLACEHOLDER_REGEX, (_matched, rawExpression, expression, offset) => {
|
|
262
|
-
const result = Expression.evaluateDetailed(rawExpression ?? expression ?? '', bindingValues);
|
|
263
|
-
hasUnresolvedReference =
|
|
264
|
-
hasUnresolvedReference || result.unresolvedReference;
|
|
265
|
-
return Procedure.isJsonStringContext(rawAttribute, offset)
|
|
266
|
-
? Procedure.stringifyJsonTemplateStringContent(result.value)
|
|
267
|
-
: Procedure.stringifyJsonTemplateValue(result.value);
|
|
268
|
-
});
|
|
269
|
-
return { value, hasUnresolvedReference };
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* data 属性を評価済みの値として取得します。
|
|
273
|
-
*
|
|
274
|
-
* @param fragment フラグメント
|
|
275
|
-
* @param attrName 属性名
|
|
276
|
-
* @returns 送信データ
|
|
277
|
-
*/
|
|
278
|
-
static resolveDataAttribute(fragment, attrName) {
|
|
279
|
-
return Procedure.resolveDataAttributeDetailed(fragment, attrName).value;
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* data 属性を評価済みの値として取得し、未解決参照の有無を返します。
|
|
283
|
-
*
|
|
284
|
-
* @param fragment フラグメント
|
|
285
|
-
* @param attrName 属性名
|
|
286
|
-
* @returns 送信データと未解決参照の有無
|
|
287
|
-
*/
|
|
288
|
-
static resolveDataAttributeDetailed(fragment, attrName) {
|
|
289
|
-
const rawAttribute = fragment.getRawAttribute(attrName);
|
|
290
|
-
const attributeEvaluation = fragment.getAttributeEvaluation(attrName);
|
|
291
|
-
const dataAttribute = attributeEvaluation?.value ?? null;
|
|
292
|
-
const hasUnresolvedReference = attributeEvaluation?.hasUnresolvedReference ?? false;
|
|
293
|
-
if (dataAttribute &&
|
|
294
|
-
typeof dataAttribute === 'object' &&
|
|
295
|
-
!Array.isArray(dataAttribute)) {
|
|
296
|
-
return {
|
|
297
|
-
value: dataAttribute,
|
|
298
|
-
hasUnresolvedReference,
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
if (typeof dataAttribute !== 'string' || rawAttribute === null) {
|
|
302
|
-
return { value: null, hasUnresolvedReference };
|
|
303
|
-
}
|
|
304
|
-
const trimmed = rawAttribute.trim();
|
|
305
|
-
if (Procedure.SINGLE_PLACEHOLDER_REGEX.test(trimmed)) {
|
|
306
|
-
return {
|
|
307
|
-
value: Core.parseDataBind(dataAttribute),
|
|
308
|
-
hasUnresolvedReference,
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
312
|
-
const resolved = Procedure.resolveDataJsonStringDetailed(rawAttribute, fragment.getBindingData());
|
|
313
|
-
return {
|
|
314
|
-
value: Core.parseDataBind(resolved.value),
|
|
315
|
-
hasUnresolvedReference: hasUnresolvedReference || resolved.hasUnresolvedReference,
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
const resolved = Procedure.resolveDataParamStringDetailed(rawAttribute, fragment.getBindingData());
|
|
319
|
-
return {
|
|
320
|
-
value: Core.parseDataBind(resolved.value),
|
|
321
|
-
hasUnresolvedReference: hasUnresolvedReference || resolved.hasUnresolvedReference,
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* オプションをフラグメントの属性から構築します。
|
|
326
|
-
*
|
|
327
|
-
* @param fragment フラグメント
|
|
328
|
-
* @param event イベント名
|
|
329
|
-
* @return 構築されたオプション
|
|
330
|
-
*/
|
|
331
|
-
static buildOptions(fragment, event) {
|
|
332
|
-
const options = {
|
|
333
|
-
targetFragment: fragment,
|
|
334
|
-
};
|
|
335
|
-
if (event) {
|
|
336
|
-
// validate(spec: data-???-validate)
|
|
337
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'validate'))) {
|
|
338
|
-
options.valid = true;
|
|
339
|
-
}
|
|
340
|
-
// confirm
|
|
341
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'confirm'))) {
|
|
342
|
-
options.confirmMessage = fragment.getAttribute(Procedure.attrName(event, 'confirm')).replace(/\\n/g, '\n');
|
|
343
|
-
}
|
|
344
|
-
// data(イベント)
|
|
345
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'data'))) {
|
|
346
|
-
options.dataAttrName = Procedure.attrName(event, 'data');
|
|
347
|
-
}
|
|
348
|
-
// form(イベント)
|
|
349
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'form'))) {
|
|
350
|
-
const formSelector = fragment.getRawAttribute(Procedure.attrName(event, 'form'));
|
|
351
|
-
if (formSelector) {
|
|
352
|
-
const formElement = document.body.querySelector(formSelector);
|
|
353
|
-
if (formElement !== null) {
|
|
354
|
-
options.formFragment = Form.getFormFragment(Fragment.get(formElement));
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
Log.error('Haori', `Form element not found: ${formSelector}` +
|
|
358
|
-
` (${Procedure.attrName(event, 'form')})`);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
else {
|
|
362
|
-
// 属性はあるが値が省略された場合は自要素もしくは先祖の form を対象
|
|
363
|
-
options.formFragment = Form.getFormFragment(fragment);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
else if (event === 'change') {
|
|
367
|
-
// changeイベントの場合、data-change-form属性がなくても自動的にフォームを検索
|
|
368
|
-
options.formFragment = Form.getFormFragment(fragment);
|
|
369
|
-
}
|
|
370
|
-
if (fragment.hasAttribute(`${Env.prefix}${event}-before-run`)) {
|
|
371
|
-
const body = fragment.getRawAttribute(`${Env.prefix}${event}-before-run`);
|
|
372
|
-
try {
|
|
373
|
-
options.beforeCallback = new Function('fetchUrl', 'fetchOptions', `
|
|
374
|
-
"use strict";
|
|
375
|
-
${body}
|
|
376
|
-
`);
|
|
377
|
-
}
|
|
378
|
-
catch (e) {
|
|
379
|
-
Log.error('Haori', `Invalid before script: ${e}`);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
// data-{event}-run: フェッチを伴わない任意 JS をクリック等で実行する。
|
|
383
|
-
// before-run と異なり {{...}} を展開した値(getAttribute)を本体にする。
|
|
384
|
-
const runAttrName = Procedure.attrName(event, 'run');
|
|
385
|
-
if (fragment.hasAttribute(runAttrName)) {
|
|
386
|
-
const body = String(fragment.getAttribute(runAttrName) ?? '');
|
|
387
|
-
try {
|
|
388
|
-
options.runScript = new Function('event', `
|
|
389
|
-
"use strict";
|
|
390
|
-
${body}
|
|
391
|
-
`);
|
|
392
|
-
}
|
|
393
|
-
catch (e) {
|
|
394
|
-
Log.error('Haori', `Invalid run script: ${e}`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
// fetch URL(イベントあり/なし)
|
|
399
|
-
const fetchAttrName = Procedure.attrName(event, 'fetch');
|
|
400
|
-
const hasFetchAttr = fragment.hasAttribute(fetchAttrName);
|
|
401
|
-
if (hasFetchAttr) {
|
|
402
|
-
const fetchEvaluation = fragment.getAttributeEvaluation(fetchAttrName);
|
|
403
|
-
if (fetchEvaluation) {
|
|
404
|
-
options.fetchHasUnresolvedReference =
|
|
405
|
-
fetchEvaluation.hasUnresolvedReference;
|
|
406
|
-
options.fetchUrl = fetchEvaluation.hasUnresolvedReference
|
|
407
|
-
? null
|
|
408
|
-
: fetchEvaluation.value;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
const fetchOptions = {};
|
|
412
|
-
// fetch-method(イベントあり/なし)
|
|
413
|
-
// event: data-{event}-fetch-method, non-event: data-fetch-method
|
|
414
|
-
if (event) {
|
|
415
|
-
const fetchMethodAttrEvent = Procedure.attrName(event, 'fetch-method');
|
|
416
|
-
if (fragment.hasAttribute(fetchMethodAttrEvent)) {
|
|
417
|
-
const fetchMethodEvaluation = fragment.getAttributeEvaluation(fetchMethodAttrEvent);
|
|
418
|
-
if (fetchMethodEvaluation?.hasUnresolvedReference) {
|
|
419
|
-
options.fetchHasUnresolvedReference = true;
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
fetchOptions.method = fetchMethodEvaluation?.value;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
else {
|
|
427
|
-
const fetchMethodAttrNonEvent = Procedure.attrName(null, 'method', true);
|
|
428
|
-
if (fragment.hasAttribute(fetchMethodAttrNonEvent)) {
|
|
429
|
-
const fetchMethodEvaluation = fragment.getAttributeEvaluation(fetchMethodAttrNonEvent);
|
|
430
|
-
if (fetchMethodEvaluation?.hasUnresolvedReference) {
|
|
431
|
-
options.fetchHasUnresolvedReference = true;
|
|
432
|
-
}
|
|
433
|
-
else {
|
|
434
|
-
fetchOptions.method = fetchMethodEvaluation?.value;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
// fetch-headers(イベントあり/なし)
|
|
439
|
-
// event: data-{event}-fetch-headers, non-event: data-fetch-headers
|
|
440
|
-
if (event) {
|
|
441
|
-
const fetchHeadersAttrEvent = Procedure.attrName(event, 'fetch-headers');
|
|
442
|
-
if (fragment.hasAttribute(fetchHeadersAttrEvent)) {
|
|
443
|
-
const headersString = fragment.getRawAttribute(fetchHeadersAttrEvent);
|
|
444
|
-
try {
|
|
445
|
-
fetchOptions.headers = Core.parseDataBind(headersString);
|
|
446
|
-
}
|
|
447
|
-
catch (e) {
|
|
448
|
-
Log.error('Haori', `Invalid fetch headers: ${e}`);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
else {
|
|
453
|
-
const fetchHeadersAttrNonEvent = Procedure.attrName(null, 'headers', true);
|
|
454
|
-
if (fragment.hasAttribute(fetchHeadersAttrNonEvent)) {
|
|
455
|
-
const headersString = fragment.getRawAttribute(fetchHeadersAttrNonEvent);
|
|
456
|
-
try {
|
|
457
|
-
fetchOptions.headers = Core.parseDataBind(headersString);
|
|
458
|
-
}
|
|
459
|
-
catch (e) {
|
|
460
|
-
Log.error('Haori', `Invalid fetch headers: ${e}`);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
// fetch-content-type(イベントあり/なし)
|
|
465
|
-
// event: data-{event}-fetch-content-type
|
|
466
|
-
// non-event: data-fetch-content-type
|
|
467
|
-
if (event) {
|
|
468
|
-
const fetchCTAttrEvent = Procedure.attrName(event, 'fetch-content-type');
|
|
469
|
-
if (fragment.hasAttribute(fetchCTAttrEvent)) {
|
|
470
|
-
const fetchContentTypeEvaluation = fragment.getAttributeEvaluation(fetchCTAttrEvent);
|
|
471
|
-
if (fetchContentTypeEvaluation?.hasUnresolvedReference) {
|
|
472
|
-
options.fetchHasUnresolvedReference = true;
|
|
473
|
-
}
|
|
474
|
-
fetchOptions.headers = {
|
|
475
|
-
...fetchOptions.headers,
|
|
476
|
-
'Content-Type': fetchContentTypeEvaluation?.value,
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
else if (fetchOptions.method &&
|
|
480
|
-
fetchOptions.method !== 'GET' &&
|
|
481
|
-
fetchOptions.method !== 'HEAD' &&
|
|
482
|
-
fetchOptions.method !== 'OPTIONS') {
|
|
483
|
-
// only set default Content-Type when one is not already provided
|
|
484
|
-
let hasContentType = false;
|
|
485
|
-
if (fetchOptions.headers && typeof fetchOptions.headers === 'object') {
|
|
486
|
-
const headersObj = fetchOptions.headers;
|
|
487
|
-
hasContentType = 'Content-Type' in headersObj;
|
|
488
|
-
}
|
|
489
|
-
if (!hasContentType) {
|
|
490
|
-
fetchOptions.headers = {
|
|
491
|
-
...fetchOptions.headers,
|
|
492
|
-
'Content-Type': 'application/json',
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
else if (fetchOptions.method &&
|
|
497
|
-
(fetchOptions.method === 'GET' ||
|
|
498
|
-
fetchOptions.method === 'HEAD' ||
|
|
499
|
-
fetchOptions.method === 'OPTIONS')) {
|
|
500
|
-
// 仕様: GET/HEAD/OPTIONS 既定は application/x-www-form-urlencoded
|
|
501
|
-
fetchOptions.headers = {
|
|
502
|
-
...fetchOptions.headers,
|
|
503
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
else {
|
|
508
|
-
const fetchCTAttrNonEvent = Procedure.attrName(null, 'content-type', true);
|
|
509
|
-
if (fragment.hasAttribute(fetchCTAttrNonEvent)) {
|
|
510
|
-
const fetchContentTypeEvaluation = fragment.getAttributeEvaluation(fetchCTAttrNonEvent);
|
|
511
|
-
if (fetchContentTypeEvaluation?.hasUnresolvedReference) {
|
|
512
|
-
options.fetchHasUnresolvedReference = true;
|
|
513
|
-
}
|
|
514
|
-
fetchOptions.headers = {
|
|
515
|
-
...fetchOptions.headers,
|
|
516
|
-
'Content-Type': fetchContentTypeEvaluation?.value,
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
else if (fetchOptions.method &&
|
|
520
|
-
fetchOptions.method !== 'GET' &&
|
|
521
|
-
fetchOptions.method !== 'HEAD' &&
|
|
522
|
-
fetchOptions.method !== 'OPTIONS') {
|
|
523
|
-
// only set default Content-Type when one is not already provided
|
|
524
|
-
let hasContentType = false;
|
|
525
|
-
if (fetchOptions.headers && typeof fetchOptions.headers === 'object') {
|
|
526
|
-
const headersObj = fetchOptions.headers;
|
|
527
|
-
hasContentType = 'Content-Type' in headersObj;
|
|
528
|
-
}
|
|
529
|
-
if (!hasContentType) {
|
|
530
|
-
fetchOptions.headers = {
|
|
531
|
-
...fetchOptions.headers,
|
|
532
|
-
'Content-Type': 'application/json',
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
else if (fetchOptions.method &&
|
|
537
|
-
(fetchOptions.method === 'GET' ||
|
|
538
|
-
fetchOptions.method === 'HEAD' ||
|
|
539
|
-
fetchOptions.method === 'OPTIONS')) {
|
|
540
|
-
// 仕様: GET/HEAD/OPTIONS 既定は application/x-www-form-urlencoded
|
|
541
|
-
fetchOptions.headers = {
|
|
542
|
-
...fetchOptions.headers,
|
|
543
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
if (Object.keys(fetchOptions).length > 0) {
|
|
548
|
-
options.fetchOptions = fetchOptions;
|
|
549
|
-
}
|
|
550
|
-
// bind(イベントあり/なし: 非イベントは data-fetch-bind)
|
|
551
|
-
const bindAttr = event
|
|
552
|
-
? Procedure.attrName(event, 'bind')
|
|
553
|
-
: Procedure.attrName(null, 'bind', true);
|
|
554
|
-
if (fragment.hasAttribute(bindAttr)) {
|
|
555
|
-
const bindSelector = fragment.getRawAttribute(bindAttr);
|
|
556
|
-
if (bindSelector) {
|
|
557
|
-
const bindElements = document.body.querySelectorAll(bindSelector);
|
|
558
|
-
if (bindElements.length > 0) {
|
|
559
|
-
options.bindFragments = [];
|
|
560
|
-
bindElements.forEach(element => {
|
|
561
|
-
const fragment = Fragment.get(element);
|
|
562
|
-
if (fragment) {
|
|
563
|
-
options.bindFragments.push(fragment);
|
|
564
|
-
}
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
else {
|
|
568
|
-
Log.error('Haori', `Bind element not found: ${bindSelector} (${bindAttr})`);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
const bindArgAttrEvent = Procedure.attrName(event, 'bind-arg');
|
|
573
|
-
const bindArgAttrNonEventLegacy = Procedure.attrName(null, 'arg', true); // data-fetch-arg
|
|
574
|
-
const bindArgAttrNonEventNew = Procedure.attrName(null, 'bind-arg', true); // data-fetch-bind-arg (less common)
|
|
575
|
-
if (event) {
|
|
576
|
-
if (fragment.hasAttribute(bindArgAttrEvent)) {
|
|
577
|
-
options.bindArg = fragment.getRawAttribute(bindArgAttrEvent);
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
else {
|
|
581
|
-
// Prefer legacy `data-fetch-arg` for non-event usage.
|
|
582
|
-
// Fallback to `data-fetch-bind-arg` if legacy is not present.
|
|
583
|
-
if (fragment.hasAttribute(bindArgAttrNonEventLegacy)) {
|
|
584
|
-
options.bindArg = fragment.getRawAttribute(bindArgAttrNonEventLegacy);
|
|
585
|
-
}
|
|
586
|
-
else if (fragment.hasAttribute(bindArgAttrNonEventNew)) {
|
|
587
|
-
options.bindArg = fragment.getRawAttribute(bindArgAttrNonEventNew);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
const bindParamsAttr = event
|
|
591
|
-
? Procedure.attrName(event, 'bind-params')
|
|
592
|
-
: Procedure.attrName(null, 'bind-params', true);
|
|
593
|
-
if (fragment.hasAttribute(bindParamsAttr)) {
|
|
594
|
-
const paramsString = fragment.getRawAttribute(bindParamsAttr);
|
|
595
|
-
options.bindParams = paramsString.split('&').map(p => p.trim());
|
|
596
|
-
}
|
|
597
|
-
const bindAppendAttr = event
|
|
598
|
-
? Procedure.attrName(event, 'bind-append')
|
|
599
|
-
: Procedure.attrName(null, 'bind-append', true);
|
|
600
|
-
if (fragment.hasAttribute(bindAppendAttr)) {
|
|
601
|
-
const paramsString = fragment.getRawAttribute(bindAppendAttr);
|
|
602
|
-
options.bindAppendParams = paramsString
|
|
603
|
-
.split('&')
|
|
604
|
-
.map(p => p.trim())
|
|
605
|
-
.filter(Boolean);
|
|
606
|
-
}
|
|
607
|
-
const bindMergeAttr = event
|
|
608
|
-
? Procedure.attrName(event, 'bind-merge')
|
|
609
|
-
: Procedure.attrName(null, 'bind-merge', true);
|
|
610
|
-
if (fragment.hasAttribute(bindMergeAttr)) {
|
|
611
|
-
options.bindMerge = true;
|
|
612
|
-
}
|
|
613
|
-
const copyParamsAttr = event
|
|
614
|
-
? Procedure.attrName(event, 'copy-params')
|
|
615
|
-
: null;
|
|
616
|
-
if (copyParamsAttr && fragment.hasAttribute(copyParamsAttr)) {
|
|
617
|
-
const paramsString = fragment.getRawAttribute(copyParamsAttr);
|
|
618
|
-
options.copyParams = paramsString
|
|
619
|
-
.split('&')
|
|
620
|
-
.map(param => param.trim())
|
|
621
|
-
.filter(Boolean);
|
|
622
|
-
}
|
|
623
|
-
if (event) {
|
|
624
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'adjust'))) {
|
|
625
|
-
const adjustSelector = fragment.getRawAttribute(Procedure.attrName(event, 'adjust'));
|
|
626
|
-
if (adjustSelector) {
|
|
627
|
-
const adjustElements = document.body.querySelectorAll(adjustSelector);
|
|
628
|
-
if (adjustElements.length > 0) {
|
|
629
|
-
options.adjustFragments = [];
|
|
630
|
-
adjustElements.forEach(element => {
|
|
631
|
-
const fragment = Fragment.get(element);
|
|
632
|
-
if (fragment) {
|
|
633
|
-
options.adjustFragments.push(fragment);
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
else {
|
|
638
|
-
Log.error('Haori', `Adjust element not found: ${adjustSelector}` +
|
|
639
|
-
` (${Procedure.attrName(event, 'adjust')})`);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'adjust-value'))) {
|
|
643
|
-
const valueString = fragment.getRawAttribute(Procedure.attrName(event, 'adjust-value'));
|
|
644
|
-
const value = Number(valueString);
|
|
645
|
-
if (!isNaN(value)) {
|
|
646
|
-
options.adjustValue = value;
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'row-add'))) {
|
|
651
|
-
options.rowAdd = true;
|
|
652
|
-
}
|
|
653
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'row-remove'))) {
|
|
654
|
-
options.rowRemove = true;
|
|
655
|
-
}
|
|
656
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'row-prev'))) {
|
|
657
|
-
options.rowMovePrev = true;
|
|
658
|
-
}
|
|
659
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'row-next'))) {
|
|
660
|
-
options.rowMoveNext = true;
|
|
661
|
-
}
|
|
662
|
-
if (fragment.hasAttribute(`${Env.prefix}${event}-after-run`)) {
|
|
663
|
-
const body = fragment.getRawAttribute(`${Env.prefix}${event}-after-run`);
|
|
664
|
-
try {
|
|
665
|
-
options.afterCallback = new Function('response', `
|
|
666
|
-
"use strict";
|
|
667
|
-
${body}
|
|
668
|
-
`);
|
|
669
|
-
}
|
|
670
|
-
catch (e) {
|
|
671
|
-
Log.error('Haori', `Invalid after script: ${e}`);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'dialog'))) {
|
|
675
|
-
options.dialogMessage = fragment.getAttribute(Procedure.attrName(event, 'dialog')).replace(/\\n/g, '\n');
|
|
676
|
-
}
|
|
677
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'toast'))) {
|
|
678
|
-
options.toastMessage = fragment.getAttribute(Procedure.attrName(event, 'toast'));
|
|
679
|
-
const rawLevel = fragment.getRawAttribute(Procedure.attrName(event, 'toast-level'));
|
|
680
|
-
const validLevels = ['info', 'warning', 'error', 'success'];
|
|
681
|
-
const isValidLevel = validLevels.includes(rawLevel);
|
|
682
|
-
options.toastLevel = isValidLevel ? rawLevel : null;
|
|
683
|
-
}
|
|
684
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'redirect'))) {
|
|
685
|
-
options.redirectUrl = fragment.getAttribute(Procedure.attrName(event, 'redirect'));
|
|
686
|
-
}
|
|
687
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'scroll-error'))) {
|
|
688
|
-
options.scrollOnError = true;
|
|
689
|
-
}
|
|
690
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'scroll'))) {
|
|
691
|
-
options.scrollTarget = fragment.getAttribute(Procedure.attrName(event, 'scroll'));
|
|
692
|
-
}
|
|
693
|
-
// history(data-{event}-history / history-data / history-form)
|
|
694
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'history'))) {
|
|
695
|
-
options.historyUrl = fragment.getAttribute(Procedure.attrName(event, 'history'));
|
|
696
|
-
}
|
|
697
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'history-data'))) {
|
|
698
|
-
options.historyDataAttrName = Procedure.attrName(event, 'history-data');
|
|
699
|
-
}
|
|
700
|
-
if (fragment.hasAttribute(Procedure.attrName(event, 'history-form'))) {
|
|
701
|
-
const historyFormSelector = fragment.getRawAttribute(Procedure.attrName(event, 'history-form'));
|
|
702
|
-
if (historyFormSelector) {
|
|
703
|
-
const historyFormElement = document.body.querySelector(historyFormSelector);
|
|
704
|
-
if (historyFormElement !== null) {
|
|
705
|
-
options.historyFormFragment = Form.getFormFragment(Fragment.get(historyFormElement));
|
|
706
|
-
}
|
|
707
|
-
else {
|
|
708
|
-
Log.error('Haori', `Form element not found: ${historyFormSelector}` +
|
|
709
|
-
` (${Procedure.attrName(event, 'history-form')})`);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
else {
|
|
713
|
-
options.historyFormFragment = Form.getFormFragment(fragment);
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
// reset/refetch/click/open/close(イベント、CSSセレクタ)
|
|
717
|
-
const selectorAttrs = [
|
|
718
|
-
'reset-before',
|
|
719
|
-
'reset',
|
|
720
|
-
'refetch',
|
|
721
|
-
'click',
|
|
722
|
-
'copy',
|
|
723
|
-
'open',
|
|
724
|
-
'close',
|
|
725
|
-
];
|
|
726
|
-
selectorAttrs.forEach(attrKey => {
|
|
727
|
-
const attrName = Procedure.attrName(event, attrKey);
|
|
728
|
-
if (!fragment.hasAttribute(attrName)) {
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
const selector = fragment.getRawAttribute(attrName);
|
|
732
|
-
const list = [];
|
|
733
|
-
if (selector) {
|
|
734
|
-
const elements = document.body.querySelectorAll(selector);
|
|
735
|
-
elements.forEach(el => {
|
|
736
|
-
const frag = Fragment.get(el);
|
|
737
|
-
if (frag) {
|
|
738
|
-
list.push(frag);
|
|
739
|
-
}
|
|
740
|
-
});
|
|
741
|
-
if (list.length === 0) {
|
|
742
|
-
Log.error('Haori', `Element not found: ${selector} (${attrName})`);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
else {
|
|
746
|
-
// 値が省略されている場合は自要素を対象
|
|
747
|
-
list.push(fragment);
|
|
748
|
-
}
|
|
749
|
-
if (list.length > 0) {
|
|
750
|
-
switch (attrKey) {
|
|
751
|
-
case 'reset-before':
|
|
752
|
-
options.resetBeforeFragments = list;
|
|
753
|
-
break;
|
|
754
|
-
case 'reset':
|
|
755
|
-
options.resetFragments = list;
|
|
756
|
-
break;
|
|
757
|
-
case 'refetch':
|
|
758
|
-
options.refetchFragments = list;
|
|
759
|
-
break;
|
|
760
|
-
case 'click':
|
|
761
|
-
options.clickFragments = list;
|
|
762
|
-
break;
|
|
763
|
-
case 'copy':
|
|
764
|
-
options.copyFragments = list;
|
|
765
|
-
break;
|
|
766
|
-
case 'open':
|
|
767
|
-
options.openFragments = list;
|
|
768
|
-
break;
|
|
769
|
-
case 'close':
|
|
770
|
-
options.closeFragments = list;
|
|
771
|
-
break;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
});
|
|
775
|
-
// copy-source(単一セレクタ)
|
|
776
|
-
const copySourceAttrName = Procedure.attrName(event, 'copy-source');
|
|
777
|
-
if (fragment.hasAttribute(copySourceAttrName)) {
|
|
778
|
-
const selector = fragment.getRawAttribute(copySourceAttrName);
|
|
779
|
-
if (selector) {
|
|
780
|
-
const el = document.body.querySelector(selector);
|
|
781
|
-
if (el !== null) {
|
|
782
|
-
const frag = Fragment.get(el);
|
|
783
|
-
if (frag) {
|
|
784
|
-
options.copySourceFragment = frag;
|
|
785
|
-
}
|
|
786
|
-
else {
|
|
787
|
-
Log.error('Haori', `Element is not managed by Haori: ${selector}` +
|
|
788
|
-
` (${copySourceAttrName})`);
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
Log.error('Haori', `Element not found: ${selector} (${copySourceAttrName})`);
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
else {
|
|
796
|
-
// 値が省略されている場合は自要素を対象
|
|
797
|
-
options.copySourceFragment = fragment;
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
// 非イベントの data / form(data-fetch-data / data-fetch-form)も取り込む
|
|
802
|
-
if (!event) {
|
|
803
|
-
if (fragment.hasAttribute(Procedure.attrName(null, 'data', true))) {
|
|
804
|
-
options.dataAttrName = Procedure.attrName(null, 'data', true);
|
|
805
|
-
}
|
|
806
|
-
if (fragment.hasAttribute(Procedure.attrName(null, 'form', true))) {
|
|
807
|
-
const formSelector = fragment.getRawAttribute(Procedure.attrName(null, 'form', true));
|
|
808
|
-
if (formSelector) {
|
|
809
|
-
const formElement = document.body.querySelector(formSelector);
|
|
810
|
-
if (formElement !== null) {
|
|
811
|
-
options.formFragment = Form.getFormFragment(Fragment.get(formElement));
|
|
812
|
-
}
|
|
813
|
-
else {
|
|
814
|
-
Log.error('Haori', `Form element not found: ${formSelector} (` +
|
|
815
|
-
`${Procedure.attrName(null, 'fetch-form', true)})`);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
else {
|
|
819
|
-
// 属性はあるが値が省略された場合は自要素もしくは先祖の form を対象
|
|
820
|
-
options.formFragment = Form.getFormFragment(fragment);
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
// fetch が指定されているのにバインド先が無い場合、デフォルトで自要素にバインド
|
|
825
|
-
if (hasFetchAttr &&
|
|
826
|
-
(!options.bindFragments || options.bindFragments.length === 0)) {
|
|
827
|
-
options.bindFragments = [fragment];
|
|
828
|
-
}
|
|
829
|
-
return options;
|
|
830
|
-
}
|
|
831
|
-
/**
|
|
832
|
-
* ElementFragment の構造的タイプガード。
|
|
833
|
-
*
|
|
834
|
-
* @param value チェックする値
|
|
835
|
-
* @returns ElementFragment である場合は true、それ以外は false
|
|
836
|
-
*/
|
|
837
|
-
static isElementFragment(value) {
|
|
838
|
-
if (typeof value !== 'object' || value === null) {
|
|
839
|
-
return false;
|
|
840
|
-
}
|
|
841
|
-
const obj = value;
|
|
842
|
-
return (typeof obj.getTarget === 'function' &&
|
|
843
|
-
typeof obj.getChildElementFragments === 'function');
|
|
844
|
-
}
|
|
845
|
-
/**
|
|
846
|
-
* コンストラクタ。
|
|
847
|
-
*
|
|
848
|
-
* @param arg1 オプションもしくはフラグメント
|
|
849
|
-
* @param arg2 イベント名
|
|
850
|
-
* @param domEvent 起点となった DOM イベント
|
|
851
|
-
*/
|
|
852
|
-
constructor(arg1, arg2 = null, domEvent = null) {
|
|
853
|
-
if (Procedure.isElementFragment(arg1)) {
|
|
854
|
-
this.options = Procedure.buildOptions(arg1, arg2);
|
|
855
|
-
this.eventType = arg2;
|
|
856
|
-
}
|
|
857
|
-
else {
|
|
858
|
-
this.options = arg1;
|
|
859
|
-
this.eventType = null;
|
|
860
|
-
}
|
|
861
|
-
this.domEvent = domEvent;
|
|
862
|
-
}
|
|
863
|
-
/**
|
|
864
|
-
* 非イベント data-fetch の自動再評価用シグネチャを解決します。
|
|
865
|
-
*
|
|
866
|
-
* @param fragment 対象フラグメント
|
|
867
|
-
* @returns フェッチシグネチャと未解決参照の有無
|
|
868
|
-
*/
|
|
869
|
-
static resolveAutoFetchSignature(fragment) {
|
|
870
|
-
return new Procedure(fragment, null).resolveFetchSignature();
|
|
871
|
-
}
|
|
872
|
-
/**
|
|
873
|
-
* 一連の処理を実行します。オプションが空の場合は即座にresolveされます。
|
|
874
|
-
*
|
|
875
|
-
* @returns 実行結果のPromise
|
|
876
|
-
*/
|
|
877
|
-
run() {
|
|
878
|
-
return this.runWithResult().then(() => undefined);
|
|
879
|
-
}
|
|
880
|
-
/**
|
|
881
|
-
* 一連の処理を実行し、成功したかどうかを返します。
|
|
882
|
-
*
|
|
883
|
-
* @returns 成功した場合は true、途中停止や失敗時は false
|
|
884
|
-
*/
|
|
885
|
-
runWithResult() {
|
|
886
|
-
return this.execute();
|
|
887
|
-
}
|
|
888
|
-
/**
|
|
889
|
-
* 一連の処理を実行します。成功結果を内部で扱うための実体です。
|
|
890
|
-
*
|
|
891
|
-
* @returns 実行成功時は true、停止や失敗時は false
|
|
892
|
-
*/
|
|
893
|
-
async execute() {
|
|
894
|
-
const executionLock = this.acquireExecutionLock();
|
|
895
|
-
if (executionLock === false) {
|
|
896
|
-
return false;
|
|
897
|
-
}
|
|
898
|
-
try {
|
|
899
|
-
if (Object.keys(this.options).length === 0) {
|
|
900
|
-
return false;
|
|
901
|
-
}
|
|
902
|
-
if (this.options.formFragment &&
|
|
903
|
-
this.validate(this.options.formFragment) === false) {
|
|
904
|
-
return false;
|
|
905
|
-
}
|
|
906
|
-
// data-{event}-run: 任意 JS を同期実行する。await を挟む前に実行することで、
|
|
907
|
-
// クリックイベント中の event.preventDefault() が間に合う。本体が false を
|
|
908
|
-
// 返した場合はデフォルト動作(リンク遷移・フォーム送信)を抑止する。
|
|
909
|
-
if (this.options.runScript) {
|
|
910
|
-
const sourceElement = this.options.targetFragment?.getTarget() ?? null;
|
|
911
|
-
try {
|
|
912
|
-
const result = this.options.runScript.call(sourceElement, this.domEvent);
|
|
913
|
-
if (result === false && this.domEvent) {
|
|
914
|
-
this.domEvent.preventDefault();
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
catch (e) {
|
|
918
|
-
Log.error('Haori', `Run script execution error: ${e}`);
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
const confirmed = await this.confirm();
|
|
922
|
-
if (!confirmed) {
|
|
923
|
-
return false;
|
|
924
|
-
}
|
|
925
|
-
if (this.options.resetBeforeFragments &&
|
|
926
|
-
this.options.resetBeforeFragments.length > 0) {
|
|
927
|
-
await Promise.all(this.options.resetBeforeFragments.map(fragment => Form.reset(fragment)));
|
|
928
|
-
this.captureHistorySnapshots();
|
|
929
|
-
}
|
|
930
|
-
const preparedRequest = this.prepareFetchRequest();
|
|
931
|
-
const payload = preparedRequest.payload;
|
|
932
|
-
let fetchUrl = preparedRequest.url;
|
|
933
|
-
let fetchOptions = preparedRequest.options;
|
|
934
|
-
if (this.options.beforeCallback) {
|
|
935
|
-
const result = this.options.beforeCallback(fetchUrl || null, fetchOptions || null);
|
|
936
|
-
if (result !== undefined && result !== null) {
|
|
937
|
-
if (result === false || (typeof result === 'object' && result.stop)) {
|
|
938
|
-
return false;
|
|
939
|
-
}
|
|
940
|
-
if (typeof result === 'object') {
|
|
941
|
-
fetchUrl = ('fetchUrl' in result ? result.fetchUrl : fetchUrl);
|
|
942
|
-
fetchOptions = ('fetchOptions' in result ? result.fetchOptions : fetchOptions);
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
const hasPayload = Object.keys(payload).length > 0;
|
|
947
|
-
if (fetchUrl) {
|
|
948
|
-
const finalOptions = { ...(fetchOptions || {}) };
|
|
949
|
-
const requestedMethod = preparedRequest.requestedMethod;
|
|
950
|
-
const method = preparedRequest.effectiveMethod;
|
|
951
|
-
const isDemoQueryNormalization = preparedRequest.transportMode === 'query-get';
|
|
952
|
-
const queryString = preparedRequest.queryString;
|
|
953
|
-
if (isDemoQueryNormalization) {
|
|
954
|
-
Log.info('Haori demo fetch normalization', {
|
|
955
|
-
runtime: Env.runtime,
|
|
956
|
-
requestedMethod,
|
|
957
|
-
effectiveMethod: method,
|
|
958
|
-
transportMode: 'query-get',
|
|
959
|
-
url: fetchUrl,
|
|
960
|
-
payload: hasPayload ? payload : undefined,
|
|
961
|
-
queryString,
|
|
962
|
-
});
|
|
963
|
-
}
|
|
964
|
-
// fetchstartイベントを発火
|
|
965
|
-
if (this.options.targetFragment && fetchUrl) {
|
|
966
|
-
const startedAt = performance.now();
|
|
967
|
-
const fetchStartMetadata = {
|
|
968
|
-
runtime: Env.runtime,
|
|
969
|
-
requestedMethod,
|
|
970
|
-
effectiveMethod: method,
|
|
971
|
-
transportMode: isDemoQueryNormalization ? 'query-get' : 'http',
|
|
972
|
-
...(isDemoQueryNormalization ? { queryString } : {}),
|
|
973
|
-
};
|
|
974
|
-
HaoriEvent.fetchStart(this.options.targetFragment.getTarget(), fetchUrl, finalOptions, hasPayload ? payload : undefined, fetchStartMetadata);
|
|
975
|
-
return fetch(fetchUrl, finalOptions)
|
|
976
|
-
.then(response => {
|
|
977
|
-
return this.handleFetchResult(response, fetchUrl || undefined, startedAt);
|
|
978
|
-
})
|
|
979
|
-
.catch(error => {
|
|
980
|
-
if (fetchUrl) {
|
|
981
|
-
HaoriEvent.fetchError(this.options.targetFragment.getTarget(), fetchUrl, error);
|
|
982
|
-
}
|
|
983
|
-
throw error;
|
|
984
|
-
});
|
|
985
|
-
}
|
|
986
|
-
return fetch(fetchUrl, finalOptions).then(response => {
|
|
987
|
-
return this.handleFetchResult(response, fetchUrl || undefined);
|
|
988
|
-
});
|
|
989
|
-
}
|
|
990
|
-
// fetchUrlが無い場合(changeイベント等)、bindFragmentsが無ければformFragmentにバインド
|
|
991
|
-
if ((!this.options.bindFragments ||
|
|
992
|
-
this.options.bindFragments.length === 0) &&
|
|
993
|
-
this.options.formFragment &&
|
|
994
|
-
hasPayload) {
|
|
995
|
-
// 双方向バインディング: フォーム値を自動的にバインディングデータに反映
|
|
996
|
-
const formFragment = this.options.formFragment;
|
|
997
|
-
const formElement = formFragment.getTarget();
|
|
998
|
-
const skipFragments = new Set();
|
|
999
|
-
if (executionLock &&
|
|
1000
|
-
executionLock.appliedDisabledAttribute &&
|
|
1001
|
-
this.options.targetFragment) {
|
|
1002
|
-
skipFragments.add(this.options.targetFragment);
|
|
1003
|
-
}
|
|
1004
|
-
formElement.setAttribute(`${Env.prefix}bind`, JSON.stringify(payload));
|
|
1005
|
-
const bindingData = formFragment.getBindingData();
|
|
1006
|
-
Object.assign(bindingData, payload);
|
|
1007
|
-
await Core.setBindingData(formElement, bindingData, skipFragments);
|
|
1008
|
-
}
|
|
1009
|
-
const merged = hasPayload ? payload : {};
|
|
1010
|
-
const response = new Response(JSON.stringify(merged), {
|
|
1011
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1012
|
-
});
|
|
1013
|
-
return this.handleFetchResult(response);
|
|
1014
|
-
}
|
|
1015
|
-
finally {
|
|
1016
|
-
this.releaseExecutionLock(executionLock);
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
/**
|
|
1020
|
-
* click 手続きの重複実行を防ぐためのロックを取得します。
|
|
1021
|
-
*
|
|
1022
|
-
* @returns ロック情報。取得不要なら null、取得失敗なら false。
|
|
1023
|
-
*/
|
|
1024
|
-
acquireExecutionLock() {
|
|
1025
|
-
if (this.eventType !== 'click' || !this.options.targetFragment) {
|
|
1026
|
-
return null;
|
|
1027
|
-
}
|
|
1028
|
-
const targetFragment = this.options.targetFragment;
|
|
1029
|
-
const target = targetFragment.getTarget();
|
|
1030
|
-
if (Procedure.RUNNING_CLICK_TARGETS.has(target) ||
|
|
1031
|
-
target.matches(':disabled') ||
|
|
1032
|
-
target.hasAttribute('disabled') ||
|
|
1033
|
-
target.hasAttribute(PROCEDURE_CLICK_LOCK_MARKER)) {
|
|
1034
|
-
return false;
|
|
1035
|
-
}
|
|
1036
|
-
// data-click-no-disabled が指定されている場合は native disabled を付与しない。
|
|
1037
|
-
// Bootstrap など他ライブラリの click ハンドラや CSS が disabled 要素を無視する
|
|
1038
|
-
// 問題を避けつつ、内部マーカーと RUNNING_CLICK_TARGETS で多重実行は防止する。
|
|
1039
|
-
const skipDisabled = target.hasAttribute(`${Env.prefix}click-no-disabled`);
|
|
1040
|
-
Procedure.RUNNING_CLICK_TARGETS.add(target);
|
|
1041
|
-
target.setAttribute(PROCEDURE_CLICK_LOCK_MARKER, '');
|
|
1042
|
-
if (!skipDisabled) {
|
|
1043
|
-
target.setAttribute('disabled', '');
|
|
1044
|
-
}
|
|
1045
|
-
return {
|
|
1046
|
-
target,
|
|
1047
|
-
appliedDisabledAttribute: !skipDisabled,
|
|
1048
|
-
};
|
|
1049
|
-
}
|
|
1050
|
-
/**
|
|
1051
|
-
* 取得済みの実行ロックを解放します。
|
|
1052
|
-
*
|
|
1053
|
-
* @param executionLock 解放対象のロック情報。
|
|
1054
|
-
* @returns 戻り値はありません。
|
|
1055
|
-
*/
|
|
1056
|
-
releaseExecutionLock(executionLock) {
|
|
1057
|
-
if (!executionLock) {
|
|
1058
|
-
return;
|
|
1059
|
-
}
|
|
1060
|
-
Procedure.RUNNING_CLICK_TARGETS.delete(executionLock.target);
|
|
1061
|
-
// マーカーは常に解除する(解除し損ねると再クリックできなくなるため)。
|
|
1062
|
-
executionLock.target.removeAttribute(PROCEDURE_CLICK_LOCK_MARKER);
|
|
1063
|
-
if (executionLock.appliedDisabledAttribute) {
|
|
1064
|
-
executionLock.target.removeAttribute('disabled');
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
/**
|
|
1068
|
-
* フェッチ後の処理を実行します。
|
|
1069
|
-
*/
|
|
1070
|
-
async handleFetchResult(response, url, startedAt) {
|
|
1071
|
-
const activeHaori = resolveProcedureHaoriApi();
|
|
1072
|
-
// エラー応答時は以後の処理を停止し、メッセージを伝播
|
|
1073
|
-
if (!response.ok) {
|
|
1074
|
-
if (this.options.targetFragment && url) {
|
|
1075
|
-
HaoriEvent.fetchError(this.options.targetFragment.getTarget(), url, new Error(`${response.status} ${response.statusText}`), response.status, startedAt);
|
|
1076
|
-
}
|
|
1077
|
-
await this.handleFetchError(response);
|
|
1078
|
-
return false;
|
|
1079
|
-
}
|
|
1080
|
-
// fetchendイベントを発火
|
|
1081
|
-
if (this.options.targetFragment && url && startedAt) {
|
|
1082
|
-
HaoriEvent.fetchEnd(this.options.targetFragment.getTarget(), url, response.status, startedAt);
|
|
1083
|
-
}
|
|
1084
|
-
if (this.options.afterCallback) {
|
|
1085
|
-
const result = this.options.afterCallback(response);
|
|
1086
|
-
if (result !== undefined && result !== null) {
|
|
1087
|
-
if (result === false || (typeof result === 'object' && result.stop)) {
|
|
1088
|
-
return false;
|
|
1089
|
-
}
|
|
1090
|
-
if (typeof result === 'object' && 'response' in result) {
|
|
1091
|
-
response = ('response' in result ? result.response : response);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
const promises = [];
|
|
1096
|
-
promises.push(this.bindResult(response));
|
|
1097
|
-
promises.push(this.adjust());
|
|
1098
|
-
promises.push(this.addRow());
|
|
1099
|
-
promises.push(this.removeRow());
|
|
1100
|
-
promises.push(this.movePrevRow());
|
|
1101
|
-
promises.push(this.moveNextRow());
|
|
1102
|
-
await Promise.all(promises);
|
|
1103
|
-
if (this.options.resetFragments && this.options.resetFragments.length > 0) {
|
|
1104
|
-
await Promise.all(this.options.resetFragments.map(fragment => Form.reset(fragment)));
|
|
1105
|
-
}
|
|
1106
|
-
await this.copy();
|
|
1107
|
-
const deferredPromises = [];
|
|
1108
|
-
if (this.options.refetchFragments &&
|
|
1109
|
-
this.options.refetchFragments.length > 0) {
|
|
1110
|
-
this.options.refetchFragments.forEach(fragment => {
|
|
1111
|
-
deferredPromises.push(new Procedure(fragment, null).run());
|
|
1112
|
-
});
|
|
1113
|
-
}
|
|
1114
|
-
if (this.options.clickFragments && this.options.clickFragments.length > 0) {
|
|
1115
|
-
// bind 後の最新 DOM を参照させるため click 前に再評価する。
|
|
1116
|
-
// 複数フラグメントは直列実行:各 click が前の evaluateAll 完了後に発火する。
|
|
1117
|
-
for (const fragment of this.options.clickFragments) {
|
|
1118
|
-
await Core.evaluateAll(fragment);
|
|
1119
|
-
const target = fragment.getTarget();
|
|
1120
|
-
if (typeof target.click === 'function') {
|
|
1121
|
-
target.click();
|
|
1122
|
-
}
|
|
1123
|
-
else {
|
|
1124
|
-
target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
if (this.options.openFragments && this.options.openFragments.length > 0) {
|
|
1129
|
-
this.options.openFragments.forEach(fragment => {
|
|
1130
|
-
const target = fragment.getTarget();
|
|
1131
|
-
if (target instanceof HTMLElement) {
|
|
1132
|
-
deferredPromises.push(activeHaori.openDialog(target));
|
|
1133
|
-
}
|
|
1134
|
-
else {
|
|
1135
|
-
Log.error('Haori', 'Element is not an HTML element: ', target);
|
|
1136
|
-
}
|
|
1137
|
-
});
|
|
1138
|
-
}
|
|
1139
|
-
if (this.options.closeFragments && this.options.closeFragments.length > 0) {
|
|
1140
|
-
this.options.closeFragments.forEach(fragment => {
|
|
1141
|
-
const target = fragment.getTarget();
|
|
1142
|
-
if (target instanceof HTMLElement) {
|
|
1143
|
-
deferredPromises.push(activeHaori.closeDialog(target));
|
|
1144
|
-
}
|
|
1145
|
-
else {
|
|
1146
|
-
Log.error('Haori', 'Element is not an HTML element: ', target);
|
|
1147
|
-
}
|
|
1148
|
-
});
|
|
1149
|
-
}
|
|
1150
|
-
// 仕様順序: 先に各種操作(bind/adjust/row/reset/refetch/click/open/close)を完了
|
|
1151
|
-
await Promise.all(deferredPromises);
|
|
1152
|
-
// その後にダイアログ/トーストを表示
|
|
1153
|
-
if (this.options.dialogMessage) {
|
|
1154
|
-
await activeHaori.dialog(this.options.dialogMessage);
|
|
1155
|
-
}
|
|
1156
|
-
if (this.options.toastMessage) {
|
|
1157
|
-
await activeHaori.toast(this.options.toastMessage, this.options.toastLevel ?? 'info');
|
|
1158
|
-
}
|
|
1159
|
-
this.pushHistory();
|
|
1160
|
-
if (this.options.scrollTarget) {
|
|
1161
|
-
const el = document.querySelector(this.options.scrollTarget);
|
|
1162
|
-
el?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
1163
|
-
}
|
|
1164
|
-
if (this.options.redirectUrl) {
|
|
1165
|
-
window.location.href = this.options.redirectUrl;
|
|
1166
|
-
}
|
|
1167
|
-
return true;
|
|
1168
|
-
}
|
|
1169
|
-
/**
|
|
1170
|
-
* history.pushState を実行します。
|
|
1171
|
-
*
|
|
1172
|
-
* `historyUrl` / `historyData` / `historyFormFragment` の内容を基に URL を組み立て、
|
|
1173
|
-
* `history.pushState()` を呼び出します。いずれも未指定の場合は何もしません。
|
|
1174
|
-
* 不正 URL・オリジン違反・例外は `Log.error` でログ出力してスキップし、後続処理は継続します。
|
|
1175
|
-
*/
|
|
1176
|
-
pushHistory() {
|
|
1177
|
-
const hasHistoryUrl = this.options.historyUrl !== undefined && this.options.historyUrl !== null;
|
|
1178
|
-
const historyDataValues = this.resolveHistoryDataValues();
|
|
1179
|
-
const historyFormValues = this.resolveHistoryFormValues();
|
|
1180
|
-
const hasHistoryData = historyDataValues !== undefined && historyDataValues !== null;
|
|
1181
|
-
const hasHistoryForm = historyFormValues !== undefined && historyFormValues !== null;
|
|
1182
|
-
if (!hasHistoryUrl && !hasHistoryData && !hasHistoryForm) {
|
|
1183
|
-
return;
|
|
1184
|
-
}
|
|
1185
|
-
try {
|
|
1186
|
-
const baseUrlString = hasHistoryUrl
|
|
1187
|
-
? this.options.historyUrl
|
|
1188
|
-
: window.location.pathname;
|
|
1189
|
-
const url = new URL(baseUrlString, window.location.href);
|
|
1190
|
-
if (url.origin !== window.location.origin) {
|
|
1191
|
-
const errorMessage = 'history.pushState: cross-origin URL is not allowed: ' +
|
|
1192
|
-
url.toString();
|
|
1193
|
-
Log.error('Haori', errorMessage);
|
|
1194
|
-
return;
|
|
1195
|
-
}
|
|
1196
|
-
const appendParams = (values) => {
|
|
1197
|
-
for (const [k, v] of Object.entries(values)) {
|
|
1198
|
-
if (v === undefined || v === null) {
|
|
1199
|
-
continue;
|
|
1200
|
-
}
|
|
1201
|
-
if (Array.isArray(v)) {
|
|
1202
|
-
v.forEach(item => url.searchParams.append(k, String(item)));
|
|
1203
|
-
}
|
|
1204
|
-
else if (typeof v === 'object') {
|
|
1205
|
-
url.searchParams.set(k, JSON.stringify(v));
|
|
1206
|
-
}
|
|
1207
|
-
else {
|
|
1208
|
-
url.searchParams.set(k, String(v));
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
};
|
|
1212
|
-
if (hasHistoryData) {
|
|
1213
|
-
appendParams(historyDataValues);
|
|
1214
|
-
}
|
|
1215
|
-
if (hasHistoryForm) {
|
|
1216
|
-
appendParams(historyFormValues);
|
|
1217
|
-
}
|
|
1218
|
-
history.pushState({ [PROCEDURE_HISTORY_STATE_KEY]: true }, '', url.toString());
|
|
1219
|
-
}
|
|
1220
|
-
catch (e) {
|
|
1221
|
-
Log.error('Haori', `history.pushState failed: ${e}`);
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
/**
|
|
1225
|
-
* フェッチエラー応答のメッセージを適切な要素へ伝播します。
|
|
1226
|
-
*/
|
|
1227
|
-
async handleFetchError(response) {
|
|
1228
|
-
// ベースとなるフォーム/フラグメントを決定
|
|
1229
|
-
let baseFragment = null;
|
|
1230
|
-
if (this.options.formFragment) {
|
|
1231
|
-
baseFragment = this.options.formFragment;
|
|
1232
|
-
}
|
|
1233
|
-
else if (this.options.targetFragment) {
|
|
1234
|
-
baseFragment =
|
|
1235
|
-
Form.getFormFragment(this.options.targetFragment) ||
|
|
1236
|
-
this.options.targetFragment;
|
|
1237
|
-
}
|
|
1238
|
-
const addGeneralMessage = async (message) => {
|
|
1239
|
-
const targetEl = baseFragment ? baseFragment.getTarget() : document.body;
|
|
1240
|
-
await resolveProcedureHaoriApi().addErrorMessage(targetEl, message);
|
|
1241
|
-
};
|
|
1242
|
-
const scrollToFirstError = () => {
|
|
1243
|
-
if (!this.options.scrollOnError) {
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1246
|
-
const root = baseFragment ? baseFragment.getTarget() : document.body;
|
|
1247
|
-
// addErrorMessage はフォーム以外の target に対して parentElement へエラーを付与するため、
|
|
1248
|
-
// root 自身・parentElement・root 配下の順で探索する
|
|
1249
|
-
const errorTarget = root.getAttribute('data-message-level') === 'error'
|
|
1250
|
-
? root
|
|
1251
|
-
: root.parentElement?.getAttribute('data-message-level') === 'error'
|
|
1252
|
-
? root.parentElement
|
|
1253
|
-
: root.querySelector('[data-message-level="error"]');
|
|
1254
|
-
errorTarget?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
1255
|
-
};
|
|
1256
|
-
// コンテンツタイプに応じて解析
|
|
1257
|
-
const contentType = response.headers.get('Content-Type') || '';
|
|
1258
|
-
if (contentType.includes('application/json')) {
|
|
1259
|
-
try {
|
|
1260
|
-
const data = await response.json();
|
|
1261
|
-
// 代表的な形式に対応
|
|
1262
|
-
const entries = [];
|
|
1263
|
-
if (data && typeof data === 'object') {
|
|
1264
|
-
if (typeof data.message === 'string') {
|
|
1265
|
-
entries.push({ message: data.message });
|
|
1266
|
-
}
|
|
1267
|
-
if (Array.isArray(data.messages)) {
|
|
1268
|
-
for (const m of data.messages) {
|
|
1269
|
-
if (typeof m === 'string') {
|
|
1270
|
-
entries.push({ message: m });
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
if (data.errors && typeof data.errors === 'object') {
|
|
1275
|
-
for (const [k, v] of Object.entries(data.errors)) {
|
|
1276
|
-
if (Array.isArray(v)) {
|
|
1277
|
-
entries.push({ key: k, message: v.join('\n') });
|
|
1278
|
-
}
|
|
1279
|
-
else if (typeof v === 'string') {
|
|
1280
|
-
entries.push({ key: k, message: v });
|
|
1281
|
-
}
|
|
1282
|
-
else if (v != null) {
|
|
1283
|
-
entries.push({ key: k, message: String(v) });
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
// キー: 値(文字列/配列)形式にフォールバック
|
|
1288
|
-
if (entries.length === 0) {
|
|
1289
|
-
for (const [k, v] of Object.entries(data)) {
|
|
1290
|
-
if (k === 'message' || k === 'messages' || k === 'errors') {
|
|
1291
|
-
continue;
|
|
1292
|
-
}
|
|
1293
|
-
if (Array.isArray(v)) {
|
|
1294
|
-
entries.push({ key: k, message: v.join('\n') });
|
|
1295
|
-
}
|
|
1296
|
-
else if (typeof v === 'string') {
|
|
1297
|
-
entries.push({ key: k, message: v });
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
if (entries.length === 0) {
|
|
1303
|
-
// 汎用メッセージ
|
|
1304
|
-
await addGeneralMessage(`${response.status} ${response.statusText}`);
|
|
1305
|
-
scrollToFirstError();
|
|
1306
|
-
return false;
|
|
1307
|
-
}
|
|
1308
|
-
// メッセージを反映
|
|
1309
|
-
for (const e of entries) {
|
|
1310
|
-
if (e.key && baseFragment) {
|
|
1311
|
-
await Form.addErrorMessage(baseFragment, e.key, e.message);
|
|
1312
|
-
}
|
|
1313
|
-
else {
|
|
1314
|
-
await addGeneralMessage(e.message);
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
scrollToFirstError();
|
|
1318
|
-
return false;
|
|
1319
|
-
}
|
|
1320
|
-
catch {
|
|
1321
|
-
// JSON 解析失敗時はテキストにフォールバック
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
// テキストとして処理
|
|
1325
|
-
try {
|
|
1326
|
-
const text = await response.text();
|
|
1327
|
-
if (text && text.trim().length > 0) {
|
|
1328
|
-
await addGeneralMessage(text.trim());
|
|
1329
|
-
}
|
|
1330
|
-
else {
|
|
1331
|
-
await addGeneralMessage(`${response.status} ${response.statusText}`);
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
catch {
|
|
1335
|
-
await addGeneralMessage(`${response.status} ${response.statusText}`);
|
|
1336
|
-
}
|
|
1337
|
-
scrollToFirstError();
|
|
1338
|
-
return false;
|
|
1339
|
-
}
|
|
1340
|
-
/**
|
|
1341
|
-
* 対象のフラグメント以下の入力要素に対してバリデーションを実行します。
|
|
1342
|
-
* バリデーションエラーがある場合は、最初のエラー要素にフォーカスを移動します。
|
|
1343
|
-
*
|
|
1344
|
-
* @param fragment 対象のフラグメント
|
|
1345
|
-
* @returns バリデーション結果(true: 成功, false: 失敗)
|
|
1346
|
-
*/
|
|
1347
|
-
validate(fragment) {
|
|
1348
|
-
if (this.options.valid !== true) {
|
|
1349
|
-
return true;
|
|
1350
|
-
}
|
|
1351
|
-
const firstInvalid = this.findFirstInvalid(fragment);
|
|
1352
|
-
if (firstInvalid === null) {
|
|
1353
|
-
return true;
|
|
1354
|
-
}
|
|
1355
|
-
// 検出フェーズ(findFirstInvalid)は checkValidity で副作用なく走査済み。
|
|
1356
|
-
// reportValidity と focus は確定した 1 要素にだけ呼び出す。
|
|
1357
|
-
firstInvalid.reportValidity();
|
|
1358
|
-
firstInvalid.focus();
|
|
1359
|
-
if (this.options.scrollOnError) {
|
|
1360
|
-
firstInvalid.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
1361
|
-
}
|
|
1362
|
-
return false;
|
|
1363
|
-
}
|
|
1364
|
-
/**
|
|
1365
|
-
* 対象フラグメント以下で DOM 順の最上部にある invalid 要素を返します。
|
|
1366
|
-
* 副作用のない checkValidity のみを使用し、検出のみを行います。
|
|
1367
|
-
*
|
|
1368
|
-
* @param fragment 対象のフラグメント
|
|
1369
|
-
* @returns 最初の invalid 要素、なければ null
|
|
1370
|
-
*/
|
|
1371
|
-
findFirstInvalid(fragment) {
|
|
1372
|
-
// 子要素を逆順に処理することで、DOM 順の先頭要素が最後に found を上書きし、
|
|
1373
|
-
// 最終的に最上部の invalid 要素が返る
|
|
1374
|
-
let found = null;
|
|
1375
|
-
for (const child of fragment.getChildElementFragments().reverse()) {
|
|
1376
|
-
const result = this.findFirstInvalid(child);
|
|
1377
|
-
if (result !== null) {
|
|
1378
|
-
found = result;
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
// 自身は子より DOM 上位にあるため、invalid なら子の結果を上書きする
|
|
1382
|
-
if (!this.checkOne(fragment)) {
|
|
1383
|
-
return fragment.getTarget();
|
|
1384
|
-
}
|
|
1385
|
-
return found;
|
|
1386
|
-
}
|
|
1387
|
-
/**
|
|
1388
|
-
* 対象のフラグメントに対して、副作用なく有効性を検査します。
|
|
1389
|
-
* reportValidity は使わず checkValidity のみ呼び出します。
|
|
1390
|
-
*
|
|
1391
|
-
* @param fragment 対象のフラグメント
|
|
1392
|
-
* @returns 有効なら true、無効なら false
|
|
1393
|
-
*/
|
|
1394
|
-
checkOne(fragment) {
|
|
1395
|
-
const target = fragment.getTarget();
|
|
1396
|
-
if (target instanceof HTMLInputElement ||
|
|
1397
|
-
target instanceof HTMLSelectElement ||
|
|
1398
|
-
target instanceof HTMLTextAreaElement) {
|
|
1399
|
-
return target.checkValidity();
|
|
1400
|
-
}
|
|
1401
|
-
return true;
|
|
1402
|
-
}
|
|
1403
|
-
/**
|
|
1404
|
-
* 確認メッセージを表示し、ユーザーの確認を求めます。
|
|
1405
|
-
* メッセージが設定されていない場合は、即座に成功とみなします。
|
|
1406
|
-
*
|
|
1407
|
-
* @returns ユーザーの確認結果を含むPromise(true: 確認, false: キャンセル)
|
|
1408
|
-
*/
|
|
1409
|
-
confirm() {
|
|
1410
|
-
const message = this.options.confirmMessage;
|
|
1411
|
-
if (message === null || message === undefined) {
|
|
1412
|
-
return Promise.resolve(true);
|
|
1413
|
-
}
|
|
1414
|
-
return resolveProcedureHaoriApi().confirm(message);
|
|
1415
|
-
}
|
|
1416
|
-
/**
|
|
1417
|
-
* 結果データを対象のフラグメントにバインドします。
|
|
1418
|
-
*
|
|
1419
|
-
* @param response フェッチのレスポンスオブジェクト
|
|
1420
|
-
*/
|
|
1421
|
-
bindResult(response) {
|
|
1422
|
-
if (!this.options.bindFragments ||
|
|
1423
|
-
this.options.bindFragments.length === 0) {
|
|
1424
|
-
return Promise.resolve();
|
|
1425
|
-
}
|
|
1426
|
-
const promise = response.headers
|
|
1427
|
-
.get('Content-Type')
|
|
1428
|
-
?.includes('application/json')
|
|
1429
|
-
? response.json()
|
|
1430
|
-
: response.text();
|
|
1431
|
-
return promise.then(data => {
|
|
1432
|
-
if (this.options.bindParams) {
|
|
1433
|
-
const newData = {};
|
|
1434
|
-
this.options.bindParams.forEach(param => {
|
|
1435
|
-
if (data && typeof data === 'object' && param in data) {
|
|
1436
|
-
newData[param] = data[param];
|
|
1437
|
-
}
|
|
1438
|
-
});
|
|
1439
|
-
data = newData;
|
|
1440
|
-
}
|
|
1441
|
-
const promises = [];
|
|
1442
|
-
if (this.options.bindArg) {
|
|
1443
|
-
this.options.bindFragments.forEach(fragment => {
|
|
1444
|
-
// バインド先の「自身の」最新 binding(getRawBindingData)を基底にして
|
|
1445
|
-
// bindArg キーだけを更新する。getBindingData()(継承込み)を基底にすると
|
|
1446
|
-
// 継承キーが own の data-bind に混入してしまうため、own のみを対象にする。
|
|
1447
|
-
// 読み取り〜書き込み(fragment.setBindingData)は await を挟まず同期で行われ、
|
|
1448
|
-
// 並行・リアクティブな複数 bind-arg が重なっても呼び出し単位で原子的に
|
|
1449
|
-
// 反映される(各呼び出しは直前の更新後の最新 own を読む)。
|
|
1450
|
-
const bindingData = { ...(fragment.getRawBindingData() ?? {}) };
|
|
1451
|
-
const bindArg = this.options.bindArg;
|
|
1452
|
-
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
1453
|
-
const currentValue = bindingData[bindArg];
|
|
1454
|
-
const currentObject = currentValue &&
|
|
1455
|
-
typeof currentValue === 'object' &&
|
|
1456
|
-
!Array.isArray(currentValue)
|
|
1457
|
-
? currentValue
|
|
1458
|
-
: {};
|
|
1459
|
-
bindingData[bindArg] = this.mergeAppendBindingData(fragment, data, currentObject);
|
|
1460
|
-
}
|
|
1461
|
-
else {
|
|
1462
|
-
bindingData[bindArg] = data;
|
|
1463
|
-
}
|
|
1464
|
-
promises.push(Core.setBindingData(fragment.getTarget(), bindingData));
|
|
1465
|
-
});
|
|
1466
|
-
}
|
|
1467
|
-
else if (typeof data === 'string') {
|
|
1468
|
-
Log.error('Haori', 'string data cannot be bound without a bindArg.');
|
|
1469
|
-
return Promise.reject(new Error('string data cannot be bound without a bindArg.'));
|
|
1470
|
-
}
|
|
1471
|
-
else {
|
|
1472
|
-
this.options.bindFragments.forEach(fragment => {
|
|
1473
|
-
const resolvedData = this.mergeAppendBindingData(fragment, data);
|
|
1474
|
-
// bind-merge 指定時は全置換せず、対象要素自身の既存 binding data へ
|
|
1475
|
-
// 浅くマージして未指定キー(例: 一覧の items)を保持する。
|
|
1476
|
-
const finalData = this.options.bindMerge
|
|
1477
|
-
? { ...(fragment.getRawBindingData() ?? {}), ...resolvedData }
|
|
1478
|
-
: resolvedData;
|
|
1479
|
-
promises.push(Core.setBindingData(fragment.getTarget(), finalData));
|
|
1480
|
-
});
|
|
1481
|
-
}
|
|
1482
|
-
return Promise.all(promises).then(() => {
|
|
1483
|
-
// バインドと対象配下の再評価(data-if / data-each 等)の完了後に
|
|
1484
|
-
// bindcomplete を発火し、外部スクリプトが同期処理を行えるようにする。
|
|
1485
|
-
const bindArg = this.options.bindArg ?? null;
|
|
1486
|
-
this.options.bindFragments.forEach(fragment => {
|
|
1487
|
-
HaoriEvent.bindComplete(fragment.getTarget(), bindArg);
|
|
1488
|
-
});
|
|
1489
|
-
return undefined;
|
|
1490
|
-
});
|
|
1491
|
-
});
|
|
1492
|
-
}
|
|
1493
|
-
/**
|
|
1494
|
-
* bind-append 指定があるキーについて、既存配列と結合したデータを返します。
|
|
1495
|
-
*/
|
|
1496
|
-
mergeAppendBindingData(fragment, data, currentData = fragment.getBindingData()) {
|
|
1497
|
-
if (!this.options.bindAppendParams ||
|
|
1498
|
-
this.options.bindAppendParams.length === 0) {
|
|
1499
|
-
return data;
|
|
1500
|
-
}
|
|
1501
|
-
const merged = { ...data };
|
|
1502
|
-
const current = currentData;
|
|
1503
|
-
for (const key of this.options.bindAppendParams) {
|
|
1504
|
-
const incoming = merged[key];
|
|
1505
|
-
const existing = current[key];
|
|
1506
|
-
if (Array.isArray(existing) && Array.isArray(incoming)) {
|
|
1507
|
-
merged[key] = existing.concat(incoming);
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
return merged;
|
|
1511
|
-
}
|
|
1512
|
-
/**
|
|
1513
|
-
* 指定されたフラグメントへバインディングデータをコピーします。
|
|
1514
|
-
*/
|
|
1515
|
-
copy() {
|
|
1516
|
-
if (!this.options.copyFragments ||
|
|
1517
|
-
this.options.copyFragments.length === 0) {
|
|
1518
|
-
return Promise.resolve();
|
|
1519
|
-
}
|
|
1520
|
-
const sourceData = this.resolveCopySourceData();
|
|
1521
|
-
const copyData = this.pickCopyData(sourceData);
|
|
1522
|
-
const promises = this.options.copyFragments.map(fragment => {
|
|
1523
|
-
const bindingData = {
|
|
1524
|
-
...fragment.getBindingData(),
|
|
1525
|
-
...copyData,
|
|
1526
|
-
};
|
|
1527
|
-
return Core.setBindingData(fragment.getTarget(), bindingData);
|
|
1528
|
-
});
|
|
1529
|
-
return Promise.all(promises).then(() => undefined);
|
|
1530
|
-
}
|
|
1531
|
-
/**
|
|
1532
|
-
* copy のコピー元データを取得します。
|
|
1533
|
-
*/
|
|
1534
|
-
resolveCopySourceData() {
|
|
1535
|
-
if (this.options.copySourceFragment) {
|
|
1536
|
-
const sourceTarget = this.options.copySourceFragment.getTarget();
|
|
1537
|
-
if (sourceTarget.tagName === 'FORM') {
|
|
1538
|
-
return Form.getValues(this.options.copySourceFragment);
|
|
1539
|
-
}
|
|
1540
|
-
return { ...this.options.copySourceFragment.getBindingData() };
|
|
1541
|
-
}
|
|
1542
|
-
if (this.options.formFragment) {
|
|
1543
|
-
return Form.getValues(this.options.formFragment);
|
|
1544
|
-
}
|
|
1545
|
-
if (this.options.targetFragment) {
|
|
1546
|
-
return { ...this.options.targetFragment.getBindingData() };
|
|
1547
|
-
}
|
|
1548
|
-
return {};
|
|
1549
|
-
}
|
|
1550
|
-
/**
|
|
1551
|
-
* data 属性とフォーム値を統合した送信データを作成します。
|
|
1552
|
-
*
|
|
1553
|
-
* @returns 送信データ。
|
|
1554
|
-
*/
|
|
1555
|
-
buildPayload() {
|
|
1556
|
-
return this.buildPayloadResolution().payload;
|
|
1557
|
-
}
|
|
1558
|
-
/**
|
|
1559
|
-
* data 属性とフォーム値を統合した送信データを作成し、未解決参照の有無を返します。
|
|
1560
|
-
*
|
|
1561
|
-
* @returns 送信データと未解決参照の有無。
|
|
1562
|
-
*/
|
|
1563
|
-
buildPayloadResolution() {
|
|
1564
|
-
const payload = {};
|
|
1565
|
-
let hasUnresolvedReference = false;
|
|
1566
|
-
if (this.options.formFragment) {
|
|
1567
|
-
Object.assign(payload, Form.getValues(this.options.formFragment));
|
|
1568
|
-
}
|
|
1569
|
-
if (this.options.data && typeof this.options.data === 'object') {
|
|
1570
|
-
Object.assign(payload, this.options.data);
|
|
1571
|
-
}
|
|
1572
|
-
if (this.options.targetFragment && this.options.dataAttrName) {
|
|
1573
|
-
const resolvedData = Procedure.resolveDataAttributeDetailed(this.options.targetFragment, this.options.dataAttrName);
|
|
1574
|
-
hasUnresolvedReference =
|
|
1575
|
-
hasUnresolvedReference || resolvedData.hasUnresolvedReference;
|
|
1576
|
-
if (resolvedData.value) {
|
|
1577
|
-
Object.assign(payload, resolvedData.value);
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
return { payload, hasUnresolvedReference };
|
|
1581
|
-
}
|
|
1582
|
-
/**
|
|
1583
|
-
* 現在の data-fetch 実行内容を比較用シグネチャへ正規化します。
|
|
1584
|
-
*
|
|
1585
|
-
* @returns フェッチシグネチャと未解決参照の有無。
|
|
1586
|
-
*/
|
|
1587
|
-
resolveFetchSignature() {
|
|
1588
|
-
const preparedRequest = this.prepareFetchRequest();
|
|
1589
|
-
return {
|
|
1590
|
-
signature: preparedRequest.signature,
|
|
1591
|
-
hasUnresolvedReference: preparedRequest.hasUnresolvedReference,
|
|
1592
|
-
};
|
|
1593
|
-
}
|
|
1594
|
-
/**
|
|
1595
|
-
* 現在のオプションから送信前の fetch リクエストを組み立てます。
|
|
1596
|
-
*
|
|
1597
|
-
* @returns リクエスト情報。
|
|
1598
|
-
*/
|
|
1599
|
-
prepareFetchRequest() {
|
|
1600
|
-
const payloadResolution = this.buildPayloadResolution();
|
|
1601
|
-
const payload = payloadResolution.payload;
|
|
1602
|
-
const hasUnresolvedReference = Boolean(this.options.fetchHasUnresolvedReference) ||
|
|
1603
|
-
payloadResolution.hasUnresolvedReference;
|
|
1604
|
-
if (!this.options.fetchUrl || hasUnresolvedReference) {
|
|
1605
|
-
return {
|
|
1606
|
-
url: null,
|
|
1607
|
-
options: null,
|
|
1608
|
-
payload,
|
|
1609
|
-
hasUnresolvedReference,
|
|
1610
|
-
requestedMethod: 'GET',
|
|
1611
|
-
effectiveMethod: 'GET',
|
|
1612
|
-
transportMode: 'http',
|
|
1613
|
-
signature: null,
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1616
|
-
let fetchUrl = this.options.fetchUrl;
|
|
1617
|
-
const finalOptions = { ...(this.options.fetchOptions || {}) };
|
|
1618
|
-
const headers = new Headers(finalOptions.headers || undefined);
|
|
1619
|
-
const requestedMethod = (finalOptions.method || 'GET').toUpperCase();
|
|
1620
|
-
const isDemoQueryNormalization = Env.runtime === 'demo' && !isQueryTransportMethod(requestedMethod);
|
|
1621
|
-
const method = isDemoQueryNormalization ? 'GET' : requestedMethod;
|
|
1622
|
-
finalOptions.method = method;
|
|
1623
|
-
if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS') {
|
|
1624
|
-
if (Object.keys(payload).length > 0) {
|
|
1625
|
-
fetchUrl = appendPayloadToUrl(fetchUrl, payload);
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
else if (Object.keys(payload).length > 0) {
|
|
1629
|
-
const contentType = headers.get('Content-Type') || '';
|
|
1630
|
-
if (/multipart\/form-data/i.test(contentType)) {
|
|
1631
|
-
headers.delete('Content-Type');
|
|
1632
|
-
const formData = new FormData();
|
|
1633
|
-
for (const [k, v] of Object.entries(payload)) {
|
|
1634
|
-
if (v === undefined || v === null) {
|
|
1635
|
-
formData.append(k, '');
|
|
1636
|
-
}
|
|
1637
|
-
else if (v instanceof Blob) {
|
|
1638
|
-
formData.append(k, v);
|
|
1639
|
-
}
|
|
1640
|
-
else if (Array.isArray(v)) {
|
|
1641
|
-
v.forEach(item => formData.append(k, String(item)));
|
|
1642
|
-
}
|
|
1643
|
-
else if (typeof v === 'object') {
|
|
1644
|
-
formData.append(k, JSON.stringify(v));
|
|
1645
|
-
}
|
|
1646
|
-
else {
|
|
1647
|
-
formData.append(k, String(v));
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
finalOptions.body = formData;
|
|
1651
|
-
}
|
|
1652
|
-
else if (/application\/x-www-form-urlencoded/i.test(contentType)) {
|
|
1653
|
-
const params = new URLSearchParams();
|
|
1654
|
-
for (const [k, v] of Object.entries(payload)) {
|
|
1655
|
-
if (v === undefined) {
|
|
1656
|
-
continue;
|
|
1657
|
-
}
|
|
1658
|
-
if (v === null) {
|
|
1659
|
-
params.append(k, '');
|
|
1660
|
-
}
|
|
1661
|
-
else if (Array.isArray(v)) {
|
|
1662
|
-
v.forEach(item => params.append(k, String(item)));
|
|
1663
|
-
}
|
|
1664
|
-
else if (typeof v === 'object') {
|
|
1665
|
-
params.append(k, JSON.stringify(v));
|
|
1666
|
-
}
|
|
1667
|
-
else {
|
|
1668
|
-
params.append(k, String(v));
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
finalOptions.body = params;
|
|
1672
|
-
}
|
|
1673
|
-
else {
|
|
1674
|
-
headers.set('Content-Type', 'application/json');
|
|
1675
|
-
finalOptions.body = JSON.stringify(payload);
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
finalOptions.headers = headers;
|
|
1679
|
-
let queryString;
|
|
1680
|
-
if (isDemoQueryNormalization) {
|
|
1681
|
-
queryString = new URL(fetchUrl, window.location.href).search || undefined;
|
|
1682
|
-
headers.delete('Content-Type');
|
|
1683
|
-
}
|
|
1684
|
-
return {
|
|
1685
|
-
url: fetchUrl,
|
|
1686
|
-
options: finalOptions,
|
|
1687
|
-
payload,
|
|
1688
|
-
hasUnresolvedReference: false,
|
|
1689
|
-
requestedMethod,
|
|
1690
|
-
effectiveMethod: method,
|
|
1691
|
-
queryString,
|
|
1692
|
-
transportMode: isDemoQueryNormalization ? 'query-get' : 'http',
|
|
1693
|
-
signature: buildFetchSignature(fetchUrl, finalOptions),
|
|
1694
|
-
};
|
|
1695
|
-
}
|
|
1696
|
-
/**
|
|
1697
|
-
* reset-before 後の history 用スナップショットを保存します。
|
|
1698
|
-
*/
|
|
1699
|
-
captureHistorySnapshots() {
|
|
1700
|
-
if (this.options.targetFragment && this.options.historyDataAttrName) {
|
|
1701
|
-
this.historyDataSnapshot = Procedure.resolveDataAttribute(this.options.targetFragment, this.options.historyDataAttrName);
|
|
1702
|
-
}
|
|
1703
|
-
else {
|
|
1704
|
-
this.historyDataSnapshot = undefined;
|
|
1705
|
-
}
|
|
1706
|
-
this.historyFormSnapshot = this.options.historyFormFragment
|
|
1707
|
-
? Form.getValues(this.options.historyFormFragment)
|
|
1708
|
-
: undefined;
|
|
1709
|
-
}
|
|
1710
|
-
/**
|
|
1711
|
-
* history-data の評価値を取得します。
|
|
1712
|
-
*
|
|
1713
|
-
* @returns history-data の評価値。
|
|
1714
|
-
*/
|
|
1715
|
-
resolveHistoryDataValues() {
|
|
1716
|
-
if (this.historyDataSnapshot !== undefined) {
|
|
1717
|
-
return this.historyDataSnapshot;
|
|
1718
|
-
}
|
|
1719
|
-
if (this.options.targetFragment && this.options.historyDataAttrName) {
|
|
1720
|
-
return Procedure.resolveDataAttribute(this.options.targetFragment, this.options.historyDataAttrName);
|
|
1721
|
-
}
|
|
1722
|
-
return this.options.historyData;
|
|
1723
|
-
}
|
|
1724
|
-
/**
|
|
1725
|
-
* history-form の評価値を取得します。
|
|
1726
|
-
*
|
|
1727
|
-
* @returns history-form の評価値。
|
|
1728
|
-
*/
|
|
1729
|
-
resolveHistoryFormValues() {
|
|
1730
|
-
if (this.historyFormSnapshot !== undefined) {
|
|
1731
|
-
return this.historyFormSnapshot;
|
|
1732
|
-
}
|
|
1733
|
-
if (this.options.historyFormFragment) {
|
|
1734
|
-
return Form.getValues(this.options.historyFormFragment);
|
|
1735
|
-
}
|
|
1736
|
-
return undefined;
|
|
1737
|
-
}
|
|
1738
|
-
/**
|
|
1739
|
-
* copy-params が指定されている場合は include / exclude を考慮して抽出します。
|
|
1740
|
-
*/
|
|
1741
|
-
pickCopyData(sourceData) {
|
|
1742
|
-
if (!this.options.copyParams || this.options.copyParams.length === 0) {
|
|
1743
|
-
return sourceData;
|
|
1744
|
-
}
|
|
1745
|
-
const includeParams = new Set();
|
|
1746
|
-
const excludeParams = new Set();
|
|
1747
|
-
this.options.copyParams.forEach(param => {
|
|
1748
|
-
const trimmedParam = param.trim();
|
|
1749
|
-
if (!trimmedParam) {
|
|
1750
|
-
return;
|
|
1751
|
-
}
|
|
1752
|
-
if (trimmedParam.startsWith('!')) {
|
|
1753
|
-
const excludedParam = trimmedParam.slice(1).trim();
|
|
1754
|
-
if (excludedParam) {
|
|
1755
|
-
excludeParams.add(excludedParam);
|
|
1756
|
-
}
|
|
1757
|
-
return;
|
|
1758
|
-
}
|
|
1759
|
-
includeParams.add(trimmedParam);
|
|
1760
|
-
});
|
|
1761
|
-
const filtered = {};
|
|
1762
|
-
const sourceKeys = includeParams.size > 0
|
|
1763
|
-
? Array.from(includeParams)
|
|
1764
|
-
: Object.keys(sourceData);
|
|
1765
|
-
sourceKeys.forEach(param => {
|
|
1766
|
-
if (!(param in sourceData)) {
|
|
1767
|
-
return;
|
|
1768
|
-
}
|
|
1769
|
-
if (excludeParams.has(param)) {
|
|
1770
|
-
return;
|
|
1771
|
-
}
|
|
1772
|
-
filtered[param] = sourceData[param];
|
|
1773
|
-
});
|
|
1774
|
-
return filtered;
|
|
1775
|
-
}
|
|
1776
|
-
/**
|
|
1777
|
-
* 値の増減を行います。
|
|
1778
|
-
*/
|
|
1779
|
-
adjust() {
|
|
1780
|
-
if (!this.options.adjustFragments ||
|
|
1781
|
-
this.options.adjustFragments.length === 0) {
|
|
1782
|
-
return Promise.resolve();
|
|
1783
|
-
}
|
|
1784
|
-
const adjustValue = this.options.adjustValue ?? 0;
|
|
1785
|
-
const promises = [];
|
|
1786
|
-
for (const fragment of this.options.adjustFragments) {
|
|
1787
|
-
let valueString = fragment.getValue();
|
|
1788
|
-
if (valueString === null ||
|
|
1789
|
-
valueString === undefined ||
|
|
1790
|
-
valueString === '') {
|
|
1791
|
-
valueString = '0';
|
|
1792
|
-
}
|
|
1793
|
-
let value = Number(valueString);
|
|
1794
|
-
if (isNaN(value)) {
|
|
1795
|
-
value = 0;
|
|
1796
|
-
}
|
|
1797
|
-
value += adjustValue;
|
|
1798
|
-
promises.push(fragment.setValue(String(value)));
|
|
1799
|
-
}
|
|
1800
|
-
return Promise.all(promises).then(() => undefined);
|
|
1801
|
-
}
|
|
1802
|
-
/**
|
|
1803
|
-
* 行フラグメントを取得します。
|
|
1804
|
-
*
|
|
1805
|
-
* @returns 行フラグメントまたはnull
|
|
1806
|
-
*/
|
|
1807
|
-
getRowFragment() {
|
|
1808
|
-
if (!this.options.targetFragment) {
|
|
1809
|
-
Log.error('Haori', 'Target fragment is not specified for row operation.');
|
|
1810
|
-
return null;
|
|
1811
|
-
}
|
|
1812
|
-
const rowFragment = this.options.targetFragment.closestByAttribute(`${Env.prefix}row`);
|
|
1813
|
-
if (!rowFragment) {
|
|
1814
|
-
Log.error('Haori', 'Row fragment not found.');
|
|
1815
|
-
return null;
|
|
1816
|
-
}
|
|
1817
|
-
return rowFragment;
|
|
1818
|
-
}
|
|
1819
|
-
/**
|
|
1820
|
-
* 行を追加します。
|
|
1821
|
-
*
|
|
1822
|
-
* @returns 処理結果のPromise
|
|
1823
|
-
*/
|
|
1824
|
-
addRow() {
|
|
1825
|
-
if (this.options.rowAdd !== true) {
|
|
1826
|
-
return Promise.resolve();
|
|
1827
|
-
}
|
|
1828
|
-
const rowFragment = this.getRowFragment();
|
|
1829
|
-
if (!rowFragment) {
|
|
1830
|
-
return Promise.reject(new Error('Row fragment not found.'));
|
|
1831
|
-
}
|
|
1832
|
-
const promises = [];
|
|
1833
|
-
const newFragment = rowFragment.clone();
|
|
1834
|
-
promises.push(rowFragment.getParent().insertAfter(newFragment, rowFragment));
|
|
1835
|
-
promises.push(Core.evaluateAll(newFragment));
|
|
1836
|
-
// 追加された行のフォーム要素をリセット
|
|
1837
|
-
promises.push(Form.reset(newFragment));
|
|
1838
|
-
return Promise.all(promises).then(() => undefined);
|
|
1839
|
-
}
|
|
1840
|
-
/**
|
|
1841
|
-
* 行を削除します。
|
|
1842
|
-
*
|
|
1843
|
-
* @returns 処理結果のPromise
|
|
1844
|
-
*/
|
|
1845
|
-
removeRow() {
|
|
1846
|
-
if (this.options.rowRemove !== true) {
|
|
1847
|
-
return Promise.resolve();
|
|
1848
|
-
}
|
|
1849
|
-
const rowFragment = this.getRowFragment();
|
|
1850
|
-
if (!rowFragment) {
|
|
1851
|
-
return Promise.reject(new Error('Row fragment not found.'));
|
|
1852
|
-
}
|
|
1853
|
-
// 1行だった場合は削除しない
|
|
1854
|
-
const parent = rowFragment.getParent();
|
|
1855
|
-
if (parent) {
|
|
1856
|
-
const siblings = parent.getChildElementFragments().filter(child => {
|
|
1857
|
-
// data-each-before と data-each-after を除外
|
|
1858
|
-
return (!child.hasAttribute(`${Env.prefix}each-before`) &&
|
|
1859
|
-
!child.hasAttribute(`${Env.prefix}each-after`));
|
|
1860
|
-
});
|
|
1861
|
-
if (siblings.length <= 1) {
|
|
1862
|
-
return Promise.resolve();
|
|
1863
|
-
}
|
|
1864
|
-
}
|
|
1865
|
-
return rowFragment.remove();
|
|
1866
|
-
}
|
|
1867
|
-
/**
|
|
1868
|
-
* 前の行へ移動します。
|
|
1869
|
-
*
|
|
1870
|
-
* @returns 処理結果のPromise
|
|
1871
|
-
*/
|
|
1872
|
-
movePrevRow() {
|
|
1873
|
-
if (this.options.rowMovePrev !== true) {
|
|
1874
|
-
return Promise.resolve();
|
|
1875
|
-
}
|
|
1876
|
-
const rowFragment = this.getRowFragment();
|
|
1877
|
-
if (!rowFragment) {
|
|
1878
|
-
return Promise.reject(new Error('Row fragment not found.'));
|
|
1879
|
-
}
|
|
1880
|
-
const prevFragment = rowFragment.getPrevious();
|
|
1881
|
-
if (!prevFragment) {
|
|
1882
|
-
return Promise.resolve();
|
|
1883
|
-
}
|
|
1884
|
-
const parent = rowFragment.getParent();
|
|
1885
|
-
if (!parent) {
|
|
1886
|
-
return Promise.resolve();
|
|
1887
|
-
}
|
|
1888
|
-
return parent.insertBefore(rowFragment, prevFragment);
|
|
1889
|
-
}
|
|
1890
|
-
/**
|
|
1891
|
-
* 次の行へ移動します。
|
|
1892
|
-
*
|
|
1893
|
-
* @returns 処理結果のPromise
|
|
1894
|
-
*/
|
|
1895
|
-
moveNextRow() {
|
|
1896
|
-
if (this.options.rowMoveNext !== true) {
|
|
1897
|
-
return Promise.resolve();
|
|
1898
|
-
}
|
|
1899
|
-
const rowFragment = this.getRowFragment();
|
|
1900
|
-
if (!rowFragment) {
|
|
1901
|
-
return Promise.reject(new Error('Row fragment not found.'));
|
|
1902
|
-
}
|
|
1903
|
-
const nextFragment = rowFragment.getNext();
|
|
1904
|
-
if (!nextFragment) {
|
|
1905
|
-
return Promise.resolve();
|
|
1906
|
-
}
|
|
1907
|
-
const parent = rowFragment.getParent();
|
|
1908
|
-
if (!parent) {
|
|
1909
|
-
return Promise.resolve();
|
|
1910
|
-
}
|
|
1911
|
-
return parent.insertAfter(rowFragment, nextFragment);
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
/** data 属性内のテンプレート式検出用正規表現 */
|
|
1915
|
-
Procedure.DATA_PLACEHOLDER_REGEX = /\{\{\{([\s\S]+?)\}\}\}|\{\{([\s\S]+?)\}\}/g;
|
|
1916
|
-
/** 属性全体が単一テンプレート式かを判定する正規表現 */
|
|
1917
|
-
Procedure.SINGLE_PLACEHOLDER_REGEX = /^(\{\{\{[\s\S]+?\}\}\}|\{\{[\s\S]+?\}\})$/;
|
|
1918
|
-
/** click 手続きの再入を防ぐ対象要素の集合 */
|
|
1919
|
-
Procedure.RUNNING_CLICK_TARGETS = new WeakSet();
|
|
1920
|
-
export default Procedure;
|
|
1921
|
-
//# sourceMappingURL=procedure.js.map
|