Skip to content

Commit 6fe6364

Browse files
committed
add What? Who? & How? sections to elmish.md for #44
1 parent 208c153 commit 6fe6364

File tree

2 files changed

+272
-242
lines changed

2 files changed

+272
-242
lines changed

Diff for: elmish.md

+267-5
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,287 @@ into a "micro framework" is to: <br />
1717
**a)** ***simplify*** the Todo List application code
1818
to just "business logic". <br />
1919
**b)** _demonstrate_ a ***re-useable*** (_fully-tested_)
20-
"**micro-framework**" that allows us to _practice_ the Elm Architecture.<br />
20+
"**micro-framework**" that allows us to _practice_ The Elm Architecture ("TEA").<br />
2121
**c)** promote the **mindset** of writing **tests _first_**
2222
and **`then`** the _least_ amount of code necessary to pass the test
2323
(_while meeting the acceptance criteria_).
2424

25+
> _Test & Document-Driven Development is **easy** and it's **easily**
26+
one of the **best habits** to form in your software development career.
27+
This walkthrough shows **how** you can do it **the right way**
28+
from the **start** of a project._
29+
30+
31+
## _What?_
32+
33+
A walkthrough of creating a
34+
_fully functional front-end_ "**micro framework**" ***from scratch***.
35+
2536
By the end of this exercise you will _understand_
2637
The Elm Architecture (TEA) _much better_
2738
because we will be analysing, documenting, testing
2839
and writing each function required
29-
to build the _fully functional_ "**micro framework**" ***from scratch***.
40+
to architect and render the our Todo List (TodoMVC) App.
3041

42+
## _Who?_
3143

