diff --git a/README.md b/README.md
index d7ce536e..efd71957 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ When we encounter this type of "_what is the **right way**_?"
question
we always follow [***Occam's Razor***](https://en.wikipedia.org/wiki/Occam%27s_razor) and _ask_:
what is the ***simplest way***?
-In the case of web application organization,
+In the case of web application organization,
the ***answer*** is:
the "**Elm _Architecture_**".
@@ -63,13 +63,13 @@ When compared to _other_ ways of organizing your code,
+ Easier to _understand_ what is going on in more advanced apps because there is no complex logic,
only one basic principal
and the "_flow_" is _always_ the same.
-+ ***Uni-directional data-flow*** means "state"
++ ***Uni-directional data-flow*** means "state"
of the app is always _predictable_;
given a specific starting "state" and sequence of update actions
the output/end state will _always_ be the same. This makes testing/testability
very easy!
+ There's **no** "***middle man***" to complicate things
-(_the way there is in other application architectures
+(_the way there is in other application architectures
such as
[Model-view-Presenter](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter) or "Model-View-ViewModel" (MVVM) which is "overkill" for most apps_.)
@@ -85,12 +85,12 @@ their code/app in a _sane_, predictable and testable way.

-+ _Basic_ JavaScript Knowledge.
-see: [github.com/iteles/**Javascript**-the-**Good-Parts**-notes](https://github.com/iteles/Javascript-the-Good-Parts-notes)
-+ _Basic_ Understanding of TDD. If you are _completely_ new to TDD,
-please see: https://github.com/dwyl/learn-tdd
-+ A computer
-+ 30 minutes.
++ **_Basic_ JavaScript Knowledge**.
+see: [github.com/dwyl/**Javascript**-the-**Good-Parts**-notes](https://github.com/iteles/Javascript-the-Good-Parts-notes)
++ _Basic_ Understanding of **TDD**. If you are _completely_ new to TDD,
+please see: [github.com/dwyl/**learn-tdd**](https://github.com/dwyl/learn-tdd)
++ A computer with a Web Browser.
++ **30 minutes**.
> No other knowledge is assumed or implied.
If you have **_any_ questions**, ***please ask***:
@@ -105,17 +105,34 @@ If you have **_any_ questions**, ***please ask***:
Start with a few definitions:
-+ **M**odel - or "data model" is the place where all data
-is often referred to as the application's `state`.
++ **M**odel - or "data model" is the place where all data stored;
+often referred to as the application's `state`.
+ **U**pdate - how the app handles `actions` performed
by people and `update` the `state`.
-+ **V**iew - what the people using the app can _see_;
-a way to `view` the Model (counter) as `HTML`
-rendered by the web browser.
++ **V**iew - what people using the app can _see_;
+a way to `view` the Model (counter) as `HTML`
+rendered in a web browser.

-Don't worry if you don't understand this diagram (_yet_),
+
+If you're not into flow diagrams, don't worry, there not everyone is,
+a _much_ more "user friendly" _explanation_
+of **The Elm Architecture** ("TEA")
+is
+[**Kolja Wilcke**'s](https://twitter.com/01k/status/986528602635358208?s=20) _fantastic_
+["View Theater" diagram](https://github.com/w0rm/creating-a-fun-game-with-elm/blob/001baf05b3879d12c0ff70075e9d25e8cc7c4656/assets/the-elm-architecture1.jpg):
+
+
+
+
+
+Creative Commons License:
+[Attribution 4.0 International (CC BY 4.0)](https://twitter.com/01k/status/986528602635358208?s=20)
+
+
+
+If this diagram is not clear (_yet_), again, don't panic,
it will all become clear when you start seeing it in _action_ (_below_)!
@@ -666,6 +683,34 @@ button('Reset', signal, Res)
```

+
+
+### 10. _Next Level: Multiple Counters_!
+
+Now that you have _understood_ the Elm Architecture
+by following the basic (_single_) counter example,
+it's time to take the example to the next level:
+multiple counters on the same page!
+
+#### Multiple Counters Exercise
+
+Follow your _instincts_ and `try` to the following:
+
+**1.** **Refactor** the "reset counter" example
+to use an `Object` for the `model` (_instead of an_ `Integer`)
+**e.g**: `var model = { counters: [0] }`
+where the value of the first element in the `model.counters` Array
+is the value for the _single_ counter example.
+
+**2.** **Display _multiple_ counters** on the **_same_ page**
+using the `var model = { counters: [0] }` approach.
+
+**3.** **Write tests** for the scenario where there
+are multiple counters on the same page.
+
+Once you have had a go, checkout our solutions: `examples/multiple-counters`
+and corresponding writeup:
+[**multiple-counters.md**](https://github.com/dwyl/learn-elm-architecture-in-javascript/blob/master/multiple-counters.md)
diff --git a/examples/counter-reset/counter.js b/examples/counter-reset/counter.js
index 9df71bec..dc0944ee 100644
--- a/examples/counter-reset/counter.js
+++ b/examples/counter-reset/counter.js
@@ -72,7 +72,8 @@ function init(doc){
/* The code block below ONLY Applies to tests run using Node.js */
/* istanbul ignore next */
-if (typeof module !== 'undefined' && module.exports) { module.exports = {
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = {
view: view,
mount: mount,
update: update,
diff --git a/examples/multiple-counters-instances/index.html b/examples/multiple-counters-instances/index.html
new file mode 100644
index 00000000..54a78080
--- /dev/null
+++ b/examples/multiple-counters-instances/index.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+ Elm Architecture in JS - Counter Reset
+
+
+
Elm Architecture in JS - Counter Reset
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/multiple-counters/counter.js b/examples/multiple-counters/counter.js
new file mode 100644
index 00000000..a28d87c4
--- /dev/null
+++ b/examples/multiple-counters/counter.js
@@ -0,0 +1,89 @@
+// Define the Component's Actions:
+var Inc = 'inc'; // increment the counter
+var Dec = 'dec'; // decrement the counter
+var Res = 'reset'; // reset counter: git.io/v9KJk
+
+function update(model, action) { // Update function takes the current state
+ var parts = action ? action.split('-') : []; // e.g: inc-0 where 0 is the counter "id"
+ var act = parts[0];
+ var index = parts[1] || 0;
+ var new_model = JSON.parse(JSON.stringify(model)) // "clone" the model
+ switch(act) { // and an action (String) runs a switch
+ case Inc:
+ new_model.counters[index] = model.counters[index] + 1;
+ break;
+ case Dec:
+ new_model.counters[index] = model.counters[index] - 1;
+ break;
+ case Res: // use ES6 Array.fill to create a new array with values set to 0:
+ new_model.counters[index] = 0;
+ break;
+ default: return model; // if action not defined, return curent state.
+ }
+ return new_model;
+}
+
+function view(signal, model, root) {
+ empty(root); // clear root element before re-rendering the App (DOM).
+ model.counters.map(function(counter, index) {
+ return container(index, [ // wrap DOM nodes in an "container"
+ button('+', signal, Inc + '-' + index), // append index to action
+ div('count', counter), // create div w/ count as text
+ button('-', signal, Dec + '-' + index), // decrement counter
+ button('Reset', signal, Res + '-' + index) // reset counter
+ ]);
+ }).forEach(function (el) { root.appendChild(el) }); // forEach is ES5 so IE9+
+}
+
+// Mount Function receives all MUV and mounts the app in the "root" DOM Element
+function mount(model, update, view, root_element_id) {
+ var root = document.getElementById(root_element_id); // root DOM element
+ function signal(action) { // signal function takes action
+ return function callback() { // and returns callback
+ model = update(model, action); // update model according to action
+ view(signal, model, root); // subsequent re-rendering
+ };
+ };
+ view(signal, model, root); // render initial model (once)
+}
+
+// The following are "Helper" Functions which each "Do ONLY One Thing" and are
+// used in the "View" function to render the Model (State) to the Browser DOM:
+
+// empty the contents of a given DOM element "node" (before re-rendering)
+function empty(node) {
+ while (node.firstChild) {
+ node.removeChild(node.firstChild);
+ }
+} // Inspired by: stackoverflow.com/a/3955238/1148249
+
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section
+function container(index, elements) {
+ var con = document.createElement('section');
+ con.id = index;
+ con.className = 'counter';
+ elements.forEach(function(el) { con.appendChild(el) });
+ return con;
+}
+
+function button(text, signal, action) {
+ var button = document.createElement('button');
+ var text = document.createTextNode(text); // human-readable button text
+ button.appendChild(text); // text goes *inside* not attrib
+ button.className = action.split('-')[0]; // use action as CSS class
+ button.id = action;
+ // console.log(signal, ' action:', action)
+ button.onclick = signal(action); // onclick tells how to process
+ return button; // return the DOM node(s)
+} // how to create a button in JavaScript: stackoverflow.com/a/8650996/1148249
+
+function div(divid, text) {
+ var div = document.createElement('div');
+ div.id = divid;
+ div.className = divid;
+ if(text !== undefined) { // if text is passed in render it in a "Text Node"
+ var txt = document.createTextNode(text);
+ div.appendChild(txt);
+ }
+ return div;
+}
diff --git a/examples/multiple-counters/index.html b/examples/multiple-counters/index.html
new file mode 100644
index 00000000..3d78a4d9
--- /dev/null
+++ b/examples/multiple-counters/index.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+ Elm Architecture in JS - Counter Reset
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/multiple-counters/test.js b/examples/multiple-counters/test.js
new file mode 100644
index 00000000..7768095c
--- /dev/null
+++ b/examples/multiple-counters/test.js
@@ -0,0 +1,74 @@
+var id = 'test-app';
+
+test('update({counters:[0]}) returns {counters:[0]} (current state unmodified)',
+ function(assert) {
+ var result = update({counters:[0]});
+ assert.equal(result.counters[0], 0);
+});
+
+test('Test Update increment: update(1, "inc") returns 2', function(assert) {
+ var result = update({counters: [1] }, "inc");
+ console.log('result', result);
+ assert.equal(result.counters[0], 2);
+});
+
+
+test('Test Update decrement: update(1, "dec") returns 0', function(assert) {
+ var result = update({counters: [1] }, "dec");
+ assert.equal(result.counters[0], 0);
+});
+
+test('Test negative state: update(-9, "inc") returns -8', function(assert) {
+ var result = update({counters: [-9] }, "inc");
+ assert.equal(result.counters[0], -8);
+});
+
+test('mount({model: 7, update: update, view: view}, "'
+ + id +'") sets initial state to 7', function(assert) {
+ mount({counters:[7]}, update, view, id);
+ var state = document.getElementById(id)
+ .getElementsByClassName('count')[0].textContent;
+ assert.equal(state, 7);
+});
+
+test('empty("test-app") should clear DOM in root node', function(assert) {
+ empty(document.getElementById(id));
+ mount({counters:[7]}, update, view, id);
+ empty(document.getElementById(id));
+ var result = document.getElementById(id).innerHtml
+ assert.equal(result, undefined);
+});
+
+test('click on "+" button to re-render state (increment model by 1)',
+function(assert) {
+ document.body.appendChild(div(id));
+ mount({counters:[7]}, update, view, id);
+ document.getElementById(id).getElementsByClassName('inc')[0].click();
+ var state = document.getElementById(id)
+ .getElementsByClassName('count')[0].textContent;
+ assert.equal(state, 8); // model was incremented successfully
+ empty(document.getElementById(id)); // clean up after tests
+});
+
+// Reset Functionality
+
+test('Test reset counter when model/state is 6 returns 0', function(assert) {
+ var result = update({counters:[7]}, "reset");
+ assert.equal(result.counters[0], 0);
+});
+
+test('reset button should be present on page', function(assert) {
+ var reset = document.getElementsByClassName('reset');
+ assert.equal(reset.length, 3);
+});
+
+test('Click reset button resets state to 0', function(assert) {
+ mount({counters:[7]}, update, view, id);
+ var root = document.getElementById(id);
+ assert.equal(root.getElementsByClassName('count')[0].textContent, 7);
+ var btn = root.getElementsByClassName("reset")[0]; // click reset button
+ btn.click(); // Click the Reset button!
+ var state = root.getElementsByClassName('count')[0].textContent;
+ assert.equal(state, 0); // state was successfully reset to 0!
+ empty(root); // clean up after tests
+});
diff --git a/examples/style.css b/examples/style.css
index b52d7184..8ddfa8da 100644
--- a/examples/style.css
+++ b/examples/style.css
@@ -2,6 +2,7 @@ body{
font-family: Courier, "Lucida Console", monospace;
font-size: 4em;
text-align: center;
+ background-color: white;
}
button {
font-size: 0.5em; color:white; border:5px solid; border-radius: 0.5em;
@@ -18,6 +19,23 @@ button {
background-color: #f39c12; border-color: #e67e22;
}
+#qunit {
+ padding-top: 0.5em;
+ width: 100%;
+ clear: both;
+}
+
#qunit-header { /* just cause the default style makes the header HUGE!! */
font-size: 0.4em !important;
}
+
+/* specific to multiple counters */
+section {
+ background-color: white;
+ float: left;
+ padding-right: 1%;
+ width: 32%;
+}
+section button {
+ width: 100%;
+}
diff --git a/multiple-counters.md b/multiple-counters.md
new file mode 100644
index 00000000..afb9cf53
--- /dev/null
+++ b/multiple-counters.md
@@ -0,0 +1,204 @@
+# _Multiple_ Counters Exercise!
+
+There are (_at least_) two ways
+of displaying multiple counters on the same page.
+
+The _easy_ way is to "_instantiate_" several counters
+each within their own "container" (DOM) element. e.g:
+```html
+
+
+
+
+
+```
+
+
+see: [link to multiple counter instances code]
+
+
+This "_works_" and "_satisfies_ the _requirement_"
+of having multiple counters on the same "page".
+_However_, it's not a "sustainable" way of "extending" an app for the long term.
+Almost no "_real_" web application uses an `Integer` as the `model`.
+
+We could leave the counter example `model` as an `Integer`
+and move on to the _next_ example (_Todo List_),
+but as a "_thought experiment_",
+let's try to implement _multiple counters_ using an `Array` of `Integers`,
+this is a good "**refactoring**" exercise.
+
+
+
+## 1. Refactor Model from `Integer` to `Object` with `Array`
+
+Using the code from
+[`example/counter-reset`](https://github.com/dwyl/learn-elm-architecture-in-javascript/tree/master/examples/counter-reset)
+as a starting point,
+refactor the `model` from `Integer` to an `Object` with an `Array`
+called `counters`:
+```js
+mount({counters:[0]}, update, view, 'app');
+```
+
+That will "_break_" the existing tests:
+
+
+(_I **temporarily commented out** all the other failing tests
+ to reduce noise, but by the time we are done refactoring,
+ all tests will pass!_)
+
+### 1.1 Make Tests Pass Again?
+
+When refactoring the _convention_ is to ***not touch the tests***,
+_However_ the _first_ test in our `test.js` file checks the `state`
+of the `model` if no _action_ is passed into the `update` function:
+```js
+test('Test Update update(0) returns 0 (current state)', function(assert) {
+ var result = update(0);
+ assert.equal(result, 0);
+});
+```
+This test is still _relevant_ because the Elm Architecture _always_
+returns the `model` _unchanged_ if no `action` is given.
+We need to _update_ this test to reflect the change in the `model` signature:
+```js
+test('update({counters:[0]}) returns {counters:[0]} (current state unmodified)', function(assert) {
+ var result = update({counters:[0]});
+ assert.equal(result.counters[0], 0);
+});
+```
+
+Snapshot of the code/changes required to make tests pass again:
+https://github.com/dwyl/learn-elm-architecture-in-javascript/pull/41/commits/c65d491d69d2d68964df36817ccbff9de3275f0b
+
+
+
+## 2. Render Multiple Counters using New Model
+
+Updating the `model` was the _start_ of our refactoring journey,
+if we were to include multiple elements in the `counters` `Array`
+now, before updating the `view` function,
+we would still only see _one_
+counter on the page because our `view`
+does not _yet_ "know" how to render multiple counters.
+
+
+### 2.1 Update the `view` function
+
+Given that we have updated the `model` to be a an `Object`
+with a `counters` `Array`, we need to update our `view` function
+to render as many counters as we have elements
+in the `counters` `Array`.
+
+_First_ create a "container" DOM element so each counter
+(_the increment, decrement and reset buttons
+ and text display of the current counter value_)
+can be "wrapped" together:
+
+```js
+function container(index, elements) {
+ var con = document.createElement('section');
+ con.id = index;
+ con.className = 'counter';
+ elements.forEach(function(el) { con.appendChild(el) });
+ return con;
+}
+```
+
+This `container` function will be used
+in the re-worked `view` function (_which we are modifying next!_)
+
+Let's modify the `view` function to accommodate
+
+#### Before:
+
+```js
+function view(signal, model, root) {
+ empty(root); // clear root element before
+ [ // Store DOM nodes in an array
+ button('+', signal, Inc), // then iterate to append them
+ div('count', model), // create div with stat as text
+ button('-', signal, Dec), // decrement counter
+ button('Reset', signal, Res) // reset counter
+ ].forEach(function(el){ root.appendChild(el) }); // forEach is ES5 so IE9+
+}
+```
+
+#### After:
+
+```js
+function view(signal, model, root) {
+ empty(root); // clear root element before re-rendering the App (DOM).
+ model.counters.map(function(counter, index) { // one counter for each
+ return container(index, [ // wrap DOM nodes in an "container"
+ button('+', signal, Inc + '-' + index), // append index to action
+ div('count', counter), // create div w/ count as text
+ button('-', signal, Dec + '-' + index), // decrement counter
+ button('Reset', signal, Res + '-' + index) // reset counter
+ ]);
+ }).forEach(function (el) { root.appendChild(el) }); // forEach is ES5 so IE9+
+}
+```
+The key differences are:
++ Wrapping the counter in a "container" DOM element.
++ Appending the index (_in the `model.counters` Array_)
+to each `action` e.g: `Inc + '-' + index`
+such that each button is unique and we can derive the
+_exact_ counter that needs to be Incremented.
+
+
+### 2.2 Refactor the `update` function
+
+The `update` function needs to be updated to support
+
+#### Before:
+
+```js
+function update(model, action) { // Update function takes the current state
+ switch(action) { // and an action (String) runs a switch
+ case Inc: return model + 1; // add 1 to the model
+ case Dec: return model - 1; // subtract 1 from model
+ case Res: return 0; // reset state to 0 (Zero) git.io/v9KJk
+ default: return model; // if no action, return curent state.
+ } // (default action always returns current)
+}
+```
+
+#### After:
+
+```js
+function update(model, action) {
+ var parts = action ? action.split('-') : []; // e.g: inc-0 where 0 is the counter "id"
+ var act = parts[0];
+ var index = parts[1] || 0; // default to 0 (assume only one counter)
+ var new_model = JSON.parse(JSON.stringify(model)) // "clone" the model
+ switch(act) { // and an action (String) runs a switch
+ case Inc:
+ new_model.counters[index] = model.counters[index] + 1;
+ break;
+ case Dec:
+ new_model.counters[index] = model.counters[index] - 1;
+ break;
+ case Res: // use ES6 Array.fill to create a new array with values set to 0:
+ new_model.counters[index] = 0;
+ break;
+ default: return model; // if action not defined, return current state.
+ }
+ return new_model;
+}
+```
+
+Try it: http://127.0.0.1:8000/examples/multiple-counters/?coverage
+
+
+
+
+If you can _simplify_ this code,
+we're happy to receive a Pull Request!
+Share your thoughts on:
+https://github.com/dwyl/learn-elm-architecture-in-javascript/issues/40