void (function (global, factory) { // eslint-disable-line "use strict" /* eslint-disable no-undef */ var m = factory(typeof window !== "undefined" ? window : {}) if (typeof module === "object" && module != null && module.exports) { module.exports = m } else if (typeof define === "function" && define.amd) { define(function () { return m }) } else { global.m = m } /* eslint-enable no-undef */ })(this, function (window, undefined) { // eslint-disable-line "use strict" var VERSION = "v0.2.1" // Save these two. var type = {}.toString var hasOwn = {}.hasOwnProperty 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]" } function noop() {} function forEach(list, f) { for (var i = 0; i < list.length && !f(list[i], i++);) { // empty } } function forOwn(obj, f) { for (var prop in obj) { if (hasOwn.call(obj, prop)) { if (f(obj[prop], prop)) break } } } var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g var attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/ // eslint-disable-line max-len // caching commonly used variables var $document, $location, $requestAnimationFrame, $cancelAnimationFrame // self invoking function needed because of the way mocks work function initialize(window) { $document = window.document $location = window.location $cancelAnimationFrame = window.cancelAnimationFrame || window.clearTimeout $requestAnimationFrame = window.requestAnimationFrame || window.setTimeout } initialize(window) // testing API m.deps = function (mock) { initialize(window = mock || window) return window } m.version = function () { return VERSION } /** * @typedef {String} Tag * A string that looks like -> div.classname#id[param=one][param2=two] * Which describes a DOM node */ function checkForAttrs(pairs) { return pairs != null && isObject(pairs) && !("tag" in pairs || "view" in pairs || "subtree" in pairs) } function parseSelector(tag, cell) { var classes = [] var match while ((match = parser.exec(tag)) != null) { 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) } } return classes } function getChildrenFromList(hasAttrs, args) { var children = hasAttrs ? args.slice(1) : args if (children.length === 1 && isArray(children[0])) { return children[0] } else { return children } } function assignAttrs(cell, attrs, classAttr, classes) { forOwn(attrs, function (value, attr) { if (attr === classAttr && attrs[attr] != null && attrs[attr] !== "") { classes.push(attrs[attr]) // create key in correct iteration order cell.attrs[attr] = "" } else { cell.attrs[attr] = attrs[attr] } }) if (classes.length) { cell.attrs[classAttr] = classes.join(" ") } } /** * @param {Tag} The DOM node tag * @param {Object=[]} optional key-value pairs to be mapped to DOM attrs * @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array, * or splat (optional) */ 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 = checkForAttrs(pairs) var attrs = hasAttrs ? pairs : {} var classAttr = "class" in attrs ? "class" : "className" var cell = {tag: "div", attrs: {}} if (!isString(tag)) { throw new Error("selector in m(selector, attrs, children) should " + "be a string") } var classes = parseSelector(tag, cell) cell.children = getChildrenFromList(hasAttrs, args) assignAttrs(cell, attrs, classAttr, classes) return cell } function forKeys(list, f) { forEach(list, function (attrs, i) { attrs = attrs && attrs.attrs return attrs && attrs.key != null && f(attrs, i) }) } // This function was causing deopts in Chrome. function dataToString(data) { // data.toString() might throw or return null if data is the return // value of Console.log in some versions of Firefox try { if (data != null && data.toString() != null) { return data } } catch (e) { // Swallow all errors here. } return "" } // This function was causing deopts in Chrome. function injectTextNode(parent, first, index, data) { try { insertNode(parent, first, index) first.nodeValue = data } catch (e) { // IE erroneously throws error when appending an empty text node // after a null } } function flatten(list) { // recursively flatten array for (var i = 0; i < list.length; i++) { if (isArray(list[i])) { list = list.concat.apply([], list) // check current index again while there is an array at this // index. i-- } } return list } function insertNode(parent, node, index) { parent.insertBefore(node, parent.childNodes[index] || null) } var DELETION = 1 var INSERTION = 2 var MOVE = 3 function handleKeysDiffer(data, existing, cached, parent) { forKeys(data, function (key, i) { key = key.key if (existing[key]) { existing[key] = { action: MOVE, index: i, from: existing[key].index, element: cached.nodes[existing[key].index] || $document.createElement("div") } } else { existing[key] = {action: INSERTION, index: i} } }) var actions = [] forOwn(existing, function (value) { actions.push(value) }) var changes = actions.sort(sortChanges) var newCached = new Array(cached.length) newCached.nodes = cached.nodes.slice() forEach(changes, function (change) { var index = change.index switch (change.action) { case DELETION: clear(cached[index].nodes, cached[index]) newCached.splice(index, 1) break case INSERTION: var dummy = $document.createElement("div") dummy.key = data[index].attrs.key insertNode(parent, dummy, index) newCached.splice(index, 0, { attrs: {key: data[index].attrs.key}, nodes: [dummy] }) newCached.nodes[index] = dummy break case MOVE: var changeElement = change.element var maybeChanged = parent.childNodes[index] if (maybeChanged !== changeElement && changeElement !== null) { parent.insertBefore(changeElement, maybeChanged || null) } newCached[index] = cached[change.from] newCached.nodes[index] = changeElement } }) return newCached } function diffKeys(data, cached, existing, parentElement) { var keysDiffer = data.length !== cached.length if (!keysDiffer) { forKeys(data, function (attrs, i) { var cachedCell = cached[i] return keysDiffer = cachedCell && cachedCell.attrs && cachedCell.attrs.key !== attrs.key }) } if (keysDiffer) { return handleKeysDiffer(data, existing, cached, parentElement) } else { return cached } } // diffs the array itself function diffArray(data, cached, nodes) { // update the list of DOM nodes by collecting the nodes from each item forEach(data, function (_, i) { 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 forEach(cached.nodes, function (node, 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 } function buildArrayKeys(data) { var guid = 0 forKeys(data, function () { forEach(data, function (attrs) { attrs = attrs && attrs.attrs if (attrs && attrs.key == null) { attrs.key = "__mithril__" + guid++ } }) return true }) } // shallow array compare, sorts function arraySortCompare(a, b) { a.sort() b.sort() var len = a.length if (len !== b.length) return false for (var i = 0; i < len; i++) { if (a[i] !== b[i]) return false } return true } function elemIsDifferentEnough(data, cached, dataAttrKeys) { if (data.tag !== cached.tag) return true if (!arraySortCompare(dataAttrKeys, Object.keys(cached.attrs))) { return true } if (data.attrs.id !== cached.attrs.id) return true if (data.attrs.key !== cached.attrs.key) return true if (m.redraw.strategy() === "all") { return !(cached.configContext && cached.configContext.retain === true) } else if (m.redraw.strategy() === "diff") { return cached.configContext && cached.configContext.retain === false } } function maybeRecreateObject(data, cached, dataAttrKeys) { // if an element is different enough from the one in cache, recreate it if (elemIsDifferentEnough(data, cached, dataAttrKeys)) { if (cached.nodes.length) clear(cached.nodes) if (cached.configContext && isFunction(cached.configContext.onunload)) { cached.configContext.onunload() } if (cached.controllers) { forEach(cached.controllers, function (controller) { if (controller.unload) { controller.onunload({preventDefault: noop}) } }) } } } function getObjectNamespace(data, namespace) { return data.attrs.xmlns ? data.attrs.xmlns : data.tag === "svg" ? "http://www.w3.org/2000/svg" : data.tag === "math" ? "http://www.w3.org/1998/Math/MathML" : namespace } var pendingRequests = 0 m.startComputation = function () { pendingRequests++ } m.endComputation = function () { if (pendingRequests > 1) { pendingRequests-- } else { pendingRequests = 0 m.redraw() } } function unloadCachedControllers(cached, views, controllers) { if (controllers.length) { cached.views = views cached.controllers = controllers forEach(controllers, function (controller) { 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 } }) } } function scheduleConfigsToBeCalled(configs, data, node, isNew, cached) { // schedule configs to be called. They are called after `build` finishes // running var config = data.attrs.config if (isFunction(config)) { var context = cached.configContext = cached.configContext || {} // bind configs.push(function () { return config.call(data, node, !isNew, context, cached) }) } } function buildUpdatedNode( cached, data, editable, hasKeys, namespace, views, configs, controllers ) { var node = cached.nodes[0] if (hasKeys) { setAttributes(node, data.tag, data.attrs, cached.attrs, namespace) } cached.children = build(node, data.tag, undefined, undefined, data.children, cached.children, false, 0, data.attrs.contenteditable ? node : editable, namespace, configs) cached.nodes.intact = true if (controllers.length) { cached.views = views cached.controllers = controllers } return node } function handleNonexistentNodes(data, parent, index) { var nodes if (data.$trusted) { nodes = injectHTML(parent, index, data) } else { nodes = [$document.createTextNode(data)] if (!voidElements.test(parent.nodeName)) { insertNode(parent, nodes[0], index) } } var cached if (typeof data === "string" || typeof data === "number" || typeof data === "boolean") { cached = new data.constructor(data) } else { cached = data } cached.nodes = nodes return cached } function reattachNodes(data, cached, parentElement, editable, index, parentTag ) { var nodes = cached.nodes if (!editable || editable !== $document.activeElement) { if (data.$trusted) { clear(nodes, cached) nodes = injectHTML(parentElement, index, data) } else if (parentTag === "textarea") { //