feat: ProductManager als Modul mit template und JSON
This commit is contained in:
35
03_dom/unterricht/tag26/05_es6-module/assets/js/main.js
Normal file
35
03_dom/unterricht/tag26/05_es6-module/assets/js/main.js
Normal file
@@ -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();
|
||||||
|
})();
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
|
};
|
||||||
50
03_dom/unterricht/tag26/05_es6-module/index.html
Normal file
50
03_dom/unterricht/tag26/05_es6-module/index.html
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Verwendung von Import - Module in (ES6)</title>
|
||||||
|
<!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" /> -->
|
||||||
|
<style>
|
||||||
|
@import url('https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css');
|
||||||
|
</style>
|
||||||
|
<script src="assets/js/main.js" type="module"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="container py-5">
|
||||||
|
<h1>Verwendung von Import - Module in (ES6)</h1>
|
||||||
|
<p>
|
||||||
|
Die statische
|
||||||
|
<a href="https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Statements/import">import</a
|
||||||
|
>-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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Um die import-Deklaration in einer Quelldatei zu verwenden, muss die Datei zur Laufzeit als Modul
|
||||||
|
interpretiert werden. In HTML geschieht dies, indem <strong>type="module"</strong> zum
|
||||||
|
<strong><script></strong>-Tag hinzugefügt wird. Module werden automatisch im Strict Mode interpretiert.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Es gibt auch eine funktionsähnliche dynamische import(), die keine Skripte des Typs
|
||||||
|
<strong>type="module"</strong> erfordert.
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
<p>Um die Modul-Schreibweise in JS zu verwenden, müssen folgende Punkte berücksichtigt werden:</p>
|
||||||
|
<ul class="list list-group">
|
||||||
|
<li class="list-group-item">Script Tag benötigt Modulangabe mit <strong>type="module"</strong></li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
Kommunikation läuft über über das HTTP-Protokoll (HTTP-Kommunikation lokal über Server)
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">Eingebundene Module müssen mit Dateiendung (.js) angegeben werden.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script>
|
||||||
|
'use strict';
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
11
03_dom/unterricht/tag26/05_es6-module/package.json
Normal file
11
03_dom/unterricht/tag26/05_es6-module/package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
11
03_dom/unterricht/tag26/06_product-manager-tpl-json/.gitignore
vendored
Normal file
11
03_dom/unterricht/tag26/06_product-manager-tpl-json/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
.vite/
|
||||||
|
coverage/
|
||||||
|
.idea/
|
||||||
|
# .vscode/
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
})();
|
||||||
@@ -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;
|
||||||
@@ -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 },
|
||||||
|
];
|
||||||
@@ -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 }
|
||||||
|
]
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Product Manager</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" />
|
||||||
|
<link rel="stylesheet" href="assets/css/main.css" />
|
||||||
|
<!-- <script src="data/products.js"></script> -->
|
||||||
|
<script src="assets/js/main.js" type="module"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<div class="product-manager my-5">
|
||||||
|
<div class="container">
|
||||||
|
<!-- table.table.table-striped.table-products>(thead.table-dark>tr>th{Name}+th{Price in €})+tbody>tr>td*2 -->
|
||||||
|
<table class="table table-striped table-products">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th class="th-name">Name</th>
|
||||||
|
<th class="th-price">Price in €</th>
|
||||||
|
<th class="th-actions">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-6 col-lg-6">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control input-product-name"
|
||||||
|
name="product-name"
|
||||||
|
placeholder="Insert product name"
|
||||||
|
aria-label="Product Name" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6 col-lg-3">
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="product-price"
|
||||||
|
class="form-control input-product-price"
|
||||||
|
placeholder="00.00"
|
||||||
|
aria-label="Product Price"
|
||||||
|
step="0.01"
|
||||||
|
min="0" />
|
||||||
|
<span class="input-group-text">€</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-6 col-lg">
|
||||||
|
<button class="btn btn-dark button-product-add">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
Add product
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- ▲ /row ▲ -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template class="template-row">
|
||||||
|
<tr>
|
||||||
|
<td class="td-name">[PRODUCT_NAME]</td>
|
||||||
|
<td class="td-price">[PRODUCT_PRICE]</td>
|
||||||
|
<td class="td-actions">
|
||||||
|
<button class="btn btn-sm btn-danger button-product-remove">
|
||||||
|
<i class="fas fa-trash-can"></i>
|
||||||
|
<span class="visually-hidden">Remove Product</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-secondary button-product-move-up">
|
||||||
|
<i class="fas fa-caret-up"></i>
|
||||||
|
<span class="visually-hidden">Move Up</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-secondary button-product-move-down">
|
||||||
|
<i class="fas fa-caret-down"></i>
|
||||||
|
<span class="visually-hidden">Move Down</span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<!-- ▲ /product-manager ▲ -->
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user