diff --git a/mithril.js b/mithril.js index 6b061ad8..ebdce7e1 100644 --- a/mithril.js +++ b/mithril.js @@ -3,7 +3,7 @@ var m = (function app(window, undefined) { 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; @@ -40,37 +40,37 @@ var m = (function app(window, undefined) { var classAttrName = "class" in attrs ? "class" : "className"; var cell = {tag: "div", attrs: {}}; var match, classes = []; - if (type.call(args[0]) != STRING) throw new Error("selector in m(selector, attrs, children) should be a string") + if (type.call(args[0]) != STRING) throw new Error("selector in m(selector, attrs, children) should be a string"); while (match = parser.exec(args[0])) { if (match[1] === "" && match[2]) cell.tag = match[2]; else if (match[1] === "#") cell.attrs.id = match[2]; else if (match[1] === ".") classes.push(match[2]); else if (match[3][0] === "[") { var pair = attrParser.exec(match[3]); - cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" :true) + cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" :true); } } var children = hasAttrs ? args.slice(2) : args.slice(1); if (children.length === 1 && type.call(children[0]) === ARRAY) { - cell.children = children[0] + cell.children = children[0]; } else { - cell.children = children + cell.children = children; } - + for (var attrName in attrs) { if (attrs.hasOwnProperty(attrName)) { if (attrName === classAttrName && attrs[attrName] != null && attrs[attrName] !== "") { - classes.push(attrs[attrName]) - cell.attrs[attrName] = "" //create key in correct iteration order + classes.push(attrs[attrName]); + cell.attrs[attrName] = ""; //create key in correct iteration order } - else cell.attrs[attrName] = attrs[attrName] + else cell.attrs[attrName] = attrs[attrName]; } } if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" "); - - return cell + + return cell; } function build(parentElement, parentTag, parentCache, parentIndex, data, cached, shouldReattach, index, editable, namespace, configs) { //`build` is a recursive function that manages creation/diffing/removal of DOM elements based on comparison between `data` and `cached` @@ -99,7 +99,7 @@ var m = (function app(window, undefined) { //- this prevents lifecycle surprises from procedural helpers that mix implicit and explicit return statements (e.g. function foo() {if (cond) return m("div")} //- it simplifies diffing code //data.toString() might throw or return null if data is the return value of Console.log in Firefox (behavior depends on version) - try {if (data == null || data.toString() == null) data = "";} catch (e) {data = ""} + try { if (data == null || data.toString() == null) data = ""; } catch (e) { data = ""; } if (data.subtree === "retain") return cached; var cachedType = type.call(cached), dataType = type.call(data); if (cached == null || cachedType !== dataType) { @@ -107,13 +107,13 @@ var m = (function app(window, undefined) { if (parentCache && parentCache.nodes) { var offset = index - parentIndex; var end = offset + (dataType === ARRAY ? 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); } cached = new data.constructor; if (cached.tag) cached = {}; //if constructor creates a virtual dom element, use a blank object as the base cached node instead of copying the virtual el (#277) - cached.nodes = [] + cached.nodes = []; } if (dataType === ARRAY) { @@ -121,11 +121,11 @@ var m = (function app(window, undefined) { for (var i = 0, len = data.length; i < len; i++) { if (type.call(data[i]) === ARRAY) { data = data.concat.apply([], data); - i-- //check current index again and flatten until there are no more nested arrays at that index - len = data.length + i--; //check current index again and flatten until there are no more nested arrays at that index + len = data.length; } } - + var nodes = [], intact = cached.length === data.length, subArrayCount = 0; //keys algorithm: sort elements without recreating them if keys are present @@ -138,30 +138,30 @@ var m = (function app(window, undefined) { for (var i = 0; i < cached.length; i++) { if (cached[i] && cached[i].attrs && cached[i].attrs.key != null) { shouldMaintainIdentities = true; - existing[cached[i].attrs.key] = {action: DELETION, index: i} + existing[cached[i].attrs.key] = {action: DELETION, index: i}; } } - - var guid = 0 + + var guid = 0; for (var i = 0, len = data.length; i < len; i++) { if (data[i] && data[i].attrs && data[i].attrs.key != null) { for (var j = 0, len = data.length; j < len; j++) { - if (data[j] && data[j].attrs && data[j].attrs.key == null) data[j].attrs.key = "__mithril__" + guid++ + if (data[j] && data[j].attrs && data[j].attrs.key == null) data[j].attrs.key = "__mithril__" + guid++; } - break + break; } } - + if (shouldMaintainIdentities) { - var keysDiffer = false - if (data.length != cached.length) keysDiffer = true + var keysDiffer = false; + if (data.length != cached.length) keysDiffer = true; else for (var i = 0, cachedCell, dataCell; cachedCell = cached[i], dataCell = data[i]; i++) { if (cachedCell.attrs && dataCell.attrs && cachedCell.attrs.key != dataCell.attrs.key) { - keysDiffer = true - break + keysDiffer = true; + break; } } - + if (keysDiffer) { for (var i = 0, len = data.length; i < len; i++) { if (data[i] && data[i].attrs) { @@ -173,35 +173,35 @@ var m = (function app(window, undefined) { index: i, from: existing[key].index, element: cached.nodes[existing[key].index] || $document.createElement("div") - } + }; } } } - var actions = [] - for (var prop in existing) actions.push(existing[prop]) + var actions = []; + for (var prop in existing) actions.push(existing[prop]); var changes = actions.sort(sortChanges); - var newCached = new Array(cached.length) - newCached.nodes = cached.nodes.slice() + var newCached = new Array(cached.length); + newCached.nodes = cached.nodes.slice(); for (var i = 0, change; change = changes[i]; i++) { if (change.action === DELETION) { clear(cached[change.index].nodes, cached[change.index]); - newCached.splice(change.index, 1) + newCached.splice(change.index, 1); } if (change.action === INSERTION) { var dummy = $document.createElement("div"); dummy.key = data[change.index].attrs.key; parentElement.insertBefore(dummy, parentElement.childNodes[change.index] || null); - newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]}) - newCached.nodes[change.index] = dummy + newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]}); + newCached.nodes[change.index] = dummy; } if (change.action === MOVE) { if (parentElement.childNodes[change.index] !== change.element && change.element !== null) { - parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null) + parentElement.insertBefore(change.element, parentElement.childNodes[change.index] || null); } - newCached[change.index] = cached[change.from] - newCached.nodes[change.index] = change.element + newCached[change.index] = cached[change.from]; + newCached.nodes[change.index] = change.element; } } cached = newCached; @@ -218,57 +218,57 @@ var m = (function app(window, undefined) { //fix offset of next element if item was a trusted string w/ more than one html element //the first clause in the regexp matches elements //the second clause (after the pipe) matches text nodes - subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || [0]).length + subArrayCount += (item.match(/<[^\/]|\>\s*[^<]/g) || [0]).length; } else subArrayCount += type.call(item) === ARRAY ? item.length : 1; - cached[cacheCount++] = item + cached[cacheCount++] = item; } if (!intact) { //diff the array itself - + //update the list of DOM nodes by collecting the nodes from each item for (var i = 0, len = data.length; i < len; i++) { - if (cached[i] != null) nodes.push.apply(nodes, cached[i].nodes) + if (cached[i] != null) nodes.push.apply(nodes, cached[i].nodes); } //remove items from the end of the array if the new array is shorter than the old one //if errors ever happen here, the issue is most likely a bug in the construction of the `cached` data structure somewhere earlier in the program for (var i = 0, node; node = cached.nodes[i]; i++) { - if (node.parentNode != null && nodes.indexOf(node) < 0) clear([node], [cached[i]]) + if (node.parentNode != null && nodes.indexOf(node) < 0) clear([node], [cached[i]]); } if (data.length < cached.length) cached.length = data.length; - cached.nodes = nodes + cached.nodes = nodes; } } else if (data != null && dataType === OBJECT) { - var views = [], controllers = [] + var views = [], controllers = []; while (data.view) { - var view = data.view.$original || data.view - var controllerIndex = m.redraw.strategy() == "diff" && cached.views ? cached.views.indexOf(view) : -1 - var controller = controllerIndex > -1 ? cached.controllers[controllerIndex] : new (data.controller || noop) - var key = data && data.attrs && data.attrs.key - data = (pendingRequests == 0 || forcing) || (cached && cached.controllers && cached.controllers.indexOf(controller) > -1) ? data.view(controller) : {tag: "placeholder"} + var view = data.view.$original || data.view; + var controllerIndex = m.redraw.strategy() == "diff" && cached.views ? cached.views.indexOf(view) : -1; + var controller = controllerIndex > -1 ? cached.controllers[controllerIndex] : new (data.controller || noop); + var key = data && data.attrs && data.attrs.key; + data = (pendingRequests == 0 || forcing) || (cached && cached.controllers && cached.controllers.indexOf(controller) > -1) ? data.view(controller) : {tag: "placeholder"}; if (data.subtree === "retain") return cached; if (key) { - if (!data.attrs) data.attrs = {} - data.attrs.key = key + if (!data.attrs) data.attrs = {}; + data.attrs.key = key; } - if (controller.onunload) unloaders.push({controller: controller, handler: controller.onunload}) - views.push(view) - controllers.push(controller) + if (controller.onunload) unloaders.push({controller: controller, handler: controller.onunload}); + views.push(view); + controllers.push(controller); } - if (!data.tag && controllers.length) throw new Error("Component template must return a virtual element, not an array, string, etc.") + if (!data.tag && controllers.length) throw new Error("Component template must return a virtual element, not an array, string, etc."); if (!data.attrs) data.attrs = {}; if (!cached.attrs) cached.attrs = {}; - var dataAttrKeys = Object.keys(data.attrs) - var hasKeys = dataAttrKeys.length > ("key" in data.attrs ? 1 : 0) + var dataAttrKeys = Object.keys(data.attrs); + var hasKeys = dataAttrKeys.length > ("key" in data.attrs ? 1 : 0); //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 && typeof cached.configContext.onunload === FUNCTION) 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 (typeof controller.onunload === FUNCTION) controller.onunload({preventDefault: noop}); } } } @@ -278,7 +278,7 @@ var m = (function app(window, undefined) { if (data.attrs.xmlns) namespace = data.attrs.xmlns; else if (data.tag === "svg") namespace = "http://www.w3.org/2000/svg"; else if (data.tag === "math") namespace = "http://www.w3.org/1998/Math/MathML"; - + if (isNew) { if (data.attrs.is) node = namespace === undefined ? $document.createElement(data.tag, data.attrs.is) : $document.createElementNS(namespace, data.tag, data.attrs.is); else node = namespace === undefined ? $document.createElement(data.tag) : $document.createElementNS(namespace, data.tag); @@ -292,22 +292,22 @@ var m = (function app(window, undefined) { nodes: [node] }; if (controllers.length) { - cached.views = views - cached.controllers = controllers + cached.views = views; + cached.controllers = controllers; for (var i = 0, controller; controller = controllers[i]; i++) { - if (controller.onunload && controller.onunload.$old) controller.onunload = controller.onunload.$old + if (controller.onunload && controller.onunload.$old) controller.onunload = controller.onunload.$old; if (pendingRequests && controller.onunload) { - var onunload = controller.onunload - controller.onunload = noop - controller.onunload.$old = onunload + var onunload = controller.onunload; + controller.onunload = noop; + controller.onunload.$old = onunload; } } } - + if (cached.children && !cached.children.nodes) cached.children.nodes = []; //edge case: setting value on