wrap-data
v0.3.18
Published
wrap-data
Downloads
39
Readme
wrap-data
Wrap data object into reactive streams, with helpers like unwrap, get, set, unset etc.
Install
NPM
npm i -S wrap-data
Browser
<script src="https://unpkg.com/wrap-data"></script>
<script>
// wrapData is a global
wrapData(...)
</script>
Usage
First you need a stream helper function or library, which conforms to the fantasy land applicative specification, flyd is recommended.
- Convert existing data and use wrapped data
const flyd = require('flyd')
const wrapData = require('wrap-data')
const data = {
firstName: 'Hello',
lastName: 'World'
}
const model = wrapData(flyd.stream)(data)
// model, and everything inside model is a stream!
// manually access a data
model().firstName // stream(Hello)
model().lastName // stream(World)
model.set('address', {city: 'Mercury'}) // set model.address
model().address().city() // get value: 'Mercury'
model().address().city('Mars') // set value: 'Mars'
const city = model.get('address.city') //stream(Mars)
city() // get value: 'Mars'
city('Earth') // set value: 'Earth'
model.unwrap('address') // {city: 'Earth'}
model.unset('address') // unset model.address
model.unwrap() // {firstName: 'Hello', lastName: 'World'}
- Observe data changes
The root model
has a change
stream, you can get callback from every data changes.
// start observe model changes
const update = model.change.map(({value, type})=>{
console.log('data mutated:', value.path, type, value.unwrap())
})
model.set('address.city', 'Mars')
// [console] data mutated: [ 'address', 'city' ] add Mars
model.get('address.city')('Earth')
// [console] data mutated: [ 'address', 'city' ] change Earth
model.unset('address.city')
// [console] data mutated: [ 'address', 'city' ] delete Earth
// stop observe model changes
update.end(true)
- Define data relations
You can define data relations using combine
, scan
etc., and unwrap
will unwrap them automatically, you can nest any level of streams.
const firstName = model.get('firstName')
const lastName = model.get('lastName')
const fullName = flyd.combine(
(a, b) => a() + ' ' + b(),
[firstName, lastName]
)
model.set('fullName', fullName)
fullName.map(console.log) // [console] Hello World
firstName('Green') // [console] Green World
model.set('age', flyd.stream(flyd.stream(20)))
model.unwrap()
// {firstName:'Green', lastName:'World', fullName:'Green World', age:20}
- Use in React
const model = wrapData(flyd.stream)({user: {name: 'earth'}})
class App extends React.Component {
constructor(props){
super(props)
const {model} = this.props
this.update = model.change.map(({value, type})=>{
this.forceUpdate()
})
this.onChange = e => {
const {name, value} = e.target
model.set(name, value)
}
}
componentWillUnmount(){
this.update.end(true)
}
render(){
const {model} = this.props
const userName = model.unwrap('user.name')
return <div>
<h3>Hello {userName}</h3>
<input name='user.name' value={userName} onChange={this.onChange} />
</div>
}
}
ReactDOM.render(<App model={model} />, app)
You can play with the demo here
API
- wrapData = require('wrap-data')
The lib expose a default
wrapData
function to use
- wrapFactory = wrapData(stream)
the
wrapFactory
is used to turn data into wrapped_data.
A wrapped_data
is just a stream, with some helper methods added to it, like get
, set
etc.
return: function(data) -> wrapped_data
var flyd = require('flyd')
var wrapFactory = wrapData(flyd.stream)
- root = wrapFactory(data: any)
the
root
is a wrapped_data, with all nested data wrapped.
return: wrapped_data for data
root.change
is also a stream, you can map
it to receive any data changes inside.
Any data inside root is a wrapped_data
, and may be contained by {}
or []
stream, keep the same structure as before.
Any wrapped_data
have root
and path
propperties, get
, set
, ... helper functions.
var root = wrapFactory({x: {y: {z: 1}}})
root().x().y().z() // 1
root.change.map(({value, type})=>{ console.log(value, type) })
root().x().y().z(2)
- wrapped_data.get(path: string|string[])
get nested wrapped data from path, path is array of string or dot(
"."
) seperated string.
return: wrapped_data at path
var z = root.get('x.y.z')
// or
var z = root.get(['x','y','z'])
z() //2
z(10)
- wrapped_data.set(path?: string|string[], value?: any, descriptor?: object)
set nested wrapped data value from path, same rule as
get
method. Thedescriptor
only applied when path not exists.
return: wrapped_data for value
, at path
path
can contain a.[3]
alike string denote 3
is an array element of a
.
value
can be any data types, if path
is omitted, set value into wrapped_data itself.
If value
is a stream, then it's an atom data, which will not be wrapped inside.
descriptor
is optional, same as 3rd argument of Object.defineProperty
, this can e.g. create non-enumerable stream which will be hidden when unwrap
.
If data not exist in path
, all intermediate object will be created.
var z = root.set('x.a', 10)
z() // 10
// same as: (only if x.a exits)
root.get('x.a').set(10)
root.get('x.a')(10)
var z = root.set('x.c', [], {enumerable: false}) // c is non-enumerable
Object.keys( z.get('x')() ) // ['a']
root.unwrap() // {x: {y: {z: 1}}, a: 10} // `c` is hidden!
root.set(`arr.[0]`, 10)
root.get('arr.0')() // 10
root.unwrap() // {x: {y: {z: 1}}, a: 10, arr:[10]} // `arr` is array!
- wrapped_data.getset(path?: string|string[], function(prevValue, empty?: boolean)->newValue, descriptor: object)
like
set
, but value is from a function, it let you setvalue
based on previous value, thedescriptor
only applied whenempty
istrue
.
return: wrapped_data for newValue
, at path
var z = root.getset('x.a', val=>val+1)
z() // 11
- wrapped_data.ensure(path: string|string[], value?: any, descriptor?: object)
like
set
, but onlyset
when the path not exists, otherwise perform aget
operation.
return: wrapped_data at path
var z = root.ensure('x.a', 5)
// x.a exists, so perform a get, `5` ignored
z() // 11
var z = root.ensure('x.b', 5)
// x.b not exists, so perform a `set`
z() // 5
- wrapped_data.unset(path: string|string[])
delete
wrapped_data
orvalue
inpath
return: deleted data been unwrapped
var z = root.unset('x.b')
z // 5
- wrapped_data.unwrap(path?: string|string[], config?: {json: true})
unwrap data and nested data while keep data structure, any level of
wrapper
on any data will be stripped.
If set config
arg with {json: true}
, then any circular referenced data will be set undefined
, suitable for JSON.stringify
.
return: unwrapped data
var z = root.unwrap()
z // {x: {y: {z: 11}}, a: [10]}, x.c is hidden
- wrapped_array.push(value: any)
push new
value
into wrapped data when it's array, all the inside will be wrapped.
return: newly pushed wrapped_data
var z = root.set('d', [])
z.push({v: 10})
z.get('d.0.v')() // 10
- wrapped_array.pop()
pop and unwrap last element in wrapped array.
return: unwrapped data in last array element
var z = root.ensure('d', [])
z.get('d').pop() // {v: 10}