Add type-checking methods, make a few things stop de-opting under Chrome
This commit is contained in:
parent
b28045aa60
commit
83a30a4d3c
3 changed files with 60 additions and 44 deletions
100
mithril.js
100
mithril.js
|
|
@ -1,9 +1,20 @@
|
|||
var m = (function app(window, undefined) {
|
||||
var OBJECT = "[object Object]", ARRAY = "[object Array]", STRING = "[object String]", FUNCTION = "function";
|
||||
function isFunction(object) {
|
||||
return typeof object === "function";
|
||||
}
|
||||
function isObject(object) {
|
||||
return type.call(object) === "[object Object]";
|
||||
}
|
||||
function isString(object) {
|
||||
return type.call(object) === "[object String]";
|
||||
}
|
||||
var isArray = Array.isArray || function (object) {
|
||||
return type.call(object) === "[object Array]";
|
||||
};
|
||||
var type = {}.toString;
|
||||
var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/;
|
||||
var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/;
|
||||
var noop = function() {};
|
||||
var noop = function () {};
|
||||
|
||||
// caching commonly used variables
|
||||
var $document, $location, $requestAnimationFrame, $cancelAnimationFrame;
|
||||
|
|
@ -32,15 +43,17 @@ var m = (function app(window, undefined) {
|
|||
* @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array, or splat (optional)
|
||||
*
|
||||
*/
|
||||
function m(tag, attrs) {
|
||||
var args = [].slice.call(arguments, 1);
|
||||
if (type.call(tag) === OBJECT) return parameterize(tag, args);
|
||||
var hasAttrs = attrs != null && type.call(attrs) === OBJECT && !("tag" in attrs || "view" in attrs) && !("subtree" in attrs);
|
||||
var attrs = hasAttrs ? attrs : {};
|
||||
function m(tag, pairs) {
|
||||
for (var args = [], i = 1; i < arguments.length; i++) {
|
||||
args[i - 1] = arguments[i];
|
||||
}
|
||||
if (isObject(tag)) return parameterize(tag, args);
|
||||
var hasAttrs = pairs != null && isObject(pairs) && !("tag" in pairs || "view" in pairs || "subtree" in pairs);
|
||||
var attrs = hasAttrs ? pairs : {};
|
||||
var classAttrName = "class" in attrs ? "class" : "className";
|
||||
var cell = {tag: "div", attrs: {}};
|
||||
var match, classes = [];
|
||||
if (type.call(tag) != STRING) throw new Error("selector in m(selector, attrs, children) should be a string");
|
||||
if (!isString(tag)) throw new Error("selector in m(selector, attrs, children) should be a string");
|
||||
while (match = parser.exec(tag)) {
|
||||
if (match[1] === "" && match[2]) cell.tag = match[2];
|
||||
else if (match[1] === "#") cell.attrs.id = match[2];
|
||||
|
|
@ -52,7 +65,7 @@ var m = (function app(window, undefined) {
|
|||
}
|
||||
|
||||
var children = hasAttrs ? args.slice(1) : args;
|
||||
if (children.length === 1 && type.call(children[0]) === ARRAY) {
|
||||
if (children.length === 1 && isArray(children[0])) {
|
||||
cell.children = children[0];
|
||||
}
|
||||
else {
|
||||
|
|
@ -131,7 +144,7 @@ var m = (function app(window, undefined) {
|
|||
if (cached != null) {
|
||||
if (parentCache && parentCache.nodes) {
|
||||
var offset = index - parentIndex;
|
||||
var end = offset + (dataType === ARRAY ? data : cached.nodes).length;
|
||||
var end = offset + (isArray(data) ? data : cached.nodes).length;
|
||||
clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end));
|
||||
}
|
||||
else if (cached.nodes) clear(cached.nodes, cached);
|
||||
|
|
@ -141,10 +154,10 @@ var m = (function app(window, undefined) {
|
|||
cached.nodes = [];
|
||||
}
|
||||
|
||||
if (dataType === ARRAY) {
|
||||
if (isArray(data)) {
|
||||
//recursively flatten array
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if (type.call(data[i]) === ARRAY) {
|
||||
if (isArray(data[i])) {
|
||||
data = data.concat.apply([], data);
|
||||
i--; //check current index again and flatten until there are no more nested arrays at that index
|
||||
}
|
||||
|
|
@ -241,7 +254,7 @@ var m = (function app(window, undefined) {
|
|||
//the second clause (after the pipe) matches text nodes
|
||||
subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || [0]).length;
|
||||
}
|
||||
else subArrayCount += type.call(item) === ARRAY ? item.length : 1;
|
||||
else subArrayCount += isArray(item) ? item.length : 1;
|
||||
cached[cacheCount++] = item;
|
||||
}
|
||||
});
|
||||
|
|
@ -262,7 +275,7 @@ var m = (function app(window, undefined) {
|
|||
cached.nodes = nodes;
|
||||
}
|
||||
}
|
||||
else if (data != null && dataType === OBJECT) {
|
||||
else if (data != null && isObject(data)) {
|
||||
var views = [], controllers = [];
|
||||
while (data.view) {
|
||||
var view = data.view.$original || data.view;
|
||||
|
|
@ -288,14 +301,14 @@ var m = (function app(window, undefined) {
|
|||
//if an element is different enough from the one in cache, recreate it
|
||||
if (data.tag != cached.tag || dataAttrKeys.sort().join() != Object.keys(cached.attrs).sort().join() || data.attrs.id != cached.attrs.id || data.attrs.key != cached.attrs.key || (m.redraw.strategy() == "all" && (!cached.configContext || cached.configContext.retain !== true)) || (m.redraw.strategy() == "diff" && cached.configContext && cached.configContext.retain === false)) {
|
||||
if (cached.nodes.length) clear(cached.nodes);
|
||||
if (cached.configContext && typeof cached.configContext.onunload === FUNCTION) cached.configContext.onunload();
|
||||
if (cached.configContext && isFunction(cached.configContext.onunload)) cached.configContext.onunload();
|
||||
if (cached.controllers) {
|
||||
for (var i = 0, controller; controller = cached.controllers[i]; i++) {
|
||||
if (typeof controller.onunload === FUNCTION) controller.onunload({preventDefault: noop});
|
||||
if (isFunction(controller.onunload)) controller.onunload({preventDefault: noop});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type.call(data.tag) != STRING) return;
|
||||
if (!isString(data.tag)) return;
|
||||
|
||||
var node, isNew = cached.nodes.length === 0;
|
||||
if (data.attrs.xmlns) namespace = data.attrs.xmlns;
|
||||
|
|
@ -344,7 +357,7 @@ var m = (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 (isFunction(data.attrs["config"])) {
|
||||
var context = cached.configContext = cached.configContext || {};
|
||||
|
||||
// bind
|
||||
|
|
@ -356,7 +369,7 @@ var m = (function app(window, undefined) {
|
|||
configs.push(callback(data, [node, !isNew, context, cached]));
|
||||
}
|
||||
}
|
||||
else if (typeof data != FUNCTION) {
|
||||
else if (!isFunction(data)) {
|
||||
//handle text nodes
|
||||
var nodes;
|
||||
if (cached.nodes.length === 0) {
|
||||
|
|
@ -410,11 +423,11 @@ var m = (function app(window, undefined) {
|
|||
//`config` isn't a real attributes, so ignore it
|
||||
if (attrName === "config" || attrName == "key") continue;
|
||||
//hook event handlers to the auto-redrawing system
|
||||
else if (typeof dataAttr === FUNCTION && attrName.indexOf("on") === 0) {
|
||||
else if (isFunction(dataAttr) && attrName.indexOf("on") === 0) {
|
||||
node[attrName] = autoredraw(dataAttr, node);
|
||||
}
|
||||
//handle `style: {...}`
|
||||
else if (attrName === "style" && dataAttr != null && type.call(dataAttr) === OBJECT) {
|
||||
else if (attrName === "style" && dataAttr != null && isObject(dataAttr)) {
|
||||
for (var rule in dataAttr) {
|
||||
if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule];
|
||||
}
|
||||
|
|
@ -461,17 +474,17 @@ var m = (function app(window, undefined) {
|
|||
if (nodes.length != 0) nodes.length = 0;
|
||||
}
|
||||
function unload(cached) {
|
||||
if (cached.configContext && typeof cached.configContext.onunload === FUNCTION) {
|
||||
if (cached.configContext && isFunction(cached.configContext.onunload)) {
|
||||
cached.configContext.onunload();
|
||||
cached.configContext.onunload = null;
|
||||
}
|
||||
if (cached.controllers) {
|
||||
for (var i = 0, controller; controller = cached.controllers[i]; i++) {
|
||||
if (typeof controller.onunload === FUNCTION) controller.onunload({preventDefault: noop});
|
||||
if (isFunction(controller.onunload)) controller.onunload({preventDefault: noop});
|
||||
}
|
||||
}
|
||||
if (cached.children) {
|
||||
if (type.call(cached.children) === ARRAY) {
|
||||
if (isArray(cached.children)) {
|
||||
for (var i = 0, child; child = cached.children[i]; i++) unload(child);
|
||||
}
|
||||
else if (cached.children.tag) unload(cached.children);
|
||||
|
|
@ -563,7 +576,7 @@ var m = (function app(window, undefined) {
|
|||
|
||||
m.prop = function (store) {
|
||||
//note: using non-strict equality check here because we're checking if store is null OR undefined
|
||||
if (((store != null && type.call(store) === OBJECT) || typeof store === FUNCTION) && typeof store.then === FUNCTION) {
|
||||
if ((store != null && isObject(store) || isFunction(store)) && isFunction(store.then)) {
|
||||
return propify(store);
|
||||
}
|
||||
|
||||
|
|
@ -577,7 +590,7 @@ var m = (function app(window, undefined) {
|
|||
return (component.controller || noop).apply(this, args) || this;
|
||||
};
|
||||
var view = function(ctrl) {
|
||||
if (arguments.length > 1) args = args.concat([].slice.call(arguments, 1));
|
||||
for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
|
||||
return component.view.apply(component, args ? [ctrl].concat(args) : [ctrl]);
|
||||
};
|
||||
view.$original = component.view;
|
||||
|
|
@ -586,7 +599,8 @@ var m = (function app(window, undefined) {
|
|||
return output;
|
||||
}
|
||||
m.component = function(component) {
|
||||
return parameterize(component, [].slice.call(arguments, 1));
|
||||
for (var args = [], i = 1; i < arguments.length; i++) args[i - 1] = arguments[i];
|
||||
return parameterize(component, args);
|
||||
};
|
||||
m.mount = m.module = function(root, component) {
|
||||
if (!root) throw new Error("Please ensure the DOM element exists before rendering a template into it.");
|
||||
|
|
@ -607,7 +621,7 @@ var m = (function app(window, undefined) {
|
|||
}
|
||||
else unloaders = [];
|
||||
|
||||
if (controllers[index] && typeof controllers[index].onunload === FUNCTION) {
|
||||
if (controllers[index] && isFunction(controllers[index].onunload)) {
|
||||
controllers[index].onunload(event);
|
||||
}
|
||||
|
||||
|
|
@ -616,7 +630,7 @@ var m = (function app(window, undefined) {
|
|||
m.startComputation();
|
||||
roots[index] = root;
|
||||
if (arguments.length > 2) component = subcomponent(component, [].slice.call(arguments, 2));
|
||||
var currentComponent = topComponent = component = component || {controller: function() {}};
|
||||
var currentComponent = topComponent = component = component || {controller: noop};
|
||||
var constructor = component.controller || noop;
|
||||
var controller = new constructor;
|
||||
//controllers may call m.mount recursively (via m.route redirects, for example)
|
||||
|
|
@ -637,7 +651,7 @@ var m = (function app(window, undefined) {
|
|||
try {
|
||||
//lastRedrawId is a positive number if a second redraw is requested before the next animation frame
|
||||
//lastRedrawID is null if it's the first redraw and not an event handler
|
||||
if (lastRedrawId && force !== true) {
|
||||
if (lastRedrawId && !force) {
|
||||
//when setTimeout: only reschedule redraw if time between now and previous redraw is bigger than a frame, otherwise keep currently scheduled timeout
|
||||
//when rAF: always reschedule redraw
|
||||
if ($requestAnimationFrame === window.requestAnimationFrame || new Date - lastRedrawCallTime > FRAME_BUDGET) {
|
||||
|
|
@ -705,7 +719,7 @@ var m = (function app(window, undefined) {
|
|||
//m.route()
|
||||
if (arguments.length === 0) return currentRoute;
|
||||
//m.route(el, defaultRoute, routes)
|
||||
else if (arguments.length === 3 && type.call(arg1) === STRING) {
|
||||
else if (arguments.length === 3 && isString(arg1)) {
|
||||
redirect = function(source) {
|
||||
var path = currentRoute = normalizeRoute(source);
|
||||
if (!routeByValue(root, arg2, path)) {
|
||||
|
|
@ -739,7 +753,7 @@ var m = (function app(window, undefined) {
|
|||
}
|
||||
}
|
||||
//m.route(route, params, shouldReplaceHistoryEntry)
|
||||
else if (type.call(root) === STRING) {
|
||||
else if (isString(root)) {
|
||||
var oldRoute = currentRoute;
|
||||
currentRoute = root;
|
||||
arg1 || {};
|
||||
|
|
@ -832,8 +846,8 @@ var m = (function app(window, undefined) {
|
|||
var value = object[prop];
|
||||
var valueType = type.call(value);
|
||||
var pair = (value === null) ? encodeURIComponent(key) :
|
||||
valueType === OBJECT ? buildQueryString(value, key) :
|
||||
valueType === ARRAY ? value.reduce(function(memo, item) {
|
||||
isObject(value) ? buildQueryString(value, key) :
|
||||
isArray(value) ? value.reduce(function(memo, item) {
|
||||
if (!duplicates[key]) duplicates[key] = {};
|
||||
if (!duplicates[key][item]) {
|
||||
duplicates[key][item] = true;
|
||||
|
|
@ -855,7 +869,7 @@ var m = (function app(window, undefined) {
|
|||
var key = decodeURIComponent(pair[0]);
|
||||
var value = pair.length == 2 ? decodeURIComponent(pair[1]) : null;
|
||||
if (params[key] != null) {
|
||||
if (type.call(params[key]) !== ARRAY) params[key] = [params[key]];
|
||||
if (!isArray(params[key])) params[key] = [params[key]];
|
||||
params[key].push(value);
|
||||
}
|
||||
else params[key] = value;
|
||||
|
|
@ -937,7 +951,7 @@ var m = (function app(window, undefined) {
|
|||
}
|
||||
|
||||
function thennable(then, successCallback, failureCallback, notThennableCallback) {
|
||||
if (((promiseValue != null && type.call(promiseValue) === OBJECT) || typeof promiseValue === FUNCTION) && typeof then === FUNCTION) {
|
||||
if (((promiseValue != null && isObject(promiseValue)) || isFunction(promiseValue)) && isFunction(then)) {
|
||||
try {
|
||||
// count protects against abuse calls from spec checker
|
||||
var count = 0;
|
||||
|
|
@ -981,10 +995,10 @@ var m = (function app(window, undefined) {
|
|||
fire();
|
||||
}, function() {
|
||||
try {
|
||||
if (state === RESOLVING && typeof successCallback === FUNCTION) {
|
||||
if (state === RESOLVING && isFunction(successCallback)) {
|
||||
promiseValue = successCallback(promiseValue);
|
||||
}
|
||||
else if (state === REJECTING && typeof failureCallback === FUNCTION) {
|
||||
else if (state === REJECTING && isFunction(failureCallback)) {
|
||||
promiseValue = failureCallback(promiseValue);
|
||||
state = RESOLVING;
|
||||
}
|
||||
|
|
@ -1101,13 +1115,13 @@ var m = (function app(window, undefined) {
|
|||
if (options.deserialize === JSON.parse) {
|
||||
xhr.setRequestHeader("Accept", "application/json, text/*");
|
||||
}
|
||||
if (typeof options.config === FUNCTION) {
|
||||
if (isFunction(options.config)) {
|
||||
var maybeXhr = options.config(xhr, options);
|
||||
if (maybeXhr != null) xhr = maybeXhr;
|
||||
}
|
||||
|
||||
var data = options.method === "GET" || !options.data ? "" : options.data;
|
||||
if (data && (type.call(data) != STRING && data.constructor != window.FormData)) {
|
||||
if (data && (!isString(data) && data.constructor != window.FormData)) {
|
||||
throw "Request data should be either be a string or FormData. Check the `serialize` option in `m.request`";
|
||||
}
|
||||
xhr.send(data);
|
||||
|
|
@ -1153,8 +1167,10 @@ var m = (function app(window, undefined) {
|
|||
var unwrap = (e.type === "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity;
|
||||
var response = unwrap(deserialize(extract(e.target, xhrOptions)), e.target);
|
||||
if (e.type === "load") {
|
||||
if (type.call(response) === ARRAY && xhrOptions.type) {
|
||||
for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i]);
|
||||
if (isArray(response) && xhrOptions.type) {
|
||||
forEach(response, function (res, i) {
|
||||
response[i] = new xhrOptions.type(res);
|
||||
});
|
||||
}
|
||||
else if (xhrOptions.type) response = new xhrOptions.type(response);
|
||||
}
|
||||
|
|
|
|||
2
mithril.min.js
vendored
2
mithril.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue