summaryrefslogtreecommitdiff
path: root/js/lib/mithril.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/lib/mithril.js')
-rw-r--r--js/lib/mithril.js674
1 files changed, 674 insertions, 0 deletions
diff --git a/js/lib/mithril.js b/js/lib/mithril.js
new file mode 100644
index 0000000..128d1d4
--- /dev/null
+++ b/js/lib/mithril.js
@@ -0,0 +1,674 @@
+Mithril = m = new function app(window) {
+ var type = {}.toString
+ var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/
+
+ function m() {
+ var args = arguments
+ var hasAttrs = type.call(args[1]) == "[object Object]" && !("tag" in args[1]) && !("subtree" in args[1])
+ var attrs = hasAttrs ? args[1] : {}
+ var classAttrName = "class" in attrs ? "class" : "className"
+ var cell = {tag: "div", attrs: {}}
+ var match, classes = []
+ while (match = parser.exec(args[0])) {
+ if (match[1] == "") 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)
+ }
+ }
+ if (classes.length > 0) cell.attrs[classAttrName] = classes.join(" ")
+
+ cell.children = hasAttrs ? args[2] : args[1]
+
+ for (var attrName in attrs) {
+ if (attrName == classAttrName) cell.attrs[attrName] = (cell.attrs[attrName] || "") + " " + attrs[attrName]
+ else cell.attrs[attrName] = attrs[attrName]
+ }
+ return cell
+ }
+ function build(parentElement, parentTag, parentCache, parentIndex, data, cached, shouldReattach, index, editable, namespace, configs) {
+ if (data === null || data === undefined) data = ""
+ if (data.subtree === "retain") return cached
+
+ var cachedType = type.call(cached), dataType = type.call(data)
+ if (cachedType != dataType) {
+ if (cached !== null && cached !== undefined) {
+ if (parentCache && parentCache.nodes) {
+ var offset = index - parentIndex
+ var end = offset + (dataType == "[object Array]" ? data : cached.nodes).length
+ clear(parentCache.nodes.slice(offset, end), parentCache.slice(offset, end))
+ }
+ else clear(cached.nodes, cached)
+ }
+ cached = new data.constructor
+ cached.nodes = []
+ }
+
+ if (dataType == "[object Array]") {
+ data = flatten(data)
+ var nodes = [], intact = cached.length === data.length, subArrayCount = 0
+
+ var DELETION = 1, INSERTION = 2 , MOVE = 3
+ var existing = {}, unkeyed = [], shouldMaintainIdentities = false
+ for (var i = 0; i < cached.length; i++) {
+ if (cached[i] && cached[i].attrs && cached[i].attrs.key !== undefined) {
+ shouldMaintainIdentities = true
+ existing[cached[i].attrs.key] = {action: DELETION, index: i}
+ }
+ }
+ if (shouldMaintainIdentities) {
+ for (var i = 0; i < data.length; i++) {
+ if (data[i] && data[i].attrs) {
+ if (data[i].attrs.key !== undefined) {
+ var key = data[i].attrs.key
+ if (!existing[key]) existing[key] = {action: INSERTION, index: i}
+ else existing[key] = {action: MOVE, index: i, from: existing[key].index, element: parentElement.childNodes[existing[key].index]}
+ }
+ else unkeyed.push({index: i, element: parentElement.childNodes[i]})
+ }
+ }
+ var actions = Object.keys(existing).map(function(key) {return existing[key]})
+ var changes = actions.sort(function(a, b) {return a.action - b.action || a.index - b.index})
+ var newCached = cached.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)
+ }
+ if (change.action == INSERTION) {
+ var dummy = window.document.createElement("div")
+ dummy.key = data[change.index].attrs.key
+ parentElement.insertBefore(dummy, parentElement.childNodes[change.index])
+ newCached.splice(change.index, 0, {attrs: {key: data[change.index].attrs.key}, nodes: [dummy]})
+ }
+
+ if (change.action == MOVE) {
+ if (parentElement.childNodes[change.index] !== change.element) {
+ parentElement.insertBefore(change.element, parentElement.childNodes[change.index])
+ }
+ newCached[change.index] = cached[change.from]
+ }
+ }
+ for (var i = 0; i < unkeyed.length; i++) {
+ var change = unkeyed[i]
+ parentElement.insertBefore(change.element, parentElement.childNodes[change.index])
+ newCached[change.index] = cached[change.index]
+ }
+ cached = newCached
+ cached.nodes = []
+ for (var i = 0, child; child = parentElement.childNodes[i]; i++) cached.nodes.push(child)
+ }
+
+ for (var i = 0, cacheCount = 0; i < data.length; i++) {
+ var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs)
+ if (item === undefined) continue
+ if (!item.nodes.intact) intact = false
+ var isArray = item instanceof Array
+ subArrayCount += isArray ? item.length : 1
+ cached[cacheCount++] = item
+ }
+ if (!intact) {
+ for (var i = 0; i < data.length; i++) {
+ if (cached[i] !== undefined) nodes = nodes.concat(cached[i].nodes)
+ }
+ for (var i = 0, node; node = cached.nodes[i]; i++) {
+ if (node.parentNode !== null && nodes.indexOf(node) < 0) node.parentNode.removeChild(node)
+ }
+ for (var i = cached.nodes.length, node; node = nodes[i]; i++) {
+ if (node.parentNode === null) parentElement.appendChild(node)
+ }
+ if (data.length < cached.length) cached.length = data.length
+ cached.nodes = nodes
+ }
+
+ }
+ else if (dataType == "[object Object]") {
+ if (data.tag != cached.tag || Object.keys(data.attrs).join() != Object.keys(cached.attrs).join() || data.attrs.id != cached.attrs.id) {
+ clear(cached.nodes)
+ if (cached.configContext && typeof cached.configContext.onunload == "function") cached.configContext.onunload()
+ }
+ if (typeof data.tag != "string") return
+
+ var node, isNew = cached.nodes.length === 0
+ if (data.attrs.xmlns) namespace = data.attrs.xmlns
+ else if (data.tag === "svg") namespace = "http://www.w3.org/2000/svg"
+ if (isNew) {
+ node = namespace === undefined ? window.document.createElement(data.tag) : window.document.createElementNS(namespace, data.tag)
+ cached = {
+ tag: data.tag,
+ //process children before attrs so that select.value works correctly
+ children: data.children !== undefined ? build(node, data.tag, undefined, undefined, data.children, cached.children, true, 0, data.attrs.contenteditable ? node : editable, namespace, configs) : undefined,
+ attrs: setAttributes(node, data.tag, data.attrs, {}, namespace),
+ nodes: [node]
+ }
+ parentElement.insertBefore(node, parentElement.childNodes[index] || null)
+ }
+ else {
+ node = cached.nodes[0]
+ 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 (shouldReattach === true) parentElement.insertBefore(node, parentElement.childNodes[index] || null)
+ }
+ if (type.call(data.attrs["config"]) == "[object Function]") {
+ configs.push(data.attrs["config"].bind(window, node, !isNew, cached.configContext = cached.configContext || {}, cached))
+ }
+ }
+ else {
+ var nodes
+ if (cached.nodes.length === 0) {
+ if (data.$trusted) {
+ nodes = injectHTML(parentElement, index, data)
+ }
+ else {
+ nodes = [window.document.createTextNode(data)]
+ parentElement.insertBefore(nodes[0], parentElement.childNodes[index] || null)
+ }
+ cached = "string number boolean".indexOf(typeof data) > -1 ? new data.constructor(data) : data
+ cached.nodes = nodes
+ }
+ else if (cached.valueOf() !== data.valueOf() || shouldReattach === true) {
+ nodes = cached.nodes
+ if (!editable || editable !== window.document.activeElement) {
+ if (data.$trusted) {
+ clear(nodes, cached)
+ nodes = injectHTML(parentElement, index, data)
+ }
+ else {
+ if (parentTag === "textarea") parentElement.value = data
+ else if (editable) editable.innerHTML = data
+ else {
+ if (nodes[0].nodeType == 1 || nodes.length > 1) { //was a trusted string
+ clear(cached.nodes, cached)
+ nodes = [window.document.createTextNode(data)]
+ }
+ parentElement.insertBefore(nodes[0], parentElement.childNodes[index] || null)
+ nodes[0].nodeValue = data
+ }
+ }
+ }
+ cached = new data.constructor(data)
+ cached.nodes = nodes
+ }
+ else cached.nodes.intact = true
+ }
+
+ return cached
+ }
+ function setAttributes(node, tag, dataAttrs, cachedAttrs, namespace) {
+ var groups = {}
+ for (var attrName in dataAttrs) {
+ var dataAttr = dataAttrs[attrName]
+ var cachedAttr = cachedAttrs[attrName]
+ if (!(attrName in cachedAttrs) || (cachedAttr !== dataAttr) || node === window.document.activeElement) {
+ cachedAttrs[attrName] = dataAttr
+ if (attrName === "config") continue
+ else if (typeof dataAttr == "function" && attrName.indexOf("on") == 0) {
+ node[attrName] = autoredraw(dataAttr, node)
+ }
+ else if (attrName === "style" && typeof dataAttr == "object") {
+ for (var rule in dataAttr) {
+ if (cachedAttr === undefined || cachedAttr[rule] !== dataAttr[rule]) node.style[rule] = dataAttr[rule]
+ }
+ for (var rule in cachedAttr) {
+ if (!(rule in dataAttr)) node.style[rule] = ""
+ }
+ }
+ else if (namespace !== undefined) {
+ if (attrName === "href") node.setAttributeNS("http://www.w3.org/1999/xlink", "href", dataAttr)
+ else if (attrName === "className") node.setAttribute("class", dataAttr)
+ else node.setAttribute(attrName, dataAttr)
+ }
+ else if (attrName === "value" && tag === "input") {
+ if (node.value !== dataAttr) node.value = dataAttr
+ }
+ else if (attrName in node && !(attrName == "list" || attrName == "style")) {
+ node[attrName] = dataAttr
+ }
+ else node.setAttribute(attrName, dataAttr)
+ }
+ }
+ return cachedAttrs
+ }
+ function clear(nodes, cached) {
+ for (var i = nodes.length - 1; i > -1; i--) {
+ if (nodes[i] && nodes[i].parentNode) {
+ nodes[i].parentNode.removeChild(nodes[i])
+ cached = [].concat(cached)
+ if (cached[i]) unload(cached[i])
+ }
+ }
+ if (nodes.length != 0) nodes.length = 0
+ }
+ function unload(cached) {
+ if (cached.configContext && typeof cached.configContext.onunload == "function") cached.configContext.onunload()
+ if (cached.children) {
+ if (cached.children instanceof Array) for (var i = 0; i < cached.children.length; i++) unload(cached.children[i])
+ else if (cached.children.tag) unload(cached.children)
+ }
+ }
+ function injectHTML(parentElement, index, data) {
+ var nextSibling = parentElement.childNodes[index]
+ if (nextSibling) {
+ var isElement = nextSibling.nodeType != 1
+ var placeholder = window.document.createElement("span")
+ if (isElement) {
+ parentElement.insertBefore(placeholder, nextSibling)
+ placeholder.insertAdjacentHTML("beforebegin", data)
+ parentElement.removeChild(placeholder)
+ }
+ else nextSibling.insertAdjacentHTML("beforebegin", data)
+ }
+ else parentElement.insertAdjacentHTML("beforeend", data)
+ var nodes = []
+ while (parentElement.childNodes[index] !== nextSibling) {
+ nodes.push(parentElement.childNodes[index])
+ index++
+ }
+ return nodes
+ }
+ function flatten(data) {
+ var flattened = []
+ for (var i = 0; i < data.length; i++) {
+ var item = data[i]
+ if (item instanceof Array) flattened.push.apply(flattened, flatten(item))
+ else flattened.push(item)
+ }
+ return flattened
+ }
+ function autoredraw(callback, object, group) {
+ return function(e) {
+ e = e || event
+ m.startComputation()
+ try {return callback.call(object, e)}
+ finally {
+ if (!lastRedrawId) lastRedrawId = -1;
+ m.endComputation()
+ }
+ }
+ }
+
+ var html
+ var documentNode = {
+ insertAdjacentHTML: function(_, data) {
+ window.document.write(data)
+ window.document.close()
+ },
+ appendChild: function(node) {
+ if (html === undefined) html = window.document.createElement("html")
+ if (node.nodeName == "HTML") html = node
+ else html.appendChild(node)
+ if (window.document.documentElement && window.document.documentElement !== html) {
+ window.document.replaceChild(html, window.document.documentElement)
+ }
+ else window.document.appendChild(html)
+ },
+ insertBefore: function(node) {
+ this.appendChild(node)
+ },
+ childNodes: []
+ }
+ var nodeCache = [], cellCache = {}
+ m.render = function(root, cell) {
+ var configs = []
+ if (!root) throw new Error("Please ensure the DOM element exists before rendering a template into it.")
+ var id = getCellCacheKey(root)
+ var node = root == window.document || root == window.document.documentElement ? documentNode : root
+ if (cellCache[id] === undefined) clear(node.childNodes)
+ cellCache[id] = build(node, null, undefined, undefined, cell, cellCache[id], false, 0, null, undefined, configs)
+ for (var i = 0; i < configs.length; i++) configs[i]()
+ }
+ function getCellCacheKey(element) {
+ var index = nodeCache.indexOf(element)
+ return index < 0 ? nodeCache.push(element) - 1 : index
+ }
+
+ m.trust = function(value) {
+ value = new String(value)
+ value.$trusted = true
+ return value
+ }
+
+ var roots = [], modules = [], controllers = [], lastRedrawId = 0, computePostRedrawHook = null
+ m.module = function(root, module) {
+ var index = roots.indexOf(root)
+ if (index < 0) index = roots.length
+ var isPrevented = false
+ if (controllers[index] && typeof controllers[index].onunload == "function") {
+ var event = {
+ preventDefault: function() {isPrevented = true}
+ }
+ controllers[index].onunload(event)
+ }
+ if (!isPrevented) {
+ m.startComputation()
+ roots[index] = root
+ modules[index] = module
+ controllers[index] = new module.controller
+ m.endComputation()
+ }
+ }
+ m.redraw = function() {
+ var cancel = window.cancelAnimationFrame || window.clearTimeout
+ var defer = window.requestAnimationFrame || window.setTimeout
+ if (lastRedrawId) {
+ cancel(lastRedrawId)
+ lastRedrawId = defer(redraw, 0)
+ }
+ else {
+ redraw()
+ lastRedrawId = defer(function() {lastRedrawId = null}, 0)
+ }
+ }
+ function redraw() {
+ for (var i = 0; i < roots.length; i++) {
+ if (controllers[i]) m.render(roots[i], modules[i].view(controllers[i]))
+ }
+ if (computePostRedrawHook) {
+ computePostRedrawHook()
+ computePostRedrawHook = null
+ }
+ lastRedrawId = null
+ }
+
+ var pendingRequests = 0
+ m.startComputation = function() {pendingRequests++}
+ m.endComputation = function() {
+ pendingRequests = Math.max(pendingRequests - 1, 0)
+ if (pendingRequests == 0) m.redraw()
+ }
+
+ m.withAttr = function(prop, withAttrCallback) {
+ return function(e) {
+ e = e || event
+ withAttrCallback(prop in e.currentTarget ? e.currentTarget[prop] : e.currentTarget.getAttribute(prop))
+ }
+ }
+
+ //routing
+ var modes = {pathname: "", hash: "#", search: "?"}
+ var redirect = function() {}, routeParams = {}, currentRoute
+ m.route = function() {
+ if (arguments.length === 0) return currentRoute
+ else if (arguments.length === 3 && typeof arguments[1] == "string") {
+ var root = arguments[0], defaultRoute = arguments[1], router = arguments[2]
+ redirect = function(source) {
+ var path = currentRoute = normalizeRoute(source)
+ if (!routeByValue(root, router, path)) {
+ m.route(defaultRoute, true)
+ }
+ }
+ var listener = m.route.mode == "hash" ? "onhashchange" : "onpopstate"
+ window[listener] = function() {
+ if (currentRoute != normalizeRoute(window.location[m.route.mode])) {
+ redirect(window.location[m.route.mode])
+ }
+ }
+ computePostRedrawHook = setScroll
+ window[listener]()
+ }
+ else if (arguments[0].addEventListener) {
+ var element = arguments[0]
+ var isInitialized = arguments[1]
+ if (element.href.indexOf(modes[m.route.mode]) < 0) {
+ element.href = window.location.pathname + modes[m.route.mode] + element.pathname
+ }
+ if (!isInitialized) {
+ element.removeEventListener("click", routeUnobtrusive)
+ element.addEventListener("click", routeUnobtrusive)
+ }
+ }
+ else if (typeof arguments[0] == "string") {
+ currentRoute = arguments[0]
+ var querystring = typeof arguments[1] == "object" ? buildQueryString(arguments[1]) : null
+ if (querystring) currentRoute += (currentRoute.indexOf("?") === -1 ? "?" : "&") + querystring
+
+ var shouldReplaceHistoryEntry = (arguments.length == 3 ? arguments[2] : arguments[1]) === true
+
+ if (window.history.pushState) {
+ computePostRedrawHook = function() {
+ window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, window.document.title, modes[m.route.mode] + currentRoute)
+ setScroll()
+ }
+ redirect(modes[m.route.mode] + currentRoute)
+ }
+ else window.location[m.route.mode] = currentRoute
+ }
+ }
+ m.route.param = function(key) {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)
+ }
+
+ for (var route in router) {
+ if (route == path) {
+ reset(root)
+ m.module(root, router[route])
+ return true
+ }
+
+ var matcher = new RegExp("^" + route.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$")
+
+ if (matcher.test(path)) {
+ reset(root)
+ path.replace(matcher, function() {
+ var keys = route.match(/:[^\/]+/g) || []
+ var values = [].slice.call(arguments, 1, -2)
+ for (var i = 0; i < keys.length; i++) routeParams[keys[i].replace(/:|\./g, "")] = decodeSpace(values[i])
+ m.module(root, router[route])
+ })
+ return true
+ }
+ }
+ }
+ function reset(root) {
+ var cacheKey = getCellCacheKey(root)
+ clear(root.childNodes, cellCache[cacheKey])
+ cellCache[cacheKey] = undefined
+ }
+ function routeUnobtrusive(e) {
+ e = e || event
+ if (e.ctrlKey || e.metaKey || e.which == 2) return
+ e.preventDefault()
+ m.route(e.currentTarget[m.route.mode].slice(modes[m.route.mode].length))
+ }
+ function setScroll() {
+ if (m.route.mode != "hash" && window.location.hash) window.location.hash = window.location.hash
+ else window.scrollTo(0, 0)
+ }
+ function buildQueryString(object, prefix) {
+ var str = []
+ for(var prop in object) {
+ var key = prefix ? prefix + "[" + prop + "]" : prop, value = object[prop]
+ str.push(typeof value == "object" ? buildQueryString(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value))
+ }
+ return str.join("&")
+ }
+ function parseQueryString(str) {
+ var pairs = str.split("&"), params = {}
+ for (var i = 0; i < pairs.length; i++) {
+ var pair = pairs[i].split("=")
+ params[decodeSpace(pair[0])] = pair[1] ? decodeSpace(pair[1]) : (pair.length === 1 ? true : "")
+ }
+ return params
+ }
+ function decodeSpace(string) {
+ return decodeURIComponent(string.replace(/\+/g, " "))
+ }
+
+ //model
+ m.prop = function(store) {
+ var prop = function() {
+ if (arguments.length) store = arguments[0]
+ return store
+ }
+ prop.toJSON = function() {
+ return store
+ }
+ return prop
+ }
+
+ var none = {}
+ m.deferred = function() {
+ var resolvers = [], rejecters = [], resolved = none, rejected = none, promise = m.prop()
+ var object = {
+ resolve: function(value) {
+ if (resolved === none) promise(resolved = value)
+ for (var i = 0; i < resolvers.length; i++) resolvers[i](value)
+ resolvers.length = rejecters.length = 0
+ },
+ reject: function(value) {
+ if (rejected === none) rejected = value
+ for (var i = 0; i < rejecters.length; i++) rejecters[i](value)
+ resolvers.length = rejecters.length = 0
+ },
+ promise: promise
+ }
+ object.promise.resolvers = resolvers
+ object.promise.then = function(success, error) {
+ var next = m.deferred()
+ if (!success) success = identity
+ if (!error) error = identity
+ function callback(method, callback) {
+ return function(value) {
+ try {
+ var result = callback(value)
+ if (result && typeof result.then == "function") result.then(next[method], error)
+ else next[method](result !== undefined ? result : value)
+ }
+ catch (e) {
+ if (e instanceof Error && e.constructor !== Error) throw e
+ else next.reject(e)
+ }
+ }
+ }
+ if (resolved !== none) callback("resolve", success)(resolved)
+ else if (rejected !== none) callback("reject", error)(rejected)
+ else {
+ resolvers.push(callback("resolve", success))
+ rejecters.push(callback("reject", error))
+ }
+ return next.promise
+ }
+ return object
+ }
+ m.sync = function(args) {
+ 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
+ }
+ }
+
+ var deferred = m.deferred()
+ var outstanding = args.length
+ var results = new Array(outstanding)
+ for (var i = 0; i < args.length; i++) {
+ args[i].then(synchronizer(i, true), synchronizer(i, false))
+ }
+ return deferred.promise
+ }
+ function identity(value) {return value}
+
+ function ajax(options) {
+ var xhr = new window.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.method != "GET") {
+ xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
+ }
+ if (typeof options.config == "function") {
+ var maybeXhr = options.config(xhr, options)
+ if (maybeXhr !== undefined) xhr = maybeXhr
+ }
+ xhr.send(options.method == "GET" ? "" : options.data)
+ return xhr
+ }
+ function bindData(xhrOptions, data, serialize) {
+ if (data && Object.keys(data).length > 0) {
+ if (xhrOptions.method == "GET") {
+ xhrOptions.url = xhrOptions.url + (xhrOptions.url.indexOf("?") < 0 ? "?" : "&") + buildQueryString(data)
+ }
+ else xhrOptions.data = serialize(data)
+ }
+ return xhrOptions
+ }
+ function parameterizeUrl(url, data) {
+ var tokens = url.match(/:[a-z]\w+/gi)
+ if (tokens && data) {
+ for (var i = 0; i < tokens.length; i++) {
+ var key = tokens[i].slice(1)
+ url = url.replace(tokens[i], data[key])
+ delete data[key]
+ }
+ }
+ return url
+ }
+
+ m.request = function(xhrOptions) {
+ if (xhrOptions.background !== true) m.startComputation()
+ var deferred = m.deferred()
+ var serialize = xhrOptions.serialize = xhrOptions.serialize || JSON.stringify
+ var deserialize = xhrOptions.deserialize = xhrOptions.deserialize || JSON.parse
+ var extract = xhrOptions.extract || function(xhr) {
+ return xhr.responseText.length === 0 && deserialize === JSON.parse ? null : xhr.responseText
+ }
+ xhrOptions.url = parameterizeUrl(xhrOptions.url, xhrOptions.data)
+ xhrOptions = bindData(xhrOptions, xhrOptions.data, serialize)
+ xhrOptions.onload = xhrOptions.onerror = function(e) {
+ try {
+ e = e || event
+ var unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity
+ var response = unwrap(deserialize(extract(e.target, xhrOptions)))
+ if (e.type == "load") {
+ if (response instanceof Array && xhrOptions.type) {
+ for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i])
+ }
+ else if (xhrOptions.type) response = new xhrOptions.type(response)
+ }
+ deferred[e.type == "load" ? "resolve" : "reject"](response)
+ }
+ catch (e) {
+ if (e instanceof SyntaxError) throw new SyntaxError("Could not parse HTTP response. See http://lhorie.github.io/mithril/mithril.request.html#using-variable-data-formats")
+ else if (e instanceof Error && e.constructor !== Error) throw e
+ else deferred.reject(e)
+ }
+ if (xhrOptions.background !== true) m.endComputation()
+ }
+ ajax(xhrOptions)
+ return deferred.promise
+ }
+
+ //testing API
+ m.deps = function(mock) {return window = mock}
+ //for internal testing only, do not use `m.deps.factory`
+ m.deps.factory = app
+
+ return m
+}(typeof window != "undefined" ? window : {})
+
+if (typeof module != "undefined" && module !== null) module.exports = m
+if (typeof define == "function" && define.amd) define(function() {return m})
+
+;;;