Skip to content

Commit

Permalink
add test and code for onclick function in attributes for/fixes #58
Browse files Browse the repository at this point in the history
  • Loading branch information
nelsonic committed Aug 10, 2018
1 parent d890749 commit d41c8d8
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 10 deletions.
96 changes: 96 additions & 0 deletions elmish.md
Original file line number Diff line number Diff line change
Expand Up @@ -1797,13 +1797,109 @@ refer to the completed code:

<br />

### `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: <br />
```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




<br />

### `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?




<br />

That's it for now! `Elm`(_ish_) is "ready" to be _used_
for our TodoMVC App!
Expand Down
8 changes: 6 additions & 2 deletions examples/todo-list/elmish.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.<String>} attrlist list of attributes to be applied to the node
* @param {Array.<String>/<Function>} 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
Expand All @@ -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':
Expand Down Expand Up @@ -90,7 +94,7 @@ function add_attributes (attrlist, node) {
break;
default:
break;
}
} // end switch
});
}
return node;
Expand Down
22 changes: 14 additions & 8 deletions test/elmish.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

Expand Down

0 comments on commit d41c8d8

Please sign in to comment.