@mmlogic/components 0.3.5 → 0.3.6
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/dist/{collection/utils/cell-renderer.js → mosterdcomponents/cell-renderer-CbRwLOo8.js} +9 -3
- package/dist/mosterdcomponents/cell-renderer-CbRwLOo8.js.map +1 -0
- package/dist/{esm/index.js → mosterdcomponents/client-layout-D88nn5zf.js} +4 -1
- package/dist/mosterdcomponents/client-layout-D88nn5zf.js.map +1 -0
- package/dist/{collection/utils/format.js → mosterdcomponents/format-BAfsQfy1.js} +12 -7
- package/dist/mosterdcomponents/format-BAfsQfy1.js.map +1 -0
- package/dist/{collection/utils/i18n.js → mosterdcomponents/i18n-hoGGKbKU.js} +6 -1
- package/dist/mosterdcomponents/i18n-hoGGKbKU.js.map +1 -0
- package/dist/mosterdcomponents/index-B_tPFIvS.js +4585 -0
- package/dist/mosterdcomponents/index-B_tPFIvS.js.map +1 -0
- package/dist/mosterdcomponents/index-I5SuYv7a.js +4 -0
- package/dist/mosterdcomponents/index-I5SuYv7a.js.map +1 -0
- package/dist/mosterdcomponents/index.esm.js +5 -1
- package/dist/mosterdcomponents/index.esm.js.map +1 -0
- package/dist/mosterdcomponents/mosterdcomponents.css +180 -1
- package/dist/mosterdcomponents/mosterdcomponents.esm.js +50 -1
- package/dist/mosterdcomponents/mosterdcomponents.esm.js.map +1 -0
- package/dist/mosterdcomponents/mrd-boolean-field.entry.js +37 -0
- package/dist/mosterdcomponents/mrd-boolean-field.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-currency-field.entry.js +67 -0
- package/dist/mosterdcomponents/mrd-currency-field.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-date-field.entry.js +46 -0
- package/dist/mosterdcomponents/mrd-date-field.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-datetime-field.entry.js +78 -0
- package/dist/mosterdcomponents/mrd-datetime-field.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-email-field.entry.js +50 -0
- package/dist/mosterdcomponents/mrd-email-field.entry.js.map +1 -0
- package/dist/{collection/components/mrd-field/mrd-field.js → mosterdcomponents/mrd-field.entry.js} +28 -179
- package/dist/mosterdcomponents/mrd-field.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-file-field.entry.js +108 -0
- package/dist/mosterdcomponents/mrd-file-field.entry.js.map +1 -0
- package/dist/{collection/components/mrd-form/mrd-form.js → mosterdcomponents/mrd-form.entry.js} +31 -308
- package/dist/mosterdcomponents/mrd-form.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-hyperlink-field.entry.js +87 -0
- package/dist/mosterdcomponents/mrd-hyperlink-field.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-image-field.entry.js +122 -0
- package/dist/mosterdcomponents/mrd-image-field.entry.js.map +1 -0
- package/dist/{collection/components/mrd-layout-section/mrd-layout-section.js → mosterdcomponents/mrd-layout-section.entry.js} +31 -418
- package/dist/mosterdcomponents/mrd-layout-section.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-list-field.entry.js +107 -0
- package/dist/mosterdcomponents/mrd-list-field.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-longtext-field.entry.js +47 -0
- package/dist/mosterdcomponents/mrd-longtext-field.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-number-field.entry.js +87 -0
- package/dist/mosterdcomponents/mrd-number-field.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-relation-field.entry.js +267 -0
- package/dist/mosterdcomponents/mrd-relation-field.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-secret-field.entry.js +49 -0
- package/dist/mosterdcomponents/mrd-secret-field.entry.js.map +1 -0
- package/dist/{collection/components/mrd-table/mrd-table.js → mosterdcomponents/mrd-table.entry.js} +32 -394
- package/dist/mosterdcomponents/mrd-table.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-text-field.entry.js +47 -0
- package/dist/mosterdcomponents/mrd-text-field.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-textarea-field.entry.js +86 -0
- package/dist/mosterdcomponents/mrd-textarea-field.entry.js.map +1 -0
- package/dist/mosterdcomponents/mrd-time-field.entry.js +46 -0
- package/dist/mosterdcomponents/mrd-time-field.entry.js.map +1 -0
- package/dist/{esm/quill-CiuCgGz_.js → mosterdcomponents/quill-C9pgw_k-.js} +16282 -1397
- package/dist/mosterdcomponents/quill-C9pgw_k-.js.map +1 -0
- package/dist/{collection/utils/validation.js → mosterdcomponents/validation-ixb43cqU.js} +12 -5
- package/dist/mosterdcomponents/validation-ixb43cqU.js.map +1 -0
- package/package.json +1 -1
- package/dist/cjs/app-globals-V2Kpy_OQ.js +0 -5
- package/dist/cjs/index-BPj2cBXs.js +0 -1570
- package/dist/cjs/index.cjs.js +0 -66
- package/dist/cjs/loader.cjs.js +0 -13
- package/dist/cjs/mosterdcomponents.cjs.js +0 -25
- package/dist/cjs/mrd-boolean-field_20.cjs.entry.js +0 -3961
- package/dist/cjs/quill-DmFfnC1f.js +0 -16272
- package/dist/collection/collection-manifest.json +0 -32
- package/dist/collection/components/mrd-boolean-field/mrd-boolean-field.js +0 -199
- package/dist/collection/components/mrd-boolean-field/mrd-boolean-field.scss +0 -77
- package/dist/collection/components/mrd-currency-field/mrd-currency-field.js +0 -248
- package/dist/collection/components/mrd-currency-field/mrd-currency-field.scss +0 -100
- package/dist/collection/components/mrd-date-field/mrd-date-field.js +0 -206
- package/dist/collection/components/mrd-date-field/mrd-date-field.scss +0 -66
- package/dist/collection/components/mrd-datetime-field/mrd-datetime-field.js +0 -240
- package/dist/collection/components/mrd-datetime-field/mrd-datetime-field.scss +0 -66
- package/dist/collection/components/mrd-email-field/mrd-email-field.js +0 -230
- package/dist/collection/components/mrd-email-field/mrd-email-field.scss +0 -69
- package/dist/collection/components/mrd-field/mrd-field.scss +0 -118
- package/dist/collection/components/mrd-file-field/mrd-file-field.js +0 -341
- package/dist/collection/components/mrd-file-field/mrd-file-field.scss +0 -153
- package/dist/collection/components/mrd-form/mrd-form.scss +0 -148
- package/dist/collection/components/mrd-hyperlink-field/mrd-hyperlink-field.js +0 -291
- package/dist/collection/components/mrd-hyperlink-field/mrd-hyperlink-field.scss +0 -91
- package/dist/collection/components/mrd-image-field/mrd-image-field.js +0 -356
- package/dist/collection/components/mrd-image-field/mrd-image-field.scss +0 -190
- package/dist/collection/components/mrd-layout-section/mrd-layout-section.scss +0 -445
- package/dist/collection/components/mrd-list-field/mrd-list-field.js +0 -313
- package/dist/collection/components/mrd-list-field/mrd-list-field.scss +0 -109
- package/dist/collection/components/mrd-longtext-field/mrd-longtext-field.js +0 -227
- package/dist/collection/components/mrd-longtext-field/mrd-longtext-field.scss +0 -78
- package/dist/collection/components/mrd-number-field/mrd-number-field.js +0 -316
- package/dist/collection/components/mrd-number-field/mrd-number-field.scss +0 -77
- package/dist/collection/components/mrd-relation-field/mrd-relation-field.js +0 -707
- package/dist/collection/components/mrd-relation-field/mrd-relation-field.scss +0 -266
- package/dist/collection/components/mrd-secret-field/mrd-secret-field.js +0 -229
- package/dist/collection/components/mrd-secret-field/mrd-secret-field.scss +0 -73
- package/dist/collection/components/mrd-table/mrd-table.scss +0 -809
- package/dist/collection/components/mrd-text-field/mrd-text-field.js +0 -227
- package/dist/collection/components/mrd-text-field/mrd-text-field.scss +0 -69
- package/dist/collection/components/mrd-textarea-field/mrd-textarea-field.js +0 -267
- package/dist/collection/components/mrd-textarea-field/mrd-textarea-field.scss +0 -135
- package/dist/collection/components/mrd-time-field/mrd-time-field.js +0 -206
- package/dist/collection/components/mrd-time-field/mrd-time-field.scss +0 -66
- package/dist/collection/dev/api.js +0 -145
- package/dist/collection/dev/app.js +0 -890
- package/dist/collection/dev/auth.js +0 -156
- package/dist/collection/dev/example-data.js +0 -403
- package/dist/collection/dev/sprites.svg +0 -55
- package/dist/collection/index.js +0 -1
- package/dist/collection/types/client-layout.js +0 -64
- package/dist/collection/types/index.js +0 -1
- package/dist/components/client-layout.js +0 -1
- package/dist/components/format.js +0 -1
- package/dist/components/i18n.js +0 -1
- package/dist/components/index.js +0 -1
- package/dist/components/mrd-boolean-field.js +0 -1
- package/dist/components/mrd-boolean-field2.js +0 -1
- package/dist/components/mrd-currency-field.js +0 -1
- package/dist/components/mrd-currency-field2.js +0 -1
- package/dist/components/mrd-date-field.js +0 -1
- package/dist/components/mrd-date-field2.js +0 -1
- package/dist/components/mrd-datetime-field.js +0 -1
- package/dist/components/mrd-datetime-field2.js +0 -1
- package/dist/components/mrd-email-field.js +0 -1
- package/dist/components/mrd-email-field2.js +0 -1
- package/dist/components/mrd-field.js +0 -1
- package/dist/components/mrd-field2.js +0 -1
- package/dist/components/mrd-file-field.js +0 -1
- package/dist/components/mrd-file-field2.js +0 -1
- package/dist/components/mrd-form.js +0 -1
- package/dist/components/mrd-hyperlink-field.js +0 -1
- package/dist/components/mrd-hyperlink-field2.js +0 -1
- package/dist/components/mrd-image-field.js +0 -1
- package/dist/components/mrd-image-field2.js +0 -1
- package/dist/components/mrd-layout-section.js +0 -1
- package/dist/components/mrd-list-field.js +0 -1
- package/dist/components/mrd-list-field2.js +0 -1
- package/dist/components/mrd-longtext-field.js +0 -1
- package/dist/components/mrd-longtext-field2.js +0 -1
- package/dist/components/mrd-number-field.js +0 -1
- package/dist/components/mrd-number-field2.js +0 -1
- package/dist/components/mrd-relation-field.js +0 -1
- package/dist/components/mrd-relation-field2.js +0 -1
- package/dist/components/mrd-secret-field.js +0 -1
- package/dist/components/mrd-secret-field2.js +0 -1
- package/dist/components/mrd-table.js +0 -1
- package/dist/components/mrd-table2.js +0 -1
- package/dist/components/mrd-text-field.js +0 -1
- package/dist/components/mrd-text-field2.js +0 -1
- package/dist/components/mrd-textarea-field.js +0 -1
- package/dist/components/mrd-textarea-field2.js +0 -1
- package/dist/components/mrd-time-field.js +0 -1
- package/dist/components/mrd-time-field2.js +0 -1
- package/dist/components/quill.js +0 -1
- package/dist/components/validation.js +0 -1
- package/dist/esm/app-globals-DQuL1Twl.js +0 -3
- package/dist/esm/index-_tsCCkAi.js +0 -1561
- package/dist/esm/loader.js +0 -11
- package/dist/esm/mosterdcomponents.js +0 -21
- package/dist/esm/mrd-boolean-field_20.entry.js +0 -3940
- package/dist/index.cjs.js +0 -1
- package/dist/index.js +0 -1
- package/dist/mosterdcomponents/p-CiuCgGz_.js +0 -1
- package/dist/mosterdcomponents/p-DQuL1Twl.js +0 -1
- package/dist/mosterdcomponents/p-_tsCCkAi.js +0 -2
- package/dist/mosterdcomponents/p-e477187c.entry.js +0 -1
|
@@ -1,890 +0,0 @@
|
|
|
1
|
-
/* =====================================================================
|
|
2
|
-
APP STATE
|
|
3
|
-
===================================================================== */
|
|
4
|
-
|
|
5
|
-
let _selectedTenant = null;
|
|
6
|
-
let _selectedType = null; // { name, pluralName }
|
|
7
|
-
let _relationMeta = {}; // relatedClass / mostSignificantClass → { name, mostSignificantClass }
|
|
8
|
-
let _baseHref = null; // tenant base href: /data/{tenant}
|
|
9
|
-
let _locale = navigator.language || 'nl-NL';
|
|
10
|
-
let _dashboardData = null; // full dashboard response
|
|
11
|
-
let _dashboardRecord = null; // data-object voor object/form dashboards
|
|
12
|
-
let _activeLayoutIndex = 0;
|
|
13
|
-
let _dashboardType = 'class'; // 'class' | 'general' | 'navigation' | 'object' | 'form'
|
|
14
|
-
let _sectionGeneration = 0; // incremented on each renderSection(); prevents stale fetches
|
|
15
|
-
let _navHistory = []; // stack van { dashboardData, dashboardRecord, activeLayoutIndex }
|
|
16
|
-
let _meUser = null; // { id: href, label: name } — resolved once after login via /accounts/me
|
|
17
|
-
|
|
18
|
-
/* =====================================================================
|
|
19
|
-
UTILITIES
|
|
20
|
-
===================================================================== */
|
|
21
|
-
|
|
22
|
-
function escHtml(str) {
|
|
23
|
-
if (str == null) return '';
|
|
24
|
-
return String(str)
|
|
25
|
-
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
26
|
-
.replace(/"/g, '"').replace(/'/g, ''');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function formatJson(value) {
|
|
30
|
-
try {
|
|
31
|
-
const parsed = typeof value === 'string' ? JSON.parse(value) : value;
|
|
32
|
-
const json = JSON.stringify(parsed, null, 2);
|
|
33
|
-
return json.replace(
|
|
34
|
-
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
|
|
35
|
-
match => {
|
|
36
|
-
let style = 'color:#2aa198';
|
|
37
|
-
if (/^"/.test(match)) {
|
|
38
|
-
style = /:$/.test(match) ? 'color:#81a1c1' : 'color:#a3be8c';
|
|
39
|
-
} else if (/true|false/.test(match)) {
|
|
40
|
-
style = 'color:#ebcb8b';
|
|
41
|
-
} else if (/null/.test(match)) {
|
|
42
|
-
style = 'color:#bf616a';
|
|
43
|
-
}
|
|
44
|
-
return `<span style="${style}">${match}</span>`;
|
|
45
|
-
},
|
|
46
|
-
);
|
|
47
|
-
} catch {
|
|
48
|
-
return escHtml(String(value));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function httpStatusText(code) {
|
|
53
|
-
const map = {
|
|
54
|
-
200: 'OK', 201: 'Created', 204: 'No Content',
|
|
55
|
-
400: 'Bad Request', 401: 'Unauthorized', 403: 'Forbidden',
|
|
56
|
-
404: 'Not Found', 409: 'Conflict', 422: 'Unprocessable Entity',
|
|
57
|
-
500: 'Internal Server Error',
|
|
58
|
-
};
|
|
59
|
-
return map[code] || '';
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const EVENT_LOG_MAX = 200;
|
|
63
|
-
|
|
64
|
-
function logEvent(type, data) {
|
|
65
|
-
const log = document.getElementById('event-log');
|
|
66
|
-
|
|
67
|
-
const empty = log.querySelector('.event-log-empty');
|
|
68
|
-
if (empty) empty.remove();
|
|
69
|
-
|
|
70
|
-
// Trim oldest entries when the log grows too large
|
|
71
|
-
while (log.children.length >= EVENT_LOG_MAX) log.removeChild(log.firstChild);
|
|
72
|
-
|
|
73
|
-
const now = new Date();
|
|
74
|
-
const time = now.toTimeString().slice(0, 8) + '.' + String(now.getMilliseconds()).padStart(3, '0');
|
|
75
|
-
const json = data !== undefined ? JSON.stringify(data, null, 2) : null;
|
|
76
|
-
|
|
77
|
-
const entry = document.createElement('div');
|
|
78
|
-
entry.className = 'event-log-entry';
|
|
79
|
-
entry.dataset.type = type;
|
|
80
|
-
entry.innerHTML = `
|
|
81
|
-
<div class="event-log-entry-head">
|
|
82
|
-
<span class="event-log-time">${escHtml(time)}</span>
|
|
83
|
-
<span class="event-log-type">${escHtml(type)}</span>
|
|
84
|
-
${json ? '<span class="event-log-arrow">▶</span>' : ''}
|
|
85
|
-
</div>
|
|
86
|
-
${json ? `<pre class="event-log-detail">${escHtml(json)}</pre>` : ''}`;
|
|
87
|
-
|
|
88
|
-
entry.querySelector('.event-log-entry-head')?.addEventListener('click', () => {
|
|
89
|
-
if (json) entry.classList.toggle('open');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
log.appendChild(entry);
|
|
93
|
-
log.scrollTop = log.scrollHeight;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function clearEventLog() {
|
|
97
|
-
const log = document.getElementById('event-log');
|
|
98
|
-
log.innerHTML = '<span class="event-log-empty">Wacht op events…</span>';
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/* =====================================================================
|
|
102
|
-
TAB SWITCHING
|
|
103
|
-
===================================================================== */
|
|
104
|
-
|
|
105
|
-
function showTab(name) {
|
|
106
|
-
const names = ['embedded', 'field-types', 'detail-view', 'live-api'];
|
|
107
|
-
document.querySelectorAll('.tab-btn').forEach((btn, i) => {
|
|
108
|
-
btn.classList.toggle('active', names[i] === name);
|
|
109
|
-
});
|
|
110
|
-
names.forEach(n => {
|
|
111
|
-
document.getElementById('tab-' + n).classList.toggle('active', n === name);
|
|
112
|
-
});
|
|
113
|
-
if (name === 'detail-view') initDetailViewTab();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/* =====================================================================
|
|
117
|
-
DETAIL VIEW TAB
|
|
118
|
-
===================================================================== */
|
|
119
|
-
|
|
120
|
-
let _detailViewInitialized = false;
|
|
121
|
-
|
|
122
|
-
function initDetailViewTab() {
|
|
123
|
-
if (_detailViewInitialized) return;
|
|
124
|
-
_detailViewInitialized = true;
|
|
125
|
-
|
|
126
|
-
const table = document.getElementById('companies-table');
|
|
127
|
-
const meta = window.EXAMPLE_COMPANY_METADATA;
|
|
128
|
-
|
|
129
|
-
table.columns = window.EXAMPLE_COMPANY_COLUMNS;
|
|
130
|
-
table.totalElements = window.EXAMPLE_COMPANIES.length;
|
|
131
|
-
table.pageSize = 20;
|
|
132
|
-
table.locale = 'nl-NL';
|
|
133
|
-
|
|
134
|
-
table.addEventListener('mrdLoadPage', async (e) => {
|
|
135
|
-
await table.setPage(e.detail.page, window.EXAMPLE_COMPANIES);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
table.addEventListener('mrdRowClick', (e) => {
|
|
139
|
-
showCompanyDetail(e.detail);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
table.init().then(() => table.setPage(0, window.EXAMPLE_COMPANIES));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function showCompanyDetail(row) {
|
|
146
|
-
document.getElementById('detail-table-panel').style.display = 'none';
|
|
147
|
-
document.getElementById('detail-record-panel').style.display = 'block';
|
|
148
|
-
|
|
149
|
-
// Use the static example company (in a real app: fetch /data/:tenant/companies/:id)
|
|
150
|
-
const company = window.EXAMPLE_COMPANY;
|
|
151
|
-
const meta = window.EXAMPLE_COMPANY_METADATA;
|
|
152
|
-
const container = document.getElementById('detail-sections');
|
|
153
|
-
container.innerHTML = '';
|
|
154
|
-
|
|
155
|
-
meta.layouts.forEach((layout, idx) => {
|
|
156
|
-
const wrapper = document.createElement('div');
|
|
157
|
-
wrapper.style.marginBottom = '2rem';
|
|
158
|
-
|
|
159
|
-
if (layout.label) {
|
|
160
|
-
const heading = document.createElement('h3');
|
|
161
|
-
heading.style.cssText = 'font-size:1.1rem;font-weight:600;margin:0 0 .75rem;padding-bottom:.5rem;border-bottom:2px solid #e5e7eb';
|
|
162
|
-
heading.textContent = layout.label;
|
|
163
|
-
wrapper.appendChild(heading);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const section = document.createElement('mrd-layout-section');
|
|
167
|
-
section.items = layout.items;
|
|
168
|
-
section.data = company;
|
|
169
|
-
section.views = meta.views;
|
|
170
|
-
section.locale = 'nl-NL';
|
|
171
|
-
|
|
172
|
-
section.addEventListener('mrdNavigate', (e) => {
|
|
173
|
-
logEvent('mrdNavigate', e.detail);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
section.addEventListener('mrdLoadView', async (e) => {
|
|
177
|
-
const { name, viewConfig } = e.detail;
|
|
178
|
-
const mockRows = name === 'view0' ? window.EXAMPLE_APPOINTMENTS : window.EXAMPLE_SHARE_CLASSES;
|
|
179
|
-
await section.setViewPage(name, 0, mockRows, mockRows.length);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
section.addEventListener('mrdLoadViewPage', async (e) => {
|
|
183
|
-
// No more pages in the mock — just set the same rows again
|
|
184
|
-
const { name } = e.detail;
|
|
185
|
-
const mockRows = name === 'view0' ? window.EXAMPLE_APPOINTMENTS : window.EXAMPLE_SHARE_CLASSES;
|
|
186
|
-
await section.setViewPage(name, e.detail.page, mockRows);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
wrapper.appendChild(section);
|
|
190
|
-
container.appendChild(wrapper);
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function showCompanyList() {
|
|
195
|
-
document.getElementById('detail-record-panel').style.display = 'none';
|
|
196
|
-
document.getElementById('detail-table-panel').style.display = 'block';
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/* =====================================================================
|
|
200
|
-
TENANT SELECTION
|
|
201
|
-
===================================================================== */
|
|
202
|
-
|
|
203
|
-
async function loadTenants() {
|
|
204
|
-
document.getElementById('panel-controls').classList.remove('hidden');
|
|
205
|
-
const sel = document.getElementById('tenant-select-compact');
|
|
206
|
-
sel.innerHTML = '<option value="">⏳ laden...</option>';
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
const tenants = await apiFetchTenants(authGetToken());
|
|
210
|
-
if (!tenants || tenants.length === 0) {
|
|
211
|
-
sel.innerHTML = '<option value="">— geen tenants gevonden —</option>';
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
sel.innerHTML = '<option value="">— selecteer tenant —</option>' +
|
|
215
|
-
tenants.map(t => {
|
|
216
|
-
const code = t.tenantCode || t.code || t.id;
|
|
217
|
-
return `<option value="${escHtml(code)}">${escHtml(t.name || code)}</option>`;
|
|
218
|
-
}).join('');
|
|
219
|
-
|
|
220
|
-
const saved = localStorage.getItem('last_tenant');
|
|
221
|
-
if (saved) {
|
|
222
|
-
sel.value = saved;
|
|
223
|
-
onCompactTenantChange(saved);
|
|
224
|
-
}
|
|
225
|
-
} catch (err) {
|
|
226
|
-
sel.innerHTML = `<option value="">❌ ${escHtml(err.message)}</option>`;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function onCompactTenantChange(code) {
|
|
231
|
-
_selectedTenant = code;
|
|
232
|
-
_baseHref = `/data/${code}`;
|
|
233
|
-
localStorage.setItem('last_tenant', code);
|
|
234
|
-
if (_dashboardType === 'class' && code) loadCompactTypes(code);
|
|
235
|
-
if (code) apiFetchMe(authGetToken(), code).then(me => { _meUser = me; }).catch(() => {});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
async function loadCompactTypes(tenantCode) {
|
|
239
|
-
const sel = document.getElementById('type-select-compact');
|
|
240
|
-
sel.innerHTML = '<option value="">⏳ laden...</option>';
|
|
241
|
-
sel.disabled = true;
|
|
242
|
-
try {
|
|
243
|
-
const types = await apiFetchTypes(authGetToken(), tenantCode);
|
|
244
|
-
if (!types || types.length === 0) {
|
|
245
|
-
sel.innerHTML = '<option value="">— geen types —</option>';
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
sel.innerHTML = '<option value="">— selecteer type —</option>' +
|
|
249
|
-
types.map(t => {
|
|
250
|
-
const plural = t.pluralName || t.name;
|
|
251
|
-
return `<option value="${escHtml(plural)}">${escHtml(t.name)}</option>`;
|
|
252
|
-
}).join('');
|
|
253
|
-
sel.disabled = false;
|
|
254
|
-
|
|
255
|
-
const saved = localStorage.getItem('last_type');
|
|
256
|
-
if (saved && sel.querySelector(`option[value="${saved}"]`)) {
|
|
257
|
-
sel.value = saved;
|
|
258
|
-
}
|
|
259
|
-
} catch (err) {
|
|
260
|
-
sel.innerHTML = `<option value="">❌ ${escHtml(err.message)}</option>`;
|
|
261
|
-
sel.disabled = false;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function onDashboardTypeChange(type) {
|
|
266
|
-
_dashboardType = type;
|
|
267
|
-
const needsClass = type === 'class' || type === 'create';
|
|
268
|
-
const needsObject = type === 'object' || type === 'form';
|
|
269
|
-
document.getElementById('controls-class-group').classList.toggle('hidden', !needsClass);
|
|
270
|
-
document.getElementById('controls-object-group').classList.toggle('hidden', !needsObject);
|
|
271
|
-
if (needsClass && _selectedTenant) loadCompactTypes(_selectedTenant);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/* =====================================================================
|
|
275
|
-
DASHBOARD — laad + render layout sections
|
|
276
|
-
===================================================================== */
|
|
277
|
-
|
|
278
|
-
async function loadDashboard() {
|
|
279
|
-
const name = document.getElementById('dashboard-name-input').value.trim() || undefined;
|
|
280
|
-
const token = authGetToken();
|
|
281
|
-
|
|
282
|
-
document.getElementById('sections-container').innerHTML =
|
|
283
|
-
'<span class="spinner"></span> Dashboard laden...';
|
|
284
|
-
document.getElementById('panel-sections').classList.remove('hidden');
|
|
285
|
-
document.getElementById('panel-response').innerHTML = '';
|
|
286
|
-
|
|
287
|
-
try {
|
|
288
|
-
let dashboard;
|
|
289
|
-
let recordData = null;
|
|
290
|
-
|
|
291
|
-
if (_dashboardType === 'create') {
|
|
292
|
-
const pluralName = document.getElementById('type-select-compact').value;
|
|
293
|
-
if (!pluralName) throw new Error('Selecteer een type');
|
|
294
|
-
const opt = document.getElementById('type-select-compact').selectedOptions[0];
|
|
295
|
-
_selectedType = { name: opt.text, pluralName };
|
|
296
|
-
localStorage.setItem('last_type', pluralName);
|
|
297
|
-
await openCreateForm(pluralName, false);
|
|
298
|
-
return;
|
|
299
|
-
|
|
300
|
-
} else if (_dashboardType === 'class') {
|
|
301
|
-
const pluralName = document.getElementById('type-select-compact').value;
|
|
302
|
-
if (!pluralName) throw new Error('Selecteer een type');
|
|
303
|
-
const opt = document.getElementById('type-select-compact').selectedOptions[0];
|
|
304
|
-
_selectedType = { name: opt.text, pluralName };
|
|
305
|
-
localStorage.setItem('last_type', pluralName);
|
|
306
|
-
dashboard = await apiFetchClassDashboard(token, _selectedTenant, pluralName, name);
|
|
307
|
-
|
|
308
|
-
} else if (_dashboardType === 'general') {
|
|
309
|
-
dashboard = await apiFetchGeneralDashboard(token, _selectedTenant, name);
|
|
310
|
-
|
|
311
|
-
} else if (_dashboardType === 'navigation') {
|
|
312
|
-
dashboard = await apiFetchNavigationPane(token, _selectedTenant, name);
|
|
313
|
-
|
|
314
|
-
} else {
|
|
315
|
-
// 'object' of 'form': fetch record → gebruik _links.metadata.href of _links.form.href
|
|
316
|
-
const objectHref = document.getElementById('object-href-input').value.trim();
|
|
317
|
-
if (!objectHref) throw new Error('Vul een Object URL in');
|
|
318
|
-
const recResp = await apiRequest('GET', objectHref, token);
|
|
319
|
-
if (!recResp.ok) throw new Error(`${recResp.status}: kon object niet ophalen`);
|
|
320
|
-
recordData = recResp.body;
|
|
321
|
-
|
|
322
|
-
const linkKey = _dashboardType === 'form' ? 'form' : 'metadata';
|
|
323
|
-
const dashHref = recordData._links?.[linkKey]?.href;
|
|
324
|
-
if (!dashHref) throw new Error(`Geen _links.${linkKey} gevonden in object`);
|
|
325
|
-
|
|
326
|
-
const dashResp = await apiRequest('GET', dashHref, token);
|
|
327
|
-
if (!dashResp.ok) throw new Error(`${dashResp.status}: dashboard ophalen mislukt`);
|
|
328
|
-
dashboard = dashResp.body;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
_dashboardData = dashboard;
|
|
332
|
-
_dashboardRecord = recordData;
|
|
333
|
-
_activeLayoutIndex = 0;
|
|
334
|
-
_navHistory = [];
|
|
335
|
-
|
|
336
|
-
renderSectionTabs(dashboard.layouts ?? []);
|
|
337
|
-
renderSection(0);
|
|
338
|
-
updateBackButton();
|
|
339
|
-
} catch (err) {
|
|
340
|
-
document.getElementById('sections-container').innerHTML =
|
|
341
|
-
`<span style="color:var(--mrd-color-danger)">❌ ${escHtml(err.message)}</span>`;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function renderSectionTabs(layouts) {
|
|
346
|
-
const bar = document.getElementById('sections-tab-bar');
|
|
347
|
-
bar.innerHTML = '';
|
|
348
|
-
if (layouts.length <= 1) return;
|
|
349
|
-
|
|
350
|
-
layouts.forEach((layout, i) => {
|
|
351
|
-
const label = layout.label ?? layout.type ?? `Sectie ${i + 1}`;
|
|
352
|
-
const btn = document.createElement('button');
|
|
353
|
-
btn.className = 'section-tab-btn' + (i === 0 ? ' active' : '');
|
|
354
|
-
btn.textContent = label;
|
|
355
|
-
btn.onclick = () => renderSection(i);
|
|
356
|
-
bar.appendChild(btn);
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
async function renderSection(index) {
|
|
361
|
-
_activeLayoutIndex = index;
|
|
362
|
-
const generation = ++_sectionGeneration; // snapshot for stale-fetch detection
|
|
363
|
-
|
|
364
|
-
document.querySelectorAll('.section-tab-btn').forEach((btn, i) =>
|
|
365
|
-
btn.classList.toggle('active', i === index));
|
|
366
|
-
|
|
367
|
-
const layout = _dashboardData.layouts[index];
|
|
368
|
-
document.getElementById('json-viewer').innerHTML = formatJson(layout);
|
|
369
|
-
|
|
370
|
-
const container = document.getElementById('sections-container');
|
|
371
|
-
container.innerHTML = '';
|
|
372
|
-
|
|
373
|
-
if (_dashboardType === 'form') {
|
|
374
|
-
await renderForm(layout, _dashboardRecord, _dashboardRecord?._links?.self?.href ?? null, container);
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const section = document.createElement('mrd-layout-section');
|
|
379
|
-
section.items = layout.items;
|
|
380
|
-
section.data = _dashboardRecord ?? { _links: _dashboardData._links ?? {} };
|
|
381
|
-
section.locale = _locale;
|
|
382
|
-
|
|
383
|
-
section.addEventListener('mrdLoadViewPage', async (e) => {
|
|
384
|
-
if (generation !== _sectionGeneration) return;
|
|
385
|
-
const { name, page, path, qs } = e.detail;
|
|
386
|
-
logEvent('mrdLoadViewPage', e.detail);
|
|
387
|
-
try {
|
|
388
|
-
const url = `${_baseHref}${path}${qs ? '?' + qs : ''}`;
|
|
389
|
-
const result = await apiFetchPage(authGetToken(), url, page);
|
|
390
|
-
const rows = Object.values(result._embedded ?? {})[0] ?? [];
|
|
391
|
-
const total = result.page?.totalElements;
|
|
392
|
-
await section.setViewPage(name, page, rows, total);
|
|
393
|
-
} catch (err) {
|
|
394
|
-
console.error('[mrdLoadViewPage] mislukt', name, err);
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
section.addEventListener('mrdLoadViewAggregations', async (e) => {
|
|
399
|
-
if (generation !== _sectionGeneration) return;
|
|
400
|
-
const { name, path, aggQs } = e.detail;
|
|
401
|
-
logEvent('mrdLoadViewAggregations', e.detail);
|
|
402
|
-
try {
|
|
403
|
-
const aggUrl = `${_baseHref}${path}/aggregations`;
|
|
404
|
-
const result = await apiRequest('GET', aggQs ? `${aggUrl}?${aggQs}` : aggUrl, authGetToken());
|
|
405
|
-
if (result.ok) await section.setViewAggregations(name, result.body);
|
|
406
|
-
} catch (err) {
|
|
407
|
-
console.error('[mrdLoadViewAggregations] mislukt', name, err);
|
|
408
|
-
}
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
section.addEventListener('mrdViewAction', async (e) => {
|
|
412
|
-
logEvent('mrdViewAction', e.detail);
|
|
413
|
-
const { action, dataClass } = e.detail;
|
|
414
|
-
if (action === 'create' && dataClass) {
|
|
415
|
-
await openCreateForm(dataClass, true);
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
section.addEventListener('mrdNavigate', async (e) => {
|
|
420
|
-
if (generation !== _sectionGeneration) return;
|
|
421
|
-
await handleNavigate(e.detail);
|
|
422
|
-
});
|
|
423
|
-
section.addEventListener('mrdDownload', (e) => logEvent('mrdDownload', e.detail));
|
|
424
|
-
section.addEventListener('mrdSearch', (e) => logEvent('mrdSearch', e.detail));
|
|
425
|
-
|
|
426
|
-
container.appendChild(section);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
function toggleJsonViewer() {
|
|
431
|
-
const viewer = document.getElementById('json-viewer');
|
|
432
|
-
const btn = document.getElementById('btn-json-toggle');
|
|
433
|
-
const open = !viewer.classList.toggle('hidden');
|
|
434
|
-
btn.classList.toggle('active', open);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function updateBackButton() {
|
|
438
|
-
const btn = document.getElementById('btn-nav-back');
|
|
439
|
-
if (btn) btn.classList.toggle('hidden', _navHistory.length === 0);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function syncDashboardTypeUI(type, objectHref) {
|
|
443
|
-
_dashboardType = type;
|
|
444
|
-
document.getElementById('dashboard-type-select').value = type;
|
|
445
|
-
const needsClass = type === 'class' || type === 'create';
|
|
446
|
-
const needsObject = type === 'object' || type === 'form';
|
|
447
|
-
document.getElementById('controls-class-group').classList.toggle('hidden', !needsClass);
|
|
448
|
-
document.getElementById('controls-object-group').classList.toggle('hidden', !needsObject);
|
|
449
|
-
if (needsObject && objectHref) {
|
|
450
|
-
document.getElementById('object-href-input').value = objectHref;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function navigateBack() {
|
|
455
|
-
if (_navHistory.length === 0) return;
|
|
456
|
-
const prev = _navHistory.pop();
|
|
457
|
-
_dashboardData = prev.dashboardData;
|
|
458
|
-
_dashboardRecord = prev.dashboardRecord;
|
|
459
|
-
_activeLayoutIndex = prev.activeLayoutIndex;
|
|
460
|
-
syncDashboardTypeUI(prev.dashboardType ?? _dashboardType, prev.objectHref ?? '');
|
|
461
|
-
renderSectionTabs(_dashboardData.layouts ?? []);
|
|
462
|
-
renderSection(_activeLayoutIndex);
|
|
463
|
-
updateBackButton();
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function pushHistory() {
|
|
467
|
-
_navHistory.push({
|
|
468
|
-
dashboardData: _dashboardData,
|
|
469
|
-
dashboardRecord: _dashboardRecord,
|
|
470
|
-
activeLayoutIndex: _activeLayoutIndex,
|
|
471
|
-
dashboardType: _dashboardType,
|
|
472
|
-
objectHref: document.getElementById('object-href-input').value,
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
async function navigateToObjectDashboard(selfHref) {
|
|
477
|
-
if (!selfHref) return;
|
|
478
|
-
|
|
479
|
-
document.getElementById('sections-container').innerHTML =
|
|
480
|
-
'<span class="spinner"></span> Object laden...';
|
|
481
|
-
document.getElementById('panel-sections').classList.remove('hidden');
|
|
482
|
-
|
|
483
|
-
try {
|
|
484
|
-
const recResp = await apiRequest('GET', selfHref, authGetToken());
|
|
485
|
-
if (!recResp.ok) throw new Error(`${recResp.status}: object ophalen mislukt`);
|
|
486
|
-
const record = recResp.body;
|
|
487
|
-
|
|
488
|
-
const dashHref = record._links?.metadata?.href;
|
|
489
|
-
if (!dashHref) throw new Error('Geen _links.metadata gevonden in object');
|
|
490
|
-
|
|
491
|
-
const dashResp = await apiRequest('GET', dashHref, authGetToken());
|
|
492
|
-
if (!dashResp.ok) throw new Error(`${dashResp.status}: dashboard ophalen mislukt`);
|
|
493
|
-
const dashboard = dashResp.body;
|
|
494
|
-
|
|
495
|
-
pushHistory();
|
|
496
|
-
_dashboardData = dashboard;
|
|
497
|
-
_dashboardRecord = record;
|
|
498
|
-
_activeLayoutIndex = 0;
|
|
499
|
-
|
|
500
|
-
const displayHref = selfHref.startsWith('http') ? selfHref : API_BASE + selfHref;
|
|
501
|
-
syncDashboardTypeUI('object', displayHref);
|
|
502
|
-
renderSectionTabs(dashboard.layouts ?? []);
|
|
503
|
-
renderSection(0);
|
|
504
|
-
updateBackButton();
|
|
505
|
-
} catch (err) {
|
|
506
|
-
document.getElementById('sections-container').innerHTML =
|
|
507
|
-
`<span style="color:var(--mrd-color-danger)">❌ ${escHtml(err.message)}</span>`;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
async function navigateToClassDashboard(pluralName) {
|
|
512
|
-
if (!_selectedTenant || !pluralName) return;
|
|
513
|
-
|
|
514
|
-
document.getElementById('sections-container').innerHTML =
|
|
515
|
-
'<span class="spinner"></span> Dashboard laden...';
|
|
516
|
-
document.getElementById('panel-sections').classList.remove('hidden');
|
|
517
|
-
|
|
518
|
-
try {
|
|
519
|
-
const dashboard = await apiFetchClassDashboard(authGetToken(), _selectedTenant, pluralName);
|
|
520
|
-
|
|
521
|
-
pushHistory();
|
|
522
|
-
_dashboardData = dashboard;
|
|
523
|
-
_dashboardRecord = null;
|
|
524
|
-
_activeLayoutIndex = 0;
|
|
525
|
-
|
|
526
|
-
syncDashboardTypeUI('class', '');
|
|
527
|
-
const sel = document.getElementById('type-select-compact');
|
|
528
|
-
if (sel.querySelector(`option[value="${pluralName}"]`)) sel.value = pluralName;
|
|
529
|
-
|
|
530
|
-
renderSectionTabs(dashboard.layouts ?? []);
|
|
531
|
-
renderSection(0);
|
|
532
|
-
updateBackButton();
|
|
533
|
-
} catch (err) {
|
|
534
|
-
document.getElementById('sections-container').innerHTML =
|
|
535
|
-
`<span style="color:var(--mrd-color-danger)">❌ ${escHtml(err.message)}</span>`;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
async function handleNavigate(detail) {
|
|
540
|
-
const { href, label, navigate } = detail;
|
|
541
|
-
if (href) {
|
|
542
|
-
logEvent('mrdNavigate', { href, label });
|
|
543
|
-
await navigateToObjectDashboard(href);
|
|
544
|
-
} else if (navigate?.dataClass) {
|
|
545
|
-
logEvent('mrdNavigate', { dataClass: navigate.dataClass, navigationType: navigate.navigationType, label });
|
|
546
|
-
await navigateToClassDashboard(navigate.dataClass);
|
|
547
|
-
} else {
|
|
548
|
-
logEvent('mrdNavigate', detail);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/* =====================================================================
|
|
553
|
-
FORM — LOAD & RENDER
|
|
554
|
-
===================================================================== */
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* Load an existing record by clicking a table row.
|
|
558
|
-
* Fetches the record (GET _links.self.href) and the form layout in parallel,
|
|
559
|
-
* then opens the form pre-filled and in PATCH mode.
|
|
560
|
-
*/
|
|
561
|
-
async function loadRecord(row) {
|
|
562
|
-
const selfHref = row?._links?.self?.href;
|
|
563
|
-
if (!selfHref) { console.warn('[loadRecord] geen _links.self.href in rij', row); return; }
|
|
564
|
-
if (!_selectedType) return;
|
|
565
|
-
|
|
566
|
-
const formPanel = document.getElementById('panel-form');
|
|
567
|
-
formPanel.classList.remove('hidden');
|
|
568
|
-
formPanel.innerHTML = '<span class="spinner"></span> Record laden...';
|
|
569
|
-
document.getElementById('panel-response').innerHTML = '';
|
|
570
|
-
|
|
571
|
-
try {
|
|
572
|
-
const recordResp = await apiRequest('GET', selfHref, authGetToken());
|
|
573
|
-
if (!recordResp.ok) throw new Error(`${recordResp.status}: kon record niet ophalen`);
|
|
574
|
-
|
|
575
|
-
const formHref = recordResp.body?._links?.form?.href ?? null;
|
|
576
|
-
const layout = await apiFetchForm(authGetToken(), _selectedTenant, _selectedType.pluralName, formHref);
|
|
577
|
-
await renderForm(layout, recordResp.body, selfHref);
|
|
578
|
-
} catch (err) {
|
|
579
|
-
formPanel.innerHTML = `<span style="color:var(--mrd-color-danger)">❌ ${escHtml(err.message)}</span>`;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
async function loadForm() {
|
|
584
|
-
const pluralName = _selectedType?.pluralName
|
|
585
|
-
|| document.getElementById('type-select-compact').value;
|
|
586
|
-
await openCreateForm(pluralName);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
async function openCreateForm(pluralName, pushNav = false) {
|
|
590
|
-
if (!pluralName || !_selectedTenant) return;
|
|
591
|
-
|
|
592
|
-
if (pushNav) pushHistory();
|
|
593
|
-
|
|
594
|
-
syncDashboardTypeUI('create', '');
|
|
595
|
-
const sel = document.getElementById('type-select-compact');
|
|
596
|
-
if (sel.querySelector(`option[value="${pluralName}"]`)) sel.value = pluralName;
|
|
597
|
-
|
|
598
|
-
document.getElementById('sections-tab-bar').innerHTML = '';
|
|
599
|
-
document.getElementById('panel-form').classList.add('hidden');
|
|
600
|
-
document.getElementById('panel-form').innerHTML = '';
|
|
601
|
-
document.getElementById('panel-response').innerHTML = '';
|
|
602
|
-
document.getElementById('panel-sections').classList.remove('hidden');
|
|
603
|
-
updateBackButton();
|
|
604
|
-
|
|
605
|
-
const container = document.getElementById('sections-container');
|
|
606
|
-
container.innerHTML = '<span class="spinner"></span> Formulier laden...';
|
|
607
|
-
|
|
608
|
-
try {
|
|
609
|
-
const layout = await apiFetchForm(authGetToken(), _selectedTenant, pluralName);
|
|
610
|
-
await renderForm(layout, null, null, container);
|
|
611
|
-
} catch (err) {
|
|
612
|
-
container.innerHTML = `<span style="color:var(--mrd-color-danger)">❌ ${escHtml(err.message)}</span>`;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
/**
|
|
617
|
-
* Mount mrd-form and wire up all event handlers.
|
|
618
|
-
*
|
|
619
|
-
* @param layout - mrd-form layout descriptor
|
|
620
|
-
* @param record - existing record for edit mode (null = new record)
|
|
621
|
-
* @param selfHref - absolute URL for PATCH; null = POST (new record)
|
|
622
|
-
*/
|
|
623
|
-
async function renderForm(layout, record = null, selfHref = null, containerEl = null) {
|
|
624
|
-
// Build relation metadata map keyed by both relatedClass and mostSignificantClass
|
|
625
|
-
// so lookups work regardless of which value arrives in events (mrdSearch uses mostSignificantClass).
|
|
626
|
-
_relationMeta = {};
|
|
627
|
-
function collectRelations(items) {
|
|
628
|
-
if (!Array.isArray(items)) return;
|
|
629
|
-
items.forEach(item => {
|
|
630
|
-
if (item.type === 'RELATION' && item.relatedClass) {
|
|
631
|
-
const meta = {
|
|
632
|
-
name: item.name,
|
|
633
|
-
mostSignificantClass: item.mostSignificantClass,
|
|
634
|
-
};
|
|
635
|
-
_relationMeta[item.relatedClass] = meta;
|
|
636
|
-
if (item.mostSignificantClass) {
|
|
637
|
-
_relationMeta[item.mostSignificantClass] = meta;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
if (Array.isArray(item.items)) collectRelations(item.items);
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
collectRelations(layout.items);
|
|
644
|
-
|
|
645
|
-
const relationFieldNames = new Set(Object.values(_relationMeta).map(m => m.name));
|
|
646
|
-
|
|
647
|
-
// Build initial form values from the fetched record (edit mode).
|
|
648
|
-
// Flat fields and _links are merged: the component normalises raw API link formats.
|
|
649
|
-
const { _links, _embedded, ...fields } = record ?? {};
|
|
650
|
-
const initialValues = record ? { ...fields, ...(_links ?? {}) } : {};
|
|
651
|
-
|
|
652
|
-
const formPanel = containerEl ?? document.getElementById('panel-form');
|
|
653
|
-
formPanel.innerHTML = `<mrd-form id="live-form" locale="${escHtml(_locale)}"></mrd-form>`;
|
|
654
|
-
|
|
655
|
-
const form = formPanel.querySelector('#live-form') ?? document.getElementById('live-form');
|
|
656
|
-
// Wait for Stencil to fully initialize this specific instance before setting props.
|
|
657
|
-
// componentOnReady() resolves after componentDidLoad — more reliable than customElements.whenDefined.
|
|
658
|
-
if (typeof form.componentOnReady === 'function') {
|
|
659
|
-
await form.componentOnReady();
|
|
660
|
-
} else {
|
|
661
|
-
await customElements.whenDefined('mrd-form');
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
form.layout = layout;
|
|
665
|
-
if (_meUser) form.me = _meUser;
|
|
666
|
-
form.values = record ? initialValues : {};
|
|
667
|
-
|
|
668
|
-
// Upload files immediately on selection; write binary URI back via setFieldValue
|
|
669
|
-
form.addEventListener('mrdChange', async (e) => {
|
|
670
|
-
const { name, value } = e.detail;
|
|
671
|
-
if (!(value instanceof File)) return;
|
|
672
|
-
try {
|
|
673
|
-
const uri = await apiUploadFile(authGetToken(), _selectedTenant, value);
|
|
674
|
-
await form.setFieldValue(name, uri);
|
|
675
|
-
} catch (err) {
|
|
676
|
-
console.error(`[upload] mislukt voor veld "${name}":`, err);
|
|
677
|
-
}
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
form.addEventListener('mrdSubmit', async (e) => {
|
|
681
|
-
document.getElementById('panel-response').innerHTML =
|
|
682
|
-
'<div class="response-card" style="background:var(--mrd-color-neutral-100);border-color:var(--mrd-color-neutral-300);max-width:860px"><span class="spinner"></span> Bezig met indienen...</div>';
|
|
683
|
-
|
|
684
|
-
// Transform relation values: { id: href, label } → href string
|
|
685
|
-
const transformValues = (source) => {
|
|
686
|
-
const out = {};
|
|
687
|
-
for (const [key, val] of Object.entries(source)) {
|
|
688
|
-
if (relationFieldNames.has(key)) {
|
|
689
|
-
if (Array.isArray(val)) {
|
|
690
|
-
out[key] = val.map(v => (v && typeof v === 'object' ? v.id : v));
|
|
691
|
-
} else if (val && typeof val === 'object' && val.id) {
|
|
692
|
-
out[key] = val.id;
|
|
693
|
-
} else {
|
|
694
|
-
out[key] = val;
|
|
695
|
-
}
|
|
696
|
-
} else {
|
|
697
|
-
out[key] = val;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
return out;
|
|
701
|
-
};
|
|
702
|
-
|
|
703
|
-
if (selfHref) {
|
|
704
|
-
// PATCH mode: send only changed fields
|
|
705
|
-
const submitted = transformValues(e.detail);
|
|
706
|
-
const original = transformValues(initialValues);
|
|
707
|
-
const patch = {};
|
|
708
|
-
for (const [key, val] of Object.entries(submitted)) {
|
|
709
|
-
if (JSON.stringify(val) !== JSON.stringify(original[key])) {
|
|
710
|
-
patch[key] = val;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
if (Object.keys(patch).length === 0) {
|
|
714
|
-
renderResponse(200, 'Geen wijzigingen.');
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
717
|
-
const result = await apiRequest('PATCH', selfHref, authGetToken(), patch);
|
|
718
|
-
renderResponse(result.status, result.body);
|
|
719
|
-
} else {
|
|
720
|
-
// POST mode: new record
|
|
721
|
-
const values = transformValues(e.detail);
|
|
722
|
-
const result = await apiSubmitForm(authGetToken(), _selectedTenant, _selectedType.pluralName, values);
|
|
723
|
-
renderResponse(result.status, result.body);
|
|
724
|
-
}
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
form.addEventListener('mrdSearch', async (e) => {
|
|
728
|
-
logEvent('mrdSearch (live)', e.detail);
|
|
729
|
-
const { name, query, relatedClass } = e.detail;
|
|
730
|
-
if (!query || query.length < 2) return;
|
|
731
|
-
|
|
732
|
-
// relatedClass here is actually mostSignificantClass (the URL segment).
|
|
733
|
-
// Use name to find the exact field, avoiding collisions when multiple
|
|
734
|
-
// fields share the same mostSignificantClass (e.g. 'companies').
|
|
735
|
-
try {
|
|
736
|
-
const results = await apiSearchRelation(authGetToken(), _selectedTenant, relatedClass, query);
|
|
737
|
-
const host = Array.from(form.querySelectorAll('mrd-relation-field'))
|
|
738
|
-
.find(el => el.name === name);
|
|
739
|
-
if (host && typeof host.setSearchResults === 'function') {
|
|
740
|
-
host.setSearchResults(results);
|
|
741
|
-
}
|
|
742
|
-
} catch (err) {
|
|
743
|
-
console.error('[mrdSearch] relation search failed:', err);
|
|
744
|
-
}
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
form.addEventListener('mrdFetchAll', async (e) => {
|
|
748
|
-
logEvent('mrdFetchAll (live)', e.detail);
|
|
749
|
-
const { name, mostSignificantClass, filter, filterValue } = e.detail;
|
|
750
|
-
if (!mostSignificantClass) return;
|
|
751
|
-
|
|
752
|
-
const host = Array.from(document.querySelectorAll('mrd-relation-field'))
|
|
753
|
-
.find(el => el.name === name);
|
|
754
|
-
if (!host || typeof host.setAllRecords !== 'function') return;
|
|
755
|
-
|
|
756
|
-
// If a filter is required but no value is set yet, clear the dropdown
|
|
757
|
-
if (filter && !filterValue) {
|
|
758
|
-
host.setAllRecords([]);
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
try {
|
|
763
|
-
// Build URL: /data/{tenant}/{mostSignificantClass}?{filter}={filterValue}&page=0
|
|
764
|
-
let baseHref = `/data/${_selectedTenant}/${mostSignificantClass}`;
|
|
765
|
-
if (filter && filterValue) baseHref += `?${encodeURIComponent(filter)}=${encodeURIComponent(filterValue)}`;
|
|
766
|
-
const result = await apiFetchPage(authGetToken(), baseHref, 0);
|
|
767
|
-
const embedded = result._embedded ?? {};
|
|
768
|
-
const records = Object.values(embedded)[0] ?? [];
|
|
769
|
-
host.setAllRecords(records.map(r => ({ id: r._links?.self?.href ?? r.id, label: r.name })));
|
|
770
|
-
} catch (err) {
|
|
771
|
-
console.error('[mrdFetchAll] failed:', err);
|
|
772
|
-
}
|
|
773
|
-
});
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
/* =====================================================================
|
|
777
|
-
RESPONSE RENDERING
|
|
778
|
-
===================================================================== */
|
|
779
|
-
|
|
780
|
-
function renderResponse(status, body) {
|
|
781
|
-
const isOk = status >= 200 && status < 300;
|
|
782
|
-
const cls = isOk ? 'success' : 'error';
|
|
783
|
-
const icon = isOk ? '✅' : '❌';
|
|
784
|
-
const label = httpStatusText(status);
|
|
785
|
-
|
|
786
|
-
let inner = '';
|
|
787
|
-
|
|
788
|
-
if (!isOk && body && typeof body === 'object') {
|
|
789
|
-
const msg = body.message || body.error || '';
|
|
790
|
-
const trace = body.trace || body.stackTrace || '';
|
|
791
|
-
inner = `
|
|
792
|
-
<h4>${icon} ${status} ${escHtml(label)}</h4>
|
|
793
|
-
${msg ? `<p style="margin:.25rem 0;font-size:.875rem">${escHtml(msg)}</p>` : ''}
|
|
794
|
-
${trace ? `<details><summary>trace</summary><pre>${escHtml(trace)}</pre></details>` : ''}
|
|
795
|
-
${!msg && !trace ? `<pre>${escHtml(JSON.stringify(body, null, 2))}</pre>` : ''}`;
|
|
796
|
-
} else {
|
|
797
|
-
const text = typeof body === 'string' ? body : JSON.stringify(body, null, 2);
|
|
798
|
-
inner = `
|
|
799
|
-
<h4>${icon} ${status} ${escHtml(label)}</h4>
|
|
800
|
-
${text ? `<pre>${escHtml(text)}</pre>` : ''}`;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
document.getElementById('panel-response').innerHTML =
|
|
804
|
-
`<div class="response-card ${cls}">${inner}</div>`;
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
/* =====================================================================
|
|
808
|
-
EMBEDDED FORM (demo tab)
|
|
809
|
-
===================================================================== */
|
|
810
|
-
|
|
811
|
-
customElements.whenDefined('mrd-form').then(() => {
|
|
812
|
-
const form = document.getElementById('demo-form');
|
|
813
|
-
form.layout = window.EXAMPLE_LAYOUT;
|
|
814
|
-
form.values = window.EXAMPLE_VALUES;
|
|
815
|
-
|
|
816
|
-
form.addEventListener('mrdSubmit', (e) => { console.log('[mrdSubmit]', e.detail); logEvent('mrdSubmit', e.detail); });
|
|
817
|
-
form.addEventListener('mrdSearch', (e) => { console.log('[mrdSearch]', e.detail); logEvent('mrdSearch', e.detail); });
|
|
818
|
-
form.addEventListener('mrdFetchAll', (e) => { console.log('[mrdFetchAll]', e.detail); logEvent('mrdFetchAll', e.detail); });
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
document.getElementById('locale-select').addEventListener('change', (e) => {
|
|
822
|
-
_locale = e.target.value;
|
|
823
|
-
const demoForm = document.getElementById('demo-form');
|
|
824
|
-
if (demoForm) demoForm.locale = _locale;
|
|
825
|
-
const liveForm = document.getElementById('live-form');
|
|
826
|
-
if (liveForm) liveForm.locale = _locale;
|
|
827
|
-
// Update actieve section als aanwezig
|
|
828
|
-
const section = document.querySelector('#sections-container mrd-layout-section');
|
|
829
|
-
if (section) section.locale = _locale;
|
|
830
|
-
});
|
|
831
|
-
|
|
832
|
-
document.getElementById('btn-inject-results').addEventListener('click', () => {
|
|
833
|
-
const relationField = document.querySelector('mrd-relation-field');
|
|
834
|
-
if (relationField && typeof relationField.setSearchResults === 'function') {
|
|
835
|
-
relationField.setSearchResults([
|
|
836
|
-
{ id: '1', label: 'Alice Johnson', description: 'Senior Engineer' },
|
|
837
|
-
{ id: '2', label: 'Bob van der Berg', description: 'Product Manager' },
|
|
838
|
-
{ id: '3', label: 'Carol Martínez', description: 'UX Designer' },
|
|
839
|
-
{ id: '4', label: 'David Müller', description: 'DevOps Lead' },
|
|
840
|
-
]);
|
|
841
|
-
} else {
|
|
842
|
-
logEvent('info', 'Type 2+ chars in the Project Manager field first, then click this');
|
|
843
|
-
}
|
|
844
|
-
});
|
|
845
|
-
|
|
846
|
-
/* =====================================================================
|
|
847
|
-
BOOT
|
|
848
|
-
===================================================================== */
|
|
849
|
-
|
|
850
|
-
document.addEventListener('DOMContentLoaded', async () => {
|
|
851
|
-
// Initialize Field Types tab (static, no auth needed)
|
|
852
|
-
const ftSection = document.getElementById('field-types-section');
|
|
853
|
-
ftSection.items = window.EXAMPLE_FIELD_TYPES_ITEMS;
|
|
854
|
-
ftSection.data = window.EXAMPLE_FIELD_TYPES_DATA;
|
|
855
|
-
ftSection.locale = 'nl-NL';
|
|
856
|
-
|
|
857
|
-
// Simulate host resolving a signed URL for IMAGE fields (thumbnail on load).
|
|
858
|
-
ftSection.addEventListener('mrdLoadImage', (e) => {
|
|
859
|
-
const staticUrl = 'https://picsum.photos/seed/mosterd/800/600';
|
|
860
|
-
ftSection.setImagePreview(e.detail.fieldName, staticUrl);
|
|
861
|
-
});
|
|
862
|
-
|
|
863
|
-
ftSection.addEventListener('mrdNavigate', (e) => {
|
|
864
|
-
logEvent('mrdNavigate', e.detail);
|
|
865
|
-
});
|
|
866
|
-
|
|
867
|
-
ftSection.addEventListener('mrdDownload', (e) => {
|
|
868
|
-
logEvent('mrdDownload', e.detail);
|
|
869
|
-
});
|
|
870
|
-
|
|
871
|
-
ftSection.addEventListener('mrdSearch', (e) => {
|
|
872
|
-
logEvent('mrdSearch', e.detail);
|
|
873
|
-
const { query, dataClass } = e.detail;
|
|
874
|
-
const mock = [
|
|
875
|
-
{ id: '/data/demo/1', label: 'Alice Johnson', description: 'Senior Engineer' },
|
|
876
|
-
{ id: '/data/demo/2', label: 'Bob van der Berg', description: 'Product Manager' },
|
|
877
|
-
{ id: '/data/demo/3', label: 'Carol Martínez', description: 'UX Designer' },
|
|
878
|
-
].filter(r => r.label.toLowerCase().includes(query.toLowerCase()));
|
|
879
|
-
ftSection.setSearchResults(mock, dataClass);
|
|
880
|
-
});
|
|
881
|
-
|
|
882
|
-
authRestoreSession();
|
|
883
|
-
await authHandleCallback();
|
|
884
|
-
|
|
885
|
-
if (authGetToken()) {
|
|
886
|
-
showTab('live-api');
|
|
887
|
-
showAuthStatus();
|
|
888
|
-
loadTenants();
|
|
889
|
-
}
|
|
890
|
-
});
|