Skip to content

Commit dbcca39

Browse files
committed
adds basic EDIT item implementation including EDIT and SAVE actions for #60
1 parent a597688 commit dbcca39

File tree

3 files changed

+98
-19
lines changed

3 files changed

+98
-19
lines changed

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

+6
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ function add_attributes (attrlist, node) {
6969
case 'autofocus':
7070
node.autofocus = "autofocus";
7171
node.focus();
72+
setTimeout(function() { // wait till DOM has rendered then focus()
73+
node.focus();
74+
}, 200)
7275
break;
7376
case 'checked':
7477
node.setAttribute('checked', true);
@@ -96,6 +99,9 @@ function add_attributes (attrlist, node) {
9699
case 'type':
97100
node.setAttribute('type', a[1]); // <input id="go" type="checkbox">
98101
break;
102+
case 'value':
103+
console.log('value:', a[1]);
104+
node.value = a[1];
99105
default:
100106
break;
101107
} // end switch

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

+90-17
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ function update(action, model, data) {
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':
26-
// can you see an "issue" with this way of generating the todo id? Bug...?
27-
var id = (typeof model.todos !== 'undefined' && model.todos.length > 0) ?
28-
(model.todos.length + 1) : 1;
26+
var last = (typeof model.todos !== 'undefined' && model.todos.length > 0)
27+
? model.todos[model.todos.length - 1] : null;
28+
var id = last ? last.id + 1 : 1;
2929
var input = document.getElementById('new-todo');
3030
new_model.todos = (new_model.todos && new_model.todos.length > 0)
3131
? new_model.todos : [];
@@ -54,17 +54,75 @@ function update(action, model, data) {
5454
});
5555
break;
5656
case 'DELETE':
57-
new_model.todos = new_model.todos.filter(function(item) {
58-
console.log(item.id, data);
57+
console.log('DELETE', data);
58+
new_model.todos = new_model.todos.filter(function (item) {
5959
return item.id !== data;
6060
});
6161
break;
62+
case 'EDIT':
63+
// this code is inspired by: https://stackoverflow.com/a/16033129/1148249
64+
// simplified as we are not altering the DOM!
65+
console.log('EDIT', data, model.editing, new_model.click_time, Date.now());
66+
67+
// if we are ALREADY editing the todo item, no need to do anything
68+
if (new_model.editing && new_model.editing === data) {
69+
console.log('already editing ...', data);
70+
}
71+
// on second click, the new_model.clicked will have been previously set
72+
else if (new_model.clicked && new_model.clicked === data &&
73+
Date.now() - 300 < new_model.click_time ) { // DOUBLE CLICK is faster than 300ms
74+
new_model.editing = data;
75+
console.log('DOUBLE CLICK', data);
76+
}
77+
else { // first click
78+
new_model.clicked = data;
79+
new_model.click_time = Date.now()
80+
new_model.editing = false;
81+
console.log('FIRST CLICK', data);
82+
}
83+
break;
84+
case 'SAVE':
85+
var edit = document.querySelectorAll('.edit')[0];
86+
var value = edit.value;
87+
var id = parseInt(edit.id, 10);
88+
// End Editing
89+
new_model.clicked = false;
90+
new_model.editing = false;
91+
92+
if (!value || value.length < 1) { // delete item if title is blank:
93+
return update('DELETE', new_model, id);
94+
}
95+
// update the value of the item.title that has been edited:
96+
new_model.todos = new_model.todos.map(function (item) {
97+
if (item.id === id && value && value.length > 0) {
98+
item.title = value.trim();
99+
}
100+
return item; // return all todo items.
101+
});
102+
break;
62103
default: // if action unrecognised or undefined,
63104
return model; // return model unmodified
64105
} // see: https://softwareengineering.stackexchange.com/a/201786/211301
65106
return new_model;
66107
}
67108

109+
function show_item (item, model, signal) {
110+
return [
111+
label([ typeof signal === 'function' ? signal('EDIT', item.id) : '' ],
112+
[text(item.title)]),
113+
button(["class=destroy",
114+
typeof signal === 'function' ? signal('DELETE', item.id) : ''])
115+
]
116+
}
117+
118+
function edit_item (item, model, signal) {
119+
return [
120+
input(["class=edit", "id=" + item.id,
121+
"value=" + item.title, "autofocus"], []),
122+
]
123+
}
124+
125+
68126
/**
69127
* `render_item` creates an DOM "tree" with a single Todo List Item
70128
* using the "elmish" DOM functions (`li`, `div`, `input`, `label` and `button`)
@@ -86,21 +144,25 @@ function render_item (item, model, signal) {
86144
li([
87145
"data-id=" + item.id,
88146
"id=" + item.id,
89-
item.done ? "class=completed" : ""
147+
item.done ? "class=completed" : "",
148+
model && model.editing && model.editing === item.id ? "class=editing" : ""
90149
], [
91150
div(["class=view"], [
92151
input([
93152
item.done ? "checked=true" : "",
94153
"class=toggle",
95154
"type=checkbox",
96155
typeof signal === 'function' ? signal('TOGGLE', item.id) : ''
97-
],
98-
[]), // <input> does not have any nested elements
99-
label([], [text(item.title)]),
100-
button(["class=destroy",
101-
typeof signal === 'function' ? signal('DELETE', item.id) : ''])
102-
]) // </div>
103-
]) // </li>
156+
], []), // <input> does not have any nested elements
157+
].concat( model && model.editing !== item.id ?
158+
show_item(item, model, signal) : []
159+
)
160+
), // </div>
161+
].concat(
162+
model && model.editing && model.editing === item.id ?
163+
edit_item(item, model, signal) : []
164+
) // concat editing input if in "editing mode"
165+
) // </li>
104166
)
105167
}
106168

@@ -236,12 +298,23 @@ function subscriptions (signal) {
236298
document.addEventListener('keyup', function handler (e) {
237299
var new_todo = document.getElementById('new-todo');
238300
console.log('e.keyCode:', e.keyCode, '| key:', e.key);
239-
if (e.keyCode === ENTER_KEY && new_todo.value.length > 0) {
240-
signal('ADD')(); // invoke singal inner callback
241-
new_todo.value = ''; // reset <input> so we can add another todo
242-
document.getElementById('new-todo').focus();
301+
var editing = document.getElementsByClassName('editing');
302+
if (e.keyCode === ENTER_KEY) {
303+
if (editing && editing.length > 0) {
304+
console.log('editing:', editing);
305+
signal('SAVE')(); // invoke singal inner callback
306+
}
307+
if(new_todo.value.length > 0) {
308+
signal('ADD')(); // invoke singal inner callback
309+
new_todo.value = ''; // reset <input> so we can add another todo
310+
document.getElementById('new-todo').focus();
311+
}
243312
}
244313
});
314+
315+
// document.addEventListener('click', function click_anywhere (e) {
316+
//
317+
// });
245318
}
246319

247320
/* module.exports is needed to run the functions using Node.js for testing! */

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ test('render_item HTML without a valid signal function', function (t) {
9797
};
9898
// render the ONE todo list item:
9999
document.getElementById(id).appendChild(
100-
app.render_item(model.todos[0]),
100+
app.render_item(model.todos[0], model),
101101
);
102102

103103
const done = document.querySelectorAll('.completed')[0].textContent;
@@ -338,7 +338,7 @@ test('3. Mark all as completed ("TOGGLE_ALL")', function (t) {
338338
t.end();
339339
});
340340

341-
test('4. DELETE an item', function (t) {
341+
test('4. Item: should allow me to mark items as complete', function (t) {
342342
elmish.empty(document.getElementById(id));
343343
localStorage.removeItem('elmish_' + id);
344344
const model = {

0 commit comments

Comments
 (0)