New index.html/index.js using the new framework. State serialization fixes.

This commit is contained in:
Mario Zechner 2023-06-11 23:49:18 +02:00
parent 846f9b5861
commit 75367c6852
13 changed files with 259 additions and 222 deletions

View File

@ -105,7 +105,7 @@ async function bundleJS(inputDir, outputDir, watch) {
entryPoints: {
carts: `${inputDir}/carts.js`,
"changes-new": `${inputDir}/changes-new.js`,
"index-new": `${inputDir}/index-new.js`,
index: `${inputDir}/index.js`,
},
bundle: true,
sourcemap: true,

View File

@ -1,11 +0,0 @@
%%_templates/_header.html%% %%_templates/_menu.html%%
<div class="w-full max-w-5-xl relative px-4 flex-1">
<h1 class="text-2xl font-bold pb-2 pt-8 text-center">Produktsuche</h1>
<items-filter x-id="items-filter" emptyquery stores misc></items-filter>
<items-list chart></items-list>
</div>
<script src="index-new.js"></script>
%%_templates/_footer.html%%

View File

@ -1,10 +0,0 @@
const model = require("./model");
require("./views");
(async () => {
await model.load();
const itemsFilter = document.querySelector("items-filter");
const itemsList = document.querySelector("items-list");
itemsFilter.model = itemsList.model = model.items;
itemsFilter.filter();
})();

31
site/index-old.html Normal file
View File

@ -0,0 +1,31 @@
%%_templates/_header.html%% %%_templates/_menu.html%%
<div class="w-full relative px-4 flex-1">
<div class="max-w-5xl text-center mx-auto">
<h1 class="text-2xl font-bold pb-2 pt-8">Produktsuche</h1>
<div>
<div id="chart" class="hidden bg-stone-200 rounded-xl p-4 max-w-4xl mx-auto md:mb-12 md:mt-6">
<canvas class="bg-white rounded-lg mb-2"></canvas>
<div class="flex items-center flex-wrap justify-center gap-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>
<div id="search" class="w-full"></div>
</div>
</div>
<script src="js/alasql.js"></script>
<script src="js/chart.js"></script>
<script src="utils.js"></script>
<script src="index.js"></script>
%%_templates/_footer.html%%

109
site/index-old.js Normal file
View File

@ -0,0 +1,109 @@
function updateCharts(canvasDom, items) {
const now = performance.now();
const sum = document.querySelector("#sum").checked;
const sumStores = document.querySelector("#sumstores").checked;
const todayOnly = document.querySelector("#todayonly").checked;
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, sum, sumStores, todayOnly, startDate, endDate);
console.log("Updating charts took: " + (performance.now() - now) / 1000 + " seconds");
}
async function load() {
const items = await loadItems();
const chartDom = document.querySelector("#chart");
const canvasDom = chartDom.querySelector("canvas");
let lastHits = null;
document.querySelector("#sum-container").innerHTML = customCheckbox(`sum`, "Preissumme Gesamt", false, "gray");
document.querySelector("#sumstores-container").innerHTML = customCheckbox(`sumstores`, "Preissumme pro Kette", false, "gray");
document.querySelector("#todayonly-container").innerHTML = customCheckbox(`todayonly`, "Nur heutige Preise", false, "gray");
document.querySelector("#sum").addEventListener("change", () => updateCharts(canvasDom, lastHits));
document.querySelector("#sumstores").addEventListener("change", () => updateCharts(canvasDom, lastHits));
document.querySelector("#todayonly").addEventListener("change", () => updateCharts(canvasDom, lastHits));
document.querySelector("#start").addEventListener("change", () => updateCharts(canvasDom, lastHits));
document.querySelector("#end").addEventListener("change", () => updateCharts(canvasDom, lastHits));
document.querySelector("#start").value = getOldestDate(items);
document.querySelector("#end").value = currentDate();
let chartEnabled = false;
let search = newSearchComponent(
document.querySelector("#search"),
items,
(hits) => {
items.forEach((item) => (item.chart = false));
if (!chartEnabled) {
chartDom.classList.add("hidden");
} else {
if (hits.length > 0) {
chartDom.classList.remove("hidden");
} else {
chartDom.classList.add("hidden");
}
updateCharts(canvasDom, hits);
}
lastHits = hits;
return hits;
},
null,
(header) => {
header.innerHTML += "<th></th>";
return header;
},
(item, itemDom, items, setQuery) => {
const checked = (item.chart = (getQueryParameter("c") ?? []).includes(`${item.store}:${item.id}`));
const dataId = item.store + ":" + item.id;
const cell = dom(
"td",
`<label class="flex">
<input class="hidden peer" type="checkbox" ${checked ? "checked" : ""} data-id="${dataId}">
<span class="ml-auto peer-checked:bg-blue-700 group-[.decreased]:scale-x-flip btn-action">📈</span>
</label>`
);
itemDom.appendChild(cell);
const handleClick = (eventShouldSetQuery = false) => {
item.chart = cell.children[0].children[0].checked;
if (item.chart && !search.chart.checked) {
search.chart.checked = true;
search.chart.dispatchEvent(new Event("change"));
}
updateCharts(canvasDom, lastHits);
!!eventShouldSetQuery && setQuery();
};
cell.addEventListener("click", handleClick);
checked && handleClick();
return itemDom;
},
(checked) => {
chartEnabled = checked;
if (checked) {
if (lastHits.length > 0) {
chartDom.classList.remove("hidden");
updateCharts(canvasDom, lastHits);
} else {
chartDom.classList.add("hidden");
}
} else {
chartDom.classList.add("hidden");
}
}
);
const query = getQueryParameter("q");
if (query) {
document.querySelector(".search").value = query;
const inputEvent = new Event("input", {
bubbles: true,
cancelable: false,
});
document.querySelector(".search").dispatchEvent(inputEvent);
}
}
load();

