/
Observe.js
168 lines (158 loc) · 4.41 KB
/
Observe.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
const toarr = require('../deps/to-arr')
const traverse = require('../deps/traverse')
// const eq = require('../deps/traversers/eq')
const match = require('../deps/matcher')
const getPathSegments = require('../deps/dot/segments')
const dot = require('../deps/dot')
const OBSERVERS_KEY = require('../deps/meta/observers')
const {eq} = traverse
/**
* scoped clones
* @private
* @type {Map}
*/
let objs = new Map()
/**
* @desc > subscribe to changes
* ❗ called only on **change**
* observers are only called when data they subscribe to changes
*
* @since 3.0.1
* @class Observe
* @member Observe
* @extends {ChainedMap}
* @extends {DotProp}
* @memberOf compose
* @category Chainable
*
* @param {Class | Composable} Target composable class
* @return {Observe} class
*
* @tests Observe
* @types Observe
*
* @see ChainedMap
* @see DotProp
* @see deps/matcher
* @see deps/traversers/eq
* @see deps/traverse
* @see DotProp
*
* {@link https://github.com/iluwatar/java-design-patterns/tree/master/observer observer-pattern}
* {@link https://github.com/ReactiveX/rxjs/blob/master/src/Subscriber.ts reactivex}
* {@link https://github.com/sindresorhus/awesome-observables awesome-observables}
* {@link https://medium.com/@benlesh/learning-observable-by-building-observable-d5da57405d87 building-observables}
* {@link https://github.com/addyosmani/essential-js-design-patterns/blob/master/diagrams/observer.png js-observer-png}
* {@link https://github.com/addyosmani/essential-js-design-patterns/blob/master/diagrams/publishsubscribe.png pubsub-png}
* {@link https://github.com/tusharmath/observable-air observable-air}
*
* @see {@link reactivex}
* @see {@link awesome-observables}
* @see {@link building-observables}
* @see {@link observer-pattern}
* @see {@link observable-air}
*
* @example
*
* const {compose} = require('chain-able')
* const {DotProp} = compose
* new DotProp()
* //=> DotProp
*
*/
module.exports = Target => {
// return class Observe extends Target {
/**
* @desc observe properties when they change
*
* @method
* @memberOf Observe
* @since 4.0.0 <- refactored with dot-prop
* @since 1.0.0
*
* @param {Matchable} properties Matchable properties to observe
* @param {Function} fn onChanged
* @return {Target} @chainable
*
* @see traversers/eq
* @see toarr
* @see matcher
*
* {@link https://jsfiddle.net/wqxuags2/28/ for a Demo Clock with observable}
*
* @see {@link for a Demo Clock with observable}
* @see examples/playground/TodoStore
*
* @TODO gotta update `data` if `deleting` too...
* @TODO un-observe
* @TODO should hash these callback properties
* @TODO just throttle the `.set` to allow easier version of .commit
*
* @example
*
* const Target = require('chain-able')
*
* const chain = new Target()
* const log = arg => console.log(arg)
*
* chain
* .extend(['eh'])
* .observe('eh', data => log(data))
* .eh(true)
* //=> {eh: true}
*
* @example
*
* chain
* .extend(['canada', 'timbuck'])
* .observe(['canad*'], data => console.log(data.canada))
* .canada(true)
* .canada(true)
* .timbuck(false)
*
* //=> true
* //=> false
*
* // only called when changed,
* // otherwise it would be 2 `true` & 1 `false`
*
*/
Target.prototype.observe = function chainObserve(properties, fn) {
const props = toarr(properties)
const hashKey = props.join('_')
let data = {}
/* prettier-ignore */
return this.meta(OBSERVERS_KEY, changed => {
/**
* match the keys, make the data out of it
*/
const m = match(changed.key, props)
// @@debugger
for (let i = 0; i < m.length; i++) {
const segments = getPathSegments(m[i])
dot.set(data, segments, this.get(segments))
}
/**
* if we have called it at least once...
* and it has not changed, leave it
* else
* clone it
* call the observer
*/
if (objs.has(hashKey) && eq(objs.get(hashKey), data)) {
// @@debugger
return
}
// @@debugger
/**
* it did change - clone it for next deepEquals check
*/
objs.set(hashKey, traverse(data).clone())
/**
* call the observer - it matched & data changed
*/
fn.call(this, data, this)
})
}
return Target
}