Skip to content

Commit 5744c55

Browse files
Remove mockup storage and inline styles from web componenets todoMVC (#521)
Currently most versions of TodoMVC use a mockup storage in the form of an array. This causes expensive array operations to be overrepresented in the benchmark (like splices in the delete100items step). Similarly inline style changes are used to manage the state of the list. This PR: 1. Removes the lists of todo entries at the app and list level and keep them only as children of the todolist element. 1. Remove inline styling of elements display, use css attribute selectors instead.
1 parent d90b6fd commit 5744c55

File tree

24 files changed

+195
-342
lines changed

24 files changed

+195
-342
lines changed

resources/todomvc/vanilla-examples/javascript-web-components-complex/dist/components/todo-app/todo-app.component.js

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import appStyles from "../../styles/app.constructable.js";
66
import mainStyles from "../../styles/main.constructable.js";
77
class TodoApp extends HTMLElement {
88
#isReady = false;
9-
#data = [];
9+
#numberOfItems = 0;
10+
#numberOfCompletedItems = 0;
1011
constructor() {
1112
super();
1213

@@ -42,37 +43,28 @@ class TodoApp extends HTMLElement {
4243

4344
addItem(event) {
4445
const { detail: item } = event;
45-
46-
this.#data.push(item);
47-
this.list.addItem(item);
48-
46+
this.list.addItem(item, this.#numberOfItems++);
4947
this.update("add-item", item.id);
5048
}
5149

5250
toggleItem(event) {
53-
this.#data.forEach((entry) => {
54-
if (entry.id === event.detail.id)
55-
entry.completed = event.detail.completed;
56-
});
51+
if (event.detail.completed)
52+
this.#numberOfCompletedItems++;
53+
else
54+
this.#numberOfCompletedItems--;
5755

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

6159
removeItem(event) {
62-
this.#data.forEach((entry, index) => {
63-
if (entry.id === event.detail.id)
64-
this.#data.splice(index, 1);
65-
});
60+
if (event.detail.completed)
61+
this.#numberOfCompletedItems--;
6662

63+
this.#numberOfItems--;
6764
this.update("remove-item", event.detail.id);
6865
}
6966

7067
updateItem(event) {
71-
this.#data.forEach((entry) => {
72-
if (entry.id === event.detail.id)
73-
entry.title = event.detail.title;
74-
});
75-
7668
this.update("update-item", event.detail.id);
7769
}
7870

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

