Skip to content

Commit

Permalink
borrow code from example/counter-reset for Multiple Counters example #40
Browse files Browse the repository at this point in the history
  • Loading branch information
nelsonic committed Jun 10, 2018
1 parent 4c44077 commit b11473c
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 2 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -699,8 +699,8 @@ to use an `Object` for the `model` (_instead of an_ `Integer`) <br />
where the value of the first element in the `model.counters` Array
is the value for the _single_ counter example. <br />

**2.** using the `var model = { counters: [0] }` approach,
**display _multiple_ counters** on the **same page**. <br />
**2.** **Display _multiple_ counters** on the **_same_ page**
using the `var model = { counters: [0] }` approach. <br />

**3.** **Write tests** for the scenario where there
are multiple counters on the same page.
Expand Down
85 changes: 85 additions & 0 deletions examples/multiple-counters/counter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// 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
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)
}

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+
} // yes, for loop is "faster" than forEach, but readability trumps "perf" here!

// 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

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; // 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;
}

function init(doc){
document = doc; // this is used for instantiating JSDOM. ignore!
}

/* The code block below ONLY Applies to tests run using Node.js */
/* istanbul ignore next */
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
view: view,
mount: mount,
update: update,
div: div,
button: button,
empty: empty,
init: init
}
} else { init(document); }
33 changes: 33 additions & 0 deletions examples/multiple-counters/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang=”en-GB”>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>Elm Architecture in JS - Counter Reset</title>
<link rel="shortcut icon"
href="http://www.dwyl.io/images/favicon.ico" type="image/x-icon">
<!-- CSS Styles are 100% optional. but they make it look *much* nicer -->
<link rel="stylesheet" href="../style.css">
</head>
<body>
<div id="app"></div>
<script src="counter.js" data-cover></script> <!-- load counter -->
<script>
// Initialise the app by "mounting" it passing in MUV Object & "root" DOM node
mount(0, update, view, 'app');
</script>

<!-- Below this point is all related to the Tests for the App -->
<div id="test-app"></div> <!-- Create a test-app div to mount the app -->
<div id="qunit"></div> <!-- test results are displayed here -->
<!-- Load the QUnit CSS file from CDN - require to display our tests -->
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-1.18.0.css">
<!-- Load the QUnit Testing Framework from CDN - to run the tests -->
<script src="https://code.jquery.com/qunit/qunit-1.18.0.js"></script>
<!-- Load Blanket.js from CDN - for test coverage stats -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/blanket.js/1.1.4/blanket.js">
</script>
<script src="test.js"></script> <!-- always load test.js last -->
</body>
</html>
71 changes: 71 additions & 0 deletions examples/multiple-counters/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
var id = 'test-app';

test('Test Update update(0) returns 0 (current state)', function(assert) {
var result = update(0);
assert.equal(result, 0);
});

test('Test Update increment: update(1, "inc") returns 2', function(assert) {
var result = update(1, "inc");
assert.equal(result, 2);
});

test('Test Update decrement: update(3, "dec") returns 2', function(assert) {
var result = update(1, "dec");
assert.equal(result, 0);
});

test('Test negative state: update(-9, "inc") returns -8', function(assert) {
var result = update(-9, "inc");
assert.equal(result, -8);
});

test('mount({model: 7, update: update, view: view}, "'
+ id +'") sets initial state to 7', function(assert) {
mount(7, update, view, id);
var state =