Client

views-and-data

  • mmo.client
  • agent skill
  • mmoframework-skill://mmo.client/mmo-client/references/views-and-data.md
# Views, rendering, and the reactive data model Source of truth: `src/js/elements/View.js` (`MMO.elements.View`, read in full) and `src/js/core/Template.js` (`MMO.core.Template`, its base class, read in full). `MMO.core.Map` (`src/js/core/Map.js`) is the underlying reactive data container — read that file directly for its exact API before relying on details not covered here. ## The three layers ``` MMO.core.Template – imperative DOM builder: open()/attr()/text()/html()/close(), no data binding ↑ extends MMO.elements.View – + binds a subtree to a MMO.core.Map path: get/set/watch/loop/test, sub-views ↑ used by CustomElement/CustomShadowElement – wires a View to a Web Component's lifecycle (see custom-elements.md) ``` A `.jhs` file's compiled `render($view)` method is just a long chain of calls on a `View` (`$view` = `this.view` for a custom element) — everything in [jhs-syntax.md](https://mcp.mmoframework.fr/search/read?uri=mmoframework-skill%3A%2F%2Fmmo.client%2Fmmo-client%2Freferences%2Fjhs-syntax.md) is sugar over this API. You can call the same methods yourself from a `<script>` method when the declarative markup isn't enough. ## Rendering a standalone `<view>` from JS ```js MMO.domReady(() => { MMO.elements.View.RenderView('my-view', document.getElementById('container'), {name: 'John'}, /*basepath*/'', /*ref*/null, /*scope*/null); }); ``` `RenderView(name, node, map, basepath, ref, scope)`: - `name` — the view's `id` as declared in the `.jhs` file. - `node` — DOM element to render into (children are inserted, existing content is not cleared for you). - `map` — a plain object/array, or an existing `MMO.core.Map`/`MMO.elements.Entity` — becomes the view's data root. - `basepath` — JSON path within `map` this view is scoped to (usually `''`). - `ref` — insert after this sibling instead of at the front. - `scope` — sets `$this` inside `on*`/`{=...}` expressions (defaults to the view itself). From inside another view/element's markup, embed instead of calling JS: `<view src="my-view" path="user"></view>` — see [jhs-syntax.md](https://mcp.mmoframework.fr/search/read?uri=mmoframework-skill%3A%2F%2Fmmo.client%2Fmmo-client%2Freferences%2Fjhs-syntax.md#view--define-or-embed-a-reusable-view). ## Paths Every data-accessing method (`get`, `set`, `has`, `watch`, `delete`, `push`, `pop`) takes a **path**: a string like `'a/b/c'`, an integer, or an array of segments. - Relative (no leading `/`): resolved against the view's own `basepath` — `'name'` inside a view scoped to `user` reads `user/name` in the root map. - Absolute (leading `/`): resolved from the **root view**'s content, ignoring this view's own scoping — `'/currentUser/id'`. - `..` segments walk up (relative-parent notation), per `walkTo()`'s docblock. - A path ending in the literal segment `$$` or `$#` targets the *key* / *numeric index* of that map entry instead of its value — same special values usable inside `{path/$$}` in markup. ## Core `View` API (all in `src/js/elements/View.js`) | Method | Does | |---|---| | `get(path)` / `has(path)` | Read a value / check existence. `get` returns `undefined` (not throw) if the path doesn't resolve. | | `set(path, value)` | Write; returns `true` only if the value actually changed (use this to guard side effects, as `mmo-radio.jhs`'s `set value()` does before dispatching a `change` event). | | `getText(path)` / `getHTML(path)` | Text/HTML string representation of a path's value (delegates to the `Map`'s own `getText`/`getHTML`). `bind-text` renders through `getText`; `{path}` in text content and `bind-html` both render through `getHTML`, whose strings go into `innerHTML` unescaped — see [jhs-syntax.md](https://mcp.mmoframework.fr/search/read?uri=mmoframework-skill%3A%2F%2Fmmo.client%2Fmmo-client%2Freferences%2Fjhs-syntax.md#expressions-path-and-js). | | `delete(path)` | Remove; if `path` is `''`/root, removes the view itself from the DOM too. | | `push(path, value)` / `pop(path)` | Array-style append/remove-last on the Map at `path`. | | `merge(value)` | Shallow-merges `value` into the view's own content (`this.content.merge(value)`). | | `watch(path, listener, onPathChange?)` | Subscribe to changes at `path`. `listener(value, map, key, special)` fires immediately on subscribe and again on every change. `onPathChange(newPathString)` fires if the *path itself* shifts (e.g. an ancestor key was renamed) — most manual `<script>` code just needs the first two params. | | `unwatch(path, bind)` | Undo a `watch()`, using the binding object it returned. | | `createFunction(name, fn, onChange, ...pathArgs)` | The primitive behind `{=...}`: re-runs `fn` whenever any of `pathArgs` changes, calling `onChange(newValue)` only when the computed value actually differs. Useful if you need `{=...}`-style reactivity from a `<script>` method instead of markup. | | `loop(path, renderFn, insertFn?, deleteFn?)` / `loopFn(...)` | Programmatic equivalent of `<for>`. | | `test(...tests)` | Programmatic equivalent of `<if>/<elseif>/<else>` — see the JSDoc above `test()` in `View.js` for the exact tuple shape. | | `createView(path, node, nodeRef?, root?)` | Low-level: create a detached sub-view (used internally by `loop`/`test`/`$view`; you rarely call this directly). | | `hide()` / `show()` / `detach()` / `reattach()` / `remove()` | Visibility/lifecycle for the view and all its sub-views. `remove()` also disconnects all Map watches — always prefer it over ripping DOM nodes out manually. | ### Events emitted by a `View` `path`, `index`, `remove`, `clear`, `show`, `hide`, `view` (async, bubbles to ancestors when a sub-view is created) — subscribe with the `EventEmitter` mixin's `.on(name, fn, scope)` (see `src/js/core/EventEmitter.js`). ## `Entity` `MMO.elements.Entity` (`src/js/elements/Entity.js`) is a `Map`-like wrapper specifically for records coming from the framework's API (schema-aware, ids, embedded/parent relationships — `View`'s constructor special-cases `map instanceof Entity && map.isEmbedded()` to resolve the *real* root and basepath through the entity's parent chain). Pass an `Entity` (or plain data) as `RenderView`'s `map` argument when rendering something schema-backed; read `Entity.js` directly for its full API before depending on specifics beyond this. ## `Template` (base class) — for imperative DOM building outside any data binding If you ever need to build DOM without a reactive `Map` behind it (rare in `.jhs` files, more common in hand-written `.js`), `new MMO.core.Template(node)` gives you the same `.open(tag)/.attr(k,v)/.text(s)/.html(s)/.close(tag?)` chain `View` extends, plus `add()`, `insertBefore/After`, `detach/reattach`, and `setAttribute(node, name, value)` — which is also where the `onxxx` → `addEventListener` and `set-xxx` → property-set conventions are implemented (`Template.setAttribute`), so those two attribute forms work identically whether you're in `.jhs` markup or hand-rolled JS.