diff options
Diffstat (limited to 'lib/vendor/mithril.js')
-rw-r--r-- | lib/vendor/mithril.js | 2132 |
1 files changed, 2132 insertions, 0 deletions
diff --git a/lib/vendor/mithril.js b/lib/vendor/mithril.js new file mode 100644 index 000000000..d023ac34c --- /dev/null +++ b/lib/vendor/mithril.js @@ -0,0 +1,2132 @@ +;(function (global, factory) { // eslint-disable-line + "use strict" + /* eslint-disable no-undef */ + var m = factory(global) + 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 */ +})(typeof window !== "undefined" ? window : {}, function (global, undefined) { // eslint-disable-line + "use strict" + + m.version = function () { + return "v0.2.2-rc.1" + } + + var hasOwn = {}.hasOwnProperty + var type = {}.toString + + 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() {} + + /* eslint-disable max-len */ + var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/ + /* eslint-enable max-len */ + + // caching commonly used variables + var $document, $location, $requestAnimationFrame, $cancelAnimationFrame + + // self invoking function needed because of the way mocks work + function initialize(mock) { + $document = mock.document + $location = mock.location + $cancelAnimationFrame = mock.cancelAnimationFrame || mock.clearTimeout + $requestAnimationFrame = mock.requestAnimationFrame || mock.setTimeout + } + + // testing API + m.deps = function (mock) { + initialize(global = mock || window) + return global + } + + m.deps(global) + + /** + * @typedef {String} Tag + * A string that looks like -> div.classname#id[param=one][param2=two] + * Which describes a DOM node + */ + + function parseTagAttrs(cell, tag) { + var classes = [] + var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g + var match + + while ((match = parser.exec(tag))) { + 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 = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/.exec(match[3]) + cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" : true) + } + } + + return classes + } + + function getVirtualChildren(args, hasAttrs) { + var children = hasAttrs ? args.slice(1) : args + + if (children.length === 1 && isArray(children[0])) { + return children[0] + } else { + return children + } + } + + function assignAttrs(target, attrs, classes) { + var classAttr = "class" in attrs ? "class" : "className" + + for (var attrName in attrs) { + if (hasOwn.call(attrs, attrName)) { + if (attrName === classAttr && + attrs[attrName] != null && + attrs[attrName] !== "") { + classes.push(attrs[attrName]) + // create key in correct iteration order + target[attrName] = "" + } else { + target[attrName] = attrs[attrName] + } + } + } + + if (classes.length) target[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) + + if (!isString(tag)) { + throw new Error("selector in m(selector, attrs, children) should " + + "be a string") + } + + var hasAttrs = pairs != null && isObject(pairs) && + !("tag" in pairs || "view" in pairs || "subtree" in pairs) + + var attrs = hasAttrs ? pairs : {} + var cell = { + tag: "div", + attrs: {}, + children: getVirtualChildren(args, hasAttrs) + } + + assignAttrs(cell.attrs, attrs, parseTagAttrs(cell, tag)) + return cell + } + + function forEach(list, f) { + for (var i = 0; i < list.length && !f(list[i], i++);) { + // function called in condition + } + } + + function forKeys(list, f) { + forEach(list, function (attrs, i) { + return (attrs = attrs && attrs.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 (behavior depends on + // version) + try { + if (data != null && data.toString() != null) return data + } catch (e) { + // silently ignore errors + } + return "" + } + + // This function was causing deopts in Chrome. + function injectTextNode(parentElement, first, index, data) { + try { + insertNode(parentElement, 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 and flatten until there are no more + // nested arrays at that index + i-- + } + } + return list + } + + function insertNode(parentElement, node, index) { + parentElement.insertBefore(node, + parentElement.childNodes[index] || null) + } + + var DELETION = 1 + var INSERTION = 2 + var MOVE = 3 + + function handleKeysDiffer(data, existing, cached, parentElement) { + forKeys(data, function (key, i) { + existing[key = key.key] = existing[key] ? { + action: MOVE, + index: i, + from: existing[key].index, + element: cached.nodes[existing[key].index] || + $document.createElement("div") + } : {action: INSERTION, index: i} + }) + + var actions = [] + for (var prop in existing) if (hasOwn.call(existing, prop)) { + actions.push(existing[prop]) + } + + var changes = actions.sort(sortChanges) + var newCached = new Array(cached.length) + + newCached.nodes = cached.nodes.slice() + + forEach(changes, function (change) { + var index = change.index + if (change.action === DELETION) { + clear(cached[index].nodes, cached[index]) + newCached.splice(index, 1) + } + if (change.action === INSERTION) { + var dummy = $document.createElement("div") + dummy.key = data[index].attrs.key + insertNode(parentElement, dummy, index) + newCached.splice(index, 0, { + attrs: {key: data[index].attrs.key}, + nodes: [dummy] + }) + newCached.nodes[index] = dummy + } + + if (change.action === MOVE) { + var changeElement = change.element + var maybeChanged = parentElement.childNodes[index] + if (maybeChanged !== changeElement && changeElement !== null) { + parentElement.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 + } + } + + function diffArray(data, cached, nodes) { + // diff the array itself + + // 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) { + if ((attrs = attrs && attrs.attrs) && attrs.key == null) { + attrs.key = "__mithril__" + guid++ + } + }) + return 1 + }) + } + + function isDifferentEnough(data, cached, dataAttrKeys) { + if (data.tag !== cached.tag) return true + + if (dataAttrKeys.sort().join() !== + Object.keys(cached.attrs).sort().join()) { + 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 + } + + if (m.redraw.strategy() === "diff") { + return cached.configContext && cached.configContext.retain === false + } + + return false + } + + function maybeRecreateObject(data, cached, dataAttrKeys) { + // if an element is different enough from the one in cache, recreate it + if (isDifferentEnough(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.onunload) controller.onunload({preventDefault: noop}); + }); + } + } + } + + function getObjectNamespace(data, namespace) { + if (data.attrs.xmlns) return data.attrs.xmlns + if (data.tag === "svg") return "http://www.w3.org/2000/svg" + if (data.tag === "math") return "http://www.w3.org/1998/Math/MathML" + return 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 + if (isFunction(data.attrs.config)) { + var context = cached.configContext = cached.configContext || {} + + // bind + configs.push(function () { + return data.attrs.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, parentElement, index) { + var nodes + if (data.$trusted) { + nodes = injectHTML(parentElement, index, data) + } else { + nodes = [$document.createTextNode(data)] + if (!parentElement.nodeName.match(voidElements)) { + insertNode(parentElement, 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") { + // <textarea> uses `value` instead of `nodeValue`. + parentElement.value = data + } else if (editable) { + // contenteditable nodes use `innerHTML` instead of `nodeValue`. + editable.innerHTML = data + } else { + // was a trusted string + if (nodes[0].nodeType === 1 || nodes.length > 1 || + (nodes[0].nodeValue.trim && + !nodes[0].nodeValue.trim())) { + clear(cached.nodes, cached) + nodes = [$document.createTextNode(data)] + } + + injectTextNode(parentElement, nodes[0], index, data) + } + } + cached = new data.constructor(data) + cached.nodes = nodes + return cached + } + + function handleTextNode( + cached, + data, + index, + parentElement, + shouldReattach, + editable, + parentTag + ) { + if (!cached.nodes.length) { + return handleNonexistentNodes(data, parentElement, index) + } else if (cached.valueOf() !== data.valueOf() || shouldReattach) { + return reattachNodes(data, cached, parentElement, editable, index, + parentTag) + } else { + return (cached.nodes.intact = true, cached) + } + } + + function getSubArrayCount(item) { + if (item.$trusted) { + // 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 + var match = item.match(/<[^\/]|\>\s*[^<]/g) + if (match != null) return match.length + } else if (isArray(item)) { + return item.length + } + return 1 + } + + function buildArray( + data, + cached, + parentElement, + index, + parentTag, + shouldReattach, + editable, + namespace, + configs + ) { + data = flatten(data) + var nodes = [] + var intact = cached.length === data.length + var subArrayCount = 0 + + // keys algorithm: sort elements without recreating them if keys are + // present + // + // 1) create a map of all existing keys, and mark all for deletion + // 2) add new keys to map and mark them for addition + // 3) if key exists in new list, change action from deletion to a move + // 4) for each key, handle its corresponding action as marked in + // previous steps + + var existing = {} + var shouldMaintainIdentities = false + + forKeys(cached, function (attrs, i) { + shouldMaintainIdentities = true + existing[cached[i].attrs.key] = {action: DELETION, index: i} + }) + + buildArrayKeys(data) + if (shouldMaintainIdentities) { + cached = diffKeys(data, cached, existing, parentElement) + } + // end key algorithm + + var cacheCount = 0 + // faster explicitly written + for (var i = 0, len = data.length; i < len; i++) { + // diff each item in the array + var item = build( + parentElement, + parentTag, + cached, + index, + data[i], + cached[cacheCount], + shouldReattach, + index + subArrayCount || subArrayCount, + editable, + namespace, + configs) + + if (item !== undefined) { + intact = intact && item.nodes.intact + subArrayCount += getSubArrayCount(item) + cached[cacheCount++] = item + } + } + + if (!intact) diffArray(data, cached, nodes) + return cached + } + + function makeCache(data, cached, index, parentIndex, parentCache) { + if (cached != null) { + if (type.call(cached) === type.call(data)) return cached + + if (parentCache && parentCache.nodes) { + var offset = index - parentIndex + 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) + } + } + + cached = new data.constructor() + // if constructor creates a virtual dom element, use a blank object as + // the base cached node instead of copying the virtual el (#277) + if (cached.tag) cached = {} + cached.nodes = [] + return cached + } + + function constructNode(data, namespace) { + if (data.attrs.is) { + if (namespace == null) { + return $document.createElement(data.tag, data.attrs.is) + } else { + return $document.createElementNS(namespace, data.tag, + data.attrs.is) + } + } else if (namespace == null) { + return $document.createElement(data.tag) + } else { + return $document.createElementNS(namespace, data.tag) + } + } + + function constructAttrs(data, node, namespace, hasKeys) { + if (hasKeys) { + return setAttributes(node, data.tag, data.attrs, {}, namespace) + } else { + return data.attrs + } + } + + function constructChildren( + data, + node, + cached, + editable, + namespace, + configs + ) { + if (data.children != null && data.children.length > 0) { + return build( + node, + data.tag, + undefined, + undefined, + data.children, + cached.children, + true, + 0, + data.attrs.contenteditable ? node : editable, + namespace, + configs) + } else { + return data.children + } + } + + function reconstructCached( + data, + attrs, + children, + node, + namespace, + views, + controllers + ) { + var cached = { + tag: data.tag, + attrs: attrs, + children: children, + nodes: [node] + } + + unloadCachedControllers(cached, views, controllers) + + if (cached.children && !cached.children.nodes) { + cached.children.nodes = [] + } + + // edge case: setting value on <select> doesn't work before children + // exist, so set it again after children have been created + if (data.tag === "select" && "value" in data.attrs) { + setAttributes(node, data.tag, {value: data.attrs.value}, {}, + namespace) + } + + return cached + } + + function getController(views, view, cachedControllers, controller) { + var controllerIndex + + if (m.redraw.strategy() === "diff" && views) { + controllerIndex = views.indexOf(view) + } else { + controllerIndex = -1 + } + + if (controllerIndex > -1) { + return cachedControllers[controllerIndex] + } else if (isFunction(controller)) { + return new controller() + } else { + return {} + } + } + + var unloaders = [] + + function updateLists(views, controllers, view, controller) { + if (controller.onunload != null) { + unloaders.push({ + controller: controller, + handler: controller.onunload + }) + } + + views.push(view) + controllers.push(controller) + } + + var forcing = false + function checkView(data, view, cached, cachedControllers, controllers, views) { + var controller = getController(cached.views, view, cachedControllers, data.controller) + var key = data && data.attrs && data.attrs.key + data = pendingRequests === 0 || forcing || cachedControllers && cachedControllers.indexOf(controller) > -1 ? data.view(controller) : {tag: "placeholder"} + if (data.subtree === "retain") return data; + data.attrs = data.attrs || {} + data.attrs.key = key + updateLists(views, controllers, view, controller) + return data + } + + function markViews(data, cached, views, controllers) { + var cachedControllers = cached && cached.controllers + + while (data.view != null) { + data = checkView( + data, + data.view.$original || data.view, + cached, + cachedControllers, + controllers, + views) + } + + return data + } + + function buildObject( // eslint-disable-line max-statements + data, + cached, + editable, + parentElement, + index, + shouldReattach, + namespace, + configs + ) { + var views = [] + var controllers = [] + + data = markViews(data, cached, views, controllers) + + if (data.subtree === "retain") return cached + + if (!data.tag && controllers.length) { + throw new Error("Component template must return a virtual " + + "element, not an array, string, etc.") + } + + data.attrs = data.attrs || {} + cached.attrs = cached.attrs || {} + + var dataAttrKeys = Object.keys(data.attrs) + var hasKeys = dataAttrKeys.length > ("key" in data.attrs ? 1 : 0) + + maybeRecreateObject(data, cached, dataAttrKeys) + + if (!isString(data.tag)) return + + var isNew = cached.nodes.length === 0 + + namespace = getObjectNamespace(data, namespace) + + var node + if (isNew) { + node = constructNode(data, namespace) + // set attributes first, then create children + var attrs = constructAttrs(data, node, namespace, hasKeys) + + var children = constructChildren(data, node, cached, editable, + namespace, configs) + + cached = reconstructCached( + data, + attrs, + children, + node, + namespace, + views, + controllers) + } else { + node = buildUpdatedNode( + cached, + data, + editable, + hasKeys, + namespace, + views, + configs, + controllers) + } + + if (isNew || shouldReattach === true && node != null) { + insertNode(parentElement, node, index) + } + + // The configs are called after `build` finishes running + scheduleConfigsToBeCalled(configs, data, node, isNew, cached) + + return cached + } + + 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` the + * diff algorithm can be summarized as this: + * + * 1 - compare `data` and `cached` + * 2 - if they are different, copy `data` to `cached` and update the DOM + * based on what the difference is + * 3 - recursively apply this algorithm for every array and for the + * children of every virtual element + * + * The `cached` data structure is essentially the same as the previous + * redraw's `data` data structure, with a few additions: + * - `cached` always has a property called `nodes`, which is a list of + * DOM elements that correspond to the data represented by the + * respective virtual element + * - in order to support attaching `nodes` as a property of `cached`, + * `cached` is *always* a non-primitive object, i.e. if the data was + * a string, then cached is a String instance. If data was `null` or + * `undefined`, cached is `new String("")` + * - `cached also has a `configContext` property, which is the state + * storage object exposed by config(element, isInitialized, context) + * - when `cached` is an Object, it represents a virtual element; when + * it's an Array, it represents a list of elements; when it's a + * String, Number or Boolean, it represents a text node + * + * `parentElement` is a DOM element used for W3C DOM API calls + * `parentTag` is only used for handling a corner case for textarea + * values + * `parentCache` is used to remove nodes in some multi-node cases + * `parentIndex` and `index` are used to figure out the offset of nodes. + * They're artifacts from before arrays started being flattened and are + * likely refactorable + * `data` and `cached` are, respectively, the new and old nodes being + * diffed + * `shouldReattach` is a flag indicating whether a parent node was + * recreated (if so, and if this node is reused, then this node must + * reattach itself to the new parent) + * `editable` is a flag that indicates whether an ancestor is + * contenteditable + * `namespace` indicates the closest HTML namespace as it cascades down + * from an ancestor + * `configs` is a list of config functions to run after the topmost + * `build` call finishes running + * + * there's logic that relies on the assumption that null and undefined + * data are equivalent to empty strings + * - 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 = dataToString(data) + if (data.subtree === "retain") return cached + cached = makeCache(data, cached, index, parentIndex, parentCache) + + if (isArray(data)) { + return buildArray( + data, + cached, + parentElement, + index, + parentTag, + shouldReattach, + editable, + namespace, + configs) + } else if (data != null && isObject(data)) { + return buildObject( + data, + cached, + editable, + parentElement, + index, + shouldReattach, + namespace, + configs) + } else if (!isFunction(data)) { + return handleTextNode( + cached, + data, + index, + parentElement, + shouldReattach, + editable, + parentTag) + } else { + return cached + } + } + + function sortChanges(a, b) { + return a.action - b.action || a.index - b.index + } + + function copyStyleAttrs(node, dataAttr, cachedAttr) { + for (var rule in dataAttr) if (hasOwn.call(dataAttr, rule)) { + if (cachedAttr == null || cachedAttr[rule] !== dataAttr[rule]) { + node.style[rule] = dataAttr[rule] + } + } + + for (rule in cachedAttr) if (hasOwn.call(cachedAttr, rule)) { + if (!hasOwn.call(dataAttr, rule)) node.style[rule] = "" + } + } + + function shouldUseSetAttribute(attrName) { + return attrName !== "list" && + attrName !== "style" && + attrName !== "form" && + attrName !== "type" && + attrName !== "width" && + attrName !== "height" + } + + function setSingleAttr( + node, + attrName, + dataAttr, + cachedAttr, + tag, + namespace + ) { + if (attrName === "config" || attrName === "key") { + // `config` isn't a real attribute, so ignore it + return true + } else if (isFunction(dataAttr) && attrName.slice(0, 2) === "on") { + // hook event handlers to the auto-redrawing system + node[attrName] = autoredraw(dataAttr, node) + } else if (attrName === "style" && dataAttr != null && + isObject(dataAttr)) { + // handle `style: {...}` + copyStyleAttrs(node, dataAttr, cachedAttr) + } else if (namespace != null) { + // handle SVG + if (attrName === "href") { + node.setAttributeNS("http://www.w3.org/1999/xlink", + "href", dataAttr) + } else { + node.setAttribute( + attrName === "className" ? "class" : attrName, + dataAttr) + } + } else if (attrName in node && shouldUseSetAttribute(attrName)) { + // handle cases that are properties (but ignore cases where we + // should use setAttribute instead) + // + // - list and form are typically used as strings, but are DOM + // element references in js + // + // - when using CSS selectors (e.g. `m("[style='']")`), style is + // used as a string, but it's an object in js + // + // #348 don't set the value if not needed - otherwise, cursor + // placement breaks in Chrome + try { + if (tag !== "input" || node[attrName] !== dataAttr) { + node[attrName] = dataAttr + } + } catch (e) { + node.setAttribute(attrName, dataAttr) + } + } + else node.setAttribute(attrName, dataAttr) + } + + function trySetAttr( + node, + attrName, + dataAttr, + cachedAttr, + cachedAttrs, + tag, + namespace + ) { + if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr)) { + cachedAttrs[attrName] = dataAttr + try { + return setSingleAttr( + node, + attrName, + dataAttr, + cachedAttr, + tag, + namespace) + } catch (e) { + // swallow IE's invalid argument errors to mimic HTML's + // fallback-to-doing-nothing-on-invalid-attributes behavior + if (e.message.indexOf("Invalid argument") < 0) throw e + } + } else if (attrName === "value" && tag === "input" && + node.value !== dataAttr) { + // #348 dataAttr may not be a string, so use loose comparison + node.value = dataAttr + } + } + + function setAttributes(node, tag, dataAttrs, cachedAttrs, namespace) { + for (var attrName in dataAttrs) if (hasOwn.call(dataAttrs, attrName)) { + if (trySetAttr( + node, + attrName, + dataAttrs[attrName], + cachedAttrs[attrName], + cachedAttrs, + tag, + namespace)) { + continue + } + } + return cachedAttrs + } + + function clear(nodes, cached) { + for (var i = nodes.length - 1; i > -1; i--) { + if (nodes[i] && nodes[i].parentNode) { + try { + nodes[i].parentNode.removeChild(nodes[i]) + } catch (e) { + /* eslint-disable max-len */ + // ignore if this fails due to order of events (see + // http://stackoverflow.com/questions/21926083/failed-to-execute-removechild-on-node) + /* eslint-enable max-len */ + } + cached = [].concat(cached) + if (cached[i]) unload(cached[i]) + } + } + // release memory if nodes is an array. This check should fail if nodes + // is a NodeList (see loop above) + if (nodes.length) { + nodes.length = 0 + } + } + + function unload(cached) { + if (cached.configContext && isFunction(cached.configContext.onunload)) { + cached.configContext.onunload() + cached.configContext.onunload = null + } + if (cached.controllers) { + forEach(cached.controllers, function (controller) { + if (isFunction(controller.onunload)) { + controller.onunload({preventDefault: noop}) + } + }) + } + if (cached.children) { + if (isArray(cached.children)) forEach(cached.children, unload) + else if (cached.children.tag) unload(cached.children) + } + } + + function appendTextFragment(parentElement, data) { + try { + parentElement.appendChild( + $document.createRange().createContextualFragment(data)) + } catch (e) { + parentElement.insertAdjacentHTML("beforeend", data) + } + } + + function injectHTML(parentElement, index, data) { + var nextSibling = parentElement.childNodes[index] + if (nextSibling) { + var isElement = nextSibling.nodeType !== 1 + var placeholder = $document.createElement("span") + if (isElement) { + parentElement.insertBefore(placeholder, nextSibling || null) + placeholder.insertAdjacentHTML("beforebegin", data) + parentElement.removeChild(placeholder) + } else { + nextSibling.insertAdjacentHTML("beforebegin", data) + } + } else { + appendTextFragment(parentElement, data) + } + + var nodes = [] + + while (parentElement.childNodes[index] !== nextSibling) { + nodes.push(parentElement.childNodes[index]) + index++ + } + + return nodes + } + + function autoredraw(callback, object) { + return function (e) { + e = e || event + m.redraw.strategy("diff") + m.startComputation() + try { + return callback.call(object, e) + } finally { + endFirstComputation() + } + } + } + + var html + var documentNode = { + appendChild: function (node) { + if (html === undefined) html = $document.createElement("html") + if ($document.documentElement && + $document.documentElement !== node) { + $document.replaceChild(node, $document.documentElement) + } else { + $document.appendChild(node) + } + + this.childNodes = $document.childNodes + }, + + insertBefore: function (node) { + this.appendChild(node) + }, + + childNodes: [] + } + + var nodeCache = [] + var cellCache = {} + + m.render = function (root, cell, forceRecreation) { + if (!root) { + throw new Error("Ensure the DOM element being passed to " + + "m.route/m.mount/m.render is not undefined.") + } + var configs = [] + var id = getCellCacheKey(root) + var isDocumentRoot = root === $document + var node + + if (isDocumentRoot || root === $document.documentElement) { + node = documentNode + } else { + node = root + } + + if (isDocumentRoot && cell.tag !== "html") { + cell = {tag: "html", attrs: {}, children: cell} + } + + if (cellCache[id] === undefined) clear(node.childNodes) + if (forceRecreation === true) reset(root) + + cellCache[id] = build( + node, + null, + undefined, + undefined, + cell, + cellCache[id], + false, + 0, + null, + undefined, + configs) + + forEach(configs, function (config) { config() }) + } + + function getCellCacheKey(element) { + var index = nodeCache.indexOf(element) + return index < 0 ? nodeCache.push(element) - 1 : index + } + + m.trust = function (value) { + value = new String(value) // eslint-disable-line no-new-wrappers + value.$trusted = true + return value + } + + function gettersetter(store) { + function prop() { + if (arguments.length) store = arguments[0] + return store + } + + prop.toJSON = function () { + return store + } + + return prop + } + + m.prop = function (store) { + if ((store != null && isObject(store) || isFunction(store)) && + isFunction(store.then)) { + return propify(store) + } + + return gettersetter(store) + } + + var roots = [] + var components = [] + var controllers = [] + var lastRedrawId = null + var lastRedrawCallTime = 0 + var computePreRedrawHook = null + var computePostRedrawHook = null + var topComponent + var FRAME_BUDGET = 16 // 60 frames per second = 1 call per 16 ms + + function parameterize(component, args) { + function controller() { + /* eslint-disable no-invalid-this */ + return (component.controller || noop).apply(this, args) || this + /* eslint-enable no-invalid-this */ + } + + if (component.controller) { + controller.prototype = component.controller.prototype + } + + function view(ctrl) { + var currentArgs = [ctrl].concat(args) + for (var i = 1; i < arguments.length; i++) { + currentArgs.push(arguments[i]) + } + + return component.view.apply(component, currentArgs) + } + + view.$original = component.view + var output = {controller: controller, view: view} + if (args[0] && args[0].key != null) output.attrs = {key: args[0].key} + return output + } + + m.component = function (component) { + for (var args = [], i = 1; i < arguments.length; i++) { + args.push(arguments[i]) + } + + return parameterize(component, args) + } + + function checkPrevented(component, root, index, isPrevented) { + if (!isPrevented) { + m.redraw.strategy("all") + m.startComputation() + roots[index] = root + var currentComponent + + if (component) { + currentComponent = topComponent = component + } else { + currentComponent = topComponent = component = {controller: noop} + } + + var controller = new (component.controller || noop)() + + // controllers may call m.mount recursively (via m.route redirects, + // for example) + // this conditional ensures only the last recursive m.mount call is + // applied + if (currentComponent === topComponent) { + controllers[index] = controller + components[index] = component + } + endFirstComputation() + if (component === null) { + removeRootElement(root, index) + } + return controllers[index] + } else if (component == null) { + removeRootElement(root, index) + } + } + + m.mount = m.module = function (root, component) { + if (!root) { + throw new Error("Please ensure the DOM element exists before " + + "rendering a template into it.") + } + + var index = roots.indexOf(root) + if (index < 0) index = roots.length + + var isPrevented = false + var event = { + preventDefault: function () { + isPrevented = true + computePreRedrawHook = computePostRedrawHook = null + } + } + + forEach(unloaders, function (unloader) { + unloader.handler.call(unloader.controller, event) + unloader.controller.onunload = null + }) + + if (isPrevented) { + forEach(unloaders, function (unloader) { + unloader.controller.onunload = unloader.handler + }) + } else { + unloaders = [] + } + + if (controllers[index] && isFunction(controllers[index].onunload)) { + controllers[index].onunload(event) + } + + return checkPrevented(component, root, index, isPrevented) + } + + function removeRootElement(root, index) { + roots.splice(index, 1) + controllers.splice(index, 1) + components.splice(index, 1) + reset(root) + nodeCache.splice(getCellCacheKey(root), 1) + } + + var redrawing = false + m.redraw = function (force) { + if (redrawing) return + redrawing = true + if (force) forcing = true + + 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) { + // 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 === global.requestAnimationFrame || + new Date() - lastRedrawCallTime > FRAME_BUDGET) { + if (lastRedrawId > 0) $cancelAnimationFrame(lastRedrawId) + lastRedrawId = $requestAnimationFrame(redraw, FRAME_BUDGET) + } + } else { + redraw() + lastRedrawId = $requestAnimationFrame(function () { + lastRedrawId = null + }, FRAME_BUDGET) + } + } finally { + redrawing = forcing = false + } + } + + m.redraw.strategy = m.prop() + function redraw() { + if (computePreRedrawHook) { + computePreRedrawHook() + computePreRedrawHook = null + } + forEach(roots, function (root, i) { + var component = components[i] + if (controllers[i]) { + var args = [controllers[i]] + m.render(root, + component.view ? component.view(controllers[i], args) : "") + } + }) + // after rendering within a routed context, we need to scroll back to + // the top, and fetch the document title for history.pushState + if (computePostRedrawHook) { + computePostRedrawHook() + computePostRedrawHook = null + } + lastRedrawId = null + lastRedrawCallTime = new Date() + m.redraw.strategy("diff") + } + + function endFirstComputation() { + if (m.redraw.strategy() === "none") { + pendingRequests-- + m.redraw.strategy("diff") + } else { + m.endComputation() + } + } + + m.withAttr = function (prop, withAttrCallback, callbackThis) { + return function (e) { + e = e || event + /* eslint-disable no-invalid-this */ + var currentTarget = e.currentTarget || this + var _this = callbackThis || this + /* eslint-enable no-invalid-this */ + var target = prop in currentTarget ? + currentTarget[prop] : + currentTarget.getAttribute(prop) + withAttrCallback.call(_this, target) + } + } + + // routing + var modes = {pathname: "", hash: "#", search: "?"} + var redirect = noop + var isDefaultRoute = false + var routeParams, currentRoute + + m.route = function (root, arg1, arg2, vdom) { // eslint-disable-line + // m.route() + if (arguments.length === 0) return currentRoute + // m.route(el, defaultRoute, routes) + if (arguments.length === 3 && isString(arg1)) { + redirect = function (source) { + var path = currentRoute = normalizeRoute(source) + if (!routeByValue(root, arg2, path)) { + if (isDefaultRoute) { + throw new Error("Ensure the default route matches " + + "one of the routes defined in m.route") + } + + isDefaultRoute = true + m.route(arg1, true) + isDefaultRoute = false + } + } + + var listener = m.route.mode === "hash" ? + "onhashchange" : + "onpopstate" + + global[listener] = function () { + var path = $location[m.route.mode] + if (m.route.mode === "pathname") path += $location.search + if (currentRoute !== normalizeRoute(path)) redirect(path) + } + + computePreRedrawHook = setScroll + global[listener]() + + return + } + + // config: m.route + if (root.addEventListener || root.attachEvent) { + var base = m.route.mode !== "pathname" ? $location.pathname : "" + root.href = base + modes[m.route.mode] + vdom.attrs.href + if (root.addEventListener) { + root.removeEventListener("click", routeUnobtrusive) + root.addEventListener("click", routeUnobtrusive) + } else { + root.detachEvent("onclick", routeUnobtrusive) + root.attachEvent("onclick", routeUnobtrusive) + } + + return + } + // m.route(route, params, shouldReplaceHistoryEntry) + if (isString(root)) { + var oldRoute = currentRoute + currentRoute = root + + var args = arg1 || {} + var queryIndex = currentRoute.indexOf("?") + var params + + if (queryIndex > -1) { + params = parseQueryString(currentRoute.slice(queryIndex + 1)) + } else { + params = {} + } + + for (var i in args) if (hasOwn.call(args, i)) { + params[i] = args[i] + } + + var querystring = buildQueryString(params) + var currentPath + + if (queryIndex > -1) { + currentPath = currentRoute.slice(0, queryIndex) + } else { + currentPath = currentRoute + } + + if (querystring) { + currentRoute = currentPath + + (currentPath.indexOf("?") === -1 ? "?" : "&") + + querystring + } + + var replaceHistory = + (arguments.length === 3 ? arg2 : arg1) === true || + oldRoute === root + + if (global.history.pushState) { + var method = replaceHistory ? "replaceState" : "pushState" + computePreRedrawHook = setScroll + computePostRedrawHook = function () { + global.history[method](null, $document.title, + modes[m.route.mode] + currentRoute) + } + redirect(modes[m.route.mode] + currentRoute) + } else { + $location[m.route.mode] = currentRoute + redirect(modes[m.route.mode] + currentRoute) + } + } + } + + m.route.param = function (key) { + if (!routeParams) { + throw new Error("You must call m.route(element, defaultRoute, " + + "routes) before calling m.route.param()") + } + + if (!key) { + return routeParams + } + + return routeParams[key] + } + + m.route.mode = "search" + + function normalizeRoute(route) { + return route.slice(modes[m.route.mode].length) + } + + function routeByValue(root, router, path) { + routeParams = {} + + var queryStart = path.indexOf("?") + if (queryStart !== -1) { + routeParams = parseQueryString( + path.substr(queryStart + 1, path.length)) + path = path.substr(0, queryStart) + } + + // Get all routes and check if there's + // an exact match for the current path + var keys = Object.keys(router) + var index = keys.indexOf(path) + + if (index !== -1){ + m.mount(root, router[keys [index]]) + return true + } + + for (var route in router) if (hasOwn.call(router, route)) { + if (route === path) { + m.mount(root, router[route]) + return true + } + + var matcher = new RegExp("^" + route + .replace(/:[^\/]+?\.{3}/g, "(.*?)") + .replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$") + + if (matcher.test(path)) { + /* eslint-disable no-loop-func */ + path.replace(matcher, function () { + var keys = route.match(/:[^\/]+/g) || [] + var values = [].slice.call(arguments, 1, -2) + forEach(keys, function (key, i) { + routeParams[key.replace(/:|\./g, "")] = + decodeURIComponent(values[i]) + }) + m.mount(root, router[route]) + }) + /* eslint-enable no-loop-func */ + return true + } + } + } + + function routeUnobtrusive(e) { + e = e || event + if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return + + if (e.preventDefault) { + e.preventDefault() + } else { + e.returnValue = false + } + + var currentTarget = e.currentTarget || e.srcElement + var args + + if (m.route.mode === "pathname" && currentTarget.search) { + args = parseQueryString(currentTarget.search.slice(1)) + } else { + args = {} + } + + while (currentTarget && !/a/i.test(currentTarget.nodeName)) { + currentTarget = currentTarget.parentNode + } + + // clear pendingRequests because we want an immediate route change + pendingRequests = 0 + m.route(currentTarget[m.route.mode] + .slice(modes[m.route.mode].length), args) + } + + function setScroll() { + if (m.route.mode !== "hash" && $location.hash) { + $location.hash = $location.hash + } else { + global.scrollTo(0, 0) + } + } + + function buildQueryString(object, prefix) { + var duplicates = {} + var str = [] + + for (var prop in object) if (hasOwn.call(object, prop)) { + var key = prefix ? prefix + "[" + prop + "]" : prop + var value = object[prop] + + if (value === null) { + str.push(encodeURIComponent(key)) + } else if (isObject(value)) { + str.push(buildQueryString(value, key)) + } else if (isArray(value)) { + var keys = [] + duplicates[key] = duplicates[key] || {} + /* eslint-disable no-loop-func */ + forEach(value, function (item) { + /* eslint-enable no-loop-func */ + if (!duplicates[key][item]) { + duplicates[key][item] = true + keys.push(encodeURIComponent(key) + "=" + + encodeURIComponent(item)) + } + }) + str.push(keys.join("&")) + } else if (value !== undefined) { + str.push(encodeURIComponent(key) + "=" + + encodeURIComponent(value)) + } + } + return str.join("&") + } + + function parseQueryString(str) { + if (str === "" || str == null) return {} + if (str.charAt(0) === "?") str = str.slice(1) + + var pairs = str.split("&") + var params = {} + + forEach(pairs, function (string) { + var pair = string.split("=") + var key = decodeURIComponent(pair[0]) + var value = pair.length === 2 ? decodeURIComponent(pair[1]) : null + if (params[key] != null) { + if (!isArray(params[key])) params[key] = [params[key]] + params[key].push(value) + } + else params[key] = value + }) + + return params + } + + m.route.buildQueryString = buildQueryString + m.route.parseQueryString = parseQueryString + + function reset(root) { + var cacheKey = getCellCacheKey(root) + clear(root.childNodes, cellCache[cacheKey]) + cellCache[cacheKey] = undefined + } + + m.deferred = function () { + var deferred = new Deferred() + deferred.promise = propify(deferred.promise) + return deferred + } + + function propify(promise, initialValue) { + var prop = m.prop(initialValue) + promise.then(prop) + prop.then = function (resolve, reject) { + return propify(promise.then(resolve, reject), initialValue) + } + + prop.catch = prop.then.bind(null, null) + return prop + } + // Promiz.mithril.js | Zolmeister | MIT + // a modified version of Promiz.js, which does not conform to Promises/A+ + // for two reasons: + // + // 1) `then` callbacks are called synchronously (because setTimeout is too + // slow, and the setImmediate polyfill is too big + // + // 2) throwing subclasses of Error cause the error to be bubbled up instead + // of triggering rejection (because the spec does not account for the + // important use case of default browser error handling, i.e. message w/ + // line number) + + var RESOLVING = 1 + var REJECTING = 2 + var RESOLVED = 3 + var REJECTED = 4 + + function Deferred(onSuccess, onFailure) { + var self = this + var state = 0 + var promiseValue = 0 + var next = [] + + self.promise = {} + + self.resolve = function (value) { + if (!state) { + promiseValue = value + state = RESOLVING + + fire() + } + + return self + } + + self.reject = function (value) { + if (!state) { + promiseValue = value + state = REJECTING + + fire() + } + + return self + } + + self.promise.then = function (onSuccess, onFailure) { + var deferred = new Deferred(onSuccess, onFailure) + + if (state === RESOLVED) { + deferred.resolve(promiseValue) + } else if (state === REJECTED) { + deferred.reject(promiseValue) + } else { + next.push(deferred) + } + + return deferred.promise + } + + function finish(type) { + state = type || REJECTED + next.map(function (deferred) { + if (state === RESOLVED) { + deferred.resolve(promiseValue) + } else { + deferred.reject(promiseValue) + } + }) + } + + function thennable(then, success, failure, notThennable) { + if (((promiseValue != null && isObject(promiseValue)) || + isFunction(promiseValue)) && isFunction(then)) { + try { + // count protects against abuse calls from spec checker + var count = 0 + then.call(promiseValue, function (value) { + if (count++) return + promiseValue = value + success() + }, function (value) { + if (count++) return + promiseValue = value + failure() + }) + } catch (e) { + m.deferred.onerror(e) + promiseValue = e + failure() + } + } else { + notThennable() + } + } + + function fire() { + // check if it's a thenable + var then + try { + then = promiseValue && promiseValue.then + } catch (e) { + m.deferred.onerror(e) + promiseValue = e + state = REJECTING + return fire() + } + + if (state === REJECTING) { + m.deferred.onerror(promiseValue) + } + + thennable(then, function () { + state = RESOLVING + fire() + }, function () { + state = REJECTING + fire() + }, function () { + try { + if (state === RESOLVING && isFunction(onSuccess)) { + promiseValue = onSuccess(promiseValue) + } else if (state === REJECTING && isFunction(onFailure)) { + promiseValue = onFailure(promiseValue) + state = RESOLVING + } + } catch (e) { + m.deferred.onerror(e) + promiseValue = e + return finish() + } + + if (promiseValue === self) { + promiseValue = TypeError() + finish() + } else { + thennable(then, function () { + finish(RESOLVED) + }, finish, function () { + finish(state === RESOLVING && RESOLVED) + }) + } + }) + } + } + + m.deferred.onerror = function (e) { + if (type.call(e) === "[object Error]" && + !e.constructor.toString().match(/ Error/)) { + pendingRequests = 0 + throw e + } + } + + m.sync = function (args) { + var deferred = m.deferred() + var outstanding = args.length + var results = new Array(outstanding) + var method = "resolve" + + function synchronizer(pos, resolved) { + return function (value) { + results[pos] = value + if (!resolved) method = "reject" + if (--outstanding === 0) { + deferred.promise(results) + deferred[method](results) + } + return value + } + } + + if (args.length > 0) { + forEach(args, function (arg, i) { + arg.then(synchronizer(i, true), synchronizer(i, false)) + }) + } else { + deferred.resolve([]) + } + + return deferred.promise + } + + function identity(value) { return value } + + function handleJsonp(options) { + var callbackKey = "mithril_callback_" + + new Date().getTime() + "_" + + (Math.round(Math.random() * 1e16)).toString(36) + + var script = $document.createElement("script") + + global[callbackKey] = function (resp) { + script.parentNode.removeChild(script) + options.onload({ + type: "load", + target: { + responseText: resp + } + }) + global[callbackKey] = undefined + } + + script.onerror = function () { + script.parentNode.removeChild(script) + + options.onerror({ + type: "error", + target: { + status: 500, + responseText: JSON.stringify({ + error: "Error making jsonp request" + }) + } + }) + global[callbackKey] = undefined + + return false + } + + script.onload = function () { + return false + } + + script.src = options.url + + (options.url.indexOf("?") > 0 ? "&" : "?") + + (options.callbackKey ? options.callbackKey : "callback") + + "=" + callbackKey + + "&" + buildQueryString(options.data || {}) + + $document.body.appendChild(script) + } + + function createXhr(options) { + var xhr = new global.XMLHttpRequest() + xhr.open(options.method, options.url, true, options.user, + options.password) + + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if (xhr.status >= 200 && xhr.status < 300) { + options.onload({type: "load", target: xhr}) + } else { + options.onerror({type: "error", target: xhr}) + } + } + } + + if (options.serialize === JSON.stringify && + options.data && + options.method !== "GET") { + xhr.setRequestHeader("Content-Type", + "application/json; charset=utf-8") + } + + if (options.deserialize === JSON.parse) { + xhr.setRequestHeader("Accept", "application/json, text/*") + } + + 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 && !isString(data) && data.constructor !== global.FormData) { + throw new Error("Request data should be either be a string or " + + "FormData. Check the `serialize` option in `m.request`") + } + + xhr.send(data) + return xhr + } + + function ajax(options) { + if (options.dataType && options.dataType.toLowerCase() === "jsonp") { + return handleJsonp(options) + } else { + return createXhr(options) + } + } + + function bindData(options, data, serialize) { + if (options.method === "GET" && options.dataType !== "jsonp") { + var prefix = options.url.indexOf("?") < 0 ? "?" : "&" + var querystring = buildQueryString(data) + options.url += (querystring ? prefix + querystring : "") + } else { + options.data = serialize(data) + } + } + + function parameterizeUrl(url, data) { + var tokens = url.match(/:[a-z]\w+/gi) + + if (tokens && data) { + forEach(tokens, function (token) { + var key = token.slice(1) + url = url.replace(token, data[key]) + delete data[key] + }) + } + + return url + } + + m.request = function (options) { + if (options.background !== true) m.startComputation() + var deferred = new Deferred() + var isJSONP = options.dataType && + options.dataType.toLowerCase() === "jsonp" + + var serialize, deserialize, extract + + if (isJSONP) { + serialize = options.serialize = + deserialize = options.deserialize = identity + + extract = function (jsonp) { return jsonp.responseText } + } else { + serialize = options.serialize = options.serialize || JSON.stringify + + deserialize = options.deserialize = + options.deserialize || JSON.parse + extract = options.extract || function (xhr) { + if (xhr.responseText.length || deserialize !== JSON.parse) { + return xhr.responseText + } else { + return null + } + } + } + + options.method = (options.method || "GET").toUpperCase() + options.url = parameterizeUrl(options.url, options.data) + bindData(options, options.data, serialize) + options.onload = options.onerror = function (ev) { + try { + ev = ev || event + var response = deserialize(extract(ev.target, options)) + if (ev.type === "load") { + if (options.unwrapSuccess) { + response = options.unwrapSuccess(response, ev.target) + } + + if (isArray(response) && options.type) { + forEach(response, function (res, i) { + response[i] = new options.type(res) + }) + } else if (options.type) { + response = new options.type(response) + } + + deferred.resolve(response) + } else { + if (options.unwrapError) { + response = options.unwrapError(response, ev.target) + } + + deferred.reject(response) + } + } catch (e) { + deferred.reject(e) + } finally { + if (options.background !== true) m.endComputation() + } + } + + ajax(options) + deferred.promise = propify(deferred.promise, options.initialValue) + return deferred.promise + } + + return m +}) |