diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 4db9d66c..00000000 --- a/.editorconfig +++ /dev/null @@ -1,10 +0,0 @@ -root = true - -[*] -charset = utf-8 - -[*.js] -indent_style = tab -trim_trailing_whitespace = true -insert_final_newline = true -end_of_line = crlf \ No newline at end of file diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 0768490a..00000000 --- a/.eslintignore +++ /dev/null @@ -1,15 +0,0 @@ -# The second line is because Git is mysteriously not recursing. -node_modules -**/node_modules - -# Build artifacts. -**/*.min.js -archive -deploy -mithril.closure-compiler-externs.js - -# This is merely a dependency for the documentation. -docs/layout/lib - -# This is probably going to disappear eventually, anyways. -tests diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 14612a91..00000000 --- a/.eslintrc +++ /dev/null @@ -1,93 +0,0 @@ -{ - "extends": "eslint:recommended", - - "env": { - "browser": true - }, - - "rules": { - "indent": [2, "tab"], - "no-trailing-spaces": 2, - "linebreak-style": [2, "windows"], - - "curly": [2, "multi-line"], - "dot-location": [2, "property"], - "dot-notation": 2, - "eqeqeq": [2, "allow-null"], - "no-alert": 2, - "no-caller": 2, - "guard-for-in": 2, - "no-empty-label": 2, - "no-eval": 2, - "no-extend-native": 2, - "no-extra-bind": 2, - "no-floating-decimal": 2, - "no-implied-eval": 2, - "no-invalid-this": 2, - "no-iterator": 2, - "no-lone-blocks": 2, - "no-loop-func": 2, - "no-multi-spaces": 2, - "no-multi-str": 2, - "no-native-reassign": 2, - "no-new-func": 2, - "no-new-wrappers": 2, - "no-octal-escape": 2, - "no-proto": 2, - "no-self-compare": 2, - "no-sequences": 2, - "no-throw-literal": 2, - "no-unused-expressions": 2, - "no-useless-call": 2, - "no-void": 2, - "no-warning-comments": 1, - "radix": 2, - "wrap-iife": [2, "inside"], - "strict": [2, "function"], - "no-catch-shadow": 2, - "no-label-var": 2, - "no-shadow-restricted-names": 2, - "no-undef-init": 2, - "no-use-before-define": [2, "nofunc"], - "array-bracket-spacing": 2, - "block-spacing": 2, - "brace-style": [2, "1tbs", {"allowSingleLine": true}], - "camelcase": 2, - "comma-spacing": 2, - "comma-style": 2, - "consistent-this": [2, "self"], - "eol-last": 2, - "func-style": [2, "declaration"], - "key-spacing": 2, - "max-nested-callbacks": [2, 4], - "new-parens": 2, - "no-array-constructor": 2, - "no-lonely-if": 2, - "no-new-object": 2, - "no-multiple-empty-lines": [2, {"max": 1}], - "no-spaced-func": 2, - "no-unneeded-ternary": 2, - "object-curly-spacing": 2, - "one-var": [2, {"initialized": "never"}], - "operator-assignment": 2, - "operator-linebreak": [2, "after"], - "padded-blocks": [2, "never"], - "quote-props": [2, "as-needed"], - "quotes": [2, "double", "avoid-escape"], - "semi-spacing": 0, - "semi": [2, "never"], - "space-after-keywords": 2, - "space-before-function-paren": [2, { - "anonymous": "always", - "named": "never" - }], - "space-before-keywords": 2, - "space-in-parens": 2, - "space-infix-ops": [2, {"int32Hint": true}], - "space-unary-ops": 2, - "spaced-comment": [2, "always"], - "max-depth": [2, 4], - "max-len": [2, 80, 4], - "max-statements": [2, 20] - } -} diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index edce79b7..00000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -* text eol=crlf -*.min.js binary diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 93f13619..00000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -npm-debug.log diff --git a/.npmignore b/.npmignore deleted file mode 100644 index e69de29b..00000000 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2b42d205..00000000 --- a/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: node_js -node_js: - - stable - -sudo: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 5d2fdfac..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,835 +0,0 @@ -# Bug reports - -Use the [issue tracker](https://github.com/lhorie/mithril.js/issues). Do check to make sure your bug hasn't already been filed. Please give the following information, where possible: - -1. The version of Mithril you're using, whether it's the dev version or [the version on npm](http://npm.im/mithril.js). The version on npm may not have all the latest bug fixes, so your bug might very well be fixed in the dev version. -2. The name and version of the browser(s) affected. -3. A detailed explanation of the bug. -4. A test case. The simpler, the better. - -# Feature requests - -Use the [issue tracker](https://github.com/lhorie/mithril.js/issues). Please do the following, where possible: - -1. Check to make sure your suggestion hasn't already been filed. There's a nice collection of some of these feature requests [here](https://github.com/lhorie/mithril.js/issues/802). -2. Clearly denote your issue as a feature request. It helps make the intent clearer. Even better is to denote it in the title. -3. Describe your idea. This is the most important part. -4. Submit, and wait for feedback. Don't forget to drop by the [Gitter room](https://gitter.im/lhorie/mithril.js) to get some publicity. It gets traffic daily. :smile: - -# Contributing - -We welcome any and all contributions. This is a community-driven project. Although we don't have a lot, we do have a few guidelines for contributions. - -1. Please try to adhere to the style guide. Most of it is checked by ESLint. ESLint is also set up to check for other common errors, such as undeclared variables and invalid `typeof` values. -2. Please make sure there are no regressions with your patch. Please don't disable existing tests, and please don't send a PR with new, disabled tests. - - There are a few known failing tests currently (PRs welcome): [*](https://github.com/lhorie/mithril.js/blob/next/test/mithril.deferred.js#L121-L125) [*](https://github.com/lhorie/mithril.js/blob/next/test/mithril.render.js#L1321-L1343) [*](https://github.com/lhorie/mithril.js/blob/next/test/mithril.route.js#L106-L131) [*](https://github.com/lhorie/mithril.js/blob/next/test/mithril.route.js#L134-L162) [*](https://github.com/lhorie/mithril.js/blob/next/test/mithril.route.js#L165-L191) [*](https://github.com/lhorie/mithril.js/blob/next/test/mithril.route.js#L194-L222) -3. For any new features introduced, be sure to write new unit tests for it. Maximum coverage is what we want. -4. Try to not leave any extra `TODO`s, `FIXME`s, etc. in your code. ESLint will nag at you until you fix whatever problem it is. - - Note that it's only a warning, not an error. It won't fail the CI tests, and there's a few outstanding ones inside Mithril right now. - - If you must, use a `TODO(): whatever` (or the equivalent `FIXME`, etc.) if it's something you are actively working on, or if it's something more general, file an issue titled "TODO: " and reference the `TODO` comment itself. - -It is assumed that for all contributions you make, you have the appropriate rights to and it may be made available under the MIT License, the license used for this project. - -# Style Guide - -The style is checked with ESLint. This style guide is here for one reason only: consistency. This should work for most code here, but it shouldn't be considered absolute &endash; consistency with the surrounding code is higher priority than following this guide. Also, if you need some sort of hack that doesn't follow this style guide like [Bluebird's `toFastProperties` hack](https://stackoverflow.com/q/24987896), make sure the code is consistent with what's around it, and disable whatever ESLint warnings you need to. (In that case, you would use `no-eval` and `no-unused-expressions`). - -### EditorConfig - -This project has its own [EditorConfig](http://editorconfig.org/). Most common editors either support it natively or have a plugin to support it. Here's links for several editors: - -- [Atom](https://atom.io/packages/editorconfig) -- [Nodepad++](https://github.com/editorconfig/editorconfig-notepad-plus-plus#readme) -- [Sublime Text](https://packagecontrol.io/packages/EditorConfig) -- [Vim](http://www.vim.org/scripts/script.php?script_id=3934) -- [Emacs](https://marmalade-repo.org/packages/editorconfig) -- IntelliJ, WebStorm: You're already set, since it supports EditorConfig natively. -- [Eclipse](https://marketplace.eclipse.org/content/editorconfig-eclipse) -- [Visual Studio](https://visualstudiogallery.msdn.microsoft.com/c8bccfe2-650c-4b42-bc5c-845e21f96328) -- [Xcode](https://github.com/MarcoSero/EditorConfig-Xcode) -- [Komodo](http://komodoide.com/packages/addons/editorconfig/) - -EditorConfig also has [their own curated list](http://editorconfig.org/#download) of plugins. Do note that Text Mate's plugin doesn't support multiple properties used in this repo's `.editorconfig`. - -### Line length - -Please keep line length down to a maximum of 80 characters. - -The only exception to this rule is with long regexes and test names. You can use `// eslint-disable-line max-len` to suppress this. - -### Function length - -Please keep function length to no more than 20 statements, including those in nested blocks. - -The only exceptions are for revealing module wrappers and Mocha `describe` and `context` blocks. If you need to suppress this, you can use `// eslint-disable-line max-statements` at the beginning of the function. - -This isn't checked for the tests, but still, please keep it reasonable. - -### Line endings - -Use Windows-style line endings (i.e. CRLF). - -### Semicolons - -Avoid semicolons. A few resources to help you understand this: [*](http://blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding) [*](http://inimino.org/~inimino/blog/javascript_semicolons) [*](http://jamesallardice.com/understanding-automatic-semi-colon-insertion-in-javascript/) - -### Redundant expressions - -Don't use redundant parentheses, curly braces, `undefined`s, ternaries, etc. - -- `var foo = value === 1 ? true : false` is better written as `var foo = value === 1`. -- `var foo = undefined` is better written as `var foo`. -- `return undefined` is better written as `return`. -- `delete(obj.foo)` is better written as `delete obj.foo`. -- As an exception, use an extra set of parentheses when assigning in conditions. - -### Equality testing - -Prefer strict equality (`===`/`!==`) to loose equality (`==`/`!=`), unless you're comparing against `null` or `undefined`, where loose equality (`== null`/`!= null`) is preferred. It's more type-safe with primitives. - -### `eval` and friends - -Never use `eval`, the `Function` constructor, and friends. Those will fail when running under CSP restrictions, and this library should work in those environments. - -### Quotes - -Prefer double quotes to single quotes, but using single quotes to avoid escaping is okay. - -### Strict mode - -Any code not in the global scope should be in strict mode unless there's a good reason it shouldn't be. Browsers run it faster, and there's certain things where it's safer. - -## Comments - -Comments are helpful. Use descriptive comments where needed, so others can read it and understand it. If a non-obvious hack is used, explain it with a comment. But don't repeat yourself with a redundant comment when the code adequately describes itself. - -```js -// Good -var CARD_DECK_SIZE = 52 - -// Shuffle the deck of cards. -for (var i = 0; i < CARD_DECK_SIZE; i++) { - var j = i + (Math.random() * CARD_DECK_SIZE - i)|0 - - var tmp = deck[i] - deck[i] = deck[j] - deck[j] = tmp -} - -// Also good -function toFastProperties(obj) { - // Bluebird's toFastProperties hack. Forces V8 to optimize object as prototype, - // significantly speeding up property access. - - /* eslint-disable no-eval */ - function C() {} - C.prototype = obj - new C() - return - eval(obj) - /* eslint-enable no-eval */ -} - -// Bad -var CARD_DECK_SIZE = 52 - -// Shuffle the deck of cards. -function shuffle(deck) { - for (var i = 0; i < CARD_DECK_SIZE; i++) { - // Generate a random card index to swap at. - var j = i + (Math.random() * CARD_DECK_SIZE - i)|0 - - // Swap the cards at each index. - var tmp = deck[i] - deck[i] = deck[j] - deck[j] = tmp - } -} - -// Also bad -// This function loops through each item in a list, calling `f` with each item -// and their respective index. If the function `f` returns explicitly with -// `false`, iteration stops. This function returns the original list for -// convenience. -function forEach(list, f) { - // Loop through each entry in the list with the loop index `i` - for (var i = 0; i < list.length; i++) { - // Call the function with the current item and index - if (f(list[i], i) === false) { - // If the function explicitly returns `false`, immediately stop - // iteration. - break - } - } - // Return the list, for convenience. - return list -} -``` - -### Magic values - -Prefer constant variables to magic values where it helps with code readability. Also, don't use comments in place of a constant. - -```js -// Good -var CARD_DECK_SIZE = 52 -for (var i = 0; i < CARD_DECK_SIZE; i++) { - var j = i + (Math.random() * CARD_DECK_SIZE - i)|0 - var tmp = a[i] - a[i] = a[j] - a[j] = tmp -} - -// Bad -for (var i = 0; i < 52; i++) { - var j = i + (Math.random() * 52 - i)|0 - var tmp = a[i] - a[i] = a[j] - a[j] = tmp -} - -// Also bad -// Note: 52 cards in a deck -for (var i = 0; i < 52; i++) { - var j = i + (Math.random() * 52 - i)|0 - var tmp = a[i] - a[i] = a[j] - a[j] = tmp -} -``` - -### Initial whitespace - -Start your comments with a space. It's more readable. - -```js -// Good -// This is a comment. - -// Bad -//This is a comment. -``` - -### Grammar and spelling - -Please try to use proper grammar and spelling. Not all of us are perfect English speakers (even us native ones struggle at times), but it's easier to understand down the road when reading the code. - -## Whitespace - -Use vertical whitespace and indentation to your advantage. It helps with readability, and minifiers are astonishingly great at removing this. :stuck_out_tongue_winking_eye: - -```js -// Good -function iterate() { - var index = 0 - var list = [] - while (hasNext()) { - list.push(next(index)) - index++ - } - - for (var i = 0; i < list.length; i++) { - read(list[i]) - } -} - -// Bad -function iterate() { - var index = 0 - var list = [] - while (hasNext()) list.push(next(index++)) - for (var i = 0; i < list.length; i++) read(list[i]) -} - -// Also bad -function iterate() { - var list = [] - for (var index = 0; hasNext(); list.push(next(index++))) {} - for (var i = 0; i < list.length; i++) read(list[i]) -} -``` - -### Trailing whitespace - -Please don't leave trailing spaces, even in blank likes. It makes diffs harder to read. If your [editor supports EditorConfig, or you've downloaded a plugin for it](http://editorconfig.org/#download), you're already set. Otherwise, searching " strip trailing whitespace blank lines" should help you. - -### Indentation and vertical whitespace - -Indent with hard tabs. Each one counts for 4 spaces. - -Never mix tabs and spaces. Don't use smart tabs. - -### Excessive whitespace - -Keep whitespace within reason. Limit vertical whitespace to no more than 1 consecutive blank line, and don't start or end blocks with plain whitespace. Don't use more than one character of horizontal whitespace beyond indentation. - -```js -// Bad -function iterate() { - var index = 0 - var list = [] - - while (hasNext()) { - list.push(next(index)) - index++ - } - - - - for (var i = 0; i < list.length; i++) { - read(list[i]) - } -} - -// Also bad -var a = 2 -// ^^ ^^ -``` - -If you're find yourself resorting to multiple consecutive blank lines to separate logic, consider refactoring the code into smaller functions if possible. - -### Control keywords - -Always surround control keywords (e.g. `if`, `else`, `for`) with whitespace. - -## Operators - -### Binary operators - -Always surround binary keyword operators (e.g. `in`, `instanceof`) with whitespace. - -Always surround any other binary operator with whitespace, including assignment operators. Add line breaks after the operator, not before. - -```js -// Good -var a = 1 + 2 -a = 3 - -var a = 1 + 2 + 3 + 4 + 5 + - 6 + 7 + 8 + 9 + 10 - -// Bad -var a = 1+2 -var a=1+2 -a=3 - -var a = 1 + 2 + 3 + 4 + 5 - + 6 + 7 + 8 + 9 + 10 -``` - -In the event you're casting to a 32-bit integer (i.e. `x|0`), it's okay to omit the whitespace. Mithril doesn't currently have any instances of this, but it may in the future. - -```js -// This is okay -var casted = value|0 -``` - -### Unary operators - -Always use whitespace between an unary keyword operator (e.g. `delete`, `new`) with whitespace. - -Do not use spaces between any other unary operator and the value they apply to. - -```js -// Good -!a --1 -~1 - -// Bad -! a -- 1 -~ 1 -``` - -## Objects - -### Exterior whitespace - -Do not include space between the opening curly brace and the first object key in single line objects. For multi-line objects, don't include any extra preceding whitespace at the beginning or end. - -```js -// Good -var object = {foo: 1} - -var object = { - foo: 1, - bar: 2, - baz: 3, - quux: "string" -} - -// Bad -var object = { foo: 1 } - -var object = { - - foo: 1, - bar: 2, - baz: 3, - quux: "string" - -} -``` - -### Interior whitespace - -Use no space before an object key and the colon, but use a single space between the colon and value. - -```js -// Good -var object = {foo: 1} - -// Bad -var object = {foo:1} -var object = {foo : 1} -var object = {foo :1} -``` - -### Larger objects - -Non-trivial objects should occupy multiple lines. - -```js -// Good -var object = { - foo: 1, - bar: 2, - baz: 3, - quux: "string" -} - -// Bad -var object = {foo: 1, bar: 2, baz: 3, quux: "string"} -``` - -Prefer plain objects over `new function () {}`, but if you feel the latter is better, use `// eslint-disable-line no-new-func` on the first line. - -### Comma placement - -Commas come last. Also, don't use trailing commas. - -```js -// Good -var object = { - foo: 1, - bar: 2 -} - -// Bad -var object = { - foo: 1, - bar: 2, // Trailing comma -} - -var object = { - foo: 1 -, bar: 2 // Comma first -} - -// Also bad -var object = {foo: 1,} -``` - -### Member access - -When chaining methods and/or properties across multiple lines, dots come first. - -```js -// Good -object - .foo() - .bar() - -// Bad -object. - foo(). - bar() -``` - -### Property quoting - -Quote properties when needed. Never quote valid identifiers. This may lead to some inconsistency in whether properties are quoted or not in the object, but that inconsistency is okay. - -```js -// Good -var object = { - foo: 1, - bar: 2 -} - -var object = { - foo: 1, - "non-identifier": 2 -} - -// Bad -var object = { - "foo": 1, - "bar": 2 -} - -var object = { - "foo": 1, - "non-identifier": 2 -} -``` - -### Iteration - -When iterating objects with `for-in`, filter it with `Object.prototype.hasOwnProperty` first. This may be on the same line if that's the only condition. If this is used more than once, make a local `hasOwn` alias to use like `hasOwn.call(object, prop)`. - -## Variables and declarations - -Use camel case (e.g. `loopIndex`) for variables, pascal case (e.g. `MyClass`) for classes, and upper case with underscores (e.g. `MAX_VALUE`) for constants. - -Use `self` to capture `this` where needed. (i.e. `var self = this`) - -Prefer short but clear, descriptive, and self-documenting variable names. - -Single letter names are generally okay in these contexts: - -- Loop variables: `i`, `j`, etc. -- Function arguments in small, trivial functional utilities: `f`, `g`, etc. -- Standard mathematical algorithms: `x`, `y`, `a`, `b`, etc. - -```js -// Good -function forEach(list, f) { - for (var i = 0; i < list.length; i++) { - f(list[i], i) - } - return list -} - -// Also good -function forEach(list, func) { - for (var index = 0; index < list.length; index++) { - func(list[index], index) - } - return list -} - -// Bad -function iterateArray(listOfEntries, callback) { - for (var index = 0; index < listOfEntries.length; index++) { - callback(listOfEntries[index], index) - } - return listOfEntries -} - -// Also bad -function e(l, c) { - for (var x = 0; x < l.length; x++) { - c(l[x], x) - } - return l -} -``` - -### Multiple variable declarations - -If a variable is assigned a value when it's declared, it gets its own line. - -If a variable is not immediately assigned a value, it may be grouped with others that aren't first assigned values. - -Do group related variables. - -Variable declarations in the init block of a for loop are excluded from this rule, but the number of declarations should still be minimized. - -```js -// Good -var a = 1 -var b = 2 -var c, d - -// Also good -var foo, bar, baz, quux -var spam, eggs, ham -var shouldUpdate, initialize - -// Okay, since it's within a for loop -for (var i = 0, test; (test = foo === 1); i++) { - doSomething(i) -} - -// Bad -var a = 1, b = 2, c, d - -// Also bad -var foo, bar, baz, quux, spam, eggs, ham, shouldUpdate, initialize -``` - -## Assignment - -### Native functions - -Don't assign to native functions or prototypes beyond polyfills. Ever. - -### Function declarations - -Don't assign to a function declaration. Declarations look like static values, so they should be treated that way. There is no difference in size, either. - -```js -// Bad -function foo() { return 1 } -foo = function () { return 2 } - -// Also bad -function foo() { return 1 } -foo = 2 -``` - -### Conditions - -Avoid assigning directly in conditions. If you need to assign in a condition, wrap them in a new set of parentheses. - -```js -// Good -var test = foo === 1 -if (test) { - doSomething() -} - -var test = foo === 1 -for (var i = 0; test; i++) { - doSomething(i) - test = foo === 1 -} - -if ((test = foo === 1)) { - doSomething() -} - -for (var i = 0, test; (test = foo === 1); i++) { - doSomething(i) -} - -// Bad -if (test = foo === 1) { - doSomething() -} - -for (var i = 0, test; test = foo === 1; i++) { - doSomething(i) -} -``` - -## Functions - -Prefer anonymous functions to named functions in expressions where possible - -Prefer named function declarations to assigning a function to a declared variable where possible. - -```js -// Good -setTimeout(function () { doSomething() }, 0) - -function foo() { - return 1 -} - -// Good, uses recursion -setTimeout(function f() { - doSomething() - if (shouldRepeat()) setTimeout(f, 0) -}, 0) - -// Bad -requestAnimationFrame(function foo() { runNext() }) - -var foo = function () { - return 1 -} -``` - -### Anonymous functions - -Anonymous functions must have a single space between `function` and the arguments and between the arguments and the opening brace. - -### Named functions, function declarations - -Named functions and function declarations must have no space between the function name and the arguments and a single space between the arguments and the opening brace. - -## Blocks and conditionals - -### Curly braces - -Curly braces are required for blocks if the body takes up more than one line. This goes for functions as well. They are optional if it fits within one line, but don't be afraid to use them where you see fit. - -Do put an extra space after the first brace and before the final brace in single-line functions. - -```js -// Good -if (condition) doSomething() - -if (condition) { - doSomething() -} - -if (condition) { - doSomething() -} else { - doSomethingElse() -} - -if (condition) { - doSomething() - doSomethingElse() -} - -setTimeout(function () { doSomething() }, 0) - -setTimeout(function () { - doSomething() -}, 0) - -// Bad -if (condition) { doSomething(); doSomethingElse() } - -if (condition) - doSomething() - -setTimeout(function () { doSomething(); doSomethingElse() }, 0) - -setTimeout(function () {doSomething()}, 0) -``` - -### Function declarations - -Function declarations should always take more than one line. - -```js -// Good -function foo() { - return bar -} - -// Bad -function foo() { return bar } -``` - -### `if`-`else` - -Be consistent with braces in `if`-`else` statements. - -```js -// Good -if (condition) doSomething() -else doSomethingElse() - -if (condition) { - doSomething() -} else { - doSomethingElse() -} - -if (condition) { - doSomething() -} else if (anotherCondition) { - doSomethingElse() -} else { - doAnotherThing() -} - -// Okay -if (condition) doSomething() -else if (anotherCondition) doSomethingElse() -else doAnotherThing() - -// Bad -if (condition) doSomething() -else { - doSomethingElse() -} - -if (condition) doSomething() -else if (anotherCondition) { - doSomethingElse() -} else doAnotherThing() - - -if (condition) { - doSomething() -} else doSomethingElse() -``` - -## `do`-`while` loops - -Always use braces. - -```js -// Good -do { - doSomething() -} while (condition) - -// Bad -do doSomething() -while (condition) -``` - -### Empty blocks - -Mark empty blocks as intentionally empty via a comment or similar. Don't just leave an empty block and/or semicolon. - -```js -// Good -for (var i = 0; i < list.length && cond(list[i]); i++) { - // do nothing -} - -// Even better -for (var i = 0; i < list.length; i++) { - if (!cond(list[i])) break -} - -// Bad -for (var i = 0; i < list.length && cond(list[i]); i++) {} - -// Also bad -for (var i = 0; i < list.length && cond(list[i]); i++) { - ; -} - -// Even worse -for (var i = 0; i < list.length && cond(list[i]); i++); -``` - -### Nesting - -Don't nest `if` statements in `else` blocks if you don't need to. - -```js -// Good -if (test) { - doSomething() -} else if (otherTest) { - doSomethingElse() -} - -if (test) { - doSomething() -} else { - if (otherTest) { - doSomethingElse() - } - - doThisOtherThing() -} - -// Bad -if (test) { - doSomething() -} else { - if (otherTest) { - doSomethingElse() - } -} -``` - -Don't nest curly braces beyond 4 levels deep. This includes blocks, loops, and functions, as well as IIFE wrappers. This also includes the tests. There's rarely a reason to go farther, as most of the time, it's a signal to refactor and/or re-organize your code. - -### Brace style - -Put the `else`, `catch`, and `finally` on the same line as its closing brace. diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100755 index d858e4ed..00000000 --- a/Gruntfile.js +++ /dev/null @@ -1,387 +0,0 @@ -/* eslint-env node */ -module.exports = function (grunt) { // eslint-disable-line - - var pkg = grunt.file.readJSON("package.json") - var currentYear = grunt.template.today("yyyy") - var inputFolder = "./docs" - var tempFolder = "./temp" - var archiveFolder = "./archive" - var outputFolder = "../mithril" - - var guide = [ - "auto-redrawing", - "benchmarks", - "community", - "optimizing-performance", - "comparison", - "components", - "getting-started", - "installation", - "integration", - "practices", - "refactoring", - "routing", - "tools", - "web-services" - ] - - var api = [ - "change-log", - "roadmap", - "how-to-read-signatures", - "mithril", - "mithril.computation", - "mithril.deferred", - "mithril.mount", - "mithril.component", - "mithril.prop", - "mithril.redraw", - "mithril.render", - "mithril.deps", - "mithril.request", - "mithril.route", - "mithril.sync", - "mithril.trust", - "mithril.withAttr", - "mithril.xhr" - ] - - var md2htmlTasks = {} - function makeTasks(layout, pages) { - pages.forEach(function (name) { - var src = inputFolder + "/" + name + ".md" - var title = "" - - if (grunt.file.exists(src)) { - title = grunt.file.read(src).split(/\n/)[0].substring(3) + - " - " - } - - md2htmlTasks[name] = { - options: { - layout: inputFolder + "/layout/" + layout + ".html", - templateData: {topic: title} - }, - files: [{ - src: [src], - dest: tempFolder + "/" + name + ".html" - }] - } - }) - } - - makeTasks("guide", guide) - makeTasks("api", api) - - var currentVersionArchiveFolder = archiveFolder + "/v" + pkg.version - - grunt.initConfig({ - // Keep this in sync with the .eslintignore - eslint: { - options: { - extensions: [".js"], - fix: true - }, - all: [ - "**/*.js", - "!node_modules/**", - "!**/*.min.js", - "!archive/**", - "!deploy/**", - "!mithril.closure-compiler-externs.js", - "!docs/layout/lib/**" - ] - }, - - mocha_phantomjs: { // eslint-disable-line camelcase - test: { - src: ["test/index.html"], - options: { - reporter: "dot" - } - } - }, - - md2html: md2htmlTasks, - - uglify: { - options: { - banner: [ - "/*", - "Mithril v" + pkg.version, - pkg.homepage, - "(c) 2014-" + currentYear + " " + pkg.author.name, - "License: " + pkg.license, - "*/" - ].join("\n"), - sourceMap: true - }, - mithril: {src: "mithril.js", dest: "mithril.min.js"} - }, - - zip: { - distribution: { - cwd: currentVersionArchiveFolder + "/", - src: [ - currentVersionArchiveFolder + "/mithril.min.js", - currentVersionArchiveFolder + "/mithril.min.js.map", - currentVersionArchiveFolder + "/mithril.js" - ], - dest: currentVersionArchiveFolder + "/mithril.min.zip" - } - }, - - replace: { - options: { - force: true, - patterns: [ - {match: /\.md/g, replacement: ".html"}, - {match: /\$version/g, replacement: pkg.version} - ] - }, - - links: { - expand: true, - flatten: true, - src: [tempFolder + "/**/*.html"], - dest: currentVersionArchiveFolder + "/" - }, - - index: { - src: inputFolder + "/layout/index.html", - dest: currentVersionArchiveFolder + "/index.html" - }, - - commonjs: { - expand: true, - flatten: true, - src: [inputFolder + "/layout/*.json"], - dest: currentVersionArchiveFolder - }, - - cdnjs: { - src: "deploy/cdnjs-package.json", - dest: "../cdnjs/ajax/libs/mithril/package.json" - } - }, - - copy: { - style: { - src: inputFolder + "/layout/style.css", - dest: currentVersionArchiveFolder + "/style.css" - }, - - pages: { - src: inputFolder + "/layout/pages.json", - dest: currentVersionArchiveFolder + "/pages.json" - }, - - lib: { - expand: true, - cwd: inputFolder + "/layout/lib/", - src: "./**", - dest: currentVersionArchiveFolder + "/lib/" - }, - - tools: { - expand: true, - cwd: inputFolder + "/layout/tools/", - src: "./**", - dest: currentVersionArchiveFolder + "/tools/" - }, - - comparisons: { - expand: true, - cwd: inputFolder + "/layout/comparisons/", - src: "./**", - dest: currentVersionArchiveFolder + "/comparisons/" - }, - - unminified: { - src: "mithril.js", - dest: currentVersionArchiveFolder + "/mithril.js" - }, - - minified: { - src: "mithril.min.js", - dest: currentVersionArchiveFolder + "/mithril.min.js" - }, - - readme: { - src: "README.md", - dest: currentVersionArchiveFolder + "/README.md" - }, - - map: { - src: "mithril.min.js.map", - dest: currentVersionArchiveFolder + "/mithril.min.js.map" - }, - - typescript: { - src: "mithril.d.ts", - dest: currentVersionArchiveFolder + "/mithril.d.ts" - }, - - publish: { - expand: true, - cwd: currentVersionArchiveFolder, - src: "./**", - dest: outputFolder - }, - - archive: { - expand: true, - cwd: currentVersionArchiveFolder, - src: "./**", - dest: outputFolder + "/archive/v" + pkg.version - } - }, - - "saucelabs-browsers": { - firefox: { - filter: function (browsers) { - return browsers.filter(function (browser) { - if (browser.browserName !== "firefox") return false - var version = browser.version - return version === "dev" || version === "beta" || - +version >= 38 // The latest ESR version - }) - } - }, - - chrome: { - filter: function (browsers) { - return browsers.filter(function (browser) { - if (browser.browserName !== "chrome") return false - var version = browser.version - return version === "dev" || version === "beta" || - +version >= 41 - }) - } - }, - - ie: { - filter: function (browsers) { - return browsers.filter(function (browser) { - return browser.browserName === "internet explorer" && - !/2003/.test(browser.platform) - }) - } - }, - - edge: { - filter: function (browsers) { - return browsers.filter(function (browser) { - return browser.browserName === "microsoftedge" - }) - } - }, - - safari: { - filter: function (browsers) { - return browsers.filter(function (browser) { - return browser.browserName === "safari" - }) - } - }, - - opera: { - filter: function (browsers) { - return browsers.filter(function (browser) { - return browser.browserName === "opera" - }) - } - } - }, - - saucelabs: { - all: { - options: { - username: process.env.SAUCE_USERNAME, - key: process.env.SAUCE_ACCESS_KEY, - testname: "Mithril Tests " + new Date().toJSON(), - browsers: "<%= saucelabs.browsers %>", - urls: ["http://localhost:8000/test/index.html"], - sauceConfig: { - "record-video": false, - "record-screenshots": false - }, - build: process.env.TRAVIS_JOB_ID, - onTestComplete: function (result, callback) { - var user = process.env.SAUCE_USERNAME - var pass = process.env.SAUCE_ACCESS_KEY - - var url = [ - "https://saucelabs.com/rest/v1", user, "jobs", - result.job_id - ].join("/") - - require("request").put({ - url: url, - auth: {user: user, pass: pass}, - json: {passed: result.passed} - }, function (error, response) { - if (error) { - return callback(error) - } else if (response.statusCode !== 200) { - return callback(new Error( - "Unexpected response status: " + - response.statusCode + "\n ")) - } else { - return callback(null, result.passed) - } - }) - }, - tunnelTimeout: 5 - } - } - }, - - connect: { - server: { - options: { - port: 8888, - base: "." - } - } - }, - - clean: { - options: {force: true}, - generated: [tempFolder] - } - }) - - grunt.loadNpmTasks("grunt-saucelabs-browsers") - grunt.loadNpmTasks("grunt-contrib-clean") - grunt.loadNpmTasks("grunt-contrib-copy") - grunt.loadNpmTasks("grunt-contrib-uglify") - grunt.loadNpmTasks("grunt-md2html") - grunt.loadNpmTasks("grunt-replace") - grunt.loadNpmTasks("grunt-zip") - grunt.loadNpmTasks("grunt-contrib-connect") - grunt.loadNpmTasks("grunt-saucelabs") - grunt.loadNpmTasks("grunt-eslint") - grunt.loadNpmTasks("grunt-mocha-phantomjs") - - grunt.registerTask("build", [ - // "lint", - "test", - "uglify", - "zip", - "md2html", - "replace", - "copy", - "clean" - ]) - - grunt.registerTask("lint", ["eslint:all"]) - grunt.registerTask("test", ["mocha_phantomjs"]) - grunt.registerTask("default", ["build"]) - - grunt.registerTask("sauce", [ - "saucelabs-browsers:all", - "connect", - "saucelabs" - ]) -} diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 79b1146c..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -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. \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index c04e8a82..00000000 --- a/README.md +++ /dev/null @@ -1,80 +0,0 @@ -[![JS.ORG](https://img.shields.io/badge/js.org-mithril-ffb400.svg?style=flat-square)](http://js.org) -[![Join the chat at https://gitter.im/lhorie/mithril.js](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/lhorie/mithril.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Build Status](https://travis-ci.org/lhorie/mithril.js.svg?branch=master)](https://travis-ci.org/lhorie/mithril.js) - -# Mithril - -A Javascript Framework for Building Brilliant Applications - -See the [website](http://mithril.js.org) for documentation - -There's also a [blog](http://lhorie.github.io/mithril-blog) and a [mailing list](https://groups.google.com/forum/#!forum/mithriljs) - ---- - -## What is Mithril? - -Mithril is a client-side MVC framework - a tool to organize code in a way that is easy to think about and to maintain. - -### Light-weight - -- Only 7.8 kB gzipped, no dependencies -- Small API, small learning curve - -### Robust - -- Safe-by-default templates -- Hierarchical MVC via components - -### Fast - -- Virtual DOM diffing and compilable templates -- Intelligent auto-redrawing system - ---- - -## Sample code - -```javascript -//namespace -var app = {}; - -//model -app.PageList = function() { - return m.request({method: "GET", url: "pages.json"}); -}; - -//controller -app.controller = function() { - var pages = app.PageList(); - return { - pages: pages, - rotate: function() { - pages().push(pages().shift()); - } - } -}; - -//view -app.view = function(ctrl) { - return [ - ctrl.pages().map(function(page) { - return m("a", {href: page.url}, page.title); - }), - m("button", {onclick: ctrl.rotate}, "Rotate links") - ]; -}; - - -//initialize -m.mount(document.getElementById("example"), app); -``` - ---- - -### Learn more - -- [Installation](http://mithril.js.org/installation.html) -- [Tutorial](http://mithril.js.org/getting-started.html) -- [Differences from Other Frameworks](http://mithril.js.org/comparison.html) -- [Benchmarks](http://mithril.js.org/benchmarks.html) diff --git a/archive/v0.2.1/README.md b/archive/v0.2.1/README.md deleted file mode 100644 index 6ed92eea..00000000 --- a/archive/v0.2.1/README.md +++ /dev/null @@ -1,80 +0,0 @@ -[![JS.ORG](https://img.shields.io/badge/js.org-mithril-ffb400.svg?style=flat-square)](http://js.org) -[![Join the chat at https://gitter.im/lhorie/mithril.js](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/lhorie/mithril.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Build Status](https://travis-ci.org/lhorie/mithril.js.svg?branch=master)](https://travis-ci.org/lhorie/mithril.js) - -# Mithril - -A Javascript Framework for Building Brilliant Applications - -See the [website](http://mithril.js.org) for documentation - -There's also a [blog](http://lhorie.github.io/mithril-blog) and a [mailing list](https://groups.google.com/forum/#!forum/mithriljs) - ---- - -## What is Mithril? - -Mithril is a client-side MVC framework - a tool to organize code in a way that is easy to think about and to maintain. - -### Light-weight - -- Only 7kb gzipped, no dependencies -- Small API, small learning curve - -### Robust - -- Safe-by-default templates -- Hierarchical MVC via components - -### Fast - -- Virtual DOM diffing and compilable templates -- Intelligent auto-redrawing system - ---- - -## Sample code - -```javascript -//namespace -var app = {}; - -//model -app.PageList = function() { - return m.request({method: "GET", url: "pages.json"}); -}; - -//controller -app.controller = function() { - var pages = app.PageList(); - return { - pages: pages, - rotate: function() { - pages().push(pages().shift()); - } - } -}; - -//view -app.view = function(ctrl) { - return [ - ctrl.pages().map(function(page) { - return m("a", {href: page.url}, page.title); - }), - m("button", {onclick: ctrl.rotate}, "Rotate links") - ]; -}; - - -//initialize -m.mount(document.getElementById("example"), app); -``` - ---- - -### Learn more - -- [Installation](http://mithril.js.org/installation.html) -- [Tutorial](http://mithril.js.org/getting-started.html) -- [Differences from Other Frameworks](http://mithril.js.org/comparison.html) -- [Benchmarks](http://mithril.js.org/benchmarks.html) diff --git a/archive/v0.2.1/auto-redrawing.html b/archive/v0.2.1/auto-redrawing.html deleted file mode 100644 index 42cd3aab..00000000 --- a/archive/v0.2.1/auto-redrawing.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - The Auto-Redrawing System - Mithril - - - - - -
- -
-
-
-
-
- -
-

