Skip to content

Commit dbbe976

Browse files
authored
feat: Implement model.off (#23)
* feat: Implement model.off * Add jsdoc for CallbackRegistry
1 parent b588447 commit dbbe976

File tree

1 file changed

+107
-22
lines changed

1 file changed

+107
-22
lines changed

inst/htmlwidgets/anyhtmlwidget.js

+107-22
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,123 @@
1+
class CallbackRegistry {
2+
/** @type {Record<string, Function[]>} */
3+
#callbacks = {};
4+
/**
5+
* @param {string} name
6+
* @param {Function} callback
7+
*/
8+
add(name, callback) {
9+
if (!this.#callbacks[name]) {
10+
this.#callbacks[name] = [];
11+
}
12+
this.#callbacks[name].push(callback);
13+
}
14+
/**
15+
* @param {string} name
16+
* @param {Function} [callback] - a specific callback to remove
17+
* @returns {Function[]} - the removed callbacks
18+
*/
19+
remove(name, callback) {
20+
if (!this.#callbacks[name]) {
21+
return [];
22+
}
23+
const callbacks = this.#callbacks[name];
24+
if (callback) {
25+
// Remove a specific callback
26+
return this.#callbacks[name] = callbacks.filter((cb) => cb !== callback);
27+
}
28+
// Remove all callbacks
29+
this.#callbacks[name] = [];
30+
return callbacks;
31+
}
32+
}
33+
34+
/**
35+
* An R-backed implementation of the @anywidget/types AnyModel interface.
36+
*
37+
* @see {@link https://github.com/manzt/anywidget/tree/main/packages/types}
38+
*/
139
class AnyModel {
40+
/** @type {Record<string, any>} */
41+
#state;
42+
/** @type {string} */
43+
#ns_id;
44+
/** @type {WebSocket | undefined} */
45+
#ws = undefined;
46+
/** @type {EventTarget} */
47+
#target = new EventTarget();
48+
/** @type {CallbackRegistry} */
49+
#callbacks = new CallbackRegistry();
50+
/** @type {Set<string>} */
51+
#unsavedKeys = new Set();
52+
53+
/**
54+
* @param {Record<string, any>} state - initial model state
55+
* @param {string} ns_id - the Shiny namespace ID
56+
* @param {WebSocket} [ws] - a WebSocket connection
57+
*/
258
constructor(state, ns_id, ws) {
3-
this.ns_id = ns_id;
4-
this.state = state;
5-
this.target = new EventTarget();
6-
this.ws = ws;
7-
this.unsavedKeys = new Set();
59+
this.#ns_id = ns_id;
60+
this.#state = state;
61+
this.#ws = ws;
862
}
63+
/** @param {string} name */
964
get(name) {
10-
return this.state[name];
65+
return this.#state[name];
1166
}
67+
/**
68+
* @param {string} key
69+
* @param {any} value
70+
*/
1271
set(key, value) {
13-
this.state[key] = value;
14-
this.unsavedKeys.add(key);
15-
this.target.dispatchEvent(
16-
new CustomEvent(`change:${key}`, { detail: value }),
17-
);
72+
this.#state[key] = value;
73+
this.#unsavedKeys.add(key);
74+
this.#target.dispatchEvent(
75+
new CustomEvent(`change:${key}`, { detail: value }),
76+
);
77+
this.#target.dispatchEvent(
78+
new CustomEvent("change", { detail: value }),
79+
);
1880
}
81+
/**
82+
* @param {string} name
83+
* @param {Function} callback
84+
*/
1985
on(name, callback) {
20-
this.target.addEventListener(name, callback);
86+
this.#target.addEventListener(name, callback);
87+
this.#callbacks.add(name, callback);
88+
}
89+
/**
90+
* @param {string} name
91+
* @param {Function} [callback]
92+
*/
93+
off(name, callback) {
94+
for (const cb of this.#callbacks.remove(name, callback)) {
95+
this.#target.removeEventListener(name, cb);
96+
}
2197
}
22-
off(name) {
23-
// Not yet implemented
98+
/**
99+
* @param {any} msg
100+
* @param {unknown} [callbacks]
101+
* @param {ArrayBuffer[]} [buffers]
102+
*/
103+
send(msg, callbacks, buffers) {
104+
// TODO: impeThrow?
105+
console.error(`model.send is not yet implemented for anyhtmlwidget`);
24106
}
25107
save_changes() {
26108
const unsavedState = Object.fromEntries(
27-
Array.from(this.unsavedKeys.values())
28-
.map(key => ([key, this.state[key]]))
109+
Array.from(this.#unsavedKeys.values())
110+
.map((key) => [key, this.#state[key]]),
29111
);
30-
this.unsavedKeys = new Set();
31-
if(window && window.Shiny && window.Shiny.setInputValue) {
32-
const eventPrefix = this.ns_id ? `${this.ns_id}-` : '';
33-
Shiny.setInputValue(`${eventPrefix}anyhtmlwidget_on_save_changes`, unsavedState);
34-
} else if(this.ws) {
35-
this.ws.send(JSON.stringify({
112+
this.#unsavedKeys = new Set();
113+
if (window && window.Shiny && window.Shiny.setInputValue) {
114+
const eventPrefix = this.#ns_id ? `${this.#ns_id}-` : "";
115+
Shiny.setInputValue(
116+
`${eventPrefix}anyhtmlwidget_on_save_changes`,
117+
unsavedState,
118+
);
119+
} else if (this.#ws) {
120+
this.#ws.send(JSON.stringify({
36121
type: "on_save_changes",
37122
payload: unsavedState,
38123
}));

0 commit comments

Comments
 (0)