sercrod

*innerHTML

Summary

*innerHTML sets the DOM innerHTML property of an element from a Sercrod expression. It is the low-level directive for inserting HTML markup as a string. The alias n-innerHTML behaves identically.

Use *innerHTML when you already have an HTML string and you want to insert it as markup, not as plain text. Unlike *print or *textContent, *innerHTML does not escape the value; it passes it through the global html filter and then assigns it directly to el.innerHTML.

Basic example

A simple example that renders an HTML fragment stored in data:

<serc-rod id="app" data='{
  "contentHtml": "<strong>Hello</strong> <em>world</em>!"
}'>
  <p *innerHTML="contentHtml"></p>
</serc-rod>

Behavior:

Behavior

Core behavior:

Aliases:

Relationship to *print and *textContent:

Evaluation timing

Within the rendering pipeline of a single element:

Important consequence:

Execution model

Conceptually, for an element <div *innerHTML="expr"> the runtime behaves like this:

  1. Clone the template node (without children) into el.
  2. Resolve any earlier host-level directives (for example *if, *include, *import, *literal, *rem, *print, *textContent).
  3. When the *innerHTML branch is reached:
    • Read the attribute value expr from the host.
    • Evaluate it with eval_expr(expr, scope, { el: node, mode: "innerHTML" }).
    • Map null or false to an empty string, otherwise use the returned value as raw.
    • Build a context object { el, expr, scope }.
    • Call Sercrod._filters.html(raw, ctx) and assign the result to el.innerHTML.
  4. Recursively render the original template children:
    • For each child in node.childNodes, call renderNode(child, scope, el).
    • This may append Sercrod-generated child content after the HTML inserted by *innerHTML.
  5. At the end of _renderElement, if cleanup.directives is enabled in the global config:
    • All attributes that are known Sercrod directives (including *innerHTML and n-innerHTML) are removed from el.
  6. Append el to the parent and run any log hooks.

The inserted HTML string is therefore a one-shot, low-level injection step; Sercrod does not automatically apply its own directives inside that string.

Variable creation and scope layering

*innerHTML does not create new variables.

Its expression is evaluated in the normal Sercrod scope:

There is no per-directive scope layering; *innerHTML simply looks at whatever is currently visible in the merged scope.

Parent access

*innerHTML does not alter parent relationships:

Example:

<serc-rod id="app" data='{
  "post": { "id": 1, "summaryHtml": "<p>Summary</p>" }
}'>
  <article>
    <header>
      <h1 *print="$parent.title"></h1>
    </header>
    <section *innerHTML="post.summaryHtml"></section>
  </article>
</serc-rod>

Here, *innerHTML sees post.summaryHtml from the current scope. The <h1> can still use $parent or $root as usual.

Use with conditionals and loops

*innerHTML composes well with structural directives when they target different elements:

Use with templates, *include, and *import

*innerHTML shares the same low-level insertion mechanism (innerHTML) as *include and *import, but with different responsibilities:

Combined effect when used together:

Recommendation:

Security and the html filter

The html filter is the main hook for controlling how HTML strings are inserted:

Guidelines:

Example with an external sanitizer:

<script>
  function safeHtmlFromMarkdown(md){
    const html = renderMarkdownToHtml(md);     // your own converter
    return sanitizeHtml(html);                // your own sanitizer
  }
</script>

<serc-rod id="doc" data='{"bodyMd": "# Title"}'>
  <article *innerHTML="safeHtmlFromMarkdown(bodyMd)"></article>
</serc-rod>

Best practices

Additional examples

Fallback summary:

<serc-rod id="post" data='{
  "post": {
    "title": "Post title",
    "summaryHtml": null
  }
}'>
  <h2 *print="post.title"></h2>
  <div *innerHTML="post.summaryHtml || '<p>No summary available.</p>'"></div>
</serc-rod>

Combining *include with *innerHTML on nested elements:

<template *template="post-card">
  <article class="post-card">
    <h2 *print="post.title"></h2>
    <div class="summary" *innerHTML="post.summaryHtml"></div>
  </article>
</template>

<serc-rod id="list" data='{"posts":[
  { "title": "First",  "summaryHtml": "<p>First summary</p>" },
  { "title": "Second", "summaryHtml": "<p>Second summary</p>" }
]}'>
  <section *each="post of posts">
    <div *include="'post-card'"></div>
  </section>
</serc-rod>

Notes