# Templates & Data ## Overview j-templates provides a functional template system built on virtual nodes (vNodes). DOM element functions like `div()`, `button()`, and `table()` create lightweight vNode trees. Reactive bindings — arrow functions passed as `props`, `on`, `attrs`, or `data` — automatically re-render when dependencies change. Data stores (`StoreSync`, `StoreAsync`) and TypeScript interfaces provide structured, type-safe data flow. ## Element Functions Every HTML element is exposed as a factory function imported from `j-templates/DOM`: ```typescript import { div, span, h1, button, input, table, tbody, tr, td, thead, th } from "j-templates/DOM"; ``` Each function has the same signature: ```typescript element( config?: { props?: FunctionOr>; attrs?: FunctionOr<{ [name: string]: string }>; on?: FunctionOr>; data?: () => T | T[] | Promise | Promise; }, children?: vNodeType[] | ((data: T) => vNodeType | vNodeType[] | string), ): vElementNode ``` | Property | Type | Description | |---|---|---| | `props` | `FunctionOr>` | DOM properties. Pass a function for reactivity: `props: () => ({ className: "active" })`. | | `attrs` | `FunctionOr<{ [name: string]: string }>` | HTML attributes. Pass a function for reactivity. | | `on` | `FunctionOr>` | Event handlers. | | `data` | `() => T \| T[] \| Promise \| Promise` | Reactive data source. Drives iterative rendering when used with a children function. | **`FunctionOr`** means the value can be either `T` directly (static) or a function `() => T` (reactive). ### Static children Pass an array of vNodes: ```typescript div({}, [ h1({}, "Title"), span({}, "Content"), ]); ``` ### Reactive text Use a function that returns a string. The framework creates an `ObservableScope` from the function and updates the text node when dependencies change: ```typescript div({}, () => `Count: ${this.count}`); ``` ### The `text` function Creates a reactive text node: ```typescript import { text } from "j-templates/DOM"; text(() => someReactiveValue); ``` ## Reactive Bindings ### Props Pass a function for `props` to make them reactive. The function is evaluated in an `ObservableScope`, so any reactive reads inside it create dependencies: ```typescript div({ props: () => ({ className: this.isActive ? "active" : "inactive" }), }, () => "Content"); ``` ### Events Event handlers are static by default. The `on` object maps DOM event names to handler functions: ```typescript button({ on: { click: (e: MouseEvent) => this.handleClick(e) }, }, () => "Click me"); ``` ### Data-driven lists The `data` property drives iterative rendering. When `data` is provided, the second argument becomes a function that receives each item: ```typescript tbody({ data: () => this.items }, (item: Todo) => tr({}, () => [ td({}, () => item.text), td({}, () => item.done ? "Done" : "Pending"), ]), ); ``` When the data array changes, the framework efficiently reconciles children using identity-based diffing (small arrays use linear scan, large arrays use `Map`-based lookup). ### Conditional rendering with data When `data` returns a falsy value (`null`, `undefined`, `0`, `false`, `""`), the framework converts it to an empty array `[]` and **destroys all children**. This provides `*ngIf`-style show/hide behavior — all child vNodes and their DOM elements are removed: ```typescript // Show the list only when items is truthy; hide all children when falsy ul({ data: () => this.items }, (item) => li({}, () => item.name), ); ``` When `this.items` becomes `null` or `undefined`, all `
  • ` children are destroyed and removed from the DOM. When `this.items` becomes truthy again, children are recreated from the new data. ### Conditional rendering with ternary Use ternary expressions inside reactive functions: ```typescript div({}, () => this.isLoading ? "Loading…" : "Content"); ``` ### Two-way binding Combine `props` and `on` to create two-way data binding: ```typescript input({ props: () => ({ value: this.searchText }), on: { input: (e: Event) => { this.searchText = (e.target as HTMLInputElement).value; } }, }); ``` ## Component Composition in Templates Component factory functions (created with `Component.ToFunction`) are used directly in templates: ```typescript import { numberCard } from "./components/number-card"; class Dashboard extends Component { Template() { return div({ props: { className: "dashboard" } }, () => [ numberCard({ data: () => ({ title: "Total Visits", value: 142 }) }), numberCard({ data: () => ({ title: "Total Time", value: 3800 }) }), ]); } } ``` ## Data Modeling ### Domain interfaces Define TypeScript interfaces for domain entities and display objects: ```typescript export interface User { id: string; name: string; email: string; } export interface Activity { id: string; timestamp: string; url: string; time_spent: number; user: User; } ``` ### DTO pattern Flatten or transform domain models for display purposes: ```typescript export interface ActivityRow { timestamp: string; url: string; time_spent: number; user: { name: string }; } function toActivityRow(activity: Activity): ActivityRow { return { timestamp: activity.timestamp, url: activity.url, time_spent: activity.time_spent, user: { name: activity.user.name }, }; } ``` ### Type-safe data in components Component generics ensure `this.Data` is typed: ```typescript class UserProfile extends Component<{ user: User }> { Template() { return div({}, () => [ h1({}, () => this.Data.user.name), div({}, () => this.Data.user.email), ]); } } ``` ### Store operations ```typescript const store = new StoreSync((value) => value.id); store.Write({ id: "1", name: "Alice" }); // Write & diff store.Patch("1", { email: "alice@example.com" }); // Deep merge store.Push("1", "tags", "admin"); // Push to nested array store.Splice("1", "tags", 0, 1); // Splice nested array const user = store.Get("1"); // Observable retrieval ``` ### Object sharing with key functions When two objects share the same key, the store reconciles them to a single instance: ```typescript const store = new StoreSync((value) => value.id); store.Write({ id: "act-1", user: { id: "usr-1", name: "John" } }); store.Write({ id: "act-2", user: { id: "usr-1", name: "John" } }); // Both activities reference the same user object ``` ## Animation The `Animation` class provides value interpolation with configurable timing. It implements `IDestroyable` for automatic cleanup with `@Destroy`. ```typescript import { Animation, AnimationType } from "j-templates/Utils"; ``` ### AnimationType | Value | Description | |---|---| | `Linear` | Linear interpolation | | `EaseIn` | Ease-in interpolation (starts slow, accelerates) | ### Constructor ```typescript new Animation(type: AnimationType, duration: number, update: (next: number) => void) ``` - `type` — Interpolation function - `duration` — Animation duration in milliseconds - `update` — Callback invoked each frame with the interpolated value ### Methods and properties | Member | Signature | Description | |---|---|---| | `Animate` | `(start: number, end: number): Promise` | Start an animation. If disabled, returns immediately. If start equals end, calls `update(end)` directly. Otherwise, schedules frame-by-frame updates. | | `Cancel` | `(): void` | Cancel the running animation. | | `Disable` | `(): void` | Cancel and prevent future animations. | | `Enable` | `(): void` | Re-enable animations. | | `Destroy` | `(): void` | Calls `Disable()`. | | `Running` | `get: boolean` | Whether an animation is in progress. | | `Start` | `get: number \| null` | Starting value of the current animation. | | `End` | `get: number \| null` | Ending value of the current animation. | | `Enabled` | `get: boolean` | Whether the animation is enabled. | ### Usage with @Destroy and @Watch ```typescript import { Animation, AnimationType } from "j-templates/Utils"; import { Component, Value, Watch, Destroy } from "j-templates/Utils"; class NumberCard extends Component<{ title: string; value: number }> { @Value() cardValue = 0; @Destroy() animator = new Animation(AnimationType.Linear, 500, (next) => { this.cardValue = Math.floor(next); }); @Watch((self) => self.Data.value) onValueChanged(value: number) { this.animator.Animate(this.cardValue, value); } Template() { return div({}, () => [ h1({}, () => this.Data.title), div({}, () => `${this.cardValue}`), ]); } } ``` ### Async animation sequences `Animate` returns a `Promise`, enabling sequential animations: ```typescript async fadeOutAndMove() { await this.fadeAnimator.Animate(1, 0); // Fade out this.element.classList.add("hidden"); await this.moveAnimator.Animate(0, 100); // Move this.element.classList.remove("hidden"); await this.fadeAnimator.Animate(0, 1); // Fade in } ``` ### Disable/Enable for instant changes ```typescript this.animator.Disable(); this.cardValue = targetValue; // Instant update, no animation this.animator.Enable(); ``` ## Best Practices - **Keep `Template()` pure.** Only return vNodes — no side effects. - **Use arrow functions for reactivity.** `props: () => ({ ... })` and `data: () => this.items` create reactive scopes. - **Use `gate` selectively.** Only when a parent scope changes but a derived value often stays the same. Not needed for basic array reactivity. - **Use `peek` to read without subscribing.** When you need a value during template evaluation but don't want the parent to depend on it. - **Use `@Computed` for expensive derived data.** It preserves object identity and prevents unnecessary downstream re-renders. - **Define domain interfaces.** TypeScript interfaces ensure type safety through `this.Data` and store operations. - **Use key functions with stores.** `new StoreSync((value) => value.id)` enables object identity sharing. - **Mark animations with `@Destroy`.** Prevents memory leaks from animation timers. - **Use `@Watch` to connect animations to state.** The handler is called immediately with the current value and on every change. ## Source - `src/DOM/elements.ts` — DOM element factory functions - `src/DOM/index.ts` — DOM module exports - `src/Node/vNode.ts` — vNode creation, initialization, reconciliation - `src/Node/vNode.types.ts` — vNode type definitions - `src/Utils/animation.ts` — Animation class - `src/Store/Store/storeSync.ts` — StoreSync - `src/Store/Store/storeAsync.ts` — StoreAsync ## See Also - [Components](./01-components.md) — Component class, lifecycle, composition - [Reactivity](./02-reactivity.md) — ObservableScope, ObservableNode, decorators - [Dependency Injection](./04-dependency-injection.md) — Injector and `@Inject`