import { Modal } from 'bootstrap'; // ohne Pfadangabe (Modul auslesen aus node_modules) funktioniert nur mit JS-Bundler (esbuild, rollup & co.) // import 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js'; // würde auch ohne Bundler funktionieren // import '../../../node_modules/bootstrap/dist/js/bootstrap.esm.js'; // würde auch ohne Bundler funktionieren // Factory Function const ProductManager = (el = null) => { // === DOM & VARS ======= const BASE_URL = 'http://127.0.0.1:8000'; const module = el || document.querySelector('.product-manager') || console.error('Product Manager not found'); if (!module) return; const DOM = { module, table: module.querySelector('.table-products'), tBody: module.querySelector('tbody'), inputName: module.querySelector('.input-product-name'), inputPrice: module.querySelector('.input-product-price'), btnAdd: module.querySelector('.button-product-add'), templateRow: module.querySelector('.template-row'), // Modal modalEdit: module.querySelector('.modal-edit'), formEdit: module.querySelector('.form-product-edit'), inputEditName: module.querySelector('.input-edit-name'), inputEditPrice: module.querySelector('.input-edit-price'), inputEditId: module.querySelector('.input-edit-id'), inputEditPosition: module.querySelector('.input-edit-position'), }; const bsModalEdit = new Modal(DOM.modalEdit, { backdrop: 'static', // true keyboard: true, }); console.log(DOM); // === INIT ============= const init = () => { console.log('init'); // Funktionaufrufe zu beginn der Anwendung initProducts(); // Event-Lauscher zu beginn der Anwendung DOM.btnAdd.addEventListener('click', onClickAdd); DOM.formEdit.addEventListener('submit', onSubmitEdit); }; // === EVENTHANDLER ===== const onSubmitEdit = (e) => { e.preventDefault(); // Standardverhalten unterbinden (Formular nicht an action versenden) const product = { name: DOM.inputEditName.value, price: Number(DOM.inputEditPrice.value), _id: DOM.inputEditId.value, position: DOM.inputEditPosition.value, }; // update async process updateProduct(product).then((data) => { console.log(data); if (data.success) { loadProducts(); // All Produkte auslesen bsModalEdit.hide(); } }); }; const onClickAdd = (e) => { console.log('click'); const product = { name: DOM.inputName.value, price: Number(DOM.inputPrice.value), }; createProduct(product).then((data) => { console.log(data); if (data.success) { console.log('add'); loadProducts(); } }); // addProduct(product); disableNonFunctionalButtons(); }; const onClickEdit = (e) => { const btnEl = e.currentTarget; const currentRow = parents(btnEl, 'tr')[0]; const id = currentRow.dataset.id; // async fetch getProduct(id).then((data) => { // console.log(data); showModalEdit(data); }); }; const onClickRemove = (e) => { const btnEl = e.currentTarget; //const currentRowEl = btnEl.parentNode.parentNode; // parentNode -> td - parentNode -> tr // MDN https://developer.mozilla.org/de/docs/Web/API/Element/closest //const currentRowEl = btnEl.closest('tr'); const currentRowEl = parents(btnEl, 'tr')[0]; currentRowEl.remove(); // kein IE11 support // DOM.tBody.removeChild(currentRowEl); disableNonFunctionalButtons(); }; const onClickMoveUp = (e) => { const btnEl = e.currentTarget; // const currentRowEl = btnEl.parentNode.parentNode; // aktuelle Zeile (tr) mit dem Button const currentRowEl = parents(btnEl, 'tr')[0]; // ParentNode.insertBefore(referenceElement, targetElement) // DOM.tBody.insertBefore(currentRowEl, currentRowEl.previousElementSibling); // targetElement.before(referenceElement) currentRowEl.previousElementSibling.before(currentRowEl); console.log('move up'); disableNonFunctionalButtons(); }; const onClickMoveDown = (e) => { const btnEl = e.currentTarget; // const currentRowEl = btnEl.parentNode.parentNode; // aktuelle Zeile (tr) mit dem Button const currentRowEl = parents(btnEl, 'tr')[0]; // ParentNode.insertBefore(referenceElement, targetElement) // DOM.tBody.insertBefore(currentRowEl, currentRowEl.nextElementSibling.nextElementSibling); // targetElement.after(referenceElement) currentRowEl.nextElementSibling.after(currentRowEl); console.log('move down'); disableNonFunctionalButtons(); }; // Drag 'n Drop EventHandler const onMouseDownDrag = (e) => { const btnEl = e.currentTarget; const currentRowEl = parents(btnEl, 'tr')[0]; currentRowEl.draggable = true; }; const onMouseUpDrag = (e) => { const btnEl = e.currentTarget; const currentRowEl = parents(btnEl, 'tr')[0]; currentRowEl.draggable = false; }; const onDragStart = (e) => { console.log('DragEvent: ', e); const currentRowEl = e.currentTarget; // tr <- aktuelle Zeie, die bewegt wird const idx = [...currentRowEl.parentNode.children].indexOf(currentRowEl); e.dataTransfer.setData('text/plain', idx); // e.dataTransfer.setData('application/json', JSON.stringify({idx, name: sourceElement})); console.log('tr index:', idx); }; const onDragOver = (e) => { e.preventDefault(); // WICHTIG! Sonst funktioniert drop EventHandler nicht. }; const onDrop = (e) => { const currentRowEl = e.currentTarget; // tr <- Zielzeile, auf die gedroppt wird. const targetIdx = [...currentRowEl.parentNode.children].indexOf(currentRowEl); const sourceIdx = parseInt(e.dataTransfer.getData('text/plain')); // const { sourceIdx } = JSON.parse(e.dataTransfer.getData('application/json')); console.log({ targetIdx, sourceIdx }); // Guard wenn Element auf sich selbst losgelassen wurde, abbruch if (sourceIdx === targetIdx) { return; } const draggedEl = DOM.tBody.querySelector(`tr:nth-child(${sourceIdx + 1})`); // const draggedEl = DOM.tBody.children[sourceIdx] // Verschiebe das Element im DOM if (sourceIdx > targetIdx) { currentRowEl.before(draggedEl); } else { currentRowEl.after(draggedEl); } disableNonFunctionalButtons(); }; // === XHR/FETCH ======== // HTTP - Methode - POST // (C)RUD - create // BRE(A)D - add - neues Produkt hinzufügen const createProduct = async (product) => { try { const response = await fetch(`${BASE_URL}/api/products`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(product), }); // HTTP Request URL if (!response.ok) { throw new Error('Fetch went wrong.'); } const data = await response.json(); // HTTP-Response return data; } catch (error) { console.error(error); return error; } }; // HTTP - Methode - GET // C(R)UD - Read // (B)READ - Browse - auslesen aller Produkte const getProducts = async () => { try { const response = await fetch(`${BASE_URL}/api/products`); // HTTP Request URL if (!response.ok) { throw new Error('Fetch went wrong.'); } const data = await response.json(); // HTTP-Response return data; } catch (error) { console.error(error); return error; } }; // HTTP - Methode - GET // C(R)UD - Read // B(R)EAD - Read - auslesen eines Produkts const getProduct = async (id) => { try { const response = await fetch(`${BASE_URL}/api/products/${id}`); // HTTP Request URL if (!response.ok) { throw new Error('Fetch went wrong.'); } const data = await response.json(); // HTTP-Response return data; } catch (error) { console.error(error); return error; } }; // HTTP - Methode - PUT // CR(U)D - update // BR(E)AD - edit - Produkt aktualisieren const updateProduct = async (product) => { try { const response = await fetch(`${BASE_URL}/api/products`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(product), }); // HTTP Request URL if (!response.ok) { throw new Error('Fetch went wrong.'); } const data = await response.json(); // HTTP-Response return data; } catch (error) { console.error(error); return error; } }; // === FUNCTIONS ======== const initProducts = () => { getProducts().then((products) => { products.forEach((product) => { addProduct(product); }); disableNonFunctionalButtons(); }); // PRODUCTS.forEach((product) => { // addProduct(product); // }); //disableNonFunctionalButtons(); // PRODUCTS.forEach(addProduct); }; const loadProducts = () => { DOM.tBody.innerHTML = ''; getProducts().then((data) => { data.forEach((product) => { addProduct(product); }); disableNonFunctionalButtons(); }); }; const addProduct = (product) => { const { name, price, _id: id, position } = product; // https://developer.mozilla.org/de/docs/Web/API/HTMLTemplateElement/content const trEl = DOM.templateRow.content.firstElementChild.cloneNode(true); const tdNameEl = trEl.querySelector('.td-name'); const tdPriceEl = trEl.querySelector('.td-price'); const btnRemoveEl = trEl.querySelector('.button-product-remove'); const btnMoveUpEl = trEl.querySelector('.button-product-move-up'); const btnMoveDownEl = trEl.querySelector('.button-product-move-down'); const btnDragEl = trEl.querySelector('.button-drag'); const btnEditEl = trEl.querySelector('.button-product-edit'); tdNameEl.textContent = name; tdPriceEl.textContent = price; btnEditEl.addEventListener('click', onClickEdit); btnRemoveEl.addEventListener('click', onClickRemove); btnMoveUpEl.addEventListener('click', onClickMoveUp); btnMoveDownEl.addEventListener('click', onClickMoveDown); // Drag 'n Drop Events btnDragEl.addEventListener('mousedown', onMouseDownDrag); btnDragEl.addEventListener('mouseup', onMouseUpDrag); trEl.addEventListener('dragstart', onDragStart); trEl.addEventListener('dragover', onDragOver); trEl.addEventListener('drop', onDrop); trEl.dataset.id = id; //