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>
JQHTML Parser
The JQHTML parser converts template files into JavaScript functions that use the component system.
Quick Start - Common Patterns
Basic Component with innerHTML (Most Common)
<!-- Define a component with default attributes -->
<Define:Card tag="article" class="card" style="padding: 10px">
<div class="card-body">
<%= content() %> <!-- Outputs whatever innerHTML was passed -->
</div>
</Define:Card>
<!-- Usage - just pass innerHTML like regular HTML -->
<Card class="featured">
<h3>Card Title</h3>
<p>This is the card content.</p>
<button>Click me</button>
</Card>
<!-- Result: <article class="card featured Card Jqhtml_Component" style="padding: 10px"> -->
This content() pattern is the primary way to compose components in JQHTML. It works like regular HTML - the innerHTML you pass gets rendered where content() is called.
Container Component Example
<!-- A container with default size that can be overridden -->
<Define:Container $size="medium" class="container">
<div class="container-inner <%= this.args.size %>">
<%= content() %> <!-- Simple innerHTML output -->
</div>
</Define:Container>
<!-- Usage - override the default size -->
<Container $size="large">
<p>Any content here</p>
<UserList />
<Footer />
</Container>
Note: Slots (<#slotname>) are an advanced feature only needed when you have multiple distinct content areas. For 95% of use cases, the simple content() pattern shown above is recommended.
Define Tag Attributes (Component Configuration)
Define tags support three types of attributes for configuring components:
<Define:Contacts_DataGrid
extends="DataGrid_Abstract"
$ajax_endpoint=Frontend_Contacts_Controller.datagrid_fetch
$per_page=25
class="card DataGrid">
<div>Content here</div>
</Define:Contacts_DataGrid>
1. extends="" - Template Inheritance
Explicitly declare parent template for inheritance (without requiring a JavaScript class).
2. $property=value - Default Args
Set default values for this.args that component invocations can override:
- Quoted:
$user_id="123"→ String literal"123" - Unquoted:
$handler=MyController.fetch→ Raw JavaScript expression - These become defaults; invocations can override them
3. Regular Attributes
Standard HTML attributes like class="", tag="" applied to root element.
Generated Output:
{
name: 'Contacts_DataGrid',
tag: 'div',
defaultAttributes: {"class": "card DataGrid"},
defineArgs: {"ajax_endpoint": Frontend_Contacts_Controller.datagrid_fetch, "per_page": "25"},
extends: 'DataGrid_Abstract',
render: function() { ... }
}
Slot-Based Template Inheritance
When a template contains ONLY slots (no HTML), it automatically inherits the parent class template:
<!-- Parent: DataGrid_Abstract.jqhtml -->
<Define:DataGrid_Abstract>
<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>
<!-- Child: Users_DataGrid.jqhtml (slot-only) -->
<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>
The parser detects slot-only templates and generates {_slots: {...}} format. Runtime walks prototype chain to find parent templates. Data passing: content('row', record) passes data to slot parameter function(row) { ... }. Reserved words: Slot names cannot be JavaScript keywords (function, if, etc.) - parser rejects with fatal error.
Lexer (Task 1 - COMPLETE)
The lexer is the first stage of the parser. It converts raw JQHTML template text into a stream of tokens.
Features
- No regex - Uses simple character scanning for maintainability
- Position tracking - Every token includes line, column, and absolute positions for source maps
- Simple token types - Clear, unambiguous token categories
- Efficient scanning - Single pass through the input
Token Types
TEXT- Plain HTML/text contentEXPRESSION_START-<%=opening tagCODE_START-<%opening tagTAG_END-%>closing tagDEFINE_START,DEFINE_END- Component definition tagsCOMPONENT_NAME- Component identifierJAVASCRIPT- JavaScript code within tags
Usage
import { Lexer } from '@jqhtml/parser';
const template = `
<Define:MyComponent>
<h1><%= this.data.title %></h1>
<% if (this.data.show) { %>
<p>Content here</p>
<% } %>
</Define:MyComponent>
`;
const lexer = new Lexer(template);
const tokens = lexer.tokenize();
// tokens is an array of Token objects with:
// - type: TokenType
// - value: string
// - line: number
// - column: number
// - start: number (absolute position)
// - end: number
Testing
# Build the parser
npm run build
# Run the test suite
node test-lexer.js
# Run the demo
node demo-lexer.js
Implementation Notes
The lexer uses a simple state machine approach:
- Text scanning - Default mode, captures plain text
- Tag detection - Looks for
<%,<%=,%>sequences - Keyword matching - After
<%, checks for control flow keywords - JavaScript capture - Captures code between tags
- Position tracking - Updates line/column for every character
No regex patterns are used. All scanning is done with:
match_sequence()- Check for exact string matchmatch_keyword()- Check for keyword with word boundary- Character-by-character advancement
Parser/AST Builder (Task 2 - COMPLETE)
The parser takes the token stream from the lexer and builds an Abstract Syntax Tree (AST) representing the template structure.
Features
- Recursive descent parsing - Simple, predictable parsing algorithm
- Clear node types - Each AST node represents a specific construct
- Position preservation - All nodes include source positions
- Error reporting - Clear messages with line/column information
AST Node Types
Program- Root node containing all top-level definitionsComponentDefinition- Component template definitionText- Plain text/HTML contentExpression- JavaScript expressions (<%= ... %>)IfStatement- Conditional rendering with optional elseForStatement- Loop constructsCodeBlock- Generic JavaScript code blocks
Usage
import { Lexer, Parser } from '@jqhtml/parser';
const template = `
<Define:Card>
<div class="card">
<h3><%= title %></h3>
<% if (showContent) { %>
<p><%= content %></p>
<% } %>
</div>
</Define:Card>
`;
// Tokenize
const lexer = new Lexer(template);
const tokens = lexer.tokenize();
// Parse to AST
const parser = new Parser(tokens);
const ast = parser.parse();
// AST structure:
// {
// type: 'Program',
// body: [{
// type: 'ComponentDefinition',
// name: 'Card',
// body: [...]
// }]
// }
Testing
# Run the parser test suite
node test-parser.js
# Run the interactive demo
node demo-parser.js
Implementation Notes
The parser uses straightforward techniques:
- Token consumption - Advances through tokens one at a time
- Lookahead - Peeks at upcoming tokens to decide parsing path
- Context tracking - Knows when inside components, loops, etc.
- Error recovery - Provides helpful error messages
No parser generators or complex algorithms - just simple recursive functions that build nodes.
Code Generator (Task 3 - COMPLETE)
The code generator takes the AST and produces executable JavaScript functions that work with the JQHTML component system.
Features
- jQuery-based DOM manipulation - Generates efficient jQuery code
- Component render functions - Each template becomes a render function
- Control flow handling - Properly handles if/else and for loops
- Expression evaluation - Safely evaluates and renders expressions
- Colon syntax support - Strips trailing colons from control statements
Generated Code Structure
// Each component gets a render function
jqhtml_components.set('ComponentName', {
name: 'ComponentName',
render: function render() {
const $root = $('<div></div>');
const $current = $root;
// Generated DOM manipulation code here
return $root.children();
},
dependencies: []
});
Usage
import { Lexer, Parser, CodeGenerator } from '@jqhtml/parser';
const template = `
<Define:MyComponent>
<h1><%= this.data.title %></h1>
<% if (this.data.items) { %>
<ul>
<% for (const item of this.data.items) { %>
<li><%= item %></li>
<% } %>
</ul>
<% } %>
</Define:MyComponent>
`;
// Generate the code
const lexer = new Lexer(template);
const tokens = lexer.tokenize();
const parser = new Parser(tokens);
const ast = parser.parse();
const generator = new CodeGenerator();
const result = generator.generate(ast);
// Use in a component
class MyComponent extends Component {
async on_render() {
const template = result.components.get('MyComponent');
const elements = template.render.call(this);
this.$.append(elements);
}
}
Testing
# Run the code generator test suite
node test-codegen.js
# Run the interactive demo
node demo-codegen.js
# View the integration example
# Open example-integration.html in a browser
Implementation Notes
The code generator:
- Traverses the AST - Visits each node and generates appropriate code
- Maintains context - Tracks current jQuery element for appending
- Handles nesting - For loops create temporary containers
- Strips syntax - Removes trailing colons from control flow
- Escapes strings - Properly escapes text content for JavaScript
v1 JavaScript Compilation Analysis
A comprehensive analysis of how JQHTML v1 compiles templates to JavaScript has been documented in JQHTML_V1_JAVASCRIPT_COMPILATION_PATTERNS.md. This analysis reveals:
- Instruction-based output: Templates compile to arrays of rendering instructions
- Three instruction types:
{tag:...},{comp:...},{block:...} - Context preservation: The
_that = thispattern for nested functions - Single-pass DOM construction: Efficient rendering with one
$.html()call - Deferred component initialization: Two-phase process for component creation
These patterns inform v2 implementation decisions, particularly around maintaining the efficient instruction-based architecture while modernizing the JavaScript output.
Implementation Status (December 2024)
✅ Completed Tasks
- Lexer - Full tokenization with position tracking
- Parser - AST generation with all v2 syntax
- Code Generator - Instruction array generation
- Component Integration - Runtime integration complete
- Slot System -
<#name>syntax fully implemented - Nested Components -
<ComponentName>recognition - Binding Syntax -
:propand@eventsupport
Current Features
- Template Syntax: HTML with embedded JavaScript
- Control Flow:
if/else/elseifandforloops (both colon and brace styles) - Components:
<Define:Name>definitions and<ComponentName>invocations - Slots:
<#name>content</#name>and<#name />syntax - Expressions:
<%= expression %>for output - Bindings:
:text,:value,:class,:stylefor data binding - Events:
@click,@change, etc. for event handlers - Scoped IDs:
$idattribute for component-scoped IDs
Instruction Format
The parser generates v1-compatible instruction arrays:
// HTML tags
{tag: ["div", {"class": "card"}, false]} // open tag
{tag: ["div", {}, true]} // close tag
// Components
{comp: ["UserCard", {name: "John"}]}
// Slots
{slot: ["header", {}, (props) => {
const _output = [];
_output.push("Header content");
return [_output, this];
}]}
// Text content
"Plain text"
// Expressions
{expr: () => this.data.title}
Integration with Core
Templates compile to functions that return [instructions, context]:
function render() {
const _output = [];
// Generate instructions...
_output.push({tag: ["h1", {}, false]});
_output.push("Hello World");
_output.push({tag: ["h1", {}, true]});
return [_output, this];
}
The core runtime's instruction processor handles these arrays to create DOM.
Next Steps
- Build Tools - Webpack loader for
.jqhtmlimports - Source Maps - Full debugging support
- VS Code Extension - Syntax highlighting
- Autoformatter - Code formatting for
.jqhtmlfiles