Intro to Components
Components are the building blocks of WildflowerJS applications. They encapsulate state, behavior, and templates into reusable, self-contained units declared entirely in HTML.
Defining a Component
Components are defined using wildflower.component() and declared in HTML with data-component:
<div data-component="user-card">
<h3 data-bind="displayName"></h3>
<p data-bind="user.email"></p>
<button class="btn btn-primary" data-action="updateUser">Update User</button>
</div>
wildflower.component('user-card', {
state: {
user: { name: 'John Doe', email: 'john@example.com' },
isLoading: false
},
computed: {
displayName() {
return this.user.name || 'Anonymous'
}
},
updateUser() {
const names = ['Alice Smith', 'Bob Johnson', 'Carol Williams', 'David Brown']
const emails = ['alice@example.com', 'bob@example.com', 'carol@example.com', 'david@example.com']
const index = Math.floor(Math.random() * names.length)
this.user = {
name: names[index],
email: emails[index]
}
},
init() {
console.log('User card initialized:', this.id)
}
})
Replacing a Definition
Registration is first-write-wins. Registering a name that already exists with a different definition keeps the original and ignores the new one, and development builds name the collision (WF-215), since it is almost always an accident: two components sharing a name, or a hot reload without teardown. Registering the identical definition again is silent, so re-running a setup script is safe.
To replace a definition, unregister the name first:
wildflower.unregister('task-card'); // removes the component and/or store by that name
wildflower.component('task-card', { /* the new definition */ });
unregister(name) is the unified entry: it removes a component, a store, or both if the name exists in both registries, and returns true when something was removed. The use cases are dynamic apps that swap definitions at runtime, hot-reload setups, and tests that re-register between cases.
Component Definition
A component definition can include any of these properties:
| Property | Type | Description |
|---|---|---|
state |
Object | Reactive data properties. See State Management |
computed |
Object | Derived values from state. See Computed Properties |
props |
Object | Parent-to-child data passing. See Props |
watch |
Object | React to specific state or store changes. See Communication |
subscribe |
Object/Array | Declare store dependencies. See Communication |
init(), destroy(), … |
Functions | Lifecycle hooks. See Lifecycle Hooks |
beforeInit() |
Function | Runs before bindings are processed. See Lifecycle Hooks |
beforeUpdate() |
Function | Runs before DOM updates. See Lifecycle Hooks |
onError() |
Function | Error handler for the component. See Error Boundaries |
onPropsChange() |
Function | Runs when received props change. See Props |
| Action methods | Functions | Event handlers referenced by data-action. See Event Handling |
Component Instance Properties
Every component instance has access to these properties via this:
| Property | Type | Description |
|---|---|---|
this.id |
String | Unique component identifier |
this.element |
HTMLElement | Component's root DOM element |
this.state |
Object | Reactive state container |
this.props |
Object | Read-only props passed from parent |
this.computed |
Object | Computed property values |
this.stores |
Object | Subscribed store instances (when using subscribe) |
this.getStore() |
Function | Fetch any store by name, including ones you never declared in subscribe: this.getStore('cart'). Defaults to 'app-store'. See Stores |
this.store() |
Function | Read or write store state by path. this.store('total') reads from the default app-store, this.store('cart', 'total') reads from a named store, and a trailing value writes instead of reading |
this.external() |
Function | Read or write another component's or store's state: this.external('cart', 'total'). A single argument returns that entity's context |
this.subscribe() |
Function | Programmatic state subscription |
this.emit() |
Function | Child-to-parent communication. See Communication |
this.$el() |
Function | jQuery-like DOM helper. See DOM Helpers |
this.find() |
Function | Query selector within component: this.find('.btn') |
this.findAll() |
Function | Query selector all within component |
this.closest() |
Function | Find closest ancestor matching selector |
this.rebindActions() |
Function | Rebind data-action handlers after you replace markup yourself. Already-bound elements are skipped, so calling it repeatedly is safe. See Third-Party Libraries |
this.update() |
Function | Batch state updates: this.update({ count: 1, name: 'x' }) |
this.pools |
Object | Declared pool handles, as in this.pools.enemies.add({...}). See Entity Pools |
this.getPool() |
Function | Fetch any pool by name with this.getPool('enemies'), including markup-only or imperative pools |
this.parent |
Object | Parent component's context (or null) |
this.listItem |
Object | List item data when inside a data-list (or null) |
this.getItemFromEvent() |
Function | Given a DOM event, return the list row or pool entity that encloses its target as { element, index, id, item }, or null. Pool entities report no index, since removal reshuffles storage. See Pool API |
this.isReady() |
Function | Whether the entity's state has finished loading. See Advanced Stores |
this.waitForReady() |
Function | Promise that resolves once state is ready, for awaiting an async store in init() |
this.saveToStorage() |
Function | Write state to storage now. Does nothing unless storageKey is set. See Advanced Stores |
this.loadFromStorage() |
Function | Reload state from storage, discarding what is in memory. Also requires storageKey |
Component Composition
Components nest naturally in HTML. Each manages its own state independently:
<div data-component="dashboard">
<header data-component="app-header"></header>
<main>
<aside data-component="sidebar-nav"></aside>
<section data-component="main-content"></section>
</main>
<footer data-component="app-footer"></footer>
</div>
Dynamic Component Creation
Components can be created programmatically and added to the DOM at runtime:
const element = document.createElement('div')
element.setAttribute('data-component', 'notification-toast')
document.getElementById('toasts').appendChild(element)
// Tell the framework to initialize the new component
wildflower.scan(element)