├── 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 = `` 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 | --------------------------------------------------------------------------------