Fix bin/publish: use correct .env path for rspade_system Fix bin/publish script: prevent grep exit code 1 from terminating script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1374 lines
48 KiB
Plaintext
Executable File
1374 lines
48 KiB
Plaintext
Executable File
JQHTML(3) RSX Framework Manual JQHTML(3)
|
|
|
|
NAME
|
|
JQHTML - jQuery-based HTML component system for semantic UI development
|
|
|
|
SYNOPSIS
|
|
// Blade directive
|
|
<User_Card $name="John" $email="john@example.com" />
|
|
|
|
// PHP helper
|
|
{!! Jqhtml::component('User_Card', ['name' => 'John']) !!}
|
|
|
|
// JavaScript lifecycle class
|
|
class User_Card extends Jqhtml_Component {
|
|
async on_load() {
|
|
// Load async data - ONLY modify this.data
|
|
this.data = await fetch('/api/user/' + this.args.id)
|
|
.then(r => r.json());
|
|
}
|
|
|
|
on_ready() {
|
|
// All children ready, safe for DOM manipulation
|
|
this.$id('edit').on('click', () => this.edit());
|
|
}
|
|
}
|
|
|
|
DESCRIPTION
|
|
JQHTML is a jQuery-based component system for building semantic,
|
|
reusable UI elements. Unlike complex frameworks requiring state
|
|
management and virtual DOM, JQHTML uses simple HTML templates with
|
|
optional jQuery-based JavaScript classes.
|
|
|
|
Components compile to JavaScript functions at build time (similar to
|
|
PHP templating). The system handles unique IDs, DOM updates, and
|
|
initialization automatically through the RSX manifest system.
|
|
|
|
Key characteristics:
|
|
- Semantic-first design (name what things ARE, not how they LOOK)
|
|
- Templates compile to JavaScript (not JSX, not runtime parsing)
|
|
- Components ARE jQuery objects (this.$ is genuine jQuery)
|
|
- Undefined components work immediately (incremental scaffolding)
|
|
- Five-stage deterministic lifecycle (render → on_render → create → load → ready)
|
|
- Double-render pattern for loading states
|
|
- No build configuration required (auto-discovered)
|
|
|
|
SEMANTIC-FIRST DESIGN
|
|
JQHTML is designed for mechanical thinkers who think structurally
|
|
rather than visually. Components represent logical concepts, not
|
|
visual elements.
|
|
|
|
From the creator:
|
|
"I think mechanically, not visually. JQHTML lets me composite
|
|
concepts in HTML, rather than elements with cryptic class names."
|
|
|
|
Example semantic approach:
|
|
<User_Profile_Card /> # What it IS
|
|
<Product_List /> # Logical concept
|
|
<Stats_Dashboard /> # Semantic name
|
|
|
|
Not visual approach:
|
|
<div class="card"> # How it LOOKS
|
|
<ul class="list"> # Visual element
|
|
<div class="dashboard"> # Generic container
|
|
|
|
Undefined components render as <div> with the component name as a
|
|
class. This enables incremental scaffolding: build page structure
|
|
with semantic names first, define component templates later.
|
|
|
|
<!-- Works immediately without definitions -->
|
|
<Dashboard>
|
|
<Stats_Panel />
|
|
<Activity_Feed />
|
|
</Dashboard>
|
|
|
|
<!-- Renders as: -->
|
|
<div class="Dashboard">
|
|
<div class="Stats_Panel"></div>
|
|
<div class="Activity_Feed"></div>
|
|
</div>
|
|
|
|
TEMPLATE SYNTAX
|
|
Templates stored as .jqhtml files compile to JavaScript functions:
|
|
|
|
<Define:User_Card>
|
|
<div class="card">
|
|
<img $id="avatar" src="<%= this.data.avatar %>" />
|
|
<h3 $id="name"><%= this.data.name %></h3>
|
|
<p $id="email"><%= this.data.email %></p>
|
|
<button $id="edit">Edit</button>
|
|
</div>
|
|
</Define:User_Card>
|
|
|
|
Template expressions:
|
|
<%= expression %> - Escaped HTML output (safe, default)
|
|
<%== expression %> - Unescaped raw output (pre-sanitized content only)
|
|
<% statement; %> - JavaScript statements (loops, conditionals)
|
|
|
|
Attributes:
|
|
$id="name" - Scoped ID (becomes id="name:component_id")
|
|
$attr=value - Component parameter (becomes this.args.attr)
|
|
Note: Also creates data-attr HTML attribute
|
|
@event=this.method - Event binding (⚠️ verify functionality)
|
|
data-attr="value" - HTML data attributes
|
|
class="my-class" - CSS classes (merged with component name)
|
|
|
|
DEFINE TAG CONFIGURATION
|
|
The <Define> tag supports three types of attributes:
|
|
|
|
1. extends="Parent_Component"
|
|
Explicit template inheritance (alternative to class-based)
|
|
|
|
2. $property=value
|
|
Set default this.args values in template
|
|
|
|
3. Regular HTML attributes
|
|
Standard attributes (class, id, tag, data-*, etc.)
|
|
|
|
Setting Default Args with $ Attributes:
|
|
Use $ prefix on <Define> tag to configure default this.args
|
|
values without requiring a JavaScript class:
|
|
|
|
<Define:Data_Grid
|
|
$api=Product_Controller.list_products
|
|
$per_page=25
|
|
$theme="dark"
|
|
>
|
|
<table class="table">
|
|
<% for (let item of this.data.items) { %>
|
|
<tr><td><%= item.name %></td></tr>
|
|
<% } %>
|
|
</table>
|
|
</Define:Data_Grid>
|
|
|
|
Component automatically has:
|
|
this.args.api = Product_Controller.list_products (function)
|
|
this.args.per_page = 25 (number)
|
|
this.args.theme = "dark" (string literal)
|
|
|
|
Quoted vs Unquoted $ Attributes:
|
|
Unquoted - Raw JavaScript expressions (function refs, identifiers):
|
|
$handler=User_Controller.fetch_user (function reference)
|
|
$count=42 (number)
|
|
$enabled=true (boolean)
|
|
|
|
Quoted - String literals:
|
|
$theme="light" (string)
|
|
$user_id="123" (string "123", not number)
|
|
|
|
Template-Only Components:
|
|
Components can be fully configured in templates without
|
|
requiring JavaScript classes. The parent class can access
|
|
this.args values set in child template Define tags.
|
|
|
|
Example - Reusable data grid configured in template:
|
|
|
|
<Define:Users_Grid
|
|
$api=User_Controller.list_users
|
|
$columns="ID,Name,Email"
|
|
>
|
|
<%= content() %>
|
|
</Define:Users_Grid>
|
|
|
|
No JavaScript class needed - template provides all configuration.
|
|
Parent component accesses this.args.api and this.args.columns.
|
|
|
|
Use Cases:
|
|
- Template-only components (no JavaScript class required)
|
|
- Reusable components with configurable controllers
|
|
- Default values overridable at instantiation
|
|
- Declarative component configuration
|
|
|
|
Template compilation:
|
|
- Compiles to JavaScript render functions at build time
|
|
- Not JSX, not runtime parsing
|
|
- Generates working sourcemaps for debugging
|
|
- Similar to PHP templating philosophy
|
|
- All code in <% %> must be SYNCHRONOUS (no async/await)
|
|
|
|
COMPONENT ARGUMENTS
|
|
The $ prefix passes arguments to child components as this.args.
|
|
Critical distinction between quoted and unquoted values:
|
|
|
|
Quoted Values = Literal Strings:
|
|
<UserCard $title="User Profile" />
|
|
<!-- Result: this.args.title = "User Profile" -->
|
|
|
|
<UserCard $expr="this.user" />
|
|
<!-- Result: this.args.expr = "this.user" (string, NOT object!) -->
|
|
|
|
Unquoted Values = JavaScript Expressions:
|
|
<UserCard $user=this.data.user />
|
|
<!-- Result: this.args.user = {actual user object} -->
|
|
|
|
<UserCard $count=42 />
|
|
<!-- Result: this.args.count = 42 (number) -->
|
|
|
|
<UserCard $enabled=true />
|
|
<!-- Result: this.args.enabled = true (boolean) -->
|
|
|
|
Complex Expressions (use parentheses):
|
|
<UserCard $status=(user.active ? 'online' : 'offline') />
|
|
<UserCard $data=({...this.user, modified: true}) />
|
|
|
|
Implementation detail: $attr=value also creates data-attr HTML
|
|
attribute. This is vestigial from v1. For practical purposes,
|
|
focus on: $attr=value sets this.args.attr
|
|
|
|
EVENT BINDING (⚠️ VERIFY FUNCTIONALITY)
|
|
Event binding with @ prefix:
|
|
|
|
<Define:Button>
|
|
<button @click=this.handle_click @mouseover=this.handle_hover>
|
|
<%= content() %>
|
|
</button>
|
|
</Define:Button>
|
|
|
|
Common events:
|
|
@click, @change, @submit, @focus, @blur, @mouseover, @mouseout
|
|
|
|
Syntax: @eventname=this.method_name
|
|
|
|
⚠️ TODO: Verify @event binding syntax is functional in current jqhtml version.
|
|
|
|
THIS.ARGS VS THIS.DATA
|
|
Two distinct data sources in components:
|
|
|
|
this.args - Component Input Parameters:
|
|
- Source: Attributes from component invocation ($attr=value)
|
|
- When Set: During component construction
|
|
- Purpose: Configuration and input parameters
|
|
- Mutability: Treat as read-only
|
|
- Examples: User ID, theme, callback functions
|
|
|
|
this.data - Async Loaded Data:
|
|
- Source: on_load() lifecycle method
|
|
- When Set: During load phase (after render, before ready)
|
|
- Purpose: Dynamic data from APIs or computations
|
|
- Mutability: Can be modified, triggers re-render
|
|
- Initial State: Empty object {} on first render
|
|
- Examples: API responses, processed data, cached values
|
|
|
|
Example usage:
|
|
class UserCard extends Jqhtml_Component {
|
|
async on_load() {
|
|
// this.data starts as {}
|
|
const userId = this.args.userId; // From $user_id=...
|
|
|
|
// Load data and store in this.data
|
|
this.data = await fetch(`/api/users/${userId}`)
|
|
.then(r => r.json());
|
|
// Triggers automatic re-render with populated data
|
|
}
|
|
|
|
on_ready() {
|
|
console.log(this.data.name); // Loaded data
|
|
console.log(this.args.theme); // Input parameter
|
|
}
|
|
}
|
|
|
|
COMMON PATTERNS AND PITFALLS
|
|
Correct usage examples:
|
|
<!-- Pass object reference -->
|
|
<UserList $users=this.data.users />
|
|
|
|
<!-- Pass literal string -->
|
|
<Header $title="Dashboard" />
|
|
|
|
<!-- Pass computed value -->
|
|
<Badge $count=this.data.items.length />
|
|
|
|
<!-- Pass callback -->
|
|
<Button $onClick=this.handleClick />
|
|
|
|
Common mistakes to avoid:
|
|
<!-- WRONG: Passes string "this.data.users" not the array -->
|
|
<UserList $users="this.data.users" />
|
|
|
|
<!-- WRONG: Missing quotes on literal string -->
|
|
<Header $title=Dashboard /> <!-- Tries to evaluate variable -->
|
|
|
|
<!-- WRONG: Using this.data for input parameters -->
|
|
class Component {
|
|
on_create() {
|
|
const userId = this.data.userId; // WRONG - use this.args
|
|
const userId = this.args.userId; // CORRECT
|
|
}
|
|
}
|
|
|
|
CONTROL FLOW AND LOOPS
|
|
Templates support full JavaScript control flow with two syntax styles:
|
|
|
|
Colon Syntax (PHP-like):
|
|
<% if (this.data.show): %>
|
|
<div>Visible content</div>
|
|
<% else: %>
|
|
<div>Hidden content</div>
|
|
<% endif; %>
|
|
|
|
<% for (let item of this.data.items): %>
|
|
<li><%= item.name %></li>
|
|
<% endfor; %>
|
|
|
|
Brace Syntax (JavaScript-like):
|
|
<% if (this.data.show) { %>
|
|
<div>Visible content</div>
|
|
<% } else { %>
|
|
<div>Hidden content</div>
|
|
<% } %>
|
|
|
|
<% for (let i = 0; i < 10; i++) { %>
|
|
<div>Item <%= i %></div>
|
|
<% } %>
|
|
|
|
Common Loop Patterns:
|
|
// Array iteration
|
|
<% for (let user of this.data.users) { %>
|
|
<p><%= user.name %> - <%= user.email %></p>
|
|
<% } %>
|
|
|
|
// With index
|
|
<% for (let [idx, user] of this.data.users.entries()) { %>
|
|
<div>#<%= idx + 1 %>: <%= user.name %></div>
|
|
<% } %>
|
|
|
|
// Object properties
|
|
<% for (let key in this.data.settings) { %>
|
|
<p><%= key %>: <%= this.data.settings[key] %></p>
|
|
<% } %>
|
|
|
|
// Object.entries
|
|
<% for (let [key, val] of Object.entries(this.data.config)) { %>
|
|
<span><%= key %> = <%= val %></span>
|
|
<% } %>
|
|
|
|
JavaScript in Templates:
|
|
Template JavaScript must be SYNCHRONOUS - no async/await:
|
|
- Templates render entire HTML at once
|
|
- All code in <% %> must complete immediately
|
|
- Async operations belong in on_load() lifecycle method
|
|
|
|
<%
|
|
// ALLOWED: Synchronous calculations
|
|
let total = 0;
|
|
for (let item of this.data.items) {
|
|
total += item.price;
|
|
}
|
|
%>
|
|
<p>Total: $<%= total.toFixed(2) %></p>
|
|
|
|
COMPONENT LIFECYCLE
|
|
Five-stage deterministic lifecycle:
|
|
|
|
render → on_render → on_create → on_load → on_ready
|
|
|
|
1. render (automatic, top-down)
|
|
- Template executes, DOM created
|
|
- First render: this.data = {} (empty object)
|
|
- Parent completes before children
|
|
- Not overridable
|
|
|
|
2. on_render() (top-down)
|
|
- Fires immediately after render, BEFORE children ready
|
|
- Hide uninitialized elements
|
|
- Set initial visual state
|
|
- Prevents flash of uninitialized content
|
|
- Parent completes before children
|
|
|
|
3. on_create() (bottom-up)
|
|
- Quick synchronous setup
|
|
- Set instance properties
|
|
- Children complete before parent
|
|
|
|
4. on_load() (bottom-up, siblings in parallel)
|
|
- Load async data
|
|
- ONLY modify this.data - NEVER DOM
|
|
- NO child component access
|
|
- Siblings at same depth execute in parallel
|
|
- Children complete before parent
|
|
|
|
5. on_ready() (bottom-up)
|
|
- All children guaranteed ready
|
|
- Safe for DOM manipulation
|
|
- Attach event handlers
|
|
- Initialize plugins
|
|
- Children complete before parent
|
|
|
|
Depth-Ordered Execution:
|
|
- Top-down: render, on_render (parent before children)
|
|
- Bottom-up: on_create, on_load, on_ready (children before parent)
|
|
- Parallel: Siblings at same depth during on_load()
|
|
|
|
Critical rules:
|
|
- Never modify DOM in on_load() - only update this.data
|
|
- on_load() runs in parallel for siblings (DOM unpredictable)
|
|
- Data changes during load trigger single re-render
|
|
|
|
DOUBLE-RENDER PATTERN
|
|
Components may render TWICE if on_load() modifies this.data:
|
|
|
|
1. First render: this.data = {} (empty)
|
|
2. on_load() populates this.data
|
|
3. Automatic re-render with populated data
|
|
4. on_ready() fires after second render (only once)
|
|
|
|
Use for loading states:
|
|
<Define:Product_List>
|
|
<% if (Object.keys(this.data).length === 0) { %>
|
|
<!-- FIRST RENDER: Loading state -->
|
|
<div class="spinner">Loading products...</div>
|
|
<% } else { %>
|
|
<!-- SECOND RENDER: Actual data -->
|
|
<% for (let product of this.data.products) { %>
|
|
<Product_Card $product_id=product.id />
|
|
<% } %>
|
|
<% } %>
|
|
</Define:Product_List>
|
|
|
|
Example class:
|
|
class Product_List extends Jqhtml_Component {
|
|
on_render() {
|
|
// Fires TWICE: before and after data load
|
|
const is_loading = Object.keys(this.data).length === 0;
|
|
console.log('Rendered, loading?', is_loading);
|
|
}
|
|
|
|
async on_load() {
|
|
this.data.products = await fetch('/api/products')
|
|
.then(r => r.json());
|
|
// Automatic re-render happens after this completes
|
|
}
|
|
|
|
on_ready() {
|
|
// Fires ONCE after second render
|
|
console.log('Ready with', this.data.products.length, 'products');
|
|
}
|
|
}
|
|
|
|
LOADING STATE PATTERN (CRITICAL)
|
|
NEVER manually call this.render() in on_load() - The framework handles
|
|
re-rendering automatically when this.data changes.
|
|
|
|
The correct pattern for loading states:
|
|
1. Use simple this.data.loaded flag at END of on_load()
|
|
2. Check !this.data.loaded in template for loading state
|
|
3. Trust automatic re-rendering - don't call this.render()
|
|
4. NO nested state objects - don't use this.data.state.loading
|
|
|
|
INCORRECT Pattern (Common Mistakes):
|
|
class Product_List extends Jqhtml_Component {
|
|
async on_load() {
|
|
// WRONG: Setting loading state at START
|
|
this.data.state = {loading: true};
|
|
this.render(); // WRONG: Manual render call
|
|
|
|
const response = await $.ajax({...}); // WRONG: $.ajax()
|
|
|
|
if (response.success) {
|
|
this.data = {
|
|
records: response.records,
|
|
state: {loading: false} // WRONG: Nested state
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
Template for incorrect pattern:
|
|
<% if (this.data.state && this.data.state.loading) { %>
|
|
<!-- WRONG: Complex state check -->
|
|
Loading...
|
|
<% } %>
|
|
|
|
CORRECT Pattern:
|
|
class Product_List extends Jqhtml_Component {
|
|
async on_load() {
|
|
// CORRECT: NO loading flags at start
|
|
// CORRECT: NO manual this.render() calls
|
|
|
|
// CORRECT: Use Ajax endpoint pattern
|
|
const response = await Product_Controller.list_products({
|
|
page: 1,
|
|
per_page: 25
|
|
});
|
|
|
|
// CORRECT: Populate this.data directly
|
|
this.data.records = response.records;
|
|
this.data.total = response.total;
|
|
this.data.page = response.page;
|
|
|
|
// CORRECT: Simple flag at END
|
|
this.data.loaded = true;
|
|
|
|
// Automatic re-render happens because this.data changed
|
|
}
|
|
}
|
|
|
|
Template for correct pattern:
|
|
<Define:Product_List>
|
|
<% if (!this.data || !this.data.loaded) { %>
|
|
<!-- FIRST RENDER: Loading state -->
|
|
<div class="loading-spinner text-center">
|
|
<div class="spinner-border"></div>
|
|
<p>Loading products...</p>
|
|
</div>
|
|
<% } else if (this.data.records && this.data.records.length > 0) { %>
|
|
<!-- SECOND RENDER: Data loaded -->
|
|
<div class="table-responsive">
|
|
<table class="table">
|
|
<% for (let record of this.data.records) { %>
|
|
<tr><td><%= record.name %></td></tr>
|
|
<% } %>
|
|
</table>
|
|
</div>
|
|
<% } else { %>
|
|
<!-- Empty state -->
|
|
<div class="text-center">
|
|
<p>No records found</p>
|
|
</div>
|
|
<% } %>
|
|
</Define:Product_List>
|
|
|
|
Why this pattern works:
|
|
1. First render: this.data = {} (empty) - Template shows loading
|
|
2. on_load() executes: Populates this.data and sets loaded = true
|
|
3. Automatic re-render: Framework detects this.data changed
|
|
4. Second render: this.data.loaded === true - Template shows data
|
|
|
|
Key principles:
|
|
- Trust the framework - Automatic re-rendering when this.data changes
|
|
- Simple is better - Use flat this.data.loaded flag, not nested objects
|
|
- Check at template level - !this.data.loaded in template, not JS
|
|
- Never call this.render() manually in on_load() - breaks lifecycle
|
|
|
|
Anti-Patterns to Avoid:
|
|
- NEVER call this.render() manually in on_load()
|
|
- NEVER use nested state objects (this.data.state.loading)
|
|
- NEVER set loading flags at START of on_load()
|
|
- ONLY set this.data.loaded = true at END
|
|
|
|
JAVASCRIPT COMPONENT CLASS
|
|
Optional JavaScript class for behavior:
|
|
|
|
class Product_Grid extends Jqhtml_Component {
|
|
on_render() {
|
|
// Fires immediately after render, before children ready
|
|
// Hide uninitialized grid to prevent visual glitches
|
|
this.$.css('opacity', '0');
|
|
}
|
|
|
|
on_create() {
|
|
// Quick synchronous setup
|
|
// Set instance properties
|
|
this.selected = [];
|
|
}
|
|
|
|
async on_load() {
|
|
// Fetch data - NO DOM manipulation allowed!
|
|
// ONLY update this.data
|
|
this.data.products = await fetch('/api/products')
|
|
.then(r => r.json());
|
|
// Template re-renders automatically with new data
|
|
}
|
|
|
|
on_ready() {
|
|
// All children ready, safe for DOM
|
|
// Attach event handlers
|
|
this.$id('select_all').on('click', () => this.select_all());
|
|
this.$.animate({opacity: 1}, 300);
|
|
}
|
|
|
|
select_all() {
|
|
// Custom method called from event handler
|
|
this.$.find('.product').addClass('selected');
|
|
}
|
|
}
|
|
|
|
COMPONENTS ARE JQUERY
|
|
this.$ is a genuine jQuery object, not a wrapper.
|
|
All jQuery methods work directly:
|
|
|
|
class Dashboard extends Jqhtml_Component {
|
|
on_ready() {
|
|
// All jQuery methods available
|
|
this.$.addClass('active');
|
|
this.$.css('background', '#f0f0f0');
|
|
this.$.fadeIn(300);
|
|
this.$.on('click', () => this.handle_click());
|
|
|
|
// Traversal
|
|
this.$.find('.item').addClass('selected');
|
|
this.$.parent().addClass('has-dashboard');
|
|
|
|
// Manipulation
|
|
this.$.append('<div>New content</div>');
|
|
this.$.empty();
|
|
}
|
|
}
|
|
|
|
No abstraction layers, no virtual DOM - direct jQuery manipulation.
|
|
The DOM is the state. Update it directly.
|
|
|
|
CLASS-TEMPLATE ASSOCIATION
|
|
Automatic by naming convention:
|
|
- Template ID in .jqhtml file: <Define:User_Card>
|
|
- JavaScript class with same name: class User_Card extends Jqhtml_Component
|
|
- Both named "User_Card" = automatically associated
|
|
|
|
The JavaScript class is optional. Components work with template only.
|
|
|
|
NAMING CONVENTIONS
|
|
Component Names (PascalCase):
|
|
- Component names MUST use PascalCase
|
|
- Examples: UserCard, TestComponent1, ProductList
|
|
|
|
Filenames (snake_case - RSpade convention):
|
|
- Filenames SHOULD use lowercase with underscores (snake_case)
|
|
- Framework allows PascalCase or snake_case variations
|
|
- snake_case is the official RSpade convention
|
|
|
|
Examples:
|
|
Component: TestComponent1
|
|
Files: test_component_1.jqhtml, test_component_1.js
|
|
|
|
Component: UserCard
|
|
Files: user_card.jqhtml, user_card.js
|
|
|
|
Component: ProductList
|
|
Files: product_list.jqhtml, product_list.js
|
|
|
|
The framework converts PascalCase to snake_case by:
|
|
- Inserting underscore before uppercase letters (except first)
|
|
- Inserting underscore before first digit in number sequences
|
|
- Converting entire result to lowercase
|
|
|
|
Allowed variations (case-insensitive in rsx/):
|
|
TestComponent1 → test_component_1.jqhtml (recommended)
|
|
TestComponent1 → testcomponent1.jqhtml (allowed)
|
|
TestComponent1 → TestComponent1.jqhtml (allowed, not convention)
|
|
|
|
INSTANTIATION METHODS
|
|
Server-side (Blade uppercase tags):
|
|
<User_Card $name="John" $email="john@example.com" />
|
|
|
|
Server-side (PHP helper):
|
|
{!! Jqhtml::component('User_Card', ['name' => 'John']) !!}
|
|
|
|
Client-side (jQuery):
|
|
$('#target').component('User_Card', {
|
|
data: {name: 'John'},
|
|
template: 'override_template.jqhtml', // Optional
|
|
append: true, // Append instead of replace
|
|
});
|
|
|
|
Client-side (HTML attributes):
|
|
<div data-component="User_Card"
|
|
data-component-data='{"name": "John"}'>
|
|
</div>
|
|
|
|
BLADE SYNTAX (UPPERCASE TAGS)
|
|
RSX includes a Blade precompiler for uppercase component tags:
|
|
|
|
Self-closing uppercase tags (recommended):
|
|
<User_Card $name="John" $email="john@example.com" />
|
|
<Counter_Widget $title="My Counter" $initial_value=0 />
|
|
|
|
These transform to Jqhtml::component() calls automatically.
|
|
|
|
Important rules:
|
|
- Component names MUST start with uppercase letter
|
|
- Only self-closing tags allowed in Blade (no innerHTML/slot content)
|
|
- Use $ prefix for component args: $name="value"
|
|
- Use content() method pattern (95% of cases), not slots (5%)
|
|
|
|
Regular PHP syntax also works:
|
|
{!! Jqhtml::component('User_Card', ['name' => 'John']) !!}
|
|
|
|
DOM CLASS CONVENTION
|
|
All instantiated jqhtml components receive CSS classes on their
|
|
DOM elements:
|
|
|
|
- 'Jqhtml_Component' - All components
|
|
- Component name (e.g., 'User_Card') - For targeting
|
|
|
|
// Select all jqhtml components on the page
|
|
const components = $('.Jqhtml_Component');
|
|
|
|
// Check if an element is a jqhtml component
|
|
if ($element.hasClass('Jqhtml_Component')) {
|
|
// This is a jqhtml component
|
|
}
|
|
|
|
// Access component instance
|
|
const component = $element.data('component');
|
|
const component = $('#my-comp').component();
|
|
|
|
CREATING COMPONENTS
|
|
Generate new components with the RSX command:
|
|
|
|
php artisan rsx:app:component:create --name=my_widget --path=rsx/theme/components
|
|
|
|
Creates:
|
|
- my_widget.jqhtml - Template with <%= content() %> pattern
|
|
- My_Widget.js - Class extending Jqhtml_Component
|
|
|
|
Components automatically discovered by manifest system.
|
|
|
|
LIFECYCLE EVENT CALLBACKS
|
|
External code can register callbacks for lifecycle events using
|
|
the .on() method. Useful when you need to know when a component
|
|
reaches a certain state.
|
|
|
|
Supported Events:
|
|
'render' - Fires after render phase completes
|
|
'create' - Fires after create phase completes
|
|
'load' - Fires after load phase completes
|
|
'ready' - Fires after ready phase completes (fully initialized)
|
|
|
|
Basic usage:
|
|
// Get component instance and register callback
|
|
const component = $('#my-component').component();
|
|
component.on('ready', (comp) => {
|
|
console.log('Component ready!', comp);
|
|
// Access component data, DOM, etc.
|
|
});
|
|
|
|
// Chain directly
|
|
$('#my-component').component().on('ready', (component) => {
|
|
console.log('User data:', component.data.user);
|
|
});
|
|
|
|
// Multiple callbacks for same event
|
|
component
|
|
.on('render', () => console.log('Dashboard rendered'))
|
|
.on('create', () => console.log('Dashboard created'))
|
|
.on('load', () => console.log('Dashboard data loaded'))
|
|
.on('ready', () => console.log('Dashboard ready'));
|
|
|
|
Key behaviors:
|
|
- Immediate execution: If lifecycle event already occurred,
|
|
callback fires immediately
|
|
- Future events: Callback also registers for future occurrences
|
|
(useful for re-renders)
|
|
- Multiple callbacks: Can register multiple for same event
|
|
- Chaining: Returns this so you can chain .on() calls
|
|
- Only lifecycle events allowed: Other event names log error
|
|
|
|
Example - Wait for component initialization:
|
|
// Initialize nested components after parent ready
|
|
$('#parent').component('Dashboard').on('ready', function() {
|
|
Jqhtml_Integration._on_framework_modules_init($(this));
|
|
});
|
|
|
|
// Process component data after load
|
|
$('#data-grid').component().on('load', (comp) => {
|
|
const total = comp.data.items.reduce((sum, i) => sum + i.value, 0);
|
|
$('#total').text(total);
|
|
});
|
|
|
|
Available in JQHTML v2.2.81+
|
|
|
|
$REDRAWABLE ATTRIBUTE - LIGHTWEIGHT COMPONENTS
|
|
Convert any HTML element into a re-renderable component using the
|
|
$redrawable attribute. This parser-level transformation enables
|
|
selective re-rendering without creating separate component classes.
|
|
|
|
How It Works:
|
|
The $redrawable attribute triggers a parser transformation that
|
|
converts the element into a <Redrawable> component:
|
|
|
|
<!-- Write this: -->
|
|
<div $redrawable $id="counter">
|
|
Count: <%= this.data.count %>
|
|
</div>
|
|
|
|
<!-- Parser transforms to: -->
|
|
<Redrawable data-tag="div" $id="counter">
|
|
Count: <%= this.data.count %>
|
|
</Redrawable>
|
|
|
|
The Redrawable component renders as the specified tag type (default
|
|
is div) while providing full component functionality including
|
|
lifecycle hooks, data management, and selective re-rendering.
|
|
|
|
Selective Re-rendering from Parent:
|
|
Use the render(id) delegation syntax to re-render only specific
|
|
child components:
|
|
|
|
class Dashboard extends Jqhtml_Component {
|
|
async increment_counter() {
|
|
this.data.count++;
|
|
// Re-render only the counter element, not entire dashboard
|
|
this.render('counter'); // Finds child with $id="counter"
|
|
}
|
|
}
|
|
|
|
render(id) Delegation Syntax:
|
|
- this.render('counter') finds child with $id="counter"
|
|
- Verifies element is a component (has $redrawable or is proper
|
|
component class)
|
|
- Calls its render() method to update only that element
|
|
- Perfect for live data displays, counters, status indicators,
|
|
real-time feeds
|
|
- Parent component's DOM remains unchanged
|
|
|
|
Error Handling:
|
|
- Clear error if $id doesn't exist in children
|
|
- Clear error if element isn't configured as component
|
|
- Guides developers to correct usage patterns
|
|
|
|
Use Cases:
|
|
- Live counters and metrics that update frequently
|
|
- Status indicators that change independently
|
|
- Real-time data feeds (notifications, chat messages)
|
|
- Dynamic lists that need incremental updates
|
|
- Any element needing selective updates without full page re-render
|
|
- Avoiding unnecessary re-renders of static sibling elements
|
|
|
|
Benefits:
|
|
- No need to create separate component classes
|
|
- Lightweight - just add $redrawable attribute
|
|
- Full component lifecycle (on_load, on_ready, etc.)
|
|
- Efficient - only updates what changed
|
|
- Clear errors guide correct usage
|
|
|
|
LIFECYCLE MANIPULATION METHODS
|
|
Components provide methods to control their lifecycle and state after
|
|
initial render.
|
|
|
|
render()
|
|
Synchronous template re-render - Updates DOM with current this.data
|
|
without triggering full lifecycle.
|
|
|
|
render(id)
|
|
Selective re-render delegation - Finds child component with matching
|
|
$id and calls its render() method. Enables efficient partial updates.
|
|
|
|
Usage:
|
|
class Product_Card extends Jqhtml_Component {
|
|
update_price(new_price) {
|
|
this.data.price = new_price;
|
|
this.render(); // Template re-renders immediately
|
|
}
|
|
}
|
|
|
|
Behavior:
|
|
- Re-executes template with current this.data
|
|
- Calls on_render() hook after render
|
|
- Does NOT trigger on_load(), on_ready(), or other lifecycle
|
|
- Synchronous - completes before next line executes
|
|
- Use when: Data changed and UI needs updating
|
|
|
|
reload_data()
|
|
Re-fetch data and update - Re-runs on_load(), re-renders template,
|
|
calls on_ready().
|
|
|
|
Usage:
|
|
class User_Card extends Jqhtml_Component {
|
|
async refresh_user_data() {
|
|
await this.reload_data(); // Fetches fresh data
|
|
console.log('User data updated');
|
|
}
|
|
}
|
|
|
|
Behavior:
|
|
- Re-runs on_load() to fetch fresh data
|
|
- Automatically re-renders template with new data
|
|
- Calls on_ready() after re-render completes
|
|
- Returns promise - await for completion
|
|
- Use when: Need fresh data from server/API
|
|
|
|
reinitialize()
|
|
Full component reset - Restarts entire lifecycle from stage 0
|
|
(nuclear option).
|
|
|
|
Usage:
|
|
class Dashboard extends Jqhtml_Component {
|
|
async switch_user(new_user_id) {
|
|
this.args.user_id = new_user_id;
|
|
await this.reinitialize(); // Complete reset
|
|
}
|
|
}
|
|
|
|
Behavior:
|
|
- Destroys current component state
|
|
- Re-runs entire lifecycle: render → on_render → create → load → ready
|
|
- Use when: Component needs complete rebuild
|
|
- Rare use case - usually reload_data() or render() sufficient
|
|
|
|
destroy()
|
|
Component destruction - Removes component and all children from DOM.
|
|
|
|
Usage:
|
|
class Modal extends Jqhtml_Component {
|
|
close() {
|
|
this.destroy(); // Remove modal from DOM
|
|
}
|
|
}
|
|
|
|
Behavior:
|
|
- Calls on_destroy() hook if defined
|
|
- Recursively destroys all child components
|
|
- Removes DOM element completely
|
|
- Adds _Component_Destroyed class before removal
|
|
- Synchronous - completes immediately
|
|
- Use when: Component no longer needed
|
|
|
|
on_destroy() hook example:
|
|
class Chat_Widget extends Jqhtml_Component {
|
|
on_destroy() {
|
|
// Clean up websocket connection
|
|
this.socket.disconnect();
|
|
console.log('Chat widget destroyed');
|
|
}
|
|
}
|
|
|
|
SYNCHRONOUS REQUIREMENTS
|
|
CRITICAL: These lifecycle methods MUST be synchronous (no async, no await):
|
|
|
|
Method Synchronous Required Can Use await
|
|
------------ -------------------- -------------
|
|
on_create() YES NO
|
|
on_render() YES NO
|
|
on_destroy() YES NO
|
|
on_load() NO (async allowed) YES
|
|
on_ready() NO (async allowed) YES
|
|
|
|
Framework needs predictable execution order for lifecycle coordination.
|
|
|
|
Example - Correct synchronous hooks:
|
|
class MyComponent extends Jqhtml_Component {
|
|
on_create() {
|
|
this.counter = 0; // Sync setup
|
|
}
|
|
|
|
on_render() {
|
|
this.counter++; // Sync update
|
|
}
|
|
|
|
on_destroy() {
|
|
console.log('Destroyed'); // Sync cleanup
|
|
}
|
|
}
|
|
|
|
Example - WRONG (async hooks not allowed):
|
|
class MyComponent extends Jqhtml_Component {
|
|
async on_create() { // DON'T DO THIS
|
|
await this.setup();
|
|
}
|
|
}
|
|
|
|
ACCESSING COMPONENT INSTANCE
|
|
From element:
|
|
const component = $('#my-component').component();
|
|
component.data.name = 'New Name';
|
|
|
|
From class:
|
|
// All instances tracked in Jqhtml_Component.instances
|
|
User_Card.instances.forEach(instance => {
|
|
instance.data.status = 'active';
|
|
});
|
|
|
|
DOM UTILITIES
|
|
Component instance methods and properties:
|
|
|
|
this.$
|
|
jQuery wrapped component root element
|
|
This is genuine jQuery - all methods work directly
|
|
|
|
this.$id(name)
|
|
Get element by scoped ID
|
|
Example: this.$id('edit') gets element with $id="edit"
|
|
Returns jQuery object
|
|
|
|
this.data
|
|
Component data object
|
|
Starts as empty object {}
|
|
Changes trigger re-render
|
|
Load data in on_load() method
|
|
|
|
this.args
|
|
Component input parameters from attributes
|
|
Set during construction from $attr=value
|
|
Treat as read-only
|
|
|
|
this._cid
|
|
Unique component instance ID for scoping
|
|
Used internally for ID generation
|
|
|
|
this.$child(name)
|
|
Get child component by name
|
|
|
|
this.$parent()
|
|
Get parent component instance
|
|
|
|
NESTING COMPONENTS
|
|
Components can contain other components:
|
|
|
|
<Define:Dashboard>
|
|
<div class="dashboard">
|
|
<% for (let widget of this.data.widgets) { %>
|
|
<Widget_Card $widget_id=widget.id $data=widget />
|
|
<% } %>
|
|
</div>
|
|
</Define:Dashboard>
|
|
|
|
Nested components initialize after parent (bottom-up for on_create,
|
|
on_load, on_ready).
|
|
|
|
SCOPED IDS
|
|
Use $id attribute for component-scoped element IDs:
|
|
|
|
Template:
|
|
<Define:User_Card>
|
|
<h3 $id="title">Name</h3>
|
|
<p $id="email">Email</p>
|
|
<button $id="edit_btn">Edit</button>
|
|
</Define:User_Card>
|
|
|
|
Rendered HTML (automatic scoping):
|
|
<div class="User_Card Jqhtml_Component">
|
|
<h3 id="title:c123">Name</h3>
|
|
<p id="email:c123">Email</p>
|
|
<button id="edit_btn:c123">Edit</button>
|
|
</div>
|
|
|
|
Access with this.$id():
|
|
class User_Card extends Jqhtml_Component {
|
|
on_ready() {
|
|
// Use logical name
|
|
this.$id('title').text('John Doe');
|
|
this.$id('email').text('john@example.com');
|
|
this.$id('edit_btn').on('click', () => this.edit());
|
|
}
|
|
}
|
|
|
|
Why scoped IDs: Multiple component instances need unique IDs
|
|
without conflicts. Framework automatically appends :component_id
|
|
to prevent collisions.
|
|
|
|
EXAMPLES
|
|
Card with async data loading:
|
|
class Product_Card extends Jqhtml_Component {
|
|
async on_load() {
|
|
// Load product data
|
|
const id = this.args.product_id;
|
|
this.data = await fetch(`/api/products/${id}`)
|
|
.then(r => r.json());
|
|
// Template re-renders automatically with data
|
|
}
|
|
|
|
on_ready() {
|
|
// Attach event handlers after data loaded
|
|
this.$id('buy').on('click', async () => {
|
|
await Cart.add(this.data.id);
|
|
this.$id('buy').text('Added!').prop('disabled', true);
|
|
});
|
|
|
|
this.$id('favorite').on('click', () => {
|
|
this.$.toggleClass('favorited');
|
|
});
|
|
}
|
|
}
|
|
|
|
List with loading state:
|
|
<!-- todo_list.jqhtml -->
|
|
<Define:Todo_List>
|
|
<div>
|
|
<% if (Object.keys(this.data).length === 0) { %>
|
|
<div class="loading">Loading todos...</div>
|
|
<% } else { %>
|
|
<% for (let todo of this.data.todos) { %>
|
|
<div class="todo">
|
|
<input type="checkbox"
|
|
<%= todo.done ? 'checked' : '' %>>
|
|
<span><%= todo.text %></span>
|
|
</div>
|
|
<% } %>
|
|
<% } %>
|
|
</div>
|
|
</Define:Todo_List>
|
|
|
|
class Todo_List extends Jqhtml_Component {
|
|
async on_load() {
|
|
this.data.todos = await fetch('/api/todos')
|
|
.then(r => r.json());
|
|
}
|
|
|
|
on_ready() {
|
|
this.$.on('change', 'input', (e) => {
|
|
const index = $(e.target).closest('.todo').index();
|
|
this.toggle_todo(index);
|
|
});
|
|
}
|
|
|
|
toggle_todo(index) {
|
|
this.data.todos[index].done = !this.data.todos[index].done;
|
|
// Update server...
|
|
}
|
|
}
|
|
|
|
Form with validation:
|
|
class Contact_Form extends Jqhtml_Component {
|
|
on_ready() {
|
|
this.$.on('submit', (e) => {
|
|
e.preventDefault();
|
|
if (this.validate()) {
|
|
this.submit();
|
|
}
|
|
});
|
|
}
|
|
|
|
validate() {
|
|
const email = this.$id('email').val();
|
|
if (!email.includes('@')) {
|
|
this.$id('error').text('Invalid email');
|
|
return false;
|
|
}
|
|
this.$id('error').text('');
|
|
return true;
|
|
}
|
|
|
|
async submit() {
|
|
const data = {
|
|
email: this.$id('email').val(),
|
|
message: this.$id('message').val(),
|
|
};
|
|
|
|
await fetch('/contact', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
this.$.html('<p>Thank you!</p>');
|
|
}
|
|
}
|
|
|
|
CONTENT AND SLOTS
|
|
JQHTML provides two patterns for passing content from parent to child:
|
|
|
|
1. content() Function (Primary Pattern - 95% of Use Cases)
|
|
The content() function is the standard way to accept innerHTML:
|
|
|
|
<!-- Define component -->
|
|
<Define:Card>
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<%= content() %> <!-- Outputs passed innerHTML -->
|
|
</div>
|
|
</div>
|
|
</Define:Card>
|
|
|
|
<!-- Use component -->
|
|
<Card>
|
|
<h3>Card Title</h3>
|
|
<p>This content goes where content() is called</p>
|
|
</Card>
|
|
|
|
Works exactly like regular HTML - intuitive and simple.
|
|
Perfect for: wrappers, containers, cards, modals.
|
|
|
|
2. Named Slots (Advanced Pattern - 5% of Use Cases)
|
|
Named slots for components needing multiple content areas.
|
|
|
|
Child templates use content('slotname') to render named slots:
|
|
|
|
<!-- Define with named slots -->
|
|
<Define:Card_Layout>
|
|
<div class="card">
|
|
<div class="card-header"><%= content('header') %></div>
|
|
<div class="card-body"><%= content('body') %></div>
|
|
<div class="card-footer"><%= content('footer') %></div>
|
|
</div>
|
|
</Define:Card_Layout>
|
|
|
|
Parent templates provide content using <#slotname> tags:
|
|
|
|
<!-- Use with named slots -->
|
|
<Card_Layout>
|
|
<#header><h3>User Profile</h3></#header>
|
|
<#body>
|
|
<p>Name: <%= this.data.name %></p>
|
|
<p>Email: <%= this.data.email %></p>
|
|
</#body>
|
|
<#footer>
|
|
<button class="btn">Save</button>
|
|
</#footer>
|
|
</Card_Layout>
|
|
|
|
Critical rules:
|
|
- Cannot mix regular content with named slots
|
|
- If ANY named slots present, ALL content must be in slots
|
|
- Child template syntax: <%= content('slotname') %>
|
|
- Parent template syntax: <#slotname>content</#slotname>
|
|
|
|
Decision Guide:
|
|
Use content() when:
|
|
- Single content area (most components)
|
|
- Building wrappers or containers
|
|
- Enhancing child content
|
|
|
|
Use slots when:
|
|
- Multiple distinct content regions
|
|
- Building complex layouts (DataGrid, Wizard)
|
|
- Different templates for different states
|
|
|
|
3. Slot-Based Template Inheritance (v2.2.108+)
|
|
When a component template contains ONLY slots (no HTML wrapper),
|
|
it automatically inherits the parent class template structure.
|
|
|
|
This enables abstract base components with customizable content,
|
|
allowing child classes to extend parent templates without
|
|
duplicating HTML structure.
|
|
|
|
Parent template (DataGrid_Abstract.jqhtml):
|
|
<Define:DataGrid_Abstract>
|
|
<table class="table">
|
|
<thead><tr><%= content('header') %></tr></thead>
|
|
<tbody>
|
|
<% for (let record of this.data.records) { %>
|
|
<tr><%= content('row', record) %></tr>
|
|
<% } %>
|
|
</tbody>
|
|
</table>
|
|
</Define:DataGrid_Abstract>
|
|
|
|
Parent JavaScript class (Users_DataGrid.js):
|
|
class Users_DataGrid extends DataGrid_Abstract {
|
|
async on_load() {
|
|
const result = await User_Controller.list_users({page: 1});
|
|
this.data.records = result.users;
|
|
this.data.loaded = true;
|
|
}
|
|
}
|
|
|
|
Child template - slot-only (Users_DataGrid.jqhtml):
|
|
<Define:Users_DataGrid>
|
|
<#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>
|
|
</Define:Users_DataGrid>
|
|
|
|
Result: Users_DataGrid renders using DataGrid_Abstract HTML
|
|
structure with customized slot content.
|
|
|
|
Data Passing to Slots:
|
|
Parents pass data to slots via second parameter:
|
|
<%= content('slotname', data) %>
|
|
|
|
Child templates receive data via slot parameter:
|
|
<#row>
|
|
<td><%= row.id %></td>
|
|
</#row>
|
|
|
|
The slot parameter name matches the slot name automatically.
|
|
|
|
Reserved Word Validation:
|
|
Slot names cannot be JavaScript reserved words.
|
|
Parser rejects with fatal error:
|
|
|
|
<#function>Content</#function> <!-- ERROR: reserved word -->
|
|
<#if>Content</#if> <!-- ERROR: reserved word -->
|
|
<#header>Content</#header> <!-- Valid -->
|
|
|
|
Reserved words include: function, if, for, class, const, let,
|
|
var, while, switch, return, try, catch, and others.
|
|
|
|
Requirements:
|
|
- Child template contains ONLY slot definitions (no HTML)
|
|
- JavaScript class extends parent: class Child extends Parent
|
|
- Parent template defines slots using content('slotname')
|
|
- Runtime walks prototype chain via Object.getPrototypeOf()
|
|
|
|
Implementation:
|
|
- Parser detects slot-only templates
|
|
- Generates {_slots: {...}} format instead of HTML instructions
|
|
- Runtime finds parent template by walking prototype chain
|
|
- Parent template structure inherited automatically
|
|
- All compilation happens at build time
|
|
|
|
Use Cases:
|
|
- Abstract data tables with customizable column layouts
|
|
- Page layouts with variable content sections
|
|
- Card variations with different headers/bodies/footers
|
|
- Form patterns with customizable field sets
|
|
- Any component hierarchy with shared structure
|
|
|
|
INTEGRATION WITH RSX
|
|
JQHTML automatically integrates with the RSX framework:
|
|
- Templates discovered by manifest system during build
|
|
- Processor converts .jqhtml to JavaScript render functions
|
|
- Bundle system includes generated code automatically
|
|
- No manual registration or configuration required
|
|
- Templates compile at build time with working sourcemaps
|
|
|
|
MANIFEST INTEGRATION
|
|
The manifest system handles automatic discovery:
|
|
- Caches component names during manifest build
|
|
- Enables Blade precompiler validation
|
|
- Supports component auto-discovery
|
|
- Generates JavaScript class stubs for components
|
|
- Tracks all .jqhtml files in included directories
|
|
|
|
Components automatically found in directories scanned by
|
|
manifest system (typically /rsx/ and included paths).
|
|
|
|
Build process flow:
|
|
1. Manifest build scans included directories
|
|
2. Finds all .jqhtml template files
|
|
3. Finds matching .js class files (optional)
|
|
4. Registers component names for path-agnostic access
|
|
5. Templates compile to JavaScript during bundle compilation
|
|
|
|
BUNDLE INCLUSION
|
|
Components included via bundle system:
|
|
|
|
class Frontend_Bundle extends Rsx_Bundle_Abstract
|
|
{
|
|
public static function define(): array
|
|
{
|
|
return [
|
|
'include' => [
|
|
'jqhtml', // Framework library
|
|
'rsx/theme/components', // All .jqhtml files
|
|
'rsx/app/frontend', // Module directory
|
|
],
|
|
];
|
|
}
|
|
}
|
|
|
|
All .jqhtml and .js files in included directories are
|
|
automatically discovered, compiled, and bundled.
|
|
|
|
Generated JavaScript added to bundle automatically.
|
|
|
|
SERVER-SIDE RENDERING
|
|
Jqhtml::component() renders initial HTML server-side.
|
|
JavaScript hydrates on client for interactivity.
|
|
|
|
Benefits:
|
|
- SEO friendly
|
|
- Fast initial render
|
|
- Progressive enhancement
|
|
- Works without JavaScript (degraded)
|
|
|
|
DEBUGGING
|
|
Enable debug mode:
|
|
window.Jqhtml_Debug = true;
|
|
|
|
Logs:
|
|
- Component initialization
|
|
- Template compilation
|
|
- Data binding
|
|
- Lifecycle events
|
|
|
|
PERFORMANCE
|
|
- Templates cached after first compilation
|
|
- Use data-component for server-side initial render
|
|
- Minimize DOM manipulation in lifecycle methods
|
|
- Leverage automatic re-render via this.data changes
|
|
- Sibling components load in parallel during on_load()
|
|
|
|
SEE ALSO
|
|
bundle_api(3), controller(3), manifest_api(3)
|
|
|
|
RSX Framework 2025-10-07 JQHTML(3)
|