added prototype

This commit is contained in:
Philippe Torrel
2026-08-03 14:46:13 +02:00
parent 831644667e
commit 44428b0495
310 changed files with 49133 additions and 93 deletions

View File

@@ -1,6 +1,6 @@
import { Modal } from 'bootstrap'; // ohne Pfadangabe (Modul auslesen aus node_modules) funktioniert nur mit JS-Bundler (esbuild, rollup & co.)
import Toast from './Toast';
import { querySelectorTyped } from '../utils/helpers';
import { querySelectorTyped, parents } from '../utils/helpers';
// import * as bootstrap from 'bootstrap';
// import 'bootstrap';
@@ -15,21 +15,28 @@ interface DOM {
inputName: HTMLInputElement;
inputPrice: HTMLInputElement;
btnAdd: HTMLButtonElement;
templateRow: HTMLTableRowElement;
templateRow: HTMLTemplateElement;
btnSave: HTMLButtonElement;
btnReset: HTMLButtonElement;
modalEdit: HTMLElement;
modalEdit: HTMLDivElement;
formEdit: HTMLFormElement;
inputEditName: HTMLInputElement;
inputEditPrice: HTMLInputElement;
inputEditId: HTMLInputElement;
inputEditPosition: HTMLInputElement;
btnUpdate: HTMLButtonElement;
modalDelete: HTMLElement;
productName: HTMLElement;
modalDelete: HTMLDivElement;
productName: HTMLDivElement;
btnConfirmDelete: HTMLButtonElement;
}
interface Product {
_id?: string;
name: string;
price: number;
position?: number;
}
// Factory Function
const ProductManager = (el?: HTMLElement) => {
// === DOM & VARS =======
@@ -47,14 +54,14 @@ const ProductManager = (el?: HTMLElement) => {
inputName: querySelectorTyped<HTMLInputElement>('.input-product-name', module),
inputPrice: querySelectorTyped<HTMLInputElement>('.input-product-price', module),
btnAdd: querySelectorTyped<HTMLButtonElement>('.button-product-add', module),
templateRow: querySelectorTyped<HTMLTableRowElement>('.template-row', module),
templateRow: querySelectorTyped<HTMLTemplateElement>('.template-row', module),
// Nav Actions
btnSave: querySelectorTyped<HTMLButtonElement>('.button-products-save', module),
btnReset: querySelectorTyped<HTMLButtonElement>('.button-products-reset', module),
// Modal Edit
modalEdit: querySelectorTyped<HTMLElement>('.modal-edit', module),
modalEdit: querySelectorTyped<HTMLDivElement>('.modal-edit', module),
formEdit: querySelectorTyped<HTMLFormElement>('.form-product-edit', module),
inputEditName: querySelectorTyped<HTMLInputElement>('.input-edit-name', module),
inputEditPrice: querySelectorTyped<HTMLInputElement>('.input-edit-price', module),
@@ -63,8 +70,8 @@ const ProductManager = (el?: HTMLElement) => {
btnUpdate: querySelectorTyped<HTMLButtonElement>('.button-product-update', module),
// Modal Delete
modalDelete: querySelectorTyped<HTMLElement>('.modal-delete', module),
productName: querySelectorTyped<HTMLElement>('strong.product-name', module),
modalDelete: querySelectorTyped<HTMLDivElement>('.modal-delete', module),
productName: querySelectorTyped<HTMLDivElement>('strong.product-name', module),
btnConfirmDelete: querySelectorTyped<HTMLButtonElement>('.button-confirm-delete', module),
};
@@ -187,9 +194,9 @@ const ProductManager = (el?: HTMLElement) => {
};
const onClickEdit = (e: PointerEvent) => {
const btnEl = e.currentTarget;
const btnEl = e.currentTarget as HTMLButtonElement;
const currentRow = parents(btnEl, 'tr')[0];
const id = currentRow.dataset.id;
const id: string = String(currentRow.dataset.id);
// async fetch
getProduct(id).then((data) => {
@@ -199,11 +206,11 @@ const ProductManager = (el?: HTMLElement) => {
};
const onClickRemove = (e: PointerEvent) => {
const btnEl = e.currentTarget;
const btnEl = e.currentTarget as HTMLButtonElement;
const currentRow = parents(btnEl, 'tr')[0];
const id = currentRow.dataset.id;
const productName = currentRow.querySelector('.td-name').textContent.trim();
const productName = querySelectorTyped<HTMLTableCellElement>('.td-name', currentRow).textContent.trim();
DOM.btnConfirmDelete.dataset.id = id;
DOM.productName.textContent = productName;
@@ -223,7 +230,7 @@ const ProductManager = (el?: HTMLElement) => {
const onClickConfirmDelete = (e: PointerEvent) => {
const btnEl = e.currentTarget as HTMLButtonElement;
const id = btnEl.dataset.id;
const id: string = String(btnEl.dataset.id);
// async fetch
deleteProduct(id).then((data) => {
@@ -239,7 +246,7 @@ const ProductManager = (el?: HTMLElement) => {
};
const onClickMoveUp = (e: PointerEvent) => {
const btnEl = e.currentTarget;
const btnEl = e.currentTarget as HTMLButtonElement;
// const currentRowEl = btnEl.parentNode.parentNode; // aktuelle Zeile (tr) mit dem Button
const currentRowEl = parents(btnEl, 'tr')[0];
@@ -247,14 +254,14 @@ const ProductManager = (el?: HTMLElement) => {
// DOM.tBody.insertBefore(currentRowEl, currentRowEl.previousElementSibling);
// targetElement.before(referenceElement)
currentRowEl.previousElementSibling.before(currentRowEl);
currentRowEl.previousElementSibling!.before(currentRowEl);
console.log('move up');
disableNonFunctionalButtons();
};
const onClickMoveDown = (e: PointerEvent) => {
const btnEl = e.currentTarget;
const btnEl = e.currentTarget as HTMLButtonElement;
// const currentRowEl = btnEl.parentNode.parentNode; // aktuelle Zeile (tr) mit dem Button
const currentRowEl = parents(btnEl, 'tr')[0];
@@ -262,7 +269,7 @@ const ProductManager = (el?: HTMLElement) => {
// DOM.tBody.insertBefore(currentRowEl, currentRowEl.nextElementSibling.nextElementSibling);
// targetElement.after(referenceElement)
currentRowEl.nextElementSibling.after(currentRowEl);
currentRowEl.nextElementSibling!.after(currentRowEl);
console.log('move down');
disableNonFunctionalButtons();
@@ -270,13 +277,13 @@ const ProductManager = (el?: HTMLElement) => {
// Drag 'n Drop EventHandler
const onMouseDownDrag = (e: MouseEvent) => {
const btnEl = e.currentTarget;
const btnEl = e.currentTarget as HTMLButtonElement;
const currentRowEl = parents(btnEl, 'tr')[0];
currentRowEl.draggable = true;
};
const onMouseUpDrag = (e: MouseEvent) => {
const btnEl = e.currentTarget;
const btnEl = e.currentTarget as HTMLButtonElement;
const currentRowEl = parents(btnEl, 'tr')[0];
currentRowEl.draggable = false;
};
@@ -369,8 +376,12 @@ const ProductManager = (el?: HTMLElement) => {
const data = await response.json(); // HTTP-Response
return data;
} catch (error) {
Toast(error, 'error').show();
} catch (error: unknown) {
if (error instanceof Error) {
Toast(error.message, 'error').show();
} else {
Toast('An unknown error occurred.', 'error').show();
}
console.error(error);
return error;
@@ -380,7 +391,7 @@ const ProductManager = (el?: HTMLElement) => {
// HTTP - Methode - POST
// (C)RUD - create
// BRE(A)D - add - neues Produkt hinzufügen
const createProduct = async (product) => {
const createProduct = async (product: Product) => {
try {
const response = await fetch(`${BASE_URL}/api/products`, {
method: 'POST',
@@ -418,8 +429,12 @@ const ProductManager = (el?: HTMLElement) => {
const data = await response.json(); // HTTP-Response
return data;
} catch (error) {
console.error(error);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
} else {
console.error('An unknown error occurred.');
}
return error;
}
};
@@ -465,8 +480,12 @@ const ProductManager = (el?: HTMLElement) => {
const data = await response.json(); // HTTP-Response
return data;
} catch (error) {
console.error(error);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
} else {
console.error('An unknown error occurred.');
}
return error;
}
};
@@ -497,8 +516,8 @@ const ProductManager = (el?: HTMLElement) => {
// === FUNCTIONS ========
const initProducts = () => {
getProducts().then((products) => {
products.forEach((product) => {
getProducts().then((products: Product[]) => {
products.forEach((product: Product) => {
addProduct(product);
});
Toast('Init products').show();
@@ -514,31 +533,31 @@ const ProductManager = (el?: HTMLElement) => {
const loadProducts = () => {
DOM.tBody.innerHTML = '';
getProducts().then((data) => {
data.forEach((product) => {
getProducts().then((data: Product[]) => {
data.forEach((product: Product) => {
addProduct(product);
});
disableNonFunctionalButtons();
});
};
const addProduct = (product) => {
const addProduct = (product: 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 trEl = DOM.templateRow.content.firstElementChild?.cloneNode(true) as HTMLTableRowElement;
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 tdNameEl = querySelectorTyped<HTMLTableCellElement>('.td-name', trEl);
const tdPriceEl = querySelectorTyped<HTMLTableCellElement>('.td-price', trEl);
const btnRemoveEl = querySelectorTyped<HTMLButtonElement>('.button-product-remove', trEl);
const btnMoveUpEl = querySelectorTyped<HTMLButtonElement>('.button-product-move-up', trEl);
const btnMoveDownEl = querySelectorTyped<HTMLButtonElement>('.button-product-move-down', trEl);
const btnDragEl = trEl.querySelector('.button-drag');
const btnEditEl = trEl.querySelector('.button-product-edit');
const btnDragEl = querySelectorTyped<HTMLButtonElement>('.button-drag', trEl);
const btnEditEl = querySelectorTyped<HTMLButtonElement>('.button-product-edit', trEl);
tdNameEl.textContent = name;
tdPriceEl.textContent = price;
tdNameEl.textContent = String(name);
tdPriceEl.textContent = String(price);
btnEditEl.addEventListener('click', onClickEdit);
btnRemoveEl.addEventListener('click', onClickRemove);
@@ -554,19 +573,19 @@ const ProductManager = (el?: HTMLElement) => {
trEl.addEventListener('drop', onDrop);
trEl.dataset.id = id; // <tr data-id="..." >...</tr>
trEl.dataset.position = position; // <tr data-position="..." >...</tr>
trEl.dataset.position = String(position); // <tr data-position="..." >...</tr>
DOM.tBody.appendChild(trEl); // Im DOM hinzufügen
};
const showModalEdit = (product) => {
const showModalEdit = (product: Product) => {
const { name = '', price = 0, _id: id, position } = product;
DOM.inputEditName.value = name;
DOM.inputEditPrice.value = price;
DOM.inputEditPrice.value = String(price);
DOM.inputEditId.value = id;
DOM.inputEditPosition.value = position;
DOM.inputEditId.value = String(id);
DOM.inputEditPosition.value = String(position);
};
const resetFields = () => {
@@ -579,23 +598,14 @@ const ProductManager = (el?: HTMLElement) => {
const disableNonFunctionalButtons = () => {
Array.from(DOM.tBody.querySelectorAll('tr')).forEach((tr) => {
const btnMoveUp = tr.querySelector('.button-product-move-up');
const btnMoveDown = tr.querySelector('.button-product-move-down');
const btnMoveUp = querySelectorTyped<HTMLButtonElement>('.button-product-move-up', tr);
const btnMoveDown = querySelectorTyped<HTMLButtonElement>('.button-product-move-down', tr);
btnMoveUp.disabled = !tr.previousElementSibling;
btnMoveDown.disabled = !tr.nextElementSibling;
});
};
// Helferfunktion aus jQuery (youmightnotjquery)
const parents = (el, selector) => {
const parents = [];
while ((el = el.parentNode) && el !== document) {
if (!selector || el.matches(selector)) parents.push(el);
}
return parents;
};
init();
return {

View File

@@ -9,3 +9,13 @@ export const querySelectorTyped = <T extends HTMLElement>(
}
return element;
};
// Helferfunktion aus jQuery (youmightnotjquery)
export const parents = (el: HTMLElement, selector?: string): HTMLElement[] => {
const parents: HTMLElement[] = [];
let currentEl: HTMLElement | null = el;
while ((currentEl = currentEl.parentElement) && currentEl !== null && currentEl !== document.documentElement) {
if (!selector || currentEl.matches(selector)) parents.push(currentEl as HTMLElement);
}
return parents;
};