> ## Documentation Index
> Fetch the complete documentation index at: https://sprincul.sinfullycoded.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Data Bindings: Sync Reactive State to DOM Elements

> Use data-bind-* attributes to declare which DOM elements update when a state property changes. Sprincul calls your callback method on every change.

Data bindings are the mechanism Sprincul uses to keep your DOM in sync with your model's state. Instead of writing code that manually reaches into the DOM after every state change, you declare the relationship once in HTML — and Sprincul takes care of calling the right method whenever the state changes.

## The `data-bind-<prop>` attribute

You add a `data-bind-<prop>` attribute to any element inside a `data-model` container. The attribute name encodes the state property to watch, and the attribute value is the name of the callback method on your model that Sprincul should call when that property changes.

```html theme={null}
<div data-model="Counter">
  <input type="number" data-bind-count="showCount" readonly />
</div>
```

```js theme={null}
import { SprinculModel } from 'sprincul';

export default class Counter extends SprinculModel {
  beforeInit() {
    this.state.count = 0;
  }

  /** @param {HTMLInputElement} el */
  showCount(el) {
    el.value = this.state.count;
  }
}
```

When `this.state.count` changes, Sprincul calls `showCount(element)`, passing the bound element as the first argument. Your callback reads from `this.state` and updates the element however it needs to — setting `textContent`, toggling a class, updating an attribute, anything.

Sprincul also calls each binding callback once immediately when bindings are first set up, so the element reflects the initial state without you having to trigger a change manually.

## How the binding pattern works

<Steps>
  <Step title="Parse the attribute">
    During `Sprincul.init()`, Sprincul reads every `data-bind-*` attribute inside the model's container. The part after `data-bind-` is the state property name; the attribute value is the callback method name.
  </Step>

  <Step title="Call the callback immediately">
    Sprincul calls the callback right away with the bound element, so the element reflects the current (initial) state.
  </Step>

  <Step title="Watch for state changes">
    Whenever `this.state.<prop>` is assigned a new value, Sprincul schedules a DOM update via `requestAnimationFrame`. On the next frame, it calls the callback again with the same element.
  </Step>
</Steps>

Your callback always receives the bound DOM element as its first argument and has access to the full model state through `this.state`.

## Lowercase state property names

<Warning>
  State property names used in `data-bind-*` attributes must be **lowercase**. Browsers lowercase all HTML attribute names when parsing the DOM, so `data-bind-btnText` is stored as `data-bind-btntext`. Sprincul reads the lowercased form, so a state property named `btnText` will never match a binding. Use `this.state.btntext` (all lowercase) to ensure bindings fire correctly.
</Warning>

## A full example: Profile

The following `Profile` model and HTML show how bindings connect two state properties to two separate elements. Sprincul calls `showName` and `showEmail` whenever the corresponding state property changes.

```html theme={null}
<section data-model="Profile">
  <p data-bind-name="showName"></p>
  <p data-bind-email="showEmail"></p>
</section>
```

```js theme={null}
// Profile.js
import { SprinculModel } from 'sprincul';

export default class Profile extends SprinculModel {
  beforeInit() {
    this.state.name = '--';
    this.state.email = '--';
  }

  /** @param {HTMLElement} el */
  showName(el) {
    el.textContent = this.state.name;
  }

  /** @param {HTMLElement} el */
  showEmail(el) {
    el.textContent = this.state.email;
  }

  async afterInit() {
    const { name, email } = await this.fakeFetch();
    this.state.name = name;
    this.state.email = email;
  }

  async fakeFetch() {
    return new Promise((resolve) => {
      setTimeout(() => resolve({ name: 'Jane Doe', email: 'jane@email.com' }), 2000);
    });
  }
}
```

## Counter bindings

The `Counter` model uses the same pattern with a number input. Multiple elements can bind to the same state property — Sprincul calls each callback independently.

```html theme={null}
<div data-model="Counter">
  <button onclick="decrement">-</button>
  <input type="number" data-bind-count="showCount" readonly />
  <button onclick="increment">+</button>
  <button type="reset" onclick="resetCounter">reset</button>
</div>
```

```js theme={null}
// Counter.js
import { SprinculModel } from 'sprincul';

export default class Counter extends SprinculModel {
  beforeInit() {
    this.state.count = 0;
  }

  increment() {
    this.state.count++;
  }

  decrement() {
    this.state.count--;
  }

  /** @param {HTMLInputElement} el */
  showCount(el) {
    el.value = this.state.count;
  }

  resetCounter() {
    this.state.count = 0;
  }
}
```

## Multiple bindings for the same property

You can put `data-bind-<prop>` on as many elements as you need. When the state property changes, Sprincul calls the associated callback for each bound element. The callbacks can be different methods, or the same method — whatever makes sense for each element.

```html theme={null}
<div data-model="Counter">
  <!-- Two elements both respond to count changes -->
  <input type="number" data-bind-count="showCount" readonly />
  <p data-bind-count="showCountText"></p>
</div>
```

## Bindings must be inside the model container

Sprincul only processes `data-bind-*` attributes that are direct or nested descendants of the `data-model` container. Elements outside the container are ignored. Nested `data-model` elements are managed by their own model instance and are not included in the parent's bindings.

<Tip>
  If a binding callback is not firing, check that: the element is inside the correct `data-model` container, the callback name matches a method defined on the class, and you are assigning to `this.state.<prop>` rather than a plain property like `this.<prop>`.
</Tip>
