├── README.md
├── index.js
└── package.json
/README.md:
--------------------------------------------------------------------------------
1 | ## Funky
2 |
3 | Front-end view system using basic functional programming and template literals.
4 |
5 | ## Usage
6 |
7 | #### `funk` | `funk.view`
8 |
9 | ```javascript
10 | var funk = require('funky')
11 | var docview = funk`
12 |
13 | ${item => item.text}
14 |
15 | `
16 |
17 | document.body.appendChild(docview({text: "Hello World!"}))
18 | ```
19 |
20 | The `funk` and `funk.view` are function handlers for string template literals.
21 | The return value is a function.
22 |
23 | When called this function returns a DOM element with some properties added.
24 | Any variables passed to the view function are passed to every function in the
25 | template literal and the element will use the return values from those
26 | functions.
27 |
28 | #### `element.update()`
29 |
30 | ```javascript
31 | var funk = require('funky')
32 | var docview = funk`
33 |
34 | ${item => item.text}
35 |
36 | `
37 |
38 | document.body.appendChild(docview({text: "Hello World!", id: 'hello'}))
39 |
40 | var element = docview({text: "Hello Second Line.", id: 'second-hello'})
41 | document.body.appendChild(element)
42 |
43 | // Update content 3 seconds after adding to DOM.
44 | setTimeout(() => {
45 | element.update({text: "Updated, Hello Second Line.", id: 'second-hello'})
46 | document.getElementById('hello').update({text: "Updated", id: "hello"})
47 | }, 1000 * 3)
48 | ```
49 |
50 | DOM elements created by funky views have a method attached named `update`. This
51 | function is used to update the element with new values as if they had been
52 | passed to the original view creation.
53 |
54 | Below the surface this uses
55 | [`yo-yo.update`](https://github.com/maxogden/yo-yo#updating-events)
56 | which uses efficient DOM diffing.
57 |
58 | #### `funk.list`
59 |
60 | List views are designed to operate almost exactly like regular JavaScript
61 | Arrays except when items are removed, added, or re-ordered, new views are
62 | rendered to the DOM.
63 |
64 | ```javascript
65 | var simpleview = funk`
66 |
67 | ${text => text}
68 |
69 | `
70 | var list = funk.list(simpleview, ["Hello World!", "Hello Second Line."])
71 | document.body.appendChild(list.node)
72 | ```
73 |
74 | The `list.node` property is the DOM node that can be attached.
75 |
76 | List views can also directly be passed into other views.
77 |
78 | ```javascript
79 | var simpleview = funk`
80 |
81 | ${text => text}
82 |
83 | `
84 | var list = funk.list(simpleview, ["Hello World!", "Hello Second Line."])
85 | var listContainer = funk` id}">${list}
`('list-container')
86 | document.body.appendChild(listContainer)
87 | ```
88 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var yo = require('yo-yo')
2 |
3 | function parse (strings, values) {
4 | // The raw arrays are no actually mutable, copy.
5 | strings = strings.slice()
6 | values = values.slice()
7 | let shadowStrings = []
8 | let shadowValues = []
9 | let elementStrings = []
10 | let elementValues = []
11 | let constructors = []
12 | let s = strings.join('').replace(/ /g, '')
13 | let name = s.slice(s.indexOf('<') + 1, s.indexOf('>'))
14 |
15 | let fullstring = ''
16 | let valueMap = {}
17 |
18 | let i = 0
19 | while (i < strings.length) {
20 | fullstring += strings[i]
21 | valueMap[fullstring.length + '-' + i] = values[i]
22 | i++
23 | }
24 |
25 | let opener = `<${name}>`
26 | let openPos = fullstring.indexOf(opener)
27 | let closer = `${name}>`
28 | let closePos = fullstring.lastIndexOf(closer)
29 |
30 | if (openPos === -1) throw new Error('Cannot find open position.')
31 | if (closePos === -1) throw new Error('Cannot find close position.')
32 |
33 | for (let posName in valueMap) {
34 | let pos = +posName.slice(0, posName.indexOf('-'))
35 | let val = valueMap[posName]
36 |
37 | if (pos < openPos) constructors.push(val)
38 | else if (pos > openPos && pos < closePos) {
39 | elementValues.push(val)
40 | } else if (pos > closePos) {
41 | shadowValues.push(val)
42 | } else {
43 | throw new Error('Parser error, cannot assign value.')
44 | }
45 | }
46 |
47 | i = 0
48 | let pos = 0
49 | while (i < strings.length) {
50 | let str = strings[i]
51 |
52 | let iter = () => {
53 | if (pos >= fullstring.length) return
54 | if (pos >= openPos) {
55 | if (pos > closePos) {
56 | shadowStrings.push(str)
57 | } else {
58 | if (str.indexOf(closer) !== -1) {
59 | let _pos = str.indexOf(closer) + closer.length + 1
60 | elementStrings.push(str.slice(0, _pos))
61 | str = str.slice(_pos)
62 | pos += _pos
63 | return iter()
64 | } else {
65 | elementStrings.push(str)
66 | }
67 | }
68 | } else {
69 | if (str.indexOf(opener) !== -1) {
70 | let _pos = str.indexOf(opener)
71 | str = str.slice(_pos)
72 | pos = pos + _pos
73 | return iter()
74 | }
75 | }
76 | pos = pos + str.length
77 | }
78 | iter()
79 | i++
80 | }
81 | // TODO: type checking on constructors and destructors
82 |
83 | let result = {
84 | name,
85 | constructors,
86 | shadowStrings,
87 | shadowValues,
88 | elementStrings,
89 | elementValues
90 | }
91 | return result
92 | }
93 |
94 | function view (strings, ...inserts) {
95 | let parsed = parse(strings, inserts)
96 |
97 | function _callInserts (args, getElement) {
98 | var ret = parsed.elementValues.map(i => {
99 | if (i.node) return i.node
100 |
101 | if (typeof i === 'function') return i.apply(this, args)
102 | return i
103 | })
104 | return ret
105 | }
106 |
107 | function _viewReturn () {
108 | var args = Array.prototype.slice.call(arguments)
109 | var element = yo(parsed.elementStrings, ..._callInserts(args))
110 | parsed.constructors.forEach(c => c(...[element].concat(args)))
111 | // TODO: attach ShadowDOM
112 | element.yoyoOpts = {childrenOnly: true}
113 | element.update = function () {
114 | if (element.onupdate) element.onupdate.apply(element, arguments)
115 | let newelement = element.processUpdate.apply(element, arguments)
116 | yo.update(element, newelement, element.yoyoOpts)
117 | }
118 | element.processUpdate = function () {
119 | var args = Array.prototype.slice.call(arguments)
120 | var _inserts = [..._callInserts(args)]
121 | var newelement = yo(parsed.elementStrings, ..._inserts)
122 | return newelement
123 | }
124 | element._funkView = true
125 | return element
126 | }
127 | return _viewReturn
128 | }
129 |
130 | module.exports = view
131 | module.exports.attr = (key) => (doc) => doc[key]
132 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "funky",
3 | "version": "2.3.1",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "Mikeal Rogers (http://www.mikealrogers.com)",
11 | "license": "Apache-2.0",
12 | "dependencies": {
13 | "yo-yo": "^1.2.2"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------