Index
# JHS template syntax Source of truth: `src/jhs/{Node,Nodes,Loop,Test,Expression,Variable,Func,View,Printer}.php` (the compiler) and `src/classes/tNodes.php` (tag→class mapping). Everything below compiles to chained calls on a `MMO.elements.View` (`$view` in generated JS — see the **views-and-data** resource). ## Expressions: `{path}` and `{=js}` Anywhere in text content or an attribute value: - `{some/path}` — reactive one-way bind to `this.view.get('some/path')`. Re-renders automatically when that path changes in the view's `Map`. Compiles to `.$html('some/path')` (text content) or `.$attr('name','some/path')` (attribute). **In text content the value is inserted as HTML, not escaped**: `$html` → `linkHtml` → `Map.getHTML()` → `MMO.core.Printer.Shared().render(value, {html: true})` (through the key's schema first, when it has one), which has no `'string'` renderer and returns a string as-is, then the view marks it with `MMO.core.dom.html()` (`appendChildren` alone inserts a string as text) and `addHTML` sets it with `innerHTML`. An `` in the data runs; a plain `a c` loses its ``. - `{=}` — inline JavaScript. Any `{path}` *inside* the JS is extracted as a function parameter (not string-substituted), so `{= {firstname} + ' ' + {lastname} }` compiles to a function `(firstname, lastname) => firstname + ' ' + lastname` that re-runs whenever either path changes, wired via `.$htmlFn(...)` (text content — the return value goes to `innerHTML` the same way, without even the Printer) / `.$attrFn(...)` (attribute). Paths containing `/` (not usable as JS identifiers) get an internal `_v0_`, `_v1_`... alias. - If your JS has no `return`, the whole expression is treated as the return value (`{= {value} + 5 }` ≈ `return value + 5`). - If it has statements, end with an explicit `return`: `{= let x = {value}; return x + 5 }`. - Both land in the HTML sink because `compileText()` in `src/jhs/Printer.php` picks `$opts['html']??true` and nothing in the compiler passes `false`. While that default stands, **untrusted (user/API) text must not go through `{path}` or `{=...}` in text content** — `docs/SECURITY_REVIEW.md` §1–§2 records this, with the proposed compiler fix. What does keep a value as text, checked against the compiled output and `View.js`/`Template.js`: - `bind-text="path"` on the element that should hold it → `.$text('path', true)` → a text node, set through `nodeValue`. Strings only: `Map.getText()` renders a number as the Printer's ``, which shows up as `[object HTMLSpanElement]`. It also writes the text back to `path` on `blur`, which only fires on a focusable/editable element. - `set-textContent="{path}"` → `.$attr('set-textContent','path')` → `node.textContent = value` (the `set-` branch of `Template.setAttribute`). One-way on a plain element, any type — but it replaces everything the element holds, so give it an element of its own. - `$view.$text('path')` from a ``; `.text(string)` or `textContent` from a ` ``` or ```html ...markup... ``` `