diff --git a/.eslintignore b/.eslintignore
index 0b24c5c3..a6ff032f 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,5 +1,5 @@
# Most of these are build artifacts.
-**/node_modules
+node_modules
**/*.min.js
archive
deploy
diff --git a/.gitignore b/.gitignore
index 3f3a9e04..aeab5a02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,2 @@
node_modules
-!bench/**
archive
diff --git a/bench/README.md b/bench/README.md
deleted file mode 100644
index 16fcc8b9..00000000
--- a/bench/README.md
+++ /dev/null
@@ -1,4 +0,0 @@
-This suite is taken with modifications from the Mithril example in
-[Merri/nom](https://github.com/Merri/nom/tree/gh-pages/todomvc/benchmark/mithril-new).
-
-(For example, it uses Mithril itself to help simplify the business logic.)
diff --git a/bench/app/.gitignore b/bench/app/.gitignore
deleted file mode 100644
index 11c90f9c..00000000
--- a/bench/app/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-node_modules/todomvc-app-css/*
-!node_modules/todomvc-app-css/index.css
-
-node_modules/todomvc-common/*
-!node_modules/todomvc-common/base.js
-!node_modules/todomvc-common/base.css
diff --git a/bench/app/index.html b/bench/app/index.html
deleted file mode 100644
index e42cfacf..00000000
--- a/bench/app/index.html
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
- Mithril • TodoMVC
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/bench/app/js/app.js b/bench/app/js/app.js
deleted file mode 100644
index d4a8e6fb..00000000
--- a/bench/app/js/app.js
+++ /dev/null
@@ -1,13 +0,0 @@
-/* global m */
-(function (app) {
- "use strict"
-
- app.ENTER_KEY = 13
- app.ESC_KEY = 27
-
- m.route.mode = "hash"
- m.route(document.getElementById("todoapp"), "/", {
- "/": app,
- "/:filter": app
- })
-})(this.app || (this.app = {}))
diff --git a/bench/app/js/controllers/todo.js b/bench/app/js/controllers/todo.js
deleted file mode 100644
index 27577689..00000000
--- a/bench/app/js/controllers/todo.js
+++ /dev/null
@@ -1,111 +0,0 @@
-/* global m */
-(function (app) {
- "use strict"
-
- app.controller = function () {
- /* eslint-disable no-warning-comments */
- // Todo collection, update with props
- /* eslint-enable no-warning-comments */
- this.list = app.storage.get().map(function (item) {
- return new app.Todo(item)
- })
-
- // Temp title placeholder
- this.title = m.prop("")
-
- /* eslint-disable no-warning-comments */
- // Todo list filter
- /* eslint-enable no-warning-comments */
- this.filter = m.prop(m.route.param("filter") || "")
-
- this.add = function () {
- var title = this.title().trim()
- if (title) {
- this.list.push(new app.Todo({title: title}))
- app.storage.put(this.list)
- }
- this.title("")
- }
-
- this.isVisible = function (todo) {
- switch (this.filter()) {
- case "active": return !todo.completed()
- case "completed": return todo.completed()
- default: return true
- }
- }
-
- this.complete = function (todo) {
- if (todo.completed()) {
- todo.completed(false)
- } else {
- todo.completed(true)
- }
- app.storage.put(this.list)
- }
-
- this.edit = function (todo) {
- todo.previousTitle = todo.title()
- todo.editing(true)
- }
-
- this.doneEditing = function (todo, index) {
- todo.editing(false)
- todo.title(todo.title().trim())
- if (!todo.title()) {
- this.list.splice(index, 1)
- }
- app.storage.put(this.list)
- }
-
- this.cancelEditing = function (todo) {
- todo.title(todo.previousTitle)
- todo.editing(false)
- }
-
- this.clearTitle = function () {
- this.title("")
- }
-
- this.remove = function (key) {
- this.list.splice(key, 1)
- app.storage.put(this.list)
- }
-
- this.clearCompleted = function () {
- for (var i = this.list.length - 1; i >= 0; i--) {
- if (this.list[i].completed()) {
- this.list.splice(i, 1)
- }
- }
- app.storage.put(this.list)
- }
-
- this.amountCompleted = function () {
- var amount = 0
- for (var i = 0; i < this.list.length; i++) {
- if (this.list[i].completed()) {
- amount++
- }
- }
- return amount
- }
-
- this.allCompleted = function () {
- for (var i = 0; i < this.list.length; i++) {
- if (!this.list[i].completed()) {
- return false
- }
- }
- return true
- }
-
- this.completeAll = function () {
- var allCompleted = this.allCompleted()
- for (var i = 0; i < this.list.length; i++) {
- this.list[i].completed(!allCompleted)
- }
- app.storage.put(this.list)
- }
- }
-})(this.app || (this.app = {}))
diff --git a/bench/app/js/models/storage.js b/bench/app/js/models/storage.js
deleted file mode 100644
index 0e867e9a..00000000
--- a/bench/app/js/models/storage.js
+++ /dev/null
@@ -1,14 +0,0 @@
-(function (app) {
- "use strict"
-
- var STORAGE_ID = "todos-mithril"
-
- app.storage = {
- get: function () {
- return JSON.parse(localStorage.getItem(STORAGE_ID) || "[]")
- },
- put: function (todos) {
- localStorage.setItem(STORAGE_ID, JSON.stringify(todos))
- }
- }
-})(this.app || (this.app = {}))
diff --git a/bench/app/js/models/todo.js b/bench/app/js/models/todo.js
deleted file mode 100644
index 77af9ba2..00000000
--- a/bench/app/js/models/todo.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/* global m */
-
-(function (app) {
- "use strict"
-
- // Todo Model
- app.Todo = function (data) {
- this.title = m.prop(data.title)
- this.completed = m.prop(data.completed || false)
- this.editing = m.prop(data.editing || false)
- }
-})(this.app || (this.app = {}))
diff --git a/bench/app/js/views/footer-view.js b/bench/app/js/views/footer-view.js
deleted file mode 100644
index 25f07f1c..00000000
--- a/bench/app/js/views/footer-view.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/* global m */
-(function (app) {
- "use strict"
-
- var filter = {
- view: function (_, ctrl, expected, name, href) {
- return m("li", [
- m("a", {
- href: href,
- config: m.route,
- class: ctrl.filter() === expected ? "selected" : ""
- }, name)
- ])
- }
- }
-
- app.footer = {
- view: function (_, ctrl) {
- var amountCompleted = ctrl.amountCompleted()
- var amountActive = ctrl.list.length - amountCompleted
-
- return m("footer#footer", [
- m("span#todo-count", [
- m("strong", amountActive), " item" +
- (amountActive !== 1 ? "s" : "") + " left"
- ]),
- m("ul#filters", [
- m(filter, ctrl, "", "All", "/"),
- m(filter, ctrl, "active", "Active", "/active"),
- m(filter, ctrl, "completed", "Completed", "/completed")
- ]),
- ctrl.amountCompleted() ? m("button#clear-completed", {
- onclick: function () {
- ctrl.clearCompleted()
- }
- }, "Clear completed") : null
- ])
- }
- }
-})(this.app || (this.app = {}))
diff --git a/bench/app/js/views/main-view.js b/bench/app/js/views/main-view.js
deleted file mode 100644
index 9dfd2667..00000000
--- a/bench/app/js/views/main-view.js
+++ /dev/null
@@ -1,119 +0,0 @@
-/* global m */
-(function (app) {
- "use strict"
-
- // View utility
- app.watchInput = function (onenter, onescape) {
- return function (e) {
- if (e.keyCode === app.ENTER_KEY) {
- onenter()
- } else if (e.keyCode === app.ESC_KEY) {
- onescape()
- }
- }
- }
-
- var header = {
- controller: function () {
- this.focused = false
- },
-
- view: function (ctrl, parentCtrl) {
- return m("header#header", [
- m("h1", "todos"),
- m('input#new-todo[placeholder="What needs to be done?"]', {
- onkeyup: app.watchInput(
- function () {
- parentCtrl.add()
- },
- function () {
- parentCtrl.clearTitle()
- }),
- value: parentCtrl.title(),
- oninput: m.withAttr("value", parentCtrl.title),
- config: function (element) {
- if (!ctrl.focused) {
- element.focus()
- ctrl.focused = true
- }
- }
- })
- ])
- }
- }
-
- var todo = {
- view: function (_, parentCtrl, task, index) {
- return m("li", {
- class: (task.completed() ? "completed" : "") +
- (task.editing() ? " editing" : "")
- }, [
- m(".view", [
- m("input.toggle[type=checkbox]", {
- onclick: m.withAttr("checked", function () {
- parentCtrl.complete(task)
- }),
- checked: task.completed()
- }),
- m("label", {
- ondblclick: function () {
- parentCtrl.edit(task)
- }
- }, task.title()),
- m("button.destroy", {
- onclick: function () {
- parentCtrl.remove(index)
- }
- })
- ]),
- m("input.edit", {
- value: task.title(),
- onkeyup: app.watchInput(
- function () {
- parentCtrl.doneEditing(task, index)
- },
- function () {
- parentCtrl.cancelEditing(task)
- }),
- oninput: m.withAttr("value", task.title),
- config: function (element) {
- if (task.editing()) {
- element.focus()
- element.selectionStart =
- element.value.length
- }
- },
- onblur: function () {
- parentCtrl.doneEditing(task, index)
- }
- })
- ])
- }
- }
-
- app.view = function (ctrl) {
- return m("div", [
- m(header, ctrl),
- m("section#main", {
- style: {
- display: ctrl.list.length ? "" : "none"
- }
- }, [
- m("input#toggle-all[type=checkbox]", {
- onclick: ctrl.completeAll.bind(ctrl),
- checked: ctrl.allCompleted()
- }),
- m("ul#todo-list", [
- ctrl.list
- .filter(function () {
- return ctrl.isVisible()
- })
- .map(function (task, index) {
- return m(todo, ctrl, task, index)
- })
- ])
- ]),
- ctrl.list.length === 0 ? "" : m(app.footer, ctrl)
- ])
- }
-})(this.app || (this.app = {}))
diff --git a/bench/app/node_modules/todomvc-app-css/index.css b/bench/app/node_modules/todomvc-app-css/index.css
deleted file mode 100644
index 54d89abd..00000000
--- a/bench/app/node_modules/todomvc-app-css/index.css
+++ /dev/null
@@ -1,378 +0,0 @@
-html,
-body {
- margin: 0;
- padding: 0;
-}
-
-button {
- margin: 0;
- padding: 0;
- border: 0;
- background: none;
- font-size: 100%;
- vertical-align: baseline;
- font-family: inherit;
- font-weight: inherit;
- color: inherit;
- -webkit-appearance: none;
- appearance: none;
- -webkit-font-smoothing: antialiased;
- -moz-font-smoothing: antialiased;
- font-smoothing: antialiased;
-}
-
-body {
- font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
- line-height: 1.4em;
- background: #f5f5f5;
- color: #4d4d4d;
- min-width: 230px;
- max-width: 550px;
- margin: 0 auto;
- -webkit-font-smoothing: antialiased;
- -moz-font-smoothing: antialiased;
- font-smoothing: antialiased;
- font-weight: 300;
-}
-
-button,
-input[type="checkbox"] {
- outline: none;
-}
-
-.hidden {
- display: none;
-}
-
-#todoapp {
- background: #fff;
- margin: 130px 0 40px 0;
- position: relative;
- box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
- 0 25px 50px 0 rgba(0, 0, 0, 0.1);
-}
-
-#todoapp input::-webkit-input-placeholder {
- font-style: italic;
- font-weight: 300;
- color: #e6e6e6;
-}
-
-#todoapp input::-moz-placeholder {
- font-style: italic;
- font-weight: 300;
- color: #e6e6e6;
-}
-
-#todoapp input::input-placeholder {
- font-style: italic;
- font-weight: 300;
- color: #e6e6e6;
-}
-
-#todoapp h1 {
- position: absolute;
- top: -155px;
- width: 100%;
- font-size: 100px;
- font-weight: 100;
- text-align: center;
- color: rgba(175, 47, 47, 0.15);
- -webkit-text-rendering: optimizeLegibility;
- -moz-text-rendering: optimizeLegibility;
- text-rendering: optimizeLegibility;
-}
-
-#new-todo,
-.edit {
- position: relative;
- margin: 0;
- width: 100%;
- font-size: 24px;
- font-family: inherit;
- font-weight: inherit;
- line-height: 1.4em;
- border: 0;
- outline: none;
- color: inherit;
- padding: 6px;
- border: 1px solid #999;
- box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
- box-sizing: border-box;
- -webkit-font-smoothing: antialiased;
- -moz-font-smoothing: antialiased;
- font-smoothing: antialiased;
-}
-
-#new-todo {
- padding: 16px 16px 16px 60px;
- border: none;
- background: rgba(0, 0, 0, 0.003);
- box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
-}
-
-#main {
- position: relative;
- z-index: 2;
- border-top: 1px solid #e6e6e6;
-}
-
-label[for='toggle-all'] {
- display: none;
-}
-
-#toggle-all {
- position: absolute;
- top: -55px;
- left: -12px;
- width: 60px;
- height: 34px;
- text-align: center;
- border: none; /* Mobile Safari */
-}
-
-#toggle-all:before {
- content: '❯';
- font-size: 22px;
- color: #e6e6e6;
- padding: 10px 27px 10px 27px;
-}
-
-#toggle-all:checked:before {
- color: #737373;
-}
-
-#todo-list {
- margin: 0;
- padding: 0;
- list-style: none;
-}
-
-#todo-list li {
- position: relative;
- font-size: 24px;
- border-bottom: 1px solid #ededed;
-}
-
-#todo-list li:last-child {
- border-bottom: none;
-}
-
-#todo-list li.editing {
- border-bottom: none;
- padding: 0;
-}
-
-#todo-list li.editing .edit {
- display: block;
- width: 506px;
- padding: 13px 17px 12px 17px;
- margin: 0 0 0 43px;
-}
-
-#todo-list li.editing .view {
- display: none;
-}
-
-#todo-list li .toggle {
- text-align: center;
- width: 40px;
- /* auto, since non-WebKit browsers doesn't support input styling */
- height: auto;
- position: absolute;
- top: 0;
- bottom: 0;
- margin: auto 0;
- border: none; /* Mobile Safari */
- -webkit-appearance: none;
- appearance: none;
-}
-
-#todo-list li .toggle:after {
- content: url('data:image/svg+xml;utf8,');
-}
-
-#todo-list li .toggle:checked:after {
- content: url('data:image/svg+xml;utf8,');
-}
-
-#todo-list li label {
- white-space: pre;
- word-break: break-word;
- padding: 15px 60px 15px 15px;
- margin-left: 45px;
- display: block;
- line-height: 1.2;
- transition: color 0.4s;
-}
-
-#todo-list li.completed label {
- color: #d9d9d9;
- text-decoration: line-through;
-}
-
-#todo-list li .destroy {
- display: none;
- position: absolute;
- top: 0;
- right: 10px;
- bottom: 0;
- width: 40px;
- height: 40px;
- margin: auto 0;
- font-size: 30px;
- color: #cc9a9a;
- margin-bottom: 11px;
- transition: color 0.2s ease-out;
-}
-
-#todo-list li .destroy:hover {
- color: #af5b5e;
-}
-
-#todo-list li .destroy:after {
- content: '×';
-}
-
-#todo-list li:hover .destroy {
- display: block;
-}
-
-#todo-list li .edit {
- display: none;
-}
-
-#todo-list li.editing:last-child {
- margin-bottom: -1px;
-}
-
-#footer {
- color: #777;
- padding: 10px 15px;
- height: 20px;
- text-align: center;
- border-top: 1px solid #e6e6e6;
-}
-
-#footer:before {
- content: '';
- position: absolute;
- right: 0;
- bottom: 0;
- left: 0;
- height: 50px;
- overflow: hidden;
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
- 0 8px 0 -3px #f6f6f6,
- 0 9px 1px -3px rgba(0, 0, 0, 0.2),
- 0 16px 0 -6px #f6f6f6,
- 0 17px 2px -6px rgba(0, 0, 0, 0.2);
-}
-
-#todo-count {
- float: left;
- text-align: left;
-}
-
-#todo-count strong {
- font-weight: 300;
-}
-
-#filters {
- margin: 0;
- padding: 0;
- list-style: none;
- position: absolute;
- right: 0;
- left: 0;
-}
-
-#filters li {
- display: inline;
-}
-
-#filters li a {
- color: inherit;
- margin: 3px;
- padding: 3px 7px;
- text-decoration: none;
- border: 1px solid transparent;
- border-radius: 3px;
-}
-
-#filters li a.selected,
-#filters li a:hover {
- border-color: rgba(175, 47, 47, 0.1);
-}
-
-#filters li a.selected {
- border-color: rgba(175, 47, 47, 0.2);
-}
-
-#clear-completed,
-html #clear-completed:active {
- float: right;
- position: relative;
- line-height: 20px;
- text-decoration: none;
- cursor: pointer;
- position: relative;
-}
-
-#clear-completed:hover {
- text-decoration: underline;
-}
-
-#info {
- margin: 65px auto 0;
- color: #bfbfbf;
- font-size: 10px;
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
- text-align: center;
-}
-
-#info p {
- line-height: 1;
-}
-
-#info a {
- color: inherit;
- text-decoration: none;
- font-weight: 400;
-}
-
-#info a:hover {
- text-decoration: underline;
-}
-
-/*
- Hack to remove background from Mobile Safari.
- Can't use it globally since it destroys checkboxes in Firefox
-*/
-@media screen and (-webkit-min-device-pixel-ratio:0) {
- #toggle-all,
- #todo-list li .toggle {
- background: none;
- }
-
- #todo-list li .toggle {
- height: 40px;
- }
-
- #toggle-all {
- -webkit-transform: rotate(90deg);
- transform: rotate(90deg);
- -webkit-appearance: none;
- appearance: none;
- }
-}
-
-@media (max-width: 430px) {
- #footer {
- height: 50px;
- }
-
- #filters {
- bottom: 10px;
- }
-}
diff --git a/bench/app/node_modules/todomvc-common/base.css b/bench/app/node_modules/todomvc-common/base.css
deleted file mode 100644
index da65968a..00000000
--- a/bench/app/node_modules/todomvc-common/base.css
+++ /dev/null
@@ -1,141 +0,0 @@
-hr {
- margin: 20px 0;
- border: 0;
- border-top: 1px dashed #c5c5c5;
- border-bottom: 1px dashed #f7f7f7;
-}
-
-.learn a {
- font-weight: normal;
- text-decoration: none;
- color: #b83f45;
-}
-
-.learn a:hover {
- text-decoration: underline;
- color: #787e7e;
-}
-
-.learn h3,
-.learn h4,
-.learn h5 {
- margin: 10px 0;
- font-weight: 500;
- line-height: 1.2;
- color: #000;
-}
-
-.learn h3 {
- font-size: 24px;
-}
-
-.learn h4 {
- font-size: 18px;
-}
-
-.learn h5 {
- margin-bottom: 0;
- font-size: 14px;
-}
-
-.learn ul {
- padding: 0;
- margin: 0 0 30px 25px;
-}
-
-.learn li {
- line-height: 20px;
-}
-
-.learn p {
- font-size: 15px;
- font-weight: 300;
- line-height: 1.3;
- margin-top: 0;
- margin-bottom: 0;
-}
-
-#issue-count {
- display: none;
-}
-
-.quote {
- border: none;
- margin: 20px 0 60px 0;
-}
-
-.quote p {
- font-style: italic;
-}
-
-.quote p:before {
- content: '“';
- font-size: 50px;
- opacity: .15;
- position: absolute;
- top: -20px;
- left: 3px;
-}
-
-.quote p:after {
- content: '”';
- font-size: 50px;
- opacity: .15;
- position: absolute;
- bottom: -42px;
- right: 3px;
-}
-
-.quote footer {
- position: absolute;
- bottom: -40px;
- right: 0;
-}
-
-.quote footer img {
- border-radius: 3px;
-}
-
-.quote footer a {
- margin-left: 5px;
- vertical-align: middle;
-}
-
-.speech-bubble {
- position: relative;
- padding: 10px;
- background: rgba(0, 0, 0, .04);
- border-radius: 5px;
-}
-
-.speech-bubble:after {
- content: '';
- position: absolute;
- top: 100%;
- right: 30px;
- border: 13px solid transparent;
- border-top-color: rgba(0, 0, 0, .04);
-}
-
-.learn-bar > .learn {
- position: absolute;
- width: 272px;
- top: 8px;
- left: -300px;
- padding: 10px;
- border-radius: 5px;
- background-color: rgba(255, 255, 255, .6);
- transition-property: left;
- transition-duration: 500ms;
-}
-
-@media (min-width: 899px) {
- .learn-bar {
- width: auto;
- padding-left: 300px;
- }
-
- .learn-bar > .learn {
- left: 8px;
- }
-}
diff --git a/bench/app/node_modules/todomvc-common/base.js b/bench/app/node_modules/todomvc-common/base.js
deleted file mode 100644
index 3c6723f3..00000000
--- a/bench/app/node_modules/todomvc-common/base.js
+++ /dev/null
@@ -1,249 +0,0 @@
-/* global _ */
-(function () {
- 'use strict';
-
- /* jshint ignore:start */
- // Underscore's Template Module
- // Courtesy of underscorejs.org
- var _ = (function (_) {
- _.defaults = function (object) {
- if (!object) {
- return object;
- }
- for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
- var iterable = arguments[argsIndex];
- if (iterable) {
- for (var key in iterable) {
- if (object[key] == null) {
- object[key] = iterable[key];
- }
- }
- }
- }
- return object;
- }
-
- // By default, Underscore uses ERB-style template delimiters, change the
- // following template settings to use alternative delimiters.
- _.templateSettings = {
- evaluate : /<%([\s\S]+?)%>/g,
- interpolate : /<%=([\s\S]+?)%>/g,
- escape : /<%-([\s\S]+?)%>/g
- };
-
- // When customizing `templateSettings`, if you don't want to define an
- // interpolation, evaluation or escaping regex, we need one that is
- // guaranteed not to match.
- var noMatch = /(.)^/;
-
- // Certain characters need to be escaped so that they can be put into a
- // string literal.
- var escapes = {
- "'": "'",
- '\\': '\\',
- '\r': 'r',
- '\n': 'n',
- '\t': 't',
- '\u2028': 'u2028',
- '\u2029': 'u2029'
- };
-
- var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
-
- // JavaScript micro-templating, similar to John Resig's implementation.
- // Underscore templating handles arbitrary delimiters, preserves whitespace,
- // and correctly escapes quotes within interpolated code.
- _.template = function(text, data, settings) {
- var render;
- settings = _.defaults({}, settings, _.templateSettings);
-
- // Combine delimiters into one regular expression via alternation.
- var matcher = new RegExp([
- (settings.escape || noMatch).source,
- (settings.interpolate || noMatch).source,
- (settings.evaluate || noMatch).source
- ].join('|') + '|$', 'g');
-
- // Compile the template source, escaping string literals appropriately.
- var index = 0;
- var source = "__p+='";
- text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
- source += text.slice(index, offset)
- .replace(escaper, function(match) { return '\\' + escapes[match]; });
-
- if (escape) {
- source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
- }
- if (interpolate) {
- source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
- }
- if (evaluate) {
- source += "';\n" + evaluate + "\n__p+='";
- }
- index = offset + match.length;
- return match;
- });
- source += "';\n";
-
- // If a variable is not specified, place data values in local scope.
- if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
-
- source = "var __t,__p='',__j=Array.prototype.join," +
- "print=function(){__p+=__j.call(arguments,'');};\n" +
- source + "return __p;\n";
-
- try {
- render = new Function(settings.variable || 'obj', '_', source);
- } catch (e) {
- e.source = source;
- throw e;
- }
-
- if (data) return render(data, _);
- var template = function(data) {
- return render.call(this, data, _);
- };
-
- // Provide the compiled function source as a convenience for precompilation.
- template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
-
- return template;
- };
-
- return _;
- })({});
-
- if (location.hostname === 'todomvc.com') {
- (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
- (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
- m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
- })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
- ga('create', 'UA-31081062-1', 'auto');
- ga('send', 'pageview');
- }
- /* jshint ignore:end */
-
- function redirect() {
- if (location.hostname === 'tastejs.github.io') {
- location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com');
- }
- }
-
- function findRoot() {
- var base = location.href.indexOf('examples/');
- return location.href.substr(0, base);
- }
-
- function getFile(file, callback) {
- if (!location.host) {
- return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.');
- }
-
- var xhr = new XMLHttpRequest();
-
- xhr.open('GET', findRoot() + file, true);
- xhr.send();
-
- xhr.onload = function () {
- if (xhr.status === 200 && callback) {
- callback(xhr.responseText);
- }
- };
- }
-
- function Learn(learnJSON, config) {
- if (!(this instanceof Learn)) {
- return new Learn(learnJSON, config);
- }
-
- var template, framework;
-
- if (typeof learnJSON !== 'object') {
- try {
- learnJSON = JSON.parse(learnJSON);
- } catch (e) {
- return;
- }
- }
-
- if (config) {
- template = config.template;
- framework = config.framework;
- }
-
- if (!template && learnJSON.templates) {
- template = learnJSON.templates.todomvc;
- }
-
- if (!framework && document.querySelector('[data-framework]')) {
- framework = document.querySelector('[data-framework]').dataset.framework;
- }
-
- this.template = template;
-
- if (learnJSON.backend) {
- this.frameworkJSON = learnJSON.backend;
- this.frameworkJSON.issueLabel = framework;
- this.append({
- backend: true
- });
- } else if (learnJSON[framework]) {
- this.frameworkJSON = learnJSON[framework];
- this.frameworkJSON.issueLabel = framework;
- this.append();
- }
-
- this.fetchIssueCount();
- }
-
- Learn.prototype.append = function (opts) {
- var aside = document.createElement('aside');
- aside.innerHTML = _.template(this.template, this.frameworkJSON);
- aside.className = 'learn';
-
- if (opts && opts.backend) {
- // Remove demo link
- var sourceLinks = aside.querySelector('.source-links');
- var heading = sourceLinks.firstElementChild;
- var sourceLink = sourceLinks.lastElementChild;
- // Correct link path
- var href = sourceLink.getAttribute('href');
- sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http')));
- sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML;
- } else {
- // Localize demo links
- var demoLinks = aside.querySelectorAll('.demo-link');
- Array.prototype.forEach.call(demoLinks, function (demoLink) {
- if (demoLink.getAttribute('href').substr(0, 4) !== 'http') {
- demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href'));
- }
- });
- }
-
- document.body.className = (document.body.className + ' learn-bar').trim();
- document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
- };
-
- Learn.prototype.fetchIssueCount = function () {
- var issueLink = document.getElementById('issue-count-link');
- if (issueLink) {
- var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos');
- var xhr = new XMLHttpRequest();
- xhr.open('GET', url, true);
- xhr.onload = function (e) {
- var parsedResponse = JSON.parse(e.target.responseText);
- if (parsedResponse instanceof Array) {
- var count = parsedResponse.length;
- if (count !== 0) {
- issueLink.innerHTML = 'This app has ' + count + ' open issues';
- document.getElementById('issue-count').style.display = 'inline';
- }
- }
- };
- xhr.send();
- }
- };
-
- redirect();
- getFile('learn.json', Learn);
-})();
diff --git a/bench/app/package.json b/bench/app/package.json
deleted file mode 100644
index 3b1e70fd..00000000
--- a/bench/app/package.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "private": true,
- "dependencies": {
- "todomvc-common": "^1.0.1",
- "todomvc-app-css": "^1.0.1"
- }
-}
diff --git a/bench/app/readme.md b/bench/app/readme.md
deleted file mode 100644
index 76a71d43..00000000
--- a/bench/app/readme.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# Mithril TodoMVC Example
-
-> [Mithril](http://lhorie.github.io/mithril/) is a client-side MVC framework - a tool to organize code in a way that is easy to think about and to maintain.
-
-> _[Mithril - lhorie.github.io/mithril/](http://lhorie.github.io/mithril/)_
-
-## Learning Mithril
-
-The [Mithril website](http://lhorie.github.io/mithril/getting-started.html) is a great resource for getting started.
-
-Here are some links you may find helpful:
-
-* [Official Documentation](http://lhorie.github.io/mithril/mithril.html)
-
-_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._
-
-## Credit
-This TodoMVC application was created by [taylorhakes](https://github.com/taylorhakes).
\ No newline at end of file
diff --git a/bench/index.html b/bench/index.html
deleted file mode 100644
index 6f399b3d..00000000
--- a/bench/index.html
+++ /dev/null
@@ -1,81 +0,0 @@
-
-TodoMVC Benchmark
-
-
-
-
-
-
-
-
diff --git a/bench/resources/benchmark-runner.js b/bench/resources/benchmark-runner.js
deleted file mode 100644
index e3d2e908..00000000
--- a/bench/resources/benchmark-runner.js
+++ /dev/null
@@ -1,268 +0,0 @@
-(function (global) { // eslint-disable-line max-statements
- "use strict"
- var m = global.m
-
- window.onhashchange = function () {
- location.reload()
- }
-
- global.BenchmarkTestStep = BenchmarkTestStep
- function BenchmarkTestStep(name, run) {
- this.name = name
- this.run = run
- }
-
- global.BenchmarkRunner = BenchmarkRunner
- function BenchmarkRunner(suites, client) {
- this._suites = suites
- this._prepareReturnValue = null
- this._measuredValues = {}
- this._client = client
- }
-
- BenchmarkRunner.prototype.waitForElement = function (selector) {
- var deferred = m.deferred()
- var contentDocument = this._frame.contentDocument
-
- function resolveIfReady() {
- var element = contentDocument.querySelector(selector)
- if (element) {
- return deferred.resolve(element)
- }
- setTimeout(resolveIfReady, 50)
- }
-
- resolveIfReady()
- return deferred.promise
- }
-
- BenchmarkRunner.prototype._removeFrame = function () {
- if (this._frame) {
- this._frame.parentNode.removeChild(this._frame)
- this._frame = null
- }
- }
-
- BenchmarkRunner.prototype._appendFrame = function () {
- var frame = document.createElement("iframe")
- frame.style.width = "800px"
- frame.style.height = "600px"
- document.body.appendChild(frame)
- this._frame = frame
- return frame
- }
-
- BenchmarkRunner.prototype._waitAndWarmUp = function () {
- var startTime = Date.now()
-
- function Fibonacci(n) {
- if (Date.now() - startTime > 100) return
- else if (n <= 0) return 0
- else if (n === 1) return 1
- else return Fibonacci(n - 2) + Fibonacci(n - 1)
- }
-
- var deferred = m.deferred()
- setTimeout(function () {
- Fibonacci(100)
- deferred.resolve()
- }, 200)
- return deferred.promise
- }
-
- var now = window.performance && window.performance.now ?
- function () { return window.performance.now() } :
- function () { return +new Date() }
-
- function logResults(document, expected, suite, test, callback) {
- var count = document.querySelectorAll(".view").length
- if (count !== expected) {
- console.error([ // eslint-disable-line no-console
- suite.name,
- test.name,
- "expected",
- expected,
- "got",
- count
- ])
- callback(NaN, NaN)
- }
- }
-
- // This function must be as simple as possible, so it doesn't interfere
- // with reading the times
- BenchmarkRunner.prototype._runTest = function (
- suite,
- test,
- prepareReturnValue,
- callback
- ) {
- var testFunction = test.run
-
- var window = this._frame.contentWindow
- var document = this._frame.contentDocument
- var expected = this._client.numberOfItemsToAdd
-
- var startTime = now()
- testFunction(prepareReturnValue, window, document)
- var endTime = now()
- var syncTime = endTime - startTime
-
- startTime = now()
- setTimeout(function () {
- setTimeout(function () {
- var endTime = now()
-
- // if the DOM count is wrong after a test, don't report its
- // results.
- if (/Adding|Completing/.test(test.name)) {
- logResults(document, expected, suite, test, callback)
- }
-
- if (/Deleting/.test(test.name)) {
- logResults(document, 0, suite, test, callback)
- }
-
- callback(syncTime, endTime - startTime)
- }, 0)
- }, 0)
- }
-
- function BenchmarkState(suites) {
- this._suites = suites
- this._suiteIndex = -1
- this._testIndex = 0
- this.next()
- }
-
- BenchmarkState.prototype.currentSuite = function () {
- return this._suites[this._suiteIndex]
- }
-
- BenchmarkState.prototype.currentTest = function () {
- var suite = this.currentSuite()
- return suite ? suite.tests[this._testIndex] : null
- }
-
- BenchmarkState.prototype.next = function () {
- this._testIndex++
-
- var suite = this._suites[this._suiteIndex]
- if (suite && this._testIndex < suite.tests.length) {
- return this
- }
-
- this._testIndex = 0
-
- var i = this._suiteIndex
- var suites = this._suites
-
- do {
- i++
- } while (i < suites.length && suites[i].disabled)
-
- this._suiteIndex = i
-
- return this
- }
-
- BenchmarkState.prototype.isFirstTest = function () {
- return !this._testIndex
- }
-
- BenchmarkState.prototype.prepareCurrentSuite = function (runner, frame) {
- var suite = this.currentSuite()
- var deferred = m.deferred()
- frame.onload = function () {
- suite.prepare(runner, frame.contentWindow, frame.contentDocument)
- .then(function (result) {
- deferred.resolve(result)
- }, function (err) {
- deferred.reject(err)
- })
- }
- frame.src = suite.url
- return deferred.promise
- }
-
- BenchmarkRunner.prototype.step = function (state) {
- if (!state) {
- state = new BenchmarkState(this._suites)
- }
-
- var suite = state.currentSuite()
- if (!suite) {
- this._finalize()
- var deferred = m.deferred()
- deferred.resolve()
- return deferred.promise
- }
-
- if (state.isFirstTest()) {
- this._masuredValuesForCurrentSuite = {}
- var self = this
- return state.prepareCurrentSuite(this, this._appendFrame())
- .then(function (prepareReturnValue) {
- self._prepareReturnValue = prepareReturnValue
- return self._runTestAndRecordResults(state)
- })
- }
-
- return this._runTestAndRecordResults(state)
- }
-
- BenchmarkRunner.prototype._runTestAndRecordResults = function (state) {
- var deferred = m.deferred()
- var suite = state.currentSuite()
- var test = state.currentTest()
-
- if (this._client && this._client.willRunTest) {
- this._client.willRunTest(suite, test)
- }
-
- var self = this
- setTimeout(function () {
- self._runTest(suite, test, self._prepareReturnValue,
- function (syncTime, asyncTime) {
- var suiteResults
- if (self._measuredValues[suite.name]) {
- suiteResults = self._measuredValues[suite.name]
- } else {
- suiteResults = {tests: {}, total: 0}
- }
-
- self._measuredValues[suite.name] = suiteResults
-
- suiteResults.tests[test.name] = {
- Sync: syncTime,
- Async: asyncTime
- }
-
- suiteResults.total += syncTime + asyncTime
-
- if (self._client && self._client.willRunTest) {
- self._client.didRunTest(suite, test)
- }
-
- state.next()
- if (state.currentSuite() !== suite) {
- self._removeFrame()
- }
-
- deferred.resolve(state)
- })
- }, 0)
- return deferred.promise
- }
-
- BenchmarkRunner.prototype._finalize = function () {
- this._removeFrame()
-
- if (this._client && this._client.didRunSuites) {
- this._client.didRunSuites(this._measuredValues)
- }
-
- // FIXME: This should be done when we start running tests.
- this._measuredValues = {}
- }
-})(this)
diff --git a/bench/resources/manager.js b/bench/resources/manager.js
deleted file mode 100644
index 3efa55e3..00000000
--- a/bench/resources/manager.js
+++ /dev/null
@@ -1,239 +0,0 @@
-/* global m */
-/* eslint no-console: 0 */
-
-(function (global) { // eslint-disable-line max-statements
- "use strict"
-
- var numberOfItemsToAdd = (~~location.hash.slice(1)) || 250
- var runs = []
- var timesRan = 0
- var runButton
-
- function forOwn(object, f) {
- for (var key in object) {
- if ({}.hasOwnProperty.call(object, key)) {
- f(object[key], key)
- }
- }
- }
-
- var append = Function.call.bind(function append() {
- /* eslint-disable no-invalid-this */
- for (var i = 0; i < arguments.length; i++) {
- var arg = arguments[i]
- if (Array.isArray(arg)) append.apply(this, arg)
- else this.appendChild(arg)
- }
- return this
- /* eslint-enable no-invalid-this */
- })
-
- function n(type, attrs) {
- var el = document.createElement(type)
- if (!attrs) return el
- forOwn(attrs, function (value, attr) {
- if (attr === "style") {
- forOwn(value, function (k, v) { el.style[k] = v })
- } else {
- el[attr] = value
- }
- })
- return el
- }
-
- function createButton(text, onclick) {
- return n("button", {textContent: text, onclick: onclick})
- }
-
- function createTest(suite, test) {
- return append(n("li"),
- append(test.anchor = n("a", {id: suite.name + "-" + test.name}),
- document.createTextNode(suite.name + "/" + test.name)))
- }
-
- function createSuiteCheckbox(suite) {
- return n("input", {
- id: suite.name,
- type: "checkbox",
- checked: true,
- onchange: function () {
- suite.disabled = !this.checked
- }
- })
- }
-
- function createSuiteLabel(suite) {
- return append(n("label", {htmlFor: suite.name}),
- document.createTextNode(suite.name + " " + suite.version))
- }
-
- function createSuite(suite) {
- return append(n("li"),
- createSuiteCheckbox(suite),
- createSuiteLabel(suite),
- append(n("ol"), suite.tests.map(createTest.bind(null, suite))))
- }
-
- function createUIForSuites(suites, onstep, onrun) {
- return append(n("nav"),
- createButton("Step Tests", onstep),
- runButton = createButton("Run All", onrun),
- append(n("ol"), suites.map(createSuite)))
- }
-
- function generateResults(measured, timesToRun) {
- var result = ""
- var total = 0 // FIXME: Compute the total properly.
-
- forOwn(measured, function (suiteResults, suite) {
- forOwn(suiteResults.tests, function (testResults, test) {
- forOwn(testResults, function (subtestResults, subtest) {
- result += suite + " : " + test + " : " + subtest + ": " +
- subtestResults + " ms\n"
- })
- })
- result += suite + " : " + suiteResults.total + " ms\n"
- total += suiteResults.total
- })
- return result + "Run " + (runs.length + 1) + "/" + timesToRun +
- " - Total : " + total + " ms\n"
- }
-
- function reportFastest() {
- var results = {}
- runs.forEach(function (runData) {
- forOwn(runData, function (data, key) {
- results[key] = Math.min(
- results[key] || Infinity,
- data.total
- )
- })
- })
- return results
- }
-
- global.google.load("visualization", "1", {packages: ["corechart"]})
- function drawChart(results) {
- var V = global.google.visualization
-
- var raw = []
- forOwn(results, function (result, key) {
- raw.push([key, Math.round(result), colorify(key)])
- })
- raw.sort(function (a, b) { return a[1] - b[1] })
- raw.unshift(["Project", "Time", {role: "style"}])
-
- var runWord = "run" + (runs.length > 1 ? "s" : "")
- var title = "Best time in milliseconds over " + runs.length + " " +
- runWord + " (lower is better)"
-
- var view = new V.DataView(V.arrayToDataTable(raw))
- view.setColumns([0, 1, {
- calc: "stringify",
- sourceColumn: 1,
- type: "string",
- role: "annotation"
- }, 2])
-
- document.getElementById("analysis").style.display = "block"
-
- new V.BarChart(document.getElementById("barchart-values")).draw(view, {
- title: "TodoMVC Benchmark",
- width: 600,
- height: 400,
- legend: {position: "none"},
- backgroundColor: "transparent",
- hAxis: {title: title},
- min: 0,
- max: 1500
- })
- }
-
- function colorPart(n, pre) {
- return Math.max(
- 0,
- ((n.toLowerCase().charCodeAt(pre % n.length) - 97) / 26 * 255) | 0
- )
- }
-
- function colorify(n) {
- return "rgb(" +
- colorPart(n, 3) + ", " +
- colorPart(n, 4) + ", " +
- colorPart(n, 5) + ")"
- }
-
- function shuffle(ary) {
- for (var i = 0; i < ary.length; i++) {
- var j = Math.floor(Math.random() * (i + 1))
- var tmp = ary[i]
- ary[i] = ary[j]
- ary[j] = tmp
- }
- }
-
- window.addEventListener("load", function () {
- var match = window.location.search.match(/[\?&]r=(\d+)/)
- var timesToRun = match ? +(match[1]) : 1
-
- var Suites = global.Suites.map(function (suite) {
- suite = suite(numberOfItemsToAdd)
- suite.disabled = false
- return suite
- })
-
- var runner = new global.BenchmarkRunner(Suites, {
- numberOfItemsToAdd: numberOfItemsToAdd,
-
- willRunTest: function (suite, test) {
- if (test.anchor.classList) {
- test.anchor.classList.add("running")
- }
- },
-
- didRunTest: function (suite, test) {
- if (test.anchor.classList) {
- test.anchor.classList.remove("running")
- test.anchor.classList.add("ran")
- }
- },
-
- didRunSuites: function (measured) {
- var results = generateResults(measured, timesToRun)
- if (results) {
- console.log(results)
-
- runs.push(measured)
- timesRan++
- if (timesRan >= timesToRun) {
- timesRan = 0
- drawChart(reportFastest())
- shuffle(Suites)
- } else {
- setTimeout(function () {
- runButton.click()
- }, 0)
- }
- }
- }
- })
-
- var currentState = m.prop()
- function callNextStep(state) {
- runner.step(state).then(currentState).then(function (newState) {
- if (newState) callNextStep(newState)
- })
- }
-
- // Don't call step while step is already executing.
- document.body.appendChild(createUIForSuites(Suites,
- function () {
- runner.step(currentState()).then(currentState)
- },
- function () {
- document.getElementById("analysis").style.display = "none"
- localStorage.clear()
- callNextStep(currentState())
- }))
- })
-})(this)
diff --git a/bench/resources/tests.js b/bench/resources/tests.js
deleted file mode 100644
index 872fcfa4..00000000
--- a/bench/resources/tests.js
+++ /dev/null
@@ -1,51 +0,0 @@
-(function (global) {
- "use strict"
-
- var BenchmarkTestStep = global.BenchmarkTestStep
- var Suites = global.Suites = []
-
- Suites.push(function (numberOfItemsToAdd) {
- return {
- name: "Mithril (TodoMVC 1.3)",
- url: "app/index.html",
- version: "next (dev version)",
- prepare: function (runner) {
- return runner.waitForElement("#new-todo")
- .then(function (element) {
- element.focus()
- return element
- })
- },
- tests: [
- new BenchmarkTestStep("Adding" + numberOfItemsToAdd + "Items",
- function (newTodo) {
- for (var i = 0; i < numberOfItemsToAdd; i++) {
- var inputEvent = document.createEvent("Event")
- inputEvent.initEvent("input", true, true)
- newTodo.value = "Mithril ------- Something to do " + i
- newTodo.dispatchEvent(inputEvent)
-
- var keydownEvent = document.createEvent("Event")
- keydownEvent.initEvent("keyup", true, true)
- keydownEvent.keyCode = 13 // VK_ENTER
- newTodo.dispatchEvent(keydownEvent)
- }
- }),
- new BenchmarkTestStep("CompletingAllItems",
- function (newTodo, contentWindow, document) {
- var checkboxes = document.getElementsByClassName("toggle")
- for (var i = 0; i < checkboxes.length; i++) {
- checkboxes[i].click()
- }
- }),
- new BenchmarkTestStep("DeletingAllItems",
- function (newTodo, contentWindow, document) {
- var buttons = document.getElementsByClassName("destroy")
- for (var i = buttons.length - 1; i > -1; i--) {
- buttons[i].click()
- }
- })
- ]
- }
- })
-})(this)
diff --git a/mithril.js b/mithril.js
index 8fac9557..dd3ecf5b 100644
--- a/mithril.js
+++ b/mithril.js
@@ -1,4 +1,4 @@
-;(function (global, factory) { // eslint-disable-line
+void (function (global, factory) { // eslint-disable-line
"use strict"
/* eslint-disable no-undef */
var m = factory(typeof window !== "undefined" ? window : {})
@@ -13,9 +13,7 @@
})(this, function (window, undefined) { // eslint-disable-line
"use strict"
- m.version = function () {
- return "v0.2.1"
- }
+ var VERSION = "v0.2.1"
// Save these two.
var type = {}.toString
@@ -40,19 +38,23 @@
function noop() {}
function forEach(list, f) {
- for (var i = 0; i < list.length; i++) {
- f(list[i], i)
+ for (var i = 0; i < list.length && !f(list[i], i++);) {
+ // empty
}
}
function forOwn(obj, f) {
for (var prop in obj) {
if (hasOwn.call(obj, prop)) {
- f(obj[prop], prop)
+ if (f(obj[prop], prop)) break
}
}
}
+ var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g
+ var attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/
+ var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/ // eslint-disable-line max-len
+
// caching commonly used variables
var $document, $location, $requestAnimationFrame, $cancelAnimationFrame
@@ -74,6 +76,1118 @@
return window
}
+ m.version = function () {
+ return VERSION
+ }
+
+ /**
+ * @typedef {String} Tag
+ * A string that looks like -> div.classname#id[param=one][param2=two]
+ * Which describes a DOM node
+ */
+
+ function checkForAttrs(pairs) {
+ return pairs != null &&
+ isObject(pairs) &&
+ !("tag" in pairs || "view" in pairs || "subtree" in pairs)
+ }
+
+ function parseSelector(tag, cell) {
+ var classes = []
+ var match
+ while ((match = parser.exec(tag)) != null) {
+ if (match[1] === "" && match[2]) {
+ cell.tag = match[2]
+ } else if (match[1] === "#") {
+ cell.attrs.id = match[2]
+ } else if (match[1] === ".") {
+ classes.push(match[2])
+ } else if (match[3][0] === "[") {
+ var pair = attrParser.exec(match[3])
+ cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" : true)
+ }
+ }
+
+ return classes
+ }
+
+ function getChildrenFromList(hasAttrs, args) {
+ var children = hasAttrs ? args.slice(1) : args
+ if (children.length === 1 && isArray(children[0])) {
+ return children[0]
+ } else {
+ return children
+ }
+ }
+
+ function assignAttrs(cell, attrs, classAttr, classes) {
+ forOwn(attrs, function (value, attr) {
+ if (attr === classAttr &&
+ attrs[attr] != null &&
+ attrs[attr] !== "") {
+ classes.push(attrs[attr])
+
+ // create key in correct iteration order
+ cell.attrs[attr] = ""
+ } else {
+ cell.attrs[attr] = attrs[attr]
+ }
+ })
+
+ if (classes.length) {
+ cell.attrs[classAttr] = classes.join(" ")
+ }
+ }
+
+ /**
+ * @param {Tag} The DOM node tag
+ * @param {Object=[]} optional key-value pairs to be mapped to DOM attrs
+ * @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array,
+ * or splat (optional)
+ */
+ function m(tag, pairs) {
+ for (var args = [], i = 1; i < arguments.length; i++) {
+ args[i - 1] = arguments[i]
+ }
+
+ if (isObject(tag)) return parameterize(tag, args)
+ var hasAttrs = checkForAttrs(pairs)
+ var attrs = hasAttrs ? pairs : {}
+ var classAttr = "class" in attrs ? "class" : "className"
+ var cell = {tag: "div", attrs: {}}
+
+ if (!isString(tag)) {
+ throw new Error("selector in m(selector, attrs, children) should " +
+ "be a string")
+ }
+
+ var classes = parseSelector(tag, cell)
+ cell.children = getChildrenFromList(hasAttrs, args)
+
+ assignAttrs(cell, attrs, classAttr, classes)
+
+ return cell
+ }
+
+ function forKeys(list, f) {
+ forEach(list, function (attrs, i) {
+ attrs = attrs && attrs.attrs
+ return attrs && attrs.key != null && f(attrs, i)
+ })
+ }
+
+ // This function was causing deopts in Chrome.
+ function dataToString(data) {
+ // data.toString() might throw or return null if data is the return
+ // value of Console.log in some versions of Firefox
+ try {
+ if (data != null && data.toString() != null) {
+ return data
+ }
+ } catch (e) {
+ // Swallow all errors here.
+ }
+
+ return ""
+ }
+
+ // This function was causing deopts in Chrome.
+ function injectTextNode(parent, first, index, data) {
+ try {
+ insertNode(parent, first, index)
+ first.nodeValue = data
+ } catch (e) {
+ // IE erroneously throws error when appending an empty text node
+ // after a null
+ }
+ }
+
+ function flatten(list) {
+ // recursively flatten array
+ for (var i = 0; i < list.length; i++) {
+ if (isArray(list[i])) {
+ list = list.concat.apply([], list)
+ // check current index again while there is an array at this
+ // index.
+ i--
+ }
+ }
+
+ return list
+ }
+
+ function insertNode(parent, node, index) {
+ parent.insertBefore(node, parent.childNodes[index] || null)
+ }
+
+ var DELETION = 1
+ var INSERTION = 2
+ var MOVE = 3
+
+ function handleKeysDiffer(data, existing, cached, parent) {
+ forKeys(data, function (key, i) {
+ key = key.key
+ if (existing[key]) {
+ existing[key] = {
+ action: MOVE,
+ index: i,
+ from: existing[key].index,
+ element: cached.nodes[existing[key].index] ||
+ $document.createElement("div")
+ }
+ } else {
+ existing[key] = {action: INSERTION, index: i}
+ }
+ })
+
+ var actions = []
+
+ forOwn(existing, function (value) {
+ actions.push(value)
+ })
+
+ var changes = actions.sort(sortChanges)
+ var newCached = new Array(cached.length)
+ newCached.nodes = cached.nodes.slice()
+
+ forEach(changes, function (change) {
+ var index = change.index
+
+ switch (change.action) {
+ case DELETION:
+ clear(cached[index].nodes, cached[index])
+ newCached.splice(index, 1)
+ break
+
+ case INSERTION:
+ var dummy = $document.createElement("div")
+ dummy.key = data[index].attrs.key
+ insertNode(parent, dummy, index)
+ newCached.splice(index, 0, {
+ attrs: {key: data[index].attrs.key},
+ nodes: [dummy]
+ })
+ newCached.nodes[index] = dummy
+ break
+
+ case MOVE:
+ var changeElement = change.element
+ var maybeChanged = parent.childNodes[index]
+ if (maybeChanged !== changeElement && changeElement !== null) {
+ parent.insertBefore(changeElement, maybeChanged || null)
+ }
+ newCached[index] = cached[change.from]
+ newCached.nodes[index] = changeElement
+ }
+ })
+
+ return newCached
+ }
+
+ function diffKeys(data, cached, existing, parentElement) {
+ var keysDiffer = data.length !== cached.length
+
+ if (!keysDiffer) {
+ forKeys(data, function (attrs, i) {
+ var cachedCell = cached[i]
+ return keysDiffer = cachedCell &&
+ cachedCell.attrs &&
+ cachedCell.attrs.key !== attrs.key
+ })
+ }
+
+ if (keysDiffer) {
+ return handleKeysDiffer(data, existing, cached, parentElement)
+ } else {
+ return cached
+ }
+ }
+
+ // diffs the array itself
+ function diffArray(data, cached, nodes) {
+ // update the list of DOM nodes by collecting the nodes from each item
+ forEach(data, function (_, i) {
+ if (cached[i] != null) nodes.push.apply(nodes, cached[i].nodes)
+ })
+
+ // remove items from the end of the array if the new array is shorter
+ // than the old one. if errors ever happen here, the issue is most
+ // likely a bug in the construction of the `cached` data structure
+ // somewhere earlier in the program
+ forEach(cached.nodes, function (node, i) {
+ if (node.parentNode != null && nodes.indexOf(node) < 0) {
+ clear([node], [cached[i]])
+ }
+ })
+
+ if (data.length < cached.length) cached.length = data.length
+
+ cached.nodes = nodes
+ }
+
+ function buildArrayKeys(data) {
+ var guid = 0
+ forKeys(data, function () {
+ forEach(data, function (attrs) {
+ attrs = attrs && attrs.attrs
+ if (attrs && attrs.key == null) {
+ attrs.key = "__mithril__" + guid++
+ }
+ })
+ return true
+ })
+ }
+
+ // shallow array compare, sorts
+ function arraySortCompare(a, b) {
+ a.sort()
+ b.sort()
+ var len = a.length
+ if (len !== b.length) return false
+ for (var i = 0; i < len; i++) {
+ if (a[i] !== b[i]) return false
+ }
+ return true
+ }
+
+ function elemIsDifferentEnough(data, cached, dataAttrKeys) {
+ if (data.tag !== cached.tag) return true
+ if (!arraySortCompare(dataAttrKeys, Object.keys(cached.attrs))) {
+ return true
+ }
+
+ if (data.attrs.id !== cached.attrs.id) return true
+ if (data.attrs.key !== cached.attrs.key) return true
+
+ if (m.redraw.strategy() === "all") {
+ return !(cached.configContext &&
+ cached.configContext.retain === true)
+ } else if (m.redraw.strategy() === "diff") {
+ return cached.configContext &&
+ cached.configContext.retain === false
+ }
+ }
+
+ function maybeRecreateObject(data, cached, dataAttrKeys) {
+ // if an element is different enough from the one in cache, recreate it
+ if (elemIsDifferentEnough(data, cached, dataAttrKeys)) {
+ if (cached.nodes.length) clear(cached.nodes)
+ if (cached.configContext &&
+ isFunction(cached.configContext.onunload)) {
+ cached.configContext.onunload()
+ }
+
+ if (cached.controllers) {
+ forEach(cached.controllers, function (controller) {
+ if (controller.unload) {
+ controller.onunload({preventDefault: noop})
+ }
+ })
+ }
+ }
+ }
+
+ function getObjectNamespace(data, namespace) {
+ return data.attrs.xmlns ? data.attrs.xmlns :
+ data.tag === "svg" ? "http://www.w3.org/2000/svg" :
+ data.tag === "math" ? "http://www.w3.org/1998/Math/MathML" :
+ namespace
+ }
+
+ var pendingRequests = 0
+ m.startComputation = function () { pendingRequests++ }
+ m.endComputation = function () {
+ if (pendingRequests > 1) {
+ pendingRequests--
+ } else {
+ pendingRequests = 0
+ m.redraw()
+ }
+ }
+
+ function unloadCachedControllers(cached, views, controllers) {
+ if (controllers.length) {
+ cached.views = views
+ cached.controllers = controllers
+ forEach(controllers, function (controller) {
+ if (controller.onunload && controller.onunload.$old) {
+ controller.onunload = controller.onunload.$old
+ }
+
+ if (pendingRequests && controller.onunload) {
+ var onunload = controller.onunload
+ controller.onunload = noop
+ controller.onunload.$old = onunload
+ }
+ })
+ }
+ }
+
+ function scheduleConfigsToBeCalled(configs, data, node, isNew, cached) {
+ // schedule configs to be called. They are called after `build` finishes
+ // running
+ var config = data.attrs.config
+ if (isFunction(config)) {
+ var context = cached.configContext = cached.configContext || {}
+
+ // bind
+ configs.push(function () {
+ return config.call(data, node, !isNew, context, cached)
+ })
+ }
+ }
+
+ function buildUpdatedNode(
+ cached,
+ data,
+ editable,
+ hasKeys,
+ namespace,
+ views,
+ configs,
+ controllers
+ ) {
+ var node = cached.nodes[0]
+ if (hasKeys) {
+ setAttributes(node, data.tag, data.attrs, cached.attrs, namespace)
+ }
+
+ cached.children = build(node, data.tag, undefined, undefined,
+ data.children, cached.children, false, 0,
+ data.attrs.contenteditable ? node : editable, namespace, configs)
+
+ cached.nodes.intact = true
+
+ if (controllers.length) {
+ cached.views = views
+ cached.controllers = controllers
+ }
+
+ return node
+ }
+
+ function handleNonexistentNodes(data, parent, index) {
+ var nodes
+ if (data.$trusted) {
+ nodes = injectHTML(parent, index, data)
+ } else {
+ nodes = [$document.createTextNode(data)]
+ if (!voidElements.test(parent.nodeName)) {
+ insertNode(parent, nodes[0], index)
+ }
+ }
+
+ var cached
+
+ if (typeof data === "string" ||
+ typeof data === "number" ||
+ typeof data === "boolean") {
+ cached = new data.constructor(data)
+ } else {
+ cached = data
+ }
+
+ cached.nodes = nodes
+
+ return cached
+ }
+
+ function reattachNodes(data,
+ cached,
+ parentElement,
+ editable,
+ index,
+ parentTag
+ ) {
+ var nodes = cached.nodes
+ if (!editable || editable !== $document.activeElement) {
+ if (data.$trusted) {
+ clear(nodes, cached)
+ nodes = injectHTML(parentElement, index, data)
+ } else if (parentTag === "textarea") {
+ //