made by alan w smith
source other projects socials

bitty.js

small component - big reactivity

Hello, bitty

Bitty is a web component. Wrapping it around elements makes them reactive.

This page has examples of all of bitty's features. The first is this counter. Click it to make the number go up.

Everything works by mapping data-* attributes to functions. For example, the counter's code looks like this:

index.html

<bitty-js
  data-bridge="./counter.js">

  <button
    data-r="showNum"
    data-c="addOne"
    data-s="showNum"
  >Click Me
  </button>

</bitty-js>
counter.js

export default class {

  #num = 0;

  _addOne(_) {
    this.#num += 1;
  }

  $showNum(el, _) {
    el.innerHTML = this.#num;
  }

}

There are more features, but that's everything you need to know to get started.

Using bitty

The sections below provide details about how everything works. Check them out to dig in.

Quick-Start

bitty is still under development. Quick-start instructions are on the way.

[This is a place holder stub while the content is being written]

Counter Details (Basic Functionality Overview)

Preface

This section uses a detailed breakdown of the counter example to look at the core reactive functionality bitty provides. Skip down to the Basic Examples section if you'd prefer looking at code samples to get a handle on things. They're laid out starting with the basics to support a general run through.

Starting With The Pieces

Three pieces of code come together to generate the counter example:

  1. A script tag that loads the bitty.js file as a module:

    <script type="module" src="bitty.js"></script>
  2. A bitty-js tag with a button in it:

    
    <bitty-js
      data-bridge="./counter.js">
    
      <button
        data-r="showNum"
        data-c="addOne"
        data-s="showNum"
      >Click Me
      </button>
    
    </bitty-js>
  3. A counter.js file:

    
    export default class {
    
      #num = 0;
    
      _addOne(_) {
        this.#num += 1;
      }
    
      $showNum(el, _) {
        el.innerHTML = this.#num;
      }
    
    }

The bitty-js tag

The data-r, data-c, and data-s attributes

button Tag Registration

Clicking the button

Four things happen when you click the button in our example. They occur in this order:

  1. The button fires a click event that gets picked up by the bitty-js element.
  2. The bitty-js element checks to see if the element that sent the click event (i.e. our button) has a data-c attribute. When it finds it, it uses the attribute's value (addOne) to call the _addOne function from the Wires class export. We'll look at that function's code a momentarily.

    (NOTE: The _ (underscore) is prepended to the function name automatically. It's part of the overall naming convention that's details in the Naming Convention section below.)

  3. After the _addOne function runs, the bitty-js element looks back at the button element to see if it has a data-s attribute.

    When it finds it, it uses its value (showNumber) to run through all the elements in the list with that name that was assembled earlier with all the elements that have a data-r="showNumber" attribute.

    [TKTKTK: rephrase this and the earlier note to describe the list as a list of receivers].

    Each element in the list is run through the $showNumber function from the Wires export. As with the _ on _addOne, the $ is prepended to call the $showNumber function automatically as part of the naming convention.

  4. Since our button has a data-r attribute with the showNumber value it goes through the function which call runs an .innerHTML update on it to change the value.

With those steps completed, the button ends up with the new number

The counter.js File

[TKTKTK: this is a placeholder stub until the content has actually been written]

Even More Things

There are other features (e.g. initialing data, batch updates, using templates, etc...), but the core functionality comes down to those three attributes.

Basic Examples

Updating HTML

Waiting

<bitty-js
  data-bridge="./updating-html.js">

  <div data-r="updateText">
    Waiting
  </div>

  <button
      data-s="updateText">
  Click me
  </button>

</bitty-js>

Storing State

x

<bitty-js
    data-bridge="./storing-state.js">

    <div data-r="displayCount">x</div>

    <button
        data-c="increment"
        data-s="displayCount">
    Click Me
    </button>

</bitty-js>

Loading Initial State


<bitty-js
    data-bridge="./loading-initial-state.js"
    data-send="displayCount">

    <div data-r="displayCount"></div>

    <button
        data-c="increment"
        data-s="displayCount">
    Click Me
    </button>

</bitty-js>

Updating Self


<bitty-js
    data-bridge="./updating-self.js"
    data-send="displayCount">

    <div></div>

    <button
        data-c="increment"
        data-s="displayCount"
        data-r="displayCount">
    Click Me
    </button>

</bitty-js>

Update Classes

Sample Text

<bitty-js
  data-bridge="./delta.js">

  <div
    data-r="setClass"
  >Sample Text</div>

  <div>

    <button
        data-c="setColor"
        data-s="setClass"
        data-color="red">
    Red
    </button>

    <button
        data-c="setColor"
        data-s="setClass"
        data-color="green">
    Green
    </button>

    <button
        data-c="setColor"
        data-s="setClass"
        data-color="blue">
    Blue
    </button>

  </div>

</bitty-js>

Updating Values


<bitty-js
  data-bridge="./charlie.js">

  <div>

    <label
        for="update-values-output-slider">
    Output Slider
    </label>

    <input
      type="range"
      id="update-values-output-slider"
      min="0"
      max="100"
      data-r="sliderValue">

  </div>

  <div>

    <label
        for="update-values-input-slider">
    Input Slider
    </label>

    <input
      type="range"
      id="update-values-input-slider"
      min="0"
      max="100"
      data-c="setValue"
      data-s="sliderValue">

  </div>

</bitty-js>

Avoiding Feedback


<bitty-js data-bridge="./echo.js">

  <div>
    <label for="avoid-feedback-slider-b">Slider B</label>
    <input
      type="range"
      id="avoid-feedback-slider-b"
      data-c="setValue"
      data-s="setSliderA"
      data-r="setSliderB"
    />
  </div>

  <div>
    <label for="avoid-feedback-slider-a">Slider A</label>
    <input
      type="range"
      id="avoid-feedback-slider-a"
      data-c="setValue"
      data-s="setSliderB"
      data-r="setSliderA"
    />
  </div>

</bitty-js>

Sent Multiple Signals

Output Text: Waiting


<bitty-js data-bridge="./foxtrot.js">

  <div>

    <p>
      Output Text:
      <span data-r="displayNumber">Waiting</span>
    </p>

    <label for="multiple-updates-output-slider">
      Output Slider
    </label>

    <input
      type="range"
      id="multiple-updates-output-slider"
      data-r="setNumberValue">

  </div>

  <div>
    <label for="multiple-updates-input-slider">
      Input Slider
    </label>

    <input
      type="range"
      id="multiple-updates-input-slider"
      data-c="setNumber"
      data-s="displayNumber|setNumberValue">

  </div>

</bitty-js>

Use Multiple Receivers

Copy 1:
Copy 2:
Copy 3:
Copy 4:
Copy 5:
Copy 6:

<bitty-js data-bridge="./golf.js">

  <div>
    <div>Copy 1: <span data-r="displayNumber"></span></div>
    <div>Copy 2: <span data-r="displayNumber"></span></div>
    <div>Copy 3: <span data-r="displayNumber"></span></div>
    <div>Copy 4: <span data-r="displayNumber"></span></div>
    <div>Copy 5: <span data-r="displayNumber"></span></div>
    <div>Copy 6: <span data-r="displayNumber"></span></div>
  </div>

  <div>

    <label for="multiple-receivers-input-slider">
      Input Slider
    </label>

    <input
      type="range"
      id="multiple-receivers-input-slider"
      min="0"
      max="100"
      data-c="setNumber"
      data-s="displayNumber">

  </div>

</bitty-js>

Initializing Values


<bitty-js
  data-send="setSliderA|setSliderB"
  data-bridge="./hotel.js">

  <div>

    <label for="init-slider-b">
      Slider B
    </label>

    <input
      id="init-slider-b"
      type="range"
      min="0"
      max="100"
      data-c="setValue"
      data-s="setSliderA"
      data-r="setSliderB">

  </div>

  <div>

    <label for="init-slider-a">
      Slider A
    </label>

    <input
      id="init-slider-a"
      type="range"
      min="0"
      max="100"
      data-c="setValue"
      data-s="setSliderB"
      data-r="setSliderA">

  </div>

