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 - there is more documentation for things that weren't that clear
- json-p support added - json-p support added
- `m()` now supports splat for children (e.g. `m("div", m("a"), m("b"), m("i"))` for nicer Coffeescript syntax - `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: ### 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. 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 ### 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). 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 ### 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` 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 ```javascript
var deferred = Q.defer() var deferred = Q.defer()
@ -82,6 +83,9 @@ users() // undefined
deferred.resolve("Hello") deferred.resolve("Hello")
users() // 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. 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 ```javascript
var module1 = {} var module1 = {}
module1.controller = function() { module1.controller = function() {
@ -39,17 +43,72 @@ module1.controller = function() {
m.redraw.strategy("diff") m.redraw.strategy("diff")
} }
module1.view = function() { 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) { 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: 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 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
```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) 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. 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) - [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) { Mithril = m = new function app(window, undefined) {
var type = {}.toString var type = function(obj) {return {}.toString.call(obj)}
var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/
var voidElements = /AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR/ 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() { function m() {
var args = Array.prototype.slice.call(arguments, 0) 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 attrs = hasAttrs ? args[1] : {}
var classAttrName = "class" in attrs ? "class" : "className" var classAttrName = "class" in attrs ? "class" : "className"
var cell = {tag: "div", attrs: {}} var cell = {tag: "div", attrs: {}}
@ -78,7 +78,7 @@ Mithril = m = new function app(window, undefined) {
if (data == null) data = "" if (data == null) data = ""
if (data.subtree === "retain") return cached 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 || cachedType != dataType) {
if (cached != null) { if (cached != null) {
if (parentCache && parentCache.nodes) { 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) var item = build(parentElement, parentTag, cached, index, data[i], cached[cacheCount], shouldReattach, index + subArrayCount || subArrayCount, editable, namespace, configs)
if (item === undefined) continue if (item === undefined) continue
if (!item.nodes.intact) intact = false if (!item.nodes.intact) intact = false
var isArray = type.call(item) == "[object Array]" var isArray = type(item) == "[object Array]"
subArrayCount += isArray ? item.length : 1 subArrayCount += isArray ? item.length : 1
cached[cacheCount++] = item cached[cacheCount++] = item
} }
@ -324,7 +324,7 @@ Mithril = m = new function app(window, undefined) {
function unload(cached) { function unload(cached) {
if (cached.configContext && typeof cached.configContext.onunload == "function") cached.configContext.onunload() if (cached.configContext && typeof cached.configContext.onunload == "function") cached.configContext.onunload()
if (cached.children) { 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]) for (var i = 0; i < cached.children.length; i++) unload(cached.children[i])
} }
else if (cached.children.tag) unload(cached.children) else if (cached.children.tag) unload(cached.children)
@ -354,7 +354,7 @@ Mithril = m = new function app(window, undefined) {
var flattened = [] var flattened = []
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
var item = data[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) else flattened.push(item)
} }
return flattened return flattened
@ -435,7 +435,7 @@ Mithril = m = new function app(window, undefined) {
return gettersetter(store) 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) { m.module = function(root, module) {
var index = roots.indexOf(root) var index = roots.indexOf(root)
if (index < 0) index = roots.length if (index < 0) index = roots.length
@ -460,12 +460,19 @@ Mithril = m = new function app(window, undefined) {
var cancel = window.cancelAnimationFrame || window.clearTimeout var cancel = window.cancelAnimationFrame || window.clearTimeout
var defer = window.requestAnimationFrame || window.setTimeout var defer = window.requestAnimationFrame || window.setTimeout
if (lastRedrawId && force !== true) { if (lastRedrawId && force !== true) {
cancel(lastRedrawId) redrawAgain = true
lastRedrawId = defer(redraw, 0)
} }
else { else {
redraw() 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() 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 unwrap = (e.type == "load" ? xhrOptions.unwrapSuccess : xhrOptions.unwrapError) || identity
var response = unwrap(deserialize(extract(e.target, xhrOptions))) var response = unwrap(deserialize(extract(e.target, xhrOptions)))
if (e.type == "load") { 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]) for (var i = 0; i < response.length; i++) response[i] = new xhrOptions.type(response[i])
} }
else if (xhrOptions.type) response = new xhrOptions.type(response) 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) deferred[e.type == "load" ? "resolve" : "reject"](response)
} }
catch (e) { 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") if (!rethrowUnchecked(e)) deferred.reject(e)
else if (!rethrowUnchecked(e)) deferred.reject(e)
} }
if (xhrOptions.background !== true) m.endComputation() if (xhrOptions.background !== true) m.endComputation()
} }

View file

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

View file

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