# @jqhtml/core Core runtime library for JQHTML v2 - jQuery-first component framework. ## Installation ```bash npm install @jqhtml/core jquery ``` ## Module Structure The core package exports two separate entry points: ### Main Runtime (Production) ```javascript import { Component, jqhtml } from '@jqhtml/core'; ``` ### Debug Utilities (Development Only) ```javascript 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 ```javascript 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(`

${this.data.user.name}

${this.data.user.email}

`); } async ready() { // Component fully initialized this.$.removeClass('loading'); } } ``` ### Using jQuery Plugin ```javascript // 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** ```jqhtml
<% if (Object.keys(this.data).length === 0): %>

Loading user profile...

<% else: %>

<%= this.data.user?.name || 'Unknown User' %>

<%= this.data.user?.bio || 'No bio available' %>

<% endif; %>
``` **Pattern 2: DOM Changes in create() and ready()** ```javascript class DataComponent extends Component { async on_create() { // Set loading state in the DOM during create phase this.$sid('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.$sid('status').removeClass('loading').text('Loaded'); this.$sid('username').text(this.data.user.name); } } ``` **NEVER do this:** ```javascript class BadComponent extends Component { async on_load() { // ❌ WRONG - DOM modification in load() this.$sid('status').text('Loading...'); // VIOLATION! this.data.user = await fetch('/api/user').then(r => r.json()); // ❌ WRONG - More DOM modification this.$sid('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()`: ```javascript 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: ```javascript 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: ```javascript class TabsComponent extends Component { async render() { this.$.html(`
`); } selectTab(tabId) { // Use $id() for scoped selection this.$sid('tab1').removeClass('active'); this.$sid('tab2').removeClass('active'); this.$sid(tabId).addClass('active'); } } ``` ## Template Integration The core runtime processes templates compiled by `@jqhtml/parser`: ```javascript 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: `$sid="my-id"` or `$data=this.complexObject` - **Regular HTML attributes**: MUST be quoted (strings only) Example: `class="container <%= this.args.theme %>"` See [ATTRIBUTE_RULES.md](../../docs/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: ```html ``` ## 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 ```javascript // 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 ```javascript // 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.$sid('email').addClass('verified'); }); ``` ### Example: Track Multiple Lifecycle Events ```javascript 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) ```javascript // 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: ```javascript 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) ```javascript // 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); ``` ```html

<%= this.data.user.name %>

``` **Result**: Uses your custom class logic + your custom template 2. **.jqhtml Template Only** (No JS class needed) ```html
Hello, <%= this.args.name %>!
``` **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) ```javascript class DynamicComponent extends Jqhtml_Component { async render() { this.$.html(`
Rendered at ${Date.now()}
`); } } 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) ```html

This content just passes through!

``` **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 ``: - **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: ```html Basic HTML content
<%= this.args.username %>
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: ```php // 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](./laravel-bridge/LARAVEL_INTEGRATION.md) for complete integration instructions. ## Development ```bash # Install dependencies npm install # Build TypeScript npm run build # Run tests npm test # Watch mode npm run watch ``` ## License MIT