sercrod

Data and *let

Summary

This reference explains in detail how *let interacts with Sercrod’s data:

It focuses on the actual behavior of the runtime, pattern by pattern, so that you can predict when Sercrod’s host data (data on <serc-rod>) is modified and when it is not.

1. Data model recap

Before looking at patterns, it helps to separate three layers:

  1. Host data (_data)

    • Each <serc-rod> keeps a data object (from its data="..." or data='{...}' attribute).
    • This is the base object used to build scopes.
    • When we say “host data changes”, we mean this object is mutated or receives new properties.
  2. Effective scope (effScope)

    • A plain object that represents “what expressions see” at a given element.
    • It starts from the host data.
    • Sercrod adds extra entries (loop variables like item, index and so on).
    • For each element, effScope may be replaced or extended (for example by *let).
  3. *Local let scope (letScope)

    • When an element has *let, Sercrod builds

      • letScope = Object.assign(Object.create(effScope), effScope)
    • The *let code runs against this letScope via a sandbox.

    • After execution:

      • effScope for this element and its children becomes letScope.
      • New names from letScope may be copied (“promoted”) into the host data.

Promotion rule (current implementation):

This rule is the core of how *let affects data.

2. Pattern reference

This section lists concrete patterns: initial data, *let code, and what happens to both the local scope and the host data.

2.1 Pattern A: New top-level variables

Case A1: Create a new helper from existing data.

<serc-rod id="invoice" data='{"price": 1200, "qty": 3}'>
  <p *let="total = price * qty">
    <span *print="total"></span>
  </p>
</serc-rod>

Case A2: Create multiple new names at once.

<serc-rod data='{"a": 2, "b": 3}'>
  <p *let="
    sum = a + b;
    diff = a - b;
  ">
    <span *print="sum"></span>
    <span *print="diff"></span>
  </p>
</serc-rod>

2.2 Pattern B: Overwriting existing top-level properties

Case B1: Reassign a field that already exists in data.

<serc-rod id="priceBox" data='{"price": 100}'>
  <p *let="price = price * 1.1">
    <span *print="price"></span>
  </p>
  <p>
    Original price: <span *print="$data.price"></span>
  </p>
</serc-rod>

Case B2: Incrementing an existing field with +=

<serc-rod id="counter" data='{"count": 0}'>
  <p *let="count += 1">
    <span *print="count"></span>
  </p>
</serc-rod>

Consequence:

2.3 Pattern C: Creating new nested objects

Case C1: Creating a nested object from scratch.

<serc-rod id="userBox" data='{}'>
  <p *let="user.name = 'Ann'">
    <span *print="user.name"></span>
  </p>
</serc-rod>

This is the standard way *let creates nested objects for you when you assign to a previously unknown path.

2.4 Pattern D: Updating existing nested structures

Case D1: Updating a nested field on an existing object.

<serc-rod id="profile" data='{"user": { "name": "Ann", "age": 30 }}'>
  <p *let="user.name = 'Bob'">
    <span *print="user.name"></span>
  </p>
  <p>
    Outside: <span *print="$data.user.name"></span>
  </p>
</serc-rod>

Key point:

2.5 Pattern E: Unknown variables with operators like +=

Case E1: Incrementing a variable that does not exist yet.

<serc-rod id="box" data='{}'>
  <p *let="count += 1">
    <span *print="count"></span>
  </p>
</serc-rod>

Consequences:

2.6 Pattern F: Inside loops (*for / *each)

*let inside loops is evaluated for each iteration. The scope for each iteration is a plain object that includes:

Example: *let inside *each.

<serc-rod id="list" data='{"items":[{"name":"A"},{"name":"B"}]}'>
  <ul *each="item of items">
    <li *let="label = item.name + '!'">
      <span *print="label"></span>
    </li>
  </ul>
</serc-rod>

Per iteration:

Practical guidance:

2.7 Pattern G: Using $parent inside *let

Inside *let, Sercrod injects $parent as a non-enumerable property:

Example:

<serc-rod id="root" data='{"currency":"JPY"}'>
  <serc-rod id="child" data='{"price": 500}'>
    <button *let="$parent.total = ($parent.total || 0) + price">
      Add to total
    </button>
    <p>Total: <span *print="$parent.total"></span> {{currency}}</p>
  </serc-rod>
</serc-rod>

This is an advanced pattern for updating parent data from a nested component via *let.

3. Comparison with *global

*let and *global both execute arbitrary JavaScript, but they target different kinds of side effects.

Rule of thumb:

4. Cheat sheet

Understanding these patterns makes it much easier to predict when *let is local, when it behaves like a helper for derived values, and when it effectively mutates your data structures in a global way.