This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
})();
|
||||
@@ -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 },
|
||||
];
|
||||
@@ -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,90 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Productmanager</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.3.1/css/all.css" />
|
||||
<link rel="stylesheet" href="assets/css/main.css" />
|
||||
<script src="data/products.js"></script>
|
||||
<script src="assets/js/main.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<!-- ▼ Product Manager ▼ -->
|
||||
<div class="product-manager">
|
||||
<div class="container py-5">
|
||||
<h1>Productmanager</h1>
|
||||
<!-- table.table.table-striped.table-products>thead.table-dark>tr>th*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"
|
||||
name="product-name"
|
||||
class="form-control input-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">
|
||||
<span class="input-group-text">€</span>
|
||||
<input
|
||||
type="number"
|
||||
name="prodcut-price"
|
||||
class="form-control input-product-price"
|
||||
placeholder="00.00"
|
||||
aria-label="Product price"
|
||||
min="0"
|
||||
step="0.01" />
|
||||
</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>
|
||||
</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-danger button-product-remove">
|
||||
<i class="fas fa-trash-can"></i>
|
||||
<span class="visually-hidden">Remove Product</span>
|
||||
</button>
|
||||
<button class="btn btn-secondary button-product-move-up">
|
||||
<i class="fas fa-chevron-up"></i>
|
||||
<span class="visually-hidden">Move up</span>
|
||||
</button>
|
||||
<button class="btn btn-secondary button-product-move-down">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
<span class="visually-hidden">Move down</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</div>
|
||||
<!-- ▲ /Product Manager ▲ -->
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Node.outerHTML vs Node.innerHTML</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="container py-5">
|
||||
<h1>
|
||||
<a href="https://developer.mozilla.org/de/docs/Web/API/Element/outerHTML">Node.outerHTML</a> vs
|
||||
<em><a href="https://developer.mozilla.org/de/docs/Web/API/Element/innerHTML">Node.innerHTML</a></em>
|
||||
</h1>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
const $ = (qs) => document.querySelector(qs);
|
||||
const $$ = (qs) => Array.from(document.querySelectorAll(qs));
|
||||
|
||||
console.log($('h1').innerHTML); //=> Node.innerHTML <em>Node.innerHTML</em>
|
||||
|
||||
console.log($('h1').outerHTML); //=> <h1>Node.outerHTML vs <em>Node.innerHTML</em></h1>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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();
|
||||
})();
|
||||
@@ -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;
|
||||
@@ -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
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
<!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 (komplette Pfadangabe).
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
'use strict';
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
})();
|
||||
+160
@@ -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;
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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 },
|
||||
];
|
||||
@@ -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,90 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Productmanager</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.3.1/css/all.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>
|
||||
<!-- ▼ Product Manager ▼ -->
|
||||
<div class="product-manager">
|
||||
<div class="container py-5">
|
||||
<h1>Productmanager</h1>
|
||||
<!-- table.table.table-striped.table-products>thead.table-dark>tr>th*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"
|
||||
name="product-name"
|
||||
class="form-control input-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">
|
||||
<span class="input-group-text">€</span>
|
||||
<input
|
||||
type="number"
|
||||
name="prodcut-price"
|
||||
class="form-control input-product-price"
|
||||
placeholder="00.00"
|
||||
aria-label="Product price"
|
||||
min="0"
|
||||
step="0.01" />
|
||||
</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>
|
||||
</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-danger button-product-remove">
|
||||
<i class="fas fa-trash-can"></i>
|
||||
<span class="visually-hidden">Remove Product</span>
|
||||
</button>
|
||||
<button class="btn btn-secondary button-product-move-up">
|
||||
<i class="fas fa-chevron-up"></i>
|
||||
<span class="visually-hidden">Move up</span>
|
||||
</button>
|
||||
<button class="btn btn-secondary button-product-move-down">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
<span class="visually-hidden">Move down</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</div>
|
||||
<!-- ▲ /Product Manager ▲ -->
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Drag 'n Drop Example</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||
|
||||
<style>
|
||||
.box {
|
||||
position: absolute;
|
||||
|
||||
z-index: 2;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: tomato;
|
||||
border: 10px solid #222;
|
||||
}
|
||||
|
||||
.target-box {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 1rem;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background-color: orange;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border: 10px solid #222;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="container py-5">
|
||||
<h1>Drag 'n Drop Example</h1>
|
||||
<hr />
|
||||
<div class="box" id="source-box"></div>
|
||||
|
||||
<div class="target-box"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
const box = document.querySelector('.box');
|
||||
const targetBox = document.querySelector('.target-box');
|
||||
|
||||
// MouseEvent
|
||||
box.addEventListener('mousedown', (e) => {
|
||||
console.log('MouseEvent: ', e);
|
||||
const el = e.currentTarget;
|
||||
el.draggable = true; // => <div class="box" draggable="true">...</div>
|
||||
});
|
||||
|
||||
box.addEventListener('mouseup', (e) => {
|
||||
console.log('MouseEvent: ', e);
|
||||
const el = e.currentTarget;
|
||||
el.draggable = false; // => <div class="box" draggable="false">...</div>
|
||||
});
|
||||
|
||||
// DragEvent
|
||||
box.addEventListener('dragstart', (e) => {
|
||||
console.log('DragEvent: ', e);
|
||||
console.log('DRAG_START');
|
||||
|
||||
const sourceEl = e.currentTarget;
|
||||
|
||||
const idx = [...sourceEl.parentNode.children].indexOf(sourceEl); // => Trick um schnell den Index (die Position) eines Elementes herauzufinden.
|
||||
|
||||
const className = sourceEl.classList.value; // => 'box'
|
||||
const id = sourceEl.id;
|
||||
|
||||
console.log({ idx, className, id });
|
||||
// e.dataTransfer.setData('text/plain', idx);
|
||||
e.dataTransfer.setData('application/json', JSON.stringify({ idx, className, id }));
|
||||
});
|
||||
|
||||
// NOTWENDIG
|
||||
targetBox.addEventListener('dragover', (e) => {
|
||||
e.preventDefault(); // Standardverhalten unterbinden. Notwendig, damit 'drop'-Event grieft
|
||||
});
|
||||
|
||||
targetBox.addEventListener('drop', (e) => {
|
||||
console.log('DragEvent: ', e);
|
||||
console.log('DROP');
|
||||
|
||||
const { idx, id, className } = JSON.parse(e.dataTransfer.getData('application/json'));
|
||||
|
||||
// Beispiel Veränderung von sourceElment
|
||||
const sourceEl = document.querySelector('.box');
|
||||
const newIdx = [...sourceEl.parentNode.children].indexOf(sourceEl);
|
||||
|
||||
console.log({ idx, className, newIdx, id });
|
||||
document.querySelector(`.${className}`).style.backgroundColor = 'limegreen';
|
||||
// document.querySelector(`.container > *:nth-child(${idx})`).style.width = `${targetBox.offsetWidth}px`;
|
||||
document.querySelector(`.${className}`).style.width = `${targetBox.offsetWidth}px`;
|
||||
document.getElementById(id).style.height = `${targetBox.offsetHeight}px`;
|
||||
|
||||
document.querySelector(`.${className}`).style.top = `${targetBox.getBoundingClientRect().top}px`;
|
||||
document.querySelector(`.${className}`).style.left = `${targetBox.getBoundingClientRect().left}px`;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
})();
|
||||
+163
@@ -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;
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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 },
|
||||
];
|
||||
@@ -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,97 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Productmanager</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.3.1/css/all.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>
|
||||
<!-- ▼ Product Manager ▼ -->
|
||||
<div class="product-manager">
|
||||
<div class="container py-5">
|
||||
<h1>Productmanager</h1>
|
||||
<!-- table.table.table-striped.table-products>thead.table-dark>tr>th*2 -->
|
||||
<table class="table table-striped table-products">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th class="th-dragger">#</th>
|
||||
<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="4">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6 col-lg-6">
|
||||
<input
|
||||
type="text"
|
||||
name="product-name"
|
||||
class="form-control input-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">
|
||||
<span class="input-group-text">€</span>
|
||||
<input
|
||||
type="number"
|
||||
name="prodcut-price"
|
||||
class="form-control input-product-price"
|
||||
placeholder="00.00"
|
||||
aria-label="Product price"
|
||||
min="0"
|
||||
step="0.01" />
|
||||
</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>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<template class="template-row">
|
||||
<tr>
|
||||
<td class="td-dragger">
|
||||
<button class="btn btn-default button-product-drag">
|
||||
<i class="fas fa-grip-vertical"></i>
|
||||
<span class="visually-hidden">Drag Product</span>
|
||||
</button>
|
||||
</td>
|
||||
<td class="td-name">{PRODUCT_NAME}</td>
|
||||
<td class="td-price">{PRODUCT_PRICE}</td>
|
||||
<td class="td-actions">
|
||||
<button class="btn btn-danger button-product-remove">
|
||||
<i class="fas fa-trash-can"></i>
|
||||
<span class="visually-hidden">Remove Product</span>
|
||||
</button>
|
||||
<button class="btn btn-secondary button-product-move-up">
|
||||
<i class="fas fa-chevron-up"></i>
|
||||
<span class="visually-hidden">Move up</span>
|
||||
</button>
|
||||
<button class="btn btn-secondary button-product-move-down">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
<span class="visually-hidden">Move down</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</div>
|
||||
<!-- ▲ /Product Manager ▲ -->
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user