Skip to content

Remove mockup storage and inline styles from web componenets todoMVC #521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions resources/tests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,39 @@ Suites.push({
],
});

Suites.push({
name: "TodoMVC-WebComponents-Old",
url: "resources/todomvc/vanilla-examples/javascript-web-components-old/dist/index.html",
tags: ["todomvc", "webcomponents"],
async prepare(page) {
await page.waitForElement("todo-app");
},
tests: [
new BenchmarkTestStep(`Adding${numberOfItemsToAdd}Items`, (page) => {
const input = page.querySelector(".new-todo-input", ["todo-app", "todo-topbar"]);
for (let i = 0; i < numberOfItemsToAdd; i++) {
input.setValue(getTodoText(defaultLanguage, i));
input.dispatchEvent("input");
input.enter("keyup");
}
}),
new BenchmarkTestStep("CompletingAllItems", (page) => {
const items = page.querySelectorAll("todo-item", ["todo-app", "todo-list"]);
for (let i = 0; i < numberOfItemsToAdd; i++) {
const item = items[i].querySelectorInShadowRoot(".toggle-todo-input");
item.click();
}
}),
new BenchmarkTestStep("DeletingAllItems", (page) => {
const items = page.querySelectorAll("todo-item", ["todo-app", "todo-list"]);
for (let i = numberOfItemsToAdd - 1; i >= 0; i--) {
const item = items[i].querySelectorInShadowRoot(".remove-todo-button");
item.click();
}
}),
],
});

