sercrod

*case / *case.break

Summary

*case marks a branch inside a *switch / n-switch block. The branch is rendered when the *switch expression matches the case condition. *case.break is sugar for “case plus break”: it behaves like *case, and additionally stops evaluating later branches.

Key points:

Basic example

Simple switch over a status value:

<serc-rod id="app" data='{"status":"ready"}'>
  <div *switch="status">
    <p *case="'idle'">Idle</p>
    <p *case="'ready'">Ready</p>
    <p *default>Unknown</p>
  </div>
</serc-rod>

Behavior:

Behavior

Inside a *switch / n-switch host:

Selection and fallthrough:

Cloning and attributes:

Case expression semantics

*case and *case.break both use the same matching engine. They evaluate their attribute value in the current scope (augmented with $switch) and compare it to the *switch value.

The raw attribute string is processed as follows:

  1. Sercrod first tries to evaluate the full expression:

    • The expression is evaluated using eval_expr(raw, {...scope, $switch: switchVal}).
    • If evaluation succeeds, the resulting value v is matched by type.
    • If evaluation throws, Sercrod falls back to a string-based list mechanism (see below).
  2. If evaluation succeeded, the result v is matched according to its type:

    • Function:

      • If typeof v === "function", Sercrod calls v(switchVal, scope).
      • If the call returns a truthy value, the case matches.
    • Regular expression:

      • If v is a RegExp, Sercrod tests v.test(String(switchVal)).
      • If the test returns true, the case matches.
    • Array:

      • If Array.isArray(v) is true, the case matches when any array element is strictly equal to switchVal (using Object.is).
    • Objects with has:

      • If v is an object and has a has method (for example a Set), Sercrod calls v.has(switchVal).
      • If has returns truthy, the case matches.
    • Boolean:

      • If v is a boolean, its value is used directly.
      • true means the case matches; false means it does not.
    • Primitive string / number / bigint:

      • For typeof v in { "string", "number", "bigint" }, Sercrod compares v and switchVal with Object.is.
      • Object.is is used rather than ===, so NaN matches NaN, and +0 and -0 are distinguished.
    • Any other type:

      • If v does not fit any of the above categories, the case does not match.
  3. If evaluation failed (expression threw), Sercrod falls back to a token list:

    • The raw string is split on commas and pipes: /[,|]/.
    • Each token is trimmed; empty tokens are ignored.
    • For each token t:
      • Sercrod tries to evaluate t as an expression.
      • If that evaluation fails, it falls back to treating t as a string literal.
      • The token value vv is then compared to switchVal with Object.is.
      • If any vv matches switchVal, the case matches.

This means you can write all of the following:

Evaluation timing

Within the host that has *switch / n-switch:

Execution model

At a high level, the switch engine works like this for *case and *case.break:

  1. Evaluate switchVal from the host’s *switch / n-switch expression.
  2. Prepare a child scope:
    • Clone the current scope.
    • Inject $switch = switchVal.
  3. Scan the host’s direct element children in DOM order.
  4. For each child c:
    • Determine whether it is:
      • a *case / n-case / *case.break / n-case.break branch, or
      • a *default / n-default branch, or
      • neither.
    • Ignore children that are neither case nor default.
  5. Before rendering has started (no branch selected yet):
    • If c is a default branch:
      • Start rendering from this branch (falling = true).
    • Else if c is a case branch:
      • Evaluate the case expression with $switch in scope.
      • If the match succeeds, start rendering from this branch (falling = true).
      • If it does not match, skip this child.
  6. After rendering has started (falling = true):
    • Clone c.
    • Strip control attributes (*case, n-case, *case.break, n-case.break, *default, n-default, *break, n-break) from the clone.
    • Render the clone with the child scope (which includes $switch).
    • Check for break:
      • If the original child c has *break, n-break, *case.break, or n-case.break, stop the switch here and do not render later branches.

As a result:

Variable creation and scope layering

*case and *case.break do not create new local variables by themselves. Instead, they rely on the scope prepared by the switch host:

The scope used inside each branch is effectively:

Use with *default and *break

*case interacts closely with *default and *break inside the same *switch block.

Example with fallthrough and break:

<serc-rod id="app" data='{"level":2}'>
  <div *switch="level">
    <p *case="1">Level 1</p>
    <p *case="2">Level 2</p>
    <p *case="3" *break>Level 3 (stop here)</p>
    <p *default>Level is 4 or more</p>
  </div>
</serc-rod>

Use with conditionals and loops

Branches can freely combine *case with other directives:

Best practices

Additional examples

Multiple values and regular expression:

<serc-rod id="router" data='{"path":"/admin/users"}'>
  <div *switch="path">
    <p *case="['/', '/home']">Home</p>
    <p *case="/^\\/admin\\//">Admin area</p>
    <p *default>Unknown path</p>
  </div>
</serc-rod>

Predicate function:

<serc-rod id="grader" data='{"score":82}'>
  <div *switch="score">
    <p *case="value => value >= 90">Grade A</p>
    <p *case="value => value >= 80">Grade B</p>
    <p *case="value => value >= 70">Grade C</p>
    <p *default>Needs improvement</p>
  </div>
</serc-rod>

Here the first predicate that returns true determines the starting branch, and fallthrough behavior applies as usual.

Notes