</bitty-js>

Create Element

Output

<bitty-js
  data-send="buttonValue"
  data-bridge="./create-element.js"
>
  <div>
    <button data-c="makeSlider" data-r="buttonValue"></button>
  </div>
  <div>
    <div>Output</div>
    <div class="output"></div>
  </div>
</bitty-js>

Call Wires init


<div class="make-two-columns">
  <bitty-js data-bridge="./call-wires-init.js"></bitty-js>
</div>

Template Loading Via Wires


<bitty-js
  data-send="showValue"
  data-bridge="./template-loading.js"
></bitty-js>

Nested Components


<bitty-js
  data-send="showValue"
  data-bridge="./nested-parent.js"
></bitty-js>

Nested Components with Ignore


<bitty-js
  data-bridge="./nested-parent-ignore.js"
  data-ignore="increment"
></bitty-js>

Getting Data From Children


<bitty-js
  data-bridge="./nested-parent-calc.js"
  data-ignore="increment"
  data-send="showValue"
></bitty-js>

Batch Updates

Greater that 10:
Greater that 20:
Greater that 30:
Greater that 40:
Greater that 50:
Greater that 60:
Greater that 70:
Greater that 80:
Greater that 90:

<bitty-js
  data-send="setValue"
  data-bridge="./batch-updates.js">

  <div>
    <div>Greater that 10: <span data-r="check10"></span></div>
    <div>Greater that 20: <span data-r="check20"></span></div>
    <div>Greater that 30: <span data-r="check30"></span></div>
    <div>Greater that 40: <span data-r="check40"></span></div>
    <div>Greater that 50: <span data-r="check50"></span></div>
    <div>Greater that 60: <span data-r="check60"></span></div>
    <div>Greater that 70: <span data-r="check70"></span></div>
    <div>Greater that 80: <span data-r="check80"></span></div>
    <div>Greater that 90: <span data-r="check90"></span></div>
  </div>

  <div>

    <label for="batch-update-input-slider">
      Input
    </label>

    <input
      id="batch-updates-input-slider"
      type="range"
      min="0"
      max="100"
      data-c="updateNumber"
      data-b="checkNumbers"
      data-r="setValue"
    />

  </div>

</bitty-js>

Batch Initialization

Greater that 10:
Greater that 20:
Greater that 30:
Greater that 40:
Greater that 50:
Greater that 60:
Greater that 70:
Greater that 80:
Greater that 90:

<bitty-js
  data-batch="checkNumbers"
  data-send="setValue"
  data-bridge="./batch-updates.js">

  <div>
    <div>Greater that 10: <span data-r="check10"></span></div>
    <div>Greater that 20: <span data-r="check20"></span></div>
    <div>Greater that 30: <span data-r="check30"></span></div>
    <div>Greater that 40: <span data-r="check40"></span></div>
    <div>Greater that 50: <span data-r="check50"></span></div>
    <div>Greater that 60: <span data-r="check60"></span></div>
    <div>Greater that 70: <span data-r="check70"></span></div>
    <div>Greater that 80: <span data-r="check80"></span></div>
    <div>Greater that 90: <span data-r="check90"></span></div>
  </div>

  <div>
    <label for="batch-update-input-slider">Input</label>
    <input
      id="batch-updates-input-slider"
      type="range"
      data-c="updateNumber"
      data-b="checkNumbers"
      data-r="setValue">
  </div>

</bitty-js>

Customize Event Listeners

Waiting...

<bitty-js
  data-listeners="mouseover|mouseout"
  data-bridge="./set-listeners.js">

  <div data-r="sawMouse">Waiting...</div>

  <button data-s="sawMouse">Mouse Instead of Click</button>

</bitty-js>

Element Access

the quick fox

<style>
  .element-access-class {
    background-color: var(--element-access-background);
  }
</style>

