git ssb

1+

Matt McKegg / mutant



Commit 4ae9282d39a3f4b2b43760e4c33d0408c400aa04

initial code dump!

Matt McKegg committed on 7/7/2016, 5:31:08 AM

Files changed

.gitignoreadded
README.mdadded
array.jsadded
computed.jsadded
html-element.jsadded
is-observable.jsadded
lib/apply-properties.jsadded
lib/parse-tag.jsadded
map.jsadded
package.jsonadded
resolve.jsadded
send.jsadded
set.jsadded
struct.jsadded
test/dom.jsadded
test/test-array.jsadded
test/test-map.jsadded
test/test-set.jsadded
test/test-struct.jsadded
test/test-varhash.jsadded
transforms/flatten.jsadded
transforms/lookup.jsadded
transforms/sort.jsadded
transforms/uniq.jsadded
value.jsadded
varhash.jsadded
watch.jsadded
.gitignoreView
@@ -1,0 +1,1 @@
1 +node_modules
README.mdView
@@ -1,0 +1,63 @@
1 +mutant
2 +===
3 +
4 +Create observables and map them to DOM elements. Massively inspired by `hyperscript` and `observ-*`.
5 +
6 +No virtual dom, just direct observable bindings. Unnecessary garbage collection is avoided by using mutable objects instead of blasting immutable junk all over the place.
7 +
8 +## Current Status: Experimental / Unpublished
9 +
10 +## Install
11 +
12 +```bash
13 +npm install @mmckegg/mutant --save
14 +```
15 +
16 +## Use
17 +
18 +```js
19 +var Struct = require('@mmckegg/mutant/struct')
20 +var send = require('@mmckegg/mutant/send')
21 +var h = require('@mmckegg/mutant/html-element')
22 +
23 +var state = Struct({
24 + text: 'Test',
25 + color: 'red',
26 + value: 0
27 +})
28 +
29 +var element = h('div.cool', {
30 + class: ['cool', state.text],
31 + style: {
32 + 'background-color': state.color
33 + }
34 +}, [
35 + h('div', [
36 + state.text, ' ', state.value, ' ', h('strong', 'test')
37 + ]),
38 + h('div', [
39 + h('button', {
40 + 'ev-click': send(state.color.set, 'blue')
41 + }, 'Change color')
42 + ])
43 +])
44 +
45 +setTimeout(function () {
46 + state.text.set('Another value')
47 +}, 5000)
48 +
49 +setInterval(function () {
50 + state.value.set(state.value() + 1)
51 +}, 1000)
52 +
53 +setInterval(function () {
54 + // bulk update state
55 + state.set({
56 + text: 'Retrieved from server (not really)',
57 + color: '#FFEECC',
58 + value: 1337
59 + })
60 +}, 10000)
61 +
62 +document.body.appendChild(element)
63 +```
array.jsView
@@ -1,0 +1,126 @@
1 +var Value = require('./value')
2 +
3 +module.exports = Array
4 +
5 +function Array (defaultValues) {
6 + var object = []
7 + var sources = []
8 + var releases = []
9 +
10 + if (defaultValues && defaultValues.length) {
11 + defaultValues.forEach(add)
12 + }
13 +
14 + var observable = Value(object)
15 + var broadcast = observable.set
16 +
17 + observable.push = function (args) {
18 + for (var i = 0; i < arguments.length; i++) {
19 + add(arguments[i])
20 + }
21 + broadcast(object)
22 + }
23 +
24 + observable.insert = function (valueOrObs, at) {
25 + sources.splice(at, 0, valueOrObs)
26 + releases.splice(at, 0, bind(valueOrObs))
27 + object.splice(at, 0, resolve(valueOrObs))
28 + broadcast(object)
29 + }
30 +
31 + observable.get = function (index) {
32 + return sources[index]
33 + }
34 +
35 + observable.getLength = function (index) {
36 + return sources.length
37 + }
38 +
39 + observable.includes = function (valueOrObs) {
40 + return !!~sources.indexOf(valueOrObs)
41 + }
42 +
43 + observable.indexOf = function (valueOrObs) {
44 + return sources.indexOf(valueOrObs)
45 + }
46 +
47 + observable.indexOf
48 +
49 + observable.pop = function () {
50 + var result = sources.pop()
51 + tryInvoke(releases.pop())
52 + object.pop()
53 + broadcast(object)
54 + return result
55 + }
56 +
57 + observable.shift = function () {
58 + var result = sources.shift()
59 + tryInvoke(releases.shift())
60 + object.shift()
61 + broadcast(object)
62 + return result
63 + }
64 +
65 + observable.clear = function () {
66 + releases.forEach(tryInvoke)
67 + sources.length = 0
68 + releases.length = 0
69 + object.length = 0
70 + broadcast(object)
71 + }
72 +
73 + observable.delete = function (valueOrObs) {
74 + var index = sources.indexOf(valueOrObs)
75 + if (~index) {
76 + sources.splice(index, 1)
77 + releases.splice(index, 1).forEach(tryInvoke)
78 + object.splice(index, 1)
79 + broadcast(object)
80 + }
81 + }
82 +
83 + observable.set = function (values) {
84 + releases.forEach(tryInvoke)
85 + sources.length = 0
86 + releases.length = 0
87 + object.length = 0
88 + values.forEach(add)
89 + broadcast(object)
90 + }
91 +
92 + observable.destroy = observable.clear
93 +
94 + return observable
95 +
96 + // scoped
97 +
98 + function add (valueOrObs) {
99 + sources.push(valueOrObs)
100 + releases.push(bind(valueOrObs))
101 + object.push(resolve(valueOrObs))
102 + }
103 +
104 + function bind (valueOrObs) {
105 + return typeof valueOrObs === 'function' ? valueOrObs(update.bind(this, valueOrObs)) : null
106 + }
107 +
108 + function update (obs, value) {
109 + sources.forEach(function (source, i) {
110 + if (source === obs) {
111 + object[i] = value
112 + }
113 + })
114 + broadcast(object)
115 + }
116 +}
117 +
118 +function resolve (source) {
119 + return typeof source === 'function' ? source() : source
120 +}
121 +
122 +function tryInvoke (func) {
123 + if (typeof func === 'function') {
124 + func()
125 + }
126 +}
computed.jsView
@@ -1,0 +1,102 @@
1 +/* A lazy binding take on computed */
2 +// doesn't start watching observables until itself is watched, and then releases if unwatched
3 +// avoids memory/watcher leakage
4 +
5 +var resolve = require('./resolve')
6 +
7 +module.exports = computed
8 +
9 +function computed (observables, lambda) {
10 + var values = []
11 + var releases = []
12 + var computedValue = null
13 + var live = false
14 + var lazy = false
15 + var listeners = []
16 +
17 + var result = function (listener) {
18 + if (!listener) {
19 + return getValue()
20 + }
21 +
22 + if (typeof listener !== 'function') {
23 + throw new Error('Listeners must be functions.')
24 + }
25 +
26 + listeners.push(listener)
27 + listen()
28 +
29 + return function remove () {
30 + for (var i = 0, len = listeners.length; i < len; i++) {
31 + if (listeners[i] === listener) {
32 + listeners.splice(i, 1)
33 + break
34 + }
35 + }
36 + if (!listeners.length) {
37 + unlisten()
38 + }
39 + }
40 + }
41 +
42 + return result
43 +
44 + // scoped
45 +
46 + function listen () {
47 + if (!live) {
48 + for (var i = 0, len = observables.length; i < len; i++) {
49 + if (typeof observables[i] === 'function') {
50 + releases.push(observables[i](onUpdate))
51 + }
52 + }
53 + live = true
54 + lazy = true
55 + }
56 + }
57 +
58 + function unlisten () {
59 + if (live) {
60 + live = false
61 + while (releases.length) {
62 + releases.pop()()
63 + }
64 + }
65 + }
66 +
67 + function update () {
68 + var changed = false
69 + for (var i = 0, len = observables.length; i < len; i++) {
70 + var newValue = resolve(observables[i])
71 + if (newValue !== values[i] || typeof newValue === 'object') {
72 + changed = true
73 + values[i] = newValue
74 + }
75 + }
76 +
77 + if (changed) {
78 + var newComputedValue = lambda.apply(null, values)
79 + if (newComputedValue !== computedValue || typeof newComputedValue === 'object') {
80 + computedValue = newComputedValue
81 + return true
82 + }
83 + }
84 + return false
85 + }
86 +
87 + function onUpdate () {
88 + if (update()) {
89 + for (var i = 0, len = listeners.length; i < len; i++) {
90 + listeners[i](computedValue)
91 + }
92 + }
93 + }
94 +
95 + function getValue () {
96 + if (!live || lazy) {
97 + lazy = false
98 + update()
99 + }
100 + return computedValue
101 + }
102 +}
html-element.jsView
@@ -1,0 +1,154 @@
1 +var applyProperties = require('./lib/apply-properties')
2 +var isObservable = require('./is-observable')
3 +var parseTag = require('./lib/parse-tag')
4 +var caches = new global.WeakMap()
5 +
6 +module.exports = function (tag, attributes, children) {
7 + return Element(global.document, null, tag, attributes, children)
8 +}
9 +
10 +module.exports.forDocument = function (document, namespace) {
11 + return Element.bind(this, document, namespace)
12 +}
13 +
14 +module.exports.destroy = function (node) {
15 + var data = caches.get(node)
16 + if (data) {
17 + Array.from(data.releases.values()).forEach(tryInvoke)
18 + caches.delete(node)
19 + }
20 + applyProperties.destroy(node)
21 +}
22 +
23 +function Element (document, namespace, tagName, properties, children) {
24 + if (!children && (Array.isArray(properties) || isText(properties))) {
25 + children = properties
26 + properties = null
27 + }
28 +
29 + properties = properties || {}
30 +
31 + var tag = parseTag(tagName, properties, namespace)
32 + var node = namespace
33 + ? document.createElementNS(namespace, tag.tagName)
34 + : document.createElement(tag.tagName)
35 +
36 + if (!namespace) {
37 + if (tag.id) {
38 + node.id = tag.id
39 + }
40 + if (tag.classes && tag.classes.length) {
41 + node.className = tag.classes.join(' ')
42 + }
43 + }
44 +
45 + var data = {
46 + targets: new Map(),
47 + releases: new Map()
48 + }
49 +
50 + caches.set(node, data)
51 + applyProperties(node, properties, namespace)
52 + if (children != null) {
53 + appendChild(document, node, data, children)
54 + }
55 +
56 + return node
57 +}
58 +
59 +function appendChild (document, target, data, node) {
60 + if (Array.isArray(node)) {
61 + node.forEach(function (child) {
62 + appendChild(document, target, data, child)
63 + })
64 + } else if (isObservable(node)) {
65 + var nodes = getNodes(document, resolve(node))
66 + nodes.forEach(append, target)
67 + data.targets.set(node, nodes)
68 + data.releases.set(node, bind(document, node, data))
69 + } else {
70 + target.appendChild(getNode(document, node))
71 + }
72 +}
73 +
74 +function append (child) {
75 + this.appendChild(child)
76 +}
77 +
78 +function bind (document, obs, data) {
79 + return obs(function (value) {
80 + var oldNodes = data.targets.get(obs)
81 + var newNodes = getNodes(document, value)
82 + if (oldNodes) {
83 + replace(oldNodes, newNodes)
84 + data.targets.set(obs, newNodes)
85 + }
86 + })
87 +}
88 +
89 +function replace (oldNodes, newNodes) {
90 + var parent = oldNodes[oldNodes.length - 1].parentNode
91 + var marker = oldNodes[oldNodes.length - 1].nextSibling
92 + oldNodes.filter(function (node) {
93 + return !~newNodes.indexOf(node)
94 + }).forEach(function (node) {
95 + parent.removeChild(node)
96 + })
97 + if (marker) {
98 + newNodes.forEach(function (node) {
99 + parent.insertBefore(node, marker)
100 + })
101 + } else {
102 + newNodes.forEach(function (node) {
103 + parent.appendChild(node)
104 + })
105 + }
106 +}
107 +
108 +function isText (value) {
109 + return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
110 +}
111 +
112 +function getNode (document, nodeOrText) {
113 + if (nodeOrText == null) {
114 + return document.createTextNode('')
115 + } else if (isText(nodeOrText)) {
116 + return document.createTextNode(nodeOrText.toString())
117 + } else {
118 + return nodeOrText
119 + }
120 +}
121 +
122 +function getNodes (document, nodeOrNodes) {
123 + if (Array.isArray(nodeOrNodes)) {
124 + if (nodeOrNodes.length) {
125 + var result = []
126 + nodeOrNodes.forEach(function (item) {
127 + if (Array.isArray(item)) {
128 + getNodes(document, item).forEach(push, result)
129 + } else {
130 + result.push(getNode(document, item))
131 + }
132 + })
133 + return result.map(getNode.bind(this, document))
134 + } else {
135 + return [getNode(document, null)]
136 + }
137 + } else {
138 + return [getNode(document, nodeOrNodes)]
139 + }
140 +}
141 +
142 +function push (item) {
143 + this.push(item)
144 +}
145 +
146 +function resolve (source) {
147 + return typeof source === 'function' ? source() : source
148 +}
149 +
150 +function tryInvoke (func) {
151 + if (typeof func === 'function') {
152 + func()
153 + }
154 +}
is-observable.jsView
@@ -1,0 +1,5 @@
1 +module.exports = isObservable
2 +
3 +function isObservable (obj) {
4 + return typeof obj === 'function'
5 +}
lib/apply-properties.jsView
@@ -1,0 +1,138 @@
1 +var isObservable = require('./is-observable')
2 +var Set = require('../set')
3 +var watch = require('../watch')
4 +var caches = new global.WeakMap()
5 +
6 +module.exports = applyProperties
7 +
8 +function applyProperties (target, properties, namespace) {
9 + var data = caches.get(target)
10 + if (!data) {
11 + data = { releases: [] }
12 + caches.set(target, data)
13 + }
14 +
15 + var classList = Set()
16 + if (target.className) {
17 + classList.add(target.className)
18 + }
19 +
20 + for (var key in properties) {
21 + var valueOrObs = properties[key]
22 + var value = resolve(valueOrObs)
23 +
24 + if (key === 'style') {
25 + // TODO: handle observable at root for style objects
26 + for (var k in value) {
27 + var styleValue = resolve(value[k])
28 + var styleObs = isObservable(value[k]) ? value[k] : null
29 + target.style.setProperty(k, styleValue)
30 +
31 + if (styleObs) {
32 + data.releases.push(bindStyle(target, styleObs, k))
33 + }
34 + }
35 + } else if (key === 'attributes') {
36 + for (var k in value) {
37 + var attrValue = resolve(value[k])
38 + var attrObs = isObservable(value[k]) ? value[k] : null
39 +
40 + if (namespace) {
41 + target.setAttributeNS(namespace, k, attrValue)
42 + } else {
43 + target.setAttribute(k, attrValue)
44 + }
45 +
46 + if (attrObs) {
47 + data.releases.push(bindAttr(target, attrObs, k, namespace))
48 + }
49 + }
50 + } else if (key === 'events') {
51 + for (var name in value) {
52 + target.addEventListener(name, value[name], true)
53 + }
54 + } else if (key.slice(0, 3) === 'ev-') {
55 + target.addEventListener(key.slice(3), value, true)
56 + } else if (key === 'className' || key === 'classList') {
57 + if (Array.isArray(value)) {
58 + value.forEach(function (v) {
59 + classList.add(v)
60 + })
61 + } else {
62 + classList.add(valueOrObs)
63 + }
64 + } else {
65 + target[key] = value
66 + }
67 +
68 + var obs = isObservable(valueOrObs) ? valueOrObs : null
69 + if (obs) {
70 + data.releases.push(bind(target, obs, key, namespace))
71 + }
72 + }
73 +
74 + watch(classList, function (value) {
75 + value = [].concat.apply([], value).filter(present).join(' ')
76 + if (value || target.className) {
77 + target.className = value
78 + }
79 + })
80 +}
81 +
82 +applyProperties.destroy = function (target) {
83 + var data = caches.get(target)
84 + if (data) {
85 + while (data.releases.length) {
86 + data.releases.pop()()
87 + }
88 + caches.delete(target)
89 + }
90 +}
91 +
92 +function bindStyle (target, styleObs, key) {
93 + return styleObs(function (value) {
94 + target.style.setProperty(key, value)
95 + })
96 +}
97 +
98 +function bindAttr (target, attrObs, key, namespace) {
99 + return attrObs(function (value) {
100 + if (value == null) {
101 + if (namespace) {
102 + target.removeAttributeNS(namespace, key)
103 + } else {
104 + target.removeAttribute(key)
105 + }
106 + } else {
107 + if (namespace) {
108 + target.setAttributeNS(namespace, key, value)
109 + } else {
110 + target.setAttribute(key, value)
111 + }
112 + }
113 + })
114 +}
115 +
116 +function bind (target, obs, key, namespace) {
117 + return obs(function (toValue) {
118 + var fromValue = namespace
119 + ? target.getAttributeNS(namespace, key)
120 + : target.getAttribute(key)
121 +
122 + if (fromValue !== toValue) {
123 + if (namespace) {
124 + target.setAttributeNS(namespace, key, toValue)
125 + } else {
126 + target.setAttribute(key, toValue)
127 + }
128 + }
129 + })
130 +}
131 +
132 +function present (val) {
133 + return val != null
134 +}
135 +
136 +function resolve (source) {
137 + return typeof source === 'function' ? source() : source
138 +}
lib/parse-tag.jsView
@@ -1,0 +1,52 @@
1 +// FROM: https://raw.githubusercontent.com/Matt-Esch/virtual-dom/master/virtual-hyperscript/parse-tag.js
2 +
3 +'use strict'
4 +
5 +var split = require('browser-split')
6 +
7 +var classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/
8 +var notClassId = /^\.|#/
9 +
10 +module.exports = parseTag
11 +
12 +function parseTag (tag, attributes, namespace) {
13 + if (!tag) {
14 + return 'DIV'
15 + }
16 +
17 + var noId = !(attributes.hasOwnProperty('id'))
18 +
19 + var tagParts = split(tag, classIdSplit)
20 + var tagName = null
21 +
22 + if (notClassId.test(tagParts[1])) {
23 + tagName = 'DIV'
24 + }
25 +
26 + var classes, part, type, i, id
27 +
28 + for (i = 0; i < tagParts.length; i++) {
29 + part = tagParts[i]
30 +
31 + if (!part) {
32 + continue
33 + }
34 +
35 + type = part.charAt(0)
36 +
37 + if (!tagName) {
38 + tagName = part
39 + } else if (type === '.') {
40 + classes = classes || []
41 + classes.push(part.substring(1, part.length))
42 + } else if (type === '#' && noId) {
43 + id = part.substring(1, part.length)
44 + }
45 + }
46 +
47 + return {
48 + tagName: namespace ? tagName : tagName.toUpperCase(),
49 + classes: classes,
50 + id: id
51 + }
52 +}
map.jsView
@@ -1,0 +1,210 @@
1 +var resolve = require('./resolve')
2 +var computed = require('./computed')
3 +
4 +module.exports = Map
5 +
6 +function Map (obs, lambda) {
7 + var releases = []
8 + var live = false
9 + var lazy = false
10 + var listeners = []
11 +
12 + var lastValues = new global.Map()
13 + var items = []
14 +
15 + var raw = []
16 + var values = []
17 + var watches = []
18 +
19 + var result = function MutantMap (listener) {
20 + if (!listener) {
21 + return getValue()
22 + }
23 +
24 + if (typeof listener !== 'function') {
25 + throw new Error('Listeners must be functions.')
26 + }
27 +
28 + listeners.push(listener)
29 + listen()
30 +
31 + return function remove () {
32 + for (var i = 0, len = listeners.length; i < len; i++) {
33 + if (listeners[i] === listener) {
34 + listeners.splice(i, 1)
35 + break
36 + }
37 + }
38 + if (!listeners.length) {
39 + unlisten()
40 + }
41 + }
42 + }
43 +
44 + result.get = function (index) {
45 + return raw[index]
46 + }
47 +
48 + result.getLength = function (index) {
49 + return raw.length
50 + }
51 +
52 + result.includes = function (valueOrObs) {
53 + return !!~raw.indexOf(valueOrObs)
54 + }
55 +
56 + result.indexOf = function (valueOrObs) {
57 + return raw.indexOf(valueOrObs)
58 + }
59 +
60 + return result
61 +
62 + // scoped
63 +
64 + function listen () {
65 + if (!live) {
66 + live = true
67 + lazy = true
68 + if (typeof obs === 'function') {
69 + releases.push(obs(onUpdate))
70 + }
71 + rebindAll()
72 + }
73 + }
74 +
75 + function unlisten () {
76 + if (live) {
77 + live = false
78 + while (releases.length) {
79 + releases.pop()()
80 + }
81 + rebindAll()
82 + }
83 + }
84 +
85 + function onUpdate () {
86 + if (update()) {
87 + broadcast()
88 + }
89 + }
90 +
91 + function update () {
92 + var changed = false
93 +
94 + if (items.length !== getLength(obs)) {
95 + changed = true
96 + }
97 +
98 + for (var i = 0, len = getLength(obs); i < len; i++) {
99 + var item = get(obs, i)
100 +
101 + if (typeof item === 'object') {
102 + items[i] = item
103 + raw[i] = lambda(item)
104 + values[i] = resolve(raw[i])
105 + changed = true
106 + rebind(i)
107 + } else if (item !== items[i]) {
108 + items[i] = item
109 + if (!lastValues.has(item)) {
110 + lastValues.set(item, lambda(item))
111 + }
112 + raw[i] = lastValues.get(item)
113 + values[i] = resolve(raw[i])
114 + changed = true
115 + rebind(i)
116 + }
117 + }
118 +
119 + if (changed) {
120 + // clean up cache
121 + Array.from(lastValues.keys()).filter(notIncluded, obs).forEach(deleteEntry, lastValues)
122 + items.length = getLength(obs)
123 + }
124 +
125 + return changed
126 + }
127 +
128 + function rebind (index) {
129 + if (watches[index]) {
130 + watches[index]()
131 + watches[index] = null
132 + }
133 +
134 + if (live) {
135 + if (typeof raw[index] === 'function') {
136 + watches[index] = updateValue(raw[index], index)
137 + }
138 + }
139 + }
140 +
141 + function rebindAll () {
142 + for (var i = 0; i < raw.length; i++) {
143 + rebind(i)
144 + }
145 + }
146 +
147 + function updateValue (obs, index) {
148 + return obs(function (value) {
149 + if (values[index] !== value || typeof value === 'object') {
150 + values[index] = value
151 + broadcast()
152 + }
153 + })
154 + }
155 +
156 + function broadcast () {
157 + for (var i = 0, len = listeners.length; i < len; i++) {
158 + listeners[i](values)
159 + }
160 + }
161 +
162 + function getValue () {
163 + if (!live || lazy) {
164 + lazy = false
165 + update()
166 + }
167 + return values
168 + }
169 +}
170 +
171 +function get (target, index) {
172 + if (typeof target === 'function' && !target.get) {
173 + target = target()
174 + }
175 +
176 + if (Array.isArray(target)) {
177 + return target[index]
178 + } else if (target && target.get) {
179 + return target.get(index)
180 + }
181 +}
182 +
183 +function getLength (target) {
184 + if (typeof target === 'function' && !target.getLength) {
185 + target = target()
186 + }
187 +
188 + if (Array.isArray(target)) {
189 + return target.length
190 + } else if (target && target.get) {
191 + return target.getLength()
192 + }
193 +
194 + return 0
195 +}
196 +
197 +function notIncluded (value) {
198 + if (typeof this === 'function' && !value.includes) {
199 + var array = this()
200 + if (array && array.includes) {
201 + array.includes(value)
202 + }
203 + } else {
204 + return !this.includes(value)
205 + }
206 +}
207 +
208 +function deleteEntry (entry) {
209 + this.delete(entry)
210 +}
package.jsonView
@@ -1,0 +1,17 @@
1 +{
2 + "name": "@mmckegg/mutant",
3 + "version": "0.0.0",
4 + "description": "",
5 + "main": "array.js",
6 + "directories": {
7 + "test": "test"
8 + },
9 + "scripts": {
10 + "test": "echo \"Error: no test specified\" && exit 1"
11 + },
12 + "author": "",
13 + "license": "ISC",
14 + "dependencies": {
15 + "browser-split": "0.0.1"
16 + }
17 +}
resolve.jsView
@@ -1,0 +1,5 @@
1 +module.exports = resolve
2 +
3 +function resolve (source) {
4 + return typeof source === 'function' ? source() : source
5 +}
send.jsView
@@ -1,0 +1,18 @@
1 +module.exports = Send
2 +
3 +function Send (fn, data, opts) {
4 + return {
5 + fn: fn,
6 + data: data,
7 + opts: opts,
8 + event: null,
9 + handleEvent: handleEvent
10 + }
11 +}
12 +
13 +function handleEvent (e) {
14 + e.stopPropagation()
15 + e.preventDefault()
16 + this.event = e
17 + this.fn(this.data)
18 +}
set.jsView
@@ -1,0 +1,107 @@
1 +var Value = require('./value')
2 +
3 +module.exports = Set
4 +
5 +function Set (defaultValues) {
6 + var object = []
7 + var sources = []
8 + var releases = []
9 +
10 + if (defaultValues && defaultValues.length) {
11 + defaultValues.forEach(function (valueOrObs) {
12 + if (!~sources.indexOf(valueOrObs)) {
13 + sources.push(valueOrObs)
14 + releases[sources.length - 1] = typeof valueOrObs === 'function'
15 + ? valueOrObs(refresh)
16 + : null
17 + }
18 + })
19 + update()
20 + }
21 +
22 + var observable = Value(object)
23 + var broadcast = observable.set
24 +
25 + observable.add = function (valueOrObs) {
26 + if (!~sources.indexOf(valueOrObs)) {
27 + sources.push(valueOrObs)
28 + releases[sources.length - 1] = typeof valueOrObs === 'function'
29 + ? valueOrObs(refresh)
30 + : null
31 + refresh()
32 + }
33 + }
34 +
35 + observable.clear = function () {
36 + releases.forEach(tryInvoke)
37 + sources.length = 0
38 + releases.length = 0
39 + refresh()
40 + }
41 +
42 + observable.delete = function (valueOrObs) {
43 + var index = sources.indexOf(valueOrObs)
44 + if (~index) {
45 + sources.splice(index, 1)
46 + releases.splice(index, 1).forEach(tryInvoke)
47 + refresh()
48 + }
49 + }
50 +
51 + observable.has = function (valueOrObs) {
52 + return !!~object.indexOf(valueOrObs)
53 + }
54 +
55 + observable.set = function (values) {
56 + sources.length = 0
57 + values.forEach(function (value) {
58 + sources.push(value)
59 + })
60 + refresh()
61 + }
62 +
63 + observable.destroy = observable.clear
64 +
65 + return observable
66 +
67 + function refresh () {
68 + update()
69 + broadcast(object)
70 + }
71 +
72 + function update () {
73 + var currentValues = object.map(get)
74 + var newValues = sources.map(resolve)
75 + currentValues.filter(notIncluded, newValues).forEach(removeFrom, object)
76 + newValues.filter(notIncluded, currentValues).forEach(addTo, object)
77 + }
78 +}
79 +
80 +function get (value) {
81 + return value
82 +}
83 +
84 +function resolve (source) {
85 + return typeof source === 'function' ? source() : source
86 +}
87 +
88 +function notIncluded (value) {
89 + return !~this.indexOf(value)
90 +}
91 +
92 +function removeFrom (item) {
93 + var index = this.indexOf(item)
94 + if (~index) {
95 + this.splice(index, 1)
96 + }
97 +}
98 +
99 +function addTo (item) {
100 + this.push(item)
101 +}
102 +
103 +function tryInvoke (func) {
104 + if (typeof func === 'function') {
105 + func()
106 + }
107 +}
struct.jsView
@@ -1,0 +1,70 @@
1 +var Value = require('./value')
2 +
3 +module.exports = Struct
4 +
5 +var blackList = {
6 + 'length': 'Clashes with `Function.prototype.length`.\n',
7 + 'name': 'Clashes with `Function.prototype.name`\n',
8 + 'destroy': '`destroy` is a reserved key of struct\n'
9 +}
10 +
11 +function Struct (properties) {
12 + var object = Object.create({})
13 + var observable = Value(object)
14 + var broadcast = observable.set
15 + var keys = Object.keys(properties)
16 + var suspendBroadcast = false
17 +
18 + var releases = keys.forEach(function (key) {
19 + if (blackList.hasOwnProperty(key)) {
20 + throw new Error("Cannot create a struct with a key named '" + key + "'.\n" + blackList[key])
21 + }
22 +
23 + var obs = typeof properties[key] === 'function'
24 + ? properties[key]
25 + : Value(properties[key])
26 +
27 + object[key] = obs()
28 + observable[key] = obs
29 +
30 + return obs(function (val) {
31 + object[key] = val
32 + if (!suspendBroadcast) {
33 + broadcast(object)
34 + }
35 + })
36 + })
37 +
38 + observable.destroy = function () {
39 + while (releases.length) {
40 + releases.pop()()
41 + }
42 + }
43 +
44 + observable.set = function (values) {
45 + var lastValue = suspendBroadcast
46 + suspendBroadcast = true
47 + values = values || {}
48 +
49 + // update inner observables
50 + keys.forEach(function (key) {
51 + if (observable[key]() !== values[key]) {
52 + observable[key].set(values[key])
53 + }
54 + })
55 +
56 + // store additional keys (but don't create observables)
57 + Object.keys(values).forEach(function (key) {
58 + if (!(key in properties)) {
59 + object[key] = values[key]
60 + }
61 + })
62 +
63 + suspendBroadcast = lastValue
64 + if (!suspendBroadcast) {
65 + broadcast(object)
66 + }
67 + }
68 +
69 + return observable
70 +}
test/dom.jsView
@@ -1,0 +1,44 @@
1 +var Struct = require('../struct')
2 +var send = require('../send')
3 +var h = require('../html-element')
4 +
5 +var state = Struct({
6 + text: 'Test',
7 + color: 'red',
8 + value: 0
9 +})
10 +
11 +var element = h('div.cool', {
12 + class: ['cool', state.text],
13 + style: {
14 + 'background-color': state.color
15 + }
16 +}, [
17 + h('div', [
18 + state.text, ' ', state.value, ' ', h('strong', 'test')
19 + ]),
20 + h('div', [
21 + h('button', {
22 + 'ev-click': send(state.color.set, 'blue')
23 + }, 'Change color')
24 + ])
25 +])
26 +
27 +setTimeout(function () {
28 + state.text.set('Another value')
29 +}, 5000)
30 +
31 +setInterval(function () {
32 + state.value.set(state.value() + 1)
33 +}, 1000)
34 +
35 +setInterval(function () {
36 + // bulk update state
37 + state.set({
38 + text: 'Retrieved from server (not really)',
39 + color: '#FFEECC',
40 + value: 1337
41 + })
42 +}, 10000)
43 +
44 +document.body.appendChild(element)
test/test-array.jsView
@@ -1,0 +1,17 @@
1 +var Array = require('../array')
2 +var Value = require('../value')
3 +
4 +var value = Value('human')
5 +var array = Array(['cat'])
6 +array(x => console.log(x))
7 +
8 +array.push('dog')
9 +array.push('cow')
10 +array.push(value)
11 +array.push('chicken')
12 +console.log('shift =>', array.shift())
13 +array.push('wolf')
14 +array.insert('sheep', 0)
15 +console.log('pop =>', array.pop())
16 +
17 +value.set('monkey')
test/test-map.jsView
@@ -1,0 +1,26 @@
1 +var Array = require('../array')
2 +var Map = require('../map')
3 +var Value = require('../value')
4 +var computed = require('../computed')
5 +
6 +var value = Value('human')
7 +var array = Array(['cat'])
8 +var map = Map(array, function (obj) {
9 + if (typeof obj === 'function') {
10 + console.log('mapping => obs')
11 + return computed([obj], (x) => x + ' [dynamic]')
12 + } else {
13 + console.log('mapping => ', obj)
14 + return obj + ' [static]'
15 + }
16 +})
17 +
18 +map(x => console.log(x))
19 +
20 +array.push('dog')
21 +array.push('cow')
22 +array.push(value)
23 +array.push('chicken')
24 +array.push('wolf')
25 +array.insert('sheep', 0)
26 +value.set('monkey')
test/test-set.jsView
@@ -1,0 +1,17 @@
1 +var Set = require('../set')
2 +var Value = require('../value')
3 +
4 +var set = Set()
5 +var value = Value('ducks')
6 +set(x => console.log(x))
7 +
8 +set.add('cats')
9 +set.add('dogs')
10 +set.add('logs')
11 +set.add('cats')
12 +set.add(value)
13 +
14 +value.set('cows')
15 +
16 +set.delete('dogs')
17 +set.set(['dogs', 'logs', value])
test/test-struct.jsView
@@ -1,0 +1,28 @@
1 +var Struct = require('../struct')
2 +
3 +var struct = Struct({
4 + a: 'Hello',
5 + b: 'You',
6 + c: 123,
7 + d: Struct({
8 + tinker: 'value'
9 + })
10 +})
11 +
12 +console.log(struct())
13 +struct(x => console.log(x))
14 +struct.a(x => console.log('a =>', x))
15 +struct.b(x => console.log('b =>', x))
16 +struct.c(x => console.log('c =>', x))
17 +struct.d(x => console.log('d =>', x))
18 +
19 +struct.b.set('Cat')
20 +
21 +struct.set({
22 + a: 'Hello',
23 + b: 'Cat',
24 + c: 123,
25 + d: {
26 + tinker: 456
27 + }
28 +})
test/test-varhash.jsView
@@ -1,0 +1,15 @@
1 +var Map = require('../map')
2 +var Value = require('../value')
3 +
4 +var value = Value('blah')
5 +var map = Map({cat: 'meow'})
6 +map(x => console.log(x))
7 +
8 +map.put('dog', 'woof')
9 +map.put('sheep', 'baa')
10 +map.put('cow', 'moo')
11 +map.put('human', value)
12 +map.delete('sheep')
13 +console.log('keys =>', map.keys())
14 +map.set({ cat: 'meow', dog: 'woof', sheep: 'baa', human: value })
15 +map.clear()
transforms/flatten.jsView
transforms/lookup.jsView
transforms/sort.jsView
transforms/uniq.jsView
@@ -1,0 +1,49 @@
1 +var Value = require('./value')
2 +
3 +module.exports = Uniq
4 +
5 +function Uniq (obs) {
6 + var object = []
7 + update()
8 +
9 + var observable = Value(object)
10 + var broadcast = observable.set
11 + obs(refresh)
12 +
13 + observable.has = function (valueOrObs) {
14 + return !!~object.indexOf(valueOrObs)
15 + }
16 +
17 + return observable
18 +
19 + function update () {
20 + var currentValues = object.map(get)
21 + var newValues = obs()
22 + currentValues.filter(notIncluded, newValues).forEach(removeFrom, object)
23 + newValues.filter(notIncluded, currentValues).forEach(addTo, object)
24 + }
25 +
26 + function refresh () {
27 + update()
28 + broadcast(object)
29 + }
30 +}
31 +
32 +function get (value) {
33 + return value
34 +}
35 +
36 +function notIncluded (value) {
37 + return !~this.indexOf(value)
38 +}
39 +
40 +function removeFrom (item) {
41 + var index = this.indexOf(item)
42 + if (~index) {
43 + this.splice(index, 1)
44 + }
45 +}
46 +
47 +function addTo (item) {
48 + this.push(item)
49 +}
value.jsView
@@ -1,0 +1,37 @@
1 +module.exports = Observable
2 +
3 +function Observable (value) {
4 + var listeners = []
5 + value = value === undefined ? null : value
6 +
7 + observable.set = function (v) {
8 + value = v
9 +
10 + for (var i = 0, len = listeners.length; i < len; i++) {
11 + listeners[i](v)
12 + }
13 + }
14 +
15 + return observable
16 +
17 + function observable (listener) {
18 + if (!listener) {
19 + return value
20 + }
21 +
22 + if (typeof listener !== 'function') {
23 + throw new Error('Listeners must be functions.')
24 + }
25 +
26 + listeners.push(listener)
27 +
28 + return function remove () {
29 + for (var i = 0, len = listeners.length; i < len; i++) {
30 + if (listeners[i] === listener) {
31 + listeners.splice(i, 1)
32 + break
33 + }
34 + }
35 + }
36 + }
37 +}
varhash.jsView
@@ -1,0 +1,100 @@
1 +var Value = require('./value')
2 +
3 +module.exports = Varhash
4 +
5 +function Varhash (defaultValues) {
6 + var object = Object.create({})
7 + var sources = []
8 + var releases = []
9 +
10 + if (defaultValues) {
11 + Object.keys(defaultValues).forEach(function (key) {
12 + put(key, defaultValues[key])
13 + })
14 + }
15 +
16 + var observable = Value(object)
17 + var broadcast = observable.set
18 +
19 + observable.put = function (key, valueOrObs) {
20 + put(key, valueOrObs)
21 + broadcast(object)
22 + }
23 +
24 + observable.get = function (key) {
25 + return sources[key]
26 + }
27 +
28 + observable.keys = function () {
29 + return Object.keys(sources)
30 + }
31 +
32 + observable.clear = function () {
33 + Object.keys(sources).forEach(function (key) {
34 + tryInvoke(releases[key])
35 + delete sources[key]
36 + delete releases[key]
37 + delete object[key]
38 + })
39 + broadcast(object)
40 + }
41 +
42 + observable.delete = function (key) {
43 + tryInvoke(releases[key])
44 + delete sources[key]
45 + delete releases[key]
46 + delete object[key]
47 + broadcast(object)
48 + }
49 +
50 + observable.includes = function (valueOrObs) {
51 + return !!~object.indexOf(valueOrObs)
52 + }
53 +
54 + observable.set = function (values) {
55 + Object.keys(sources).forEach(function (key) {
56 + tryInvoke(releases[key])
57 + delete sources[key]
58 + delete releases[key]
59 + delete object[key]
60 + })
61 +
62 + Object.keys(values).forEach(function (key) {
63 + put(key, values[key])
64 + })
65 +
66 + broadcast(object)
67 + }
68 +
69 + observable.destroy = observable.clear
70 +
71 + return observable
72 +
73 + // scoped
74 +
75 + function put (key, valueOrObs) {
76 + tryInvoke(releases[key])
77 + sources[key] = valueOrObs
78 + releases[key] = bind(key, valueOrObs)
79 + object[key] = resolve(valueOrObs)
80 + }
81 +
82 + function bind (key, valueOrObs) {
83 + return typeof valueOrObs === 'function' ? valueOrObs(update.bind(this, key)) : null
84 + }
85 +
86 + function update (key, value) {
87 + object[key] = value
88 + broadcast(object)
89 + }
90 +}
91 +
92 +function resolve (source) {
93 + return typeof source === 'function' ? source() : source
94 +}
95 +
96 +function tryInvoke (func) {
97 + if (typeof func === 'function') {
98 + func()
99 + }
100 +}
watch.jsView
@@ -1,0 +1,14 @@
1 +module.exports = watch
2 +
3 +function watch (observable, listener) {
4 + if (typeof observable === 'function') {
5 + var remove = observable(listener)
6 + listener(observable())
7 + return remove
8 + } else {
9 + listener(observable)
10 + return noop
11 + }
12 +}
13 +
14 +function noop () {}

Built with git-ssb-web