Last active
April 15, 2022 19:53
-
-
Save ebrensi/c636410e07f587b032653523fe6a9a76 to your computer and use it in GitHub Desktop.
JavaScript (TypeScript) Object extended with onChange method
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* An Object with an onChange method and debounced callbacks. | |
* Example: | |
* ``` | |
* type P = {name: string, age: number, birthday: Date} | |
* const live_obj = const watch<P>({ name: "Doctor Foo", age: 43 }) | |
* live_obj.onChange("name", (newVal: string) => { console.log("yo! name=${newVal} now!") }) | |
* ``` | |
* !!! In order for types to work correctly, instantiate a LiveParams object using the `watch<P>()` | |
* factory function rather than `new LiveParams<P>()`. | |
*/ | |
type CallbackFunction<V> = (newval: V) => void | |
type Binding<V> = [V, CallbackFunction<V>[]] | |
type Bindings<T> = { | |
[K in keyof T]?: [T[K], CallbackFunction<T[K]>[]] | |
} | |
/** | |
* An Object with an onChange method and debounced callbacks. | |
*/ | |
class LiveParams<Params> { | |
#bindings: Bindings<Params> | |
constructor(obj: Params) { | |
Object.assign(this, obj) | |
this.#bindings = {} | |
} | |
onChange<K extends keyof Params>( | |
this: Params & LiveParams<Params>, | |
key: K, | |
callback: CallbackFunction<Params[K]>, | |
trigger = true | |
): void { | |
type V = Params[K] | |
let binding = this.#bindings[key] | |
if (binding) { | |
const [val, callbacks] = binding | |
callbacks.push(callback) | |
if (trigger) callback(val) | |
} else { | |
const val = this[key] | |
delete this[key] | |
binding = this.#bindings[key] = <Binding<V>>[val, [callback]] | |
Object.defineProperty(this, key, { | |
get: () => this.#bindings[key][0], | |
set: (newValue: V) => { | |
const binding: Binding<V> = this.#bindings[key] | |
binding[0] = newValue | |
debounce(() => { | |
const callbacks = binding[1] | |
for (let i=0; i < callbacks.length; i++) callbacks[i](newValue) | |
}, CALLBACK_DEBOUNCE_DELAY) | |
}, | |
enumerable: true, | |
}) | |
if (trigger) callback(val) | |
} | |
} | |
} | |
const CALLBACK_DEBOUNCE_DELAY = 20 | |
function debounce(func: () => void, timeout: number) { | |
let timer: number | |
return () => { | |
clearTimeout(timer) | |
timer = setTimeout(() => func(), timeout) | |
} | |
} | |
export type Live<T> = T & LiveParams<T> | |
export function watch<T>(obj: T) { | |
return new LiveParams<T>(obj) as Live<T> | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment