improve several aspects
changed set to make reactive by default, and also traverse and make reactive a plain object by defaultmaster
parent
47e4c34ef5
commit
7ea8c13cbd
|
@ -27,8 +27,8 @@ console.log('changing test to 20, 30 directly')
|
||||||
example.test = 20
|
example.test = 20
|
||||||
example.test = 30
|
example.test = 30
|
||||||
example.rxSubscribe('test',(prop)=>console.log('#############late subscription to \'test\' after 30 ############',prop),'custom')
|
example.rxSubscribe('test',(prop)=>console.log('#############late subscription to \'test\' after 30 ############',prop),'custom')
|
||||||
console.log('changing test to 40 via setPath')
|
console.log('changing test to 40 via set')
|
||||||
example.setPath('test',40)
|
example.set('test',40)
|
||||||
|
|
||||||
console.log('making a deeper property \'another.foo\' reactive')
|
console.log('making a deeper property \'another.foo\' reactive')
|
||||||
example.rxAdd('another.foo')
|
example.rxAdd('another.foo')
|
||||||
|
@ -36,7 +36,7 @@ example.rxAdd('another.foo')
|
||||||
example.another.foo = 7
|
example.another.foo = 7
|
||||||
|
|
||||||
console.log('direct access of another.foo',example.another.foo)
|
console.log('direct access of another.foo',example.another.foo)
|
||||||
console.log('access via get of another.foo',example.getPath('another.foo'))
|
console.log('access via get of another.foo',example.get('another.foo'))
|
||||||
|
|
||||||
console.log('making a deeper property \'some.thing\' reactive that did not exist')
|
console.log('making a deeper property \'some.thing\' reactive that did not exist')
|
||||||
example.rxAdd('some.thing')
|
example.rxAdd('some.thing')
|
||||||
|
@ -51,7 +51,7 @@ console.log('--------------------------')
|
||||||
|
|
||||||
console.log('now removing reactivity from \'test\'')
|
console.log('now removing reactivity from \'test\'')
|
||||||
example.rxRemove('test')
|
example.rxRemove('test')
|
||||||
example.setPath('test',42)
|
example.set('test',42)
|
||||||
console.log('\'test\' now holds unreactive value')
|
console.log('\'test\' now holds unreactive value')
|
||||||
console.log('now removing reactivity from \'test\' again')
|
console.log('now removing reactivity from \'test\' again')
|
||||||
example.rxRemove('test')
|
example.rxRemove('test')
|
||||||
|
|
|
@ -4,8 +4,8 @@ class Example extends RxClass {
|
||||||
constructor(opts={}){
|
constructor(opts={}){
|
||||||
super(opts)
|
super(opts)
|
||||||
this.test = {
|
this.test = {
|
||||||
yesterday: {bar:'bust', foo:3},
|
tomrrow: {bar:'fight', foo:'fighters'},
|
||||||
today: {bing:5, bong:{who:'first',what:'second'}}
|
today: {bing:'bing', bong:{who:'first',what:'second'}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,35 +13,42 @@ class Example extends RxClass {
|
||||||
const example = new Example({
|
const example = new Example({
|
||||||
_rx_ : {
|
_rx_ : {
|
||||||
handler: value => {console.log('-----------default subscription handler-------', value)},
|
handler: value => {console.log('-----------default subscription handler-------', value)},
|
||||||
skip: 0
|
skip: 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
example.foundation = null
|
||||||
|
|
||||||
|
console.log('-----EXAMPLE: TRAVERSE OF EXISTING OBJECT OR WHEN SETTING A PLAIN OBJECT-----------')
|
||||||
console.log('instance before doing rx on properties\n')
|
console.log('instance before doing rx on properties\n')
|
||||||
console.log(example)
|
console.log(example)
|
||||||
console.log('--------------------------')
|
console.log('--------------------------')
|
||||||
|
|
||||||
example.on('changed', (prop) => console.log('emitted----a property changed-----',prop))
|
// example.on('changed', (prop) => console.log('emitted----a property changed-----',prop))
|
||||||
example.on('test', (val) => console.log('emitted value of test',val))
|
// example.on('test', (val) => console.log('emitted value of test',val))
|
||||||
|
|
||||||
console.log('traversing \'test\' and making all leaves reactive')
|
console.log('traversing exitings object \'test\' and making all leaves reactive')
|
||||||
|
console.log(example.test)
|
||||||
example.rxAdd('test',{traverse:true})
|
example.rxAdd('test',{traverse:true})
|
||||||
|
|
||||||
example.test.today.bong.what = 'third'
|
console.log('adding new property which is made reactive by default \'test.today.bong.why\'')
|
||||||
example.setPath('test.today.bong.what','forth')
|
example.set('test.today.bong.why','do not know')
|
||||||
example.setPath('test.today.bong.why','do not know')
|
|
||||||
|
|
||||||
console.log('\'test\', values object >', example.getPath('test'))
|
console.log('------\'test\', raw object ------ \n', example.get('test',true))
|
||||||
console.log('\'test\', raw object >', example.getPath('test',true))
|
console.log('------ \'test\', values object ------\n>', example.get('test'))
|
||||||
|
|
||||||
|
console.log('now add a completely new object to root of \'foundation\' and make it reactive')
|
||||||
|
const obj = {giskard:'daneel', hari: {name:'seldon', planet:'Helcion'}}
|
||||||
|
example.set('foundation',obj)
|
||||||
|
|
||||||
|
console.log('------\'foundation\', raw object ------ \n', example.get('foundation',true))
|
||||||
|
console.log('------ \'foundation\', values object ------\n>', example.get('foundation'))
|
||||||
|
|
||||||
|
console.log('--------instance after doing rx----\n')
|
||||||
|
console.log(example)
|
||||||
console.log('--------------------------')
|
console.log('--------------------------')
|
||||||
console.log('instance after adding rx to leaves\n')
|
|
||||||
console.dir(example.test)
|
console.log('now change a reactive property directly like')
|
||||||
console.dir(example._rx_.props)
|
console.log('example.foundation.giskard = \'daneel a robot\'')
|
||||||
console.log('--------------------------')
|
example.foundation.giskard = 'daneel a robot'
|
||||||
console.log('now remove all rx on \'test.yesterday\'\n')
|
console.log('the new value extracted by the getter is',example.foundation.giskard)
|
||||||
example.rxRemove('test.today',{traverse:true})
|
|
||||||
console.dir(example.test)
|
|
||||||
console.log('rx props')
|
|
||||||
console.dir(example._rx_.props)
|
|
||||||
console.log('--------------------------')
|
|
||||||
console.dir(example)
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# uCOMmandIt Reactive Class
|
# uCOMmandIt Reactive Class
|
||||||
|
|
||||||
A Great Class to Extend has built in support for make reactive any/all class properties and their nested leaves.
|
A Great Class to Extend! It has built in support to make reactive any/all class properties and their nested leaves.
|
||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
|
|
||||||
|
|
103
src/rx-class.js
103
src/rx-class.js
|
@ -2,9 +2,9 @@ import { EventEmitter } from 'events'
|
||||||
import { BehaviorSubject, from } from 'rxjs'
|
import { BehaviorSubject, from } from 'rxjs'
|
||||||
import { distinctUntilChanged, skip } from 'rxjs/operators'
|
import { distinctUntilChanged, skip } from 'rxjs/operators'
|
||||||
// ** nested object access **
|
// ** nested object access **
|
||||||
import _get from 'get-value'
|
import $get from 'get-value'
|
||||||
import _set from 'set-value'
|
import $set from 'set-value'
|
||||||
import _del from 'unset-value'
|
import $del from 'unset-value'
|
||||||
// *********************
|
// *********************
|
||||||
import equal from 'deep-equal'
|
import equal from 'deep-equal'
|
||||||
import traverse from 'traverse'
|
import traverse from 'traverse'
|
||||||
|
@ -12,13 +12,13 @@ import isPlainObject from 'is-plain-object'
|
||||||
|
|
||||||
export default class RxClass extends EventEmitter {
|
export default class RxClass extends EventEmitter {
|
||||||
constructor(opts){
|
constructor(opts){
|
||||||
// console.log('constructor', opts)
|
|
||||||
super(opts)
|
super(opts)
|
||||||
this._rx_ = {
|
this._rx_ = {
|
||||||
event: opts._rx_.event || 'changed',
|
event: opts._rx_.event || 'changed',
|
||||||
handler: opts._rx_.handler,
|
handler: opts._rx_.handler,
|
||||||
props: {},
|
props: {},
|
||||||
hooks: [],
|
hooks: [],
|
||||||
|
root: '', // root path/namespace to added to all get/set paths
|
||||||
operators:[],
|
operators:[],
|
||||||
skip: opts._rx_.skip == null ? 1 : opts._rx_.skip
|
skip: opts._rx_.skip == null ? 1 : opts._rx_.skip
|
||||||
}
|
}
|
||||||
|
@ -27,44 +27,60 @@ export default class RxClass extends EventEmitter {
|
||||||
// if property exists it moves value to _ version and creates getter and setter and behavior subject
|
// if property exists it moves value to _ version and creates getter and setter and behavior subject
|
||||||
rxAdd(opath,opts={}) {
|
rxAdd(opath,opts={}) {
|
||||||
const path = this._rxFormatPath(opath)
|
const path = this._rxFormatPath(opath)
|
||||||
|
if (this.isRx(path)) {
|
||||||
|
console.log(path, 'is already reactive, aborting rxAdd')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// console.log('making reactive', path,opts)
|
||||||
if (opts.traverse) {
|
if (opts.traverse) {
|
||||||
|
const obj = opts.values ? opts.values : $get(this,path)
|
||||||
const self = this
|
const self = this
|
||||||
traverse(_get(this,path)).map(function () {
|
// console.log('object to traverse', obj)
|
||||||
|
traverse(obj).map(function () {
|
||||||
if (this.isLeaf) {
|
if (this.isLeaf) {
|
||||||
|
// let lpath = []
|
||||||
|
// if (opts.values) {
|
||||||
|
// lpath =[]
|
||||||
|
// } else {
|
||||||
const lpath = this.path
|
const lpath = this.path
|
||||||
lpath.unshift(path)
|
lpath.unshift(path)
|
||||||
// console.log('leaf',lpath,this.node)
|
// console.log(`${!opts.values ? 'existing':'new'} leaf on path '${lpath}' with value: ${this.node}`)
|
||||||
self.rxAdd(lpath)
|
self.rxAdd(lpath,{value:this.node})
|
||||||
// console.log(this.get())
|
// console.log(this.get())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
} // end traverse
|
} // end traverse
|
||||||
const value = _get(this,path)
|
const value = $get(this,path)
|
||||||
if (value === undefined) {
|
if (value === undefined ) {
|
||||||
console.log ('no current property for', path)
|
// console.log ('no current property or value for',path,'creating temporary null')
|
||||||
_set(this,path,null)
|
$set(this,path,null)
|
||||||
}
|
}
|
||||||
const {parent,name} = this.rxGetObj(path,'__parent__')
|
const {parent,name} = this.rxGetObj(path,'__parent__')
|
||||||
const rx = this.setPath(['_rx_.props',path],{})
|
$set(this,this._rxFormatPath(['_rx_.props',path]),{})
|
||||||
rx.value = value
|
const rx = $get(this,this._rxFormatPath(['_rx_.props',path]))
|
||||||
|
|
||||||
|
// console.log('moving',opts.value != null ? opts.value : value,path)
|
||||||
|
rx.value = opts.value != null ? opts.value : value
|
||||||
|
rx.path = path
|
||||||
rx.obs = from(new BehaviorSubject({value:value, path:path}).pipe(
|
rx.obs = from(new BehaviorSubject({value:value, path:path}).pipe(
|
||||||
// rx.obs = from(new ReplaySubject(1).pipe(
|
// rx.obs = from(new ReplaySubject(1).pipe(
|
||||||
skip(this._rx_.skip),
|
skip(this._rx_.skip),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged()
|
||||||
// ,
|
// ,
|
||||||
// takeUntil(_get(this._deleted,path))
|
// takeUntil($get(this.$deleted,path))
|
||||||
))
|
))
|
||||||
rx.subs = {}
|
rx.subs = {}
|
||||||
this.rxSubscribe(rx,this._rx_.handler,'_default')
|
this.rxSubscribe(rx,this._rx_.handler,'_default')
|
||||||
this.rxSubscribe(rx,(opts.subscribe ||{}).handler,(opts.subscribe || {}).name)
|
this.rxSubscribe(rx,(opts.subscribe ||{}).handler,(opts.subscribe || {}).name)
|
||||||
|
|
||||||
const self = this
|
const self = this
|
||||||
Object.defineProperty(parent, name, {
|
Object.defineProperty(parent, name, {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get () {return rx.value},
|
get () {
|
||||||
|
return rx.value
|
||||||
|
},
|
||||||
set (value) {
|
set (value) {
|
||||||
rx.value = value
|
rx.value = value
|
||||||
rx.obs.next({value:value, path:path})
|
rx.obs.next({value:value, path:path})
|
||||||
|
@ -91,11 +107,11 @@ export default class RxClass extends EventEmitter {
|
||||||
path = path.split('.')
|
path = path.split('.')
|
||||||
const name = path.pop()
|
const name = path.pop()
|
||||||
path = path.join('.')
|
path = path.join('.')
|
||||||
let parent = path ? _get(this,path) : this
|
let parent = path ? $get(this,path) : this
|
||||||
// console.log(name,path,parent)
|
// console.log(name,path,parent)
|
||||||
if (parent === null) parent = {}
|
if (parent === null) parent = {}
|
||||||
if (prop==='__parent__') return {name:name, parent:parent, parentPath:path}
|
if (prop==='__parent__') return {name:name, parent:parent, parentPath:path}
|
||||||
const rx = this.getPath(['_rx_.props',path,name],true)
|
const rx = this.get(['_rx_.props',path,name],true)
|
||||||
// console.log(path,name,'reactive object',rx)
|
// console.log(path,name,'reactive object',rx)
|
||||||
return prop ? (rx || {})[prop] : rx
|
return prop ? (rx || {})[prop] : rx
|
||||||
}
|
}
|
||||||
|
@ -104,7 +120,7 @@ export default class RxClass extends EventEmitter {
|
||||||
rxRemove(path,opts={}) {
|
rxRemove(path,opts={}) {
|
||||||
if (opts.traverse) {
|
if (opts.traverse) {
|
||||||
const self = this
|
const self = this
|
||||||
traverse(_get(this,path)).map(function () {
|
traverse($get(this,path)).map(function () {
|
||||||
if (this.isLeaf) {
|
if (this.isLeaf) {
|
||||||
const lpath = this.path
|
const lpath = this.path
|
||||||
lpath.unshift(path)
|
lpath.unshift(path)
|
||||||
|
@ -116,7 +132,7 @@ export default class RxClass extends EventEmitter {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let { parent, name, parentPath } = this.rxGetObj(path,'__parent__')
|
let { parent, name, parentPath } = this.rxGetObj(path,'__parent__')
|
||||||
const rxparent = this.getPath(['_rx_.props',parentPath],true)
|
const rxparent = this.get(['_rx_.props',parentPath],true)
|
||||||
const rx = rxparent[name]
|
const rx = rxparent[name]
|
||||||
if (rx && Object.getOwnPropertyDescriptor(parent, name)['get']) {
|
if (rx && Object.getOwnPropertyDescriptor(parent, name)['get']) {
|
||||||
Object.values(rx.subs).forEach(sub=>sub.unsubscribe())
|
Object.values(rx.subs).forEach(sub=>sub.unsubscribe())
|
||||||
|
@ -128,7 +144,6 @@ export default class RxClass extends EventEmitter {
|
||||||
|
|
||||||
// pass rx as object or path
|
// pass rx as object or path
|
||||||
rxSubscribe(rxObj,handler,name){
|
rxSubscribe(rxObj,handler,name){
|
||||||
// console.log('subscription',rxObj)
|
|
||||||
if (typeof name !== 'string') return
|
if (typeof name !== 'string') return
|
||||||
rxObj = (typeof rxObj === 'string') ? this.rxGetObj(rxObj) : rxObj
|
rxObj = (typeof rxObj === 'string') ? this.rxGetObj(rxObj) : rxObj
|
||||||
if (typeof handler==='function') rxObj.subs[name] = rxObj.obs.subscribe(handler)
|
if (typeof handler==='function') rxObj.subs[name] = rxObj.obs.subscribe(handler)
|
||||||
|
@ -142,40 +157,50 @@ export default class RxClass extends EventEmitter {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
setObjPath () {return _set(...arguments)}
|
isRx(path) {
|
||||||
getObjPath () {return _get(...arguments)}
|
const { parent, name, parentPath } = this.rxGetObj(path,'__parent__')
|
||||||
delObjPath () { return _del(...arguments)}
|
const rxparent = this.get(['_rx_.props',parentPath],true) || {}
|
||||||
|
const rx = rxparent[name]
|
||||||
|
// console.log ('rx parent path',rxparent, rx, !!rx && !!Object.getOwnPropertyDescriptor(parent, name)['get'])
|
||||||
|
return (!!rx && !!Object.getOwnPropertyDescriptor(parent, name)['get'])
|
||||||
|
}
|
||||||
|
|
||||||
|
setObjPath () {return $set(...arguments)}
|
||||||
|
getObjPath () {return $get(...arguments)}
|
||||||
|
delObjPath () {return $del(...arguments)}
|
||||||
|
|
||||||
delPath(path,confirm) {
|
delPath(path,confirm) {
|
||||||
path = this._rxFormatPath(path)
|
path = this._rxFormatPath(path)
|
||||||
if (confirm) _del(this,path)
|
if (confirm) $del(this,path)
|
||||||
else console.warn('must confirm to delete path',path)
|
else console.warn('must confirm to delete path',path)
|
||||||
}
|
}
|
||||||
|
|
||||||
getPath(path,raw){
|
get(path,raw){
|
||||||
path = this._rxFormatPath(path)
|
path = this._rxFormatPath(path)
|
||||||
const value = _get(this,path)
|
const value = $get(this,path)
|
||||||
if (!isPlainObject(value) || raw) return value
|
if (!isPlainObject(value) || raw) return value
|
||||||
let obj = {}
|
let obj = {} // return a copy with actual values
|
||||||
traverse(_get(this,path)).map(function () {
|
traverse($get(this,path)).map(function () {
|
||||||
if (this.isLeaf) {
|
if (this.isLeaf) {
|
||||||
// let leaf = this.path // this path is immutable
|
// console.log(obj,this.path.join('.'),this.node)
|
||||||
// console.log(this.path,this.node)
|
$set(obj,this.path.join('.'),this.node)
|
||||||
// leaf = leaf.pop()
|
|
||||||
_set(obj,this.path.join('.'),this.node)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
setPath(path,value){
|
set(path,value,opts={}){
|
||||||
path = this._rxFormatPath(path)
|
if (!opts.noRx || !this.isRx(path)) {
|
||||||
const curValue = _get(this,path)
|
opts = Object.assign(isPlainObject(value) ? {values:value,traverse:true} : {value:value},opts)
|
||||||
if (!equal(curValue,value) || !value) {
|
this.rxAdd(path,opts)
|
||||||
_set(this,path,value)
|
|
||||||
}
|
}
|
||||||
return _get(this,path)
|
path = this._rxFormatPath(path)
|
||||||
|
const curValue = $get(this,path)
|
||||||
|
if (!equal(curValue,value) || !value) {
|
||||||
|
$set(this,path,value)
|
||||||
|
}
|
||||||
|
return $get(this,path)
|
||||||
}
|
}
|
||||||
|
|
||||||
} // end class
|
} // end class
|
||||||
|
|
Loading…
Reference in New Issue