Add semantic token highlighting for 'that' variable and comment file references in VS Code extension Add Phone_Text_Input and Currency_Input components with formatting utilities Implement client widgets, form standardization, and soft delete functionality Add modal scroll lock and update documentation Implement comprehensive modal system with form integration and validation Fix modal component instantiation using jQuery plugin API Implement modal system with responsive sizing, queuing, and validation support Implement form submission with validation, error handling, and loading states Implement country/state selectors with dynamic data loading and Bootstrap styling Revert Rsx::Route() highlighting in Blade/PHP files Target specific PHP scopes for Rsx::Route() highlighting in Blade Expand injection selector for Rsx::Route() highlighting Add custom syntax highlighting for Rsx::Route() and Rsx.Route() calls Update jqhtml packages to v2.2.165 Add bundle path validation for common mistakes (development mode only) Create Ajax_Select_Input widget and Rsx_Reference_Data controller Create Country_Select_Input widget with default country support Initialize Tom Select on Select_Input widgets Add Tom Select bundle for enhanced select dropdowns Implement ISO 3166 geographic data system for country/region selection Implement widget-based form system with disabled state support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
@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:
- constructor - Instance creation, jQuery element setup (
this.datais{}) - render - Create initial DOM structure (top-down, atomic/synchronous operation)
- create - Quick setup after DOM creation (bottom-up, siblings parallel)
- load - Fetch data (bottom-up, fully parallel, ABSOLUTELY NO DOM MODIFICATIONS)
- render (automatic) - Re-render if data changed during load (empties DOM first)
- create (automatic) - Re-setup if component was re-rendered
- ready - Fully initialized (bottom-up, siblings parallel)
- 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.datacontains 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:
- Calls
render()again (which empties the DOM with$.empty()first) - Calls
create()again to re-setup the new DOM - 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:
- Empties the component's DOM with
this.$.empty() - Calls
render()to rebuild the DOM atomically - For automatic re-renders, calls
create()again for the new DOM - Finally calls
ready() - 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
- Immediate Execution: If the lifecycle event has already occurred when you register the callback, it fires immediately
- Future Events: The callback also registers for future occurrences of the same event (useful for re-renders)
- Multiple Callbacks: You can register multiple callbacks for the same event
- Error Safety: Errors in callbacks are caught and logged without breaking the component
- 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 instanceasync render()- Create DOM structureasync init()- Quick setup phaseasync load()- Data fetching phaseasync ready()- Final initializationdestroy()- Cleanupshould_rerender()- Control re-rendering after loademit(event, data)- Emit jQuery eventson(event, callback)- Register lifecycle event callback$id(localId)- Get scoped jQuery elementid(localId)- Get scoped component instanceparent()- Get parent componentchildren()- Get direct child componentsfind(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 templatewith_template(template)- Component mixinprocess_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
-
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
-
.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_Componentclass + your template. Perfect for presentational components that don't need custom logic. -
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.
-
Neither Class nor Template (Passthrough)
<!-- In your JQHTML file --> <FooBar> <p>This content just passes through!</p> </FooBar>Result: Uses default
Jqhtml_Componentclass + 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