From 33e6bef2e5965af8f833605f54c91caacfb337d6 Mon Sep 17 00:00:00 2001 From: Philippe Torrel Date: Mon, 20 Jul 2026 14:21:44 +0200 Subject: [PATCH] feat: ProductManager als Modul mit template und JSON --- .../tag26/05_es6-module/assets/js/main.js | 35 ++++ .../assets/js/modules/Example.js | 9 + .../assets/js/modules/examples.js | 21 +++ .../unterricht/tag26/05_es6-module/index.html | 50 ++++++ .../tag26/05_es6-module/package.json | 11 ++ .../06_product-manager-tpl-json/.gitignore | 11 ++ .../assets/css/main.css | 34 ++++ .../assets/js/main.js | 24 +++ .../assets/js/modules/ProductManager.js | 169 ++++++++++++++++++ .../data/products.js | 7 + .../data/products.json | 5 + .../06_product-manager-tpl-json/index.html | 92 ++++++++++ .../06_product-manager-tpl-json/package.json | 11 ++ 13 files changed, 479 insertions(+) create mode 100644 03_dom/unterricht/tag26/05_es6-module/assets/js/main.js create mode 100644 03_dom/unterricht/tag26/05_es6-module/assets/js/modules/Example.js create mode 100644 03_dom/unterricht/tag26/05_es6-module/assets/js/modules/examples.js create mode 100644 03_dom/unterricht/tag26/05_es6-module/index.html create mode 100644 03_dom/unterricht/tag26/05_es6-module/package.json create mode 100644 03_dom/unterricht/tag26/06_product-manager-tpl-json/.gitignore create mode 100644 03_dom/unterricht/tag26/06_product-manager-tpl-json/assets/css/main.css create mode 100644 03_dom/unterricht/tag26/06_product-manager-tpl-json/assets/js/main.js create mode 100644 03_dom/unterricht/tag26/06_product-manager-tpl-json/assets/js/modules/ProductManager.js create mode 100644 03_dom/unterricht/tag26/06_product-manager-tpl-json/data/products.js create mode 100644 03_dom/unterricht/tag26/06_product-manager-tpl-json/data/products.json create mode 100644 03_dom/unterricht/tag26/06_product-manager-tpl-json/index.html create mode 100644 03_dom/unterricht/tag26/06_product-manager-tpl-json/package.json diff --git a/03_dom/unterricht/tag26/05_es6-module/assets/js/main.js b/03_dom/unterricht/tag26/05_es6-module/assets/js/main.js new file mode 100644 index 0000000..94caf9f --- /dev/null +++ b/03_dom/unterricht/tag26/05_es6-module/assets/js/main.js @@ -0,0 +1,35 @@ +// 'use strict'; // - nicht mehr notwendig, da JS über type="module" in den strikten Modus gesetzt wird. + +import EigenerName from './modules/Example.js'; // WICHTIG mit Dateiendung ohne JS-Bundler (esbuild, rollup, browserify, parcel, webpack). Es muss der komplette Pfad (absolut oder relativ) mit Dateiendung angegeben werden. + +import { Example as ExampleRenamed, Example2, TAX_RATE } from './modules/examples.js'; + +import Examples from './modules/examples.js'; + +(() => { + // === DOM & VARS ======= + const DOM = {}; + + // === INIT ============= + const init = () => { + EigenerName(); // => init Example + + ExampleRenamed(); // => Example init aus examples + Example2(); // => Example2 init aus examples + console.log(TAX_RATE); // => 1.19 + + Examples.method(); + Examples.method2(); + + console.log(Examples.eigenschaft); //=> 'Wert' + console.log(Examples.text); // => 'Ich bin ein Text' + }; + + // === EVENTHANDLER ===== + + // === XHR/FETCH ======== + + // === FUNCTIONS ======== + + init(); +})(); diff --git a/03_dom/unterricht/tag26/05_es6-module/assets/js/modules/Example.js b/03_dom/unterricht/tag26/05_es6-module/assets/js/modules/Example.js new file mode 100644 index 0000000..9061616 --- /dev/null +++ b/03_dom/unterricht/tag26/05_es6-module/assets/js/modules/Example.js @@ -0,0 +1,9 @@ +const Example = () => { + console.log('init Example'); +}; + +// Freigabe in ES6 // import Example from './Example.js' +export default Example; + +// CommonJS ("old" Node) -> require('./Example) +// exports.Example = Example; diff --git a/03_dom/unterricht/tag26/05_es6-module/assets/js/modules/examples.js b/03_dom/unterricht/tag26/05_es6-module/assets/js/modules/examples.js new file mode 100644 index 0000000..d839ec5 --- /dev/null +++ b/03_dom/unterricht/tag26/05_es6-module/assets/js/modules/examples.js @@ -0,0 +1,21 @@ +export const Example = () => { + console.log('Example init aus examples'); +}; + +export const Example2 = () => { + console.log('Example2 init aus examples'); +}; + +export const TAX_RATE = 1.19; +export const UPDATE_TODO = 'updateTodo'; + +const text = 'Ich bin ein Text'; + +// Default Objekt als Freigabe +export default { + method: Example, + method2: Example2, + eigenschaft: 'Wert', + // text: text + text, // Kurzschreibweise für text: text +}; diff --git a/03_dom/unterricht/tag26/05_es6-module/index.html b/03_dom/unterricht/tag26/05_es6-module/index.html new file mode 100644 index 0000000..b261638 --- /dev/null +++ b/03_dom/unterricht/tag26/05_es6-module/index.html @@ -0,0 +1,50 @@ + + + + + + Verwendung von Import - Module in (ES6) + + + + + +
+
+

