Document listener

This commit is contained in:
Talia 2022-03-17 09:49:57 +01:00
parent 5e19b2d70f
commit ca600401ed
3 changed files with 163 additions and 44 deletions

View file

@ -55,6 +55,25 @@
<a class="button" href="page/element.html">Read More</a>
</section>
<section>
<h2>Listener</h2>
<p>
Like a normal object, except you can register callbacks for property changes.
</p>
<p>
<h3 class="all-unset"><b>Code Sample</b>:</h3>
<code-block>
import listener from 'listener.js'
const user = listener({name: "John Doe"})
user.listen('name', value =&gt; console.warn(`User name has changed to ${value}`))
</code-block>
</p>
<a class="button" href="page/listener.html">Read More</a>
</section>
<section>
<h2>Debounce</h2>
<p>

View file

@ -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.

125
page/listener.html Normal file
View file

@ -0,0 +1,125 @@
<link rel="stylesheet" href="style.css">
<script type="module" src="codeblock.js"></script>
<script type="module" src="filesize.js"></script>
<h1 class="js module">listener.js</h1>
<code-block>import listener from 'listener.js'</code-block>
<section>
<h2>Description</h2>
<p>
Listeners are special proxy objects that can trigger (one or several) callbacks when any of its properties are set.
</p>
<dl>
<code>
<dt>listener</dt>
<dd>target &xrarr; proxy</dd>
<dd><i>nil</i> &xrarr; proxy</dd>
</code>
<dd>
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.
</dd>
</dl>
</section>
<section>
<h2>Example</h2>
<code-block>
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 =&gt; alert(`Hello, ${newName}!`))
// Save the in the back-end whenever it changes:
user.listen(null, (value, old) =&gt; { if (value != old) backend.saveUserSomehow(user) }))
document.body.apend(new MyUserComponent(user))
</code-block>
<p>
This code uses a listener proxy to represent a User so it can respond whenever the user gets updated.
</p>
</section>
<section>
<h2>Methods</h2>
<p>
The proxy object will override the following two properties of the target object:
<dl>
<dt><code>listener.listen(&lt;prop&gt;, &lt;callback&gt;)</code></dt>
<dd>Registers a callback on a single property that will be called whenever the property changes.</dd>
<dt><code>listener.listen(null, &lt;callback&gt;)</code></dt>
<dd>Registers a callback that will be called whenever <em>any</em> property changes.</dd>
<dt><code>listener.listen(&lt;prop&gt;|null, &lt;callback&gt;, {once: true})</code></dt>
<dd>Registers a callback that will only be called once, then remove itself.</dd>
<dt><code>listener.forget(&lt;prop&gt;, &lt;callback&gt;)</code></dt>
<dd>Forgets a specific callback for a specific property.</dd>
<dt><code>listener.forget(&lt;prop&gt;)</code></dt>
<dd>Forgets all callbacks for a certain property.</dd>
<dt><code>listener.forget(null[, &lt;callback&gt;])</code></dt>
<dd>
Forgets one or all generic callbacks.
This will <em>not</em> delete any specific property callbacks.
In other words, this is not a mechanism for clearing all callbacks of a listener.
</dd>
</dl>
</p>
<dl>
<code>
<dt>callback</dt>
<dd>new, old, prop &xrarr; <i>nil</i></dd>
<dt>prop</dt>
<dd>string</dd>
</code>
</dl>
<p>
<strong>Note</strong> 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.
</p>
<p>
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.
</p>
<p>
<strong>Note</strong> that one-time callbacks registered with <code>{once: true}</code> will generate
a new wrapper function that removes itself before calling the actual callback.
This has two implications:
<ul>
<li> One-time callbacks cannot be forgotten by passing the original function, as that is not the callback that is saved internally
<li> Adding the same one-time callback repeatedly will generate a new (unique) wrapper every time, so they will also run more than once
</ul>
This behaviour of one-time callbacks <em>may</em> change in the future and should <em>not</em> be relied on.
</p>
<p>
Additionally, the <code>listener</code> factory itself has the following method<!-- Change to plural when adding more! -->:
<dl>
<code>
<dt>listener.raw</dt>
<dd>proxy &rarr; target</dd>
</code>
<dd>
This static method can be used to retrieve the target object for a given listener proxy.
</dd>
</dl>
</p>
<p>
<strong>Note</strong> 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.
</p>
</section>