The Auto-Redrawing System

-

Mithril is designed around the principle that data always flows from the model to the view. This makes it easy to reason about the state of the UI and to test it. In order to implement this principle, the rendering engine must run a redraw algorithm globally to ensure no parts of the UI are out of sync with the data. While at first glance, it may seem expensive to run a global redraw every time data changes, Mithril makes it possible to do this efficiently thanks to its fast diffing algorithm, which only updates the DOM where it needs to be updated. Because the DOM is by far the largest bottleneck in rendering engines, Mithril's approach of running a diff against a virtual representation of the DOM and only batching changes to the real DOM as needed is surprisingly performant.

-

In addition, Mithril attempts to intelligently redraw only when it is appropriate in an application lifecycle. Most frameworks redraw aggressively and err on the side of redrawing too many times because, as it turns out, determining the best time to do a redraw is quite complicated if we want to be as efficient as possible.

-

Mithril employs a variety of mechanisms to decide the best time and the best strategy to redraw. By default, Mithril is configured to auto-redraw from scratch after component controllers are initialized, and it is configured to diff after event handlers are triggered. In addition, it's possible for non-Mithril asynchronous callbacks to trigger auto-redrawing by calling m.startComputation and m.endComputation in appropriate places (see below). Any code that is between a m.startComputation and its respective m.endComputation call is said to live in the context of its respective pair of function calls.

-

It's possible to defer a redraw by calling m.request or by manually nesting m.startComputation and m.endComputation contexts. The way the redrawing engine defers redrawing is by keeping an internal counter that is incremented by m.startComputation and decremented by m.endComputation. Once that counter reaches zero, Mithril redraws. By strategically placing calls to this pair of functions, it is possible to stack asynchronous data services in any number of ways within a context without the need to pass state variables around the entire application. The end result is that you can call m.request and other integrated data services seamlessly, and Mithril will wait for all of the asynchronous operations to complete before attempting to redraw.

-

In addition to being aware of data availability when deciding to redraw, Mithril is also aware of browser availability: if several redraws are triggered in a short amount of time, Mithril batches them so that at most only one redraw happens within a single animation frame (around 16ms). Since computer screens are not able to display changes faster than a frame, this optimization saves CPU cycles and helps UIs stay responsive even in the face of spammy data changes.

-

Mithril also provides several hooks to control its redrawing behavior with a deep level of granularity: m.startComputation and m.endComputation create redrawable contexts. m.redraw forces a redraw to happen in the next available frame (or optionally, it can redraw immediately for synchronous processing). The config's retain flag can be used to change how specific elements are redrawn when routes change. m.redraw.strategy can change the way Mithril runs the next scheduled redraw. Finally, the low-level m.render can also be used if a developer chooses to opt out of rest of the framework altogether.

-
-

Integrating with The Auto-Redrawing System

-

If you need to do custom asynchronous calls without using Mithril's API, and find that your views are not redrawing automatically, you should consider using m.startComputation / m.endComputation so that Mithril can intelligently auto-redraw once your custom code finishes running.

-

In order to integrate asynchronous code to Mithril's autoredrawing system, you should call m.startComputation BEFORE making an asynchronous call, and m.endComputation at the end of the asynchronous callback.

-
//this service waits 1 second, logs "hello" and then notifies the view that
-//it may start redrawing (if no other asynchronous operations are pending)
-var doStuff = function() {
-    m.startComputation(); //call `startComputation` before the asynchronous `setTimeout`
-
-    setTimeout(function() {
-        console.log("hello");
-
-        m.endComputation(); //call `endComputation` at the end of the callback
-    }, 1000);
-};
-
-

To integrate synchronous code, call m.startComputation at the beginning of the method, and m.endComputation at the end.

-
window.onfocus = function() {
-    m.startComputation(); //call before everything else in the event handler
-
-    doStuff();
-
-    m.endComputation(); //call after everything else in the event handler
-}
-
-

For each m.startComputation call a library makes, it MUST also make one and ONLY one corresponding m.endComputation call.

-

If you want to a recurring callback (such as setInterval or a web socket event handler) to trigger redraws, you should call m.startComputation at the beginning of the function, not outside of it.

-
setInterval(function() {
-    m.startComputation(); //call before everything else in the event handler
-
-    doStuff();
-
-    m.endComputation(); //call after everything else in the event handler
-})
-

-

Integrating multiple execution threads

-

When integrating with third party libraries, you might find that you need to call asynchronous methods from outside of Mithril's API.

-

In order to integrate non-trivial asynchronous code with Mithril's auto-redrawing system, you need to ensure all execution threads call m.startComputation / m.endComputation.

-

An execution thread is basically any amount of code that runs before other asynchronous threads start to run.

-

Integrating multiple execution threads can be done in two different ways: in a layered fashion or in comprehensive fashion.

-

Layered integration

-

Layered integration is recommended for modular code where many different APIs may be put together at the application level.

-

Below is an example where various methods implemented with a third party library can be integrated in layered fashion: any of the methods can be used in isolation or in combination.

-

Notice how doBoth repeatedly calls m.startComputation since that method calls both doSomething and doAnother. This is perfectly valid: there are three asynchronous computations pending after the jQuery.when method is called, and therefore, three pairs of m.startComputation / m.endComputation in play.

-
var doSomething = function(callback) {
-    m.startComputation(); //call `startComputation` before the asynchronous AJAX request
-
-    return jQuery.ajax("/something").done(function() {
-        if (callback) callback();
-
-        m.endComputation(); //call `endComputation` at the end of the callback
-    });
-};
-var doAnother = function(callback) {
-    m.startComputation(); //call `startComputation` before the asynchronous AJAX request
-
-    return jQuery.ajax("/another").done(function() {
-        if (callback) callback();
-        m.endComputation(); //call `endComputation` at the end of the callback
-    });
-};
-var doBoth = function(callback) {
-    m.startComputation(); //call `startComputation` before the asynchronous synchronization method
-
-    jQuery.when(doSomething(), doAnother()).then(function() {
-        if (callback) callback();
-
-        m.endComputation(); //call `endComputation` at the end of the callback
-    })
-};
-
-

Comprehensive integration

-

Comprehensive integration is recommended if integrating a monolithic series of asynchronous operations. In contrast to layered integration, it minimizes the number of m.startComputation / m.endComputation calls to avoid clutter.

-

The example below shows a convoluted series of AJAX requests implemented with a third party library.

-
var doSomething = function(callback) {
-    m.startComputation(); //call `startComputation` before everything else
-
-    jQuery.ajax("/something").done(function() {
-        doStuff();
-        jQuery.ajax("/another").done(function() {
-            doMoreStuff();
-            jQuery.ajax("/more").done(function() {
-                if (callback) callback();
-
-                m.endComputation(); //call `endComputation` at the end of everything
-            });
-        });
-    });
-};
-
- -
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/benchmarks.html b/archive/v0.2.1/benchmarks.html deleted file mode 100644 index 729dcfc6..00000000 --- a/archive/v0.2.1/benchmarks.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - Benchmarks - Mithril - - - - - -
- -
-
-
-
-
- -
-

Benchmarks

-

These benchmarks were designed to measure Javascript running time for Mithril in comparison with other popular Javascript MVC frameworks. Javascript running time is significant because the gzipped size of a framework can be misleading in terms of how much code actually runs on page loads. In my experience, page loads happen far more commonly than one would expect in single page applications: power users open multiple tabs, and mobile users open and close the browser very frequently. And as far as templating engines go, the initial page load represents the worst case for the rendering algorithm since there is very little room for performance optimization tricks. It's arguably also one of the most important metrics when it comes to performance.

-

The numbers shown here are best-run results for all frameworks, except for Mithril's case, for which I'm taking the worst-run result. The numbers aren't statistically rigorous (e.g. I didn't bother to calculate standard deviation), but they should be enough to give a rough idea of what is faster than what.

-

Generally speaking, these tests are making a deliberate effort to be biased in favor of other frameworks: for example, I don't load "optional-but-usually-used-in-real-life" things like the router module for Angular, or Marionette in Backbone's case, and I load the entirety of Mithril. In addition, this test deliberately avoids triggering requestAnimationFrame-based performance optimizations for Mithril, since this optimization does not exist in many frameworks and severely skews numbers in Mithril's favor in CPU-intensive situations like parallax sites. I'm also NOT using the Mithril template compiler, which would also skew the benchmark in Mithril's favor.

-

To run the execution time tests below, click on their respective links, run the profiler from your desired browser's developer tools and measure the running time of a page refresh (lower is better).

-
-
-
-

Loading

- - - - - - -
Mithril 0.28ms
jQuery 13.11ms
Backbone 18.54ms
Angular 7.49ms
React 24.99ms
-
-
-

Rendering

- - - - - - -
Mithril 9.44ms (uncompiled)
jQuery 40.27ms
Backbone 23.05ms
Angular 118.63ms
React 79.65ms
-
-
-
- -

Feel free to implement versions of the tests above in other frameworks, if you wish. The code is very simple.

-
-

TodoMVC Benchmark

-

There's a TodoMVC benchmark with a variety of popular and obscure frameworks here:

-

http://matt-esch.github.io/mercury-perf/

-

The benchmark consists of creating 100 todos, marking them as completed, and then deleting them. It aims to give an idea of how frameworks perform under real-world-ish conditions running idiomatic code (as opposed to micro-benchmarks, which tend to take advantage of obscure tricks and aggressive optimizations that sacrifice maintainability for extra speed).

- -
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/bower.json b/archive/v0.2.1/bower.json deleted file mode 100644 index b9a52c4e..00000000 --- a/archive/v0.2.1/bower.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "mithril", - "version": "0.2.1", - "main": "mithril.min.js", - "description": "A Javascript Framework for building brilliant applications", - "authors": ["Leo Horie "], - "license": "MIT", - "ignore": [ - "*.html", - "*.css", - "*.zip", - "*.json", - "mithril-tests.js", - "archive", - "comparisons", - "lib", - "tools" - ], - "keywords": ["mithril", "mvc", "framework"] -} \ No newline at end of file diff --git a/archive/v0.2.1/change-log.html b/archive/v0.2.1/change-log.html deleted file mode 100644 index 13ebfa78..00000000 --- a/archive/v0.2.1/change-log.html +++ /dev/null @@ -1,548 +0,0 @@ - - - - Change Log - Mithril - - - - - -
- -
-
-
-
-
- -
-

Change Log

-

v0.2.1

-

News:

-
    -
  • added catch to promises
  • -
  • improvements and fixes in the documentation and wiki
  • -
  • large refactor to take better advantage of Chrome js optimizations and improve source code readability (thanks to @impinball)
  • -
  • m(component, ...args) can now be used as a shorthand for m.component(component, ...args)
  • -
-

Bug Fixes:

-
    -
  • errors thrown from the exception monitor no longer freeze redrawing
  • -
  • fix edge case with falsy keys
  • -
  • fix controller prototype inheritance in component controllers
  • -
  • fix return value of parseQueryString if input is empty string
  • -
-
-

v0.2.0 - improved components

-

News:

-
    -
  • Mithril modules will be referred to as components from now on.
  • -
  • Virtual DOM tree can now contain components
  • -
  • Components can now be parameterized via m.component
  • -
-

Deprecations:

-
    -
  • m.module has been renamed m.mount. Calling m.module will still work, but should be considered deprecated. Rationale: Mithril modules and components are the same thing, therefore from now on, they will be referred to as components, since that name is more descriptive of their purpose, and causes less confusion in the face of ES6 modules.

    -

    In order to migrate, search for m.module calls and replace them with m.mount. The method signature is the same.

    -
  • -
-

Bug Fixes:

-
    -
  • fix diff edge case in <select> #569
  • -
  • fix support for arrays in template compiler
  • -
-
-

v0.1.34 - maintenance

-

Bug Fixes:

-
    -
  • fix identity bug when mixing unkeyable elements in a tree #524
  • -
-
-

v0.1.33 - maintenance

-

Bug Fixes:

-
    -
  • fix diff bug when mixing undefined in a tree #524
  • -
  • fix reference to map file in package.json for cdnjs
  • -
  • fix links in documentation
  • -
-
-

v0.1.32 - maintenance

-

Bug Fixes:

-
    -
  • fix regression caused by #454
  • -
-
-

v0.1.31 - maintenance

-

News:

-
    -
  • Typescript definitions are more strongly typed
  • -
  • m.request's unwrapSuccess and unwrapError callbacks now receive the XMLHttpRequest instance as a second parameter
  • -
  • 3rd parameter for m.route(route, params, shouldReplaceHistory) is now public
  • -
  • exact routes now have higher precedence than routes w/ variables #452
  • -
  • there's now a retain flag to control on-route-change diff strategy on a per-element basis
  • -
-

Bug Fixes:

-
    -
  • fix routing bug in IE9 #320
  • -
  • fix ordering bug in m.trust when using HTML entities #453
  • -
  • set promise's default value to initialValue if coming from m.request #454
  • -
  • fix dom element ownership bug when mixing keyed elements and third party plugin elements #463
  • -
  • fix edge case in flatten algorithm #448
  • -
  • prevent unnecessary DOM move operation when mixing keyed and unkeyed elements #398
  • -
  • revert #382 due to diff regression #512
  • -
-
-

v0.1.30 - maintenance

-

Bug Fixes:

-
    -
  • fix history.back() regression #435
  • -
  • fix module.view's this association regression in Haxe environment #434
  • -
  • fix array serialization syntax in querystrings #440
  • -
-
-

v0.1.29 - maintenance

-

News:

-
    -
  • Calling m.module without a module now unloads the current one #420
  • -
  • Both controller and view properties in modules are now optional
  • -
-

Bug Fixes:

-
    -
  • prevent empty class attributes #382
  • -
  • array-to-querystring serialization in m.request now behaves like jQuery #426
  • -
  • fix querystring detection bug in pathname mode #425
  • -
  • don't add history entry if reloading from a link #428
  • -
  • fix key association when DOM order is modified by external code #424
  • -
-
-

v0.1.28 - maintenance

-

News:

-
    -
  • Landed some performance improvements
  • -
-

Bug Fixes:

-
    -
  • throw error if root element is null in m.module/m.route #388
  • -
-
-

v0.1.27 - maintenance

-

Bug Fixes:

-
    -
  • prevent strategy("none") event contamination #378
  • -
  • fix equality strictness #379
  • -
  • fix keys bug when list has nulls #299
  • -
  • make sure empty value in option tag creates attribute #380
  • -
-
-

v0.1.26 - maintenance

-

Bug Fixes:

-
    -
  • make sure input[type] is CSS-targetable #364
  • -
  • throw error if m.route.param is called before initializing routes #361
  • -
-
-

v0.1.25 - maintenance

-

Bug Fixes:

-
    -
  • fixed input cursor jumping regression
  • -
  • fixed interop bug when QUnit and AMD are used at the same time #355
  • -
  • fixed route arg duplication in edge case #352
  • -
  • prevented meaningless error in Chrome edge case #358
  • -
-
-

v0.1.24 - maintenance

-

Bug Fixes:

-
    -
  • Prevent rogue is attribute from being created in Chrome
  • -
  • Fix data regression in m.request
  • -
-
-

v0.1.23 - maintenance

-

News:

-
    -
  • There's now support for extended custom elements (e.g. m("button[is=my-button]"))
  • -
  • m.request now supports a initialValue option to help prevent type errors in views when using the background option
  • -
-

Bug Fixes:

-
    -
  • docs now have anchor links for easier navigation
  • -
  • fixed a bunch of IE8 issues #298
  • -
  • fixed handling of method option in JSONP mode #292
  • -
  • fixed source map files
  • -
  • fixed handling of select[multiple]
  • -
  • fixed template compiler edge case #286
  • -
  • fixed pathname bug in m.route #290
  • -
  • fixed pathname querystring bug in routed links #304
  • -
  • fixed handling of value in inputs when model value is not in sync with input value #336
  • -
-
-

v0.1.22 - maintenance

-

News:

