Files
rspade_system/node_modules/@jqhtml/core
root 9ebcc359ae Fix code quality violations and enhance ROUTE-EXISTS-01 rule
Implement JQHTML function cache ID system and fix bundle compilation
Implement underscore prefix for system tables
Fix JS syntax linter to support decorators and grant exception to Task system
SPA: Update planning docs and wishlists with remaining features
SPA: Document Navigation API abandonment and future enhancements
Implement SPA browser integration with History API (Phase 1)
Convert contacts view page to SPA action
Convert clients pages to SPA actions and document conversion procedure
SPA: Merge GET parameters and update documentation
Implement SPA route URL generation in JavaScript and PHP
Implement SPA bootstrap controller architecture
Add SPA routing manual page (rsx:man spa)
Add SPA routing documentation to CLAUDE.md
Phase 4 Complete: Client-side SPA routing implementation
Update get_routes() consumers for unified route structure
Complete SPA Phase 3: PHP-side route type detection and is_spa flag
Restore unified routes structure and Manifest_Query class
Refactor route indexing and add SPA infrastructure
Phase 3 Complete: SPA route registration in manifest
Implement SPA Phase 2: Extract router code and test decorators
Rename Jqhtml_Component to Component and complete SPA foundation setup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 17:48:15 +00:00
..

@jqhtml/core

Core runtime library for JQHTML v2 - jQuery-first component framework.

Installation

npm install @jqhtml/core jquery

Module Structure

The core package exports two separate entry points:

Main Runtime (Production)

import { Component, jqhtml } from '@jqhtml/core';

Debug Utilities (Development Only)

import { showDebugOverlay, hideDebugOverlay } from '@jqhtml/core/debug';

The debug module is kept separate to avoid including debug code in production bundles. It provides:

  • Visual debug overlay with performance profiling
  • Component flash visualization
  • Lifecycle logging
  • Slow render detection

Overview

The core package provides:

  • Component base class with 5-stage lifecycle
  • Lifecycle manager for phase coordination
  • Component registry for dynamic instantiation
  • Instruction processor for template rendering
  • jQuery plugin integration
  • Template rendering with data bindings

Quick Start

Basic Component

import { Component } from '@jqhtml/core';

class UserCard extends Component {
  async init() {
    // Quick setup, hide elements
    this.$.addClass('loading');
  }
  
  async load() {
    // Fetch data (runs in parallel with siblings)
    this.data.user = await fetch('/api/user').then(r => r.json());
  }
  
  async render() {
    // Create DOM structure
    this.$.html(`
      <div class="card">
        <h2>${this.data.user.name}</h2>
        <p>${this.data.user.email}</p>
      </div>
    `);
  }
  
  async ready() {
    // Component fully initialized
    this.$.removeClass('loading');
  }
}

Using jQuery Plugin

// Create component on existing element
$('#user-container').component(UserCard, { userId: 123 });

// Get component instance
const card = $('#user-container').component();

Component Lifecycle

Components follow a multi-stage lifecycle with automatic re-rendering:

  1. constructor - Instance creation, jQuery element setup (this.data is {})
  2. render - Create initial DOM structure (top-down, atomic/synchronous operation)
  3. create - Quick setup after DOM creation (bottom-up, siblings parallel)
  4. load - Fetch data (bottom-up, fully parallel, ABSOLUTELY NO DOM MODIFICATIONS)
  5. render (automatic) - Re-render if data changed during load (empties DOM first)
  6. create (automatic) - Re-setup if component was re-rendered
  7. ready - Fully initialized (bottom-up, siblings parallel)
  8. destroy - Cleanup when removed

Critical: NO DOM Modifications in load()

The requirement that on_load() must not modify the DOM is ABSOLUTE. This is not a guideline but a strict architectural requirement. Violating this will cause rendering issues and race conditions.

Data Initialization Timeline:

  • Before load(): this.data = {} (empty object)
  • After load(): this.data contains fetched data (remains {} if no data loaded)

Correct Patterns for Loading States

If you need to show different DOM states before and after loading, use these patterns:

Pattern 1: Conditional Rendering in Template

<Define:UserProfile>
  <div class="user-profile">
    <% if (Object.keys(this.data).length === 0): %>
      <!-- Show loading state (no data loaded yet) -->
      <div class="loading">
        <div class="spinner"></div>
        <p>Loading user profile...</p>
      </div>
    <% else: %>
      <!-- Show loaded content -->
      <h2><%= this.data.user?.name || 'Unknown User' %></h2>
      <p><%= this.data.user?.bio || 'No bio available' %></p>
    <% endif; %>
  </div>
</Define:UserProfile>

Pattern 2: DOM Changes in create() and ready()

class DataComponent extends Component {
  async on_create() {
    // Set loading state in the DOM during create phase
    this.$id('status').addClass('loading').text('Loading...');
  }

  async on_load() {
    // ONLY fetch data - NO DOM modifications here!
    this.data.user = await fetch('/api/user').then(r => r.json());
  }

  async on_ready() {
    // Update DOM after data is loaded
    this.$id('status').removeClass('loading').text('Loaded');
    this.$id('username').text(this.data.user.name);
  }
}

NEVER do this:

class BadComponent extends Component {
  async on_load() {
    // ❌ WRONG - DOM modification in load()
    this.$id('status').text('Loading...');  // VIOLATION!

    this.data.user = await fetch('/api/user').then(r => r.json());

    // ❌ WRONG - More DOM modification
    this.$id('status').text('Loaded');  // VIOLATION!
  }
}

Important: The render() method is atomic and essentially synchronous - it empties the DOM and rebuilds it in a single operation. While marked async for technical reasons, it completes immediately without yielding control.

Phase Batching

All components complete each phase before moving to the next:

  • Parents render before children
  • Children create/load/ready before parents
  • Siblings execute in parallel where safe

Automatic Re-rendering

After the load phase, if this.data has changed, the component automatically:

  1. Calls render() again (which empties the DOM with $.empty() first)
  2. Calls create() again to re-setup the new DOM
  3. Then proceeds to ready()

This ensures components can fetch data and re-render with that data seamlessly.

Controlling Re-rendering

By default, components automatically re-render if this.data changes during load():

class DataComponent extends Component {
  async on_load() {
    // If this modifies this.data, component will re-render
    this.data.user = await fetch('/api/user').then(r => r.json());
  }

  // Override to customize re-render logic
  should_rerender() {
    // Default: returns true if JSON.stringify(this.data) changed
    // Override for custom logic:
    return this.data.user && !this.data.cached;
  }
}

Manual Re-rendering

You can manually re-render a component at any time:

class InteractiveComponent extends Component {
  async handleUpdate(newData) {
    // Update data
    this.data = { ...this.data, ...newData };

    // Manually re-render (atomic operation)
    await this.render();

    // Optionally re-initialize (call create/ready as needed)
    await this.on_create();  // Re-setup event handlers
    await this.on_ready();   // Re-initialize component state
  }

  // Lifecycle manipulation methods
  redraw() {
    // Synchronously re-renders with current data
  }

  async reload_data() {
    // Re-fetches data via on_load(), then redraws
  }

  async reinitialize() {
    // Full lifecycle reset from stage 0
  }

  destroy() {
    // Cleanup component and children
  }
}

Note: The re-render process:

  1. Empties the component's DOM with this.$.empty()
  2. Calls render() to rebuild the DOM atomically
  3. For automatic re-renders, calls create() again for the new DOM
  4. Finally calls ready()
  5. For manual re-renders, you control which lifecycle methods to call

Scoped IDs

Components use _cid for scoped element selection:

class TabsComponent extends Component {
  async render() {
    this.$.html(`
      <button id="tab1:${this._cid}">Tab 1</button>
      <button id="tab2:${this._cid}">Tab 2</button>
      <div id="content:${this._cid}"></div>
    `);
  }
  
