sercrod

*methods

Summary

*methods and n-methods import global functions into a Sercrod host so that they can be called from expressions inside that host.

*methods and n-methods are aliases. They behave identically.

Basic example

Single global function:

<script>
  // Define a global helper
  function toLabel(value){
    return `Value: ${value}`;
  }
</script>

<serc-rod id="app" data='{"value": 1}' *methods="toLabel">
  <p *print="toLabel(value)"></p>
</serc-rod>

Object of functions:

<script>
  // Group helpers under a single global object
  window.calc = {
    inc(x){ return x + 1; },
    double(x){ return x * 2; }
  };
</script>

<serc-rod id="app" data='{"value": 2}' *methods="calc">
  <p *print="inc(value)"></p>
  <p *print="double(value)"></p>
</serc-rod>

In this example:

Behavior

*methods is an attribute on the Sercrod host element:

Key properties:

Configuration syntax

*methods and n-methods accept a space-separated list of identifiers:

Resolution for each token name:

The import model is intentionally simple:

Evaluation timing

*methods influences expression evaluation at the point where Sercrod builds the sandbox for each expression.

Important points:

Execution model

In pseudocode, evaluation with *methods looks like this:

This model guarantees:

Scope and resolution order

When you call foo() inside an expression on a host with *methods, Sercrod resolves foo in the following effective order:

  1. Local scope (loop variables, *let bindings, and similar).
  2. Host data and any stage buffer associated with it.
  3. $parent and $root references (if you explicitly use those names).
  4. Functions imported via *methods and n-methods:
    • First, functions from global functions listed directly in _methods_names.
    • Then, functions from global objects listed in _methods_names.
  5. Internal helpers from _internal_methods (only if the name is still free).

Collisions:

This allows you to:

Use with conditionals, loops, and bindings

*methods affects any directive that relies on eval_expr or eval_let inside this host. That includes:

Example: formatting in conditionals and loops

<script>
  window.userHelpers = {
    isAdult(user){ return user.age >= 18; },
    displayName(user){ return `${user.last}, ${user.first}`; }
  };
</script>

<serc-rod
  id="users"
  data='{"users":[{"first":"Ann","last":"Lee","age":22},{"first":"Bob","last":"Smith","age":15}]}'
  *methods="userHelpers"
>
  <ul *each="user of users">
    <li *if="isAdult(user)">
      <span *print="displayName(user)"></span>
    </li>
  </ul>
</serc-rod>

Example: derived values via *let

<script>
  function priceWithTax(price, rate){
    return Math.round(price * (1 + rate));
  }
</script>

<serc-rod
  id="cart"
  data='{"price": 1000, "taxRate": 0.1}'
  *methods="priceWithTax"
>
  <p *let="total = priceWithTax(price, taxRate)">
    Subtotal: <span *print="price"></span><br>
    Total: <span *print="total"></span>
  </p>
</serc-rod>

Use with events

Event handlers (@click, @input, and similar) are evaluated via a different helper (eval_event), which has a built-in window fallback.

Example:

<script>
  function logClick(message){
    console.log("clicked:", message);
  }
</script>

<serc-rod id="app" data='{"label":"Hello"}'>
  <button @click="logClick(label)">
    Click
  </button>
</serc-rod>

This works even without *methods, because:

Where *methods still helps:

In short:

Best practices

Examples

Multiple containers:

<script>
  window.math = {
    add(a, b){ return a + b; },
    mul(a, b){ return a * b; }
  };

  window.format = {
    asCurrency(yen){
      return `${yen} JPY`;
    }
  };
</script>

<serc-rod
  id="checkout"
  data='{"unitPrice": 1200, "quantity": 3}'
  *methods="math format"
>
  <p *let="total = mul(unitPrice, quantity)">
    Total: <span *print="asCurrency(total)"></span>
  </p>
</serc-rod>

Overriding internal helpers:

Example (conceptual):

<script>
  window.helpers = {
    htmlEscape(s){
      // Your own implementation
      return String(s).replace(/[&<>"]/g, "_");
    }
  };
</script>

<serc-rod
  id="app"
  data='{"text": "<b>unsafe</b>"}'
  *methods="helpers"
>
  <p *print="htmlEscape(text)"></p>
</serc-rod>

Here, if Sercrod has a built-in htmlEscape, your version imported via *methods will be used instead, because expression scopes are filled from data and imported methods before _internal_methods.

Notes