Before you begin, it’s important to understand the main elements you’ll be working with in the framework.
self): The first argument of the factory function, conventionally named self. This object is the actual component instance, analogous to this in class-based systems. All your data and methods will be attached to it..ori property..ori property.Note: Component tags must start with x-, e.g., <x-users></x-users>.
Note: Components must be registered with the framework using the $ function. You can register a single component, $(MyComponent), or an array of components, $([MyComponent, MySubComponent]).
Note: New component instances are created using the same $ function. If a component has been registered, calling $(MyComponent) will return a new instance of it.
Note: Using template literals (backticks `) for component templates is often more convenient than using <template> tags. IDEs like WebBolt and editors like VS Code (with the lit-html or similar extensions) provide excellent syntax highlighting for HTML inside these strings.
Note: All element queries are handled internally by querySelector(), ensuring full support for CSS selectors.
<template> TagsThis example demonstrates a basic component with a property (counter) and a method (click) that modifies it.
<body>
<x-first-component>
<button x-click="click">Click</button>
<div>Number of clicks </div>
</x-first-component>
</body>
<script type="text/javascript">
function FirstComponent(self) {
self.counter = 0;
self.click = () => {
self.counter++;
}
}
$(FirstComponent);
</script>
The component instance is the self object passed as the first argument to your factory function. After defining the component, you must register it using $(FirstComponent).
Components can be easily nested within each other. Each component manages its own scope and events.
<x-outer-component>
<button x-click="click">Click outer</button>
<x-inner-component>
<button x-click="click">Click inner</button>
</x-inner-component>
</x-outer-component>
<script type="text/javascript">
function OuterComponent(self) {
self.click = () => {
alert("outer");
}
}
function InnerComponent(self) {
self.click = () => {
alert("inner");
}
}
$([OuterComponent, InnerComponent]);
</script>
To create a new instance of a registered component, use the same $ function you used for registration. You can then append the new component to the DOM.
<body>
<x-add-component>
<div x-click="add">Add new component</div>
</x-add-component>
</body>
<script type="text/javascript">
function AddComponent(self) {
self.add = () => {
let item = $(Item);
self.append(item);
}
}
function Item(self) {
self.time = new Date().toLocaleString();
self.template = `<div>Added at: </div>`;
}
$([AddComponent, Item]);
</script>
<template> TagsAs an alternative to template literals, you can define a component’s HTML structure using a <template> tag. The x-for attribute associates the template with the specified component.
<body>
<x-add-component>
<button x-click="add">Add new component</button>
</x-add-component>
<template x-for="Item">
<div>Time: </div>
</template>
</body>
<script type="text/javascript">
function AddComponent(self) {
self.add = () => {
let item = $(Item);
self.append(item);
}
}
function Item(self) {
self.time = new Date().toLocaleString();
}
$([AddComponent, Item]);
</script>
You can pass arguments when creating a new component instance. These arguments are passed to your factory function, following the initial self object.
<script type="text/javascript">
function AddComponent(self) {
self.add = () => {
// 'My new item' is passed as the 'text' argument to the Item constructor
let item = $(Item, 'My new item');
self.append(item);
}
}
function Item(self, text) {
self.text = text;
self.template = `<div></div>`;
}
$([AddComponent, Item]);
</script>
The first argument to $(...) is always the component to create. Subsequent arguments ('My new item') are passed as the second, third, etc., arguments to the constructor function (since self occupies the first position).
The framework provides a simple event system for communication. Use self.emit() to send a custom event and self.on() to listen for it. This allows unrelated components to interact.
<x-sender-component>
<button x-click="send">Send message</button>
</x-sender-component>
<x-receiver-component>
<div>Received messages:</div>
</x-receiver-component>
<script type="text/javascript">
function SenderComponent(self) {
self.send = () => {
self.emit("new-msg", "Hello World!");
}
}
function ReceiverComponent(self) {
self.on("new-msg", (msg) => {
self.appendHtml("<div>" + msg + "</div>");
});
}
$([SenderComponent, ReceiverComponent]);
</script>
WatchList for Dynamic ListsThe WatchList is a reactive object for managing dynamic lists of components. When you add or remove items from the list, the DOM is automatically updated. This eliminates the need for manual DOM manipulation.
<x-users-form>
<input type="text" placeholder="Username" x-enter-pressed="add" value=""/>
<button x-click="add">Add</button>
<div x-list="items"></div>
</x-users-form>
<script type="text/javascript">
function UsernameComponent(self, username) {
self.username = username;
self.template = `<div class="user-item"></div>`;
}
function UsersForm(self) {
self.username = "";
self.items = new WatchList([$(UsernameComponent, "John")]);
self.add = () => {
if (!self.username) {
alert("Username is empty.");
return;
}
self.items.add($(UsernameComponent, self.username));
self.username = "";
}
}
$([UsersForm, UsernameComponent]);
</script>
You can initialize a component’s data directly from an HTML attribute using x-init. This is useful for server-side rendering (SSR), allowing search engines to index content that will later become dynamic.
<x-init-component x-init='{"id": 4, "username": "John"}'>
<button x-click="alert">Show user data</button>
</x-init-component>
<script type="text/javascript">
function InitComponent(self) {
// self.id and self.username are automatically populated from x-init
self.alert = () => {
alert(`ID: ${self.id}, Username: ${self.username}`);
}
}
$([InitComponent]);
</script>
This example combines several concepts to build a classic TODO list application, demonstrating state management, event communication, and dynamic lists.
<x-todo-form>
<input type="text" placeholder="Your todo" value="" x-enter-pressed="add"/>
<button x-click="add">Add</button>
</x-todo-form>
<x-todo-list>
<div>Tasks todo: of 8</div>
<div x-list="items"></div>
</x-todo-list>
<script type="text/javascript">
function TodoItem(self, name) {
self.name = name;
self.done = false;
self.delete = () => self.emit('del-todo', self);
self.changed = () => {
self.done = !self.done;
self.emit('task-changed');
};
self.template = `
<div class="todo-item">
<input type="checkbox" x-change="changed" />
<div x-class="done:done"></div>
<div x-click="delete" class="del">delete</div>
</div>`;
}
function TodoForm(self) {
self.todo = "";
self.add = () => {
if (!self.todo) {
alert("TODO can not be empty.");
return;
}
self.emit("new-todo", self.todo);
self.todo = "";
}
}
function TodoList(self) {
self.items = new WatchList([$(TodoItem, "My first TODO")]);
self.left = 1;
self.count = 1;
self.countTasks = () => {
self.left = self.items.count(x => x.done === false);
self.count = self.items.count();
};
self.on('new-todo', todo => {
self.items.add($(TodoItem, todo));
self.countTasks();
});
self.on('del-todo', todo => {
self.items.delete(x => x === todo);
self.countTasks();
});
self.on('task-changed', self.countTasks);
}
$([TodoForm, TodoList, TodoItem]);
</script>
You can show a loading message while waiting for asynchronous data. The component’s main content is defined in a <template> tag, which you can render by calling self.showTemplate() once the data is available.
<x-user-data>
<div>Loading user data...</div>
<template>
Hi, !
</template>
</x-user-data>
<script type="text/javascript">
function UserData(self) {
// This function is called automatically on component initialization
self.init = () => {
setTimeout(() => self.onAjaxSuccess(), 1000); // Simulate network request
}
self.onAjaxSuccess = () => {
self.username = "John";
self.showTemplate(); // Replace loading message with the template
}
}
$([UserData]);
</script>
You can fetch HTML containing new components from a server and inject it into the DOM. The framework will automatically initialize any components found in the new markup.
<x-main-component>
<div>Downloading data...</div>
<template>
<div id="items"></div>
</template>
</x-main-component>
<script type="text/javascript">
function MainComponent(self) {
self.init = () => {
setTimeout(() => self.onAjaxSuccess(), 1000); // Simulate AJAX call
};
self.onAjaxSuccess = () => {
self.showTemplate();
const html = `
<div>
<x-item x-init='{"id": "First"}'><button x-click="click">Show ID</button></x-item>
<x-item x-init='{"id": "Second"}'><button x-click="click">Show ID</button></x-item>
</div>`;
self.find("#items").appendHtml(html);
}
}
function Item(self) {
self.click = () => {
alert(self.id);
}
}
$([MainComponent, Item]);
</script>
Use the self.find(selector) method to get a BoltElement wrapper for an element within your component’s scope. This allows you to attach event listeners or manipulate the element directly.
<x-example-component>
<button class="click-me">Click</button>
<div id="clicks"></div>
</x-example-component>
<script type="text/javascript">
function ExampleComponent(self) {
self.clickCount = 1;
self.find(".click-me").on("click", function() {
let time = new Date().toLocaleTimeString();
let html = `<div>Click no. ${self.clickCount} at ${time}</div>`;
self.find('#clicks').appendHtml(html);
self.clickCount++;
});
}
$(ExampleComponent);
</script>
The `` directive provides two-way data binding for form inputs. The self.find('form').formData() method simplifies collecting form data for submission.
<x-form-component>
<form>
First Name: <input type="text" value="" /> (Bound value: )
<br/>
Email: <input type="email" value=""/> (Bound value: )
<br/>
<button x-click="print">Print FormData</button>
</form>
<div id="form-result"></div>
</x-form-component>
<script type="text/javascript">
function FormComponent(self) {
self.name = self.email = self.password = self.about = "";
self.print = (e) => {
e.preventDefault();
const formResult = self.find('#form-result');
formResult.setHtml('');
const formData = self.find('form').formData();
for (let [key, value] of formData) {
formResult.appendHtml(`<div>${key}: ${value}</div>`);
}
}
}
$(FormComponent);
</script>
This example shows how to create a reusable modal component that can be dynamically created and appended to the page to confirm an action.
<x-delete-button x-init='{
"msg": "Are you sure? Type in the name of the account to delete.",
"account": "John"
}'>
<a href="/delete-user/32" class="delete-button">Delete User 'John'</a>
</x-delete-button>
<script type="text/javascript">
function DeleteModal(self, href,msg, confirmation) {
self.confirmation = null;
self.account = null;
self.msg = msg;
self.close = () => {
self.remove();
}
self.delete = () => {
if (self.confirmation === confirmation) {
alert("Successfully deleted!");
self.remove();
} else {
self.confirmation = "";
}
}
self.template = `
<div class="overlay" >
<div class="modal">
<div class="msg"></div>
<input type="text" value="" x-enter-pressed="delete" />
<div class="form-buttons">
<div class="form-submit cancel" x-click="close">Close</div>
<div class="form-submit delete" x-click="delete">Delete</div>
</div>
</div>
</div>`
}
function DeleteButton(self) {
const link = self.find('a');
link.on('click', (e) => {
e.preventDefault();
let href = link.getAttribute('href');
self.append($(DeleteModal, href, self.msg, self.account))
});
}
$([DeleteButton, DeleteModal]);
</script>
Directives are special HTML attributes that provide declarative reactivity, allowing you to manipulate the DOM based on your component’s state. The most common directives are:
x-if="expression": Conditionally renders an element. The element is added or removed from the DOM based on whether the expression is truthy.x-class="expression: 'className'": Toggles CSS classes on an element.x-enter-pressed="methodName": Calls a component method when the “Enter” key is pressed inside an input field.x-checked="propertyName": Provides two-way data binding for checkbox and radio inputs.A key feature of directives like x-if and x-class is their ability to evaluate JavaScript-like expressions. This allows you to create dynamic and responsive components based on your component’s state. You are not limited to simple property names; you can use comparisons, logical operators, and property access to control your template’s structure and styling.
For example, you can use expressions like items.length > 0 or user.role == 'admin' && user.active. This same powerful syntax works for both conditionally rendering elements with x-if and for applying classes with x-class.
The example below demonstrates how to use x-if, x-class, and x-checked to dynamically change styles and content.
<style>
.checked-style { background-color: lightblue; padding: 5px; margin-bottom: 10px; }
.radio-checked-style { background-color: lightcoral; padding: 5px; }
</style>
<x-directives-component>
<div x-class="isCheckboxChecked: checked-style">
Toggle background:
<input type="checkbox" x-checked="isCheckboxChecked" />
<span x-if="isCheckboxChecked">Background is now lightblue!</span>
</div>
<div x-class="radioValue == 'on': radio-checked-style">
Turn radio background on/off:
<input type="radio" name="mode" value="on" x-checked="radioValue" /> on
<input type="radio" name="mode" value="off" x-checked="radioValue" /> off
<span x-if="radioValue == 'on'" style="margin-left:5px">Radio background is ON.</span>
</div>
</x-directives-component>
</div>
<script type="text/javascript">
function DirectivesComponent(self) {
self.isCheckboxChecked = true;
self.radioValue = 'on'
}
$(DirectivesComponent);
</script>