87-
update(type = "", id = "") {
88-
const totalItems = this.#data.length;
89-
const activeItems = this.#data.filter((entry) => !entry.completed).length;
90-
const completedItems = totalItems - activeItems;
79+
update() {
80+
const totalItems = this.#numberOfItems;
81+
const completedItems = this.#numberOfCompletedItems;
82+
const activeItems = totalItems - completedItems;
9183

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

9586
this.topbar.setAttribute("total-items", totalItems);
9687
this.topbar.setAttribute("active-items", activeItems);

resources/todomvc/vanilla-examples/javascript-web-components-complex/dist/components/todo-bottombar/todo-bottombar.component.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ import template from "./todo-bottombar.template.js";
33
import globalStyles from "../../styles/global.constructable.js";
44
import bottombarStyles from "../../styles/bottombar.constructable.js";
55

6+
const customStyles = new CSSStyleSheet();
7+
customStyles.replaceSync(`
8+
:host([total-items="0"]) > .bottombar {
9+
display: none;
10+
}
11+
`);
12+
613
class TodoBottombar extends HTMLElement {
714
static get observedAttributes() {
815
return ["total-items", "active-items"];
@@ -20,18 +27,13 @@ class TodoBottombar extends HTMLElement {
2027
this.shadow = this.attachShadow({ mode: "open" });
2128
this.htmlDirection = document.dir || "ltr";
2229
this.setAttribute("dir", this.htmlDirection);
23-
this.shadow.adoptedStyleSheets = [globalStyles, bottombarStyles];
30+
this.shadow.adoptedStyleSheets = [globalStyles, bottombarStyles, customStyles];
2431
this.shadow.append(node);
2532

2633
this.clearCompletedItems = this.clearCompletedItems.bind(this);
2734
}
2835

2936
updateDisplay() {
30-
if (parseInt(this["total-items"]) !== 0)
31-
this.element.style.display = "block";
32-
else
33-
this.element.style.display = "none";
34-
3537
this.todoStatus.textContent = `${this["active-items"]} ${this["active-items"] === "1" ? "item" : "items"} left!`;
3638
}
3739

resources/todomvc/vanilla-examples/javascript-web-components-complex/dist/components/todo-bottombar/todo-bottombar.template.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const template = document.createElement("template");
22

33
template.id = "todo-bottombar-template";
44
template.innerHTML = `
5-
<footer class="bottombar" style="display:none">
5+
<footer class="bottombar">
66
<div class="todo-status"><span class="todo-count">0</span> item left</div>
77
<ul class="filter-list">
88
<li class="filter-item">

resources/todomvc/vanilla-examples/javascript-web-components-complex/dist/components/todo-item/todo-item.component.js

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class TodoItem extends HTMLElement {
6565
}
6666
break;
6767
case "itemcompleted":
68-
this.toggleInput.checked = this.itemcompleted === "true" ? true : false;
68+
this.toggleInput.checked = this.itemcompleted === "true";
6969
break;
7070
}
7171
});
@@ -92,7 +92,7 @@ class TodoItem extends HTMLElement {
9292

9393
this.dispatchEvent(
9494
new CustomEvent("toggle-item", {
95-
detail: { id: this.itemid, completed: this.toggleInput.checked },
95+
detail: { completed: this.toggleInput.checked },
9696
bubbles: true,
9797
})
9898
);
@@ -103,7 +103,7 @@ class TodoItem extends HTMLElement {
103103
// (therefore the removal has to happen after the list is updated)
104104
this.dispatchEvent(
105105
new CustomEvent("remove-item", {
106-
detail: { id: this.itemid },
106+
detail: { completed: this.toggleInput.checked },
107107
bubbles: true,
108108
})
109109
);
@@ -112,17 +112,10 @@ class TodoItem extends HTMLElement {
112112

113113
updateItem(event) {
114114
if (event.target.value !== this.itemtitle) {
115-
if (!event.target.value.length) {
115+
if (!event.target.value.length)
116116
this.removeItem();
117-
} else {
117+
else
118118
this.setAttribute("itemtitle", event.target.value);
119-
this.dispatchEvent(
120-
new CustomEvent("update-item", {
121-
detail: { id: this.itemid, title: event.target.value },
122-
bubbles: true,
123-
})
124-
);
125-
}
126119
}
127120

128121
this.cancelEdit();

resources/todomvc/vanilla-examples/javascript-web-components-complex/dist/components/todo-list/todo-list.component.js

Lines changed: 25 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,22 @@ import TodoItem from "../todo-item/todo-item.component.js";
44
import globalStyles from "../../styles/global.constructable.js";
55
import listStyles from "../../styles/todo-list.constructable.js";
66

7-
class TodoList extends HTMLElement {
8-
static get observedAttributes() {
9-
return ["total-items"];
7+
const customListStyles = new CSSStyleSheet();
8+
customListStyles.replaceSync(`
9+
.todo-list[route="completed"] > [itemcompleted="false"] {
10+
display: none;
11+
}
12+
13+
.todo-list[route="active"] > [itemcompleted="true"] {
14+
display: none;
1015
}
1116
12-
#elements = [];
17+
:host([total-items="0"]) > .todo-list {
18+
display: none;
19+
}
20+
`);
21+
22+
class TodoList extends HTMLElement {
1323
#route = undefined;
1424

1525
constructor() {
@@ -21,7 +31,7 @@ class TodoList extends HTMLElement {
2131
this.shadow = this.attachShadow({ mode: "open" });
2232
this.htmlDirection = document.dir || "ltr";
2333
this.setAttribute("dir", this.htmlDirection);
24-
this.shadow.adoptedStyleSheets = [globalStyles, listStyles];
34+
this.shadow.adoptedStyleSheets = [globalStyles, listStyles, customListStyles];
2535
this.shadow.append(node);
2636
this.classList.add("show-priority");
2737

@@ -32,96 +42,51 @@ class TodoList extends HTMLElement {
3242
}
3343
}
3444

35-
addItem(entry) {
45+
addItem(entry, itemIndex) {
3646
const { id, title, completed } = entry;
3747
const element = new TodoItem();
3848

3949
element.setAttribute("itemid", id);
4050
element.setAttribute("itemtitle", title);
4151
element.setAttribute("itemcompleted", completed);
52+
element.setAttribute("data-priority", 4 - (itemIndex % 5));
4253

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

4957
addItems(items) {
5058
items.forEach((entry) => this.addItem(entry));
5159
}
5260

5361
removeCompletedItems() {
54-
this.#elements = this.#elements.filter((element) => {
62+
Array.from(this.listNode.children).forEach((element) => {
5563
if (element.itemcompleted === "true")
5664
element.removeItem();
57-
58-
return element.itemcompleted === "false";
5965
});
6066
}
6167

6268
toggleItems(completed) {
63-
this.#elements.forEach((element) => {
69+
Array.from(this.listNode.children).forEach((element) => {
6470
if (completed && element.itemcompleted === "false")
6571
element.toggleInput.click();
6672
else if (!completed && element.itemcompleted === "true")
6773
element.toggleInput.click();
6874
});
6975
}
7076

71-
updateStyles() {
72-
if (parseInt(this["total-items"]) !== 0)
73-
this.listNode.style.display = "block";
74-
else
75-
this.listNode.style.display = "none";
76-
}
77-
78-
updateView(element) {
79-
switch (this.#route) {
77+
updateRoute(route) {
78+
this.#route = route;
79+
switch (route) {
8080
case "completed":
81-
element.style.display = element.itemcompleted === "true" ? "block" : "none";
81+
this.listNode.setAttribute("route", "completed");
8282
break;
8383
case "active":
84-
element.style.display = element.itemcompleted === "true" ? "none" : "block";
84+
this.listNode.setAttribute("route", "active");
8585
break;
8686
default:
87-
element.style.display = "block";
87+
this.listNode.setAttribute("route", "all");
8888
}
8989
}
90-
91-
updateElements(type = "", id = "") {
92-
switch (type) {
93-
case "route-change":
94-
this.#elements.forEach((element) => this.updateView(element));
95-
break;
96-
case "toggle-item":
97-
case "add-item":
98-
this.#elements.forEach((element) => {
99-
if (element.itemid === id)
100-
this.updateView(element);
101-
});
102-
break;
103-
case "remove-item":
104-
this.#elements = this.#elements.filter((element) => element.itemid !== id);
105-
break;
106-
}
107-
}
108-
109-
updateRoute(route) {
110-
this.#route = route;
111-
this.updateElements("route-change");
112-
}
113-
114-
attributeChangedCallback(property, oldValue, newValue) {
115-
if (oldValue === newValue)
116-
return;
117-
this[property] = newValue;
118-
if (this.isConnected)
119-
this.updateStyles();
120-
}
121-
122-
connectedCallback() {
123-
this.updateStyles();
124-
}
12590
}
12691

12792
customElements.define("todo-list", TodoList);

resources/todomvc/vanilla-examples/javascript-web-components-complex/dist/components/todo-list/todo-list.template.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const template = document.createElement("template");
22

33
template.id = "todo-list-template";
44
template.innerHTML = `
5-
<ul class="todo-list" style="display:none"></ul>
5+
<ul class="todo-list"></ul>
66
`;
77

88
export default template;

resources/todomvc/vanilla-examples/javascript-web-components-complex/dist/components/todo-topbar/todo-topbar.component.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,16 @@ import { nanoid } from "../../utils/nanoid.js";
55
import globalStyles from "../../styles/global.constructable.js";
66
import topbarStyles from "../../styles/topbar.constructable.js";
77

8+
const customListStyles = new CSSStyleSheet();
9+
customListStyles.replaceSync(`
10+
:host([total-items="0"]) .toggle-all-container {
11+
display: none;
12+
}
13+
`);
14+
815
class TodoTopbar extends HTMLElement {
916
static get observedAttributes() {
10-
return ["total-items", "active-items", "completed-items"];
17+
return ["active-items", "completed-items"];
1118
}
1219

1320
#route = undefined;
@@ -23,7 +30,7 @@ class TodoTopbar extends HTMLElement {
2330
this.shadow = this.attachShadow({ mode: "open" });
2431
this.htmlDirection = document.dir || "ltr";
2532
this.setAttribute("dir", this.htmlDirection);
26-
this.shadow.adoptedStyleSheets = [globalStyles, topbarStyles];
33+
this.shadow.adoptedStyleSheets = [globalStyles, topbarStyles, customListStyles];
2734
this.shadow.append(node);
2835

2936
this.keysListeners = [];
@@ -58,13 +65,6 @@ class TodoTopbar extends HTMLElement {
5865
}
5966

6067
updateDisplay() {
61-
if (!parseInt(this["total-items"])) {
62-
this.toggleContainer.style.display = "none";
63-
return;
64-
}
65-
66-
this.toggleContainer.style.display = "block";
67-
6868
switch (this.#route) {
6969
case "active":
7070
this.toggleInput.checked = false;
@@ -75,7 +75,7 @@ class TodoTopbar extends HTMLElement {
7575
this.toggleInput.disabled = !parseInt(this["completed-items"]);
7676
break;
7777
default:
78-
this.toggleInput.checked = this["total-items"] === this["completed-items"];
78+
this.toggleInput.checked = !parseInt(this["active-items"]);
7979
this.toggleInput.disabled = false;
8080
}
8181
}

resources/todomvc/vanilla-examples/javascript-web-components-complex/dist/components/todo-topbar/todo-topbar.template.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ template.innerHTML = `
77
<label for="new-todo" class="visually-hidden">Enter a new todo.</label>
88
<input id="new-todo" class="new-todo-input" placeholder="What needs to be done?" autofocus />
99
</div>
10-
<div class="toggle-all-container" style="display:none">
10+
<div class="toggle-all-container">
1111
<input id="toggle-all" class="toggle-all-input" type="checkbox" />
1212
<label for="toggle-all" class="toggle-all-label">Mark all todos as complete.</label>
1313
</div>

0 commit comments

Comments
 (0)