improve several aspects

changed set to make reactive by default, and also traverse and make reactive a plain object by default
master
David Kebler 2020-06-08 14:32:52 -07:00
parent 47e4c34ef5
commit 7ea8c13cbd
4 changed files with 98 additions and 66 deletions

View File

@ -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')

View File

@ -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)

View File

@ -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?

View File

@ -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