dbxapp 4.1.3
CMS, Shop, Workflows und modulare Geschäftsanwendungen
Loading...
Searching...
No Matches
shopAdmin.js
Go to the documentation of this file.
1/*!
2 * @file shopAdmin.js
3 * Shop-Administration: CMS-Medienbrowser fuer Artikelbilder.
4 */
5(function (window, document) {
6 "use strict";
7
8 if (!window.dbx || !window.dbx.feature) {
9 return;
10 }
11
12 const dbx = window.dbx;
13 const LIB = "shopAdmin";
14 let reloadTimer = null;
15
16 function cfg(root) {
17 root = root || document.querySelector(".dbx-shop-media-manager") || document;
18 return {
19 media: root.getAttribute("data-shop-media") || "",
20 upload: root.getAttribute("data-shop-upload") || "",
21 externalvideo: root.getAttribute("data-shop-externalvideo") || "",
22 mediafolders: root.getAttribute("data-shop-mediafolders") || "",
23 mediafoldercreate: root.getAttribute("data-shop-mediafoldercreate") || "",
24 mediafolderdelete: root.getAttribute("data-shop-mediafolderdelete") || "",
25 mediafolderrename: root.getAttribute("data-shop-mediafolderrename") || "",
26 mediamove: root.getAttribute("data-shop-mediamove") || "",
27 mediaunused: root.getAttribute("data-shop-mediaunused") || "",
28 mediaprocess: root.getAttribute("data-shop-mediaprocess") || "",
29 deletemedia: root.getAttribute("data-shop-deletemedia") || "",
30 editmedia: root.getAttribute("data-shop-editmedia") || "",
31 uploadmediafolder: root.getAttribute("data-shop-uploadmediafolder") || "",
32 assignurl: root.getAttribute("data-shop-assignurl") || ""
33 };
34 }
35
36 function loadSharedMediaBrowserAssets() {
37 return new Promise(function (resolve) {
38 if (!dbx.load) {
39 resolve(false);
40 return;
41 }
42 dbx.load([
43 ["css", "design", "c-cms.css"],
44 ["js", "lib", "ajax.js"],
45 ["js", "lib", "openWin.js"],
46 ["js", "lib", "cms.js"]
47 ], function () {
48 resolve(!!(dbx.cmsMediaBrowser && typeof dbx.cmsMediaBrowser.open === "function"));
49 });
50 });
51 }
52
53 function ensureCmsMediaBrowser() {
54 if (dbx.cmsMediaBrowser && typeof dbx.cmsMediaBrowser.open === "function") {
55 return loadSharedMediaBrowserAssets();
56 }
57 return loadSharedMediaBrowserAssets();
58 }
59
60 function ensureAjax() {
61 return new Promise(function (resolve) {
62 if (dbx.ajax && typeof dbx.ajax.request === "function") {
63 resolve(true);
64 return;
65 }
66 if (!dbx.load) {
67 resolve(false);
68 return;
69 }
70 dbx.load([["js", "lib", "ajax.js"]], function () {
71 resolve(!!(dbx.ajax && typeof dbx.ajax.request === "function"));
72 });
73 });
74 }
75
76 function fetchJson(url, options) {
77 options = options || {};
78 if (!dbx.ajax || typeof dbx.ajax.request !== "function") {
79 return Promise.reject(new Error("ajax.js nicht geladen."));
80 }
81 return dbx.ajax.request({
82 url: url,
83 method: options.method || "GET",
84 mode: "json",
85 body: typeof options.body !== "undefined" ? options.body : null,
86 headers: options.headers || {},
87 timeout: Number(options.timeout || 20000)
88 });
89 }
90
91 function scheduleReload() {
92 if (reloadTimer) {
93 window.clearTimeout(reloadTimer);
94 }
95 reloadTimer = window.setTimeout(function () {
96 if (window.dbx && dbx.utilities && dbx.utilities.leaveGuard) {
97 dbx.utilities.leaveGuard.allowOnce();
98 }
99 window.location.reload();
100 }, 900);
101 }
102
103 function assignMedia(root, row) {
104 const settings = cfg(root);
105 if (!settings.assignurl) {
106 return Promise.reject(new Error("Shop-Zuordnung ist nicht konfiguriert."));
107 }
108 const product = root.querySelector("[data-shop-product-select]");
109 const group = root.querySelector("[data-shop-group-select]");
110 const sorter = root.querySelector("[data-shop-sorter]");
111 const primary = root.querySelector("[data-shop-primary]");
112 const productId = product ? parseInt(product.value || "0", 10) || 0 : 0;
113 const groupId = group ? parseInt(group.value || "0", 10) || 0 : 0;
114 if (productId <= 0 && groupId <= 0) {
115 window.alert("Bitte zuerst einen Artikel oder eine Artikelgruppe auswaehlen.");
116 return Promise.resolve(false);
117 }
118 const payload = {
119 product_id: productId,
120 group_id: groupId,
121 media_id: parseInt(row.id || row.media_id || "0", 10) || 0,
122 title: row.title || row.label || row.file_name || "",
123 alt: row.alt || row.title || row.label || "",
124 sorter: sorter ? (parseInt(sorter.value || "100", 10) || 100) : 100,
125 is_primary: primary && primary.checked ? 1 : 0
126 };
127 if (payload.media_id <= 0) {
128 window.alert("Das ausgewaehlte Medium hat keine gueltige ID.");
129 return Promise.resolve(false);
130 }
131 return fetchJson(settings.assignurl, {
132 method: "POST",
133 headers: { "Content-Type": "application/json" },
134 body: JSON.stringify(payload)
135 }).then(function (data) {
136 if (!data || !data.ok) {
137 window.alert((data && data.msg) ? data.msg : "Bild konnte nicht zugeordnet werden.");
138 return false;
139 }
140 scheduleReload();
141 return true;
142 }).catch(function (err) {
143 window.alert(err && err.message ? err.message : "Bild konnte nicht zugeordnet werden.");
144 return false;
145 });
146 }
147
148 function openMediaBrowser(root, button) {
149 ensureCmsMediaBrowser().then(function (ok) {
150 if (!ok || !dbx.cmsMediaBrowser) {
151 window.alert("CMS-Medienbrowser konnte nicht geladen werden.");
152 return;
153 }
154 const settings = cfg(root);
155 dbx.cmsMediaBrowser.open(root, settings, {
156 mode: "pick",
157 mediaFolder: button.getAttribute("data-shop-media-folder") || settings.uploadmediafolder || "all",
158 afterAssign: function (row) {
159 return assignMedia(root, row);
160 }
161 });
162 });
163 }
164
165 function readDragPayload(event) {
166 try {
167 return JSON.parse(event.dataTransfer.getData("application/json") || "{}");
168 } catch (err) {
169 return {};
170 }
171 }
172
173 function moveTreeNode(root, drop, payload) {
174 const url = root.getAttribute("data-shop-tree-moveurl") || "";
175 const targetGroupId = parseInt(drop.getAttribute("data-shop-tree-drop") || "0", 10) || 0;
176 if (!url || !payload || !payload.type) {
177 return;
178 }
179 if (payload.type === "product" && targetGroupId <= 0) {
180 window.alert("Artikel koennen nur auf eine Artikelgruppe gezogen werden.");
181 return;
182 }
183 const body = {
184 type: payload.type,
185 target_group_id: targetGroupId
186 };
187 if (payload.type === "product") {
188 body.product_id = parseInt(payload.id || "0", 10) || 0;
189 } else if (payload.type === "group") {
190 body.group_id = parseInt(payload.id || "0", 10) || 0;
191 }
192 ensureAjax().then(function (ok) {
193 if (!ok) {
194 window.alert("AJAX konnte nicht geladen werden.");
195 return;
196 }
197 return fetchJson(url, {
198 method: "POST",
199 headers: { "Content-Type": "application/json" },
200 body: JSON.stringify(body)
201 }).then(function (data) {
202 if (!data || !data.ok) {
203 window.alert((data && data.msg) ? data.msg : "Verschieben nicht moeglich.");
204 return;
205 }
206 scheduleReload();
207 });
208 }).catch(function (err) {
209 window.alert(err && err.message ? err.message : "Verschieben nicht moeglich.");
210 });
211 }
212
213 function normalizeSearch(value) {
214 return String(value || "")
215 .toLowerCase()
216 .normalize("NFD")
217 .replace(/[\u0300-\u036f]/g, "")
218 .replace(/\s+/g, " ")
219 .trim();
220 }
221
222 function setGroupCollapsed(group, collapsed) {
223 if (!group) return;
224 group.classList.toggle("is-collapsed", !!collapsed);
225 const button = group.querySelector(":scope > .dbx-shop-tree-group-head [data-shop-tree-group-toggle]");
226 if (button) {
227 button.setAttribute("aria-expanded", collapsed ? "false" : "true");
228 const icon = button.querySelector("i");
229 if (icon) {
230 icon.classList.toggle("bi-chevron-right", !!collapsed);
231 icon.classList.toggle("bi-chevron-down", !collapsed);
232 }
233 }
234 }
235
236 function clearTreeSearchState(root) {
237 root.querySelectorAll(".is-search-match,.is-search-path").forEach(function (node) {
238 node.classList.remove("is-search-match", "is-search-path");
239 });
240 root.querySelectorAll("[data-shop-tree-hidden]").forEach(function (node) {
241 node.hidden = false;
242 node.removeAttribute("data-shop-tree-hidden");
243 });
244 }
245
246 function applyTreeSearch(root) {
247 const input = root.querySelector("[data-shop-tree-search]");
248 const term = normalizeSearch(input ? input.value : "");
249 root.classList.toggle("is-searching", term !== "");
250 clearTreeSearchState(root);
251 if (term === "") {
252 return;
253 }
254
255 function nodeText(node) {
256 return normalizeSearch(node.getAttribute("data-shop-tree-search-text") || node.textContent || "");
257 }
258
259 function filterProduct(product) {
260 const match = nodeText(product).includes(term);
261 product.hidden = !match;
262 if (!match) {
263 product.setAttribute("data-shop-tree-hidden", "1");
264 } else {
265 product.classList.add("is-search-match");
266 }
267 return match;
268 }
269
270 function filterProductList(list) {
271 let any = false;
272 list.querySelectorAll(":scope > .dbx-shop-tree-products > .dbx-shop-tree-product").forEach(function (product) {
273 any = filterProduct(product) || any;
274 });
275 list.hidden = !any;
276 if (!any) {
277 list.setAttribute("data-shop-tree-hidden", "1");
278 }
279 return any;
280 }
281
282 function filterGroup(group) {
283 const ownMatch = nodeText(group).includes(term);
284 let childMatch = false;
285 const children = group.querySelector(":scope > .dbx-shop-tree-children");
286 if (children) {
287 Array.prototype.forEach.call(children.children, function (child) {
288 if (child.matches(".dbx-shop-tree-group")) {
289 childMatch = filterGroup(child) || childMatch;
290 } else if (child.matches(".dbx-shop-tree-product-list")) {
291 childMatch = filterProductList(child) || childMatch;
292 }
293 });
294 }
295 const visible = ownMatch || childMatch;
296 group.hidden = !visible;
297 if (!visible) {
298 group.setAttribute("data-shop-tree-hidden", "1");
299 } else if (ownMatch) {
300 group.classList.add("is-search-match");
301 if (children) {
302 children.querySelectorAll("[data-shop-tree-hidden]").forEach(function (node) {
303 node.hidden = false;
304 node.removeAttribute("data-shop-tree-hidden");
305 });
306 }
307 } else {
308 group.classList.add("is-search-path");
309 }
310 return visible;
311 }
312
313 root.querySelectorAll(".dbx-shop-tree-list > .dbx-shop-tree-group").forEach(filterGroup);
314 const ungrouped = root.querySelector(".dbx-shop-tree-ungrouped");
315 if (ungrouped) {
316 let anyUngrouped = false;
317 ungrouped.querySelectorAll(".dbx-shop-tree-product").forEach(function (product) {
318 anyUngrouped = filterProduct(product) || anyUngrouped;
319 });
320 ungrouped.hidden = !anyUngrouped;
321 if (!anyUngrouped) {
322 ungrouped.setAttribute("data-shop-tree-hidden", "1");
323 }
324 }
325 }
326
327 function initProductTree(root) {
328 if (root.__dbxShopProductTreeBound) return;
329 root.__dbxShopProductTreeBound = true;
330
331 root.querySelectorAll("[data-shop-tree-node]").forEach(function (node) {
332 node.setAttribute("draggable", "true");
333 });
334
335 root.addEventListener("dragstart", function (event) {
336 if (event.target.closest("button,a,input,select,textarea")) return;
337 const node = event.target.closest("[data-shop-tree-node]");
338 if (!node || !root.contains(node)) return;
339 const type = node.getAttribute("data-shop-tree-node");
340 const id = type === "product"
341 ? node.getAttribute("data-shop-tree-product")
342 : node.getAttribute("data-shop-tree-group");
343 event.dataTransfer.effectAllowed = "move";
344 event.dataTransfer.setData("application/json", JSON.stringify({ type: type, id: id }));
345 event.dataTransfer.setData("text/plain", type + ":" + id);
346 node.classList.add("is-dragging");
347 });
348
349 root.addEventListener("dragend", function () {
350 root.querySelectorAll(".is-dragging,.is-drop-target").forEach(function (el) {
351 el.classList.remove("is-dragging", "is-drop-target");
352 });
353 });
354
355 root.addEventListener("dragover", function (event) {
356 const drop = event.target.closest("[data-shop-tree-drop]");
357 if (!drop || !root.contains(drop)) return;
358 event.preventDefault();
359 event.dataTransfer.dropEffect = "move";
360 root.querySelectorAll(".is-drop-target").forEach(function (el) {
361 if (el !== drop) el.classList.remove("is-drop-target");
362 });
363 drop.classList.add("is-drop-target");
364 });
365
366 root.addEventListener("dragleave", function (event) {
367 const drop = event.target.closest("[data-shop-tree-drop]");
368 if (drop) drop.classList.remove("is-drop-target");
369 });
370
371 root.addEventListener("drop", function (event) {
372 const drop = event.target.closest("[data-shop-tree-drop]");
373 if (!drop || !root.contains(drop)) return;
374 event.preventDefault();
375 drop.classList.remove("is-drop-target");
376 moveTreeNode(root, drop, readDragPayload(event));
377 });
378
379 const search = root.querySelector("[data-shop-tree-search]");
380 if (search) {
381 search.addEventListener("input", function () {
382 applyTreeSearch(root);
383 });
384 }
385 }
386
387 function findProductTreeShell(element, fallbackRoot) {
388 if (element) {
389 const direct = element.closest("[data-shop-tree-shell]");
390 if (direct) return direct;
391 const panel = element.closest(".dbx-panel");
392 if (panel) {
393 const shell = panel.querySelector("[data-shop-tree-shell]");
394 if (shell) return shell;
395 }
396 }
397 if (fallbackRoot && fallbackRoot.querySelector) {
398 return fallbackRoot.querySelector("[data-shop-tree-shell]") || null;
399 }
400 return null;
401 }
402
403 function setProductTreeOpen(shell, open) {
404 if (!shell) return;
405 const container = shell.closest(".dbx-panel") || shell;
406 shell.classList.toggle("is-shop-tree-collapsed", !open);
407 shell.classList.toggle("is-shop-tree-open", !!open);
408 container.querySelectorAll("[data-shop-tree-toggle]").forEach(function (button) {
409 button.setAttribute("aria-expanded", open ? "true" : "false");
410 button.setAttribute("aria-label", open ? "Artikelgruppen-Baum ausblenden" : "Artikelgruppen-Baum anzeigen");
411 button.setAttribute("title", open ? "Artikelgruppen-Baum ausblenden" : "Artikelgruppen-Baum anzeigen");
412 const icon = button.querySelector("i");
413 if (icon) {
414 icon.classList.toggle("bi-diagram-3", !open);
415 icon.classList.toggle("bi-x-lg", !!open);
416 }
417 });
418 }
419
420 function toggleProductTree(shell) {
421 setProductTreeOpen(shell, shell.classList.contains("is-shop-tree-collapsed"));
422 }
423
424 function init(root) {
425 root = root || document;
426 root.querySelectorAll("[data-shop-tree-moveurl]").forEach(initProductTree);
427 if (root.matches && root.matches("[data-shop-tree-moveurl]")) {
428 initProductTree(root);
429 }
430 root.addEventListener("click", function (event) {
431 const stopPropagation = event.target.closest("[data-shop-stop-propagation]");
432 if (stopPropagation) {
433 event.stopPropagation();
434 }
435 const toggle = event.target.closest("[data-shop-tree-toggle]");
436 if (toggle) {
437 event.preventDefault();
438 toggleProductTree(findProductTreeShell(toggle, root));
439 return;
440 }
441 const close = event.target.closest("[data-shop-tree-close]");
442 if (close) {
443 event.preventDefault();
444 setProductTreeOpen(findProductTreeShell(close, root), false);
445 return;
446 }
447 const groupToggle = event.target.closest("[data-shop-tree-group-toggle]");
448 if (groupToggle) {
449 event.preventDefault();
450 event.stopPropagation();
451 const group = groupToggle.closest("[data-shop-tree-group-wrap]");
452 setGroupCollapsed(group, !group.classList.contains("is-collapsed"));
453 return;
454 }
455 const searchClear = event.target.closest(".dbx-clear-btn");
456 if (searchClear && searchClear.closest(".dbx-shop-tree-search-wrap")) {
457 window.setTimeout(function () {
458 const tree = searchClear.closest("[data-shop-tree-panel]");
459 if (tree) applyTreeSearch(tree);
460 }, 0);
461 return;
462 }
463 const button = event.target.closest(".dbx-shop-media-pick");
464 if (!button) {
465 return;
466 }
467 event.preventDefault();
468 const panel = button.closest(".dbx-shop-media-manager") || root;
469 openMediaBrowser(panel, button);
470 });
471 }
472
473 dbx.feature.register(LIB, {
474 scope: "element",
475 js: [],
476 init
477 });
478})(window, document);