JQHTML(3) RSX Framework Manual JQHTML(3) NAME JQHTML - jQuery-based HTML component system for semantic UI development SYNOPSIS // Blade directive // 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.$sid('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: # What it IS # Logical concept # Semantic name Not visual approach:
# How it LOOKS
    # Visual element
    # Generic container Undefined components render as
    with the component name as a class. This enables incremental scaffolding: build page structure with semantic names first, define component templates later.
    TEMPLATE SYNTAX Templates stored as .jqhtml files compile to JavaScript functions:

    <%= this.data.name %>

    <%= this.data.email %>

    Template expressions: <%= expression %> - Escaped HTML output (safe, default) <%!= expression %> - Unescaped raw output (pre-sanitized content only) <%br= expression %> - Escaped output with newlines converted to
    tags <% statement; %> - JavaScript statements (loops, conditionals) <%-- comment --%> - JQHTML comments (not HTML comments) The <%br= %> syntax is useful for displaying multi-line text (like notes or descriptions) where you want to preserve line breaks without allowing arbitrary HTML. It escapes HTML first, then converts \n to
    . Attributes: $sid="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) Conditional Attributes (v2.2.162+): Place if statements directly in attribute context to conditionally apply attributes based on component arguments: required="required"<% } %> <% if (this.args.min !== undefined) { %>min="<%= this.args.min %>"<% } %> /> Works with static values, interpolated expressions, and multiple conditionals per element. Compiles to Object.assign() with ternary operators at build time. No nested conditionals or else clauses supported - use separate if statements. DEFINE TAG CONFIGURATION The 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 tag to configure default this.args values without requiring a JavaScript class: <% for (let item of this.data.items) { %> <% } %>
    <%= item.name %>
    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: <%= content() %> 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: Unquoted Values = JavaScript Expressions: Complex Expressions (use parentheses): 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: 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 with strict lifecycle rules: this.args - Component State (what to load): - Source: Passed from parent via $attr=value - Purpose: Component state that determines what on_load() fetches - Mutability: Modifiable in all methods EXCEPT on_load() - Usage: Page numbers, filters, sort order, configuration - Examples: this.args.page, this.args.filter, this.args.user_id this.data - Loaded Data (from APIs): - Source: Set in on_load() lifecycle method - Purpose: Data fetched from APIs based on this.args - Mutability: ONLY modifiable in on_create() and on_load() - Freeze Cycle: Frozen after on_create(), unfrozen during on_load(), frozen again after on_load() completes - Initial State: Empty object {} on first render - Examples: API responses, fetched records, computed results Lifecycle Restrictions (ENFORCED): - on_create(): Can modify this.data (set defaults) - on_load(): Can ONLY access this.args and this.data Cannot access this.$, this.$sid(), or any other properties Can modify this.data freely - on_ready() / event handlers: Can modify this.args, read this.data CANNOT modify this.data (frozen) State Management Pattern: class Product_List extends Jqhtml_Component { on_create() { // Set default state for on_load() this.args.filter = this.args.filter || 'all'; this.args.page = this.args.page || 1; // Set default data for template this.data.products = []; this.data.loading = false; } async on_load() { // Read state from this.args const filter = this.args.filter; const page = this.args.page; // Fetch and set this.data this.data = await Product_Controller.list({ filter: filter, page: page }); } on_ready() { // Modify state, then reload this.$sid('filter_btn').on('click', () => { this.args.filter = 'active'; // Change state this.reload(); // Re-fetch with new state }); } } Error Messages: Violations throw runtime errors with detailed fix suggestions. Example: "Cannot modify this.data outside of on_create() or on_load()." COMMON PATTERNS AND PITFALLS Correct usage examples:
    Rendered HTML (automatic scoping):

    Name

    Email

    Access with this.$sid(): class User_Card extends Jqhtml_Component { on_ready() { // Use logical name this.$sid('title').text('John Doe'); this.$sid('email').text('john@example.com'); this.$sid('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.$sid('buy').on('click', async () => { await Cart.add(this.data.id); this.$sid('buy').text('Added!').prop('disabled', true); }); this.$sid('favorite').on('click', () => { this.$.toggleClass('favorited'); }); } } List with loading state:
    <% if (Object.keys(this.data).length === 0) { %>
    Loading todos...
    <% } else { %> <% for (let todo of this.data.todos) { %>
    > <%= todo.text %>
    <% } %> <% } %>
    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.$sid('email').val(); if (!email.includes('@')) { this.$sid('error').text('Invalid email'); return false; } this.$sid('error').text(''); return true; } async submit() { const data = { email: this.$sid('email').val(), message: this.$sid('message').val(), }; await fetch('/contact', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) }); this.$.html('

    Thank you!

    '); } } 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:
    <%= content() %>

    Card Title

    This content goes where content() is called

    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:
    <%= content('header') %>
    <%= content('body') %>
    Parent templates provide content using tags:

    User Profile

    Name: <%= this.data.name %>

    Email: <%= this.data.email %>

    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: content 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): <%= content('header') %> <% for (let record of this.data.records) { %> <%= content('row', record) %> <% } %>
    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): ID Name Email <%= row.id %> <%= row.name %> <%= row.email %> 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.id %> The slot parameter name matches the slot name automatically. Reserved Word Validation: Slot names cannot be JavaScript reserved words. Parser rejects with fatal error: Content Content Content 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 TEMPLATE-ONLY COMPONENTS Components can exist as .jqhtml files without a companion .js file. This is fine for simple display-only components that just render input data with conditionals - no lifecycle hooks or event handlers needed beyond what's possible inline. When to use template-only: - Component just displays data passed via arguments - Only needs simple conditionals in the template - No complex event handling beyond simple button clicks - Mentally easier than creating a separate .js file Inline Event Handlers: Define handlers in template code, reference with @event syntax: <% this.handle_click = () => window.location.reload(); %> Note: @event values must be UNQUOTED (not @click="this.method"). Inline Argument Validation: Throw errors early if required arguments are missing: <% if (!this.args.user_id) { throw new Error('User_Badge: $user_id is required'); } if (!this.args.name) { throw new Error('User_Badge: $name is required'); } %> <%= this.args.name %> Complete Example (error page component): <%-- Not_Found_Error_Page_Component Displays when a record cannot be found. --%>

    <%= this.args.record_type %> Not Found

    The <%= this.args.record_type.toLowerCase() %> you are looking for does not exist or has been deleted.

    <%= this.args.back_label %>
    This pattern is not necessarily "best practice" for complex components, but it works well and is pragmatic for simple display components. If the component needs lifecycle hooks, state management, or complex event handling, create a companion .js file instead. 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)