@@ -17,25 +17,287 @@ into a "micro framework" is to: <br />
1717** a)** *** simplify*** the Todo List application code
1818to 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_ **
2222and ** ` 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+
2536By the end of this exercise you will _ understand_
2637The Elm Architecture (TEA) _ much better_
2738because we will be analysing, documenting, testing
2839and 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