nearly Promises/A+ compliant via Promiz.mithril.js
This commit is contained in:
parent
0f5d051d4b
commit
54474f5aef
2 changed files with 159 additions and 51 deletions
204
mithril.js
204
mithril.js
|
|
@ -1,7 +1,7 @@
|
||||||
Mithril = m = new function app(window) {
|
Mithril = m = new function app(window) {
|
||||||
var type = {}.toString
|
var type = {}.toString
|
||||||
var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/
|
var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/
|
||||||
|
|
||||||
function m() {
|
function m() {
|
||||||
var args = arguments
|
var args = arguments
|
||||||
var hasAttrs = type.call(args[1]) == "[object Object]" && !("tag" in args[1]) && !("subtree" in args[1])
|
var hasAttrs = type.call(args[1]) == "[object Object]" && !("tag" in args[1]) && !("subtree" in args[1])
|
||||||
|
|
@ -19,9 +19,9 @@ Mithril = m = new function app(window) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ")
|
if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ")
|
||||||
|
|
||||||
cell.children = hasAttrs ? args[2] : args[1]
|
cell.children = hasAttrs ? args[2] : args[1]
|
||||||
|
|
||||||
for (var attrName in attrs) {
|
for (var attrName in attrs) {
|
||||||
if (attrName == classAttrName) cell.attrs[attrName] = (cell.attrs[attrName] || "") + " " + attrs[attrName]
|
if (attrName == classAttrName) cell.attrs[attrName] = (cell.attrs[attrName] || "") + " " + attrs[attrName]
|
||||||
else cell.attrs[attrName] = attrs[attrName]
|
else cell.attrs[attrName] = attrs[attrName]
|
||||||
|
|
@ -49,7 +49,7 @@ Mithril = m = new function app(window) {
|
||||||
if (dataType == "[object Array]") {
|
if (dataType == "[object Array]") {
|
||||||
data = flatten(data)
|
data = flatten(data)
|
||||||
var nodes = [], intact = cached.length === data.length, subArrayCount = 0
|
var nodes = [], intact = cached.length === data.length, subArrayCount = 0
|
||||||
|
|
||||||
var DELETION = 1, INSERTION = 2 , MOVE = 3
|
var DELETION = 1, INSERTION = 2 , MOVE = 3
|
||||||
var existing = {}, unkeyed = [], shouldMaintainIdentities = false
|
var existing = {}, unkeyed = [], shouldMaintainIdentities = false
|
||||||
for (var i = 0; i < cached.length; i++) {
|
for (var i = 0; i < cached.length; i++) {
|
||||||
|
|
@ -72,7 +72,7 @@ Mithril = m = new function app(window) {
|
||||||
var actions = Object.keys(existing).map(function(key) {return existing[key]})
|
var actions = Object.keys(existing).map(function(key) {return existing[key]})
|
||||||
var changes = actions.sort(function(a, b) {return a.action - b.action || a.index - b.index})
|
var changes = actions.sort(function(a, b) {return a.action - b.action || a.index - b.index})
|
||||||
var newCached = cached.slice()
|
var newCached = cached.slice()
|
||||||
|
|
||||||
for (var i = 0, change; change = changes[i]; i++) {
|
for (var i = 0, change; change = changes[i]; i++) {
|
||||||
if (change.action == DELETION) {
|
if (change.action == DELETION) {
|
||||||
clear(cached[change.index].nodes, cached[change.index])
|
clear(cached[change.index].nodes, cached[change.index])
|
||||||
|
|
@ -84,7 +84,7 @@ Mithril = m = new function app(window) {
|
||||||
parentElement.insertBefore(dummy, parentElement.childNodes[change.index])
|
parentElement.insertBefore(dummy, parentElement.childNodes[change.index])
|
||||||
newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]})
|
newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (change.action == MOVE) {
|
if (change.action == MOVE) {
|
||||||
if (parentElement.childNodes[change.index] !== change.element) {
|
if (parentElement.childNodes[change.index] !== change.element) {
|
||||||
parentElement.insertBefore(change.element, parentElement.childNodes[change.index])
|
parentElement.insertBefore(change.element, parentElement.childNodes[change.index])
|
||||||
|
|
@ -101,7 +101,7 @@ Mithril = m = new function app(window) {
|
||||||
cached.nodes = []
|
cached.nodes = []
|
||||||
for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes.push(child)
|
for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes.push(child)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0, cacheCount = 0; i < data.length; i++) {
|
for (var i = 0, cacheCount = 0; i < data.length; i++) {
|
||||||
var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs)
|
var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs)
|
||||||
if (item === undefined) continue
|
if (item === undefined) continue
|
||||||
|
|
@ -123,7 +123,7 @@ Mithril = m = new function app(window) {
|
||||||
if (data.length < cached.length) cached.length = data.length
|
if (data.length < cached.length) cached.length = data.length
|
||||||
cached.nodes = nodes
|
cached.nodes = nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (dataType == "[object Object]") {
|
else if (dataType == "[object Object]") {
|
||||||
if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) {
|
if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) {
|
||||||
|
|
@ -417,7 +417,7 @@ Mithril = m = new function app(window) {
|
||||||
if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring
|
if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring
|
||||||
|
|
||||||
var shouldReplaceHistoryEntry = (arguments.length == 3 ? arguments[2] : arguments[1]) === true
|
var shouldReplaceHistoryEntry = (arguments.length == 3 ? arguments[2] : arguments[1]) === true
|
||||||
|
|
||||||
if (window.history.pushState) {
|
if (window.history.pushState) {
|
||||||
computePostRedrawHook = function() {
|
computePostRedrawHook = function() {
|
||||||
window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, window.document.title, modes[m.route.mode] + currentRoute)
|
window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, window.document.title, modes[m.route.mode] + currentRoute)
|
||||||
|
|
@ -509,49 +509,157 @@ Mithril = m = new function app(window) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var none = {}
|
var none = {}
|
||||||
m.deferred = function() {
|
m.deferred = function () {
|
||||||
var resolvers = [], rejecters = [], resolved = none, rejected = none, promise = m.prop()
|
|
||||||
var object = {
|
// Promiz.mithril.js | Zolmeister | MIT
|
||||||
resolve: function(value) {
|
function Deferred(fn, er) {
|
||||||
if (resolved === none) promise(resolved = value)
|
// states
|
||||||
for (var i = 0; i < resolvers.length; i++) resolvers[i](value)
|
// 0: pending
|
||||||
resolvers.length = rejecters.length = 0
|
// 1: resolving
|
||||||
},
|
// 2: rejecting
|
||||||
reject: function(value) {
|
// 3: resolved
|
||||||
if (rejected === none) rejected = value
|
// 4: rejected
|
||||||
for (var i = 0; i < rejecters.length; i++) rejecters[i](value)
|
var self = this,
|
||||||
resolvers.length = rejecters.length = 0
|
state = 0,
|
||||||
},
|
val = 0,
|
||||||
promise: promise
|
next = [];
|
||||||
}
|
|
||||||
object.promise.resolvers = resolvers
|
self['promise'] = self
|
||||||
object.promise.then = function(success, error) {
|
|
||||||
var next = m.deferred()
|
self['resolve'] = function (v) {
|
||||||
if (!success) success = identity
|
if (!state) {
|
||||||
if (!error) error = identity
|
val = v
|
||||||
function callback(method, callback) {
|
state = 1
|
||||||
return function(value) {
|
|
||||||
try {
|
fire()
|
||||||
var result = callback(value)
|
|
||||||
if (result && typeof result.then == "function") result.then(next[method], error)
|
|
||||||
else next[method](result !== undefined ? result : value)
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
if (e instanceof Error && e.constructor !== Error) throw e
|
|
||||||
else next.reject(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
if (resolved !== none) callback("resolve", success)(resolved)
|
|
||||||
else if (rejected !== none) callback("reject", error)(rejected)
|
self['reject'] = function (v) {
|
||||||
else {
|
if (!state) {
|
||||||
resolvers.push(callback("resolve", success))
|
val = v
|
||||||
rejecters.push(callback("reject", error))
|
state = 2
|
||||||
|
|
||||||
|
fire()
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
self['then'] = function (fn, er) {
|
||||||
|
var d = new Deferred(fn, er)
|
||||||
|
if (state == 3) {
|
||||||
|
d.resolve(val)
|
||||||
|
}
|
||||||
|
else if (state == 4) {
|
||||||
|
d.reject(val)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
next.push(d)
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
var finish = function (type) {
|
||||||
|
state = type || 4
|
||||||
|
next.map(function (p) {
|
||||||
|
state == 3 && p.resolve(val) || p.reject(val)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ref : reference to 'then' function
|
||||||
|
// cb, ec, cn : successCallback, failureCallback, notThennableCallback
|
||||||
|
function thennable (ref, cb, ec, cn) {
|
||||||
|
if ((typeof val == 'object' || typeof val == 'function') && typeof ref == 'function') {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// cnt protects against abuse calls from spec checker
|
||||||
|
var cnt = 0
|
||||||
|
ref.call(val, function (v) {
|
||||||
|
if (cnt++) return
|
||||||
|
val = v
|
||||||
|
cb()
|
||||||
|
}, function (v) {
|
||||||
|
if (cnt++) return
|
||||||
|
val = v
|
||||||
|
ec()
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
val = e
|
||||||
|
ec()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cn()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function fire() {
|
||||||
|
|
||||||
|
// check if it's a thenable
|
||||||
|
var ref;
|
||||||
|
try {
|
||||||
|
ref = val && val.then
|
||||||
|
} catch (e) {
|
||||||
|
val = e
|
||||||
|
state = 2
|
||||||
|
return fire()
|
||||||
|
}
|
||||||
|
thennable(ref, function () {
|
||||||
|
state = 1
|
||||||
|
fire()
|
||||||
|
}, function () {
|
||||||
|
state = 2
|
||||||
|
fire()
|
||||||
|
}, function () {
|
||||||
|
try {
|
||||||
|
if (state == 1 && typeof fn == 'function') {
|
||||||
|
val = fn(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (state == 2 && typeof er == 'function') {
|
||||||
|
val = er(val)
|
||||||
|
state = 1
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
val = e
|
||||||
|
return finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val == self) {
|
||||||
|
val = TypeError()
|
||||||
|
finish()
|
||||||
|
} else thennable(ref, function () {
|
||||||
|
finish(3)
|
||||||
|
}, finish, function () {
|
||||||
|
finish(state == 1 && 3)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return next.promise
|
|
||||||
}
|
}
|
||||||
return object
|
|
||||||
}
|
function newPromisedProp(prop, promise) {
|
||||||
|
prop.then = function () {
|
||||||
|
var newProp = m.prop(prop())
|
||||||
|
return newPromisedProp(newProp,
|
||||||
|
promise.then.apply(promise, arguments).then(newProp))
|
||||||
|
}
|
||||||
|
prop.promise = prop
|
||||||
|
prop.resolve = function (val) {
|
||||||
|
prop(val)
|
||||||
|
promise = promise.resolve.apply(promise, arguments)
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
prop.reject = function () {
|
||||||
|
promise = promise.reject.apply(promise, arguments)
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPromisedProp(m.prop(), new Deferred())
|
||||||
|
}
|
||||||
m.sync = function(args) {
|
m.sync = function(args) {
|
||||||
var method = "resolve"
|
var method = "resolve"
|
||||||
function synchronizer(pos, resolved) {
|
function synchronizer(pos, resolved) {
|
||||||
|
|
|
||||||
|
|
@ -1322,7 +1322,7 @@ function testMithril(mock) {
|
||||||
var error = m.prop("no error")
|
var error = m.prop("no error")
|
||||||
var prop = m.request({method: "GET", url: "test", deserialize: function() {throw new Error("error occurred")}}).then(null, error)
|
var prop = m.request({method: "GET", url: "test", deserialize: function() {throw new Error("error occurred")}}).then(null, error)
|
||||||
mock.XMLHttpRequest.$instances.pop().onreadystatechange()
|
mock.XMLHttpRequest.$instances.pop().onreadystatechange()
|
||||||
return prop() === undefined && error().message === "error occurred"
|
return prop().message === "error occurred" && error().message === "error occurred"
|
||||||
})
|
})
|
||||||
test(function() {
|
test(function() {
|
||||||
var error = m.prop("no error"), exception
|
var error = m.prop("no error"), exception
|
||||||
|
|
@ -1365,7 +1365,7 @@ function testMithril(mock) {
|
||||||
test(function() {
|
test(function() {
|
||||||
var value
|
var value
|
||||||
var deferred = m.deferred()
|
var deferred = m.deferred()
|
||||||
deferred.promise.then(null, function(data) {return "foo"}).then(null, function(data) {value = data})
|
deferred.promise.then(null, function(data) {return "foo"}).then(function(data) {value = data})
|
||||||
deferred.reject("test")
|
deferred.reject("test")
|
||||||
return value === "foo"
|
return value === "foo"
|
||||||
})
|
})
|
||||||
|
|
@ -1534,7 +1534,7 @@ function testMithril(mock) {
|
||||||
controller.value = "foo"
|
controller.value = "foo"
|
||||||
m.endComputation()
|
m.endComputation()
|
||||||
mock.requestAnimationFrame.$resolve()
|
mock.requestAnimationFrame.$resolve()
|
||||||
|
|
||||||
return root.childNodes[0].nodeValue === "foo"
|
return root.childNodes[0].nodeValue === "foo"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue