Skip to content

Commit 39ed374

Browse files
committed
implement DELETE todo item using least possible code. closes #59
1 parent 77546af commit 39ed374

File tree

4 files changed

+170
-102
lines changed

4 files changed

+170
-102
lines changed

Diff for: examples/todo-list/elmish.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ function mount (model, update, view, root_element_id, subscriptions) {
3434
function signal(action, data, model) { // signal function takes action
3535
return function callback() { // and returns callback
3636
model = JSON.parse(localStorage.getItem(store_name)) //|| model;
37-
var updatedModel = update(action, model, data); // update model for the action
37+
var updatedModel = update(action, model, data); // update model for action
3838
render(updatedModel, signal, ROOT);
3939
};
4040
};

Diff for: examples/todo-list/todo-app.js

+11-7
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ var initial_model = {
1818
* @return {Object} updated_model - the transformed model.
1919
*/
2020
function update(action, model, data) {
21-
console.log(arguments)
22-
console.log(' - - - - - - - - - - - ');
21+
// console.log(arguments)
22+
// console.log(' - - - - - - - - - - - ');
2323
var new_model = JSON.parse(JSON.stringify(model)) // "clone" the model
2424
switch(action) { // and an action (String) runs a switch
2525
case 'ADD':
@@ -53,9 +53,12 @@ function update(action, model, data) {
5353
item.done = new_model.all_done;
5454
});
5555
break;
56-
// case 'DELETE':
57-
// console.log('DELETE', data)
58-
// break;
56+
case 'DELETE':
57+
new_model.todos = new_model.todos.filter(function(item) {
58+
console.log(item.id, data);
59+
return item.id !== data;
60+
});
61+
break;
5962
default: // if action unrecognised or undefined,
6063
return model; // return model unmodified
6164
} // see: https://softwareengineering.stackexchange.com/a/201786/211301
@@ -94,7 +97,8 @@ function render_item (item, model, signal) {
9497
],
9598
[]), // <input> does not have any nested elements
9699
label([], [text(item.title)]),
97-
button(["class=destroy", ]) // signal('DELETE', 'blah!', model)])
100+
button(["class=destroy",
101+
typeof signal === 'function' ? signal('DELETE', item.id) : ''])
98102
]) // </div>
99103
]) // </li>
100104
)
@@ -231,7 +235,7 @@ function subscriptions (signal) {
231235

232236
document.addEventListener('keyup', function handler (e) {
233237
var new_todo = document.getElementById('new-todo');
234-
console.log('e.keyCode', e);
238+
console.log('e.keyCode:', e.keyCode, '| key:', e.key);
235239
if (e.keyCode === ENTER_KEY && new_todo.value.length > 0) {
236240
signal('ADD')(); // invoke singal inner callback
237241
new_todo.value = ''; // reset <input> so we can add another todo

Diff for: test/todo-app.test.js

+34-4
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,36 @@ test('`TOGGLE` (undo) a todo item from done=true to done=false', function (t) {
5959
t.end();
6060
});
6161

62+
// this is used for testing view functions which require a signal function
63+
function mock_signal () {
64+
return function inner_function() {
65+
console.log('done');
66+
}
67+
}
68+
6269
test('render_item HTML for a single Todo Item', function (t) {
70+
const model = {
71+
todos: [
72+
{ id: 1, title: "Learn Elm Architecture", done: true },
73+
],
74+
hash: '#/' // the "route" to display
75+
};
76+
// render the ONE todo list item:
77+
document.getElementById(id).appendChild(
78+
app.render_item(model.todos[0], model, mock_signal),
79+
);
80+
81+
const done = document.querySelectorAll('.completed')[0].textContent;
82+
t.equal(done, 'Learn Elm Architecture', 'Done: Learn "TEA"');
83+
84+
const checked = document.querySelectorAll('input')[0].checked;
85+
t.equal(checked, true, 'Done: ' + model.todos[0].title + " is done=true");
86+
87+
elmish.empty(document.getElementById(id)); // clear DOM ready for next test
88+
t.end();
89+
});
90+
91+
test('render_item HTML without a valid signal function', function (t) {
6392
const model = {
6493
todos: [
6594
{ id: 1, title: "Learn Elm Architecture", done: true },
@@ -69,7 +98,7 @@ test('render_item HTML for a single Todo Item', function (t) {
6998
// render the ONE todo list item:
7099
document.getElementById(id).appendChild(
71100
app.render_item(model.todos[0]),
72-
)
101+
);
73102

74103
const done = document.querySelectorAll('.completed')[0].textContent;
75104
t.equal(done, 'Learn Elm Architecture', 'Done: Learn "TEA"');
@@ -91,7 +120,7 @@ test('render_main "main" view using (elmish) HTML DOM functions', function (t) {
91120
hash: '#/' // the "route" to display
92121
};
93122
// render the "main" view and append it to the DOM inside the `test-app` node:
94-
document.getElementById(id).appendChild(app.render_main(model));
123+
document.getElementById(id).appendChild(app.render_main(model, mock_signal));
95124
// test that the title text in the model.todos was rendered to <label> nodes:
96125
document.querySelectorAll('.view').forEach(function (item, index) {
97126
t.equal(item.textContent, model.todos[index].title,
@@ -339,7 +368,7 @@ test('4. DELETE an item', function (t) {
339368
t.end();
340369
});
341370

342-
test.only('4.1 DELETE item by clicking <button class="destroy">', function (t) {
371+
test('4.1 DELETE item by clicking <button class="destroy">', function (t) {
343372
elmish.empty(document.getElementById(id));
344373
localStorage.removeItem('elmish_' + id);
345374
const model = {
@@ -354,12 +383,13 @@ test.only('4.1 DELETE item by clicking <button class="destroy">', function (t) {
354383
t.equal(document.querySelectorAll('.destroy').length, 1, "one destroy button")
355384

356385
const item = document.getElementById('0')
357-
t.equal(item.textContent, model.todos[0].title, 'Item contained in model.');
386+
t.equal(item.textContent, model.todos[0].title, 'Item contained in DOM.');
358387
// DELETE the item by clicking on the <button class="destroy">:
359388
const button = item.querySelectorAll('button.destroy')[0];
360389
button.click()
361390
// confirm that there is no loger a <button class="destroy">
362391
t.equal(document.querySelectorAll('button.destroy').length, 0,
363392
'there is no loger a <button class="destroy"> as the only item was DELETEd')
393+
t.equal(document.getElementById('0'), null, 'todo item successfully DELETEd');
364394
t.end();
365395
});

Diff for: todo-list.md

+124-90
Original file line numberDiff line numberDiff line change
@@ -1693,94 +1693,6 @@ in order to make this test pass; just run it and move on.
16931693

16941694

16951695

1696-
1697-
#### 4.2 `EDIT` an Item
1698-
1699-
```
1700-
should allow me to edit an item
1701-
```
1702-
1703-
Editing a Todo List item is (_by far_)
1704-
the most "complex" functionality in the TodoMVC app
1705-
because it involves multiple steps and "dynamic UI".
1706-
1707-
1708-
1709-
+ [ ] Double-click on Item **`<label>title</label>`** to begin editing.
1710-
+ [ ] Render an **`<input class="edit">`** if in "**editing _mode_**"
1711-
(_see screenshot and markup below_)
1712-
+ [ ] Add `case` in `keyup` Event Listener for **`[Enter]`** keyup
1713-
(_see **`subscriptions`** above_) if we are in "**editing _mode_**",
1714-
get the text value from the **`<input class="edit">`**
1715-
_instead_ of **`<input id="new-todo">`**
1716-
so that we _update_ the _existing_ Todo Item title (text).
1717-
+ [ ] When **`[Enter]`** is pressed while in "**editing _mode_**",
1718-
Dispatch the **`END_EDIT`** action: `signal('END_EDIT')`
1719-
1720-
![todo-edit-html](https://user-images.githubusercontent.com/194400/43995210-f4f484e0-9da1-11e8-8cc5-09f7309db963.png)
1721-
1722-
Here is the _sample_ HTML in "**editing _mode_**"
1723-
(_copy-pasted_) from the VanillaJS TodoMVC implementation
1724-
the _second_ **`<li>`** is the one being edited (_as per screenshot above_):
1725-
```HTML
1726-
<ul class="todo-list">
1727-
<li data-id="1533987109280" class="completed ">
1728-
<div class="view">
1729-
<input class="toggle" type="checkbox" checked="">
1730-
<label>hello world</label>
1731-
<button class="destroy"></button>
1732-
</div>
1733-
</li>
1734-
<li data-id="1534013859716" class="editing">
1735-
<div class="view"><input class="toggle" type="checkbox">
1736-
<label>totes editing this todo item</label>
1737-
<button class="destroy">
1738-
</button>
1739-
</div>
1740-
<input class="edit">
1741-
</li>
1742-
</ul>
1743-
```
1744-
1745-
1746-
1747-
```js
1748-
1749-
```
1750-
1751-
There are _two_ steps to Editing a Todo List item:
1752-
1753-
+ [ ] Receiving the `singal('EDIT', item.id)` "activates" editing mode.
1754-
1755-
1756-
1757-
1758-
BEFORE:
1759-
```js
1760-
function render_item (item, signal) {
1761-
return (
1762-
li([
1763-
"data-id=" + item.id,
1764-
"id=" + item.id,
1765-
item.done ? "class=completed" : ""
1766-
], [
1767-
div(["class=view"], [
1768-
input([
1769-
item.done ? "checked=true" : "",
1770-
"class=toggle",
1771-
"type=checkbox",
1772-
typeof signal === 'function' ? signal('TOGGLE', item.id) : ''
1773-
],
1774-
[]), // <input> does not have any nested elements
1775-
label([], [text(item.title)]),
1776-
button(["class=destroy"])
1777-
]) // </div>
1778-
]) // </li>
1779-
)
1780-
}
1781-
```
1782-
1783-
17841696
#### 4.1 `DELETE` an Item
17851697

17861698
```
@@ -1859,25 +1771,147 @@ test.only('4.1 DELETE item by clicking <button class="destroy">', function (t) {
18591771
t.equal(document.querySelectorAll('.destroy').length, 1, "one destroy button")
18601772

18611773
const item = document.getElementById('0')
1862-
t.equal(item.textContent, model.todos[0].title, 'Item contained in model.');
1774+
t.equal(item.textContent, model.todos[0].title, 'Item contained in DOM.');
18631775
// DELETE the item by clicking on the <button class="destroy">:
18641776
const button = item.querySelectorAll('button.destroy')[0];
18651777
button.click()
18661778
// confirm that there is no loger a <button class="destroy">
18671779
t.equal(document.querySelectorAll('button.destroy').length, 0,
18681780
'there is no loger a <button class="destroy"> as the only item was DELETEd')
1781+
t.equal(document.getElementById('0'), null, 'todo item successfully DELETEd');
18691782
t.end();
18701783
});
18711784
```
18721785

1873-
If you run the tests `node test/todo-app.test.js`
1786+
If you run the tests `node test/todo-app.test.js`
18741787
you should now see:
18751788
![delete-test-one-assertion-failing](https://user-images.githubusercontent.com/194400/44953479-21313300-ae96-11e8-971a-51757702bacc.png)
18761789

1790+
The first two assertions are _optional_ and _should_ (_always_)
1791+
pass given that they rely on functionality defined previously.
1792+
The second two will only pass once you _make_ them pass!
1793+
1794+
##### `DELETE` Item _Implementation_
1795+
1796+
The _first_ step is to add an invocation of `signal('DELETE' ...)`
1797+
to the `render_item` view rendering function. _Specifically_ the
1798+
`button` line:
1799+
1800+
```js
1801+
button(["class=destroy"])
1802+
```
1803+
Add the `signal` function invocation:
1804+
```js
1805+
button(["class=destroy", signal('DELETE', item.id, model)])
1806+
```
1807+
1808+
simply adding this function invocation will set it
1809+
as an `onclick` attribute for the `<button>`
1810+
therefore when the _user_ clicks the button it will
1811+
"trigger" the `signal` function with the appropriate arguments.
1812+
1813+
1814+
_Second_ we need to add a `case` statement
1815+
to the `update` function.
1816+
You should attempt to "solve" this yourself.
1817+
There is no "right" answer, there are at least
1818+
5 ways of solving this, as always, you should write the code
1819+
that you feel is most _readable_.
1820+
1821+
1822+
1823+
18771824

18781825

18791826
<!--
18801827
1828+
1829+
#### 4.2 `EDIT` an Item
1830+
1831+
```
1832+
should allow me to edit an item
1833+
```
1834+
1835+
Editing a Todo List item is (_by far_)
1836+
the most "complex" functionality in the TodoMVC app
1837+
because it involves multiple steps and "dynamic UI".
1838+
1839+
1840+
1841+
+ [ ] Double-click on Item **`<label>title</label>`** to begin editing.
1842+
+ [ ] Render an **`<input class="edit">`** if in "**editing _mode_**"
1843+
(_see screenshot and markup below_)
1844+
+ [ ] Add `case` in `keyup` Event Listener for **`[Enter]`** keyup
1845+
(_see **`subscriptions`** above_) if we are in "**editing _mode_**",
1846+
get the text value from the **`<input class="edit">`**
1847+
_instead_ of **`<input id="new-todo">`**
1848+
so that we _update_ the _existing_ Todo Item title (text).
1849+
+ [ ] When **`[Enter]`** is pressed while in "**editing _mode_**",
1850+
Dispatch the **`END_EDIT`** action: `signal('END_EDIT')`
1851+
1852+
![todo-edit-html](https://user-images.githubusercontent.com/194400/43995210-f4f484e0-9da1-11e8-8cc5-09f7309db963.png)
1853+
1854+
Here is the _sample_ HTML in "**editing _mode_**"
1855+
(_copy-pasted_) from the VanillaJS TodoMVC implementation
1856+
the _second_ **`<li>`** is the one being edited (_as per screenshot above_):
1857+
```HTML
1858+
<ul class="todo-list">
1859+
<li data-id="1533987109280" class="completed ">
1860+
<div class="view">
1861+
<input class="toggle" type="checkbox" checked="">
1862+
<label>hello world</label>
1863+
<button class="destroy"></button>
1864+
</div>
1865+
</li>
1866+
<li data-id="1534013859716" class="editing">
1867+
<div class="view"><input class="toggle" type="checkbox">
1868+
<label>totes editing this todo item</label>
1869+
<button class="destroy">
1870+
</button>
1871+
</div>
1872+
<input class="edit">
1873+
</li>
1874+
</ul>
1875+
```
1876+
1877+
1878+
```js
1879+
1880+
```
1881+
1882+
There are _two_ steps to Editing a Todo List item:
1883+
1884+
+ [ ] Receiving the `singal('EDIT', item.id)` "activates" editing mode.
1885+
1886+
1887+
1888+
1889+
BEFORE:
1890+
```js
1891+
function render_item (item, signal) {
1892+
return (
1893+
li([
1894+
"data-id=" + item.id,
1895+
"id=" + item.id,
1896+
item.done ? "class=completed" : ""
1897+
], [
1898+
div(["class=view"], [
1899+
input([
1900+
item.done ? "checked=true" : "",
1901+
"class=toggle",
1902+
"type=checkbox",
1903+
typeof signal === 'function' ? signal('TOGGLE', item.id) : ''
1904+
],
1905+
[]), // <input> does not have any nested elements
1906+
label([], [text(item.title)]),
1907+
button(["class=destroy"])
1908+
]) // </div>
1909+
]) // </li>
1910+
)
1911+
}
1912+
```
1913+
1914+
18811915
## What _Next_?
18821916
18831917
If you feel _confident_ with your "TEA" skills you can _either_:

0 commit comments

Comments
 (0)