From 54474f5aefb81eb639106a83f5a1dc680f03c9af Mon Sep 17 00:00:00 2001 From: Zolmeister Date: Tue, 22 Jul 2014 01:56:07 -0700 Subject: [PATCH 1/4] nearly Promises/A+ compliant via Promiz.mithril.js --- mithril.js | 204 +++++++++++++++++++++++++++++++---------- tests/mithril-tests.js | 6 +- 2 files changed, 159 insertions(+), 51 deletions(-) diff --git a/mithril.js b/mithril.js index f2d10db2..b8b96b9b 100644 --- a/mithril.js +++ b/mithril.js @@ -1,7 +1,7 @@ Mithril = m = new function app(window) { var type = {}.toString var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ - + function m() { var args = arguments 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(" ") - + cell.children = hasAttrs ? args[2] : args[1] - + for (var attrName in attrs) { if (attrName == classAttrName) cell.attrs[attrName] = (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]") { data = flatten(data) var nodes = [], intact = cached.length === data.length, subArrayCount = 0 - + var DELETION = 1, INSERTION = 2 , MOVE = 3 var existing = {}, unkeyed = [], shouldMaintainIdentities = false 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 changes = actions.sort(function(a, b) {return a.action - b.action || a.index - b.index}) var newCached = cached.slice() - + for (var i = 0, change; change = changes[i]; i++) { if (change.action == DELETION) { 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]) newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]}) } - + if (change.action == MOVE) { if (parentElement.childNodes[change.index] !== change.element) { parentElement.insertBefore(change.element, parentElement.childNodes[change.index]) @@ -101,7 +101,7 @@ Mithril = m = new function app(window) { cached.nodes = [] for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes.push(child) } - + 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) if (item === undefined) continue @@ -123,7 +123,7 @@ Mithril = m = new function app(window) { if (data.length < cached.length) cached.length = data.length cached.nodes = nodes } - + } 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) { @@ -417,7 +417,7 @@ Mithril = m = new function app(window) { if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring var shouldReplaceHistoryEntry = (arguments.length == 3 ? arguments[2] : arguments[1]) === true - + if (window.history.pushState) { computePostRedrawHook = function() { 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 = {} - m.deferred = function() { - var resolvers = [], rejecters = [], resolved = none, rejected = none, promise = m.prop() - var object = { - resolve: function(value) { - if (resolved === none) promise(resolved = value) - for (var i = 0; i < resolvers.length; i++) resolvers[i](value) - resolvers.length = rejecters.length = 0 - }, - reject: function(value) { - if (rejected === none) rejected = value - for (var i = 0; i < rejecters.length; i++) rejecters[i](value) - resolvers.length = rejecters.length = 0 - }, - promise: promise - } - object.promise.resolvers = resolvers - object.promise.then = function(success, error) { - var next = m.deferred() - if (!success) success = identity - if (!error) error = identity - function callback(method, callback) { - return function(value) { - try { - 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) - } + m.deferred = function () { + + // Promiz.mithril.js | Zolmeister | MIT + function Deferred(fn, er) { + // states + // 0: pending + // 1: resolving + // 2: rejecting + // 3: resolved + // 4: rejected + var self = this, + state = 0, + val = 0, + next = []; + + self['promise'] = self + + self['resolve'] = function (v) { + if (!state) { + val = v + state = 1 + + fire() } + return this } - if (resolved !== none) callback("resolve", success)(resolved) - else if (rejected !== none) callback("reject", error)(rejected) - else { - resolvers.push(callback("resolve", success)) - rejecters.push(callback("reject", error)) + + self['reject'] = function (v) { + if (!state) { + val = v + 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) { var method = "resolve" function synchronizer(pos, resolved) { diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index b0d54648..c4b86a32 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -1322,7 +1322,7 @@ function testMithril(mock) { var error = m.prop("no error") var prop = m.request({method: "GET", url: "test", deserialize: function() {throw new Error("error occurred")}}).then(null, error) mock.XMLHttpRequest.$instances.pop().onreadystatechange() - return prop() === undefined && error().message === "error occurred" + return prop().message === "error occurred" && error().message === "error occurred" }) test(function() { var error = m.prop("no error"), exception @@ -1365,7 +1365,7 @@ function testMithril(mock) { test(function() { var value 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") return value === "foo" }) @@ -1534,7 +1534,7 @@ function testMithril(mock) { controller.value = "foo" m.endComputation() mock.requestAnimationFrame.$resolve() - + return root.childNodes[0].nodeValue === "foo" }) From 6b62681da867558f760f16e58b456ca514e292b2 Mon Sep 17 00:00:00 2001 From: Zolmeister Date: Wed, 13 Aug 2014 00:50:35 -0700 Subject: [PATCH 2/4] promise prop resolution --- archive/v0.1.20/mithril-tests.js | 122 +++++++++++++++++++++---------- mithril.js | 46 ++++++++---- tests/mithril-tests.js | 27 ++++++- tests/test.js | 49 +++++++------ 4 files changed, 168 insertions(+), 76 deletions(-) diff --git a/archive/v0.1.20/mithril-tests.js b/archive/v0.1.20/mithril-tests.js index 7c5b738b..15612a4b 100644 --- a/archive/v0.1.20/mithril-tests.js +++ b/archive/v0.1.20/mithril-tests.js @@ -1,7 +1,7 @@ Mithril = m = new function app(window, undefined) { var type = {}.toString var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ - + function m() { var args = arguments var hasAttrs = args[1] !== undefined && type.call(args[1]) == "[object Object]" && !("tag" in args[1]) && !("subtree" in args[1]) @@ -19,9 +19,9 @@ Mithril = m = new function app(window, undefined) { } } if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ") - + cell.children = hasAttrs ? args[2] : args[1] - + for (var attrName in attrs) { if (attrName == classAttrName) cell.attrs[attrName] = (cell.attrs[attrName] || "") + " " + attrs[attrName] else cell.attrs[attrName] = attrs[attrName] @@ -40,7 +40,7 @@ Mithril = m = new function app(window, undefined) { //`editable` is a flag that indicates whether an ancestor is contenteditable //`namespace` indicates the closest HTML namespace as it cascades down from an ancestor //`configs` is a list of config functions to run after the topmost `build` call finishes running - + //there's logic that relies on the assumption that null and undefined data are equivalent to empty strings //- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements //- it simplifies diffing code @@ -64,7 +64,7 @@ Mithril = m = new function app(window, undefined) { if (dataType == "[object Array]") { data = flatten(data) var nodes = [], intact = cached.length === data.length, subArrayCount = 0 - + //key algorithm: sort elements without recreating them if keys are present //1) create a map of all existing keys, and mark all for deletion //2) add new keys to map and mark them for addition @@ -93,7 +93,7 @@ Mithril = m = new function app(window, undefined) { 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 newCached = cached.slice() - + for (var i = 0, change; change = changes[i]; i++) { if (change.action == DELETION) { clear(cached[change.index].nodes, cached[change.index]) @@ -105,7 +105,7 @@ Mithril = m = new function app(window, undefined) { parentElement.insertBefore(dummy, parentElement.childNodes[change.index]) newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]}) } - + if (change.action == MOVE) { if (parentElement.childNodes[change.index] !== change.element && change.element !== null) { parentElement.insertBefore(change.element, parentElement.childNodes[change.index]) @@ -123,7 +123,7 @@ Mithril = m = new function app(window, undefined) { for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes.push(child) } //end key algorithm - + 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) if (item === undefined) continue @@ -145,7 +145,7 @@ Mithril = m = new function app(window, undefined) { if (data.length < cached.length) cached.length = data.length cached.nodes = nodes } - + } else if (data !== undefined && dataType == "[object Object]") { //if an element is different enough from the one in cache, recreate it @@ -362,16 +362,32 @@ Mithril = m = new function app(window, undefined) { } m.prop = function(store) { + function isPromise(obj) { + return typeof store === 'object' && typeof store.then === 'function' + } + var prop = function() { - if (arguments.length) store = arguments[0] - return store + if (arguments.length) { + store = arguments[0] + if (isPromise(store)) { + store.then(prop) + } + } + + return isPromise(store) ? undefined : store } + prop.toJSON = function() { - return store + return isPromise(store) ? undefined : store } + + if (isPromise(store)) { + store.then(prop) + } + return prop } - + var roots = [], modules = [], controllers = [], lastRedrawId = 0, computePostRedrawHook = null, prevented = false m.module = function(root, module) { var index = roots.indexOf(root) @@ -472,7 +488,7 @@ Mithril = m = new function app(window, undefined) { if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring var shouldReplaceHistoryEntry = (arguments.length == 3 ? arguments[2] : arguments[1]) === true - + if (window.history.pushState) { computePostRedrawHook = function() { window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, window.document.title, modes[m.route.mode] + currentRoute) @@ -724,7 +740,7 @@ Mithril = m = new function app(window, undefined) { } } else deferred.resolve() - + return deferred.promise } function identity(value) {return value} @@ -817,37 +833,42 @@ if (typeof define == "function" && define.amd) define(function() {return m}) ;;; function test(condition) { - var duration = 0; - var start = 0; - var result = true; + var duration = 0 + var start = 0 + var result = true test.total++ + if (typeof performance != "undefined") { + start = performance.now() + } + try { + if (!condition()) throw new Error() + } + catch (e) { + result = false + console.error(e) + test.failures.push(condition) + } + if (typeof performance != "undefined") { + duration = performance.now() - start + } + + test_obj = { + name: "" + test.total, + result: result, + duration: duration + } + if (typeof window != "undefined") { - if (typeof performance != "undefined") { - start = performance.now(); - } - try {if (!condition()) throw new Error} - catch (e) {result = false;console.error(e);test.failures.push(condition)} - if (typeof performance != "undefined") { - duration = performance.now() - start; - } - - - test_obj = { - name: "" + test.total, - result: result, - duration: duration - } if (!result) { - message: "failed: " + condition, window.global_test_results.tests.push(test_obj) } - window.global_test_results.duration += duration; + window.global_test_results.duration += duration if (result) { - window.global_test_results.passed++; + window.global_test_results.passed++ } else { - window.global_test_results.failed++; + window.global_test_results.failed++ } } } @@ -1715,7 +1736,7 @@ function testMithril(mock) { }) mock.requestAnimationFrame.$resolve() //teardown m.redraw() //should run synchronously - + m.redraw() //rest should run asynchronously since they're spamming m.redraw() m.redraw() @@ -2392,6 +2413,31 @@ function testMithril(mock) { var obj = {prop: m.prop("test")} return JSON.stringify(obj) === '{"prop":"test"}' }) + test(function() { + var prop = m.prop({ + then: function(cb) {cb("test")} + }) + return prop() === "test" + }) + test(function() { + var prop = m.prop({ + then: function() {} + }) + + return prop() === undefined + }) + test(function() { + var promise = { + then: function(cb) {this.cb = cb}, + resolve: function (x) { + this.cb(x) + } + } + var prop = m.prop(promise) + promise.resolve("test") + + return prop() === "test" + }) //m.request test(function() { diff --git a/mithril.js b/mithril.js index d4b08d78..077bcdea 100644 --- a/mithril.js +++ b/mithril.js @@ -1,7 +1,7 @@ Mithril = m = new function app(window, undefined) { var type = {}.toString var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ - + function m() { var args = arguments var hasAttrs = args[1] !== undefined && type.call(args[1]) == "[object Object]" && !("tag" in args[1]) && !("subtree" in args[1]) @@ -19,9 +19,9 @@ Mithril = m = new function app(window, undefined) { } } if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ") - + cell.children = hasAttrs ? args[2] : args[1] - + for (var attrName in attrs) { if (attrName == classAttrName) cell.attrs[attrName] = (cell.attrs[attrName] || "") + " " + attrs[attrName] else cell.attrs[attrName] = attrs[attrName] @@ -40,7 +40,7 @@ Mithril = m = new function app(window, undefined) { //`editable` is a flag that indicates whether an ancestor is contenteditable //`namespace` indicates the closest HTML namespace as it cascades down from an ancestor //`configs` is a list of config functions to run after the topmost `build` call finishes running - + //there's logic that relies on the assumption that null and undefined data are equivalent to empty strings //- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements //- it simplifies diffing code @@ -64,7 +64,7 @@ Mithril = m = new function app(window, undefined) { if (dataType == "[object Array]") { data = flatten(data) var nodes = [], intact = cached.length === data.length, subArrayCount = 0 - + //key algorithm: sort elements without recreating them if keys are present //1) create a map of all existing keys, and mark all for deletion //2) add new keys to map and mark them for addition @@ -93,7 +93,7 @@ Mithril = m = new function app(window, undefined) { 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 newCached = cached.slice() - + for (var i = 0, change; change = changes[i]; i++) { if (change.action == DELETION) { clear(cached[change.index].nodes, cached[change.index]) @@ -105,7 +105,7 @@ Mithril = m = new function app(window, undefined) { parentElement.insertBefore(dummy, parentElement.childNodes[change.index]) newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]}) } - + if (change.action == MOVE) { if (parentElement.childNodes[change.index] !== change.element && change.element !== null) { parentElement.insertBefore(change.element, parentElement.childNodes[change.index]) @@ -123,7 +123,7 @@ Mithril = m = new function app(window, undefined) { for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes.push(child) } //end key algorithm - + 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) if (item === undefined) continue @@ -145,7 +145,7 @@ Mithril = m = new function app(window, undefined) { if (data.length < cached.length) cached.length = data.length cached.nodes = nodes } - + } else if (data !== undefined && dataType == "[object Object]") { //if an element is different enough from the one in cache, recreate it @@ -362,16 +362,32 @@ Mithril = m = new function app(window, undefined) { } m.prop = function(store) { + function isPromise(obj) { + return typeof store === 'object' && typeof store.then === 'function' + } + var prop = function() { - if (arguments.length) store = arguments[0] - return store + if (arguments.length) { + store = arguments[0] + if (isPromise(store)) { + store.then(prop) + } + } + + return isPromise(store) ? undefined : store } + prop.toJSON = function() { - return store + return isPromise(store) ? undefined : store } + + if (isPromise(store)) { + store.then(prop) + } + return prop } - + var roots = [], modules = [], controllers = [], lastRedrawId = 0, computePostRedrawHook = null, prevented = false m.module = function(root, module) { var index = roots.indexOf(root) @@ -472,7 +488,7 @@ Mithril = m = new function app(window, undefined) { if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring var shouldReplaceHistoryEntry = (arguments.length == 3 ? arguments[2] : arguments[1]) === true - + if (window.history.pushState) { computePostRedrawHook = function() { window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, window.document.title, modes[m.route.mode] + currentRoute) @@ -724,7 +740,7 @@ Mithril = m = new function app(window, undefined) { } } else deferred.resolve() - + return deferred.promise } function identity(value) {return value} diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 01b20dc8..f053a9b6 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -736,7 +736,7 @@ function testMithril(mock) { }) mock.requestAnimationFrame.$resolve() //teardown m.redraw() //should run synchronously - + m.redraw() //rest should run asynchronously since they're spamming m.redraw() m.redraw() @@ -1413,6 +1413,31 @@ function testMithril(mock) { var obj = {prop: m.prop("test")} return JSON.stringify(obj) === '{"prop":"test"}' }) + test(function() { + var prop = m.prop({ + then: function(cb) {cb("test")} + }) + return prop() === "test" + }) + test(function() { + var prop = m.prop({ + then: function() {} + }) + + return prop() === undefined + }) + test(function() { + var promise = { + then: function(cb) {this.cb = cb}, + resolve: function (x) { + this.cb(x) + } + } + var prop = m.prop(promise) + promise.resolve("test") + + return prop() === "test" + }) //m.request test(function() { diff --git a/tests/test.js b/tests/test.js index 025d8216..a07a82b1 100644 --- a/tests/test.js +++ b/tests/test.js @@ -1,35 +1,40 @@ function test(condition) { - var duration = 0; - var start = 0; - var result = true; + var duration = 0 + var start = 0 + var result = true test.total++ + if (typeof performance != "undefined") { + start = performance.now() + } + try { + if (!condition()) throw new Error() + } + catch (e) { + result = false + console.error(e) + test.failures.push(condition) + } + if (typeof performance != "undefined") { + duration = performance.now() - start + } + + test_obj = { + name: "" + test.total, + result: result, + duration: duration + } + if (typeof window != "undefined") { - if (typeof performance != "undefined") { - start = performance.now(); - } - try {if (!condition()) throw new Error} - catch (e) {result = false;console.error(e);test.failures.push(condition)} - if (typeof performance != "undefined") { - duration = performance.now() - start; - } - - - test_obj = { - name: "" + test.total, - result: result, - duration: duration - } if (!result) { - message: "failed: " + condition, window.global_test_results.tests.push(test_obj) } - window.global_test_results.duration += duration; + window.global_test_results.duration += duration if (result) { - window.global_test_results.passed++; + window.global_test_results.passed++ } else { - window.global_test_results.failed++; + window.global_test_results.failed++ } } } From d567de08a8850a9fe5adcd7d8d723000cc125dbc Mon Sep 17 00:00:00 2001 From: Zolmeister Date: Wed, 13 Aug 2014 01:14:23 -0700 Subject: [PATCH 3/4] promise props resolve to promises instead of blank prop functions --- archive/v0.1.20/mithril-tests.js | 100 ++++++++++++++----------------- mithril.js | 73 +++++++++++----------- tests/mithril-tests.js | 27 +++------ 3 files changed, 88 insertions(+), 112 deletions(-) diff --git a/archive/v0.1.20/mithril-tests.js b/archive/v0.1.20/mithril-tests.js index 15612a4b..07324e42 100644 --- a/archive/v0.1.20/mithril-tests.js +++ b/archive/v0.1.20/mithril-tests.js @@ -361,33 +361,31 @@ Mithril = m = new function app(window, undefined) { return value } - m.prop = function(store) { - function isPromise(obj) { - return typeof store === 'object' && typeof store.then === 'function' - } - + function _prop(store) { var prop = function() { - if (arguments.length) { - store = arguments[0] - if (isPromise(store)) { - store.then(prop) - } - } - - return isPromise(store) ? undefined : store + if (arguments.length) store = arguments[0] + return store } prop.toJSON = function() { - return isPromise(store) ? undefined : store - } - - if (isPromise(store)) { - store.then(prop) + return store } return prop } + m.prop = function (store) { + if ((typeof store === 'object' || typeof store === 'function') && + typeof store.then === 'function') { + var prop = _prop() + newPromisedProp(prop, store).then(prop) + + return prop + } + + return _prop(store) + } + var roots = [], modules = [], controllers = [], lastRedrawId = 0, computePostRedrawHook = null, prevented = false m.module = function(root, module) { var index = roots.indexOf(root) @@ -566,6 +564,25 @@ Mithril = m = new function app(window, undefined) { } var none = {} + function newPromisedProp(prop, promise) { + prop.then = function () { + var newProp = m.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 + } m.deferred = function () { // Promiz.mithril.js | Zolmeister | MIT @@ -695,26 +712,6 @@ Mithril = m = new function app(window, undefined) { } } - 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) { @@ -2414,29 +2411,20 @@ function testMithril(mock) { return JSON.stringify(obj) === '{"prop":"test"}' }) test(function() { - var prop = m.prop({ - then: function(cb) {cb("test")} - }) + var defer = m.deferred() + var prop = m.prop(defer.promise) + defer.resolve("test") + return prop() === "test" }) test(function() { - var prop = m.prop({ - then: function() {} + var defer = m.deferred() + var prop = m.prop(defer.promise).then(function () { + return "test2" }) + defer.resolve("test") - return prop() === undefined - }) - test(function() { - var promise = { - then: function(cb) {this.cb = cb}, - resolve: function (x) { - this.cb(x) - } - } - var prop = m.prop(promise) - promise.resolve("test") - - return prop() === "test" + return prop() === "test2" }) //m.request diff --git a/mithril.js b/mithril.js index 077bcdea..e1a10c34 100644 --- a/mithril.js +++ b/mithril.js @@ -361,33 +361,31 @@ Mithril = m = new function app(window, undefined) { return value } - m.prop = function(store) { - function isPromise(obj) { - return typeof store === 'object' && typeof store.then === 'function' - } - + function _prop(store) { var prop = function() { - if (arguments.length) { - store = arguments[0] - if (isPromise(store)) { - store.then(prop) - } - } - - return isPromise(store) ? undefined : store + if (arguments.length) store = arguments[0] + return store } prop.toJSON = function() { - return isPromise(store) ? undefined : store - } - - if (isPromise(store)) { - store.then(prop) + return store } return prop } + m.prop = function (store) { + if ((typeof store === 'object' || typeof store === 'function') && + typeof store.then === 'function') { + var prop = _prop() + newPromisedProp(prop, store).then(prop) + + return prop + } + + return _prop(store) + } + var roots = [], modules = [], controllers = [], lastRedrawId = 0, computePostRedrawHook = null, prevented = false m.module = function(root, module) { var index = roots.indexOf(root) @@ -566,6 +564,25 @@ Mithril = m = new function app(window, undefined) { } var none = {} + function newPromisedProp(prop, promise) { + prop.then = function () { + var newProp = m.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 + } m.deferred = function () { // Promiz.mithril.js | Zolmeister | MIT @@ -695,26 +712,6 @@ Mithril = m = new function app(window, undefined) { } } - 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) { diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index f053a9b6..a009ee72 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -1414,29 +1414,20 @@ function testMithril(mock) { return JSON.stringify(obj) === '{"prop":"test"}' }) test(function() { - var prop = m.prop({ - then: function(cb) {cb("test")} - }) + var defer = m.deferred() + var prop = m.prop(defer.promise) + defer.resolve("test") + return prop() === "test" }) test(function() { - var prop = m.prop({ - then: function() {} + var defer = m.deferred() + var prop = m.prop(defer.promise).then(function () { + return "test2" }) + defer.resolve("test") - return prop() === undefined - }) - test(function() { - var promise = { - then: function(cb) {this.cb = cb}, - resolve: function (x) { - this.cb(x) - } - } - var prop = m.prop(promise) - promise.resolve("test") - - return prop() === "test" + return prop() === "test2" }) //m.request From 774be35cc29821e4cc3521b1c6e2b0d08e6c094d Mon Sep 17 00:00:00 2001 From: Zolmeister Date: Wed, 13 Aug 2014 12:59:03 -0700 Subject: [PATCH 4/4] added optional param to m.redraw() to allow forced update --- archive/v0.1.20/mithril-tests.js | 23 +++++++++++++++++++++-- mithril.js | 4 ++-- tests/mithril-tests.js | 19 +++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/archive/v0.1.20/mithril-tests.js b/archive/v0.1.20/mithril-tests.js index 07324e42..0d6a257e 100644 --- a/archive/v0.1.20/mithril-tests.js +++ b/archive/v0.1.20/mithril-tests.js @@ -406,10 +406,10 @@ Mithril = m = new function app(window, undefined) { m.endComputation() } } - m.redraw = function() { + m.redraw = function(force) { var cancel = window.cancelAnimationFrame || window.clearTimeout var defer = window.requestAnimationFrame || window.setTimeout - if (lastRedrawId) { + if (lastRedrawId && !force) { cancel(lastRedrawId) lastRedrawId = defer(redraw, 0) } @@ -1740,6 +1740,25 @@ function testMithril(mock) { mock.requestAnimationFrame.$resolve() //teardown return count === 3 }) + test(function() { + mock.requestAnimationFrame.$resolve() //setup + var count = 0 + var root = mock.document.createElement("div") + m.module(root, { + controller: function() {}, + view: function(ctrl) { + count++ + } + }) + mock.requestAnimationFrame.$resolve() //teardown + m.redraw(true) //should run synchronously + + m.redraw(true) //forced to run synchronously + m.redraw(true) + m.redraw(true) + mock.requestAnimationFrame.$resolve() //teardown + return count === 5 + }) //m.route test(function() { diff --git a/mithril.js b/mithril.js index e1a10c34..187febf0 100644 --- a/mithril.js +++ b/mithril.js @@ -406,10 +406,10 @@ Mithril = m = new function app(window, undefined) { m.endComputation() } } - m.redraw = function() { + m.redraw = function(force) { var cancel = window.cancelAnimationFrame || window.clearTimeout var defer = window.requestAnimationFrame || window.setTimeout - if (lastRedrawId) { + if (lastRedrawId && !force) { cancel(lastRedrawId) lastRedrawId = defer(redraw, 0) } diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index a009ee72..3de5fe91 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -743,6 +743,25 @@ function testMithril(mock) { mock.requestAnimationFrame.$resolve() //teardown return count === 3 }) + test(function() { + mock.requestAnimationFrame.$resolve() //setup + var count = 0 + var root = mock.document.createElement("div") + m.module(root, { + controller: function() {}, + view: function(ctrl) { + count++ + } + }) + mock.requestAnimationFrame.$resolve() //teardown + m.redraw(true) //should run synchronously + + m.redraw(true) //forced to run synchronously + m.redraw(true) + m.redraw(true) + mock.requestAnimationFrame.$resolve() //teardown + return count === 5 + }) //m.route test(function() {