bitty-js is a web component. It uses signals to make pages reactive.
For example:
Waiting for click
The bitty-js API is made from data-* attributes. For example,
here's the HTML that makes up the example:
<bitty-jsdata-connect="/modules/timestamp-demo.js"><buttondata-send="updateTimestamp">Show Timestamp</button><divdata-receive="updateTimestamp">Waiting for click</div></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
The bitty-js tag's data-connect attribute defines the path to a JavaScript module responsible for providing functionality.
The button tag's data-send attribute sets it up to send update signals when its clicked.
The div tag's data-receive attribute registers it to react to update signals when they occur.
Functionality
The update values from the data-send and data-receive
attributes map to a function in the module with the same name.
The event that triggered the update (e.g. click events on
the button in the example)
The element to update (e.g. the div element in the example)
The event and element are the same ones you'd get
from document.addEventListener() and document.querySelector()
functions.
When the example button is clicked the div element is passed
through the function where it has its innerHTML updated.
(The event isn't used in this example.)
Basic Examples
This is a collection of examples
showing some of the ways bitty-js
can be used. The examples all work. The source code
for each is available in the Show Example Code
sections.
Send/Receive
Waiting for click
Show Example Code
example.html
<bitty-jsdata-connect="/modules/send-and-receive.js"><divdata-receive="updateSendAndReceive">Waiting for click</div><buttondata-send="updateSendAndReceive">Click Me</button></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
<bitty-jsdata-connect="/modules/send-multiple-signals.js"><div><divdata-receive="multiAlfa">Waiting for Alfa</div><divdata-receive="multiBravo">Waiting for Bravo</div><divdata-receive="multiCharlie">Waiting for Charlie</div></div><buttondata-send="multiAlfa|multiBravo|multiCharlie">Click Me</button></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
<bitty-jsdata-connect="/modules/additional-attributes.js"><divdata-receive="updateFromData">Waiting for clicks</div><div><buttondata-color="red"data-send="updateFromData">red</button><buttondata-color="green"data-send="updateFromData">green</button><buttondata-color="blue"data-send="updateFromData">blue</button></div></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
<bitty-jsdata-connect="/modules/multiple-senders-and-multiple-receivers.js"><div><divdata-receive="bookTitle">Waiting for click</div><divdata-receive="bookAuthor">Waiting for click</div><divdata-receive="bookPages">Waiting for click</div></div><div><buttondata-type="fiction"data-send="setBookType|bookTitle|bookAuthor|bookPages">Fiction</button><buttondata-type="nonFiction"data-send="setBookType|bookTitle|bookAuthor|bookPages">Non-Fiction</button></div></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
<bitty-jsdata-connect="/modules/forward-inside-a-module.js"><divdata-receive="forwardedSignal">Waiting for click</div><buttondata-send="forwardUpdate">Click Me</button></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
exportdefaultclass{updateFromBitty(_event,element){element.innerHTML=`Initialized at ${Date().toString()}`;}}
ID: 0023-sending-from-bitty
Storing State
Waiting for click
Show Example Code
example.html
<bitty-jsdata-connect="/modules/storing-state.js"><divdata-receive="updateStoredState">Waiting for click</div><buttondata-send="updateStoredState">Click Me</button></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
<bitty-jsdata-connect="/modules/calling-functions.js"><div>Waiting for click</div><buttondata-send="updateWithoutReceiver">Click Me</button></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
<bitty-jsdata-connect="/modules/using-event-data.js"><divdata-receive="updateWithEventData">Waiting on click</div><buttondata-send="updateWithEventData">Click Me</button></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
<bitty-jsdata-connect="/modules/element-uuids.js"><divdata-receive="uuidDemo">Waiting for click <br />---</div><div><buttondata-send="uuidDemo">Alfa</button><buttondata-send="uuidDemo">Bravo</button></div></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
<bitty-jsdata-connect="/modules/create-elements.js"><divdata-receive="createElements">
Waiting for click
</div><divdata-receive="elementsAdded"><buttondata-send="createElements|elementsAdded">
Create Elements
</button></div></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
modules/create-elements.js
exportdefaultclass{createElements(_event,element){constdisplay=document.createElement("div");display.innerHTML="Waiting for slider";display.dataset.receive="update";element.replaceChildren(display);constslider=document.createElement("input");slider.type="range";slider.value="0";slider.dataset.send="update";element.appendChild(slider);}elementsAdded(_event,element){element.innerHTML="Elements added";}update(event,element){element.innerHTML=event.target.value;}}
ID: 0120-create-elements
Copy Button
The quick brown fox
jumps over
the lazy dog
Show Example Code
example.html
<bitty-jsdata-connect="/modules/copy-button.js"><preid="text-to-copy">
The quick brown fox
jumps over
the lazy dog</pre><div><buttondata-target="#text-to-copy"data-send="copyText">Copy Text</button></div></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
<bitty-jsdata-connect="/modules/alternate-event-listeners.js"data-listeners="mouseover|mouseout"><buttondata-send="sawMouse">
Mouseover
</button><divdata-receive="sawMouse">
Waiting for mouse
</div></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
modules/alternate-event-listeners.js
exportdefaultclass{sawMouse(event,element){element.innerHTML=`${event.type} at ${Date.now()}`;}}
ID: 0190-alternate-event-listeners
Multiple Class Exports
Waiting for click
Waiting for click
Show Example Code
example.html
<bitty-jsdata-connect="/modules/multiple-class-exports.js"><buttondata-send="updateDefaultClass">Default Class</button><divdata-receive="updateDefaultClass">Waiting for click</div></bitty-js><bitty-jsdata-connect="/modules/multiple-class-exports.js|AlternativeClass"><buttondata-send="updateAltClass">Alternative Class</button><divdata-receive="updateAltClass">Waiting for click</div></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
exportdefaultclass{
#value = 0;
updateIsolatedInstance(event,element){// The same signal name is used in both
// elements in this example. Separating
// the data can be done by comparing
// the bitty-js component they are
// connect to with the data from
// the triggering event.
// get the uuid of the parent bitty-js
// component
constbittyUUID=this.api.dataset.uuid;// get the uuid of the parent element from
// the event target
constcheckUUID=event.target.parentNode.dataset.uuid;if(bittyUUID===checkUUID){this.#value+=1;element.innerHTML=this.#value;}}}
Sending Signals From Parents To Children
Waiting
Code
<bitty-jsdata-connect="/modules/sending-signals-from-parents-to-children.js"><div><buttondata-send="demoParentToChildSignal">
Click Me
</button></div><div><bitty-jsdata-connect="/modules/sending-signals-from-parents-to-children.js"><divdata-receive="demoParentToChildSignal">
Waiting
</div></bitty-js></div></bitty-js><scripttype="module"src="bitty-1.0.0-rc3.min.js"></script>
exportclassParent{demoSendingFromChildToParent(_event,element){element.innerHTML=Date.now();}}exportclassChild{// must exist, but no methods required
}
Using it is done by setting the data-connect
attribute of a bitty-js to the name of
the class in the bittyClasses object:
<bitty-jsdata-connect="InlineExample"><divdata-receive="updateFromPage">Waiting for click</div><buttondata-send="updateFromPage">Click Me</button></bitty-js>
Here's what it looks like:
Waiting for click
Documentation In Progress
Docs are in the process of being overhauled.
Check out the code from the examples and test cases
for the time being.
Release Notes
Version: 1.0.0-rc3
Sept. 23, 2025
Fixed bug where this.api.send() would update the
data-send attribute on the original element so
it no longer send the same signal. The method
is now called this.api.forward().
Removed data-watch. Any element
can receive a signal from anywhere else on the
page without extra overhead. The biggest
negative to this approach is that you have
to be a little more careful to avoid naming
collisions. But, the ease of use is well
worth that minor trade off.
Added Test Suite Results Report to site
for easier viewing.
Applying passed and failed colors to
individual tests for quicker scanning.
Updated tests to use unique signal
names to avoid collisions.
Version: 1.0.0-rc2
Sept. 19, 2025
data-watch is applied to bitty-js elements
directly instead of individual elements.
The signals are send back down the tree
when one is received via data-watch
Refactored multiple UUID() calls to single
function to save a few characters.
Version: 1.0.0-rc1
Sept. 18, 2025
Initial release candidate for version 1.0.0.
No changes since v0.4.0.
Version: 0.4.0
Sept. 17, 2025
Flipped module arguments from example(el, event)
to example(event, el) since that lines up better
with the order of send/receive. (i.e. the event
is what gets sent from data-send and the el
is the element from data-receive
Remove HTML output of error message. They're
nice during debug but wouldn't want them
showing up in prod. Could use a debug flag
but that's more overhead than I want to add.
So, message just go to the console.
Version: 0.3.0
Sept. 17, 2025
Big refactor based on experience using 0.2.0.
Added more generic error handing where you just send
a message and it outputs to the page along with the
UUID of the element that had the problem. The
elements have bitty-js-error style classes on them
so their display can be controlled via CSS.
Updated to use the name data-connect instead of data-module
The data-connect can point to either a module or
to a class in a global bittyClasses window object.
Alternate module classes are selected by passing a | followed
by the desired class name instead of using data-use.
(e.g. data-connect="./module.js|AltClass)
If there's an bittyInit() function in a module it's the
first thing that gets called once bitty is loaded.
Removed data-call. The data-send attribute is used
for everything. Nothing would fire from it in prior versions
if there wasn't at least one element with a data-receive
with the same name. Now it calls the function once
with no element if there's no associated data-receive
Added test suite.
The biggest update is changing data-b, data-c,
data-r, and data-s to data-batch,
data-call, data-receive, and data-send,
respectively. I originally use the shorter names to reduce the text
length. The increased clarity of the longer names is worth the
few extra characters.
Remove expanded error messages. They were nice, but added a bunch of size
without a significant improvement.
The other big change is removing the leading _ and
$ characters from function names. They were originally
put in place to create a naming convention that differentiated
between functions that were hit with data-call
and data-send. In practice, that wasn't necessary.
Removed the data-batch attribute. While there are some use
cases where it might be nice, the extra complexity, mental overhead, and maintance
aren't worth it.
Added Mutation Observer so data-* functionality works
on elements that are added after initialization.
Added Mutation Observer to watch for
removed elements to pull them out of the mix when
they get gone.
Split the web site page up into individual template for sections. Much
nicer to work with.
Updated example functions to always use (el, _event) instead
of (el, _) for clarity.
Removed inert/include/ignore from the top level
components then remove them. This was originally a way to tell
parent components to ignore specific calls and signals from children.
After using it a bit, I don't think the complexity is worth it.
Better to just name functions an signals so they don't collide.
Isolated default signal travel to only go down the DOM.
That is, if there are nested bitty-js tags, signals from
the child tags don't propagate to the parent by default.
(see data-watch for how to send signals up and to
siblings)
Added data-watch so parents and siblings
can receive signals from their children and other
siblings. With this, signals can be sent up, down, and
to siblings.
Ignore events directly from bitty-js
elements (i.e. only process events from child elements).
This is done to prevent data-send attributes on
bitty-js elements from firing repeatedly
when things inside the element send events (e.g. clicks).
Renamed scripts directory to modules.
Moved bitty source script file under bitty-versions/bitty-v0.3.0.js
and copying to prod at the root of the site (i.e. /bitty-v0.3.0.js)
so examples look cleaner.
Renamed data-bridge to data-connect for clarity.
Renamed this.widget.bridge to this.module.api in bitty-js
Added this.api.send(key, event) to send/forward
events from inside modules.
Added Progressive Enhancement and JavaScript Data comparison examples.
Added/polished a bunch of other examples.
Version: 0.2.3
June 5, 2025
Lots of error handling work in this one.
Moved UUID generation for the bitty-js and all
data-* reactive elements to the first thing in
connectedCallback() to aid in error messaging.
Made connectedCallback() and async function to
throw loading the widget module into its own funcitoun
Created an #errors private var to store error messages and
help by ID.
Added the first few error messages
Added this.error() for outputting errors. It takes two
optional arguments: an ID and an Element. The ID maps to the IDs in
#errors. They're used to add detail and help message to the
error output.
The ID defaults to 0 which is an unclassified error type.
The message for that ID includes a note to the developer to use an ID to
classify the error. I consider it a bug if an appropriate ID doesn't
exist and request an issue be open to fix it.
The this.error() method dumps the
bitty-js elemnet after the message.
If an element was passed to this.error() it's dumped out as
well.
The error message end up being pretty long. The end up adding a bunch of
lines to the source file. That's an explicit decision aimed at making
bitty easier to work with.
Added top level debug function that uses a
debug search query param from the
window.location to see if it should output.
Only thing I don't like about it is that it shows the function's line
number instead of the line number from where it was called. Something to
look into.
The debug function takes an optional element. It dumps it to
the console if one comes in.
Renamed data-wires to data-bridge. Fits better
and maps nicer to the .bridge coming back in from the support
class.
Set up to load the default class exported from a widget module if no other
class is defined (which happens with 'data-widget')
Moved all examples to use a default class export instead of a named class
Added a data-widget attribute to the
bitty-js elements to allow using multiple classes from inside
a single supporting .js module file.
bitty-component-error and
bitty-element-error classes are added to the class list of
elements where errors occur.
Version: 0.2.2
June 4, 2025
Added UUIDs for pending error handling. Shuffeld a bunch of stuff around to
make the examples look nicer.
Added uuids as data-uuid attrs to all elements with related
data-* attributes. Added them to the
bitty-js elements too. I was originally thinking I'd add
the UUIDs on the elements internally (i.e. el.uuid instead
of el.dataset.uuid).
I decided on the attribute approach because it offers two benefits: 1.
You can see the IDs in the Element tree view of developer consoles, and
2. You can address them with CSS to update styles based on the UUID.
Both of those thing will go to supporting better error messages and bug
hunting.
Mostly content updates largely focused on detailing the opening counter
example.
Added CONTRIBUTING file.
Added watcher script to build site when files change.
Refined the initial counter example to remove the text and change the
private variable to this.num to make the examples look nicer.
Moved the Examples section directly below the initial basic functionality
overview section.
Added reminders all over the place to edit the template and instead of the
output for HTML page (which would be overwritten. ask me how I know).
Started adding prettier-ignore comments to code snippets to
prevent the display output from being mangled.
Started stripping prettier-ignore comments from the HTML
output so it doesn't show in the example
Same goes for // deno-fmt-ignore-file in .js files
Added script to maintain the open open/closed states of the section
details elements across page loads. (Having them close every time I made a
change was maddening)
Moved all the example .js files into the site root. Not a fan
of that in general, but it makes the examples look better since the path
is shorter.
Version: 0.2.1
June 3, 2025
Some more wiring and some support tools for making the demo/docs site.
Made a Hello, World example that shows basic features. (Compared to the
single button press from the prior first example.)
Showing Hello, World code as part of the example.
Load templates automatically.
Throttling via .requestAnimationFrame().
Renamed data-f to data-c (i.e. "call")
for clarity.
Renamed data-prep attribute on bitty-js tag to
data-call (i.e. "call") for clarity.
Renamed data-init attribute on bitty-js tag to
data-send so it matches data-s more closely.
Made basic site builder for landing page.
Moved everything into details elements for the landing page.
Version: 0.2.0
June 3, 2025
Setting up a bunch of the basic wiring.
Rewrite to provide direct access to receiving elements. They are fully
accessible in the send/receive functions. (As compared to the 0.1.0
approach which required explicitly defining what would happen via an
update (e.g. update .innerHTML or .value).
Renamed data-wrapper and the target
Wrapper class to data-wires and
Wires. (bitty.js is the wrapper. The
Wires class is how things are hooked up.).
Added data-call attribute parsing to
bitty-js tag. It runs functions like data-c but
does so prior to adding the event listeners to the element.
Added data-batch attribute processing to
bitty-js tags. It operates like data-b but fires
before event listeners are added.
Added data-ignore attribute to allow components to avoid
calls to the named functions. (Send signals bubble up to parents of nested
components by default. This provides a way to ignore them).
The bitty-js element looks for an .init() method
in the Wires class. If it finds one it calls it during the
initialization process.
Passing both the element and the triggering event to the send/receive
functions.
Moved to using data-b explicitly to separate block calls from
individual function calls.
Order of operations is: function calls, batch calls, single send calls.
Temporarily removed preflight check until global functionality is defined.
Created example of loading a template via the
.init() call back to the Wires class.
Created example with nested components.
Created example with parent of child components pulling data from the
children to do a calculation.
Created example showing parent ignoring send signals from children.
Created example showing on components loading a child component via a template that becomes fully reactive.
Version: 0.1.0
June 2, 2025
Getting the project started.
Initial prototype.
<bitty-js>; wraps elements to provide them with reactive
capabilities.
Basic data-c, data-s, and data-r
in place to call functions, send, and receive updates, respectively.
Functionality is loaded using data-wrapper to point to a
module to load. The module must export a Wrapper class that
gets loaded and used to provide functions and send/receive callbacks.
Uses string prefixes to make determinations about how to handle data
through the send/receive channels (e.g.
htmlSOMETHING
updates .innerHTML of an element while
valueSOMETHING updates the .value).
Defined convention for functions. data-c maps to functions in
the Wrapper class that start with a
_ (underscore). The data-s and
data-r attributes map to functions in the
Wrapper class that start with a $ (dollar sign).
Decided against using data-c data-s and data-r on the
bitty.js tags. That would involved a lot of extra overhead in
parsing to differentiate between the top level element and the content it
wraps. Using data-send instead as a replacement for
data-c. Others, TBD.
Set up data-c="batchSOMETHIGN" functionality to send a single
single that gets turned into multiple signals in the Wrapper.
Defined convention of using .batches to look for batches. It
must be a hash where the keys match the incoming request and the value is
an array of functions to run.
Defined .bridge to allow Wrapper functions to
access the parent bitty-js element.
Scoped event listeners to the bitty-js elements.
Set up data-listeners attribute on bitty-js tags
to override the default listeners (which are input and
click).
Created example that stores its own state.
Created example that updates an element with the same content it sent
(i.e. verified feedback can be avoided).
Created example using data-send to load initial values.
Created example that sends multiple signals.
Created example with multiple elements receiving the same signal.
Created example showing how to update global CSS variables/properties.
Created example showing custom event listeners.
Created example showing how to update CSS variables/properties scoped to
the content of individual bitty-js tags.
Examples use parent page's CSS for styling. It confirms I'm happy with the
choice to use the light DOM instead of the shadow DOM.
Set up initial preflight check to ensure functions are in place.
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("div").innerHTML="PASSED";}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTest0010(_event,el){el.innerHTML="PASSED";}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTest0015(_event,el){el.innerHTML="PASSED";}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTestAlfa0030(_event,el){el.innerHTML="PASSED";}runTestBravo0030(_event,el){el.innerHTML="PASSED";}runTestCharlie0030(_event,el){el.innerHTML="PASSED";}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTest0040(_event,el){el.innerHTML="PASSED";}}
Access data-* Attributes from Sending Elements
FAILED
Test Code ID: 0050-accessing-data-from-sending-element
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTest50(event,element){element.innerHTML=event.target.dataset.status;}}
Access data-* Attributes from Receiving Elements
FAILED
Test Code ID: 0060-access-data-from-receiving-element
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTest0060(_event,el){el.innerHTML=el.dataset.status;}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTestAlfa0070(_event,el){el.innerHTML="PASSED";}runTestBravo0070(_event,el){el.innerHTML="PASSED";}runTestCharlie0070(_event,el){el.innerHTML="PASSED";}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTest0075(event,_el){this.api.forward(event,"forwardEvent0075");}forwardEvent0075(_event,el){el.innerHTML="PASSED";}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{
#count = 0;
asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();this.api.querySelector("button").click();this.api.querySelector("button").click();}runTest0080(_event,el){this.#count+=1;if(this.#count===3){el.innerHTML="PASSED";}}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
constbutton=this.api.querySelector("button");button.click();}runTest0090(_event,el){el.innerHTML="PASSED";}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTest0100(event,el){if(event.type==="click"){el.innerHTML="PASSED";}}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTest0110(event,el){if(event.target.dataset.uuid!==undefined){el.innerHTML="PASSED";}}}
Verify UUIDs are Created on Receiving Elements
FAILED
Test Code ID: 0120-verify-uuids-exist-on-receivers
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTest0120(_event,element){if(element.dataset.uuid!==undefined){element.innerHTML="PASSED";}}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
constel=this.api.querySelector("div");if(el.dataset.uuid){el.innerHTML="PASSED";}}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTest0127(event,element){if(event.uuid!==undefined){element.innerHTML="PASSED";}}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
constels=this.api.querySelectorAll("[data-uuid]");[...els].forEach((el)=>{if(el.dataset.uuid==="original-uuid"){el.innerHTML="PASSED";}});}}
Ensure New Elements Get a data-uuid if They Have data-send/receive
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}asyncrunTest0130(_event,element){constnewButton=document.createElement("button");newButton.dataset.receive="placeholderToGenerateUUID";newButton.innerHTML="FAILED";element.replaceChildren(newButton);// sleep for test to wait for observer
// to update the UUID.
awaitsleep(200);if(newButton.dataset.uuid!==undefined){newButton.innerHTML="PASSED";newButton.classList.add("test");}}}
Ensure Elements Made from Templates Get UUIDs
FAILED
Test Code ID: 0140-ensure-template-elements-get-uuids
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}consttemplate=document.createElement("template");template.innerHTML=`<button class="test" data-send="testStub">FAILED</button>`;exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}asyncrunTest0140(_event,element){letnewButton=template.content.cloneNode(true);element.replaceChildren(newButton);// sleep for test to wait for observer
// to update the UUID.
awaitsleep(100);if(element.childNodes[0].dataset.uuid!==undefined){element.childNodes[0].innerHTML="PASSED";element.childNodes[0].classList.add("test");}}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportclassAltClass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTest0150(_event,el){el.innerHTML="PASSED";}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.innerHTML="PASSED";}}
Use data-send To Call a Function When There's No Element With a Corresponding Receiver
FAILED
Test Code ID: 0170-use-data-send-without-a-receiver
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTest0170(event,_el){this.api.querySelector("div").innerHTML="PASSED";}}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportclassParent{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTest0180(_event,element){element.innerHTML=`PASSED`;}}exportclassChild{// must exist, but no methods required
}
Parents Receive Child Component Signals
FAILED
Test Code ID: 0200-parent-receive-child-component-signlas
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportclassParent{asyncbittyInit(){awaitsleep(100)// time pad for test
this.api.querySelector("button").click();}runTest0200(event,element){element.innerHTML="PASSED";}}exportclassChild{// must exist, but no methods required
}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportclassAlfa{runTest0210(event,element){element.innerHTML="PASSED";}}exportclassBravo{asyncbittyInit(){awaitsleep(100)// time pad for test
constbtn=this.api.querySelector("button");btn.click();}}
Send Signals Up and Down with data-watch
FAILED
FAILED
FAILED
FAILED
FAILED
FAILED
FAILED
FAILED
FAILED
FAILED
Test Code ID: 0215-send-signals-up-and-down-with-watch
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
constbtn=this.api.querySelector("button");btn.click();}runTest0215Echo(_event,element){element.innerHTML="PASSED";}}exportclassChildAlfa{runTest0215Alfa(_event,element){element.innerHTML="PASSED";}}exportclassChildBravo{runTest0215Bravo(_event,element){element.innerHTML="PASSED";}}exportclassChildCharlie{runTest0215Charlie(_event,element){element.innerHTML="PASSED";}}exportclassChildDelta{runTest0215Delta(_event,element){element.innerHTML="PASSED";}}
// Ensure the `data-send` value of the
// event generating element doesn't
// get changed
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{
#counterAlfa = 0;
#counterBravo = 0;
#counterCharlie = 0;
#counterDelta = 0;
asyncbittyInit(){awaitsleep(100)// time pad for test
constbutton=this.api.querySelector("button");button.click();button.click();button.click();button.click();}runTest0220(event,element){this.#counterAlfa+=1;event.target.dataset.counter=`${this.#counterAlfa}`;if(event.target.dataset.counter=="4"){element.innerHTML="PASSED";}this.api.forward(event,"secondSignal0220");}secondSignal0220(event,element){this.#counterBravo+=1;event.target.dataset.counter=`${this.#counterBravo}`;if(event.target.dataset.counter=="4"){element.innerHTML="PASSED";}this.api.forward(event,"thirdSignal0220");}thirdSignal0220(event,element){this.#counterCharlie+=1;event.target.dataset.counter=`${this.#counterCharlie}`;if(event.target.dataset.counter=="4"){element.innerHTML="PASSED";}this.api.forward(event,"fourthSignal0220");}fourthSignal0220(event,element){this.#counterDelta+=1;event.target.dataset.counter=`${this.#counterDelta}`;if(event.target.dataset.counter=="4"){element.innerHTML="PASSED";}}//}
functionsleep(ms){returnnewPromise((resolve)=>setTimeout(resolve,ms));}exportdefaultclass{asyncbittyInit(){awaitsleep(100)// time pad for test
constbutton=this.api.querySelector("button");button.click();}runTest0230(event,element){element.innerHTML="PASSED";this.api.forward(event,"secondSignal0230|thirdSignal0230|fourthSignal0230");}secondSignal0230(_event,element){element.innerHTML="PASSED";}thirdSignal0230(_event,element){element.innerHTML="PASSED";}fourthSignal0230(_event,element){element.innerHTML="PASSED";}}
Change Event Listeners
Manual Test By Mousing Over
Test Code ID: 0240-change-event-listeners
<bitty-jsdata-connect="/tests/[@ folder.name @]/test.js"data-listeners="mouseover|mouseout"><buttondata-send="runTest0240">Test Target For Mouseover</button><divdata-receive="runTest0240">Manual Test By Mousing Over</div></bitty-js>
- Test receivers are properly removed
when their elements are removed from the tree
Test Code ID: 9000-todo
<pre>
- Test receivers are properly removed
when their elements are removed from the tree
</pre>
// no code for test todo stub
License
MIT License
Copyright (c) 2025 Alan Smith - https://bitty-js.alanwsmith.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice, this permission notice, and the
ID "2y1pBoEREr3eWA1ubCCOXdmRCdn" shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Feedback Requested
bitty-js is currently bitty-1.0.0-rc3.min.js.
The API is stable. Aside from bug fixes, it will
become version 1.0. Play around with it.
Let me know if you find bugs or show-stoppers.
Happy Coding
I put a lot of thought and effort into bitty-js. All
of it aimed at making things simpler. I'm really proud of where it ended up.
I hope it helps you enjoy making things for the web.