@@ -17,25 +17,287 @@ into a "micro framework" is to: <br />
17
17
** a)** *** simplify*** the Todo List application code
18
18
to just "business logic". <br />
19
19
** 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 />
21
21
** c)** promote the ** mindset** of writing ** tests _ first_ **
22
22
and ** ` then ` ** the _ least_ amount of code necessary to pass the test
23
23
(_ while meeting the acceptance criteria_ ).
24
24
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
+
25
36
By the end of this exercise you will _ understand_
26
37
The Elm Architecture (TEA) _ much better_
27
38
because we will be analysing, documenting, testing
28
39
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 .
30
41
42
+ ## _ Who?_
31
43
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
33
289
290
+ _ Play_ with the app by adding a few items,
291
+ checking-off and toggling the views in the footer.
34
292
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._
35
299
36
300
37
- You are already _ familiar_ with the first few functions
38
- ` mount ` and ` empty `
39
301
40
302
## Notes
41
303
0 commit comments