From d41c8d87aea1a656b33d450c868bab92eb1d7ae8 Mon Sep 17 00:00:00 2001 From: nelsonic Date: Fri, 10 Aug 2018 13:46:25 +0100 Subject: [PATCH] add test and code for onclick function in attributes for/fixes #58 --- elmish.md | 96 ++++++++++++++++++++++++++++++++++++ examples/todo-list/elmish.js | 8 ++- test/elmish.test.js | 22 ++++++--- 3 files changed, 116 insertions(+), 10 deletions(-) diff --git a/elmish.md b/elmish.md index 513793d5..c89478fb 100644 --- a/elmish.md +++ b/elmish.md @@ -1797,13 +1797,109 @@ refer to the completed code:
+### `onclick` `attribute` to invoke the "dispatcher" when element clicked + +In order to allow click/tap interactions with buttons, +we need to add an `onclick` attribute which then _invokes_ the desired update. + +Add the following _test code_ to your `test/elmish.test.js` file:
+```js +test.only('elmish.add_attributes onclick=signal(action) events!', function (t) { + const root = document.getElementById(id); + elmish.empty(root); + let counter = 0; // global to this test. + function signal (action) { // simplified version of TEA "dispatcher" function + return function callback() { + switch (action) { + case 'inc': + counter++; // "mutating" ("impure") counters for test simplicity. + break; + } + } + } + + root.appendChild( // signal('inc') should be applied as "onclick" function: + elmish.add_attributes(["id=btn", signal('inc')], + document.createElement('button')) + ); + + // "click" the button! + document.getElementById("btn").click() + // confirm that the counter was incremented by the onclick being triggered: + t.equal(counter, 1, "Counter incremented via onclick attribute (function)!"); + elmish.empty(root); + t.end(); +}); +``` + +Run the test: +```sh +node test/elmish.test.js +``` +![onclick-test-failing](https://user-images.githubusercontent.com/194400/43955072-99712c7e-9c96-11e8-94a0-8c6d6d9169cb.png) + +Making this test pass requires a little knowledge of how JavaScript +does "type checking" and the fact that we can "pass around" functions +as variables. + +The amount of code required to make this test pass is _minimal,_ +you could even get it down to ***1 line***. +The key is thinking through what the test is doing +and figuring out how to apply an `onclick` function to a DOM node. + +Relevant/useful reading: + ++ https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onclick ++ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof ++ https://stackoverflow.com/questions/6956258/adding-onclick-event-to-dynamically-added-button ++ https://stackoverflow.com/questions/14569320/simulating-button-click-in-javascript + + + +
### `subscriptions` for event listeners +In Elm, when we want to "listen" for an event or "external input" +we use ***`subscriptions`***. Examples are: + ++ [Keyboard events](http://package.elm-lang.org/packages/elm-lang/keyboard/latest/Keyboard) ++ [Mouse movements](http://package.elm-lang.org/packages/elm-lang/mouse/latest/Mouse) ++ Browser locations changes ++ [Websocket events](http://package.elm-lang.org/packages/elm-lang/websocket/latest/WebSocket) + +In order to listen for and respond to Keyboard events, +specifically the **`Enter`** and **`[Escape]`** key press, +we need a way of "attaching" event listeners to the DOM +when mounting our App. +To demonstrate **`subscriptions`**, +let's _briefly re-visit_ the Counter Example +and consider an alternative User Interaction/Experience: Keyboard! +#### Use-case: Use Up/Down Keyboard (Arrow) Keys to Increment/Decrement Counter +Let's start by making a "copy" of the code in `/examples/counter-reset`: +```sh +mkdir examples/counter-reset-keyboard +cp examples/counter-reset/* examples/counter-reset-keyboard/ +``` + +First step is to _re-factor_ the code in +`examples/counter-reset-keyboard/counter.js` +to use the "DOM" functions we've been creating for `Elm`(_ish_). +This will _simplify_ the `counter.js` down to the _bare minimum_. + + + + +#### How do We _Test_ for Subscription Events? + + + + +
That's it for now! `Elm`(_ish_) is "ready" to be _used_ for our TodoMVC App! diff --git a/examples/todo-list/elmish.js b/examples/todo-list/elmish.js index c658e767..e3bc8eb9 100644 --- a/examples/todo-list/elmish.js +++ b/examples/todo-list/elmish.js @@ -47,7 +47,8 @@ function mount (model, update, view, root_element_id, subscriptions) { * `add_attributes` applies the desired attribute(s) to the specified DOM node. * Note: this function is "impure" because it "mutates" the node. * however it is idempotent; the "side effect" is only applied once. -* @param {Array.} attrlist list of attributes to be applied to the node +* @param {Array./} attrlist list of attributes to be applied +* to the node accepts both String and Function (for onclick handlers). * @param {Object} node DOM node upon which attribute(s) should be applied * @example * // returns node with attributes applied @@ -56,6 +57,9 @@ function mount (model, update, view, root_element_id, subscriptions) { function add_attributes (attrlist, node) { if(attrlist && attrlist.length) { attrlist.forEach(function (attr) { // apply all props in array + // do not attempt to "split" an onclick function as it's not a string! + if (typeof attr === 'function') { node.onclick = attr; return node; } + // apply any attributes that are *not* functions (i.e. Strings): var a = attr.split('='); switch(a[0]) { case 'autofocus': @@ -90,7 +94,7 @@ function add_attributes (attrlist, node) { break; default: break; - } + } // end switch }); } return node; diff --git a/test/elmish.test.js b/test/elmish.test.js index 2d5cd74c..bf9ecea8 100644 --- a/test/elmish.test.js +++ b/test/elmish.test.js @@ -423,24 +423,30 @@ test('elmish.mount sets model in localStorage', function (t) { t.end() }); -test.only('elmish.add_attributes onclick=signal(action) events!', function (t) { +test('elmish.add_attributes onclick=signal(action) events!', function (t) { const root = document.getElementById(id); elmish.empty(root); - let counter = 0; - function signal(action) { + let counter = 0; // global to this test. + function signal (action) { // simplified version of TEA "dispacher" function return function callback() { - counter++ + switch (action) { + case 'inc': + counter++; // "mutating" ("impure") counters for test simplicity. + break; + } } } - let btn = document.createElement('button'); + root.appendChild( - elmish.add_attributes(["id=btn", "onclick=" + signal('inc')], btn) + elmish.add_attributes(["id=btn", signal('inc')], + document.createElement('button')) ); // "click" the button! document.getElementById("btn").click() - t.equal(counter, 1, "Counter incremented"); - + // confirm that the counter was incremented by the onclick being triggered: + t.equal(counter, 1, "Counter incremented via onclick attribute (function)!"); + elmish.empty(root); t.end(); });