  selectTab(tabId) {
    // Use $id() for scoped selection
    this.$id('tab1').removeClass('active');
    this.$id('tab2').removeClass('active');
    this.$id(tabId).addClass('active');
  }
}

Template Integration

The core runtime processes templates compiled by @jqhtml/parser:

import { render_template, with_template } from '@jqhtml/core';

// Template function (usually generated by parser)
const template = function(data, args, content) {
  const _output = [];
  _output.push({tag: ["h1", {"data-bind-text": "title"}, false]});
  _output.push({tag: ["h1", {}, true]});
  return [_output, this];
};

// Component with template
class Article extends Component {
  async on_render() {
    await render_template(this, template);
  }
}

// Or use mixin
const Article = with_template(template)(Component);

Attribute Rules

JQHTML has specific rules for attribute quoting and value passing:

  • @ Event attributes: MUST be unquoted (pass function references) Example: @click=this.handleClick
  • $ Data attributes: Can be quoted OR unquoted (flexible) Example: $id="my-id" or $data=this.complexObject
  • Regular HTML attributes: MUST be quoted (strings only) Example: class="container <%= this.args.theme %>"

See ATTRIBUTE_RULES.md for comprehensive documentation.

Data Bindings

Templates support reactive data bindings:

  • :text="expression" - Update text content
  • :value="expression" - Form input values
  • :class="{active: isActive}" - Dynamic classes
  • :style="{color: textColor}" - Dynamic styles
  • @click=handler - Event handlers (unquoted)

Browser Bundle

For browser usage without a build tool:

<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="jqhtml-bundle.js"></script>
<script>
  const { Component } = JQHTML;

  class MyComponent extends Component {
    // ...
  }
</script>

Lifecycle Event Callbacks

JQHTML provides a .on() method for registering callbacks that fire after lifecycle events complete. This is useful for external code that needs to know when a component reaches a certain state.

Supported Events

  • 'render' - Fires after the render phase completes
  • 'create' - Fires after the create phase completes
  • 'load' - Fires after the load phase completes
  • 'ready' - Fires after the ready phase completes (component fully initialized)

Usage

// Get component instance and register callback
const component = $('#my-component').component();

component.on('ready', (comp) => {
  console.log('Component is ready!', comp);
  // Access component data, DOM, etc.
});

// If the event already occurred, callback fires immediately
// AND still registers for future occurrences (e.g., on re-render)

Behavior

  1. Immediate Execution: If the lifecycle event has already occurred when you register the callback, it fires immediately
  2. Future Events: The callback also registers for future occurrences of the same event (useful for re-renders)
  3. Multiple Callbacks: You can register multiple callbacks for the same event
  4. Error Safety: Errors in callbacks are caught and logged without breaking the component
  5. Validation: Only lifecycle events are allowed; other event names trigger a console error

Example: Wait for Component to be Ready

// Register callback before or after component initialization
$('#user-profile').component().on('ready', (component) => {
  // Component is fully initialized, data is loaded
  console.log('User data:', component.data.user);

  // Safe to access all DOM elements
  component.$id('email').addClass('verified');
});

Example: Track Multiple Lifecycle Events

const component = $('#dashboard').component();

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'));

Example: Invalid Event (Error)

// This will log an error to console
$('#my-component').component().on('click', callback);
// Error: Component.on() only supports lifecycle events: render, create, load, ready

Note on Re-renders

When a component re-renders (manually or automatically after load()), lifecycle events fire again:

const component = $('#widget').component();

component.on('render', () => {
  console.log('Widget rendered');
  // Fires on initial render AND every re-render
});

// Later, trigger re-render
await component.render();  // "Widget rendered" logs again

API Reference

