Merge remote-tracking branch 'origin/next' into next
Conflicts: mithril.min.js mithril.min.js.map
This commit is contained in:
commit
484a9d6c70
15 changed files with 66 additions and 44 deletions
|
|
@ -478,7 +478,7 @@
|
|||
### 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
|
||||
- returning a thenable 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
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ var MyComponent = {
|
|||
controller: function(data) {
|
||||
return {
|
||||
increment: function() {
|
||||
//This is a simplication for the sake of the example.
|
||||
//This is a simplification for the sake of the example.
|
||||
//Typically, values are modified via model methods,
|
||||
//rather than modified directly
|
||||
model.count++
|
||||
|
|
@ -590,7 +590,7 @@ where:
|
|||
|
||||
- **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.
|
||||
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 controller is not specified, Mithril will automatically create an empty controller function.
|
||||
|
||||
- **Object attributes**
|
||||
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ The `m.startComputation` / `m.endComputation` pair is designed to be "stacked",
|
|||
|
||||
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).
|
||||
When computation methods are used diligently 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`](mithril.request.md) and basic null checks (e.g. `data ? m("div", data) : null`).
|
||||
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ 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
|
||||
.then(null, error) //handle the application error: bind to a getter-setter for displaying it on the template
|
||||
```
|
||||
|
||||
Note that the default promise exception handling semantics can be modified. See the next section.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ The [`m.startComputation` / `m.endComputation` pair](mithril.computation.md) is
|
|||
|
||||
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).
|
||||
When computation methods are used diligently 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`](mithril.request.md) and basic null checks (e.g. `data ? m("div", data) : null`).
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ var users = m.request({method: "GET", url: "/user"});
|
|||
|
||||
Note that this getter-setter holds an *undefined* value until the AJAX request completes. Attempting to unwrap its value early will likely result in errors.
|
||||
|
||||
The returned getter-setter also implements the [promise](mithril.deferred.md) interface (also known as a *thennable*): this is the mechanism you should always use to queue operations to be performed on the data from the web service.
|
||||
The returned getter-setter also implements the [promise](mithril.deferred.md) interface (also known as a *thenable*): this is the mechanism you should always use to queue operations to be performed on the data from the web service.
|
||||
|
||||
The simplest use case of this feature is to implement functional value assignment via `m.prop` (i.e. the same thing as above). You can bind a pre-existing getter-setter by passing it in as a parameter to a `.then` method:
|
||||
|
||||
|
|
@ -73,9 +73,9 @@ var doSomething = function() { /*...*/ }
|
|||
m.request({method: "GET", url: "/user"}).then(users).then(doSomething)
|
||||
```
|
||||
|
||||
While both basic assignment syntax and thennable syntax can be used to the same effect, typically it's recommended that you use the assignment syntax whenever possible, as it's easier to read.
|
||||
While both basic assignment syntax and thenable syntax can be used to the same effect, typically it's recommended that you use the assignment syntax whenever possible, as it's easier to read.
|
||||
|
||||
The thennable mechanism is intended to be used in three ways:
|
||||
The thenable mechanism is intended to be used in three ways:
|
||||
|
||||
- in the model layer: to process web service data in transformative ways (e.g. filtering a list based on a parameter that the web service doesn't support)
|
||||
- in the controller layer: to bind redirection code upon a condition
|
||||
|
|
@ -122,7 +122,7 @@ var controller = function() {
|
|||
|
||||
#### Binding errors
|
||||
|
||||
Mithril thennables take two functions as optional parameters: the first parameter is called if the web service request completes successfully. The second one is called if it completes with an error.
|
||||
Mithril thenables take two functions as optional parameters: the first parameter is called if the web service request completes successfully. The second one is called if it completes with an error.
|
||||
|
||||
Error binding is meant to be done in the controller layer. Doing it in the model level is also possible, but generally leads to more code in order to connect all the dots.
|
||||
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ In the example below, we bind an error getter-setter to our previous controller
|
|||
//controller
|
||||
var controller = function() {
|
||||
this.error = m.prop("")
|
||||
|
||||
|
||||
this.users = User.listEven().then(function(users) {
|
||||
if (users.length == 0) m.route("/add");
|
||||
}, this.error)
|
||||
|
|
@ -121,7 +121,7 @@ If the controller doesn't already have a success callback to run after a request
|
|||
//controller
|
||||
var controller = function() {
|
||||
this.error = m.prop("")
|
||||
|
||||
|
||||
this.users = User.listEven().then(null, this.error)
|
||||
}
|
||||
```
|
||||
|
|
@ -145,7 +145,7 @@ var users = m.request({method: "GET", url: "/user"})
|
|||
//add one more user to the response
|
||||
return users.concat({name: "Jane"})
|
||||
})
|
||||
|
||||
|
||||
function log(value) {
|
||||
console.log(value)
|
||||
return value
|
||||
|
|
|
|||
28
mithril.d.ts
vendored
28
mithril.d.ts
vendored
|
|
@ -171,13 +171,13 @@ declare module _mithril {
|
|||
* 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
|
||||
* @param promise A thenable to initialize the property with. It may
|
||||
* optionally be a Mithril promise.
|
||||
* @return A getter-setter function wrapping the promise.
|
||||
*
|
||||
* @see m.withAttr
|
||||
*/
|
||||
prop<T>(promise: Thennable<T>) : MithrilPromiseProperty<T>;
|
||||
prop<T>(promise: Thenable<T>) : MithrilPromiseProperty<T>;
|
||||
|
||||
/**
|
||||
* Creates a getter-setter function that wraps a simple value. Useful
|
||||
|
|
@ -554,7 +554,7 @@ declare module _mithril {
|
|||
}
|
||||
|
||||
/**
|
||||
* Takes a list of promises or thennables and returns a Mithril promise
|
||||
* Takes a list of promises or thenables and returns a Mithril promise
|
||||
* that resolves once all in the list are resolved, or rejects if any of
|
||||
* them reject.
|
||||
*
|
||||
|
|
@ -562,7 +562,7 @@ declare module _mithril {
|
|||
* @return A promise that resolves to all the promises if all resolve, or
|
||||
* rejects with the error contained in the first rejection.
|
||||
*/
|
||||
sync<T>(promises: Thennable<T>[]): MithrilPromise<T[]>;
|
||||
sync<T>(promises: Thenable<T>[]): MithrilPromise<T[]>;
|
||||
|
||||
/**
|
||||
* Use this and endComputation if your views aren't redrawing after
|
||||
|
|
@ -911,32 +911,32 @@ declare module _mithril {
|
|||
}
|
||||
|
||||
/**
|
||||
* This represents a thennable success callback.
|
||||
* This represents a thenable success callback.
|
||||
*/
|
||||
interface MithrilSuccessCallback<T, U> {
|
||||
(value: T): U | Thennable<U>;
|
||||
(value: T): U | Thenable<U>;
|
||||
}
|
||||
|
||||
/**
|
||||
* This represents a thennable error callback.
|
||||
* This represents a thenable error callback.
|
||||
*/
|
||||
interface MithrilErrorCallback<T> {
|
||||
(value: Error): T | Thennable<T>;
|
||||
(value: Error): T | Thenable<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* This represents a thennable.
|
||||
* This represents a thenable.
|
||||
*/
|
||||
interface Thennable<T> {
|
||||
then<U>(success: (value: T) => U): Thennable<U>;
|
||||
then<U,V>(success: (value: T) => U, error: (value: Error) => V): Thennable<U>|Thennable<V>;
|
||||
catch?: <U>(error: (value: Error) => U) => Thennable<U>;
|
||||
interface Thenable<T> {
|
||||
then<U>(success: (value: T) => U): Thenable<U>;
|
||||
then<U,V>(success: (value: T) => U, error: (value: Error) => V): Thenable<U>|Thenable<V>;
|
||||
catch?: <U>(error: (value: Error) => U) => Thenable<U>;
|
||||
}
|
||||
|
||||
/**
|
||||
* This represents a Mithril promise object.
|
||||
*/
|
||||
interface MithrilPromise<T> extends Thennable<T>, MithrilProperty<MithrilPromise<T>> {
|
||||
interface MithrilPromise<T> extends Thenable<T>, MithrilProperty<MithrilPromise<T>> {
|
||||
/**
|
||||
* Chain this promise with a simple success callback, propogating
|
||||
* rejections.
|
||||
|
|
|
|||
12
mithril.js
12
mithril.js
|
|
@ -1859,15 +1859,15 @@
|
|||
var RESOLVED = 3
|
||||
var REJECTED = 4
|
||||
|
||||
function coerce(value, next, error, inst) {
|
||||
function coerce(value, next, error) {
|
||||
if (isPromise(value)) {
|
||||
return value.then(function (value) {
|
||||
coerce(value, next, error, inst)
|
||||
coerce(value, next, error)
|
||||
}, function (e) {
|
||||
coerce(e, error, error, inst)
|
||||
coerce(e, error, error)
|
||||
})
|
||||
} else {
|
||||
return next.call(inst, value)
|
||||
return next(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1980,7 +1980,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
function notThennable(value, state, deferred) {
|
||||
function notThenable(value, state, deferred) {
|
||||
try {
|
||||
if (state === RESOLVING && isFunction(onSuccess)) {
|
||||
value = onSuccess(value)
|
||||
|
|
@ -2017,7 +2017,7 @@
|
|||
if (thenable) {
|
||||
return doThen(value, deferred)
|
||||
} else {
|
||||
return notThennable(value, state, deferred)
|
||||
return notThenable(value, state, deferred)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
mithril.min.js
vendored
2
mithril.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -15,6 +15,7 @@
|
|||
"url": "http://github.com/lhorie/mithril.js/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"preversion": "grunt",
|
||||
"test": "grunt test"
|
||||
},
|
||||
"main": "mithril.js",
|
||||
|
|
|
|||
|
|
@ -227,6 +227,15 @@ this.mock = (function (global) {
|
|||
function Request() {
|
||||
this.$headers = {}
|
||||
|
||||
this.$resolve = function (data, status) {
|
||||
if (data === undefined) data = this // eslint-disable-line
|
||||
this.responseText = JSON.stringify(data)
|
||||
this.readyState = 4
|
||||
this.status = status || 200
|
||||
this.onreadystatechange()
|
||||
return this
|
||||
}
|
||||
|
||||
this.setRequestHeader = function (key, value) {
|
||||
this.$headers[key] = value
|
||||
}
|
||||
|
|
@ -237,9 +246,6 @@ this.mock = (function (global) {
|
|||
}
|
||||
|
||||
this.send = function () {
|
||||
this.responseText = JSON.stringify(this)
|
||||
this.readyState = 4
|
||||
this.status = 200
|
||||
Request.$instances.push(this)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -648,7 +648,7 @@ describe("m.mount()", function () {
|
|||
})
|
||||
|
||||
function resolveXhr() {
|
||||
mock.XMLHttpRequest.$instances.pop().onreadystatechange()
|
||||
mock.XMLHttpRequest.$instances.pop().$resolve()
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ describe("m.request()", function () {
|
|||
// Much easier to read
|
||||
function resolve() {
|
||||
var xhr = mock.XMLHttpRequest.$instances.pop()
|
||||
xhr.onreadystatechange()
|
||||
xhr.$resolve.apply(xhr, arguments)
|
||||
return xhr
|
||||
}
|
||||
|
||||
|
|
@ -181,8 +181,7 @@ describe("m.request()", function () {
|
|||
data: {foo: 1}
|
||||
}).then(null, error)
|
||||
|
||||
var xhr = mock.XMLHttpRequest.$instances.pop()
|
||||
xhr.onreadystatechange()
|
||||
var xhr = mock.XMLHttpRequest.$instances.pop().$resolve()
|
||||
|
||||
expect(xhr.$headers).to.have.property(
|
||||
"Content-Type",
|
||||
|
|
@ -197,8 +196,7 @@ describe("m.request()", function () {
|
|||
url: "test"
|
||||
}).then(null, error)
|
||||
|
||||
var xhr = mock.XMLHttpRequest.$instances.pop()
|
||||
xhr.onreadystatechange()
|
||||
var xhr = mock.XMLHttpRequest.$instances.pop().$resolve()
|
||||
|
||||
expect(xhr.$headers).to.not.have.property("Content-Type")
|
||||
})
|
||||
|
|
@ -381,4 +379,21 @@ describe("m.request()", function () {
|
|||
// For good measure
|
||||
mock.requestAnimationFrame.$resolve()
|
||||
})
|
||||
|
||||
it("can use a config correctly", function () {
|
||||
var config = sinon.spy()
|
||||
var result = m.prop()
|
||||
var error = sinon.spy
|
||||
var opts = {
|
||||
method: "GET",
|
||||
url: "/test",
|
||||
config: config
|
||||
}
|
||||
m.request(opts).then(result, error)
|
||||
var xhr = resolve({foo: "bar"})
|
||||
|
||||
expect(config).to.be.calledWithExactly(xhr, opts)
|
||||
expect(result()).to.eql({foo: "bar"})
|
||||
expect(error).to.not.be.called
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue