Skip to content

Commit

Permalink
before week 10
Browse files Browse the repository at this point in the history
  • Loading branch information
Dierk Koenig committed May 9, 2021
1 parent 68c0b4c commit 3c88477
Show file tree
Hide file tree
Showing 11 changed files with 571 additions and 1 deletion.
36 changes: 36 additions & 0 deletions week10/Milestone_5/allTests.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<title> All Tests </title>
<script src="util/test.js"></script>
</head>
<body>

<h1>All Tests in HtmlJs</h1>

<pre>

<script>

const testNames = [
'observable',
'todo'
];

testNames.forEach( testName => {


document.write(`<script src="${testName}/${testName}.js"></s`+'cript>'); // dirty trick of the day
document.write(`<script src="${testName}/${testName}Test.js"></s`+'cript>');


});

document.writeln("\nCheck possible 'compile' errors in the console .")

</script>

</pre>

</body>
</html>
27 changes: 27 additions & 0 deletions week10/Milestone_5/observable/View.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<title>View</title>
<script src="observable.js"></script>
</head>
<body>

<input id="name" type="text">
<div id="label"></div>
<div id="size"></div>

<script>
const nameInput = document.getElementById("name");
const label = document.getElementById("label");
const size = document.getElementById("size");

const inputAttr = Observable("");
inputAttr.onChange(val => label.innerText = val);
inputAttr.onChange(val => size.innerText = val.length);

nameInput.oninput = _ => inputAttr.setValue(nameInput.value);

</script>

</body>
</html>
44 changes: 44 additions & 0 deletions week10/Milestone_5/observable/observable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@


const Observable = value => {
const listeners = [];
return {
onChange: callback => {
listeners.push(callback);
callback(value, value);
},
getValue: () => value,
setValue: newValue => {
if (value === newValue) return;
const oldValue = value;
value = newValue;
listeners.forEach(callback => callback(value, oldValue));
}
}
};


const ObservableList = list => {
const addListeners = [];
const delListeners = [];
const removeAt = array => index => array.splice(index, 1);
const removeItem = array => item => { const i = array.indexOf(item); if (i>=0) removeAt(array)(i); };
const listRemoveItem = removeItem(list);
const delListenersRemove = removeAt(delListeners);
return {
onAdd: listener => addListeners.push(listener),
onDel: listener => delListeners.push(listener),
add: item => {
list.push(item);
addListeners.forEach( listener => listener(item))
},
del: item => {
listRemoveItem(item);
const safeIterate = [...delListeners]; // shallow copy as we might change listeners array while iterating
safeIterate.forEach( (listener, index) => listener(item, () => delListenersRemove(index) ));
},
removeDeleteListener: removeItem(delListeners),
count: () => list.length,
countIf: pred => list.reduce( (sum, item) => pred(item) ? sum + 1 : sum, 0)
}
};
61 changes: 61 additions & 0 deletions week10/Milestone_5/observable/observableTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

test("observable-value", assert => {

const obs = Observable("");

// initial state
assert.equals(obs.getValue(), "");

// subscribers get notified
let found;
obs.onChange(val => found = val);
obs.setValue("firstValue");
assert.equals(found, "firstValue");

// value is updated
assert.equals(obs.getValue(), "firstValue");

// it still works when the receiver symbols changes
const newRef = obs;
newRef.setValue("secondValue");
// listener updates correctly
assert.equals(found, "secondValue");

// Attributes are isolated, no "new" needed
const secondAttribute = Observable("");

// initial state
assert.equals(secondAttribute.getValue(), "");

// subscribers get notified
let secondFound;
secondAttribute.onChange(val => secondFound = val);
secondAttribute.setValue("thirdValue");
assert.equals(found, "secondValue");
assert.equals(secondFound, "thirdValue");

// value is updated
assert.equals(secondAttribute.getValue(), "thirdValue");

});

test("observable-list", assert => {
const raw = [];
const list = ObservableList( raw ); // decorator pattern

assert.equals(list.count(), 0);
let addCount = 0;
let delCount = 0;
list.onAdd( item => addCount += item);
list.add(1);
assert.equals(addCount, 1);
assert.equals(list.count(), 1);
assert.equals(raw.length, 1);

list.onDel( item => delCount += item);
list.del(1);
assert.equals(delCount, 1);
assert.equals(list.count(), 0);
assert.equals(raw.length, 0);

});
39 changes: 39 additions & 0 deletions week10/Milestone_5/todo/View.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Todos</title>
<link rel="stylesheet" href="../../todo.css">
</head>
<body>

<main>
<h1>Todo List</h1>

<div class="holder">
<button id="plus" autofocus onclick="todoController.addTodo();"> + </button>

<div class="table" id="todoContainer"></div>

<div>Tasks: <span id="numberOfTasks">0</span></div>
<div>Open: <span id="openTasks" >0</span></div>
</div>
</main>


<script src="../observable/observable.js"></script>
<script src="todo.js"></script>
<script>
const todoController = TodoController();

TodoItemsView(todoController, document.getElementById('todoContainer'));
TodoTotalView(todoController, document.getElementById('numberOfTasks'));
TodoOpenView (todoController, document.getElementById('openTasks'));

todoController.addTodo();

</script>

</body>

</html>
100 changes: 100 additions & 0 deletions week10/Milestone_5/todo/todo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// requires ../observable/observable.js

const TodoController = () => {

const Todo = () => { // facade
const textAttr = Observable("text"); // we current don't expose it as we don't use it elsewhere
const doneAttr = Observable(false);
return {
getDone: doneAttr.getValue,
setDone: doneAttr.setValue,
onDoneChanged: doneAttr.onChange,
}
};

const todoModel = ObservableList([]); // observable array of Todos, this state is private

const addTodo = () => {
const newTodo = Todo();
todoModel.add(newTodo);
return newTodo;
};

return {
numberOfTodos: todoModel.count,
numberOfopenTasks: () => todoModel.countIf( todo => ! todo.getDone() ),
addTodo: addTodo,
removeTodo: todoModel.del,
onTodoAdd: todoModel.onAdd,
onTodoRemove: todoModel.onDel,
removeTodoRemoveListener: todoModel.removeDeleteListener, // only for the test case, not used below
}
};


// View-specific parts

const TodoItemsView = (todoController, rootElement) => {

const render = todo => {

function createElements() {
const template = document.createElement('DIV'); // only for parsing
template.innerHTML = `
<button class="delete">&times;</button>
<input type="text" size="42">
<input type="checkbox">
`;
return template.children;
}
const [deleteButton, inputElement, checkboxElement] = createElements();

checkboxElement.onclick = _ => todo.setDone(checkboxElement.checked);
deleteButton.onclick = _ => todoController.removeTodo(todo);

todoController.onTodoRemove( (removedTodo, removeMe) => {
if (removedTodo !== todo) return;
rootElement.removeChild(inputElement);
rootElement.removeChild(deleteButton);
rootElement.removeChild(checkboxElement);
removeMe();
} );

rootElement.appendChild(deleteButton);
rootElement.appendChild(inputElement);
rootElement.appendChild(checkboxElement);
};

// binding

todoController.onTodoAdd(render);

// we do not expose anything as the view is totally passive.
};

const TodoTotalView = (todoController, numberOfTasksElement) => {

const render = () =>
numberOfTasksElement.innerText = "" + todoController.numberOfTodos();

// binding

todoController.onTodoAdd(render);
todoController.onTodoRemove(render);
};

const TodoOpenView = (todoController, numberOfOpenTasksElement) => {

const render = () =>
numberOfOpenTasksElement.innerText = "" + todoController.numberOfopenTasks();

// binding

todoController.onTodoAdd(todo => {
render();
todo.onDoneChanged(render);
});
todoController.onTodoRemove(render);
};


Loading

0 comments on commit 3c88477

Please sign in to comment.