hullabaloo
v1.0.0
Published
A Luxury Programming Framework
Readme
Hullabaloo
A Programming Framework for Young Coders
Hullabaloo is a way to build web apps and command-line programs by describing what you want, instead of writing hundreds of lines of instructions.
You tell Hullabaloo what your program has — signals, buttons, regions on the page, data pipelines — and it takes care of wiring everything together. You focus on the ideas. Hullabaloo handles the rest.
this.declare({
signals: { name: 'Ada' },
regions: { greeting: '#greetingBox' },
content: { name: 'greeting' },
});That is all it takes. The #greetingBox element on your page now shows "Ada", and updates
automatically whenever this.name changes — no extra code required.
The big idea
Most programming frameworks make you write a lot of plumbing code — event listeners, state management, manual DOM updates, cleanup functions. Hullabaloo hides all of that.
You write a plugin — a class that describes itself with this.declare({...}).
Inside that object you list the features you want. Each feature is one key in the object.
Hullabaloo reads the list and sets everything up for you.
import { Hullabaloo } from 'hullabaloo';
export default class MyPlugin extends Hullabaloo {
constructor(options = {}, app) {
super({ app });
this.declare({
objects: { app, options }, // attach things to this
signals: { loaded: false, started: false }, // values that can change
capture: { started: true, loaded: true }, // listen for events → save to signals
combine: { ready: { loaded: true, started: true } }, // combine signals → new signal
regions: { hello: '#helloRegion' }, // connect to HTML elements
buttons: { 'button#hello': 'say:hello' }, // wire clicks to events
emitter: { say: { hello: () => this.hello() } }, // define what events do
binding: { '#emailInput': 'email' }, // two-way input ↔ signal sync
content: { email: 'hello' }, // signal value → region text
filters: [ ['email:changed', 'validate', 'email:valid'] ], // data pipelines
persist: { localStorage: { email: { load: true, tabs: true } } }, // remember values
});
}
hello() {
this.region.hello.textContent = 'Hello!';
}
async validate(packet) {
return packet.includes('@') ? packet : null; // null drops the packet
}
async start() {
if (this.started) return this;
this.emit('loaded', true);
this.emit('started', true);
return this;
}
async stop() {
if (!this.started && !this.loaded) return this;
this.emit('started', false);
this.emit('loaded', false);
return this;
}
}Features
Every key in this.declare({...}) is a feature. Here is what each one does:
| Feature | What it gives you | Read more |
|---|---|---|
| objects | Attach things to your plugin so you can reach them with this. | → |
| signals | Values that your plugin watches and reacts to automatically | → |
| capture | Listen for events and save what arrived into a signal | → |
| combine | Create a signal that is only truthy when several others all match | → |
| regions | Connect your plugin to HTML elements on the page | → |
| buttons | Wire button clicks to events in your plugin | → |
| emitter | Define what your plugin does when events are fired | → |
| binding | Two-way sync between an input field and a signal | → |
| content | Automatically put a signal's value into a region on the page | → |
| filters | Describe data pipelines: where data comes from, how it transforms, where it goes | → |
| persist | Remember signal values in the browser so they survive a page reload | → |
The Application
Plugins live inside an Application. The Application starts them in order, stops them in reverse order, and cleans everything up automatically.
import { Application } from 'hullabaloo';
import Logger from './plug-ins/system/logger/Logger.js';
import MyPlugin from './plug-ins/examples/my-plugin/MyPlugin.js';
const app = new Application();
app.use(Logger, { outputs: [ConsoleOutput({ level: 'info' })] });
app.use(MyPlugin, { greeting: 'Hello!' });
await app.start();
// ... your program runs ...
await app.stop();The Application itself is also a Hullabaloo — it has the same signals, declare,
when, and unless methods.
Signals up close
A Signal is a value that tells everyone watching it when it changes.
import { Signal } from 'hullabaloo';
const count = new Signal(0);
count.subscribe(v => console.log('count is now', v)); // fires immediately: "count is now 0"
count.value = 1; // fires again: "count is now 1"
count.value = 1; // nothing — same value, no notificationInside a plugin, signals declared with signals: get a convenient shorthand:
// After signals: { loaded: false } ...
this.loaded // reads the current value (false)
this.loaded = true // changes the value and notifies everyone
this.signals.loaded // the Signal itself, for subscribingautoSignal — signals on demand
Pass autoSignal: true to super() to make this.signals smart.
Any signal name you access that does not exist yet will be created automatically as Signal(null).
super({ app, autoSignal: true });This solves a timing problem: normally this.signals.myName inside a declare({}) argument
is evaluated before any features run, so it would be undefined. With autoSignal: true it
is always a live Signal, ready to use as a source or destination in filters:.
filters — data pipelines
The filters feature lets you describe how data flows through your plugin step by step.
Each pipeline is one array inside the filters: list.
filters: [
// [ source, ...transforms, destination ]
['user:typed', 'sanitize', 'display:text'],
// ↑ event ↑ method ↑ event to fire
[this.signals.email, p => p.toUpperCase(), this.signals.upperEmail],
// ↑ Signal source ↑ arrow function ↑ Signal destination
['data:in', 'fetchFromServer', 'log:result'],
// transforms can be async — await is handled for you
]To use Filters.fromEvent(emitter, 'event') for external emitters, import it:
import { filters } from 'hullabaloo';
filters: [
[ filters.fromEvent(someOtherThing, 'click'), 'handleClick', 'ui:updated' ],
]Exports
import {
Application, // orchestrates plugins
Hullabaloo, // base class for plugins
Signal, // observable value
Disposable, // lifecycle / cleanup helper
Events, // event emitter base class
Filters, // filter pipeline helper (also exported as 'filters')
filters, // same as Filters — lowercase for readability
} from 'hullabaloo';Example plugins
| File | What it shows |
|---|---|
| plug-ins/examples/my-plugin/MyPlugin.js | Basic plugin with signals, capture, combine |
| plug-ins/examples/my-region/MyRegion.js | Full example: regions, buttons, binding, content, filters, persist |
| HelloCommandLine.js | Running an app from the terminal |
| HelloWebApp.js | Running an app in the browser |
Philosophy
Programming should feel like building with blocks — snap things together, see what happens, break it, try again, learn by doing.
Hullabaloo is designed to get beginners to interesting programs as fast as possible. The hard parts (memory cleanup, event wiring, state synchronisation, cross-tab communication) are handled automatically. What is left for you is the fun part: describing what your program is and what it does.
Every error message in this framework is written to teach, not to scold. If something goes wrong, Hullabaloo will show you exactly what to fix and how.
"We are the HyperCard to the youngest generation of young programmers. We need to help them program and understand — and set them free to innovate."
