Merge branch 'rewrite' into multi-mount-test

This commit is contained in:
Barney Carroll 2016-07-18 09:02:49 +01:00
commit e43d638f54
15 changed files with 515 additions and 194 deletions

View file

@ -6,7 +6,7 @@ module.exports = function(root, renderer, pubsub, callback) {
var run = throttle(callback)
if (renderer != null) {
renderer.setEventCallback(function(e) {
if (e.redraw !== false) run()
if (e.redraw !== false) pubsub.publish()
})
}

View file

@ -23,7 +23,7 @@ module.exports = function($window, renderer, pubsub) {
renderer.render(root, Node(payload, null, args, undefined, undefined, undefined))
}
}, function() {
router.setPath(defaultRoute)
router.setPath(defaultRoute, null, {replace: true})
})
autoredraw(root, renderer, pubsub, replay)
}
@ -31,6 +31,6 @@ module.exports = function($window, renderer, pubsub) {
route.prefix = router.setPrefix
route.set = router.setPath
route.get = router.getPath
return route
}

View file

@ -49,6 +49,45 @@ o.spec("route", function() {
})
})
o("default route doesn't break back button", function(done) {
$window.location.href = "http://google.com"
route(root, "/a", {
"/a" : {
view: function() {
return m("div")
}
}
})
setTimeout(function() {
o(root.firstChild.nodeName).equals("DIV")
$window.history.back()
o($window.location.pathname).equals("/")
done()
}, FRAME_BUDGET)
})
o("default route does not inherit params", function(done) {
$window.location.href = "/invalid?foo=bar"
route(root, "/a", {
"/a" : {
oninit: init,
view: function() {
return m("div")
}
}
})
function init(vnode) {
o(vnode.attrs).deepEquals({})
done()
}
})
o("redraws when render function is executed", function(done) {
var onupdate = o.spy()
var oninit = o.spy()

View file

@ -4,6 +4,7 @@
- [Static members](#static-members)
- [prop.combine](#prop-combine)
- [prop.reject](#prop-reject)
- [prop.merge](#prop-merge)
- [prop.HALT](#prop-halt)
- [Instance members](#static-members)
- [stream.run](#stream-run)
@ -85,6 +86,19 @@ Argument | Type | Required | Description
[How to read signatures](signatures.md)
##### prop.merge
Creates a stream whose value is the array of values from an array of streams
`stream = m.prop.merge(streams)`
Argument | Type | Required | Description
------------ | -------------------- | -------- | ---
`streams` | `Array<Stream>` | Yes | A list of streams
**returns** | `Stream` | | Returns a stream whose value is an array of input stream values
[How to read signatures](signatures.md)
##### prop.HALT
A special value that can be returned to stream callbacks to halt execution of downstreams

View file

@ -11,6 +11,7 @@
- [Reading/writing the current route](#readingwriting-the-current-route)
- [Accessing route params](#accessing-route-params)
- [Setting route prefix](#setting-route-prefix)
- [m.request](#mrequest)
## `config` function
@ -270,3 +271,57 @@ m.route.mode = "pathname";
```js
m.route.prefix("");
```
## m.request
[m.request](request.md) now returns an [m.prop stream](prop.md) instead of a promise. The main difference is you'll have to use `.run` to get similar functionality as a promise's `.then`:
### `v0.2.x`
```js
m.request({ method: 'GET', url: 'https://api.github.com/' })
.then(function (responseBody) {
return m.request({ method: 'GET', url: responseBody.emojis_url });
})
.then(function (emojis) {
console.log("+1 url:", emojis['+1']);
});
```
### `v1.x`
```js
m.request({ method: 'GET', url: 'https://api.github.com/' })
.run(function (responseBody) {
return m.request({ method: 'GET', url: responseBody.emojis_url });
})
.run(function (emojis) {
console.log("+1 url:", emojis['+1']);
});
```
The equivalent of `m.sync` is now `m.prop.sync`:
### `v0.2.x`
```js
m.sync([
m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }),
m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }),
])
.then(function (users) {
console.log("Contributors:", users[0].name, "and", users[1].name);
});
```
### `v1.x`
```js
m.prop.sync([
m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }),
m.request({ method: 'GET', url: 'https://api.github.com/users/isiahmeadows' }),
])
.run(function (users) {
console.log("Contributors:", users[0].name, "and", users[1].name);
});
```

View file

@ -84,7 +84,7 @@ The `tag` property of a vnode determines its type. There are five vnode types:
Vnode type | Example | Description
------------ | ------------------------------ | ---
Element | `{tag: "div"}` | Represents a DOM element.
Fragment | `{tag: "[", children: []}` | Represents a list of DOM elements whose parent DOM element may also contain other elements that are not in the fragment.
Fragment | `{tag: "[", children: []}` | Represents a list of DOM elements whose parent DOM element may also contain other elements that are not in the fragment. When using the [`m()`](hyperscript.md) helper function, fragment vnodes can only be created by nesting arrays into the `children` parameter of `m()`. `m("[")` does not create a valid vnode.
Text | `{tag: "#", children: ""}` | Represents a DOM text node.
Trusted HTML | `{tag: "<", children: "<br>"}` | Represents a list of DOM elements from an HTML string.
Component | `{tag: ExampleComponent}` | If `tag` is a Javascript object with a `view` method, the vnode represents the DOM generated by rendering the component.

View file

@ -19,6 +19,7 @@ m.trust = require("./render/trust")
m.prop = Stream.stream
m.prop.combine = Stream.combine
m.prop.reject = Stream.reject
m.prop.merge = Stream.merge
m.prop.HALT = Stream.HALT
m.withAttr = require("./util/withAttr")
m.render = renderService.render

View file

@ -7,18 +7,15 @@ function createStream() {
return stream._state.value
}
initStream(stream, arguments)
if (arguments.length > 0) updateStream(stream, arguments[0], undefined)
return stream
}
function initStream(stream, args) {
stream.constructor = createStream
stream._state = {id: guid++, value: undefined, error: undefined, state: 0, derive: undefined, recover: undefined, deps: {}, parents: [], errorStream: undefined, endStream: undefined}
stream.map = map, stream.ap = ap, stream.of = createStream
stream.valueOf = valueOf, stream.toJSON = toJSON
stream.valueOf = valueOf, stream.toJSON = toJSON, stream.toString = valueOf
stream.run = run, stream.catch = doCatch
Object.defineProperties(stream, {
error: {get: function() {
if (!stream._state.errorStream) {
@ -58,7 +55,10 @@ function updateState(stream, value, error) {
if (recovered === HALT) return
updateValues(stream, recovered, undefined)
}
catch (e) {updateValues(stream, undefined, e)}
catch (e) {
updateValues(stream, undefined, e)
reportUncaughtError(stream, e)
}
}
else updateValues(stream, value, error)
stream._state.changed = true
@ -81,6 +81,7 @@ function updateDependency(stream, mustSync) {
}
catch (e) {
updateState(stream, undefined, e)
reportUncaughtError(stream, e)
}
}
}
@ -96,6 +97,13 @@ function finalize(stream) {
stream._state.changed = false
for (var id in stream._state.deps) stream._state.deps[id]._state.changed = false
}
function reportUncaughtError(stream, e) {
if (Object.keys(stream._state.deps).length === 0) {
setTimeout(function() {
if (Object.keys(stream._state.deps).length === 0) console.error(e)
}, 0)
}
}
function run(fn) {
var self = createStream(), stream = this
return initDependency(self, [stream], function() {
@ -130,10 +138,8 @@ function initDependency(dep, streams, derive, recover) {
state.derive = derive
state.recover = recover
state.parents = streams.filter(notEnded)
registerDependency(dep, state.parents)
updateDependency(dep, true)
return dep
}
function registerDependency(stream, parents) {
@ -168,7 +174,12 @@ function reject(e) {
stream.error(e)
return stream
}
var Stream = {stream: createStream, combine: combine, reject: reject, HALT: HALT}
function merge(streams) {
return combine(function () {
return streams.map(function (s) {return s()})
}, streams)
}
var Stream = {stream: createStream, merge: merge, combine: combine, reject: reject, HALT: HALT}
function Node(tag, key, attrs, children, text, dom) {
return {tag: tag, key: key, attrs: attrs, children: children, text: text, dom: dom, domSize: undefined, state: {}, events: undefined, instance: undefined}
}
@ -328,10 +339,13 @@ var renderService = function($window) {
if (vnode.instance != null) {
var element = createNode(vnode.instance, hooks, ns)
vnode.dom = vnode.instance.dom
vnode.domSize = vnode.instance.domSize
vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0
return element
}
else return $emptyFragment
else {
vnode.domSize = 0
return $emptyFragment
}
}
//update
function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) {
@ -341,12 +355,12 @@ var renderService = function($window) {
else {
var recycling = isRecyclable(old, vnodes)
if (recycling) old = old.concat(old.pool)
var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map
while (oldEnd >= oldStart && end >= start) {
var o = old[oldStart], v = vnodes[start]
if (o === v) oldStart++, start++
else if (o != null && v != null && o.key === v.key) {
else if (o != null && v != null && o.key === v.key && o.tag === v.tag) {
oldStart++, start++
updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), recycling, ns)
if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
@ -354,7 +368,7 @@ var renderService = function($window) {
else {
var o = old[oldEnd]
if (o === v) oldEnd--, start++
else if (o != null && v != null && o.key === v.key) {
else if (o != null && v != null && o.key === v.key && o.tag === v.tag) {
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling))
oldEnd--, start++
@ -365,10 +379,10 @@ var renderService = function($window) {
while (oldEnd >= oldStart && end >= start) {
var o = old[oldEnd], v = vnodes[end]
if (o === v) oldEnd--, end--
else if (o != null && v != null && o.key === v.key) {
else if (o != null && v != null && o.key === v.key && o.tag === v.tag) {
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
nextSibling = o.dom
if (o.dom != null) nextSibling = o.dom
oldEnd--, end--
}
else {
@ -380,7 +394,7 @@ var renderService = function($window) {
updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
insertNode(parent, toFragment(movable), nextSibling)
old[oldIndex].skip = true
nextSibling = movable.dom
if (movable.dom != null) nextSibling = movable.dom
}
else {
var dom = createNode(v, hooks, undefined)
@ -440,7 +454,7 @@ var renderService = function($window) {
if (children != null) {
for (var i = 0; i < children.length; i++) {
var child = children[i]
if (child != null) {
if (child != null && child.dom != null) {
if (vnode.dom == null) vnode.dom = child.dom
domSize += child.domSize || 1
}
@ -479,7 +493,12 @@ var renderService = function($window) {
}
else if (old.instance != null) {
removeNode(parent, old.instance, null, false)
vnode.dom = vnode.domSize = undefined
vnode.dom = undefined
vnode.domSize = 0
}
else {
vnode.dom = old.dom
vnode.domSize = old.domSize
}
}
function isRecyclable(old, vnodes) {
@ -506,7 +525,7 @@ var renderService = function($window) {
}
function toFragment(vnode) {
var count = vnode.domSize
if (count != null) {
if (count != null || vnode.dom == null) {
var fragment = $doc.createDocumentFragment()
if (count > 0) {
var dom = vnode.dom
@ -519,7 +538,7 @@ var renderService = function($window) {
}
function getNextSibling(vnodes, i, nextSibling) {
for (; i < vnodes.length; i++) {
if (vnodes[i] != null) return vnodes[i].dom
if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom
}
return nextSibling
}
@ -572,11 +591,14 @@ var renderService = function($window) {
function onremove(vnode) {
if (vnode.attrs && vnode.attrs.onremove) vnode.attrs.onremove.call(vnode.state, vnode)
if (typeof vnode.tag !== "string" && vnode.tag.onremove) vnode.tag.onremove.call(vnode.state, vnode)
var children = vnode.children
if (children instanceof Array) {
for (var i = 0; i < children.length; i++) {
var child = children[i]
if (child != null) onremove(child)
if (vnode.instance != null) onremove(vnode.instance)
else {
var children = vnode.children
if (children instanceof Array) {
for (var i = 0; i < children.length; i++) {
var child = children[i]
if (child != null) onremove(child)
}
}
}
}
@ -699,12 +721,12 @@ var renderService = function($window) {
function copy(data) {
if (data instanceof Array) {
var output = []
for (var i = 0; i < data.length; i++) output[i] = copy(data[i])
for (var i = 0; i < data.length; i++) output[i] = data[i]
return output
}
else if (typeof data === "object") {
var output = {}
for (var i in data) output[i] = copy(data[i])
for (var i in data) output[i] = data[i]
return output
}
return data
@ -1030,7 +1052,7 @@ var autoredraw = function(root, renderer, pubsub, callback) {
var run = throttle(callback)
if (renderer != null) {
renderer.setEventCallback(function(e) {
if (e.redraw !== false) run()
if (e.redraw !== false) pubsub.publish()
})
}
if (pubsub != null) {
@ -1058,7 +1080,7 @@ m.route = function($window, renderer, pubsub) {
renderer.render(root, Node(payload, null, args, undefined, undefined, undefined))
}
}, function() {
router.setPath(defaultRoute)
router.setPath(defaultRoute, null, {replace: true})
})
autoredraw(root, renderer, pubsub, replay)
}
@ -1083,6 +1105,7 @@ m.trust = function(html) {
m.prop = Stream.stream
m.prop.combine = Stream.combine
m.prop.reject = Stream.reject
m.prop.merge = Stream.merge
m.prop.HALT = Stream.HALT
m.withAttr = function(attrName, callback, context) {
return function(e) {

38
mithril.min.js vendored Normal file
View file

@ -0,0 +1,38 @@
(function(){function y(){function a(){0<arguments.length&&G(a,arguments[0],void 0);return a._state.value}M(a,arguments);0<arguments.length&&G(a,arguments[0],void 0);return a}function M(a,b){a.constructor=y;a._state={id:Y++,value:void 0,error:void 0,state:0,derive:void 0,recover:void 0,deps:{},parents:[],errorStream:void 0,endStream:void 0};a.map=Z;a.ap=aa;a.of=y;a.valueOf=N;a.toJSON=ba;a.toString=N;a.run=ca;a["catch"]=da;Object.defineProperties(a,{error:{get:function(){if(!a._state.errorStream){var b=
function(){0<arguments.length&&G(a,void 0,arguments[0]);return a._state.error};M(b,[]);C(b,[a],O,O);a._state.errorStream=b}return a._state.errorStream}},end:{get:function(){if(!a._state.endStream){var b=y();b.map(function(e){!0===e&&(P(a),P(b));return e});a._state.endStream=b}return a._state.endStream}}})}function G(a,b,h){D(a,b,h);for(var e in a._state.deps)Q(a._state.deps[e],!1);a._state.changed=!1;for(var f in a._state.deps)a._state.deps[f]._state.changed=!1}function D(a,b,h){h=R(b,h);if(void 0!==
h&&"function"===typeof a._state.recover)try{var e=a._state.recover();if(e===E)return;a._state.value=e;a._state.error=void 0}catch(f){a._state.value=void 0,a._state.error=f}else a._state.value=b,a._state.error=h;a._state.changed=!0;2!==a._state.state&&(a._state.state=1)}function Q(a,b){var h=a._state,e=h.parents;if(0<e.length&&e.filter(ea).length===e.length&&(b||0<e.filter(S).length))if(e=e.filter(T),0<e.length)D(a,void 0,e[0]._state.error);else try{var f=h.derive();f!==E&&D(a,f,void 0)}catch(k){D(a,
void 0,k)}}function R(a,b){null!=a&&a.constructor===y&&(b=void 0!==a._state.error?a._state.error:R(a._state.value,a._state.error));return b}function ca(a){var b=y(),h=this;return C(b,[h],function(){return U(b,a(h()))},void 0)}function da(a){var b=y(),h=this;return C(b,[h],function(){return h._state.value},function(){return U(b,a(h._state.error))})}function H(a,b){return C(y(),b,function(){var h=b.filter(T);if(0<h.length)throw h[0]._state.error;return a.apply(this,b.concat([b.filter(S)]))},void 0)}
function U(a,b){if(null!=b&&b.constructor===y){b.error.map(a.error);b.map(a);if(0===b._state.state)return E;if(b._state.error)throw b._state.error;b=b._state.value}return b}function C(a,b,h,e){var f=a._state;f.derive=h;f.recover=e;f.parents=b.filter(fa);V(a,f.parents);Q(a,!0);return a}function V(a,b){for(var h=0;h<b.length;h++)b[h]._state.deps[a._state.id]=a,V(a,b[h]._state.parents)}function P(a){for(var b=0;b<a._state.parents.length;b++)delete a._state.parents[b]._state.deps[a._state.id];for(var h in a._state.deps){var b=
a._state.deps[h],e=b._state.parents.indexOf(a);-1<e&&b._state.parents.splice(e,1)}a._state.state=2;a._state.deps={}}function Z(a){return H(function(b){return a(b())},[this])}function aa(a){return H(function(a,h){return a()(h())},[this,a])}function N(){return this._state.value}function ba(){return JSON.stringify(this._state.value)}function ea(a){return 1===a._state.state}function S(a){return a._state.changed}function fa(a){return 2!==a._state.state}function T(a){return a._state.error}function r(a,
b,h,e,f,k){return{tag:a,key:b,attrs:h,children:e,text:f,dom:k,domSize:void 0,state:{},events:void 0,instance:void 0}}var Y=0,O=function(){},E={},z={stream:y,combine:H,reject:function(a){var b=y();b.error(a);return b},HALT:E};r.normalize=function(a){return a instanceof Array?r("[",void 0,void 0,r.normalizeChildren(a),void 0,void 0):null!=a&&"object"!==typeof a?r("#",void 0,void 0,a,void 0,void 0):a};r.normalizeChildren=function(a){for(var b=0;b<a.length;b++)a[b]=r.normalize(a[b]);return a};var ga=
/(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g,I={},n=function(a){if("string"===typeof a&&void 0===I[a]){for(var b,h,e=[],f={};b=ga.exec(a);){var k=b[1],l=b[2];""===k&&""!==l?h=l:"#"===k?f.id=l:"."===k?e.push(l):"["===b[3][0]&&((k=b[6])&&(k=k.replace(/\\(["'])/g,"$1").replace(/\\\\/g,"\\")),f[b[4]]=k||!0)}0<e.length&&(f.className=e.join(" "));I[a]=function(a,b){var d=!1,e,k,l=a.className||a["class"],w;for(w in f)a[w]=f[w];void 0!==l&&(void 0!==a["class"]&&(a["class"]=
void 0,a.className=l),void 0!==f.className&&(a.className=f.className+" "+l));for(w in a)if("key"!==w){d=!0;break}b instanceof Array&&1==b.length&&null!=b[0]&&"#"===b[0].tag?k=b[0].children:e=b;return r(h||"div",a.key,d?a:void 0,e,k,void 0)}}var d;null!=arguments[1]&&("object"!==typeof arguments[1]||void 0!==arguments[1].tag||arguments[1]instanceof Array)?e=1:(d=arguments[1],e=2);if(arguments.length===e+1)b=arguments[e]instanceof Array?arguments[e]:[arguments[e]];else for(b=[];e<arguments.length;e++)b.push(arguments[e]);
return"string"===typeof a?I[a](d||{},r.normalizeChildren(b)):r(a,d&&d.key,d||{},r.normalizeChildren(b),void 0,void 0)},J=function(a){function b(a,c,g,b,d,e,f){for(;g<b;g++){var k=c[g];null!=k&&m(a,h(k,d,f),e)}}function h(a,c,g){var d=a.tag;null!=a.attrs&&n(a.attrs,a,c);if("string"===typeof d)switch(d){case "#":return a.dom=q.createTextNode(a.children);case "<":return e(a);case "[":var m=q.createDocumentFragment();null!=a.children&&(d=a.children,b(m,d,0,d.length,c,null,g));a.dom=m.firstChild;a.domSize=
m.childNodes.length;return m;default:var f=a.tag;switch(a.tag){case "svg":g="http://www.w3.org/2000/svg";break;case "math":g="http://www.w3.org/1998/Math/MathML"}var k=(d=a.attrs)&&d.is,f=g?k?q.createElementNS(g,f,k):q.createElementNS(g,f):k?q.createElement(f,k):q.createElement(f);a.dom=f;if(null!=d)for(m in k=g,d)B(a,m,null,d[m],k);null!=a.text&&(""!==a.text?f.textContent=a.text:a.children=[r("#",void 0,void 0,a.text,void 0,void 0)]);null!=a.children&&(m=a.children,b(f,m,0,m.length,c,null,g),c=a.attrs,
"select"===a.tag&&null!=c&&("value"in c&&B(a,"value",null,c.value,void 0),"selectedIndex"in c&&B(a,"selectedIndex",null,c.selectedIndex,void 0)));return f}else return a.state=z(a.tag),n(a.tag,a,c),a.instance=r.normalize(a.tag.view.call(a.state,a)),null!=a.instance?(c=h(a.instance,c,g),a.dom=a.instance.dom,a.domSize=a.instance.domSize,a=c):a=ha,a}function e(a){var c={caption:"table",thead:"table",tbody:"table",tfoot:"table",tr:"tbody",th:"tr",td:"tr",colgroup:"table",col:"colgroup"}[(a.children.match(/^\s*?<(\w+)/im)||
[])[1]]||"div",c=q.createElement(c);c.innerHTML=a.children;a.dom=c.firstChild;a.domSize=c.childNodes.length;a=q.createDocumentFragment();for(var g;g=c.firstChild;)a.appendChild(g);return a}function f(a,c,g,e,f,p){if(null!=c||null!=g)if(null==c)b(a,g,0,g.length,e,f,void 0);else if(null==g)A(a,c,0,c.length,g);else{var t;a:{if(null!=c.pool&&Math.abs(c.pool.length-g.length)<=Math.abs(c.length-g.length)&&(t=g[0]&&g[0].children&&g[0].children.length||0,Math.abs((c.pool[0]&&c.pool[0].children&&c.pool[0].children.length||
0)-t)<=Math.abs((c[0]&&c[0].children&&c[0].children.length||0)-t))){t=!0;break a}t=!1}t&&(c=c.concat(c.pool));for(var B=0,w=0,u=c.length-1,r=g.length-1,n;u>=B&&r>=w;){var v=c[B],x=g[w];if(v===x)B++,w++;else if(null!=v&&null!=x&&v.key===x.key)B++,w++,k(a,v,x,e,d(c,B,f),t,p),t&&v.tag===x.tag&&m(a,l(v),f);else if(v=c[u],v===x)u--,w++;else if(null!=v&&null!=x&&v.key===x.key)k(a,v,x,e,d(c,u+1,f),t,p),m(a,l(v),d(c,B,f)),u--,w++;else break}for(;u>=B&&r>=w;){v=c[u];x=g[r];if(v===x)u--;else if(null!=v&&null!=
x&&v.key===x.key)k(a,v,x,e,d(c,u+1,f),t,p),t&&v.tag===x.tag&&m(a,l(v),f),f=v.dom,u--;else{if(!n){n=c;var v=u,q={},y;for(y=0;y<v;y++){var z=n[y];null!=z&&(z=z.key,null!=z&&(q[z]=y))}n=q}null!=x&&(v=n[x.key],null!=v?(q=c[v],k(a,q,x,e,d(c,u+1,f),t,p),m(a,l(q),f),c[v].skip=!0,f=q.dom):(x=h(x,e,void 0),m(a,x,f),f=x))}r--;if(r<w)break}b(a,g,w,r+1,e,f,void 0);A(a,c,B,u+1,g)}}function k(a,c,g,b,d,A,t){var u=c.tag;if(u===g.tag){g.state=c.state;g.events=c.events;var w;var n;null!=g.attrs&&"function"===typeof g.attrs.onbeforeupdate&&
(w=g.attrs.onbeforeupdate.call(g.state,g,c));"string"!==typeof g.tag&&"function"===typeof g.tag.onbeforeupdate&&(n=g.tag.onbeforeupdate.call(g.state,g,c));void 0===w&&void 0===n||w||n?w=!1:(g.dom=c.dom,g.domSize=c.domSize,g.instance=c.instance,w=!0);if(!w)if(null!=g.attrs&&y(g.attrs,g,b,A),"string"===typeof u)switch(u){case "#":c.children.toString()!==g.children.toString()&&(c.dom.nodeValue=g.children);g.dom=c.dom;break;case "<":c.children!==g.children?(l(c),m(a,e(g),d)):g.dom=c.dom;break;case "[":f(a,
c.children,g.children,b,d,t);c=0;b=g.children;g.dom=null;if(null!=b){for(var q=0;q<b.length;q++)a=b[q],null!=a&&(null==g.dom&&(g.dom=a.dom),c+=a.domSize||1);1!==c&&(g.domSize=c)}break;default:a=t;d=g.dom=c.dom;switch(g.tag){case "svg":a="http://www.w3.org/2000/svg";break;case "math":a="http://www.w3.org/1998/Math/MathML"}"textarea"===g.tag&&(null==g.attrs&&(g.attrs={}),null!=g.text&&(g.attrs.value=g.text));A=c.attrs;t=g.attrs;u=a;if(null!=t)for(q in t)B(g,q,A&&A[q],t[q],u);if(null!=A)for(q in A)null!=
t&&q in t||"key"!==q&&g.dom.removeAttribute(q);null!=c.text&&null!=g.text&&""!==g.text?c.text.toString()!==g.text.toString()&&(c.dom.firstChild.nodeValue=g.text):(null!=c.text&&(c.children=[r("#",void 0,void 0,c.text,void 0,c.dom.firstChild)]),null!=g.text&&(g.children=[r("#",void 0,void 0,g.text,void 0,void 0)]),f(d,c.children,g.children,b,null,a))}else g.instance=r.normalize(g.tag.view.call(g.state,g)),y(g.tag,g,b,A),null!=g.instance?(null==c.instance?m(a,h(g.instance,b,t),d):k(a,c.instance,g.instance,
b,d,A,t),g.dom=g.instance.dom,g.domSize=g.instance.domSize):null!=c.instance&&(p(a,c.instance,null,!1),g.dom=g.domSize=void 0)}else p(a,c,null,!1),m(a,h(g,b,void 0),d)}function l(a){var c=a.domSize;if(null!=c){var b=q.createDocumentFragment();if(0<c){for(a=a.dom;--c;)b.appendChild(a.nextSibling);b.insertBefore(a,b.firstChild)}return b}return a.dom}function d(a,c,b){for(;c<a.length;c++)if(null!=a[c])return a[c].dom;return b}function m(a,c,b){b&&b.parentNode?a.insertBefore(c,b):a.appendChild(c)}function A(a,
c,b,d,f){for(;b<d;b++){var e=c[b];null!=e&&(e.skip?e.skip=void 0:p(a,e,f,!1))}}function p(a,c,b,d){if(!1===d){var f=0,e=0;d=function(){++e===f&&p(a,c,b,!0)};c.attrs&&c.attrs.onbeforeremove&&(f++,c.attrs.onbeforeremove.call(c,c,d));"string"!==typeof c.tag&&c.tag.onbeforeremove&&(f++,c.tag.onbeforeremove.call(c,c,d));if(0<f)return}t(c);if(c.dom){d=c.domSize||1;if(1<d)for(var m=c.dom;--d;)a.removeChild(m.nextSibling);null!=c.dom.parentNode&&a.removeChild(c.dom);null==b||null!=c.domSize||u(c.attrs)||
"string"!==typeof c.tag||(b.pool?b.pool.push(c):b.pool=[c])}}function t(a){a.attrs&&a.attrs.onremove&&a.attrs.onremove.call(a.state,a);"string"!==typeof a.tag&&a.tag.onremove&&a.tag.onremove.call(a.state,a);a=a.children;if(a instanceof Array)for(var c=0;c<a.length;c++){var b=a[c];null!=b&&t(b)}}function B(a,c,b,d,f){var e=a.dom;if("key"!==c&&(b!==d||"value"===c||"checked"===c||"selectedIndex"===c||"selected"===c&&a.dom===q.activeElement||"object"===typeof d)&&"undefined"!==typeof d&&"oninit"!==c&&
"oncreate"!==c&&"onupdate"!==c&&"onremove"!==c&&"onbeforeremove"!==c&&"onbeforeupdate"!==c){var m=c.indexOf(":");if(-1<m&&"xlink"===c.substr(0,m))e.setAttributeNS("http://www.w3.org/1999/xlink",c.slice(m+1),d);else if("o"===c[0]&&"n"===c[1]&&"function"===typeof d)w(a,c,d);else if("style"===c)if(a=b,a===d&&(e.style="",a=null),null==d)e.style="";else if("string"===typeof d)e.style=d;else{"string"===typeof a&&(e.style="");for(var h in d)e.style[h]=d[h];if(null!=a&&"string"!==typeof a)for(h in a)h in
d||(e.style[h]="")}else if(c in e&&"href"!==c&&"list"!==c&&"form"!==c&&void 0===f){if("input"!==a.tag||"value"!==c||a.dom.value!==d||a.dom!==q.activeElement)e[c]=d}else"boolean"===typeof d?d?e.setAttribute(c,""):e.removeAttribute(c):e.setAttribute("className"===c?"class":c,d)}}function u(a){return null!=a&&(a.oncreate||a.onupdate||a.onbeforeremove||a.onremove)}function w(a,c,b){var d=a.dom,e=function(a){var c=b.call(d,a);"function"===typeof C&&C.call(d,a);return c};if(c in d)d[c]=e;else{var f=c.slice(2);
void 0===a.events&&(a.events={});null!=a.events[c]&&d.removeEventListener(f,a.events[c],!1);a.events[c]=e;d.addEventListener(f,a.events[c],!1)}}function n(a,c,b){"function"===typeof a.oninit&&a.oninit.call(c.state,c);"function"===typeof a.oncreate&&b.push(a.oncreate.bind(c.state,c))}function y(a,c,b,d){d?n(a,c,b):"function"===typeof a.onupdate&&b.push(a.onupdate.bind(c.state,c))}function z(a){if(a instanceof Array){for(var c=[],b=0;b<a.length;b++)c[b]=z(a[b]);return c}if("object"===typeof a){c={};
for(b in a)c[b]=z(a[b]);return c}return a}var q=a.document,ha=q.createDocumentFragment(),C;return{render:function(a,b){var d=[],e=q.activeElement;null==a.vnodes&&(a.vnodes=[]);b instanceof Array||(b=[b]);f(a,a.vnodes,r.normalizeChildren(b),d,null,void 0);for(var m=0;m<d.length;m++)d[m]();a.vnodes=b;q.activeElement!==e&&e.focus()},setEventCallback:function(a){return C=a}}}(window),F=function(){var a=[];return{subscribe:a.push.bind(a),unsubscribe:function(b){b=a.indexOf(b);-1<b&&a.splice(b,1)},publish:function(){for(var b=
0;b<a.length;b++)a[b].apply(this,arguments)}}}(),K=function(a){function b(a,e){if(e instanceof Array)for(var l=0;l<e.length;l++)b(a+"["+l+"]",e[l]);else if("[object Object]"===Object.prototype.toString.call(e))for(l in e)b(a+"["+l+"]",e[l]);else h.push(encodeURIComponent(a)+(null!=e&&""!==e?"="+encodeURIComponent(e):""))}if("[object Object]"!==Object.prototype.toString.call(a))return"";var h=[],e;for(e in a)b(e,a[e]);return h.join("&")},L=function(a){function b(a,b){if(null==b)return a;for(var e=
a.match(/:[^\/]+/gi)||[],f=0;f<e.length;f++){var h=e[f].slice(1);null!=b[h]&&(a=a.replace(e[f],b[h]),delete b[h])}return a}function h(a,b){var e=K(b);if(""!==e){var f=0>a.indexOf("?")?"?":"&";a+=f+e}return a}function e(a){try{return""!==a?JSON.parse(a):null}catch(b){throw Error(a);}}function f(a){return a.responseText}var k=0,l;return{xhr:function(d){var m=z.stream();void 0!==d.initialValue&&m(d.initialValue);var k="boolean"===typeof d.useBody?d.useBody:"GET"!==d.method&&"TRACE"!==d.method;"function"!==
typeof d.serialize&&(d.serialize=JSON.stringify);"function"!==typeof d.deserialize&&(d.deserialize=e);"function"!==typeof d.extract&&(d.extract=f);d.url=b(d.url,d.data);k?d.data=d.serialize(d.data):d.url=h(d.url,d.data);var p=new a.XMLHttpRequest;p.open(d.method,d.url,"boolean"===typeof d.async?d.async:!0,"string"===typeof d.user?d.user:void 0,"string"===typeof d.password?d.password:void 0);d.serialize===JSON.stringify&&k&&p.setRequestHeader("Content-Type","application/json; charset=utf-8");d.deserialize===
e&&p.setRequestHeader("Accept","application/json, text/*");"function"===typeof d.config&&(p=d.config(p,d)||p);p.onreadystatechange=function(){if(4===p.readyState){try{var a=d.deserialize(d.extract(p,d));if(200<=p.status&&300>p.status){if("function"===typeof d.type)if(a instanceof Array)for(var b=0;b<a.length;b++)a[b]=new d.type(a[b]);else a=new d.type(a);m(a)}else{var b=Error(p.responseText),e;for(e in a)b[e]=a[e];m.error(b)}}catch(f){m.error(f)}"function"===typeof l&&l()}};k?p.send(d.data):p.send();
return m},jsonp:function(d){var e=z.stream(),f=d.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+k++,p=a.document.createElement("script");a[f]=function(b){p.parentNode.removeChild(p);e(b);"function"===typeof l&&l();delete a[f]};p.onerror=function(){p.parentNode.removeChild(p);e.error(Error("JSONP request failed"));"function"===typeof l&&l();delete a[f]};null==d.data&&(d.data={});d.url=b(d.url,d.data);d.data[d.callbackKey||"callback"]=f;p.src=h(d.url,d.data);a.document.documentElement.appendChild(p);
return e},setCompletionCallback:function(a){l=a}}}(window);L.setCompletionCallback(F.publish);n.version="1.0.0";n.request=L.xhr;n.jsonp=L.jsonp;var W=function(a){if(""===a||null==a)return{};"?"===a.charAt(0)&&(a=a.slice(1));a=a.split("&");for(var b={},h={},e=0;e<a.length;e++){var f=a[e].split("="),k=decodeURIComponent(f[0]),f=2===f.length?decodeURIComponent(f[1]):"",l=Number(f);""!==f&&!isNaN(l)||"NaN"===f?f=l:"true"===f?f=!0:"false"===f?f=!1:(l=new Date(f),isNaN(l.getTime())||(f=l));var l=k.split(/\]\[?|\[/),
d=b;-1<k.indexOf("[")&&l.pop();for(var m=0;m<l.length;m++){var k=l[m],A=l[m+1],A=""==A||!isNaN(parseInt(A,10)),p=m===l.length-1;""===k&&(k=l.slice(0,m).join(),null==h[k]&&(h[k]=0),k=h[k]++);null==d[k]&&(d[k]=p?f:A?[]:{});d=d[k]}}return b},ia=function(a){function b(b){var d=a.location[b].replace(/(?:%[a-f89][a-f0-9])+/gim,decodeURIComponent);"pathname"===b&&"/"!==d[0]&&(d="/"+d);return d}function h(a,b,d){var e=a.indexOf("?"),f=a.indexOf("#"),h=-1<e?e:-1<f?f:a.length;if(-1<e){var e=W(a.slice(e+1,-1<
f?f:a.length)),k;for(k in e)b[k]=e[k]}if(-1<f)for(k in b=W(a.slice(f+1)),b)d[k]=b[k];return a.slice(0,h)}function e(){switch(d.charAt(0)){case "#":return b("hash").slice(d.length);case "?":return b("search").slice(d.length)+b("hash");default:return b("pathname").slice(d.length)+b("search")+b("hash")}}function f(b,e,f){var l={},n={};b=h(b,l,n);if(null!=e){for(var u in e)l[u]=e[u];b=b.replace(/:([^\/]+)/g,function(a,b){delete l[b];return e[b]})}(u=K(l))&&(b+="?"+u);(n=K(n))&&(b+="#"+n);k?(f&&f.replace?
a.history.replaceState(null,null,d+b):a.history.pushState(null,null,d+b),a.onpopstate()):a.location.href=d+b}var k="function"===typeof a.history.pushState&&"file:"!==a.location.protocol,l="function"===typeof setImmediate?setImmediate:setTimeout,d="#!";return{setPrefix:function(a){d=a},getPath:e,setPath:f,defineRoutes:function(b,f,p){function n(){var a=e(),d={},k=h(a,d,d);l(function(){for(var e in b){var h=new RegExp("^"+e.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(h.test(k)){k.replace(h,
function(){for(var h=e.match(/:[^\/]+/g)||[],k=[].slice.call(arguments,1,-2),l=0;l<h.length;l++)d[h[l].replace(/:|\./g,"")]=decodeURIComponent(k[l]);f(b[e],d,a,e)});return}}p(a,d)})}k?a.onpopstate=n:"#"===d.charAt(0)&&(a.onhashchange=n);n();return n},link:function(a){a.dom.setAttribute("href",d+a.attrs.href);a.dom.onclick=function(b){b.preventDefault();b.redraw=!1;f(a.attrs.href,void 0,void 0)}}}},ja=function(a){var b=0,h=null,e="function"===typeof requestAnimationFrame?requestAnimationFrame:setTimeout;
return function(f){var k=(new Date).getTime();!0===f||0===b||16<=k-b?(b=k,a()):null===h&&(h=e(function(){h=null;a();b=(new Date).getTime()},16-(k-b)))}},X=function(a,b,h,e){var f=ja(e);null!=b&&b.setEventCallback(function(a){!1!==a.redraw&&f()});null!=h&&(a.redraw&&h.unsubscribe(a.redraw),h.subscribe(f));return a.redraw=f};n.route=function(a,b,h){var e=ia(a);a=function(a,k,l){var d=null,m=null;l=e.defineRoutes(l,function(e,h,k,l){if("function"!==typeof e.view){"function"!==typeof e.render&&(e.render=
function(a){return a});var n=function(k){d=l;m=k;b.render(a,e.render(r(k,null,h,void 0,void 0,void 0)))};"function"!==typeof e.resolve&&(e.resolve=function(){n(m)});l!==d?e.resolve(n,h,k,l):n(m)}else b.render(a,r(e,null,h,void 0,void 0,void 0))},function(){e.setPath(k)});X(a,b,h,l)};a.link=e.link;a.prefix=e.setPrefix;a.set=e.setPath;a.get=e.getPath;return a}(window,J,F);n.mount=function(a,b){return function(h,e){X(h,a,b,function(){a.render(h,{tag:e})})()}}(J,F);n.trust=function(a){return r("<",void 0,
void 0,a,void 0,void 0)};n.prop=z.stream;n.prop.combine=z.combine;n.prop.reject=z.reject;n.prop.HALT=z.HALT;n.withAttr=function(a,b,h){return function(e){return b.call(h||this,a in e.currentTarget?e.currentTarget[a]:e.currentTarget.getAttribute(a))}};n.render=J.render;n.redraw=F.publish;"object"===typeof module?module.exports=n:window.m=n})();

View file

@ -98,10 +98,13 @@ module.exports = function($window) {
if (vnode.instance != null) {
var element = createNode(vnode.instance, hooks, ns)
vnode.dom = vnode.instance.dom
vnode.domSize = vnode.instance.domSize
vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0
return element
}
else return $emptyFragment
else {
vnode.domSize = 0
return $emptyFragment
}
}
//update
@ -112,12 +115,12 @@ module.exports = function($window) {
else {
var recycling = isRecyclable(old, vnodes)
if (recycling) old = old.concat(old.pool)
var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map
while (oldEnd >= oldStart && end >= start) {
var o = old[oldStart], v = vnodes[start]
if (o === v) oldStart++, start++
else if (o != null && v != null && o.key === v.key) {
else if (o != null && v != null && o.key === v.key && o.tag === v.tag) {
oldStart++, start++
updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), recycling, ns)
if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
@ -125,7 +128,7 @@ module.exports = function($window) {
else {
var o = old[oldEnd]
if (o === v) oldEnd--, start++
else if (o != null && v != null && o.key === v.key) {
else if (o != null && v != null && o.key === v.key && o.tag === v.tag) {
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling))
oldEnd--, start++
@ -136,10 +139,10 @@ module.exports = function($window) {
while (oldEnd >= oldStart && end >= start) {
var o = old[oldEnd], v = vnodes[end]
if (o === v) oldEnd--, end--
else if (o != null && v != null && o.key === v.key) {
else if (o != null && v != null && o.key === v.key && o.tag === v.tag) {
updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
nextSibling = o.dom
if (o.dom != null) nextSibling = o.dom
oldEnd--, end--
}
else {
@ -151,7 +154,7 @@ module.exports = function($window) {
updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
insertNode(parent, toFragment(movable), nextSibling)
old[oldIndex].skip = true
nextSibling = movable.dom
if (movable.dom != null) nextSibling = movable.dom
}
else {
var dom = createNode(v, hooks, undefined)
@ -211,7 +214,7 @@ module.exports = function($window) {
if (children != null) {
for (var i = 0; i < children.length; i++) {
var child = children[i]
if (child != null) {
if (child != null && child.dom != null) {
if (vnode.dom == null) vnode.dom = child.dom
domSize += child.domSize || 1
}
@ -250,7 +253,12 @@ module.exports = function($window) {
}
else if (old.instance != null) {
removeNode(parent, old.instance, null, false)
vnode.dom = vnode.domSize = undefined
vnode.dom = undefined
vnode.domSize = 0
}
else {
vnode.dom = old.dom
vnode.domSize = old.domSize
}
}
function isRecyclable(old, vnodes) {
@ -277,7 +285,7 @@ module.exports = function($window) {
}
function toFragment(vnode) {
var count = vnode.domSize
if (count != null) {
if (count != null || vnode.dom == null) {
var fragment = $doc.createDocumentFragment()
if (count > 0) {
var dom = vnode.dom
@ -290,7 +298,7 @@ module.exports = function($window) {
}
function getNextSibling(vnodes, i, nextSibling) {
for (; i < vnodes.length; i++) {
if (vnodes[i] != null) return vnodes[i].dom
if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom
}
return nextSibling
}
@ -346,12 +354,14 @@ module.exports = function($window) {
function onremove(vnode) {
if (vnode.attrs && vnode.attrs.onremove) vnode.attrs.onremove.call(vnode.state, vnode)
if (typeof vnode.tag !== "string" && vnode.tag.onremove) vnode.tag.onremove.call(vnode.state, vnode)
var children = vnode.children
if (children instanceof Array) {
for (var i = 0; i < children.length; i++) {
var child = children[i]
if (child != null) onremove(child)
if (vnode.instance != null) onremove(vnode.instance)
else {
var children = vnode.children
if (children instanceof Array) {
for (var i = 0; i < children.length; i++) {
var child = children[i]
if (child != null) onremove(child)
}
}
}
}
@ -479,12 +489,12 @@ module.exports = function($window) {
function copy(data) {
if (data instanceof Array) {
var output = []
for (var i = 0; i < data.length; i++) output[i] = copy(data[i])
for (var i = 0; i < data.length; i++) output[i] = data[i]
return output
}
else if (typeof data === "object") {
var output = {}
for (var i in data) output[i] = copy(data[i])
for (var i in data) output[i] = data[i]
return output
}
return data

View file

@ -640,10 +640,11 @@ o.spec("component", function() {
})
})
o.spec("state", function() {
o("deep copies state", function() {
o("copies state", function() {
var called = 0
var data = {a: 1}
var component = {
data: [{a: 1}],
data: data,
oninit: init,
view: function() {
return ""
@ -653,7 +654,27 @@ o.spec("component", function() {
render(root, [{tag: component}])
function init(vnode) {
o(vnode.state.data).deepEquals([{a: 1}])
o(vnode.state.data).deepEquals(data)
o(vnode.state.data).equals(data)
}
})
o("state copy is shallow", function() {
var called = 0
var body = {a: 1}
var data = [body]
var component = {
data: data,
oninit: init,
view: function() {
return ""
}
}
render(root, [{tag: component}])
function init(vnode) {
o(vnode.state.data).equals(data)
o(vnode.state.data[0]).equals(body)
}
})
})

View file

@ -79,6 +79,39 @@ o.spec("onremove", function() {
o(remove.this).equals(vnode.state)
o(remove.args[0]).equals(vnode)
})
o("calls onremove on nested component", function() {
var spy = o.spy()
var comp = {
view: function() {return m(outer)}
}
var outer = {
view: function() {return m(inner)}
}
var inner = {
onremove: spy,
view: function() {return m("div")}
}
render(root, {tag: comp})
render(root, null)
o(spy.callCount).equals(1)
})
o("calls onremove on nested component child", function() {
var spy = o.spy()
var comp = {
view: function() {return m(outer)}
}
var outer = {
view: function() {return m(inner, m("a", {onremove: spy}))}
}
var inner = {
view: function(vnode) {return m("div", vnode.children)}
}
render(root, {tag: comp})
render(root, null)
o(spy.callCount).equals(1)
})
o("does not set onremove as an event handler", function() {
var remove = o.spy()
var vnode = {tag: "div", attrs: {onremove: remove}, children: []}

View file

@ -813,4 +813,36 @@ o.spec("updateNodes", function() {
o(root.childNodes[0].nodeName).equals("A")
o(root.childNodes[1].nodeName).equals("B")
})
o("fragment child toggles from null when followed by null component then tag", function() {
var component = {view: function() {return null}}
var vnodes = [{tag: "[", children: [{tag: "a"}, {tag: component}, {tag: "b"}]}]
var temp = [{tag: "[", children: [null, {tag: component}, {tag: "b"}]}]
var updated = [{tag: "[", children: [{tag: "a"}, {tag: component}, {tag: "b"}]}]
render(root, vnodes)
render(root, temp)
render(root, updated)
o(root.childNodes.length).equals(2)
o(root.childNodes[0].nodeName).equals("A")
o(root.childNodes[1].nodeName).equals("B")
})
o("fragment child toggles from null in component when followed by null component then tag", function() {
var flag = true
var a = {view: function() {return flag ? {tag: "a"} : null}}
var b = {view: function() {return null}}
var vnodes = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
var temp = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
var updated = [{tag: "[", children: [{tag: a}, {tag: b}, {tag: "s"}]}]
render(root, vnodes)
flag = false
render(root, temp)
flag = true
render(root, updated)
o(root.childNodes.length).equals(2)
o(root.childNodes[0].nodeName).equals("A")
o(root.childNodes[1].nodeName).equals("S")
})
})

View file

@ -7,18 +7,18 @@ function createStream() {
return stream._state.value
}
initStream(stream, arguments)
if (arguments.length > 0) updateStream(stream, arguments[0], undefined)
return stream
}
function initStream(stream, args) {
stream.constructor = createStream
stream._state = {id: guid++, value: undefined, error: undefined, state: 0, derive: undefined, recover: undefined, deps: {}, parents: [], errorStream: undefined, endStream: undefined}
stream.map = map, stream.ap = ap, stream.of = createStream
stream.valueOf = valueOf, stream.toJSON = toJSON
stream.valueOf = valueOf, stream.toJSON = toJSON, stream.toString = valueOf
stream.run = run, stream.catch = doCatch
Object.defineProperties(stream, {
error: {get: function() {
if (!stream._state.errorStream) {
@ -58,7 +58,10 @@ function updateState(stream, value, error) {
if (recovered === HALT) return
updateValues(stream, recovered, undefined)
}
catch (e) {updateValues(stream, undefined, e)}
catch (e) {
updateValues(stream, undefined, e)
reportUncaughtError(stream, e)
}
}
else updateValues(stream, value, error)
stream._state.changed = true
@ -81,6 +84,7 @@ function updateDependency(stream, mustSync) {
}
catch (e) {
updateState(stream, undefined, e)
reportUncaughtError(stream, e)
}
}
}
@ -96,6 +100,13 @@ function finalize(stream) {
stream._state.changed = false
for (var id in stream._state.deps) stream._state.deps[id]._state.changed = false
}
function reportUncaughtError(stream, e) {
if (Object.keys(stream._state.deps).length === 0) {
setTimeout(function() {
if (Object.keys(stream._state.deps).length === 0) console.error(e)
}, 0)
}
}
function run(fn) {
var self = createStream(), stream = this
@ -132,10 +143,10 @@ function initDependency(dep, streams, derive, recover) {
state.derive = derive
state.recover = recover
state.parents = streams.filter(notEnded)
registerDependency(dep, state.parents)
updateDependency(dep, true)
return dep
}
function registerDependency(stream, parents) {
@ -174,4 +185,10 @@ function reject(e) {
return stream
}
module.exports = {stream: createStream, combine: combine, reject: reject, HALT: HALT}
function merge(streams) {
return combine(function () {
return streams.map(function (s) {return s()})
}, streams)
}
module.exports = {stream: createStream, merge: merge, combine: combine, reject: reject, HALT: HALT}

View file

@ -11,24 +11,24 @@ o.spec("stream", function() {
var initialValue = stream()
stream(2)
var newValue = stream()
o(initialValue).equals(1)
o(newValue).equals(2)
})
o("has undefined value by default", function() {
var stream = Stream.stream()
o(stream()).equals(undefined)
})
o("can update to undefined", function() {
var stream = Stream.stream(1)
stream(undefined)
o(stream()).equals(undefined)
})
o("can be stream of streams", function() {
var stream = Stream.stream(Stream.stream(1))
o(stream()()).equals(1)
})
})
@ -36,41 +36,41 @@ o.spec("stream", function() {
o("transforms value", function() {
var stream = Stream.stream()
var doubled = Stream.combine(function(s) {return s() * 2}, [stream])
stream(2)
o(doubled()).equals(4)
})
o("transforms default value", function() {
var stream = Stream.stream(2)
var doubled = Stream.combine(function(s) {return s() * 2}, [stream])
o(doubled()).equals(4)
})
o("transforms multiple values", function() {
var s1 = Stream.stream()
var s2 = Stream.stream()
var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2])
s1(2)
s2(3)
o(added()).equals(5)
})
o("transforms multiple default values", function() {
var s1 = Stream.stream(2)
var s2 = Stream.stream(3)
var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2])
o(added()).equals(5)
})
o("transforms mixed default and late-bound values", function() {
var s1 = Stream.stream(2)
var s2 = Stream.stream()
var added = Stream.combine(function(s1, s2) {return s1() + s2()}, [s1, s2])
s2(3)
o(added()).equals(5)
})
o("combines atomically", function() {
@ -82,9 +82,9 @@ o.spec("stream", function() {
count++
return b() + c()
}, [b, c])
a(3)
o(d()).equals(15)
o(count).equals(1)
})
@ -97,7 +97,7 @@ o.spec("stream", function() {
count++
return b() + c()
}, [b, c])
o(d()).equals(15)
o(count).equals(1)
})
@ -108,10 +108,10 @@ o.spec("stream", function() {
var c = Stream.combine(function(a, b, changed) {
streams = changed
}, [a, b])
a(3)
b(5)
o(streams.length).equals(1)
o(streams[0]).equals(b)
})
@ -122,9 +122,9 @@ o.spec("stream", function() {
var c = Stream.combine(function(a, b, changed) {
streams = changed
}, [a, b])
a(7)
o(streams.length).equals(1)
o(streams[0]).equals(a)
})
@ -133,7 +133,7 @@ o.spec("stream", function() {
var b = Stream.combine(function(a) {
return undefined
}, [a])
o(b()).equals(undefined)
})
o("combine can return stream", function() {
@ -141,7 +141,7 @@ o.spec("stream", function() {
var b = Stream.combine(function(a) {
return Stream.stream(2)
}, [a])
o(b()()).equals(2)
})
o("combine can return pending stream", function() {
@ -149,7 +149,7 @@ o.spec("stream", function() {
var b = Stream.combine(function(a) {
return Stream.stream()
}, [a])
o(b()()).equals(undefined)
})
o("combine can halt", function() {
@ -162,48 +162,73 @@ o.spec("stream", function() {
count++
return 1
})
o(b()).equals(undefined)
})
})
o.spec("merge", function() {
o("transforms an array of streams to an array of values", function() {
var all = Stream.merge([
Stream.stream(10),
Stream.stream("20"),
Stream.stream({value: 30}),
])
o(all()).deepEquals([10, "20", {value: 30}])
})
o("remains pending until all streams are active", function() {
var straggler = Stream.stream()
var all = Stream.merge([
Stream.stream(10),
Stream.stream("20"),
straggler,
])
o(all()).equals(undefined)
straggler(30)
o(all()).deepEquals([10, "20", 30])
})
})
o.spec("end", function() {
o("end stream works", function() {
var stream = Stream.stream()
var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream])
stream.end(true)
stream(3)
o(doubled()).equals(undefined)
})
o("end stream works with default value", function() {
var stream = Stream.stream(2)
var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream])
stream.end(true)
stream(3)
o(doubled()).equals(4)
})
o("cannot add downstream to ended stream", function() {
var stream = Stream.stream(2)
stream.end(true)
var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream])
stream(3)
o(doubled()).equals(undefined)
})
o("upstream does not affect ended stream", function() {
var stream = Stream.stream(2)
var doubled = Stream.combine(function(stream) {return stream() * 2}, [stream])
doubled.end(true)
stream(4)
o(doubled()).equals(4)
})
})
@ -211,16 +236,16 @@ o.spec("stream", function() {
o("error() works", function() {
var stream = Stream.stream()
var errored = Stream.combine(function(stream) {throw new Error("error")}, [stream])
stream(3)
o(errored()).equals(undefined)
o(errored.error().message).equals("error")
})
o("error() works with default value", function() {
var stream = Stream.stream(3)
var errored = Stream.combine(function(stream) {throw new Error("error")}, [stream])
o(errored()).equals(undefined)
o(errored.error().message).equals("error")
})
@ -230,9 +255,9 @@ o.spec("stream", function() {
if (typeof stream() !== "number") throw new Error("error")
else return stream() * 2
}, [stream])
stream(3)
o(doubled()).equals(6)
o(doubled.error()).equals(undefined)
})
@ -243,9 +268,9 @@ o.spec("stream", function() {
count++
return 2
})
stream.error(new Error("error"))
o(handled()).equals(2)
o(handled.error()).equals(undefined)
o(count).equals(1)
@ -262,7 +287,7 @@ o.spec("stream", function() {
count++
return value * 3
})
o(stream()).equals(undefined)
o(stream.error().message).equals("error")
o(count).equals(0)
@ -279,7 +304,7 @@ o.spec("stream", function() {
return value * 3
})
stream.error(new Error("error"))
o(mapped()).equals(undefined)
o(mapped.error().message).equals("error")
o(count).equals(0)
@ -287,13 +312,13 @@ o.spec("stream", function() {
o("error.map works", function() {
var stream = Stream.stream(1)
var mappedFromError = stream.error.map(function(value) {
return "from" + value.message
if (value) return "from" + value.message
})
o(mappedFromError()).equals(undefined)
stream.error(new Error("error"))
o(mappedFromError()).equals("fromerror")
})
o("error from error.map propagates", function() {
@ -304,11 +329,11 @@ o.spec("stream", function() {
.map(function(value) {
return "a" + value
})
o(mappedFromError()).equals(undefined)
stream.error(new Error("error"))
o(mappedFromError()).equals("afromerror")
})
o("error thrown from error.map propagates downstream", function() {
@ -317,15 +342,15 @@ o.spec("stream", function() {
var mappedFromError = stream.error.map(function(value) {
throw new Error("b")
})
var downstream = mappedFromError.map(function() {
count++
})
o(mappedFromError()).equals(undefined)
stream.error(new Error("a"))
o(mappedFromError()).equals(undefined)
o(mappedFromError.error().message).equals("b")
o(downstream()).equals(undefined)
@ -341,7 +366,7 @@ o.spec("stream", function() {
count++
return 1
})
o(stream()).equals(undefined)
o(count).equals(0)
})
@ -350,15 +375,15 @@ o.spec("stream", function() {
var error = stream.error.map(function(value) {
return Stream.stream(1)
})
o(error()()).equals(1)
})
o("combined stream of two errored streams adopts error from first", function() {
var a = Stream.stream(1)
var b = Stream.combine(function(a) {throw new Error("error from b")}, [a])
var c = Stream.combine(function(a) {throw new Error("error from c")}, [a])
var d = Stream.combine(function(a, b) {return 2}, [a, b])
var d = Stream.combine(function(b, c) {return 2}, [b, c])
o(d()).equals(undefined)
o(d.error().message).equals("error from b")
})
@ -366,7 +391,7 @@ o.spec("stream", function() {
o.spec("reject", function() {
o("reject works", function() {
var stream = Stream.reject(new Error("error"))
o(stream()).equals(undefined)
o(stream.error().message).equals("error")
})
@ -381,7 +406,7 @@ o.spec("stream", function() {
count++
return value * 3
})
o(stream()).equals(undefined)
o(stream.error().message).equals("error")
})
@ -390,9 +415,9 @@ o.spec("stream", function() {
var doubled = stream.map(function(value) {
return value * 2
})
stream(1)
o(doubled()).equals(2)
o(stream.error()).equals(undefined)
})
@ -404,7 +429,7 @@ o.spec("stream", function() {
count++
return a() + b()
}, [a, b])
o(combined()).equals(undefined)
o(combined.error().message).equals("a")
o(count).equals(0)
@ -414,29 +439,29 @@ o.spec("stream", function() {
o("works", function() {
var stream = Stream.stream()
var doubled = stream.run(function(value) {return value * 2})
stream(3)
o(doubled()).equals(6)
})
o("works with default value", function() {
var stream = Stream.stream(3)
var doubled = stream.run(function(value) {return value * 2})
o(doubled()).equals(6)
})
o("works with undefined value", function() {
var stream = Stream.stream()
var mapped = stream.run(function(value) {return String(value)})
stream(undefined)
o(mapped()).equals("undefined")
})
o("works with default undefined value", function() {
var stream = Stream.stream(undefined)
var mapped = stream.run(function(value) {return String(value)})
o(mapped()).equals("undefined")
})
o("works with stream that throws", function() {
@ -447,7 +472,7 @@ o.spec("stream", function() {
count++
return value
})
o(errored()).equals(undefined)
o(errored.error().message).equals("error")
o(mapped()).equals(undefined)
@ -462,20 +487,20 @@ o.spec("stream", function() {
count++
return value
})
o(mapped()).equals(undefined)
o(count).equals(0)
})
o("works with active stream", function() {
var stream = Stream.stream(undefined)
var mapped = stream.run(function(value) {return Stream.stream(1)})
o(mapped()).equals(1)
})
o("works with errored stream", function() {
var stream = Stream.stream(undefined)
var mapped = stream.run(function(value) {return Stream.reject(new Error("error"))})
o(mapped()).equals(undefined)
o(mapped.error().message).equals("error")
})
@ -486,36 +511,36 @@ o.spec("stream", function() {
ended.end(true)
return ended
})
stream(3)
o(mapped()).equals(2)
})
o("works when active stream updates", function() {
var stream = Stream.stream(undefined)
var absorbed = Stream.stream(1)
var mapped = stream.run(function(value) {return absorbed})
absorbed(2)
o(mapped()).equals(2)
absorbed(3)
o(mapped()).equals(3)
})
o("works when updating stream to errored state", function() {
var stream = Stream.stream(undefined)
var absorbed = Stream.stream(1)
var mapped = stream.run(function(value) {return absorbed})
absorbed.error(new Error("error"))
o(mapped()).equals(undefined)
o(mapped.error().message).equals("error")
absorbed.error(new Error("another error"))
o(mapped()).equals(undefined)
o(mapped.error().message).equals("another error")
})
@ -523,18 +548,18 @@ o.spec("stream", function() {
var stream = Stream.stream(undefined)
var absorbed = Stream.stream()
var mapped = stream.run(function(value) {return absorbed})
absorbed(2)
o(mapped()).equals(2)
})
o("works when updating pending stream to errored state", function() {
var stream = Stream.stream(undefined)
var absorbed = Stream.stream()
var mapped = stream.run(function(value) {return absorbed})
absorbed.error(new Error("error"))
o(mapped()).equals(undefined)
o(mapped.error().message).equals("error")
})
@ -542,14 +567,14 @@ o.spec("stream", function() {
var stream = Stream.stream(undefined)
var absorbed = Stream.stream(1)
var mapped = stream.run(function(value) {return absorbed})
absorbed.error(new Error("error"))
o(mapped()).equals(undefined)
o(mapped.error().message).equals("error")
absorbed(2)
o(mapped()).equals(2)
o(mapped.error()).equals(undefined)
})
@ -564,7 +589,7 @@ o.spec("stream", function() {
.map(function(value) {
return value + "mapped"
})
o(count).equals(1)
o(stream()).equals("noerrormapped")
o(stream.error()).equals(undefined)
@ -578,7 +603,7 @@ o.spec("stream", function() {
.map(function(value) {
return value + "mapped"
})
o(count).equals(1)
o(stream()).equals("noerrormapped")
o(stream.error()).equals(undefined)
@ -593,9 +618,9 @@ o.spec("stream", function() {
.map(function(value) {
return value + "mapped"
})
stream("a")
o(count).equals(0)
o(handled()).equals("aamapped")
o(handled.error()).equals(undefined)
@ -609,7 +634,7 @@ o.spec("stream", function() {
.map(function(value) {
return value + "mapped"
})
o(count).equals(0)
o(stream()).equals("aamapped")
o(stream.error()).equals(undefined)
@ -619,7 +644,7 @@ o.spec("stream", function() {
throw new Error("b")
})
var mapped = stream.map(function(value) {return value + "ok"})
o(stream()).equals(undefined)
o(stream.error().message).equals("b")
o(mapped()).equals(undefined)
@ -627,7 +652,7 @@ o.spec("stream", function() {
})
o("catch can return undefined", function() {
var stream = Stream.reject(new Error("b")).catch(function(e) {}).map(function(value) {return String(value)})
o(stream()).equals("undefined")
o(stream.error()).equals(undefined)
})
@ -641,7 +666,7 @@ o.spec("stream", function() {
count++
return String(value)
})
o(mapped()).equals(undefined)
o(count).equals(0)
})
@ -651,7 +676,7 @@ o.spec("stream", function() {
return stream
})
.map(function(value) {return String(value)})
o(mapped()).equals("1")
})
o("catch absorbs errored stream", function() {
@ -660,7 +685,7 @@ o.spec("stream", function() {
return stream
})
.map(function(value) {return String(value)})
o(mapped()).equals(undefined)
o(mapped.error().message).equals("a")
})
@ -669,7 +694,7 @@ o.spec("stream", function() {
var b = a.map(function(value) {return value + "b"}).catch(function(e) {})
var c = a.map(function(value) {return value + "c"})
var d = Stream.combine(function(b, c) {return b() + c()}, [b, c])
o(d()).equals(undefined)
o(d.error().message).equals("a")
})
@ -685,7 +710,7 @@ o.spec("stream", function() {
.map(function(value) {
return value + "mapped"
})
o(stream()).equals("noerrormapped")
})
o("catches nested wrapped rejected stream", function() {
@ -702,7 +727,7 @@ o.spec("stream", function() {
.map(function(value) {
return value + "mapped"
})
o(stream()).equals("noerrormapped")
})
})
@ -717,6 +742,19 @@ o.spec("stream", function() {
o(Stream.stream([1, 2, 3]).valueOf()).deepEquals([1, 2, 3])
o(Stream.stream().valueOf()).equals(undefined)
})
o("allows implicit value access in mathematical operations", function() {
o(Stream.stream(1) + Stream.stream(1)).equals(2)
})
})
o.spec("toString", function() {
o("aliases valueOf", function() {
var stream = Stream.stream(1)
o(stream.toString).equals(stream.valueOf)
})
o("allows implicit value access in string operations", function() {
o(Stream.stream("a") + Stream.stream("b")).equals("ab")
})
})
o.spec("toJSON", function() {
o("works", function() {
@ -734,35 +772,35 @@ o.spec("stream", function() {
o("works", function() {
var stream = Stream.stream()
var doubled = stream.map(function(value) {return value * 2})
stream(3)
o(doubled()).equals(6)
})
o("works with default value", function() {
var stream = Stream.stream(3)
var doubled = stream.map(function(value) {return value * 2})
o(doubled()).equals(6)
})
o("works with undefined value", function() {
var stream = Stream.stream()
var mapped = stream.map(function(value) {return String(value)})
stream(undefined)
o(mapped()).equals("undefined")
})
o("works with default undefined value", function() {
var stream = Stream.stream(undefined)
var mapped = stream.map(function(value) {return String(value)})
o(mapped()).equals("undefined")
})
o("works with pending stream", function() {
var stream = Stream.stream(undefined)
var mapped = stream.map(function(value) {return Stream.stream()})
o(mapped()()).equals(undefined)
})
})
@ -771,26 +809,26 @@ o.spec("stream", function() {
var apply = Stream.stream(function(value) {return value * 2})
var stream = Stream.stream(3)
var applied = apply.ap(stream)
o(applied()).equals(6)
apply(function(value) {return value / 3})
o(applied()).equals(1)
stream(9)
o(applied()).equals(3)
})
o("works with undefined value", function() {
var apply = Stream.stream(function(value) {return String(value)})
var stream = Stream.stream(undefined)
var applied = apply.ap(stream)
o(applied()).equals("undefined")
apply(function(value) {return String(value) + "a"})
o(applied()).equals("undefineda")
})
})
@ -799,18 +837,18 @@ o.spec("stream", function() {
o("identity", function() {
var stream = Stream.stream(3)
var mapped = stream.map(function(value) {return value})
o(stream()).equals(mapped())
})
o("composition", function() {
function f(x) {return x * 2}
function g(x) {return x * x}
var stream = Stream.stream(3)
var mapped = stream.map(function(value) {return f(g(value))})
var composed = stream.map(g).map(f)
o(mapped()).equals(18)
o(mapped()).equals(composed())
})
@ -820,7 +858,7 @@ o.spec("stream", function() {
var a = Stream.stream(function(value) {return value * 2})
var u = Stream.stream(function(value) {return value * 3})
var v = Stream.stream(5)
var mapped = a.map(function(f) {
return function(g) {
return function(x) {
@ -828,9 +866,9 @@ o.spec("stream", function() {
}
}
}).ap(u).ap(v)
var composed = a.ap(u.ap(v))
o(mapped()).equals(30)
o(mapped()).equals(composed())
})
@ -839,7 +877,7 @@ o.spec("stream", function() {
o("identity", function() {
var a = Stream.stream().of(function(value) {return value})
var v = Stream.stream(5)
o(a.ap(v)()).equals(5)
o(a.ap(v)()).equals(v())
})
@ -847,7 +885,7 @@ o.spec("stream", function() {
var a = Stream.stream(0)
var f = function(value) {return value * 2}
var x = 3
o(a.of(f).ap(a.of(x))()).equals(6)
o(a.of(f).ap(a.of(x))()).equals(a.of(f(x))())
})
@ -855,10 +893,10 @@ o.spec("stream", function() {
var u = Stream.stream(function(value) {return value * 2})
var a = Stream.stream()
var y = 3
o(u.ap(a.of(y))()).equals(6)
o(u.ap(a.of(y))()).equals(a.of(function(f) {return f(y)}).ap(u)())
})
})
})
})
})