dbxapp 4.1.3
CMS, Shop, Workflows und modulare Geschäftsanwendungen
Loading...
Searching...
No Matches
report.js
Go to the documentation of this file.
1/*!
2 * @file report.js
3 * Clientseitige Report-Funktionen fuer dbXapp.
4 *
5 * Sinn:
6 * - reportbezogene Checkbox-Auswahl synchronisieren
7 * - sichtbare Zeilen und gespeicherte Auswahl getrennt behandeln
8 * - Sortier-/Reihenfolge-Aktionen fuer Reportlisten kapseln
9 * - Ajax-Anfragen an dbxReport-Endpunkte standardisieren
10 *
11 * Beispiel:
12 * ```html
13 * <div class="dbxReport" data-dbx="lib=report">
14 * <input type="checkbox" data-report-action="rows-select">
15 * </div>
16 * ```
17 */
18(function (window, document) {
19 "use strict";
20
21 function bootReportLib() {
22 if (!window.dbx || !window.dbx.feature) {
23 return false;
24 }
25 if (window.dbx.feature.has("report")) {
26 return true;
27 }
28
29 const dbx = window.dbx;
30 const LIB = "report";
31 dbx.report = dbx.report || {};
32
33 function reportText(key) {
34 const texts = {
35 sortDirty: {
36 de: "Reihenfolge geändert. Bitte speichern.",
37 en: "Order changed. Please save.",
38 es: "El orden ha cambiado. Guárdelo."
39 },
40 sortUrlMissing: {
41 de: "Sortier-URL fehlt.",
42 en: "The sort URL is missing.",
43 es: "Falta la URL de ordenación."
44 },
45 sortEmpty: {
46 de: "Keine Reihenfolge zum Speichern.",
47 en: "There is no order to save.",
48 es: "No hay ningún orden que guardar."
49 },
50 ajaxMissing: {
51 de: "ajax.js ist nicht geladen.",
52 en: "ajax.js is not loaded.",
53 es: "ajax.js no está cargado."
54 },
55 sortSaving: {
56 de: "Reihenfolge wird gespeichert …",
57 en: "Saving order …",
58 es: "Guardando el orden …"
59 },
60 sortSaved: {
61 de: "Reihenfolge wurde gespeichert.",
62 en: "The order was saved.",
63 es: "El orden se ha guardado."
64 },
65 sortSaveError: {
66 de: "Reihenfolge konnte nicht gespeichert werden.",
67 en: "The order could not be saved.",
68 es: "No se ha podido guardar el orden."
69 }
70 };
71
72 return dbx.translate(texts[key] || {});
73 }
74
75 function readAttr(el, name, def = "") {
76 if (!el || !el.getAttribute) return def;
77 const value = el.getAttribute(name);
78 return value == null ? def : String(value).trim();
79 }
80
81 function readReportAttr(el, key, def = "") {
82 return readAttr(el, "data-report-" + key, def);
83 }
84
85 function bool(value, def = false) {
86 if (value === undefined || value === null || value === "") return def;
87 if (value === true || value === 1 || value === "1" || value === "on" || value === "true") return true;
88 if (value === false || value === 0 || value === "0" || value === "off" || value === "false") return false;
89 return def;
90 }
91
92 function bindFilterAutoSubmit(root) {
93
94 const form = getForm(root);
95 if (!form || form.__dbxFilterAutoBound) {
96 return;
97 }
98
99 form.__dbxFilterAutoBound = true;
100
101 form.addEventListener("change", function (e) {
102
103 const field = e.target && e.target.closest
104 ? e.target.closest("[name='dbx_rrows']")
105 : null;
106
107 if (!field || !form.contains(field)) {
108 return;
109 }
110
111 const rpos = form.querySelector("[name='dbx_rpos']");
112 if (rpos) {
113 rpos.value = "0";
114 }
115
116 if (typeof form.requestSubmit === "function") {
117 form.requestSubmit();
118 } else {
119 form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
120 }
121 });
122 }
123
124 function emit(name, data) {
125 if (dbx.event && typeof dbx.event.emit === "function") {
126 dbx.event.emit(name, data || {});
127 }
128 }
129
130 function reportRoots(root) {
131 if (!root || !root.querySelectorAll) return [];
132 if (root.matches && (root.matches(".dbxReport") || root.matches(".dbx-report"))) return [root];
133 return Array.from(root.querySelectorAll(".dbxReport, .dbx-report"));
134 }
135
136 function findRoot(el) {
137 return el && el.closest ? el.closest(".dbxReport, .dbx-report, [data-dbx-report-root='1']") : null;
138 }
139
140 function ensureTableScrollWrap(root) {
141 if (!root || !root.querySelectorAll) return;
142
143 root.querySelectorAll("form > table.table, form > table[data-toggle='table']").forEach(table => {
144 const parent = table.parentElement;
145 if (!parent) return;
146 if (parent.classList.contains("table-responsive") || parent.classList.contains("dbx-report-table-scroll")) {
147 return;
148 }
149
150 const wrap = document.createElement("div");
151 wrap.className = "table-responsive dbx-report-table-scroll";
152 parent.insertBefore(wrap, table);
153 wrap.appendChild(table);
154 });
155 }
156
157 function getForm(root) {
158 if (!root || !root.querySelector) return null;
159 return root.querySelector("form") || (root.closest ? root.closest("form") : null);
160 }
161
162 function getActionUrl(root) {
163 const form = getForm(root);
164 return (form && form.getAttribute("action")) || window.location.href;
165 }
166
167 function getUrlParam(url, name) {
168 try {
169 return new URL(url || "", window.location.href).searchParams.get(name) || "";
170 } catch (err) {
171 return "";
172 }
173 }
174
175 function appendValue(body, name, value) {
176 body.set(name, value == null ? "" : String(value));
177 }
178
179 function getRowCheckboxes(root) {
180 if (!root || !root.querySelectorAll) return [];
181
182 return Array.from(root.querySelectorAll(
183 ".cb-row-select, .form-check-input-multi, [data-report-action='row-select']"
184 )).filter(input => {
185 return input && input.type === "checkbox" && !input.disabled;
186 });
187 }
188
189 function isHeaderCheckbox(input) {
190 return input && input.matches && input.matches(".cb-col-select, [data-report-action='rows-select']");
191 }
192
193 function isRowCheckbox(input) {
194 return input && input.matches && input.matches(".cb-row-select, .form-check-input-multi, [data-report-action='row-select']");
195 }
196
197 function isReportCheckbox(input) {
198 return isHeaderCheckbox(input) || isRowCheckbox(input);
199 }
200
201 function stopReportSelectionEvent(e) {
202 if (!e) return;
203 e.stopPropagation();
204 if (typeof e.stopImmediatePropagation === "function") {
205 e.stopImmediatePropagation();
206 }
207 }
208
209 function getHeaderCheckboxes(root) {
210 if (!root || !root.querySelectorAll) return [];
211
212 return Array.from(root.querySelectorAll(
213 ".cb-col-select, [data-report-action='rows-select']"
214 )).filter(input => {
215 return input && input.type === "checkbox" && !input.disabled;
216 });
217 }
218
219 function getRowId(input) {
220 return readReportAttr(input, "rid") || readAttr(input, "data-rid") || input.value || "";
221 }
222
223 function getVisibleIds(root) {
224 return getRowCheckboxes(root)
225 .map(getRowId)
226 .filter(Boolean);
227 }
228
229 function updateHeaderState(root, state) {
230 const rows = getRowCheckboxes(root);
231 const headers = getHeaderCheckboxes(root);
232
233 if (!headers.length) return;
234
235 let headerState = state || "";
236
237 if (!headerState) {
238 const total = rows.length;
239 const checked = rows.filter(input => input.checked).length;
240 headerState = (total > 0 && checked === total) ? "all" : (checked > 0 ? "partial" : "none");
241 }
242
243 headers.forEach(header => {
244 header.checked = headerState === "all";
245 header.indeterminate = headerState === "partial";
246 header.setAttribute("aria-checked", header.indeterminate ? "mixed" : (header.checked ? "true" : "false"));
247 header.setAttribute("data-report-header-state", headerState);
248 });
249 }
250
251 function updateCountTargets(root, response) {
252 if (!root || !root.querySelectorAll) return;
253
254 const checkedVisible = getRowCheckboxes(root).filter(input => input.checked).length;
255 const hasTotal = response && typeof response.count_selects !== "undefined";
256
257 root.querySelectorAll("[data-report-count='selected-visible']").forEach(el => {
258 el.textContent = String(checkedVisible);
259 });
260
261 if (hasTotal) {
262 root.querySelectorAll("[data-report-count='selected-total']").forEach(el => {
263 el.textContent = String(response.count_selects);
264 });
265 }
266 }
267
268 function sync(root, response) {
269 updateHeaderState(root);
270 updateCountTargets(root, response || null);
271 }
272
273 function buildBody(root, params) {
274 const body = new URLSearchParams();
275
276 Object.keys(params || {}).forEach(key => appendValue(body, key, params[key]));
277 if (!body.has("dbx_sync")) appendValue(body, "dbx_sync", "0");
278 if (!body.has("dbx_select_quick")) appendValue(body, "dbx_select_quick", "0");
279
280 return body;
281 }
282
283 function request(root, params) {
284 const url = getActionUrl(root);
285 const body = buildBody(root, params);
286
287 if (!dbx.ajax || typeof dbx.ajax.request !== "function") {
288 return Promise.reject(new Error("ajax.js nicht geladen."));
289 }
290
291 const send = keepalive => dbx.ajax.request({
292 url: url,
293 method: "POST",
294 mode: "json",
295 body: body,
296 keepalive: keepalive,
297 headers: {
298 "X-DBX-UI-State": "report-select"
299 }
300 });
301
302 return send(true).catch(err => {
303 if (err && String(err.message || err).toLowerCase().indexOf("keepalive") !== -1) {
304 return send(false);
305 }
306
307 dbx.warn("[report] select request failed", err);
308 return null;
309 }).then(response => {
310 if (response && Number(response.ok || 0) === 1) {
311 applyResponse(root, response);
312 }
313 return response;
314 });
315 }
316
317 function flushSelectionSnapshot(root) {
318 return Promise.resolve(null);
319 }
320
321 function applyResponse(root, response) {
322 const selected = new Set((response.selected_ids_visible || []).map(String));
323 const visible = new Set((response.visible_ids || []).map(String));
324
325 if (visible.size) {
326 getRowCheckboxes(root).forEach(input => {
327 const rid = String(getRowId(input));
328 if (visible.has(rid)) {
329 input.checked = selected.has(rid);
330 }
331 });
332 }
333
334 sync(root, response);
335
336 emit("report:select-response", {
337 id: root.id || "",
338 root: root,
339 response: response
340 });
341 }
342
343 function selectRows(root, checked) {
344 const ids = getVisibleIds(root);
345
346 getRowCheckboxes(root).forEach(input => {
347 input.checked = checked;
348 });
349 sync(root);
350
351 emit("report:rows-select", {
352 id: root.id || "",
353 root: root,
354 checked: checked ? 1 : 0,
355 visibleIds: ids
356 });
357
358 return request(root, {
359 dbx_do: "rows_select",
360 dbx_select_state: checked ? 1 : 0,
361 dbx_select_ids: JSON.stringify(ids),
362 dbx_select_visible_ids: JSON.stringify(ids)
363 });
364 }
365
366 function selectRow(root, input) {
367 const rid = getRowId(input);
368 const visibleIds = getVisibleIds(root);
369
370 sync(root);
371
372 emit("report:row-select", {
373 id: root.id || "",
374 root: root,
375 input: input,
376 rid: rid,
377 checked: input.checked ? 1 : 0,
378 visibleIds: visibleIds
379 });
380
381 return request(root, {
382 dbx_do: "row_select",
383 dbx_select_id: rid,
384 dbx_select_state: input.checked ? 1 : 0,
385 dbx_select_visible_ids: JSON.stringify(visibleIds)
386 });
387 }
388
389 function clearRows(root) {
390 getRowCheckboxes(root).forEach(input => {
391 input.checked = false;
392 });
393 sync(root);
394
395 emit("report:clear-selects", {
396 id: root.id || "",
397 root: root
398 });
399
400 return request(root, {
401 dbx_do: "clear_selects",
402 dbx_select_visible_ids: JSON.stringify(getVisibleIds(root))
403 });
404 }
405
406 function sortListFrom(el) {
407 return el && el.closest ? el.closest("[data-report-sort-list]") : null;
408 }
409
410 function sortRows(list) {
411 if (!list || !list.children) return [];
412
413 return Array.from(list.children).filter(el => {
414 return el && el.matches && el.matches("[data-report-sort-row]");
415 });
416 }
417
418 function sortRowValue(row) {
419 return readAttr(row, "data-sort-value");
420 }
421
422 function sortStatus(list, msg, type) {
423 const root = findRoot(list) || list;
424 const el = root && root.querySelector ? root.querySelector("[data-report-sort-status]") : null;
425 if (!el) return;
426
427 el.textContent = msg || "";
428 el.classList.toggle("text-danger", type === "error");
429 el.classList.toggle("text-success", type === "success");
430 el.classList.toggle("text-muted", !type || type === "info");
431 }
432
433 function renumberSortRows(list) {
434 sortRows(list).forEach((row, idx) => {
435 const no = row.querySelector("[data-report-sort-no], .dbx-ddedit-order-no");
436 if (no) no.textContent = String(idx + 1);
437 });
438 }
439
440 function markSortDirty(list) {
441 if (!list) return;
442 list.classList.add("is-dirty");
443 const root = findRoot(list) || list;
444 if (root) root.classList.add("is-sort-dirty");
445 sortStatus(list, reportText("sortDirty"), "info");
446 }
447
448 function clearSortDirty(list) {
449 if (!list) return;
450 list.classList.remove("is-dirty");
451 const root = findRoot(list) || list;
452 if (root) root.classList.remove("is-sort-dirty");
453 }
454
455 function normalizeSortValues(list) {
456 sortRows(list).forEach((row, idx) => {
457 row.setAttribute("data-sort-value", String(idx));
458 });
459 }
460
461 function reloadSortContext(list) {
462 if (!bool(readAttr(list, "data-sort-reload"), false)) return;
463
464 const ddRoot = list.closest ? list.closest("[data-ddedit-root='1']") : null;
465 const reload = ddRoot && ddRoot.querySelector ? ddRoot.querySelector(".dbx-ddedit-head-actions a.dbxAjax") : null;
466 if (reload && typeof reload.click === "function") {
467 window.setTimeout(() => reload.click(), 250);
468 }
469 }
470
471 function moveSortRow(list, from, to, clientY) {
472 if (!list || !from || !to || from === to) return false;
473
474 const rect = to.getBoundingClientRect();
475 const before = clientY < rect.top + (rect.height / 2);
476 list.insertBefore(from, before ? to : to.nextSibling);
477 renumberSortRows(list);
478 markSortDirty(list);
479 return true;
480 }
481
482 function saveSortList(list) {
483 const url = readAttr(list, "data-sort-url");
484 const order = sortRows(list).map(sortRowValue).filter(value => value !== "");
485
486 if (!url) {
487 sortStatus(list, reportText("sortUrlMissing"), "error");
488 return Promise.resolve(null);
489 }
490
491 if (!order.length) {
492 sortStatus(list, reportText("sortEmpty"), "error");
493 return Promise.resolve(null);
494 }
495
496 if (!dbx.ajax || typeof dbx.ajax.request !== "function") {
497 sortStatus(list, reportText("ajaxMissing"), "error");
498 return Promise.resolve(null);
499 }
500
501 const body = new URLSearchParams();
502 body.set("order", JSON.stringify(order));
503
504 list.classList.add("is-saving");
505 sortStatus(list, reportText("sortSaving"), "info");
506
507 return dbx.ajax.request({
508 url: url,
509 method: "POST",
510 mode: "json",
511 body: body,
512 headers: {
513 "X-DBX-UI-State": "report-sort"
514 }
515 }).then(response => {
516 if (!response || Number(response.ok || 0) !== 1) {
517 throw new Error((response && response.msg) || "Reihenfolge konnte nicht gespeichert werden.");
518 }
519
520 clearSortDirty(list);
521 normalizeSortValues(list);
522 sortStatus(list, response.msg || reportText("sortSaved"), "success");
523 reloadSortContext(list);
524 return response;
525 }).catch(err => {
526 dbx.warn("[report] sort save failed", err);
527 sortStatus(list, (err && err.message) || reportText("sortSaveError"), "error");
528 return null;
529 }).finally(() => {
530 list.classList.remove("is-saving");
531 });
532 }
533
534 function appendActionParam(url, name, value) {
535 try {
536 const parsed = new URL(url || window.location.href, window.location.href);
537 parsed.searchParams.set(name, value);
538 return parsed.toString();
539 } catch (err) {
540 return url || window.location.href;
541 }
542 }
543
544 function runFooterAction(root, picker) {
545 if (!root || !picker || !picker.value) {
546 return;
547 }
548
549 const dbxDo = String(picker.value || "").trim();
550
551 if (!dbxDo) {
552 return;
553 }
554
555 const actionNode = root.querySelector("[data-report-footer-action='" + dbxDo.replace(/'/g, "\\'") + "']");
556 const actionLink = actionNode && actionNode.matches && actionNode.matches("a,button,input")
557 ? actionNode
558 : (actionNode && actionNode.querySelector ? actionNode.querySelector("a,button,input") : null);
559
560 if (!actionLink) {
561 return;
562 }
563
564 flushSelectionSnapshot(root).finally(() => actionLink.click());
565 }
566
567 function bindSort(root) {
568 if (!root || root.__dbxReportSortBound) return;
569 root.__dbxReportSortBound = true;
570
571 root.addEventListener("dragstart", e => {
572 const row = e.target && e.target.closest ? e.target.closest("[data-report-sort-row]") : null;
573 if (!row || !root.contains(row)) return;
574
575 const list = sortListFrom(row);
576 if (!list || !root.contains(list)) return;
577
578 const handle = row.querySelector("[data-report-sort-handle]");
579 if (handle && !e.target.closest("[data-report-sort-handle]")) {
580 e.preventDefault();
581 return;
582 }
583
584 root._dbxReportSortDragRow = row;
585 row.classList.add("is-dragging");
586 list.classList.add("is-dragging");
587 e.dataTransfer.effectAllowed = "move";
588 e.dataTransfer.setData("text/plain", sortRowValue(row));
589 });
590
591 root.addEventListener("dragend", e => {
592 const row = root._dbxReportSortDragRow || (e.target && e.target.closest ? e.target.closest("[data-report-sort-row]") : null);
593 const list = row ? sortListFrom(row) : null;
594 if (row) row.classList.remove("is-dragging");
595 if (list) list.classList.remove("is-dragging");
596 root.querySelectorAll("[data-report-sort-row].is-drop-before,[data-report-sort-row].is-drop-after").forEach(el => {
597 el.classList.remove("is-drop-before", "is-drop-after");
598 });
599 root._dbxReportSortDragRow = null;
600 });
601
602 root.addEventListener("dragover", e => {
603 const row = e.target && e.target.closest ? e.target.closest("[data-report-sort-row]") : null;
604 const from = root._dbxReportSortDragRow;
605 if (!row || !from || !root.contains(row)) return;
606
607 const list = sortListFrom(row);
608 if (!list || sortListFrom(from) !== list) return;
609
610 e.preventDefault();
611 e.dataTransfer.dropEffect = "move";
612 root.querySelectorAll("[data-report-sort-row].is-drop-before,[data-report-sort-row].is-drop-after").forEach(el => {
613 if (el !== row) el.classList.remove("is-drop-before", "is-drop-after");
614 });
615
616 const rect = row.getBoundingClientRect();
617 const before = e.clientY < rect.top + (rect.height / 2);
618 row.classList.toggle("is-drop-before", before);
619 row.classList.toggle("is-drop-after", !before);
620 });
621
622 root.addEventListener("dragleave", e => {
623 const row = e.target && e.target.closest ? e.target.closest("[data-report-sort-row]") : null;
624 if (row && !row.contains(e.relatedTarget)) {
625 row.classList.remove("is-drop-before", "is-drop-after");
626 }
627 });
628
629 root.addEventListener("drop", e => {
630 const to = e.target && e.target.closest ? e.target.closest("[data-report-sort-row]") : null;
631 const from = root._dbxReportSortDragRow;
632 if (!to || !from || !root.contains(to)) return;
633
634 const list = sortListFrom(to);
635 if (!list || sortListFrom(from) !== list) return;
636
637 e.preventDefault();
638 to.classList.remove("is-drop-before", "is-drop-after");
639 moveSortRow(list, from, to, e.clientY);
640 });
641
642 root.addEventListener("click", e => {
643 const btn = e.target && e.target.closest ? e.target.closest("[data-report-sort-save]") : null;
644 if (!btn || !root.contains(btn)) return;
645
646 const name = readAttr(btn, "data-report-sort-save");
647 const list = root.querySelector('[data-report-sort-list="' + name.replace(/"/g, '\\"') + '"]') || root.querySelector("[data-report-sort-list]");
648 if (!list) return;
649
650 e.preventDefault();
651 saveSortList(list);
652 });
653 }
654
655 dbx.report.sync = sync;
656 dbx.report.getVisibleIds = getVisibleIds;
657 dbx.report.request = request;
658 dbx.report.saveSortList = saveSortList;
659 dbx.report.ensureTableScrollWrap = ensureTableScrollWrap;
660
661 if (!dbx.report.__tableScrollAjaxBound && dbx.event && typeof dbx.event.on === "function") {
662 dbx.report.__tableScrollAjaxBound = true;
663 dbx.event.on("ajax:after", data => {
664 const el = data && data.targetElement;
665 if (!el) return;
666
667 const root = findRoot(el);
668 if (root) {
669 ensureTableScrollWrap(root);
670 return;
671 }
672
673 reportRoots(el).forEach(ensureTableScrollWrap);
674 });
675 }
676
677 dbx.feature.register(LIB, {
678 scope: "element",
679 priority: "last",
680
681 css: [
682 ["css", "design", "c-form.css"],
683 ["css", "design", "c-report.css"]
684 ],
685
686 js: [
687 ["js", "lib", "ajax.js"],
688 ["js", "lib", "form.js"]
689 ],
690
691 init(el, cfg) {
692 if (!el) return;
693
694 reportRoots(el).forEach(root => {
695 ensureTableScrollWrap(root);
696
697 root.__dbxInitialized = root.__dbxInitialized || {};
698 if (root.__dbxInitialized[LIB]) return;
699 root.__dbxInitialized[LIB] = true;
700
701 root.setAttribute("data-dbx-report-root", "1");
702 root._dbxReportConfig = cfg || {};
703 bindSort(root);
704
705 root.addEventListener("click", function (e) {
706 const input = e.target && e.target.closest ? e.target.closest("input[type='checkbox']") : null;
707 if (!input || !root.contains(input) || !isReportCheckbox(input)) return;
708 e.stopPropagation();
709 });
710
711 root.addEventListener("change", function (e) {
712 const input = e.target && e.target.closest ? e.target.closest("input[type='checkbox']") : null;
713 if (!input || !root.contains(input)) return;
714
715 if (isHeaderCheckbox(input)) {
716 stopReportSelectionEvent(e);
717 selectRows(root, input.checked === true);
718 return;
719 }
720
721 if (isRowCheckbox(input)) {
722 stopReportSelectionEvent(e);
723 selectRow(root, input);
724 }
725 }, true);
726
727 root.addEventListener("click", function (e) {
728 const actionEl = e.target && e.target.closest ? e.target.closest("[data-report-action]") : null;
729
730 if (actionEl && root.contains(actionEl)) {
731 const action = readReportAttr(actionEl, "action");
732
733 if (action === "clear-selects") {
734 e.preventDefault();
735 clearRows(root);
736 }
737
738 if (action === "run") {
739 e.preventDefault();
740 const picker = root.querySelector("[data-report-action='picker']");
741 runFooterAction(root, picker);
742 }
743
744 return;
745 }
746
747 const formAction = e.target && e.target.closest ? e.target.closest("a.dbxAjaxFormAction") : null;
748 if (!formAction || !root.contains(formAction)) return;
749
750 const dbxDo = getUrlParam(formAction.getAttribute("href"), "dbx_do");
751
752 if (dbxDo === "multi_select" || dbxDo === "multi_deselect") {
753 e.preventDefault();
754 selectRows(root, dbxDo === "multi_select");
755 }
756 }, true);
757
758 if (bool(cfg && cfg.form, true) && dbx.feature && dbx.feature.has && dbx.feature.has("form")) {
759 try {
760 dbx.feature.init("form", root, Object.assign({}, cfg || {}, { lib: "form" }));
761 } catch (err) {
762 dbx.warn("[report] form init failed", err);
763 }
764 }
765
766 bindFilterAutoSubmit(root);
767
768 root._dbxReportBeforeUnload = () => {
769 flushSelectionSnapshot(root);
770 };
771 window.addEventListener("beforeunload", root._dbxReportBeforeUnload);
772
773 sync(root);
774
775 dbx.log("[report] init", {
776 id: root.id || "",
777 rows: getRowCheckboxes(root).length
778 });
779 });
780 },
781
782 destroy(el) {
783 reportRoots(el).forEach(root => {
784 flushSelectionSnapshot(root);
785
786 if (root._dbxReportBeforeUnload) {
787 window.removeEventListener("beforeunload", root._dbxReportBeforeUnload);
788 delete root._dbxReportBeforeUnload;
789 }
790
791 delete root._dbxReportConfig;
792
793 if (root.__dbxInitialized && root.__dbxInitialized[LIB]) {
794 delete root.__dbxInitialized[LIB];
795 }
796
797 root.removeAttribute("data-dbx-report-root");
798
799 dbx.log("[report] destroy", {
800 id: root.id || ""
801 });
802 });
803 }
804 });
805
806 return true;
807 }
808
809 if (!bootReportLib()) {
810 let attempts = 0;
811 const wait = window.setInterval(function () {
812 if (bootReportLib() || ++attempts > 80) {
813 window.clearInterval(wait);
814 }
815 }, 50);
816 }
817
818})(window, document);