support for name spacing of reactive methods and objects/fields
support for namesapce and preface of get and setmaster
parent
4b76b80f07
commit
ce0e77399a
37
.eslintrc.js
37
.eslintrc.js
|
@ -1,37 +0,0 @@
|
|||
module.exports = {
|
||||
"ecmaFeatures": {
|
||||
"modules": true,
|
||||
"spread" : true,
|
||||
"restParams" : true
|
||||
},
|
||||
// "plugins": [
|
||||
// "unicorn"
|
||||
// ],
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"mocha": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2017,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
2
|
||||
],
|
||||
// "unicorn/no-array-instanceof": "error",
|
||||
"no-console": 0,
|
||||
"semi": ["error", "never"],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"single"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,59 +1,79 @@
|
|||
import RxClass from '../src/rx-class.js'
|
||||
|
||||
class Example extends RxClass {
|
||||
constructor(opts={}){
|
||||
const log = o => console.dir(o, { depth: 5 })
|
||||
|
||||
const rxopts = Symbol.for('rx-class-opts')
|
||||
|
||||
class Example extends RxClass {
|
||||
constructor (opts = {}) {
|
||||
super(opts)
|
||||
this.test = {
|
||||
tomrrow: {bar:'fight', foo:'fighters'},
|
||||
today: {bing:'bing', bong:{who:'first',what:'second'}}
|
||||
tomrrow: { bar: 'fight', foo: 'fighters' },
|
||||
today: { bing: 'bing', bong: { who: 'first', what: 'second' } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const example = new Example({
|
||||
_rx_ : {
|
||||
handler: value => {console.log('-----------default subscription handler-------', value)},
|
||||
const opts = {
|
||||
[rxopts]: {
|
||||
handler: value => { console.log('-----------default subscription handler-------', value) },
|
||||
skip: 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
example.foundation = null
|
||||
console.log('options\n', opts)
|
||||
|
||||
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('--------------------------')
|
||||
const example = new Example(opts)
|
||||
|
||||
// example.on('changed', (prop) => console.log('emitted----a property changed-----',prop))
|
||||
// example.on('test', (val) => console.log('emitted value of test',val))
|
||||
// console.log('-----EXAMPLE: TRAVERSE OF EXISTING OBJECT OR WHEN SETTING A PLAIN OBJECT-----------')
|
||||
// console.log('instance before doing rx on properties\n')
|
||||
// // log(example)
|
||||
// console.log('------------------------------------------')
|
||||
|
||||
console.log('traversing exitings object \'test\' and making all leaves reactive')
|
||||
console.log(example.test)
|
||||
example.rxAdd('test',{traverse:true})
|
||||
// example.on('changed', (prop) => console.log('emitted----a property changed-----', prop))
|
||||
// example.on('test', (val) => console.log('emitted value of test', val))
|
||||
|
||||
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('----- traversing existing object \'test\' and making all leaves reactive ------\n')
|
||||
// example.rxAdd('test', { traverse: true })
|
||||
// // log(example)
|
||||
// console.log('------------------------------------------')
|
||||
|
||||
console.log('------\'test\', raw object ------ \n', example.$get('test'))
|
||||
console.log('------ \'test\', values object ------\n>', example.get('test'))
|
||||
console.log(example)
|
||||
console.log('now removing reactivty from \'test\'')
|
||||
example.rxRemove('test',{confirm:true})
|
||||
console.log(example.$get('test'))
|
||||
console.log('-------------------------')
|
||||
// console.log('now get value for bong directly and via get', example.test.today.bong, example.get('test.today.bong'))
|
||||
// console.log('Note: using example.get will traverse bong and get actual values')
|
||||
|
||||
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('adding new property which is made reactive by default \'test.today.bong.why\'')
|
||||
// example.set('test.today.bong.why', 'do not know')
|
||||
|
||||
console.log('------\'foundation\', raw object ------ \n', example.$get('foundation'))
|
||||
console.log('------ \'foundation\', values object ------\n>', example.get('foundation'))
|
||||
// console.log('------\'.bong\', raw object ------ \n', example.test.today.bong)
|
||||
// console.log('------ \'.bong\', values object ------\n>', example.get('test.today.bong'))
|
||||
// console.log('------, note: raw leaf access returns value not [Getter/Setter] ==>', example.test.today.bong.why)
|
||||
// console.log('\nnow removing reactivty from \'test\'')
|
||||
// example.rxRemove('test', { confirm: true })
|
||||
// console.log('------\'.test\', raw object ------ \n', example.test)
|
||||
// console.log('--------------------------')
|
||||
|
||||
console.log('--------instance after doing rx----\n')
|
||||
console.log(example)
|
||||
console.log('--------------------------')
|
||||
console.log('\n\nnow add a completely new object to root of \'foundation\' and make it reactive without traverse')
|
||||
example.foundation = { planets: ['Trantor', 'Helcion'], persons: { giskard: { alias: 'daneel' }, hari: { name: 'seldon', planet: 'Helcion' } } }
|
||||
console.dir(example.foundation)
|
||||
example.rxAdd('foundation.persons.hari.name')
|
||||
// console.log('foundation', example.isRx('foundation'))
|
||||
console.log(example.isRx('foundation.persons.hari'))
|
||||
// console.log('------\'foundation\', raw object ------ \n')
|
||||
// // log(example)
|
||||
// console.log('get raw nested property raw or with get returns the same')
|
||||
// console.log(example.foundation.hari, example.get('foundation.hari'))
|
||||
|
||||
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)
|
||||
// console.log('now change one of the nested properties and see if whole object foundation is reactive')
|
||||
|
||||
// example.foundation.hari.planet = 'Trantor'
|
||||
// example.set('foundation.hari.planet', 'Trantor')
|
||||
|
||||
// console.log(example.foundation)
|
||||
// log(example)
|
||||
// console.log('--------instance after doing rx----\n')
|
||||
// console.log(example)
|
||||
// console.log('--------------------------')
|
||||
|
||||
// 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)
|
||||
|
|
|
@ -31,8 +31,9 @@
|
|||
},
|
||||
"homepage": "https://github.com/uCOMmandIt/uci-utils#readme",
|
||||
"dependencies": {
|
||||
"@uci-utils/bind-funcs": "^0.3.0",
|
||||
"@uci-utils/logger": "^0.1.0",
|
||||
"@uci-utils/obj-nested-prop": "^0.1.1",
|
||||
"@uci-utils/obj-nested-prop": "^0.1.2",
|
||||
"@uci-utils/type": "^0.6.2",
|
||||
"deep-equal": "^2.0.5",
|
||||
"rxjs": "^7.0.0",
|
||||
|
|
310
src/rx-class.js
310
src/rx-class.js
|
@ -6,129 +6,138 @@ import { BehaviorSubject, from } from 'rxjs'
|
|||
import { distinctUntilChanged, skip } from 'rxjs/operators'
|
||||
// uci modules
|
||||
import { check } from '@uci-utils/type'
|
||||
import { get, set, del, walkPath, getKeys, pathToString } from '@uci-utils/obj-nested-prop'
|
||||
import bind from '@uci-utils/bind-funcs'
|
||||
import { get as $get, set as $set, del as $del, walkPath, getKeys, pathToString } from '@uci-utils/obj-nested-prop'
|
||||
import { logger } from '@uci-utils/logger'
|
||||
// supporting
|
||||
import equal from 'deep-equal'
|
||||
import traverse from 'traverse'
|
||||
|
||||
const log = logger({ file: '/src/rx-class.js', package: '@uci-utils/rx-class', class: 'RxClass' })
|
||||
|
||||
// to pass rx options use 'Symbol.for('rx-class-opts')'
|
||||
class RxClass extends EventEmitter {
|
||||
// private fields
|
||||
#rx
|
||||
|
||||
constructor (opts = {}) {
|
||||
super(opts)
|
||||
const rxopts = opts[Symbol.for('rx-class-opts')] || {}
|
||||
this.#rx = {
|
||||
Object.defineProperty(this, '$$__rxns__$$', {
|
||||
enumerable: false,
|
||||
value: rxopts.namespace || 'rx'
|
||||
})
|
||||
this[this.$$__rxns__$$] = bind(methods, this)
|
||||
this[this.$$__rxns__$$].props = {} // where reactive property info is stored including actual values
|
||||
this[this.$$__rxns__$$].opts = {
|
||||
emitter: rxopts.emitter != null ? rxopts.emitter : true,
|
||||
event: rxopts.event === false ? false : rxopts.event || 'changed',
|
||||
handler: rxopts.handler, // default subscription handler
|
||||
props: {}, // where reactive property info is store including actual values
|
||||
amendValue: rxopts.amendValue,
|
||||
// amend_path: rxopts.amend_path, // by default amend value with path
|
||||
hook: rxopts.hook, // function called when setting
|
||||
namespace: rxopts.namespace || 'rx', // TODO namespace to added to all public rx methods
|
||||
namespace: this.$$__rxns__$$, // TODO namespace to added to all public rx methods
|
||||
operators: new Map(), // TODO added to all rx pipes, in order
|
||||
skip: rxopts.skip == null ? 1 : rxopts.skip
|
||||
}
|
||||
this.isRx.bind(this)
|
||||
}
|
||||
console.log(`${rxopts.getSetPrefix || ''}set`)
|
||||
$set(this, [rxopts.getSetNamespace, `${rxopts.getSetPrefix || ''}set`], set.bind(this))
|
||||
$set(this, [rxopts.getSetNamespace, `${rxopts.getSetPrefix || ''}get`], get.bind(this))
|
||||
console.log(this)
|
||||
} // end constructor
|
||||
// reactive methods are bound at instantiation
|
||||
} // end class
|
||||
|
||||
get (path) {
|
||||
const value = this.#get(path)
|
||||
if (!check.isPlainObject(value)) return value
|
||||
const obj = {} // return a copy with actual values instead of getters
|
||||
const self = this
|
||||
traverse(this.#get(path)).map(function () {
|
||||
if (this.isLeaf) {
|
||||
self.#set(obj, this.path, this.node)
|
||||
}
|
||||
}
|
||||
)
|
||||
return obj
|
||||
}
|
||||
// helpers
|
||||
|
||||
set (path, value, opts = {}) {
|
||||
console.log('set:', path, value, opts, !this.isRx(path))
|
||||
if (!opts.noRx && !this.isRx(path)) { // travsere
|
||||
opts = Object.assign(
|
||||
(check.isPlainObject(value) && opts.traverse !== false)
|
||||
? { values: value, traverse: true }
|
||||
: { value: value }
|
||||
, opts)
|
||||
console.log('adding rx from set', opts)
|
||||
this.rxAdd(path, opts)
|
||||
} else {
|
||||
const curValue = this.#get(path)
|
||||
if (!equal(curValue, value) && value !== undefined) {
|
||||
console.log('setting already reactive value', path, value)
|
||||
this.#set(path, value)
|
||||
console.log('value that was set', this.#get(path))
|
||||
}
|
||||
return this.#get(path)
|
||||
export default RxClass
|
||||
export { RxClass, $set as set, $get as get, $del as del, getKeys, walkPath, pathToString }
|
||||
|
||||
function get (path) {
|
||||
const value = _.get.call(this, path)
|
||||
if (!check.isPlainObject(value)) return value
|
||||
const obj = {} // return a copy with actual values instead of getters
|
||||
const self = this
|
||||
traverse(_.get.call(this, path)).map(function () {
|
||||
if (this.isLeaf) {
|
||||
_.set.call(self, obj, this.path, this.node)
|
||||
}
|
||||
}
|
||||
)
|
||||
return obj
|
||||
}
|
||||
|
||||
getRaw () {
|
||||
return get(this, [this.#rx.namespace, ...arguments])
|
||||
function set (path, value, opts = {}) {
|
||||
console.log('set:', path, value, opts, !this[this.$$__rxns__$$].isRx(path))
|
||||
if (!opts.noRx && !this[this.$$__rxns__$$].isRx(path)) { // travsere
|
||||
opts = Object.assign(
|
||||
(check.isPlainObject(value) && opts.traverse !== false)
|
||||
? { values: value, traverse: true }
|
||||
: { value: value }
|
||||
, opts)
|
||||
console.log('adding rx from set', opts)
|
||||
this[this.$$__rxns__$$].add(path, opts)
|
||||
} else {
|
||||
const curValue = _.get.call(this, path)
|
||||
if (!equal(curValue, value) && value !== undefined) {
|
||||
console.log('setting already reactive value', path, value)
|
||||
_.set.call(this, path, value)
|
||||
console.log('value that was set', _.get.call(this, path))
|
||||
}
|
||||
return _.get.call(this, path)
|
||||
}
|
||||
}
|
||||
|
||||
// if property exists it moves value to #rx.props and creates getter and setter and behavior subject
|
||||
rxAdd (path, opts = {}) {
|
||||
// public methods will be namespaced (default is rx.)
|
||||
const methods = {
|
||||
add: function add (path, opts = {}) {
|
||||
if (opts.traverse) { // will add rx to all leaves
|
||||
const obj = opts.values ? opts.values : this.#get(path)
|
||||
const obj = opts.values ? opts.values : _.get.call(this, path)
|
||||
const self = this
|
||||
// console.log('object to traverse', obj)
|
||||
traverse(obj).map(function () {
|
||||
// console.log('traversing', this)
|
||||
// console.log('traversing', this)
|
||||
if (this.isLeaf) {
|
||||
const lpath = this.path
|
||||
lpath.unshift(path)
|
||||
// console.log('#{!opts.values ? \'existing\':\'new\'} leaf on path \'#{lpath}\' with value: #{this.node}')
|
||||
self.rxAdd(lpath, { value: this.node })
|
||||
self[this.$$__rxns__$$].add(lpath, { value: this.node })
|
||||
}
|
||||
}
|
||||
)
|
||||
return true
|
||||
} // end traverse
|
||||
const value = this.#get(path) // current value
|
||||
const value = _.get.call(this, path) // current value
|
||||
if (value === undefined) {
|
||||
// console.log('no current property or value for', path, 'creating temporary null')
|
||||
this.#set(path, null)
|
||||
// console.log('no current property or value for', path, 'creating temporary null')
|
||||
_.set.call(this, path, null)
|
||||
}
|
||||
if (this.isRx(path) && !opts.force) return true
|
||||
if (this[this.$$__rxns__$$].isRx(path) && !opts.force) return true
|
||||
console.log(path, 'making reactive')
|
||||
const { parent, name } = this.#rxGetObj(path, '__parent__')
|
||||
const { parent, name } = _.rxGetObj.call(this, path, '__parent__')
|
||||
log.debug({
|
||||
class: 'RxClass',
|
||||
method: '#rxGetObj',
|
||||
method: 'rx.add',
|
||||
path: path,
|
||||
name: name,
|
||||
parent: parent,
|
||||
msg: 'got name and parent'
|
||||
})
|
||||
this.#set(['#rx.props', path], {})
|
||||
const rx = this.#get(['#rx.props', path])
|
||||
_.set.call(this, [this.$$__rxns__$$, 'props', path], {})
|
||||
const rx = _.get.call(this, [this.$$__rxns__$$, 'props', path])
|
||||
// console.log('moving',opts.value != null ? opts.value : value,path)
|
||||
rx.value = opts.value != null ? opts.value : value
|
||||
rx.path = path
|
||||
rx.amendValue = opts.amendValue || this.#rx.amendValue || ((val) => val)
|
||||
rx.amendValue = opts.amendValue || this[this.$$__rxns__$$].opts.amendValue || ((val) => val)
|
||||
rx.obs = from(new BehaviorSubject(rx.amendValue(rx.value, rx.path)).pipe(
|
||||
skip(opts.skip != null ? opts.skip : this.#rx.skip),
|
||||
skip(opts.skip != null ? opts.skip : this[this.$$__rxns__$$].opts.skip),
|
||||
distinctUntilChanged() // allow custom comparator
|
||||
// allow custom/additional operators here
|
||||
// takeUntil(#get(this.#deleted,path))
|
||||
// allow custom/additional operators here
|
||||
// takeUntil(#get(this.#deleted,path))
|
||||
))
|
||||
// console.log(path,'---------------\n',Object.getOwnPropertyNames(Object.getPrototypeOf(rx.obs)),rx.obs.subscribe)
|
||||
rx.subs = {}
|
||||
rx.hook = opts.hook
|
||||
if (check.isFunction(this.#rx.handler)) this.rxSubscribe(rx, this.#rx.handler, '_default_')
|
||||
if (check.isFunction(this[this.$$__rxns__$$].opts.handler)) this[this.$$__rxns__$$].subscribe(rx, this[this.$$__rxns__$$].opts.handler, '_default_')
|
||||
const subs = Object.entries(opts.subscribe || {})
|
||||
subs.forEach(sub => {
|
||||
if (check.isFunction(sub[1])) this.rxSubscribe(rx, sub[1], sub[0])
|
||||
if (check.isFunction(sub[1])) this[this.$$__rxns__$$].subscribe(rx, sub[1], sub[0])
|
||||
})
|
||||
const self = this
|
||||
Object.defineProperty(parent, name, {
|
||||
|
@ -141,9 +150,9 @@ class RxClass extends EventEmitter {
|
|||
rx.value = value
|
||||
value = rx.amendValue(value, path)
|
||||
rx.obs.next(value) // react
|
||||
if (self.#rx.emitter) { // emit
|
||||
if (self[this.$$__rxns__$$].opts.emitter) { // emit
|
||||
if (opts.event) self.emit(opts.event, value, path) // custom event
|
||||
if (self.#rx.event) self.emit(self.#rx.event, value, path) // global event
|
||||
if (self[this.$$__rxns__$$].opts.event) self.emit(self[this.$$__rxns__$$].opts.event, value, path) // global event
|
||||
const spath = pathToString(path)
|
||||
if (spath) self.emit(spath, value, path) // also emit path if is stringable.
|
||||
self.emit(name, value, path)
|
||||
|
@ -151,52 +160,47 @@ class RxClass extends EventEmitter {
|
|||
// any hook function that is already bound will use that context, otherwise this
|
||||
// hooks
|
||||
// TODO write with for loop and async await
|
||||
if (check.isFunction(self.#rx.hook)) self.#rx.hook.call(self, value) // global hook
|
||||
if (check.isFunction(self[this.$$__rxns__$$].opts.hook)) self[this.$$__rxns__$$].opts.hook.call(self, value) // global hook
|
||||
if (check.isFunction(rx.hook)) rx.hook.call(self, value) // property hook
|
||||
}
|
||||
})
|
||||
// console.log('rxadd done', path, opts, this.#get(['#rx.props', path]))
|
||||
// console.log('rxadd done', path, opts, _.get.call(this,[this.$$__rxns__$$,'props', path]))
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
||||
// if name is only passed remove
|
||||
// rxHook (func, path) {
|
||||
// this.#rx.hooks[name] = func
|
||||
// }
|
||||
|
||||
rxAmendValue (path, func) {
|
||||
if (arguments.length === 0) { this.#rx.amendValue = null; return }
|
||||
if (typeof path === 'function') { this.#rx.amendValue = path; return }
|
||||
const rx = this.#rxGetObj(path)
|
||||
amendValue: function amendValue (path, func) {
|
||||
if (arguments.length === 0) { this[this.$$__rxns__$$].opts.amendValue = null; return }
|
||||
if (typeof path === 'function') { this[this.$$__rxns__$$].opts.amendValue = path; return }
|
||||
const rx = _.rxGetObj.call(this, path)
|
||||
if (!check.isEmptyPlainObject(rx)) return false
|
||||
func = func || (val => val)
|
||||
rx.amendValue = func === 'path' ? (value, path) => { return { value: value, path: path } } : func
|
||||
return !!rx.amendValue
|
||||
}
|
||||
},
|
||||
|
||||
// if name is only passed remove
|
||||
rxOperator (func, name) {
|
||||
this.#rx.operators[name] = func
|
||||
}
|
||||
operator: function Operator (func, name) {
|
||||
this[this.$$__rxns__$$].opts.operators[name] = func
|
||||
},
|
||||
|
||||
// removes obser, subscriptions, and getter and setter and make back into plain value
|
||||
rxRemove (path, opts = {}) {
|
||||
remove: function remove (path, opts = {}) {
|
||||
if (!opts.confirm) return false // must confirm to remove
|
||||
if (!this.isRx(path) && check.isPlainObject(this.#get(path))) {
|
||||
if (!this[this.$$__rxns__$$].isRx(path) && check.isPlainObject(_.get.call(this, path))) {
|
||||
const self = this
|
||||
traverse(this.#get(path)).map(function () {
|
||||
traverse(_.get.call(this, path)).map(function () {
|
||||
if (this.isLeaf) {
|
||||
const lpath = this.path
|
||||
lpath.unshift(path)
|
||||
if (self.isRx(lpath)) self.rxRemove(lpath, { confirm: true })
|
||||
if (self[this.$$__rxns__$$].isRx(lpath)) self[this.$$__rxns__$$].remove(lpath, { confirm: true })
|
||||
}
|
||||
})
|
||||
// all done removing the leaves so remove branch
|
||||
this.#del(['#rx.props', path], true)
|
||||
_.del.call(this, [this.$$__rxns__$$, 'props', path], true)
|
||||
return true
|
||||
}
|
||||
const { parent, name, parentPath } = this.#rxGetObj(path, '__parent__')
|
||||
const rxparent = this.#get(['#rx.props', parentPath])
|
||||
const { parent, name, parentPath } = _.rxGetObj.call(this, path, '__parent__')
|
||||
const rxparent = _.get.call(this, [this.$$__rxns__$$, 'props', parentPath])
|
||||
const rx = rxparent[name]
|
||||
if (!rx) return true // // not reactive nothing to remove
|
||||
Object.values(rx.subs).forEach(sub => sub.unsubscribe())
|
||||
|
@ -205,22 +209,21 @@ class RxClass extends EventEmitter {
|
|||
delete rxparent[name]
|
||||
// console.log('removed rx from', path)
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
||||
rxGetObs (path) {
|
||||
return (this.#rxGetObj(path) || {}).obs
|
||||
}
|
||||
getObs: function getObs (path) {
|
||||
return (_.rxGetObj.call(this, path) || {}).obs
|
||||
},
|
||||
|
||||
rxGetSubs (path, name) {
|
||||
const rx = this.#rxGetObj(path)
|
||||
getSubs: function getSubs (path, name) {
|
||||
const rx = _.rxGetObj.call(this, path)
|
||||
if (rx) return (name ? rx.subs[name] : Object.keys(rx.subs))
|
||||
}
|
||||
|
||||
},
|
||||
// pass rx as object or path
|
||||
rxSubscribe (path, handler, name) {
|
||||
const rx = this.#rxGetObj(path)
|
||||
subscribe: function subscribe (path, handler, name) {
|
||||
const rx = _.rxGetObj.call(this, path)
|
||||
if (rx) {
|
||||
// TODO if no name generate a name and return with handle
|
||||
// TODO if no name generate a name and return with handle
|
||||
if (check.isFunction(handler)) {
|
||||
if (rx.subs[name]) rx.subs[name].unsubscribe() // replace if exits
|
||||
rx.subs[name] = rx.obs.subscribe(handler)
|
||||
|
@ -229,10 +232,10 @@ class RxClass extends EventEmitter {
|
|||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
rxRemoveSubs (path, name) {
|
||||
const rx = this.#rxGetObj(path)
|
||||
removeSubs: function removeSubs (path, name) {
|
||||
const rx = _.rxGetObj.call(this, path)
|
||||
// if (name && name!=='_default' && (rxObj||{}).path) console.log('rx object for',rxObj.path)
|
||||
if (rx) {
|
||||
if (name) {
|
||||
|
@ -248,122 +251,71 @@ class RxClass extends EventEmitter {
|
|||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
isGetSet (path) {
|
||||
},
|
||||
isGetSet: function isGetSet (path) {
|
||||
const keys = getKeys(path)
|
||||
const prop = keys.pop()
|
||||
const parent = this.#get(keys)
|
||||
const parent = _.get.call(this, keys)
|
||||
// console.log('isgetset', path, keys, prop, 'parent?', !!parent)
|
||||
if (parent && prop) {
|
||||
return !!(Object.getOwnPropertyDescriptor(parent, prop) || {}).get &&
|
||||
!!(Object.getOwnPropertyDescriptor(parent, prop) || {}).set
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
},
|
||||
// is already reactive, checks for setter/getter and the corresponding rx object
|
||||
isRx (path) {
|
||||
// console.log('in isRX', path)
|
||||
isRx: function isRx (path) {
|
||||
// console.log('in isRX', path)
|
||||
const keys = getKeys(path)
|
||||
const cnt = keys.length
|
||||
for (let index = 0; index < cnt; index++) {
|
||||
// console.log('testing rx', index, cnt, keys, !!this.#get(keys), !!this.isGetSet(keys), !!this.#get(['#rx.props', keys]))
|
||||
if (this.#get(keys) === undefined) return false
|
||||
if (this.isGetSet(keys) && this.#get(['#rx.props', keys])) return true
|
||||
// console.log('testing rx', index, cnt, keys, !!_.get.call(this,keys), !!this.isGetSet(keys), !!_.get.call(this,[this.$$__rxns__$$,'props', keys]))
|
||||
if (_.get.call(this, keys) === undefined) return false
|
||||
if (this[this.$$__rxns__$$].isGetSet(keys) && _.get.call(this, [this.$$__rxns__$$, 'props', keys])) return true
|
||||
keys.pop()
|
||||
}
|
||||
return false
|
||||
// // console.log('in rx every', lpath)
|
||||
// // if (this.#get(['#rx.props', ...lpath]) === undefined) {
|
||||
// // return false
|
||||
// // }
|
||||
// //
|
||||
// console.log(parent, name, parentPath)
|
||||
// const rxparent = this.#get(['#rx.props', parentPath]) || {}
|
||||
// const rx = rxparent[name]
|
||||
// console.log(path, 'rxparent,rx', !!parent, !!name, !!rxparent, !!rx)
|
||||
// if (!rx) return true
|
||||
// else {
|
||||
// console.log(Object.getOwnPropertyDescriptor(parent, name).get)
|
||||
// isRx = !!Object.getOwnPropertyDescriptor(parent, name).get
|
||||
// return false
|
||||
// }
|
||||
// })
|
||||
// return isRx
|
||||
}
|
||||
}
|
||||
|
||||
// isRx(path) {
|
||||
// // console.log(path)
|
||||
// let isRx = false
|
||||
// const lpath = []
|
||||
// getKeys(path).every(key => {
|
||||
// lpath.push(key)
|
||||
// console.log('in rx every', lpath, this.#get(lpath))
|
||||
// if (this.#get(lpath) === undefined) {
|
||||
// return false
|
||||
// }
|
||||
// // const { parent, name, parentPath } = this.#rxGetObj(lpath, '__parent__')
|
||||
// // console.log(parent, name, parentPath)
|
||||
// const rxparent = this.#get(['#rx.props', parentPath]) || {}
|
||||
// const rx = rxparent[name]
|
||||
// console.log(path, 'rxparent,rx', !!parent, !!name, !!rxparent, !!rx)
|
||||
// if (!rx) return true
|
||||
// else {
|
||||
// console.log(Object.getOwnPropertyDescriptor(parent, name).get)
|
||||
// isRx = !!Object.getOwnPropertyDescriptor(parent, name).get
|
||||
// return false
|
||||
// }
|
||||
// })
|
||||
// return isRx
|
||||
// }
|
||||
// private methods
|
||||
|
||||
// PRIVATE METHODS
|
||||
|
||||
// pass '__parent__' when getting objects for creating reactive prop
|
||||
// otherwise it gets the rx prop object and/or one of it's props
|
||||
#rxGetObj (path, prop) {
|
||||
log.debug({ class: 'RxClass', method: '#rxGetObj', path: path, prop: prop, msg: 'getting rx object' })
|
||||
const _ = {
|
||||
rxGetObj: function rxGetObj (path, prop) {
|
||||
log.debug({ class: 'RxClass', method: '_.rxGetObj', path: path, prop: prop, msg: 'getting rx object' })
|
||||
let name, parent, parentPath, rx
|
||||
// if (!path) parent = this
|
||||
if (check.isString(path) || check.isArray(path)) { // it's a normal path
|
||||
const keys = getKeys(path)
|
||||
name = (keys || []).pop()
|
||||
parent = this.#get(keys)
|
||||
parent = _.get.call(this, keys)
|
||||
parentPath = keys
|
||||
log.debug({ method: '#rxGetObj', path: path, prop: prop, key: name, parent: parent, msg: 'getting rx object' })
|
||||
log.debug({ method: '_.rxGetObj', path: path, prop: prop, key: name, parent: parent, msg: 'getting rx object' })
|
||||
if (parent === null) parent = {}
|
||||
if (prop === '__parent__') return { name: name, parent: parent, parentPath: parentPath }
|
||||
rx = this.#get(['#rx.props', path])
|
||||
rx = _.get.call(this, [this.$$__rxns__$$, 'props', path])
|
||||
} else rx = check.isPlainObject(path) ? path : rx // if path was plain object assume it's already rx object
|
||||
return prop ? (rx || {})[prop] : rx
|
||||
}
|
||||
|
||||
#set () {
|
||||
},
|
||||
set: function set () {
|
||||
const args = [...arguments]
|
||||
if (args.length < 2) return false
|
||||
if (args.length === 2) args.unshift(this)
|
||||
else if (!args[0]) return false
|
||||
// console.log('in #set',args[1],args[2])
|
||||
return set(...args)
|
||||
}
|
||||
|
||||
#get () {
|
||||
return $set(...args)
|
||||
},
|
||||
get: function get () {
|
||||
const args = [...arguments]
|
||||
if (args.length === 1) args.unshift(this)
|
||||
return get(...args)
|
||||
}
|
||||
|
||||
#del () {
|
||||
return $get(...args)
|
||||
},
|
||||
del: function del () {
|
||||
const args = [...arguments]
|
||||
if (args.length < 2) return false
|
||||
if (args[args.length - 1] !== true) return false
|
||||
if (args.length === 2) args.unshift(this)
|
||||
return del(...args)
|
||||
return $del(...args)
|
||||
}
|
||||
} // end class
|
||||
|
||||
// helpers
|
||||
|
||||
export default RxClass
|
||||
export { RxClass, set, get, del, getKeys, pathToString }
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import assert from 'assert'
|
||||
import assert, { doesNotMatch } from 'assert'
|
||||
import { RxClass } from '../src/rx-class.js'
|
||||
|
||||
const rxopts = Symbol.for('rx-class-opts')
|
||||
|
@ -8,6 +8,8 @@ const obj = {
|
|||
today: { bing: 'bing', bong: { who: 'first', what: 'second' } }
|
||||
}
|
||||
|
||||
const foundation = { planets: ['Trantor', 'Helcion'], persons: { giskard: { alias: 'daneel' }, hari: { name: 'seldon', planet: 'Helcion' } } }
|
||||
|
||||
class Test extends RxClass {
|
||||
constructor (opts = {}) {
|
||||
super(opts)
|
||||
|
@ -17,121 +19,151 @@ class Test extends RxClass {
|
|||
|
||||
const opts = {
|
||||
[rxopts]: {
|
||||
skip: 1,
|
||||
handler: () => {}
|
||||
// handler: () => {}
|
||||
handler: (value) => { console.log('default subscription handler', value) }
|
||||
}
|
||||
}
|
||||
|
||||
const test = new Test(opts)
|
||||
opts[rxopts].namespace = 'rx2'
|
||||
opts[rxopts].getSetPrefix = '_'
|
||||
opts[rxopts].getSetNamespace = '$'
|
||||
const test2 = new Test(opts)
|
||||
|
||||
describe('Reactive Class', function () {
|
||||
it('Can be extended', function () {
|
||||
assert.deepStrictEqual(test.obj, obj)
|
||||
})
|
||||
|
||||
it('Can get non rx deep value', function () {
|
||||
assert.strictEqual(test.obj.today.bing, 'bing')
|
||||
assert.strictEqual(test.get('obj.today.bing'), 'bing')
|
||||
assert(!test.isRx('obj.today.bing'))
|
||||
})
|
||||
// it('Can get non rx deep value', function () {
|
||||
// assert.strictEqual(test.obj.today.bing, 'bing')
|
||||
// assert.strictEqual(test.get('obj.today.bing'), 'bing')
|
||||
// assert(!test.isRx('obj.today.bing'))
|
||||
// })
|
||||
|
||||
it('rx added to value', function () {
|
||||
test.rxAdd('obj.today.bing')
|
||||
assert.strictEqual(test.obj.today.bing, 'bing')
|
||||
assert.strictEqual(test.get('obj.today.bing'), 'bing')
|
||||
assert(test.isRx('obj.today.bing'))
|
||||
// it('can make a property reactive', function () {
|
||||
// test.rxAdd('obj.today.bing')
|
||||
// assert(test.isRx('obj.today.bing'))
|
||||
// })
|
||||
|
||||
it('can make a property containing object reactive', function () {
|
||||
// test.foundation = foundation
|
||||
test.set('foundation.test', foundation, { traverse: false })
|
||||
test2.$._set('foundation.test', foundation, { traverse: false })
|
||||
// console.log(test.foundation)
|
||||
// test.rx.set('foundation.test.persons.jimbo', { name: 'jim', plant: 'remulak' })
|
||||
// console.log(test.foundation.test)
|
||||
assert(test.rx.isRx('foundation.test'))
|
||||
assert(test2.rx2.isRx('foundation.test'))
|
||||
// assert(test.rx.isRx('foundation.test.persons'))
|
||||
// assert(test.rx.isRx('foundation.test.persons.giskard.alias'))
|
||||
// assert(!test.rx.isRx('foundation.test.persons.bogus'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Emitted Events', function () {
|
||||
function etest (event, value, path, done) {
|
||||
test.once(event, (v, p) => {
|
||||
// console.log('fired:', event, value, path)
|
||||
assert.strictEqual(v, value)
|
||||
assert.strictEqual(p, path)
|
||||
clearTimeout(timeout)
|
||||
done()
|
||||
})
|
||||
const timeout = setTimeout(() => {
|
||||
assert(false, `Event ${event} did not fire in 1000 ms.`)
|
||||
done()
|
||||
}, 1000)
|
||||
test.set(path, value)
|
||||
}
|
||||
// describe('Emitted Events', function () {
|
||||
// function etest (event, value, path, done) {
|
||||
// test.once(event, (v, p) => {
|
||||
// // console.log('fired:', event, value, path)
|
||||
// assert.strictEqual(v, value)
|
||||
// assert.strictEqual(p, path)
|
||||
// clearTimeout(timeout)
|
||||
// done()
|
||||
// })
|
||||
// const timeout = setTimeout(() => {
|
||||
// assert(false, `Event ${event} did not fire in 1000 ms.`)
|
||||
// done()
|
||||
// }, 1000)
|
||||
// test.set(path, value)
|
||||
// }
|
||||
|
||||
const path = 'obj.today.bing'
|
||||
it('should emit global default event', function (done) {
|
||||
const event = 'changed'
|
||||
etest(event, event, path, done)
|
||||
})
|
||||
it('should emit path if stringable', function (done) {
|
||||
const event = path
|
||||
etest(event, event, path, done)
|
||||
})
|
||||
// const path = 'obj.today.bing'
|
||||
// it('should emit global default event', function (done) {
|
||||
// const event = 'changed'
|
||||
// etest(event, event, path, done)
|
||||
// })
|
||||
// it('should emit path if stringable', function (done) {
|
||||
// const event = path
|
||||
// etest(event, event, path, done)
|
||||
// })
|
||||
|
||||
it('should emit the key name of value being changed', function (done) {
|
||||
const event = path.split('.').pop()
|
||||
etest(event, event, path, done)
|
||||
})
|
||||
// it('should emit the key name of value being changed', function (done) {
|
||||
// const event = path.split('.').pop()
|
||||
// etest(event, event, path, done)
|
||||
// })
|
||||
|
||||
it('should emit a custom set event for key/value change after force rxAdd', function (done) {
|
||||
const event = 'testing'
|
||||
test.rxAdd('obj.today.bing', { force: true, event: 'testing' })
|
||||
etest(event, event, path, done)
|
||||
})
|
||||
}) // end emits
|
||||
// it('should emit a custom set event for key/value change after force rxAdd', function (done) {
|
||||
// const event = 'testing'
|
||||
// test.rxAdd('obj.today.bing', { force: true, event: 'testing' })
|
||||
// etest(event, event, path, done)
|
||||
// })
|
||||
// }) // end emits
|
||||
|
||||
describe('Subscriptions', function () {
|
||||
const subsHandler = (value, timeout, name, done, v) => {
|
||||
assert.strictEqual(v, value)
|
||||
clearTimeout(timeout)
|
||||
test.rxRemoveSubs(name)
|
||||
done()
|
||||
}
|
||||
function subs (name, path, value, done, handler) {
|
||||
const timeout = setTimeout(() => {
|
||||
assert(false, 'subscription handler did not react in 1000 ms.')
|
||||
done()
|
||||
}, 1000)
|
||||
const subscribe = handler ? { [handler]: subsHandler.bind(null, value, timeout, name, done) } : {}
|
||||
test.rxAdd(path, { force: true, subscribe: subscribe })
|
||||
if (!handler) {
|
||||
test.rxSubscribe('obj.today.bing',
|
||||
subsHandler.bind(null, value, timeout, name, done)
|
||||
, name)
|
||||
}
|
||||
test.set(path, value)
|
||||
}
|
||||
// describe('Subscriptions', function () {
|
||||
// const subsHandler = (value, timeout, name, done, v) => {
|
||||
// assert.strictEqual(v, value)
|
||||
// clearTimeout(timeout)
|
||||
// test.rxRemoveSubs(name)
|
||||
// done()
|
||||
// }
|
||||
// function subs (name, path, value, done, handler) {
|
||||
// const timeout = setTimeout(() => {
|
||||
// assert(false, 'subscription handler did not react in 1000 ms.')
|
||||
// done()
|
||||
// }, 1000)
|
||||
// const subscribe = handler ? { [handler]: subsHandler.bind(null, value, timeout, name, done) } : {}
|
||||
// test.rxAdd(path, { force: true, subscribe: subscribe })
|
||||
// if (!handler) {
|
||||
// test.rxSubscribe('obj.today.bing',
|
||||
// subsHandler.bind(null, value, timeout, name, done)
|
||||
// , name)
|
||||
// }
|
||||
// test.set(path, value)
|
||||
// }
|
||||
|
||||
const path = 'obj.today.bing'
|
||||
it('should react to default subscription', function (done) {
|
||||
const name = '_default_'
|
||||
subs(name, path, name, done, name)
|
||||
})
|
||||
it('should react to property/key subscription', function (done) {
|
||||
const name = 'keytest'
|
||||
subs(name, path, name, done, name)
|
||||
})
|
||||
it('should react to new subscription', function (done) {
|
||||
const name = 'atest'
|
||||
subs(name, path, name, done)
|
||||
})
|
||||
}) // end subscriptions
|
||||
// const path = 'obj.today.bing'
|
||||
// it('should react to default subscription', function (done) {
|
||||
// const name = '_default_'
|
||||
// subs(name, path, name, done, name)
|
||||
// })
|
||||
// it('should react to property/key subscription', function (done) {
|
||||
// const name = 'keytest'
|
||||
// subs(name, path, name, done, name)
|
||||
// })
|
||||
// it('should react to new subscription', function (done) {
|
||||
// const name = 'atest'
|
||||
// subs(name, path, name, done)
|
||||
// })
|
||||
// }) // end subscriptions
|
||||
|
||||
describe('TODO: Amend,Hooks,Operators', function () {
|
||||
it('should run a default hook', function (done) {
|
||||
done()
|
||||
})
|
||||
// describe('TODO: Amend,Hooks,Operators', function () {
|
||||
// it('should run a default hook', function (done) {
|
||||
// done()
|
||||
// })
|
||||
|
||||
it('should run a custom hook', function (done) {
|
||||
done()
|
||||
})
|
||||
// it('should run a custom hook', function (done) {
|
||||
// done()
|
||||
// })
|
||||
|
||||
it('should support custom rxjs operators', function (done) {
|
||||
done()
|
||||
})
|
||||
// it('should support custom rxjs operators', function (done) {
|
||||
// done()
|
||||
// })
|
||||
|
||||
it('should amend a value that is set', function (done) {
|
||||
done()
|
||||
})
|
||||
}) // end hooks
|
||||
// it('should amend a value that is set', function (done) {
|
||||
// done()
|
||||
// })
|
||||
// }) // end hooks
|
||||
|
||||
// describe('TODO: Misc', function () {
|
||||
// it('get should return values of object nested props', function (done) {
|
||||
// done()
|
||||
// })
|
||||
|
||||
// it('set should make all nested leaves reactive if traverse is requested', function (done) {
|
||||
// done()
|
||||
// })
|
||||
|
||||
// it('should remove rx for property', function (done) {
|
||||
// done()
|
||||
// })
|
||||
// }) // end hooks
|
||||
|
|
Loading…
Reference in New Issue