https://github.com/jonathanhefner/taproot
Low-budget Stimulus knock-off
https://github.com/jonathanhefner/taproot
Last synced: 8 months ago
JSON representation
Low-budget Stimulus knock-off
- Host: GitHub
- URL: https://github.com/jonathanhefner/taproot
- Owner: jonathanhefner
- License: mit
- Created: 2021-06-25T20:16:55.000Z (about 5 years ago)
- Default Branch: master
- Last Pushed: 2021-06-30T18:24:31.000Z (about 5 years ago)
- Last Synced: 2025-04-20T11:04:39.398Z (about 1 year ago)
- Language: JavaScript
- Size: 78.1 KB
- Stars: 5
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Taproot
A low-budget [Stimulus](https://stimulus.hotwire.dev/) knock-off,
weighing in at 6 kB minified / 2.5 kB minified + gzipped.
The key differences are:
* The core HTML attributes are `data-controllers` and `data-actions`,
instead of `data-controller` and `data-action`. This lets you test
drive Taproot and Stimulus at the same time, if desired.
* The `data-actions` syntax is:
```html
```
Modifiers such as `+once` or `+passive` can be appended to the event.
Likewise, the event target can be changed by appending `+document` or
`+window` to the event.
* Implicit events are not supported, but commas can be used as shorthand
to bind multiple actions to the same event:
```html
```
Likewise, commas can be used as shorthand to bind the same action to
multiple events:
```html
```
* Namespaced data attributes on the controller element can be accessed
via a controller's `data` property. Default values can be specified
via a static `defaults` property on the controller. A default value
that is neither a string nor `undefined` indicates that attribute
should be deserialized with `JSON.parse` when reading, and serialized
with `JSON.stringify` when writing. For example:
```javascript
class BottlesController extends Taproot.Controller {
static defaults = { count: 99 }
decrement() {
this.data.count -= 1
}
}
```
If the `data-bottles-count` attribute is not present on the controller
element, `this.data.count` will return `99`. Otherwise, it will
return the value of `data-bottles-count` parsed by `JSON.parse`.
* Controllers provide a `dataFor` method that returns a proxy object
which is similar to the `data` object for any element. For example:
```javascript
class BottlesController extends Taproot.Controller {
static defaults = { count: 99 }
takeN({ currentTarget }) {
this.data.count -= this.dataFor(currentTarget, { n: 1 }).n
}
}
```
If the `data-bottles-n` attribute is not present on `currentTarget`,
`this.dataFor(currentTarget, { n: 1 }).n` will return `1`. Otherwise,
it will return the value of `data-bottles-n` parsed by `JSON.parse`.
If the defaults argument is not specified, `dataFor` will use the
controller's static `defaults`.
* The `nodes` and `nodeSets` controller properties can be used to query
for nodes that have a particular namespaced attribute. For example:
```javascript
class BottlesController extends Taproot.Controller {
static defaults = { count: 99 }
countChanged({ count }) {
const { status } = this.nodes
if (status) status.textContent = `${count} bottles of beer on the wall.`
}
}
```
`this.nodes.status` will return the first node in the controller
element tree that has a `data-bottles-status` attribute.
* A controller's `connect` and `disconnect` methods will be invoked only
if they reflect the state of the controller element at the time of
invocation. For example:
```javascript
class ItemController extends Taproot.Controller {
connect() { /* ... */ }
disconnect() { /* ... */ }
moveToTop() {
this.element.parentElement.prepend(this.element)
}
remove() {
this.element.remove()
}
}
```
`moveToTop` generates two DOM mutation events because `prepend`
removes the controller element from the DOM before prepending it to
`parentElement`. However, at the time both mutation events are
processed, the controller element is no longer disconnected from the
DOM, so neither `disconnect` nor `connect` will be invoked.
On the other hand, `remove` permanently removes the controller element
from the DOM. When its corresponding mutation event is processed,
`disconnect` will be invoked.
* The `Taproot.register` method accepts an object that maps controller
descriptors to constructors. It also automatically kebab-cases the
given descriptors and strips their `-controller` suffix. Thus, for
example, the following are all equivalent:
```javascript
import * as Taproot from "@jonathanhefner/taproot"
import { FooController, BarController } from "./foo-and-bar.js"
Taproot.register({ foo: FooController, bar: BarController })
```
```javascript
import * as Taproot from "@jonathanhefner/taproot"
import { FooController as foo, BarController as bar } from "./foo-and-bar.js"
Taproot.register({ foo, bar })
```
```javascript
import * as Taproot from "@jonathanhefner/taproot"
import { FooController, BarController } from "./foo-and-bar.js"
Taproot.register({ FooController, BarController })
```
```javascript
import * as Taproot from "@jonathanhefner/taproot"
import * as fooAndBar from "./foo-and-bar.js"
Taproot.register(fooAndBar)
```
`Taproot.register` can also accept a non-anonymous constructor, and
use its name as the descriptor. For example:
```javascript
import { register, Controller } from "@jonathanhefner/taproot"
register(class BottlesController extends Controller {
/* ... */
})
```
will register the `bottles` descriptor to `BottlesController`.
* The `Taproot.register` method also acts as a "start" method. Taproot
will not start any observers or perform any initialization until after
`register` is called. When `register` is called, Taproot schedules a
(re-)evaluation of all `data-controllers` elements after the current
JavaScript task. Therefore, `register` can be called at any time, and
additional calls within the same JavaScript task do not incur
additional overhead.