-
    -
  • docs now have anchor links for easier navigation
  • -
  • there is more documentation for things that weren't that clear
  • -
  • json-p support added
  • -
  • m() now supports splat for children (e.g. m("div", m("a"), m("b"), m("i")) for nicer Coffeescript syntax
  • -
  • by popular demand, m.module now returns a controller instance
  • -
-

Bug Fixes:

-
    -
  • gracefully degrade on IE exceptions when setting invalid values
  • -
  • fixes for Typescript definition file
  • -
  • fixed bug in keys algorithm when mixing keyed and unkeyed elements #246
  • -
  • added promise exception monitor and reverted promise exception handling semantics to v0.1.19 semantics (see docs)
  • -
  • fixed redraw scheduling bug in old version of IE
  • -
  • fixed incorrect diff when document is root, and html element is omitted
  • -
  • fixed querystring clobbering in links w/ config:m.route #261
  • -
  • fixed rare bug that made events get dropped #214
  • -
  • don't send Content-Type header if there's no request data #280
  • -
-
-

v0.1.21 - maintenance

-

News:

-
    -
  • passing a promise to an m.prop now populates it with the resolved value upon resolution, and returns undefined otherwise
  • -
  • m.redraw can now be forced to called synchronously
  • -
-

Bug Fixes:

-
    -
  • fixed handling of + character in m.route.param #204
  • -
  • fixed corner case for undefined children in diff #206
  • -
  • fixed context.onunload for array items #200
  • -
  • fixed handling on comments in HTML converter tool
  • -
-
-

v0.1.20 - maintenance

-

News:

-
    -
  • redraw strategy can now be modified via m.redraw.strategy
  • -
  • math tags now automatically get created with the MathML namespace
  • -
-

Bug Fixes:

-
    -
  • fixed IE8 null reference exception in m
  • -
  • fixed IE8 empty-text-node-in-input issue #195
  • -
  • fixed m.sync resolution when passed an empty array #191
  • -
-
-

v0.1.19 - maintenance

-

Bug Fixes:

-
    -
  • fixed double redraw when events fire simultaneously #151
  • -
  • fixed node insertion bug when using document as root #153
  • -
  • prevent routes from reverting to original route in some cases
  • -
  • fixed nested array ordering #156
  • -
  • fixed key ordering in interpolation case #157
  • -
-
-

v0.1.18 - maintenance

-

Bug Fixes:

-
    -
  • routing now correctly clears diff cache #148
  • -
  • fixed incorrect context unloading when reattaching a child to a new parent
  • -
-
-

v0.1.17 - maintenance

-

News:

-
    -
  • config contexts can now have an onunload property for clean up tasks after elements are detached from the document
  • -
  • route changes now re-render from scratch, rather than attempting a virtual dom diff
  • -
  • virtual elements that are children of an array can now accept a key attribute which maintains the identity of the underlying DOM elements when the array gets shuffled #98
  • -
-

Bug Fixes:

-
    -
  • fixed a subtree directive bug that happened in inputs inside loops
  • -
  • fixed select.value so that the correct option is displayed on first render
  • -
  • in m.request, non-idempotent methods now automatically send appropriate Content-Type header if serialize is JSON.stringify #139
  • -
  • m selectors now correctly handle empty attribute values like [href='']
  • -
  • pre-existing nodes in a root element now get cleared if there's no cell cache associated with the element #60
  • -
-
-

v0.1.16 - maintenance

-

News:

-
    -
  • controller::onunload now receives an event parameter so that the unloading can be aborted #135
  • -
-

Bug Fixes:

-
    -
  • prevent route change when only hash changes in non-hash mode #107
  • -
  • config now always runs after template is attached to document #109
  • -
  • fix null reference exception with Browserify #110
  • -
  • fix nested array removal edge cases #120
  • -
  • ignore redraw calls when controller is not ready #127
  • -
  • fix null reference exception in nested array edge case #129
  • -
  • fix a contenteditable null reference error #134
  • -
  • fix textarea value diffing when value is a node inside an array #136
  • -
  • fix diff bug with trusted strings #138
  • -
-

Breaking changes:

-
    -
  • Due to the poor level of compatibility between XDomainRequest and XHR2, XDomainRequest is no longer called internally by Mithril. If you need to use CORS in IE9 or lower, you will need to return an XDomainRequest instance from m.request's config method #121
  • -
-
-

v0.1.15 - maintenance

-

Bug Fixes:

-
    -
  • m.sync now correctly passes arguments to resolver in same order as input arguments #96
  • -
  • fixed diff deletion bug #99
  • -
  • updating textarea attributes updates its value correctly #100
  • -
-
-

v0.1.14 - maintenance

-

News:

-
    -
  • The signature of m now accepts virtual elements as the second parameter of the function.
  • -
  • m.route(path, params) now accepts an argument that gets parsed as a querystring.
  • -
  • routes now ignore trailing slashes #88
  • -
-

Bug Fixes:

-
    -
  • Resolving promises early without a value now works #85
  • -
  • Throwing exceptions within m.request now follow the same resolution procedure as m.deferred #86
  • -
  • Promises now always update their m.prop on success (and leave the m.prop alone on error)
  • -
  • Nested arrays no longer cause double removal of elements #87
  • -
  • HTTP error codes now correctly reject promises
  • -
-
-

v0.1.13 - maintenance

-

News:

-
    -
  • m.module now runs clean-up code in root module controllers that implement an onunload instance method #82
  • -
-

Bug Fixes:

-
    -
  • Removing CSS rules now diffs correctly #79
  • -
-
-

v0.1.12 - maintenance

-

News:

- -

Bug Fixes:

-
    -
  • Fix link location in links using config: m.route after redraws #74
  • -
  • Fixed support for list attribute in inputs #69
  • -
  • Fixed URL decoding in route params #75
  • -
-
-

v0.1.11 - maintenance

-

News:

-
    -
  • Added m.route() overload to allow reading of current route #61
  • -
  • Added background option to m.request to allow requests that don't affect rendering #62
  • -
-

Bug Fixes:

-
    -
  • Links using config: m.route can now be opened in new tab correctly #64
  • -
  • Fixed diff within contenteditable areas #65
  • -
-
-

v0.1.10 - maintenance

-

News:

-
    -
  • Added social buttons to homepage
  • -
-

Bug Fixes:

-
    -
  • Bi-directional bindings no longer wipe out cursor position in Chrome #58
  • -
-
-

v0.1.9 - maintenance

-

News:

-
    -
  • Added comparison with React to homepage
  • -
  • Added support for multi-island apps #34
  • -
  • m.prop is now JSON-serializable #54
  • -
  • Added extract option to m.request to allow access to response metadata #53
  • -
-

Bug Fixes:

-
    -
  • Fixed node index displacement by null/undefined nodes #56
  • -
  • Fixed mock's insertBefore and appendChild when dealing w/ reattachments
  • -
-

Breaking changes:

-
    -
  • changing an id in a virtual element now recreates the element, instead of recycling it #55
  • -
-
-

v0.1.8 - maintenance

-

News:

-
    -
  • Mock now contains a basic insertAdjacentHTML implementation to enable better testing of m.trust / m.render interactions
  • -
-

Bug Fixes:

-
    -
  • Fixed ordering bug in deep subchildren #51
  • -
  • Fixed ordering bug with trusted strings #51
  • -
-
-

v0.1.7 - maintenance

-

News:

-
    -
  • Mithril will be on a accelerated release cycle for the rest of the v0.1.x series. This means CDNs may lag behind in versions, so it's recommended that you either use one of the supported NodeJS package managers or fork from the Github repo directly. More information can be found here
  • -
-

Bug Fixes:

-
    -
  • Fixed ordering bug when virtual element is preceded by array #50
  • -
-
-

v0.1.6 - maintenance

-

Bug Fixes:

-
    -
  • Fixed serious bug when mixing cached text nodes with new virtual elements #49
  • -
-
-

v0.1.5 - maintenance

-

News:

- -

Bug Fixes:

-
    -
  • Fixed serious ordering problem when mixing arrays with virtual elements #48
  • -
-
-

v0.1.4 - maintenance

-

News:

-
    -
  • added regression tests for reported bugs
  • -
  • added support for SVG
  • -
-

Bug Fixes:

-
    -
  • URLs with port numbers are now handled correctly #40
  • -
  • NPM package now contains unminified version for map files #39
  • -
  • fixed ordering issue when mixing newly created virtual elements with elements from cache #44
  • -
  • fixed caching bug in links w/ config option attached #43
  • -
  • fixed attribute update bug when an element has both oninput and onkeydown handlers #36
  • -
-
-

v0.1.3 - maintenance

-

News:

-
    -
  • Mithril is now available via Component
  • -
  • There's now an extra low-level optimization hook called a SubtreeDirective, which allows implementing plugins that only create virtual trees if necessary.
  • -
-

Bug Fixes:

-
    -
  • diff no longer touch the DOM when processing style attributes and event handlers
  • -
  • returning a thennable to a resolution callback in m.deferred().promise now causes the promise to adopt its state
  • -
  • diff now correctly clears subtree if null or undefined is passed as a node
  • -
-
-

v0.1.2 - maintenance

-

News:

- -

Bug Fixes:

-
    -
  • m.render now correctly reattaches reused DOM elements to replaced parent nodes #31
  • -
  • UI actions that can potentially de-synchronize the DOM from cache now force synchronization #29
  • -
-
-

v0.1.1 - maintenance

-

News:

- -

Bug Fixes:

-
    -
  • m.route.param now resets on route change correctly #15
  • -
  • m.render now correctly ignores undefined values in the virtual tree#16
  • -
  • errors thrown in promises now cause downstreams to be rejected #1
  • -
-

Breaking changes:

-
    -
  • changed default value for xhr.withCredentials from true to false for m.request, since public APIs are more common than auth-walled ones. #14

    -

    In order to configure this flag, the following configuration should be used:

    -
    var privateAPI = function(xhr) {xhr.withCredentials = true};
    -
    -m.request({method: "GET", url: "http://foo.com/api", config: privateAPI});
    -
    -
  • -
-
-

v0.1 - Initial release

- -
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/community.html b/archive/v0.2.1/community.html deleted file mode 100644 index da0ff02e..00000000 --- a/archive/v0.2.1/community.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - Community - Mithril - - - - - -
- -
-
-
-
-
- -
-

Community

-

Learn Mithril

-

Read Mithril tutorials and articles about web app development.

-

Go to the Learn Mithril site

-
-

Gitter

-

There's a Gitter chat room. This is a great place to ask questions, discuss ideas and connect with Mithril users.

-

You can sign in with your Github credentials.

-
-

Mailing List

-

Looking for a place to talk about Mithril? Suggestions?

-

Feel free to post on the mailing list

-
-

StackOverflow

-

Looking for help on StackOverflow? Tag your questions with mithril.js.

-

Want to help fellow Mithril developers and gain karma while at it? Keep an eye on the tagged questions

-
-

IRC

-

Join the #mithriljs IRC channel on Freenode.

-
-

Github Wiki

-

A collection of community projects, tutorials, starter kits and snippets created by Mithril users. A great place to find useful tools.

-

Go to the Mithril wiki

-
-

Bug Tracker

-

You can file bugs in the issues page on Github

- -
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/comparison.html b/archive/v0.2.1/comparison.html deleted file mode 100644 index 585abd68..00000000 --- a/archive/v0.2.1/comparison.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - How is Mithril Different from Other Frameworks - Mithril - - - - - -
- -
-
-
-
-
- -
-

How is Mithril Different from Other Frameworks

-

There are a lot of different Javascript frameworks and evaluating their merits and shortcomings can be a daunting task.

-

This page aims to provide a comparison between Mithril and some of the most widely used frameworks, as well as some of the younger, but relevant ones.

-

Code Size

-

One of the most obvious differences between Mithril and most frameworks is in file size: Mithril is around 7kb gzipped and has no dependencies on other libraries.

-

Note that while a small gzipped size can look appealing, that number is often used to "hide the weight" of the uncompressed code: remember that the decompressed Javascript still needs to be parsed and evaluated on every page load, and this cost (which can be in the dozens of milliseconds range for some frameworks in some browsers) cannot be cached.

-

This cost might be less of a concern in single page apps, but not necessarily if the app is typically opened simultaneously in multiple tabs, or run on less powerful devices.

-

The performance tests in the homepage show execution times for parsing and evaluation of Mithril's code, compared to some popular frameworks. As you can see, it paints a much less flattering picture for some frameworks than when we look at gzipped size alone.

-

Documentation

-

Another point of comparison is documentation. Most of the popular frameworks have at least a bare minimum amount of documentation nowadays, but many leave a bit to be desired: some lack usage examples, and some frameworks' communities need to rely heavily on third party sites for explanations of more advanced topics, and sometimes even for learning the basics.

-

This is a problem particularly for frameworks that had breaking changes in the past: It's common to find answers in StackOverflow that are out-of-date and no longer work with the latest version of said frameworks.

-

Mithril has more documentation in its Github repo than source code, and none of the documentation is auto-generated.

-

All API points are explained in prose, and have code examples. Because the entire documentation is hand-crafted, you get the benefit of actually having explanations for things that documentation-generator tools don't support well (for example, interfaces and callback parameter documentation).

-

In addition, this guide section covers topics related to how to fit all the pieces together.

-

From the get-go, Mithril's build system produces archived versions of the code and documentation so that you'll never be stuck without docs for out-of-date versions.

-

Given how young Mithril is, hopefully you can appreciate the level of commitment for providing good documentation.

-

Architecture

-

In terms of architecture, one of Mithril's main differences is that it does not provide base classes to extend from.

-

It's often said that frameworks, in contrast to libraries, dictate how code should be written. In this sense, one could argue that Mithril isn't really a framework.

-

Instead of locking developers down to very specific implementations of design patterns, Mithril's approach is to provide an idiomatic pattern to follow, and tools to aid the developer when required. This approach means that developers can get discoverable codebases without necessarily getting locked into the framework.

-

One related difference is that other frameworks often have hard-coded base classes where every conceivable convenience method gets inherited by the developer's classes (remember, in Javascript, this can mean copying all of the utility methods over to the child class, regardless of whether they're going to be used or not).

-

Mithril's on-demand tooling approach means there are no hidden performance costs when implementing core MVC patterns, and there's also no extra learning curve for framework-specific syntax for those patterns.

-

View Layer Paradigm

-

Some of the older frameworks among the popular ones (out-of-the-box jQuery and Backbone, specifically) take a more procedural paradigm when it comes to the view layer; this means every action requires the developer to write custom view-level code to handle it.

-

This can get noticeably bulky when you look at thing like collections: you often need to implement insertion code and deletion code, in addition to a "draw everything" routine for performance. And this is for every list that needs to be displayed in some way.

-

Mithril's view layer paradigm is designed to be declarative, much like HTML, such that the same code implicitly does everything it needs to. As it turns out, this design decision is actually a compromise: it offers the benefit of decreased application code complexity at the cost of some performance loss. However, as the performance tests in the homepage show, this does not necessarily hurt Mithril in a meaningful way.

-
-

Specific Framework Comparisons

-

Warning: this section is likely biased. Take it with a grain of salt.

-

jQuery

-

jQuery is ubiquitous and has a large ecosystem, but it's not an MVC framework.

-

There's no idiomatic way to organize jQuery code in an MVC pattern and many frameworks were created specifically to overcome that shortcoming.

-

As summarized above, Mithril differs from jQuery by allowing DOM-related code to be written largely in a declarative style (thereby decreasing code complexity), in addition to providing an idiomatic way to structure applications.

-

One other difference that is extremely clear is the treatment of data. In jQuery it's common to use the DOM as a data storage mechanism, whereas Mithril encourages data to exist in an isolated model layer.

-

Backbone

-

Backbone was originally designed as a way to structure jQuery-based applications. One of its selling points is that it allows developers to leverage their existing jQuery knowledge, while providing some "walls" to organize the code in a more structured manner.

-

As with jQuery, Mithril differs from Backbone by enforcing view code to be written in a declarative style.

-

Another marking difference is that Backbone is workflow agnostic, providing no idiomatic way to organize applications. This is good for framework adoption, but not necessarily ideal for team scalability and codebase discoverability.

-

In contrast, Mithril encourages you to develop applications using the patterns found throughout this guide, and discourages the use of "bastardized" MVC pattern variations.

-

One technical aspect that is also different is that Backbone is heavily event-oriented. Mithril, on the other hand, purposely avoids the observer pattern in an attempt to abolish "come-from hell", a class of debugging problems where you don't know what triggers some code because of a long chain of events triggering other events.

-

A particularly nasty instance of this problem that sometimes occurs in "real-time" applications is when event triggering chains become circular due to a conditional statement bug, causing infinite loops and browser crashes.

-

Another significant difference between Backbone and Mithril is in their approach to familiarity: Backbone appeals to people familiar w/ jQuery; Mithril is designed to be familiar to people with server-side MVC framework experience.

-

Angular

-

Angular is an MVC framework maintained by Google, and it provides a declarative view layer and an emphasis on testability. It leverages developer experience with server-side MVC frameworks, and in many ways, is very similar in scope to Mithril.

-

The main difference between Angular templates and Mithril templates is that Angular templates follow the tradition of being defined in HTML. This has the benefit of cleaner syntax for writing static text, but it comes with the disadvantage of features getting awkwardly tied to HTML syntax, as well as providing poor debugging support.

-

One thing you may have noticed on the Mithril homepage is that, out of the box, Angular is not as performant as other frameworks. Steep performance degradation is a notoriously common issue in non-trivial Angular applications and there are several third party libraries which attempt to get around performance problems. Speaking from experience, it's generally difficult to reason about performance in Angular.

-

Mithril takes some learnings from that and implements a templating redrawing system that renders less aggressively, is less complex and is easier to profile.

-

A noteworthy difference between Angular and Mithril is in framework complexity: Angular implements several subsystems that would seem more logical in programming language implementations (e.g. a parser, a dynamic scoping mechanism, decorators, etc). Mithril, on the other hand, tries to provide only features that support a more classic MVC paradigm.

-

Ember

-

Ember is a highly comprehensive MVC framework, providing a large API that covers not only traditional MVC patterns, but also a vast range of helper utilities as well.

-

The biggest difference between Ember and Mithril is summarized in the Architecture section above: Ember's comprehensiveness comes at the cost of a steep learning curve and a high degree of vendor lock-in.

-

Ember is also more opinionated in terms of how application architecture should look, and as a result, tends to be less transparent in terms of what is actually happening under the hood.

-

React

-

React is a component engine developed by Facebook. It's relevant for comparison because it uses the same architecture as Mithril's templating engine: i.e. it acknowledges that DOM operations are the bottleneck of templating systems, and implements a virtual DOM tree which keeps track of changes and only applies diffs to the real DOM where needed.

-

The most visible difference between React and Mithril is that React's JSX syntax does not run natively in the browser, whereas Mithril's uncompiled templates do. Both can be compiled, but React's compiled code still has function calls for each virtual DOM element; Mithril templates compile into static Javascript data structures.

-

Another difference is that Mithril, being an MVC framework, rather than a templating engine, provides an auto-redrawing system that is aware of network asynchrony and that can render views efficiently without cluttering application code with redraw calls, and without letting the developer unintentionally bleed out of the MVC pattern.

-

Note also that, despite having a bigger scope, Mithril has a smaller file size than React.

-

Knockout

-

Knockout is a library focused on data binding. It is not an MVC framework in the traditional sense, but idiomatic Knockout code uses the similar concept of view models.

-

A Knockout view model is an amalgamation of model and controller layers in a single class. In contrast, Mithril separates the two layers more distinctly.

-

As with Angular, Knockout templates are written in HTML, and therefore have the same pros and cons as Angular templates.

-

Vue

-

Vue is a relatively new templating engine, but it boasts impressive results in its performance benchmark.

-

It is not a full MVC framework, but it is similar to Angular templates, and uses the same terminology for its features (e.g. directives and filters).

-

The most relevant difference is that Vue uses browser features that don't work (and cannot be made to work) in Internet Explorer 8 and lower. Mithril does not rely on unpolyfillable features, so developers can support browsers all the way back to IE6 and Blackberry by using shims if support for those older browsers is required.

-

Vue's implementation cleverly hijacks array methods, but it should be noted that Javascript Arrays cannot be truly subclassed and as such, Vue suffers from abstraction leaks.

-

In contrast, Mithril avoids "magic" types.

- -
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/comparisons/angular.parsing.html b/archive/v0.2.1/comparisons/angular.parsing.html deleted file mode 100644 index 642d6448..00000000 --- a/archive/v0.2.1/comparisons/angular.parsing.html +++ /dev/null @@ -1,2 +0,0 @@ - -To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better) \ No newline at end of file diff --git a/archive/v0.2.1/comparisons/angular.rendering.html b/archive/v0.2.1/comparisons/angular.rendering.html deleted file mode 100644 index 8cf81d82..00000000 --- a/archive/v0.2.1/comparisons/angular.rendering.html +++ /dev/null @@ -1,14 +0,0 @@ - - - -

To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better)

-
- -
- - - diff --git a/archive/v0.2.1/comparisons/angular.safety.html b/archive/v0.2.1/comparisons/angular.safety.html deleted file mode 100644 index 01403424..00000000 --- a/archive/v0.2.1/comparisons/angular.safety.html +++ /dev/null @@ -1,13 +0,0 @@ - - - -
- -
- - - diff --git a/archive/v0.2.1/comparisons/backbone.parsing.html b/archive/v0.2.1/comparisons/backbone.parsing.html deleted file mode 100644 index e30e1eaf..00000000 --- a/archive/v0.2.1/comparisons/backbone.parsing.html +++ /dev/null @@ -1,4 +0,0 @@ - - - -To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better) \ No newline at end of file diff --git a/archive/v0.2.1/comparisons/backbone.rendering.html b/archive/v0.2.1/comparisons/backbone.rendering.html deleted file mode 100644 index 07f38489..00000000 --- a/archive/v0.2.1/comparisons/backbone.rendering.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - -

To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better)

-
- - - - - diff --git a/archive/v0.2.1/comparisons/backbone.safety.html b/archive/v0.2.1/comparisons/backbone.safety.html deleted file mode 100644 index e5a71e08..00000000 --- a/archive/v0.2.1/comparisons/backbone.safety.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - -
- - - - - diff --git a/archive/v0.2.1/comparisons/jquery.parsing.html b/archive/v0.2.1/comparisons/jquery.parsing.html deleted file mode 100644 index 1fa16592..00000000 --- a/archive/v0.2.1/comparisons/jquery.parsing.html +++ /dev/null @@ -1,2 +0,0 @@ - -To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better) \ No newline at end of file diff --git a/archive/v0.2.1/comparisons/jquery.rendering.html b/archive/v0.2.1/comparisons/jquery.rendering.html deleted file mode 100644 index dde611ec..00000000 --- a/archive/v0.2.1/comparisons/jquery.rendering.html +++ /dev/null @@ -1,17 +0,0 @@ - - - -

To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better)

-
- - - diff --git a/archive/v0.2.1/comparisons/jquery.safety.html b/archive/v0.2.1/comparisons/jquery.safety.html deleted file mode 100644 index e9667269..00000000 --- a/archive/v0.2.1/comparisons/jquery.safety.html +++ /dev/null @@ -1,16 +0,0 @@ - - - -
- - - diff --git a/archive/v0.2.1/comparisons/mithril.parsing.html b/archive/v0.2.1/comparisons/mithril.parsing.html deleted file mode 100644 index c2957a15..00000000 --- a/archive/v0.2.1/comparisons/mithril.parsing.html +++ /dev/null @@ -1,2 +0,0 @@ - -To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better) \ No newline at end of file diff --git a/archive/v0.2.1/comparisons/mithril.rendering.html b/archive/v0.2.1/comparisons/mithril.rendering.html deleted file mode 100644 index 2df0c3ca..00000000 --- a/archive/v0.2.1/comparisons/mithril.rendering.html +++ /dev/null @@ -1,20 +0,0 @@ - - - -

To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better)

-
- - - diff --git a/archive/v0.2.1/comparisons/mithril.safety.html b/archive/v0.2.1/comparisons/mithril.safety.html deleted file mode 100644 index 83a1dad6..00000000 --- a/archive/v0.2.1/comparisons/mithril.safety.html +++ /dev/null @@ -1,19 +0,0 @@ - - - -
- - - diff --git a/archive/v0.2.1/comparisons/react.parsing.html b/archive/v0.2.1/comparisons/react.parsing.html deleted file mode 100644 index a7253b71..00000000 --- a/archive/v0.2.1/comparisons/react.parsing.html +++ /dev/null @@ -1,2 +0,0 @@ - -To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better) \ No newline at end of file diff --git a/archive/v0.2.1/comparisons/react.rendering.html b/archive/v0.2.1/comparisons/react.rendering.html deleted file mode 100644 index f2e2ccce..00000000 --- a/archive/v0.2.1/comparisons/react.rendering.html +++ /dev/null @@ -1,24 +0,0 @@ - - - -

To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better)

-
-
- - - \ No newline at end of file diff --git a/archive/v0.2.1/comparisons/react.safety.html b/archive/v0.2.1/comparisons/react.safety.html deleted file mode 100644 index daf870b4..00000000 --- a/archive/v0.2.1/comparisons/react.safety.html +++ /dev/null @@ -1,24 +0,0 @@ - - - -

To run an execution time test on this page, run the profiler from your browser's developer tools and measure the running time of a page refresh. (Lower is better)

-
-
- - - \ No newline at end of file diff --git a/archive/v0.2.1/component.json b/archive/v0.2.1/component.json deleted file mode 100644 index 14b70808..00000000 --- a/archive/v0.2.1/component.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "mithril", - "description": "A Javascript framework for building brilliant applications", - "keywords": ["mvc", "framework"], - "repository": "lhorie/mithril", - "main": "mithril.js", - "scripts": ["mithril.js"], - "version": "0.2.1", - "license": "MIT" -} \ No newline at end of file diff --git a/archive/v0.2.1/components.html b/archive/v0.2.1/components.html deleted file mode 100644 index ea724b12..00000000 --- a/archive/v0.2.1/components.html +++ /dev/null @@ -1,561 +0,0 @@ - - - - Components - Mithril - - - - - -
- -
-
-
-
-
- -
-

Components

-
- -
-

Application architecture with components

-

Components are versatile tools to organize code and can be used in a variety of ways.

-

Let's create a simple model entity which we'll use in a simple application, to illustrate different usage patterns for components:

-
var Contact = function(data) {
-    data = data || {}
-    this.id = m.prop(data.id || "")
-    this.name = m.prop(data.name || "")
-    this.email = m.prop(data.email || "")
-}
-Contact.list = function(data) {
-    return m.request({method: "GET", url: "/api/contact", type: Contact})
-}
-Contact.save = function(data) {
-    return m.request({method: "POST", url: "/api/contact", data: data})
-}
-
-

Here, we've defined a class called Contact. A contact has an id, a name and an email. There are two static methods: list for retrieving a list of contacts, and save to save a single contact. These methods assume that the AJAX responses return contacts in JSON format, containing the same fields as the class.

-

Aggregation of responsibility

-

One way of organizing components is to use component parameter lists to send data downstream, and to define events to bubble data back upstream to a centralized module who is responsible for interfacing with the model layer.

-
var ContactsWidget = {
-    controller: function update() {
-        this.contacts = Contact.list()
-        this.save = function(contact) {
-            Contact.save(contact).then(update.bind(this))
-        }.bind(this)
-    },
-    view: function(ctrl) {
-        return [
-            m.component(ContactForm, {onsave: ctrl.save}),
-            m.component(ContactList, {contacts: ctrl.contacts})
-        ]
-    }
-}
-
-var ContactForm = {
-    controller: function(args) {
-        this.contact = m.prop(args.contact || new Contact())
-    },
-    view: function(ctrl, args) {
-        var contact = ctrl.contact()
-
-        return m("form", [
-            m("label", "Name"),
-            m("input", {oninput: m.withAttr("value", contact.name), value: contact.name()}),
-
-            m("label", "Email"),
-            m("input", {oninput: m.withAttr("value", contact.email), value: contact.email()}),
-
-            m("button[type=button]", {onclick: args.onsave.bind(this, contact)}, "Save")
-        ])
-    }
-}
-
-var ContactList = {
-    view: function(ctrl, args) {
-        return m("table", [
-            args.contacts().map(function(contact) {
-                return m("tr", [
-                    m("td", contact.id()),
-                    m("td", contact.name()),
-                    m("td", contact.email())
-                ])
-            })
-        ])
-    }
-}
-
-m.mount(document.body, ContactsWidget)
-
-

In the example above, there are 3 components. ContactsWidget is the top level module being rendered to document.body, and it is the module that has the responsibility of talking to our Model entity Contact, which we defined earlier.

-

The ContactForm component is, as its name suggests, a form that allows us to edit the fields of a Contact entity. It exposes an event called onsave which is fired when the Save button is pressed on the form. In addition, it stores the unsaved contact entity internally within the component (this.contact = m.prop(args.contact || new Contact())).

-

The ContactList component displays a table showing all the contact entities that are passed to it via the contacts argument.

-

The most interesting component is ContactsWidget:

-
    -
  1. on initialization, it fetches the list of contacts (this.contacts = Contact.list)

    -
  2. -
  3. when save is called, it saves a contact (Contact.save(contact))

    -
  4. -
  5. after saving the contact, it reloads the list (.then(update.bind(this)))

    -
  6. -
-

update is the controller function itself, so defining it as a promise callback simply means that the controller is re-initialized after the previous asynchronous operation (Contact.save())

-

Aggregating responsibility in a top-level component allows the developer to manage multiple model entities easily: any given AJAX request only needs to be performed once regardless of how many components need its data, and refreshing the data set is simple.

-

In addition, components can be reused in different contexts. Notice that the ContactList does not care about whether args.contacts refers to all the contacts in the database, or just contacts that match some criteria. Similarly, ContactForm can be used to both create new contacts as well as edit existing ones. The implications of saving are left to the parent component to handle.

-

