From ebe9ab0e34cc666ef4949625357245a777b6915a Mon Sep 17 00:00:00 2001 From: Philippe Torrel Date: Thu, 8 Oct 2026 14:26:06 +0200 Subject: [PATCH] --- .../assets/css/main.css | 40 +++ .../assets/js/main.js | 264 ++++++++++++++++++ .../data/products.js | 5 + .../data/products.json | 5 + .../02_product-manager-tpl-json/index.html | 90 ++++++ .../02_product-manager-tpl-json/package.json | 11 + .../03_outer-html-vs-inner-html/index.html | 29 ++ .../tag24/04_es6-module/assets/js/main.js | 39 +++ .../assets/js/modules/Example.js | 9 + .../assets/js/modules/examples.js | 21 ++ .../unterricht/tag24/04_es6-module/index.html | 52 ++++ .../tag24/04_es6-module/package-lock.json | 21 ++ .../tag24/04_es6-module/package.json | 14 + .../assets/css/main.css | 40 +++ .../assets/js/main.js | 23 ++ .../assets/js/modules/ProductManager.js | 160 +++++++++++ .../assets/js/utils/helpers.js | 32 +++ .../data/products.js | 5 + .../data/products.json | 5 + .../05_product-manager-tpl-json/index.html | 90 ++++++ .../05_product-manager-tpl-json/package.json | 11 + .../unterricht/tag24/06_drag-drop/index.html | 105 +++++++ .../assets/css/main.css | 43 +++ .../assets/js/main.js | 23 ++ .../assets/js/modules/ProductManager.js | 163 +++++++++++ .../assets/js/utils/helpers.js | 32 +++ .../07_product-manager-final/data/products.js | 5 + .../data/products.json | 5 + .../tag24/07_product-manager-final/index.html | 97 +++++++ .../07_product-manager-final/package.json | 11 + 30 files changed, 1450 insertions(+) create mode 100644 03_js-dom/unterricht/tag24/02_product-manager-tpl-json/assets/css/main.css create mode 100644 03_js-dom/unterricht/tag24/02_product-manager-tpl-json/assets/js/main.js create mode 100644 03_js-dom/unterricht/tag24/02_product-manager-tpl-json/data/products.js create mode 100644 03_js-dom/unterricht/tag24/02_product-manager-tpl-json/data/products.json create mode 100644 03_js-dom/unterricht/tag24/02_product-manager-tpl-json/index.html create mode 100644 03_js-dom/unterricht/tag24/02_product-manager-tpl-json/package.json create mode 100644 03_js-dom/unterricht/tag24/03_outer-html-vs-inner-html/index.html create mode 100644 03_js-dom/unterricht/tag24/04_es6-module/assets/js/main.js create mode 100644 03_js-dom/unterricht/tag24/04_es6-module/assets/js/modules/Example.js create mode 100644 03_js-dom/unterricht/tag24/04_es6-module/assets/js/modules/examples.js create mode 100644 03_js-dom/unterricht/tag24/04_es6-module/index.html create mode 100644 03_js-dom/unterricht/tag24/04_es6-module/package-lock.json create mode 100644 03_js-dom/unterricht/tag24/04_es6-module/package.json create mode 100644 03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/css/main.css create mode 100644 03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/js/main.js create mode 100644 03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/js/modules/ProductManager.js create mode 100644 03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/js/utils/helpers.js create mode 100644 03_js-dom/unterricht/tag24/05_product-manager-tpl-json/data/products.js create mode 100644 03_js-dom/unterricht/tag24/05_product-manager-tpl-json/data/products.json create mode 100644 03_js-dom/unterricht/tag24/05_product-manager-tpl-json/index.html create mode 100644 03_js-dom/unterricht/tag24/05_product-manager-tpl-json/package.json create mode 100644 03_js-dom/unterricht/tag24/06_drag-drop/index.html create mode 100644 03_js-dom/unterricht/tag24/07_product-manager-final/assets/css/main.css create mode 100644 03_js-dom/unterricht/tag24/07_product-manager-final/assets/js/main.js create mode 100644 03_js-dom/unterricht/tag24/07_product-manager-final/assets/js/modules/ProductManager.js create mode 100644 03_js-dom/unterricht/tag24/07_product-manager-final/assets/js/utils/helpers.js create mode 100644 03_js-dom/unterricht/tag24/07_product-manager-final/data/products.js create mode 100644 03_js-dom/unterricht/tag24/07_product-manager-final/data/products.json create mode 100644 03_js-dom/unterricht/tag24/07_product-manager-final/index.html create mode 100644 03_js-dom/unterricht/tag24/07_product-manager-final/package.json diff --git a/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/assets/css/main.css b/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/assets/css/main.css new file mode 100644 index 0000000..00febcc --- /dev/null +++ b/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/assets/css/main.css @@ -0,0 +1,40 @@ +*, +html { + box-sizing: border-box; +} + +html, +body { + padding: 0; + margin: 0; + height: 100%; +} + +/* Mobile first */ +.product-manager table tfoot .row > * { + margin-bottom: 0.5rem; +} + +@media (min-width: 992px) { + .product-manager table tfoot .row > * { + margin-bottom: 0; + } +} + +.product-manager table td { + vertical-align: middle; +} + +.product-manager table tbody:empty::after { + content: 'loading products...'; + color: #999; + padding: 1rem; +} + +.product-manager table td button { + margin: 0 3px; +} + +.product-manager table .th-actions { + width: 185px; +} diff --git a/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/assets/js/main.js b/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/assets/js/main.js new file mode 100644 index 0000000..2865d84 --- /dev/null +++ b/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/assets/js/main.js @@ -0,0 +1,264 @@ +'use strict'; + +(() => { + // === DOM & VARS ======= + + const module = document.querySelector('.product-manager') || console.warn('Product Manager Element 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 = () => { + // Funktionsaufrufe zu beginn der Anwendung + initProducts(); + // Event-Lauscher zu beginn der Anwendung + DOM.btnAdd.addEventListener('click', onClickAdd); + }; + + // === EVENTHANDLER ===== + const onClickAdd = (e) => { + const product = { + name: DOM.inputName.value, // + price: Number(DOM.inputPrice.valueAsNumber), + }; + + addProduct(product); + disableNonFunctionalButtons(); + }; + + const onClickRemove = (e) => { + const btn = e.currentTarget; + const currentRow = parents(btn, 'tr')[0]; // btn.closest('tr'); // btn.parentNode.parentNode; + + currentRow.remove(); + // currentRow.parentNode.removeChild(currentRow); // IE11 support + disableNonFunctionalButtons(); + }; + + const onClickMoveUp = (e) => { + const btn = e.currentTarget; + const currentRow = parents(btn, 'tr')[0]; + DOM.tbody.insertBefore(currentRow, currentRow.previousElementSibling); + disableNonFunctionalButtons(); + }; + + const onClickMoveDown = (e) => { + const btn = e.currentTarget; + const currentRow = parents(btn, 'tr')[0]; + DOM.tbody.insertBefore(currentRow, currentRow.nextElementSibling.nextElementSibling); + 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-Body in JS-Objekt geparsed -> Promise + + return data; + } catch (error) { + console.error(error); + throw error; + } + }; + // === FUNCTIONS ======== + const initProducts = () => { + fetchProducts().then((products) => { + products.forEach(addProduct); + disableNonFunctionalButtons(); + }); + + // PRODUCTS.forEach((product) => { + // addProduct(product); + // }); + // PRODUCTS.forEach(addProduct); + // disableNonFunctionalButtons(); + }; + + const addProduct = (product) => { + const { name, price } = product; + + // https://developer.mozilla.org/de/docs/Web/HTML/Reference/Elements/template#anwendungshinweise + 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 = Number(price).toFixed(2); + btnRemoveEl.addEventListener('click', onClickRemove); + btnMoveUpEl.addEventListener('click', onClickMoveUp); + btnMoveDownEl.addEventListener('click', onClickMoveDown); + + // const tdNameEl = createTd(name, 'td-name'); + // const tdPriceEl = createTd(price, 'td-price'); + // const tdActionsEl = createTdWithActions(); + + // const trEl = createTr(tdNameEl, tdPriceEl, tdActionsEl); + + DOM.tbody.appendChild(trEl); + }; + + // const createTdWithActions = () => { + // const td = document.createElement('td'); + // td.classList.add('td-actions'); + + // const btnRemove = createBtn({ + // classNames: ['btn-danger', 'button-product-remove'], + // iconName: 'trash-can', + // label: 'Remove product', + // clickHandler: onClickRemove, + // }); + // const btnMoveUp = createBtn({ + // classNames: ['btn-secondary', 'button-product-move-up'], + // iconName: 'chevron-up', + // label: 'Move up', + // clickHandler: onClickMoveUp, + // }); + // const btnMoveDown = createBtn({ + // classNames: ['btn-secondary', 'button-product-move-down'], + // iconName: 'chevron-down', + // label: 'Move down', + // clickHandler: onClickMoveDown, + // }); + + // td.append(btnRemove, btnMoveUp, btnMoveDown); + + // return td; + // }; + + // const createBtnRemove = () => { + // const btn = document.createElement('button'); + // const icon = document.createElement('i'); + // const span = document.createElement('span'); + + // btn.classList.add('btn', 'btn-danger', 'button-product-remove'); + // icon.classList.add('fas', 'fa-trash-can'); + // span.classList.add('visually-hidden'); + // span.textContent = 'Remove Product'; + + // btn.addEventListener('click', onClickRemove); + + // btn.appendChild(icon); + // btn.appendChild(span); + + // return btn; + // }; + + // const createBtnMoveUp = () => { + // const btn = document.createElement('button'); + // const icon = document.createElement('i'); + // const span = document.createElement('span'); + + // btn.classList.add('btn', 'btn-secondary', 'button-product-move-up'); + // icon.classList.add('fas', 'fa-chevron-up'); + // span.classList.add('visually-hidden'); + // span.textContent = 'Move up'; + + // btn.addEventListener('click', onClickMoveUp); + + // btn.appendChild(icon); + // btn.appendChild(span); + + // return btn; + // }; + + // const createBtnMoveDown = () => { + // const btn = document.createElement('button'); + // const icon = document.createElement('i'); + // const span = document.createElement('span'); + + // btn.classList.add('btn', 'btn-secondary', 'button-product-move-down'); + // icon.classList.add('fas', 'fa-chevron-down'); + // span.classList.add('visually-hidden'); + // span.textContent = 'Move down'; + + // btn.addEventListener('click', onClickMoveDown); + + // btn.appendChild(icon); + // btn.appendChild(span); + + // return btn; + // }; + + // const createBtn = (options = {}) => { + // const { + // classNames = ['btn-primary'], + // iconName = 'circle', + // label, + // clickHandler = (e) => { + // console.log('no click action'); + // }, + // } = options; + // const btn = document.createElement('button'); + // const icon = document.createElement('i'); + // const span = document.createElement('span'); + + // btn.classList.add('btn', ...classNames); + // icon.classList.add('fas', `fa-${iconName}`); + // span.classList.add('visually-hidden'); + // span.textContent = label; + + // btn.addEventListener('click', clickHandler); + + // btn.appendChild(icon); + // btn.appendChild(span); + + // return btn; + // }; + + // const createTd = (text, ...classNames) => { + // const td = document.createElement('td'); + // td.classList.add(...classNames); + // td.textContent = text; + + // return td; + // }; + + // const createTr = (...tds) => { + // const tr = document.createElement('tr'); + // // tds.forEach((td) => { + // // tr.appendChild(td); + // // }); + // tr.append(...tds); + + // return tr; + // }; + + const disableNonFunctionalButtons = () => { + DOM.tbody.querySelectorAll('tr').forEach((tr) => { + tr.querySelector('.button-product-move-up').disabled = !tr.previousElementSibling; + tr.querySelector('.button-product-move-down').disabled = !tr.nextElementSibling; + }); + }; + + // Helper + function parents(el, selector) { + const parents = []; + while ((el = el.parentNode) && el !== document) { + if (!selector || el.matches(selector)) parents.push(el); + } + return parents; + } + + init(); +})(); diff --git a/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/data/products.js b/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/data/products.js new file mode 100644 index 0000000..abae521 --- /dev/null +++ b/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/data/products.js @@ -0,0 +1,5 @@ +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 }, +]; diff --git a/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/data/products.json b/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/data/products.json new file mode 100644 index 0000000..67e8369 --- /dev/null +++ b/03_js-dom/unterricht/tag24/02_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_js-dom/unterricht/tag24/02_product-manager-tpl-json/index.html b/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/index.html new file mode 100644 index 0000000..0a63e4a --- /dev/null +++ b/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/index.html @@ -0,0 +1,90 @@ + + + + + + Productmanager + + + + + + + +
+ +
+
+

Productmanager

+ + + + + + + + + + + + + + + +
NamePrice in €Actions
+
+
+ +
+
+
+ € + +
+
+
+ +
+
+
+
+ +
+ +
+ + diff --git a/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/package.json b/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/package.json new file mode 100644 index 0000000..dab83c0 --- /dev/null +++ b/03_js-dom/unterricht/tag24/02_product-manager-tpl-json/package.json @@ -0,0 +1,11 @@ +{ + "name": "product-manager-tpl-json", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "npx http-server -p 3000 -c-1" + }, + "keywords": [], + "type": "module" +} diff --git a/03_js-dom/unterricht/tag24/03_outer-html-vs-inner-html/index.html b/03_js-dom/unterricht/tag24/03_outer-html-vs-inner-html/index.html new file mode 100644 index 0000000..835aa33 --- /dev/null +++ b/03_js-dom/unterricht/tag24/03_outer-html-vs-inner-html/index.html @@ -0,0 +1,29 @@ + + + + + + Node.outerHTML vs Node.innerHTML + + + +
+ +
+ + + diff --git a/03_js-dom/unterricht/tag24/04_es6-module/assets/js/main.js b/03_js-dom/unterricht/tag24/04_es6-module/assets/js/main.js new file mode 100644 index 0000000..14f350f --- /dev/null +++ b/03_js-dom/unterricht/tag24/04_es6-module/assets/js/main.js @@ -0,0 +1,39 @@ +// 'use strict'; // nicht mehr notwendig, da JS über type="module" in den strikten Modus gesetzt wird. + +import '../../node_modules/jquery/dist/jquery.js'; +// import $ from 'https://esm.sh/jquery'; +import _ from 'https://esm.sh/lodash'; + +import EigenerName from './modules/Example.js'; // WICHTIG mit Dateiendung, wenn kein JS Bundler im Einsatz + +import { Example as ExampleRenamed, Example2, TAX_RATE } from './modules/examples.js'; + +import Examples from './modules/examples.js'; + +(() => { + // === DOM & VARS ======= + const DOM = {}; + + // === INIT ============= + const init = () => { + console.log('init...'); + EigenerName(); + console.log($('h1').css({ color: 'tomato' })); + console.log(_.shuffle([1, 2, 3, 4, 5])); + Examples.method(); + Examples.method2(); + console.log(Examples.eigenschaft); // => 'wert' + + ExampleRenamed(); + Example2(); + console.log(TAX_RATE); // => 1.19 + }; + + // === EVENTHANDLER ===== + + // === XHR/FETCH ======== + + // === FUNCTIONS ======== + + init(); +})(); diff --git a/03_js-dom/unterricht/tag24/04_es6-module/assets/js/modules/Example.js b/03_js-dom/unterricht/tag24/04_es6-module/assets/js/modules/Example.js new file mode 100644 index 0000000..fc4feaf --- /dev/null +++ b/03_js-dom/unterricht/tag24/04_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 ("alte, gängige" Node, die es nicht Browserseitig gibt) -> require('./Example') +// exports.Example = Example; diff --git a/03_js-dom/unterricht/tag24/04_es6-module/assets/js/modules/examples.js b/03_js-dom/unterricht/tag24/04_es6-module/assets/js/modules/examples.js new file mode 100644 index 0000000..ced0b29 --- /dev/null +++ b/03_js-dom/unterricht/tag24/04_es6-module/assets/js/modules/examples.js @@ -0,0 +1,21 @@ +export const Example = () => { + console.log('Example init aus "examples"'); +}; + +export function Example2() { + console.log('Example2 init aus "examples"'); +} + +export const TAX_RATE = 1.19; +export const DB_NAME = 'todos_db'; + +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_js-dom/unterricht/tag24/04_es6-module/index.html b/03_js-dom/unterricht/tag24/04_es6-module/index.html new file mode 100644 index 0000000..01064e6 --- /dev/null +++ b/03_js-dom/unterricht/tag24/04_es6-module/index.html @@ -0,0 +1,52 @@ + + + + + + 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 (komplette Pfadangabe). +
  • +
