├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── elmish.md ├── index.html ├── lib ├── elmish.js ├── favicon.ico ├── server.js ├── test.js ├── todo-app.js ├── todo.html ├── todomvc-app.css └── todomvc-common-base.css ├── package-lock.json ├── package.json └── test ├── counter-reset-keyboard.js ├── counter.js ├── elmish.test.js └── todo-app.test.js /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | time: "17:00" 8 | timezone: Europe/London 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [22.x, 24.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | # - run: npm run build --if-present 31 | - run: npm test 32 | - name: Upload coverage to Codecov 33 | uses: codecov/codecov-action@v5 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # Bower dependency directory (https://bower.io/) 25 | bower_components 26 | 27 | # node-waf configuration 28 | .lock-wscript 29 | 30 | # Compiled binary addons (https://nodejs.org/api/addons.html) 31 | build/Release 32 | 33 | # Dependency directories 34 | node_modules/ 35 | jspm_packages/ 36 | 37 | # TypeScript v1 declaration files 38 | typings/ 39 | 40 | # Optional npm cache directory 41 | .npm 42 | 43 | # Optional eslint cache 44 | .eslintcache 45 | 46 | # Optional REPL history 47 | .node_repl_history 48 | 49 | # Output of 'npm pack' 50 | *.tgz 51 | 52 | # dotenv environment variables file 53 | .env 54 | 55 | # next.js build output 56 | .next 57 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | _**Please read** our_ [**contribution guide**](https://github.com/dwyl/contributing) (_thank you_!) 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | <<<<<<< HEAD 294 | 295 | Copyright (C) 296 | ======= 297 | {description} 298 | Copyright (C) {year} {fullname} 299 | >>>>>>> project-a/master 300 | 301 | This program is free software; you can redistribute it and/or modify 302 | it under the terms of the GNU General Public License as published by 303 | the Free Software Foundation; either version 2 of the License, or 304 | (at your option) any later version. 305 | 306 | This program is distributed in the hope that it will be useful, 307 | but WITHOUT ANY WARRANTY; without even the implied warranty of 308 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 309 | GNU General Public License for more details. 310 | 311 | You should have received a copy of the GNU General Public License along 312 | with this program; if not, write to the Free Software Foundation, Inc., 313 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 314 | 315 | Also add information on how to contact you by electronic and paper mail. 316 | 317 | If the program is interactive, make it output a short notice like this 318 | when it starts in an interactive mode: 319 | 320 | Gnomovision version 69, Copyright (C) year name of author 321 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 322 | This is free software, and you are welcome to redistribute it 323 | under certain conditions; type `show c' for details. 324 | 325 | The hypothetical commands `show w' and `show c' should show the appropriate 326 | parts of the General Public License. Of course, the commands you use may 327 | be called something other than `show w' and `show c'; they could even be 328 | mouse-clicks or menu items--whatever suits your program. 329 | 330 | You should also get your employer (if you work as a programmer) or your 331 | school, if any, to sign a "copyright disclaimer" for the program, if 332 | necessary. Here is a sample; alter the names: 333 | 334 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 335 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 336 | 337 | <<<<<<< HEAD 338 | , 1 April 1989 339 | ======= 340 | {signature of Ty Coon}, 1 April 1989 341 | >>>>>>> project-a/master 342 | Ty Coon, President of Vice 343 | 344 | This General Public License does not permit incorporating your program into 345 | proprietary programs. If your program is a subroutine library, you may 346 | consider it more useful to permit linking proprietary applications with the 347 | library. If this is what you want to do, use the GNU Lesser General 348 | Public License instead of this License. 349 | -------------------------------------------------------------------------------- /elmish.md: -------------------------------------------------------------------------------- 1 | # `Elm`(_ish_) 2 | 3 | ![elmlogo-ish](https://user-images.githubusercontent.com/194400/43213139-b70a4c68-902d-11e8-8162-3c7cb56b6360.png) 4 | 6 | 7 | `Elm`(_ish_) is an **`Elm`**-_inspired_ `JavaScript` (**ES5**) 8 | fully functional front-end _micro_-framework from _scratch_.[1](#notes) 9 | 10 |
11 | 12 | ## _Why?_ 13 | 14 | The purpose of building `Elm`(_ish_) is _not_ to "_replace_" Elm 15 | or to create [_yet another_ front-end JS framework](https://medium.com/tastejs-blog/yet-another-framework-syndrome-yafs-cf5f694ee070)! 16 | 17 | The purpose of _separating_ the `Elm`(_ish_) functions 18 | into a "micro framework" is to:
19 | **a)** **abstract** the "plumbing" so that we can 20 | ***simplify*** the Todo List application code 21 | to _just_ 22 | ["**application logic**"](https://en.wikipedia.org/wiki/Business_logic).
23 | **b)** _demo_ a ***re-useable*** (_fully-tested_) 24 | "**micro-framework**" that allows us 25 | to _practice_ using The Elm Architecture ("TEA").
26 | **c)** promote the **mindset** of writing **tests _first_** 27 | and **`then`** the _least_ amount of code necessary to pass the test 28 | (_while meeting the acceptance criteria_). 29 | 30 | > _**Test** & **Document-Driven Development** is **easy** and it's **easily** 31 | one of the **best habits** to form in your software development "career". 32 | This walkthrough shows **how** you can do it **the right way**; 33 | from the **start** of a project._ 34 | 35 |
36 | 37 | ## _What?_ 38 | 39 | A walkthrough of creating a 40 | _fully functional front-end_ "**micro framework**" ***from scratch***. 41 | 42 | By the end of this exercise you will _understand_ 43 | The Elm Architecture (TEA) _much better_ 44 | because we will be analysing, documenting, testing 45 | and writing each function required 46 | to architect and render our Todo List (TodoMVC) App. 47 | 48 |

