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
+
+
+
+
+ Name
+ Price in €
+ Actions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Product
+
+
+
+
+
+
+
+
+
+
+ {PRODUCT_NAME}
+ {PRODUCT_PRICE}
+
+
+
+ Remove Product
+
+
+
+ Move up
+
+
+
+ Move down
+
+
+
+
+
+
+
+
+
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
+
+
+
+
+ Name
+ Price in €
+ Actions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Product
+
+
+
+
+
+
+
+
+
+
+ {PRODUCT_NAME}
+ {PRODUCT_PRICE}
+
+
+
+ Remove Product
+
+
+
+ Move up
+
+
+
+ Move down
+
+
+
+
+
+
+
+
+
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
+
+
+
+
+ #
+ Name
+ Price in €
+ Actions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Product
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Drag Product
+
+
+ {PRODUCT_NAME}
+ {PRODUCT_PRICE}
+
+
+
+ Remove Product
+
+
+
+ Move up
+
+
+
+ Move down
+
+
+
+
+
+
+
+
+
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"
+}