<bitty-js
  data-bridge="./element-access.js">

  <div class="element-access-class">
    the quick fox
  </div>

  <button data-c="randomizeBackground">
    Randomize Background
  </button>

</bitty-js>

Isolated Elements

TODO: change so div backgrounds are changed since you can't see the button backgrounds change when the over CSS is in play

<style>
    .isolated-element {
    background-color: var(--isolated-element-background);
    }
</style>
<div>
  TODO: change so div backgrounds are changed since you can't see the button
  backgrounds change when the over CSS is in play
</div>

<bitty-js data-bridge="./isolate-element.js">
  <button data-c="randomizeBackground" class="isolated-element">
    Randomize Background
  </button>
</bitty-js>
<bitty-js data-bridge="./isolate-element.js">
  <button data-c="randomizeBackground" class="isolated-element">
    Randomize Background
  </button>
</bitty-js>
Examples To Make
Color Picker Example
Status: Beta

I'm still working on the initial feature set. Everything you see in the examples below is working. More to come with the TODOs further below.

I'll move views of the source code into the examples soon. For now, you'll need to peak at the .js files themselves to get and idea.

Here's the bitty.js web component file.

Other files are in the examples folder.

Usage

I'll push to npm soon. Copy the .js file down and use it directly for now.

That's it.

You should have your own working copy of Storing State example from below.

Quick Notes

I'm working on docs. For now, the important things to know are:

That should get you started. Poke around the examples to see more until I get more docs up

Getting Updates

Checkout the scratch notes at the end of the page for things I'm working on and considering. Follow me on mastodon, bluesky, or my RSS feed for updates. And, if you have feedback, please let me know!

-a

Design Goals

[NOTE: these are scratch notes. some of this will got to the features list below]

  1. [x] No build steps required
  2. [x] Single file usage
  3. [x] Minimal API. Minimal boilerplate.
  4. As simple as possible. As complex as necessary.
  5. [x] Default data-s is limited to a specific instances and its ancestors
  6. [] First class error reporting
  7. [x] Functionality defied by loading an arbitrary Wires class
  8. [x] Separation of concerts between function calls, sending signals, and receiving signals
  9. [x] Allow any standard manipulation of the receiving element (e.g. can change innerHTML, value, classes, etc.)
  10. [x] Use naming conventions for all core functionality
  11. [x] Style directly from the parent page
  12. [x] New elements participate in the function execution and signals
  13. [x] Rely on built-in functionality as much as possible
  14. [x] Event throttling through .requestAnimationFrame
  15. [x] Provide synchronous function calls
  16. [] Provide asynchronous function calls
  17. [] Provide asynchronous await function calls
  18. [] Provide asynchronous then function calls
  19. [x] Provide synchronous signals
  20. [] Provide asynchronous signals
  21. [] Provide asynchronous await signals
  22. [] Provide asynchronous then signals
  23. [x] Multiple instances work independently a single page by default
  24. [] Monitor elements outside the component for changes by attaching listeners to them via query selector
  25. Wires components can communicate with their parent bitty-js through a bridge (e.g. to run .querySelector() calls on the bitty-js element
  26. TODO: Multiple instances on the same page can cross communicate with and update each other
  27. TODO: Set instance specific style variables from inside the component
  28. Use values directly (i.e. don't have to call out to an external function to update a slider value you just changes)
  29. TODO: Components can be nested
  30. MAYBE: Child components can communicate with parent through bridge
  31. MAYBE: Parent components can communicate with children through bridge
  32. Intuitive data passing
Naming Convention

[This section is currently just a placeholder stub. Content will populate as progress continues]

Error Handling

Providing good, actionable error messages is an explicit design goal. Details about them will be added here as work continues.

Things to add

Features

This section is currently at the level of draft/scratch notes. Expect some duplication and possibly conflicting items.

Out-Of-Scope

These items are not in scope for bitty.js. They are the responsibility of Wires classes:

Known Bugs

This is largely a placeholder. Things will be added as features become stable

Queue
Release Notes