@jonathanhefner/taproot v0.0.1
Taproot
A low-budget Stimulus knock-off, weighing in at 6 kB minified / 2.5 kB minified + gzipped.
The key differences are:
The core HTML attributes are
data-controllersanddata-actions, instead ofdata-controlleranddata-action. This lets you test drive Taproot and Stimulus at the same time, if desired.The
data-actionssyntax is:<div data-actions="kebab-case-controller:kebab-case-method@event [...]" />Modifiers such as
+onceor+passivecan be appended to the event. Likewise, the event target can be changed by appending+documentor+windowto the event.Implicit events are not supported, but commas can be used as shorthand to bind multiple actions to the same event:
<div data-actions="selection:all,clipboard:copy@click" />Likewise, commas can be used as shorthand to bind the same action to multiple events:
<input type="text" data-actions="autocomplete:suggest@change,keyup" />Namespaced data attributes on the controller element can be accessed via a controller's
dataproperty. Default values can be specified via a staticdefaultsproperty on the controller. A default value that is neither a string norundefinedindicates that attribute should be deserialized withJSON.parsewhen reading, and serialized withJSON.stringifywhen writing. For example:class BottlesController extends Taproot.Controller { static defaults = { count: 99 } decrement() { this.data.count -= 1 } }If the
data-bottles-countattribute is not present on the controller element,this.data.countwill return99. Otherwise, it will return the value ofdata-bottles-countparsed byJSON.parse.Controllers provide a
dataFormethod that returns a proxy object which is similar to thedataobject for any element. For example:class BottlesController extends Taproot.Controller { static defaults = { count: 99 } takeN({ currentTarget }) { this.data.count -= this.dataFor(currentTarget, { n: 1 }).n } }If the
data-bottles-nattribute is not present oncurrentTarget,this.dataFor(currentTarget, { n: 1 }).nwill return1. Otherwise, it will return the value ofdata-bottles-nparsed byJSON.parse. If the defaults argument is not specified,dataForwill use the controller's staticdefaults.The
nodesandnodeSetscontroller properties can be used to query for nodes that have a particular namespaced attribute. For example: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.statuswill return the first node in the controller element tree that has adata-bottles-statusattribute.A controller's
connectanddisconnectmethods will be invoked only if they reflect the state of the controller element at the time of invocation. For example:class ItemController extends Taproot.Controller { connect() { /* ... */ } disconnect() { /* ... */ } moveToTop() { this.element.parentElement.prepend(this.element) } remove() { this.element.remove() } }moveToTopgenerates two DOM mutation events becauseprependremoves the controller element from the DOM before prepending it toparentElement. However, at the time both mutation events are processed, the controller element is no longer disconnected from the DOM, so neitherdisconnectnorconnectwill be invoked.On the other hand,
removepermanently removes the controller element from the DOM. When its corresponding mutation event is processed,disconnectwill be invoked.The
Taproot.registermethod accepts an object that maps controller descriptors to constructors. It also automatically kebab-cases the given descriptors and strips their-controllersuffix. Thus, for example, the following are all equivalent:import * as Taproot from "@jonathanhefner/taproot" import { FooController, BarController } from "./foo-and-bar.js" Taproot.register({ foo: FooController, bar: BarController })import * as Taproot from "@jonathanhefner/taproot" import { FooController as foo, BarController as bar } from "./foo-and-bar.js" Taproot.register({ foo, bar })import * as Taproot from "@jonathanhefner/taproot" import { FooController, BarController } from "./foo-and-bar.js" Taproot.register({ FooController, BarController })import * as Taproot from "@jonathanhefner/taproot" import * as fooAndBar from "./foo-and-bar.js" Taproot.register(fooAndBar)Taproot.registercan also accept a non-anonymous constructor, and use its name as the descriptor. For example:import { register, Controller } from "@jonathanhefner/taproot" register(class BottlesController extends Controller { /* ... */ })will register the
bottlesdescriptor toBottlesController.The
Taproot.registermethod also acts as a "start" method. Taproot will not start any observers or perform any initialization until afterregisteris called. Whenregisteris called, Taproot schedules a (re-)evaluation of alldata-controllerselements after the current JavaScript task. Therefore,registercan be called at any time, and additional calls within the same JavaScript task do not incur additional overhead.
4 years ago