sercrod

*input

Summary

*input binds a form control’s value to a data path. It keeps a DOM control (such as <input>, <textarea>, or <select>) in sync with a field in Sercrod’s data or staged data. The alias n-input behaves identically.

*input is focused on value-level binding and works together with *lazy and *eager to control how often the host re-renders after a change.

Basic example

A simple text field bound to form.name:

<serc-rod id="app" data='{"form":{"name":"Alice"}}'>
  <label>
    Name:
    <input type="text" *input="form.name">
  </label>

  <p>Hello, <span *print="form.name"></span>!</p>
</serc-rod>

Behavior:

Behavior

*input provides a two-way binding between a control and a data expression:

Supported tags:

Other elements with a .value property may technically work, but *input is designed primarily for standard form controls.

Expression syntax

The *input expression is evaluated as a normal Sercrod expression and used as an assignment target:

Assignment semantics:

Best practice:

Evaluation timing

*input participates in two phases:

  1. Initial reflection (data to UI):

    • During render, after Sercrod has prepared the scope, it:
      • Evaluates the *input expression on either staged or main data.
      • Applies the value to the control:
        • For input[type="checkbox"]: sets checked from a boolean or array value.
        • For input[type="radio"]: sets checked based on equality against the bound value.
        • For other inputs and textarea: sets .value.
        • For select: sets the selected option(s).
  2. Event-driven updates (UI to data):

    • Sercrod attaches event listeners:
      • input for text-like controls.
      • change for checkboxes, radios, selects, and others.
    • On those events, it:
      • Normalizes the new value (including optional numeric conversion).
      • Passes the value through input_in filter.
      • Writes the result back via assign_expr.
      • Triggers either a full update or a more focused child update depending on *lazy and *eager, and whether staging is active.

If the host has staged data, updates do not trigger a live host re-render until the staged changes are applied.

Execution model

A simplified execution model for *input:

  1. Resolve binding:

    • Sercrod obtains the expression string from n-input or *input.
    • It chooses a target object:
      • Prefer staged data (this._stage) when present.
      • Otherwise use main data (this._data).
    • It tests evaluation:
      • If evaluating the expression on staged or main data throws a ReferenceError, Sercrod falls back to the current scope object.
      • This allows bindings to work when the path only exists in the scope, not yet in the main data.
  2. Initial data to UI:

    • Evaluate the expression to get curVal.
    • For different controls:
      • INPUT:
        • type="checkbox":
          • If curVal is an array, the checkbox is checked when its value is included in that array (string comparison).
          • Otherwise, the checkbox is checked when curVal is truthy.
        • type="radio":
          • After rendering, a postApply hook re-evaluates the bound expression and sets checked when the bound value equals the control’s value.
        • Other types:
          • curVal is passed through model_out (if defined), then normalized:
            • null, undefined, and false become an empty string.
          • The resulting string is assigned to el.value.
      • TEXTAREA:
        • Similar to text inputs:
          • null and false normalize to an empty string.
          • model_out (if defined) can transform the value before it is written.
      • SELECT:
        • For multiple:
          • Expects curVal as an array of values (strings after normalization).
          • A postApply hook selects all options whose value is contained in that array.
        • For single select:
          • Writes a string value, with null mapped to an empty string.
  3. IME composition handling:

    • For text-like controls (input except checkbox/radio, and textarea), Sercrod tracks IME composition:
      • While composing (between compositionstart and compositionend), input events are ignored.
      • After composition ends, normal input handling resumes.
  4. UI to data (input events):

    • For input (text-like) and textarea:
      • On each input event, if not composing:
        • Read el.value as nextVal.
        • Align numeric types:
          • If type="number", convert to a number when possible, keeping empty string as empty.
          • Otherwise, if the current bound value is a number and the new value is not empty, try to parse a number.
        • Pass nextVal through input_in filter.
        • Assign the result back to the expression via assign_expr.
        • If there is no staged data:
          • If *eager is active, call update() on the host.
          • Otherwise, call _updateChildren(...) to update only the host’s descendants.
  5. UI to data (change events):

    • For change on all relevant controls:
      • Compute nextVal depending on control type:
        • Checkbox:
          • If the bound value is an array, toggle membership of el.value in that array.
          • Otherwise, use el.checked as a boolean.
        • Radio:
          • If el.checked, use el.value.
        • Select:
          • For multiple, use an array of selected option.value.
          • Otherwise, use el.value.
        • Others:
          • Use el.value.
      • If the original bound value is a number (and nextVal is not empty and not an array), try to convert to number.
      • Pass nextVal through input_in and assign to the binding expression.
      • If there is no staged data:
        • If *lazy is active, update only the host’s children.
        • Otherwise, perform a full update() of the host.
  6. Staged data:

    • When the host uses *stage, *input writes into this._stage instead of this._data.
    • Because the update logic checks if (!this._stage), staged inputs do not trigger automatic re-rendering of the live view.
    • Applying or restoring the stage (for example via *apply / *restore) will drive the next visible update.

Variable creation and scope layering

*input does not create new variables in the template scope.

Scope behavior:

Guidelines:

Parent access

*input does not provide its own parent helper, but you can target parent data explicitly:

The binding still obeys the same assignment semantics: the expression is the left-hand side of an assignment inside the current scope.

Use with conditionals and loops

*input can be freely combined with conditional rendering and loops, as long as the rendered control remains a valid form element:

Use with *stage, *lazy and *eager

*input is designed to integrate with staged editing and timing control directives.

Project-level recommendation:

Best practices

Additional examples

Checkbox bound to a boolean:

<serc-rod id="flag" data='{"settings":{"enabled":true}}'>
  <label>
    <input type="checkbox" *input="settings.enabled">
    Enabled
  </label>

  <p>Status: <span *print="settings.enabled ? 'ON' : 'OFF'"></span></p>
</serc-rod>

Checkbox group bound to an array:

<serc-rod id="colors" data='{"colors":["red","green","blue"],"selected":["red"]}'>
  <div *for="color of colors">
    <label>
      <input type="checkbox" *input="selected" :value="color">
      <span *print="color"></span>
    </label>
  </div>

  <p>Selected: <span *print="selected.join(', ')"></span></p>
</serc-rod>

Radio group bound to a single value:

<serc-rod id="plan" data='{"plan":"basic"}'>
  <label>
    <input type="radio" name="plan" *input="plan" value="basic">
    Basic
  </label>
  <label>
    <input type="radio" name="plan" *input="plan" value="pro">
    Pro
  </label>

  <p>Current plan: <span *print="plan"></span></p>
</serc-rod>

Select with multiple:

<serc-rod id="multi" data='{
  "allOptions":["A","B","C"],
  "chosen":["A","C"]
}'>
  <label>
    Choose:
    <select multiple *input="chosen">
      <option *for="opt of allOptions" :value="opt" *print="opt"></option>
    </select>
  </label>

  <p>Chosen: <span *print="chosen.join(', ')"></span></p>
</serc-rod>

Notes