Merge branch 'typeof_refactor' of github.com:Naddiseo/mithril.js into Naddiseo-typeof_refactor

Conflicts:
	mithril.js
	tests/mithril-tests.js
This commit is contained in:
Leo Horie 2014-09-27 19:38:49 -04:00
commit d993da1cfd
2 changed files with 120 additions and 31 deletions

View file

@ -1,5 +1,10 @@
Mithril = m = new function app(window, undefined) { Mithril = m = new function app(window, undefined) {
var type = function(obj) {return {}.toString.call(obj)} 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 parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/
var voidElements = /AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR/ var voidElements = /AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR/
@ -17,8 +22,9 @@ Mithril = m = new function app(window, undefined) {
* *
*/ */
function m() { function m() {
var args = Array.prototype.slice.call(arguments, 0) var arrSlice = Array.prototype.slice;
var hasAttrs = args[1] != null && type(args[1]) == "[object Object]" && !("tag" in args[1]) && !("subtree" in args[1]) 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 attrs = hasAttrs ? args[1] : {}
var classAttrName = "class" in attrs ? "class" : "className" var classAttrName = "class" in attrs ? "class" : "className"
var cell = {tag: "div", attrs: {}} var cell = {tag: "div", attrs: {}}
@ -36,8 +42,8 @@ Mithril = m = new function app(window, undefined) {
var children = hasAttrs ? args[2] : args[1] var children = hasAttrs ? args[2] : args[1]
if (children instanceof Array) { if (isArr(children) || type(children) == "[object Arguments]") {
cell.children = children cell.children = arrSlice.call(children, 0)
} }
else { else {
cell.children = hasAttrs ? args.slice(2) : args.slice(1) cell.children = hasAttrs ? args.slice(2) : args.slice(1)
@ -82,7 +88,7 @@ Mithril = m = new function app(window, undefined) {
if (cached != null) { if (cached != null) {
if (parentCache && parentCache.nodes) { if (parentCache && parentCache.nodes) {
var offset = index - parentIndex 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)) clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end))
} }
else if (cached.nodes) clear(cached.nodes, cached) else if (cached.nodes) clear(cached.nodes, cached)
@ -92,7 +98,7 @@ Mithril = m = new function app(window, undefined) {
cached.nodes = [] cached.nodes = []
} }
if (dataType == "[object Array]") { if (dataType == sArr) {
data = flatten(data) data = flatten(data)
var nodes = [], intact = cached.length === data.length, subArrayCount = 0 var nodes = [], intact = cached.length === data.length, subArrayCount = 0
@ -165,8 +171,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) 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
if (!item.nodes.intact) intact = false if (!item.nodes.intact) intact = false
var isArray = type(item) == "[object Array]" subArrayCount += isArr(item) ? item.length : 1
subArrayCount += isArray ? item.length : 1
cached[cacheCount++] = item cached[cacheCount++] = item
} }
if (!intact) { if (!intact) {
@ -189,13 +194,14 @@ Mithril = m = new function app(window, undefined) {
cached.nodes = nodes 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 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) { if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) {
clear(cached.nodes) 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 if (!isStr(data.tag)) return
var node, isNew = cached.nodes.length === 0 var node, isNew = cached.nodes.length === 0
if (data.attrs.xmlns) namespace = data.attrs.xmlns if (data.attrs.xmlns) namespace = data.attrs.xmlns
else if (data.tag === "svg") namespace = "http://www.w3.org/2000/svg" else if (data.tag === "svg") namespace = "http://www.w3.org/2000/svg"
@ -219,7 +225,7 @@ Mithril = m = new function app(window, undefined) {
if (shouldReattach === true && node != null) parentElement.insertBefore(node, parentElement.childNodes[index] || null) if (shouldReattach === true && node != null) parentElement.insertBefore(node, parentElement.childNodes[index] || null)
} }
//schedule configs to be called. They are called after `build` finishes running //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 || {} var context = cached.configContext = cached.configContext || {}
// bind // bind
@ -231,7 +237,7 @@ Mithril = m = new function app(window, undefined) {
configs.push(callback(data, [node, !isNew, context, cached])) configs.push(callback(data, [node, !isNew, context, cached]))
} }
} }
else if (typeof dataType != "function") { else if (!isFn(dataType)) {
//handle text nodes //handle text nodes
var nodes var nodes
if (cached.nodes.length === 0) { if (cached.nodes.length === 0) {
@ -286,11 +292,11 @@ Mithril = m = new function app(window, undefined) {
//we don't ignore `key` because it must be unique and having it on the DOM helps debugging //we don't ignore `key` because it must be unique and having it on the DOM helps debugging
if (attrName === "config") continue if (attrName === "config") continue
//hook event handlers to the auto-redrawing system //hook event handlers to the auto-redrawing system
else if (typeof dataAttr == "function" && attrName.indexOf("on") == 0) { else if (isFn(dataAttr) && attrName.indexOf("on") == 0) {
node[attrName] = autoredraw(dataAttr, node) node[attrName] = autoredraw(dataAttr, node)
} }
//handle `style: {...}` //handle `style: {...}`
else if (attrName === "style" && typeof dataAttr == "object") { else if (attrName === "style" && isObj(dataAttr)) {
for (var rule in dataAttr) { for (var rule in dataAttr) {
if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule] if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule]
} }
@ -338,9 +344,9 @@ Mithril = m = new function app(window, undefined) {
if (nodes.length != 0) nodes.length = 0 if (nodes.length != 0) nodes.length = 0
} }
function unload(cached) { 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 (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]) for (var i = 0; i < cached.children.length; i++) unload(cached.children[i])
} }
else if (cached.children.tag) unload(cached.children) else if (cached.children.tag) unload(cached.children)
@ -370,7 +376,7 @@ Mithril = m = new function app(window, undefined) {
var flattened = [] var flattened = []
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
var item = data[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) else flattened.push(item)
} }
return flattened return flattened
@ -442,8 +448,8 @@ Mithril = m = new function app(window, undefined) {
return prop return prop
} }
m.prop = function(store) { 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)) {
return propify(store) return propify(store)
} }
@ -456,7 +462,7 @@ Mithril = m = new function app(window, undefined) {
var index = roots.indexOf(root) var index = roots.indexOf(root)
if (index < 0) index = roots.length if (index < 0) index = roots.length
var isPrevented = false var isPrevented = false
if (controllers[index] && typeof controllers[index].onunload == "function") { if (controllers[index] && isFn(controllers[index].onunload)) {
var event = { var event = {
preventDefault: function() {isPrevented = true} preventDefault: function() {isPrevented = true}
} }
@ -527,7 +533,7 @@ Mithril = m = new function app(window, undefined) {
var redirect = function() {}, routeParams = {}, currentRoute var redirect = function() {}, routeParams = {}, currentRoute
m.route = function() { m.route = function() {
if (arguments.length === 0) return currentRoute 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] var root = arguments[0], defaultRoute = arguments[1], router = arguments[2]
redirect = function(source) { redirect = function(source) {
var path = currentRoute = normalizeRoute(source) var path = currentRoute = normalizeRoute(source)
@ -555,9 +561,9 @@ Mithril = m = new function app(window, undefined) {
element.addEventListener("click", routeUnobtrusive) element.addEventListener("click", routeUnobtrusive)
} }
} }
else if (typeof arguments[0] == "string") { else if (isStr(arguments[0])) {
currentRoute = arguments[0] 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 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
@ -619,7 +625,7 @@ Mithril = m = new function app(window, undefined) {
var str = [] var str = []
for(var prop in object) { for(var prop in object) {
var key = prefix ? prefix + "[" + prop + "]" : prop, value = object[prop] 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("&") return str.join("&")
} }
@ -705,7 +711,7 @@ Mithril = m = new function app(window, undefined) {
} }
function thennable(then, successCallback, failureCallback, notThennableCallback) { function thennable(then, successCallback, failureCallback, notThennableCallback) {
if ((typeof promiseValue == "object" || typeof promiseValue == "function") && typeof then == "function") { if ((isObj(promiseValue) || isFn(promiseValue)) && isFn(then)) {
try { try {
// count protects against abuse calls from spec checker // count protects against abuse calls from spec checker
var count = 0 var count = 0
@ -749,10 +755,10 @@ Mithril = m = new function app(window, undefined) {
fire() fire()
}, function() { }, function() {
try { try {
if (state == RESOLVING && typeof successCallback == "function") { if (state == RESOLVING && isFn(successCallback)) {
promiseValue = successCallback(promiseValue) promiseValue = successCallback(promiseValue)
} }
else if (state == REJECTING && typeof failureCallback == "function") { else if (state == REJECTING && isFn(failureCallback)) {
promiseValue = failureCallback(promiseValue) promiseValue = failureCallback(promiseValue)
state = RESOLVING state = RESOLVING
} }
@ -867,7 +873,7 @@ Mithril = m = new function app(window, undefined) {
if (options.deserialize == JSON.parse) { if (options.deserialize == JSON.parse) {
xhr.setRequestHeader("Accept", "application/json, text/*"); xhr.setRequestHeader("Accept", "application/json, text/*");
} }
if (typeof options.config == "function") { if (isFn(options.config)) {
var maybeXhr = options.config(xhr, options) var maybeXhr = options.config(xhr, options)
if (maybeXhr != null) xhr = maybeXhr if (maybeXhr != null) xhr = maybeXhr
} }
@ -914,7 +920,7 @@ Mithril = m = new function app(window, undefined) {
var unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity var unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity
var response = unwrap(deserialize(extract(e.target, xhrOptions))) var response = unwrap(deserialize(extract(e.target, xhrOptions)))
if (e.type == "load") { 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]) for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i])
} }
else if (xhrOptions.type) response = new xhrOptions.type(response) else if (xhrOptions.type) response = new xhrOptions.type(response)

View file

@ -154,6 +154,18 @@ function testMithril(mock) {
m.render(root, m("ul", [{}])) m.render(root, m("ul", [{}]))
return root.childNodes[0].childNodes.length === 0 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() { test(function() {
var root = mock.document.createElement("div") var root = mock.document.createElement("div")
m.render(root, m("ul", [m("li", [m("a")])])) m.render(root, m("ul", [m("li", [m("a")])]))
@ -763,6 +775,15 @@ function testMithril(mock) {
m.render(root, new Field()) m.render(root, new Field())
return root.childNodes.length == 1 return root.childNodes.length == 1
}) })
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 //end m.render
//m.redraw //m.redraw
@ -1486,6 +1507,68 @@ function testMithril(mock) {
}) })
return value == "foo+bar" 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"
})
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 //end m.route
//m.prop //m.prop