Component Class

  • constructor(element, args) - Create component instance
  • async render() - Create DOM structure
  • async init() - Quick setup phase
  • async load() - Data fetching phase
  • async ready() - Final initialization
  • destroy() - Cleanup
  • should_rerender() - Control re-rendering after load
  • emit(event, data) - Emit jQuery events
  • on(event, callback) - Register lifecycle event callback
  • $id(localId) - Get scoped jQuery element
  • id(localId) - Get scoped component instance
  • parent() - Get parent component
  • children() - Get direct child components
  • find(selector) - Find descendant components

jQuery Plugin

  • $(el).component() - Get component instance
  • $(el).component(Class, args) - Create component
  • $.jqhtml.register(name, Class) - Register component
  • $.jqhtml.create(name, args) - Create by name

Template Functions

  • render_template(component, template) - Render template
  • with_template(template) - Component mixin
  • process_instructions(instructions, target, context) - Process instruction array

Component and Template Registration Flexibility

JQHTML provides complete flexibility in how you define components - you can use JavaScript classes, .jqhtml template files, both, or neither. The framework automatically handles all combinations:

All Possible Combinations

  1. Both JS Class and .jqhtml Template (Standard approach)

    // UserCard.js
    class UserCard extends Jqhtml_Component {
      async load() {
        this.data.user = await fetch('/api/user').then(r => r.json());
      }
    }
    jqhtml.register_component('UserCard', UserCard);
    
    <!-- UserCard.jqhtml -->
    <Define:UserCard>
      <div class="card">
        <h2><%= this.data.user.name %></h2>
      </div>
    </Define:UserCard>
    

    Result: Uses your custom class logic + your custom template

  2. .jqhtml Template Only (No JS class needed)

    <!-- SimpleWidget.jqhtml -->
    <Define:SimpleWidget>
      <div class="widget">
        Hello, <%= this.args.name %>!
      </div>
    </Define:SimpleWidget>
    

    Result: Uses default Jqhtml_Component class + your template. Perfect for presentational components that don't need custom logic.

  3. JS Class Only (No .jqhtml template)

    class DynamicComponent extends Jqhtml_Component {
      async render() {
        this.$.html(`<div>Rendered at ${Date.now()}</div>`);
      }
    }
    jqhtml.register_component('DynamicComponent', DynamicComponent);
    

    Result: Uses your class + default passthrough template. Useful when you want full programmatic control.

  4. Neither Class nor Template (Passthrough)

    <!-- In your JQHTML file -->
    <FooBar>
      <p>This content just passes through!</p>
    </FooBar>
    

    Result: Uses default Jqhtml_Component class + default passthrough template. Acts as a simple wrapper that renders its inner content.

How It Works

When you use <ComponentName>:

  • JS Class Resolution: Looks for registered class, falls back to Jqhtml_Component
  • Template Resolution: Looks for registered template by class, then by name, then uses default passthrough
  • Default Passthrough Template: Simply renders the component's inner HTML as-is

This means you can start simple and progressively add complexity:

<!-- Start with just markup -->
<UserBadge>Basic HTML content</UserBadge>

<!-- Later, add a .jqhtml template for consistent styling -->
<Define:UserBadge>
  <div class="badge"><%= this.args.username %></div>
</Define:UserBadge>

<!-- Finally, add JS class for complex behavior -->
class UserBadge extends Jqhtml_Component {
  async load() { /* fetch user data */ }
}

Laravel Integration

This package includes a Laravel bridge for error handling and source map support. After installing via npm, you can load it directly from node_modules in your Laravel application:

// In app/Providers/AppServiceProvider.php
public function register()
{
    $jqhtmlBridge = base_path('node_modules/@jqhtml/core/laravel-bridge/autoload.php');
    if (file_exists($jqhtmlBridge)) {
        require_once $jqhtmlBridge;
    }
}

See laravel-bridge/LARAVEL_INTEGRATION.md for complete integration instructions.

Development

# Install dependencies
npm install

# Build TypeScript
npm run build

# Run tests
npm test

# Watch mode
npm run watch

License

MIT