49 | 50 | ## _Who?_ 51 | 52 | People who want to gain an _in-depth_ understanding 53 | of The Elm Architecture ("TEA") 54 | and thus _intrinsically_ 55 | [grok](https://en.wikipedia.org/wiki/Grok) Redux/React JavaScript apps. 56 | 57 | This tutorial is intended for _beginners_ with _modest_ 58 | JavaScript knowledge (_variables, functions, DOM methods & TDD_).
59 | If you have any questions or get "stuck", 60 | please open an issue: 61 | https://github.com/dwyl/learn-elm-architecture-in-javascript/issues
62 | @dwyl is a "safe space" and we are all here to help don't be shy/afraid;
63 | the _more_ questions you ask, the more you are helping yourself and _others_! 64 | 65 |
66 | 67 | ## _How_? 68 | 69 | _Before_ diving into _writing functions_ for `Elm`(_ish_), 70 | we need to consider how we are going to _test_ it.
71 | By ensuring that we follow **TDD** from the _start_ of an project, 72 | we _avoid_ having to "correct" any "**bad habits**" later. 73 | 74 | We will be using **Tape** & **`JSDOM`** for testing the functions. 75 | Tape is a _minimalist_ testing library 76 | that is _fast_ and has _everything we need_. 77 | **`JSDOM`** is a JavaScript implementation of the 78 | WHATWG DOM & HTML standards, for use with node.js.
79 | If _either_ of these tools is _unfamiliar_ to you, 80 | please see: 81 | [https://github.com/dwyl/**learn-tape**](https://github.com/dwyl/learn-tape) 82 | and 83 | [**front-end**-with-tape.md](https://github.com/dwyl/learn-tape/blob/master/front-end-with-tape.md) 84 | 85 | 86 | ### What _Can_ We _Generalise_ ? 87 | 88 | Our **first step** in creating `Elm`(_ish_) 89 | is to _re-visit_ the functions we wrote for the "counter app" 90 | and consider what _can_ be _generalised_ into 91 | an application-independent re-useable framework. 92 | 93 | > Our **rule-of-thumb** is: anything that creates (_or destroys_) 94 | a DOM element or looks like "plumbing" 95 | (_that which is common to **all apps**, e.g: "routing" or "managing state"_) 96 | is _generic_ and should thus be abstracted into the `Elm`(_ish_) framework. 97 | 98 | 99 | Recall that there are **3 parts** to the Elm Architecture: 100 | `model`, `update` and `view`.
101 | These correspond to the `M`odel, `C`ontroller and `V`iew 102 | of 103 | ["**MVC** pattern"](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller), 104 | which is the most _widely used_ "software architecture pattern". 105 | 106 | > **Aside**: "**software architecture**" is just a fancy way of saying 107 | "how code is **organised**" and/or how "data **flows**" through a system. 108 | Whenever you see the word "**pattern**" it just means 109 | "a bunch of experienced people have concluded that this works well, 110 | so as beginners, we don't have to think too hard (up-front)." 111 | 112 | The _reason_ Elm refers to the "**Controller**" as "***Update***" is because 113 | this name _more accurately_ reflects what the function _does_: 114 | it _updates_ the _state_ (Model) of the application. 115 | 116 | Our `update` and `view` functions will form 117 | the "**domain logic**" of our Todo List App,
118 | (_i.e. they are "**specific**" to the Todo List_) 119 | so we cannot abstract them.
120 | The `model` will be a JavaScript `Object` where the App's 121 | data (todo list items) will be stored. 122 | 123 | The `update` function is a simple `switch` statement 124 | that "decides" how to to _`update`_ the app's `model` 125 | each `case` will call a function 126 | that _belongs_ to the Todo List App.
127 | 128 | The `view` function _invokes_ several "helper" functions 129 | which create HTML ("DOM") elements e.g: `
`, `
` & ` 523 |
524 | 525 |
  • 526 |
    527 | 528 | 529 | 531 |
    532 |
  • 533 | 534 |
    535 |
    536 | 1 item left 537 | 548 | 549 |
    550 | 551 | ``` 552 | 553 | Let's split each one of these elements into it's own `function` 554 | (_with any necessary "helpers"_) in the order they appear. 555 | 556 | > For a "checklist" of these features see: https://github.com/dwyl/learn-elm-architecture-in-javascript/issues/44 557 | 558 | When building a House we don't think "build house" as our _first_ action.
    559 | _Instead_ we think: what are the "foundations" that need to be in place 560 | ***before*** we lay the _first_ "brick"? 561 | 562 | In our Todo List App we need a few "Helper Functions" 563 | before we start building the App. 564 | 565 | ### HTML / DOM Creation Generic Helper Functions 566 | 567 | All "grouping" or "container" HTML elements 568 | e.g: `
    `, `
    ` or `` 569 | will be called with ***two arguments***: 570 | e.g: `var sec = section(attributes, childnodes)` 571 | + `attributes` - a list (Array) of HTML attributes/properties 572 | e.g: `id` or `class`. 573 | + `childnodes` - a list (Array) of child HTML elements 574 | (_nested within the_ `
    ` _element_) 575 | 576 | Each of these function arguments will be "_applied_" to the HTML element. 577 | We therefore need a pair of "helper" functions (_one for each argument_). 578 | 579 | 580 | ### `add_attributes` 581 | 582 | The `JSDOC` comment for our `add_attributes` function is: 583 | ```js 584 | /** 585 | * add_attributes applies the desired attributes to the desired node. 586 | * Note: this function is "impure" because it "mutates" the node. 587 | * however it is idempotent; the "side effect" is only applied once 588 | * and no other nodes in the DOM are "affected" (undesirably). 589 | * @param {Array.} attrlist list of attributes to be applied to the node 590 | * @param {Object} node DOM node upon which attribute(s) should be applied 591 | * @example 592 | * // returns node with attributes applied 593 | * div = add_attributes(["class=item", "id=mydiv", "active=true"], div); 594 | */ 595 | ``` 596 | This should give you a _good idea_ of what code needs to be written. 597 | 598 | But let's write the _test_ first! 599 | Add the following test to the `test/elmish.test.js` file:
    600 | 601 | ```js 602 | test('elmish.add_attributes applies class HTML attribute to a node', function (t) { 603 | const root = document.getElementById(id); 604 | let div = document.createElement('div'); 605 | div.id = 'divid'; 606 | div = elmish.add_attributes(["class=apptastic"], div); 607 | root.appendChild(div); 608 | // test the div has the desired class: 609 | const nodes = document.getElementsByClassName('apptastic'); 610 | t.equal(nodes.length, 1, "
    has 'apptastic' class applied"); 611 | t.end(); 612 | }); 613 | ``` 614 | 615 | If you (_attempt to_) run this test (_and you **should**_), 616 | you will see something like this: 617 | 618 | ![image](https://user-images.githubusercontent.com/194400/43414770-af5ee0e4-942b-11e8-9d1c-1cbab3adc136.png) 619 | 620 | Test is failing because the `elmish.add_attributes` function does not _exist_. 621 | 622 | Go ahead and _create_ the `elmish.add_attributes` function 623 | (_just the function without passing the test_) and _export_ it in `elmish.js`: 624 | ```js 625 | /** 626 | * add_attributes applies the desired attributes to the desired node. 627 | * Note: this function is "impure" because it "mutates" the node. 628 | * however it is idempotent; the "side effect" is only applied once 629 | * and no other nodes in the DOM are "affected" (undesirably). 630 | * @param {Array.} attrlist list of attributes to be applied to the node 631 | * @param {Object} node DOM node upon which attribute(s) should be applied 632 | * @example 633 | * // returns node with attributes applied 634 | * div = add_attributes(["class=item", "id=mydiv", "active=true"], div); 635 | */ 636 | function add_attributes (attrlist, node) { 637 | if(attrlist && attrlist.length) { 638 | attrlist.forEach(function (attr) { // apply each prop in array 639 | var a = attr.split('='); 640 | switch(a[0]) { 641 | // code to make test pass goes here ... 642 | default: 643 | break; 644 | } 645 | }); 646 | } 647 | return node; 648 | } 649 | // ... at the end of the file, "export" the add_attributes funciton: 650 | if (typeof module !== 'undefined' && module.exports) { 651 | module.exports = { 652 | add_attributes: add_attributes, // export the function so we can test it! 653 | empty: empty, 654 | mount: mount 655 | } 656 | } 657 | ``` 658 | 659 | When you re-run the test you will see something like this: 660 | ![image](https://user-images.githubusercontent.com/194400/43416008-ff63d70e-942e-11e8-97ee-6544efb7d43a.png) 661 | The function _exists_ but it does not make the tests pass. 662 | Your _quest_ is to turn this **`0`** into a **`1`**. 663 | 664 | Given the **`JSDOC`** comment and _test_ above, 665 | take a moment to think of how _you_ would write 666 | the `add_attributes` function to apply a CSS `class` to an element.
    667 | 668 | If you can, make the test _pass_ 669 | by writing the `add_attributes` function.
    670 | (_don't forget to_ `export` _the function at the bottom of the file_). 671 | 672 | If you get "stuck", checkout the _complete_ example: 673 | [/lib/elmish.js](https://github.com/dwyl/learn-elm-architecture-in-javascript/tree/master/examples/todo-list/elmish.js) 674 | 675 | > **Note 0**: we have "_seen_" the code _before_ in the `counter` example: 676 | > [counter.js#L51](https://github.com/dwyl/learn-elm-architecture-in-javascript/blob/814467e81b1b9739da74378455bd12721b096ebd/examples/counter-reset/counter.js#L51)
    677 | > The _difference_ is this time we want it to be "generic"; 678 | we want to apply a CSS `class` to _any_ DOM node. 679 | 680 | > **Note 1**: it's not "cheating" to look at "the solution", 681 | the whole point of having a step-by-step tutorial 682 | is that you can check if you get "stuck", 683 | but you should only check _after_ making 684 | a good attempt to write the code _yourself_. 685 | 686 | > **Note 2**: The `add_attributes` function is "impure" as it "mutates" 687 | the target DOM `node`, this is more of a "fact of life" in JavaScript, 688 | and given that the application of attributes 689 | to DOM node(s) is idempotent we aren't "concerned" with "side effects"; 690 | the attribute will only be applied _once_ to the node 691 | regardless of how many times the `add_attributes` function is called. 692 | see: https://en.wikipedia.org/wiki/Idempotence 693 | 694 | For reference, the Elm HTML Attributes function on Elm package is: 695 | https://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Attributes 696 | 697 | Once you make the test _pass_ you _should_ see the following in your Terminal: 698 | ![image](https://user-images.githubusercontent.com/194400/43416304-d06339da-942f-11e8-9546-06af9c494a45.png) 699 | 700 | 701 | 702 |
    703 | 704 | #### Input `placeholder` Attribute 705 | 706 | The `` form element (_where we create new Todo List items_) 707 | has a helpful `placeholder` attribute _prompting_ us with a question: 708 | "_What needs to be done?_" 709 | 710 | Add the following test to the `test/elmish.test.js` file:
    711 | 712 | ```js 713 | test('elmish.add_attributes set placeholder on element', function (t) { 714 | const root = document.getElementById(id); 715 | let input = document.createElement('input'); 716 | input.id = 'new-todo'; 717 | input = elmish.add_attributes(["placeholder=What needs to be done?"], input); 718 | root.appendChild(input); 719 | const placeholder = document.getElementById('new-todo') 720 | .getAttribute("placeholder"); 721 | t.equal(placeholder, "What needs to be done?", "paceholder set on "); 722 | t.end(); 723 | }); 724 | ``` 725 | 726 | _Run_ the test `node test/elmish.test.js`: 727 | 728 | ![image](https://user-images.githubusercontent.com/194400/43416801-34e48d2c-9431-11e8-8786-7676f9e3972f.png) 729 | 730 | You _know_ "the drill"; write the necessary code 731 | in the `add_attributes` function of `elmish.js` 732 | to add a `placeholder` to an `` element 733 | and make this test _pass_: 734 | 735 | ![image](https://user-images.githubusercontent.com/194400/43416921-8506baaa-9431-11e8-9585-814e704a694d.png) 736 | 737 | If you get "stuck", checkout the _complete_ example: 738 | [/lib/elmish.js](https://github.com/dwyl/learn-elm-architecture-in-javascript/tree/master/examples/todo-list/elmish.js) 739 | 740 |
    741 | 742 | #### `default` `case` ("branch") test? 743 | 744 | At this point in our `Elm`(_ish_) quest, 745 | all our tests are _passing_, 746 | which is good, 747 | however that is not the "full picture" ... 748 | 749 | If you use Istanbul to check the "test coverage" 750 | (_the measure of which lines/branches of code are being executed during tests_), 751 | you will see that only **98.5%** of lines of code is being "covered": 752 | 753 | ![image](https://user-images.githubusercontent.com/194400/43436198-2d156248-947b-11e8-8e5a-03f608424fcb.png) 754 | 755 | `@dwyl` we are "_keen_" on having "**100% Test Coverage**" ... 756 | anything less than **100%** is _guaranteed_ to result in "regressions", 757 | disappointment and a _lonely loveless life_. 💔 758 | 759 | ![87% Test Coverage](https://i.imgur.com/NTI4Pxw.png) 760 | 761 | See: 762 | [https://github.com/dwyl/**learn-istanbul**](https://github.com/dwyl/learn-istanbul) 763 | 764 | This means that if we have a `switch` statement 765 | as in the case of the `add_attributes` function we need to add a ***test***, 766 | that "_exercises_" that "branch" of the code. 767 | Add the following test code to your `test/elmish.test.js` file:
    768 | 769 | ```js 770 | /** DEFAULT BRANCH Test **/ 771 | test('test default case of elmish.add_attributes (no effect)', function (t) { 772 | const root = document.getElementById(id); 773 | let div = document.createElement('div'); 774 | div.id = 'divid'; 775 | // "Clone" the div DOM node before invoking elmish.attributes to compare 776 | const clone = div.cloneNode(true); 777 | div = elmish.add_attributes(["unrecognised_attribute=noise"], div); 778 | t.deepEqual(div, clone, "
    has not been altered"); 779 | t.end(); 780 | }); 781 | ``` 782 | 783 | By _definition_ this test will _pass_ without adding any additional code 784 | because we _already_ added the `default: break;` lines above 785 | (_which is "good practice" in `switch` statements_).
    786 | Run the test(s) `node test/elmish.test.js`: 787 | ![image](https://user-images.githubusercontent.com/194400/43418987-8c5138f2-9437-11e8-92c5-7c62f1cac2d7.png) 788 | 789 | 790 | So "_why bother_" adding a _test_ if it's _always_ going to _pass_? 791 | **_Two_ reasons**:
    792 | **First**: It _won't_ "_always pass_". 793 | if someone decides to _remove_ the "default" `case` 794 | from `add_attributes` function (_people do "strange things" all the time!_) 795 | it will _fail_ so by having a test, 796 | we will _know_ that the `switch` is "_incomplete_".
    797 | **Second**: Having "full coverage" of our code from the _start_ of the project, 798 | and not having to"debate" or "discuss" the "merits" of it means 799 | we can have _confidence_ in the code. 800 | 801 | #### Test `null` Attribute Argument (`attrlist`) in `add_attributes` Function 802 | 803 | Since JavaScript is _not_ statically/strictly typed we need to _consider_ 804 | the situation where someone might _accidentally_ pass a `null` value. 805 | 806 | Thankfully, this is _easy_ to write a test for. 807 | Add the following test to `test/elmish.test.js`:
    808 | 809 | ```js 810 | test('test elmish.add_attributes attrlist null (no effect)', function (t) { 811 | const root = document.getElementById(id); 812 | let div = document.createElement('div'); 813 | div.id = 'divid'; 814 | // "Clone" the div DOM node before invoking elmish.attributes to compare 815 | const clone = div.cloneNode(true); 816 | div = elmish.add_attributes(null, div); // should not "explode" 817 | t.deepEqual(div, clone, "
    has not been altered"); 818 | t.end(); 819 | }); 820 | ``` 821 | 822 | This test should _also_ pass without the addition of any code: 823 | 824 | ![image](https://user-images.githubusercontent.com/194400/43423518-93a8fa74-9444-11e8-97a3-c7e74f71a5f7.png) 825 | 826 | Now the Coverage should be 100% when you run `npm test`: 827 | 828 | ![image](https://user-images.githubusercontent.com/194400/43423046-355f3056-9443-11e8-826f-ed61f76dddc0.png) 829 | 830 | In your terminal, type/run the follwoing command: `open coverage/lcov-report/index.html` 831 | 832 | ![image](https://user-images.githubusercontent.com/194400/43423103-5ebde1a4-9443-11e8-835b-0dd1ef8a513c.png) 833 | 834 | 835 | #### Check-Coverage Pre-Commit Hook 836 | 837 | Once you _achieve_ 100% test coverage, 838 | there is no _reason_ to "compromise" 839 | by going _below_ this level. 840 | Let's add a `pre-commit` check 841 | to make sure we maintain our desired standard. 842 | 843 | > We wrote a detailed guide to git pre-commit hooks with npm: 844 | [https://github.com/dwyl/learn-**pre-commit**]https://github.com/dwyl/learn-pre-commit 845 | 846 | Install the `pre-commit` module: 847 | 848 | ```sh 849 | npm install pre-commit istanbul --save-dev 850 | ``` 851 | 852 | In your `package.json` file add: 853 | 854 | ```js 855 | { 856 | "scripts": { 857 | "check-coverage": "istanbul check-coverage --statements 100 --functions 100 --lines 100 --branches 100", 858 | "test": "istanbul cover tape ./test/*.test.js | tap-spec" 859 | }, 860 | "pre-commit": [ 861 | "test", 862 | "check-coverage" 863 | ] 864 | } 865 | ``` 866 | 867 | Now whenever you `commit` your code, your tests will run 868 | and `istanbul` will check the test coverage level for you. 869 | 870 | Let's get back to our `add_attributes` function! 871 | 872 |
    873 | 874 | #### Input `autofocus` 875 | 876 | In order to "_guide_" the person using our Todo List app 877 | to create their _first_ Todo List _item_, 878 | **we want** the `` field to be automatically "active" 879 | **so that** they can just start typing as soon as the app loads. 880 | 881 | This is achieved using the `autofocus` attribute. 882 | 883 | Add the following test to the `test/elmish.test.js` file:
    884 | 885 | ```js 886 | test.only('elmish.add_attributes add "autofocus" attribute', function (t) { 887 | document.getElementById(id).appendChild( 888 | elmish.add_attributes(["class=new-todo", "autofocus", "id=new"], 889 | document.createElement('input') 890 | ) 891 | ); 892 | // document.activeElement via: https://stackoverflow.com/a/17614883/1148249 893 | t.equal(document.getElementById('new'), document.activeElement, 894 | ' is "activeElement"'); 895 | elmish.empty(document); 896 | t.end(); 897 | }); 898 | ``` 899 | 900 | Write the necessary code to make this test _pass_ 901 | as a `case` in `add_attributes` in `elmish.js`. 902 | 903 | Relevant reading: 904 | + `` attributes: 905 | https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Attributes 906 | + https://caniuse.com/#feat=autofocus (_**unavailable** on **iOS Safari**!_) 907 | 908 | > **Note**: while _all_ our _other_ HTML attributes 909 | follow the `key="value"` syntax, 910 | according to the W3C _specification_, 911 | simply adding the attribute _key_ in the element is "valid" 912 | e.g: `` 913 | see: https://stackoverflow.com/questions/4445765/html5-is-it-autofocus-autofocus-or-autofocus 914 | 915 | 916 | #### add `data-id` attribute to `
  • ` 917 | 918 | `data-*` attributes allow us to store extra information on standard, 919 | semantic HTML elements without affecting regular attributes. 920 | For example in the case of a Todo List item, 921 | we want to store a reference to the "item id" in the DOM 922 | for that item, so that we know which item to check-off when 923 | the checkbox is clicked/tapped. _However_ we don't want to use the 924 | "traditional" `id` attribute, we can use `data-id` 925 | to keep a clear separation between the data and presentation. 926 | 927 | See: "Using data attributes" 928 | https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes 929 | 930 | In the TodoMVC HTML code 931 | there are two `
  • ` (_list elements_) 932 | which have the `data-id` attribute (_see above_). 933 | 934 | Add the following test to the `test/elmish.test.js` file:
    935 | 936 | ```js 937 | test('elmish.add_attributes set data-id on
  • element', function (t) { 938 | const root = document.getElementById(id); 939 | let li = document.createElement('li'); 940 | li.id = 'task1'; 941 | li = elmish.add_attributes(["data-id=123"], li); 942 | root.appendChild(li); 943 | const data_id = document.getElementById('task1').getAttribute("data-id"); 944 | t.equal(data_id, '123', "data-id successfully added to
  • element"); 945 | t.end(); 946 | }); 947 | ``` 948 | Write the "case" in to make this test _pass_ in `elmish.js`. 949 | 950 | Tip: use `setAttribute()` method: 951 | https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute 952 | 953 | #### label `for` attribute 954 | 955 | Apply the `for` attribute to a `