Document listener
This commit is contained in:
parent
5e19b2d70f
commit
ca600401ed
3 changed files with 163 additions and 44 deletions
19
index.html
19
index.html
|
@ -55,6 +55,25 @@
|
||||||
<a class="button" href="page/element.html">Read More</a>
|
<a class="button" href="page/element.html">Read More</a>
|
||||||
</section>
|
</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 => console.warn(`User name has changed to ${value}`))
|
||||||
|
</code-block>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a class="button" href="page/listener.html">Read More</a>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>Debounce</h2>
|
<h2>Debounce</h2>
|
||||||
<p>
|
<p>
|
||||||
|
|
63
listener.md
63
listener.md
|
@ -5,53 +5,28 @@ A generator for proxy objects that run callbacks when properties are changed.
|
||||||
## Interface
|
## Interface
|
||||||
|
|
||||||
```js
|
```js
|
||||||
listener(target={})
|
listener()
|
||||||
// Creates a new proxy for target
|
// 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)
|
listener.listen(prop, callback)
|
||||||
// Adds a callback on a property change
|
// Adds a callback on a specific property change
|
||||||
listener.listen([prop, ...], callback)
|
listener.listen(null, callback)
|
||||||
// Adds a callback to several properties at once
|
// Adds a callback on any property change
|
||||||
listener.listen(prop)
|
listener.listen(prop, callback, {once: true})
|
||||||
// Removes all callbacks from a given property
|
// Adds a one-time callback
|
||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```js
|
||||||
text(listener, "property")
|
listener.forget(prop, callback)
|
||||||
// Returns a text node bound to listener.property
|
// Removes a specific callback on a specific property
|
||||||
text(listener)
|
listener.forget(prop, null)
|
||||||
// Returns a text node proxy for the listener
|
// Removes all callbacks from a property
|
||||||
text(listener).property
|
// Note that undefined won't work, to avoid accidents
|
||||||
// Same as first example
|
// 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
|
Note: Forgetting one-time callbacks is not (yet) possible.
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
125
page/listener.html
Normal file
125
page/listener.html
Normal 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 ⟶ proxy</dd>
|
||||||
|
<dd><i>nil</i> ⟶ 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 => 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))
|
||||||
|
</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(<prop>, <callback>)</code></dt>
|
||||||
|
<dd>Registers a callback on a single property that will be called whenever the property changes.</dd>
|
||||||
|
<dt><code>listener.listen(null, <callback>)</code></dt>
|
||||||
|
<dd>Registers a callback that will be called whenever <em>any</em> property changes.</dd>
|
||||||
|
<dt><code>listener.listen(<prop>|null, <callback>, {once: true})</code></dt>
|
||||||
|
<dd>Registers a callback that will only be called once, then remove itself.</dd>
|
||||||
|
<dt><code>listener.forget(<prop>, <callback>)</code></dt>
|
||||||
|
<dd>Forgets a specific callback for a specific property.</dd>
|
||||||
|
<dt><code>listener.forget(<prop>)</code></dt>
|
||||||
|
<dd>Forgets all callbacks for a certain property.</dd>
|
||||||
|
<dt><code>listener.forget(null[, <callback>])</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 ⟶ <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 → 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>
|
Loading…
Reference in a new issue