View File

@ -1,31 +1,11 @@
%%_templates/_header.html%% %%_templates/_menu.html%%
<div class="w-full relative px-4 flex-1">
<div class="max-w-5xl text-center mx-auto">
<h1 class="text-2xl font-bold pb-2 pt-8">Produktsuche</h1>
<div>
<div id="chart" class="hidden bg-stone-200 rounded-xl p-4 max-w-4xl mx-auto md:mb-12 md:mt-6">
<canvas class="bg-white rounded-lg mb-2"></canvas>
<div class="flex items-center flex-wrap justify-center gap-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>
<div id="search" class="w-full"></div>
</div>
<div class="w-full max-w-5-xl relative px-4 flex-1">
<h1 class="text-2xl font-bold pb-2 pt-8 text-center">Produktsuche</h1>
<items-filter x-id="items-filter" emptyquery stores misc></items-filter>
<items-list share json chart></items-list>
</div>
<script src="js/alasql.js"></script>
<script src="js/chart.js"></script>
<script src="utils.js"></script>
<script src="index.js"></script>
%%_templates/_footer.html%%

View File

@ -1,109 +1,30 @@
function updateCharts(canvasDom, items) {
const now = performance.now();
const sum = document.querySelector("#sum").checked;
const sumStores = document.querySelector("#sumstores").checked;
const todayOnly = document.querySelector("#todayonly").checked;
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, sum, sumStores, todayOnly, startDate, endDate);
console.log("Updating charts took: " + (performance.now() - now) / 1000 + " seconds");
}
async function load() {
const items = await loadItems();
const chartDom = document.querySelector("#chart");
const canvasDom = chartDom.querySelector("canvas");
let lastHits = null;
const { getQueryParameter } = require("./misc");
const model = require("./model");
require("./views");
document.querySelector("#sum-container").innerHTML = customCheckbox(`sum`, "Preissumme Gesamt", false, "gray");
document.querySelector("#sumstores-container").innerHTML = customCheckbox(`sumstores`, "Preissumme pro Kette", false, "gray");
document.querySelector("#todayonly-container").innerHTML = customCheckbox(`todayonly`, "Nur heutige Preise", false, "gray");
(async () => {
await model.load();
const itemsFilter = document.querySelector("items-filter");
const itemsList = document.querySelector("items-list");
const itemsChart = document.querySelector("items-chart");
itemsFilter.model = itemsList.model = model.items;
document.querySelector("#sum").addEventListener("change", () => updateCharts(canvasDom, lastHits));
document.querySelector("#sumstores").addEventListener("change", () => updateCharts(canvasDom, lastHits));
document.querySelector("#todayonly").addEventListener("change", () => updateCharts(canvasDom, lastHits));
const stateToUrl = (event) => {
const filterState = JSON.stringify(itemsFilter.state);
const listState = JSON.stringify(itemsList.state);
const chartState = JSON.stringify(itemsChart.state);
history.pushState({}, null, location.pathname + "?f=" + filterState + "&l=" + listState + "&c=" + chartState);
};
document.querySelector("#start").addEventListener("change", () => updateCharts(canvasDom, lastHits));
document.querySelector("#end").addEventListener("change", () => updateCharts(canvasDom, lastHits));
document.querySelector("#start").value = getOldestDate(items);
document.querySelector("#end").value = currentDate();
itemsFilter.addEventListener("x-change", stateToUrl);
itemsList.addEventListener("x-change", stateToUrl);
let chartEnabled = false;
let search = newSearchComponent(
document.querySelector("#search"),
items,
(hits) => {
items.forEach((item) => (item.chart = false));
if (!chartEnabled) {
chartDom.classList.add("hidden");
} else {
if (hits.length > 0) {
chartDom.classList.remove("hidden");
} else {
chartDom.classList.add("hidden");
}
updateCharts(canvasDom, hits);
}
lastHits = hits;
return hits;
},
null,
(header) => {
header.innerHTML += "<th></th>";
return header;
},
(item, itemDom, items, setQuery) => {
const checked = (item.chart = (getQueryParameter("c") ?? []).includes(`${item.store}:${item.id}`));
const dataId = item.store + ":" + item.id;
const cell = dom(
"td",
`<label class="flex">
<input class="hidden peer" type="checkbox" ${checked ? "checked" : ""} data-id="${dataId}">
<span class="ml-auto peer-checked:bg-blue-700 group-[.decreased]:scale-x-flip btn-action">📈</span>
</label>`
);
itemDom.appendChild(cell);
const f = getQueryParameter("f");
const l = getQueryParameter("l");
const c = getQueryParameter("c");
if (f) itemsFilter.state = JSON.parse(f);
if (l) itemsList.state = JSON.parse(l);
if (c) itemsChart.state = JSON.parse(c);
const handleClick = (eventShouldSetQuery = false) => {
item.chart = cell.children[0].children[0].checked;
if (item.chart && !search.chart.checked) {
search.chart.checked = true;
search.chart.dispatchEvent(new Event("change"));
}
updateCharts(canvasDom, lastHits);
!!eventShouldSetQuery && setQuery();
};
cell.addEventListener("click", handleClick);
checked && handleClick();
return itemDom;
},
(checked) => {
chartEnabled = checked;
if (checked) {
if (lastHits.length > 0) {
chartDom.classList.remove("hidden");
updateCharts(canvasDom, lastHits);
} else {
chartDom.classList.add("hidden");
}
} else {
chartDom.classList.add("hidden");
}
}
);
const query = getQueryParameter("q");
if (query) {
document.querySelector(".search").value = query;
const inputEvent = new Event("input", {
bubbles: true,
cancelable: false,
});
document.querySelector(".search").dispatchEvent(inputEvent);
}
}
load();
itemsFilter.filter();
})();

