This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,54 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// Helper
|
||||||
|
function parents(el,selector){const parents=[];while((el=el.parentNode)&&el!==document){if(!selector||el.matches(selector))parents.push(el);}return parents;}
|
||||||
|
|
||||||
|
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();DOM.btnAdd.disabled=true;// Event-Lauscher zu beginn der Anwendung
|
||||||
|
DOM.btnAdd.addEventListener('click',onClickAdd);window.addEventListener('keyup',onKeyUp);};// === EVENTHANDLER =====
|
||||||
|
const onKeyUp=e=>{DOM.btnAdd.disabled=!(DOM.inputName.value!==''&&DOM.inputPrice.value!=='');// if (DOM.inputName.value !== '' && DOM.inputPrice.value !== '') {
|
||||||
|
// DOM.btnAdd.disabled = false;
|
||||||
|
// } else {
|
||||||
|
// DOM.btnAdd.disabled = true;
|
||||||
|
// }
|
||||||
|
};const onClickAdd=e=>{const product={name:DOM.inputName.value,//
|
||||||
|
price:Number(DOM.inputPrice.valueAsNumber)};addProduct(product);DOM.inputName.value=DOM.inputPrice.value='';DOM.btnAdd.disabled=true;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];currentRow.previousElementSibling.before(currentRow);// DOM.tbody.insertBefore(currentRow, currentRow.previousElementSibling);
|
||||||
|
disableNonFunctionalButtons();};const onClickMoveDown=e=>{const btn=e.currentTarget;const currentRow=parents(btn,'tr')[0];currentRow.nextElementSibling.after(currentRow);// DOM.tbody.insertBefore(currentRow, currentRow.nextElementSibling.nextElementSibling);
|
||||||
|
disableNonFunctionalButtons();};const onMouseDownDrag=e=>{const btn=e.currentTarget;const currentRow=parents(btn,'tr')[0];// btn.closest('tr'); // btn.parentNode.parentNode;
|
||||||
|
currentRow.draggable=true;// => <tr draggable="true" >...</tr>
|
||||||
|
};const onMouseUpDrag=e=>{const btn=e.currentTarget;const currentRow=parents(btn,'tr')[0];// btn.closest('tr'); // btn.parentNode.parentNode;
|
||||||
|
currentRow.draggable=false;// => <tr draggable="false" >...</tr>
|
||||||
|
};const onDragStart=e=>{// console.log('DragEvent: ', e);
|
||||||
|
const currentRow=e.currentTarget;const idx=[...currentRow.parentNode.children].indexOf(currentRow);e.dataTransfer.setData('text/plain',String(idx));// console.log('tr reference index: ', idx);
|
||||||
|
};const onDragOver=e=>{e.preventDefault();// WICHTIG: sonst funktioniet drop-EventHandler nicht
|
||||||
|
};const onDrop=e=>{const currentRow=e.currentTarget;// <- Zielzeile, aud die gedroppt wird
|
||||||
|
const targetIdx=[...currentRow.parentNode.children].indexOf(currentRow);const sourceIdx=Number(e.dataTransfer.getData('text/plain'));console.log({targetIdx,sourceIdx});//const draggedRow = DOM.tbody.querySelector(`tr:nth-child(${sourceIdx + 1})`);
|
||||||
|
const draggedRow=DOM.tbody.children[sourceIdx];// Guard wenn Element auf sich selbst losgelassen wurde, abbruch
|
||||||
|
if(sourceIdx===targetIdx)return;// Verschiebe das Element im DOM
|
||||||
|
if(sourceIdx>targetIdx){currentRow.before(draggedRow);}else {currentRow.after(draggedRow);}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');// Drag 'n Drop hinzufügen.
|
||||||
|
btnDragEl.addEventListener('mousedown',onMouseDownDrag);btnDragEl.addEventListener('mouseup',onMouseUpDrag);trEl.addEventListener('dragstart',onDragStart);trEl.addEventListener('dragover',onDragOver);trEl.addEventListener('drop',onDrop);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 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};};
|
||||||
|
|
||||||
(()=>{// === DOM & VARS =======
|
(()=>{// === DOM & VARS =======
|
||||||
const init=()=>{console.log('init!');};// === EVENTHANDLER =====
|
const init=()=>{console.log('init!');ProductManager();};// === EVENTHANDLER =====
|
||||||
// === XHR/FETCH ========
|
// === XHR/FETCH ========
|
||||||
// === FUNCTIONS ========
|
// === FUNCTIONS ========
|
||||||
init();})();
|
init();})();
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -38,7 +38,7 @@
|
|||||||
<!-- ul.sublist>li*3>a[href="#"]{Project $} -->
|
<!-- ul.sublist>li*3>a[href="#"]{Project $} -->
|
||||||
<ul class="sublist">
|
<ul class="sublist">
|
||||||
<li><a href="./projects/01_jokes.html">01 Chuck Norris Jokes</a></li>
|
<li><a href="./projects/01_jokes.html">01 Chuck Norris Jokes</a></li>
|
||||||
<li><a href="#">Project 2</a></li>
|
<li><a href="/projects/02_product-manager.html">02 Product Manager</a></li>
|
||||||
<li><a href="#">Project 3</a></li>
|
<li><a href="#">Project 3</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<!-- ul.sublist>li*3>a[href="#"]{Project $} -->
|
<!-- ul.sublist>li*3>a[href="#"]{Project $} -->
|
||||||
<ul class="sublist">
|
<ul class="sublist">
|
||||||
<li><a href="@@webRoot/projects/01_jokes.html">01 Chuck Norris Jokes</a></li>
|
<li><a href="@@webRoot/projects/01_jokes.html">01 Chuck Norris Jokes</a></li>
|
||||||
<li><a href="#">Project 2</a></li>
|
<li><a href="/projects/02_product-manager.html">02 Product Manager</a></li>
|
||||||
<li><a href="#">Project 3</a></li>
|
<li><a href="#">Project 3</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Chuck Norris Joke (API)</title>
|
||||||
|
<!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" /> -->
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" href="/favicon/favicon-96x96.png" sizes="96x96" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon/favicon.svg" />
|
||||||
|
<link rel="shortcut icon" href="/favicon/favicon.ico" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="JS Course" />
|
||||||
|
<link rel="manifest" href="/favicon/site.webmanifest" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/assets/css/main.css" />
|
||||||
|
<script src="/assets/js/build.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- header.header-main>.content-box>figure.logo-main>img+figc -->
|
||||||
|
<!-- <header class="header-main">
|
||||||
|
<div class="content-box">
|
||||||
|
<figure class="logo-main">
|
||||||
|
<img src="/assets/img/logo-js.min.svg" alt="Logo - JS Course" />
|
||||||
|
<figcaption>JS course</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</header> -->
|
||||||
|
<!-- nav.menu-main>ul.list>li*4>a[href="#"]{Link $} -->
|
||||||
|
<nav class="menu-main">
|
||||||
|
<ul class="list">
|
||||||
|
<li><a href="/">Home</a></li>
|
||||||
|
<li><a href="/about.html">About</a></li>
|
||||||
|
<li>
|
||||||
|
<a href="#dropdown">Projects</a>
|
||||||
|
<!-- ul.sublist>li*3>a[href="#"]{Project $} -->
|
||||||
|
<ul class="sublist">
|
||||||
|
<li><a href="/projects/01_jokes.html">01 Chuck Norris Jokes</a></li>
|
||||||
|
<li><a href="#">Project 2</a></li>
|
||||||
|
<li><a href="#">Project 3</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><a href="/contact.html">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
<section class="section-joke">
|
||||||
|
<!-- .chuck-norris-joke>blockquote.joke-text+button.btn.btn-dark.button-get-joke{Get Joke} -->
|
||||||
|
<div class="chuck-norris-joke">
|
||||||
|
<blockquote class="joke-text"></blockquote>
|
||||||
|
<button class="btn btn-dark button-get-joke">Get Joke</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
@@include('head.html')
|
||||||
|
<body>
|
||||||
|
@@include('menu-main.html')
|
||||||
|
<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>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
'use strict';
|
import ProductManager from './modules/ProductManager'; // Dateiendung nicht notwendig ?
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
// === DOM & VARS =======
|
// === DOM & VARS =======
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
// === INIT =============
|
// === INIT =============
|
||||||
const init = () => {
|
const init = () => {
|
||||||
console.log('init!');
|
console.log('init!');
|
||||||
|
ProductManager();
|
||||||
};
|
};
|
||||||
|
|
||||||
// === EVENTHANDLER =====
|
// === EVENTHANDLER =====
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateBtn;
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
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();
|
||||||
|
DOM.btnAdd.disabled = true;
|
||||||
|
// Event-Lauscher zu beginn der Anwendung
|
||||||
|
DOM.btnAdd.addEventListener('click', onClickAdd);
|
||||||
|
window.addEventListener('keyup', onKeyUp);
|
||||||
|
};
|
||||||
|
|
||||||
|
// === EVENTHANDLER =====
|
||||||
|
const onKeyUp = (e) => {
|
||||||
|
DOM.btnAdd.disabled = !(DOM.inputName.value !== '' && DOM.inputPrice.value !== '');
|
||||||
|
// if (DOM.inputName.value !== '' && DOM.inputPrice.value !== '') {
|
||||||
|
// DOM.btnAdd.disabled = false;
|
||||||
|
// } else {
|
||||||
|
// DOM.btnAdd.disabled = true;
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickAdd = (e) => {
|
||||||
|
const product = {
|
||||||
|
name: DOM.inputName.value, //
|
||||||
|
price: Number(DOM.inputPrice.valueAsNumber),
|
||||||
|
};
|
||||||
|
|
||||||
|
addProduct(product);
|
||||||
|
DOM.inputName.value = DOM.inputPrice.value = '';
|
||||||
|
DOM.btnAdd.disabled = true;
|
||||||
|
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];
|
||||||
|
|
||||||
|
currentRow.previousElementSibling.before(currentRow);
|
||||||
|
// DOM.tbody.insertBefore(currentRow, currentRow.previousElementSibling);
|
||||||
|
disableNonFunctionalButtons();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickMoveDown = (e) => {
|
||||||
|
const btn = e.currentTarget;
|
||||||
|
const currentRow = parents(btn, 'tr')[0];
|
||||||
|
currentRow.nextElementSibling.after(currentRow);
|
||||||
|
// DOM.tbody.insertBefore(currentRow, currentRow.nextElementSibling.nextElementSibling);
|
||||||
|
disableNonFunctionalButtons();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseDownDrag = (e) => {
|
||||||
|
const btn = e.currentTarget;
|
||||||
|
const currentRow = parents(btn, 'tr')[0]; // btn.closest('tr'); // btn.parentNode.parentNode;
|
||||||
|
currentRow.draggable = true; // => <tr draggable="true" >...</tr>
|
||||||
|
};
|
||||||
|
const onMouseUpDrag = (e) => {
|
||||||
|
const btn = e.currentTarget;
|
||||||
|
const currentRow = parents(btn, 'tr')[0]; // btn.closest('tr'); // btn.parentNode.parentNode;
|
||||||
|
currentRow.draggable = false; // => <tr draggable="false" >...</tr>
|
||||||
|
};
|
||||||
|
const onDragStart = (e) => {
|
||||||
|
// console.log('DragEvent: ', e);
|
||||||
|
const currentRow = e.currentTarget;
|
||||||
|
const idx = [...currentRow.parentNode.children].indexOf(currentRow);
|
||||||
|
|
||||||
|
e.dataTransfer.setData('text/plain', String(idx));
|
||||||
|
|
||||||
|
// console.log('tr reference index: ', idx);
|
||||||
|
};
|
||||||
|
const onDragOver = (e) => {
|
||||||
|
e.preventDefault(); // WICHTIG: sonst funktioniet drop-EventHandler nicht
|
||||||
|
};
|
||||||
|
const onDrop = (e) => {
|
||||||
|
const currentRow = e.currentTarget; // <- Zielzeile, aud die gedroppt wird
|
||||||
|
const targetIdx = [...currentRow.parentNode.children].indexOf(currentRow);
|
||||||
|
const sourceIdx = Number(e.dataTransfer.getData('text/plain'));
|
||||||
|
|
||||||
|
console.log({ targetIdx, sourceIdx });
|
||||||
|
|
||||||
|
//const draggedRow = DOM.tbody.querySelector(`tr:nth-child(${sourceIdx + 1})`);
|
||||||
|
const draggedRow = DOM.tbody.children[sourceIdx];
|
||||||
|
|
||||||
|
// Guard wenn Element auf sich selbst losgelassen wurde, abbruch
|
||||||
|
if (sourceIdx === targetIdx) return;
|
||||||
|
|
||||||
|
// Verschiebe das Element im DOM
|
||||||
|
if (sourceIdx > targetIdx) {
|
||||||
|
currentRow.before(draggedRow);
|
||||||
|
} else {
|
||||||
|
currentRow.after(draggedRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
// Drag 'n Drop hinzufügen.
|
||||||
|
btnDragEl.addEventListener('mousedown', onMouseDownDrag);
|
||||||
|
btnDragEl.addEventListener('mouseup', onMouseUpDrag);
|
||||||
|
|
||||||
|
trEl.addEventListener('dragstart', onDragStart);
|
||||||
|
trEl.addEventListener('dragover', onDragOver);
|
||||||
|
trEl.addEventListener('drop', onDrop);
|
||||||
|
|
||||||
|
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 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,
|
||||||
|
};
|
||||||
@@ -2,3 +2,4 @@
|
|||||||
@import 'header-main';
|
@import 'header-main';
|
||||||
@import 'logo-main';
|
@import 'logo-main';
|
||||||
@import 'menu-main';
|
@import 'menu-main';
|
||||||
|
@import 'product-manager';
|
||||||
|
|||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
/* 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;
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
<!-- ul.sublist>li*3>a[href="#"]{Project $} -->
|
<!-- ul.sublist>li*3>a[href="#"]{Project $} -->
|
||||||
<ul class="sublist">
|
<ul class="sublist">
|
||||||
<li><a href="./projects/01_jokes.html">01 Chuck Norris Jokes</a></li>
|
<li><a href="./projects/01_jokes.html">01 Chuck Norris Jokes</a></li>
|
||||||
<li><a href="#">Project 2</a></li>
|
<li><a href="/projects/02_product-manager.html">02 Product Manager</a></li>
|
||||||
<li><a href="#">Project 3</a></li>
|
<li><a href="#">Project 3</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "website-gulp-js",
|
"name": "website-frontend",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"description": "First Website with sass",
|
"description": "webseite mit sas, rollup, gulp, module",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
"dev": "npx gulp dev"
|
"dev": "npx gulp dev"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^8.0.6",
|
"@babel/core": "^8.0.7",
|
||||||
"@babel/plugin-transform-runtime": "^8.0.6",
|
"@babel/plugin-transform-runtime": "^8.0.6",
|
||||||
"@babel/preset-env": "^8.0.6",
|
"@babel/preset-env": "^8.0.7",
|
||||||
"@rollup/plugin-commonjs": "^29.0.3",
|
"@rollup/plugin-commonjs": "^29.0.3",
|
||||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||||
"@rollup/plugin-replace": "^6.0.3",
|
"@rollup/plugin-replace": "^6.0.3",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"gulp-sass": "^6.0.1",
|
"gulp-sass": "^6.0.1",
|
||||||
"gulp-sourcemaps": "^3.0.0",
|
"gulp-sourcemaps": "^3.0.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"rollup": "^4.64.0",
|
"rollup": "^4.64.3",
|
||||||
"sass": "^1.105.1",
|
"sass": "^1.105.1",
|
||||||
"vinyl-buffer": "^1.0.1",
|
"vinyl-buffer": "^1.0.1",
|
||||||
"vinyl-source-stream": "^2.0.0"
|
"vinyl-source-stream": "^2.0.0"
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Webseite mit HTML & CSS (SCSS)</title>
|
||||||
|
<!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" /> -->
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" href="../favicon/favicon-96x96.png" sizes="96x96" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="../favicon/favicon.svg" />
|
||||||
|
<link rel="shortcut icon" href="../favicon/favicon.ico" />
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="../favicon/apple-touch-icon.png" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="JS Course" />
|
||||||
|
<link rel="manifest" href="../favicon/site.webmanifest" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="../assets/css/main.css" />
|
||||||
|
<script src="../assets/js/build.js" defer></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<nav class="menu-main">
|
||||||
|
<ul class="list">
|
||||||
|
<li><a href="../">Home</a></li>
|
||||||
|
<li><a href="../about.html">About</a></li>
|
||||||
|
<li>
|
||||||
|
<a href="#dropdown">Projects</a>
|
||||||
|
<!-- ul.sublist>li*3>a[href="#"]{Project $} -->
|
||||||
|
<ul class="sublist">
|
||||||
|
<li><a href="../projects/01_jokes.html">01 Chuck Norris Jokes</a></li>
|
||||||
|
<li><a href="/projects/02_product-manager.html">02 Product Manager</a></li>
|
||||||
|
<li><a href="#">Project 3</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><a href="../contact.html">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<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>
|
||||||
Reference in New Issue
Block a user