diff --git a/packages/client/src/listeners.js b/packages/client/src/listeners.js
index 5131782ab..2f27421c7 100644
--- a/packages/client/src/listeners.js
+++ b/packages/client/src/listeners.js
@@ -2,8 +2,10 @@ import _ from 'lodash/fp.js'
import { eventEmitter, hasSome } from './util/futil.js'
import { encode } from './util/tree.js'
+let matchesKeys = (keys, delta) => _.isEmpty(keys) || hasSome(keys, delta)
+
export let setupListeners = (tree) => {
- let { on, emit } = eventEmitter()
+ let { on, onAny, emit } = eventEmitter()
// Assume first arg is node which might have path
tree.onChange = (node = {}, delta) => emit(encode(node.path), node, delta)
// Public API
@@ -12,4 +14,14 @@ export let setupListeners = (tree) => {
// Trigger watcher if keys match or no keys passed
if (_.isEmpty(keys) || hasSome(keys, delta)) f(node, delta)
})
+ tree.watchTree = (f, keys, path) =>
+ onAny((eventPath, node, delta) => {
+ // already encoded, so isParent not needed
+ // Use case is, for example, watching a group and all its children
+ // might be better solved by watchable group flags
+ let matchesPath = _.isEmpty(path) || _.startsWith(path, eventPath)
+ // TODO: should getNode return root for [] or empty paths?
+ let treeNode = path ? tree.getNode(path) : tree.tree
+ if (matchesPath && matchesKeys(keys, delta)) f(treeNode, node, delta)
+ })
}
diff --git a/packages/client/src/listeners.test.js b/packages/client/src/listeners.test.js
index 3307e23b8..c3c566558 100644
--- a/packages/client/src/listeners.test.js
+++ b/packages/client/src/listeners.test.js
@@ -192,6 +192,51 @@ let AllTests = (ContextureClient) => {
await tree.mutate(['root', 'results'], { pageSize: 2 })
expect(resultWatcher).toBeCalledTimes(2)
})
+ it('watchTree', async () => {
+ let service = jest.fn(mockService({ delay: 10 }))
+ let tree = ContextureClient(
+ { service, debounce: 1 },
+ {
+ key: 'root',
+ join: 'and',
+ children: [
+ {
+ key: 'filter',
+ type: 'facet',
+ field: 'facetfield',
+ values: ['some value'],
+ },
+ { key: 'results', type: 'results' },
+ ],
+ }
+ )
+ let filterDom = ''
+ let resultsDom = ''
+ tree.watchTree((root) => {
+ filterDom = `
+
Facet
+ Field: ${root.children[0].field}
+ values: ${_.join(', ', root.children[0].values)}
+
`
+ resultsDom = `${_.map(
+ (result) =>
+ `\n${_.map((val) => `| ${val} | `, _.values(result))}
`,
+ root.children[1].context.results
+ )}
+
`
+ })
+ expect(filterDom).toBe('')
+ let action = tree.mutate(['root', 'filter'], { values: ['other Value'] })
+ expect(filterDom).toBe(`
+
Facet
+ Field: facetfield
+ values: other Value
+
`)
+ await action
+ expect(resultsDom).toBe(``)
+ })
})
}
diff --git a/packages/client/src/util/futil.js b/packages/client/src/util/futil.js
index 8f8e06088..705067ccb 100644
--- a/packages/client/src/util/futil.js
+++ b/packages/client/src/util/futil.js
@@ -7,18 +7,24 @@ export let hasSome = (keys, obj) => _.some(F.hasIn(obj), keys)
// Sets up basic event emitter/listener registry with an array of listeners per topic
// e.g. listeners: { topic1: [fn1, fn2, ...], topic2: [...], ... }
-export let eventEmitter = (listeners = {}) => ({
- listeners,
- emit: (topic, ...args) => _.over(listeners[topic])(...args),
- on(topic, fn) {
+// Also emit on a special symbol for all topics
+let allTopics = Symbol('allTopics')
+export let eventEmitter = (listeners = {}) => {
+ let emit = (topic, ...args) => {
+ _.over(listeners[topic])(...args)
+ _.over(listeners[allTopics])(topic, ...args)
+ }
+ let on = (topic, fn) => {
if (!listeners[topic]) listeners[topic] = []
listeners[topic].push(fn)
// unlisten
return () => {
listeners[topic] = _.without(fn, listeners[topic])
}
- },
-})
+ }
+ let onAny = (fn) => on(allTopics, fn)
+ return { listeners, emit, on, onAny }
+}
export let transformTreePostOrder = (next = F.traverse) =>
_.curry((f, x) => {