diff --git a/.gitignore b/.gitignore index d42b6616..31af3606 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ jspm_packages # Optional REPL history .node_repl_history .DS_Store +package-lock.json diff --git a/.travis.yml b/.travis.yml index ed4597c7..e6186937 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: node_js node_js: - - 6 -before_install: - - pip install --user codecov + - node after_success: - - codecov --file coverage/lcov.info --disable search + - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index d57cbe5c..ac736d8a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ the Elm ("Model Update View") Architecture in "_plain_" JavaScript. > We think Elm is the _future_ of Front End Web Development
for all the _reasons_ described in: [github.com/dwyl/**learn-elm#why**](https://github.com/dwyl/learn-elm#why)
-_However_ we _acknowledge_ that Elm is _not_ for _everyone_!
+_However_ we _acknowledge_ that Elm is "_**not everyone's taste**_"!
> What [_many_](https://youtu.be/VNGFep6rncY) Front-End Developers _are_ learning/using is @@ -34,7 +34,7 @@ apps.
the Elm Architecture,
for people who write JavaScript and want a _**functional**, **elegant** and **fast**_
-way of organizing their JavaScript _without_ +way of organizing their JavaScript code _without_ having the learning curve
of a completely new (_functional_) programming language! @@ -46,12 +46,14 @@ of a completely new (_functional_) programming language! _Organizing_ `code` in a Web (_or Mobile_) Application is _really easy_ to ***over-complicate***,
_especially_ when you are just starting out and there -are dozens of competing ideas
-all claiming to be the "_right way_"... +are _dozens_ of competing ideas +all _claiming_ to be the "***right way***"... 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_: +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, the ***answer*** is: @@ -63,15 +65,23 @@ 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 the "state" of the app is always _predictable_; -given a specific starting "state" and sequence of update actions +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 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_.) +[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_). + +> _**Note**: **don't panic** if any of the terms above are strange +or even confusing to you right now. +> Our **quest** is to put all the concepts into **context**. +> And if you get "**stuck**" at any point, we are here to help! +> Simply **open a question** on GitHub:_ +[github.com/dwyl/**learn-elm-architecture**-in-javascript/**issues**](https://github.com/dwyl/learn-elm-architecture-in-javascript/issues) + ## _Who? (Should I Read/Learn This...?)_ @@ -83,7 +93,7 @@ their code/app in a _sane_, predictable and testable way. ### _Prerequisites_? -![all-you-need-is-less](https://cloud.githubusercontent.com/assets/194400/25772135/a4230490-325b-11e7-9f12-da19fa4eb5e9.png) +[![all-you-need-is-less](https://cloud.githubusercontent.com/assets/194400/25772135/a4230490-325b-11e7-9f12-da19fa4eb5e9.png)](https://www.ted.com/talks/graham_hill_less_stuff_more_happiness) + **_Basic_ JavaScript Knowledge**. see: [github.com/dwyl/**Javascript**-the-**Good-Parts**-notes](https://github.com/iteles/Javascript-the-Good-Parts-notes) @@ -99,19 +109,21 @@ If you have **_any_ questions**, ***please ask***:
## _What?_ -![image](https://cloud.githubusercontent.com/assets/194400/25772120/3fa2492c-325b-11e7-9aee-90b059360c14.png) +[![image](https://cloud.githubusercontent.com/assets/194400/25772120/3fa2492c-325b-11e7-9aee-90b059360c14.png)](https://youtu.be/yYCmhHFhopA?t=4s) ### A _Complete Beginner's_ Guide to "MUV" Start with a few definitions: -+ **Model** - or "data model" is the place where all data stored; ++ **Model** - or "data model" is the place where all data is stored; often referred to as the application's `state`. + **Update** - how the app handles `actions` performed -by people and `update` the `state`. +by people and `update`s the `state`, +usually organised as a `switch` with various `case` statements corresponding +to the different "_actions_" the user can take in your App. + **View** - what people using the app can _see_; -a way to `view` the Model (counter) as `HTML` -rendered in a web browser. +a way to `view` the Model (in the case of the first tutorial below, +the counter) as `HTML` rendered in a web browser. ![elm-muv-architecture-diagram](https://cloud.githubusercontent.com/assets/194400/25773775/b6a4b850-327b-11e7-9857-79b6972b49c3.png) @@ -135,7 +147,14 @@ Creative Commons License -If this diagram is not clear (_yet_), again, don't panic, +In the "View Theatre" diagram, the: ++ **`model`** is the ensamble of characters (_or "**puppets**"_) ++ **`update`** is the function that transforms (_"changes"_) the `model` +(_the "**puppeteer**"_). ++ **`view`** what the audience sees through "view port" (_stage_). + + +> If this diagram is not clear (_yet_), again, don't panic, it will all be clarified when you start seeing it in _action_ (_below_)! @@ -160,18 +179,20 @@ When you open `examples/counter-basic/index.html` you should see: Try clicking on the buttons to increase/decrease the counter. -### 3. Edit Some Code! +### 3. Edit Some Code In your Text Editor of choice, edit the _initial value_ of the model -(_e.g: change the initial value from 0 to 9_): +(_e.g: change the initial value from 0 to 9_). +Don't forget to save the file! ![elm-architecture-code-update](https://cloud.githubusercontent.com/assets/194400/25780662/adff6418-3323-11e7-8089-fae4bdc515e8.gif) ### 4. Refresh the Web Browser When you refresh the your Web Browser you will see -that the "_initial state_" is now **9**: +that the "_initial state_" is now **9** +(or whichever number you changed the initial value to): ![update-initial-model-to-9](https://cloud.githubusercontent.com/assets/194400/25780667/c84d0bf4-3323-11e7-929d-2019f5face2c.png) @@ -226,8 +247,8 @@ where your app will be "_mounted to_". In other words your app will be _contained_ within this root element.
(_so make sure it is empty before `mount`ing_) -The first line in `mount` is to get a _reference_ to the root DOM element
-we do this _once_ in the entire application to _minimze_ DOM lookups. +The first line in `mount` is to get a _reference_ to the root DOM element;
+we do this _once_ in the entire application to _minimize_ DOM lookups. #### `mount` > `signal` > `callback` ? @@ -262,7 +283,7 @@ buttons each time it creates a "_chain reaction_" which almost instantly _exceeds_ the "***call stack***" (_i.e. exhausts the allocated memory_) of the browser! -The putting the `callback` in a _closure_ means we can pass a _reference_ +Putting the `callback` in a _closure_ means we can pass a _reference_ to the `signal` (_parent/outer_) function to the `view` function. ##### Further Reading on Closures @@ -277,7 +298,7 @@ or you want _more_ detail/examples, #### 5.1.1 `mount` > render initial view The last line in the `mount` function is to _render_ the `view` function -for the first time passing in the `signal` function, initial model ("state") +for the first time, passing in the `signal` function, initial model ("state") and root element. This is the _initial_ rendering of the UI. @@ -321,7 +342,7 @@ function update(model, action) { // Update function takes the current model } // (default action always returns current) } ``` -However this if the "_handlers_" for each `action` were "_bigger_", +However if the "_handlers_" for each `action` were "_bigger_", we would split them out into their own functions e.g: ```js @@ -337,12 +358,12 @@ function update(model, action) { // Update function takes the current state switch(action) { // and an action (String) runs a switch case Inc: return increment(model); // add 1 to the model case Dec: return decrement(model); // subtract 1 from model - default: return model; // if no action, return curent state. + default: return model; // if no action, return current state. } // (default action always returns current) } ``` This is _functionally_ equivalent to the simpler `update` (_above_)
-But does not offer any _advantage_ at this stage. (_just remember it for later_) +But does not offer any _advantage_ at this stage (_just remember it for later_). ### 5.4 Define the `view` Function @@ -361,8 +382,8 @@ function view(signal, model, root) { ``` The `view` receives three arguments: -+ `signal` defined above in `mount` (_above_) tells each (DOM) element -how to to "handle" the user input. ++ `signal` defined above in `mount` tells each (DOM) element +how to "handle" the user input. + `model` a reference to the _current_ value of the counter. + `root` a reference to the root DOM element where the app is _mounted_. @@ -381,10 +402,10 @@ The `view` creates a _list_ (`Array`) of DOM nodes that need to be rendered. The `view` makes use of three "helper" (_DOM manipulation_) functions: 1. `empty`: empty the `root` element of any "child" nodes. -_Essentially_ `delete` the DOM inside which ever element passed into `empty`. +_Essentially_ `delete` the DOM inside whichever element's passed into `empty`. ```js function empty(node) { - while (node.firstChild) { // while there are stil nodes inside the "parent" + while (node.firstChild) { // while there are still nodes inside the "parent" node.removeChild(node.firstChild); // remove any children recursively } } @@ -406,9 +427,9 @@ function button(buttontext, signal, action) { } ``` -3. `div`: creates a `
` DOM element and apply an `id` to it, +3. `div`: creates a `
` DOM element and applies an `id` to it, then if some `text` was supplied in the _second_ argument, -create a "text node" to display that text. +creates a "text node" to display that text. (_in the case of our counter the `text` is the current value of the model, i.e. the count_) ```js @@ -427,7 +448,7 @@ function div(divid, text) { [`elm-html`](http://package.elm-lang.org/packages/evancz/elm-html/latest/) package, but we have defined them in this counter example so there are **no dependencies** and you can see **exactly** -how everything is "made" from "**first principals**"_ +how everything is "made" from "**first principals**"_. Once you have read through the functions (_and corresponding comments_),
@@ -435,7 +456,7 @@ take a look at the _tests_. > _**Pro Tip**: Writing code is an **iterative** (repetitive) process, **manually refreshing** the web browser each time you update -some code get's **tedious** quite fast, Live Server to the rescue!_ +some code gets **tedious** quite fast, Live Server to the rescue!_ ### 6. (_Optional_) Install "Live Server" for "_Live Reloading_" @@ -444,7 +465,8 @@ e.g. if you are on a computer where you cannot install anything, the examples will still work in your web browser. Live Reloading helps you iterate/work faster because you don't have to
-_manually_ refresh the page each time, simply run the following command: +_manually_ refresh the page each time. +Simply run the following command: ``` npm install && npm start @@ -716,12 +738,27 @@ 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` +Once you have had a go, checkout our solutions: +[`examples/multiple-counters`](https://github.com/dwyl/learn-elm-architecture-in-javascript/tree/master/examples/multiple-counters) +
and corresponding writeup: [**multiple-counters.md**](https://github.com/dwyl/learn-elm-architecture-in-javascript/blob/master/multiple-counters.md) -

+
+ +### 11. Todo List! + +The _ultimate_ test of whether you _learned/understood_ something is
+_applying_ your knowledge to _different_ context from the one you learned in. + +Let's "_turn this up to eleven_" and build something "_useful_"! + +GOTO: +[`todo-list.md`](https://github.com/dwyl/learn-elm-architecture-in-javascript/blob/master/todo-list.md) + +
+ ## Futher/Background Reading @@ -734,7 +771,7 @@ http://stackoverflow.com/questions/18666821/what-does-the-term-reason-about-mean + Elm Architecture with JQuery by @steos: https://medium.com/javascript-inside/elm-architecture-with-jquery-152cb98a62f (_written in JQuery and no Tests so - not great for teaching beginners good habits, but still a v. good post!_) + not ideal for teaching beginners good habits, but still a v. good post!_) + Pure functions: https://en.wikipedia.org/wiki/Pure_function + Higher Order Functions in JavaScript: http://eloquentjavascript.net/05_higher_order.html @@ -742,7 +779,7 @@ http://eloquentjavascript.net/05_higher_order.html https://youtu.be/BMUiFMZr7vk -











+


# tl;dr @@ -764,12 +801,12 @@ the (Core) _Elm **Language**_...
This is a _fair_ assumption given the _ordering_ of the Guide _however_ ... we have a _different_ idea: -### Hypothesis: Learn (& Practice) Elm Architecture `before` Learning Elm? +### Hypothesis: Learn (& Practice) Elm Architecture _`before`_ Learning Elm? We ***hypothesize*** that if we _**explain** the **Elm Architecture**_ (_**in detail**_) using a **language**
people are _**already familiar**_ with (_i.e **JavaScript**_) -`before` diving into the Elm Language
+_`before`_ diving into the Elm Language
it will ["***flatten***"](https://english.stackexchange.com/questions/6212/whats-the-opposite-for-steep-learning-curve) the **learning curve**. @@ -790,7 +827,7 @@ part of any "**client-side**" web app.
(including **Elm**, React and Vue.js) now use a "**Virtual DOM**". > For the purposes of `this` tutorial, and for **most small apps** Virtual DOM is total **overkill**!
-It's akin to putting a Ferrari engine in a gocart!_ +It's akin to putting a **jet engine** in a [**go kart**](https://en.wikipedia.org/wiki/Go-kart)!_ ### What is "_Plain_" JavaScript? @@ -805,13 +842,13 @@ to build something full-featured and easy/fast to read!! [![babel](https://cloud.githubusercontent.com/assets/194400/25772913/72a818f4-326c-11e7-8020-9b5dab715987.png)](https://twitter.com/iamdevloper/status/787969734918668289 "Babel, how to show off that you don't have core ES5 skills.") If you can build with "ES5" JavaScript:
-a) you side-step the +**a)** you side-step the [_noise_](https://twitter.com/iamdevloper/status/610191865216786432) -and focus on core skills that _already_ work everywhere! +and focus on core skills that **_already_ work everywhere**!
(_don't worry you can always "top-up" your -JS knowledge later with ES6, etc!)
-b) you don't need to waste time installing +JS knowledge later with ES6, etc!_)
+**b)** you **don't** need to **waste time** installing [_**Two Hundred Megabytes**_](https://cloud.githubusercontent.com/assets/194400/13321493/39fcfa30-dbc7-11e5-8b05-f046675f9cb6.png) of dependencies just to run a simple project!
-c) You ***save time*** (_for yourself, your team and end-users!_) +**c)** You ***save time*** (_for yourself, your team and end-users!_) because your code is _already_ optimized to run in _any_ browser! diff --git a/elmish.md b/elmish.md new file mode 100644 index 00000000..f2d4f0a3 --- /dev/null +++ b/elmish.md @@ -0,0 +1,2058 @@ +# `Elm`(_ish_) + +![elmlogo-ish](https://user-images.githubusercontent.com/194400/43213139-b70a4c68-902d-11e8-8162-3c7cb56b6360.png) + + +`Elm`(_ish_) is an **`Elm`**-_inspired_ `JavaScript` (**ES5**) +fully functional front-end _micro_-framework from _scratch_.[1](#notes) + +
+ +## _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:
+**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).
+**b)** _demo_ a ***re-useable*** (_fully-tested_) +"**micro-framework**" that allows us +to _practice_ using The Elm Architecture ("TEA").
+**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._ + +
+ +## _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. + +

+ +## _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_).
+If you have any questions or get "stuck", +please open an issue: +https://github.com/dwyl/learn-elm-architecture-in-javascript/issues
+@dwyl is a "safe space" and we are all here to help don't be shy/afraid;
+the _more_ questions you ask, the more you are helping yourself and _others_! + +
+ +## _How_? + +_Before_ diving into _writing functions_ for `Elm`(_ish_), +we need to consider how we are going to _test_ it.
+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.
+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`.
+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,
+(_i.e. they are "**specific**" to the Todo List_) +so we cannot abstract them.
+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.
+ +The `view` function _invokes_ several "helper" functions +which create HTML ("DOM") elements e.g: `
`, `
` & ` +
+ +
  • +
    + + + +
    +
  • + +
    + + +``` + +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.
    +_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: `
    `, `
    ` or `` +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_ `
    ` _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.} 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:
    + +```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, "
    has 'apptastic' class applied"); + t.end(); +}); +``` + +If you (_attempt to_) run this test (_and you **should**_), +you will see something like this: + +![image](https://user-images.githubusercontent.com/194400/43414770-af5ee0e4-942b-11e8-9d1c-1cbab3adc136.png) + +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.} 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: +![image](https://user-images.githubusercontent.com/194400/43416008-ff63d70e-942e-11e8-97ee-6544efb7d43a.png) +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.
    + +If you can, make the test _pass_ +by writing the `add_attributes` function.
    +(_don't forget to_ `export` _the function at the bottom of the file_). + +If you get "stuck", checkout the _complete_ example: +[/examples/todo-list/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)
    +> 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: +![image](https://user-images.githubusercontent.com/194400/43416304-d06339da-942f-11e8-9546-06af9c494a45.png) + + + +
    + +#### Input `placeholder` Attribute + +The `` 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:
    + +```js +test('elmish.add_attributes set placeholder on 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 "); + t.end(); +}); +``` + +_Run_ the test `node test/elmish.test.js`: + +![image](https://user-images.githubusercontent.com/194400/43416801-34e48d2c-9431-11e8-8786-7676f9e3972f.png) + +You _know_ "the drill"; write the necessary code +in the `add_attributes` function of `elmish.js` +to add a `placeholder` to an `` element +and make this test _pass_: + +![image](https://user-images.githubusercontent.com/194400/43416921-8506baaa-9431-11e8-9585-814e704a694d.png) + +If you get "stuck", checkout the _complete_ example: +[/examples/todo-list/elmish.js](https://github.com/dwyl/learn-elm-architecture-in-javascript/tree/master/examples/todo-list/elmish.js) + +
    + +#### `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": + +![image](https://user-images.githubusercontent.com/194400/43436198-2d156248-947b-11e8-8e5a-03f608424fcb.png) + +`@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_. 💔 + +![87% Test Coverage](http://i.imgur.com/NTI4Pxw.png) + +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:
    + +```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, "
    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_).
    +Run the test(s) `node test/elmish.test.js`: +![image](https://user-images.githubusercontent.com/194400/43418987-8c5138f2-9437-11e8-92c5-7c62f1cac2d7.png) + + +So "_why bother_" adding a _test_ if it's _always_ going to _pass_? +**_Two_ reasons**:
    +**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_".
    +**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`:
    + +```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, "
    has not been altered"); + t.end(); +}); +``` + +This test should _also_ pass without the addition of any code: + +![image](https://user-images.githubusercontent.com/194400/43423518-93a8fa74-9444-11e8-97a3-c7e74f71a5f7.png) + +Now the Coverage should be 100% when you run `npm test`: + +![image](https://user-images.githubusercontent.com/194400/43423046-355f3056-9443-11e8-826f-ed61f76dddc0.png) + +In your terminal, type/run the follwoing command: `open coverage/lcov-report/index.html` + +![image](https://user-images.githubusercontent.com/194400/43423103-5ebde1a4-9443-11e8-835b-0dd1ef8a513c.png) + + +#### 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! + +
    + +#### Input `autofocus` + +In order to "_guide_" the person using our Todo List app +to create their _first_ Todo List _item_, +**we want** the `` 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:
    + +```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, + ' 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: ++ `` 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: `` +see: https://stackoverflow.com/questions/4445765/html5-is-it-autofocus-autofocus-or-autofocus + + +#### add `data-id` attribute to `
  • ` + +`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 `
  • ` (_list elements_) +which have the `data-id` attribute (_see above_). + +Add the following test to the `test/elmish.test.js` file:
    + +```js +test('elmish.add_attributes set data-id on
  • 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
  • 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 `