+
+
+ + + diff --git a/03_js-dom/unterricht/tag24/04_es6-module/package-lock.json b/03_js-dom/unterricht/tag24/04_es6-module/package-lock.json new file mode 100644 index 0000000..9fcd857 --- /dev/null +++ b/03_js-dom/unterricht/tag24/04_es6-module/package-lock.json @@ -0,0 +1,21 @@ +{ + "name": "04_es6-module", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "04_es6-module", + "version": "1.0.0", + "dependencies": { + "jquery": "^4.0.0" + } + }, + "node_modules/jquery": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-4.0.0.tgz", + "integrity": "sha512-TXCHVR3Lb6TZdtw1l3RTLf8RBWVGexdxL6AC8/e0xZKEpBflBsjh9/8LXw+dkNFuOyW9B7iB3O1sP7hS0Kiacg==", + "license": "MIT" + } + } +} diff --git a/03_js-dom/unterricht/tag24/04_es6-module/package.json b/03_js-dom/unterricht/tag24/04_es6-module/package.json new file mode 100644 index 0000000..c8521f2 --- /dev/null +++ b/03_js-dom/unterricht/tag24/04_es6-module/package.json @@ -0,0 +1,14 @@ +{ + "name": "04_es6-module", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "npx http-server -p 3000 -c-1" + }, + "keywords": [], + "type": "commonjs", + "dependencies": { + "jquery": "^4.0.0" + } +} diff --git a/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/css/main.css b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/css/main.css new file mode 100644 index 0000000..00febcc --- /dev/null +++ b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/css/main.css @@ -0,0 +1,40 @@ +*, +html { + box-sizing: border-box; +} + +html, +body { + padding: 0; + margin: 0; + height: 100%; +} + +/* Mobile first */ +.product-manager table tfoot .row > * { + margin-bottom: 0.5rem; +} + +@media (min-width: 992px) { + .product-manager table tfoot .row > * { + margin-bottom: 0; + } +} + +.product-manager table td { + vertical-align: middle; +} + +.product-manager table tbody:empty::after { + content: 'loading products...'; + color: #999; + padding: 1rem; +} + +.product-manager table td button { + margin: 0 3px; +} + +.product-manager table .th-actions { + width: 185px; +} diff --git a/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/js/main.js b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/js/main.js new file mode 100644 index 0000000..77d2f68 --- /dev/null +++ b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/js/main.js @@ -0,0 +1,23 @@ +import ProductManager from './modules/ProductManager.js'; + +(() => { + // === DOM & VARS ======= + const DOM = { + productManagers: Array.from(document.querySelectorAll('.product-manager')), + }; + + // === INIT ============= + const init = () => { + DOM.productManagers.forEach((el) => { + ProductManager(el); + }); + }; + + // === EVENTHANDLER ===== + + // === XHR/FETCH ======== + + // === FUNCTIONS ======== + + init(); +})(); diff --git a/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/js/modules/ProductManager.js b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/js/modules/ProductManager.js new file mode 100644 index 0000000..5c8d810 --- /dev/null +++ b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/js/modules/ProductManager.js @@ -0,0 +1,160 @@ +import { parents } from '../utils/helpers.js'; + +// Factory Function +const ProductManager = (el = null, options = {}) => { + // === DOM & VARS ======= + + const module = el || document.querySelector('.product-manager') || console.warn('Product Manager Element 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 = () => { + // Funktionsaufrufe zu beginn der Anwendung + initProducts(); + // Event-Lauscher zu beginn der Anwendung + DOM.btnAdd.addEventListener('click', onClickAdd); + }; + + // === EVENTHANDLER ===== + const onClickAdd = (e) => { + const product = { + name: DOM.inputName.value, // + price: Number(DOM.inputPrice.valueAsNumber), + }; + + addProduct(product); + disableNonFunctionalButtons(); + }; + + const onClickRemove = (e) => { + const btn = e.currentTarget; + const currentRow = parents(btn, 'tr')[0]; // btn.closest('tr'); // btn.parentNode.parentNode; + + currentRow.remove(); + // currentRow.parentNode.removeChild(currentRow); // IE11 support + disableNonFunctionalButtons(); + }; + + const onClickMoveUp = (e) => { + const btn = e.currentTarget; + const currentRow = parents(btn, 'tr')[0]; + DOM.tbody.insertBefore(currentRow, currentRow.previousElementSibling); + disableNonFunctionalButtons(); + }; + + const onClickMoveDown = (e) => { + const btn = e.currentTarget; + const currentRow = parents(btn, 'tr')[0]; + DOM.tbody.insertBefore(currentRow, currentRow.nextElementSibling.nextElementSibling); + 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-Body in JS-Objekt geparsed -> Promise + + return data; + } catch (error) { + console.error(error); + throw error; + } + }; + // === FUNCTIONS ======== + const initProducts = () => { + fetchProducts().then((products) => { + products.forEach(addProduct); + disableNonFunctionalButtons(); + }); + }; + + const addProduct = (product) => { + const { name, price } = product; + + // https://developer.mozilla.org/de/docs/Web/HTML/Reference/Elements/template#anwendungshinweise + 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 = Number(price).toFixed(2); + btnRemoveEl.addEventListener('click', onClickRemove); + btnMoveUpEl.addEventListener('click', onClickMoveUp); + btnMoveDownEl.addEventListener('click', onClickMoveDown); + + DOM.tbody.appendChild(trEl); + }; + + // const createBtn = (options = {}) => { + // const { + // classNames = ['btn-primary'], + // iconName = 'circle', + // label, + // clickHandler = (e) => { + // console.log('no click action'); + // }, + // } = options; + // const btn = document.createElement('button'); + // const icon = document.createElement('i'); + // const span = document.createElement('span'); + + // btn.classList.add('btn', ...classNames); + // icon.classList.add('fas', `fa-${iconName}`); + // span.classList.add('visually-hidden'); + // span.textContent = label; + + // btn.addEventListener('click', clickHandler); + + // btn.appendChild(icon); + // btn.appendChild(span); + + // return btn; + // }; + + const disableNonFunctionalButtons = () => { + DOM.tbody.querySelectorAll('tr').forEach((tr) => { + tr.querySelector('.button-product-move-up').disabled = !tr.previousElementSibling; + tr.querySelector('.button-product-move-down').disabled = !tr.nextElementSibling; + }); + }; + + // Helper + // function parents(el, selector) { + // const parents = []; + // while ((el = el.parentNode) && el !== document) { + // if (!selector || el.matches(selector)) parents.push(el); + // } + // return parents; + // } + + init(); + + return { + element: DOM.module, + init, + }; +}; + +export default ProductManager; diff --git a/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/js/utils/helpers.js b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/js/utils/helpers.js new file mode 100644 index 0000000..419c157 --- /dev/null +++ b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/assets/js/utils/helpers.js @@ -0,0 +1,32 @@ +// Helper +export function parents(el, selector) { + const parents = []; + while ((el = el.parentNode) && el !== document) { + if (!selector || el.matches(selector)) parents.push(el); + } + return parents; +} + +export function addClass(el, className) { + if (Array.isArray(className)) { + el.classList.add(...className); + } else { + el.classList.add(className); + } + return el; +} + +export function removeClass(el, className) { + if (Array.isArray(className)) { + el.classList.remove(...className); + } else { + el.classList.remove(className); + } + return el; +} + +export default { + parents, + addClass, + removeClass, +}; diff --git a/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/data/products.js b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/data/products.js new file mode 100644 index 0000000..abae521 --- /dev/null +++ b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/data/products.js @@ -0,0 +1,5 @@ +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 }, +]; diff --git a/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/data/products.json b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/data/products.json new file mode 100644 index 0000000..67e8369 --- /dev/null +++ b/03_js-dom/unterricht/tag24/05_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_js-dom/unterricht/tag24/05_product-manager-tpl-json/index.html b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/index.html new file mode 100644 index 0000000..ac6a65b --- /dev/null +++ b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/index.html @@ -0,0 +1,90 @@ + + + + + + Productmanager + + + + + + + +
+ +
+
+

