From 7ea8c13cbde1e25a7d0f34a27bfe7bbaef912206 Mon Sep 17 00:00:00 2001 From: David Kebler Date: Mon, 8 Jun 2020 14:32:52 -0700 Subject: [PATCH] improve several aspects changed set to make reactive by default, and also traverse and make reactive a plain object by default --- examples/example.js | 8 ++-- examples/traverse.js | 51 ++++++++++++--------- readme.md | 2 +- src/rx-class.js | 103 +++++++++++++++++++++++++++---------------- 4 files changed, 98 insertions(+), 66 deletions(-) diff --git a/examples/example.js b/examples/example.js index aef151f..9b64cb6 100644 --- a/examples/example.js +++ b/examples/example.js @@ -27,8 +27,8 @@ console.log('changing test to 20, 30 directly') example.test = 20 example.test = 30 example.rxSubscribe('test',(prop)=>console.log('#############late subscription to \'test\' after 30 ############',prop),'custom') -console.log('changing test to 40 via setPath') -example.setPath('test',40) +console.log('changing test to 40 via set') +example.set('test',40) console.log('making a deeper property \'another.foo\' reactive') example.rxAdd('another.foo') @@ -36,7 +36,7 @@ example.rxAdd('another.foo') example.another.foo = 7 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') example.rxAdd('some.thing') @@ -51,7 +51,7 @@ console.log('--------------------------') console.log('now removing reactivity from \'test\'') example.rxRemove('test') -example.setPath('test',42) +example.set('test',42) console.log('\'test\' now holds unreactive value') console.log('now removing reactivity from \'test\' again') example.rxRemove('test') diff --git a/examples/traverse.js b/examples/traverse.js index 3dcdd46..8ad3159 100644 --- a/examples/traverse.js +++ b/examples/traverse.js @@ -4,8 +4,8 @@ class Example extends RxClass { constructor(opts={}){ super(opts) this.test = { - yesterday: {bar:'bust', foo:3}, - today: {bing:5, bong:{who:'first',what:'second'}} + tomrrow: {bar:'fight', foo:'fighters'}, + today: {bing:'bing', bong:{who:'first',what:'second'}} } } } @@ -13,35 +13,42 @@ class Example extends RxClass { const example = new Example({ _rx_ : { 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(example) console.log('--------------------------') -example.on('changed', (prop) => console.log('emitted----a property changed-----',prop)) -example.on('test', (val) => console.log('emitted value of test',val)) +// example.on('changed', (prop) => console.log('emitted----a property changed-----',prop)) +// 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.test.today.bong.what = 'third' -example.setPath('test.today.bong.what','forth') -example.setPath('test.today.bong.why','do not know') +console.log('adding new property which is made reactive by default \'test.today.bong.why\'') +example.set('test.today.bong.why','do not know') -console.log('\'test\', values object >', example.getPath('test')) -console.log('\'test\', raw object >', example.getPath('test',true)) +console.log('------\'test\', raw object ------ \n', example.get('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('instance after adding rx to leaves\n') -console.dir(example.test) -console.dir(example._rx_.props) -console.log('--------------------------') -console.log('now remove all rx on \'test.yesterday\'\n') -example.rxRemove('test.today',{traverse:true}) -console.dir(example.test) -console.log('rx props') -console.dir(example._rx_.props) -console.log('--------------------------') -console.dir(example) + +console.log('now change a reactive property directly like') +console.log('example.foundation.giskard = \'daneel a robot\'') +example.foundation.giskard = 'daneel a robot' +console.log('the new value extracted by the getter is',example.foundation.giskard) diff --git a/readme.md b/readme.md index 3b03f33..270b475 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # 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? diff --git a/src/rx-class.js b/src/rx-class.js index 19f7875..d80529b 100644 --- a/src/rx-class.js +++ b/src/rx-class.js @@ -2,9 +2,9 @@ import { EventEmitter } from 'events' import { BehaviorSubject, from } from 'rxjs' import { distinctUntilChanged, skip } from 'rxjs/operators' // ** nested object access ** -import _get from 'get-value' -import _set from 'set-value' -import _del from 'unset-value' +import $get from 'get-value' +import $set from 'set-value' +import $del from 'unset-value' // ********************* import equal from 'deep-equal' import traverse from 'traverse' @@ -12,13 +12,13 @@ import isPlainObject from 'is-plain-object' export default class RxClass extends EventEmitter { constructor(opts){ - // console.log('constructor', opts) super(opts) this._rx_ = { event: opts._rx_.event || 'changed', handler: opts._rx_.handler, props: {}, hooks: [], + root: '', // root path/namespace to added to all get/set paths operators:[], 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 rxAdd(opath,opts={}) { 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) { + const obj = opts.values ? opts.values : $get(this,path) const self = this - traverse(_get(this,path)).map(function () { + // console.log('object to traverse', obj) + traverse(obj).map(function () { if (this.isLeaf) { + // let lpath = [] + // if (opts.values) { + // lpath =[] + // } else { const lpath = this.path lpath.unshift(path) - // console.log('leaf',lpath,this.node) - self.rxAdd(lpath) + // console.log(`${!opts.values ? 'existing':'new'} leaf on path '${lpath}' with value: ${this.node}`) + self.rxAdd(lpath,{value:this.node}) // console.log(this.get()) } } ) return } // end traverse - const value = _get(this,path) - if (value === undefined) { - console.log ('no current property for', path) - _set(this,path,null) + const value = $get(this,path) + if (value === undefined ) { + // console.log ('no current property or value for',path,'creating temporary null') + $set(this,path,null) } const {parent,name} = this.rxGetObj(path,'__parent__') - const rx = this.setPath(['_rx_.props',path],{}) - rx.value = value + $set(this,this._rxFormatPath(['_rx_.props',path]),{}) + 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 ReplaySubject(1).pipe( skip(this._rx_.skip), distinctUntilChanged() // , - // takeUntil(_get(this._deleted,path)) + // takeUntil($get(this.$deleted,path)) )) rx.subs = {} this.rxSubscribe(rx,this._rx_.handler,'_default') this.rxSubscribe(rx,(opts.subscribe ||{}).handler,(opts.subscribe || {}).name) - const self = this Object.defineProperty(parent, name, { configurable: true, enumerable: true, - get () {return rx.value}, + get () { + return rx.value + }, set (value) { rx.value = value rx.obs.next({value:value, path:path}) @@ -91,11 +107,11 @@ export default class RxClass extends EventEmitter { path = path.split('.') const name = path.pop() path = path.join('.') - let parent = path ? _get(this,path) : this + let parent = path ? $get(this,path) : this // console.log(name,path,parent) if (parent === null) parent = {} 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) return prop ? (rx || {})[prop] : rx } @@ -104,7 +120,7 @@ export default class RxClass extends EventEmitter { rxRemove(path,opts={}) { if (opts.traverse) { const self = this - traverse(_get(this,path)).map(function () { + traverse($get(this,path)).map(function () { if (this.isLeaf) { const lpath = this.path lpath.unshift(path) @@ -116,7 +132,7 @@ export default class RxClass extends EventEmitter { return } 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] if (rx && Object.getOwnPropertyDescriptor(parent, name)['get']) { Object.values(rx.subs).forEach(sub=>sub.unsubscribe()) @@ -128,7 +144,6 @@ export default class RxClass extends EventEmitter { // pass rx as object or path rxSubscribe(rxObj,handler,name){ - // console.log('subscription',rxObj) if (typeof name !== 'string') return rxObj = (typeof rxObj === 'string') ? this.rxGetObj(rxObj) : rxObj if (typeof handler==='function') rxObj.subs[name] = rxObj.obs.subscribe(handler) @@ -142,40 +157,50 @@ export default class RxClass extends EventEmitter { return path } - setObjPath () {return _set(...arguments)} - getObjPath () {return _get(...arguments)} - delObjPath () { return _del(...arguments)} + isRx(path) { + const { parent, name, parentPath } = this.rxGetObj(path,'__parent__') + 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) { path = this._rxFormatPath(path) - if (confirm) _del(this,path) + if (confirm) $del(this,path) else console.warn('must confirm to delete path',path) } - getPath(path,raw){ + get(path,raw){ path = this._rxFormatPath(path) - const value = _get(this,path) + const value = $get(this,path) if (!isPlainObject(value) || raw) return value - let obj = {} - traverse(_get(this,path)).map(function () { + let obj = {} // return a copy with actual values + traverse($get(this,path)).map(function () { if (this.isLeaf) { - // let leaf = this.path // this path is immutable - // console.log(this.path,this.node) - // leaf = leaf.pop() - _set(obj,this.path.join('.'),this.node) + // console.log(obj,this.path.join('.'),this.node) + $set(obj,this.path.join('.'),this.node) } } ) return obj } - setPath(path,value){ - path = this._rxFormatPath(path) - const curValue = _get(this,path) - if (!equal(curValue,value) || !value) { - _set(this,path,value) + set(path,value,opts={}){ + if (!opts.noRx || !this.isRx(path)) { + opts = Object.assign(isPlainObject(value) ? {values:value,traverse:true} : {value:value},opts) + this.rxAdd(path,opts) } - 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