Verwendung von Import - Module in (ES6)

+

+ Die statische + import-Statement Deklaration wird verwendet, um schreibgeschützte, dynamische Bindings zu importieren, die von + einem anderen Modul exportiert werden. Die importierten Bindings werden dynamische Bindings genannt, weil sie + durch das Modul, das das Binding exportiert, aktualisiert werden, aber nicht durch das importierende Modul neu + zugewiesen werden können. +

+

+ Um die import-Deklaration in einer Quelldatei zu verwenden, muss die Datei zur Laufzeit als Modul + interpretiert werden. In HTML geschieht dies, indem type="module" zum + <script>-Tag hinzugefügt wird. Module werden automatisch im Strict Mode interpretiert. +

+ +

+ Es gibt auch eine funktionsähnliche dynamische import(), die keine Skripte des Typs + type="module" erfordert. +

+
+

Um die Modul-Schreibweise in JS zu verwenden, müssen folgende Punkte berücksichtigt werden:

+
    +
  • Script Tag benötigt Modulangabe mit type="module"
  • +
  • + Kommunikation läuft über über das HTTP-Protokoll (HTTP-Kommunikation lokal über Server) +
  • +
  • Eingebundene Module müssen mit Dateiendung (.js) angegeben werden.
  • +
+
+
+ + + diff --git a/03_dom/unterricht/tag26/05_es6-module/package.json b/03_dom/unterricht/tag26/05_es6-module/package.json new file mode 100644 index 0000000..dd68fb5 --- /dev/null +++ b/03_dom/unterricht/tag26/05_es6-module/package.json @@ -0,0 +1,11 @@ +{ + "name": "05_es6-module", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "npx http-server -c-1 -p 3000" + }, + "keywords": [], + "type": "commonjs" +} diff --git a/03_dom/unterricht/tag26/06_product-manager-tpl-json/.gitignore b/03_dom/unterricht/tag26/06_product-manager-tpl-json/.gitignore new file mode 100644 index 0000000..4073eac --- /dev/null +++ b/03_dom/unterricht/tag26/06_product-manager-tpl-json/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +dist/ +.env +.env.local +.env.*.local +.DS_Store +*.log +.vite/ +coverage/ +.idea/ +# .vscode/ diff --git a/03_dom/unterricht/tag26/06_product-manager-tpl-json/assets/css/main.css b/03_dom/unterricht/tag26/06_product-manager-tpl-json/assets/css/main.css new file mode 100644 index 0000000..1a93676 --- /dev/null +++ b/03_dom/unterricht/tag26/06_product-manager-tpl-json/assets/css/main.css @@ -0,0 +1,34 @@ +*, +html { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + height: 100%; +} + +body { + background-color: #efefef; +} + +/* Mobile first */ +.product-manager table tfoot .row > * { + margin-bottom: 1rem; +} + +@media (min-width: 992px) { + .product-manager table tfoot .row > * { + margin-bottom: 0; + } +} + +.product-manager table .th-actions { + width: 150px; +} + +.product-manager table tbody td button { + margin: 3px; +} diff --git a/03_dom/unterricht/tag26/06_product-manager-tpl-json/assets/js/main.js b/03_dom/unterricht/tag26/06_product-manager-tpl-json/assets/js/main.js new file mode 100644 index 0000000..b9b9a5f --- /dev/null +++ b/03_dom/unterricht/tag26/06_product-manager-tpl-json/assets/js/main.js @@ -0,0 +1,24 @@ +import ProductManager from './modules/ProductManager.js'; + +(() => { + // === DOM & VARS ======= + const DOM = { + productManagers: Array.from(document.querySelectorAll('.product-manager')), + }; + + // === INIT ============= + const init = () => { + DOM.productManagers.forEach((el) => { + const pm = ProductManager(el); + // pm.init() + }); + }; + + // === EVENTHANDLER ===== + + // === XHR/FETCH ======== + + // === FUNCTIONS ======== + + init(); +})(); diff --git a/03_dom/unterricht/tag26/06_product-manager-tpl-json/assets/js/modules/ProductManager.js b/03_dom/unterricht/tag26/06_product-manager-tpl-json/assets/js/modules/ProductManager.js new file mode 100644 index 0000000..5f742b5 --- /dev/null +++ b/03_dom/unterricht/tag26/06_product-manager-tpl-json/assets/js/modules/ProductManager.js @@ -0,0 +1,169 @@ +// Factory Function +const ProductManager = (el = null) => { + // === DOM & VARS ======= + + 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'), + }; + + 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); + }; + + // === EVENTHANDLER ===== + const onClickAdd = (e) => { + console.log('click'); + + const product = { + name: DOM.inputName.value, + price: Number(DOM.inputPrice.value), + }; + + addProduct(product); + + disableNonFunctionalButtons(); + }; + + 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(); + }; + + // === XHR/FETCH ======== + const fetchProducts = async () => { + try { + const response = await fetch('data/products.json'); // 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 = () => { + fetchProducts().then((products) => { + products.forEach((product) => { + addProduct(product); + }); + disableNonFunctionalButtons(); + }); + + // PRODUCTS.forEach((product) => { + // addProduct(product); + // }); + //disableNonFunctionalButtons(); + // PRODUCTS.forEach(addProduct); + }; + + const addProduct = (product) => { + const { name, price } = product; + + // + 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'); + + tdNameEl.textContent = name; + tdPriceEl.textContent = price; + + btnRemoveEl.addEventListener('click', onClickRemove); + btnMoveUpEl.addEventListener('click', onClickMoveUp); + btnMoveDownEl.addEventListener('click', onClickMoveDown); + + DOM.tBody.appendChild(trEl); // Im DOM hinzufügen + }; + + 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'); + + 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 { + init, + }; +}; + +export default ProductManager; diff --git a/03_dom/unterricht/tag26/06_product-manager-tpl-json/data/products.js b/03_dom/unterricht/tag26/06_product-manager-tpl-json/data/products.js new file mode 100644 index 0000000..940c04c --- /dev/null +++ b/03_dom/unterricht/tag26/06_product-manager-tpl-json/data/products.js @@ -0,0 +1,7 @@ +// Konfigurationsvariable +const PRODUCTS = [ + { name: '3Doodler 3D Printing Pen', price: 29.99 }, + { name: 'Powerstation 5- E. Maximus Chargus', price: 44.95 }, + { name: '8-Bit Legendary Hero Heat-Change Mug', price: 6.99 }, + { name: '16-Bit Legendary Hero Heat-Change Mug', price: 10.99 }, +]; diff --git a/03_dom/unterricht/tag26/06_product-manager-tpl-json/data/products.json b/03_dom/unterricht/tag26/06_product-manager-tpl-json/data/products.json new file mode 100644 index 0000000..67e8369 --- /dev/null +++ b/03_dom/unterricht/tag26/06_product-manager-tpl-json/data/products.json @@ -0,0 +1,5 @@ +[ + { "name": "3Doodler 3D Printing Pen", "price": 29.99 }, + { "name": "Powerstation 5- E. Maximus Chargus", "price": 44.95 }, + { "name": "8-Bit Legendary Hero Heat-Change Mug", "price": 6.99 } +] diff --git a/03_dom/unterricht/tag26/06_product-manager-tpl-json/index.html b/03_dom/unterricht/tag26/06_product-manager-tpl-json/index.html new file mode 100644 index 0000000..18884b0 --- /dev/null +++ b/03_dom/unterricht/tag26/06_product-manager-tpl-json/index.html @@ -0,0 +1,92 @@ + + + + + + Product Manager + + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
NamePrice in €Actions
+
+
+ +
+ +
+
+ + +
+
+ +
+ +
+
+ +
+
+ + +
+ +
+ + diff --git a/03_dom/unterricht/tag26/06_product-manager-tpl-json/package.json b/03_dom/unterricht/tag26/06_product-manager-tpl-json/package.json new file mode 100644 index 0000000..af9677e --- /dev/null +++ b/03_dom/unterricht/tag26/06_product-manager-tpl-json/package.json @@ -0,0 +1,11 @@ +{ + "name": "06_product-manager-tpl-json", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "npx http-server -c-1 -p 3000" + }, + "keywords": [], + "type": "module" +}