Productmanager

+ + + + + + + + + + + + + + + +
NamePrice in €Actions
+
+
+ +
+
+
+ € + +
+
+
+ +
+
+
+
+ +
+ +
+ + diff --git a/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/package.json b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/package.json new file mode 100644 index 0000000..dab83c0 --- /dev/null +++ b/03_js-dom/unterricht/tag24/05_product-manager-tpl-json/package.json @@ -0,0 +1,11 @@ +{ + "name": "product-manager-tpl-json", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "npx http-server -p 3000 -c-1" + }, + "keywords": [], + "type": "module" +} diff --git a/03_js-dom/unterricht/tag24/06_drag-drop/index.html b/03_js-dom/unterricht/tag24/06_drag-drop/index.html new file mode 100644 index 0000000..d075e1f --- /dev/null +++ b/03_js-dom/unterricht/tag24/06_drag-drop/index.html @@ -0,0 +1,105 @@ + + + + + + Drag 'n Drop Example + + + + + +
+
+

Drag 'n Drop Example

+
+
+ +
+
+
+ + + diff --git a/03_js-dom/unterricht/tag24/07_product-manager-final/assets/css/main.css b/03_js-dom/unterricht/tag24/07_product-manager-final/assets/css/main.css new file mode 100644 index 0000000..2948877 --- /dev/null +++ b/03_js-dom/unterricht/tag24/07_product-manager-final/assets/css/main.css @@ -0,0 +1,43 @@ +*, +html { + box-sizing: border-box; +} + +html, +body { + padding: 0; + margin: 0; + height: 100%; +} + +/* Mobile first */ +.product-manager table tfoot .row > * { + margin-bottom: 0.5rem; +} + +@media (min-width: 992px) { + .product-manager table tfoot .row > * { + margin-bottom: 0; + } +} + +.product-manager table td { + vertical-align: middle; +} + +.product-manager table tbody:empty::after { + content: 'loading products...'; + color: #999; + padding: 1rem; +} + +.product-manager table td button { + margin: 0 3px; +} + +.product-manager table .th-actions { + width: 185px; +} +.product-manager table .th-dragger { + width: 50px; +} diff --git a/03_js-dom/unterricht/tag24/07_product-manager-final/assets/js/main.js b/03_js-dom/unterricht/tag24/07_product-manager-final/assets/js/main.js new file mode 100644 index 0000000..77d2f68 --- /dev/null +++ b/03_js-dom/unterricht/tag24/07_product-manager-final/assets/js/main.js @@ -0,0 +1,23 @@ +import ProductManager from './modules/ProductManager.js'; + +(() => { + // === DOM & VARS ======= + const DOM = { + productManagers: Array.from(document.querySelectorAll('.product-manager')), + }; + + // === INIT ============= + const init = () => { + DOM.productManagers.forEach((el) => { + ProductManager(el); + }); + }; + + // === EVENTHANDLER ===== + + // === XHR/FETCH ======== + + // === FUNCTIONS ======== + + init(); +})(); diff --git a/03_js-dom/unterricht/tag24/07_product-manager-final/assets/js/modules/ProductManager.js b/03_js-dom/unterricht/tag24/07_product-manager-final/assets/js/modules/ProductManager.js new file mode 100644 index 0000000..fe917d1 --- /dev/null +++ b/03_js-dom/unterricht/tag24/07_product-manager-final/assets/js/modules/ProductManager.js @@ -0,0 +1,163 @@ +import { parents } from '../utils/helpers.js'; + +// Factory Function +const ProductManager = (el = null, options = {}) => { + // === DOM & VARS ======= + + const module = el || document.querySelector('.product-manager') || console.warn('Product Manager Element 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 = () => { + // Funktionsaufrufe zu beginn der Anwendung + initProducts(); + // Event-Lauscher zu beginn der Anwendung + DOM.btnAdd.addEventListener('click', onClickAdd); + }; + + // === EVENTHANDLER ===== + const onClickAdd = (e) => { + const product = { + name: DOM.inputName.value, // + price: Number(DOM.inputPrice.valueAsNumber), + }; + + addProduct(product); + disableNonFunctionalButtons(); + }; + + const onClickRemove = (e) => { + const btn = e.currentTarget; + const currentRow = parents(btn, 'tr')[0]; // btn.closest('tr'); // btn.parentNode.parentNode; + + currentRow.remove(); + // currentRow.parentNode.removeChild(currentRow); // IE11 support + disableNonFunctionalButtons(); + }; + + const onClickMoveUp = (e) => { + const btn = e.currentTarget; + const currentRow = parents(btn, 'tr')[0]; + DOM.tbody.insertBefore(currentRow, currentRow.previousElementSibling); + disableNonFunctionalButtons(); + }; + + const onClickMoveDown = (e) => { + const btn = e.currentTarget; + const currentRow = parents(btn, 'tr')[0]; + DOM.tbody.insertBefore(currentRow, currentRow.nextElementSibling.nextElementSibling); + 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-Body in JS-Objekt geparsed -> Promise + + return data; + } catch (error) { + console.error(error); + throw error; + } + }; + // === FUNCTIONS ======== + const initProducts = () => { + fetchProducts().then((products) => { + products.forEach(addProduct); + disableNonFunctionalButtons(); + }); + }; + + const addProduct = (product) => { + const { name, price } = product; + + // https://developer.mozilla.org/de/docs/Web/HTML/Reference/Elements/template#anwendungshinweise + const trEl = DOM.templateRow.content.firstElementChild.cloneNode(true); + + const tdNameEl = trEl.querySelector('.td-name'); + const tdPriceEl = trEl.querySelector('.td-price'); + const btnDragEl = trEl.querySelector('.button-product-drag'); + const btnRemoveEl = trEl.querySelector('.button-product-remove'); + const btnMoveUpEl = trEl.querySelector('.button-product-move-up'); + const btnMoveDownEl = trEl.querySelector('.button-product-move-down'); + + // TODO: Optional Drag 'n Drop hinzufügen. + + tdNameEl.textContent = name; + tdPriceEl.textContent = Number(price).toFixed(2); + btnRemoveEl.addEventListener('click', onClickRemove); + btnMoveUpEl.addEventListener('click', onClickMoveUp); + btnMoveDownEl.addEventListener('click', onClickMoveDown); + + DOM.tbody.appendChild(trEl); + }; + + // const createBtn = (options = {}) => { + // const { + // classNames = ['btn-primary'], + // iconName = 'circle', + // label, + // clickHandler = (e) => { + // console.log('no click action'); + // }, + // } = options; + // const btn = document.createElement('button'); + // const icon = document.createElement('i'); + // const span = document.createElement('span'); + + // btn.classList.add('btn', ...classNames); + // icon.classList.add('fas', `fa-${iconName}`); + // span.classList.add('visually-hidden'); + // span.textContent = label; + + // btn.addEventListener('click', clickHandler); + + // btn.appendChild(icon); + // btn.appendChild(span); + + // return btn; + // }; + + const disableNonFunctionalButtons = () => { + DOM.tbody.querySelectorAll('tr').forEach((tr) => { + tr.querySelector('.button-product-move-up').disabled = !tr.previousElementSibling; + tr.querySelector('.button-product-move-down').disabled = !tr.nextElementSibling; + }); + }; + + // Helper + // function parents(el, selector) { + // const parents = []; + // while ((el = el.parentNode) && el !== document) { + // if (!selector || el.matches(selector)) parents.push(el); + // } + // return parents; + // } + + init(); + + return { + element: DOM.module, + init, + }; +}; + +export default ProductManager; diff --git a/03_js-dom/unterricht/tag24/07_product-manager-final/assets/js/utils/helpers.js b/03_js-dom/unterricht/tag24/07_product-manager-final/assets/js/utils/helpers.js new file mode 100644 index 0000000..419c157 --- /dev/null +++ b/03_js-dom/unterricht/tag24/07_product-manager-final/assets/js/utils/helpers.js @@ -0,0 +1,32 @@ +// Helper +export function parents(el, selector) { + const parents = []; + while ((el = el.parentNode) && el !== document) { + if (!selector || el.matches(selector)) parents.push(el); + } + return parents; +} + +export function addClass(el, className) { + if (Array.isArray(className)) { + el.classList.add(...className); + } else { + el.classList.add(className); + } + return el; +} + +export function removeClass(el, className) { + if (Array.isArray(className)) { + el.classList.remove(...className); + } else { + el.classList.remove(className); + } + return el; +} + +export default { + parents, + addClass, + removeClass, +}; diff --git a/03_js-dom/unterricht/tag24/07_product-manager-final/data/products.js b/03_js-dom/unterricht/tag24/07_product-manager-final/data/products.js new file mode 100644 index 0000000..abae521 --- /dev/null +++ b/03_js-dom/unterricht/tag24/07_product-manager-final/data/products.js @@ -0,0 +1,5 @@ +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 }, +]; diff --git a/03_js-dom/unterricht/tag24/07_product-manager-final/data/products.json b/03_js-dom/unterricht/tag24/07_product-manager-final/data/products.json new file mode 100644 index 0000000..67e8369 --- /dev/null +++ b/03_js-dom/unterricht/tag24/07_product-manager-final/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_js-dom/unterricht/tag24/07_product-manager-final/index.html b/03_js-dom/unterricht/tag24/07_product-manager-final/index.html new file mode 100644 index 0000000..65e6f90 --- /dev/null +++ b/03_js-dom/unterricht/tag24/07_product-manager-final/index.html @@ -0,0 +1,97 @@ + + + + + + Productmanager + + + + + + + +
+ +
+
+

Productmanager

+ + + + + + + + + + + + + + + + +
#NamePrice in €Actions
+
+
+ +
+
+
+ € + +
+
+
+ +
+
+
+
+ +
+ +
+ + diff --git a/03_js-dom/unterricht/tag24/07_product-manager-final/package.json b/03_js-dom/unterricht/tag24/07_product-manager-final/package.json new file mode 100644 index 0000000..dab83c0 --- /dev/null +++ b/03_js-dom/unterricht/tag24/07_product-manager-final/package.json @@ -0,0 +1,11 @@ +{ + "name": "product-manager-tpl-json", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "npx http-server -p 3000 -c-1" + }, + "keywords": [], + "type": "module" +}