Move small tasks from wishlist to todo, update npm packages Replace #[Auth] attributes with manual auth checks and code quality rule Remove on_jqhtml_ready lifecycle method from framework Complete ACL system with 100-based role indexing and /dev/acl tester WIP: ACL system implementation with debug instrumentation Convert rsx:check JS linting to RPC socket server Clean up docs and fix $id→$sid in man pages, remove SSR/FPC feature Reorganize wishlists: priority order, mark sublayouts complete, add email Update model_fetch docs: mark MVP complete, fix enum docs, reorganize Comprehensive documentation overhaul: clarity, compression, and critical rules Convert Contacts/Projects CRUD to Model.fetch() and add fetch_or_null() Add JS ORM relationship lazy-loading and fetch array handling Add JS ORM relationship fetching and CRUD documentation Fix ORM hydration and add IDE resolution for Base_* model stubs Rename Json_Tree_Component to JS_Tree_Debug_Component and move to framework Enhance JS ORM infrastructure and add Json_Tree class name badges 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
721 lines
21 KiB
Markdown
Executable File
721 lines
21 KiB
Markdown
Executable File
# JQHTML v2 Complete Syntax & Instruction Format Specification
|
|
|
|
## Overview
|
|
|
|
JQHTML v2 is a composable component templating language for jQuery that compiles to efficient JavaScript instruction arrays. This document defines the complete syntax specification and the instruction format that templates compile to.
|
|
|
|
## Critical Concepts
|
|
|
|
### The Instruction Array Pattern
|
|
|
|
Templates compile to functions that return arrays of rendering instructions:
|
|
|
|
```javascript
|
|
// Every template function returns this structure:
|
|
function render(Component, data, args, content) {
|
|
const _output = []; // Instruction array
|
|
// ... push instructions ...
|
|
return [_output, this]; // CRITICAL: Returns tuple [instructions, context]
|
|
}
|
|
```
|
|
|
|
### Why This Pattern Exists
|
|
|
|
1. **Deferred Execution**: Instructions can be processed when optimal
|
|
2. **Single DOM Write**: All HTML built as string, then one `$.html()` call
|
|
3. **Complex Attribute Handling**: Functions and objects can't go in HTML strings
|
|
4. **Component Coordination**: Parent/child relationships preserved through execution
|
|
|
|
## Template Syntax
|
|
|
|
### 1. Component Definition
|
|
|
|
Components are defined using the `<Define:>` tag:
|
|
|
|
```jqhtml
|
|
<Define:ComponentName>
|
|
<!-- Component template -->
|
|
</Define:ComponentName>
|
|
```
|
|
|
|
### 2. $ Attribute System
|
|
|
|
JQHTML uses the `$` prefix for passing data to components as `this.args`:
|
|
|
|
#### Syntax Rules
|
|
|
|
1. **Quoted = Literal String**
|
|
```jqhtml
|
|
$title="User Profile" → args.title = "User Profile"
|
|
$expr="this.user" → args.expr = "this.user" (the string, not evaluated!)
|
|
```
|
|
|
|
2. **Unquoted = JavaScript Expression**
|
|
```jqhtml
|
|
$user=this.user → args.user = {actual user object}
|
|
$count=42 → args.count = 42
|
|
$enabled=true → args.enabled = true
|
|
$handler=this.onClick → args.handler = function reference
|
|
```
|
|
|
|
3. **Parentheses for Complex Expressions**
|
|
```jqhtml
|
|
$status=(active ? 'online' : 'offline')
|
|
$data=({...this.user, modified: true})
|
|
$items=(this.data.items || [])
|
|
```
|
|
|
|
#### Runtime Behavior
|
|
|
|
- All $ attributes become properties in `this.args` (NOT `this.data`)
|
|
- Stringable values (strings/numbers) appear as `data-*` DOM attributes
|
|
- Complex values (objects/arrays/functions) accessible only via `this.args`
|
|
- Component constructor syncs stringable args to DOM
|
|
|
|
#### Example
|
|
|
|
```jqhtml
|
|
<!-- Parent component with this.user = {name: "Alice", id: 123} -->
|
|
<UserCard
|
|
$user=this.user <!-- args.user = {name: "Alice", id: 123} -->
|
|
$title="User Profile" <!-- args.title = "User Profile" -->
|
|
$count=42 <!-- args.count = 42 -->
|
|
data-role="admin" <!-- args.role = "admin" -->
|
|
/>
|
|
```
|
|
|
|
Result in UserCard component:
|
|
- `this.args.user` = {name: "Alice", id: 123} (object)
|
|
- `this.args.title` = "User Profile" (string)
|
|
- `this.args.count` = 42 (number)
|
|
- `this.args.role` = "admin" (from data- attribute)
|
|
|
|
DOM shows: `<div data-title="User Profile" data-count="42" data-role="admin">` (no data-user since it's an object)
|
|
|
|
### 3. Expressions
|
|
|
|
Output JavaScript expressions using `<%= %>` (escaped) or `<%!= %>` (unescaped):
|
|
|
|
```jqhtml
|
|
<%= this.data.title %> <!-- Escaped output -->
|
|
<%!= this.data.rawHtml %> <!-- Unescaped output -->
|
|
<%@= this.data.maybeUndefined %> <!-- Escaped with error suppression -->
|
|
<%!@= this.data.maybeUndefinedHtml %> <!-- Unescaped with error suppression -->
|
|
```
|
|
|
|
### 3. Code Blocks
|
|
|
|
Execute JavaScript code using `<% %>`. Regular JavaScript passes through unchanged:
|
|
|
|
```jqhtml
|
|
<% const items = this.data.items || []; %>
|
|
<% console.log('Rendering:', items.length); %>
|
|
|
|
<!-- Regular JavaScript control flow -->
|
|
<% if (condition) { %>
|
|
<p>True branch</p>
|
|
<% } else { %>
|
|
<p>False branch</p>
|
|
<% } %>
|
|
|
|
<% for (const item of items) { %>
|
|
<div><%= item.name %></div>
|
|
<% } %>
|
|
```
|
|
|
|
**Design Rationale**: JavaScript code blocks pass through directly into the generated function. This allows developers to use any JavaScript construct without parser limitations.
|
|
|
|
## Component Invocation
|
|
|
|
### Basic Component Usage
|
|
|
|
Components are invoked using capital-letter tags:
|
|
|
|
```jqhtml
|
|
<UserCard /> <!-- Self-closing, no content -->
|
|
<UserCard></UserCard> <!-- Empty paired tags, no content -->
|
|
<UserCard>Default content</UserCard> <!-- With innerHTML content (most common) -->
|
|
```
|
|
|
|
### Property Passing
|
|
|
|
```jqhtml
|
|
<!-- String literals -->
|
|
<UserCard name="John Doe" role="admin" />
|
|
|
|
<!-- String interpolation -->
|
|
<UserCard title="Welcome <%= this.data.userName %>!" />
|
|
<div class="card <%= this.data.active ? 'active' : '' %>">
|
|
|
|
<!-- Data attributes ($property syntax) -->
|
|
<UserCard $user=this.data.currentUser $active=true />
|
|
<!-- Compiles to: data-user and data-active attributes -->
|
|
|
|
<!-- Property binding (:property syntax) -->
|
|
<UserCard :user="currentUser" :settings="{ theme: 'dark' }" />
|
|
<!-- Compiles to: data-bind-user and data-bind-settings attributes -->
|
|
|
|
<!-- Event binding (@event syntax) -->
|
|
<UserCard @save="handleSave" @cancel="() => closeModal()" />
|
|
<!-- Compiles to: data-on-save and data-on-cancel attributes -->
|
|
```
|
|
|
|
### $ Attribute System
|
|
|
|
JQHTML uses the `$` prefix as a shorthand for data attributes with special handling:
|
|
|
|
#### General Case: `$foo="bar"` → `data-foo`
|
|
|
|
```jqhtml
|
|
<div $user=currentUser $theme="dark" $count=42>
|
|
<!-- Compiles to: -->
|
|
{tag: ["div", {"data-user": currentUser, "data-theme": "dark", "data-count": 42}, false]}
|
|
```
|
|
|
|
**Runtime Behavior**:
|
|
1. The attribute is set via jQuery's `.data()` method: `$element.data('user', currentUser)`
|
|
2. For debugging visibility, if the value is a string or number, it's also set as a DOM attribute
|
|
3. Objects and arrays are stored in `.data()` but not visible in DOM
|
|
|
|
#### Special Case: `$sid="name"` → Scoped IDs
|
|
|
|
The `$sid` attribute has special handling for component-scoped element selection:
|
|
|
|
```jqhtml
|
|
<Define:UserCard>
|
|
<div $sid="container">
|
|
<input $sid="username" type="text" />
|
|
<button $sid="submit">Submit</button>
|
|
</div>
|
|
</Define:UserCard>
|
|
```
|
|
|
|
**Compilation**:
|
|
```javascript
|
|
// In render function (receives _cid parameter)
|
|
function render(_cid) {
|
|
const _output = [];
|
|
_output.push({tag: ["div", {"id": "container:" + _cid}, false]});
|
|
_output.push({tag: ["input", {"id": "username:" + _cid, "type": "text"}, false]});
|
|
_output.push({tag: ["button", {"id": "submit:" + _cid}, false]});
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Runtime Usage**:
|
|
```javascript
|
|
class UserCard extends Component {
|
|
init() {
|
|
// Find scoped elements
|
|
const $username = this.$sid('username'); // Returns $('#username:123')
|
|
const $submit = this.$sid('submit'); // Returns $('#submit:123')
|
|
|
|
$submit.on('click', () => {
|
|
const value = $username.val();
|
|
// ...
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Lexical Scoping of `_cid`
|
|
|
|
Component IDs flow through lexical scope naturally:
|
|
|
|
```jqhtml
|
|
<Define:ParentComponent>
|
|
<div $sid="parent-element"> <!-- Gets ParentComponent's _cid -->
|
|
<ChildComponent>
|
|
<div $sid="slot-element" /> <!-- Also gets ParentComponent's _cid -->
|
|
</ChildComponent>
|
|
</div>
|
|
</Define:ParentComponent>
|
|
|
|
<Define:ChildComponent>
|
|
<div $sid="child-element"> <!-- Gets ChildComponent's _cid -->
|
|
<%= content() %> <!-- Slot content preserves parent's _cid -->
|
|
</div>
|
|
</Define:ChildComponent>
|
|
```
|
|
|
|
This happens because content functions capture their defining scope:
|
|
```javascript
|
|
// ParentComponent render
|
|
function render(_cid) { // Parent's _cid = 123
|
|
_output.push({comp: ["ChildComponent", {}, () => {
|
|
// This arrow function captures Parent's _cid
|
|
const _output = [];
|
|
_output.push({tag: ["div", {"id": "slot-element:" + _cid}, false]}); // slot-element:123
|
|
return [_output, this];
|
|
}]});
|
|
}
|
|
|
|
// ChildComponent render
|
|
function render(_cid) { // Child's _cid = 456
|
|
_output.push({tag: ["div", {"id": "child-element:" + _cid}, false]}); // child-element:456
|
|
}
|
|
```
|
|
|
|
#### Attribute Value Interpolation
|
|
|
|
Within quoted attribute values, `<%= %>` and `<%!= %>` perform string interpolation:
|
|
|
|
```jqhtml
|
|
<!-- Both of these work identically in attributes -->
|
|
<div title="User: <%= user.name %>">
|
|
<div title="User: <%!= user.name %>">
|
|
|
|
<!-- Compiles to -->
|
|
{tag: ["div", {"title": "User: " + user.name}, false]}
|
|
```
|
|
|
|
**Design Rationale**: Inside attribute values, HTML escaping is inappropriate because we're building a JavaScript string, not HTML content. The JavaScript string serialization handles all necessary escaping (quotes, backslashes, etc.). This is semantically different from top-level `<%= %>` which outputs to HTML and requires escaping.
|
|
|
|
## Binding Syntax (v2)
|
|
|
|
### Property Binding with `:`
|
|
|
|
The `:` prefix creates dynamic property bindings that are always evaluated as JavaScript expressions:
|
|
|
|
```jqhtml
|
|
<!-- Simple property binding -->
|
|
<input :value="formData.name" :disabled="isLoading" />
|
|
|
|
<!-- Complex expressions -->
|
|
<div :style="{ color: textColor, fontSize: size + 'px' }" />
|
|
|
|
<!-- Method calls -->
|
|
<select :options="getOptions(category)" />
|
|
```
|
|
|
|
**Compilation**:
|
|
- `:prop="expr"` → `{"data-bind-prop": expr}` (no quotes around expr)
|
|
- Values are ALWAYS treated as JavaScript expressions
|
|
- Even quoted strings like `:prop="value"` become expressions
|
|
|
|
### Event Binding with `@`
|
|
|
|
The `@` prefix binds event handlers:
|
|
|
|
```jqhtml
|
|
<!-- Simple handler -->
|
|
<button @click="handleClick">Click Me</button>
|
|
|
|
<!-- With arguments -->
|
|
<button @click="deleteItem(item.id)">Delete</button>
|
|
|
|
<!-- Inline arrow function -->
|
|
<input @input="(e) => updateValue(e.target.value)" />
|
|
|
|
<!-- Multiple events -->
|
|
<form @submit="save" @reset="clear">
|
|
```
|
|
|
|
**Compilation**:
|
|
- `@event="handler"` → `{"data-on-event": handler}`
|
|
- Handlers are JavaScript expressions (function references or inline functions)
|
|
|
|
### Binding Design Rationale
|
|
|
|
1. **Vue.js Familiarity**: Uses the same `:prop` and `@event` syntax as Vue.js
|
|
2. **Always Expressions**: Unlike `$` attributes which distinguish strings vs expressions, bindings are ALWAYS expressions
|
|
3. **Runtime Processing**: The runtime will need to handle these specially:
|
|
- `data-bind-*` attributes set properties dynamically
|
|
- `data-on-*` attributes attach event listeners
|
|
4. **Separate from `$` System**: Bindings serve a different purpose than data attributes
|
|
|
|
## Component Content & innerHTML (Primary Pattern)
|
|
|
|
### Using `content()` Function - The Most Common Approach
|
|
|
|
**The vast majority of components will use the simple `content()` function to output innerHTML passed by parent components.** This is the primary, recommended pattern for component composition:
|
|
|
|
```jqhtml
|
|
<!-- Define a component that outputs innerHTML -->
|
|
<Define:Container>
|
|
<div class="container">
|
|
<div class="container-body">
|
|
<%= content() %> <!-- Outputs whatever innerHTML was passed -->
|
|
</div>
|
|
</div>
|
|
</Define:Container>
|
|
|
|
<!-- Usage - just pass innerHTML like regular HTML -->
|
|
<Container>
|
|
<p>This is the content</p>
|
|
<span>More content here</span>
|
|
<!-- Any HTML/components can go here -->
|
|
</Container>
|
|
```
|
|
|
|
### Common Patterns with `content()`
|
|
|
|
```jqhtml
|
|
<!-- Card component with innerHTML -->
|
|
<Define:Card>
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<%= content() %> <!-- Simple innerHTML output -->
|
|
</div>
|
|
</div>
|
|
</Define:Card>
|
|
|
|
<!-- Panel with conditional innerHTML -->
|
|
<Define:Panel>
|
|
<div class="panel">
|
|
<% if (this.args.title): %>
|
|
<div class="panel-header"><%= this.args.title %></div>
|
|
<% endif; %>
|
|
<div class="panel-body">
|
|
<%= content() %>
|
|
</div>
|
|
</div>
|
|
</Define:Panel>
|
|
|
|
<!-- Wrapper that adds behavior to innerHTML -->
|
|
<Define:Collapsible>
|
|
<div class="collapsible" $sid="wrapper">
|
|
<button $onclick="toggle">Toggle</button>
|
|
<div class="content" $sid="content">
|
|
<%= content() %> <!-- Wrapped innerHTML -->
|
|
</div>
|
|
</div>
|
|
</Define:Collapsible>
|
|
```
|
|
|
|
### How `content()` Works
|
|
|
|
1. **Parent passes innerHTML**: When invoking `<Component>innerHTML here</Component>`
|
|
2. **Template receives it**: The `content` parameter in render function contains the innerHTML
|
|
3. **Output with `<%= content() %>`**: Call the function to output the HTML where needed
|
|
4. **No special syntax needed**: Just regular HTML/components as children
|
|
|
|
### Important Rules
|
|
|
|
- **No mixing**: If a component uses ANY slots (`<#slotname>`), ALL content must be in slots
|
|
- **Most components don't need slots**: The simple `content()` pattern handles 95% of use cases
|
|
- **Slots are for advanced scenarios**: Only use slots when you need multiple named content areas
|
|
|
|
## Slot System (Advanced Feature)
|
|
|
|
**Note: Slots are an advanced feature for specific use cases. Most components should use the simpler `content()` pattern shown above.**
|
|
|
|
### When to Use Slots vs `content()`
|
|
|
|
Use `content()` (recommended for most cases):
|
|
- Single content area
|
|
- Wrapping/decorating content
|
|
- Simple component composition
|
|
- Standard container components
|
|
|
|
Use slots (advanced cases only):
|
|
- Multiple distinct content areas (header/body/footer)
|
|
- Complex layouts with specific placement
|
|
- Data table templates with row/header/footer sections
|
|
|
|
### 1. Named Slots
|
|
|
|
Pass content to specific slots using `<#slotname>` syntax:
|
|
|
|
```jqhtml
|
|
<DataTable $items=this.data.users>
|
|
<#header>
|
|
<th>ID</th>
|
|
<th>Name</th>
|
|
<th>Email</th>
|
|
</#header>
|
|
|
|
<#row>
|
|
<td><%= row.id %></td>
|
|
<td><%= row.name %></td>
|
|
<td><%= row.email %></td>
|
|
</#row>
|
|
|
|
<#empty /> <!-- Self-closing slot -->
|
|
</DataTable>
|
|
```
|
|
|
|
### 2. Accessing Slots in Components (Advanced)
|
|
|
|
For the rare cases where slots are needed, components can check for and render specific named slots:
|
|
|
|
```jqhtml
|
|
<Define:Card>
|
|
<div class="card">
|
|
<!-- Render default slot -->
|
|
<%= content() %>
|
|
|
|
<!-- Render named slot -->
|
|
<% if (content('footer')) { %>
|
|
<footer>
|
|
<%= content('footer', { timestamp: new Date() }) %>
|
|
</footer>
|
|
<% } %>
|
|
</div>
|
|
</Define:Card>
|
|
```
|
|
|
|
## Instruction Format Reference
|
|
|
|
### Instruction Types
|
|
|
|
Templates compile to three instruction types:
|
|
|
|
1. **`{tag: [...]}`** - HTML elements
|
|
2. **`{comp: [...]}`** - Component invocations
|
|
3. **`{slot: [...]}`** - Slot definitions
|
|
|
|
### 1. Tag Instruction
|
|
|
|
```javascript
|
|
{tag: [tagname, attributes, self_closing]}
|
|
```
|
|
|
|
**Format:**
|
|
- `tagname`: String - HTML tag name
|
|
- `attributes`: Object - Tag attributes (may contain functions!)
|
|
- `self_closing`: Boolean - Whether tag self-closes
|
|
|
|
**Examples:**
|
|
```javascript
|
|
{tag: ["div", {"class": "user-card"}, false]}
|
|
{tag: ["img", {"src": "/avatar.jpg", "alt": "User"}, true]}
|
|
{tag: ["button", {"onclick": this.handleClick}, false]}
|
|
{tag: ["div", {"data-sid": "header"}, false]} // $sid becomes data-id
|
|
```
|
|
|
|
### 2. Component Instruction
|
|
|
|
```javascript
|
|
{comp: [component_name, attributes, optional_innerhtml_function]}
|
|
```
|
|
|
|
**Format:**
|
|
- `component_name`: String - Component to instantiate
|
|
- `attributes`: Object - Props/attributes to pass
|
|
- `optional_innerhtml_function`: Function - Contains slots and default content
|
|
|
|
**CRITICAL**: When a component invocation contains slots or any child content, it MUST use the three-parameter form with a content function. Slots are NOT separate instructions after the component - they are contained within the component's content function.
|
|
|
|
**Examples:**
|
|
```javascript
|
|
// Simple component (no children)
|
|
{comp: ["UserAvatar", {"data-userId": this.data.id}]}
|
|
|
|
// Component with slots
|
|
{comp: ["DataTable", {"data-rows": this.data.items}, (DataTable) => {
|
|
const _output = [];
|
|
_output.push({slot: ["header", {}, (header) => {
|
|
const _output = [];
|
|
_output.push({tag: ["th", {}, false]});
|
|
_output.push("Name");
|
|
_output.push("</th>");
|
|
return [_output, this];
|
|
}]});
|
|
return [_output, this];
|
|
}]}
|
|
```
|
|
|
|
### 3. Slot Instruction
|
|
|
|
```javascript
|
|
{slot: [slot_name, attributes, render_function]}
|
|
```
|
|
|
|
**Format:**
|
|
- `slot_name`: String - Slot identifier
|
|
- `attributes`: Object - Props for slot
|
|
- `render_function`: Function - Returns `[instructions, context]`
|
|
|
|
**Example:**
|
|
```javascript
|
|
{slot: ["actions", {}, (actions) => {
|
|
const _output = [];
|
|
_output.push({tag: ["button", {"onclick": this.delete}, false]});
|
|
_output.push("Delete");
|
|
_output.push("</button>");
|
|
return [_output, this];
|
|
}]}
|
|
```
|
|
|
|
### 4. String Output
|
|
|
|
Raw strings are pushed directly:
|
|
|
|
```javascript
|
|
_output.push({tag: ["div", {}, false]}); // Opening tag (instruction)
|
|
_output.push("Hello, "); // Raw text
|
|
_output.push(html(this.data.name)); // Escaped text
|
|
_output.push("</div>"); // Closing tag (raw string!)
|
|
```
|
|
|
|
**CRITICAL**: Closing tags are ALWAYS raw strings, not instructions.
|
|
|
|
## Context Preservation
|
|
|
|
### v1 Pattern: `_that = this`
|
|
|
|
The v1 pattern preserves component context through nested functions:
|
|
|
|
```javascript
|
|
function render(Component, data, args, content) {
|
|
let _that = this; // Preserve component instance
|
|
let _output = [];
|
|
|
|
_output.push({comp: ["Child", {}, function(Child) {
|
|
let _output = [];
|
|
// _that still refers to parent component
|
|
_output.push(html(_that.data.value));
|
|
return [_output, _that];
|
|
}.bind(_that)]});
|
|
|
|
return [_output, _that];
|
|
}
|
|
```
|
|
|
|
### v2 Pattern: Arrow Functions
|
|
|
|
v2 uses arrow functions for natural context preservation:
|
|
|
|
```javascript
|
|
function render(Component, data, args, content) {
|
|
const _output = [];
|
|
|
|
_output.push({comp: ["Child", {}, (Child) => {
|
|
const _output = [];
|
|
// 'this' refers to parent component naturally
|
|
_output.push(html(this.data.value));
|
|
return [_output, this];
|
|
}]});
|
|
|
|
return [_output, this];
|
|
}
|
|
```
|
|
|
|
## Runtime Processing
|
|
|
|
### Phase 1: Instruction Execution
|
|
|
|
```javascript
|
|
let [instructions, context] = template.render.bind(component)(
|
|
component,
|
|
component.data,
|
|
args,
|
|
content_fn
|
|
);
|
|
```
|
|
|
|
### Phase 2: Instruction Processing
|
|
|
|
1. **Build HTML**: Process all string and simple tag instructions
|
|
2. **Track Complex Elements**: Store elements with function attributes
|
|
3. **Single DOM Write**: `component.$.html(html.join(''))`
|
|
4. **Apply Attributes**: Attach event handlers and complex attributes
|
|
5. **Initialize Components**: Create child component instances
|
|
|
|
## Self-Closing HTML Tags
|
|
|
|
The following HTML tags are automatically treated as self-closing:
|
|
- `area`, `base`, `br`, `col`, `embed`, `hr`, `img`, `input`
|
|
- `link`, `meta`, `param`, `source`, `track`, `wbr`
|
|
|
|
## Complete Example
|
|
|
|
### Template
|
|
|
|
```jqhtml
|
|
<Define:UserDashboard>
|
|
<div class="dashboard">
|
|
<Header $title="User Management">
|
|
<#actions>
|
|
<button $onclick=this.addUser>Add User</button>
|
|
</#actions>
|
|
</Header>
|
|
|
|
<% if (this.data.loading): %>
|
|
<LoadingSpinner />
|
|
<% else: %>
|
|
<UserTable $users=this.data.users>
|
|
<#header>
|
|
<th>ID</th>
|
|
<th>Name</th>
|
|
<th>Actions</th>
|
|
</#header>
|
|
|
|
<#row>
|
|
<td><%= row.id %></td>
|
|
<td><%= row.name %></td>
|
|
<td>
|
|
<button $onclick=this.editUser>Edit</button>
|
|
</td>
|
|
</#row>
|
|
</UserTable>
|
|
<% endif; %>
|
|
</div>
|
|
</Define:UserDashboard>
|
|
```
|
|
|
|
### Compiled Output Structure
|
|
|
|
```javascript
|
|
function render(Component, data, args, content) {
|
|
const _output = [];
|
|
|
|
_output.push({tag: ["div", {"class": "dashboard"}, false]});
|
|
|
|
// Header component with slots
|
|
_output.push({comp: ["Header", {"data-title": "User Management"}, (Header) => {
|
|
const _output = [];
|
|
_output.push({slot: ["actions", {}, (actions) => {
|
|
const _output = [];
|
|
_output.push({tag: ["button", {"data-onclick": this.addUser}, false]});
|
|
_output.push("Add User");
|
|
_output.push("</button>");
|
|
return [_output, this];
|
|
}]});
|
|
return [_output, this];
|
|
}]});
|
|
|
|
// ... rest of compilation
|
|
|
|
_output.push("</div>");
|
|
return [_output, this];
|
|
}
|
|
```
|
|
|
|
## Component CSS Classes
|
|
|
|
When components are rendered, the runtime automatically applies CSS classes based on the component's inheritance hierarchy:
|
|
|
|
```html
|
|
<!-- For a UserCard extending BaseCard extending Component -->
|
|
<div class="UserCard BaseCard Component" ...>
|
|
```
|
|
|
|
This enables CSS targeting at any level of the hierarchy:
|
|
```css
|
|
.Component { /* all components */ }
|
|
.UserCard { /* specific component type */ }
|
|
.BaseCard { /* all cards */ }
|
|
```
|
|
|
|
**Implementation Note**: The Component base class automatically applies these classes during initialization. This feature works regardless of minification since class names are preserved through the component's constructor name.
|
|
|
|
## Critical Implementation Rules
|
|
|
|
### What MUST Be Preserved
|
|
|
|
1. **Instruction Array Structure**: The `{tag:...}`, `{comp:...}`, `{slot:...}` format
|
|
2. **Return Tuple**: Functions MUST return `[instructions, context]`
|
|
3. **Deferred Component Init**: Components render as placeholders first
|
|
4. **Single DOM Write**: Build all HTML, write once
|
|
5. **Content Function**: Parent components receive content function to access slots
|
|
|
|
### Common Pitfalls
|
|
|
|
1. **Forgetting Return Format**: Must return `[array, context]`, not just array
|
|
2. **String vs Instruction**: Closing tags are strings, opening tags are instructions
|
|
3. **Context Loss**: Without proper binding, nested functions lose component reference
|
|
4. **Direct DOM Manipulation**: Never manipulate DOM during instruction building
|
|
5. **Missing html() Escaping**: User content must be escaped
|
|
6. **Slots Outside Components**: Slots must be inside component content functions |