Merge remote-tracking branch 'origin/next' into es6-promise

Conflicts:
	mithril.js
This commit is contained in:
Leo Horie 2014-09-18 22:26:13 -04:00
commit ac4863ff7c
9 changed files with 172 additions and 34 deletions

View file

@ -8,6 +8,7 @@
- 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:

View file

@ -74,6 +74,71 @@ The reason Mithril waits for all asynchronous services to complete before redraw
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.
```javascript
//`background` option example
var module = {}
module.controller = function() {
//setting `background` allows the module 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`](mithril.redraw.md#changing-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.
```javascript
//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 module1 = {}
module1.controller = function() {
m.redraw.strategy("diff")
}
module1.view = function() {
return [
m("h1", "Hello Foo"),
m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
]
}
var module2 = {}
module2.controller = function() {
m.redraw.strategy("diff")
}
module2.view = function() {
return [
m("h1", "Hello Bar"),
m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
]
}
m.route(document.body, "/foo", {
"/foo": module1,
"/bar": module2,
})
```
```javascript
//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("button[type=button]", {onkeypress: save}, "Save"),
saved ? "Saved" : ""
]
}
```
---
### Integrating multiple execution threads

View file

@ -181,5 +181,6 @@ where:
Components are nothing more than decoupled classes that can be dynamically brought together as required. This permits the swapping of implementations at a routing level (for example, if implementing widgetized versions of existing components), and class dependency hierarchies can be structurally organized to provide uniform interfaces (for unit tests, for example).
- **returns Object controllerInstance**
An instance of the controller constructor

View file

@ -69,10 +69,11 @@ m.request({method: "GET", url: "/users"})
### Third-party promise library support
If a promise is passed into `m.prop()`, its value will populate the prop after resolution.
If a promise is passed into `m.prop()`, a Mithril promise is returned. Mithril promises are also getter-setter functions, which are populated with the resolved value if the promise is fulfilled successfully.
Until the promise is resolved, the value of the prop will resolve to `undefined`
Example using [Q](https://github.com/kriskowal/q)
Here's an example using the [Q](https://github.com/kriskowal/q) promise library:
```javascript
var deferred = Q.defer()
@ -82,6 +83,9 @@ users() // undefined
deferred.resolve("Hello")
users() // Hello
users.then(function(value) {
console.log(value) //Hello
})
```
---

View file

@ -31,6 +31,10 @@ If you are developing an asynchronous model-level service and finding that Mithr
If you need to change how Mithril performs redraws, you can change the value of the `m.redraw.strategy` getter-setter to either `"all"`, `"diff"` or `"none"`. By default, this value is set to `"all"` when running controller constructors, and it's set to `"diff"` for all subsequent redraws.
The strategy flag is meant to only be changed in a context where Mithril auto-redraws. This means `m.redraw.strategy` can be called from controller constructors and from template event handlers. Note that changing this flag only affects the next scheduled redraw.
After the redraw, Mithril resets the value of the flag to either "all" or "diff", depending on whether the redraw was due to a route change or not.
```javascript
var module1 = {}
module1.controller = function() {
@ -39,17 +43,72 @@ module1.controller = function() {
m.redraw.strategy("diff")
}
module1.view = function() {
return m("h1", {config: module1.config}, "test")
return m("h1", {config: module1.config}, "test") //assume all routes display the same thing
}
module1.config = function(el, isInit, ctx) {
if (!isInit) ctx.data = "foo"
if (!isInit) ctx.data = "foo" //we wish to initialize this only once, even if the route changes
}
```
Common reasons why one might need to change redraw strategy are:
- in order to avoid the full-page recreation when changing routes, for the sake of performance of global 3rd party components
- in order to prevent redraw when dealing with `keypress` events where the event's keyCode is not of interest
- in order to avoid the full-page recreation when changing routes, for the sake of performance of global 3rd party components
```javascript
//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 module1 = {}
module1.controller = function() {
m.redraw.strategy("diff")
}
module1.view = function() {
return [
m("h1", "Hello Foo"),
m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
]
}
var module2 = {}
module2.controller = function() {
m.redraw.strategy("diff")
}
module2.view = function() {
return [
m("h1", "Hello Bar"),
m("input", {config: plugin}) //assuming `plugin` initializes a 3rd party library
]
}
m.route(document.body, "/foo", {
"/foo": module1,
"/bar": module2,
})
```
- in order to prevent redraw when dealing with `keypress` events where the event's keyCode is not of interest
```javascript
//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("button[type=button]", {onkeypress: save}, "Save"),
saved ? "Saved" : ""
]
}
```
Note that the redraw strategy is a global setting that affects the entire template trees of all modules on the page. In order to prevent redraws in *some parts* of an application, but not others, see [subtree directives](mithril.render.md#subtree-directives)

View file

@ -59,7 +59,12 @@ You can use it by adding a reference to your Typescript files. This will allow t
Mithril relies on some Ecmascript 5 features, namely: `Array::indexOf`, `Array::map` and `Object::keys`, as well as the `JSON` object.
You can use polyfill libraries to support these features in IE7.
The easiest way to polyfill these features is to include this script:
```markup
<script src="https://polyfill.io/readable/gimme(array.prototype.indexof,object.keys,function.prototype.bind,array.prototype.foreach,JSON)"></script>
```
You can also use other polyfills to support these features in IE7.
- [ES5 Shim](https://github.com/es-shims/es5-shim) or Mozilla.org's [Array::indexOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf), [Array::map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) and [Object::keys](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys)

View file

@ -1,5 +1,5 @@
Mithril = m = new function app(window, undefined) {
var type = {}.toString
var type = function(obj) {return {}.toString.call(obj)}
var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/
var voidElements = /AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR/
@ -18,7 +18,7 @@ Mithril = m = new function app(window, undefined) {
*/
function m() {
var args = Array.prototype.slice.call(arguments, 0)
var hasAttrs = args[1] != null && type.call(args[1]) == "[object Object]" && !("tag" in args[1]) && !("subtree" in args[1])
var hasAttrs = args[1] != null && type(args[1]) == "[object Object]" && !("tag" in args[1]) && !("subtree" in args[1])
var attrs = hasAttrs ? args[1] : {}
var classAttrName = "class" in attrs ? "class" : "className"
var cell = {tag: "div", attrs: {}}
@ -78,7 +78,7 @@ Mithril = m = new function app(window, undefined) {
if (data == null) data = ""
if (data.subtree === "retain") return cached
var cachedType = type.call(cached), dataType = type.call(data)
var cachedType = type(cached), dataType = type(data)
if (cached == null || cachedType != dataType) {
if (cached != null) {
if (parentCache && parentCache.nodes) {
@ -160,7 +160,7 @@ Mithril = m = new function app(window, undefined) {
var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs)
if (item === undefined) continue
if (!item.nodes.intact) intact = false
var isArray = type.call(item) == "[object Array]"
var isArray = type(item) == "[object Array]"
subArrayCount += isArray ? item.length : 1
cached[cacheCount++] = item
}
@ -324,7 +324,7 @@ Mithril = m = new function app(window, undefined) {
function unload(cached) {
if (cached.configContext && typeof cached.configContext.onunload == "function") cached.configContext.onunload()
if (cached.children) {
if (type.call(cached.children) == "[object Array]") {
if (type(cached.children) == "[object Array]") {
for (var i = 0; i < cached.children.length; i++) unload(cached.children[i])
}
else if (cached.children.tag) unload(cached.children)
@ -354,7 +354,7 @@ Mithril = m = new function app(window, undefined) {
var flattened = []
for (var i = 0; i < data.length; i++) {
var item = data[i]
if (type.call(item) == "[object Array]") flattened.push.apply(flattened, flatten(item))
if (type(item) == "[object Array]") flattened.push.apply(flattened, flatten(item))
else flattened.push(item)
}
return flattened
@ -435,7 +435,7 @@ Mithril = m = new function app(window, undefined) {
return gettersetter(store)
}
var roots = [], modules = [], controllers = [], lastRedrawId = 0, computePostRedrawHook = null, prevented = false
var roots = [], modules = [], controllers = [], lastRedrawId = 0, redrawAgain = false, computePostRedrawHook = null, prevented = false
m.module = function(root, module) {
var index = roots.indexOf(root)
if (index < 0) index = roots.length
@ -460,12 +460,19 @@ Mithril = m = new function app(window, undefined) {
var cancel = window.cancelAnimationFrame || window.clearTimeout
var defer = window.requestAnimationFrame || window.setTimeout
if (lastRedrawId && force !== true) {
cancel(lastRedrawId)
lastRedrawId = defer(redraw, 0)
redrawAgain = true
}
else {
redraw()
lastRedrawId = defer(function() {lastRedrawId = null}, 0)
lastRedrawId = defer(delay, 16) //60 frames per second = 1 call per 16 ms
}
function delay() {
lastRedrawId = null
if (redrawAgain) {
redrawAgain = false
m.redraw()
}
}
}
m.redraw.strategy = m.prop()
@ -885,7 +892,7 @@ Mithril = m = new function app(window, undefined) {
var unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity
var response = unwrap(deserialize(extract(e.target, xhrOptions)))
if (e.type == "load") {
if (type.call(response) == "[object Array]" && xhrOptions.type) {
if (type(response) == "[object Array]" && xhrOptions.type) {
for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i])
}
else if (xhrOptions.type) response = new xhrOptions.type(response)
@ -893,8 +900,7 @@ Mithril = m = new function app(window, undefined) {
deferred[e.type == "load" ? "resolve" : "reject"](response)
}
catch (e) {
if (e instanceof SyntaxError) throw new SyntaxError("Could not parse HTTP response. See http://lhorie.github.io/mithril/mithril.request.html#using-variable-data-formats")
else if (!rethrowUnchecked(e)) deferred.reject(e)
if (!rethrowUnchecked(e)) deferred.reject(e)
}
if (xhrOptions.background !== true) m.endComputation()
}

View file

@ -592,7 +592,6 @@ function testMithril(mock) {
var firstBefore = root.childNodes[0]
m.render(root, [m("a", {key: 2}), m("br"), m("a", {key: 1})])
var firstAfter = root.childNodes[2]
console.log(root.childNodes)
return firstBefore == firstAfter && root.childNodes[0].key == 2 && root.childNodes.length == 3
})
test(function() {
@ -1455,6 +1454,7 @@ function testMithril(mock) {
}
})
root.childNodes[0].onclick({})
mock.requestAnimationFrame.$resolve() //teardown
return strategy == "diff" && root.childNodes[0].childNodes[0].nodeValue == "1"
})
test(function() {

View file

@ -15,16 +15,10 @@ mock.window = new function() {
insertBefore: function(node, reference) {
node.parentNode = this
var referenceIndex = this.childNodes.indexOf(reference)
if (referenceIndex < 0) {
var index = this.childNodes.indexOf(node)
if (index > -1) this.childNodes.splice(index, 1)
this.childNodes.push(node)
}
else {
var index = this.childNodes.indexOf(node)
if (index > -1) this.childNodes.splice(index, 1)
this.childNodes.splice(referenceIndex, 0, node)
}
var index = this.childNodes.indexOf(node)
if (index > -1) this.childNodes.splice(index, 1)
if (referenceIndex < 0) this.childNodes.push(node)
else this.childNodes.splice(referenceIndex, 0, node)
},
insertAdjacentHTML: function(position, html) {
//todo: accept markup
@ -99,8 +93,11 @@ mock.window = new function() {
}
window.requestAnimationFrame.$id = 1
window.requestAnimationFrame.$resolve = function() {
if (window.requestAnimationFrame.$callback) window.requestAnimationFrame.$callback()
window.requestAnimationFrame.$callback = null
if (window.requestAnimationFrame.$callback) {
var callback = window.requestAnimationFrame.$callback
window.requestAnimationFrame.$callback = null
callback()
}
}
window.XMLHttpRequest = new function() {
var request = function() {