From 20b102b478ca55a72656ad734cc20b976ec32fc4 Mon Sep 17 00:00:00 2001 From: Sergey Kirillov Date: Sat, 31 May 2014 13:38:55 +0300 Subject: [PATCH 01/14] Added context for config handlers --- mithril.js | 2 +- tests/e2e/tests.js | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/mithril.js b/mithril.js index b3b260fd..419ca4fd 100644 --- a/mithril.js +++ b/mithril.js @@ -86,7 +86,7 @@ Mithril = m = new function app(window) { cached.nodes.intact = true if (shouldReattach === true) parentElement.insertBefore(node, parentElement.childNodes[index] || null) } - if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew) + if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew, cached.config_context=cached.config_context || {}) } else { var node diff --git a/tests/e2e/tests.js b/tests/e2e/tests.js index a8c03e3d..c9ecbbf7 100644 --- a/tests/e2e/tests.js +++ b/tests/e2e/tests.js @@ -45,3 +45,20 @@ test('issue99 regression', function() { m.render(dummyEl, view2); equal(dummyEl.innerHTML, '
0
', 'view2 should be rendered correctly'); }); + + +test('config handler context', function() { + expect(3); + var view = m('div', {config: function(evt, isInitialized, context){ + equal(context instanceof Object, true); + context.data = 1; + }}) + m.render(dummyEl, view); + + var view = m('div', {config: function(evt, isInitialized, context){ + equal(context instanceof Object, true); + equal(context.data, 1); + }}) + m.render(dummyEl, view); + +}) From 5a2d7fdf815363cea4800c05ca607ee92efd1b55 Mon Sep 17 00:00:00 2001 From: Sergey Kirillov Date: Sat, 31 May 2014 13:49:24 +0300 Subject: [PATCH 02/14] Added mocked test as well --- tests/mithril-tests.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index c3874ad8..3a24b1fa 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -926,9 +926,19 @@ function testMithril(mock) { test(function() { return m.deps.factory.toString().indexOf("console") < 0 }) + + // config context + test(function() { + var root = mock.document.createElement("div") + + var success = false; + m.render(root, m("div", {config: function(elem, isInitialized, ctx) {ctx.data=1}})); + m.render(root, m("div", {config: function(elem, isInitialized, ctx) {success = ctx.data===1}})); + return success; + }) } //mocks testMithril(mock.window) -test.print(console.log) \ No newline at end of file +test.print(console.log) From 164e5b178c753096677e251fcd99cfba2a053325 Mon Sep 17 00:00:00 2001 From: Sergey Kirillov Date: Sat, 31 May 2014 18:49:04 +0300 Subject: [PATCH 03/14] Added more tests for config context --- tests/mithril-tests.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 3a24b1fa..3b33a72e 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -936,6 +936,26 @@ function testMithril(mock) { m.render(root, m("div", {config: function(elem, isInitialized, ctx) {success = ctx.data===1}})); return success; }) + + // more complex config context + test(function() { + var root = mock.document.createElement("div") + + var idx = 0; + var success = true; + var statefulConfig = function(elem, isInitialized, ctx) {ctx.data=idx++} + var node = m("div", {config: statefulConfig}); + m.render(root, [node, node]); + + idx = 0; + var checkConfig = function(elem, isInitialized, ctx) { + success = success && (ctx.data === idx++) + } + node = m("div", {config: statefulConfig}); + m.render(root, [node, node]); + return success; + }) + } //mocks From af2fdc042103d5d14947a42f5ea86432d901c3be Mon Sep 17 00:00:00 2001 From: Sergey Kirillov Date: Sat, 31 May 2014 18:51:13 +0300 Subject: [PATCH 04/14] Fixed typo in complex config test --- tests/mithril-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 3b33a72e..214bd316 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -951,7 +951,7 @@ function testMithril(mock) { var checkConfig = function(elem, isInitialized, ctx) { success = success && (ctx.data === idx++) } - node = m("div", {config: statefulConfig}); + node = m("div", {config: checkConfig}); m.render(root, [node, node]); return success; }) From 1d2ba8b0b0276dce50e0ef592da147c795e29d77 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Sun, 1 Jun 2014 22:09:37 -0400 Subject: [PATCH 05/14] prevent route change event if only hash changes --- mithril.js | 17 ++++++++++++----- tests/mithril-tests.js | 26 +++++++++++++++++++++----- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/mithril.js b/mithril.js index b3b260fd..2dc953c6 100644 --- a/mithril.js +++ b/mithril.js @@ -53,11 +53,15 @@ Mithril = m = new function app(window) { cached[cacheCount++] = item } if (!intact) { - for (var i = 0; i < data.length; i++) if (cached[i] !== undefined) nodes = nodes.concat(cached[i].nodes) + for (var i = 0; i < data.length; i++) { + if (cached[i] !== undefined) nodes = nodes.concat(cached[i].nodes) + } for (var i = nodes.length, node; node = cached.nodes[i]; i++) { if (node.parentNode !== null && node.parentNode.childNodes.length != nodes.length) node.parentNode.removeChild(node) } - for (var i = cached.nodes.length, node; node = nodes[i]; i++) if (node.parentNode === null) parentElement.appendChild(node) + for (var i = cached.nodes.length, node; node = nodes[i]; i++) { + if (node.parentNode === null) parentElement.appendChild(node) + } if (data.length < cached.length) cached.length = data.length cached.nodes = nodes } @@ -274,20 +278,22 @@ Mithril = m = new function app(window) { m.route = function() { if (arguments.length === 0) return currentRoute else if (arguments.length === 3) { - currentRoute = window.location[m.route.mode].slice(modes[m.route.mode].length) var root = arguments[0], defaultRoute = arguments[1], router = arguments[2] redirect = function(source) { - var path = currentRoute = source.slice(modes[m.route.mode].length) + var path = currentRoute = normalizeRoute(source) if (!routeByValue(root, router, path)) { m.route(defaultRoute, true) } } var listener = m.route.mode == "hash" ? "onhashchange" : "onpopstate" window[listener] = function() { - redirect(window.location[m.route.mode]) + if (currentRoute != normalizeRoute(window.location[m.route.mode])) { + redirect(window.location[m.route.mode]) + } } computePostRedrawHook = scrollToHash window[listener]() + currentRoute = normalizeRoute(window.location[m.route.mode]) } else if (arguments[0].addEventListener) { var element = arguments[0] @@ -319,6 +325,7 @@ Mithril = m = new function app(window) { } m.route.param = function(key) {return routeParams[key]} m.route.mode = "search" + function normalizeRoute(route) {return route.slice(modes[m.route.mode].length)} function routeByValue(root, router, path) { routeParams = {} diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index c3874ad8..288d8d80 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -616,8 +616,8 @@ function testMithril(mock) { var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/", { - "/": {controller: function() {}, view: function() {return;}}, - "/test12": {controller: function() {}, view: function() {return;}} + "/": {controller: function() {}, view: function() {}}, + "/test12": {controller: function() {}, view: function() {}} }) mock.performance.$elapse(50) m.route("/test12?a=foo&b=bar") @@ -647,7 +647,7 @@ function testMithril(mock) { m.route.mode = "search" m.route(root, "/", { "/": {controller: function() {}, view: function() {return "bar"}}, - "/test14": {controller: function() {}, view: function() {return "foo" }} + "/test14": {controller: function() {}, view: function() {return "foo"}} }) mock.performance.$elapse(50) m.route("/test14?test&test2=") @@ -661,14 +661,30 @@ function testMithril(mock) { var root = mock.document.createElement("div") m.route.mode = "search" m.route(root, "/", { - "/": {controller: function() {}, view: function() {return;}}, - "/test12": {controller: function() {}, view: function() {return;}} + "/": {controller: function() {}, view: function() {}}, + "/test12": {controller: function() {}, view: function() {}} }) mock.performance.$elapse(50) m.route("/test12", {a: "foo", b: "bar"}) mock.performance.$elapse(50) //teardown return mock.location.search == "?/test12?a=foo&b=bar" && m.route.param("a") == "foo" && m.route.param("b") == "bar" }) + test(function() { + mock.performance.$elapse(50) //setup + mock.location.search = "?" + + var root = mock.document.createElement("div") + var route1, route2 + m.route.mode = "search" + m.route(root, "/", { + "/": {controller: function() {route1 = m.route()}, view: function() {}}, + "/test13": {controller: function() {route2 = m.route()}, view: function() {}} + }) + mock.performance.$elapse(50) + m.route("/test13") + mock.performance.$elapse(50) //teardown + return route1 == "/" && route2 == "/test13" + }) //end m.route //m.prop From 75fa3bd3f282f7283f0bcca01a2d0833027f38fa Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Sun, 1 Jun 2014 22:10:56 -0400 Subject: [PATCH 06/14] version bump --- Gruntfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 607b693f..1beee29f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,6 +1,6 @@ module.exports = function(grunt) { - var version = "0.1.15" + var version = "0.1.16" var inputFolder = "./docs" var tempFolder = "./temp" From 9792a0ac8540e9226c9899cfab9beae79435e3c1 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Sun, 1 Jun 2014 22:12:05 -0400 Subject: [PATCH 07/14] update change log --- docs/change-log.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/change-log.md b/docs/change-log.md index b44f040d..72ff3699 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -1,5 +1,13 @@ ## Change Log +[v0.1.16](/mithril/archive/v0.1.6) - maintenance + +### Bug Fixes: + +- prevent route change when only hash changes in non-hash mode [#107](https://github.com/lhorie/mithril.js/issues/107) + +--- + [v0.1.15](/mithril/archive/v0.1.15) - maintenance ### Bug Fixes: From ec69af46600051808d2745fee90bf98485faddc2 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Mon, 2 Jun 2014 07:32:24 -0400 Subject: [PATCH 08/14] unbreak browserify --- mithril.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mithril.js b/mithril.js index 2dc953c6..f5631848 100644 --- a/mithril.js +++ b/mithril.js @@ -530,7 +530,7 @@ Mithril = m = new function app(window) { m.deps.factory = app return m -}(this) +}(typeof window != "undefined" ? window : {}) if (typeof module != "undefined" && module !== null) module.exports = m if (typeof define == "function" && define.amd) define(function() {return m}) From 4d04ce034dc10f9a39d3db9024b238098e364226 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 3 Jun 2014 22:42:14 -0400 Subject: [PATCH 09/14] fix diff on nested arrays --- docs/change-log.md | 2 ++ mithril.js | 18 ++++++++----- tests/mithril-tests.js | 58 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/docs/change-log.md b/docs/change-log.md index 72ff3699..f4256410 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -5,6 +5,8 @@ ### Bug Fixes: - prevent route change when only hash changes in non-hash mode [#107](https://github.com/lhorie/mithril.js/issues/107) +- fix null reference exception with Browserify [#110](https://github.com/lhorie/mithril.js/issues/110) +- fix nested array removal edge cases [#120](https://github.com/lhorie/mithril.js/issues/120) --- diff --git a/mithril.js b/mithril.js index f5631848..b59438c2 100644 --- a/mithril.js +++ b/mithril.js @@ -32,13 +32,19 @@ Mithril = m = new function app(window) { } return cell } - function build(parentElement, parentTag, data, cached, shouldReattach, index, editable, namespace) { + function build(parentElement, parentTag, parentCache, parentIndex, data, cached, shouldReattach, index, editable, namespace) { if (data === null || data === undefined) data = "" if (data.subtree === "retain") return var cachedType = type.call(cached), dataType = type.call(data) if (cachedType != dataType) { - if (cached !== null && cached !== undefined) clear(cached.nodes) + if (cached !== null && cached !== undefined) { + if (parentCache && parentCache.nodes) { + var offset = index - parentIndex + clear(parentCache.nodes.slice(offset, offset + (dataType == "[object Array]" ? data : cached.nodes).length)) + } + else clear(cached.nodes) + } cached = new data.constructor cached.nodes = [] } @@ -46,7 +52,7 @@ Mithril = m = new function app(window) { if (dataType == "[object Array]") { var nodes = [], intact = cached.length === data.length, subArrayCount = 0 for (var i = 0, cacheCount = 0; i < data.length; i++) { - var item = build(parentElement, null, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace) + var item = build(parentElement, null, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace) if (item === undefined) continue if (!item.nodes.intact) intact = false subArrayCount += item instanceof Array ? item.length : 1 @@ -78,7 +84,7 @@ Mithril = m = new function app(window) { cached = { tag: data.tag, attrs: setAttributes(node, data.tag, data.attrs, {}, namespace), - children: data.children !== undefined ? build(node, data.tag, data.children, cached.children, true, 0, data.attrs.contenteditable ? node : editable, namespace) : undefined, + children: data.children !== undefined ? build(node, data.tag, undefined, undefined, data.children, cached.children, true, 0, data.attrs.contenteditable ? node : editable, namespace) : undefined, nodes: [node] } parentElement.insertBefore(node, parentElement.childNodes[index] || null) @@ -86,7 +92,7 @@ Mithril = m = new function app(window) { else { node = cached.nodes[0] setAttributes(node, data.tag, data.attrs, cached.attrs, namespace) - cached.children = build(node, data.tag, data.children, cached.children, false, 0, data.attrs.contenteditable ? node : editable, namespace) + cached.children = build(node, data.tag, undefined, undefined, data.children, cached.children, false, 0, data.attrs.contenteditable ? node : editable, namespace) cached.nodes.intact = true if (shouldReattach === true) parentElement.insertBefore(node, parentElement.childNodes[index] || null) } @@ -217,7 +223,7 @@ Mithril = m = new function app(window) { var index = nodeCache.indexOf(root) var id = index < 0 ? nodeCache.push(root) - 1 : index var node = root == window.document || root == window.document.documentElement ? documentNode : root - cellCache[id] = build(node, null, cell, cellCache[id], false, 0, null, undefined) + cellCache[id] = build(node, null, undefined, undefined, cell, cellCache[id], false, 0, null, undefined) } m.trust = function(value) { diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 288d8d80..f76b2264 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -134,7 +134,7 @@ function testMithril(mock) { var root = mock.document.createElement("div") m.render(root, m("ul", [m("li"), m("li")])) m.render(root, m("ul", [m("li"), undefined])) - return root.childNodes[0].childNodes[1].nodeValue === "" + return root.childNodes[0].childNodes.length == 2 && root.childNodes[0].childNodes[1].nodeValue === "" }) test(function() { var root = mock.document.createElement("div") @@ -398,6 +398,62 @@ function testMithril(mock) { m.render(root, m("div", [m("a")])) return root.childNodes[0].childNodes.length == 1 && root.childNodes[0].childNodes[0].nodeName == "A" }) + test(function() { + //https://github.com/lhorie/mithril.js/issues/120 + var root = mock.document.createElement("div") + m.render(root, m("div", ["a", "b", "c", "d"])) + m.render(root, m("div", [["d", "e"]])) + var children = root.childNodes[0].childNodes + return children.length == 2 && children[0].nodeValue == "d" && children[1].nodeValue == "e" + }) + test(function() { + //https://github.com/lhorie/mithril.js/issues/120 + var root = mock.document.createElement("div") + m.render(root, m("div", [["a", "b", "c", "d"]])) + m.render(root, m("div", ["d", "e"])) + var children = root.childNodes[0].childNodes + return children.length == 2 && children[0].nodeValue == "d" && children[1].nodeValue == "e" + }) + test(function() { + //https://github.com/lhorie/mithril.js/issues/120 + var root = mock.document.createElement("div") + m.render(root, m("div", ["x", [["a"], "b", "c", "d"]])) + m.render(root, m("div", ["d", ["e"]])) + var children = root.childNodes[0].childNodes + return children.length == 2 && children[0].nodeValue == "d" && children[1].nodeValue == "e" + }) + test(function() { + //https://github.com/lhorie/mithril.js/issues/120 + var root = mock.document.createElement("div") + m.render(root, m("div", ["b"])) + m.render(root, m("div", [["e"]])) + var children = root.childNodes[0].childNodes + return children.length == 1 && children[0].nodeValue == "e" + }) + test(function() { + //https://github.com/lhorie/mithril.js/issues/120 + var root = mock.document.createElement("div") + m.render(root, m("div", ["a", ["b"]])) + m.render(root, m("div", ["d", [["e"]]])) + var children = root.childNodes[0].childNodes + return children.length == 2 && children[0].nodeValue == "d" && children[1].nodeValue == "e" + }) + test(function() { + //https://github.com/lhorie/mithril.js/issues/120 + var root = mock.document.createElement("div") + m.render(root, m("div", ["a", [["b"]]])) + m.render(root, m("div", ["d", ["e"]])) + var children = root.childNodes[0].childNodes + return children.length == 2 && children[0].nodeValue == "d" && children[1].nodeValue == "e" + }) + test(function() { + //https://github.com/lhorie/mithril.js/issues/120 + var root = mock.document.createElement("div") + m.render(root, m("div", ["a", [["b"], "c"]])) + m.render(root, m("div", ["d", [[["e"]], "x"]])) + var children = root.childNodes[0].childNodes + return children.length == 3 && children[0].nodeValue == "d" && children[1].nodeValue == "e" + }) //end m.render //m.redraw From ff2cb55a0482c5bde76cbcccc39d7d8e10a131a7 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Tue, 3 Jun 2014 23:26:04 -0400 Subject: [PATCH 10/14] add config context --- docs/mithril.md | 36 +++++++++++++++++++++++++++++++++--- mithril.js | 4 ++-- tests/e2e/tests.js | 14 ++++++++++++++ tests/mithril-tests.js | 25 +++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/docs/mithril.md b/docs/mithril.md index f5b68bae..ae50bc34 100644 --- a/docs/mithril.md +++ b/docs/mithril.md @@ -162,7 +162,7 @@ You can define a non-HTML-standard attribute called `config`. This special param This is useful, for example, if you declare a `canvas` element and want to use the Javascript API to draw: ```javascript -function draw(element, isInitialized) { +function draw(element, isInitialized, context) { //don't redraw if we did once already if (isInitialized) return; @@ -202,6 +202,21 @@ You can use this mechanism to attach custom event listeners to controller method --- +The third argument for `config` allows you to map data to a virtual DOM element in a way that persists across redraws. This is useful when a `config` instantiates 3rd party classes and accesses the instance on redraws. + +The example below shows a contrived redraw counter. In it, the count is stored in the context object and re-accessed on each redraw. + +```javascript +function alertsRedrawCount(element, isInit, context) { + if (!isInit) context.count = 0 + alert(++context.count) +} + +m("div", {config: alertsRedrawCount}) +``` + +--- + You can use Mithril to create SVG documents (as long as you don't need to support browsers that don't support SVG natively). Mithril automatically figures out the correct XML namespaces when it sees an SVG island in the virtual DOM tree. @@ -223,7 +238,7 @@ VirtualElement m(String selector [, Attributes attributes] [, Children children] where: VirtualElement :: Object { String tag, Attributes attributes, Children children } - Attributes :: Object + Attributes :: Object Children :: String text | VirtualElement virtualElement | SubtreeDirective directive | Array SubtreeDirective :: Object { String subtree } ``` @@ -286,7 +301,7 @@ where: - #### The `config` attribute - **void config(DOMElement element, Boolean isInitialized)** (optional) + **void config(DOMElement element, Boolean isInitialized, Object context)** (optional) You can define a non-HTML-standard attribute called `config`. This special parameter allows you to call methods on the DOM element after it gets created. @@ -339,6 +354,21 @@ where: Whether this is the first time we are running this function on this element. This flag is false the first time it runs on an element, and true on redraws that happen after the element has been created. + - **Object context** + + An object that retains its state across redraws. It can be used to store instances of 3rd party classes that need to be accessed more than one time throughout the lifecycle of a page. + + The example below shows a contrived redraw counter. In it, the count is stored in the context object and re-accessed on each redraw. + + ```javascript + function alertsRedrawCount(element, isInit, context) { + if (!isInit) context.count = 0 + alert(++context.count) + } + + m("div", {config: alertsRedrawCount}) + ``` + - **Children children** (optional) If this argument is a string, it will be rendered as a text node. To render a string as HTML, see [`m.trust`](mithril.trust) diff --git a/mithril.js b/mithril.js index b59438c2..b5bc54c5 100644 --- a/mithril.js +++ b/mithril.js @@ -96,7 +96,7 @@ Mithril = m = new function app(window) { cached.nodes.intact = true if (shouldReattach === true) parentElement.insertBefore(node, parentElement.childNodes[index] || null) } - if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew) + if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew, cached.configContext = cached.configContext || {}) } else { var node @@ -283,7 +283,7 @@ Mithril = m = new function app(window) { var redirect = function() {}, routeParams = {}, currentRoute m.route = function() { if (arguments.length === 0) return currentRoute - else if (arguments.length === 3) { + else if (arguments.length === 3 && typeof arguments[1] == "string") { var root = arguments[0], defaultRoute = arguments[1], router = arguments[2] redirect = function(source) { var path = currentRoute = normalizeRoute(source) diff --git a/tests/e2e/tests.js b/tests/e2e/tests.js index a8c03e3d..456a57fa 100644 --- a/tests/e2e/tests.js +++ b/tests/e2e/tests.js @@ -45,3 +45,17 @@ test('issue99 regression', function() { m.render(dummyEl, view2); equal(dummyEl.innerHTML, '
0
', 'view2 should be rendered correctly'); }); +test('config handler context', function() { + expect(3); + var view = m('div', {config: function(evt, isInitialized, context){ + equal(context instanceof Object, true); + context.data = 1; + }}) + m.render(dummyEl, view); + + var view = m('div', {config: function(evt, isInitialized, context){ + equal(context instanceof Object, true); + equal(context.data, 1); + }}) + m.render(dummyEl, view); +}) \ No newline at end of file diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index f76b2264..e0272473 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -454,6 +454,31 @@ function testMithril(mock) { var children = root.childNodes[0].childNodes return children.length == 3 && children[0].nodeValue == "d" && children[1].nodeValue == "e" }) + test(function() { + var root = mock.document.createElement("div") + + var success = false + m.render(root, m("div", {config: function(elem, isInitialized, ctx) {ctx.data = 1}})) + m.render(root, m("div", {config: function(elem, isInitialized, ctx) {success = ctx.data === 1}})) + return success + }) + test(function() { + var root = mock.document.createElement("div") + + var index = 0; + var success = true; + var statefulConfig = function(elem, isInitialized, ctx) {ctx.data = index++} + var node = m("div", {config: statefulConfig}); + m.render(root, [node, node]); + + index = 0; + var checkConfig = function(elem, isInitialized, ctx) { + success = success && (ctx.data === index++) + } + node = m("div", {config: checkConfig}); + m.render(root, [node, node]); + return success; + }) //end m.render //m.redraw From 0f4f26344121b94eb7492888f45c107df565ac7e Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Wed, 4 Jun 2014 08:30:12 -0400 Subject: [PATCH 11/14] improve error when error response is not JSON --- docs/mithril.request.md | 17 +++++++++++++++++ mithril.js | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/mithril.request.md b/docs/mithril.request.md index 603e6bc8..0b75068c 100644 --- a/docs/mithril.request.md +++ b/docs/mithril.request.md @@ -228,6 +228,23 @@ var file = m.request({ --- +### Using variable data formats + +By default, Mithril assumes both success and error responses are in JSON format, but some servers may not return JSON responses when returning HTTP error codes (e.g. 404) + +You can get around this issue by using `extract` + +```javascript +var nonJsonErrors = function(xhr) { + return xhr.status > 200 ? JSON.stringify(xhr.responseText) : xhr.responseText +} + +m.request({method: "GET", url: "/foo/bar.x", extract: nonJsonErrors}) + .then(function(data) {}, function(error) {console.log(error)}) +``` + +--- + ### Extracting Metadata from the Response The `extract` method can be used to read metadata from HTTP response headers or the status field of an XMLHttpRequest. diff --git a/mithril.js b/mithril.js index b5bc54c5..5311a10e 100644 --- a/mithril.js +++ b/mithril.js @@ -521,7 +521,8 @@ Mithril = m = new function app(window) { deferred[e.type == "load" ? "resolve" : "reject"](response) } catch (e) { - if (e instanceof Error && e.constructor !== Error) throw e + if (e instanceof SyntaxError) throw new SyntaxError("Could not parse HTTP response. See http://lhorie.github.io/mithril/mithril.request.html#using-variable-data-formats") + else if (e instanceof Error && e.constructor !== Error) throw e else deferred.reject(e) } if (xhrOptions.background !== true) m.endComputation() From cb7e9b668a302ad6bc10b6b82df7161fa6643242 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Wed, 4 Jun 2014 08:30:26 -0400 Subject: [PATCH 12/14] make example work in Chrome --- docs/web-services.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/web-services.md b/docs/web-services.md index 96be5349..e3a548ef 100644 --- a/docs/web-services.md +++ b/docs/web-services.md @@ -144,9 +144,9 @@ var users = m.request({method: "GET", url: "/user"}) return users.concat({name: "Jane"}) }) -function log(load) { - console.log(load) - return load +function log(value) { + console.log(value) + return value } //assuming the response contains the following data: `[{name: "John"}, {name: "Mary"}]` From 2d8db79cef712aa00fbbdb39793b8cb489a5db95 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Wed, 4 Jun 2014 09:22:49 -0400 Subject: [PATCH 13/14] fix test --- tests/mithril-tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 9aa82bed..b369d34e 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -816,7 +816,7 @@ function testMithril(mock) { }) test(function() { var error = m.prop("no error"), exception - var prop = m.request({method: "GET", url: "test", deserialize: function() {throw new SyntaxError("error occurred")}}).then(null, error) + var prop = m.request({method: "GET", url: "test", deserialize: function() {throw new TypeError("error occurred")}}).then(null, error) try {mock.XMLHttpRequest.$instances.pop().onreadystatechange()} catch (e) {exception = e} m.endComputation() From e82ac2ef71eaa2d47e43add085a11b68105cea89 Mon Sep 17 00:00:00 2001 From: Leo Horie Date: Thu, 5 Jun 2014 21:54:51 -0400 Subject: [PATCH 14/14] defer calling of configs until DOM exists --- mithril.js | 7 ++++++- tests/e2e/tests.js | 29 +++++++++++++++++++++++++++++ tests/mithril-tests.js | 8 ++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/mithril.js b/mithril.js index 5311a10e..e0281a9e 100644 --- a/mithril.js +++ b/mithril.js @@ -32,6 +32,7 @@ Mithril = m = new function app(window) { } return cell } + var configs = [] function build(parentElement, parentTag, parentCache, parentIndex, data, cached, shouldReattach, index, editable, namespace) { if (data === null || data === undefined) data = "" if (data.subtree === "retain") return @@ -96,7 +97,9 @@ Mithril = m = new function app(window) { cached.nodes.intact = true if (shouldReattach === true) parentElement.insertBefore(node, parentElement.childNodes[index] || null) } - if (type.call(data.attrs["config"]) == "[object Function]") data.attrs["config"](node, !isNew, cached.configContext = cached.configContext || {}) + if (type.call(data.attrs["config"]) == "[object Function]") { + configs.push(data.attrs["config"].bind(window, node, !isNew, cached.configContext = cached.configContext || {})) + } } else { var node @@ -224,6 +227,8 @@ Mithril = m = new function app(window) { var id = index < 0 ? nodeCache.push(root) - 1 : index var node = root == window.document || root == window.document.documentElement ? documentNode : root cellCache[id] = build(node, null, undefined, undefined, cell, cellCache[id], false, 0, null, undefined) + for (var i = 0; i < configs.length; i++) configs[i]() + configs.length = 0 } m.trust = function(value) { diff --git a/tests/e2e/tests.js b/tests/e2e/tests.js index 0d60cf7f..0b4cf22c 100644 --- a/tests/e2e/tests.js +++ b/tests/e2e/tests.js @@ -1,3 +1,32 @@ +//qunit doesn't support Function.prototype.bind... +if (!Function.prototype.bind) { + Function.prototype.bind = function (oThis) { + if (typeof this !== "function") { + // closest thing possible to the ECMAScript 5 + // internal IsCallable function + throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); + } + + var aArgs = Array.prototype.slice.call(arguments, 1), + fToBind = this, + fNOP = function () {}, + fBound = function () { + return fToBind.apply(this instanceof fNOP && oThis + ? this + : oThis, + aArgs.concat(Array.prototype.slice.call(arguments))); + }; + + fNOP.prototype = this.prototype; + fBound.prototype = new fNOP(); + + return fBound; + }; +} + + + +//tests var dummyEl = document.getElementById('dummy') test('Mithril accessible as window.m', function() { diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index b369d34e..3ce7a667 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -479,6 +479,14 @@ function testMithril(mock) { m.render(root, [node, node]); return success; }) + test(function() { + var root = mock.document.createElement("div") + var parent + m.render(root, m("div", m("a", { + config: function(el) {parent = el.parentNode.parentNode} + }))); + return parent === root + }) //end m.render //m.redraw