From 885285734733b71c88dfb0da513988eb8678aa14 Mon Sep 17 00:00:00 2001
From: Florian Dold <florian.dold@gmail.com>
Date: Mon, 12 Sep 2016 20:25:56 +0200
Subject: fix compiler warnings

---
 popup/popup.tsx | 29 ++++++++++++++++-------------
 1 file changed, 16 insertions(+), 13 deletions(-)

(limited to 'popup/popup.tsx')

diff --git a/popup/popup.tsx b/popup/popup.tsx
index a818e9228..8a604b457 100644
--- a/popup/popup.tsx
+++ b/popup/popup.tsx
@@ -29,12 +29,15 @@
 "use strict";
 
 import {substituteFulfillmentUrl} from "../lib/wallet/helpers";
+import BrowserClickedEvent = chrome.browserAction.BrowserClickedEvent;
+import {Wallet} from "../lib/wallet/wallet";
+import {AmountJson} from "../lib/wallet/types";
 
 declare var m: any;
 declare var i18n: any;
 
 
-function onUpdateNotification(f) {
+function onUpdateNotification(f: () => void) {
   let port = chrome.runtime.connect({name: "notifications"});
   port.onMessage.addListener((msg, port) => {
     f();
@@ -56,7 +59,7 @@ export function main() {
 console.log("this is popup");
 
 
-function makeTab(target, name) {
+function makeTab(target: string, name: string) {
   let cssClass = "";
   if (target == m.route()) {
     cssClass = "active";
@@ -79,8 +82,8 @@ namespace WalletNavBar {
 }
 
 
-function openInExtension(element, isInitialized) {
-  element.addEventListener("click", (e) => {
+function openInExtension(element: HTMLAnchorElement, isInitialized: boolean) {
+  element.addEventListener("click", (e: Event) => {
     chrome.tabs.create({
                          "url": element.href
                        });
@@ -94,7 +97,7 @@ namespace WalletBalance {
   }
 
   class Controller {
-    myWallet;
+    myWallet: any;
     gotError = false;
 
     constructor() {
@@ -128,7 +131,7 @@ namespace WalletBalance {
     if (!wallet) {
       throw Error("Could not retrieve wallet");
     }
-    let listing = _.map(wallet, x => m("p", formatAmount(x)));
+    let listing = _.map(wallet, (x: any) => m("p", formatAmount(x)));
     if (listing.length > 0) {
       return listing;
     }
@@ -141,13 +144,13 @@ namespace WalletBalance {
 }
 
 
-function formatTimestamp(t) {
+function formatTimestamp(t: number) {
   let x = new Date(t);
   return x.toLocaleString();
 }
 
 
-function formatAmount(amount) {
+function formatAmount(amount: AmountJson) {
   let v = amount.value + amount.fraction / 1e6;
   return `${v.toFixed(2)} ${amount.currency}`;
 }
@@ -158,7 +161,7 @@ function abbrevKey(s: string) {
 }
 
 
-function retryPayment(url, contractHash) {
+function retryPayment(url: string, contractHash: string) {
   return function() {
     chrome.tabs.create({
                          "url": substituteFulfillmentUrl(url,
@@ -168,7 +171,7 @@ function retryPayment(url, contractHash) {
 }
 
 
-function formatHistoryItem(historyItem) {
+function formatHistoryItem(historyItem: any) {
   const d = historyItem.detail;
   const t = historyItem.timestamp;
   console.log("hist item", historyItem);
@@ -210,7 +213,7 @@ namespace WalletHistory {
   }
 
   class Controller {
-    myHistory;
+    myHistory: any;
     gotError = false;
 
     constructor() {
@@ -287,7 +290,7 @@ var WalletDebug = {
 };
 
 
-function openExtensionPage(page) {
+function openExtensionPage(page: string) {
   return function() {
     chrome.tabs.create({
                          "url": chrome.extension.getURL(page)
@@ -296,7 +299,7 @@ function openExtensionPage(page) {
 }
 
 
-function openTab(page) {
+function openTab(page: string) {
   return function() {
     chrome.tabs.create({
                          "url": page
-- 
cgit v1.2.3


From fc6db1824e6974898f177ffb0ffd2eb138a653ce Mon Sep 17 00:00:00 2001
From: Florian Dold <florian.dold@gmail.com>
Date: Wed, 14 Sep 2016 15:20:18 +0200
Subject: move to ES6, clean up views

---
 gulpfile.js                      |    2 +-
 lib/vendor/mithril.js            | 4390 +++++++++++++++++++-------------------
 pages/confirm-create-reserve.tsx |  183 +-
 popup/popup.tsx                  |    2 +
 tsconfig.json                    |    2 +-
 5 files changed, 2326 insertions(+), 2253 deletions(-)

(limited to 'popup/popup.tsx')

diff --git a/gulpfile.js b/gulpfile.js
index 00a82e1a7..f8379d638 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -117,7 +117,7 @@ const paths = {
 
 
 const tsBaseArgs = {
-  target: "es5",
+  target: "es6",
   jsx: "react",
   experimentalDecorators: true,
   module: "system",
diff --git a/lib/vendor/mithril.js b/lib/vendor/mithril.js
index 4b225299e..55bef997f 100644
--- a/lib/vendor/mithril.js
+++ b/lib/vendor/mithril.js
@@ -1,2157 +1,2233 @@
-/*
-  The MIT License (MIT)
-
-  Copyright (c) 2014 Leo Horie
-
-  Permission is hereby granted, free of charge, to any person obtaining a copy
-  of this software and associated documentation files (the "Software"), to deal
-  in the Software without restriction, including without limitation the rights
-  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-  copies of the Software, and to permit persons to whom the Software is
-  furnished to do so, subject to the following conditions:
-
-  The above copyright notice and this permission notice shall be included in all
-  copies or substantial portions of the Software.
-
-  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-  SOFTWARE.
-*/
-
-
-;(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
-})
+;(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 : this, function (global, undefined) { // eslint-disable-line
+	"use strict"
+
+	m.version = function () {
+		return "v0.2.5"
+	}
+
+	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() {}
+
+	var voidElements = {
+		AREA: 1,
+		BASE: 1,
+		BR: 1,
+		COL: 1,
+		COMMAND: 1,
+		EMBED: 1,
+		HR: 1,
+		IMG: 1,
+		INPUT: 1,
+		KEYGEN: 1,
+		LINK: 1,
+		META: 1,
+		PARAM: 1,
+		SOURCE: 1,
+		TRACK: 1,
+		WBR: 1
+	}
+
+	// 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] || ""
+			}
+		}
+
+		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) {
+		var args = []
+
+		for (var i = 1, length = arguments.length; i < 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 in 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.map(function (u) { return u.handler })
+					.indexOf(controller.onunload) < 0) {
+			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
+
+		if (pendingRequests === 0 ||
+				forcing ||
+				cachedControllers &&
+					cachedControllers.indexOf(controller) > -1) {
+			data = data.view(controller)
+		} else {
+			data = {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)
+
+			// add the node to its parent before attaching children to it
+			insertNode(parentElement, node, index)
+
+			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] = ""
+			}
+		}
+	}
+
+	var shouldUseSetAttribute = {
+		list: 1,
+		style: 1,
+		form: 1,
+		type: 1,
+		width: 1,
+		height: 1
+	}
+
+	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) || ($document.activeElement === node)) {
+			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)
+			replaceScriptNodes(parentElement)
+		}
+	}
+
+	// Replace script tags inside given DOM element with executable ones.
+	// Will also check children recursively and replace any found script
+	// tags in same manner.
+	function replaceScriptNodes(node) {
+		if (node.tagName === "SCRIPT") {
+			node.parentNode.replaceChild(buildExecutableNode(node), node)
+		} else {
+			var children = node.childNodes
+			if (children && children.length) {
+				for (var i = 0; i < children.length; i++) {
+					replaceScriptNodes(children[i])
+				}
+			}
+		}
+
+		return node
+	}
+
+	// Replace script element with one whose contents are executable.
+	function buildExecutableNode(node){
+		var scriptEl = document.createElement("script")
+		var attrs = node.attributes
+
+		for (var i = 0; i < attrs.length; i++) {
+			scriptEl.setAttribute(attrs[i].name, attrs[i].value)
+		}
+
+		scriptEl.text = node.innerHTML
+		return scriptEl
+	}
+
+	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)) || ((typeof Promise !== "undefined") && (store instanceof Promise))) &&
+				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) {
+		var args = new Array(arguments.length - 1)
+
+		for (var i = 1; i < arguments.length; i++) {
+			args[i - 1] = 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 || window.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 () {
+					try {
+						global.history[method](null, $document.title,
+							modes[m.route.mode] + currentRoute)
+					} catch (err) {
+						// In the event of a pushState or replaceState failure,
+						// fallback to a standard redirect. This is specifically
+						// to address a Safari security error when attempting to
+						// call pushState more than 100 times.
+						$location[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]" &&
+				!/ Error/.test(e.constructor.toString())) {
+			pendingRequests = 0
+			throw e
+		}
+	}
+
+	m.sync = function (args) {
+		var deferred = m.deferred()
+		var outstanding = args.length
+		var results = []
+		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 = options.callbackName || "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) {
+		if (data) {
+			url = url.replace(/:[a-z]\w+/gi, function (token){
+				var key = token.slice(1)
+				var value = data[key] || token
+				delete data[key]
+				return value
+			})
+		}
+		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)
+				m.deferred.onerror(e)
+			} finally {
+				if (options.background !== true) m.endComputation()
+			}
+		}
+
+		ajax(options)
+		deferred.promise = propify(deferred.promise, options.initialValue)
+		return deferred.promise
+	}
+
+	return m
+}); // eslint-disable-line
diff --git a/pages/confirm-create-reserve.tsx b/pages/confirm-create-reserve.tsx
index b965f35c9..667323044 100644
--- a/pages/confirm-create-reserve.tsx
+++ b/pages/confirm-create-reserve.tsx
@@ -27,7 +27,6 @@
 import {amountToPretty, canonicalizeBaseUrl} from "../lib/wallet/helpers";
 import {AmountJson, CreateReserveResponse} from "../lib/wallet/types";
 import m from "mithril";
-import {IExchangeInfo} from "../lib/wallet/types";
 import {ReserveCreationInfo, Amounts} from "../lib/wallet/types";
 import MithrilComponent = _mithril.MithrilComponent;
 import {Denomination} from "../lib/wallet/types";
@@ -190,34 +189,60 @@ class Controller {
 }
 
 function view(ctrl: Controller): any {
-  let controls: any[] = [];
-  let mx = (x: any, ...args: any[]) => controls.push(m(x, ...args));
-
-  mx("p",
-     i18n.parts`You are about to withdraw ${m("strong", amountToPretty(
-       ctrl.amount))} from your bank account into your wallet.`);
+  function* f() {
+    yield m("p",
+            i18n.parts`You are about to withdraw ${m("strong", amountToPretty(
+              ctrl.amount))} from your bank account into your wallet.`);
+
+    if (ctrl.complexViewRequested || !ctrl.suggestedExchangeUrl) {
+      yield viewComplex(ctrl);
+      return;
+    }
+    yield viewSimple(ctrl);
+  }
+  return Array.from(f());
+}
 
-  if (ctrl.complexViewRequested || !ctrl.suggestedExchangeUrl) {
-    return controls.concat(viewComplex(ctrl));
+function viewSimple(ctrl: Controller) {
+  function *f() {
+    if (ctrl.statusString) {
+      yield m("p", "Error: ", ctrl.statusString);
+      yield m("button.linky", {
+        onclick: () => {
+          ctrl.complexViewRequested = true;
+        }
+      }, "advanced options");
+    }
+    else if (ctrl.reserveCreationInfo != undefined) {
+      yield m("button.accept", {
+           onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo!,
+                                              ctrl.url(),
+                                              ctrl.amount,
+                                              ctrl.callbackUrl),
+           disabled: !ctrl.isValidExchange
+         },
+         "Accept fees and withdraw");
+      yield m("span.spacer");
+      yield m("button.linky", {
+        onclick: () => {
+          ctrl.complexViewRequested = true;
+        }
+      }, "advanced options");
+      let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead,
+                                  ctrl.reserveCreationInfo.withdrawFee).amount;
+      yield m("p", `Withdraw cost: ${amountToPretty(totalCost)}`);
+    } else {
+      yield m("p", "Please wait ...");
+    }
   }
 
-  return controls.concat(viewSimple(ctrl));
+  return Array.from(f());
 }
 
-function viewSimple(ctrl: Controller) {
-  let controls: any[] = [];
-  let mx = (x: any, ...args: any[]) => controls.push(m(x, ...args));
 
-  if (ctrl.statusString) {
-    mx("p", "Error: ", ctrl.statusString);
-    mx("button.linky", {
-      onclick: () => {
-        ctrl.complexViewRequested = true;
-      }
-    }, "advanced options");
-  }
-  else if (ctrl.reserveCreationInfo != undefined) {
-    mx("button.accept", {
+function viewComplex(ctrl: Controller) {
+  function *f() {
+    yield m("button.accept", {
          onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo!,
                                             ctrl.url(),
                                             ctrl.amount,
@@ -225,84 +250,53 @@ function viewSimple(ctrl: Controller) {
          disabled: !ctrl.isValidExchange
        },
        "Accept fees and withdraw");
-    mx("span.spacer");
-    mx("button.linky", {
+    yield m("span.spacer");
+    yield m("button.linky", {
       onclick: () => {
-        ctrl.complexViewRequested = true;
+        ctrl.complexViewRequested = false;
       }
-    }, "advanced options");
-    let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead,
-                                ctrl.reserveCreationInfo.withdrawFee).amount;
-    mx("p", `Withdraw cost: ${amountToPretty(totalCost)}`);
-  } else {
-    mx("p", "Please wait ...");
-  }
+    }, "back to simple view");
 
+    yield m("br");
 
-  return controls;
-}
 
+    yield m("input", {
+         className: "url",
+         type: "text",
+         spellcheck: false,
+         value: ctrl.url(),
+         oninput: m.withAttr("value", ctrl.onUrlChanged.bind(ctrl)),
+       });
 
-function viewComplex(ctrl: Controller) {
-  let controls: any[] = [];
-  let mx = (x: any, ...args: any[]) => controls.push(m(x, ...args));
-
-  mx("button.accept", {
-       onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo!,
-                                          ctrl.url(),
-                                          ctrl.amount,
-                                          ctrl.callbackUrl),
-       disabled: !ctrl.isValidExchange
-     },
-     "Accept fees and withdraw");
-  mx("span.spacer");
-  mx("button.linky", {
-    onclick: () => {
-      ctrl.complexViewRequested = false;
-    }
-  }, "back to simple view");
-
-  mx("br");
-
-
-  mx("input",
-     {
-       className: "url",
-       type: "text",
-       spellcheck: false,
-       value: ctrl.url(),
-       oninput: m.withAttr("value", ctrl.onUrlChanged.bind(ctrl)),
-     });
-
-  mx("br");
+    yield m("br");
 
-  if (ctrl.statusString) {
-    mx("p", ctrl.statusString);
-  } else if (!ctrl.reserveCreationInfo) {
-    mx("p", "Checking URL, please wait ...");
-  }
+    if (ctrl.statusString) {
+      yield m("p", ctrl.statusString);
+    } else if (!ctrl.reserveCreationInfo) {
+      yield m("p", "Checking URL, please wait ...");
+    }
 
-  if (ctrl.reserveCreationInfo) {
-    let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead,
-                                ctrl.reserveCreationInfo.withdrawFee).amount;
-    mx("p", `Withdraw cost: ${amountToPretty(totalCost)}`);
-    if (ctrl.detailCollapsed()) {
-      mx("button.linky", {
-        onclick: () => {
-          ctrl.detailCollapsed(false);
-        }
-      }, "show more details");
-    } else {
-      mx("button.linky", {
-        onclick: () => {
-          ctrl.detailCollapsed(true);
-        }
-      }, "hide details");
-      mx("div", {}, renderReserveCreationDetails(ctrl.reserveCreationInfo))
+    if (ctrl.reserveCreationInfo) {
+      let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead,
+                                  ctrl.reserveCreationInfo.withdrawFee).amount;
+      yield m("p", `Withdraw cost: ${amountToPretty(totalCost)}`);
+      if (ctrl.detailCollapsed()) {
+        yield m("button.linky", {
+          onclick: () => {
+            ctrl.detailCollapsed(false);
+          }
+        }, "show more details");
+      } else {
+        yield m("button.linky", {
+          onclick: () => {
+            ctrl.detailCollapsed(true);
+          }
+        }, "hide details");
+        yield m("div", {}, renderReserveCreationDetails(ctrl.reserveCreationInfo))
+      }
     }
   }
-
-  return m("div", controls);
+  return Array.from(f());
 }
 
 
@@ -368,6 +362,7 @@ function getSuggestedExchange(currency: string): Promise<string> {
 }
 
 
+
 export function main() {
   const url = URI(document.location.href);
   const query: any = URI.parseQuery(url.query());
@@ -378,7 +373,7 @@ export function main() {
 
   getSuggestedExchange(amount.currency)
     .then((suggestedExchangeUrl) => {
-      const controller = () => new Controller(suggestedExchangeUrl, amount, callback_url, wt_types);
+      const controller = function () { return new Controller(suggestedExchangeUrl, amount, callback_url, wt_types); };
       var ExchangeSelection = {controller, view};
       m.mount(document.getElementById("exchange-selection"), ExchangeSelection);
     })
@@ -386,6 +381,6 @@ export function main() {
       // TODO: provide more context information, maybe factor it out into a
       // TODO:generic error reporting function or component.
       document.body.innerText = `Fatal error: "${e.message}".`;
-      console.error(`got backend error "${e.message}"`);
+      console.error(`got error "${e.message}"`, e);
     });
-}
+}
\ No newline at end of file
diff --git a/popup/popup.tsx b/popup/popup.tsx
index 8a604b457..fa222125f 100644
--- a/popup/popup.tsx
+++ b/popup/popup.tsx
@@ -91,6 +91,8 @@ function openInExtension(element: HTMLAnchorElement, isInitialized: boolean) {
   });
 }
 
+
+
 namespace WalletBalance {
   export function controller() {
     return new Controller();
diff --git a/tsconfig.json b/tsconfig.json
index 2df3b2ce8..cbe7d039f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,6 @@
 {
   "compilerOptions": {
-    "target": "es5",
+    "target": "es6",
     "jsx": "react",
     "experimentalDecorators": true,
     "module": "system",
-- 
cgit v1.2.3

