sercrod

*print

Summary

*print evaluates an expression and writes the result into the element as plain text by setting textContent. The value is passed through Sercrod’s text filter and then rendered as a string, with null, undefined, and false treated as empty. The alias n-print behaves the same and shares the same implementation.

Related low level variants:

Basic example

The simplest case, printing a single property from host data:

<serc-rod id="app" data='{"name":"Alice"}'>
  <p *print="name"></p>
</serc-rod>

A simple greeting based on host data:

<serc-rod id="app" data='{"user":{"name":"Alice"}}'>
  <p *print="`Hello, ${user.name}!`"></p>
</serc-rod>

At runtime:

Behavior

Core behavior of *print and n-print:

Important details:

Value normalization and filters

The implementation normalizes values in two steps:

  1. Expression result:

    • v = eval_expr(...)
    • If v is null, undefined, or false, it is treated as empty and replaced with "".
    • All other values (including 0, true, and objects) are kept as raw.
  2. Text filter:

    • The normalized raw value is passed to this.constructor._filters.text(raw, { el, expr, scope }).

    • Default text filter is:

      • text: (raw, ctx) => String(raw ?? "")
    • This means:

      • null, undefined, or false become "".
      • 0 becomes "0".
      • true becomes "true".
      • Objects become "[object Object]" unless you override the text filter.

You can override Sercrod._filters.text (or provide window.__Sercrod_filter.text before Sercrod is loaded) to change how *print and *textContent values are normalized. The same text filter is also used by the fallback path that renders %expr% text expansions into plain text.

Evaluation timing

*print is a non structural directive. It is handled in the per element rendering stage after structural directives and before child nodes are rendered.

More precisely:

Inside _renderElement:

As a result:

Execution model

Conceptually, the runtime behaves as follows for *print and n-print:

  1. Detect directive:

    • If the element has *print or n-print or *textContent or n-textContent, Sercrod enters the text assignment path.
  2. Choose the active attribute:

    • Priority is:

      • *print
      • n-print
      • *textContent
      • n-textContent
    • The first one that exists on the element provides the expression string.

  3. Prepare the expression:

    • Read the raw attribute value.
    • If normalizeTpl exists on the Sercrod class, pass the expression string through it.
    • Otherwise use the original string.
  4. Evaluate:

    • Run eval_expr with the current effective scope and the element as context.
    • If eval_expr fails internally, it logs a warning with mode:"print" or "textContent" and returns false.
  5. Normalize and filter:

    • Map null, undefined, and false to "".
    • Pass the result to the text filter with { el, expr, scope }.
  6. Assign:

    • Set el.textContent to the filtered string.
  7. Cleanup and append:

    • Optionally remove *print, n-print, *textContent, and n-textContent from the element if cleanup.directives is enabled.
    • Append the element to its parent.
    • Return from _renderElement without rendering child nodes.

For *textContent and n-textContent, the same steps are taken, except that mode is "textContent" or "n-textContent" for logging purposes. Functionally they are equivalent to *print and n-print in the current implementation.

Variable creation

*print does not create any new variables.

The expression on *print is evaluated in exactly the same environment as other expression based directives.

Scope layering

*print uses the same scope that is effective at the point where the element is rendered.

*print itself does not change or wrap the scope; it only reads from it.

Parent access

Because *print uses eval_expr, it supports the usual Sercrod special variables for accessing parent data:

Examples:

<serc-rod id="app" data='{"title":"Dashboard","user":{"name":"Alice"}}'>
  <header>
    <h1 *print="$data.title"></h1>
    <p *print="`Signed in as ${$data.user.name}`"></p>
  </header>
</serc-rod>

In nested hosts, you can use $parent or $root when the inner host wants to display some outer header information inside a *print expression.

Use with conditionals and loops

*print is often used inside elements that are controlled by *if, *for, or *each.

There is no special interaction with loops beyond using the scope that loops provide.

Comparison with text interpolation (%expr%)

Sercrod supports %expr% style text interpolation inside plain text nodes using the configured delimiters. By default the delimiters are % and %, and interpolation is implemented by _expand_text.

Key differences between *print and %expr%:

Example with %expr% only:

<serc-rod id="app" data='{"user":{"name":"Alice"}}'>
  <p>Hello, %user.name%!</p>
</serc-rod>

Example with *print taking precedence:

<serc-rod id="app" data='{"user":{"name":"Alice"}}'>
  <p *print="`Hello, ${user.name}!`">
    This inner text, including %user.name%, is ignored.
  </p>
</serc-rod>

In this second case:

Combination rules and caveats:

To keep behavior predictable, treat *print, *textContent, *innerHTML, and n-innerHTML as mutually exclusive on a single element and choose one per element.

Best practices

Additional examples

Display a number with a suffix:

<serc-rod id="counter" data='{"count": 42}'>
  <p *print="`${count} items`"></p>
</serc-rod>

Fallback for missing values:

<serc-rod id="app" data='{"user":{}}'>
  <p *print="user.name || 'Anonymous'"></p>
</serc-rod>

Using methods:

Assume window.formatUser is a function that returns a display name.

function formatUser(user) {
  return user && user.name ? `User: ${user.name}` : "Guest";
}
<serc-rod id="app" data='{"user":{"name":"Alice"}}' *methods="formatUser">
  <p *print="formatUser(user)"></p>
</serc-rod>

The *methods="formatUser" directive makes formatUser available in the expression scope for *print and other directives.

Notes