Suites.push({
name: "TodoMVC-WebComponents-Complex-DOM",
url: "resources/todomvc/vanilla-examples/javascript-web-components-complex/dist/index.html",
Expand Down Expand Up @@ -308,6 +341,40 @@ Suites.push({
],
});

Suites.push({
name: "TodoMVC-WebComponents-Old-Complex-DOM",
url: "resources/todomvc/vanilla-examples/javascript-web-components-old-complex/dist/index.html",
tags: ["todomvc", "webcomponents", "complex"],
disabled: true,
async prepare(page) {
await page.waitForElement("todo-app");
},
tests: [
new BenchmarkTestStep(`Adding${numberOfItemsToAdd}Items`, (page) => {
const input = page.querySelector(".new-todo-input", ["todo-app", "todo-topbar"]);
for (let i = 0; i < numberOfItemsToAdd; i++) {
input.setValue(getTodoText(defaultLanguage, i));
input.dispatchEvent("input");
input.enter("keyup");
}
}),
new BenchmarkTestStep("CompletingAllItems", (page) => {
const items = page.querySelectorAll("todo-item", ["todo-app", "todo-list"]);
for (let i = 0; i < numberOfItemsToAdd; i++) {
const item = items[i].querySelectorInShadowRoot(".toggle-todo-input");
item.click();
}
}),
new BenchmarkTestStep("DeletingAllItems", (page) => {
const items = page.querySelectorAll("todo-item", ["todo-app", "todo-list"]);
for (let i = numberOfItemsToAdd - 1; i >= 0; i--) {
const item = items[i].querySelectorInShadowRoot(".remove-todo-button");
item.click();
}
}),
],
});

Suites.push({
name: "TodoMVC-React",
url: "resources/todomvc/architecture-examples/react/dist/index.html#/home",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import appStyles from "../../styles/app.constructable.js";
import mainStyles from "../../styles/main.constructable.js";
class TodoApp extends HTMLElement {
#isReady = false;
#data = [];
#numberOfItems = 0;
#numberOfCompletedItems = 0;
constructor() {
super();

Expand Down Expand Up @@ -42,37 +43,28 @@ class TodoApp extends HTMLElement {

addItem(event) {
const { detail: item } = event;

this.#data.push(item);
this.list.addItem(item);

this.list.addItem(item, this.#numberOfItems++);
this.update("add-item", item.id);
}

toggleItem(event) {
this.#data.forEach((entry) => {
if (entry.id === event.detail.id)
entry.completed = event.detail.completed;
});
if (event.detail.completed)
this.#numberOfCompletedItems++;
else
this.#numberOfCompletedItems--;

this.update("toggle-item", event.detail.id);
}

removeItem(event) {
this.#data.forEach((entry, index) => {
if (entry.id === event.detail.id)
this.#data.splice(index, 1);
});
if (event.detail.completed)
this.#numberOfCompletedItems--;

this.#numberOfItems--;
this.update("remove-item", event.detail.id);
}

updateItem(event) {
this.#data.forEach((entry) => {
if (entry.id === event.detail.id)
entry.title = event.detail.title;
});

this.update("update-item", event.detail.id);
}

Expand All @@ -84,13 +76,12 @@ class TodoApp extends HTMLElement {
this.list.removeCompletedItems();
}

update(type = "", id = "") {
const totalItems = this.#data.length;
const activeItems = this.#data.filter((entry) => !entry.completed).length;
const completedItems = totalItems - activeItems;
update() {
const totalItems = this.#numberOfItems;
const completedItems = this.#numberOfCompletedItems;
const activeItems = totalItems - completedItems;

this.list.setAttribute("total-items", totalItems);
this.list.updateElements(type, id);

this.topbar.setAttribute("total-items", totalItems);
this.topbar.setAttribute("active-items", activeItems);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ import template from "./todo-bottombar.template.js";
import globalStyles from "../../styles/global.constructable.js";
import bottombarStyles from "../../styles/bottombar.constructable.js";

const customStyles = new CSSStyleSheet();
customStyles.replaceSync(`
.bottombar {
display: block;
}

:host([total-items="0"]) > .bottombar {
display: none;
}
`);

class TodoBottombar extends HTMLElement {
static get observedAttributes() {
return ["total-items", "active-items"];
Expand All @@ -20,18 +31,13 @@ class TodoBottombar extends HTMLElement {
this.shadow = this.attachShadow({ mode: "open" });
this.htmlDirection = document.dir || "ltr";
this.setAttribute("dir", this.htmlDirection);
this.shadow.adoptedStyleSheets = [globalStyles, bottombarStyles];
this.shadow.adoptedStyleSheets = [globalStyles, bottombarStyles, customStyles];
this.shadow.append(node);

this.clearCompletedItems = this.clearCompletedItems.bind(this);
}

updateDisplay() {
if (parseInt(this["total-items"]) !== 0)
this.element.style.display = "block";
else
this.element.style.display = "none";

this.todoStatus.textContent = `${this["active-items"]} ${this["active-items"] === "1" ? "item" : "items"} left!`;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const template = document.createElement("template");

template.id = "todo-bottombar-template";
template.innerHTML = `
<footer class="bottombar" style="display:none">
<footer class="bottombar">
<div class="todo-status"><span class="todo-count">0</span> item left</div>
<ul class="filter-list">
<li class="filter-item">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class TodoItem extends HTMLElement {
}
break;
case "itemcompleted":
this.toggleInput.checked = this.itemcompleted === "true" ? true : false;
this.toggleInput.checked = this.itemcompleted === "true";
break;
}
});
Expand All @@ -92,7 +92,7 @@ class TodoItem extends HTMLElement {

this.dispatchEvent(
new CustomEvent("toggle-item", {
detail: { id: this.itemid, completed: this.toggleInput.checked },
detail: { completed: this.toggleInput.checked },
bubbles: true,
})
);
Expand All @@ -103,7 +103,7 @@ class TodoItem extends HTMLElement {
// (therefore the removal has to happen after the list is updated)
this.dispatchEvent(
new CustomEvent("remove-item", {
detail: { id: this.itemid },
detail: { completed: this.toggleInput.checked },
bubbles: true,
})
);
Expand All @@ -112,17 +112,10 @@ class TodoItem extends HTMLElement {

updateItem(event) {
if (event.target.value !== this.itemtitle) {
if (!event.target.value.length) {
if (!event.target.value.length)
this.removeItem();
} else {
else
this.setAttribute("itemtitle", event.target.value);
this.dispatchEvent(
new CustomEvent("update-item", {
detail: { id: this.itemid, title: event.target.value },
bubbles: true,
})
);
}
}

this.cancelEdit();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,30 @@ import TodoItem from "../todo-item/todo-item.component.js";
import globalStyles from "../../styles/global.constructable.js";
import listStyles from "../../styles/todo-list.constructable.js";

class TodoList extends HTMLElement {
static get observedAttributes() {
return ["total-items"];
const customListStyles = new CSSStyleSheet();
customListStyles.replaceSync(`
.todo-list > todo-item {
display: block;
}

.todo-list[route="completed"] > [itemcompleted="false"] {
display: none;
}

.todo-list[route="active"] > [itemcompleted="true"] {
display: none;
}

.todo-list {
display: block;
}

#elements = [];
:host([total-items="0"]) > .todo-list {
display: none;
}
`);

class TodoList extends HTMLElement {
#route = undefined;

constructor() {
Expand All @@ -21,7 +39,7 @@ class TodoList extends HTMLElement {
this.shadow = this.attachShadow({ mode: "open" });
this.htmlDirection = document.dir || "ltr";
this.setAttribute("dir", this.htmlDirection);
this.shadow.adoptedStyleSheets = [globalStyles, listStyles];
this.shadow.adoptedStyleSheets = [globalStyles, listStyles, customListStyles];
this.shadow.append(node);
this.classList.add("show-priority");

Expand All @@ -32,96 +50,51 @@ class TodoList extends HTMLElement {
}
}

addItem(entry) {
addItem(entry, itemIndex) {
const { id, title, completed } = entry;
const element = new TodoItem();

element.setAttribute("itemid", id);
element.setAttribute("itemtitle", title);
element.setAttribute("itemcompleted", completed);
element.setAttribute("data-priority", 4 - (itemIndex % 5));

const elementIndex = this.#elements.length;
this.#elements.push(element);
this.listNode.append(element);
element.setAttribute("data-priority", 4 - (elementIndex % 5));
}

addItems(items) {
items.forEach((entry) => this.addItem(entry));
}

removeCompletedItems() {
this.#elements = this.#elements.filter((element) => {
Array.from(this.listNode.children).forEach((element) => {
if (element.itemcompleted === "true")
element.removeItem();

return element.itemcompleted === "false";
});
}

toggleItems(completed) {
this.#elements.forEach((element) => {
Array.from(this.listNode.children).forEach((element) => {
if (completed && element.itemcompleted === "false")
element.toggleInput.click();
else if (!completed && element.itemcompleted === "true")
element.toggleInput.click();
});
}

updateStyles() {
if (parseInt(this["total-items"]) !== 0)
this.listNode.style.display = "block";
else
this.listNode.style.display = "none";
}

updateView(element) {
switch (this.#route) {
updateRoute(route) {
this.#route = route;
switch (route) {
case "completed":
element.style.display = element.itemcompleted === "true" ? "block" : "none";
this.listNode.setAttribute("route", "completed");
break;
case "active":
element.style.display = element.itemcompleted === "true" ? "none" : "block";
this.listNode.setAttribute("route", "active");
break;
default:
element.style.display = "block";
this.listNode.setAttribute("route", "all");
}
}

updateElements(type = "", id = "") {
switch (type) {
case "route-change":
this.#elements.forEach((element) => this.updateView(element));
break;
case "toggle-item":
case "add-item":
this.#elements.forEach((element) => {
if (element.itemid === id)
this.updateView(element);
});
break;
case "remove-item":
this.#elements = this.#elements.filter((element) => element.itemid !== id);
break;
}
}

updateRoute(route) {
this.#route = route;
this.updateElements("route-change");
}

attributeChangedCallback(property, oldValue, newValue) {
if (oldValue === newValue)
return;
this[property] = newValue;
if (this.isConnected)
this.updateStyles();
}

connectedCallback() {
this.updateStyles();
}
}

customElements.define("todo-list", TodoList);
Expand Down
Loading