32-
## _What?_
44+
People who wants to gain an _in-depth_ understanding
45+
of The Elm Architecture ("TEA")
46+
and thus _intrinsically_ grok Redux/React JavaScript apps.
47+
48+
This tutorial is intended for _beginners_ who have _modest_
49+
JavaScript knowledge (_variables, functions, DOM methods and TDD_),
50+
if you have any questions or get "stuck",
51+
please open an issue:
52+
https://github.com/dwyl/learn-elm-architecture-in-javascript/issues <br />
53+
@dwyl is a "safe space" and we are all here to help don't be shy/afraid.
54+
55+
56+
## _How_?
57+
58+
_Before_ diving into _writing functions_ for `Elm`(_ish_),
59+
we need to consider how we are going to _test_ it.
60+
By ensuring that we follow **TDD** from the _start_ of an App,
61+
we _avoid_ having to "correct" any "**bad habits**".
62+
63+
We will be using **Tape** and **JSDOM** for testing the functions.
64+
If you are `new` to _either_ of these tools,
65+
please see:
66+
[https://github.com/dwyl/**learn-tape**](https://github.com/dwyl/learn-tape)
67+
and
68+
[front-end-with-tape.md](https://github.com/dwyl/learn-tape/blob/master/front-end-with-tape.md)
69+
70+
It's "OK" to ask: "_Where do I **start** (my **TDD** quest)?_" <br />
71+
The answer is: create **two** new files:
72+
`examples/todo-list/elmish.js` and `test/elmish.test.js`
73+
74+
We will create a couple of tests and their corresponding functions _next_!
75+
76+
Our **first step** is to _abstract_ and _generalise_
77+
the Elm Architecture (`mount`) and HTML ("DOM") functions
78+
we used in the "counter" example.
79+
80+
Recall that there are **3 parts** to "TEA": `model`, `update` and `view`. <br />
81+
These correspond to the `M`odel, `C`ontroller and `V`iew of "**MVC**".
82+
The _reason_ Elm refers to the "Controller" as "Update" is because
83+
this name _more accurately_ reflects what the function _does_:
84+
it updates the _state_ of the application.
85+
86+
Our `update` and `view` functions form
87+
the "business logic" of our Todo List App,
88+
so we cannot abstract these. <br />
89+
The `update` function is a simple `switch` statement
90+
that "decides" how to to _update_ the app's `model`
91+
each `case` is functionality that is _specific_ to the Todo List App. <br />
92+
The `view` function _invokes_ several "helper" functions
93+
which create HTML elements e.g: `div` & `<button>`;
94+
these _can_ (_will_) be generalised (_below_).
95+
96+
Let's kick-off with a couple of "_familiar_" _generic_ functions:
97+
`empty` and `mount`.
98+
99+
100+
101+
102+
#### `empty` the DOM
103+
104+
Given that we _know_ we are going to use the `empty`
105+
function we used previously in our `counter`,
106+
`counter-reset` and `multiple-counters` examples (_in the "basic" TEA tutorial_)
107+
we can write a _test_ for the `empty` function quite easily.
108+
109+
In the `test/elmish.test.js` file, type the following code:
110+
```js
111+
const test = require('tape'); // https://github.com/dwyl/learn-tape
112+
const fs = require('fs'); // read html files (see below)
113+
const path = require('path'); // so we can open files cross-platform
114+
const elmish = require('../examples/todo-list/elmish.js');
115+
const html = fs.readFileSync(path.resolve(__dirname,
116+
'../examples/todo-list/index.html'));
117+
require('jsdom-global')(html); // https://github.com/rstacruz/jsdom-global
118+
elmish.init(document); // pass JSDOM into elmish for DOM functions
119+
const id = 'test-app'; // all tests use separate root element
120+
121+
test('empty("root") removes DOM elements from container', function (t) {
122+
// setup the test div:
123+
const text = 'Hello World!'
124+
const root = document.getElementById(id);
125+
const div = document.createElement('div');
126+
div.id = 'mydiv';
127+
const txt = document.createTextNode(text);
128+
div.appendChild(txt);
129+
root.appendChild(div);
130+
// check text of the div:
131+
const actual = document.getElementById('mydiv').textContent;
132+
t.equal(actual, text, "Contents of mydiv is: " + actual + ' == ' + text);
133+
t.equal(root.childElementCount, 1, "Root element " + id + " has 1 child el");
134+
// empty the root DOM node:
135+
elmish.empty(root); // exercise the `empty` function!
136+
t.equal(root.childElementCount, 0, "After empty(root) has 0 child elements!")
137+
t.end();
138+
});
139+
```
140+
141+
> _**Note**: if any line in this file is **unfamiliar** to you,
142+
please **first** go back over the previous example(s)_:
143+
`counter-basic` _and_ `counter-reset`,
144+
_**then** do bit of "googling" for any words or functions you don't recognise
145+
e.g: `childElementCount`,
146+
and if you are **still** "**stuck**"_,
147+
[***please open an
148+
issue***!](https://github.com/dwyl/learn-elm-architecture-in-javascript/issues)
149+
_It's **essential** that you **understand** each **character**
150+
in the code **before** continuing to **avoid** "**confusion**" later._
151+
152+
Now that we have the **test** for our `empty` function written,
153+
we can add the `empty` function to `examples/todo-list/elmish.js`:
154+
```js
155+
/**
156+
* `empty` the contents of a given DOM element "node" (before re-rendering).
157+
* This is the *fastest* way according to: stackoverflow.com/a/3955238/1148249
158+
* @param {Object} node the exact DOM node you want to empty
159+
* @example
160+
* // returns true (once the 'app' node is emptied)
161+
* const node = document.getElementById('app');
162+
* empty(node);
163+
*/
164+
function empty(node) {
165+
while (node.lastChild) {
166+
node.removeChild(node.lastChild);
167+
}
168+
}
169+
```
170+
171+
If the **comment syntax**
172+
above the function definition
173+
is _unfamiliar_,
174+
please see:
175+
[https://github.com/dwyl/**learn-jsdoc**](https://github.com/dwyl/learn-jsdoc)
176+
177+
178+
### `mount` the App
179+
180+
The `mount` function is the "glue" or "wiring" function that
181+
connects the `model`, `update` and `view`; we _can_ _generalise_ it.
182+
183+
In the `test/elmish.test.js` file, type the following code:
184+
```js
185+
// use view and update from counter-reset example
186+
// to invoke elmish.mount() function and confirm it is generic!
187+
const { view, update } = require('../examples/counter-reset/counter.js');
188+
189+
test('elmish.mount app expect state to be Zero', function (t) {
190+
const root = document.getElementById(id);
191+
elmish.mount(7, update, view, id);
192+
const actual = document.getElementById(id).textContent;
193+
const actual_stripped = parseInt(actual.replace('+', '')
194+
.replace('-Reset', ''), 10);
195+
const expected = 7;
196+
t.equal(expected, actual_stripped, "Inital state set to 7.");
197+
// reset to zero:
198+
const btn = root.getElementsByClassName("reset")[0]; // click reset button
199+
btn.click(); // Click the Reset button!
200+
const state = parseInt(root.getElementsByClassName('count')[0]
201+
.textContent, 10);
202+
t.equal(state, 0, "State is 0 (Zero) after reset."); // state reset to 0!
203+
elmish.empty(root); // clean up after tests
204+
t.end()
205+
});
206+
```
207+
> _**Note**: we have "**borrowed**" this test from our previous example.
208+
see:_ `test/counter-reset.test.js`
209+
210+
The corresponding code with JSDOC for the `mount` function
211+
in `examples/todo-list/elmish.js` is:
212+
```js
213+
/**
214+
* `mount` mounts the app in the "root" DOM Element.
215+
* @param {Object} model store of the application's state.
216+
* @param {Function} update how the application state is updated ("controller")
217+
* @param {Function} view function that renders HTML/DOM elements with model.
218+
* @param {String} root_element_id root DOM element in which the app is mounted
219+
*/
220+
function mount(model, update, view, root_element_id) {
221+
var root = document.getElementById(root_element_id); // root DOM element
222+
function signal(action) { // signal function takes action
223+
return function callback() { // and returns callback
224+
var updatedModel = update(model, action); // update model for the action
225+
empty(root); // clear root el before rerender
226+
view(signal, updatedModel, root); // subsequent re-rendering
227+
};
228+
};
229+
view(signal, model, root); // render initial model (once)
230+
}
231+
```
232+
233+
### `module.exports`
234+
235+
In order to test the `elmish` functions we need to `export` them.
236+
Additionally, because we are using JSDOM
237+
to test our front-end functions using `tape`,
238+
we need an `init` function to pass in the JSDOM `document`.
239+
add the following lines to `examples/todo-list/elmish.js`:
240+
241+
```js
242+
/**
243+
* init initialises the document (Global) variable for DOM operations.
244+
* @param {Object} doc window.document in browser and JSDOM.document in tests.
245+
* @return {Object} document returns whatever is passed in.
246+
*/
247+
function init(doc){
248+
document = doc; // this is used for instantiating JSDOM for testing.
249+
return document;
250+
}
251+
252+
/* module.exports is needed to run the functions using Node.js for testing! */
253+
/* istanbul ignore next */
254+
if (typeof module !== 'undefined' && module.exports) {
255+
module.exports = {
256+
empty: empty,
257+
mount: mount,
258+
init: init
259+
}
260+
} else { init(document); }
261+
```
262+
263+
Now that we have started creating the `elmish` generic functions,
264+
we need to know which _other_ functions we need. <br />
265+
Let's take a look at the TodoMVC App to "_analyse_ the requirements".
266+
267+
### _Analyse_ the TodoMVC App to "Gather Requirements"
268+
269+
Our _next_ step is to _analyse_ the required functionality of a Todo List.
270+
271+
### _Recommended_ Background Reading: TodoMVC "_Vanilla_" JS
272+
273+
By _far_ the best place to start for _understanding_ TodoMVC's layout/format,
274+
is the "Vanilla" JavaScript (_no "framework"_) implementation:
275+
https://github.com/tastejs/todomvc/tree/gh-pages/examples/vanillajs
276+
277+
Run it locally with:
278+
```
279+
git clone https://github.com/tastejs/todomvc.git
280+
cd todomvc/examples/vanillajs
281+
python -m SimpleHTTPServer 8000
282+
```
283+
Open your web browser to: http://localhost:8000
284+
285+
![vanillajs-localhost](https://user-images.githubusercontent.com/194400/42632838-6e68c20c-85d6-11e8-8ae4-d688f5977704.png)
286+
287+
> If you are unable to run the TodoMVC locally, you can always view it online:
288+
http://todomvc.com/examples/vanillajs
33289

290+
_Play_ with the app by adding a few items,
291+
checking-off and toggling the views in the footer.
34292

293+
> _**Note**: IMO the "**Vanilla**" **JS** implementation
294+
is quite complex and insufficiently documented_
295+
(_very few code comments and sparse_
296+
[`README.md`](https://github.com/tastejs/todomvc/tree/25a9e31eb32db752d959df18e4d214295a2875e8/examples/vanillajs)),
297+
_so don't expect to **understand** it all the first time without study._
298+
_Don't worry, we will walk through building each feature in detail._
35299

36300

37-
You are already _familiar_ with the first few functions
38-
`mount` and `empty`
39301

40302
## Notes
41303

0 commit comments

Comments
 (0)