From ca600401ed7c0d2a6bc1f8f6293be2744210e2c8 Mon Sep 17 00:00:00 2001 From: DarkWiiPlayer Date: Thu, 17 Mar 2022 09:49:57 +0100 Subject: [PATCH] Document listener --- index.html | 19 +++++++ listener.md | 63 +++++++---------------- page/listener.html | 125 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 44 deletions(-) create mode 100644 page/listener.html diff --git a/index.html b/index.html index 0f705d6..22f7475 100644 --- a/index.html +++ b/index.html @@ -55,6 +55,25 @@ Read More +
+

Listener

+

+ Like a normal object, except you can register callbacks for property changes. +

+ +

+

Code Sample:

+ + import listener from 'listener.js' + + const user = listener({name: "John Doe"}) + user.listen('name', value => console.warn(`User name has changed to ${value}`)) + +

+ + Read More +
+

Debounce

diff --git a/listener.md b/listener.md index 08aee40..ebb11a3 100644 --- a/listener.md +++ b/listener.md @@ -5,53 +5,28 @@ A generator for proxy objects that run callbacks when properties are changed. ## Interface ```js -listener(target={}) -// Creates a new proxy for target +listener() +// Creates a new listener object +listener(some_object) +// Creates a new listener object that acts as a proxy for some_object listener.listen(prop, callback) -// Adds a callback on a property change -listener.listen([prop, ...], callback) -// Adds a callback to several properties at once -listener.listen(prop) -// Removes all callbacks from a given property +// Adds a callback on a specific property change +listener.listen(null, callback) +// Adds a callback on any property change +listener.listen(prop, callback, {once: true}) +// Adds a one-time callback ``` ```js -text(listener, "property") -// Returns a text node bound to listener.property -text(listener) -// Returns a text node proxy for the listener -text(listener).property -// Same as first example +listener.forget(prop, callback) +// Removes a specific callback on a specific property +listener.forget(prop, null) +// Removes all callbacks from a property +// Note that undefined won't work, to avoid accidents +// it really has to be null +listener.forget(null, callback) +// This is not a special case, it simply removes a +// callback that was registered with lisetner.listen(null, callback) ``` -When called with two arguments, this function returns a new text node that will -automatically update to reflect the given property on the listener. - -When called with only the listener, it creates a proxy object that, when -indexed, returns the result of calling `text` on the listener and the indexed -property name. - -Note that repeatedly indexing the proxy will return a new text node each time. - -## Example - -```js -import Listener from 'listener.js' -const listener = Listener({}) - -// Listen for any changed property -listener.listen("*", (value, prop) => console.log(`${prop} changed to ${value}`)) - -// Listen only for changes to the foo property -listener.listen("foo", prop => console.log("it was foo, by the way")) - -// Several listeners for one property are possible -// They will be executed in order of definition -listener.listen("foo", prop => do_something()) - -listener.foo = "New Value" -// Triggers 3 handlers - -listener.bar = "New Value" -// Triggers only the * handler -``` +Note: Forgetting one-time callbacks is not (yet) possible. diff --git a/page/listener.html b/page/listener.html new file mode 100644 index 0000000..954dd69 --- /dev/null +++ b/page/listener.html @@ -0,0 +1,125 @@ + + + + + +

listener.js

+ +import listener from 'listener.js' + +
+

Description

+

+ Listeners are special proxy objects that can trigger (one or several) callbacks when any of its properties are set. +

+
+ +
listener
+
target ⟶ proxy
+
nil ⟶ proxy
+
+
+ A factory function for new listener objects. + When an object is passed as an argument, the listener will act as a proxy for that object. + Otherwise a new target object is created. +
+
+
+ +
+

Example

+ + + import MyUserComponent from '/path/to/MyUserComponent.js' + // Suppose this component allows the user to enter a new name + import backend from '/path/to/backendController.js' + // Suppose this class abstracts some backend API + + const user = listener({name: "World"}) + // Greet the user whenever the name changes: + user.listen("name", newName => alert(`Hello, ${newName}!`)) + // Save the in the back-end whenever it changes: + user.listen(null, (value, old) => { if (value != old) backend.saveUserSomehow(user) })) + + document.body.apend(new MyUserComponent(user)) + + +

+ This code uses a listener proxy to represent a User so it can respond whenever the user gets updated. +

+ +
+ +
+

Methods

+

+ The proxy object will override the following two properties of the target object: +

+
listener.listen(<prop>, <callback>)
+
Registers a callback on a single property that will be called whenever the property changes.
+
listener.listen(null, <callback>)
+
Registers a callback that will be called whenever any property changes.
+
listener.listen(<prop>|null, <callback>, {once: true})
+
Registers a callback that will only be called once, then remove itself.
+
listener.forget(<prop>, <callback>)
+
Forgets a specific callback for a specific property.
+
listener.forget(<prop>)
+
Forgets all callbacks for a certain property.
+
listener.forget(null[, <callback>])
+
+ Forgets one or all generic callbacks. + This will not delete any specific property callbacks. + In other words, this is not a mechanism for clearing all callbacks of a listener. +
+
+

+ +
+ +
callback
+
new, old, prop ⟶ nil
+
prop
+
string
+
+
+ +

+ Note that callbacks will run regardless of whether the new and old values are actually different. + It is up to the user to compare the new value to the old one and possibly only take action when there is a meaningful difference. +

+ +

+ Callbacks are internally stored as a map from property name to a set of callbacks. + Therefore, registering the same callback on the same property more than once will have no effect; + the callback will only be called once for every property change. +

+ +

+ Note that one-time callbacks registered with {once: true} will generate + a new wrapper function that removes itself before calling the actual callback. + This has two implications: +

    +
  • One-time callbacks cannot be forgotten by passing the original function, as that is not the callback that is saved internally +
  • Adding the same one-time callback repeatedly will generate a new (unique) wrapper every time, so they will also run more than once +
+ This behaviour of one-time callbacks may change in the future and should not be relied on. +

+ +

+ Additionally, the listener factory itself has the following method: +

+ +
listener.raw
+
proxy → target
+
+
+ This static method can be used to retrieve the target object for a given listener proxy. +
+
+

+

+ Note that callbacks will only trigger when the property is set via the proxy object. + Changing the raw object directly will not trigger any callbacks as javascript provides + no mechanism to detect such property changes on plain objects. +

+