Skip to content

Commit cb41458

Browse files
committed
add elmish.attributes function to apply HTML attributes to node for #44
1 parent 560c9bc commit cb41458

File tree

4 files changed

+110
-30
lines changed

4 files changed

+110
-30
lines changed

Diff for: examples/todo-list/elmish.js

+30-3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,32 @@ function mount(model, update, view, root_element_id) {
3636
view(signal, model, root); // render initial model (once)
3737
}
3838

39+
40+
/**
41+
* attributes applies the desired attributes to the desired node.
42+
* Note: this function is "impure" because it "mutates" the node.
43+
* however it is idempotent; the "side effect" is only applied once.
44+
* @param {Array.<String>} attrlist list of attributes to be applied to the node
45+
* @param {Object} node DOM node upon which attribute(s) should be applied
46+
* @example
47+
* // returns node with attributes applied
48+
* div = attributes(["class=item", "id=mydiv", "active=true"], div);
49+
*/
50+
function attributes(attrlist, node) {
51+
attrlist.forEach(function (attr) {
52+
var a = attr.split('=');
53+
switch(a[0]) {
54+
case 'class':
55+
node.className = a[1]; // apply CSS classes
56+
break;
57+
default:
58+
break;
59+
}
60+
});
61+
return node;
62+
}
63+
64+
3965
/**
4066
* init initialises the document (Global) variable for DOM operations.
4167
* @param {Object} doc window.document in browser and JSDOM.document in tests.
@@ -51,11 +77,12 @@ function init(doc){
5177
if (typeof module !== 'undefined' && module.exports) {
5278
module.exports = {
5379
// view: view,
54-
mount: mount,
80+
attributes: attributes,
81+
empty: empty,
82+
init: init,
83+
mount: mount
5584
// update: update,
5685
// div: div,
5786
// button: button,
58-
empty: empty,
59-
init: init
6087
}
6188
} else { init(document); }

Diff for: test/counter-reset.test.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ const id = 'test-app';
1313
test('Mount app expect state to be Zero', function (t) {
1414
// console.log('mount', mount);
1515
mount(0, update, view, id);
16-
console.log('document.getElementById(id)', document.getElementById(id), document.getElementById(id).childElementCount);
16+
// console.log('document.getElementById(id)', document.getElementById(id), document.getElementById(id).childElementCount);
1717
var actual = document.getElementById(id).textContent;
18-
console.log('actual:', actual);
18+
// console.log('actual:', actual);
1919
var actual_stripped = parseInt(actual.replace('+', '').replace('-Reset', ''), 10);
2020
var expected = 0;
2121
t.equal(actual_stripped, expected, "Inital state set to 0.");
@@ -42,7 +42,7 @@ test('Test Update decrement: update(3, "dec") returns 2', function (t) {
4242

4343

4444
test('click on "+" button (increment model by 1)', function (t) {
45-
console.log('document', typeof document, document);
45+
// console.log('document', typeof document, document);
4646
document.body.appendChild(div(id));
4747
mount(7, update, view, id);
4848

@@ -53,10 +53,10 @@ test('click on "+" button (increment model by 1)', function (t) {
5353
var btn = document.getElementById('inc');
5454

5555
btn.addEventListener('click', function() {
56-
console.log('increment button CLICKed!!');
56+
// console.log('increment button CLICKed!!');
5757
var state = parseInt( document.getElementById(id)
5858
.getElementsByClassName('count')[0].innerHTML, 10);
59-
console.log('state:', state, 'typeof state:', typeof state);
59+
// console.log('state:', state, 'typeof state:', typeof state);
6060
t.equal(state, 8,
6161
"End State is 8 after incrementing 7 by 1 (as expected)"); // incremented
6262
t.end();

Diff for: test/elmish.test.js

+27-8
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,18 @@ require('jsdom-global')(html); // https://github.com/rstacruz/jsdom-global
88
elmish.init(document); // pass JSDOM into elmish for DOM functions
99
const id = 'test-app'; // all tests use separate root element
1010

11-
test('empty("root") removes DOM elements from container', function (t) {
11+
test('elmish.empty("root") removes DOM elements from container', function (t) {
1212
// setup the test div:
1313
const text = 'Hello World!'
14+
const divid = "mydiv";
1415
const root = document.getElementById(id);
1516
const div = document.createElement('div');
16-
div.id = 'mydiv';
17+
div.id = divid;
1718
const txt = document.createTextNode(text);
1819
div.appendChild(txt);
1920
root.appendChild(div);
2021
// check text of the div:
21-
const actual = document.getElementById('mydiv').textContent;
22+
const actual = document.getElementById(divid).textContent;
2223
t.equal(actual, text, "Contents of mydiv is: " + actual + ' == ' + text);
2324
t.equal(root.childElementCount, 1, "Root element " + id + " has 1 child el");
2425
// empty the root DOM node:
@@ -49,8 +50,26 @@ test('elmish.mount app expect state to be Zero', function (t) {
4950
t.end()
5051
});
5152

52-
// test('Test Update update(0) returns 0 (current state)', function (t) {
53-
// var result = update(0);
54-
// t.equal(result, 0, "Initial state: 0, No Action, Final state: 0");
55-
// t.end();
56-
// });
53+
54+
test('elmish.attributes applies an HTML attribute to a node', function (t) {
55+
const root = document.getElementById(id);
56+
let div = document.createElement('div');
57+
div.id = 'divid';
58+
div = elmish.attributes(["class=apptastic"], div);
59+
root.appendChild(div);
60+
// test the div has the desired class:
61+
const nodes = document.getElementsByClassName('apptastic');
62+
t.equal(nodes.length, 1, "<div> has 'apptastic' class applied");
63+
t.end();
64+
});
65+
66+
test('test default branch of elmish.attributes (no effect)', function (t) {
67+
const root = document.getElementById(id);
68+
let div = document.createElement('div');
69+
div.id = 'divid';
70+
// "Clone" the div DOM node before invoking the elmish.attributes
71+
const clone = div.cloneNode(true);
72+
div = elmish.attributes(["unrecognised_attribute=noise"], div);
73+
t.deepEqual(div, clone, "<div> has not been altered");
74+
t.end();
75+
});

Diff for: todo-list.md

+48-14
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,8 @@ please see:
184184

185185
### `mount` the App
186186

187-
The next function we need for "TEA" is `mount`.
187+
The `mount` function is the "glue" or "wiring" function that
188+
connects the `model`, `update` and `view`; we _can_ _generalise_ it.
188189

189190
In the `test/elmish.test.js` file, type the following code:
190191
```js
@@ -213,7 +214,7 @@ test('elmish.mount app expect state to be Zero', function (t) {
213214
> _**Note**: we have "**borrowed**" this test from our previous example.
214215
see:_ `test/counter-reset.test.js`
215216

216-
The corresponding code for the `mount` function
217+
The corresponding code with JSDOC for the `mount` function
217218
in `examples/todo-list/elmish.js` is:
218219
```js
219220
/**
@@ -236,6 +237,36 @@ function mount(model, update, view, root_element_id) {
236237
}
237238
```
238239

240+
### `module.exports`
241+
242+
In order to test the `elmish` functions we need to `export` them.
243+
Additionally, because we are using JSDOM
244+
to test our front-end functions using `tape`,
245+
we need an `init` function to pass in the JSDOM `document`.
246+
add the following lines to `examples/todo-list/elmish.js`:
247+
248+
```js
249+
/**
250+
* init initialises the document (Global) variable for DOM operations.
251+
* @param {Object} doc window.document in browser and JSDOM.document in tests.
252+
* @return {Object} document returns whatever is passed in.
253+
*/
254+
function init(doc){
255+
document = doc; // this is used for instantiating JSDOM for testing.
256+
return document;
257+
}
258+
259+
/* module.exports is needed to run the functions using Node.js for testing! */
260+
/* istanbul ignore next */
261+
if (typeof module !== 'undefined' && module.exports) {
262+
module.exports = {
263+
empty: empty,
264+
mount: mount,
265+
init: init
266+
}
267+
} else { init(document); }
268+
```
269+
239270

240271
Now that we have started creating the `elmish` generic functions,
241272
we need to know which _other_ functions we need.
@@ -261,6 +292,9 @@ Open your web browser to: http://localhost:8000
261292

262293
![vanillajs-localhost](https://user-images.githubusercontent.com/194400/42632838-6e68c20c-85d6-11e8-8ae4-d688f5977704.png)
263294

295+
> If you are unable to run the TodoMVC locally, you can always view it online:
296+
http://todomvc.com/examples/vanillajs
297+
264298
_Play_ with the app by adding a few items,
265299
checking-off and toggling the views in the footer.
266300

@@ -271,9 +305,6 @@ is quite complex and insufficiently documented_
271305
_so don't expect to **understand** it all the first time without study._
272306
_Don't worry, we will walk through building each feature in detail._
273307

274-
> If you are unable to run the TodoMVC locally, you can always view it online:
275-
>> Please add direct link here!
276-
277308

278309

279310
### Todo List _Basic_ Functionality
@@ -404,6 +435,16 @@ We therefore need a pair of "helper" functions (_one for each argument_).
404435

405436

406437

438+
#### `attributes`
439+
440+
`attributes(list, node)`
441+
442+
The `attributes` function is "impure" as it "mutates"
443+
the target DOM `node`, however the application of attributes
444+
to DOM node(s) is idempotent;
445+
the attribute will only be applied _once_ to the node
446+
regardless of how many times the `attributes` function is called.
447+
see: https://en.wikipedia.org/wiki/Idempotence
407448

408449

409450

@@ -415,15 +456,8 @@ Demo: https://ellie-app.com/LTtNVQjfWVa1
415456

416457

417458

418-
419-
### `mount`
420-
421-
The `mount` function is the "glue" or "wiring" function that
422-
connects the `model`, `update` and `view`; we _can_ `generalise` it.
423-
424-
425-
426-
459+
The Elm HTML Attributes package is:
460+
http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Attributes
427461

428462

429463

0 commit comments

Comments
 (0)