From 9ae6d0ede18c7987d7aacb1603d2f269863b4da8 Mon Sep 17 00:00:00 2001 From: Richard Eames Date: Wed, 17 Sep 2014 18:01:25 -0600 Subject: [PATCH 1/5] Created common functions for typeof --- mithril.js | 53 ++++++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/mithril.js b/mithril.js index 24cc7581..1f9aac4e 100644 --- a/mithril.js +++ b/mithril.js @@ -1,5 +1,9 @@ Mithril = m = new function app(window, undefined) { - var type = function(obj) {return {}.toString.call(obj)} + var sObj = "[object Object]", sArr = "[object Array]"; + function type(obj) {return {}.toString.call(obj)} + function isObj(obj) {return type(obj) == sObj} + function isArr(obj) {return type(obj) == sArr} + function isFn(obj) {return typeof obj == "function"} var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ var voidElements = /AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TR‌​ACK|WBR/ @@ -18,7 +22,7 @@ Mithril = m = new function app(window, undefined) { */ function m() { var args = Array.prototype.slice.call(arguments, 0) - var hasAttrs = args[1] != null && type(args[1]) == "[object Object]" && !("tag" in args[1]) && !("subtree" in args[1]) + var hasAttrs = args[1] != null && isObj(args[1]) && !("tag" in args[1]) && !("subtree" in args[1]) var attrs = hasAttrs ? args[1] : {} var classAttrName = "class" in attrs ? "class" : "className" var cell = {tag: "div", attrs: {}} @@ -83,7 +87,7 @@ Mithril = m = new function app(window, undefined) { if (cached != null) { if (parentCache && parentCache.nodes) { var offset = index - parentIndex - var end = offset + (dataType == "[object Array]" ? data : cached.nodes).length + var end = offset + (dataType == sArr ? data : cached.nodes).length clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end)) } else if (cached.nodes) clear(cached.nodes, cached) @@ -92,7 +96,7 @@ Mithril = m = new function app(window, undefined) { cached.nodes = [] } - if (dataType == "[object Array]") { + if (dataType == sArr) { data = flatten(data) var nodes = [], intact = cached.length === data.length, subArrayCount = 0 @@ -160,8 +164,7 @@ Mithril = m = new function app(window, undefined) { var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs) if (item === undefined) continue if (!item.nodes.intact) intact = false - var isArray = type(item) == "[object Array]" - subArrayCount += isArray ? item.length : 1 + subArrayCount += isArr(item) ? item.length : 1 cached[cacheCount++] = item } if (!intact) { @@ -184,11 +187,11 @@ Mithril = m = new function app(window, undefined) { cached.nodes = nodes } } - else if (data != null && dataType == "[object Object]") { + else if (data != null && dataType == sObj) { //if an element is different enough from the one in cache, recreate it if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) { clear(cached.nodes) - if (cached.configContext && typeof cached.configContext.onunload == "function") cached.configContext.onunload() + if (cached.configContext && isFn(cached.configContext.onunload)) cached.configContext.onunload() } if (typeof data.tag != "string") return @@ -215,7 +218,7 @@ Mithril = m = new function app(window, undefined) { if (shouldReattach === true && node != null) parentElement.insertBefore(node, parentElement.childNodes[index] || null) } //schedule configs to be called. They are called after `build` finishes running - if (typeof data.attrs["config"] === "function") { + if (isFn(data.attrs["config"])) { var context = cached.configContext = cached.configContext || {} // bind @@ -227,7 +230,7 @@ Mithril = m = new function app(window, undefined) { configs.push(callback(data, [node, !isNew, context, cached])) } } - else if (typeof dataType != "function") { + else if (!isFn(dataType)) { //handle text nodes var nodes if (cached.nodes.length === 0) { @@ -279,10 +282,10 @@ Mithril = m = new function app(window, undefined) { cachedAttrs[attrName] = dataAttr try { if (attrName === "config") continue - else if (typeof dataAttr == "function" && attrName.indexOf("on") == 0) { + else if (isFn(dataAttr) && attrName.indexOf("on") == 0) { node[attrName] = autoredraw(dataAttr, node) } - else if (attrName === "style" && typeof dataAttr == "object") { + else if (attrName === "style" && isObj(dataAttr)) { for (var rule in dataAttr) { if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule] } @@ -322,9 +325,9 @@ Mithril = m = new function app(window, undefined) { if (nodes.length != 0) nodes.length = 0 } function unload(cached) { - if (cached.configContext && typeof cached.configContext.onunload == "function") cached.configContext.onunload() + if (cached.configContext && isFn(cached.configContext.onunload)) cached.configContext.onunload() if (cached.children) { - if (type(cached.children) == "[object Array]") { + if (isArr(cached.children)) { for (var i = 0; i < cached.children.length; i++) unload(cached.children[i]) } else if (cached.children.tag) unload(cached.children) @@ -354,7 +357,7 @@ Mithril = m = new function app(window, undefined) { var flattened = [] for (var i = 0; i < data.length; i++) { var item = data[i] - if (type(item) == "[object Array]") flattened.push.apply(flattened, flatten(item)) + if (isArr(item)) flattened.push.apply(flattened, flatten(item)) else flattened.push(item) } return flattened @@ -428,8 +431,8 @@ Mithril = m = new function app(window, undefined) { } m.prop = function (store) { - if ((typeof store === 'object' || typeof store === 'function') && store !== null && - typeof store.then === 'function') { + if ((isObj(store) || isFn(store)) && store !== null && + isFn(store.then)) { var prop = _prop() newPromisedProp(prop, store).then(prop) @@ -444,7 +447,7 @@ Mithril = m = new function app(window, undefined) { var index = roots.indexOf(root) if (index < 0) index = roots.length var isPrevented = false - if (controllers[index] && typeof controllers[index].onunload == "function") { + if (controllers[index] && isFn(controllers[index].onunload)) { var event = { preventDefault: function() {isPrevented = true} } @@ -543,7 +546,7 @@ Mithril = m = new function app(window, undefined) { } else if (typeof arguments[0] == "string") { currentRoute = arguments[0] - var querystring = typeof arguments[1] == "object" ? buildQueryString(arguments[1]) : null + var querystring = isObj(arguments[1]) ? buildQueryString(arguments[1]) : null if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring var shouldReplaceHistoryEntry = (arguments.length == 3 ? arguments[2] : arguments[1]) === true @@ -604,7 +607,7 @@ Mithril = m = new function app(window, undefined) { var str = [] for(var prop in object) { var key = prefix ? prefix + "[" + prop + "]" : prop, value = object[prop] - str.push(typeof value == "object" ? buildQueryString(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value)) + str.push(isObj(value) ? buildQueryString(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value)) } return str.join("&") } @@ -696,7 +699,7 @@ Mithril = m = new function app(window, undefined) { } function thennable(then, successCallback, failureCallback, notThennableCallback) { - if ((typeof promiseValue == "object" || typeof promiseValue == "function") && typeof then == "function") { + if ((isObj(promiseValue) || isFn(promiseValue)) && isFn(then)) { try { // count protects against abuse calls from spec checker var count = 0 @@ -738,10 +741,10 @@ Mithril = m = new function app(window, undefined) { fire() }, function() { try { - if (state == RESOLVING && typeof successCallback == "function") { + if (state == RESOLVING && isFn(successCallback)) { promiseValue = successCallback(promiseValue) } - else if (state == REJECTING && typeof failureCallback == "function") { + else if (state == REJECTING && isFn(failureCallback)) { promiseValue = failureCallback(promiseValue) state = RESOLVING } @@ -849,7 +852,7 @@ Mithril = m = new function app(window, undefined) { if (options.serialize == JSON.stringify && options.method != "GET") { xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") } - if (typeof options.config == "function") { + if (isFn(options.config)) { var maybeXhr = options.config(xhr, options) if (maybeXhr != null) xhr = maybeXhr } @@ -896,7 +899,7 @@ Mithril = m = new function app(window, undefined) { var unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity var response = unwrap(deserialize(extract(e.target, xhrOptions))) if (e.type == "load") { - if (type(response) == "[object Array]" && xhrOptions.type) { + if (isArr(response) && xhrOptions.type) { for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i]) } else if (xhrOptions.type) response = new xhrOptions.type(response) From e94a0a7c222fa139c859125d48b9de2a42017d88 Mon Sep 17 00:00:00 2001 From: Richard Eames Date: Thu, 18 Sep 2014 10:53:16 -0600 Subject: [PATCH 2/5] Added tests for other kinds of string --- mithril.js | 5 +++-- tests/mithril-tests.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/mithril.js b/mithril.js index 1f9aac4e..47870d97 100644 --- a/mithril.js +++ b/mithril.js @@ -1,9 +1,10 @@ Mithril = m = new function app(window, undefined) { - var sObj = "[object Object]", sArr = "[object Array]"; + var sObj = "[object Object]", sArr = "[object Array]", sStr = "[object String]" function type(obj) {return {}.toString.call(obj)} function isObj(obj) {return type(obj) == sObj} function isArr(obj) {return type(obj) == sArr} function isFn(obj) {return typeof obj == "function"} + function isStr(obj){ return type(obj) == sStr} var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ var voidElements = /AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TR‌​ACK|WBR/ @@ -544,7 +545,7 @@ Mithril = m = new function app(window, undefined) { element.addEventListener("click", routeUnobtrusive) } } - else if (typeof arguments[0] == "string") { + else if (isStr(arguments[0])) { currentRoute = arguments[0] var querystring = isObj(arguments[1]) ? buildQueryString(arguments[1]) : null if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index a6e9b42e..9efe378c 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -1495,6 +1495,36 @@ function testMithril(mock) { }) return value == "foo+bar" }) + test(function() { + mock.requestAnimationFrame.$resolve() //setup + mock.location.search = "?" + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/", { + "/": {controller: function() {}, view: function() {return "foo"}}, + "/test22": {controller: function() {}, view: function() {return "bar"}} + }) + mock.requestAnimationFrame.$resolve() + m.route(String("/test22/")) + mock.requestAnimationFrame.$resolve() //teardown + return mock.location.search == "?/test22/" && root.childNodes[0].nodeValue === "bar" + }) + test(function() { + mock.requestAnimationFrame.$resolve() //setup + mock.location.search = "?" + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/", { + "/": {controller: function() {}, view: function() {return "foo"}}, + "/test23": {controller: function() {}, view: function() {return "bar"}} + }) + mock.requestAnimationFrame.$resolve() + m.route(new String("/test23/")) + mock.requestAnimationFrame.$resolve() //teardown + return mock.location.search == "?/test23/" && root.childNodes[0].nodeValue === "bar" + }) //end m.route //m.prop From c3d3fa7315dc0f80785978a6a702955e17fe4a9e Mon Sep 17 00:00:00 2001 From: Richard Eames Date: Thu, 18 Sep 2014 10:57:30 -0600 Subject: [PATCH 3/5] Check that the 2nd argument to m.route is a string --- mithril.js | 2 +- tests/mithril-tests.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/mithril.js b/mithril.js index 47870d97..a85b490a 100644 --- a/mithril.js +++ b/mithril.js @@ -517,7 +517,7 @@ Mithril = m = new function app(window, undefined) { var redirect = function() {}, routeParams = {}, currentRoute m.route = function() { if (arguments.length === 0) return currentRoute - else if (arguments.length === 3 && typeof arguments[1] == "string") { + else if (arguments.length === 3 && isStr(arguments[1])) { var root = arguments[0], defaultRoute = arguments[1], router = arguments[2] redirect = function(source) { var path = currentRoute = normalizeRoute(source) diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 9efe378c..6d293b74 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -1525,6 +1525,38 @@ function testMithril(mock) { mock.requestAnimationFrame.$resolve() //teardown return mock.location.search == "?/test23/" && root.childNodes[0].nodeValue === "bar" }) + test(function() { + mock.requestAnimationFrame.$resolve() //setup + mock.location.search = "?" + + var root = mock.document.createElement("div") + var value + m.route(root, String("/foo+bar"), { + "/:arg": { + controller: function() {value = m.route.param("arg")}, + view: function(ctrl) { + return "" + } + } + }) + return value == "foo+bar" + }) + test(function() { + mock.requestAnimationFrame.$resolve() //setup + mock.location.search = "?" + + var root = mock.document.createElement("div") + var value + m.route(root, new String("/foo+bar"), { + "/:arg": { + controller: function() {value = m.route.param("arg")}, + view: function(ctrl) { + return "" + } + } + }) + return value == "foo+bar" + }) //end m.route //m.prop From f3018776b8d0ae1f4746aaf3a664271b40b1eddc Mon Sep 17 00:00:00 2001 From: Richard Eames Date: Thu, 18 Sep 2014 12:04:02 -0600 Subject: [PATCH 4/5] Check for weird edge-case & tests --- mithril.js | 2 +- tests/mithril-tests.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mithril.js b/mithril.js index a85b490a..e20ef884 100644 --- a/mithril.js +++ b/mithril.js @@ -194,7 +194,7 @@ Mithril = m = new function app(window, undefined) { clear(cached.nodes) if (cached.configContext && isFn(cached.configContext.onunload)) cached.configContext.onunload() } - if (typeof data.tag != "string") return + if (!isStr(data.tag)) return var node, isNew = cached.nodes.length === 0 if (data.attrs.xmlns) namespace = data.attrs.xmlns diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 6d293b74..9a8cf482 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -154,6 +154,18 @@ function testMithril(mock) { m.render(root, m("ul", [{}])) return root.childNodes[0].childNodes.length === 0 }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [{tag: "b", attrs: {}}])) + return root.childNodes[0].childNodes[0].nodeName == "B" + }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + m.render(root, m("ul", [{tag: new String("b"), attrs: {}}])) + return root.childNodes[0].childNodes[0].nodeName == "B" + }) test(function() { var root = mock.document.createElement("div") m.render(root, m("ul", [m("li", [m("a")])])) From c56f75a4c373a039e4ea10555a9007f0e93826bf Mon Sep 17 00:00:00 2001 From: Richard Eames Date: Thu, 18 Sep 2014 12:37:22 -0600 Subject: [PATCH 5/5] Check for arguments object edge case, and remove `instanceof` --- mithril.js | 7 ++++--- tests/mithril-tests.js | 9 +++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/mithril.js b/mithril.js index e20ef884..bb277ee2 100644 --- a/mithril.js +++ b/mithril.js @@ -22,7 +22,8 @@ Mithril = m = new function app(window, undefined) { * */ function m() { - var args = Array.prototype.slice.call(arguments, 0) + var arrSlice = Array.prototype.slice; + var args = arrSlice.call(arguments, 0) var hasAttrs = args[1] != null && isObj(args[1]) && !("tag" in args[1]) && !("subtree" in args[1]) var attrs = hasAttrs ? args[1] : {} var classAttrName = "class" in attrs ? "class" : "className" @@ -41,8 +42,8 @@ Mithril = m = new function app(window, undefined) { var children = hasAttrs ? args[2] : args[1] - if (children instanceof Array) { - cell.children = children + if (isArr(children) || type(children) == "[object Arguments]") { + cell.children = arrSlice.call(children, 0) } else { cell.children = hasAttrs ? args.slice(2) : args.slice(1) diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 9a8cf482..339cba1f 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -784,6 +784,15 @@ function testMithril(mock) { m.render(root, [m("div.green", [m("div")]), m("div.blue")]) return root.childNodes.length == 2 }) + test(function() { + var root = mock.document.createElement("div") + m.render(root, m("ul", [m("li")])) + var change = function() { + m.render(root, m("ul", arguments)) + } + change(m("b")); + return root.childNodes[0].childNodes[0].nodeName == "B" + }) //end m.render //m.redraw