This architecture can yield highly flexible and reusable code, but flexibility can also increase the cognitive load of the system (for example, you need to look at both the top-level module and ContactList in order to know what is the data being displayed (and how it's being filtered, etc). In addition, having a deeply nested tree of components can result in a lot of intermediate "pass-through" arguments and event handlers.

-
-

Distribution of concrete responsibilities

-

Another way of organizing code is to distribute concrete responsibilities across multiple modules.

-

Here's a refactored version of the sample app above to illustrate:

-
var ContactForm = {
-    controller: function() {
-        this.contact = m.prop(new Contact())
-        this.save = function(contact) {
-            Contact.save(contact)
-        }
-    },
-    view: function(ctrl) {
-        var contact = ctrl.contact()
-
-        return m("form", [
-            m("label", "Name"),
-            m("input", {oninput: m.withAttr("value", contact.name), value: contact.name()}),
-
-            m("label", "Email"),
-            m("input", {oninput: m.withAttr("value", contact.email), value: contact.email()}),
-
-            m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
-        ])
-    }
-}
-
-var ContactList = {
-    controller: function() {
-        this.contacts = Contact.list()
-    },
-    view: function(ctrl) {
-        return m("table", [
-            ctrl.contacts().map(function(contact) {
-                return m("tr", [
-                    m("td", contact.id()),
-                    m("td", contact.name()),
-                    m("td", contact.email())
-                ])
-            })
-        ])
-    }
-}
-
-m.route(document.body, "/", {
-    "/list": ContactList,
-    "/create": ContactForm
-})
-
-

Notice that now each component is self-contained: each has a separate route, and each component does exactly one thing. These components are designed to not interface with other components. On the one hand, it's extremely easy to reason about the behavior of the components since they only serve a single purpose, but on the other hand they don't have the flexibility that the previous example did (e.g. in this iteration, ContactList can only list all of the contacts in the database, not an arbitrary subset.

-

Also, notice that since these components are designed to encapsulate their behavior, they cannot easily affect other components. In practice, this means that if the two components were in a ContactsWidget component as before, saving a contact would not update the list without some extra code.

-

Cross-communication in single-purpose components

-

Here's one way to implement cross-communication between single purpose components:

-
var Observable = function() {
-    var controllers = []
-    return {
-        register: function(controller) {
-            return function() {
-                var ctrl = new controller
-                ctrl.onunload = function() {
-                    controllers.splice(controllers.indexOf(ctrl), 1)
-                }
-                controllers.push({instance: ctrl, controller: controller})
-                return ctrl
-            }
-        },
-        trigger: function() {
-            controllers.map(function(c) {
-                ctrl = new c.controller
-                for (var i in ctrl) c.instance[i] = ctrl[i]
-            })
-        }
-    }
-}.call()
-
-
-var ContactsWidget = {
-    view: function(ctrl) {
-        return [
-            ContactForm,
-            ContactList
-        ]
-    }
-}
-
-var ContactForm = {
-    controller: function() {
-        this.contact = m.prop(new Contact())
-        this.save = function(contact) {
-            Contact.save(contact).then(Observable.trigger)
-        }
-    },
-    view: function(ctrl) {
-        var contact = ctrl.contact()
-
-        return m("form", [
-            m("label", "Name"),
-            m("input", {oninput: m.withAttr("value", contact.name), value: contact.name()}),
-
-            m("label", "Email"),
-            m("input", {oninput: m.withAttr("value", contact.email), value: contact.email()}),
-
-            m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
-        ])
-    }
-}
-
-var ContactList = {
-    controller: Observable.register(function() {
-        this.contacts = Contact.list()
-    }),
-    view: function(ctrl) {
-        return m("table", [
-            ctrl.contacts().map(function(contact) {
-                return m("tr", [
-                    m("td", contact.id()),
-                    m("td", contact.name()),
-                    m("td", contact.email())
-                ])
-            })
-        ])
-    }
-}
-
-m.mount(document.body, ContactsWidget)
-
-

In this iteration, both the ContactForm and ContactList components are now children of the ContactsWidget component and they appear simultaneously on the same page.

-

The Observable object exposes two methods: register which marks a controller as a Observable entity, and trigger which reloads controllers marked by register. Controllers are deregistered when their onunload event is triggered.

-

The ContactList component's controller is marked as Observable, and the save event handler in ContactForm calls Observable.trigger after saving.

-

This mechanism allows multiple components to be reloaded in response to non-idempotent operations.

-

One extremely important aspect of this architecture is that since components encapsulate their internal state, then by definition it's harder to reason about AJAX request redundancy (i.e. how to prevent two identical AJAX requests originating from two different components).

-

The observer pattern

-

The Observable object can be further refactored so that trigger broadcasts to "channels", which controllers can subscribe to. This is known, appropriately, as the observer pattern.

-
var Observable = function() {
-    var channels = {}
-    return {
-        register: function(subscriptions, controller) {
-            return function self() {
-                var ctrl = new controller
-                var reload = controller.bind(ctrl)
-                Observable.on(subscriptions, reload)
-                ctrl.onunload = function() {
-                    Observable.off(reload)
-                }
-                return ctrl
-            }
-        },
-        on: function(subscriptions, callback) {
-            subscriptions.forEach(function(subscription) {
-                if (!channels[subscription]) channels[subscription] = []
-                channels[subscription].push(callback)
-            })
-        },
-        off: function(callback) {
-            for (var channel in channels) {
-                var index = channels[channel].indexOf(callback)
-                if (index > -1) channels[channel].splice(index, 1)
-            }
-        },
-        trigger: function(channel, args) {
-            console.log("triggered: " + channel)
-            channels[channel].map(function(callback) {
-                callback(args)
-            })
-        }
-    }
-}.call()
-
-

This pattern is useful to decouple chains of dependencies (however care should be taken to avoid "come-from hell", i.e. difficulty in following a chains of events because they are too numerous and arbitrarily inter-dependent)

-

Hybrid architecture

-

It's of course possible to use both aggregation of responsibility and the observer pattern at the same time.

-

The example below shows a variation of the contacts app where ContactForm is responsible for saving.

-
var ContactsWidget = {
-    controller: Observable.register(["updateContact"], function() {
-        this.contacts = Contact.list()
-    }),
-    view: function(ctrl) {
-        return [
-            m.component(ContactForm),
-            m.component(ContactList, {contacts: ctrl.contacts})
-        ]
-    }
-}
-
-var ContactForm = {
-    controller: function(args) {
-        this.contact = m.prop(new Contact())
-        this.save = function(contact) {
-            Contact.save(contact).then(Observable.trigger("updateContact"))
-        }
-    },
-    view: function(ctrl, args) {
-        var contact = ctrl.contact()
-
-        return m("form", [
-            m("label", "Name"),
-            m("input", {oninput: m.withAttr("value", contact.name), value: contact.name()}),
-
-            m("label", "Email"),
-            m("input", {oninput: m.withAttr("value", contact.email), value: contact.email()}),
-
-            m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
-        ])
-    }
-}
-
-var ContactList = {
-    view: function(ctrl, args) {
-        return m("table", [
-            args.contacts().map(function(contact) {
-                return m("tr", [
-                    m("td", contact.id()),
-                    m("td", contact.name()),
-                    m("td", contact.email())
-                ])
-            })
-        ])
-    }
-}
-
-m.mount(document.body, ContactsWidget)
-
-

Here, the data fetching is still centralized in the top-level component, so that we can avoid duplicate AJAX requests when fetching data.

-

And moving the responsibility of saving to the ContactForm component alleviates the need to send data back up the component tree, making the handling of non-idempotent operations less prone to pass-through argument noise.

-
-

Classic MVC

-

Here's one last, but relevant variation of the pattern above.

-
//model layer observer
-Observable.on(["saveContact"], function(data) {
-    Contact.save(data.contact).then(Observable.trigger("updateContact"))
-})
-
-//ContactsWidget is the same as before
-var ContactsWidget = {
-    controller: Observable.register(["updateContact"], function() {
-        this.contacts = Contact.list()
-    }),
-    view: function(ctrl) {
-        return [
-            m.component(ContactForm),
-            ctrl.contacts() === undefined
-              ? m("div", "loading contacts...") //waiting for promise to resolve
-              : m.component(ContactList, {contacts: ctrl.contacts})
-        ]
-    }
-}
-
-//ContactList no longer calls `Contact.save`
-var ContactForm = {
-    controller: function(args) {
-            var ctrl = this
-        ctrl.contact = m.prop(new Contact())
-        ctrl.save = function(contact) {
-            Observable.trigger("saveContact", {contact: contact})
-            ctrl.contact = m.prop(new Contact()) //reset to empty contact
-        }
-        return ctrl
-    },
-    view: function(ctrl, args) {
-        var contact = ctrl.contact()
-
-        return m("form", [
-            m("label", "Name"),
-            m("input", {oninput: m.withAttr("value", contact.name), value: contact.name()}),
-
-            m("label", "Email"),
-            m("input", {oninput: m.withAttr("value", contact.email), value: contact.email()}),
-
-            m("button[type=button]", {onclick: ctrl.save.bind(this, contact)}, "Save")
-        ])
-    }
-}
-
-//ContactList is the same as before
-var ContactList = {
-    view: function(ctrl, args) {
-        return m("table", [
-            args.contacts().map(function(contact) {
-                return m("tr", [
-                    m("td", contact.id()),
-                    m("td", contact.name()),
-                    m("td", contact.email())
-                ])
-            })
-        ])
-    }
-}
-
-m.mount(document.body, ContactsWidget)
-
-

Here we've moved Contact.save(contact).then(Observable.trigger("updateContact")) out of the ContactForm component and into the model layer. In its place, ContactForm merely emits an action, which is then handled by this model layer observer.

-

This allows swapping the implementation of the saveContact handler without changing the ContactForm component.

-
-

Example: HTML5 drag-n-drop file uploader component

-

Here's an example of a not-so-trivial component: a drag-n-drop file uploader. In addition to the controller and view properties that make the Uploader object usable as a component, it also has an upload convenience function that provides a basic upload model method, and a serialize function that allows files to be serialized as JSON in regular requests encoded as application/x-www-form-urlencoded.

-

These two functions are here to illustrate the ability to expose APIs to component consumers that complement the component's user interface. By bundling model methods in the component, we avoid hard-coding how files are handled once they're dropped in, and instead, we provide a useful library of functions that can be consumed flexibly to meet the demands on an application.

-
var Uploader = {
-    upload: function(options) {
-        var formData = new FormData
-        for (var key in options.data) {
-            for (var i = 0; i < options.data[key].length; i++) {
-                formData.append(key, options.data[key][i])
-            }
-        }
-
-        //simply pass the FormData object intact to the underlying XMLHttpRequest, instead of JSON.stringify'ing it
-        options.serialize = function(value) {return value}
-        options.data = formData
-
-        return m.request(options)
-    },
-    serialize: function(files) {
-        var promises = files.map(function(file) {
-            var deferred = m.deferred()
-
-            var reader = new FileReader
-            reader.readAsDataURL()
-            reader.onloadend = function(e) {
-                deferred.resolve(e.result)
-            }
-            reader.onerror = deferred.reject
-            return deferred.promise
-        })
-        return m.sync(promises)
-    },
-    controller: function(args) {
-        this.noop = function(e) {
-            e.preventDefault()
-        }
-        this.update = function(e) {
-            e.preventDefault()
-            if (typeof args.onchange == "function") {
-                args.onchange([].slice.call((e.dataTransfer || e.target).files))
-            }
-        }
-    },
-    view: function(ctrl, args) {
-        return m(".uploader", {ondragover: ctrl.noop, ondrop: ctrl.update})
-    }
-}
-
-

Below are some examples of consuming the Uploader component:

-
//usage demo 1: standalone multipart/form-data upload when files are dropped into the component
-var Demo1 = {
-    controller: function() {
-        return {
-            upload: function(files) {
-                Uploader.upload({method: "POST", url: "/api/files", data: {files: files}}).then(function() {
-                    alert("uploaded!")
-                })
-            }
-        }
-    },
-    view: function(ctrl) {
-        return [
-            m("h1", "Uploader demo"),
-            m.component(Uploader, {onchange: ctrl.upload})
-        ]
-    }
-}
-
-

Demo

-
//usage demo 2: upload as base-64 encoded data url from a parent form
-var Demo2 = {
-    Asset: {
-        save: function(data) {
-            return m.request({method: "POST", url: "/api/assets", data: data})
-        }
-    },
-
-    controller: function() {
-        var files = m.prop([])
-        return {
-            files: files,
-            save: function() {
-                Uploader.serialize(files()).then(function(files) {
-                    Demo2.Asset.save({files: files}).then(function() {
-                        alert("Uploaded!")
-                    })
-                })
-            }
-        }
-    },
-    view: function(ctrl) {
-        return [
-            m("h1", "Uploader demo"),
-            m("p", "Drag and drop a file below. An alert box will appear when the upload finishes"),
-            m("form", [
-                m.component(Uploader, {onchange: ctrl.files}),
-                ctrl.files().map(function(file) {
-                    return file.name
-                }).join(),
-                m("button[type=button]", {onclick: ctrl.save}, "Upload")
-            ])
-        ]
-    }
-}
-
-

Demo

- -
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/getting-started.html b/archive/v0.2.1/getting-started.html deleted file mode 100644 index 035f3605..00000000 --- a/archive/v0.2.1/getting-started.html +++ /dev/null @@ -1,509 +0,0 @@ - - - - Getting Started - Mithril - - - - - -
- -
-
-
-
-
- -
-

Getting Started

-

What is Mithril?

-

Mithril is a client-side Javascript MVC framework, i.e. it's a tool to make application code divided into a data layer (called Model), a UI layer (called View), and a glue layer (called Controller)

-

Mithril is around 7kb gzipped thanks to its small, focused, API. It provides a templating engine with a virtual DOM diff implementation for performant rendering, utilities for high-level modelling via functional composition, as well as support for routing and componentization.

-

The goal of the framework is to make application code discoverable, readable and maintainable, and hopefully help you become an even better developer.

-

Unlike some frameworks, Mithril tries very hard to avoid locking you into a web of dependencies: you can use as little of the framework as you need.

-

However, using its entire toolset idiomatically can bring lots of benefits: learning to use functional programming in real world scenarios and solidifying good coding practices for OOP and MVC are just some of them.

-
-

A Simple Application

-

Once you have a copy of Mithril, getting started is surprisingly boilerplate-free:

-
<!doctype html>
-<title>Todo app</title>
-<script src="mithril.min.js"></script>
-<script>
-//app goes here
-</script>
-
-

Yes, this is valid HTML 5! According to the specs, the <html>, <head> and <body> tags can be omitted, but their respective DOM elements will still be there implicitly when a browser renders that markup.

-
-

Model

-

In Mithril, an application typically lives in a namespace and contains components. Components are merely structures that represent a viewable "page" or a part of a page. In addition, an application can be organizationally divided into three major layers: Model, Controller and View.

-

For simplicity, our application will have only one component, and we're going to use it as the namespace for our application.

-

In Mithril, a component is an object that contains a view function and optionally a controller function.

-
//an empty Mithril component
-var myComponent = {
-    controller: function() {},
-    view: function() {}
-}
-

In addition to holding a controller and a view, a component can also be used to store data that pertains to it.

-

Let's create a component.

-
<script>
-//this application only has one component: todo
-var todo = {};
-</script>
-
-

Typically, model entities are reusable and live outside of components (e.g. var User = ...). In our example, since the whole application lives in one component, we're going to use the component as a namespace for our model entities.

-
var todo = {};
-
-//for simplicity, we use this component to namespace the model classes
-
-//the Todo class has two properties
-todo.Todo = function(data) {
-    this.description = m.prop(data.description);
-    this.done = m.prop(false);
-};
-
-//the TodoList class is a list of Todo's
-todo.TodoList = Array;
-
-

m.prop is simply a factory for a getter-setter function. Getter-setters work like this:

-
//define a getter-setter with initial value `John`
-var a_name = m.prop("John");
-
-//read the value
-var a = a_name(); //a == "John"
-
-//set the value to `Mary`
-a_name("Mary"); //Mary
-
-//read the value
-var b = a_name(); //b == "Mary"
-
-

Note that the Todo and TodoList classes we defined above are plain vanilla Javascript constructors. They can be initialized and used like this:

-
var myTask = new todo.Todo({description: "Write code"});
-
-//read the description
-myTask.description(); //Write code
-
-//is it done?
-var isDone = myTask.done(); //isDone == false
-
-//mark as done
-myTask.done(true); //true
-
-//now it's done
-isDone = myTask.done(); //isDone == true
-
-

The TodoList class is simply an alias of the native Array class.

-
var list = new todo.TodoList();
-list.length; //0
-
-

According to the classic definition of the MVC pattern, the model layer is responsible for data storage, state management and business logic.

-

You can see that our classes above fit the criteria: they have all the methods and properties that they need to be assembled into a meaningful state. A Todo can be instantiated, and have its properties changed. The list can have todo items added to it via the push method. And so on.

-

View-Model

-

Our next step is to write a view-model that will use our model classes. A view-model is a model level entity that stores UI state. In many frameworks UI state is typically stored in a controller, but doing so makes the code harder to scale since controllers aren't designed to be data providers. In Mithril, UI state is understood to be model data, even though it doesn't necessarily map to a database ORM entity.

-

View-models are also responsible for handling business logic that revolves around UI-specific restrictions. For example a form might have an input and a cancel button. In such a case, it's the view-model's responsibility to track the current state of the input vs the original state and to apply a cancellation, if required. In the event the form was saved, then view-model would delegate saving to a more appropriate ORM model entity.

-

In the case of our todo application, the view-model needs a few things: it needs to track a running list of todos and a field for adding new todos, and it needs to handle the logic of adding to the todo and the implications of this action of the UI.

-
//define the view-model
-todo.vm = {
-    init: function() {
-        //a running list of todos
-        todo.vm.list = new todo.TodoList();
-
-        //a slot to store the name of a new todo before it is created
-        todo.vm.description = m.prop('');
-
-        //adds a todo to the list, and clears the description field for user convenience
-        todo.vm.add = function(description) {
-            if (description()) {
-                todo.vm.list.push(new todo.Todo({description: description()}));
-                todo.vm.description("");
-            }
-        };
-    }
-};
-
-

The code above defines a view-model object called vm. It is simply a javascript object that has an init function. This function initializes the vm object with three members: list, which is simply an array, description, which is an m.prop getter-setter function with an empty string as the initial value, and add, which is a method that adds a new Todo instance to list if an input description getter-setter is not an empty string.

-

Later in this guide, we'll pass the description property as the parameter to this function. When we get there, I'll explain why we're passing description as an argument instead of simply using OOP-style member association.

-

You can use the view-model like this:

-
//initialize our view-model
-todo.vm.init();
-
-todo.vm.description(); //[empty string]
-
-//try adding a to-do
-todo.vm.add(todo.vm.description);
-todo.vm.list.length; //0, because you can't add a to-do with an empty description
-
-//add it properly
-todo.vm.description("Write code");
-todo.vm.add(todo.vm.description);
-todo.vm.list.length; //1
-
-
-

Controller

-

In classic MVC, the role of the controller is to dispatch actions from the view to the model layer. In traditional server-side frameworks, the controller layer is of large significance because the nature of HTTP requests, responses and the framework abstractions that are exposed to developers require that the controller act as an adapter layer to transform the serialized data from HTTP requests to something that can be passed to ORM model methods.

-

In client-side MVC, however, this dissonance doesn't exist, and controllers can be extremely simple. Mithril controllers can be stripped down to a bare minimum, so that they only perform a single essential role: to expose a scoped set of model-level functionality. As you may recall, models are responsible for encapsulating business logic, and view-models encapsulate logic that pertains specifically to UI state, so there's really nothing else for a controller to abstract away, and all it needs to do is expose a slice of the model layer that pertains to the UI that is currently in view.

-

In other words, all our controller needs to do is this:

-
todo.controller = function() {
-    todo.vm.init()
-}
-
-
-

View

-

The next step is to write a view so users can interact with the application. In Mithril, views are plain Javascript. This comes with several benefits (proper error reporting, proper lexical scoping, etc.), while still allowing HTML syntax to be used via a preprocessor tool

-
todo.view = function() {
-    return m("html", [
-        m("body", [
-            m("input"),
-            m("button", "Add"),
-            m("table", [
-                m("tr", [
-                    m("td", [
-                        m("input[type=checkbox]")
-                    ]),
-                    m("td", "task description"),
-                ])
-            ])
-        ])
-    ]);
-};
-
-

The utility method m() creates virtual DOM elements. As you can see, you can use CSS selectors to specify attributes. You can also use the . syntax to add CSS classes and the # to add an id.

-

In fact, when not using the MSX HTML syntax preprocessor, it's recommended that you embrace using CSS selectors (e.g. m(".modal-body")) to really benefit from their inherent semantic expressiveness.

-

For the purposes of testing out our code so far, the view can be rendered using the m.render method:

-
m.render(document, todo.view());
-
-

Notice that we pass a root DOM element to attach our template to, as well as the template itself.

-

This renders the following markup:

-
<html>
-    <body>
-        <input />
-        <button>Add</button>
-        <table>
-            <tr>
-                <td><input type="checkbox" /></td>
-                <td>task description</td>
-            </tr>
-        </table>
-    </body>
-</html>
-
-

Note that m.render is a very low level method in Mithril that draws only once and doesn't attempt to run the auto-redrawing system. In order to enable auto-redrawing, the todo component must be initialized by either calling m.mount or by creating a route definition with m.route. Also note that, unlike observable-based frameworks like Knockout.js, setting a value in a m.prop getter-setter does NOT trigger redrawing side-effects in Mithril.

-
-

Data Bindings

-

Let's implement a data binding on the text input. Data bindings connect a DOM element to a Javascript variable so that updating one updates the other.

-
//binding a model value to an input in a template
-m("input", {value: todo.vm.description()})
-
-

This binds the description getter-setter to the text input. Updating the value of the description in the model updates the DOM input when Mithril redraws.

-
todo.vm.init();
-
-todo.vm.description(); // empty string
-m.render(document, todo.view()); // input is blank
-
-todo.vm.description("Write code"); //set the description in the controller
-m.render(document, todo.view()); // input now says "Write code"
-
-

At a glance it may seem like we're doing something very expensive by redrawing, but as it turns out, calling the todo.view method multiple times does not actually re-render the entire template. Internally, Mithril keeps a virtual representation of the DOM in cache, scans for changes, and then only modifies the absolute minimum required to apply the change to the DOM. In practice, this results in surprisingly fast re-rendering.

-

In the case above, Mithril only touches the value attribute of the input.

-

Note that the example above only sets the value of the input element in the DOM, but it never reads it. This means that typing something on the input and then re-rendering will clobber the text on screen.

-
-

Fortunately, bindings can also be bi-directional: that is, they can be coded in such a way that, in addition to setting the DOM value, it's also possible to read it as a user types, and then update the description getter-setter in the view-model.

-

Here's the most basic way of implementing the view-to-model part of the binding:

-
m("input", {onchange: m.withAttr("value", todo.vm.description), value: todo.vm.description()})
-
-

The code bound to the onchange can be read like this: "with the attribute value, set todo.vm.description".

-

Note that Mithril does not prescribe how the binding updates: you can bind it to onchange, onkeypress, oninput, onblur or any other event that you prefer.

-

You can also specify what attribute to bind. This means that just as you are able to bind the value attribute in an <select>, you are also able to bind the selectedIndex property, if needed for whatever reason.

-

The m.withAttr utility is a functional programming tool provided by Mithril to minimize the need for anonymous functions in the view.

-

The m.withAttr("value", todo.vm.description) call above returns a function that is the rough equivalent of this code:

-
onchange: function(e) {
-    todo.vm.description(e.target["value"]);
-}
-
-

The difference, aside from avoiding an anonymous function, is that the m.withAttr idiom also takes care of catching the correct event target and selecting the appropriate source of the data - i.e. whether it should come from a Javascript property or from DOMElement::getAttribute()

-
-

In addition to bi-directional data binding, we can also bind parameterized functions to events:

-
var vm = todo.vm
-
-m("button", {onclick: vm.add.bind(vm, vm.description)}, "Add")
-
-

In the code above, we are simply using the native Javascript Function::bind method. This creates a new function with the parameter already set. In functional programming, this is called partial application.

-

The vm.add.bind(vm, vm.description) expression above returns a function that is equivalent to this code:

-
onclick: function(e) {
-    todo.vm.add(todo.vm.description)
-}
-
-

Note that when we construct the parameterized binding, we are passing the description getter-setter by reference, and not its value. We only evaluate the getter-setter to get its value in the controller method. This is a form of lazy evaluation: it allows us to say "use this value later, when the event handler gets called".

-

Hopefully by now, you're starting to see why Mithril encourages the usage of m.prop: Because Mithril getter-setters are functions, they naturally compose well with functional programming tools, and allow for some very powerful idioms. In this case, we're using them in a way that resembles C pointers.

-

Mithril uses them in other interesting ways elsewhere.

-

Clever readers will probably notice that we can refactor the add method to make it much simpler:

-
vm.add = function() {
-    if (vm.description()) {
-        vm.list.push(new todo.Todo({description: vm.description()}));
-        vm.description("");
-    }
-};
-
-

The difference with the modified version is that add no longer takes an argument.

-

With this, we can make the onclick binding on the template much simpler:

-
m("button", {onclick: todo.vm.add}, "Add")
-

The only reason I talked about partial application here was to make you aware of that technique, since it becomes useful when dealing with parameterized event handlers. In real life, given a choice, you should always pick the simplest idiom for your use case.

-
-

To implement flow control in Mithril views, we simply use Javascript Array methods:

-
//here's the view
-m("table", [
-    todo.vm.list.map(function(task, index) {
-        return m("tr", [
-            m("td", [
-                m("input[type=checkbox]")
-            ]),
-            m("td", task.description()),
-        ])
-    })
-])
-
-

In the code above, todo.vm.list is an Array, and map is one of its native functional methods. It allows us to iterate over the list and merge transformed versions of the list items into an output array.

-

As you can see, we return a partial template with two <td>'s. The second one has a data binding to the description getter-setter of the Todo class instance.

-

You're probably starting to notice that Javascript has strong support for functional programming and that it allows us to naturally do things that can be clunky in other frameworks (e.g. looping inside a <dl>/<dt>/<dd> construct).

-
-

The rest of the code can be implemented using idioms we already covered. The complete view looks like this:

-
todo.view = function() {
-    return m("html", [
-        m("body", [
-            m("input", {onchange: m.withAttr("value", todo.vm.description), value: todo.vm.description()}),
-            m("button", {onclick: todo.vm.add}, "Add"),
-            m("table", [
-                todo.vm.list.map(function(task, index) {
-                    return m("tr", [
-                        m("td", [
-                            m("input[type=checkbox]", {onclick: m.withAttr("checked", task.done), checked: task.done()})
-                        ]),
-                        m("td", {style: {textDecoration: task.done() ? "line-through" : "none"}}, task.description()),
-                    ])
-                })
-            ])
-        ])
-    ]);
-};
-
-

Here are the highlights of the template above:

-
    -
  • The template is rendered as a child of the implicit <html> element of the document.
  • -
  • The text input saves its value to the todo.vm.description getter-setter we defined earlier.
  • -
  • The button calls the todo.vm.add method when clicked.
  • -
  • The table lists all the existing to-dos, if any.
  • -
  • The checkboxes save their value to the task.done getter setter.
  • -
  • The description gets crossed out via CSS if the task is marked as done.
  • -
  • When updates happen, the template is not wholly re-rendered - only the changes are applied.
  • -
-
-

So far, we've been using m.render to manually redraw after we made a change to the data. However, as I mentioned before, you can enable an auto-redrawing system, by initializing the todo component via m.mount.

-
//render the todo component inside the document DOM node
-m.mount(document, {controller: todo.controller, view: todo.view});
-
-

Mithril's auto-redrawing system keeps track of controller stability, and only redraws the view once it detects that the controller has finished running all of its code, including asynchronous AJAX payloads. Likewise, it intelligently waits for asynchronous services inside event handlers to complete before redrawing.

-

You can learn more about how redrawing heuristics work here.

-
-

Summary

-

Here's the application code in its entirety:

-
<!doctype html>
-<script src="mithril.min.js"></script>
-<script>
-//this application only has one component: todo
-var todo = {};
-
-//for simplicity, we use this component to namespace the model classes
-
-//the Todo class has two properties
-todo.Todo = function(data) {
-    this.description = m.prop(data.description);
-    this.done = m.prop(false);
-};
-
-//the TodoList class is a list of Todo's
-todo.TodoList = Array;
-
-//the view-model tracks a running list of todos,
-//stores a description for new todos before they are created
-//and takes care of the logic surrounding when adding is permitted
-//and clearing the input after adding a todo to the list
-todo.vm = (function() {
-    var vm = {}
-    vm.init = function() {
-        //a running list of todos
-        vm.list = new todo.TodoList();
-
-        //a slot to store the name of a new todo before it is created
-        vm.description = m.prop("");
-
-        //adds a todo to the list, and clears the description field for user convenience
-        vm.add = function() {
-            if (vm.description()) {
-                vm.list.push(new todo.Todo({description: vm.description()}));
-                vm.description("");
-            }
-        };
-    }
-    return vm
-}())
-
-//the controller defines what part of the model is relevant for the current page
-//in our case, there's only one view-model that handles everything
-todo.controller = function() {
-    todo.vm.init()
-}
-
-//here's the view
-todo.view = function() {
-    return m("html", [
-        m("body", [
-            m("input", {onchange: m.withAttr("value", todo.vm.description), value: todo.vm.description()}),
-            m("button", {onclick: todo.vm.add}, "Add"),
-            m("table", [
-                todo.vm.list.map(function(task, index) {
-                    return m("tr", [
-                        m("td", [
-                            m("input[type=checkbox]", {onclick: m.withAttr("checked", task.done), checked: task.done()})
-                        ]),
-                        m("td", {style: {textDecoration: task.done() ? "line-through" : "none"}}, task.description()),
-                    ])
-                })
-            ])
-        ])
-    ]);
-};
-
-//initialize the application
-m.mount(document, {controller: todo.controller, view: todo.view});
-</script>
-
-

This example is also available as a jsFiddle. -There is also Extended example available on jsfiddle.

-
-

Notes on Architecture

-

Idiomatic Mithril code is meant to apply good programming conventions and be easy to refactor.

-

In the application above, notice how the Todo class can easily be moved to a different component if code re-organization is required.

-

Todos are self-contained and their data aren't tied to the DOM like in typical jQuery based code. The Todo class API is reusable and unit-test friendly, and in addition, it's a plain-vanilla Javascript class, and so has almost no framework-specific learning curve.

-

m.prop is a simple but surprisingly versatile tool: it's functionally composable, it enables uniform data access and allows a higher degree of decoupling when major refactoring is required.

-

When refactoring is unavoidable, the developer can simply replace the m.prop call with an appropriate getter-setter implementation, instead of having to grep for API usage across the entire application.

-

For example, if todo descriptions needed to always be uppercased, one could simply change the description getter-setter:

-
this.description = m.prop(data.description)
-
-

becomes:

-
//private store
-var description;
-
-//public getter-setter
-this.description = function(value) {
-    if (arguments.length > 0) description = value.toUpperCase();
-    return description;
-}
-
-//make it serializable
-this.description.toJSON = function() {return description}
-
-//set the value
-this.description(data.description)
-
-

In the view-model, we aliased the native Array class for TodoList. Be aware that by using the native Array class, we're making an implicit statement that we are going to support all of the standard Array methods as part of our API.

-

While this decision allows better API discoverability, the trade-off is that we're largely giving up on custom constraints and behavior. For example, if we wanted to change the application to make the list be persisted, a native Array would most certainly not be a suitable class to use.

-

In order to deal with that type of refactoring, one can explicitly decide to support only a subset of the Array API, and implement another class with the same interface as this subset API.

-

Given the code above, the replacement class would only need to implement the .push() and .map() methods. By freezing APIs and swapping implementations, the developer can completely avoid touching other layers in the application while refactoring.

-
todo.TodoList = Array;
-
-

becomes:

-
todo.TodoList = function () {
-    this.push = function() { /*...*/ },
-    this.map = function() { /*...*/ }
-};
-
-

Hopefully these examples give you an idea of ways requirements can change over time and how Mithril's philosophy allows developers to use standard OOP techniques to refactor their codebases, rather than needing to modify large portions of the application.

-
-

The first and most obvious thing you may have noticed in the view layer is that the view is not written in HTML.

-

While superficially this may seem like an odd design, this actually has a lot of benefits:

-
    -
  • No flash-of-unbehaviored-content (FOUC). In fact, Mithril is able to render a fully functional application - with working event handlers - before the "DOM ready" event fires!

    -
  • -
  • There's no need for a parse-and-compile pre-processing step to turn strings containing HTML + templating syntax into working DOM elements.

    -
  • -
  • Mithril views can provide accurate and informative error reporting, with line numbers and meaningful stack traces.

    -
  • -
  • You get the ability to automate linting, unit testing and minifying of the entire view layer.

    -
  • -
  • It provides full Turing completeness: full control over evaluation eagerness/laziness and caching in templates. You can even build components that take other components as first-class-citizen parameters!

    -
  • -
-

And if you really do want to use HTML syntax after all, you can use a package called MSX.

-

Views in Mithril use a virtual DOM diff implementation, which sidesteps performance problems related to opaque dirty-checking and excessive browser repaint that are present in some frameworks.

-

Another feature - the optional m() utility - allows writing terse templates in a declarative style using CSS shorthands, similar to popular HTML preprocessors from server-side MVC frameworks.

-

And because Mithril views are Javascript, the developer has full freedom to abstract common patterns - from bidirectional binding helpers to full blown components - using standard Javascript refactoring techniques.

-

Mithril templates are also more collision-proof than other component systems since there's no way to pollute the HTML tag namespace by defining ad-hoc tag names.

-

A more intellectually interesting aspect of the framework is that event handling is encouraged to be done via functional composition (i.e. by using tools like m.withAttr, m.prop and the native .bind() method for partial application).

-

If you've been interested in learning or using Functional Programming in the real world, Mithril provides very pragmatic opportunities to get into it.

-
-

Learn More

-

Mithril provides a few more facilities that are not demonstrated in this page. The following topics are good places to start a deeper dive.

- -

Advanced Topics

- -

Misc

- - -
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/how-to-read-signatures.html b/archive/v0.2.1/how-to-read-signatures.html deleted file mode 100644 index 262d6d3d..00000000 --- a/archive/v0.2.1/how-to-read-signatures.html +++ /dev/null @@ -1,210 +0,0 @@ - - - - How to Read Signatures - Mithril - - - - - -
- -
-
-
-
-
- -
-

How to Read Signatures

-

Rather than providing concrete classes like other frameworks, Mithril provides methods that operate on plain old Javascript objects (POJOs) that match given signatures.

-

A signature is a description of its static type. For functions, it shows the parameters of the function, its return value and their expected types. For objects and arrays, it shows the expected data structure and the expected types of their members.

-

Method signatures in this documentation follow a syntax similar to Java syntax, with some extra additions:

-
ReturnType methodName(ParameterType1 param1, ParameterType2 param2)
-
-

Optional Parameters

-

Square brackets denote optional parameters. In the example below, param2 and param3 can both be omitted, but passing a value to param2 is required if also passing a value to param3:

-
String test(String arg1 [, String arg2 [, String arg3]])
-
-
//examples of valid function calls
-test("first");
-test("first", "second");
-test("first", "second", "third");
-
-

Type Placeholders

-

The word void is used as a type when a function does not return a value (i.e. undefined):

-
void test()
-
-
console.log(test()); // undefined
-
-

The word any is used as a type if there are no type restrictions on a parameter:

-
void test(any value)
-
-
//examples of valid function calls
-test("hello");
-test(1);
-test(["hello", "world"]);
-
-

Arrays

-

Arrays use Generics syntax to denote the expected type of array members:

-
void test(Array<String> values)
-
-
//example of a valid function call
-test(["first", "second"]);
-
-

Objects as Key-Value Maps

-

Objects also use Generics syntax when they are meant to be used as a key-value map. Keys are always strings and, in key-value maps, can have any name.

-
void test(Object<Number> values)
-
-
//example of a valid function call
-test({first: 1, second: 2});
-
-

Objects as Class Interfaces

-

Objects that require specific keys are denoted using curly brace syntax:

-
void test(Object {String first, Number second} value)
-
-
//example of a valid function call
-test({first: "first", second: 2});
-
-

Type Aliasing

-

Some types are aliases of more complex types. For example, in the example below, we created an alias called ComplexType for the type from the previous example

-
void test(ComplexType value)
-
-where:
-    ComplexType :: Object {String first, Number second}
-
-//example of a valid function call
-test({first: "first", second: 2})
-
-

Mixin Types

-

Curly brace syntax can also appear on other base types to denote that the value contains static members. For example, in the example below, a value of type ComplexType is a string, but it also has a boolean property called flag:

-
ComplexType :: String { Boolean flag }
-
-
//an example
-var a = aComplexTypeValue
-typeof a == "string" // true
-"flag" in a // true
-a.flag = true
-
-

In the following example, a value of type ComplexType is a function, with a property called label

-
ComplexType :: void test() { String label }
-
-
//an example
-var a = aComplexTypeValue
-typeof a == "function" // true
-"label" in a // true
-a.label = "first"
-
-

Polymorphic Types

-

When multiple (but not all) types are accepted, the pipe | is used to delimit the list of valid types

-
void test(Children children, Value value)
-
-where:
-    Children :: Array<String text | Number number>
-    Value :: String | Number
-
-
//examples of valid function calls
-test(["test", 2], "second")
-test([1, 2, 3], "second")
-test([1, "test", 3], 2)
-
-

Pipe syntax within Object curly brace syntax means that, for a specific key, name has specific type requirements.

-

In the example below, the value parameter should be a key-value map. This map may contain a key called config, whose value should be a function.

-
void test(Object { any | void config(DOMElement) } value)
-
-
//example of a valid function call
-test({ first: "first", config: function(element) { /*do stuff*/ } })
-
-

Nullable Types

-

A question mark ? after a type denotes that a value can be either of that type or undefined.

-
XMLHttpRequest? config()
-
-

In the example above, the config function is expected to return either an instance of the XMLHttpRequest object or undefined

-

Splat

-

An ellipsis ... means that an array argument can instead be a list of arguments

-
VirtualElement m(String selector [, Attributes attributes] [, Children... children])
-
-

In the example above, we can define children as an array:

-
m("div", {title: "foo"}, ["child 1", "child 2", "child 3"])
-
-

And we also define it as a list of arguments (note that lack of square brackets)

-
m("div", {title: "foo"}, "child 1", "child 2", "child 3")
-
- -
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/index.html b/archive/v0.2.1/index.html deleted file mode 100644 index 7babc427..00000000 --- a/archive/v0.2.1/index.html +++ /dev/null @@ -1,274 +0,0 @@ - - - - Mithril - - - - - -
- -
- -
-
-
-

Mithril

- -

A Javascript Framework for Building Brilliant Applications

- -

- Guide - Download v0.2.1 -

- - - - - - - Flattr - Change Log -
-
- -
-
-

What is Mithril?

-

Mithril is a client-side MVC framework - a tool to organize code in a way that is easy to think about and to maintain.

-
-
-
-

Light-weight

-
    -
  • Only 7kb gzipped, no dependencies
  • -
  • Small API, small learning curve
  • -
-
- -
-

Robust

-
    -
  • Safe-by-default templates
  • -
  • Hierarchical MVC via components
  • -
-
- -
-

Fast

-
    -
  • Virtual DOM diffing and compilable templates
  • -
  • Intelligent auto-redrawing system
  • -
-
-
-
- -
-
-
-

Sample code

- -

-//model
-var Page = {
-	list: function() {
-		return m.request({method: "GET", url: "pages.json"});
-	}
-};
-
-var Demo = {
-	//controller
-	controller: function() {
-		var pages = Page.list();
-		return {
-			pages: pages,
-			rotate: function() {
-				pages().push(pages().shift());
-			}
-		}
-	},
-
-	//view
-	view: function(ctrl) {
-		return m("div", [
-			ctrl.pages().map(function(page) {
-				return m("a", {href: page.url}, page.title);
-			}),
-			m("button", {onclick: ctrl.rotate}, "Rotate links")
-		]);
-	}
-};
-
-
-//initialize
-m.mount(document.getElementById("example"), Demo);
- -
-
-

Output

-
- - -
-
-
-
- -
-
-

Performance

-

To run the execution time tests below, click on their respective links, run the profiler from your desired browser's developer tools and measure the running time of a page refresh (Lower is better). Read more

-
-
-

Loading

- - - - - - -
Mithril 0.28ms
jQuery 13.11ms
Backbone 18.54ms
Angular 7.49ms
React 24.99ms
-
-
-

Rendering

- - - - - - -
Mithril 9.44ms (uncompiled)
jQuery 40.27ms
Backbone 23.05ms
Angular 118.63ms
React 79.65ms
-
-
-
-
- -
-
-
-
-

Safety

-

Mithril templates are safe by default, i.e. you can't unintentionally create security holes.

-

To run the tests for each framework, click on the respective links. If you see an alert box, ensuring security with that framework is more work for you.

-
-
-

Test Summary

- Mithril (pass) ✓
- jQuery (fail) ✗
- Backbone (fail) ✗
- Angular (pass) ✓
- React (pass) ✓
-
-
-
-
- -
-
-
-
-

Guide

-

Build a simple application. Learn the ropes

-

Read Guide

-
-
- -
-
-

API

-

Docs and code samples for your reference

-

Read docs

-
-
- -
-
-

Learn Mithril

-

Weekly articles on how to use Mithril to its full potential.

-

Read articles

-
-
- -
-
-

Mailing List

-

Post questions and discuss Mithril related topics.

-

Go to mailing list

-
-
-
-
- - -
-
-
- Released under the MIT license
- © 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/installation.html b/archive/v0.2.1/installation.html deleted file mode 100644 index 3782051e..00000000 --- a/archive/v0.2.1/installation.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - Installation - Mithril - - - - - -
- -
-
-
-
-
- -
-

Installation

-

Mithril is available from a variety of sources:

-
-

Direct download

-

You can download a zip of the latest version here.

-

Links to older versions can be found in the change log.

-

In order to use Mithril, extract it from the zip file and point a script tag to the .js file:

-
<script src="mithril.min.js"></script>
-
-

Note that in order to support older versions of IE, you need to include some shims.

-
-

CDNs (Content Delivery Networks)

-

You can also find Mithril in cdnjs and jsDelivr.

-

Content delivery networks allow the library to be cached across different websites that use the same version of the framework, and help reduce latency by serving the files from a server that is physically near the user's location.

-

cdnjs

-
<script src="//cdnjs.cloudflare.com/ajax/libs/mithril/0.2.1/mithril.min.js"></script>
-
-

jsDelivr

-
<script src="//cdn.jsdelivr.net/mithril/0.2.1/mithril.min.js"></script>
-
-
-

NPM

-

NPM is the default package manager for NodeJS. If you're using NodeJS already or planning on using Grunt to create a build system, you can use NPM to conveniently keep up-to-date with Mithril versions.

-

Assuming you have NodeJS installed, you can download Mithril by typing this:

-
npm install mithril
-

Then, to use Mithril, point a script tag to the downloaded file:

-
<script src="/node_modules/mithril/mithril.min.js"></script>
-
-
-

Bower

-

Bower is a frontend package manager built in NodeJS. If you're using NodeJS already or planning on using Grunt to create a build system, you can use Bower to conveniently keep up-to-date with Mithril versions.

-

Assuming you have NodeJS installed, you can install Bower by typing this in the command line:

-
npm install -g bower
-

And you can download Mithril by typing this:

-
bower install mithril
-

Then, to use Mithril, point a script tag to the downloaded file:

-
<script src="/bower_components/mithril/mithril.min.js"></script>
-
-
-

Component

-

Component is another package manager for NodeJS. If you're using NodeJS already or planning on using Grunt to create a build system, you can use Component to conveniently keep up-to-date with Mithril versions.

-

Assuming you have NodeJS installed, you can install Component by typing this in the command line:

-
npm install -g component
-

And you can download Mithril by typing this:

-
component install lhorie/mithril
-

Then, to use Mithril, point a script tag to the downloaded file:

-
<script src="/components/lhorie/mithril/master/mithril.js"></script>
-
-
-

Rails

-

Jordan Humphreys created a gem to allow integration with Rails:

-

Mithril-Rails

-

It includes support for the MSX HTML templating syntax from Jonathan Buchanan.

-
-

Github

-

You can also fork the latest stable project directly from Github.

-

If you want to use the bleeding edge version, you can fork the development repository.

-

Be aware that even though Mithril has tests running in a continuous integration environment, the bleeding edge version might occasionally break. If you're interested in helping improve Mithril, you're welcome to use the bleeding edge version and report any bugs that you find.

-

In order to update a forked version of Mithril, follow the instructions on this page.

-

Using bleeding edge from NPM

-

To use the bleeding edge version from npm, use the following command:

-
npm install git://github.com/lhorie/mithril.js#next --save
-
-
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/integration.html b/archive/v0.2.1/integration.html deleted file mode 100644 index d7c7cd67..00000000 --- a/archive/v0.2.1/integration.html +++ /dev/null @@ -1,175 +0,0 @@ - - - - Integrating with Other Libraries - Mithril - - - - - -
- -
-
-
-
-
- -
-

Integrating with Other Libraries

-

Integration with third party libraries or vanilla javascript code can be achieved via the config attribute of virtual elements.

-

It's recommended that you encapsulate integration code in a component or a helper function.

-

The example below shows a simple component that integrates with the select2 library.

-
var Select2 = {
-    //    Returns a select box
-    view: function(ctrl, attrs) {
-        var selectedId = attrs.value().id;
-        //Create a Select2 progrssively enhanced SELECT element
-        return m("select", {config: Select2.config(attrs)}, [
-            attrs.data.map(function(item) {
-                var args = {value: item.id};
-                //    Set selected option
-                if(item.id == selectedId) {
-                    args.selected = "selected";
-                }
-                return m("option", args, item.name);
-            })
-        ]);
-    },
-    /**
-    Select2 config factory. The params in this doc refer to properties of the `ctrl` argument
-    @param {Object} data - the data with which to populate the <option> list
-    @param {prop} value - the prop of the item in `data` that we want to select
-    @param {function(Object id)} onchange - the event handler to call when the selection changes.
-        `id` is the the same as `value`
-    */
-    //    Note: The config is never run server side.
-    config: function(ctrl) {
-        return function(element, isInitialized) {
-            if(typeof jQuery !== 'undefined' && typeof jQuery.fn.select2 !== 'undefined') {
-                var el = $(element);
-                if (!isInitialized) {
-                    el.select2()
-                        .on("change", function(e) {
-                            var id = el.select2("val");
-                            m.startComputation();
-                            //Set the value to the selected option
-                            ctrl.data.map(function(d){
-                                if(d.id == id) {
-                                    ctrl.value(d);
-                                }
-                            });
-
-                            if (typeof ctrl.onchange == "function"){
-                                ctrl.onchange(el.select2("val"));
-                            }
-                            m.endComputation();
-                        });
-                }
-                el.val(ctrl.value().id).trigger("change");
-            } else {
-                console.warn('ERROR: You need jquery and Select2 in the page');    
-            }
-        };
-    }
-};
-
-var Dashboard = {
-    controller: function() {
-        var ctrl = this,
-          //list of users to show
-          data = [
-            {id: 1, name: "John"}, 
-            {id: 2, name: "Mary"}, 
-            {id: 3, name: "Seniqua"}
-          ];
-
-        ctrl.data = data;
-        //  Has to use a prop for the current user
-        ctrl.currentUser = m.prop(data[1]);
-        ctrl.changeUser = function(id) {
-          console.log(id);
-        };
-    },
-
-    view: function(ctrl) {
-        return m("div", [
-            m("label", "User:"),
-            m.component(Select2, {
-              data: ctrl.data, 
-              value: ctrl.currentUser, 
-              onchange: ctrl.changeUser
-            })
-        ]);
-    }
-};
-
-m.mount(document.body, Dashboard);
-
-

select2.config is a factory that creates a config function based on a given controller. We declare this outside of the select2.view function to avoid cluttering the template.

-

The config function created by our factory only runs the initialization code if it hasn't already. This if statement is important, because this function may be called multiple times by Mithril's auto-redrawing system and we don't want to re-initialize select2 at every redraw.

-

The initialization code defines a change event handler. Because this handler is not created using Mithril's templating engine (i.e. we're not defining an attribute in a virtual element), we must manually integrate it to the auto-redrawing system.

-

This can be done by simply calling m.startComputation at the beginning, and m.endComputation at the end of the function. You must add a pair of these calls for each asynchronous execution thread, unless the thread is already integrated.

-

For example, if you were to call a web service using m.request, you would not need to add more calls to m.startComputation / m.endComputation (you would still need the first pair in the event handler, though).

-

On the other hand, if you were to call a web service using jQuery, then you would be responsible for adding a m.startComputation call before the jQuery ajax call, and for adding a m.endComputation call at the end of the completion callback, in addition to the calls within the change event handler. Refer to the auto-redrawing guide for an example.

-

One important note about the config method is that you should avoid calling m.redraw, m.startComputation and m.endComputation in the config function's execution thread. (An execution thread is basically any amount of code that runs before other asynchronous threads start to run.)

-

While Mithril technically does support this use case, relying on multiple redraw passes degrades performance and makes it possible to code yourself into an infinite execution loop situation, which is extremely difficult to debug.

-

The dashboard component in the example shows how a developer would consume the select2 component.

-

You should always document integration components so that others can find out what attribute parameters can be used to initialize the component.

-
-

Integrating to legacy code

-

If you need to add separate widgets to different places on a same page, you can simply initialize each widget as you would a regular Mithril application (i.e. use m.render, m.mount or m.route).

-

There's just one caveat: while simply initializing multiple "islands" in this fashion works, their initialization calls are not aware of each other and can cause redraws too frequently. To optimize rendering, you should add a m.startComputation call before the first widget initialization call, and a m.endComputation after the last widget initialization call in each execution thread.

-
m.startComputation()
-
-m.mount(document.getElementById("widget1-container"), Widget1)
-
-m.mount(document.getElementById("widget2-container"), Widget2)
-
-m.endComputation()
-
- -
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/lib/prism/prism.css b/archive/v0.2.1/lib/prism/prism.css deleted file mode 100644 index 1e61e11d..00000000 --- a/archive/v0.2.1/lib/prism/prism.css +++ /dev/null @@ -1,126 +0,0 @@ -/** - * prism.js default theme for JavaScript, CSS and HTML - * Based on dabblet (http://dabblet.com) - * @author Lea Verou - */ - -code[class*="language-"], -pre[class*="language-"] { - color: black; - text-shadow: 0 1px white; - font-family: Consolas, Monaco, 'Andale Mono', monospace; - direction: ltr; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - - - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - -pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, -code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { - text-shadow: none; - background: #b3d4fc; -} - -pre[class*="language-"]::selection, pre[class*="language-"] ::selection, -code[class*="language-"]::selection, code[class*="language-"] ::selection { - text-shadow: none; - background: #b3d4fc; -} - -@media print { - code[class*="language-"], - pre[class*="language-"] { - text-shadow: none; - } -} - -/* Code blocks */ -pre[class*="language-"] { - padding: 1em; - margin: .5em 0; - overflow: auto; -} - -:not(pre) > code[class*="language-"], -pre[class*="language-"] { - background: #f5f2f0; -} - -/* Inline code */ -:not(pre) > code[class*="language-"] { - padding: .1em; - border-radius: .3em; -} - -.token.comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: slategray; -} - -.token.punctuation { - color: #999; -} - -.namespace { - opacity: .7; -} - -.token.property, -.token.tag, -.token.boolean, -.token.number, -.token.constant, -.token.symbol { - color: #905; -} - -.token.selector, -.token.attr-name, -.token.string, -.token.builtin { - color: #690; -} - -.token.operator, -.token.entity, -.token.url, -.language-css .token.string, -.style .token.string, -.token.variable { - color: #a67f59; - background: hsla(0,0%,100%,.5); -} - -.token.atrule, -.token.attr-value, -.token.keyword { - color: #07a; -} - - -.token.regex, -.token.important { - color: #e90; -} - -.token.important { - font-weight: bold; -} - -.token.entity { - cursor: help; -} - diff --git a/archive/v0.2.1/lib/prism/prism.js b/archive/v0.2.1/lib/prism/prism.js deleted file mode 100644 index b7f84870..00000000 --- a/archive/v0.2.1/lib/prism/prism.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Prism: Lightweight, robust, elegant syntax highlighting - * MIT license http://www.opensource.org/licenses/mit-license.php/ - * @author Lea Verou http://lea.verou.me - */(function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{type:function(e){return Object.prototype.toString.call(e).match(/\[object (\w+)\]/)[1]},clone:function(e){var n=t.util.type(e);switch(n){case"Object":var r={};for(var i in e)e.hasOwnProperty(i)&&(r[i]=t.util.clone(e[i]));return r;case"Array":return e.slice()}return e}},languages:{extend:function(e,n){var r=t.util.clone(t.languages[e]);for(var i in n)r[i]=n[i];return r},insertBefore:function(e,n,r,i){i=i||t.languages;var s=i[e],o={};for(var u in s)if(s.hasOwnProperty(u)){if(u==n)for(var a in r)r.hasOwnProperty(a)&&(o[a]=r[a]);o[u]=s[u]}return i[e]=o},DFS:function(e,n){for(var r in e){n.call(e,r,e[r]);t.util.type(e)==="Object"&&t.languages.DFS(e[r],n)}}},highlightAll:function(e,n){var r=document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');for(var i=0,s;s=r[i++];)t.highlightElement(s,e===!0,n)},highlightElement:function(r,i,s){var o,u,a=r;while(a&&!e.test(a.className))a=a.parentNode;if(a){o=(a.className.match(e)||[,""])[1];u=t.languages[o]}if(!u)return;r.className=r.className.replace(e,"").replace(/\s+/g," ")+" language-"+o;a=r.parentNode;/pre/i.test(a.nodeName)&&(a.className=a.className.replace(e,"").replace(/\s+/g," ")+" language-"+o);var f=r.textContent;if(!f)return;f=f.replace(/&/g,"&").replace(/e.length)break e;if(p instanceof i)continue;a.lastIndex=0;var d=a.exec(p);if(d){l&&(c=d[1].length);var v=d.index-1+c,d=d[0].slice(c),m=d.length,g=v+m,y=p.slice(0,v+1),b=p.slice(g+1),w=[h,1];y&&w.push(y);var E=new i(u,f?t.tokenize(d,f):d);w.push(E);b&&w.push(b);Array.prototype.splice.apply(s,w)}}}return s},hooks:{all:{},add:function(e,n){var r=t.hooks.all;r[e]=r[e]||[];r[e].push(n)},run:function(e,n){var r=t.hooks.all[e];if(!r||!r.length)return;for(var i=0,s;s=r[i++];)s(n)}}},n=t.Token=function(e,t){this.type=e;this.content=t};n.stringify=function(e,r,i){if(typeof e=="string")return e;if(Object.prototype.toString.call(e)=="[object Array]")return e.map(function(t){return n.stringify(t,r,e)}).join("");var s={type:e.type,content:n.stringify(e.content,r,i),tag:"span",classes:["token",e.type],attributes:{},language:r,parent:i};s.type=="comment"&&(s.attributes.spellcheck="true");t.hooks.run("wrap",s);var o="";for(var u in s.attributes)o+=u+'="'+(s.attributes[u]||"")+'"';return"<"+s.tag+' class="'+s.classes.join(" ")+'" '+o+">"+s.content+""};if(!self.document){self.addEventListener("message",function(e){var n=JSON.parse(e.data),r=n.language,i=n.code;self.postMessage(JSON.stringify(t.tokenize(i,t.languages[r])));self.close()},!1);return}var r=document.getElementsByTagName("script");r=r[r.length-1];if(r){t.filename=r.src;document.addEventListener&&!r.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)}})();; -Prism.languages.markup={comment:/<!--[\w\W]*?-->/g,prolog:/<\?.+?\?>/,doctype:/<!DOCTYPE.+?>/,cdata:/<!\[CDATA\[[\w\W]*?]]>/i,tag:{pattern:/<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|\w+))?\s*)*\/?>/gi,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/gi,inside:{punctuation:/=|>|"/g}},punctuation:/\/?>/g,"attr-name":{pattern:/[\w:-]+/g,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/gi};Prism.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))});; -Prism.languages.clike={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,lookbehind:!0},string:/("|')(\\?.)*?\1/g,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/ig,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/g,"boolean":/\b(true|false)\b/g,"function":{pattern:/[a-z0-9_]+\(/ig,inside:{punctuation:/\(/}}, number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|(&){1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,ignore:/&(lt|gt|amp);/gi,punctuation:/[{}[\];(),.:]/g}; -; -Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(var|let|if|else|while|do|for|return|in|instanceof|function|get|set|new|with|typeof|try|throw|catch|finally|null|break|continue)\b/g,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g});Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,lookbehind:!0}});Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(<|<)script[\w\W]*?(>|>)[\w\W]*?(<|<)\/script(>|>)/ig,inside:{tag:{pattern:/(<|<)script[\w\W]*?(>|>)|(<|<)\/script(>|>)/ig,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript}}});; diff --git a/archive/v0.2.1/mithril.component.html b/archive/v0.2.1/mithril.component.html deleted file mode 100644 index a0ced17d..00000000 --- a/archive/v0.2.1/mithril.component.html +++ /dev/null @@ -1,587 +0,0 @@ - - - - m.component - Mithril - - - - - -
- -
-
-
-
-
- -
-

m.component

-
- -
-

Components are building blocks for Mithril applications. They allow developers to encapsulate functionality into reusable units.

-
-

Rendering components

-

In Mithril, a component is nothing more than an object that has a view function and, optionally, a controller function.

-
var MyComponent = {
-    controller: function(data) {
-        return {greeting: "Hello"}
-    },
-    view: function(ctrl) {
-        return m("h1", ctrl.greeting)
-    }
-}
-
-m.mount(document.body, MyComponent) // renders <h1>Hello</h1> into <body>
-
-

The optional controller function creates an object that may be used in the following recommended ways:

-
    -
  • It can contain methods meant to be called by a view.
  • -
  • It can call model methods directly or from methods inside the resulting object.
  • -
  • It can store contextual data returned from model methods (i.e. a promise from a request).
  • -
  • It can hold a reference to a view model.
  • -
-

The view has access to methods and properties that the controller chooses to expose in the returned object. With those methods and properties, it creates a template that can consume model data and call controller methods to affect the model. This is the recommended way for views and models to exchange data.

-
//a simple MVC example
-
-//a sample model that exposes a value
-var model = {count: 0}
-
-var MyComponent = {
-    controller: function(data) {
-        return {
-            increment: function() {
-                //This is a simplication for the sake of the example.
-                //Typically, values are modified via model methods,
-                //rather than modified directly
-                model.count++
-            }
-        }
-    },
-    view: function(ctrl) {
-        return m("a[href=javascript:;]", {
-            onclick: ctrl.increment //view calls controller method on click
-        }, "Count: " + model.count)
-    }
-}
-
-m.mount(document.body, MyComponent)
-
-//renders:
-//<a href="javascript:;">Count: 0</a>
-//
-//the number increments when the link is clicked
-
-

Note that there is no requirement to tightly couple a controller and view while organizing code. It's perfectly valid to define controllers and views separately, and only bring them together when mounting them:

-
//controller.js
-var controller = function(data) {
-    return {greeting: "Hello"}
-}
-
-//view.js
-var view = function(ctrl) {
-    return m("h1", ctrl.greeting)
-}
-
-//render
-m.mount(document.body, {controller: controller, view: view}) // renders <h1>Hello</h1>
-
-

There are three ways to render a component:

-
    -
  • m.route (if you are building a single-page application that has multiple pages)
  • -
  • m.mount (if your app only has one page)
  • -
  • m.render (if you are integrating Mithril's rendering engine into a larger framework and wish to manage redrawing yourself).
  • -
-

The controller function is called once when the component is rendered. Subsequently, the view function is called and will be called again anytime a redraw is required. The return value of the controller function is passed to the view as its first argument.

-

Optional controller

-

The controller function is optional and defaults to an empty function controller: function() {}

-
//a component without a controller
-var MyComponent = {
-    view: function() {
-        return m("h1", "Hello")
-    }
-}
-
-m.mount(document.body, MyComponent) // renders <h1>Hello</h1>
-
-

Controller as a class constructor

-

A controller can also be used as a class constructor (i.e. it's possible to attach properties to the this object within the constructor, instead of returning a value).

-
var MyComponent = {
-    controller: function(data) {
-        this.greeting = "Hello"
-    },
-    view: function(ctrl) {
-        return m("h1", ctrl.greeting)
-    }
-}
-
-m.mount(document.body, MyComponent) // renders <h1>Hello</h1>
-
-

Notes on the view function

-

The view function does not create a DOM tree when called. The return value of the view function is merely a plain Javascript data structure that represents a DOM tree. Internally, Mithril uses this data representation of the DOM to probe for data changes and update the DOM only where necessary. This rendering technique is known as virtual DOM diffing.

-

The view function is run again whenever a redraw is required (i.e. whenever event handlers are triggered by user input). Its return value is used to diff against the previous virtual DOM tree.

-

It may sound expensive to recompute an entire view any time there's a change to be displayed, but this operation actually turns out to be quite fast, compared to rendering strategies used by older frameworks. Mithril's diffing algorithm makes sure expensive DOM operations are performed only if absolutely necessary, and as an extra benefit, the global nature of the redraw makes it easy to reason about and troubleshoot the state of the application.

-

Shorthand syntax

-

If the first argument to m() is a component, it acts as an alias of m.component()

-
var MyComponent = {
-    controller: function() {
-        return {greeting: "hello"}
-    },
-    view: function(ctrl, args) {
-        return m("h1", ctrl.greeting + " " + args.data)
-    }
-}
-
-m.render(document.body, [
-    //the two lines below are equivalent
-    m(component, {data: "world"}),
-    m.component(component, {data: "world"})
-])
-
-
-

Parameterized components

-

Components can have arguments "preloaded". In practice, this means that calling m.component(MyComponent, {foo: "bar"}) will return a component that behaves exactly the same as MyComponent, but {foo: "bar"} will be bound as an argument to both the controller and view functions.

-
//declare a component
-var MyComponent = {
-    controller: function(args, extras) {
-        console.log(args.name, extras)
-        return {greeting: "Hello"}
-    },
-    view: function(ctrl, args, extras) {
-        return m("h1", ctrl.greeting + " " + args.name + " " + extras)
-    }
-}
-
-//create a component whose controller and view functions receive some arguments
-var component = m.component(MyComponent, {name: "world"}, "this is a test")
-
-var ctrl = new component.controller() // logs "world", "this is a test"
-
-m.render(document.body, component.view(ctrl)) // render the virtual DOM tree manually
-
-//<body><h1>Hello world this is a test</h1></body>
-
-

The first parameter after the component object is meant to be used as an attribute map and should be an object (e.g. {name: "world"}). Subsequent parameters have no restrictions (e.g. "this is a test")

-
-

Nesting components

-

Component views can include other components:

-
var App = {
-    view: function() {
-        return m(".app", [
-            m("h1", "My App"),
-
-            //nested component
-            m.component(MyComponent, {message: "Hello"})
-        ])
-    }
-}
-
-var MyComponent = {
-    controller: function(args) {
-        return {greeting: args.message}
-    },
-    view: function(ctrl) {
-        return m("h2", ctrl.greeting)
-    }
-}
-
-m.mount(document.body, App)
-
-// <div class="app">
-//      <h1>My App</h1>
-//      <h2>Hello</h2>
-// </div>
-
-

Components can be placed anywhere a regular element can. If you have components inside a sortable list, you should add key attributes to your components to ensure that DOM elements are not recreated from scratch, but merely moved when possible. Keys must be unique within a list of sibling DOM elements, and they must be either a string or a number:

-
var App = {
-    controller: function() {
-        return {data: [1, 2, 3]}
-    },
-    view: function(ctrl) {
-        return m(".app", [
-            //pressing the button reverses the list
-            m("button[type=button]", {onclick: function() {ctrl.data.reverse()}}, "My App"),
-
-            ctrl.data.map(function(item) {
-                //the key ensures the components aren't recreated from scratch, if they merely exchanged places
-                return m.component(MyComponent, {message: "Hello " + item, key: item})
-            })
-        ])
-    }
-}
-
-var MyComponent = {
-    controller: function(args) {
-        return {greeting: args.message}
-    },
-    view: function(ctrl) {
-        return m("h2", ctrl.greeting)
-    }
-}
-
-m.mount(document.body, App)
-
-

Dealing with state

-

Stateless components

-

A component is said to be stateless when it does not store data internally. Instead, it's composed of pure functions. It's a good practice to make components stateless because they are more predictable, and easier to reason about, test and troubleshoot.

-

Instead of copying arguments to the controller object and then passing the controller object to the view (thereby creating internal state in the component), it is often desirable that views update based on the current value of arguments initially passed to a component.

-

The following example illustrates this pattern:

-
var MyApp = {
-    controller: function() {
-        return {
-            temp: m.prop(10) // kelvin
-        }
-    },
-    view: function(ctrl) {
-        return m("div", [
-            m("input", {oninput: m.withAttr("value", ctrl.temp), value: ctrl.temp()}), "K",
-            m("br"),
-            m.component(TemperatureConverter, {value: ctrl.temp()})
-        ]);
-    }
-};
-var TemperatureConverter = {
-    controller: function() {
-        //note how the controller does not handle the input arguments
-
-        //define some helper functions to be called from the view
-        return {
-            kelvinToCelsius: function(value) {
-                return value - 273.15
-            },
-            kelvinToFahrenheit: function(value) {
-                return (9 / 5 * (value - 273.15)) + 32
-            }
-        }
-    },
-    view: function(ctrl, args) {
-        return m('div', [
-            "celsius:", ctrl.kelvinToCelsius(args.value),
-            m("br"),
-            "fahrenheit:", ctrl.kelvinToFahrenheit(args.value),
-        ]);
-    }
-};
-m.mount(document.body, MyApp);
-
-

In the example above, the text input is bi-directionally bound to a temp getter-setter. Changing the temperature value from the input updates the temperature value, which is passed to the TemperatureConverter view directly, and transformation functions are called from there. The TemperatureConverter controller never stores the value.

-

Testing the various parts of the component is trivial:

-
//test a transformation function in the controller
-var ctrl = new TemperatureConverter.controller();
-assert(ctrl.kelvinToCelsius(273.15) == 0)
-
-//test the template
-var tpl = TemperatureConverter.view(null, {value: 273.15})
-assert(tpl.children[1] == 0)
-
-//test with real DOM
-var testRoot = document.createElement("div")
-m.render(testRoot, TemperatureConverter.view(null, {value: 273.15}))
-assert(testRoot.innerHTML.indexOf("celsius:0") > -1)
-
-

Note that the sample component above is illustrative. Ideally, temperature conversion functions (and any functions that deal strictly within the domain of the data) should go in the model layer, not in a component's controller.

-
-

Stateful components

-

Usually it's recommended that you store application state outside of components (either in a view-model or in the top-level component in the case of nested components). Components can be stateful, but the purpose of component state is to prevent the pollution of the model layer with aspects that are inherently related to the component. For example, an autocompleter component may need to internally store a flag to indicate whether the dropdown is visible, but this kind of state is not relevant to an application's business logic.

-

You might also elect to maintain component state when it's not meaningful outside the scope of a single component. For example, you might have a UserForm component that lives alongside other unrelated components on a bigger page, but it probably doesn't make sense for the parent page to be aware of the unsaved user data stored within the UserForm component.

-
-

Parameterized initial state

-

The ability to handle arguments in the controller is useful for setting up the initial state for a component whose state depends on input data:

-
var MyComponent = {
-    controller: function(args) {
-        //we only want to make this call once
-        return {
-            things: m.request({method: "GET", url: "/api/things/", data: args}) //slice the data in some way
-        }
-    },
-    view: function(ctrl) {
-        return m("ul", [
-            ctrl.things().map(function(name) {
-                return m("li", thing.name)
-            })
-        ]);
-    }
-};
-
-

However, it's recommended that you aggregate all of your requests in a single place instead of scattering them across multiple components. Aggregating requests in a top-level component makes it easier to replay the request chain (i.e. fetching an updated list of items after you've saved something that changes that list), and it ensures the entire data set is loaded in memory before drilling down into nested components, avoiding redundant AJAX calls for sibling components that need the same data. Be sure to read the Application Architecture section to learn more about organizing componentized code.

-
-

Data-driven component identity

-

A component can be re-initialized from scratch by changing the key associated with it. This is useful for re-running ajax calls for different model entities.

-

Suppose we have a component called ProjectList and the following data:

-

-var people = [
-    {id: 1, name: "John"},
-    {id: 2, name: "Mary"}
-]
-
-//ajax and display a list of projects for John
-m.render(document.body, ProjectList({key: people[0].id, value: people[0]})
-
-//ajax and display a list of projects for Mary
-m.render(document.body, ProjectList({key: people[1].id, value: people[1]})
-
-

In the example above, since the key is different, the ProjectList component is recreated from scratch. As a result, the controller runs again, the DOM is re-generated, and any applicable 3rd party plugins in configs are re-initialized.

-

Remember that the rules for keys apply to components the same way they do to regular elements: it is not allowed to have duplicate keys on children of the same parent, and they must be either strings or numbers (or something with a .toString() implementation that makes the entity uniquely identifiable in the local scope when serialized). You can learn more about keys here.

-
-

Unloading components

-

If a component's controller contains the function onunload, it will be called under one of these circumstances:

-
    -
  • when a new call to m.mount updates the root DOM element of the component in question
  • -
  • when a route changes (if you are using m.route)
  • -
-

To unload/unmount a component without loading another component, you can simply call m.mount with a null as the component parameter:

-
m.mount(rootElement, null);
-
-

Often, you will want to do some work before the component is unloaded (i.e. clear timers or unsubscribe event handlers):

-
var MyComponent = {
-    controller: function() {
-        return {
-            onunload: function() {
-                console.log("unloading my component");
-            }
-        }
-    },
-    view: function() {
-        return m("div", "test")
-    }
-};
-
-m.mount(document.body, MyComponent);
-
-//...
-
-var AnotherComponent = {
-    view: function() {
-        return m("div", "another")
-    }
-};
-
-// mount on the same DOM element, replacing MyComponent
-m.mount(document.body, AnotherComponent); // logs "unloading my component"
-
-

You can also use the onunload function to PREVENT a component from being unloaded in the context of a route change (i.e. to alert a user to save their changes before navigating away from a page)

-
var component = {
-    controller: function() {
-        var unsaved = m.prop(false)
-        return {
-            unsaved: unsaved,
-
-            onunload: function(e) {
-                if (unsaved()) {
-                    e.preventDefault()
-                }
-            }
-        }
-    },
-    //...
-}
-
-

Normally, calling m.mount will return the controller instance for that component, but there's one corner case: if e.preventDefault() is called from a controller's onunload method, then the m.mount call will not instantiate the new controller, and will return undefined.

-

Mithril does not hook into the browser's onbeforeunload event. To prevent unloading when attempting to navigate away from a page, you can check the return value of m.mount

-
window.onbeforeunload = function() {
-    if (!m.mount(rootElement, null)) {
-        //onunload's preventDefault was called
-        return "Are you sure you want to leave?"
-    }
-}
-
-

Components that are nested inside other components can also call onunload and its e.preventDefault() like top-level components. The onunload event is called if an instantiated component is removed from a virtual element tree via a redraw.

-

In the example below, clicking the button triggers the component's onunload event and logs "unloaded!".

-
var MyApp = {
-    controller: function() {
-        return {loaded: true}
-    },
-    view: function(ctrl) {
-        return [
-            m("button[type=button]", {onclick: function() {ctrl.loaded = false}}),
-            ctrl.loaded ? MyComponent : ""
-        ]
-    }
-}
-
-var MyComponent = {
-    controller: function() {
-        return {
-            onunload: function() {
-                console.log("unloaded!")
-            }
-        }
-    },
-    view: function() {
-        return m("h1", "My component")
-    }
-}
-
-m.mount(document.body, MyApp)
-
-

Calling e.preventDefault() from a component's onunload function aborts route changes, but it does not abort rollback or affect the current redraw in any way.

-
-

Nested asynchronous components

-

Since controllers can call model methods, it's possible for nested components to encapsulate asynchronous behavior. When components aren't nested, Mithril waits for all asynchronous tasks to complete, but when components are nested, a component's parent view renders before the component completes its asynchronous tasks. The existence of the component only becomes known to the diff engine at the time when the template is rendered.

-

When a component has asynchronous payloads and they are queued by the auto-redrawing system, its view is NOT rendered until all asynchronous operations complete. When the component's asynchronous operations complete, another redraw is triggered and the entire template tree is evaluated again. This means that the virtual dom tree may take two or more redraws (depending on how many nested asynchronous components there are) to be fully rendered.

-

There are different ways to organize components that can side-step the need for multiple redraws. Regardless, you could also force multiple redraws to happen by using the background and initialValue options in m.request, or by manually calling m.redraw().

-

If a component A contains another component B that calls asynchronous services, when component A is rendered, a <placeholder> tag is rendered in place of component B until B's asynchronous services resolve. Once resolved, the placeholder is replaced with component B's view.

-
-

Limitations and caveats

-

One important limitation to be aware of when using components is that you cannot call Mithril's redrawing methods (m.startComputation / m.endComputation and m.redraw) from templates.

-

In addition, you cannot call m.request from templates. Doing so will trigger another redraw, which will result in an infinite loop.

-

There are a few other technical caveats when nesting components:

-
    -
  1. Nested component views must return either a virtual element or another component. Returning an array, a string, a number, boolean, falsy value, etc will result in an error.

    -
  2. -
  3. Nested components cannot change m.redraw.strategy from the controller constructor (but they can from event handlers). It's recommended that you use the ctx.retain flag instead of changing the redraw strategy in controller constructors.

    -
  4. -
  5. The root DOM element in a component's view must not be changed during the lifecycle of the component, otherwise undefined behavior will occur. In other words, don't do this:

    -
    var MyComponent = {
    -  view: function() {
    -      return someCondition ? m("a") : m("b")
    -  }
    -}
    -
    -
  6. -
  7. If a component's root element is a subtree directive on its first rendering pass, undefined behavior will occur.

    -
  8. -
-
-

Opting out of the auto redrawing system

-

Components can be rendered without enabling the auto-redrawing system, via m.render:

-
var MyComponent = {
-    controller: function() {
-        return {greeting: "Hello"}
-    },
-    view: function(ctrl) {
-        return m("h1", ctrl.greeting)
-    }
-}
-
-m.render(document.body, MyComponent)
-
-

However, using m.render is only recommended if you want to use Mithril as part of a larger framework that manages the rendering lifecycle on its own. The vast majority of times, it's advisable to use m.mount instead.

-
-

Signature

-

How to read signatures

-
Component component(Component component [, Object attributes [, any... args]])
-
-where:
-    Component :: Object { Controller, View }
-    Controller :: SimpleController | UnloadableController
-    SimpleController :: void controller([Object attributes [, any... args]])
-    UnloadableController :: void controller([Object attributes [, any... args]]) { prototype: void unload(UnloadEvent e) }
-    UnloadEvent :: Object {void preventDefault()}
-    View :: void view(Object controllerInstance [, Object attributes [, any... args]])
-
-
    -
  • Component component

    -

    A component is supposed to be an Object with two keys: controller and view. Each of these should point to a Javascript function. If a contoller is not specified, Mithril will automatically create an empty controller function.

    -
  • -
  • Object attributes

    -

    A key/value map of attributes that gets bound as an argument to both the controller and view functions of the component.

    -
  • -
  • any... args

    -

    Other arguments to be bound as arguments to both the controller and view functions

    -
  • -
  • returns Component parameterizedComponent

    -

    A component with arguments bound

    -
  • -
- -
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/mithril.computation.html b/archive/v0.2.1/mithril.computation.html deleted file mode 100644 index f9180a59..00000000 --- a/archive/v0.2.1/mithril.computation.html +++ /dev/null @@ -1,293 +0,0 @@ - - - - m.startComputation / m.endComputation - Mithril - - - - - -
- -
-
-
-
-
- -
-

m.startComputation / m.endComputation

-
- -
-

Typically, m.startComputation / m.endComputation don't need to be called from application space. These methods are only intended to be used by people who are writing libraries that do things asynchronously, or when calling vanilla javascript asynchronous functions from template config functions.

-

If you need to do custom asynchronous calls without using Mithril's API, and find that your views are not redrawing, you should consider using m.startComputation / m.endComputation so that Mithril can intelligently auto-redraw once your custom code finishes running.

-

In order to integrate an asynchronous code to Mithril's autoredrawing system, you should call m.startComputation BEFORE making an asynchronous call, and m.endComputation after the asynchronous callback completes.

-
//this service waits 1 second, logs "hello" and then notifies the view that
-//it may start redrawing (if no other asynchronous operations are pending)
-var doStuff = function() {
-    m.startComputation(); //call `startComputation` before the asynchronous `setTimeout`
-
-    setTimeout(function() {
-        console.log("hello");
-
-        m.endComputation(); //call `endComputation` at the end of the callback
-    }, 1000);
-};
-
-

To integrate synchronous code, call m.startComputation at the beginning of the method, and m.endComputation at the end.

-
window.onfocus = function() {
-    m.startComputation(); //call before everything else in the event handler
-
-    doStuff();
-
-    m.endComputation(); //call after everything else in the event handler
-}
-
-

For each m.startComputation call a library makes, it MUST also make one and ONLY one corresponding m.endComputation call.

-

You should not use these methods if your code is intended to run repeatedly (e.g. by using setInterval). If you want to repeatedly redraw the view without necessarily waiting for user input, you should manually call m.redraw within the repeatable context.

-

Note that failing to call endComputation after a respective startComputation call will halt the redrawing system. It's a good idea to wrap exception-prone code in a try block and call m.endComputation from within the respective finally block, in order to prevent rendering from halting.

-
window.onfocus = function() {
-    m.startComputation();
-
-    try {
-        doStuff();
-    }
-    finally {
-        m.endComputation(); //redraw regardless of whether `doStuff` threw errors
-    }
-}
-
-
-

How auto-redrawing works

-

The auto-redrawing system in Mithril is not affected by changes in values of m.prop getter-setters. Instead, Mithril relies on m.startComputation and m.endComputation calls to figure out when to redraw.

-

Mithril has an internal counter, which is incremented every time m.startComputation is called, and decremented every time m.endComputation is called. Once the counter reaches zero, Mithril redraws. Mithril internally calls this pair of functions when you call m.mount, m.route, m.request, and whenever an event defined with m() is triggered.

-

So calling m.request multiple times from a controller context increments the internal counter. Once each request completes, the counter is decremented. The end result is that Mithril waits for all requests to complete before attempting to redraw. This also applies for asynchronous functions called from 3rd party libraries or from vanilla javascript, if they call this pair of functions.

-

The reason Mithril waits for all asynchronous services to complete before redrawing is to avoid wasteful browser repaints, and to minimize the need for null reference checks in templates.

-

It's possible to opt out of the redrawing schedule by using the background option for m.request, or by simply not calling m.startComputation / m.endComputation when calling non-Mithril asynchronous functions.

-
//`background` option example
-var component = m.component({
-    controller: function() {
-        //setting `background` allows the component to redraw immediately, without waiting for the request to complete
-        m.request({method: "GET", url: "/foo", background: true})
-    },
-    //...
-})
-
-

It's also possible to modify the strategy that Mithril uses for any given redraw, by using m.redraw.strategy. Note that changing the redraw strategy only affects the next scheduled redraw. After that, Mithril resets the m.redraw.strategy flag to either "all" or "diff" depending on whether the redraw was due to a route change or whether it was triggered by some other action.

-
//diff when routing, instead of redrawing from scratch
-//this preserves the `<input>` element and its 3rd party plugin after route changes, since the `<input>` doesn't change
-var Component1 = m.component({
-    controller: function() {
-        m.redraw.strategy("diff")
-    },
-    view: function() {
-        return m("div", [
-            m("h1", "Hello Foo"),
-            m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
-        ])
-    }
-})
-
-var Component2 = m.component({
-    controller: function() {
-        m.redraw.strategy("diff")
-    },
-    view: function() {
-        return m("div", [
-            m("h1", "Hello Bar"),
-            m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
-        ])
-    }
-})
-
-m.route(document.body, "/foo", {
-    "/foo": Component1,
-    "/bar": Component2,
-})
-
-
//model
-var saved = false
-function save(e) {
-    if (e.keyCode == 13) {
-        //this causes a redraw, since event handlers active auto-redrawing by default
-        saved = true
-    }
-    else {
-        //we don't care about other keys, so don't redraw
-        m.redraw.strategy("none")
-    }
-}
-
-//view
-var view = function() {
-    return m("div", [
-        m("button[type=button]", {onkeypress: save}, "Save"),
-        saved ? "Saved" : ""
-    ])
-}
-
-
-

Difference between computation methods and m.redraw

-

The m.startComputation / m.endComputation pair is designed to be "stacked", i.e. multiple asynchronous services can each call this pair of functions to indicate that they want the redrawing algorithm to wait for them to finish before a redraw occurs. In contrast, m.redraw is "aggressive": it redraws as many times as it is called (with the caveat that redraws are batched if they occur less than one animation frame apart in time). In practice, this means that calling m.redraw may cause a redraw to happen before some AJAX calls have finished, which in turn, may cause null reference exceptions in templates that try to use the data from these requests without first checking that the data exists.

-

Therefore, using the computation methods is recommended in order to reduce the amount of intermediate redraws that would otherwise occur as multiple asynchronous services are resolved.

-

When computation methods are used dilligently and religiously, templates are never redrawn with incomplete data. However, it's important to always write conditional tests in templates to account for the possibility of nullables, because redraws may come to occur more aggressively than data is available (perhaps because a newly introduced 3rd party library calls m.redraw, or because you might want a more aggressive redraw policy to implement a specific feature down the road).

-

Defending against nullables can typically be achieved via the initialValue option in m.request and basic null checks (e.g. data ? m("div", data) : null).

-
-

Integrating multiple execution threads

-

When integrating with third party libraries, you might find that you need to call asynchronous methods from outside of Mithril's API.

-

In order to integrate non-trivial asynchronous code to Mithril's auto-redrawing system, you need to ensure all execution threads call m.startComputation / m.endComputation.

-

An execution thread is basically any amount of code that runs before other asynchronous threads start to run.

-

Integrating multiple execution threads can be done in a two different ways: in a layered fashion or in comprehensive fashion

-

Layered integration

-

Layered integration is recommended for modular code where many different APIs may be put together at the application level.

-

Below is an example where various methods implemented with a third party library can be integrated in layered fashion: any of the methods can be used in isolation or in combination.

-

Notice how doBoth repeatedly calls m.startComputation since that method calls both doSomething and doAnother. This is perfectly valid: there are three asynchronous computations pending after the jQuery.when method is called, and therefore, three pairs of m.startComputation / m.endComputation in play.

-
var doSomething = function(callback) {
-    m.startComputation(); //call `startComputation` before the asynchronous AJAX request
-
-    return jQuery.ajax("/something").done(function() {
-        if (callback) callback();
-
-        m.endComputation(); //call `endComputation` at the end of the callback
-    });
-};
-var doAnother = function(callback) {
-    m.startComputation(); //call `startComputation` before the asynchronous AJAX request
-
-    return jQuery.ajax("/another").done(function() {
-        if (callback) callback();
-        m.endComputation(); //call `endComputation` at the end of the callback
-    });
-};
-var doBoth = function(callback) {
-    m.startComputation(); //call `startComputation` before the asynchronous synchronization method
-
-    jQuery.when(doSomething(), doAnother()).then(function() {
-        if (callback) callback();
-
-        m.endComputation(); //call `endComputation` at the end of the callback
-    })
-};
-
-

Comprehensive integration

-

Comprehensive integration is recommended if integrating a monolithic series of asynchronous operations. In contrast to layered integration, it minimizes the number of m.startComputation / m.endComputation to avoid clutter.

-

The example below shows a convoluted series of AJAX requests implemented with a third party library.

-
var doSomething = function(callback) {
-    m.startComputation(); //call `startComputation` before everything else
-
-    jQuery.ajax("/something").done(function() {
-        doStuff();
-        jQuery.ajax("/another").done(function() {
-            doMoreStuff();
-            jQuery.ajax("/more").done(function() {
-                if (callback) callback();
-
-                m.endComputation(); //call `endComputation` at the end of everything
-            });
-        });
-    });
-};
-
-
-

Integrating to legacy code

-

If you need to add separate widgets to different places on a same page, you can simply initialize each widget as you would a regular Mithril application (i.e. use m.render, m.mount or m.route).

-

There's just one caveat: while simply initializing multiple "islands" in this fashion works, their initialization calls are not aware of each other and can cause redraws too frequently. To optimize rendering, you should add a m.startComputation call before the first widget initialization call, and a m.endComputation after the last widget initialization call in each execution thread.

-
m.startComputation()
-
-m.mount(document.getElementById("widget1-container"), Widget1)
-
-m.mount(document.getElementById("widget2-container"), Widget2)
-
-m.endComputation()
-

-

Signature

-

How to read signatures

-
void startComputation()
-
-
void endComputation()
-
- -
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/mithril.d.ts b/archive/v0.2.1/mithril.d.ts deleted file mode 100644 index 56d16901..00000000 --- a/archive/v0.2.1/mithril.d.ts +++ /dev/null @@ -1,907 +0,0 @@ -// Mithril type definitions for Typescript - -/** -* This is the module containing all the types/declarations/etc. for Mithril -*/ -declare module _mithril { - interface MithrilStatic { - /** - * Creates a virtual element for use with m.render, m.mount, etc. - * - * @param selector A simple CSS selector. May include SVG tags. Nested - * selectors are not supported. - * @param attributes Attributes to add. Any DOM attribute may be used - * as an attribute, although innerHTML and the like may be overwritten - * silently. - * @param children Child elements, components, and text to add. - * @return A virtual element. - * - * @see m.render - * @see m.mount - * @see m.component - */ - ( - selector: string, - attributes: MithrilAttributes, - ...children: Array | - MithrilComponent> - ): MithrilVirtualElement; - - /** - * Initializes a component for use with m.render, m.mount, etc. - * - * @param component A component. - * @param args Arguments to optionally pass to the component. - * @return A component. - * - * @see m.render - * @see m.mount - * @see m - */ - ( - component: MithrilComponent, - ...args: any[] - ): MithrilComponent; - - /** - * Creates a virtual element for use with m.render, m.mount, etc. - * - * @param selector A simple CSS selector. Nested selectors are not - * supported. - * @param children Child elements, components, and text to add. - * @return A virtual element. - * - * @see m.render - * @see m.mount - * @see m.component - */ - ( - selector: string, - ...children: Array | - MithrilComponent> - ): MithrilVirtualElement; - - /** - * Initializes a component for use with m.render, m.mount, etc. - * Shorthand for m.component. - * - * @param selector A component. - * @param args Arguments to optionally pass to the component. - * @return A component. - * - * @see m.render - * @see m.mount - * @see m.component - */ - ( - component: MithrilComponent, - ...args: any[] - ): MithrilComponent; - - /** - * Creates a getter-setter function that wraps a Mithril promise. Useful - * for uniform data access, m.withAttr, etc. - * - * @param promise A thennable to initialize the property with. It may - * optionally be a Mithril promise. - * @return A getter-setter function wrapping the promise. - * - * @see m.withAttr - */ - prop(promise: Thennable) : MithrilPromiseProperty; - - /** - * Creates a getter-setter function that wraps a simple value. Useful - * for uniform data access, m.withAttr, etc. - * - * @param value A value to initialize the property with - * @return A getter-setter function wrapping the value. - * - * @see m.withAttr - */ - prop(value: T): MithrilBasicProperty; - - /** - * Creates a getter-setter function that wraps a simple value. Useful - * for uniform data access, m.withAttr, etc. - * - * @return A getter-setter function wrapping the value. - * - * @see m.withAttr - */ - prop(): MithrilBasicProperty; - - /** - * Returns a event handler that can be bound to an element, firing with - * the specified property. - * - * @param property The property to get from the event. - * @param callback The handler to use the value from the event. - * @return A function suitable for listening to an event. - */ - withAttr( - property: string, - callback: (value: any) => void, - callbackThis: any - ): (e: Event) => any; - - /** - * @deprecated Use m.mount instead - */ - module( - rootElement: Node, - component: MithrilComponent - ): T; - - /** - * Mounts a component to a base DOM node. - * - * @param rootElement The base node. - * @param component The component to mount. - * @return An instance of the top-level component's controller - */ - mount( - rootElement: Node, - component: MithrilComponent - ): T; - - /** - * Initializes a component for use with m.render, m.mount, etc. - * - * @param selector A component. - * @param args Arguments to optionally pass to the component. - * @return A component. - * - * @see m.render - * @see m.mount - * @see m - */ - component( - component: MithrilComponent, - ...args: any[] - ): MithrilComponent; - - /** - * Trust this string of HTML. - * - * @param html The HTML to trust - * @return A String object instance with an added internal flag to mark - * it as trusted. - */ - trust(html: string): MithrilTrustedString; - - /** - * Render a virtual DOM tree. - * - * @param rootElement The base element/node to render the tree from. - * @param children One or more child nodes to add to the tree. - * @param forceRecreation If true, overwrite the entire tree without - * diffing against it. - */ - render( - rootElement: Element, - children: MithrilVirtualElement|MithrilVirtualElement[], - forceRecreation?: boolean - ): void; - - redraw: { - /** - * Force a redraw the active component. It redraws asynchronously by - * default to allow for simultaneous events to run before redrawing, - * such as the event combination keypress + input frequently used for - * input. - * - * @param force If true, redraw synchronously. - */ - (force?: boolean): void; - - strategy: { - /** - * Gets the current redraw strategy, which returns one of the - * following: - * - * "all" - recreates the DOM tree from scratch - * "diff" - recreates the DOM tree from scratch - * "none" - leaves the DOM tree intact - * - * This is useful for event handlers, which may want to cancel - * the next redraw if the event doesn't update the UI. - * - * @return The current strategy - */ - (): string; - - /** - * Sets the current redraw strategy. The parameter must be one of - * the following values: - * - * "all" - recreates the DOM tree from scratch - * "diff" - recreates the DOM tree from scratch - * "none" - leaves the DOM tree intact - * - * This is useful for event handlers, which may want to cancel - * the next redraw if the event doesn't update the UI. - * - * @param value The value to set - * @return The new strategy - */ - (value: string): string; - - /** - * @private - * Implementation detail - it's a MithrilBasicProperty instance - */ - toJSON(): string; - } - } - - route: { - /** - * Enable routing, mounting a controller based on the route. It - * automatically mounts the components for you, starting with the one - * specified by the default route. - * - * @param rootElement The element to mount the active controller to. - * @param defaultRoute The route to start with. - * @param routes A key-value mapping of pathname to controller. - */ - ( - rootElement: Element, - defaultRoute: string, - routes: MithrilRoutes - ): void; - - /** - * This allows m.route to be used as the `config` attribute for a - * virtual element, particularly useful for cases like this: - * - * ```ts - * // Note that the '#' is not required in `href`, thanks to the - * `config` setting. - * m("a[href='/dashboard/alicesmith']", {config: m.route}); - * ``` - */ - ( - element: Element, - isInitialized: boolean, - context?: MithrilContext, - vdom?: MithrilVirtualElement - ): void; - - /** - * Programmatically redirect to another route. - * - * @param path The route to go to. - * @param params Parameters to pass as a query string. - * @param shouldReplaceHistory Whether to replace the current history - * instead of adding a new one. - */ - (path: string, params?: any, shouldReplaceHistory?: boolean): void; - - /** - * Gets the current route. - * - * @return The current route. - */ - (): string; - - /** - * Gets a route parameter. - * - * @param key The key to get. - * @return The value associated with the parameter key. - */ - param(key: string): string; - - /** - * The current routing mode. This may be changed before calling - * m.route to change the part of the URL used to perform the routing. - * - * The value can be set to one of the following, defaulting to - * "hash": - * - * "search" - Uses the query string. This allows for named anchors to - * work on the page, but changes cause IE8 and lower to refresh the - * page. - * - * "hash" - Uses the hash. This is the only routing mode that does - * not cause page refreshes on any browser, but it does not support - * named anchors. - * - * "pathname" - Uses the URL pathname. This requires server-side - * setup to support bookmarking and page refreshes. It always causes - * page refreshes on IE8 and lower. Note that this requires that the - * application to be run from the root of the URL. - */ - mode: string; - - /** - * Serialize an object into a query string. - * - * @param data The data to serialize. - * @return The serialized string. - */ - buildQueryString(data: Object): String - - /** - * Parse a query string into an object. - * - * @param data The data to parse. - * @return The parsed object data. - */ - parseQueryString(data: String): Object - } - - /** - * Send a request to a server to server. Note that the `url` option is - * required. - * - * @param options The options to use - * @return A promise to the returned data for "GET" requests, or a void - * promise for any other request type. - * - * @see MithrilXHROptions for the available options. - */ - request(options: MithrilXHROptions): MithrilPromise; - - deferred: { - /** - * Create a Mithril deferred object. It behaves synchronously if - * possible, an intentional deviation from Promises/A+. Note that - * deferreds are completely separate from the redrawing system, and - * never trigger a redraw on their own. - * - * @return A new Mithril deferred instance. - * - * @see m.deferred.onerror for the error callback called for Error - * subclasses - */ - (): MithrilDeferred; - - /** - * A callback for all uncaught native Error subclasses in deferreds. - * This defaults to synchronously rethrowing all errors, a deviation - * from Promises/A+, but the behavior is configurable. To restore - * Promises/A+-compatible behavior. simply set this to a no-op. - */ - onerror(e: Error): void; - } - - /** - * Takes a list of promises or thennables and returns a Mithril promise - * that resolves once all in the list are resolved, or rejects if any of - * them reject. - * - * @param promises A list of promises to try to resolve. - * @return A promise that resolves to all the promises if all resolve, or - * rejects with the error contained in the first rejection. - */ - sync(promises: Thennable[]): MithrilPromise; - - /** - * Use this and endComputation if your views aren't redrawing after - * calls to third-party libraries. For integrating asynchronous code, - * this should be called before any asynchronous work is done. For - * synchronous code, this should be called at the beginning of the - * problematic segment. Note that these calls must be balanced, much like - * braces and parentheses. This is mostly used internally. Prefer - * m.redraw where possible, especially when making repeated calls. - * - * @see endComputation - * @see m.render - */ - startComputation(): void; - - /** - * Use startComputation and this if your views aren't redrawing after - * calls to third-party libraries. For integrating asynchronous code, - * this should be called after all asynchronous work completes. For - * synchronous code, this should be called at the end of the problematic - * segment. Note that these calls must be balanced, much like braces and - * parentheses. This is mostly used internally. Prefer m.redraw where - * possible, especially when making repeated calls. - * - * @see startComputation - * @see m.render - */ - endComputation(): void; - - /** - * This overwrites the internal version of window used by Mithril. - * It's mostly useful for testing, and is also used internally by - * Mithril to test itself. By default Mithril uses `window` for the - * dependency. - * - * @param mockWindow The mock to use for the window. - * @return The mock that was passed in. - */ - deps(mockWindow: Window): Window; - } - - interface MithrilTrustedString extends String { - /** @private Implementation detail. Don't depend on it. */ - $trusted: boolean; - } - - /** - * The interface for a virtual element. It's best to consider this immutable - * for most use cases. - * - * @see m - */ - interface MithrilVirtualElement { - /** - * A key to optionally associate with this element. - */ - key?: number; - - /** - * The tag name of this element. - */ - tag?: string; - - /** - * The attributes of this element. - */ - attrs?: MithrilAttributes; - - /** - * The children of this element. - */ - children?: Array|MithrilComponent>; - } - - /** - * An event passed by Mithril to unload event handlers. - */ - interface MithrilEvent { - /** - * Prevent the default behavior of scrolling the page and updating the - * URL on next route change. - */ - preventDefault(): void; - } - - /** - * A context object for configuration functions. - * - * @see MithrilElementConfig - */ - interface MithrilContext { - /** - * A function to call when the node is unloaded. Useful for cleanup. - */ - onunload?(): any; - - /** - * Set true if the backing DOM node needs to be retained between route - * changes if possible. Set false if this node needs to be recreated - * every single time, regardless of how "different" it is. - */ - retain?: boolean; - } - - /** - * This represents a callback function for a virtual element's config - * attribute. It's a low-level function useful for extra cleanup after - * removal from the tree, storing instances of third-party classes that - * need to be associated with the DOM, etc. - * - * @see MithrilAttributes - * @see MithrilContext - */ - interface MithrilElementConfig { - /** - * A callback function for a virtual element's config attribute. - * - * @param element The associated DOM element. - * @param isInitialized Whether this is the first call for the virtual - * element or not. - * @param context The associated context for this element. - * @param vdom The associated virtual element. - */ - ( - element: Element, - isInitialized: boolean, - context: MithrilContext, - vdom: MithrilVirtualElement - ): void; - } - - /** - * This represents the attributes available for configuring virtual elements, - * beyond the applicable DOM attributes. - * - * @see m - */ - interface MithrilAttributes { - /** - * The class name(s) for this virtual element, as a space-separated list. - */ - className?: string; - - /** - * The class name(s) for this virtual element, as a space-separated list. - */ - class?: string; - - /** - * A custom, low-level configuration in case this element needs special - * cleanup after removal from the tree. - * - * @see MithrilElementConfig - */ - config?: MithrilElementConfig; - } - - /** - * The basis of a Mithril controller instance. - */ - interface MithrilController { - /** - * An optional handler to call when the associated virtual element is - * destroyed. - * - * @param evt An associated event. - */ - onunload?(evt: MithrilEvent): any; - } - - /** - * This represents a controller function. - * - * @see MithrilControllerConstructor - */ - interface MithrilControllerFunction { - (): T; - } - - /** - * This represents a controller constructor. - * - * @see MithrilControllerFunction - */ - interface MithrilControllerConstructor { - new(): T; - } - - /** - * This represents a view factory. - */ - interface MithrilView { - /** - * Creates a view out of virtual elements. - */ - (ctrl: T): MithrilVirtualElement; - } - - /** - * This represents a Mithril component. - * - * @see m - * @see m.component - */ - interface MithrilComponent { - /** - * The component's controller. - * - * @see m.component - */ - controller: MithrilControllerFunction | - MithrilControllerConstructor; - - /** - * Creates a view out of virtual elements. - * - * @see m.component - */ - view(ctrl: T): MithrilVirtualElement; - } - - /** - * This is the base interface for property getter-setters - * - * @see m.prop - */ - interface MithrilProperty { - /** - * Gets the contained value. - * - * @return The contained value. - */ - (): T; - - /** - * Sets the contained value. - * - * @param value The new value to set. - * @return The newly set value. - */ - (value: T): T; - } - - /** - * This represents a non-promise getter-setter functions. - * - * @see m.prop which returns objects that implement this interface. - */ - interface MithrilBasicProperty extends MithrilProperty { - /** - * Makes this serializable to JSON. - */ - toJSON(): T; - } - - /** - * This represents a promise getter-setter function. - * - * @see m.prop which returns objects that implement this interface. - */ - interface MithrilPromiseProperty extends MithrilPromise, - MithrilProperty> { - /** - * Gets the contained promise. - * - * @return The contained value. - */ - (): MithrilPromise; - - /** - * Sets the contained promise. - * - * @param value The new value to set. - * @return The newly set value. - */ - (value: MithrilPromise): MithrilPromise; - - /** - * Sets the contained wrapped value. - * - * @param value The new value to set. - * @return The newly set value. - */ - (value: T): MithrilPromise; - } - - /** - * This represents a key-value mapping linking routes to components. - */ - interface MithrilRoutes { - /** - * The key represents the route. The value represents the corresponding - * component. - */ - [key: string]: MithrilComponent; - } - - /** - * This represents a Mithril deferred object. - */ - interface MithrilDeferred { - /** - * Resolve this deferred's promise with a value. - * - * @param value The value to resolve the promise with. - */ - resolve(value?: T): void; - - /** - * Reject this deferred with an error. - * - * @param value The reason for rejecting the promise. - */ - reject(reason?: any): void; - - /** - * The backing promise. - * - * @see MithrilPromise - */ - promise: MithrilPromise; - } - - /** - * This represents a thennable success callback. - */ - interface MithrilSuccessCallback { - (value: T): U | Thennable; - } - - /** - * This represents a thennable error callback. - */ - interface MithrilErrorCallback { - (value: Error): T | Thennable; - } - - /** - * This represents a thennable. - */ - interface Thennable { - then(success: (value: T) => U): Thennable; - then(success: (value: T) => U, error: (value: Error) => V): Thennable|Thennable; - catch?: (error: (value: Error) => U) => Thennable; - } - - /** - * This represents a Mithril promise object. - */ - interface MithrilPromise extends Thennable, MithrilProperty> { - /** - * Chain this promise with a simple success callback, propogating - * rejections. - * - * @param success The callback to call when the promise is resolved. - * @return The chained promise. - */ - then(success: MithrilSuccessCallback): MithrilPromise; - - /** - * Chain this promise with a success callback and error callback, without - * propogating rejections. - * - * @param success The callback to call when the promise is resolved. - * @param error The callback to call when the promise is rejected. - * @return The chained promise. - */ - then( - success: MithrilSuccessCallback, - error: MithrilErrorCallback - ): MithrilPromise | MithrilPromise; - - /** - * Chain this promise with a single error callback, without propogating - * rejections. - * - * @param error The callback to call when the promise is rejected. - * @return The chained promise. - */ - catch(error: MithrilErrorCallback): MithrilPromise | - MithrilPromise; - } - - /** - * This represents the available options for configuring m.request. - * - * @see m.request - */ - interface MithrilXHROptions { - /** - * This represents the HTTP method used, one of the following: - * - * - "GET" (default) - * - "POST" - * - "PUT" - * - "DELETE" - * - "HEAD" - * - "OPTIONS" - */ - method?: string; - - /** - * The URL to send the request to. - */ - url: string; - - /** - * The username for HTTP authentication. - */ - user?: string; - - /** - * The password for HTTP authentication. - */ - password?: string; - - /** - * The data to be sent. It's automatically serialized in the right format - * depending on the method (with exception of HTML5 FormData), and put in - * the appropriate section of the request. - */ - data?: any; - - /** - * Whether to run it in the background, i.e. true if it doesn't affect - * template rendering. - */ - background?: boolean; - - /** - * Set an initial value while the request is working, to populate the - * promise getter-setter. - */ - initialValue?: T; - - /** - * An optional preprocessor function to unwrap a successful response, in - * case the response contains metadata wrapping the data. - * - * @param data The data to unwrap. - * @return The unwrapped result. - */ - unwrapSuccess?(data: any): T; - - /** - * An optional preprocessor function to unwrap an unsuccessful response, - * in case the response contains metadata wrapping the data. - * - * @param data The data to unwrap. - * @return The unwrapped result. - */ - unwrapError?(data: any): T; - - /** - * An optional function to serialize the data. This defaults to - * `JSON.stringify`. - * - * @param dataToSerialize The data to serialize. - * @return The serialized form as a string. - */ - serialize?(dataToSerialize: any): string; - - /** - * An optional function to deserialize the data. This defaults to - * `JSON.parse`. - * - * @param dataToSerialize The data to parse. - * @return The parsed form. - */ - deserialize?(dataToDeserialize: string): any; - - /** - * An optional function to extract the data from a raw XMLHttpRequest, - * useful if the relevant data is in a response header or the status - * field. - * - * @param xhr The associated XMLHttpRequest. - * @param options The options passed to this request. - * @return string The serialized format. - */ - extract?(xhr: XMLHttpRequest, options: MithrilXHROptions): string; - - /** - * The parsed data, or its children if it's an array, will be passed to - * this class constructor if it's given, to parse it into classes. - * - * @param data The data to parse. - * @return The new instance for the list. - */ - type?: new (data: Object) => any; - - /** - * An optional function to run between `open` and `send`, useful for - * adding request headers or using XHR2 features such as the `upload` - * property. It is even possible to override the XHR altogether with a - * similar object, such as an XDomainRequest instance. - * - * @param xhr The associated XMLHttpRequest. - * @param options The options passed to this request. - * @return The new XMLHttpRequest, or nothing if the same one is kept. - */ - config?(xhr: XMLHttpRequest, options: MithrilXHROptions): any; - - /** - * For JSONP requests, this must be the string "jsonp". Otherwise, it's - * ignored. - */ - dataType?: string; - - /** - * For JSONP requests, this is the query string key for the JSONP - * request. This is useful for APIs that don't use common conventions, - * such as `www.example.com/?jsonpCallback=doSomething`. It defaults to - * `callback` for JSONP requests, and is ignored for any other kind of - * request. - */ - callbackKey?: string; - } -} - -declare var Mithril: _mithril.MithrilStatic; -declare var m: _mithril.MithrilStatic; - -declare module "mithril" { - export = m; -} diff --git a/archive/v0.2.1/mithril.deferred.html b/archive/v0.2.1/mithril.deferred.html deleted file mode 100644 index d7b449ec..00000000 --- a/archive/v0.2.1/mithril.deferred.html +++ /dev/null @@ -1,281 +0,0 @@ - - - - m.deferred - Mithril - - - - - -
- -
-
-
-
-
- -
-

m.deferred

-
- -
-

This is a low-level method in Mithril. It's a modified version of the Thenable API.

-

A deferred is an asynchrony monad. It exposes a promise property which can bind callbacks to build a computation tree.

-

The deferred object can then apply a value by calling either resolve or reject, which then dispatches the value to be processed to the computation tree.

-

Each computation function takes a value as a parameter and is expected to return another value, which in turns is forwarded along to the next computation function (or functions) in the tree.

-

The deferred object returned by m.deferred has two methods: resolve and reject, and one property called promise. The methods can be called to dispatch a value to the promise tree. The promise property is the root of the promise tree. It has a method then which takes a successCallback and a errorCallback callbacks. Calling the then method attaches the computations represented by successCallback and errorCallback to the promise, which will be called when either resolve or reject is called. The then method returns a child promise, which, itself, can have more child promises, recursively.

-

The promise object is actually a function - specifically, it's an m.prop getter-setter, which gets populated with the value returned by successCallback if the promise is resolved successfully.

-

Note that Mithril promises are not automatically integrated to its automatic redrawing system. If you wish to use third party asynchronous libraries (for example, jQuery.ajax), you should also consider using m.startComputation / m.endComputation if you want views to redraw after requests complete.

-
-

Usage

-
//standalone usage
-var greetAsync = function() {
-    var deferred = m.deferred();
-    setTimeout(function() {
-        deferred.resolve("hello");
-    }, 1000);
-    return deferred.promise;
-};
-
-greetAsync()
-    .then(function(value) {return value + " world"})
-    .then(function(value) {console.log(value)}); //logs "hello world" after 1 second
-
-
-

Retrieving a value via the getter-setter API

-

The promise object is actually a getter-setter function that gets populated when the promise is fulfilled.

-
//asynchronous service
-var greetAsync = function() {
-    var deferred = m.deferred();
-    setTimeout(function() {
-        deferred.resolve("hello");
-    }, 1000);
-    return deferred.promise;
-};
-
-//asynchronous consumer
-var greeting = greetAsync()
-var processed = greeting.then(function(value) {return value + " world"})
-
-console.log(greeting()) // undefined - because `deferred.resolve` has not been called yet
-
-setTimeout(function() {
-    //now `deferred.resolve` has been called
-    console.log(greeting()) // "hello"
-    console.log(processed()) // "hello world"
-}, 2000)
-
-
-

Integrating to the Mithril redrawing system

-

By default, promises are not integrated to the Mithril auto-redrawing system. When dealing with asynchronous functions, you must call [m.startComputation / m.endComputation] if you want the asynchronous payload to affect the view.

-
//asynchronous service
-var greetAsync = function() {
-    //tell Mithril to wait for this service to complete before redrawing
-    m.startComputation();
-
-    var deferred = m.deferred();
-    setTimeout(function() {
-        deferred.resolve("hello");
-
-        //the service is done, tell Mithril that it may redraw
-        m.endComputation();
-    }, 1000);
-    return deferred.promise;
-};
-
-

Some cases may not require a redraw upon completion of the asynchronous callbacks. In such cases, simply omit the m.startComputation/m.endComputation calls.

-

Some asynchronous operations might need to affect redrawing both before and after their completion. In those cases, you can call m.redraw instead of using m.startComputation/m.endComputation.

-
//asynchronous service
-var greetAsync = function() {
-    //don't wait for this service; redraw right away
-
-    var deferred = m.deferred();
-    setTimeout(function() {
-        deferred.resolve("hello");
-
-        //redraw again
-        m.redraw()
-    }, 1000);
-    return deferred.promise;
-};
-
-
-

Differences from Promises/A+

-

For the most part, Mithril promises behave as you'd expect a Promise/A+ promise to behave, but have one difference: Mithril promises attempt to execute synchronously if possible.

-

Synchronous execution

-

Mithril promises attempt to execute synchronously if possible. To illustrate the difference between Mithril and A+ promises, consider the code below:

-
var deferred = m.deferred()
-
-deferred.promise.then(function() {
-    console.log(1)
-})
-
-deferred.resolve("value")
-
-console.log(2)
-
-

In the example above, A+ promises are required to log 2 before logging 1, whereas Mithril logs 1 before 2. Typically resolve/reject are called asynchronously after the then method is called, so normally this difference does not matter.

-

There are a couple of reasons why Mithril runs callbacks synchronously. Conforming to the spec requires either a setImmediate polyfill (which is a significantly large library), or setTimeout (which is required to take at least 4 milliseconds per call, according to its specs). Neither of these trade-offs are acceptable, given Mithril's focus on nimbleness and performance.

-

Unchecked Error Handling

-

By default, Mithril does not swallow errors if these errors are subclasses of the Error class. Manually throwing an instance of the Error class itself (or any other objects or primitives) does trigger the rejection callback path as per the Promises/A+ spec.

-

This deviation from the spec is there to make it easier for developers to find common logical errors such as typos that lead to null reference exceptions. By default, the spec requires that all thrown errors trigger rejection, which result in silent failures if the developer forgets to explicitly handle the failure case.

-

For example, there is simply never a case where a developer would want to programmatically handle the error of accessing the property of a nullable entity without first checking for its existence. The only reasonable course of action to prevent the potential null reference exceptions in this case is to add the existence check in the source code. It is expected that such an error would bubble up to the console and display a developer-friendly error message and line number there.

-
m.request({method: "GET", url: "/things"})
-    .then(function(items) {
-        item.foreach(doSomething) //programmer error: typo will throw runtime error to the console
-    })
-
-

The other side of the coin is still supported: if a developer needs to signal an exceptional condition within a promise callback, they can manually throw a new Error (for example, if a validation rule failed, and there should be an error message displayed to the user).

-
var error = m.prop()
-m.request({method: "GET", url: "/user/:id", data: {id: 1}})
-    .then(function(user) {
-        if (!user.isAdmin) throw new Error("Sorry, you don't have permissions")
-    })
-    .then(null, error) //handle the application error: bind to a getter-setter for diplaying it on the template
-
-

Note that the default promise exception handling semantics can be modified. See the next section.

-
-

The exception monitor

-

Any time an exception is thrown inside a promise callback, Mithril calls m.deferred.onerror(e).

-

By default, this event handler rethrows the exception to the console if an error is a subclass of Error (but not an instance of Error itself). Otherwise it follows the Promises/A+ specifications. It does this because people expect unexpected errors like null reference exceptions to be thrown to the console for debugging purposes, and these errors are always subclasses of Error.

-

On the other hand, javascript developers rarely ever throw errors that are subclasses of Error, and for the purposes of application error handling, the underlying prototypal chain of the error class is typically not relevant.

-

The onerror function can be safely replaced if the default error monitoring semantics are not desired.

-
//swallow all errors
-m.deferred.onerror = function() {}
-
-//only log errors
-m.deferred.onerror = function(e) {console.error(e)}
-

-

Signature

-

How to read signatures

-
Deferred deferred() {void onerror(Error e)}
-
-where:
-    Deferred :: Object { Promise promise, void resolve(any value), void reject(any value) }
-    Promise :: GetterSetter { Promise then(any successCallback(any value), any errorCallback(any value)), Promise catch(any errorCallback(any value)) }
-    GetterSetter :: any getterSetter([any value])
-
-
    -
  • GetterSetter { Promise then([any successCallback(any value) [, any errorCallback(any value)]]) } promise

    -

    A promise has a method called then which takes two computation callbacks as parameters.

    -

    The then method returns another promise whose computations (if any) receive their inputs from the parent promise's computation.

    -

    A promise is also a getter-setter (see m.prop). After a call to either resolve or reject, it holds the result of the parent's computation (or the resolve/reject value, if the promise has no parent promises)

    -

    Promises also have a method called catch, which is equivalent to calling then(null, errorCallback)

    -
      -
    • Promise then([any successCallback(any value) [, any errorCallback(any value)]])

      -

      This method accepts two callbacks which process a value passed to the resolve and reject methods, respectively, and pass the processed value to the returned promise

      -
        -
      • any successCallback(any value) (optional)

        -

        The successCallback is called if resolve is called in the root deferred.

        -

        The default value (if this parameter is falsy) is the identity function function(value) {return value}

        -

        If this function returns undefined, then it passes the value argument to the next step in the thenable queue, if any

        -
      • -
      • any errorCallback(any value) (optional)

        -

        The errorCallback is called if reject is called in the root deferred.

        -

        The default value (if this parameter is falsy) is the identity function function(value) {return value}

        -

        If this function returns undefined, then it passes the value argument to the next step in the thenable queue, if any

        -
      • -
      • returns Promise promise

        -
      • -
      -
    • -
    -
  • -
  • void resolve(any value)

    -

    This method passes a value to the successCallback of the deferred object's child promise

    -
  • -
  • void reject(any value)

    -

    This method passes a value to the errorCallback of the deferred object's child promise

    -
  • -
  • -

    m.deferred.onerror

    -

    void onerror(Error e)

    -

    This method gets called every time an exception is thrown inside a promise callback. By default, it rethrows to the console if an error is a subclass of Error (but not an instance of Error itself). Otherwise it follows the Promises/A+ specifications.

    -
  • -
- -
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/mithril.deps.html b/archive/v0.2.1/mithril.deps.html deleted file mode 100644 index b2c34be2..00000000 --- a/archive/v0.2.1/mithril.deps.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - m.deps - Mithril - - - - - -
- -
-
-
-
-
- -
-

m.deps

-
- -
-

This function overwrites the reference to the window object that is used internally by Mithril. It is useful for injecting a mock window dependency for the purposes of testing and for running Mithril in non-browser environments. The mock object used by Mithril for its own test suite can be found in the development repo.

-

By default, Mithril uses window itself as the dependency. Note that Mithril only uses the mock object for browser APIs such as the DOM API and requestAnimationFrame, but relies on the environment for ECMAScript features like Object.keys.

-
-

Usage

-

Call it at the beginning of your test file to supply a mock window:

-
function testMithril(mockWindow) {
-    window = m.deps(mockWindow);
-
-    // Your tests here...
-}
-
-
-

Signature

-

How to read signatures

-
Window m.deps(Object window)
-
-where:
-    Window :: Object<any>
-
-
    -
  • Object Window

    -

    This should be either window or a mock of the window object.

    -

    Mithril uses certain window methods that will need to be made available for complete test coverage, depending on your application:

    -
      -
    • window.document
    • -
    • Mithril also uses certain methods on the DOM node object
    • -
    • window.requestAnimationFrame/window.cancelAnimationFrame
    • -
    • Falls back to window.setTimeout/window.clearTimeout
    • -
    • window.location
    • -
    • window.history
    • -
    • window.scrollTo
    • -
    • window.XMLHttpRequest
    • -
    -
  • -
  • returns Window

    -

    The returned window is the same as what is passed in.

    -
  • -
- -
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/mithril.html b/archive/v0.2.1/mithril.html deleted file mode 100644 index f3d7e1ba..00000000 --- a/archive/v0.2.1/mithril.html +++ /dev/null @@ -1,578 +0,0 @@ - - - - m - Mithril - - - - - -
- -
-
-
-
-
- -
-

m

-
- -
-

This is a convenience method to compose virtual elements that can be rendered via m.render().

-

You are encouraged to use CSS selectors to define virtual elements. See "Signature" section for details.

-
-

Usage

-

You can use simple tag selectors to make templates resemble HTML:

-
m("br"); //yields a virtual element that represents <br>
-
-m("div", "Hello"); //yields <div>Hello</div>
-
-m("div", {class: "container"}, "Hello"); //yields <div class="container">Hello</div>
-
-

Note that the output value from m() is not an actual DOM element. In order to turn the virtual element into a real DOM element, you must call m.render().

-
m.render(document.body, m("br")); //puts a <br> in <body>
-
-

You can also use more complex CSS selectors:

-
m(".container"); //yields <div class="container"></div>
-
-m("#layout"); //yields <div id="layout"></div>
-
-m("a[name=top]"); //yields <a name="top"></a>
-
-m("[contenteditable]"); //yields <div contenteditable></div>
-
-m("a#google.external[href='http://google.com']", "Google"); //yields <a id="google" class="external" href="http://google.com">Google</a>
-
-

Each m() call creates a virtual DOM element, that is, a Javascript object that represents a DOM element, and which is eventually converted into one.

-

You can, of course, nest virtual elements:

-
m("ul", [
-    m("li", "item 1"),
-    m("li", "item 2"),
-]);
-
-/*
-yields
-<ul>
-    <li>item 1</li>
-    <li>item 2</li>
-</ul>
-*/
-
-
-

The CSS selector syntax (e.g. a#google.external[href='http://google.com']) is meant to be used for declaring static attributes in the element, i.e. attribute values that don't change dynamically when the user interacts with the app.

-

The attributes argument (i.e. the second parameter in the m("div", {class: "container"}, "Hello") example) is meant to be used for attributes whose values we want to dynamically populate.

-

For example, let's say that you're generating a link from an entry that comes from a web service:

-
//assume the variable `link` came from a web service
-var link = {url: "http://google.com", title: "Google"}
-
-m("a", {href: link.url}, link.title); //yields <a href="http://google.com">Google</a>
-
-

Here's a less trivial example:

-
var links = [
-    {title: "item 1", url: "/item1"},
-    {title: "item 2", url: "/item2"},
-    {title: "item 3", url: "/item3"}
-];
-
-m.render(document.body, [
-    m("ul.nav", 
-        links.map(function(link) {
-            return m("li",
-                m("a", {href: link.url}, link.title) 
-            );
-        })
-    )
-]);
-
-

yields:

-
<body>
-    <ul class="nav">
-        <li><a href="/item1">item 1</a></li>
-        <li><a href="/item2">item 2</a></li>
-        <li><a href="/item3">item 3</a></li>
-    </ul>
-</body>
-
-

As you can see, flow control is done with vanilla Javascript. This allows the developer to abstract away any aspect of the template at will.

-
-

Note that you can use both Javascript property names and HTML attribute names to set values in the attributes argument, but you should pass a value of appropriate type. If an attribute has the same name in Javascript and in HTML, then Mithril assumes you're setting the Javascript property.

-
m("div", {class: "widget"}); //yields <div class="widget"></div>
-
-m("div", {className: "widget"}); //yields <div class="widget"></div>
-
-m("button", {onclick: alert}); //yields <button></button>, which alerts its event argument when clicked
-
-//note this uses the Javascript syntax (uppercase "O") for `readonly`
-//in order to set the boolean javascript property instead of the HTML attribute
-m("input", {readOnly: true}); //yields <input readonly />
-
-//using the HTML attribute name will call `setAttribute`, which may not be what you want
-m("input", {readonly: false}); //yields <input readonly="false" />, which is still readonly
-
-
-

Note that you can use JSON syntax if the attribute name you are setting has non-alphanumeric characters:

-
m("div", {"data-index": 1}); //yields <div data-index="1"></div>
-
-

You can set inline styles like this:

-
m("div", {style: {border: "1px solid red"}}); //yields <div style="border:1px solid red;"></div>
-
-

Note that in order to keep the framework lean, Mithril does not auto-append units like px or % to any values. Typically, you should not even be using inline styles to begin with (unless you are dynamically changing them).

-

Mithril also does not auto-camel-case CSS properties on inline style attributes, so you should use the Javascript syntax when setting them via Javascript objects:

-
m("div", {style: {textAlign: "center"}}); //yields <div style="text-align:center;"></div>
-m("div", {style: {cssFloat: "left"}}); //yields <div style="float:left;"></div>
-
-//this does not work
-m("div", {style: {"text-align": "center"}});
-m("div", {style: {float: "left"}});
-
-

You can find the Javascript syntax for all the CSS rules here.

-

You can, however, use CSS syntax when defining style rules as inline strings:

-
m("div[style='text-align:center']"); //yields <div style="text-align:center;"></div>
-
-

One caveat of using the CSS syntax is that it clobbers the style attribute in the DOM element on redraws, so this syntax is not appropriate if you need to use it in conjunction with 3rd party tools that modify the element's style outside of Mithril's templates (e.g. via config, which is explained below)

-
-

Binding to data

-

In order to stay flexible, Mithril doesn't provide helpers for bi-directional bindings out of the box. However, bindings can be implemented easily:

-
//a data store
-var name = m.prop("")
-
-//binding the data store in a view
-m("input", {oninput: m.withAttr("value", name), value: name()})
-
-

In the code above, the oninput event handler updates the name getter-setter, and the Mithril auto-redrawing system redraws the template in order to update the displayed value. You can read more about the m.prop getter-setter utility here and the m.withAttr event handler factory here. You can also learn how the redrawing system works here.

-

Note that Mithril always considers the model layer data to be canonical. This means that in the code below, the input on screen will overwritten by the model data any time a redraw happens:

-
//note that we are not updating the value of the `name` getter-setter via an event handler
-//redraws will always overwrite the current UI value with the value of `name()`
-m("input", {value: name()})
-
-

Expressiveness can be achieved using standard refactoring techniques:

-
//refactor the binding to a simple helper
-var binds = function(prop) {
-    return {oninput: m.withAttr("value", prop), value: prop()}
-}
-
-//a data store
-var name = m.prop("")
-
-//binding the data store in a view
-m("input", binds(name))
-
-

Here's an example of a more aggressive refactor:

-
//refactor the binding to a simple helper
-var input = function(prop) {
-    return m("input", {oninput: m.withAttr("value", prop), value: prop()})
-}
-
-//a data store
-var name = m.prop("")
-
-//binding the data store in a view
-input(name)
-
-

Alternatively, you can also explore other techniques in order to achieve better performance and expressiveness.

-
-

Using HTML entities

-

By default, Mithril escapes HTML strings in order to help prevent XSS attacks.

-
m("div", "&times;") //becomes <div>&amp;times;</div>
-
-

You can unescape trusted HTML strings by using m.trust

-
m("div", m.trust("&times;")) //becomes <div>&times;</div>
-
-
-

Accessing the real DOM element

-

You can define a non-HTML-standard attribute called config. This special parameter allows you to call methods on the DOM element after it gets created.

-

This is useful, for example, if you declare a canvas element and want to use the Javascript API to draw:

-
function draw(element, isInitialized, context) {
-    //don't redraw if we did once already
-    if (isInitialized) return;
-
-    var ctx = element.getContext("2d");
-    /* draws stuff */
-}
-
-var view = [
-    m("canvas", {config: draw})
-]
-
-//this creates the canvas element, and therefore, `isInitialized` is false
-m.render(document.body, view);
-
-//here, isInitialized is `true`
-m.render(document.body, view);
-
-

One common way of using config is in conjunction with m.route, which is an unobtrusive extension to links that allow Mithril's routing system to work transparently regardless of which routing mode is used.

-
//this link can use any of Mithril's routing system modes
-//(i.e. it can use either the hash, the querystring or the pathname as the router implementation)
-//without needing to hard-code any syntax (`#` or `?`) in the `href` attribute.
-m("a[href='/dashboard']", {config: m.route}, "Dashboard");
-
-

The config mechanism can also be used to put focus on form inputs, and call methods that would not be possible to execute via the regular attribute syntax.

-

Also note that the config callback only runs after a rendering lifecycle is done. Therefore, you should not use config to modify controller and model values, if you expect these changes to render immediately. Changes to controller and model values in this fashion will only render on the next m.render or m.mount call.

-

You can use this mechanism to attach custom event listeners to controller methods (for example, when integrating with third party libraries), but you are responsible for making sure the integration with Mithril's autoredrawing system is in place. See the integration guide for more information.

-

You can also use it to attach events to other elements (for example, window.onresize), but you should remove such event handlers via ctx.onunload to avoid surprises.

-
-

Persisting config data

-

The third argument for config allows you to map data to a virtual DOM element in a way that persists across redraws. This is useful when a config instantiates 3rd party classes and accesses the instance on redraws.

-

The example below shows a contrived redraw counter. In it, the count is stored in the context object and re-accessed on each redraw.

-
function alertsRedrawCount(element, isInit, context) {
-    if (!isInit) context.count = 0
-    alert(++context.count)
-}
-
-m("div", {config: alertsRedrawCount})
-
-
-

Destructors

-

If the context object that is passed to a config function has a property called onunload, this function will be called when the element gets detached from the document by Mithril's diff engine.

-

This is useful if there are cleanup tasks that need to be run when an element is destroyed (e.g. clearing setTimeout's, etc)

-
function unloadable(element, isInit, context) {
-    context.timer = setTimeout(function() {
-        alert("timed out!");
-    }, 1000);
-
-    context.onunload = function() {
-        clearTimeout(context.timer);
-        console.log("unloaded the div");
-    }
-};
-
-m.render(document, m("div", {config: unloadable}));
-
-m.render(document, m("a")); //logs `unloaded the div` and `alert` never gets called
-
-
-

Persisting DOM elements across route changes

-

When using the router, a route change recreates the DOM tree from scratch in order to unload plugins from the previous page. If you want to keep a DOM element intact across a route change, you can set the retain flag in the config's context object.

-

In the example below, there are two routes, each of which loads a component when a user navigates to their respective URLs. Both components use a menu template, which contains links for navigation between the two components, and an expensive-to-reinitialize element. Setting context.retain = true in the element's config function allows the span to stay intact after a route change.

-
//a menu template
-var menu = function() {
-    return m("div", [
-        m("a[href='/']", {config: m.route}, "Home"),
-        m("a[href='/contact']", {config: m.route}, "Contact"),
-        //an expensive-to-initialize DOM element
-        m("span", {config: persistent})
-    ])
-}
-//a configuration that persists across route changes
-function persistent(el, isInit, context) {
-    context.retain = true
-
-    if (!isInit) {
-        //only runs once, even if you move back and forth between `/` and `/contact`
-        doSomethingExpensive(el)
-    }
-}
-
-//components that use the menu above
-var Home = {
-    controller: function() {},
-    view: function() {
-        return m("div", [
-            menu(),
-            m("h1", "Home")
-        ])
-    }
-}
-var Contact = {
-    view: function() {
-        return m("div", [
-            menu(),
-            m("h2", "Contact")
-        ])
-    }
-}
-
-m.route(document.body, "/", {
-    "/": Home,
-    "/contact": Contact
-})
-
-

Note that even if you set context.retain = true, the element will still be destroyed and recreated if it is different enough from the existing element. An element is considered "different enough" if:

-
    -
  • the tag name changes, or
  • -
  • the list of HTML attributes changes, or
  • -
  • the value of the element's id attribute changes
  • -
-

In addition, setting context.retain = false will also cause the element to be recreated, even if it is not considered different enough.

-
-

SVG

-

You can use Mithril to create SVG documents (as long as you don't need to support browsers that don't support SVG natively).

-

Mithril automatically figures out the correct XML namespaces when it sees an SVG island in the virtual DOM tree.

-
m("svg[height='200px'][width='200px']", [
-    m("image[href='foo.jpg'][height='200px'][width='200px']")
-])
-
-
-

Dealing with focus

-

The virtual DOM diffing algorithm has a weakness: a naive diff is not aware of the identity of DOM elements. In practice, this means performing operations like shifting an item from the beginning of a list would cause every element in the list to be diffed and potentially recreated. Another side-effect is that UI state like input focus is not tracked correctly if the focused element moves around, and likewise, state for 3rd party plugins that are added via config can also end up in the wrong element.

-

Fortunately, with Mithril, it's possible for developers to attach an identity key to elements so that array operations like shift, splice and sort only affect the minimum amount of elements required, leaving the rest of the DOM elements untouched when a redraw happens. This allows us to maintain input focus and plugin state correctly.

-

To maintain the identities of DOM elements, you need to add a key property to the direct children of the array that you're planning to modify. The key for each child must be unique among a list of sibling DOM elements, but it does not need to be globally unique. Also, keys must be either strings or numbers.

-
m("ul", [
-    items.map(function(item) {
-        return m("li", {key: item.id}, [
-            m("input")
-        ]);
-    })
-]);
-
-

In the example above, input focus would be maintained correctly after a redraw even if items got sorted or reversed. The key is defined in the li, which is the closest element to the items array, not directly on the input, even though we want to track focus on the input.

-

Note that in addition to the presence of the key attribute, diffing rules also apply in determining whether an element is recreated. Elements are recreated if either their node name changes, or if the list of attribute names change, or if the ID attribute changes. To avoid surprises, be sure to change only attribute values, using undefined or null as values if appropriate, rather than conditionally substituting attribute dictionaries altogether.

-
//avoid using this idiom
-m("li", selected ? {class: "active"} : {})
-
-//use this idiom instead
-m("li", {class: selected ? "active" : ""})
-
-
-

Dealing with sorting and deleting in lists

-

As with input focus, we can maintain referential integrity between data in a list and the respective DOM representation by using keys.

-
m("ul", [
-    items.map(function(item) {
-        return m("li", {key: item.id}, [
-            m("input")
-        ]);
-    })
-]);
-
-

You should always use keys if you need to sort lists, remove items from them or splice them in any way.

-
-

Component Shorthand

-

If the first argument to m() is a component, it acts as an alias of m.component()

-
var MyComponent = {
-    controller: function() {
-        return {greeting: "hello"}
-    },
-    view: function(ctrl, args) {
-        return m("h1", ctrl.greeting + " " + args.data)
-    }
-}
-
-m.render(document.body, [
-    //the two lines below are equivalent
-    m(component, {data: "world"}),
-    m.component(component, {data: "world"})
-])
-
-

See components for more information.

-
-

Signature

-

How to read signatures

-
VirtualElement m(String selector [, Attributes attributes] [, Children... children])
-
-where:
-    VirtualElement :: Object { String tag, Attributes attributes, Children children }
-    Attributes :: Object<any | void config(DOMElement element, Boolean isInitialized, Object context, VirtualElement vdom)>
-    Children :: String text | VirtualElement virtualElement | Component | SubtreeDirective directive | Array<Children children>
-    Component :: Object { Function? controller, Function view }
-    SubtreeDirective :: Object { String subtree }
-
-
    -
  • String selector

    -

    This string should be a CSS rule that represents a DOM element.

    -

    Only tag, id, class and attribute selectors are supported.

    -

    If the tag selector is omitted, it defaults to div.

    -

    Note that if the same attribute is defined in the both selector and attributes parameters, the value in attributes is used.

    -

    For developer convenience, Mithril makes an exception for the class attribute: if there are classes defined in both parameters, they are concatenated as a space separated list. It does not, however, de-dupe classes if the same class is declared twice.

    -

    Examples:

    -

    "div"

    -

    "#container"

    -

    ".active"

    -

    "[title='Application']"

    -

    "div#container.active[title='Application']"

    -

    ".active#container"

    -
  • -
  • Attributes attributes (optional)

    -

    This key-value map should define a list of HTML attributes and their respective values.

    -

    You can use both HTML and Javascript attribute names. For example, both class and className are valid.

    -

    Values' types should match the expected type for the respective attribute.

    -

    For example, the value for className should be a string.

    -

    When a attribute name expects different types for the value in HTML and Javascript, the Javascript type should be used.

    -

    For example, the value for the onclick attribute should be a function.

    -

    Similar, setting the value of attribute readonly to false is equivalent to removing the attribute in HTML.

    -

    It's also possible to set values to Javascript-only properties, such as hash in a <a> element.

    -

    Note that if the same attribute is defined in the both selector and attributes parameters, the value in attributes is used.

    -

    For developer convenience, Mithril makes an exception for the class attribute: if there are classes defined in both parameters, they are concatenated as a space separated list. It does not, however, de-dupe classes if the same class is declared twice.

    -

    Examples:

    -

    { title: "Application" }

    -

    { onclick: function(e) { /*do stuff*/ } }

    -

    { style: {border: "1px solid red"} }

    -
  • -
  • The config attribute

    -

    void config(DOMElement element, Boolean isInitialized, Object context, VirtualElement vdom) (optional)

    -

    You can define a non-HTML-standard attribute called config. This special parameter allows you to call methods on the DOM element after it gets created.

    -

    This is useful, for example, if you declare a canvas element and want to use the Javascript API to draw:

    -
    function draw(element, isInitialized) {
    -   //don't redraw if we did once already
    -   if (isInitialized) return;
    -
    -   var ctx = element.getContext("2d");
    -   /* draws stuff */
    -}
    -
    -var view = [
    -   m("canvas", {config: draw})
    -]
    -
    -//this creates the canvas element, and therefore, `isInitialized` is false
    -m.render(document.body, view);
    -
    -//here, isInitialized is `true`
    -m.render(document.body, view);
    -
    -

    One common way of using config is in conjunction with m.route, which is an unobtrusive extension to links that allow Mithril's routing system to work transparently regardless of which routing mode is used.

    -
    //this link can use any of Mithril's routing system modes
    -//(i.e. it can use either the hash, the querystring or the pathname as the router implementation)
    -//without needing to hard-code any syntax (`#` or `?`) in the `href` attribute.
    -m("a[href='/dashboard']", {config: m.route}, "Dashboard");
    -
    -

    The config mechanism can also be used to put focus on form inputs, and call methods that would not be possible to execute via the regular attribute syntax.

    -

    Also note that the config callback only runs after a rendering lifecycle is done. Therefore, you should not use config to modify controller and model values, if you expect these changes to render immediately. Changes to controller and model values in this fashion will only render on the next m.render or m.mount call.

    -

    You can use this mechanism to attach custom event listeners to controller methods (for example, when integrating with third party libraries), but you are responsible for making sure the integration with Mithril's autoredrawing system is in place. See the integration guide for more information.

    -

    You can also use it to attach events to other elements (for example, window.onresize), but you should remove such event handlers via ctx.onunload to avoid surprises.

    -
      -
    • DOMElement element
    • -
    -

    The DOM element that corresponds to virtual element defined by the m() call.

    -
      -
    • Boolean isInitialized
    • -
    -

    Whether this is the first time we are running this function on this element. This flag is false the first time it runs on an element, and true on redraws that happen after the element has been created.

    -
      -
    • Object context
    • -
    -

    An object that retains its state across redraws. It can be used to store instances of 3rd party classes that need to be accessed more than one time throughout the lifecycle of a page.

    -

    The example below shows a contrived redraw counter. In it, the count is stored in the context object and re-accessed on each redraw.

    -
    function alertsRedrawCount(element, isInit, context) {
    -   if (!isInit) context.count = 0
    -   alert(++context.count)
    -}
    -
    -m("div", {config: alertsRedrawCount})
    -
    -

    If the context object that is passed to a config function has a property called onunload, this function will be called when the element gets detached from the document by Mithril's diff engine.

    -

    This is useful if there are cleanup tasks that need to be run when an element is destroyed (e.g. clearing setTimeout's, etc)

    -
    function unloadable(element, isInit, context) {
    -   context.timer = setTimeout(function() {
    -       alert("timed out!");
    -   }, 1000);
    -
    -   context.onunload = function() {
    -       clearTimeout(context.timer);
    -       console.log("unloaded the div");
    -   }
    -};
    -
    -m.render(document, m("div", {config: unloadable}));
    -
    -m.render(document, m("a")); //logs `unloaded the div` and `alert` never gets called
    -
    -
      -
    • VirtualElement vdom
    • -
    -

    The virtual DOM element to which the config function is attached

    -
  • -
  • Children children (optional)

    -

    If this argument is a string, it will be rendered as a text node. To render a string as HTML, see m.trust

    -

    If it's a VirtualElement, it will be rendered as a DOM Element.

    -

    If it's a component, the component will be instantiated and managed internally by Mithril

    -

    If it's a list, its contents will recursively be rendered as appropriate and appended as children of the element being created.

    -

    If it's a SubtreeDirective with the value "retain", it will retain the existing DOM tree in place, if any. See subtree directives.html for more information.

    -
  • -
  • returns VirtualElement

    -

    The returned VirtualElement is a Javascript data structure that represents the DOM element to be rendered by m.render

    -
  • -
- -
-
-
-
-
-
-
- Released under the MIT license -
© 2014 Leo Horie -
-
- - - diff --git a/archive/v0.2.1/mithril.js b/archive/v0.2.1/mithril.js deleted file mode 100644 index 656ea6e5..00000000 --- a/archive/v0.2.1/mithril.js +++ /dev/null @@ -1,1408 +0,0 @@ -var m = (function app(window, undefined) { - "use strict"; - var VERSION = "v0.2.1"; - 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]"; - }; - var type = {}.toString; - var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/; - var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/; - var noop = function () {}; - - // caching commonly used variables - var $document, $location, $requestAnimationFrame, $cancelAnimationFrame; - - // self invoking function needed because of the way mocks work - function initialize(window) { - $document = window.document; - $location = window.location; - $cancelAnimationFrame = window.cancelAnimationFrame || window.clearTimeout; - $requestAnimationFrame = window.requestAnimationFrame || window.setTimeout; - } - - initialize(window); - - 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 - */ - - /** - * - * @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 = pairs != null && isObject(pairs) && !("tag" in pairs || "view" in pairs || "subtree" in pairs); - var attrs = hasAttrs ? pairs : {}; - var classAttrName = "class" in attrs ? "class" : "className"; - var cell = {tag: "div", attrs: {}}; - var match, classes = []; - if (!isString(tag)) throw new Error("selector in m(selector, attrs, children) should be a string"); - 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); - } - } - - var children = hasAttrs ? args.slice(1) : args; - if (children.length === 1 && isArray(children[0])) { - cell.children = children[0]; - } - else { - cell.children = children; - } - - for (var attrName in attrs) { - if (attrs.hasOwnProperty(attrName)) { - if (attrName === classAttrName && attrs[attrName] != null && attrs[attrName] !== "") { - classes.push(attrs[attrName]); - cell.attrs[attrName] = ""; //create key in correct iteration order - } - else cell.attrs[attrName] = attrs[attrName]; - } - } - if (classes.length) cell.attrs[classAttrName] = classes.join(" "); - - return cell; - } - function forEach(list, f) { - for (var i = 0; i < list.length && !f(list[i], i++);) {} - } - 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 Firefox (behavior depends on version) - try { - if (data == null || data.toString() == null) return ""; - } catch (e) { - return ""; - } - return data; - } - // 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, INSERTION = 2, 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) actions.push(existing[prop]); - var changes = actions.sort(sortChanges), 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; - }); - } - - return keysDiffer ? handleKeysDiffer(data, existing, cached, parentElement) : 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 maybeRecreateObject(data, cached, dataAttrKeys) { - //if an element is different enough from the one in cache, recreate it - if (data.tag !== cached.tag || - dataAttrKeys.sort().join() !== Object.keys(cached.attrs).sort().join() || - data.attrs.id !== cached.attrs.id || - data.attrs.key !== cached.attrs.key || - (m.redraw.strategy() === "all" && (!cached.configContext || cached.configContext.retain !== true)) || - (m.redraw.strategy() === "diff" && cached.configContext && cached.configContext.retain === false)) { - 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; - } - - 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 = typeof data === "string" || typeof data === "number" || typeof data === "boolean" ? new data.constructor(data) : 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") { - //