todomvc-example-in-vanilla-javascript-using-elm-architecture-
Version:
Learn how to use The Elm Architecture in JavaScript to create functional and fast UI!
1,371 lines (1,091 loc) • 76.1 kB
Markdown
# `Elm`(_ish_)

<!-- the colors are deliberately "a bit off" to emphasize that
this is a "inspired by" but really a "poor immitation" of Elm! -->
`Elm`(_ish_) is an **`Elm`**-_inspired_ `JavaScript` (**ES5**)
fully functional front-end _micro_-framework from _scratch_.[<sup>1</sup>](#notes)
<br />
## _Why?_
The purpose of building `Elm`(_ish_) is _not_ to "_replace_" Elm
or to create [_yet another_ front-end JS framework](https://medium.com/tastejs-blog/yet-another-framework-syndrome-yafs-cf5f694ee070)!
The purpose of _separating_ the `Elm`(_ish_) functions
into a "micro framework" is to: <br />
**a)** **abstract** the "plumbing" so that we can
***simplify*** the Todo List application code
to _just_
["**application logic**"](https://en.wikipedia.org/wiki/Business_logic). <br />
**b)** _demo_ a ***re-useable*** (_fully-tested_)
"**micro-framework**" that allows us
to _practice_ using The Elm Architecture ("TEA").<br />
**c)** promote the **mindset** of writing **tests _first_**
and **`then`** the _least_ amount of code necessary to pass the test
(_while meeting the acceptance criteria_).
> _**Test** & **Document-Driven Development** is **easy** and it's **easily**
one of the **best habits** to form in your software development "career".
This walkthrough shows **how** you can do it **the right way**;
from the **start** of a project._
<br />
## _What?_
A walkthrough of creating a
_fully functional front-end_ "**micro framework**" ***from scratch***.
By the end of this exercise you will _understand_
The Elm Architecture (TEA) _much better_
because we will be analysing, documenting, testing
and writing each function required
to architect and render our Todo List (TodoMVC) App.
<br /><br />
## _Who?_
People who want to gain an _in-depth_ understanding
of The Elm Architecture ("TEA")
and thus _intrinsically_
[grok](https://en.wikipedia.org/wiki/Grok) Redux/React JavaScript apps.
This tutorial is intended for _beginners_ with _modest_
JavaScript knowledge (_variables, functions, DOM methods & TDD_). <br />
If you have any questions or get "stuck",
please open an issue:
https://github.com/dwyl/learn-elm-architecture-in-javascript/issues <br />
@dwyl is a "safe space" and we are all here to help don't be shy/afraid; <br />
the _more_ questions you ask, the more you are helping yourself and _others_!
<br />
## _How_?
_Before_ diving into _writing functions_ for `Elm`(_ish_),
we need to consider how we are going to _test_ it. <br />
By ensuring that we follow **TDD** from the _start_ of an project,
we _avoid_ having to "correct" any "**bad habits**" later.
We will be using **Tape** & **`JSDOM`** for testing the functions.
Tape is a _minimalist_ testing library
that is _fast_ and has _everything we need_.
**`JSDOM`** is a JavaScript implementation of the
WHATWG DOM & HTML standards, for use with node.js. <br />
If _either_ of these tools is _unfamiliar_ to you,
please see:
[https://github.com/dwyl/**learn-tape**](https://github.com/dwyl/learn-tape)
and
[**front-end**-with-tape.md](https://github.com/dwyl/learn-tape/blob/master/front-end-with-tape.md)
### What _Can_ We _Generalise_ ?
Our **first step** in creating `Elm`(_ish_)
is to _re-visit_ the functions we wrote for the "counter app"
and consider what _can_ be _generalised_ into
an application-independent re-useable framework.
> Our **rule-of-thumb** is: anything that creates (_or destroys_)
a DOM element or looks like "plumbing"
(_that which is common to **all apps**, e.g: "routing" or "managing state"_)
is _generic_ and should thus be abstracted into the `Elm`(_ish_) framework.
Recall that there are **3 parts** to the Elm Architecture:
`model`, `update` and `view`. <br />
These correspond to the `M`odel, `C`ontroller and `V`iew
of
["**MVC** pattern"](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller),
which is the most _widely used_ "software architecture pattern".
> **Aside**: "**software architecture**" is just a fancy way of saying
"how code is **organised**" and/or how "data **flows**" through a system.
Whenever you see the word "**pattern**" it just means
"a bunch of experienced people have concluded that this works well,
so as beginners, we don't have to think too hard (up-front)."
The _reason_ Elm refers to the "**Controller**" as "***Update***" is because
this name _more accurately_ reflects what the function _does_:
it _updates_ the _state_ (Model) of the application.
Our `update` and `view` functions will form
the "**domain logic**" of our Todo List App, <br />
(_i.e. they are "**specific**" to the Todo List_)
so we cannot abstract them. <br />
The `model` will be a JavaScript `Object` where the App's
data (todo list items) will be stored.
The `update` function is a simple `switch` statement
that "decides" how to to _`update`_ the app's `model`
each `case` will call a function
that _belongs_ to the Todo List App. <br />
The `view` function _invokes_ several "helper" functions
which create HTML ("DOM") elements e.g: `<section>`, `<div>` & `<button>`;
these _can_ (_will_) be generalised (_below_).
Let's start with a couple of "_familiar_" _generic_ functions
(_which we used in the "counter-reset" example_): `empty` and `mount`. <br />
<br />
### Start by Creating the Files
It's _essential_ to ask: "_Where do I **start** (my **TDD** quest)?_" <br />
The answer is: create **two** new files:
`lib/elmish.js` and `test/elmish.test.js`
### Test Setup
In order to run our test, we need some "setup" code
that "requires" the libraries/files so we can _execute_ the functions.
In the `test/elmish.test.js` file, type the following code:
```js
const test = require('tape'); // https://github.com/dwyl/learn-tape
const fs = require('fs'); // to read html files (see below)
const path = require('path'); // so we can open files cross-platform
const html = fs.readFileSync(path.resolve(__dirname,
'../index.html')); // sample HTML file to initialise JSDOM.
require('jsdom-global')(html); // https://github.com/rstacruz/jsdom-global
const elmish = require('../lib/elmish.js'); // functions to test
const id = 'test-app'; // all tests use 'test-app' as root element
```
> Most of this code should be _familiar_ to you
if you have followed previous tutorials.
> If anything is _unclear_ please revisit
https://github.com/dwyl/learn-tape
and
If you attempt to run the test file: `node test/elmish.test.js`
you should see no output.
(_this is expected as we haven't written any tests yet!_)
### `empty` the DOM
Start by _describing_ what the `empty` function _does_. <br />
This is both to clarify our _own_ understanding
as the people _writing_ the code <br />
and to _clearly communicate_ with the **`humans` _reading_** the code.
#### `empty` Function _Description_
The `empty` function deletes all the DOM elements
from within a specific "root" element. <br />
it is used to erase the DOM before re-rendering the app.
Following "**_Document(ation)_ Driven Development**",
we create a **`JSDOC`** comment block
in the `lib/elmish.js` file
with _just_ the function description:
```js
/**
* `empty` deletes all the DOM elements from within a specific "root" element.
* it is used to erase the DOM before re-rendering the app.
*/
```
Writing out the function documentation _first_
allows (_our subconscious_) time to _think_ about the functionality
and how to _test_ for the "_acceptance criteria_".
Even if you know _exactly_ what code needs to be written,
_resist_ the temptation to write the code until it is documented.
Even if you are writing code alone,
always imagine that you are "_pairing_" with someone
who does _not_ (_already_) "know the solution"
and you are _explaining_ it to them.
#### `empty` Function _Test_
We previously used the `empty` function in our `counter`,
`counter-reset` and `multiple-counters` examples (_in the "basic" TEA tutorial_)
so we have a "head start" on writing the test.
<!--
> _The **reason**(s) we write the **test first**
even when we (already) know the "solution" is:_ <br />
>
-->
In the `test/elmish.test.js` file, append the following code:
```js
test('empty("root") removes DOM elements from container', function (t) {
// setup the test div:
const text = 'Hello World!'
const root = document.getElementById(id);
const div = document.createElement('div');
div.id = 'mydiv';
const txt = document.createTextNode(text);
div.appendChild(txt);
root.appendChild(div);
// check text of the div:
const actual = document.getElementById('mydiv').textContent;
t.equal(actual, text, "Contents of mydiv is: " + actual + ' == ' + text);
t.equal(root.childElementCount, 1, "Root element " + id + " has 1 child el");
// empty the root DOM node:
elmish.empty(root); // <-- exercise the `empty` function!
t.equal(root.childElementCount, 0, "After empty(root) has 0 child elements!");
t.end();
});
```
> _**Note**: if any line in this file is **unfamiliar** to you,
please **first** go back over the previous example(s)_:
`counter-basic` _and_ `counter-reset`,
_**then** do bit of "googling" for any words or functions you don't recognise
e.g: `childElementCount`,
and if you are **still** "**stuck**"_,
[***please open an
issue***!](https://github.com/dwyl/learn-elm-architecture-in-javascript/issues)
_It's **essential** that you **understand** each **character**
in the code **before** continuing to **avoid** "**confusion**" later._
Run the test:
```sh
node test/elmish.test.js
```
You should see the following:

#### `empty` Function _Implementation_
Now that we have the **test** for our `empty` function written,
we can add the `empty` function to `lib/elmish.js`:
```js
/**
* `empty` deletes all the DOM elements from within a specific "root" element.
* it is used to erase the DOM before re-rendering the app.
* This is the *fastest* way according to: stackoverflow.com/a/3955238/1148249
* @param {Object} node the exact ("parent") DOM node you want to empty
* @example
* // returns true (once the 'app' node is emptied)
* empty(document.getElementById('app'));
*/
function empty(node) {
while (node.lastChild) {
node.removeChild(node.lastChild);
}
}
```
#### Add `module.exports` statement to "export" the `empty` function
Adding the function to the `elmish.js` file is a good _start_,
but we need to ***`export`*** it to be able to _invoke_ it in our test. <br />
Add the following code at the end of `lib/elmish.js`:
```js
/* module.exports is needed to run the functions using Node.js for testing! */
/* istanbul ignore next */
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
empty: empty // export the `empty` function so we can test it.
}
} else { init(document); }
```
When you run the test in your terminal with the command
`node test/elmish.test.js`
you should see something _similar_ to this:

Boom! our first test is passing!
(_the test has **3 assertions**, that's why Tape says "tests 3. pass 3"_).
### `mount` the App
The `mount` function is the "glue" or "wiring" function that
connects the `model`, `update` and `view`; we _can_ _generalise_ it.
#### `mount` function _Documentation_
Think about what the `mount` function _does_;
it "mounts" ("_renders_") the App in the "root" DOM element.
It also tells our app to "re-render"
when a `signal` with an `action` is received.
In `lib/elmish.js` add the following `JSDOC` comment:
```js
/**
* `mount` mounts the app in the "root" DOM Element.
* @param {Object} model store of the application's state.
* @param {Function} update how the application state is updated ("controller")
* @param {Function} view function that renders HTML/DOM elements with model.
* @param {String} root_element_id root DOM element in which the app is mounted
*/
```
#### `mount` function _Test_
In the `test/elmish.test.js` file, append the following code:
```js
// use view and update from counter-reset example
// to invoke elmish.mount() function and confirm it is generic!
const { view, update } = require('./counter.js');
test('elmish.mount app expect state to be Zero', function (t) {
const root = document.getElementById(id);
elmish.mount(7, update, view, id);
const actual = document.getElementById(id).textContent;
const actual_stripped = parseInt(actual.replace('+', '')
.replace('-Reset', ''), 10);
const expected = 7;
t.equal(expected, actual_stripped, "Inital state set to 7.");
// reset to zero:
const btn = root.getElementsByClassName("reset")[0]; // click reset button
btn.click(); // Click the Reset button!
const state = parseInt(root.getElementsByClassName('count')[0]
.textContent, 10);
t.equal(state, 0, "State is 0 (Zero) after reset."); // state reset to 0!
elmish.empty(root); // clean up after tests
t.end()
});
```
> _**Note**: we have "**borrowed**" this test from our previous example.
see:_ `test/counter-reset.test.js`
#### `mount` Function _Implementation_
Add the following code to the `mount` function body to make the test _pass_
in `lib/elmish.js`:
```js
/**
* `mount` mounts the app in the "root" DOM Element.
* @param {Object} model store of the application's state.
* @param {Function} update how the application state is updated ("controller")
* @param {Function} view function that renders HTML/DOM elements with model.
* @param {String} root_element_id root DOM element in which the app is mounted
*/
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
var updatedModel = update(action, model); // update model for the action
empty(root); // clear root el before rerender
view(signal, updatedModel, root); // subsequent re-rendering
};
};
view(signal, model, root); // render initial model (once)
}
```
#### Add `mount` to `module.exports` Object
Recall that in order to test the `elmish` functions we need to `export` them.
Your `module.exports` statement should now look something like this:
```js
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
empty: empty,
mount: mount
}
} else { init(document); }
```
#### Re-Run the Test(s)
Re-run the test suite:
```sh
node test/elmish.test.js
```
You should expect to see: (_tests passing_)

Now that we have started creating the `elmish` generic functions,
we need to know which _other_ functions we need. <br />
Let's take a look at the TodoMVC App to "_analyse_ the requirements".
### _Analyse_ the TodoMVC App to "Gather Requirements"
In our quest to _analyse_ the required functionality of a Todo List,
the _easiest_ way is to _observe_ a functioning TodoMVC Todo List.
### _Recommended_ Background Reading: TodoMVC "_Vanilla_" JS
By _far_ the best place to start for _understanding_ TodoMVC's layout/format,
is the "Vanilla" JavaScript (_no "framework"_) implementation:
https://github.com/tastejs/todomvc/tree/gh-pages/examples/vanillajs
Run it locally with:
```
git clone https://github.com/tastejs/todomvc.git
cd todomvc/examples/vanillajs
python -m SimpleHTTPServer 8000
```
Open your web browser to: http://localhost:8000

> If you are unable to run the TodoMVC locally, you can always view it online:
http://todomvc.com/examples/vanillajs
_Play_ with the app by adding a few items,
checking-off and toggling the views in the footer.
> _**Note**: having read through the the "**Vanilla**" **JS** implementation
we feel it is quite complex and insufficiently documented_
(_very few code comments and sparse_
[`README.md`](https://github.com/tastejs/todomvc/tree/25a9e31eb32db752d959df18e4d214295a2875e8/examples/vanillajs)),
_so don't expect to **understand** it all the first time without "study"._
_Don't worry, we will walk through building each feature in detail._
### Todo List _Basic_ Functionality
A todo list has only 2 _basic_ functions:
1. **Add** a `new` item to the list (when the **`[Enter]`** key is pressed)
2. **Check-off** an item as "**completed**" (_done/finished_)
> **Add** item and "**Check-off**" is _exactly_ the "functionality"
you would have in a _paper_-based Todo List.
#### TodoMVC "Advanced" Functionality
In _addition_ to these basic functions,
**TodoMVC** has the ability to:
+ **Un-check** an item as to make it "**active**" (_still to be done_)
+ **Double-click/tap** on todo **item description** to **`edit` it**.
+ **Mark _all_ as complete**
+ **Click `X`** on item row to remove from list.
#### `<footer>` Menu
below the main interface there is a `<footer>`
with a **count**, **3 view toggles** and **one action**:

+ "{cont} item(s) left": <br />
`{store.items.filter(complete==false)}` item`{store.items.length > 1 ? 's' : '' }` left
+ Show **`All`**
+ Show **`Active`**
+ Show **`Completed`**
+ **_Clear_ `Completed`**
#### Routing / Navigation
Finally, if you click around the `<footer>` toggle menu,
you will notice that the Web Bowser Address bar
changes to reflect the chosen view.

> Thinking about a task or challenge from
["first principals"](https://en.wikipedia.org/wiki/First_principle)
is ~~a great~~ the best way to _understand_ it. <br />
This is the "physics" approach. see: https://youtu.be/L-s_3b5fRd8?t=22m37s
### HTML Elements (Functions)
The _requirements_ for the HTML elements we _need_ for a Todo List
can be _gathered_ by viewing the source code of the VanillaJS TodoMVC
in a web browser:

This is a "copy-paste" of the _generated_ code including the Todo items:
```html
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input class="new-todo" placeholder="What needs to be done?" autofocus="">
</header>
<section class="main" style="display: block;">
<input id="toggle-all" class="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li data-id="1531397960010" class="completed">
<div class="view">
<input class="toggle" type="checkbox" checked="">
<label>Learn The Elm Architecture ("TEA")</label>
<button class="destroy"></button>
</div>
</li>
<li data-id="1531397981603" class="">
<div class="view">
<input class="toggle" type="checkbox">
<label>Build TEA Todo List App</label>
<button class="destroy">
</button>
</div>
</li>
</ul>
</section>
<footer class="footer" style="display: block;">
<span class="todo-count"><strong>1</strong> item left</span>
<ul class="filters">
<li>
<a href="#/" class="selected">All</a>
</li>
<li>
<a href="#/active" class="">Active</a>
</li>
<li>
<a href="#/completed">Completed</a>
</li>
</ul>
<button class="clear-completed" style="display: block;">Clear completed</button>
</footer>
</section>
```
Let's split each one of these elements into it's own `function`
(_with any necessary "helpers"_) in the order they appear.
> For a "checklist" of these features see: https://github.com/dwyl/learn-elm-architecture-in-javascript/issues/44
When building a House we don't think "build house" as our _first_ action. <br />
_Instead_ we think: what are the "foundations" that need to be in place
***before*** we lay the _first_ "brick"?
In our Todo List App we need a few "Helper Functions"
before we start building the App.
### HTML / DOM Creation Generic Helper Functions
All "grouping" or "container" HTML elements
e.g: `<div>`, `<section>` or `<span>`
will be called with ***two arguments***:
e.g: `var sec = section(attributes, childnodes)`
+ `attributes` - a list (Array) of HTML attributes/properties
e.g: `id` or `class`.
+ `childnodes` - a list (Array) of child HTML elements
(_nested within the_ `<section>` _element_)
Each of these function arguments will be "_applied_" to the HTML element.
We therefore need a pair of "helper" functions (_one for each argument_).
### `add_attributes`
The `JSDOC` comment for our `add_attributes` function is:
```js
/**
* add_attributes applies the desired attributes to the desired node.
* Note: this function is "impure" because it "mutates" the node.
* however it is idempotent; the "side effect" is only applied once
* and no other nodes in the DOM are "affected" (undesirably).
* @param {Array.<String>} attrlist list of attributes to be applied to the node
* @param {Object} node DOM node upon which attribute(s) should be applied
* @example
* // returns node with attributes applied
* div = add_attributes(["class=item", "id=mydiv", "active=true"], div);
*/
```
This should give you a _good idea_ of what code needs to be written.
But let's write the _test_ first!
Add the following test to the `test/elmish.test.js` file: <br />
```js
test('elmish.add_attributes applies class HTML attribute to a node', function (t) {
const root = document.getElementById(id);
let div = document.createElement('div');
div.id = 'divid';
div = elmish.add_attributes(["class=apptastic"], div);
root.appendChild(div);
// test the div has the desired class:
const nodes = document.getElementsByClassName('apptastic');
t.equal(nodes.length, 1, "<div> has 'apptastic' class applied");
t.end();
});
```
If you (_attempt to_) run this test (_and you **should**_),
you will see something like this:

Test is failing because the `elmish.add_attributes` function does not _exist_.
Go ahead and _create_ the `elmish.add_attributes` function
(_just the function without passing the test_) and _export_ it in `elmish.js`:
```js
/**
* add_attributes applies the desired attributes to the desired node.
* Note: this function is "impure" because it "mutates" the node.
* however it is idempotent; the "side effect" is only applied once
* and no other nodes in the DOM are "affected" (undesirably).
* @param {Array.<String>} attrlist list of attributes to be applied to the node
* @param {Object} node DOM node upon which attribute(s) should be applied
* @example
* // returns node with attributes applied
* div = add_attributes(["class=item", "id=mydiv", "active=true"], div);
*/
function add_attributes (attrlist, node) {
if(attrlist && attrlist.length) {
attrlist.forEach(function (attr) { // apply each prop in array
var a = attr.split('=');
switch(a[0]) {
// code to make test pass goes here ...
default:
break;
}
});
}
return node;
}
// ... at the end of the file, "export" the add_attributes funciton:
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
add_attributes: add_attributes, // export the function so we can test it!
empty: empty,
mount: mount
}
}
```
When you re-run the test you will see something like this:

The function _exists_ but it does not make the tests pass.
Your _quest_ is to turn this **`0`** into a **`1`**.
Given the **`JSDOC`** comment and _test_ above,
take a moment to think of how _you_ would write
the `add_attributes` function to apply a CSS `class` to an element. <br />
If you can, make the test _pass_
by writing the `add_attributes` function. <br />
(_don't forget to_ `export` _the function at the bottom of the file_).
If you get "stuck", checkout the _complete_ example:
[/lib/elmish.js](https://github.com/dwyl/learn-elm-architecture-in-javascript/tree/master/examples/todo-list/elmish.js)
> **Note 0**: we have "_seen_" the code _before_ in the `counter` example:
> [counter.js#L51](https://github.com/dwyl/learn-elm-architecture-in-javascript/blob/814467e81b1b9739da74378455bd12721b096ebd/examples/counter-reset/counter.js#L51) <br />
> The _difference_ is this time we want it to be "generic";
we want to apply a CSS `class` to _any_ DOM node.
> **Note 1**: it's not "cheating" to look at "the solution",
the whole point of having a step-by-step tutorial
is that you can check if you get "stuck",
but you should only check _after_ making
a good attempt to write the code _yourself_.
> **Note 2**: The `add_attributes` function is "impure" as it "mutates"
the target DOM `node`, this is more of a "fact of life" in JavaScript,
and given that the application of attributes
to DOM node(s) is idempotent we aren't "concerned" with "side effects";
the attribute will only be applied _once_ to the node
regardless of how many times the `add_attributes` function is called.
see: https://en.wikipedia.org/wiki/Idempotence
For reference, the Elm HTML Attributes function on Elm package is:
http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Attributes
Once you make the test _pass_ you _should_ see the following in your Terminal:

<!-- Onto the next one! https://vimeo.com/8503138 -->
<br />
#### Input `placeholder` Attribute
The `<input>` form element (_where we create new Todo List items_)
has a helpful `placeholder` attribute _prompting_ us with a question:
"_What needs to be done?_"
Add the following test to the `test/elmish.test.js` file: <br />
```js
test('elmish.add_attributes set placeholder on <input> element', function (t) {
const root = document.getElementById(id);
let input = document.createElement('input');
input.id = 'new-todo';
input = elmish.add_attributes(["placeholder=What needs to be done?"], input);
root.appendChild(input);
const placeholder = document.getElementById('new-todo')
.getAttribute("placeholder");
t.equal(placeholder, "What needs to be done?", "paceholder set on <input>");
t.end();
});
```
_Run_ the test `node test/elmish.test.js`:

You _know_ "the drill"; write the necessary code
in the `add_attributes` function of `elmish.js`
to add a `placeholder` to an `<input>` element
and make this test _pass_:

If you get "stuck", checkout the _complete_ example:
[/lib/elmish.js](https://github.com/dwyl/learn-elm-architecture-in-javascript/tree/master/examples/todo-list/elmish.js)
<br />
#### `default` `case` ("branch") test?
At this point in our `Elm`(_ish_) quest,
all our tests are _passing_,
which is good,
however that is not the "full picture" ...
If you use Istanbul to check the "test coverage"
(_the measure of which lines/branches of code are being executed during tests_),
you will see that only **98.5%** of lines of code is being "covered":

`@dwyl` we are "_keen_" on having "**100% Test Coverage**" ...
anything less than **100%** is _guaranteed_ to result in "regressions",
disappointment and a _lonely loveless life_. 💔

See:
[https://github.com/dwyl/**learn-istanbul**](https://github.com/dwyl/learn-istanbul)
This means that if we have a `switch` statement
as in the case of the `add_attributes` function we need to add a ***test***,
that "_exercises_" that "branch" of the code.
Add the following test code to your `test/elmish.test.js` file: <br />
```js
/** DEFAULT BRANCH Test **/
test('test default case of elmish.add_attributes (no effect)', function (t) {
const root = document.getElementById(id);
let div = document.createElement('div');
div.id = 'divid';
// "Clone" the div DOM node before invoking elmish.attributes to compare
const clone = div.cloneNode(true);
div = elmish.add_attributes(["unrecognised_attribute=noise"], div);
t.deepEqual(div, clone, "<div> has not been altered");
t.end();
});
```
By _definition_ this test will _pass_ without adding any additional code
because we _already_ added the `default: break;` lines above
(_which is "good practice" in `switch` statements_). <br />
Run the test(s) `node test/elmish.test.js`:

So "_why bother_" adding a _test_ if it's _always_ going to _pass_?
**_Two_ reasons**: <br />
**First**: It _won't_ "_always pass_".
if someone decides to _remove_ the "default" `case`
from `add_attributes` function (_people do "strange things" all the time!_)
it will _fail_ so by having a test,
we will _know_ that the `switch` is "_incomplete_". <br />
**Second**: Having "full coverage" of our code from the _start_ of the project,
and not having to"debate" or "discuss" the "merits" of it means
we can have _confidence_ in the code.
#### Test `null` Attribute Argument (`attrlist`) in `add_attributes` Function
Since JavaScript is _not_ statically/strictly typed we need to _consider_
the situation where someone might _accidentally_ pass a `null` value.
Thankfully, this is _easy_ to write a test for.
Add the following test to `test/elmish.test.js`: <br />
```js
test('test elmish.add_attributes attrlist null (no effect)', function (t) {
const root = document.getElementById(id);
let div = document.createElement('div');
div.id = 'divid';
// "Clone" the div DOM node before invoking elmish.attributes to compare
const clone = div.cloneNode(true);
div = elmish.add_attributes(null, div); // should not "explode"
t.deepEqual(div, clone, "<div> has not been altered");
t.end();
});
```
This test should _also_ pass without the addition of any code:

Now the Coverage should be 100% when you run `npm test`:

In your terminal, type/run the follwoing command: `open coverage/lcov-report/index.html`

#### Check-Coverage Pre-Commit Hook
Once you _achieve_ 100% test coverage,
there is no _reason_ to "compromise"
by going _below_ this level.
Let's add a `pre-commit` check
to make sure we maintain our desired standard.
> We wrote a detailed guide to git pre-commit hooks with npm:
[https://github.com/dwyl/learn-**pre-commit**]https://github.com/dwyl/learn-pre-commit
Install the `pre-commit` module:
```sh
npm install pre-commit istanbul --save-dev
```
In your `package.json` file add:
```js
{
"scripts": {
"check-coverage": "istanbul check-coverage --statements 100 --functions 100 --lines 100 --branches 100",
"test": "istanbul cover tape ./test/*.test.js | tap-spec"
},
"pre-commit": [
"test",
"check-coverage"
]
}
```
Now whenever you `commit` your code, your tests will run
and `istanbul` will check the test coverage level for you.
Let's get back to our `add_attributes` function!
<br />
#### Input `autofocus`
In order to "_guide_" the person using our Todo List app
to create their _first_ Todo List _item_,
**we want** the `<input>` field to be automatically "active"
**so that** they can just start typing as soon as the app loads.
This is achieved using the `autofocus` attribute.
Add the following test to the `test/elmish.test.js` file: <br />
```js
test.only('elmish.add_attributes add "autofocus" attribute', function (t) {
document.getElementById(id).appendChild(
elmish.add_attributes(["class=new-todo", "autofocus", "id=new"],
document.createElement('input')
)
);
// document.activeElement via: https://stackoverflow.com/a/17614883/1148249
t.equal(document.getElementById('new'), document.activeElement,
'<input autofocus> is "activeElement"');
elmish.empty(document);
t.end();
});
```
Write the necessary code to make this test _pass_
as a `case` in `add_attributes` in `elmish.js`.
Relevant reading:
+ `<input>` attributes:
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Attributes
+ https://caniuse.com/#feat=autofocus (_**unavailable** on **iOS Safari**!_)
> **Note**: while _all_ our _other_ HTML attributes
follow the `key="value"` syntax,
according to the W3C _specification_,
simply adding the attribute _key_ in the element is "valid"
e.g: `<input placeholder="What needs to be done?" autofocus>`
see: https://stackoverflow.com/questions/4445765/html5-is-it-autofocus-autofocus-or-autofocus
#### add `data-id` attribute to `<li>`
`data-*` attributes allow us to store extra information on standard,
semantic HTML elements without affecting regular attributes.
For example in the case of a Todo List item,
we want to store a reference to the "item id" in the DOM
for that item, so that we know which item to check-off when
the checkbox is clicked/tapped. _However_ we don't want to use the
"traditional" `id` attribute, we can use `data-id`
to keep a clear separation between the data and presentation.
See: "Using data attributes"
https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
In the TodoMVC HTML code
there are two `<li>` (_list elements_)
which have the `data-id` attribute (_see above_).
Add the following test to the `test/elmish.test.js` file: <br />
```js
test('elmish.add_attributes set data-id on <li> element', function (t) {
const root = document.getElementById(id);
let li = document.createElement('li');
li.id = 'task1';
li = elmish.add_attributes(["data-id=123"], li);
root.appendChild(li);
const data_id = document.getElementById('task1').getAttribute("data-id");
t.equal(data_id, '123', "data-id successfully added to <li> element");
t.end();
});
```
Write the "case" in to make this test _pass_ in `elmish.js`.
Tip: use `setAttribute()` method:
https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute
#### label `for` attribute
Apply the `for` attribute to a `<label>`
e.g: `<label for="toggle-all">`
HTML `<label>` attributes `for`:
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label#Attributes
Add the following test to the `test/elmish.test.js` file: <br />
```js
test.only('elmish.add_attributes set "for" attribute <label> element', function (t) {
const root = document.getElementById(id);
let li = document.createElement('li');
li.id = 'toggle';
li = elmish.add_attributes(["for=toggle-all"], li);
root.appendChild(li);
const label_for = document.getElementById('toggle').getAttribute("for");
t.equal(label_for, "toggle-all", '<label for="toggle-all">');
t.end();
});
```
Add the "`case`" in the `add_attributes` function's `switch` statement
to make this test _pass_ in `elmish.js`.
<br />
#### `<input>` attribute `type`
In order to use a Checkbox in our Todo List UI,
we need to set the `type=checkbox` on the `<input>` element.
Add the following test to the `test/elmish.test.js` file: <br />
```js
test('elmish.add_attributes type="checkbox" on <input> element', function (t) {
const root = document.getElementById(id);
let input = document.createElement('input');
input = elmish.add_attributes(["type=checkbox", "id=toggle-all"], input);
root.appendChild(input);
const type_atrr = document.getElementById('toggle-all').getAttribute("type");
t.equal(type_atrr, "checkbox", '<input id="toggle-all" type="checkbox">');
t.end();
});
```
Write the "case" in `add_attributes` to make this test _pass_ in `elmish.js`.
Relevant reading
+ `<input>` attribute `type`:
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Attributes
<br />
#### Add `style` attribute to HTML element?
In TodoMVC there are _three_ instances of in-line CSS styles.
they are _all_ `style="display: block;"`.
It's _unclear_ why setting _inline_ styles is _necessary_;
we _prefer_ to be _consistent_ and
***either*** use CSS `classes`
with an _external_ stylesheet (_which TodoMVC already does!_)
***or*** go _full_ "inline styles"
e.g: http://package.elm-lang.org/packages/mdgriffith/style-elements/latest
For now, let's add the `style` attribute
to our `add_attributes` function for "completeness".
see:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style
Add the following test to the `test/elmish.test.js` file: <br />
```js
test.only('elmish.add_attributes apply style="display: block;"', function (t) {
const root = document.getElementById(id);
elmish.empty(root);
let sec = document.createElement('section');
root.appendChild(
elmish.add_attributes(["id=main", "style=display: block;"], sec)
);
const style = window.getComputedStyle(document.getElementById('main'));
t.equal(style._values.display, 'block', 'style="display: block;" applied!')
t.end();
});
```
Write the "case" in to make this test _pass_ in `elmish.js`.
If you get "stuck", checkout:
https://github.com/dwyl/todomvc-vanilla-javascript-elm-architecture-example/blob/master/lib/elmish.js
<br />
#### `checked=true` attribute for "complete"/"done" items
Todo List items that have been marked as "done" will have the `checked=true`
attribute applied to them.
Add the following test to the `test/elmish.test.js` file: <br />
```js
test('elmish.add_attributes checked=true on "done" item', function (t) {
const root = document.getElementById(id);
elmish.empty(root);
let input = document.createElement('input');
input = elmish.add_attributes(["type=checkbox", "id=item1", "checked=true"],
input);
root.appendChild(input);
const checked = document.getElementById('item1').checked;
t.equal(checked, true, '<input type="checkbox" checked=true>');
let input2
t.end();
});
```
Write the code to make the test pass!
> _**Implementation note**: while the VanillaJS TodoMVC view has
`checked=""` (just an attribute with **no value**),
we find this "unfriendly" to beginners
so instead we are using `checked=true` instead because it's clearer.
See: https://stackoverflow.com/a/10650302/1148249
"Use true as it is marginally more efficient
and is **more intention revealing** to maintainers._"
For more detail on the `<input type="checkbox">`
see: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox
<br />
#### Set `href` on `<a>` (anchor) element
The "filters" in the `<footer>` of TodoMVC contain 3 links ("anchors") `<a>`
each of which have an `href` attribute indicating where
clicking/tapping on the link (filter) should "route" to.
> We will return to routing later (_below_),
for now we simply need to set the `href` attribute.
Add the following test to the `test/elmish.test.js` file: <br />
```js
test('elmish.add_attributes <a href="#/active">Active</a>', function (t) {
const root = document.getElementById(id);
elmish.empty(root);
root.appendChild(
elmish.add_attributes(["href=#/active", "class=selected", "id=active"],
document.createElement('a')
)
);
// note: "about:blank" is the JSDOM default "window.location.href"
console.log('JSDOM window.location.href:', window.location.href);
// so when an href is set *relative* to this it becomes "about:blank#/my-link"
// so we *remove* it before the assertion below, but it works fine in browser!
const href = document.getElementById('active').href.replace('about:blank', '')
t.equal(href, "#/active", 'href="#/active" applied to "active" link');
t.end();
});
```
Write the code to make the test pass!
Useful knowledge:
+ What: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#Attributes
+ Why: https://stackoverflow.com/questions/4855168/what-is-href-and-why-is-it-used
+ How: https://stackoverflow.com/questions/4689344/how-can-i-add-href-attribute-to-a-link-dynamically-using-javascript
<br />
### `append_childnodes`
The `append_childnodes` _functionality_ is a "_one-liner_": <br />
```js
childnodes.forEach(function (el) { parent.appendChild(el) });
```
It's easy to think: "_why bother to create a_ `function`...?" <br />
The _reasons_ to create _small_ functions are: <br />
**a)** Keep the _functionality_ "DRY" https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
which means we can _easily_ track down all instances of function invocation.
<br />
**b)** If we ever need to modify the function, e.g: to performance optimise it, there is a _single_ definition.
<br />
**c)** It makes unit-testing the functionality easy;
that's _great_ news for reliability!
With that in mind, let's write a _test_ for the `childnodes` function!
Add the following code to the `test/elmish.test.js` file: <br />
```js
test.only('elmish.append_childnodes appends child DOM nodes to parent', function (t) {
const root = document.getElementById(id);
elmish.empty(root); // clear the test DOM before!
let div = document.createElement('div');
let p = document.createElement('p');
let section = document.createElement('section');
elmish.append_childnodes([div, p, section], root);
t.equal(root.childElementCount, 3, "Root element " + id + " has 3 child els");
t.end();
});
```
Now, based on the following `JSDOC` comment:
```js
/**
* `append_childnodes` appends an array of HTML elements to a parent DOM node.
* @param {Array.<Object>} childnodes array of child DOM nodes.
* @param {Object} parent the "parent" DOM node where children will be added.
* @return {Object} returns parent DOM node with appended children
* @example
* // returns the parent node with the "children" appended
* var parent = elmish.append_childnodes([div, p, section], parent);
*/
```
_Implement_ this function to make the test pass.
It _should_ be the _easiest_ one so far.
(_see above for "one-liner" clue_...). <br />
Don't forget to remove the `.only` from the test, once you finish.
If you get "stuck", checkout:
[`lib/elmish.js`](https://github.com/dwyl/todomvc-vanilla-javascript-elm-architecture-example/blob/master/lib/elmish.js)
<br />
### `<section>` HTML Element
The _first_ HTML element we encounter in the TodoMVC app is `<section>`. <br />
`<section>` represents a standalone section — which doesn't have
a more specific semantic element to represent it —
it's an alternative way to group elements to a `<div>`.
> info: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section <br />
> difference:
https://stackoverflow.com/questions/6939864/what-is-the-difference-between-section-and-div
We want to make our `view` function "***declarative***",
this means our `view` should contain **no** "**control flow**"
(i.e. `if` statements).
The function invocations should reflect the final DOM quite closely
see: https://en.wikipedia.org/wiki/Declarative_programming
Example `view`:
```js
elmish.append_childnodes([
section(["class=todoapp"], [ // array of "child" elements
header(["class=header"], [
h1([], [
text("todos")
]), // </h1>
input([
"class=new-todo",
"placeholder=What needs to be done?",
"autofocus"
]) // <input> is "self-closing"
]) // </header>
])
], document.getElementById('my-app'));
```
Add the following _test_ to your `test/elmish.test.js` file: <br />
```js
test('elmish.section creates a <section> HTML element', function (t) {
const p = document.createElement('p');
p.id = 'para';
const text = 'Hello World!'
const txt = document.createTextNode(text);
p.appendChild(txt);
// create the `<section>` HTML element using our section function
const section = elmish.section(["class=new-todo"], [p])
document.getElementById(id).appendChild(section); // add section with <p>
// document.activeElement via: https://stackoverflow.com/a/17614883/1148249
t.equal(document.getElementById('para').textContent, text,
'<section> <p>' + text + '</p></section> works as expected!');
elmish.empty(document.getElementById(id));
t.end();
});
```
Based on the following `JSDOC` comment:
```js
/**
* section creates a <section> HTML element with attributes and childnodes
* @param {Array.<String>} attrlist list of attributes to be applied to the node
* @param {Array.<Object>} childnodes array of child DOM nodes.
* @return {Object} returns the <section> DOM node with appended children
* @example
* // returns <section> DOM element with attributes applied & children appended
* var section = elmish.section(["class=todoapp"], [h1, input]);
*/
```
Attempt to create the `section` function
using the `add_attributes` and `append_childnodes` "helper" functions.
If you get "stuck", checkout:
[`lib/elmish.js`](https://github.com/dwyl/todomvc-vanilla-javascript-elm-architecture-example/blob/master/lib/elmish.js)
<br />
> _**Note**: in our "solution" we created a "helper" function
called `create_element` to "DRY" the HTML element creation code;
this is a *recommended** "best practice" improves maintainability._
The `JSDOC` comment for our `create_element` function is:
```js
/**
* create_element is a "helper" function to "DRY" HTML element creation code
* creat *any* element with attributes and childnodes.
* @param {String} type of element to be created e.g: 'div', 'section'
* @param {Array.<String>} attrlist list of attributes to be applied to the node
* @param {Array.<Object>} childnodes array of child DOM nodes.
* @return {Object} returns the <section> DOM node with appended children
* @example
* // returns the parent node with the "children" appended
* var div = elmish.create_element('div', ["class=todoapp"], [h1, input]);
*/
```
`try` to write it for yourself before looking at the "answer".
For reference, the section function in Elm:
http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html
<br />
Demo: https://ellie-app.com/LTtNVQjfWVa1

### Create a `view` using HTML Element Functions!
Once we know how to create _one_ HTML element,
it's _easy_ to create _all_ of them!
Consider the following HTML for the `<header>` section of the TodoMVC App:
```html
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input class="new-todo" placeholder="What needs to be done?" autofocus="">
</header>
</section>
```
There are five HTML elements: `<section>`, `<header>`, `<h1>`
(_which has a `text` element_) and `<input>`.
We need a _function_ to represent (_create_) each one of these HTML elements.
Here is a **test** that creates the "real" header `view`:
(_notice how the "shape" of the "elmish" functions matches the HTML_)
```js
test('elmish create <header> view using HTML element functions', function (t) {
const { append_childnodes, section, header, h1, text, input } = elmish;
append_childnodes([
section(["class=todoapp"], [ // array of "child" elements
header(["class=header"], [
h1([], [
text("todos")
]), // </h1>
input([
"id=new",
"class=new-todo",
"placeholder=What needs to be done?",
"autofocus"
], []) // <input> is "self-closing"
]) // </header>
])
], document.getElementById(id));
const place = document.getElementById('new').getAttribute('placeholder');
t.equal(place, "What needs to be done?", "placeholder set in <input> el");
t.equal(document.querySelector('h1').textContent, 'todos', '<h1>todos</h1>');
elmish.empty(document.getElementById(id));
t.end();
});
```
We can define the required HTML element creation functions
in only a few lines of code.
Create (_and export_) the necessary functions to make the test pass:
`header`, `h1`, `input` and `text`.
**Tip**: each one of these HTML creation functions is a "_one-liner_" function body
that invokes the `create_element` function defined above.
Except the `text` function, which is still a "_one-liner_",
but has only one argument and invokes a native method.
If you get stuck trying to make this test pass,
refer to the completed code:
[/lib/elmish.js](https://github.com/dwyl/learn-elm-architecture-in-javascript/tree/master/examples/todo-list/elmish.js )
#### Create the "main" `view` functions
Once you have the code to pass the above test(s),
you will be ready to tackle something a bit bigger.
Our next `view` is the `main` App:
```html
<section class="main" style="display: block;">
<input id="toggle-all" class="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li data-id="1531397960010" class="completed">
<div class="view">
<input class="toggle" type="checkbox" checked="">
<label>Learn The Elm Architecture ("TEA")</label>
<button class="destroy"></button>
</div>
</li>
<li data-id="1531397981603" class="">
<div class="view">
<input class="toggle" type="checkbox">
<label>Build TEA Todo List App</label>
<button class="destroy">