View File

@ -38,8 +38,6 @@ if (typeof window !== "undefined") {
script.onload = () => {
let lastChangeTimestamp = null;
let socket = io({ transports: ["websocket"] });
socket.on("connect", () => console.log("Connected"));
socket.on("disconnect", () => console.log("Disconnected"));
socket.on("message", (timestamp) => {
if (lastChangeTimestamp != timestamp) {
setTimeout(() => location.reload(), 100);
@ -89,6 +87,12 @@ exports.dom = (element, innerHTML) => {
return el;
};
exports.getQueryParameter = (name) => {
const url = new URL(window.location.href);
const params = url.searchParams.getAll(name);
return params.length > 1 ? params : params?.[0];
};
exports.getBooleanAttribute = (element, name) => {
return element.hasAttribute(name) && (element.getAttribute(name).length == 0 || element.getAttribute(name) === "true");
};
@ -220,3 +224,16 @@ exports.onVisibleOnce = (target, callback) => {
});
observer.observe(target);
};
exports.log = (message) => {
const now = new Date();
const hours = String(now.getHours()).padStart(2, "0");
const minutes = String(now.getMinutes()).padStart(2, "0");
const seconds = String(now.getSeconds()).padStart(2, "0");
console.log(`${hours}:${minutes}:${seconds}: ${message}`);
};
exports.deltaTime = (start) => {
return (performance.now() - start) / 1000;
};

View File

@ -1,3 +1,4 @@
const { deltaTime, log } = require("../misc");
const { stores, STORE_KEYS } = require("./stores");
const { Model } = require("./model");
@ -69,32 +70,29 @@ class Items extends Model {
}
async load() {
let now = performance.now();
let start = performance.now();
const compressedItemsPerStore = [];
for (const store of STORE_KEYS) {
compressedItemsPerStore.push(
new Promise(async (resolve) => {
const now = performance.now();
const start = performance.now();
try {
const response = await fetch(`data/latest-canonical.${store}.compressed.json`);
const json = await response.json();
console.log(`Loading compressed items for ${store} took ${(performance.now() - now) / 1000} secs`);
log(`Items - loading compressed items for ${store} took ${deltaTime(start)} secs`);
resolve(decompress(json));
} catch (e) {
console.error(e);
console.log(
`Error while loading compressed items for ${store}. It took ${(performance.now() - now) / 1000} secs, continueing...`
);
log(`Items - error while loading compressed items for ${store} ${e.message}`);
resolve([]);
}
})
);
}
const items = [].concat(...(await Promise.all(compressedItemsPerStore)));
console.log("Loading compressed items in parallel took " + (performance.now() - now) / 1000 + " secs");
log(`Items - loaded ${items.length} items took ${deltaTime(start).toFixed(4)} secs`);
const lookup = {};
now = performance.now();
start = performance.now();
for (const item of items) {
lookup[item.store + item.id] = item;
item.search = item.name + " " + item.quantity + " " + item.unit;
@ -137,7 +135,7 @@ class Items extends Model {
return 0;
});
console.log("Processing items took " + (performance.now() - now) / 1000 + " secs");
log(`Items - processing ${items.length} items took ${deltaTime(start).toFixed(4)} secs`);
this._items = items;
this._lookup = lookup;

View File

@ -1,5 +1,5 @@
const { STORE_KEYS } = require("../model/stores");
const { today } = require("../misc");
const { today, log, deltaTime } = require("../misc");
const { View } = require("./view");
require("./custom-checkbox");
const { Chart, registerables } = require("chart.js");
@ -13,20 +13,20 @@ class ItemsChart extends View {
<div class="bg-stone-200 p-4 mx-auto">
<canvas x-id="canvas" class="bg-white rounded-lg py-4"></canvas>
<div class="filters flex items-center flex-wrap justify-center gap-2 pt-2">
<custom-checkbox x-id="sumTotal" x-change label="Preissumme Gesamt"></custom-checkbox>
<custom-checkbox x-id="sumStores" x-change label="Preissumme Ketten"></custom-checkbox>
<custom-checkbox x-id="onlyToday" x-change label="Nur heutige Preise"></custom-checkbox>
<custom-checkbox x-id="sumTotal" x-change x-state label="Preissumme Gesamt"></custom-checkbox>
<custom-checkbox x-id="sumStores" x-change x-state label="Preissumme Ketten"></custom-checkbox>
<custom-checkbox x-id="onlyToday" x-change x-state label="Nur heutige Preise"></custom-checkbox>
<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 x-id="startDate" x-change type="date" value="2020-01-01" />
<input x-id="startDate" x-change x-state type="date" value="2020-01-01" />
-
<input x-id="endDate" x-change type="date" value="${today()}"/>
<input x-id="endDate" x-change x-state type="date" value="${today()}"/>
</div>
</div>
</div>
`;
this.setupEventHandlers();
this.addEventListener("change", () => {
this.addEventListener("x-change", () => {
this.render();
});
}
@ -140,6 +140,7 @@ class ItemsChart extends View {
render() {
if (!this.model) return;
const start = performance.now();
const items = this.model.filteredItems;
const elements = this.elements;
const onlyToday = this.elements.onlyToday.checked;
@ -177,9 +178,8 @@ class ItemsChart extends View {
}
});
console.log("Items to show " + itemsToShow.length);
this.renderChart(itemsToShow, onlyToday ? "bar" : "line");
log(`ItemsChart - charted ${itemsToShow.length} items in ${deltaTime(start).toFixed(2)} secs`);
}
}
customElements.define("items-chart", ItemsChart);

View File

@ -1,4 +1,4 @@
const { today, parseNumber, dom, getBooleanAttribute, queryItems } = require("../misc");
const { today, parseNumber, dom, getBooleanAttribute, queryItems, log, deltaTime } = require("../misc");
const { stores, STORE_KEYS, BUDGET_BRANDS } = require("../model/stores");
const { View } = require("./view");
@ -19,7 +19,7 @@ class ItemsFilter extends View {
const placeholder = this.hasAttribute("placeholder") ? this.getAttribute("placeholder") : "Produkte suchen...";
this.innerHTML = /*html*/ `
<input x-id="query" x-state x-input class="rounded-lg px-2 py-1 w-full" type="text" placeholder="${placeholder}" />
<input x-id="query" x-state x-input-debounce class="rounded-lg px-2 py-1 w-full" type="text" placeholder="${placeholder}" />
<div x-id="stores" class="flex justify-center gap-2 flex-wrap mt-4 ${hideStores}">
<custom-checkbox x-id="allStores" label="Alle" checked></custom-checkbox>
@ -70,7 +70,7 @@ class ItemsFilter extends View {
const elements = this.elements;
elements.query.addEventListener("input", (event) => {
elements.query.addEventListener("input", () => {
let query = elements.query.value.trim();
if (query.length > 0 && query.charAt(0) == "!") {
elements.stores.classList.add("hidden");
@ -81,22 +81,19 @@ class ItemsFilter extends View {
}
});
elements.allStores.addEventListener("change", (event) => {
event.stopPropagation();
elements.allStores.addEventListener("change", () => {
const checked = elements.allStores.checked;
STORE_KEYS.forEach((store) => (elements[store].checked = checked));
this.fireChangeEvent();
});
elements.priceChangesToday.addEventListener("change", (event) => {
event.stopPropagation();
elements.priceChangesToday.addEventListener("change", () => {
if (elements.priceChangesToday.checked) elements.priceDirection.classList.remove("hidden");
else elements.priceDirection.classList.add("hidden");
this.fireChangeEvent();
});
elements.priceChangesSinceLast.addEventListener("change", (event) => {
event.stopPropagation();
elements.priceChangesSinceLast.addEventListener("change", () => {
if (elements.priceChangesSinceLast.checked) elements.priceDirection.classList.add("hidden");
else elements.priceDirection.classList.remove("hidden");
this.fireChangeEvent();
@ -104,7 +101,7 @@ class ItemsFilter extends View {
this.setupEventHandlers();
this.addEventListener("x-change", (event) => {
this.addEventListener("x-change", () => {
this.filter();
});
}
@ -112,7 +109,7 @@ class ItemsFilter extends View {
filter() {
if (!this.model) return;
const now = performance.now();
const start = performance.now();
const elements = this.elements;
this.model.totalItems = this.model.items.length;
let filteredItems = [...this.model.items];
@ -193,7 +190,7 @@ class ItemsFilter extends View {
}
if (query.length > 0) {
filteredItems = queryItems(query, filteredItems);
filteredItems = queryItems(query, filteredItems, elements.exact.checked);
}
if (this._lastQuery != query) {
@ -201,7 +198,7 @@ class ItemsFilter extends View {
}
this._lastQuery = query;
console.log("Filtering items took " + (performance.now() - now) / 1000 + " secs");
log(`ItemsFilter - Filtering ${this.model.items.length} took ${deltaTime(start).toFixed(4)} secs`);
this.model.removeListener(this._listener);
this.model.filteredItems = filteredItems;
@ -209,7 +206,7 @@ class ItemsFilter extends View {
}
render() {
const now = performance.now();
const start = performance.now();
const elements = this.elements;
const items = this.model.items;
const dates = {};
@ -236,7 +233,7 @@ class ItemsFilter extends View {
priceChangesDates.append(dateDom);
}
console.log("Rendering items filter took " + (performance.now() - now) / 1000);
log(`ItemsFilter - rendering items filter took ${deltaTime(start)}`);
}
get checkedStores() {

View File

@ -1,4 +1,4 @@
const { downloadJSON, dom, onVisibleOnce, isMobile, getBooleanAttribute } = require("../misc");
const { downloadJSON, dom, onVisibleOnce, isMobile, getBooleanAttribute, deltaTime, log } = require("../misc");
const { vectorizeItems, similaritySortItems } = require("../knn");
const { stores } = require("../model/stores");
const { View } = require("./view");
@ -8,7 +8,6 @@ class ItemsList extends View {
constructor() {
super();
this._share = getBooleanAttribute(this, "share");
this._json = getBooleanAttribute(this, "json");
this._chart = getBooleanAttribute(this, "chart");
this._remove = getBooleanAttribute(this, "remove");
@ -21,15 +20,14 @@ class ItemsList extends View {
<div class="flex flex-col md:flex-row gap-2 items-center">
<span x-id="numItems"></span>
<span>
<a x-id="shareLink" class="hidden querylink text-primary font-medium hover:underline">Teilen</a>
<a x-id="json" class="hidden text-primary font-medium hover:underline" href="">JSON</a>
</span>
<custom-checkbox x-id="enableChart" label="Diagramm" class="${this._chart}"></custom-checkbox>
<custom-checkbox x-id="enableChart" x-change x-state label="Diagramm" class="${this._chart}"></custom-checkbox>
</div>
</div>
<label>
Sortieren
<select x-id="sort" x-change>
<select x-id="sort" x-change x-state>
<option value="price-asc">Preis aufsteigend</option>
<option value="price-desc">Preis absteigend</option>
<option value="quantity-asc">Menge aufsteigend</option>
@ -71,10 +69,6 @@ class ItemsList extends View {
else elements.chart.classList.add("hidden");
});
elements.chart.addEventListener("change", (event) => {
event.stopPropagation();
});
// Cache in a field, so we don't have to call this.elements in each renderItem() call.
this._showAllPriceHistories = false;
elements.expandPriceHistories.addEventListener("click", () => {
@ -83,7 +77,9 @@ class ItemsList extends View {
elements.tableBody.querySelectorAll(".priceinfo").forEach((el) => (showAll ? el.classList.remove("hidden") : el.classList.add("hidden")));
});
this.addEventListener("change", () => {
this.setupEventHandlers();
this.addEventListener("x-change", (event) => {
this.render();
});
}
@ -270,7 +266,6 @@ class ItemsList extends View {
document.activeElement.blur();
});
elements.chartCheckbox.addEventListener("change", (event) => {
event.stopPropagation();
item.chart = elements.chartCheckbox.checked;
this.elements.chart.render();
});
@ -280,12 +275,13 @@ class ItemsList extends View {
}
render() {
const start = performance.now();
const elements = this.elements;
if (this.model.filteredItems.length != 0 && this.model.filteredItems.length <= (isMobile() ? 200 : 1000)) {
elements.nameSimilarity.removeAttribute("disabled");
} else {
elements.nameSimilarity.setAttribute("disabled", "true");
if (elements.sort.value == "name-similarity") elements.sort.value = "price-asc";
if (this.model.filteredItems.length != 0 && elements.sort.value == "name-similarity") elements.sort.value = "price-asc";
}
const items = this.sort([...this.model.filteredItems]);
@ -303,7 +299,6 @@ class ItemsList extends View {
const tableBody = elements.tableBody;
tableBody.innerHTML = "";
const now = performance.now();
let i = 0;
const batches = [];
let batch = [];
@ -329,7 +324,7 @@ class ItemsList extends View {
renderBatch();
console.log("Rendering items list took " + ((performance.now() - now) / 1000).toFixed(4) + " secs");
log(`ItemsList - rendering ${items.length} items took ${deltaTime(start).toFixed(4)} secs`);
}
}

View File

@ -1,4 +1,4 @@
const { getBooleanAttribute } = require("../misc");
const { getBooleanAttribute, log } = require("../misc");
class View extends HTMLElement {
constructor() {
@ -63,20 +63,38 @@ class View extends HTMLElement {
return this._model;
}
static getStateProperty(element) {
if (element instanceof HTMLInputElement) {
if (element.type === "checkbox" || element.type === "radio") {
return "checked";
} else {
return "value";
}
} else if (element instanceof HTMLOptionElement) {
return "selected";
} else if (element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
return "value";
} else if (element.localName === "custom-checkbox") {
return "checked";
} else {
return null;
}
}
get state() {
const elements = this.elements;
const properties = ["checked", "value"];
const state = {};
for (const key of Object.keys(elements)) {
const element = elements[key];
if (!element.hasAttribute("x-state")) continue;
const elementState = {};
for (const property of properties) {
if (property in element) {
elementState[property] = element[property];
}
const property = View.getStateProperty(element);
if (property == null) {
log(`View.state() - Unknown state property for element ${element.getAttribute("x-id")} in ${this.localName}`);
continue;
}
if (property in element) {
state[key] = element[property];
}
state[key] = elementState;
}
return state;
}
@ -84,20 +102,12 @@ class View extends HTMLElement {
set state(state) {
const elements = this.elements;
this._disableChangeEvent = true;
for (const key of Object.keys(elements)) {
for (const key of Object.keys(state)) {
const elementState = state[key];
if (elementState) {
const element = elements[key];
for (const property in elementState) {
element[property] = elementState[property];
if (element.localName === "input" && element.getAttribute("type") === "radio") {
const changeEvent = new CustomEvent("x-change", {
bubbles: true,
cancelable: true,
});
element.dispatchEvent(changeEvent);
}
}
const element = elements[key];
if (element) {
const property = View.getStateProperty(element);
element[property] = elementState;
}
}
this._disableChangeEvent = false;