New cart page.

This commit is contained in:
Mario Zechner 2023-06-13 01:04:12 +02:00
parent f2bf774d00
commit d70d7bbfe3
10 changed files with 533 additions and 289 deletions

View File

@ -104,6 +104,7 @@ async function bundleJS(inputDir, outputDir, watch) {
let buildContext = await esbuild.context({
entryPoints: {
carts: `${inputDir}/carts.js`,
cart: `${inputDir}/cart.js`,
changes: `${inputDir}/changes.js`,
index: `${inputDir}/index.js`,
},

44
site/cart-old.html Normal file
View File

@ -0,0 +1,44 @@
%%_templates/_header.html%% %%_templates/_menu.html%%
<div class="w-full relative px-4">
<div id="cart">
<h1 class="text-2xl font-bold pb-2 pt-8 text-center" id="cartname"></h1>
<div id="noproducts" class="hidden text-center text-gray-600">Noch keine Produkte im Warenkorb</div>
<div id="hasproducts" class="column hidden">
<input type="button" id="save" value="Speichern" class="hidden" />
<div class="bg-stone-200 rounded-xl p-4 max-w-4xl mx-auto md:mb-12 md:mt-6">
<canvas class="bg-white rounded-lg py-4" id="chart"></canvas>
<div class="filters flex items-center flex-wrap justify-center gap-2 pt-2">
<div id="sum-container"></div>
<div id="sumstores-container"></div>
<div id="todayonly-container"></div>
<div
class="cursor-pointer inline-flex items-center gap-x-1 rounded-full bg-white border border-gray-400 px-2 py-1 text-xs font-medium text-gray-600"
>
<input id="start" type="date" />
-
<input id="end" type="date" />
</div>
</div>
</div>
<div class="bg-stone-200 rounded-xl p-4 max-w-4xl mx-auto md:mb-12 md:mt-6">
<div>
<input id="filter" class="search rounded-lg px-2 py-1 w-full mb-4" placeholder="Filtern... (mind. 3 Zeichen)" />
<div class="filters flex justify-center gap-2 flex-wrap" id="filters-store"></div>
</div>
</div>
<div class="px-4 py-2 my-4 text-sm border rounded-xl md:mt-8 md:rounded-b-none md:mb-0 bg-gray-100">
<span id="numitems"></span> <a id="json" href="" class="text-primary font-medium hover:underline">JSON</a>
</div>
<table id="cartitems" class="w-full"></table>
</div>
</div>
<div id="search" class="w-full"></div>
</div>
<script src="js/alasql.js"></script>
<script src="js/chart.js"></script>
<script src="utils.js"></script>
<script src="cart-old.js"></script>
%%_templates/_footer.html%%

277
site/cart-old.js Normal file
View File

@ -0,0 +1,277 @@
const shoppingCarts = new ShoppingCarts();
shoppingCarts.load();
async function load() {
const items = await loadItems();
const lookup = {};
for (item of items) {
lookup[item.store + item.id] = item;
}
let cart = null;
const cartName = getQueryParameter("name");
if (cartName) {
for (c of shoppingCarts.carts) {
if (c.name == cartName) {
cart = c;
break;
}
}
// Update cart pricing info
const items = [];
for (cartItem of cart.items) {
const item = lookup[cartItem.store + cartItem.id];
if (!item) items.push(cartItem);
else items.push(item);
}
cart.items = items;
shoppingCarts.save();
}
const cartDesc = getQueryParameter("cart");
if (cartDesc) {
let tokens = cartDesc.split(";");
cart = {
name: tokens[0],
items: [],
linked: true,
};
for (let i = 1; i < tokens.length; i++) {
const item = lookup[tokens[i]];
if (item) cart.items.push(item);
}
let saveButton = document.querySelector("#save");
saveButton.classList.remove("hidden");
saveButton.addEventListener("click", () => {
let index = shoppingCarts.carts.findIndex((c) => c.name === cart.name);
if (index != -1) {
if (confirm("Existierenden Warenkorb '" + cart.name + " überschreiben?")) {
shoppingCarts.carts[index] = cart;
}
} else {
shoppingCarts.carts.push(importedCart);
}
location.href = "/cart.html?name=" + encodeURIComponent(cart.name);
});
}
if (cart == null) {
alert("Warenkorb '" + cartName + "' existiert nicht.");
location.href = "carts.html";
}
if (cart.name != "Momentum Eigenmarken Vergleich" && !cart.linked) showSearch(cart, items);
const canvasDom = document.querySelector("#chart");
document.querySelector("#sum-container").innerHTML = customCheckbox(`sum`, "Preissumme Gesamt", true, "gray");
document.querySelector("#sumstores-container").innerHTML = customCheckbox(`sumstores`, "Preissumme pro Kette", true, "gray");
document.querySelector("#todayonly-container").innerHTML = customCheckbox(`todayonly`, "Nur heutige Preise", false, "gray");
document.querySelector("#sum").addEventListener("change", () => updateCharts(canvasDom, filter(cart.items)));
document.querySelector("#sumstores").addEventListener("change", () => updateCharts(canvasDom, filter(cart.items)));
document.querySelector("#todayonly").addEventListener("change", () => updateCharts(canvasDom, filter(cart.items)));
document.querySelector("#start").addEventListener("change", () => updateCharts(canvasDom, filter(cart.items)));
document.querySelector("#end").addEventListener("change", () => updateCharts(canvasDom, filter(cart.items)));
document.querySelector("#start").value = getOldestDate(cart.items);
document.querySelector("#end").value = currentDate();
const filtersStore = document.querySelector("#filters-store");
filtersStore.innerHTML = `
${customCheckbox("all", "<strong>Alle</strong>", true, "gray")}
${STORE_KEYS.map((store) =>
customCheckbox(store, stores[store].name, stores[store].name.toLowerCase().endsWith("de") ? false : true, stores[store].color)
).join(" ")}
`;
filtersStore.querySelectorAll("input").forEach((input) => {
if (input.id == "all") return;
input.addEventListener("change", () => showCart(cart));
});
filtersStore.querySelector("#all").addEventListener("change", () => {
STORE_KEYS.forEach((store) => (filtersStore.querySelector(`#${store}`).checked = filtersStore.querySelector("#all").checked));
showCart(cart);
});
document.querySelector("#filter").addEventListener("input", () => showCart(cart));
showCart(cart);
}
function filter(cartItems) {
const query = document.querySelector("#filter").value.trim();
const storeCheckboxes = STORE_KEYS.map((store) => document.querySelector(`#${store}`));
const checkedStores = STORE_KEYS.filter((store, i) => storeCheckboxes[i].checked);
let items = [];
if (query.charAt(0) != "!") {
for (item of cartItems) {
if (!checkedStores.includes(item.store)) continue;
items.push(item);
}
} else {
items = cartItems;
}
if (query.length >= 3) items = searchItems(items, document.querySelector("#filter").value, checkedStores, false, 0, 10000, false, false);
return items;
}
function showSearch(cart, items) {
const searchDom = document.querySelector("#search");
searchDom.innerHTML = "";
newSearchComponent(
searchDom,
items,
null,
null,
(header) => {
header.innerHTML += "<th></th>";
return header;
},
(item, itemDom) => {
const cell = dom("td", `<input type="button" class="ml-auto btn-action" value="+">`);
cell.children[0].addEventListener("click", () => {
cart.items.push(item);
shoppingCarts.save();
document.querySelector("#start").value = getOldestDate(cart.items);
document.querySelector("#end").value = currentDate();
showCart(cart);
});
cell.classList.add("order-6");
itemDom.appendChild(cell);
return itemDom;
}
);
searchDom.querySelector("input").setAttribute("placeholder", "Produkte suchen und hinzufügen...");
}
function updateCharts(canvasDom, items) {
let startDate = document.querySelector("#start").value;
let endDate = document.querySelector("#end").value;
if (start > endDate) {
let tmp = start;
start = endDate;
endDate = tmp;
}
showCharts(
canvasDom,
items,
document.querySelector("#sum").checked,
document.querySelector("#sumstores").checked,
document.querySelector("#todayonly").checked,
startDate,
endDate
);
}
function showCart(cart) {
if (cart.items.length == 0) {
document.querySelector("#noproducts").classList.remove("hidden");
document.querySelector("#hasproducts").classList.add("hidden");
} else {
document.querySelector("#noproducts").classList.add("hidden");
document.querySelector("#hasproducts").classList.remove("hidden");
}
let link = encodeURIComponent(cart.name) + ";";
for (cartItem of cart.items) {
link += cartItem.store + cartItem.id + ";";
}
document.querySelector("#cartname").innerHTML = `
Warenkorb '${cart.name}' <a class="text-sm text-primary block hover:underline" href="cart.html?cart=${link}">Teilen</a>
`;
const canvasDom = document.querySelector("#chart");
let items = filter(cart.items);
if (items.length == cart.items.length) {
document.querySelector("#numitems").innerText = `${cart.items.length} Artikel`;
} else {
document.querySelector("#numitems").innerText = `${items.length} / ${cart.items.length} Artikel`;
}
document.querySelector("#json").addEventListener("click", (event) => {
event.preventDefault();
downloadFile("items.json", JSON.stringify(items, null, 2));
});
updateCharts(canvasDom, items);
const itemTable = document.querySelector("#cartitems");
itemTable.innerHTML = "";
header = dom(
"thead",
`<tr>
<th class="text-center">Kette</th>
<th>Name</th>
<th>Preis <span class="expander">+</span></th>
<th></th>
</tr>`
);
const showHideAll = header.querySelectorAll("th:nth-child(3)")[0];
showHideAll.style["cursor"] = "pointer";
showHideAll.showAll = true;
showHideAll.addEventListener("click", () => {
showHideAll.querySelector(".expander").innerText = showHideAll.querySelector(".expander").innerText == "+" ? "-" : "+";
itemTable.querySelectorAll(".priceinfo").forEach((el) => (showHideAll.showAll ? el.classList.remove("hidden") : el.classList.add("hidden")));
showHideAll.showAll = !showHideAll.showAll;
});
itemTable.append(header);
items.forEach((cartItem, idx) => {
const itemDom = itemToDOM(cartItem);
const cell = dom(
"td",
`
<label>
<input type="checkbox" class="hidden peer">
<span class="peer-checked:bg-blue-700 btn-action">📈</span>
</label>
<input type="button" class="ml-auto btn-action" value="-">
<input type="button" class="btn-action" value="▲">
<input type="button" class="btn-action" value="▼">
`
);
cell.classList.add("action");
if (cartItem.chart) cell.children[0].setAttribute("checked", true);
cell.children[0].addEventListener("change", () => {
cartItem.chart = cell.children[0].children[0].checked;
shoppingCarts.save();
updateCharts(canvasDom, items);
});
if (cart.name != "Momentum Eigenmarken Vergleich" && !cart.linked) {
cell.children[1].addEventListener("click", () => {
cart.items.splice(idx, 1);
shoppingCarts.save();
document.querySelector("#start").value = getOldestDate(cart.items);
document.querySelector("#end").value = currentDate();
showCart(cart);
});
cell.children[2].addEventListener("click", () => {
if (idx == 0) return;
let otherItem = cart.items[idx - 1];
cart.items[idx - 1] = cartItem;
cart.items[idx] = otherItem;
shoppingCarts.save();
showCart(cart);
});
cell.children[3].addEventListener("click", () => {
if (idx == cart.items.length - 1) return;
let otherItem = cart.items[idx + 1];
cart.items[idx + 1] = cartItem;
cart.items[idx] = otherItem;
shoppingCarts.save();
showCart(cart);
});
} else {
cell.querySelectorAll("input[type='button']").forEach((button) => button.classList.add("hidden"));
}
itemDom.append(cell);
itemTable.append(itemDom);
});
}
load();

View File

@ -1,44 +1,16 @@
%%_templates/_header.html%% %%_templates/_menu.html%%
<div class="w-full relative px-4">
<div id="cart">
<h1 class="text-2xl font-bold pb-2 pt-8 text-center" id="cartname"></h1>
<div id="noproducts" class="hidden text-center text-gray-600">Noch keine Produkte im Warenkorb</div>
<div id="hasproducts" class="column hidden">
<input type="button" id="save" value="Speichern" class="hidden" />
<div class="bg-stone-200 rounded-xl p-4 max-w-4xl mx-auto md:mb-12 md:mt-6">
<canvas class="bg-white rounded-lg py-4" id="chart"></canvas>
<div class="filters flex items-center flex-wrap justify-center gap-2 pt-2">
<div id="sum-container"></div>
<div id="sumstores-container"></div>
<div id="todayonly-container"></div>
<div
class="cursor-pointer inline-flex items-center gap-x-1 rounded-full bg-white border border-gray-400 px-2 py-1 text-xs font-medium text-gray-600"
>
<input id="start" type="date" />
-
<input id="end" type="date" />
</div>
</div>
</div>
<div class="bg-stone-200 rounded-xl p-4 max-w-4xl mx-auto md:mb-12 md:mt-6">
<div>
<input id="filter" class="search rounded-lg px-2 py-1 w-full mb-4" placeholder="Filtern... (mind. 3 Zeichen)" />
<div class="filters flex justify-center gap-2 flex-wrap" id="filters-store"></div>
</div>
</div>
<div class="px-4 py-2 my-4 text-sm border rounded-xl md:mt-8 md:rounded-b-none md:mb-0 bg-gray-100">
<span id="numitems"></span> <a id="json" href="" class="text-primary font-medium hover:underline">JSON</a>
</div>
<table id="cartitems" class="w-full"></table>
</div>
</div>
<div id="search" class="w-full"></div>
<div class="w-full relative px-4 flex-1">
<cart-header x-id="cartHeader"></cart-header>
<div x-id="noItems" class="hidden block text-center my-3">Noch keine Produkte im Warenkorb</div>
<items-filter x-id="cartFilter" stores nochartclear class="hidden" placeholder="Filtern..."></items-filter>
<items-list x-id="linkedCartList" chart json nosort class="hidden"></items-list>
<items-list x-id="cartList" chart json nosort remove updown class="hidden"></items-list>
<items-filter x-id="productsFilter" class="hidden" stores misc placeholder="Produkt hinzufügen..."></items-filter>
<items-list x-id="productsList" class="hidden" add></items-list>
</div>
<script src="js/alasql.js"></script>
<script src="js/chart.js"></script>
<script src="utils.js"></script>
<script src="cart.js"></script>
%%_templates/_footer.html%%

View File

@ -1,32 +1,87 @@
const shoppingCarts = new ShoppingCarts();
shoppingCarts.load();
const { getQueryParameter } = require("./misc");
const models = require("./model");
const { Model } = require("./model/model");
const { View } = require("./views/view");
const { STORE_KEYS, stores } = require("./model/stores");
require("./views");
async function load() {
const items = await loadItems();
const lookup = {};
for (item of items) {
lookup[item.store + item.id] = item;
let carts = null;
class CartModel extends Model {
constructor(cart, linked) {
super();
this.cart = cart;
this.items = cart.items;
this._filteredItems = [...this.items];
this.linked = linked;
}
get filteredItems() {
return this._filteredItems;
}
set filteredItems(newItems) {
this._filteredItems = newItems;
this.notify();
}
}
class CartHeader extends View {
constructor() {
super();
this.innerHTML = `
<h1 class="text-2xl font-bold pb-2 pt-8 text-center">
<span x-id="name"></span>
</h1>
<a x-id="share" class="hidden cursor-pointer font-bold text-sm text-primary hover:underline block text-center mt-3">Teilen</a>
<input x-id="save" class="hidden cursor-pointer font-bold text-sm text-primary block mx-auto mt-3" type="button" value="Speichern">
`;
const elements = this.elements;
elements.save.addEventListener("click", () => {
const cart = this.model.cart;
let index = carts.findIndex((c) => c.name === cart.name);
if (index != -1) {
if (confirm("Existierenden Warenkorb '" + cart.name + " überschreiben?")) {
carts[index] = cart;
}
} else {
carts.push(cart);
}
// model.carts.save();
location.href = "/cart-new.html?name=" + encodeURIComponent(cart.name);
});
}
render() {
const cart = this.model.cart;
const elements = this.elements;
elements.name.innerText = `Warenkorb '${cart.name}'`;
if (this.model.linked) {
elements.save.classList.remove("hidden");
} else {
elements.share.classList.remove("hidden");
let link = encodeURIComponent(cart.name) + ";";
for (const cartItem of cart.items) {
link += cartItem.store + cartItem.id + ";";
}
elements.share.href = "cart-new.html?cart=" + link;
}
}
}
customElements.define("cart-header", CartHeader);
function loadCart() {
let cart = null;
let linked = false;
const cartName = getQueryParameter("name");
if (cartName) {
for (c of shoppingCarts.carts) {
for (const c of carts) {
if (c.name == cartName) {
cart = c;
break;
}
}
// Update cart pricing info
const items = [];
for (cartItem of cart.items) {
const item = lookup[cartItem.store + cartItem.id];
if (!item) items.push(cartItem);
else items.push(item);
}
cart.items = items;
shoppingCarts.save();
}
const cartDesc = getQueryParameter("cart");
@ -35,25 +90,12 @@ async function load() {
cart = {
name: tokens[0],
items: [],
linked: true,
};
for (let i = 1; i < tokens.length; i++) {
const item = lookup[tokens[i]];
const item = models.items.lookup[tokens[i]];
if (item) cart.items.push(item);
}
let saveButton = document.querySelector("#save");
saveButton.classList.remove("hidden");
saveButton.addEventListener("click", () => {
let index = shoppingCarts.carts.findIndex((c) => c.name === cart.name);
if (index != -1) {
if (confirm("Existierenden Warenkorb '" + cart.name + " überschreiben?")) {
shoppingCarts.carts[index] = cart;
}
} else {
shoppingCarts.carts.push(importedCart);
}
location.href = "/cart.html?name=" + encodeURIComponent(cart.name);
});
linked = true;
}
if (cart == null) {
@ -61,217 +103,54 @@ async function load() {
location.href = "carts.html";
}
if (cart.name != "Momentum Eigenmarken Vergleich" && !cart.linked) showSearch(cart, items);
return new CartModel(cart, linked);
}
const canvasDom = document.querySelector("#chart");
(async () => {
await models.load();
carts = models.carts.carts;
const cart = loadCart();
document.querySelector("#sum-container").innerHTML = customCheckbox(`sum`, "Preissumme Gesamt", true, "gray");
document.querySelector("#sumstores-container").innerHTML = customCheckbox(`sumstores`, "Preissumme pro Kette", true, "gray");
document.querySelector("#todayonly-container").innerHTML = customCheckbox(`todayonly`, "Nur heutige Preise", false, "gray");
const elements = View.elements(document.body);
const cartHeader = elements.cartHeader;
cartHeader.model = cart;
document.querySelector("#sum").addEventListener("change", () => updateCharts(canvasDom, filter(cart.items)));
document.querySelector("#sumstores").addEventListener("change", () => updateCharts(canvasDom, filter(cart.items)));
document.querySelector("#todayonly").addEventListener("change", () => updateCharts(canvasDom, filter(cart.items)));
document.querySelector("#start").addEventListener("change", () => updateCharts(canvasDom, filter(cart.items)));
document.querySelector("#end").addEventListener("change", () => updateCharts(canvasDom, filter(cart.items)));
document.querySelector("#start").value = getOldestDate(cart.items);
document.querySelector("#end").value = currentDate();
const filtersStore = document.querySelector("#filters-store");
filtersStore.innerHTML = `
${customCheckbox("all", "<strong>Alle</strong>", true, "gray")}
${STORE_KEYS.map((store) =>
customCheckbox(store, stores[store].name, stores[store].name.toLowerCase().endsWith("de") ? false : true, stores[store].color)
).join(" ")}
`;
filtersStore.querySelectorAll("input").forEach((input) => {
if (input.id == "all") return;
input.addEventListener("change", () => showCart(cart));
const cartFilter = elements.cartFilter;
const cartList = cart.linked ? elements.linkedCartList : elements.cartList;
STORE_KEYS.forEach((store) => {
cartFilter.elements[store].checked = true;
});
filtersStore.querySelector("#all").addEventListener("change", () => {
STORE_KEYS.forEach((store) => (filtersStore.querySelector(`#${store}`).checked = filtersStore.querySelector("#all").checked));
showCart(cart);
});
document.querySelector("#filter").addEventListener("input", () => showCart(cart));
showCart(cart);
}
cartList.elements.numItemsLabel.innerHTML = "<strong>Artikel:</strong>";
cartList.model = cartFilter.model = cart;
cartList.elements.enableChart.checked = true;
cartList.elements.chart.elements.sumStores.checked = true;
cartList.fireChangeEvent();
cartList.elements.chart.fireChangeEvent();
function filter(cartItems) {
const query = document.querySelector("#filter").value.trim();
const storeCheckboxes = STORE_KEYS.map((store) => document.querySelector(`#${store}`));
const checkedStores = STORE_KEYS.filter((store, i) => storeCheckboxes[i].checked);
let items = [];
if (query.charAt(0) != "!") {
for (item of cartItems) {
if (!checkedStores.includes(item.store)) continue;
items.push(item);
}
} else {
items = cartItems;
}
if (query.length >= 3) items = searchItems(items, document.querySelector("#filter").value, checkedStores, false, 0, 10000, false, false);
return items;
}
function showSearch(cart, items) {
const searchDom = document.querySelector("#search");
searchDom.innerHTML = "";
newSearchComponent(
searchDom,
items,
null,
null,
(header) => {
header.innerHTML += "<th></th>";
return header;
},
(item, itemDom) => {
const cell = dom("td", `<input type="button" class="ml-auto btn-action" value="+">`);
cell.children[0].addEventListener("click", () => {
cart.items.push(item);
shoppingCarts.save();
document.querySelector("#start").value = getOldestDate(cart.items);
document.querySelector("#end").value = currentDate();
showCart(cart);
});
cell.classList.add("order-6");
itemDom.appendChild(cell);
return itemDom;
}
);
searchDom.querySelector("input").setAttribute("placeholder", "Produkte suchen und hinzufügen...");
}
function updateCharts(canvasDom, items) {
let startDate = document.querySelector("#start").value;
let endDate = document.querySelector("#end").value;
if (start > endDate) {
let tmp = start;
start = endDate;
endDate = tmp;
}
showCharts(
canvasDom,
items,
document.querySelector("#sum").checked,
document.querySelector("#sumstores").checked,
document.querySelector("#todayonly").checked,
startDate,
endDate
);
}
function showCart(cart) {
if (cart.items.length == 0) {
document.querySelector("#noproducts").classList.remove("hidden");
document.querySelector("#hasproducts").classList.add("hidden");
elements.noItems.classList.remove("hidden");
} else {
document.querySelector("#noproducts").classList.add("hidden");
document.querySelector("#hasproducts").classList.remove("hidden");
cartFilter.classList.remove("hidden");
cartList.classList.remove("hidden");
}
let link = encodeURIComponent(cart.name) + ";";
for (cartItem of cart.items) {
link += cartItem.store + cartItem.id + ";";
cartList.removeCallback = (item) => models.carts.save();
cartList.upCallback = (item) => models.carts.save();
cartList.downCallback = (item) => models.carts.save();
const productsFilter = elements.productsFilter;
const productsList = elements.productsList;
productsList.model = productsFilter.model = models.items;
if (!cart.linked) {
productsFilter.classList.remove("hidden");
productsList.classList.remove("hidden");
}
document.querySelector("#cartname").innerHTML = `
Warenkorb '${cart.name}' <a class="text-sm text-primary block hover:underline" href="cart.html?cart=${link}">Teilen</a>
`;
const canvasDom = document.querySelector("#chart");
let items = filter(cart.items);
if (items.length == cart.items.length) {
document.querySelector("#numitems").innerText = `${cart.items.length} Artikel`;
} else {
document.querySelector("#numitems").innerText = `${items.length} / ${cart.items.length} Artikel`;
}
document.querySelector("#json").addEventListener("click", (event) => {
event.preventDefault();
downloadFile("items.json", JSON.stringify(items, null, 2));
});
updateCharts(canvasDom, items);
const itemTable = document.querySelector("#cartitems");
itemTable.innerHTML = "";
header = dom(
"thead",
`<tr>
<th class="text-center">Kette</th>
<th>Name</th>
<th>Preis <span class="expander">+</span></th>
<th></th>
</tr>`
);
const showHideAll = header.querySelectorAll("th:nth-child(3)")[0];
showHideAll.style["cursor"] = "pointer";
showHideAll.showAll = true;
showHideAll.addEventListener("click", () => {
showHideAll.querySelector(".expander").innerText = showHideAll.querySelector(".expander").innerText == "+" ? "-" : "+";
itemTable.querySelectorAll(".priceinfo").forEach((el) => (showHideAll.showAll ? el.classList.remove("hidden") : el.classList.add("hidden")));
showHideAll.showAll = !showHideAll.showAll;
});
itemTable.append(header);
items.forEach((cartItem, idx) => {
const itemDom = itemToDOM(cartItem);
const cell = dom(
"td",
`
<label>
<input type="checkbox" class="hidden peer">
<span class="peer-checked:bg-blue-700 btn-action">📈</span>
</label>
<input type="button" class="ml-auto btn-action" value="-">
<input type="button" class="btn-action" value="▲">
<input type="button" class="btn-action" value="▼">
`
);
cell.classList.add("action");
if (cartItem.chart) cell.children[0].setAttribute("checked", true);
cell.children[0].addEventListener("change", () => {
cartItem.chart = cell.children[0].children[0].checked;
shoppingCarts.save();
updateCharts(canvasDom, items);
});
if (cart.name != "Momentum Eigenmarken Vergleich" && !cart.linked) {
cell.children[1].addEventListener("click", () => {
cart.items.splice(idx, 1);
shoppingCarts.save();
document.querySelector("#start").value = getOldestDate(cart.items);
document.querySelector("#end").value = currentDate();
showCart(cart);
});
cell.children[2].addEventListener("click", () => {
if (idx == 0) return;
let otherItem = cart.items[idx - 1];
cart.items[idx - 1] = cartItem;
cart.items[idx] = otherItem;
shoppingCarts.save();
showCart(cart);
});
cell.children[3].addEventListener("click", () => {
if (idx == cart.items.length - 1) return;
let otherItem = cart.items[idx + 1];
cart.items[idx + 1] = cartItem;
cart.items[idx] = otherItem;
shoppingCarts.save();
showCart(cart);
});
} else {
cell.querySelectorAll("input[type='button']").forEach((button) => button.classList.add("hidden"));
}
itemDom.append(cell);
itemTable.append(itemDom);
});
}
load();
productsList.addCallback = (item) => {
cart.items.push(item);
models.carts.save();
cartFilter.filter();
cartFilter.classList.remove("hidden");
cartList.classList.remove("hidden");
};
})();

View File

@ -17,7 +17,8 @@ require("./views");
.filter((item) => item.chart)
.map((item) => item.store + item.id)
.join(";");
history.pushState({}, null, location.pathname + "?f=" + filterState + "&l=" + listState + "&c=" + chartState + "&d=" + chartedItems);
history.replaceState({}, null, location.pathname + "?f=" + filterState + "&l=" + listState + "&c=" + chartState + "&d=" + chartedItems);
};
itemsFilter.addEventListener("x-change", stateToUrl);

View File

@ -2,3 +2,4 @@ require("./custom-checkbox");
require("./carts-list");
require("./items-filter");
require("./items-list");
require("./view");

View File

@ -11,6 +11,7 @@ class ItemsFilter extends View {
this._filterByPriceDirection = getBooleanAttribute(this, "pricedirection");
this._filterByStores = getBooleanAttribute(this, "stores");
this._filterByMisc = getBooleanAttribute(this, "misc");
this._noChartClear = getBooleanAttribute(this, "nochartclear");
const hidePriceChanges = this._filterByPriceChanges ? "" : "hidden";
const hidePriceDirection = this._filterByPriceDirection ? "" : "hidden";
@ -76,8 +77,8 @@ class ItemsFilter extends View {
elements.stores.classList.add("hidden");
elements.misc.classList.add("hidden");
} else {
elements.stores.classList.remove("hidden");
elements.misc.classList.remove("hidden");
if (!hideStores) elements.stores.classList.remove("hidden");
if (!hideMisc) elements.misc.classList.remove("hidden");
}
});
@ -193,7 +194,7 @@ class ItemsFilter extends View {
filteredItems = queryItems(query, filteredItems, elements.exact.checked);
}
if (this.model.lastQuery != query) {
if (this.model.lastQuery != query && !this._noChartClear) {
filteredItems.forEach((item) => (item.chart = false));
}
this.model.lastQuery = query;

View File

@ -11,21 +11,25 @@ class ItemsList extends View {
this._json = getBooleanAttribute(this, "json");
this._chart = getBooleanAttribute(this, "chart");
this._remove = getBooleanAttribute(this, "remove");
this._remove = getBooleanAttribute(this, "add");
this._add = getBooleanAttribute(this, "add");
this._updown = getBooleanAttribute(this, "updown");
this._noSort = getBooleanAttribute(this, "nosort");
const hideSort = this._noSort ? "hidden" : "";
this.innerHTML = /*html*/ `
<div x-id="options" class="hidden flex flex-col md:flex-row gap-4 px-4 py-2 my-4 justify-between items-center text-sm border rounded-xl md:mt-8 md:rounded-b-none md:mb-0 bg-gray-100 ">
<div>
<div class="flex flex-col md:flex-row gap-2 items-center">
<span x-id="numItems"></span>
<span x-id="numItemsLabel">Resultate</span><span x-id="numItems"></span>
<span>
<a x-id="json" class="hidden text-primary font-medium hover:underline" href="">JSON</a>
</span>
<custom-checkbox x-id="enableChart" x-change x-state label="Diagramm" class="${this._chart}"></custom-checkbox>
<custom-checkbox x-id="enableChart" x-change x-state label="Diagramm" class="${
this._chart ? "" : "hidden"
}"></custom-checkbox>
</div>
</div>
<label>
<label class="${hideSort}">
Sortieren
<select x-id="sort" x-change x-state>
<option value="price-asc">Preis aufsteigend</option>
@ -180,7 +184,7 @@ class ItemsList extends View {
<input x-id="chartCheckbox" type="checkbox" class="hidden peer">
<span class="peer-checked:bg-blue-700 btn-action">📈</span>
</label>
<input x-id="add" type="button" class="${this._remove ? "" : "hidden"} btn-action" value="+">
<input x-id="add" type="button" class="${this._add ? "" : "hidden"} btn-action" value="+">
<input x-id="remove" type="button" class="${this._remove ? "" : "hidden"} btn-action" value="-">
<input x-id="up" type="button" class="${this._updown ? "" : "hidden"} btn-action" value="▲">
<input x-id="down" type="button" class="${this._updown ? "" : "hidden"} btn-action" value="▼">
@ -277,6 +281,53 @@ class ItemsList extends View {
this.fireChangeEvent();
});
}
if (this.model.items.length != this.model.filteredItems.length) {
elements.up.classList.add("hidden");
elements.down.classList.add("hidden");
}
elements.add.addEventListener("click", () => {
if (this._addCallback) this._addCallback(item);
});
elements.remove.addEventListener("click", () => {
let index = this.model.items.indexOf(item);
if (index >= 0) this.model.items.splice(index, 1);
index = this.model.filteredItems.indexOf(item);
if (index >= 0) this.model.filteredItems.splice(index, 1);
itemDom.remove();
this.elements.chart.render();
if (this._removeCallback) this._removeCallback(item);
});
elements.up.addEventListener("click", () => {
const index = itemDom.rowIndex - 1;
if (index == 0) return;
let otherItem = this.model.items[index - 1];
this.model.items[index - 1] = item;
this.model.items[index] = otherItem;
let rowBefore = this.elements.tableBody.rows[index - 1];
this.elements.tableBody.insertBefore(itemDom, rowBefore);
if (this._upCallback) this._upCallback(item);
});
elements.down.addEventListener("click", () => {
const index = itemDom.rowIndex - 1;
if (index == this.model.items.length - 1) return;
let otherItem = this.model.items[index + 1];
this.model.items[index + 1] = item;
this.model.items[index] = otherItem;
let rowAfter = this.elements.tableBody.rows[index + 1];
this.elements.tableBody.insertBefore(rowAfter, itemDom);
if (this._downCallback) this._downCallback(item);
});
if (this._showAllPriceHistories) elements.priceHistory.classList.remove("hidden");
return itemDom;
}
@ -295,8 +346,10 @@ class ItemsList extends View {
if (this.model.lastQuery && this.model.lastQuery.charAt(0) == "!") {
elements.sort.parentElement.classList.add("hidden");
} else {
elements.sort.parentElement.classList.remove("hidden");
items = this.sort(items);
if (!this._noSort) {
elements.sort.parentElement.classList.remove("hidden");
items = this.sort(items);
}
}
if (items.length == 0) {
elements.chart.classList.add("hidden");
@ -307,21 +360,20 @@ class ItemsList extends View {
elements.options.classList.remove("hidden");
elements.itemsTable.classList.remove("hidden");
}
elements.numItems.innerHTML =
"<strong>Resultate:</strong> " + items.length + (this.model.totalItems > items.length ? " / " + this.model.totalItems : "");
elements.numItems.innerHTML = items.length + (this.model.totalItems > items.length ? " / " + this.model.totalItems : "");
const tableBody = elements.tableBody;
tableBody.innerHTML = "";
let i = 0;
const batches = [];
let batch = [];
for (const item of items) {
items.forEach((item, index) => {
if (batch.length == 100) {
batches.push(batch);
batch = [];
}
batch.push(item);
}
});
if (batch.length > 0) batches.push(batch);
const renderBatch = () => {
@ -339,6 +391,22 @@ class ItemsList extends View {
log(`ItemsList - rendering ${items.length} items took ${deltaTime(start).toFixed(4)} secs`);
}
set addCallback(callback) {
this._addCallback = callback;
}
set removeCallback(callback) {
this._removeCallback = callback;
}
set upCallback(callback) {
this._upCallback = callback;
}
set downCallback(callback) {
this._downCallback = callback;
}
}
customElements.define("items-list", ItemsList);

View File

@ -41,7 +41,7 @@ class View extends HTMLElement {
const result = {};
elements.forEach((element) => {
if (result[element.getAttribute("x-id")]) {
console.log(`Duplicate element x-id ${element.getAttribute("x-id")} in ${view.localName}`);
log(`View - Duplicate element x-id ${element.getAttribute("x-id")} in ${view.localName}`);
}
result[element.getAttribute("x-id")] = element;
});