> ## 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.

# Sprincul Model Lifecycle: beforeInit and afterInit Hooks

> Sprincul calls beforeInit() before bindings attach and afterInit() after they're ready. Use these hooks to set up state, fetch data, and run async work.

Every Sprincul model goes through a predictable initialization sequence when `Sprincul.init()` runs. Understanding this sequence tells you exactly when your state, bindings, and async operations are active — and which hook is the right place for each kind of work.

## The initialization flow

When you call `Sprincul.init()`, Sprincul queries the DOM for every element with a `data-model` attribute and processes each one in order:

<Steps>
  <Step title="Sprincul.init() scans the DOM">
    Sprincul finds all `[data-model]` elements and looks up the registered class for each one.
  </Step>

  <Step title="Model instance is created">
    Sprincul calls `new YourModel(element)`, which sets up `this.$el` and the reactive `this.state` proxy.
  </Step>

  <Step title="beforeInit() is called">
    Sprincul calls `beforeInit()` synchronously. This is where you initialize state values and register computed properties — before any bindings exist.
  </Step>

  <Step title="Bindings and event listeners are attached">
    Sprincul reads every `data-bind-*` and `on<event>` attribute in the container and wires them to your model's methods. Each binding callback is called once immediately with the current state.
  </Step>

  <Step title="afterInit() is called">
    Sprincul calls `afterInit()`. Bindings are already active at this point, so any state changes here will trigger DOM updates. `afterInit()` can be `async`.
  </Step>

  <Step title="Ready events fire">
    After all models' `afterInit` hooks are called (not necessarily awaited to completion), Sprincul fires `onReady` callbacks and dispatches the `sprincul:ready` DOM event.
  </Step>
</Steps>

## `beforeInit()`

`beforeInit()` runs synchronously before Sprincul attaches any bindings to the DOM. Use it to:

* Set initial values on `this.state`
* Register computed properties with `this.addComputedProp()`
* Run any synchronous setup that must complete before bindings are active

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

export default class Counter extends SprinculModel {
  beforeInit() {
    this.state.count = 0;
    this.addComputedProp(
      'doubled',
      () => this.state.count * 2,
      ['count']
    );
  }
}
```

<Note>
  `beforeInit()` is called synchronously, but it can return a `Promise`. If it does, Sprincul will not await it — bindings are set up immediately after `beforeInit()` returns. Keep synchronous work in `beforeInit()` and defer async work to `afterInit()`.
</Note>

## `afterInit()`

`afterInit()` runs after bindings are fully active. Use it to:

* Fetch data from an API and update `this.state`
* Connect to an external store or pub/sub system
* Start timers or observers that rely on the DOM being ready

`afterInit()` can be `async`. Sprincul does not block the rest of initialization while it completes — it calls `afterInit()`, captures the returned promise, and moves on to the next model. When the promise settles, Sprincul removes `data-cloaked` from the model's root element if present.

```js theme={null}
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 fetch('/api/profile').then(r => r.json());
    this.state.name = name;
    this.state.email = email;
  }
}
```

## `Sprincul.onReady(callback)`

Register a callback to be notified when all models have been initialized. The callback receives an array of `SprinculModelInfo` objects, one per model instance Sprincul processed.

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

Sprincul.register('Counter', Counter);

Sprincul.onReady((models) => {
  console.log(`Sprincul initialized ${models.length} models`);
  models.forEach(({ name, element }) => {
    console.log(`Model "${name}" on element:`, element);
  });
});

Sprincul.init();
```

<Warning>
  You must register `onReady` callbacks **before** calling `Sprincul.init()`. Callbacks registered after `init()` will not be invoked for that initialization cycle. `onReady` callbacks are one-shot — they fire once per `init()` call and are then cleared.
</Warning>

## The `sprincul:ready` DOM event

As an alternative to `onReady`, you can listen for the `sprincul:ready` `CustomEvent` on the `document`. It fires at the same time as `onReady` callbacks and carries the same model information in `event.detail.models`.

```js theme={null}
document.addEventListener('sprincul:ready', ({ detail }) => {
  const { models } = detail;
  console.log(`Initialized ${models.length} models`);
  models.forEach(({ name, element }) => {
    console.log(`Model "${name}" on element:`, element);
  });
});

Sprincul.init();
```

Both approaches give you access to the same data. Use whichever fits your codebase — they are equivalent.

## Development mode

Pass `{ devMode: true }` to `Sprincul.init()` to enable additional diagnostics:

* Console warnings when bindings reference callbacks that don't exist on the model.
* Console warnings when event attributes reference methods that don't exist on the model.
* The `instance` property on each `SprinculModelInfo` object, giving you direct access to the model instance in `onReady` callbacks and the `sprincul:ready` event.

```js theme={null}
Sprincul.onReady((models) => {
  models.forEach(({ name, element, instance }) => {
    // instance is the live model object — useful for debugging in the console
    console.log(name, instance);
  });
});

Sprincul.init({ devMode: true });
```

<Warning>
  **Security note:** Model instances are excluded from ready events in production (when `devMode` is `false`). This prevents internal state from being accessible through the browser console. Only enable `devMode` during local development.
</Warning>

## Cloaking elements during initialization

Sprincul supports a `data-cloaked` attribute that hides elements until they are ready. You provide the CSS rule; Sprincul removes the attribute at the right time.

```html theme={null}
<style>[data-cloaked] { display: none; }</style>

<!-- Model-level: uncloaks after this model's afterInit() completes -->
<div data-model="Profile" data-cloaked>…</div>

<!-- Page-level: uncloaks after all models' afterInit() hooks are called -->
<body data-cloaked>
  <div data-model="Profile">…</div>
  <div data-model="Settings">…</div>
</body>
```

Model-level cloaking waits for `afterInit()` to fully resolve, making it safe for models that fetch data before rendering. Page-level cloaking removes as soon as all `afterInit()` calls are dispatched — not when they complete — so it is better suited for showing a loading state than for waiting on async work.
