Add m.prop (#2268)

Fixes #2095
This commit is contained in:
Isiah Meadows 2018-11-07 12:18:55 -05:00 committed by GitHub
parent 75626b30db
commit 6042b001f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 203 additions and 1 deletions

View file

@ -143,6 +143,26 @@ m.mount(document.body, Component)
---
#### m.prop(initial) - [docs](prop.md)
```javascript
var Component = {
oninit: function(vnode) {
vnode.state.current = m.prop("")
},
view: function(vnode) {
return m("input", {
oninput: function(ev) { vnode.state.current.set(ev.target.value) },
value: vnode.state.current.get(),
})
}
}
m.mount(document.body, Component)
```
---
#### m.trust(htmlString) - [docs](trust.md)
```javascript

View file

@ -43,6 +43,7 @@
- API: add support for raw SVG in `m.trust()` string ([#2097](https://github.com/MithrilJS/mithril.js/pull/2097))
- render/core: remove the DOM nodes recycling pool ([#2122](https://github.com/MithrilJS/mithril.js/pull/2122))
- render/core: revamp the core diff engine, and introduce a longest-increasing-subsequence-based logic to minimize DOM operations when re-ordering keyed nodes.
- API: Introduction of `m.prop()` ([#2268](https://github.com/MithrilJS/mithril.js/pull/2268))
#### Bug fixes

View file

@ -8,6 +8,7 @@
- [m.parseQueryString](parseQueryString.md)
- [m.buildQueryString](buildQueryString.md)
- [m.withAttr](withAttr.md)
- [m.prop](prop.md)
- [m.trust](trust.md)
- [m.fragment](fragment.md)
- [m.redraw](redraw.md)

152
docs/prop.md Normal file
View file

@ -0,0 +1,152 @@
# prop(attrName, callback)
- [Description](#description)
- [Signature](#signature)
- [How it works](#how-it-works)
- [Sending through requests](#sending-through-requests)
---
### Description
Returns a simple getter/setter object.
```javascript
var name = m.prop("John")
var oldName = name.get() // First, it's set to "John"
name.set("Mary") // Set the value to "Mary"
var newName = name.get() // Now it's "Mary", not "John"
```
---
### Signature
`prop = m.prop(initial?)`
Argument | Type | Required | Description
----------- | ------ | -------- | ---
`initial` | `any` | No | The prop's initial value
**returns** | `Prop` | | A prop
`value = prop.get()`
Argument | Type | Required | Description
----------- | ----- | -------- | ---
**returns** | `any` | | The prop's current value
`newValue = prop.set(newValue)`
Argument | Type | Required | Description
----------- | ----- | -------- | ---
`newValue` | `any` | Yes | The value to set the prop to
**returns** | `any` | | The value you just set the prop to, for convenience
[How to read signatures](signatures.md)
---
### How it works
The `m.prop` method creates a prop, a getter/setter object wrapping a single mutable reference. You can get the current value with `prop.get()` and set it with `prop.set(value)`. Unlike [streams](stream.md), you can't observe them, so you can't do as much with them.
In conjunction with [`m.withAttr`](withAttr.md), you can emulate two-way binding pretty easily.
```javascript
var Component = {
oninit: function(vnode) {
vnode.state.current = m.prop("")
},
view: function(vnode) {
return m("input", {
oninput: m.withAttr("value", vnode.state.current.set),
value: vnode.state.current.get(),
})
}
}
```
They're also useful for making simpler models.
```javascript
// With props
var Auth = {
username: m.prop(""),
password: m.prop(""),
canSubmit: function() {
return Auth.username.get() !== "" && Auth.password.get() !== ""
},
login: function() {
// ...
},
}
// Without props
var Auth = {
username: "",
password: "",
setUsername: function(value) {
Auth.username = value
}
setPassword: function(value) {
Auth.password = value
}
canSubmit: function() {
return Auth.username !== "" && Auth.password !== ""
},
login: function() {
// ...
},
}
```
---
### Sending through requests
For convenience, props define `.toJSON` as an alias for `.get`. This is so you can send them through `m.request` without serializing them manually.
We could also take this model and simplify it:
```javascript
// How it's loaded
User.load = function(id) {
return m.request({
method: "GET",
url: "https://rem-rest-api.herokuapp.com/api/users/" + id,
withCredentials: true,
})
.then(function(result) {
User.current = {
id: result.id,
firstName: m.prop(result.firstName),
lastName: m.prop(result.lastName),
}
})
}
// Original
User.save = function(user) {
return m.request({
method: "PUT",
url: "https://rem-rest-api.herokuapp.com/api/users/" + user.id,
data: {
id: user.id,
firstName: user.firstName.get(),
lastName: user.lastName.get(),
},
withCredentials: true,
})
}
// Simplified
User.save = function(user) {
return m.request({
method: "PUT",
url: "https://rem-rest-api.herokuapp.com/api/users/" + user.id,
data: user,
withCredentials: true,
})
}
```

View file

@ -9,6 +9,7 @@ requestService.setCompletionCallback(redrawService.redraw)
m.mount = require("./mount")
m.route = require("./route")
m.withAttr = require("./util/withAttr")
m.prop = require("./util/prop")
m.render = require("./render").render
m.redraw = redrawService.redraw
m.request = requestService.request

9
util/prop.js Normal file
View file

@ -0,0 +1,9 @@
"use strict"
module.exports = function (store) {
return {
get: function() { return store },
toJSON: function() { return store },
set: function(value) { return store = value }
}
}

View file

@ -8,8 +8,10 @@
<script src="../../ospec/ospec.js"></script>
<script src="../../util/withAttr.js"></script>
<script src="../../util/prop.js"></script>
<script src="test-withAttr.js"></script>
<script src="test-prop.js"></script>
<script>require("../../ospec/ospec").run()</script>
</body>
</html>

16
util/tests/test-prop.js Normal file
View file

@ -0,0 +1,16 @@
"use strict"
var o = require("../../ospec/ospec")
var prop = require("../../util/prop")
o.spec("prop", function() {
o("works", function() {
var p = prop(1)
o(p.get()).equals(1)
o(p.toJSON()).equals(1)
o(p.set(2)).equals(2)
o(p.get()).equals(2)
o(p.toJSON()).equals(2)
})
})