Skip to content

Commit

Permalink
publish article
Browse files Browse the repository at this point in the history
  • Loading branch information
jsebrech committed Sep 28, 2024
1 parent 112ffea commit 9124631
Show file tree
Hide file tree
Showing 20 changed files with 1,336 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
customElements.define('task-add', class extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<input type="text" placeholder="Add task" />
<button>Add</button>
`;
this.querySelector('button').onclick = () => {
const input = this.querySelector('input');
this.closest('tasks-context').dispatch({
type: 'added',
id: nextId++,
text: input.value
});
input.value = '';
};
}
})

let nextId = 3;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
customElements.define('tasks-app', class extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<tasks-context>
<h1>Day off in Kyoto</h1>
<task-add></task-add>
<task-list></task-list>
</tasks-context>
`;
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
customElements.define('task-list', class extends HTMLElement {
get context() { return this.closest('tasks-context'); }

connectedCallback() {
this.context.addEventListener('change', () => this.update());
this.append(document.createElement('ul'));
this.update();
}

update() {
const ul = this.querySelector('ul');
let before = ul.firstChild;
this.context.tasks.forEach(task => {
let li = ul.querySelector(`:scope > [data-key="${task.id}"]`);
if (!li) {
li = document.createElement('li');
li.dataset.key = task.id;
li.append(document.createElement('task-item'));
}
li.firstChild.task = task;
// move to the right position in the list if not there yet
if (li !== before) ul.insertBefore(li, before);
before = li.nextSibling;
});
// remove unknown nodes
while (before) {
const remove = before;
before = before.nextSibling;
ul.removeChild(remove);
}
}
});

customElements.define('task-item', class extends HTMLElement {
#isEditing = false;
#task;
set task(task) { this.#task = task; this.update(); }
get context() { return this.closest('tasks-context'); }

connectedCallback() {
if (this.querySelector('label')) return;
this.innerHTML = `
<label>
<input type="checkbox" />
<input type="text" />
<span></span>
<button id="edit">Edit</button>
<button id="save">Save</button>
<button id="delete">Delete</button>
</label>
`;
this.querySelector('input[type=checkbox]').onchange = e => {
this.context.dispatch({
type: 'changed',
task: {
...this.#task,
done: e.target.checked
}
});
};
this.querySelector('input[type=text]').onchange = e => {
this.context.dispatch({
type: 'changed',
task: {
...this.#task,
text: e.target.value
}
});
};
this.querySelector('button#edit').onclick = () => {
this.#isEditing = true;
this.update();
};
this.querySelector('button#save').onclick = () => {
this.#isEditing = false;
this.update();
};
this.querySelector('button#delete').onclick = () => {
this.context.dispatch({
type: 'deleted',
id: this.#task.id
});
};
this.context.addEventListener('change', () => this.update());
this.update();
}

update() {
if (this.isConnected && this.#task) {
this.querySelector('input[type=checkbox]').checked = this.#task.done;
const inputEdit = this.querySelector('input[type=text]');
inputEdit.style.display = this.#isEditing ? 'inline' : 'none';
inputEdit.value = this.#task.text;
const span = this.querySelector('span');
span.style.display = this.#isEditing ? 'none' : 'inline';
span.textContent = this.#task.text;
this.querySelector('button#edit').style.display = this.#isEditing ? 'none' : 'inline';
this.querySelector('button#save').style.display = this.#isEditing ? 'inline' : 'none';
}
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
customElements.define('tasks-context', class extends HTMLElement {
#tasks = structuredClone(initialTasks);
get tasks() { return this.#tasks; }
set tasks(tasks) {
this.#tasks = tasks;
this.dispatchEvent(new Event('change'));
}

dispatch(action) {
this.tasks = tasksReducer(this.tasks, action);
}

connectedCallback() {
this.style.display = 'content';
}
});

function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [...tasks, {
id: action.id,
text: action.text,
done: false
}];
}
case 'changed': {
return tasks.map(t => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter(t => t.id !== action.id);
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}

const initialTasks = [
{ id: 0, text: 'Philosopher’s Path', done: true },
{ id: 1, text: 'Visit the temple', done: false },
{ id: 2, text: 'Drink matcha', done: false }
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="root"></div>
<script type="module" src="index.js"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import './App.js';
import './AddTask.js';
import './TaskList.js';
import './TasksContext.js';

const render = () => {
const root = document.getElementById('root');
root.append(document.createElement('tasks-app'));
}

document.addEventListener('DOMContentLoaded', render);
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
* {
box-sizing: border-box;
}

body {
font-family: sans-serif;
margin: 20px;
padding: 0;
}

h1 {
margin-top: 0;
font-size: 22px;
}

h2 {
margin-top: 0;
font-size: 20px;
}

h3 {
margin-top: 0;
font-size: 18px;
}

h4 {
margin-top: 0;
font-size: 16px;
}

h5 {
margin-top: 0;
font-size: 14px;
}

h6 {
margin-top: 0;
font-size: 12px;
}

code {
font-size: 1.2em;
}

ul {
padding-inline-start: 20px;
}

button { margin: 5px; }
li { list-style-type: none; }
ul, li { margin: 0; padding: 0; }
Binary file not shown.
Loading

0 comments on commit 9124631

Please sign in to comment.